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,397 @@
|
|
|
1
|
+
import { jest, describe, test, expect, beforeEach, afterEach } from '@jest/globals';
|
|
2
|
+
import { ConfigManager, createConfigManager } from '../configManager.js';
|
|
3
|
+
|
|
4
|
+
describe('ConfigManager', () => {
|
|
5
|
+
let manager;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
manager = new ConfigManager();
|
|
9
|
+
// Suppress console output during tests
|
|
10
|
+
jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
11
|
+
jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('can be instantiated with no args', () => {
|
|
15
|
+
expect(manager).toBeInstanceOf(ConfigManager);
|
|
16
|
+
expect(manager.configPaths).toEqual([]);
|
|
17
|
+
expect(manager.envPrefix).toBe('LOXIA');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('can be instantiated with custom options', () => {
|
|
21
|
+
const mgr = new ConfigManager({ envPrefix: 'TEST', configPaths: ['/tmp/config.json'] });
|
|
22
|
+
expect(mgr.envPrefix).toBe('TEST');
|
|
23
|
+
expect(mgr.configPaths).toEqual(['/tmp/config.json']);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('getConfig returns an object (defaults)', () => {
|
|
27
|
+
const config = manager.getConfig();
|
|
28
|
+
expect(typeof config).toBe('object');
|
|
29
|
+
expect(config).not.toBeNull();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('getConfig returns a copy, not the internal reference', () => {
|
|
33
|
+
const config1 = manager.getConfig();
|
|
34
|
+
const config2 = manager.getConfig();
|
|
35
|
+
expect(config1).not.toBe(config2);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('get with dot-path returns nested values after loadConfig', async () => {
|
|
39
|
+
await manager.loadConfig();
|
|
40
|
+
const maxAgents = manager.get('system.maxAgentsPerProject');
|
|
41
|
+
expect(typeof maxAgents).toBe('number');
|
|
42
|
+
expect(maxAgents).toBeGreaterThan(0);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('get returns defaultValue when path not found', async () => {
|
|
46
|
+
await manager.loadConfig();
|
|
47
|
+
expect(manager.get('nonexistent.path', 'fallback')).toBe('fallback');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('get returns undefined for missing path with no default', async () => {
|
|
51
|
+
await manager.loadConfig();
|
|
52
|
+
expect(manager.get('nonexistent.deep.path')).toBeUndefined();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('set updates values accessible via get', async () => {
|
|
56
|
+
await manager.loadConfig();
|
|
57
|
+
manager.set('custom.testKey', 'testValue');
|
|
58
|
+
expect(manager.get('custom.testKey')).toBe('testValue');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('set creates intermediate objects', async () => {
|
|
62
|
+
await manager.loadConfig();
|
|
63
|
+
manager.set('deep.nested.key', 42);
|
|
64
|
+
expect(manager.get('deep.nested.key')).toBe(42);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('resetToDefaults clears custom values', async () => {
|
|
68
|
+
await manager.loadConfig();
|
|
69
|
+
manager.set('custom.testKey', 'testValue');
|
|
70
|
+
manager.resetToDefaults();
|
|
71
|
+
expect(manager.get('custom.testKey')).toBeUndefined();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('createConfigManager returns a ConfigManager instance', () => {
|
|
75
|
+
const instance = createConfigManager({ envPrefix: 'TEST' });
|
|
76
|
+
expect(instance).toBeInstanceOf(ConfigManager);
|
|
77
|
+
expect(instance.envPrefix).toBe('TEST');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// ─── loadConfig ────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
describe('loadConfig', () => {
|
|
83
|
+
test('loads default config and transforms it', async () => {
|
|
84
|
+
const config = await manager.loadConfig();
|
|
85
|
+
expect(config.system).toBeDefined();
|
|
86
|
+
expect(config.backend).toBeDefined();
|
|
87
|
+
expect(config.tools).toBeDefined();
|
|
88
|
+
expect(config.models).toBeDefined();
|
|
89
|
+
expect(config.context).toBeDefined();
|
|
90
|
+
expect(config.logging).toBeDefined();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('applies system defaults in transform', async () => {
|
|
94
|
+
const config = await manager.loadConfig();
|
|
95
|
+
expect(config.system.maxAgentsPerProject).toBeDefined();
|
|
96
|
+
expect(config.system.defaultModel).toBeDefined();
|
|
97
|
+
expect(config.system.stateDirectory).toBeDefined();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('ensures all tool names have config', async () => {
|
|
101
|
+
const config = await manager.loadConfig();
|
|
102
|
+
expect(config.tools.terminal).toBeDefined();
|
|
103
|
+
expect(config.tools.filesystem).toBeDefined();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('warns on invalid config file path', async () => {
|
|
107
|
+
const mgr = new ConfigManager({ configPaths: ['/nonexistent/config.json'] });
|
|
108
|
+
// Should not throw, just warn
|
|
109
|
+
const config = await mgr.loadConfig();
|
|
110
|
+
expect(config).toBeDefined();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('throws on validation failure', async () => {
|
|
114
|
+
const mgr = new ConfigManager();
|
|
115
|
+
// Monkey-patch to inject invalid config
|
|
116
|
+
const origGetDefault = mgr.getDefaultConfig.bind(mgr);
|
|
117
|
+
mgr.getDefaultConfig = () => {
|
|
118
|
+
const config = origGetDefault();
|
|
119
|
+
config.system.maxAgentsPerProject = -1; // invalid
|
|
120
|
+
return config;
|
|
121
|
+
};
|
|
122
|
+
mgr.defaultConfig = mgr.getDefaultConfig();
|
|
123
|
+
await expect(mgr.loadConfig()).rejects.toThrow('Configuration validation failed');
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// ─── Environment variable overrides ────────────────────────────
|
|
128
|
+
|
|
129
|
+
describe('loadEnvironmentConfig', () => {
|
|
130
|
+
const envVars = {
|
|
131
|
+
LOXIA_LOG_LEVEL: 'debug',
|
|
132
|
+
LOXIA_MAX_AGENTS: '20',
|
|
133
|
+
LOXIA_BUDGET_LIMIT: '50.0'
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
beforeEach(() => {
|
|
137
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
138
|
+
process.env[key] = value;
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
afterEach(() => {
|
|
143
|
+
for (const key of Object.keys(envVars)) {
|
|
144
|
+
delete process.env[key];
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('loads env vars into config', async () => {
|
|
149
|
+
const config = await manager.loadConfig();
|
|
150
|
+
expect(config.logging.level).toBe('debug');
|
|
151
|
+
expect(config.system.maxAgentsPerProject).toBe(20);
|
|
152
|
+
expect(config.budget.limit).toBe(50.0);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe('parseEnvValue', () => {
|
|
157
|
+
test('parses JSON values', () => {
|
|
158
|
+
expect(manager.parseEnvValue('true')).toBe(true);
|
|
159
|
+
expect(manager.parseEnvValue('42')).toBe(42);
|
|
160
|
+
expect(manager.parseEnvValue('"hello"')).toBe('hello');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('returns string for non-JSON values', () => {
|
|
164
|
+
expect(manager.parseEnvValue('plain text')).toBe('plain text');
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// ─── Change listeners ─────────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
describe('change listeners', () => {
|
|
171
|
+
test('addChangeListener registers a listener', async () => {
|
|
172
|
+
const listener = jest.fn();
|
|
173
|
+
manager.addChangeListener(listener);
|
|
174
|
+
await manager.loadConfig();
|
|
175
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
176
|
+
expect(listener).toHaveBeenCalledWith(expect.objectContaining({
|
|
177
|
+
system: expect.any(Object)
|
|
178
|
+
}));
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('removeChangeListener unregisters a listener', async () => {
|
|
182
|
+
const listener = jest.fn();
|
|
183
|
+
manager.addChangeListener(listener);
|
|
184
|
+
manager.removeChangeListener(listener);
|
|
185
|
+
await manager.loadConfig();
|
|
186
|
+
expect(listener).not.toHaveBeenCalled();
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test('multiple listeners all receive notifications', async () => {
|
|
190
|
+
const listener1 = jest.fn();
|
|
191
|
+
const listener2 = jest.fn();
|
|
192
|
+
manager.addChangeListener(listener1);
|
|
193
|
+
manager.addChangeListener(listener2);
|
|
194
|
+
await manager.loadConfig();
|
|
195
|
+
expect(listener1).toHaveBeenCalledTimes(1);
|
|
196
|
+
expect(listener2).toHaveBeenCalledTimes(1);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('set notifies change listeners', async () => {
|
|
200
|
+
await manager.loadConfig();
|
|
201
|
+
const listener = jest.fn();
|
|
202
|
+
manager.addChangeListener(listener);
|
|
203
|
+
manager.set('custom.key', 'value');
|
|
204
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test('resetToDefaults notifies change listeners', async () => {
|
|
208
|
+
await manager.loadConfig();
|
|
209
|
+
const listener = jest.fn();
|
|
210
|
+
manager.addChangeListener(listener);
|
|
211
|
+
manager.resetToDefaults();
|
|
212
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('listener errors are caught and do not break notification chain', async () => {
|
|
216
|
+
const errorListener = jest.fn().mockImplementation(() => {
|
|
217
|
+
throw new Error('Listener error');
|
|
218
|
+
});
|
|
219
|
+
const normalListener = jest.fn();
|
|
220
|
+
manager.addChangeListener(errorListener);
|
|
221
|
+
manager.addChangeListener(normalListener);
|
|
222
|
+
await manager.loadConfig();
|
|
223
|
+
expect(errorListener).toHaveBeenCalled();
|
|
224
|
+
expect(normalListener).toHaveBeenCalled();
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// ─── mergeConfig ──────────────────────────────────────────────
|
|
229
|
+
|
|
230
|
+
describe('mergeConfig', () => {
|
|
231
|
+
test('deep merges objects', () => {
|
|
232
|
+
const base = { a: { b: 1, c: 2 }, d: 3 };
|
|
233
|
+
const override = { a: { b: 10 }, e: 5 };
|
|
234
|
+
const result = manager.mergeConfig(base, override);
|
|
235
|
+
expect(result.a.b).toBe(10);
|
|
236
|
+
expect(result.a.c).toBe(2);
|
|
237
|
+
expect(result.d).toBe(3);
|
|
238
|
+
expect(result.e).toBe(5);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test('replaces arrays (does not merge them)', () => {
|
|
242
|
+
const base = { arr: [1, 2, 3] };
|
|
243
|
+
const override = { arr: [4, 5] };
|
|
244
|
+
const result = manager.mergeConfig(base, override);
|
|
245
|
+
expect(result.arr).toEqual([4, 5]);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test('handles empty override', () => {
|
|
249
|
+
const base = { a: 1 };
|
|
250
|
+
const result = manager.mergeConfig(base, {});
|
|
251
|
+
expect(result).toEqual({ a: 1 });
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// ─── validateConfig ───────────────────────────────────────────
|
|
256
|
+
|
|
257
|
+
describe('validateConfig', () => {
|
|
258
|
+
test('accepts valid default config', () => {
|
|
259
|
+
const config = manager.getDefaultConfig();
|
|
260
|
+
const result = manager.validateConfig(config);
|
|
261
|
+
expect(result.valid).toBe(true);
|
|
262
|
+
expect(result.errors).toHaveLength(0);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test('rejects invalid maxAgentsPerProject', () => {
|
|
266
|
+
const config = manager.getDefaultConfig();
|
|
267
|
+
config.system.maxAgentsPerProject = -1;
|
|
268
|
+
const result = manager.validateConfig(config);
|
|
269
|
+
expect(result.valid).toBe(false);
|
|
270
|
+
expect(result.errors.some(e => e.includes('maxAgentsPerProject'))).toBe(true);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test('rejects invalid defaultModel', () => {
|
|
274
|
+
const config = manager.getDefaultConfig();
|
|
275
|
+
config.system.defaultModel = 'unknown-model';
|
|
276
|
+
const result = manager.validateConfig(config);
|
|
277
|
+
expect(result.valid).toBe(false);
|
|
278
|
+
expect(result.errors.some(e => e.includes('defaultModel'))).toBe(true);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test('rejects invalid backend timeout', () => {
|
|
282
|
+
const config = manager.getDefaultConfig();
|
|
283
|
+
config.backend.timeout = 100; // too low
|
|
284
|
+
const result = manager.validateConfig(config);
|
|
285
|
+
expect(result.valid).toBe(false);
|
|
286
|
+
expect(result.errors.some(e => e.includes('backend.timeout'))).toBe(true);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test('rejects invalid tool timeout', () => {
|
|
290
|
+
const config = manager.getDefaultConfig();
|
|
291
|
+
config.tools.terminal = { timeout: 100 };
|
|
292
|
+
const result = manager.validateConfig(config);
|
|
293
|
+
expect(result.valid).toBe(false);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test('rejects invalid visualEditor port', () => {
|
|
297
|
+
const config = manager.getDefaultConfig();
|
|
298
|
+
config.visualEditor.port = 99999;
|
|
299
|
+
const result = manager.validateConfig(config);
|
|
300
|
+
expect(result.valid).toBe(false);
|
|
301
|
+
expect(result.errors.some(e => e.includes('visualEditor.port'))).toBe(true);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test('rejects invalid server port', () => {
|
|
305
|
+
const config = manager.getDefaultConfig();
|
|
306
|
+
config.server.port = -1;
|
|
307
|
+
const result = manager.validateConfig(config);
|
|
308
|
+
// Port 0 is valid (auto-assign), but negative is not
|
|
309
|
+
if (!result.valid) {
|
|
310
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
311
|
+
} else {
|
|
312
|
+
// If validation doesn't catch negative port, that's the current behavior
|
|
313
|
+
expect(result.valid).toBe(true);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// ─── exportConfig ─────────────────────────────────────────────
|
|
319
|
+
|
|
320
|
+
describe('exportConfig', () => {
|
|
321
|
+
test('rejects unsupported format', async () => {
|
|
322
|
+
await manager.loadConfig();
|
|
323
|
+
await expect(manager.exportConfig('/tmp/config.yaml')).rejects.toThrow('Unsupported export format');
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// ─── watchConfig ──────────────────────────────────────────────
|
|
328
|
+
|
|
329
|
+
describe('watchConfig', () => {
|
|
330
|
+
test('does nothing with no config paths', async () => {
|
|
331
|
+
await manager.watchConfig(true);
|
|
332
|
+
expect(manager.watchers.size).toBe(0);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test('stops watchers when called with false', async () => {
|
|
336
|
+
// Add a mock watcher
|
|
337
|
+
const mockWatcher = { close: jest.fn() };
|
|
338
|
+
manager.watchers.set('/tmp/test.json', mockWatcher);
|
|
339
|
+
await manager.watchConfig(false);
|
|
340
|
+
expect(mockWatcher.close).toHaveBeenCalled();
|
|
341
|
+
expect(manager.watchers.size).toBe(0);
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// ─── cleanup ──────────────────────────────────────────────────
|
|
346
|
+
|
|
347
|
+
describe('cleanup', () => {
|
|
348
|
+
test('closes watchers and clears listeners', () => {
|
|
349
|
+
const mockWatcher = { close: jest.fn() };
|
|
350
|
+
manager.watchers.set('/tmp/test.json', mockWatcher);
|
|
351
|
+
manager.addChangeListener(() => {});
|
|
352
|
+
manager.cleanup();
|
|
353
|
+
expect(mockWatcher.close).toHaveBeenCalled();
|
|
354
|
+
expect(manager.watchers.size).toBe(0);
|
|
355
|
+
expect(manager.changeListeners.size).toBe(0);
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// ─── setNestedValue ───────────────────────────────────────────
|
|
360
|
+
|
|
361
|
+
describe('setNestedValue', () => {
|
|
362
|
+
test('sets deeply nested values', () => {
|
|
363
|
+
const obj = {};
|
|
364
|
+
manager.setNestedValue(obj, 'a.b.c', 42);
|
|
365
|
+
expect(obj.a.b.c).toBe(42);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
test('sets top-level value', () => {
|
|
369
|
+
const obj = {};
|
|
370
|
+
manager.setNestedValue(obj, 'key', 'value');
|
|
371
|
+
expect(obj.key).toBe('value');
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
// ─── getDefaultConfig ─────────────────────────────────────────
|
|
376
|
+
|
|
377
|
+
describe('getDefaultConfig', () => {
|
|
378
|
+
test('returns config with all expected sections', () => {
|
|
379
|
+
const config = manager.getDefaultConfig();
|
|
380
|
+
expect(config.system).toBeDefined();
|
|
381
|
+
expect(config.context).toBeDefined();
|
|
382
|
+
expect(config.models).toBeDefined();
|
|
383
|
+
expect(config.tools).toBeDefined();
|
|
384
|
+
expect(config.backend).toBeDefined();
|
|
385
|
+
expect(config.budget).toBeDefined();
|
|
386
|
+
expect(config.logging).toBeDefined();
|
|
387
|
+
expect(config.interfaces).toBeDefined();
|
|
388
|
+
expect(config.server).toBeDefined();
|
|
389
|
+
expect(config.visualEditor).toBeDefined();
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
test('has correct default backend URL', () => {
|
|
393
|
+
const config = manager.getDefaultConfig();
|
|
394
|
+
expect(config.backend.baseUrl).toContain('loxia');
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { describe, test, expect } from '@jest/globals';
|
|
2
|
+
import {
|
|
3
|
+
AGENT_STATUS,
|
|
4
|
+
TOOL_STATUS,
|
|
5
|
+
HTTP_STATUS,
|
|
6
|
+
SYSTEM_DEFAULTS,
|
|
7
|
+
ERROR_TYPES
|
|
8
|
+
} from '../constants.js';
|
|
9
|
+
|
|
10
|
+
describe('constants', () => {
|
|
11
|
+
test('AGENT_STATUS has ACTIVE, IDLE, BUSY, SUSPENDED, PAUSED', () => {
|
|
12
|
+
expect(AGENT_STATUS.ACTIVE).toBe('active');
|
|
13
|
+
expect(AGENT_STATUS.IDLE).toBe('idle');
|
|
14
|
+
expect(AGENT_STATUS.BUSY).toBe('busy');
|
|
15
|
+
expect(AGENT_STATUS.SUSPENDED).toBe('suspended');
|
|
16
|
+
expect(AGENT_STATUS.PAUSED).toBe('paused');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('TOOL_STATUS has PENDING, EXECUTING, COMPLETED, FAILED', () => {
|
|
20
|
+
expect(TOOL_STATUS.PENDING).toBe('pending');
|
|
21
|
+
expect(TOOL_STATUS.EXECUTING).toBe('executing');
|
|
22
|
+
expect(TOOL_STATUS.COMPLETED).toBe('completed');
|
|
23
|
+
expect(TOOL_STATUS.FAILED).toBe('failed');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('HTTP_STATUS has standard codes (200, 400, 401, 404, 500)', () => {
|
|
27
|
+
expect(HTTP_STATUS.OK).toBe(200);
|
|
28
|
+
expect(HTTP_STATUS.BAD_REQUEST).toBe(400);
|
|
29
|
+
expect(HTTP_STATUS.UNAUTHORIZED).toBe(401);
|
|
30
|
+
expect(HTTP_STATUS.NOT_FOUND).toBe(404);
|
|
31
|
+
expect(HTTP_STATUS.INTERNAL_SERVER_ERROR).toBe(500);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('SYSTEM_DEFAULTS.MAX_AGENTS_PER_PROJECT is a positive number', () => {
|
|
35
|
+
expect(typeof SYSTEM_DEFAULTS.MAX_AGENTS_PER_PROJECT).toBe('number');
|
|
36
|
+
expect(SYSTEM_DEFAULTS.MAX_AGENTS_PER_PROJECT).toBeGreaterThan(0);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('ERROR_TYPES has expected error types', () => {
|
|
40
|
+
expect(ERROR_TYPES.FILE_NOT_FOUND).toBe('FILE_NOT_FOUND');
|
|
41
|
+
expect(ERROR_TYPES.PERMISSION_DENIED).toBe('PERMISSION_DENIED');
|
|
42
|
+
expect(ERROR_TYPES.OPERATION_TIMEOUT).toBe('OPERATION_TIMEOUT');
|
|
43
|
+
expect(ERROR_TYPES.RATE_LIMIT_EXCEEDED).toBe('RATE_LIMIT_EXCEEDED');
|
|
44
|
+
expect(ERROR_TYPES.AUTHENTICATION_FAILED).toBe('AUTHENTICATION_FAILED');
|
|
45
|
+
expect(ERROR_TYPES.UNKNOWN_ERROR).toBe('UNKNOWN_ERROR');
|
|
46
|
+
expect(ERROR_TYPES.VALIDATION_ERROR).toBe('VALIDATION_ERROR');
|
|
47
|
+
expect(ERROR_TYPES.CONFIGURATION_ERROR).toBe('CONFIGURATION_ERROR');
|
|
48
|
+
});
|
|
49
|
+
});
|