Phase 0: - Synced latest live-status.js from workspace (9928 bytes) - Fixed 43 lint issues: empty catch blocks, console statements - Added pino dependency - Created src/tool-labels.json with all known tool mappings - make check passes Phase 1 (Core Components): - src/config.js: env-var config with validation, throws on missing required vars - src/logger.js: pino singleton with child loggers, level validation - src/circuit-breaker.js: CLOSED/OPEN/HALF_OPEN state machine with callbacks - src/tool-labels.js: exact/prefix/regex tool->label resolver with external override - src/status-box.js: Mattermost post manager (keepAlive, throttle, retry, circuit breaker) - src/status-formatter.js: pure SessionState->text formatter (nested, compact) - src/health.js: HTTP health endpoint + metrics - src/status-watcher.js: JSONL file watcher (inotify, compaction detection, idle detection) Tests: - test/unit/config.test.js: 7 tests - test/unit/circuit-breaker.test.js: 12 tests - test/unit/logger.test.js: 5 tests - test/unit/status-formatter.test.js: 20 tests - test/unit/tool-labels.test.js: 15 tests All 59 unit tests pass. make check clean.
186 lines
5.3 KiB
JavaScript
186 lines
5.3 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Unit tests for tool-labels.js
|
|
*/
|
|
|
|
const { describe, it, beforeEach, afterEach } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
|
|
const { loadLabels, resolve, resetLabels } = require('../../src/tool-labels');
|
|
|
|
describe('tool-labels.js', () => {
|
|
beforeEach(() => {
|
|
resetLabels();
|
|
});
|
|
|
|
afterEach(() => {
|
|
resetLabels();
|
|
});
|
|
|
|
describe('exact match', () => {
|
|
it('resolves known tools by exact name', () => {
|
|
loadLabels(null);
|
|
assert.equal(resolve('exec'), 'Running command...');
|
|
assert.equal(resolve('Read'), 'Reading file...');
|
|
assert.equal(resolve('Write'), 'Writing file...');
|
|
assert.equal(resolve('Edit'), 'Editing file...');
|
|
assert.equal(resolve('web_search'), 'Searching the web...');
|
|
assert.equal(resolve('web_fetch'), 'Fetching URL...');
|
|
assert.equal(resolve('message'), 'Sending message...');
|
|
assert.equal(resolve('tts'), 'Generating speech...');
|
|
assert.equal(resolve('subagents'), 'Managing sub-agents...');
|
|
assert.equal(resolve('image'), 'Analyzing image...');
|
|
assert.equal(resolve('process'), 'Managing process...');
|
|
assert.equal(resolve('browser'), 'Controlling browser...');
|
|
});
|
|
});
|
|
|
|
describe('prefix match', () => {
|
|
it('resolves camofox_ tools via prefix', () => {
|
|
loadLabels(null);
|
|
assert.equal(resolve('camofox_create_tab'), 'Opening browser tab...'); // exact takes priority
|
|
assert.equal(resolve('camofox_some_new_tool'), 'Using browser...');
|
|
});
|
|
|
|
it('resolves claude_code_ tools via prefix', () => {
|
|
loadLabels(null);
|
|
assert.equal(resolve('claude_code_start'), 'Starting Claude Code task...'); // exact takes priority
|
|
assert.equal(resolve('claude_code_something_new'), 'Running Claude Code...');
|
|
});
|
|
});
|
|
|
|
describe('default label', () => {
|
|
it('returns default for unknown tools', () => {
|
|
loadLabels(null);
|
|
assert.equal(resolve('some_unknown_tool'), 'Working...');
|
|
assert.equal(resolve(''), 'Working...');
|
|
assert.equal(resolve('xyz'), 'Working...');
|
|
});
|
|
});
|
|
|
|
describe('external override', () => {
|
|
let tmpFile;
|
|
|
|
beforeEach(() => {
|
|
tmpFile = path.join(os.tmpdir(), `tool-labels-test-${Date.now()}.json`);
|
|
});
|
|
|
|
afterEach(() => {
|
|
try {
|
|
fs.unlinkSync(tmpFile);
|
|
} catch (_e) {
|
|
/* ignore */
|
|
}
|
|
});
|
|
|
|
it('external exact overrides built-in', () => {
|
|
fs.writeFileSync(
|
|
tmpFile,
|
|
JSON.stringify({
|
|
exact: { exec: 'Custom exec label...' },
|
|
prefix: {},
|
|
}),
|
|
);
|
|
loadLabels(tmpFile);
|
|
assert.equal(resolve('exec'), 'Custom exec label...');
|
|
// Non-overridden built-in still works
|
|
assert.equal(resolve('Read'), 'Reading file...');
|
|
});
|
|
|
|
it('external prefix adds new prefix', () => {
|
|
fs.writeFileSync(
|
|
tmpFile,
|
|
JSON.stringify({
|
|
exact: {},
|
|
prefix: { my_tool_: 'My custom tool...' },
|
|
}),
|
|
);
|
|
loadLabels(tmpFile);
|
|
assert.equal(resolve('my_tool_do_something'), 'My custom tool...');
|
|
});
|
|
|
|
it('external default overrides built-in default', () => {
|
|
fs.writeFileSync(
|
|
tmpFile,
|
|
JSON.stringify({
|
|
exact: {},
|
|
prefix: {},
|
|
default: 'Custom default...',
|
|
}),
|
|
);
|
|
loadLabels(tmpFile);
|
|
assert.equal(resolve('completely_unknown'), 'Custom default...');
|
|
});
|
|
|
|
it('handles missing external file gracefully', () => {
|
|
loadLabels('/nonexistent/path/tool-labels.json');
|
|
// Should fall back to built-in
|
|
assert.equal(resolve('exec'), 'Running command...');
|
|
});
|
|
|
|
it('handles malformed external JSON gracefully', () => {
|
|
fs.writeFileSync(tmpFile, 'not valid json {{{');
|
|
loadLabels(tmpFile);
|
|
// Should fall back to built-in
|
|
assert.equal(resolve('exec'), 'Running command...');
|
|
});
|
|
});
|
|
|
|
describe('regex match', () => {
|
|
let tmpFile;
|
|
|
|
beforeEach(() => {
|
|
tmpFile = path.join(os.tmpdir(), `tool-labels-regex-${Date.now()}.json`);
|
|
});
|
|
|
|
afterEach(() => {
|
|
try {
|
|
fs.unlinkSync(tmpFile);
|
|
} catch (_e) {
|
|
/* ignore */
|
|
}
|
|
});
|
|
|
|
it('resolves via regex pattern', () => {
|
|
fs.writeFileSync(
|
|
tmpFile,
|
|
JSON.stringify({
|
|
exact: {},
|
|
prefix: {},
|
|
regex: [{ pattern: '/^my_api_/', label: 'Calling API...' }],
|
|
}),
|
|
);
|
|
loadLabels(tmpFile);
|
|
assert.equal(resolve('my_api_create'), 'Calling API...');
|
|
assert.equal(resolve('my_api_update'), 'Calling API...');
|
|
assert.equal(resolve('other_tool'), 'Working...');
|
|
});
|
|
|
|
it('handles invalid regex gracefully', () => {
|
|
fs.writeFileSync(
|
|
tmpFile,
|
|
JSON.stringify({
|
|
exact: {},
|
|
prefix: {},
|
|
regex: [{ pattern: '/[invalid(/', label: 'oops' }],
|
|
}),
|
|
);
|
|
loadLabels(tmpFile);
|
|
// Invalid regex skipped — returns default
|
|
assert.equal(resolve('anything'), 'Working...');
|
|
});
|
|
});
|
|
|
|
describe('auto-load', () => {
|
|
it('auto-loads built-in labels on first resolve call', () => {
|
|
// resetLabels was called in beforeEach — no explicit loadLabels call
|
|
const label = resolve('exec');
|
|
assert.equal(label, 'Running command...');
|
|
});
|
|
});
|
|
});
|