// domain-filter.test.mjs // Vakt: ingen sporet fil -- innhold ELLER sti -- skal treffe den lokale // ekskluderingslista. // // Termlista er med vilje IKKE sporet: den ligger i tests/.excluded-terms.local.md // (gitignored via *.local.md). Mangler fila, hoppes vakten over med en tydelig // melding -- da er ingenting maalt, og det skal ikke leses som null treff. // Hver term baerer en kjent-positiv proeve, og lista har kjent-negative proever, // saa et moenster som ikke kan finne noe aldri passerer som en null-maaling. import { test } from 'node:test'; import assert from 'node:assert/strict'; import { execFileSync } from 'node:child_process'; import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'; import { join, dirname, relative, sep } from 'node:path'; import { fileURLToPath } from 'node:url'; const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..'); const SELF = relative(ROOT, fileURLToPath(import.meta.url)).split(sep).join('/'); const TERMFIL = join(ROOT, 'tests', '.excluded-terms.local.md'); const MANGLER = `ekskluderingslista mangler (${relative(ROOT, TERMFIL)}) -- vakten er IKKE kjoert`; // Navngitt unntaksliste: sti -> begrunnelse. Hver oppfoering maa finnes og // fortsatt treffe, ellers er den foreldet og testen feiler. const UNNTAK = new Map([]); function lesTermer() { const termer = []; const negative = []; for (const raa of readFileSync(TERMFIL, 'utf8').split('\n')) { const linje = raa.trim(); if (!linje || linje.startsWith('#')) continue; if (linje.startsWith('!')) { negative.push(linje.slice(1).trim()); continue; } const [kilde, proeve] = linje.split(' => '); termer.push({ kilde: kilde.trim(), proeve: proeve === undefined ? undefined : proeve.trim() }); } return { termer, negative }; } function sporedeFiler() { try { const ut = execFileSync('git', ['ls-files', '-z'], { cwd: ROOT, encoding: 'utf8' }); const filer = ut.split('\0').filter(Boolean); if (filer.length > 0) return filer; } catch { // Ingen git (f.eks. installert plugin-kopi) -> gaa filtreet. } const ut = []; const gaa = (dir) => { for (const navn of readdirSync(join(ROOT, dir))) { if (navn === '.git' || navn === 'node_modules' || navn === '.claude') continue; if (navn.endsWith('.local.md')) continue; const rel = dir ? `${dir}/${navn}` : navn; if (statSync(join(ROOT, rel)).isDirectory()) gaa(rel); else ut.push(rel); } }; gaa(''); return ut; } function finnTreff(filer, les, moenster) { const treff = []; for (const sti of filer) { if (moenster.test(sti)) treff.push(`${sti} (filnavn)`); les(sti).split('\n').forEach((linje, i) => { const m = linje.match(moenster); if (m) treff.push(`${sti}:${i + 1}: ${m[0]}`); }); } return treff; } const lesFil = (sti) => readFileSync(join(ROOT, sti)).toString('utf8'); test('finnTreff melder baade innhold og filnavn (syntetisk kjent-positiv)', () => { const syntetisk = { 'a/zz-forbudt-zz.md': 'ren tekst', 'b.md': 'linje\nher er zz-forbudt-zz' }; const treff = finnTreff(Object.keys(syntetisk), (s) => syntetisk[s], /zz-forbudt-zz/iu); assert.equal(treff.length, 2); assert.match(treff[0], /\(filnavn\)$/); assert.match(treff[1], /^b\.md:2: /); }); test('ingen sporet fil treffer den lokale ekskluderingslista (innhold eller sti)', (t) => { if (!existsSync(TERMFIL)) { t.skip(MANGLER); return; } const { termer, negative } = lesTermer(); assert.ok(termer.length > 0, 'ekskluderingslista har ingen termer'); assert.ok(negative.length > 0, 'ekskluderingslista har ingen kjent-negative proever'); // Validering foer bruk: hver term treffer sin kjent-positive proeve. for (const { kilde, proeve } of termer) { assert.ok(proeve, `term uten kjent-positiv proeve: ${kilde}`); assert.match(proeve, new RegExp(kilde, 'iu'), `term treffer ikke sin egen proeve: ${kilde}`); } const moenster = new RegExp(termer.map((x) => x.kilde).join('|'), 'iu'); for (const s of negative) assert.doesNotMatch(s, moenster, `kjent-negativ treffer: ${s}`); // Nevneren: skanningen maa faktisk se repoet, inkl. seg selv og fixturene. const filer = sporedeFiler(); assert.ok(filer.length > 100, `for faa filer skannet (${filer.length}) -- enumereringen er brukket`); assert.ok(filer.includes(SELF), 'testfila selv skal vaere med i skanningen'); assert.ok(filer.some((f) => f.startsWith('tests/fixtures/')), 'fixturene skal vaere med i skanningen'); assert.ok(!filer.some((f) => f.endsWith('.local.md')), 'en *.local.md-fil er sporet -- ekskluderingslista kan lekke'); for (const [sti, grunn] of UNNTAK) { assert.ok(filer.includes(sti), `unntak for ukjent fil: ${sti} (${grunn})`); assert.ok(finnTreff([sti], lesFil, moenster).length > 0, `foreldet unntak, treffer ikke lenger: ${sti}`); } const treff = finnTreff(filer.filter((f) => !UNNTAK.has(f)), lesFil, moenster); assert.deepEqual(treff, [], `${treff.length} treff:\n${treff.join('\n')}`); });