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,129 @@
|
|
|
1
|
+
import { jest, describe, test, expect, beforeEach, afterEach } from '@jest/globals';
|
|
2
|
+
|
|
3
|
+
// Mock constants before importing Logger
|
|
4
|
+
jest.unstable_mockModule('../constants.js', () => ({
|
|
5
|
+
SYSTEM_VERSION: '1.0.0-test'
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
const { Logger, createLogger } = await import('../logger.js');
|
|
9
|
+
|
|
10
|
+
describe('Logger', () => {
|
|
11
|
+
let consoleSpy;
|
|
12
|
+
let consoleErrorSpy;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
16
|
+
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
consoleSpy.mockRestore();
|
|
21
|
+
consoleErrorSpy.mockRestore();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('constructor with no args creates logger with default level info', () => {
|
|
25
|
+
const logger = new Logger();
|
|
26
|
+
expect(logger).toBeInstanceOf(Logger);
|
|
27
|
+
expect(logger.currentLevel).toBe(logger.levels['info']);
|
|
28
|
+
expect(logger.outputs).toEqual(['console']);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('constructor with level=error only logs errors', () => {
|
|
32
|
+
const logger = new Logger({ level: 'error' });
|
|
33
|
+
expect(logger.currentLevel).toBe(0);
|
|
34
|
+
|
|
35
|
+
logger.error('error msg');
|
|
36
|
+
logger.warn('warn msg');
|
|
37
|
+
logger.info('info msg');
|
|
38
|
+
|
|
39
|
+
// error goes to console.error, warn/info would go to console.log
|
|
40
|
+
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
|
|
41
|
+
expect(consoleSpy).toHaveBeenCalledTimes(0);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('info() calls log() internally', () => {
|
|
45
|
+
const logger = new Logger();
|
|
46
|
+
const logSpy = jest.spyOn(logger, 'log');
|
|
47
|
+
logger.info('test message', { key: 'val' });
|
|
48
|
+
|
|
49
|
+
expect(logSpy).toHaveBeenCalledWith('info', 'test message', { key: 'val' });
|
|
50
|
+
logSpy.mockRestore();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('error() logs error level messages via console.error', () => {
|
|
54
|
+
const logger = new Logger();
|
|
55
|
+
logger.error('something broke');
|
|
56
|
+
|
|
57
|
+
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
|
|
58
|
+
expect(consoleErrorSpy.mock.calls[0][0]).toContain('something broke');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('debug() is suppressed when level is info', () => {
|
|
62
|
+
const logger = new Logger({ level: 'info' });
|
|
63
|
+
logger.debug('debug detail');
|
|
64
|
+
|
|
65
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
66
|
+
expect(consoleErrorSpy).not.toHaveBeenCalled();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('setLevel changes the active log level', () => {
|
|
70
|
+
const logger = new Logger({ level: 'info' });
|
|
71
|
+
expect(logger.currentLevel).toBe(2);
|
|
72
|
+
|
|
73
|
+
logger.setLevel('debug');
|
|
74
|
+
expect(logger.currentLevel).toBe(3);
|
|
75
|
+
|
|
76
|
+
// Now debug messages should be logged
|
|
77
|
+
consoleSpy.mockClear();
|
|
78
|
+
logger.debug('now visible');
|
|
79
|
+
expect(consoleSpy).toHaveBeenCalled();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('child() returns a new logger inheriting parent settings', () => {
|
|
83
|
+
const parent = new Logger({ level: 'debug' });
|
|
84
|
+
const child = parent.child({ component: 'test-child' });
|
|
85
|
+
|
|
86
|
+
expect(child).not.toBe(parent);
|
|
87
|
+
expect(child.childContext).toEqual({ component: 'test-child' });
|
|
88
|
+
// Child should inherit parent level
|
|
89
|
+
expect(child.currentLevel).toBe(parent.currentLevel);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('createLogger returns Logger instance', () => {
|
|
93
|
+
const logger = createLogger({ level: 'warn', autoInit: false });
|
|
94
|
+
expect(logger).toBeInstanceOf(Logger);
|
|
95
|
+
expect(logger.currentLevel).toBe(logger.levels['warn']);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('logToolExecution uses error level when status is failed', () => {
|
|
99
|
+
const logger = new Logger();
|
|
100
|
+
const errorSpy = jest.spyOn(logger, 'error');
|
|
101
|
+
const infoSpy = jest.spyOn(logger, 'info');
|
|
102
|
+
|
|
103
|
+
logger.logToolExecution('tool-1', 'op-1', 'failed', 500);
|
|
104
|
+
expect(errorSpy).toHaveBeenCalledTimes(1);
|
|
105
|
+
expect(infoSpy).not.toHaveBeenCalled();
|
|
106
|
+
|
|
107
|
+
errorSpy.mockRestore();
|
|
108
|
+
infoSpy.mockRestore();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('logApiRequest uses error level when status >= 400', () => {
|
|
112
|
+
const logger = new Logger();
|
|
113
|
+
const errorSpy = jest.spyOn(logger, 'error');
|
|
114
|
+
const infoSpy = jest.spyOn(logger, 'info');
|
|
115
|
+
|
|
116
|
+
logger.logApiRequest('GET', '/api/data', 500, 120);
|
|
117
|
+
expect(errorSpy).toHaveBeenCalledTimes(1);
|
|
118
|
+
|
|
119
|
+
errorSpy.mockClear();
|
|
120
|
+
infoSpy.mockClear();
|
|
121
|
+
|
|
122
|
+
logger.logApiRequest('GET', '/api/data', 200, 80);
|
|
123
|
+
expect(infoSpy).toHaveBeenCalledTimes(1);
|
|
124
|
+
expect(errorSpy).not.toHaveBeenCalled();
|
|
125
|
+
|
|
126
|
+
errorSpy.mockRestore();
|
|
127
|
+
infoSpy.mockRestore();
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { jest, describe, test, expect, beforeEach } from '@jest/globals';
|
|
2
|
+
import {
|
|
3
|
+
PLATFORMS,
|
|
4
|
+
getPlatform,
|
|
5
|
+
isMacOS,
|
|
6
|
+
isWindows,
|
|
7
|
+
isLinux,
|
|
8
|
+
normalizePath,
|
|
9
|
+
pathsEqual,
|
|
10
|
+
pathStartsWith,
|
|
11
|
+
getPlatformInfo,
|
|
12
|
+
getDefaultUserAgent,
|
|
13
|
+
getUserShell,
|
|
14
|
+
getSystemRestrictedPaths,
|
|
15
|
+
getPlatformBlockedExtensions
|
|
16
|
+
} from '../platformUtils.js';
|
|
17
|
+
|
|
18
|
+
describe('platformUtils', () => {
|
|
19
|
+
describe('getPlatform', () => {
|
|
20
|
+
test('returns a string matching process.platform', () => {
|
|
21
|
+
const platform = getPlatform();
|
|
22
|
+
expect(typeof platform).toBe('string');
|
|
23
|
+
expect(platform).toBe(process.platform);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('PLATFORMS', () => {
|
|
28
|
+
test('has MACOS, WINDOWS, LINUX keys', () => {
|
|
29
|
+
expect(PLATFORMS.MACOS).toBe('darwin');
|
|
30
|
+
expect(PLATFORMS.WINDOWS).toBe('win32');
|
|
31
|
+
expect(PLATFORMS.LINUX).toBe('linux');
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('normalizePath', () => {
|
|
36
|
+
test('handles forward and back slashes consistently', () => {
|
|
37
|
+
const result = normalizePath('src/utilities/file.js');
|
|
38
|
+
expect(typeof result).toBe('string');
|
|
39
|
+
expect(result).not.toBe('');
|
|
40
|
+
|
|
41
|
+
// On case-insensitive platforms (macOS/Windows), should lowercase
|
|
42
|
+
if (process.platform === 'win32' || process.platform === 'darwin') {
|
|
43
|
+
expect(normalizePath('SRC/File.JS')).toBe(normalizePath('src/file.js'));
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('pathsEqual', () => {
|
|
49
|
+
test('compares case correctly for current platform', () => {
|
|
50
|
+
expect(pathsEqual('src/file.js', 'src/file.js')).toBe(true);
|
|
51
|
+
|
|
52
|
+
if (process.platform === 'win32' || process.platform === 'darwin') {
|
|
53
|
+
// Case-insensitive platforms
|
|
54
|
+
expect(pathsEqual('SRC/File.js', 'src/file.js')).toBe(true);
|
|
55
|
+
} else {
|
|
56
|
+
// Case-sensitive platforms (Linux)
|
|
57
|
+
expect(pathsEqual('SRC/File.js', 'src/file.js')).toBe(false);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('pathStartsWith', () => {
|
|
63
|
+
test('detects prefix correctly', () => {
|
|
64
|
+
expect(pathStartsWith('src/utilities/file.js', 'src')).toBe(true);
|
|
65
|
+
expect(pathStartsWith('src/utilities/file.js', 'other')).toBe(false);
|
|
66
|
+
|
|
67
|
+
if (process.platform === 'win32' || process.platform === 'darwin') {
|
|
68
|
+
expect(pathStartsWith('SRC/utilities/file.js', 'src')).toBe(true);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('getPlatformInfo', () => {
|
|
74
|
+
test('returns object with expected keys', () => {
|
|
75
|
+
const info = getPlatformInfo();
|
|
76
|
+
expect(info).toHaveProperty('platform');
|
|
77
|
+
expect(info).toHaveProperty('arch');
|
|
78
|
+
expect(info).toHaveProperty('shell');
|
|
79
|
+
expect(info).toHaveProperty('shellPath');
|
|
80
|
+
expect(info).toHaveProperty('homeDir');
|
|
81
|
+
expect(info).toHaveProperty('tmpDir');
|
|
82
|
+
expect(info).toHaveProperty('nodeVersion');
|
|
83
|
+
expect(typeof info.platform).toBe('string');
|
|
84
|
+
expect(typeof info.nodeVersion).toBe('string');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { jest, describe, test, expect, beforeEach } from '@jest/globals';
|
|
2
|
+
import { createMockLogger } from '../../__test-utils__/mockFactories.js';
|
|
3
|
+
|
|
4
|
+
// Mock fs for validateStructuredFile
|
|
5
|
+
jest.unstable_mockModule('fs', () => ({
|
|
6
|
+
promises: {
|
|
7
|
+
readFile: jest.fn()
|
|
8
|
+
}
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
detectFormat,
|
|
13
|
+
getSupportedFormats,
|
|
14
|
+
validateContent,
|
|
15
|
+
validateStructuredFile,
|
|
16
|
+
validateForToolResponse,
|
|
17
|
+
registerValidator,
|
|
18
|
+
hasValidator,
|
|
19
|
+
default: validator
|
|
20
|
+
} = await import('../structuredFileValidator.js');
|
|
21
|
+
|
|
22
|
+
const { promises: mockFs } = await import('fs');
|
|
23
|
+
|
|
24
|
+
describe('detectFormat', () => {
|
|
25
|
+
test('returns json for .json files', () => {
|
|
26
|
+
expect(detectFormat('config.json')).toBe('json');
|
|
27
|
+
expect(detectFormat('/some/path/data.json')).toBe('json');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('returns yaml for .yml and .yaml files', () => {
|
|
31
|
+
expect(detectFormat('config.yml')).toBe('yaml');
|
|
32
|
+
expect(detectFormat('config.yaml')).toBe('yaml');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('returns xml for .xml files', () => {
|
|
36
|
+
expect(detectFormat('data.xml')).toBe('xml');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('returns toml for .toml files', () => {
|
|
40
|
+
expect(detectFormat('config.toml')).toBe('toml');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('returns ini for .ini files', () => {
|
|
44
|
+
expect(detectFormat('settings.ini')).toBe('ini');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('returns env for .env and .env.* files', () => {
|
|
48
|
+
expect(detectFormat('.env')).toBe('env');
|
|
49
|
+
expect(detectFormat('.env.local')).toBe('env');
|
|
50
|
+
expect(detectFormat('.env.production')).toBe('env');
|
|
51
|
+
expect(detectFormat('.env.custom')).toBe('env');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('returns properties for .properties files', () => {
|
|
55
|
+
expect(detectFormat('app.properties')).toBe('properties');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('returns null for unknown extensions', () => {
|
|
59
|
+
expect(detectFormat('readme.txt')).toBeNull();
|
|
60
|
+
expect(detectFormat('script.py')).toBeNull();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('getSupportedFormats', () => {
|
|
65
|
+
test('returns array including all built-in formats', () => {
|
|
66
|
+
const formats = getSupportedFormats();
|
|
67
|
+
expect(Array.isArray(formats)).toBe(true);
|
|
68
|
+
expect(formats).toContain('json');
|
|
69
|
+
expect(formats).toContain('yaml');
|
|
70
|
+
expect(formats).toContain('xml');
|
|
71
|
+
expect(formats).toContain('toml');
|
|
72
|
+
expect(formats).toContain('ini');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('returns unique format names', () => {
|
|
76
|
+
const formats = getSupportedFormats();
|
|
77
|
+
const unique = [...new Set(formats)];
|
|
78
|
+
expect(formats.length).toBe(unique.length);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('validateContent - JSON', () => {
|
|
83
|
+
test('valid JSON returns { valid: true }', () => {
|
|
84
|
+
const result = validateContent('{"key": "value"}', 'json');
|
|
85
|
+
expect(result.valid).toBe(true);
|
|
86
|
+
expect(result.format).toBe('json');
|
|
87
|
+
expect(result.errors).toHaveLength(0);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('invalid JSON returns { valid: false } with errors', () => {
|
|
91
|
+
const result = validateContent('{key: value}', 'json');
|
|
92
|
+
expect(result.valid).toBe(false);
|
|
93
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
94
|
+
expect(result.errors[0]).toHaveProperty('message');
|
|
95
|
+
expect(result.errors[0].severity).toBe('error');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('JSON with returnParsed option includes parsed data', () => {
|
|
99
|
+
const result = validateContent('{"a": 1}', 'json', { returnParsed: true });
|
|
100
|
+
expect(result.valid).toBe(true);
|
|
101
|
+
expect(result.parsed).toEqual({ a: 1 });
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('validateContent - YAML', () => {
|
|
106
|
+
test('valid YAML returns { valid: true }', () => {
|
|
107
|
+
const result = validateContent('key: value\nother: data', 'yaml');
|
|
108
|
+
expect(result.valid).toBe(true);
|
|
109
|
+
expect(result.format).toBe('yaml');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('YAML with tabs returns error', () => {
|
|
113
|
+
const result = validateContent('\tkey: value', 'yaml');
|
|
114
|
+
expect(result.valid).toBe(false);
|
|
115
|
+
expect(result.errors.some(e => e.message.includes('tabs'))).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('validateContent - XML', () => {
|
|
120
|
+
test('valid XML returns { valid: true }', () => {
|
|
121
|
+
const result = validateContent('<root><child>text</child></root>', 'xml');
|
|
122
|
+
expect(result.valid).toBe(true);
|
|
123
|
+
expect(result.format).toBe('xml');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('XML with mismatched tags returns error', () => {
|
|
127
|
+
const result = validateContent('<root><child>text</wrong></root>', 'xml');
|
|
128
|
+
expect(result.valid).toBe(false);
|
|
129
|
+
expect(result.errors.some(e => e.message.includes('Mismatched'))).toBe(true);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('XML with unclosed tags returns error', () => {
|
|
133
|
+
const result = validateContent('<root><child></root>', 'xml');
|
|
134
|
+
expect(result.valid).toBe(false);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('self-closing XML tags are valid', () => {
|
|
138
|
+
const result = validateContent('<root><br/></root>', 'xml');
|
|
139
|
+
expect(result.valid).toBe(true);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe('validateContent - TOML', () => {
|
|
144
|
+
test('valid TOML returns { valid: true }', () => {
|
|
145
|
+
const result = validateContent('[section]\nkey = "value"', 'toml');
|
|
146
|
+
expect(result.valid).toBe(true);
|
|
147
|
+
expect(result.format).toBe('toml');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('TOML with invalid section header returns error', () => {
|
|
151
|
+
const result = validateContent('[bad section', 'toml');
|
|
152
|
+
expect(result.valid).toBe(false);
|
|
153
|
+
expect(result.errors.some(e => e.message.includes('Invalid section header'))).toBe(true);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('TOML with duplicate sections returns error', () => {
|
|
157
|
+
const result = validateContent('[section]\nkey = "a"\n[section]\nkey = "b"', 'toml');
|
|
158
|
+
expect(result.valid).toBe(false);
|
|
159
|
+
expect(result.errors.some(e => e.message.includes('Duplicate section'))).toBe(true);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe('validateContent - INI/ENV/properties', () => {
|
|
164
|
+
test('valid INI returns { valid: true }', () => {
|
|
165
|
+
const result = validateContent('[section]\nkey=value', 'ini');
|
|
166
|
+
expect(result.valid).toBe(true);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('INI with missing closing bracket returns error', () => {
|
|
170
|
+
const result = validateContent('[section\nkey=value', 'ini');
|
|
171
|
+
expect(result.valid).toBe(false);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('valid ENV returns { valid: true }', () => {
|
|
175
|
+
const result = validateContent('MY_VAR=hello\nOTHER=world', 'env');
|
|
176
|
+
expect(result.valid).toBe(true);
|
|
177
|
+
expect(result.meta.variableCount).toBe(2);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('ENV with invalid variable name returns error', () => {
|
|
181
|
+
const result = validateContent('123BAD=value', 'env');
|
|
182
|
+
expect(result.valid).toBe(false);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('ENV detects duplicate variables as warning', () => {
|
|
186
|
+
const result = validateContent('VAR=a\nVAR=b', 'env');
|
|
187
|
+
expect(result.errors.some(e => e.message.includes('Duplicate'))).toBe(true);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe('validateContent - unsupported format', () => {
|
|
192
|
+
test('returns valid=false for unsupported format', () => {
|
|
193
|
+
const result = validateContent('content', 'unsupported_format_xyz');
|
|
194
|
+
expect(result.valid).toBe(false);
|
|
195
|
+
expect(result.errors[0].message).toContain('No validator available');
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe('validateStructuredFile', () => {
|
|
200
|
+
test('reads file and validates content', async () => {
|
|
201
|
+
mockFs.readFile.mockResolvedValue('{"a": 1}');
|
|
202
|
+
const result = await validateStructuredFile('/path/to/config.json');
|
|
203
|
+
expect(result.valid).toBe(true);
|
|
204
|
+
expect(result.meta.filePath).toBe('/path/to/config.json');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test('returns error when file cannot be read', async () => {
|
|
208
|
+
mockFs.readFile.mockRejectedValue(new Error('ENOENT'));
|
|
209
|
+
const result = await validateStructuredFile('/path/to/missing.json');
|
|
210
|
+
expect(result.valid).toBe(false);
|
|
211
|
+
expect(result.meta.readError).toBe(true);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test('returns error for unknown format', async () => {
|
|
215
|
+
const result = await validateStructuredFile('/path/to/file.unknown');
|
|
216
|
+
expect(result.valid).toBe(false);
|
|
217
|
+
expect(result.format).toBe('unknown');
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe('validateForToolResponse', () => {
|
|
222
|
+
test('returns simplified result for known format', () => {
|
|
223
|
+
const result = validateForToolResponse('{"a": 1}', 'config.json');
|
|
224
|
+
expect(result.valid).toBe(true);
|
|
225
|
+
expect(result.format).toBe('json');
|
|
226
|
+
expect(result.errorCount).toBe(0);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test('returns null for unknown format', () => {
|
|
230
|
+
const result = validateForToolResponse('content', 'file.txt');
|
|
231
|
+
expect(result).toBeNull();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test('includes errorCount and warningCount for invalid content', () => {
|
|
235
|
+
const result = validateForToolResponse('{bad json}', 'file.json');
|
|
236
|
+
expect(result.valid).toBe(false);
|
|
237
|
+
expect(result.errorCount).toBeGreaterThan(0);
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe('registerValidator / hasValidator', () => {
|
|
242
|
+
test('registerValidator adds a custom validator', () => {
|
|
243
|
+
registerValidator('custom_test_format', (content) => ({
|
|
244
|
+
valid: true, format: 'custom_test_format', errors: [], meta: {}
|
|
245
|
+
}));
|
|
246
|
+
expect(hasValidator('custom_test_format')).toBe(true);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test('hasValidator returns false for unregistered format', () => {
|
|
250
|
+
expect(hasValidator('totally_unknown_format_xyz')).toBe(false);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test('registered custom validator is used by validateContent', () => {
|
|
254
|
+
registerValidator('myformat', (content) => ({
|
|
255
|
+
valid: content === 'ok',
|
|
256
|
+
format: 'myformat',
|
|
257
|
+
errors: content === 'ok' ? [] : [{ message: 'not ok', severity: 'error' }],
|
|
258
|
+
meta: {}
|
|
259
|
+
}));
|
|
260
|
+
expect(validateContent('ok', 'myformat').valid).toBe(true);
|
|
261
|
+
expect(validateContent('bad', 'myformat').valid).toBe(false);
|
|
262
|
+
});
|
|
263
|
+
});
|