// session-start-load-handoff.test.mjs import { test } from 'node:test'; import { strict as assert } from 'node:assert'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { mkdtempSync, mkdirSync, writeFileSync, existsSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { runHook } from './hook-helper.mjs'; const __dirname = dirname(fileURLToPath(import.meta.url)); const HOOK = join(__dirname, '..', '..', 'hooks', 'scripts', 'session-start-load-handoff.mjs'); function makeFixture() { return mkdtempSync(join(tmpdir(), 'sessionstart-')); } test('source: startup → silent (no injection)', async () => { const dir = makeFixture(); writeFileSync(join(dir, 'NEXT-SESSION-PROMPT.local.md'), 'should not load\n'); const res = await runHook(HOOK, { source: 'startup', cwd: dir }); assert.equal(res.code, 0); assert.equal(res.stdout.trim(), '', 'startup source should not inject'); assert.ok(existsSync(join(dir, 'NEXT-SESSION-PROMPT.local.md')), 'file should not be archived'); rmSync(dir, { recursive: true, force: true }); }); test('source: clear → silent (no injection)', async () => { const dir = makeFixture(); writeFileSync(join(dir, 'NEXT-SESSION-PROMPT.local.md'), 'should not load\n'); const res = await runHook(HOOK, { source: 'clear', cwd: dir }); assert.equal(res.code, 0); assert.equal(res.stdout.trim(), ''); rmSync(dir, { recursive: true, force: true }); }); test('source: resume + handoff in cwd → injected and archived', async () => { const dir = makeFixture(); writeFileSync(join(dir, 'NEXT-SESSION-PROMPT.local.md'), '# my handoff\n\nimportant content\n'); const res = await runHook(HOOK, { source: 'resume', cwd: dir }); assert.equal(res.code, 0); // Stdout should be JSON with additionalContext containing the file const json = JSON.parse(res.stdout); assert.equal(json.hookSpecificOutput.hookEventName, 'SessionStart'); assert.match(json.hookSpecificOutput.additionalContext, /important content/); assert.match(json.hookSpecificOutput.additionalContext, / { const dir = makeFixture(); writeFileSync(join(dir, 'NEXT-SESSION-PROMPT.local.md'), '# compact handoff\n'); const res = await runHook(HOOK, { source: 'compact', cwd: dir }); assert.equal(res.code, 0); const json = JSON.parse(res.stdout); assert.match(json.hookSpecificOutput.additionalContext, /compact handoff/); rmSync(dir, { recursive: true, force: true }); }); test('source: resume + handoff 2 levels above cwd → found and injected', async () => { const root = makeFixture(); const sub = join(root, 'a', 'b'); mkdirSync(sub, { recursive: true }); writeFileSync(join(root, 'NEXT-SESSION-PROMPT.local.md'), '# parent handoff\n'); const res = await runHook(HOOK, { source: 'resume', cwd: sub }); assert.equal(res.code, 0); const json = JSON.parse(res.stdout); assert.match(json.hookSpecificOutput.additionalContext, /parent handoff/); // Archived in the original parent location assert.ok(existsSync(join(root, 'NEXT-SESSION-PROMPT.archived.local.md'))); rmSync(root, { recursive: true, force: true }); }); test('source: resume + no handoff anywhere → silent', async () => { const dir = makeFixture(); const res = await runHook(HOOK, { source: 'resume', cwd: dir }); assert.equal(res.code, 0); assert.equal(res.stdout.trim(), ''); rmSync(dir, { recursive: true, force: true }); }); test('source: resume + topic-slug variant NEXT-SESSION-foo.local.md → found', async () => { const dir = makeFixture(); writeFileSync(join(dir, 'NEXT-SESSION-feature-x.local.md'), '# topic handoff\n'); const res = await runHook(HOOK, { source: 'resume', cwd: dir }); assert.equal(res.code, 0); const json = JSON.parse(res.stdout); assert.match(json.hookSpecificOutput.additionalContext, /topic handoff/); rmSync(dir, { recursive: true, force: true }); }); test('archived files are not re-loaded on subsequent runs', async () => { const dir = makeFixture(); writeFileSync(join(dir, 'NEXT-SESSION-PROMPT.archived.local.md'), 'stale - should not load\n'); const res = await runHook(HOOK, { source: 'resume', cwd: dir }); assert.equal(res.code, 0); assert.equal(res.stdout.trim(), '', 'archived files must be ignored'); rmSync(dir, { recursive: true, force: true }); }); test('malformed JSON payload: silent exit 0', async () => { const res = await runHook(HOOK, '{not valid'); assert.equal(res.code, 0); assert.equal(res.stdout.trim(), ''); });