okr/tests/org-profile-write.test.mjs
Kjell Tore Guttormsen 482effbad1 fix(okr): B2 ingestion-kode-hygiene + bump 1.7.1 (patch-lane)
8 hygiene-fikser fra review 2026-07-16 par.4 kode-lista, TDD roed-groenn
(suite 138 -> 149):
- innboks-split: slugify translittererer ae/oe (datatap-fiks)
- innboks-frontmatter: beskrivende feil ved manglende sourceMtime
- okf-index: --okf-version bumper eksisterende rot, flagg-tolerant CLI
  (exit 2 ved manglende verdi), sanitizeEntry strip C1/zero-width/bidi/
  Unicode-tag
- write-org-profile: circuit-breaker MERGER i stedet for aa overskrive
  full config (M4); test beviser at eksisterende config overlever
- compose-org-profile: intern ----linje trunkerer ikke blokken
- coaching-hook: at-risk teller status-markerte tabellrader (M1/m1)
- inject-okr-context: topic-guard treffer boeyningsformer (maalene)
- frontmatter: BOM/CRLF-toleranse (falsk mangler-type-fiks)

Versjonsflater bumpet til 1.7.1 (package/plugin/lock/CLAUDE/README/
SKILL x2/package-shape-test) + CHANGELOG 1.7.1-seksjon (B1+B2).
Release-tag + katalog-ref venter paa [G-B] operatoer-go.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 04:02:10 +02:00

110 lines
4.9 KiB
JavaScript

// org-profile-write.test.mjs
// Tester atomisk org-profil-skrivehelper (SC1): hjem-skriv, circuit-breaker
// fallback til prosjektlokal, og round-trip mot inject-okr-context.
// Spawner helperen som subprosess med kontrollert HOME + cwd + stdin.
// Zero npm deps. Plassert i tests/ (samme moenster som 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, readFileSync, existsSync, realpathSync, rmSync,
} from 'node:fs';
import { tmpdir } from 'node:os';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
const HELPER = join(ROOT, 'scripts', 'write-org-profile.mjs');
const HOOK = join(ROOT, 'hooks', 'scripts', 'inject-okr-context.mjs');
function runHelper(cwd, home, input) {
// execFileSync returnerer stdout; helperen avslutter alltid med exit 0.
return execFileSync('node', [HELPER], {
cwd,
env: { ...process.env, HOME: home },
input,
encoding: 'utf8',
});
}
function runHook(cwd, home) {
return execFileSync('node', [HOOK], {
cwd,
env: { ...process.env, HOME: home },
encoding: 'utf8',
});
}
function withDirs(fn) {
const home = mkdtempSync(join(tmpdir(), 'okrhome-'));
const work = mkdtempSync(join(tmpdir(), 'okrwork-'));
try {
fn(home, work);
} finally {
rmSync(home, { recursive: true, force: true });
rmSync(work, { recursive: true, force: true });
}
}
test('hjem-skriv: helper skriver org-profil til ~/.claude/okr/org/profil.md', () => {
withDirs((home, work) => {
const profil = '---\nnavn: "HjemskrivOrg"\n---\n';
const out = runHelper(work, home, profil);
const target = join(home, '.claude', 'okr', 'org', 'profil.md');
assert.ok(existsSync(target), 'hjem-profil skal finnes etter skriv');
assert.equal(readFileSync(target, 'utf8'), profil, 'innhold skal matche stdin');
// realpathSync normaliserer macOS /var -> /private/var-symlink paa begge sider.
assert.equal(realpathSync(out.trim()), realpathSync(target), 'stdout skal rapportere faktisk brukt sti (hjem)');
});
});
test('circuit-breaker: uskrivbart hjem -> fallback til prosjektlokal uten error', () => {
withDirs((home, work) => {
// Gjoer hjem-skriv umulig: ~/.claude er en FIL, ikke katalog -> mkdirSync
// (recursive) under den feiler med ENOTDIR (rot-uavhengig, deterministisk).
writeFileSync(join(home, '.claude'), 'not a directory\n');
const profil = '---\nnavn: "FallbackOrg"\n---\n';
// execFileSync kaster hvis exit != 0; at dette IKKE kaster beviser exit 0.
const out = runHelper(work, home, profil);
const fallback = join(work, '.claude', 'okr.local.md');
assert.ok(existsSync(fallback), 'fallback-profil skal finnes i prosjektlokal sti');
assert.equal(readFileSync(fallback, 'utf8'), profil, 'fallback-innhold skal matche stdin');
assert.equal(realpathSync(out.trim()), realpathSync(fallback), 'stdout skal rapportere fallback-sti');
});
});
test('circuit-breaker M4: eksisterende okr.local.md OVERLEVER fallback (merge, ikke overskriv)', () => {
withDirs((home, work) => {
writeFileSync(join(home, '.claude'), 'not a directory\n');
// Pre-eksisterende FULL config (syklus + onboarding + body) i prosjektlokal fil
// -- foer B2 ble denne overskrevet i sin helhet av profil-fallbacken (M4).
mkdirSync(join(work, '.claude'), { recursive: true });
writeFileSync(
join(work, '.claude', 'okr.local.md'),
'---\nnavn: "GammelOrg"\nid: "T2-2026"\nonboarding_status: fullfort\n---\nNotater under frontmatter.\n',
);
const profil = '---\nnavn: "NyOrg"\n---\n';
const out = runHelper(work, home, profil);
const fallback = join(work, '.claude', 'okr.local.md');
assert.equal(realpathSync(out.trim()), realpathSync(fallback), 'stdout rapporterer fallback-sti');
const merged = readFileSync(fallback, 'utf8');
assert.match(merged, /id: "T2-2026"/, 'syklus-id overlever fallback');
assert.match(merged, /onboarding_status: fullfort/, 'onboarding-state overlever fallback');
assert.match(merged, /Notater under frontmatter\./, 'body under frontmatter overlever');
// Round-trip: hooken resolver NY org (first-match) OG GAMMEL syklus fra samme fil.
const injected = runHook(work, home);
assert.match(injected, /NyOrg/, 'ny profil er effektiv (first-match foran gammel blokk)');
assert.match(injected, /T2-2026/, 'gammel syklus-config resolver fortsatt');
});
});
test('round-trip: hjem-skrevet org reflekteres av inject-okr-context', () => {
withDirs((home, work) => {
const profil = '---\nnavn: "RoundTripOrg"\n---\n';
runHelper(work, home, profil);
// Tom prosjektkatalog (work) -> hooken faller til hjem-profil (mest-spesifikk-vinner).
const injected = runHook(work, home);
assert.match(injected, /RoundTripOrg/, 'inject skal lese hjem-profilen helperen skrev');
});
});