// coaching-hook.test.mjs // Tester coaching-hook (SC4): graceful-exit-grener + gyldig-syklus-emisjon + // deterministisk fase via OKR_NOW klokke-seam. Spawner hooken som subprosess // med kontrollert cwd + env. Zero npm deps. Coaching leser kun cwd, saa én // temp-dir per test holder. Moenster: tests/inject-okr-context.test.mjs. import { test } from 'node:test'; import assert from 'node:assert/strict'; import { execFileSync } from 'node:child_process'; import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; const HOOK = join( dirname(fileURLToPath(import.meta.url)), '..', 'hooks', 'scripts', 'coaching-hook.mjs', ); function writeConfig(workDir, body) { const p = join(workDir, '.claude'); mkdirSync(p, { recursive: true }); writeFileSync(join(p, 'okr.local.md'), body); } function runHook(cwd, okrNow) { // execFileSync returnerer stdout; hooken avslutter alltid med exit 0. const env = { ...process.env }; if (okrNow) env.OKR_NOW = okrNow; else delete env.OKR_NOW; return execFileSync('node', [HOOK], { cwd, env, encoding: 'utf8' }); } function withWork(fn) { const work = mkdtempSync(join(tmpdir(), 'okrcoach-')); try { fn(work); } finally { rmSync(work, { recursive: true, force: true }); } } test('ingen config: exit 0, tom stdout (graceful)', () => { withWork((work) => { const out = runHook(work); assert.equal(out.trim(), '', 'ingen config -> tom stdout, ingen blokkering'); }); }); test('config uten id: exit 0, tom stdout', () => { withWork((work) => { writeConfig(work, '---\nnavn: "Org"\n---\n'); const out = runHook(work); assert.equal(out.trim(), '', 'manglende id -> tom stdout'); }); }); test('ugyldig id: exit 0, tom stdout', () => { withWork((work) => { writeConfig(work, '---\nid: "ugyldig"\n---\n'); const out = runHook(work); assert.equal(out.trim(), '', 'id som ikke matcher T/Q-moenster -> tom stdout'); }); }); test('gyldig syklus: emitterer systemMessage', () => { withWork((work) => { writeConfig(work, '---\nid: "T2-2026"\n---\n'); const out = runHook(work); assert.match(out, /OKR coaching/, 'gyldig syklus skal emittere coaching-melding'); }); }); test('OKR_NOW tidlig fase: early-coaching', () => { withWork((work) => { writeConfig(work, '---\nid: "T2-2026"\n---\n'); const out = runHook(work, '2026-05-05'); assert.match(out, /Tidlig i syklusen/, 'uke 1 av T2 -> tidlig fase'); }); }); test('OKR_NOW midtveis fase: mid-coaching', () => { withWork((work) => { writeConfig(work, '---\nid: "T2-2026"\n---\n'); const out = runHook(work, '2026-06-20'); assert.match(out, /Midtveis i syklusen/, 'uke ~8 av T2 -> midtveis fase'); }); }); test('OKR_NOW sen fase: late-coaching', () => { withWork((work) => { writeConfig(work, '---\nid: "T2-2026"\n---\n'); const out = runHook(work, '2026-08-25'); assert.match(out, /sluttspurt/, 'uke ~16 av T2 -> sen fase'); }); });