From c1b6e0bf11af4262ae244ab9ac04bd1349d0a164 Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Fri, 13 Feb 2026 01:07:23 -0800 Subject: [PATCH] test: add coverage for Claude Code JSONL format and assistant tool blocks Tests the new transcript parsing from PR #215: - entry.message.content format (string and array content) - tool_use blocks nested in assistant message content arrays - Verifies file paths and tool names extracted from both formats --- tests/hooks/hooks.test.js | 69 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/tests/hooks/hooks.test.js b/tests/hooks/hooks.test.js index 0cf800a7..f0dd118c 100644 --- a/tests/hooks/hooks.test.js +++ b/tests/hooks/hooks.test.js @@ -814,6 +814,75 @@ async function runTests() { cleanupTestDir(testDir); })) passed++; else failed++; + if (await asyncTest('parses Claude Code JSONL format (entry.message.content)', async () => { + const testDir = createTestDir(); + const transcriptPath = path.join(testDir, 'transcript.jsonl'); + + // Claude Code v2.1.41+ JSONL format: user messages nested in entry.message + const lines = [ + '{"type":"user","message":{"role":"user","content":"Fix the build error"}}', + '{"type":"user","message":{"role":"user","content":[{"type":"text","text":"Also update tests"}]}}', + ]; + fs.writeFileSync(transcriptPath, lines.join('\n')); + + const stdinJson = JSON.stringify({ transcript_path: transcriptPath }); + const result = await runScript(path.join(scriptsDir, 'session-end.js'), stdinJson, { + HOME: testDir + }); + assert.strictEqual(result.code, 0); + + const claudeDir = path.join(testDir, '.claude', 'sessions'); + if (fs.existsSync(claudeDir)) { + const files = fs.readdirSync(claudeDir).filter(f => f.endsWith('.tmp')); + if (files.length > 0) { + const content = fs.readFileSync(path.join(claudeDir, files[0]), 'utf8'); + assert.ok(content.includes('Fix the build error'), 'Should extract string content from message'); + assert.ok(content.includes('Also update tests'), 'Should extract array content from message'); + } + } + cleanupTestDir(testDir); + })) passed++; else failed++; + + if (await asyncTest('extracts tool_use from assistant message content blocks', async () => { + const testDir = createTestDir(); + const transcriptPath = path.join(testDir, 'transcript.jsonl'); + + // Claude Code JSONL: tool uses nested in assistant message content array + const lines = [ + '{"type":"user","content":"Edit the config"}', + JSON.stringify({ + type: 'assistant', + message: { + role: 'assistant', + content: [ + { type: 'text', text: 'I will edit the file.' }, + { type: 'tool_use', name: 'Edit', input: { file_path: '/src/app.ts' } }, + { type: 'tool_use', name: 'Write', input: { file_path: '/src/new.ts' } }, + ] + } + }), + ]; + fs.writeFileSync(transcriptPath, lines.join('\n')); + + const stdinJson = JSON.stringify({ transcript_path: transcriptPath }); + const result = await runScript(path.join(scriptsDir, 'session-end.js'), stdinJson, { + HOME: testDir + }); + assert.strictEqual(result.code, 0); + + const claudeDir = path.join(testDir, '.claude', 'sessions'); + if (fs.existsSync(claudeDir)) { + const files = fs.readdirSync(claudeDir).filter(f => f.endsWith('.tmp')); + if (files.length > 0) { + const content = fs.readFileSync(path.join(claudeDir, files[0]), 'utf8'); + assert.ok(content.includes('Edit'), 'Should extract Edit tool from content blocks'); + assert.ok(content.includes('/src/app.ts'), 'Should extract file path from Edit block'); + assert.ok(content.includes('/src/new.ts'), 'Should extract file path from Write block'); + } + } + cleanupTestDir(testDir); + })) passed++; else failed++; + // hooks.json validation console.log('\nhooks.json Validation:');