Skip to content

Commit

Permalink
Merge pull request #28 from wix-incubator/create-cacheHandler2
Browse files Browse the repository at this point in the history
* test: add `fs` mocking util.

* feat: Add CacheHandler

test: Add tests to CacheHandler

test: Change tests due to the addition of CacheHandler

chore: Change files due to the addition of CacheHandler

chore: changes due to CacheHandler

* chore: Change from "reset" to "start" & "end"

* chore: Change the default behavior to cache saving

- The parameter of the 'end' function changed from "saveToCache" to "isCacheDisabled"
- Test were modified accordingly

* chore: remove redundant file.

* feat: Prevent initialization more than once

feat: Initialization more than once

test: fit tests to init changes

* chore: Remove unused imports
  • Loading branch information
asafkorem authored Dec 11, 2024
2 parents 629378c + 8455dcb commit 1d163e5
Show file tree
Hide file tree
Showing 15 changed files with 464 additions and 171 deletions.
4 changes: 0 additions & 4 deletions detox_copilot_cache.json

This file was deleted.

76 changes: 63 additions & 13 deletions src/Copilot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@ import { Copilot } from '@/Copilot';
import { StepPerformer } from '@/actions/StepPerformer';
import { CopilotError } from '@/errors/CopilotError';
import { Config } from "@/types";
import fs from "fs";
import { mockCache, mockedCacheFile } from "./test-utils/cache";

jest.mock('@/actions/StepPerformer');
jest.mock('fs');

const INTENT = 'tap button';

