From 162222a46c411ccbbbb3a4d986c476b6b34595ac Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Fri, 13 Feb 2026 07:10:54 -0800 Subject: [PATCH] test: add 3 tests for session-manager noIdMatch, session-end fallbacks (Round 66) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - session-manager.js: getSessionById with date-only string exercises the noIdMatch path for old-format sessions (2026-02-10 → 2026-02-10-session.tmp) - session-end.js: extract user messages from role-only JSONL format ({"role":"user",...} without type field) exercises line 48 fallback - session-end.js: nonexistent transcript_path triggers "Transcript not found" log path (lines 153-155), creates session with blank template Total: 803 tests, all passing --- tests/hooks/hooks.test.js | 63 +++++++++++++++++++++++++++++++ tests/lib/session-manager.test.js | 16 ++++++++ 2 files changed, 79 insertions(+) diff --git a/tests/hooks/hooks.test.js b/tests/hooks/hooks.test.js index ebfa41fd..3768b287 100644 --- a/tests/hooks/hooks.test.js +++ b/tests/hooks/hooks.test.js @@ -3012,6 +3012,69 @@ async function runTests() { 'Should pass through data unchanged when tool_input is absent'); })) passed++; else failed++; + // ── Round 66: session-end.js entry.role === 'user' fallback and nonexistent transcript ── + console.log('\nRound 66: session-end.js (entry.role user fallback):'); + + if (await asyncTest('extracts user messages from role-only format (no type field)', async () => { + const isoHome = path.join(os.tmpdir(), `ecc-role-only-${Date.now()}`); + const sessionsDir = path.join(isoHome, '.claude', 'sessions'); + fs.mkdirSync(sessionsDir, { recursive: true }); + + const testDir = createTestDir(); + const transcriptPath = path.join(testDir, 'transcript.jsonl'); + // Use entries with ONLY role field (no type:"user") to exercise the fallback + const lines = [ + '{"role":"user","content":"Deploy the production build"}', + '{"role":"assistant","content":"I will deploy now"}', + '{"role":"user","content":"Check the logs after deploy"}', + ]; + fs.writeFileSync(transcriptPath, lines.join('\n')); + const stdinJson = JSON.stringify({ transcript_path: transcriptPath }); + + try { + const result = await runScript(path.join(scriptsDir, 'session-end.js'), stdinJson, { + HOME: isoHome, USERPROFILE: isoHome + }); + assert.strictEqual(result.code, 0); + + const files = fs.readdirSync(sessionsDir).filter(f => f.endsWith('-session.tmp')); + assert.ok(files.length > 0, 'Should create session file'); + const content = fs.readFileSync(path.join(sessionsDir, files[0]), 'utf8'); + // The role-only user messages should be extracted + assert.ok(content.includes('Deploy the production build') || content.includes('deploy'), + `Session file should include role-only user messages. Got: ${content.substring(0, 300)}`); + } finally { + fs.rmSync(isoHome, { recursive: true, force: true }); + cleanupTestDir(testDir); + } + })) passed++; else failed++; + + console.log('\nRound 66: session-end.js (nonexistent transcript path):'); + + if (await asyncTest('logs "Transcript not found" for nonexistent transcript_path', async () => { + const isoHome = path.join(os.tmpdir(), `ecc-notfound-${Date.now()}`); + const sessionsDir = path.join(isoHome, '.claude', 'sessions'); + fs.mkdirSync(sessionsDir, { recursive: true }); + + const stdinJson = JSON.stringify({ transcript_path: '/tmp/nonexistent-transcript-99999.jsonl' }); + + try { + const result = await runScript(path.join(scriptsDir, 'session-end.js'), stdinJson, { + HOME: isoHome, USERPROFILE: isoHome + }); + assert.strictEqual(result.code, 0, 'Should exit 0 for missing transcript'); + assert.ok( + result.stderr.includes('Transcript not found') || result.stderr.includes('not found'), + `Should log transcript not found. Got stderr: ${result.stderr.substring(0, 300)}` + ); + // Should still create a session file (with blank template, since summary is null) + const files = fs.readdirSync(sessionsDir).filter(f => f.endsWith('-session.tmp')); + assert.ok(files.length > 0, 'Should still create session file even without transcript'); + } finally { + fs.rmSync(isoHome, { recursive: true, force: true }); + } + })) passed++; else failed++; + // Summary console.log('\n=== Test Results ==='); console.log(`Passed: ${passed}`); diff --git a/tests/lib/session-manager.test.js b/tests/lib/session-manager.test.js index 81b8e26d..3e651acf 100644 --- a/tests/lib/session-manager.test.js +++ b/tests/lib/session-manager.test.js @@ -978,6 +978,22 @@ src/main.ts assert.ok(result.includes('sessions'), 'Path should include sessions directory'); })) passed++; else failed++; + // ── Round 66: getSessionById noIdMatch path (date-only string for old format) ── + console.log('\nRound 66: getSessionById (noIdMatch — date-only match for old format):'); + + if (test('getSessionById finds old-format session by date-only string (noIdMatch)', () => { + // File is 2026-02-10-session.tmp (old format, shortId = 'no-id') + // Calling with '2026-02-10' → filenameMatch fails (filename !== '2026-02-10' and !== '2026-02-10.tmp') + // shortIdMatch fails (shortId === 'no-id', not !== 'no-id') + // noIdMatch succeeds: shortId === 'no-id' && filename === '2026-02-10-session.tmp' + const result = sessionManager.getSessionById('2026-02-10'); + assert.ok(result, 'Should find old-format session by date-only string'); + assert.strictEqual(result.shortId, 'no-id', 'Should have no-id shortId'); + assert.ok(result.filename.includes('2026-02-10-session.tmp'), 'Should match old-format file'); + assert.ok(result.sessionPath, 'Should have sessionPath'); + assert.ok(result.date === '2026-02-10', 'Should have correct date'); + })) passed++; else failed++; + // Cleanup — restore both HOME and USERPROFILE (Windows) process.env.HOME = origHome; if (origUserProfile !== undefined) {