feat(okr): rapport-kommando og CLI for syklusgeneratorene
This commit is contained in:
parent
50933d839d
commit
7d1de8049d
6 changed files with 298 additions and 0 deletions
|
|
@ -146,3 +146,98 @@ test('(6) tertialrapport kaster ved tom eller ufullstendig syklus', () => {
|
|||
assert.throws(() => tertialrapport({ id: 'T1-2026', okrer: [] }), /ingen OKR/i);
|
||||
assert.throws(() => tertialrapport(undefined), /syklus/i);
|
||||
});
|
||||
|
||||
// --- (7) CLI-kontrakten (steg 10) ---
|
||||
//
|
||||
// Testet BEGGE VEIER i samme fil: direkte import for atferd, execFileSync for
|
||||
// exit-koden -- fordi exit-koden ER kontrakten kommandofila mapper til norsk
|
||||
// brukertekst. En atferdstest alene ville ikke fanget at 1 og 2 byttet plass.
|
||||
// Moenster: tests/innboks-ingest.test.mjs (execFileSync for exit-kode).
|
||||
|
||||
const CLI = join(ROOT, 'scripts/syklus-rapport.mjs');
|
||||
|
||||
async function kjoerCli(args, opts = {}) {
|
||||
const { execFileSync } = await import('node:child_process');
|
||||
try {
|
||||
const stdout = execFileSync(process.execPath, [CLI, ...args], {
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
env: { ...process.env, OKR_NOW: NAA, ...(opts.env ?? {}) },
|
||||
});
|
||||
return { code: 0, stdout };
|
||||
} catch (e) {
|
||||
return { code: e.status, stdout: e.stdout ?? '', stderr: e.stderr ?? '' };
|
||||
}
|
||||
}
|
||||
|
||||
// Egen kopi av syklusen per test som skriver -- CLI-en persisterer, og en test
|
||||
// skal aldri skitne til fixturen den delte med de andre testene.
|
||||
async function midlertidigSyklus(filer) {
|
||||
const { mkdtempSync, writeFileSync, mkdirSync, copyFileSync, readdirSync } = await import('node:fs');
|
||||
const { tmpdir } = await import('node:os');
|
||||
const rot = mkdtempSync(join(tmpdir(), 'okr-rapport-'));
|
||||
const dir = join(rot, 'T1-2026');
|
||||
mkdirSync(dir);
|
||||
if (filer) {
|
||||
for (const [navn, innhold] of Object.entries(filer)) writeFileSync(join(dir, navn), innhold);
|
||||
} else {
|
||||
for (const f of readdirSync(SYKLUS_DIR)) copyFileSync(join(SYKLUS_DIR, f), join(dir, f));
|
||||
}
|
||||
return { rot, dir };
|
||||
}
|
||||
|
||||
test('(7a) CLI: exit 2 ved feil aritet', async () => {
|
||||
assert.equal((await kjoerCli([])).code, 2);
|
||||
assert.equal((await kjoerCli([SYKLUS_DIR])).code, 2);
|
||||
});
|
||||
|
||||
test('(7b) CLI: exit 2 ved ukjent og ved ennaa uimplementert form', async () => {
|
||||
assert.equal((await kjoerCli([SYKLUS_DIR, 'tullball'])).code, 2);
|
||||
// arsrapport/etatsstyring kommer i steg 11-12 og skal si det, ikke feile stumt.
|
||||
for (const form of ['arsrapport', 'etatsstyring']) {
|
||||
const r = await kjoerCli([SYKLUS_DIR, form]);
|
||||
assert.equal(r.code, 2, `${form} burde gi exit 2`);
|
||||
assert.match(r.stderr, /ikke implementert/i, `${form} mangler forklarende melding`);
|
||||
}
|
||||
});
|
||||
|
||||
test('(7c) CLI: exit 2 naar syklus-katalogen ikke finnes', async () => {
|
||||
assert.equal((await kjoerCli([join(ROOT, 'finnes-ikke'), 'tertial'])).code, 2);
|
||||
});
|
||||
|
||||
test('(7d) CLI: exit 1 ved ufullstendig syklusdata', async (t) => {
|
||||
const { rmSync } = await import('node:fs');
|
||||
const { rot, dir } = await midlertidigSyklus({
|
||||
'okr-ufullstendig.md': ['---', 'type: OKR', 'title: Ufullstendig', 'kr1_navn: Mangler tall', 'kr1_baseline: 3', '---', '# Ufullstendig', ''].join('\n'),
|
||||
});
|
||||
t.after(() => rmSync(rot, { recursive: true, force: true }));
|
||||
const r = await kjoerCli([dir, 'tertial']);
|
||||
assert.equal(r.code, 1, 'domenefeil skal gi exit 1, ikke 2');
|
||||
assert.match(r.stderr, /kr1/i);
|
||||
});
|
||||
|
||||
test('(7e) CLI: exit 0 og fil paa disk ved gyldig input', async (t) => {
|
||||
const { rmSync, existsSync, readFileSync: les } = await import('node:fs');
|
||||
const { rot, dir } = await midlertidigSyklus();
|
||||
t.after(() => rmSync(rot, { recursive: true, force: true }));
|
||||
const r = await kjoerCli([dir, 'tertial']);
|
||||
assert.equal(r.code, 0, `forventet exit 0, fikk ${r.code}: ${r.stderr ?? ''}`);
|
||||
const ut = join(dir, 'rapport-tertial.md');
|
||||
assert.ok(existsSync(ut), 'rapporten ble ikke skrevet');
|
||||
const innhold = les(ut, 'utf8');
|
||||
assert.match(innhold, /^---\ntype: Status\n/, 'skrevet fil mangler OKF-frontmatter');
|
||||
assert.match(innhold, /## Committed Key Results/);
|
||||
});
|
||||
|
||||
// Idempotens: rapporten skal kunne kjoeres om igjen uten aa endre bundlen, og
|
||||
// uten aa bli lest som en OKR neste gang (filnavnet starter ikke med okr-).
|
||||
test('(7f) CLI: to kjoeringer gir byte-identisk fil', async (t) => {
|
||||
const { rmSync, readFileSync: les } = await import('node:fs');
|
||||
const { rot, dir } = await midlertidigSyklus();
|
||||
t.after(() => rmSync(rot, { recursive: true, force: true }));
|
||||
await kjoerCli([dir, 'tertial']);
|
||||
const foerste = les(join(dir, 'rapport-tertial.md'), 'utf8');
|
||||
await kjoerCli([dir, 'tertial']);
|
||||
assert.equal(les(join(dir, 'rapport-tertial.md'), 'utf8'), foerste);
|
||||
assert.equal(lesSyklus(dir).okrer.length, 2, 'rapporten ble lest som en OKR');
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue