86 lines
4.3 KiB
JavaScript
86 lines
4.3 KiB
JavaScript
// innboks-relations.test.mjs
|
|
// Step 6 (SC relasjoner): delt bundle-root-relativ lenke-resolver (okf-links) +
|
|
// deterministisk relasjons-resolusjon (innboks-relations). Beviser: >=1 emittert
|
|
// lenke matcher SC-regex /\]\(\/.*\.md\)/ (leading '/'); hver lenke resolverer til
|
|
// et emittert konsept (zero-dangling); '../'/absolutt/file://-/http-lenker avvises
|
|
// av isSafeBundleLink (allow-list). Bygger det emitterte settet gjennom Step 4+5.
|
|
// Moenster: tests/okf-retrieval.test.mjs.
|
|
|
|
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { readFileSync } from 'node:fs';
|
|
import { join, dirname } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { isSafeBundleLink, resolveBundleLink } from '../lib/okf-links.mjs';
|
|
import { resolveRelations } from '../lib/innboks-relations.mjs';
|
|
import { splitConcepts } from '../lib/innboks-split.mjs';
|
|
import { projectFrontmatter } from '../lib/innboks-frontmatter.mjs';
|
|
|
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
const FIX = join(HERE, 'fixtures', 'inbox-converted');
|
|
const MTIME = new Date('2026-03-15T08:30:00.000Z');
|
|
const SC_REGEX = /\]\(\/.*\.md\)/;
|
|
|
|
// Det emitterte konsept-settet (split + project) for begge fixture-dokumentene.
|
|
function buildConcepts() {
|
|
const out = [];
|
|
for (const slug of ['dok-a', 'dok-b']) {
|
|
const md = readFileSync(join(FIX, `${slug}.md`), 'utf8');
|
|
for (const c of splitConcepts(md, { sourceSlug: slug })) {
|
|
out.push(projectFrontmatter(c, { sourcePath: `innboks/${slug}.txt`, sourceMtime: MTIME }));
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
test('resolveRelations: >=1 emittert lenke matcher SC-regex (leading-/ bundle-link)', () => {
|
|
const related = resolveRelations(buildConcepts());
|
|
const targets = related.flatMap((c) => c.relations.map((r) => r.target));
|
|
assert.ok(targets.length >= 1, 'minst en relasjon emitteres fra fixturene');
|
|
assert.ok(targets.every((t) => SC_REGEX.test(`](${t})`)), 'hver lenke matcher SC-regex');
|
|
// Samme lenke maa finnes i body-en som faktisk skrives + grepes av SC4.
|
|
const bodies = related.map((c) => c.body).join('\n');
|
|
assert.match(bodies, SC_REGEX, 'relasjons-lenke finnes i konsept-body');
|
|
});
|
|
|
|
test('resolveRelations: hver lenke resolverer til et emittert konsept (zero-dangling)', () => {
|
|
const concepts = buildConcepts();
|
|
const emitted = new Set(concepts.map((c) => c.destRel));
|
|
const related = resolveRelations(concepts);
|
|
for (const c of related) {
|
|
for (const r of c.relations) {
|
|
assert.ok(emitted.has(r.destRel), `lenke-mal ${r.destRel} er et emittert konsept`);
|
|
assert.equal(r.target, `/${r.destRel}`, 'leading-/ bundle-root-lenke');
|
|
assert.equal(isSafeBundleLink(r.target), true, 'emittert lenke er trygg');
|
|
}
|
|
}
|
|
});
|
|
|
|
test('resolveRelations: deterministisk (2 kjoeringer byte-like body + relations)', () => {
|
|
const pick = (arr) => arr.map((c) => ({ slug: c.slug, body: c.body, relations: c.relations }));
|
|
assert.deepEqual(pick(resolveRelations(buildConcepts())), pick(resolveRelations(buildConcepts())));
|
|
});
|
|
|
|
test('isSafeBundleLink: aksepterer leading-/ .md bundle-lenke', () => {
|
|
assert.equal(isSafeBundleLink('/strategisk-kontekst/virksomhetsplan-2026.md'), true);
|
|
assert.equal(isSafeBundleLink('/dokumenter/notat.md'), true);
|
|
});
|
|
|
|
test('isSafeBundleLink: avviser escape/scheme/relativ/ikke-md/backslash', () => {
|
|
assert.equal(isSafeBundleLink('../escape.md'), false, 'relativ ..');
|
|
assert.equal(isSafeBundleLink('/foo/../../etc/passwd.md'), false, '.. segment');
|
|
assert.equal(isSafeBundleLink('file:///etc/passwd.md'), false, 'file:// scheme');
|
|
assert.equal(isSafeBundleLink('http://evil.example/x.md'), false, 'http scheme');
|
|
assert.equal(isSafeBundleLink('relativ/sti.md'), false, 'ingen leading /');
|
|
assert.equal(isSafeBundleLink('/strategisk-kontekst/uten-ext'), false, 'ikke .md');
|
|
assert.equal(isSafeBundleLink('C:\\win\\path.md'), false, 'backslash');
|
|
assert.equal(isSafeBundleLink(''), false, 'tom streng');
|
|
assert.equal(isSafeBundleLink(null), false, 'ikke-streng');
|
|
});
|
|
|
|
test('resolveBundleLink: trygg lenke -> sti under bundleRoot; escape/scheme -> null', () => {
|
|
const root = '/tmp/bundle';
|
|
assert.equal(resolveBundleLink('/dokumenter/notat.md', root), join(root, 'dokumenter/notat.md'));
|
|
assert.equal(resolveBundleLink('../escape.md', root), null);
|
|
assert.equal(resolveBundleLink('http://x/y.md', root), null);
|
|
});
|