Update customAction and add tests
- Change temp subdirectory from 'devchat/context' to 'devchat/action'. - Add check for tempFile existence before unlinking. - Add new test cases for actionManager and customAction.
This commit is contained in:
parent
3363250305
commit
944e4e0c9b
@ -96,7 +96,7 @@ export class CustomActions {
|
|||||||
|
|
||||||
handlerAction: async (args: { [key: string]: string }) => {
|
handlerAction: async (args: { [key: string]: string }) => {
|
||||||
// Implement the handler logic for the custom action
|
// Implement the handler logic for the custom action
|
||||||
const tempDir = await createTempSubdirectory('devchat/context');
|
const tempDir = await createTempSubdirectory('devchat/action');
|
||||||
const tempFile = path.join(tempDir, 'apply.json');
|
const tempFile = path.join(tempDir, 'apply.json');
|
||||||
|
|
||||||
const contextMap = {
|
const contextMap = {
|
||||||
@ -109,6 +109,7 @@ export class CustomActions {
|
|||||||
|
|
||||||
// Save contextMap to temp file
|
// Save contextMap to temp file
|
||||||
await UiUtilWrapper.writeFile(tempFile, JSON.stringify(contextMap));
|
await UiUtilWrapper.writeFile(tempFile, JSON.stringify(contextMap));
|
||||||
|
|
||||||
// replace ${contextFile} with tempFile for arg in handler
|
// replace ${contextFile} with tempFile for arg in handler
|
||||||
let handlerArgs = action.handler.map(arg => arg.replace('${contextFile}', tempFile));
|
let handlerArgs = action.handler.map(arg => arg.replace('${contextFile}', tempFile));
|
||||||
if (args !== undefined) {
|
if (args !== undefined) {
|
||||||
@ -140,7 +141,9 @@ export class CustomActions {
|
|||||||
logger.channel()?.info(`stderr:`, result.stderr);
|
logger.channel()?.info(`stderr:`, result.stderr);
|
||||||
|
|
||||||
// remove temp file
|
// remove temp file
|
||||||
|
if (fs.existsSync(tempFile)) {
|
||||||
fs.unlinkSync(tempFile);
|
fs.unlinkSync(tempFile);
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -66,4 +66,115 @@ describe('ActionManager', () => {
|
|||||||
expect(result.stdout).to.equal('');
|
expect(result.stdout).to.equal('');
|
||||||
expect(result.stderr).to.equal('');
|
expect(result.stderr).to.equal('');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should apply action with content.content.xxx type args correctly', async () => {
|
||||||
|
const actionManager = ActionManager.getInstance();
|
||||||
|
const testActionWithNestedArgs: Action = {
|
||||||
|
...testAction,
|
||||||
|
name: 'testActionWithNestedArgs',
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
name: 'arg1',
|
||||||
|
description: 'arg1 description',
|
||||||
|
type: 'string',
|
||||||
|
from: 'content.content.field1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
handlerAction: async (args) => {
|
||||||
|
if (args.arg1 === 'value1') {
|
||||||
|
return { exitCode: 0, stdout: '', stderr: '' };
|
||||||
|
} else {
|
||||||
|
return { exitCode: -1, stdout: '', stderr: 'Incorrect arg1 value' };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
actionManager.registerAction(testActionWithNestedArgs);
|
||||||
|
const actionName = 'testActionWithNestedArgs';
|
||||||
|
const content = {
|
||||||
|
command: 'test',
|
||||||
|
content: JSON.stringify({ field1: 'value1' }),
|
||||||
|
fileName: 'test.txt',
|
||||||
|
};
|
||||||
|
const result = await actionManager.applyAction(actionName, content);
|
||||||
|
expect(result.exitCode).to.equal(0);
|
||||||
|
expect(result.stdout).to.equal('');
|
||||||
|
expect(result.stderr).to.equal('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return error when content is missing required args', async () => {
|
||||||
|
const actionManager = ActionManager.getInstance();
|
||||||
|
const testActionWithMissingArgs: Action = {
|
||||||
|
...testAction,
|
||||||
|
name: 'testActionWithMissingArgs',
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
name: 'arg1',
|
||||||
|
description: 'arg1 description',
|
||||||
|
type: 'string',
|
||||||
|
from: 'content.field1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
handlerAction: async (args) => {
|
||||||
|
return { exitCode: 0, stdout: '', stderr: '' };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
actionManager.registerAction(testActionWithMissingArgs);
|
||||||
|
const actionName = 'testActionWithMissingArgs';
|
||||||
|
const content = {
|
||||||
|
command: 'test',
|
||||||
|
content: JSON.stringify({}),
|
||||||
|
fileName: 'test.txt',
|
||||||
|
};
|
||||||
|
const result = await actionManager.applyAction(actionName, content);
|
||||||
|
expect(result.exitCode).to.equal(-1);
|
||||||
|
expect(result.stdout).to.equal('');
|
||||||
|
expect(result.stderr).to.equal('Action testActionWithMissingArgs arg arg1 from content.field1 is undefined');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should trigger handlerAction of CommandRunAction', async () => {
|
||||||
|
class TestAction implements Action {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
type: string[];
|
||||||
|
action: string;
|
||||||
|
handler: string[];
|
||||||
|
args: { name: string; description: string; type: string; as?: string | undefined; from: string; }[];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.name = 'testAction2';
|
||||||
|
this.description = 'Test action for unit testing';
|
||||||
|
this.type = ['test'];
|
||||||
|
this.action = 'test';
|
||||||
|
this.handler = [];
|
||||||
|
this.args = [];
|
||||||
|
}
|
||||||
|
async handlerAction(content: any): Promise<{ exitCode: number; stdout: string; stderr: string }> {
|
||||||
|
return {
|
||||||
|
exitCode: 0,
|
||||||
|
stdout: 'Test action executed successfully',
|
||||||
|
stderr: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionManager = ActionManager.getInstance();
|
||||||
|
const testAction = new TestAction();
|
||||||
|
actionManager.registerAction(testAction);
|
||||||
|
|
||||||
|
const commandRunActionContent = {
|
||||||
|
command: 'testAction2',
|
||||||
|
args: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const content = {
|
||||||
|
command: 'command_run',
|
||||||
|
content: JSON.stringify(commandRunActionContent),
|
||||||
|
fileName: 'test.txt',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await actionManager.applyAction('command_run', content);
|
||||||
|
expect(result.exitCode).to.equal(0);
|
||||||
|
expect(result.stdout).to.equal('Test action executed successfully');
|
||||||
|
expect(result.stderr).to.equal('');
|
||||||
|
});
|
||||||
});
|
});
|
@ -1,5 +1,7 @@
|
|||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
import { CustomActions } from '../../src/action/customAction';
|
import { CustomActions } from '../../src/action/customAction';
|
||||||
|
|
||||||
describe('CustomActions', () => {
|
describe('CustomActions', () => {
|
||||||
@ -27,4 +29,125 @@ describe('CustomActions', () => {
|
|||||||
const instruction = customActions.actionInstruction();
|
const instruction = customActions.actionInstruction();
|
||||||
expect(instruction).to.include('sampleAction: A sample action for testing');
|
expect(instruction).to.include('sampleAction: A sample action for testing');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return action instruction with args', () => {
|
||||||
|
// Add a sample action with args to the customActions instance
|
||||||
|
customActions.getActions().push({
|
||||||
|
name: 'sampleActionWithArgs',
|
||||||
|
description: 'A sample action with args for testing',
|
||||||
|
type: ['test'],
|
||||||
|
action: 'sample',
|
||||||
|
handler: [],
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
"name": "arg1",
|
||||||
|
"description": "Argument 1",
|
||||||
|
"type": "string",
|
||||||
|
"from": "content.fileName"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "arg2",
|
||||||
|
"description": "Argument 2",
|
||||||
|
"type": "number",
|
||||||
|
"from": "content.content"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
handlerAction: async (args: { [key: string]: string }) => {
|
||||||
|
return { exitCode: 0, stdout: '', stderr: '' };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const instruction = customActions.actionInstruction();
|
||||||
|
expect(instruction).to.include('sampleActionWithArgs: A sample action with args for testing');
|
||||||
|
expect(instruction).to.include('Args:');
|
||||||
|
expect(instruction).to.include('name: arg1 type: (string) description: Argument 1');
|
||||||
|
expect(instruction).to.include('name: arg2 type: (number) description: Argument 2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse actions from workflows directory', () => {
|
||||||
|
// Create a temporary workflows directory with a sample action
|
||||||
|
const workflowsDir = path.join(__dirname, 'temp_workflows');
|
||||||
|
fs.mkdirSync(workflowsDir);
|
||||||
|
fs.mkdirSync(path.join(workflowsDir, 'sample_extension'));
|
||||||
|
fs.mkdirSync(path.join(workflowsDir, 'sample_extension', 'action'));
|
||||||
|
fs.mkdirSync(path.join(workflowsDir, 'sample_extension', 'action', 'sample_action'));
|
||||||
|
const settingsPath = path.join(workflowsDir, 'sample_extension', 'action', 'sample_action', '_setting_.json');
|
||||||
|
const sampleActionSettings = {
|
||||||
|
name: 'sampleParsedAction',
|
||||||
|
description: 'A sample parsed action for testing',
|
||||||
|
type: ['test'],
|
||||||
|
action: 'sample',
|
||||||
|
handler: [],
|
||||||
|
args: [],
|
||||||
|
};
|
||||||
|
fs.writeFileSync(settingsPath, JSON.stringify(sampleActionSettings));
|
||||||
|
|
||||||
|
// Call parseActions with the temporary workflows directory
|
||||||
|
customActions.parseActions(workflowsDir);
|
||||||
|
|
||||||
|
// Check if the parsed action is in the actions list
|
||||||
|
const actions = customActions.getActions();
|
||||||
|
const parsedAction = actions.find(action => action.name === 'sampleParsedAction');
|
||||||
|
expect(parsedAction).to.not.be.undefined;
|
||||||
|
expect(parsedAction?.description).to.equal('A sample parsed action for testing');
|
||||||
|
|
||||||
|
// Clean up the temporary workflows directory
|
||||||
|
fs.unlinkSync(settingsPath);
|
||||||
|
fs.rmdirSync(path.join(workflowsDir, 'sample_extension', 'action', 'sample_action'));
|
||||||
|
fs.rmdirSync(path.join(workflowsDir, 'sample_extension', 'action'));
|
||||||
|
fs.rmdirSync(path.join(workflowsDir, 'sample_extension'));
|
||||||
|
fs.rmdirSync(workflowsDir);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse handlerAction correctly from directory with echo command', async () => {
|
||||||
|
// Create a temporary directory for the sample action
|
||||||
|
const workflowsDir = path.join(__dirname, 'temp_workflows');
|
||||||
|
fs.mkdirSync(workflowsDir);
|
||||||
|
fs.mkdirSync(path.join(workflowsDir, 'sample_extension'));
|
||||||
|
fs.mkdirSync(path.join(workflowsDir, 'sample_extension', 'action'));
|
||||||
|
fs.mkdirSync(path.join(workflowsDir, 'sample_extension', 'action', 'sample_action'));
|
||||||
|
const settingsPath = path.join(workflowsDir, 'sample_extension', 'action', 'sample_action', '_setting_.json');
|
||||||
|
const sampleActionJson = {
|
||||||
|
name: 'sampleAction',
|
||||||
|
description: 'A sample action with a handlerAction method for testing',
|
||||||
|
type: ['test'],
|
||||||
|
action: 'sample',
|
||||||
|
handler: ["echo", "${arg1}"],
|
||||||
|
args: [
|
||||||
|
{ name: 'arg1', type: 'string' },
|
||||||
|
{ name: 'arg2', type: 'string' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
fs.writeFileSync(settingsPath, JSON.stringify(sampleActionJson));
|
||||||
|
|
||||||
|
|
||||||
|
// Call parseActions with the temporary directory
|
||||||
|
customActions.parseActions(workflowsDir);
|
||||||
|
const actions = customActions.getActions();
|
||||||
|
|
||||||
|
// Clean up the temporary directory
|
||||||
|
fs.unlinkSync(settingsPath);
|
||||||
|
fs.rmdirSync(path.join(workflowsDir, 'sample_extension', 'action', 'sample_action'));
|
||||||
|
fs.rmdirSync(path.join(workflowsDir, 'sample_extension', 'action'));
|
||||||
|
fs.rmdirSync(path.join(workflowsDir, 'sample_extension'));
|
||||||
|
fs.rmdirSync(workflowsDir);
|
||||||
|
|
||||||
|
// Check if the returned actions array has the expected length
|
||||||
|
expect(actions.length).equal(1);
|
||||||
|
|
||||||
|
// Get the parsed action object
|
||||||
|
const parsedAction = actions[0];
|
||||||
|
|
||||||
|
// Call the handlerAction method with valid args
|
||||||
|
const validResult = await parsedAction.handlerAction({ arg1: 'value1', arg2: 'value2' });
|
||||||
|
|
||||||
|
// Check if the returned CommandResult is as expected
|
||||||
|
expect(validResult).to.deep.equal({ exitCode: 0, stdout: 'value1\n', stderr: '' });
|
||||||
|
|
||||||
|
// Call the handlerAction method with invalid args
|
||||||
|
const invalidResult = await parsedAction.handlerAction({ arg1: 'wrongValue', arg2: 'value2' });
|
||||||
|
|
||||||
|
// Check if the returned CommandResult is as expected
|
||||||
|
expect(invalidResult).to.deep.equal({ exitCode: 0, stdout: 'wrongValue\n', stderr: '' });
|
||||||
|
});
|
||||||
});
|
});
|
@ -119,33 +119,6 @@ describe('sendMessageBase', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('sendMessageBase', async () => {
|
describe('sendMessageBase', async () => {
|
||||||
it('should send message correct with openai api key', async () => {
|
|
||||||
const message = {
|
|
||||||
text: 'Hello, world!'
|
|
||||||
};
|
|
||||||
const handlePartialData = (data: { command: string, text: string, user: string, date: string }) => {
|
|
||||||
// Handle partial data
|
|
||||||
};
|
|
||||||
|
|
||||||
workspaceFoldersFirstPathStub.returns('./');
|
|
||||||
|
|
||||||
getConfigurationStub.withArgs('DevChat', 'API_KEY').returns(process.env.TEST_OPENAI_API_KEY);
|
|
||||||
getConfigurationStub.withArgs('DevChat', 'OpenAI.model').returns('gpt-4');
|
|
||||||
getConfigurationStub.withArgs('DevChat', 'OpenAI.temperature').returns(0);
|
|
||||||
getConfigurationStub.withArgs('DevChat', 'OpenAI.stream').returns('true');
|
|
||||||
getConfigurationStub.withArgs('DevChat', 'llmModel').returns('OpenAI');
|
|
||||||
getConfigurationStub.withArgs('DevChat', 'OpenAI.tokensPerPrompt').returns(9000);
|
|
||||||
|
|
||||||
const result = await sendMessageBase(message, handlePartialData);
|
|
||||||
expect(result).to.be.an('object');
|
|
||||||
expect(result!.command).to.equal('receiveMessage');
|
|
||||||
expect(result!.text).to.be.a('string');
|
|
||||||
expect(result!.hash).to.be.a('string');
|
|
||||||
expect(result!.user).to.be.a('string');
|
|
||||||
expect(result!.date).to.be.a('string');
|
|
||||||
expect(result!.isError).to.be.false;
|
|
||||||
}).timeout(10000);
|
|
||||||
|
|
||||||
it('should send message correct with DevChat access key', async () => {
|
it('should send message correct with DevChat access key', async () => {
|
||||||
const message = {
|
const message = {
|
||||||
text: 'Hello, world!'
|
text: 'Hello, world!'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user