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 }) => {
|
||||
// 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 contextMap = {
|
||||
@ -109,6 +109,7 @@ export class CustomActions {
|
||||
|
||||
// Save contextMap to temp file
|
||||
await UiUtilWrapper.writeFile(tempFile, JSON.stringify(contextMap));
|
||||
|
||||
// replace ${contextFile} with tempFile for arg in handler
|
||||
let handlerArgs = action.handler.map(arg => arg.replace('${contextFile}', tempFile));
|
||||
if (args !== undefined) {
|
||||
@ -140,7 +141,9 @@ export class CustomActions {
|
||||
logger.channel()?.info(`stderr:`, result.stderr);
|
||||
|
||||
// remove temp file
|
||||
fs.unlinkSync(tempFile);
|
||||
if (fs.existsSync(tempFile)) {
|
||||
fs.unlinkSync(tempFile);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
};
|
||||
|
@ -66,4 +66,115 @@ describe('ActionManager', () => {
|
||||
expect(result.stdout).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 'mocha';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { CustomActions } from '../../src/action/customAction';
|
||||
|
||||
describe('CustomActions', () => {
|
||||
@ -27,4 +29,125 @@ describe('CustomActions', () => {
|
||||
const instruction = customActions.actionInstruction();
|
||||
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 () => {
|
||||
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 () => {
|
||||
const message = {
|
||||
text: 'Hello, world!'
|
||||
|
Loading…
x
Reference in New Issue
Block a user