From f000d9b02dd0837487f21db478b0eb00905188d2 Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Fri, 13 Feb 2026 10:21:06 -0800 Subject: [PATCH] test: cover getSessionStats file-path read, hasContent field, and wrapped hooks format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round 78 — shifted from catch blocks to untested conditional branches: - getSessionStats: exercise looksLikePath → getSessionContent path (real .tmp file) - getAllSessions: verify hasContent true/false for non-empty vs empty files - validate-hooks: test wrapped { hooks: { PreToolUse: [...] } } production format --- tests/ci/validators.test.js | 24 ++++++++++++ tests/lib/session-manager.test.js | 61 +++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/tests/ci/validators.test.js b/tests/ci/validators.test.js index fd35d5b7..94937c05 100644 --- a/tests/ci/validators.test.js +++ b/tests/ci/validators.test.js @@ -2007,6 +2007,30 @@ function runTests() { cleanupTestDir(testDir); })) passed++; else failed++; + // ── Round 78: validate-hooks.js wrapped { hooks: { ... } } format ── + console.log('\nRound 78: validate-hooks.js (wrapped hooks format):'); + + if (test('validates wrapped format { hooks: { PreToolUse: [...] } }', () => { + const testDir = createTestDir(); + const hooksFile = path.join(testDir, 'hooks.json'); + // The production hooks.json uses this wrapped format — { hooks: { ... } } + // data.hooks is the object with event types, not data itself + fs.writeFileSync(hooksFile, JSON.stringify({ + "$schema": "https://json.schemastore.org/claude-code-settings.json", + hooks: { + PreToolUse: [{ matcher: 'Write', hooks: [{ type: 'command', command: 'echo ok' }] }], + PostToolUse: [{ matcher: 'Read', hooks: [{ type: 'command', command: 'echo done' }] }] + } + })); + + const result = runValidatorWithDir('validate-hooks', 'HOOKS_FILE', hooksFile); + assert.strictEqual(result.code, 0, + `Should pass wrapped hooks format, got exit ${result.code}. stderr: ${result.stderr}`); + assert.ok(result.stdout.includes('Validated 2'), + `Should validate 2 matchers, got: ${result.stdout}`); + cleanupTestDir(testDir); + })) passed++; else failed++; + // Summary console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`); process.exit(failed > 0 ? 1 : 0); diff --git a/tests/lib/session-manager.test.js b/tests/lib/session-manager.test.js index 102d252b..6b2c437d 100644 --- a/tests/lib/session-manager.test.js +++ b/tests/lib/session-manager.test.js @@ -1207,6 +1207,67 @@ src/main.ts } })) passed++; else failed++; + // ── Round 78: getSessionStats reads real file when given existing .tmp path ── + console.log('\nRound 78: getSessionStats (actual file path → reads from disk):'); + + if (test('getSessionStats reads from disk when given path to existing .tmp file', () => { + const dir = createTempSessionDir(); + try { + const sessionPath = path.join(dir, '2026-03-01-test1234-session.tmp'); + const content = '# Real File Stats Test\n\n**Date:** 2026-03-01\n**Started:** 09:00\n\n### Completed\n- [x] First task\n- [x] Second task\n\n### In Progress\n- [ ] Third task\n\n### Notes for Next Session\nDon\'t forget the edge cases\n'; + fs.writeFileSync(sessionPath, content); + + // Pass the FILE PATH (not content) — this exercises looksLikePath branch + const stats = sessionManager.getSessionStats(sessionPath); + assert.strictEqual(stats.completedItems, 2, 'Should find 2 completed items from file'); + assert.strictEqual(stats.inProgressItems, 1, 'Should find 1 in-progress item from file'); + assert.strictEqual(stats.totalItems, 3, 'Should find 3 total items from file'); + assert.strictEqual(stats.hasNotes, true, 'Should detect notes section from file'); + assert.ok(stats.lineCount > 5, `Should have multiple lines from file, got ${stats.lineCount}`); + } finally { + cleanup(dir); + } + })) passed++; else failed++; + + // ── Round 78: getAllSessions hasContent field ── + console.log('\nRound 78: getAllSessions (hasContent field):'); + + if (test('getAllSessions hasContent is true for non-empty and false for empty files', () => { + const isoHome = path.join(os.tmpdir(), `ecc-hascontent-${Date.now()}`); + const isoSessions = path.join(isoHome, '.claude', 'sessions'); + fs.mkdirSync(isoSessions, { recursive: true }); + const savedHome = process.env.HOME; + const savedProfile = process.env.USERPROFILE; + try { + // Create one non-empty session and one empty session + fs.writeFileSync(path.join(isoSessions, '2026-04-01-nonempty-session.tmp'), '# Has content'); + fs.writeFileSync(path.join(isoSessions, '2026-04-02-emptyfile-session.tmp'), ''); + + process.env.HOME = isoHome; + process.env.USERPROFILE = isoHome; + delete require.cache[require.resolve('../../scripts/lib/session-manager')]; + delete require.cache[require.resolve('../../scripts/lib/utils')]; + const freshSM = require('../../scripts/lib/session-manager'); + + const result = freshSM.getAllSessions({ limit: 100 }); + assert.strictEqual(result.total, 2, 'Should find both sessions'); + + const nonEmpty = result.sessions.find(s => s.shortId === 'nonempty'); + const empty = result.sessions.find(s => s.shortId === 'emptyfile'); + + assert.ok(nonEmpty, 'Should find the non-empty session'); + assert.ok(empty, 'Should find the empty session'); + assert.strictEqual(nonEmpty.hasContent, true, 'Non-empty file should have hasContent: true'); + assert.strictEqual(empty.hasContent, false, 'Empty file should have hasContent: false'); + } finally { + process.env.HOME = savedHome; + process.env.USERPROFILE = savedProfile; + delete require.cache[require.resolve('../../scripts/lib/session-manager')]; + delete require.cache[require.resolve('../../scripts/lib/utils')]; + fs.rmSync(isoHome, { recursive: true, force: true }); + } + })) passed++; else failed++; + // ── Round 75: deleteSession catch — unlinkSync throws on read-only dir ── console.log('\nRound 75: deleteSession (unlink failure in read-only dir):');