describe('Copilot', () => {
let mockConfig: Config;
Expand Down Expand Up @@ -56,14 +61,11 @@ describe('Copilot', () => {
expect(Copilot.getInstance()).toBeInstanceOf(Copilot);
});

it('should overwrite existing instance when called multiple times', () => {
Copilot.init(mockConfig);
const instance1 = Copilot.getInstance();

it('should throw an error when trying to initialize Copilot multiple times', () => {
Copilot.init(mockConfig);
const instance2 = Copilot.getInstance();

expect(instance1).not.toBe(instance2);
expect(() => Copilot.init(mockConfig))
.toThrow('Copilot has already been initialized. Please call the `init()` method only once.');
});

it('should throw an error if config is invalid', () => {
Expand All @@ -78,26 +80,27 @@ describe('Copilot', () => {

Copilot.init(mockConfig);
const instance = Copilot.getInstance();
const intent = 'tap button';
instance.start();

await instance.performStep(intent);
await instance.performStep(INTENT);

expect(StepPerformer.prototype.perform).toHaveBeenCalledWith(intent, []);
expect(StepPerformer.prototype.perform).toHaveBeenCalledWith(INTENT, []);
});

it('should return the result from StepPerformer.perform', async () => {
Copilot.init(mockConfig);
const instance = Copilot.getInstance();
const intent = 'tap button';
instance.start();

const result = await instance.performStep(intent);
const result = await instance.performStep(INTENT);

expect(result).toBe(true);
});

it('should accumulate previous intents', async () => {
Copilot.init(mockConfig);
const instance = Copilot.getInstance();
instance.start();
const intent1 = 'tap button 1';
const intent2 = 'tap button 2';

Expand All @@ -112,18 +115,65 @@ describe('Copilot', () => {
});
});

describe('reset', () => {
describe('start', () => {
it('should clear previous intents', async () => {
Copilot.init(mockConfig);
const instance = Copilot.getInstance();
instance.start();
const intent1 = 'tap button 1';
const intent2 = 'tap button 2';

await instance.performStep(intent1);
instance.reset();
instance.end(true);
instance.start();
await instance.performStep(intent2);

expect(StepPerformer.prototype.perform).toHaveBeenLastCalledWith(intent2, []);
});
});

describe('start and end behavior', () => {
it('should not perform before start', async () => {
Copilot.init(mockConfig);
const instance = Copilot.getInstance();

await expect(instance.performStep(INTENT)).rejects.toThrowError('Copilot is not running. Please call the `start()` method before performing any steps.');
});

it('should not start without end the previous flow(start->start)', async () => {
Copilot.init(mockConfig);
const instance = Copilot.getInstance();
instance.start();

await instance.performStep(INTENT);

expect(() => instance.start()).toThrowError('Copilot was already started. Please call the `end()` method before starting a new test flow.');
});

it('should not end without start a new flow(end->end)', async () => {
Copilot.init(mockConfig);
const instance = Copilot.getInstance();
instance.start();

await instance.performStep(INTENT);
instance.end(true);

expect(() => instance.end(true)).toThrowError('Copilot is not running. Please call the `start()` method before ending the test flow.');
});
});

describe('end', () => {
it('end with disable cache=true should not save to cache', async () => {
mockCache();

Copilot.init(mockConfig);
const instance = Copilot.getInstance();
instance.start();

await instance.performStep(INTENT);
instance.end(true);

expect(mockedCacheFile).toBeUndefined();
});
});
});
42 changes: 38 additions & 4 deletions src/Copilot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {CodeEvaluator} from "@/utils/CodeEvaluator";
import {SnapshotManager} from "@/utils/SnapshotManager";
import {StepPerformer} from "@/actions/StepPerformer";
import {Config, PreviousStep} from "@/types";
import {CacheHandler} from "@/utils/CacheHandler";

/**
* The main Copilot class that provides AI-assisted testing capabilities for a given underlying testing framework.
Expand All @@ -18,18 +19,23 @@ export class Copilot {
private readonly snapshotManager: SnapshotManager;
private previousSteps: PreviousStep[] = [];
private stepPerformer: StepPerformer;
private cacheHandler: CacheHandler;
private isRunning: boolean = false;

private constructor(config: Config) {
this.promptCreator = new PromptCreator(config.frameworkDriver.apiCatalog);
this.codeEvaluator = new CodeEvaluator();
this.snapshotManager = new SnapshotManager(config.frameworkDriver);
this.cacheHandler = new CacheHandler();
this.stepPerformer = new StepPerformer(
config.frameworkDriver.apiCatalog.context,
this.promptCreator,
this.codeEvaluator,
this.snapshotManager,
config.promptHandler
config.promptHandler,
this.cacheHandler
);

}

/**
Expand All @@ -49,6 +55,10 @@ export class Copilot {
* @param config The configuration options for Copilot.
*/
static init(config: Config): void {
if (Copilot.instance) {
throw new CopilotError('Copilot has already been initialized. Please call the `init()` method only once.');
}

Copilot.instance = new Copilot(config);
}

Expand All @@ -57,18 +67,42 @@ export class Copilot {
* @param step The step describing the operation to perform.
*/
async performStep(step: string): Promise<any> {
if (!this.isRunning) {
throw new CopilotError('Copilot is not running. Please call the `start()` method before performing any steps.');
}

const {code, result} = await this.stepPerformer.perform(step, this.previousSteps);
this.didPerformStep(step, code, result);

return result;
}

/**
* Resets the Copilot by clearing the previous steps.
* Starts the Copilot by clearing the previous steps and temporary cache.
* @note This must be called before starting a new test flow, in order to clean context from previous tests.
*/
reset(): void {
start(): void {
if (this.isRunning) {
throw new CopilotError('Copilot was already started. Please call the `end()` method before starting a new test flow.');
}

this.isRunning = true;
this.previousSteps = [];
this.cacheHandler.clearTemporaryCache();
}

/**
* Ends the Copilot test flow and optionally saves the temporary cache to the main cache.
* @param isCacheDisabled - boolean flag indicating whether the temporary cache data should be saved to the main cache.
*/
end(isCacheDisabled: boolean = false): void {
if (!this.isRunning) {
throw new CopilotError('Copilot is not running. Please call the `start()` method before ending the test flow.');
}

this.isRunning = false;

if (!isCacheDisabled)
this.cacheHandler.flushTemporaryCache();
}

private didPerformStep(step: string, code: string, result: any): void {
Expand Down
Loading

0 comments on commit 1d163e5

Please sign in to comment.