mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-30 16:45:48 +08:00
test: cover session activity tracker edge paths
This commit is contained in:
parent
f92dc544c4
commit
33edfd3bb3
@ -16,6 +16,13 @@ const script = path.join(
|
|||||||
'hooks',
|
'hooks',
|
||||||
'session-activity-tracker.js'
|
'session-activity-tracker.js'
|
||||||
);
|
);
|
||||||
|
const {
|
||||||
|
buildActivityRow,
|
||||||
|
extractFileEvents,
|
||||||
|
extractFilePaths,
|
||||||
|
summarizeOutput,
|
||||||
|
run,
|
||||||
|
} = require(script);
|
||||||
|
|
||||||
function test(name, fn) {
|
function test(name, fn) {
|
||||||
try {
|
try {
|
||||||
@ -52,6 +59,15 @@ function runScript(input, envOverrides = {}, options = {}) {
|
|||||||
return { code: result.status || 0, stdout: result.stdout || '', stderr: result.stderr || '' };
|
return { code: result.status || 0, stdout: result.stdout || '', stderr: result.stderr || '' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readMetricRows(homeDir) {
|
||||||
|
const metricsFile = path.join(homeDir, '.claude', 'metrics', 'tool-usage.jsonl');
|
||||||
|
return fs.readFileSync(metricsFile, 'utf8')
|
||||||
|
.trim()
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.filter(Boolean)
|
||||||
|
.map(line => JSON.parse(line));
|
||||||
|
}
|
||||||
|
|
||||||
function runTests() {
|
function runTests() {
|
||||||
console.log('\n=== Testing session-activity-tracker.js ===\n');
|
console.log('\n=== Testing session-activity-tracker.js ===\n');
|
||||||
|
|
||||||
@ -405,6 +421,246 @@ function runTests() {
|
|||||||
fs.rmSync(tmpHome, { recursive: true, force: true });
|
fs.rmSync(tmpHome, { recursive: true, force: true });
|
||||||
}) ? passed++ : failed++);
|
}) ? passed++ : failed++);
|
||||||
|
|
||||||
|
(test('skips non-PostToolUse events and rows without required identifiers', () => {
|
||||||
|
assert.strictEqual(buildActivityRow(
|
||||||
|
{ tool_name: 'Read', tool_input: { file_path: 'README.md' } },
|
||||||
|
{ CLAUDE_HOOK_EVENT_NAME: 'PreToolUse', ECC_SESSION_ID: 'sess' }
|
||||||
|
), null);
|
||||||
|
assert.strictEqual(buildActivityRow(
|
||||||
|
{ tool_name: 'Read', tool_input: { file_path: 'README.md' } },
|
||||||
|
{ CLAUDE_HOOK_EVENT_NAME: 'PostToolUse' }
|
||||||
|
), null);
|
||||||
|
assert.strictEqual(buildActivityRow(
|
||||||
|
{ tool_input: { file_path: 'README.md' } },
|
||||||
|
{ CLAUDE_HOOK_EVENT_NAME: 'PostToolUse', ECC_SESSION_ID: 'sess' }
|
||||||
|
), null);
|
||||||
|
}) ? passed++ : failed++);
|
||||||
|
|
||||||
|
(test('sanitizes nested params, long summaries, and output variants', () => {
|
||||||
|
const longValue = `start ${'x'.repeat(260)} ghp_${'A'.repeat(20)}`;
|
||||||
|
const row = buildActivityRow(
|
||||||
|
{
|
||||||
|
tool_name: 'Lookup',
|
||||||
|
tool_input: {
|
||||||
|
query: longValue,
|
||||||
|
secret: `gho_${'B'.repeat(20)}`,
|
||||||
|
count: 3,
|
||||||
|
enabled: false,
|
||||||
|
omitted: null,
|
||||||
|
nested: { a: { b: { c: { d: 'too deep' } } } },
|
||||||
|
list: [1, true, null, 4],
|
||||||
|
},
|
||||||
|
tool_output: `line one\nline two ${'y'.repeat(260)}`,
|
||||||
|
},
|
||||||
|
{ CLAUDE_HOOK_EVENT_NAME: 'PostToolUse', CLAUDE_SESSION_ID: 'claude-fallback' }
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(row.session_id, 'claude-fallback');
|
||||||
|
assert.strictEqual(row.file_paths.length, 0);
|
||||||
|
assert.ok(row.input_summary.endsWith('...'), 'Expected long shallow summary to be truncated');
|
||||||
|
assert.ok(!row.input_summary.includes('ghp_'), 'Expected GitHub token redaction in input summary');
|
||||||
|
assert.ok(row.output_summary.endsWith('...'), 'Expected long output summary to be truncated');
|
||||||
|
assert.ok(!row.output_summary.includes('\n'), 'Expected output summary to normalize whitespace');
|
||||||
|
|
||||||
|
const params = JSON.parse(row.input_params_json);
|
||||||
|
assert.strictEqual(params.count, 3);
|
||||||
|
assert.strictEqual(params.enabled, false);
|
||||||
|
assert.strictEqual(params.omitted, null);
|
||||||
|
assert.strictEqual(params.secret, '<REDACTED>');
|
||||||
|
assert.strictEqual(params.nested.a.b.c, '[Truncated]');
|
||||||
|
assert.deepStrictEqual(params.list.slice(0, 3), [1, true, null]);
|
||||||
|
assert.strictEqual(params.list[3], 4);
|
||||||
|
assert.ok(params.query.endsWith('...'), 'Expected long param value to be truncated');
|
||||||
|
|
||||||
|
assert.strictEqual(summarizeOutput(null), '');
|
||||||
|
assert.strictEqual(summarizeOutput(undefined), '');
|
||||||
|
assert.strictEqual(summarizeOutput('hello\nworld'), 'hello world');
|
||||||
|
assert.strictEqual(summarizeOutput({ ok: true }), '{"ok":true}');
|
||||||
|
}) ? passed++ : failed++);
|
||||||
|
|
||||||
|
(test('extracts file paths from nested arrays while filtering duplicates and remote URIs', () => {
|
||||||
|
const paths = extractFilePaths({
|
||||||
|
file_paths: [
|
||||||
|
'src/a.js',
|
||||||
|
'src/a.js',
|
||||||
|
'https://example.com/file.js',
|
||||||
|
'',
|
||||||
|
{ file_path: 'src/b.js' },
|
||||||
|
],
|
||||||
|
nested: {
|
||||||
|
source_path: 'app://connector/item',
|
||||||
|
deep: [
|
||||||
|
{ new_file_path: 'src/c.js' },
|
||||||
|
{ old_file_path: 'plugin://plugin/item' },
|
||||||
|
42,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
ignored: 'not-a-path-field',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepStrictEqual(paths, ['src/a.js', 'src/b.js', 'src/c.js']);
|
||||||
|
assert.deepStrictEqual(extractFilePaths(null), []);
|
||||||
|
assert.deepStrictEqual(extractFilePaths('src/not-collected.js'), []);
|
||||||
|
}) ? passed++ : failed++);
|
||||||
|
|
||||||
|
(test('extracts file event previews for create delete and one-sided edits', () => {
|
||||||
|
const events = extractFileEvents('Write', {
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
file_path: 'src/new.ts',
|
||||||
|
content: 'first line\nsecond line',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file_path: 'src/new.ts',
|
||||||
|
content: 'first line\nsecond line',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file_path: 'https://example.com/remote.ts',
|
||||||
|
content: 'ignored',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
assert.deepStrictEqual(events, [
|
||||||
|
{
|
||||||
|
path: 'src/new.ts',
|
||||||
|
action: 'create',
|
||||||
|
diff_preview: '+ first line second line',
|
||||||
|
patch_preview: '+ first line second line',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.deepStrictEqual(extractFileEvents('Remove', {
|
||||||
|
file_path: 'src/old.ts',
|
||||||
|
content: 'legacy line',
|
||||||
|
}), [
|
||||||
|
{
|
||||||
|
path: 'src/old.ts',
|
||||||
|
action: 'delete',
|
||||||
|
patch_preview: '- legacy line',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.deepStrictEqual(extractFileEvents('Edit', {
|
||||||
|
edits: [
|
||||||
|
{ file_path: 'src/before.ts', old_string: 'legacy', new_string: '' },
|
||||||
|
{ file_path: 'src/after.ts', old_string: '', new_string: 'modern' },
|
||||||
|
{ file_path: 'src/no-preview.ts', old_string: '', new_string: '' },
|
||||||
|
],
|
||||||
|
}), [
|
||||||
|
{
|
||||||
|
path: 'src/before.ts',
|
||||||
|
action: 'modify',
|
||||||
|
diff_preview: 'legacy ->',
|
||||||
|
patch_preview: '@@\n- legacy',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'src/after.ts',
|
||||||
|
action: 'modify',
|
||||||
|
diff_preview: '-> modern',
|
||||||
|
patch_preview: '@@\n+ modern',
|
||||||
|
},
|
||||||
|
{ path: 'src/no-preview.ts', action: 'modify' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.deepStrictEqual(extractFileEvents('Rename', {
|
||||||
|
old_file_path: 'src/old-name.ts',
|
||||||
|
new_file_path: 'src/new-name.ts',
|
||||||
|
}), [
|
||||||
|
{ path: 'src/old-name.ts', action: 'move' },
|
||||||
|
{ path: 'src/new-name.ts', action: 'move' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.deepStrictEqual(extractFileEvents('Read', null), []);
|
||||||
|
assert.deepStrictEqual(extractFileEvents('Touch', { file_path: 'src/touched.ts' }), [
|
||||||
|
{ path: 'src/touched.ts', action: 'touch' },
|
||||||
|
]);
|
||||||
|
}) ? passed++ : failed++);
|
||||||
|
|
||||||
|
(test('records creation previews unchanged when running outside a git repository', () => {
|
||||||
|
const tmpHome = makeTempDir();
|
||||||
|
const tmpCwd = makeTempDir();
|
||||||
|
|
||||||
|
const input = {
|
||||||
|
tool_name: 'Write',
|
||||||
|
tool_input: {
|
||||||
|
file_path: 'created.txt',
|
||||||
|
content: 'alpha\nbeta',
|
||||||
|
},
|
||||||
|
tool_output: 17,
|
||||||
|
};
|
||||||
|
const result = runScript(input, {
|
||||||
|
...withTempHome(tmpHome),
|
||||||
|
CLAUDE_HOOK_EVENT_NAME: 'PostToolUse',
|
||||||
|
ECC_SESSION_ID: 'ecc-session-non-git-create',
|
||||||
|
}, {
|
||||||
|
cwd: tmpCwd,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.strictEqual(result.code, 0);
|
||||||
|
const [row] = readMetricRows(tmpHome);
|
||||||
|
assert.strictEqual(row.output_summary, '17');
|
||||||
|
assert.deepStrictEqual(row.file_events, [
|
||||||
|
{
|
||||||
|
path: 'created.txt',
|
||||||
|
action: 'create',
|
||||||
|
diff_preview: '+ alpha beta',
|
||||||
|
patch_preview: '+ alpha beta',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
fs.rmSync(tmpHome, { recursive: true, force: true });
|
||||||
|
fs.rmSync(tmpCwd, { recursive: true, force: true });
|
||||||
|
}) ? passed++ : failed++);
|
||||||
|
|
||||||
|
(test('preserves absolute paths outside the repo without git enrichment', () => {
|
||||||
|
const tmpHome = makeTempDir();
|
||||||
|
const outsideDir = makeTempDir();
|
||||||
|
const outsideFile = path.join(outsideDir, 'outside.txt');
|
||||||
|
fs.writeFileSync(outsideFile, 'outside', 'utf8');
|
||||||
|
|
||||||
|
const input = {
|
||||||
|
tool_name: 'Read',
|
||||||
|
tool_input: {
|
||||||
|
file_path: outsideFile,
|
||||||
|
},
|
||||||
|
tool_output: 'read outside',
|
||||||
|
};
|
||||||
|
const result = runScript(input, {
|
||||||
|
...withTempHome(tmpHome),
|
||||||
|
CLAUDE_HOOK_EVENT_NAME: 'PostToolUse',
|
||||||
|
ECC_SESSION_ID: 'ecc-session-absolute-outside',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.strictEqual(result.code, 0);
|
||||||
|
const [row] = readMetricRows(tmpHome);
|
||||||
|
assert.deepStrictEqual(row.file_paths, [outsideFile]);
|
||||||
|
assert.deepStrictEqual(row.file_events, [
|
||||||
|
{ path: outsideFile, action: 'read' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
fs.rmSync(tmpHome, { recursive: true, force: true });
|
||||||
|
fs.rmSync(outsideDir, { recursive: true, force: true });
|
||||||
|
}) ? passed++ : failed++);
|
||||||
|
|
||||||
|
(test('passes empty stdin through without creating metrics', () => {
|
||||||
|
const tmpHome = makeTempDir();
|
||||||
|
const result = runScript('', {
|
||||||
|
...withTempHome(tmpHome),
|
||||||
|
CLAUDE_HOOK_EVENT_NAME: 'PostToolUse',
|
||||||
|
ECC_SESSION_ID: 'sess-empty',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.strictEqual(result.code, 0);
|
||||||
|
assert.strictEqual(result.stdout, '');
|
||||||
|
assert.strictEqual(run(''), '');
|
||||||
|
assert.strictEqual(
|
||||||
|
fs.existsSync(path.join(tmpHome, '.claude', 'metrics', 'tool-usage.jsonl')),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
fs.rmSync(tmpHome, { recursive: true, force: true });
|
||||||
|
}) ? passed++ : failed++);
|
||||||
|
|
||||||
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
||||||
process.exit(failed > 0 ? 1 : 0);
|
process.exit(failed > 0 ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user