onbuzz 3.6.1 → 3.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/__test-utils__/fixtures/malformedJson.js +31 -0
- package/src/__test-utils__/globalSetup.js +9 -0
- package/src/__test-utils__/globalTeardown.js +12 -0
- package/src/__test-utils__/mockFactories.js +101 -0
- package/src/analyzers/__tests__/CSSAnalyzer.test.js +41 -0
- package/src/analyzers/__tests__/ConfigValidator.test.js +362 -0
- package/src/analyzers/__tests__/ESLintAnalyzer.test.js +271 -0
- package/src/analyzers/__tests__/JavaScriptAnalyzer.test.js +40 -0
- package/src/analyzers/__tests__/PrettierFormatter.test.js +197 -0
- package/src/analyzers/__tests__/PythonAnalyzer.test.js +208 -0
- package/src/analyzers/__tests__/SecurityAnalyzer.test.js +303 -0
- package/src/analyzers/__tests__/SparrowAnalyzer.test.js +270 -0
- package/src/analyzers/__tests__/TypeScriptAnalyzer.test.js +187 -0
- package/src/core/__tests__/agentPool.test.js +601 -0
- package/src/core/__tests__/agentScheduler.test.js +576 -0
- package/src/core/__tests__/contextManager.test.js +252 -0
- package/src/core/__tests__/flowExecutor.test.js +262 -0
- package/src/core/__tests__/messageProcessor.test.js +627 -0
- package/src/core/__tests__/orchestrator.test.js +257 -0
- package/src/core/__tests__/stateManager.test.js +375 -0
- package/src/core/agentPool.js +11 -1
- package/src/index.js +25 -9
- package/src/interfaces/terminal/__tests__/smoke/imports.test.js +3 -5
- package/src/services/__tests__/agentActivityService.test.js +319 -0
- package/src/services/__tests__/apiKeyManager.test.js +206 -0
- package/src/services/__tests__/benchmarkService.test.js +184 -0
- package/src/services/__tests__/budgetService.test.js +211 -0
- package/src/services/__tests__/contextInjectionService.test.js +205 -0
- package/src/services/__tests__/conversationCompactionService.test.js +280 -0
- package/src/services/__tests__/credentialVault.test.js +469 -0
- package/src/services/__tests__/errorHandler.test.js +314 -0
- package/src/services/__tests__/fileAttachmentService.test.js +278 -0
- package/src/services/__tests__/flowContextService.test.js +199 -0
- package/src/services/__tests__/memoryService.test.js +450 -0
- package/src/services/__tests__/modelRouterService.test.js +388 -0
- package/src/services/__tests__/modelsService.test.js +261 -0
- package/src/services/__tests__/portRegistry.test.js +123 -0
- package/src/services/__tests__/projectDetector.test.js +34 -0
- package/src/services/__tests__/promptService.test.js +242 -0
- package/src/services/__tests__/qualityInspector.test.js +97 -0
- package/src/services/__tests__/scheduleService.test.js +308 -0
- package/src/services/__tests__/serviceRegistry.test.js +74 -0
- package/src/services/__tests__/skillsService.test.js +402 -0
- package/src/services/__tests__/tokenCountingService.test.js +48 -0
- package/src/tools/__tests__/agentCommunicationTool.test.js +500 -0
- package/src/tools/__tests__/agentDelayTool.test.js +342 -0
- package/src/tools/__tests__/asyncToolManager.test.js +344 -0
- package/src/tools/__tests__/baseTool.test.js +420 -0
- package/src/tools/__tests__/codeMapTool.test.js +348 -0
- package/src/tools/__tests__/fileContentReplaceTool.test.js +309 -0
- package/src/tools/__tests__/fileSystemTool.test.js +717 -0
- package/src/tools/__tests__/fileTreeTool.test.js +274 -0
- package/src/tools/__tests__/helpTool.test.js +204 -0
- package/src/tools/__tests__/jobDoneTool.test.js +296 -0
- package/src/tools/__tests__/memoryTool.test.js +297 -0
- package/src/tools/__tests__/seekTool.test.js +282 -0
- package/src/tools/__tests__/skillsTool.test.js +226 -0
- package/src/tools/__tests__/staticAnalysisTool.test.js +509 -0
- package/src/tools/__tests__/taskManagerTool.test.js +725 -0
- package/src/tools/__tests__/terminalTool.test.js +384 -0
- package/src/tools/__tests__/userPromptTool.test.js +297 -0
- package/src/tools/__tests__/webTool.e2e.test.js +25 -11
- package/src/tools/webTool.js +6 -12
- package/src/types/__tests__/agent.test.js +499 -0
- package/src/types/__tests__/contextReference.test.js +606 -0
- package/src/types/__tests__/conversation.test.js +555 -0
- package/src/types/__tests__/toolCommand.test.js +584 -0
- package/src/types/contextReference.js +1 -1
- package/src/utilities/__tests__/attachmentValidator.test.js +80 -0
- package/src/utilities/__tests__/configManager.test.js +397 -0
- package/src/utilities/__tests__/constants.test.js +49 -0
- package/src/utilities/__tests__/directoryAccessManager.test.js +388 -0
- package/src/utilities/__tests__/fileProcessor.test.js +104 -0
- package/src/utilities/__tests__/jsonRepair.test.js +104 -0
- package/src/utilities/__tests__/logger.test.js +129 -0
- package/src/utilities/__tests__/platformUtils.test.js +87 -0
- package/src/utilities/__tests__/structuredFileValidator.test.js +263 -0
- package/src/utilities/__tests__/tagParser.test.js +887 -0
- package/src/utilities/__tests__/toolConstants.test.js +94 -0
- package/src/utilities/tagParser.js +2 -2
- package/src/tools/browserTool.js +0 -897
- package/src/utilities/platformUtils.test.js +0 -98
- /package/src/tools/{filesystemTool.js → fileSystemTool.js} +0 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { jest, describe, test, expect, beforeEach } from '@jest/globals';
|
|
2
|
+
import { createMockLogger, createMockConfig } from '../../__test-utils__/mockFactories.js';
|
|
3
|
+
|
|
4
|
+
// Mock fs/promises before import
|
|
5
|
+
const mockFs = {
|
|
6
|
+
stat: jest.fn(),
|
|
7
|
+
readFile: jest.fn(),
|
|
8
|
+
readdir: jest.fn()
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
jest.unstable_mockModule('fs', () => ({
|
|
12
|
+
promises: mockFs,
|
|
13
|
+
default: { promises: mockFs }
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
// Mock constants
|
|
17
|
+
jest.unstable_mockModule('../../utilities/constants.js', () => ({
|
|
18
|
+
TOOL_STATUS: { PENDING: 'pending', EXECUTING: 'executing', COMPLETED: 'completed', FAILED: 'failed' },
|
|
19
|
+
OPERATION_STATUS: { EXECUTING: 'executing', COMPLETED: 'completed', FAILED: 'failed', NOT_FOUND: 'not_found' },
|
|
20
|
+
ERROR_TYPES: {},
|
|
21
|
+
SYSTEM_DEFAULTS: { MAX_TOOL_EXECUTION_TIME: 300000 }
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
// Mock tagParser
|
|
25
|
+
jest.unstable_mockModule('../../utilities/tagParser.js', () => ({
|
|
26
|
+
default: { extractContent: jest.fn(() => []) }
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
const { default: SeekTool } = await import('../seekTool.js');
|
|
30
|
+
|
|
31
|
+
describe('SeekTool', () => {
|
|
32
|
+
let tool;
|
|
33
|
+
let logger;
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
jest.clearAllMocks();
|
|
37
|
+
logger = createMockLogger();
|
|
38
|
+
tool = new SeekTool({}, logger);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('constructor sets metadata correctly', () => {
|
|
42
|
+
expect(tool.id).toBe('seek');
|
|
43
|
+
expect(tool.requiresProject).toBe(true);
|
|
44
|
+
expect(tool.isAsync).toBe(true);
|
|
45
|
+
expect(tool.timeout).toBe(120000);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('getDescription returns seek description', () => {
|
|
49
|
+
const desc = tool.getDescription();
|
|
50
|
+
expect(desc).toContain('Seek Tool');
|
|
51
|
+
expect(desc).toContain('filePaths');
|
|
52
|
+
expect(desc).toContain('searchTerms');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('getRequiredParameters returns filePaths and searchTerms', () => {
|
|
56
|
+
expect(tool.getRequiredParameters()).toEqual(['filePaths', 'searchTerms']);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('parseParameters parses JSON content', () => {
|
|
60
|
+
const content = JSON.stringify({
|
|
61
|
+
filePaths: ['src/*.js'],
|
|
62
|
+
searchTerms: ['import React']
|
|
63
|
+
});
|
|
64
|
+
const result = tool.parseParameters(content);
|
|
65
|
+
expect(result.filePaths).toEqual(['src/*.js']);
|
|
66
|
+
expect(result.searchTerms).toEqual(['import React']);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('parseParameters parses XML content', () => {
|
|
70
|
+
const content = `
|
|
71
|
+
<in-files>
|
|
72
|
+
src/index.js
|
|
73
|
+
src/app.js
|
|
74
|
+
</in-files>
|
|
75
|
+
<search-terms>
|
|
76
|
+
<term>useState</term>
|
|
77
|
+
<term>useEffect</term>
|
|
78
|
+
</search-terms>
|
|
79
|
+
`;
|
|
80
|
+
const result = tool.parseParameters(content);
|
|
81
|
+
expect(result.filePaths).toEqual(['src/index.js', 'src/app.js']);
|
|
82
|
+
expect(result.searchTerms).toEqual(['useState', 'useEffect']);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('parseParameters returns parseError on invalid content', () => {
|
|
86
|
+
const result = tool.parseParameters('{ broken json');
|
|
87
|
+
expect(result).toHaveProperty('parseError');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('customValidateParameters rejects empty filePaths', () => {
|
|
91
|
+
const result = tool.customValidateParameters({ filePaths: [], searchTerms: ['foo'] });
|
|
92
|
+
expect(result.valid).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('customValidateParameters rejects empty searchTerms', () => {
|
|
96
|
+
const result = tool.customValidateParameters({ filePaths: ['a.js'], searchTerms: [] });
|
|
97
|
+
expect(result.valid).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('customValidateParameters rejects path traversal', () => {
|
|
101
|
+
const result = tool.customValidateParameters({
|
|
102
|
+
filePaths: ['../../etc/passwd'],
|
|
103
|
+
searchTerms: ['test']
|
|
104
|
+
});
|
|
105
|
+
expect(result.valid).toBe(false);
|
|
106
|
+
expect(result.errors.some(e => e.includes('traversal'))).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('customValidateParameters accepts valid params', () => {
|
|
110
|
+
const result = tool.customValidateParameters({
|
|
111
|
+
filePaths: ['src/index.js'],
|
|
112
|
+
searchTerms: ['import']
|
|
113
|
+
});
|
|
114
|
+
expect(result.valid).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('execute searches file and returns matches', async () => {
|
|
118
|
+
const fileContent = 'line 1\nimport React from "react";\nline 3\nimport useState from "react";\n';
|
|
119
|
+
|
|
120
|
+
mockFs.stat.mockResolvedValue({
|
|
121
|
+
isFile: () => true,
|
|
122
|
+
isDirectory: () => false,
|
|
123
|
+
size: 100
|
|
124
|
+
});
|
|
125
|
+
mockFs.readFile.mockResolvedValue(fileContent);
|
|
126
|
+
|
|
127
|
+
const result = await tool.execute(
|
|
128
|
+
{ filePaths: ['src/app.js'], searchTerms: ['import React'] },
|
|
129
|
+
{ projectDir: '/project', agentId: 'agent-1' }
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
expect(result.success).toBe(true);
|
|
133
|
+
expect(result.totalMatches).toBe(1);
|
|
134
|
+
expect(result.filesSearched).toBe(1);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('execute handles file not found', async () => {
|
|
138
|
+
mockFs.stat.mockRejectedValue({ code: 'ENOENT', message: 'not found' });
|
|
139
|
+
|
|
140
|
+
const result = await tool.execute(
|
|
141
|
+
{ filePaths: ['missing.js'], searchTerms: ['test'] },
|
|
142
|
+
{ projectDir: '/project', agentId: 'agent-1' }
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
expect(result.success).toBe(true);
|
|
146
|
+
expect(result.filesNotFound).toBe(1);
|
|
147
|
+
expect(result.totalMatches).toBe(0);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('execute returns no matches when search term not found', async () => {
|
|
151
|
+
mockFs.stat.mockResolvedValue({
|
|
152
|
+
isFile: () => true,
|
|
153
|
+
isDirectory: () => false,
|
|
154
|
+
size: 50
|
|
155
|
+
});
|
|
156
|
+
mockFs.readFile.mockResolvedValue('no matching content here\n');
|
|
157
|
+
|
|
158
|
+
const result = await tool.execute(
|
|
159
|
+
{ filePaths: ['src/app.js'], searchTerms: ['nonexistent'] },
|
|
160
|
+
{ projectDir: '/project', agentId: 'agent-1' }
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
expect(result.success).toBe(true);
|
|
164
|
+
expect(result.totalMatches).toBe(0);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('execute skips binary files', async () => {
|
|
168
|
+
mockFs.stat.mockResolvedValue({
|
|
169
|
+
isFile: () => true,
|
|
170
|
+
isDirectory: () => false,
|
|
171
|
+
size: 100
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const result = await tool.execute(
|
|
175
|
+
{ filePaths: ['image.png'], searchTerms: ['test'] },
|
|
176
|
+
{ projectDir: '/project', agentId: 'agent-1' }
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
expect(result.success).toBe(true);
|
|
180
|
+
// Binary file is resolved but skipped during search
|
|
181
|
+
expect(mockFs.readFile).not.toHaveBeenCalled();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test('execute skips oversized files', async () => {
|
|
185
|
+
mockFs.stat
|
|
186
|
+
.mockResolvedValueOnce({ isFile: () => true, isDirectory: () => false, size: 100 }) // resolveFilePaths
|
|
187
|
+
.mockResolvedValueOnce({ size: 20 * 1024 * 1024 }); // searchFiles - too large
|
|
188
|
+
|
|
189
|
+
const result = await tool.execute(
|
|
190
|
+
{ filePaths: ['large.js'], searchTerms: ['test'] },
|
|
191
|
+
{ projectDir: '/project', agentId: 'agent-1' }
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
expect(result.success).toBe(true);
|
|
195
|
+
expect(result.filesWithErrors).toBe(1);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test('execute truncates long line content around match', async () => {
|
|
199
|
+
const longLine = 'a'.repeat(100) + 'MATCH_HERE' + 'b'.repeat(200);
|
|
200
|
+
mockFs.stat.mockResolvedValue({
|
|
201
|
+
isFile: () => true,
|
|
202
|
+
isDirectory: () => false,
|
|
203
|
+
size: 500
|
|
204
|
+
});
|
|
205
|
+
mockFs.readFile.mockResolvedValue(longLine + '\n');
|
|
206
|
+
|
|
207
|
+
const result = await tool.execute(
|
|
208
|
+
{ filePaths: ['file.js'], searchTerms: ['MATCH_HERE'] },
|
|
209
|
+
{ projectDir: '/project', agentId: 'agent-1' }
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
expect(result.success).toBe(true);
|
|
213
|
+
expect(result.totalMatches).toBe(1);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('execute uses directoryAccess when provided', async () => {
|
|
217
|
+
mockFs.stat.mockResolvedValue({
|
|
218
|
+
isFile: () => true,
|
|
219
|
+
isDirectory: () => false,
|
|
220
|
+
size: 50
|
|
221
|
+
});
|
|
222
|
+
mockFs.readFile.mockResolvedValue('hello world\n');
|
|
223
|
+
|
|
224
|
+
const result = await tool.execute(
|
|
225
|
+
{ filePaths: ['test.js'], searchTerms: ['hello'] },
|
|
226
|
+
{
|
|
227
|
+
projectDir: '/project',
|
|
228
|
+
agentId: 'agent-1',
|
|
229
|
+
directoryAccess: {
|
|
230
|
+
workingDirectory: '/project',
|
|
231
|
+
readOnlyDirectories: ['/shared'],
|
|
232
|
+
writeEnabledDirectories: ['/output']
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
expect(result.success).toBe(true);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test('matchesPattern handles wildcard patterns', () => {
|
|
241
|
+
expect(tool.matchesPattern('file.js', '*.js')).toBe(true);
|
|
242
|
+
expect(tool.matchesPattern('file.ts', '*.js')).toBe(false);
|
|
243
|
+
expect(tool.matchesPattern('test.spec.js', '*.spec.js')).toBe(true);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test('shouldSkipDirectory returns true for node_modules', () => {
|
|
247
|
+
expect(tool.shouldSkipDirectory('node_modules')).toBe(true);
|
|
248
|
+
expect(tool.shouldSkipDirectory('.git')).toBe(true);
|
|
249
|
+
expect(tool.shouldSkipDirectory('src')).toBe(false);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test('shouldSkipFile returns true for binary extensions', () => {
|
|
253
|
+
expect(tool.shouldSkipFile('image.png')).toBe(true);
|
|
254
|
+
expect(tool.shouldSkipFile('script.js')).toBe(false);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test('formatResults with no matches', () => {
|
|
258
|
+
const output = tool.formatResults([], [], [], 5);
|
|
259
|
+
expect(output).toContain('No matches found');
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test('formatResults with matches', () => {
|
|
263
|
+
const matches = [
|
|
264
|
+
{ term: 'foo', filePath: 'a.js', lineNumber: 10, lineContent: 'const foo = 1;' }
|
|
265
|
+
];
|
|
266
|
+
const output = tool.formatResults(matches, [], [], 1);
|
|
267
|
+
expect(output).toContain('SEARCH RESULTS');
|
|
268
|
+
expect(output).toContain('foo');
|
|
269
|
+
expect(output).toContain('a.js:10');
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test('formatResults with not found and error files', () => {
|
|
273
|
+
const output = tool.formatResults(
|
|
274
|
+
[],
|
|
275
|
+
[{ filePath: 'err.js', error: 'read error' }],
|
|
276
|
+
['missing.js (ENOENT)'],
|
|
277
|
+
0
|
|
278
|
+
);
|
|
279
|
+
expect(output).toContain('FILES NOT FOUND');
|
|
280
|
+
expect(output).toContain('FILES WITH ERRORS');
|
|
281
|
+
});
|
|
282
|
+
});
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { jest, describe, test, expect, beforeEach } from '@jest/globals';
|
|
2
|
+
import { createMockLogger, createMockConfig } from '../../__test-utils__/mockFactories.js';
|
|
3
|
+
|
|
4
|
+
// Mock the skills service
|
|
5
|
+
const mockSkillsService = {
|
|
6
|
+
initialize: jest.fn().mockResolvedValue(undefined),
|
|
7
|
+
listSkills: jest.fn().mockResolvedValue([{ name: 'code-review', description: 'Code review' }]),
|
|
8
|
+
describeSkill: jest.fn().mockResolvedValue({ name: 'code-review', sections: [] }),
|
|
9
|
+
readSkill: jest.fn().mockResolvedValue({ name: 'code-review', content: '# Code Review' }),
|
|
10
|
+
readSkillSection: jest.fn().mockResolvedValue({ content: 'Section content' }),
|
|
11
|
+
readSkillFile: jest.fn().mockResolvedValue({ content: 'file content' }),
|
|
12
|
+
createSkill: jest.fn().mockResolvedValue({ name: 'new-skill' }),
|
|
13
|
+
updateSkill: jest.fn().mockResolvedValue({ name: 'updated-skill' }),
|
|
14
|
+
deleteSkill: jest.fn().mockResolvedValue(undefined),
|
|
15
|
+
importSkill: jest.fn().mockResolvedValue({ name: 'imported-skill' })
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
jest.unstable_mockModule('../../services/skillsService.js', () => ({
|
|
19
|
+
getSkillsService: jest.fn().mockReturnValue(mockSkillsService)
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
jest.unstable_mockModule('../../utilities/toolConstants.js', () => ({
|
|
23
|
+
SKILLS_ACTIONS: {
|
|
24
|
+
LIST: 'list',
|
|
25
|
+
DESCRIBE: 'describe',
|
|
26
|
+
READ: 'read',
|
|
27
|
+
READ_SECTION: 'read-section',
|
|
28
|
+
READ_FILE: 'read-file',
|
|
29
|
+
CREATE: 'create',
|
|
30
|
+
UPDATE: 'update',
|
|
31
|
+
DELETE: 'delete',
|
|
32
|
+
IMPORT: 'import'
|
|
33
|
+
}
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
const { default: SkillsTool } = await import('../skillsTool.js');
|
|
37
|
+
|
|
38
|
+
describe('SkillsTool', () => {
|
|
39
|
+
let tool;
|
|
40
|
+
let logger;
|
|
41
|
+
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
logger = createMockLogger();
|
|
44
|
+
tool = new SkillsTool({}, logger);
|
|
45
|
+
tool.skillsService = null; // Reset so _ensureSkillsService re-initializes
|
|
46
|
+
jest.clearAllMocks();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('constructor', () => {
|
|
50
|
+
test('should set correct metadata', () => {
|
|
51
|
+
expect(tool.requiresProject).toBe(false);
|
|
52
|
+
expect(tool.isAsync).toBe(false);
|
|
53
|
+
expect(tool.timeout).toBe(30000);
|
|
54
|
+
expect(tool.skillsService).toBeNull();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('getDescription', () => {
|
|
59
|
+
test('should return description with all actions', () => {
|
|
60
|
+
const desc = tool.getDescription();
|
|
61
|
+
expect(desc).toContain('Skills Tool');
|
|
62
|
+
expect(desc).toContain('list');
|
|
63
|
+
expect(desc).toContain('describe');
|
|
64
|
+
expect(desc).toContain('read');
|
|
65
|
+
expect(desc).toContain('create');
|
|
66
|
+
expect(desc).toContain('delete');
|
|
67
|
+
expect(desc).toContain('import');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('parseParameters', () => {
|
|
72
|
+
test('should return content as-is', () => {
|
|
73
|
+
const result = tool.parseParameters('test');
|
|
74
|
+
expect(result).toBe('test');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('getRequiredParameters', () => {
|
|
79
|
+
test('should require action', () => {
|
|
80
|
+
expect(tool.getRequiredParameters()).toContain('action');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('getSupportedActions', () => {
|
|
85
|
+
test('should return all skill actions', () => {
|
|
86
|
+
const actions = tool.getSupportedActions();
|
|
87
|
+
expect(actions).toContain('list');
|
|
88
|
+
expect(actions).toContain('describe');
|
|
89
|
+
expect(actions).toContain('read');
|
|
90
|
+
expect(actions).toContain('create');
|
|
91
|
+
expect(actions).toContain('delete');
|
|
92
|
+
expect(actions).toContain('import');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('validateParameterTypes', () => {
|
|
97
|
+
test('should reject non-string action', () => {
|
|
98
|
+
const errors = tool.validateParameterTypes({ action: 123 });
|
|
99
|
+
expect(errors).toContain('action must be a string');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('should reject non-string name', () => {
|
|
103
|
+
const errors = tool.validateParameterTypes({ name: 123 });
|
|
104
|
+
expect(errors).toContain('name must be a string');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('should accept valid params', () => {
|
|
108
|
+
const errors = tool.validateParameterTypes({ action: 'list' });
|
|
109
|
+
expect(errors).toHaveLength(0);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('customValidateParameters', () => {
|
|
114
|
+
test('should reject invalid action', () => {
|
|
115
|
+
const errors = tool.customValidateParameters({ action: 'invalid' });
|
|
116
|
+
expect(errors.length).toBeGreaterThan(0);
|
|
117
|
+
expect(errors[0]).toContain('Invalid action');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('should require name for describe', () => {
|
|
121
|
+
const errors = tool.customValidateParameters({ action: 'describe' });
|
|
122
|
+
expect(errors).toContain('"name" is required for action "describe"');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('should require content for create', () => {
|
|
126
|
+
const errors = tool.customValidateParameters({ action: 'create', name: 'test' });
|
|
127
|
+
expect(errors).toContain('"content" is required for action "create"');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('should require section for read-section', () => {
|
|
131
|
+
const errors = tool.customValidateParameters({ action: 'read-section', name: 'test' });
|
|
132
|
+
expect(errors).toContain('"section" is required for action "read-section"');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('should require file for read-file', () => {
|
|
136
|
+
const errors = tool.customValidateParameters({ action: 'read-file', name: 'test' });
|
|
137
|
+
expect(errors).toContain('"file" is required for action "read-file"');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('should require source for import', () => {
|
|
141
|
+
const errors = tool.customValidateParameters({ action: 'import' });
|
|
142
|
+
expect(errors).toContain('"source" is required for action "import"');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('should accept valid list params', () => {
|
|
146
|
+
const errors = tool.customValidateParameters({ action: 'list' });
|
|
147
|
+
expect(errors).toHaveLength(0);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('execute', () => {
|
|
152
|
+
test('should list skills', async () => {
|
|
153
|
+
const result = await tool.execute({ action: 'list' });
|
|
154
|
+
expect(result.success).toBe(true);
|
|
155
|
+
expect(result.message).toContain('listed');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('should describe a skill', async () => {
|
|
159
|
+
const result = await tool.execute({ action: 'describe', name: 'code-review' });
|
|
160
|
+
expect(result.success).toBe(true);
|
|
161
|
+
expect(mockSkillsService.describeSkill).toHaveBeenCalledWith('code-review');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('should read a skill', async () => {
|
|
165
|
+
const result = await tool.execute({ action: 'read', name: 'code-review' });
|
|
166
|
+
expect(result.success).toBe(true);
|
|
167
|
+
expect(mockSkillsService.readSkill).toHaveBeenCalledWith('code-review');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('should read a section', async () => {
|
|
171
|
+
const result = await tool.execute({ action: 'read-section', name: 'code-review', section: 'Checklist' });
|
|
172
|
+
expect(result.success).toBe(true);
|
|
173
|
+
expect(mockSkillsService.readSkillSection).toHaveBeenCalledWith('code-review', 'Checklist');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('should read a file', async () => {
|
|
177
|
+
const result = await tool.execute({ action: 'read-file', name: 'templates', file: 'welcome.html' });
|
|
178
|
+
expect(result.success).toBe(true);
|
|
179
|
+
expect(mockSkillsService.readSkillFile).toHaveBeenCalledWith('templates', 'welcome.html');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test('should create a skill', async () => {
|
|
183
|
+
const result = await tool.execute({ action: 'create', name: 'new', content: '# New' });
|
|
184
|
+
expect(result.success).toBe(true);
|
|
185
|
+
expect(mockSkillsService.createSkill).toHaveBeenCalled();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test('should update a skill', async () => {
|
|
189
|
+
const result = await tool.execute({ action: 'update', name: 'existing', content: '# Updated' });
|
|
190
|
+
expect(result.success).toBe(true);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test('should delete a skill', async () => {
|
|
194
|
+
const result = await tool.execute({ action: 'delete', name: 'old' });
|
|
195
|
+
expect(result.success).toBe(true);
|
|
196
|
+
expect(result.result.deleted).toBe('old');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('should import a skill', async () => {
|
|
200
|
+
const result = await tool.execute({ action: 'import', source: '/path/to/skill' });
|
|
201
|
+
expect(result.success).toBe(true);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('should return error for unknown action', async () => {
|
|
205
|
+
const result = await tool.execute({ action: 'unknown' });
|
|
206
|
+
expect(result.success).toBe(false);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test('should handle service errors', async () => {
|
|
210
|
+
mockSkillsService.readSkill.mockRejectedValueOnce(new Error('Skill not found'));
|
|
211
|
+
const result = await tool.execute({ action: 'read', name: 'nonexistent' });
|
|
212
|
+
expect(result.success).toBe(false);
|
|
213
|
+
expect(result.error).toContain('not found');
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe('getParameterSchema', () => {
|
|
218
|
+
test('should return valid schema', () => {
|
|
219
|
+
const schema = tool.getParameterSchema();
|
|
220
|
+
expect(schema.type).toBe('object');
|
|
221
|
+
expect(schema.required).toContain('action');
|
|
222
|
+
expect(schema.properties.action).toBeDefined();
|
|
223
|
+
expect(schema.properties.name).toBeDefined();
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
});
|