import test from 'node:test'; import assert from 'node:assert/strict'; import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, existsSync, readdirSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { injectAnalytics, stripComments } from '../src/analytics-inject.js'; import { validateJob } from '../src/validate.js'; import { loadConfig } from '../src/config.js'; import { createJobs } from '../src/jobs.js'; const BASE = 'https://project.webklar.com/a/'; const KEY = 'abcdef0123456789abcd'; const A = (o = {}) => ({ siteKey: KEY, color: '#2b6cb0', privacy: { company: 'Muster GmbH', address: 'Weg 1, 12345 Ort', email: 'info@muster.de' }, ...o }); const TAG = ``; const dir = () => mkdtempSync(join(tmpdir(), 'inj-')); const put = (d, f, c) => { mkdirSync(join(d, f, '..'), { recursive: true }); writeFileSync(join(d, f), c); }; const get = (d, f) => readFileSync(join(d, f), 'utf8'); const count = (s, sub) => s.split(sub).length - 1; test('mehrere Dateien inkl. Unterordner: Tag vor , node_modules/Punktordner übersprungen', async () => { const d = dir(); put(d, 'index.html', 'xa'); put(d, 'sub/seite.htm', '

b

'); put(d, 'node_modules/p/x.html', ''); put(d, '.hidden/x.html', ''); await injectAnalytics(d, A(), { baseUrl: BASE }); assert.equal(get(d, 'index.html'), `x${TAG}a`); assert.equal(get(d, 'sub/seite.htm'), `${TAG}

b

`); assert.equal(get(d, 'node_modules/p/x.html'), ''); assert.equal(get(d, '.hidden/x.html'), ''); }); test('zweimal ausführen: kein Doppel; alter Tag wird ersetzt', async () => { const d = dir(); put(d, 'index.html', ''); await injectAnalytics(d, A(), { baseUrl: BASE }); await injectAnalytics(d, A(), { baseUrl: BASE }); assert.equal(count(get(d, 'index.html'), 'data-wk-analytics'), 1); put(d, 'b.html', ''); await injectAnalytics(d, A(), { baseUrl: BASE }); assert.equal(get(d, 'b.html'), `${TAG}`); }); test('ohne head: nach ; ohne body: am Dateianfang; großes ', async () => { const d = dir(); put(d, 'a.html', 'hi'); put(d, 'b.html', '

nur text

'); put(d, 'c.html', 'xy'); await injectAnalytics(d, A(), { baseUrl: BASE }); assert.equal(get(d, 'a.html'), `${TAG}hi`); assert.equal(get(d, 'b.html'), `${TAG}

nur text

`); assert.equal(get(d, 'c.html'), `x${TAG}y`); }); test('Nicht-UTF8-Bytes bleiben erhalten', async () => { const d = dir(); const before = Buffer.concat([Buffer.from(''), Buffer.from([0xe4, 0xf6, 0xfc, 0xff, 0x80]), Buffer.from('')]); writeFileSync(join(d, 'l.html'), before); await injectAnalytics(d, A(), { baseUrl: BASE }); const after = readFileSync(join(d, 'l.html')); assert.deepEqual(after, Buffer.concat([Buffer.from(''), Buffer.from([0xe4, 0xf6, 0xfc, 0xff, 0x80]), Buffer.from(TAG), Buffer.from('')])); }); test('Datei > 5 MB wird übersprungen (Logzeile)', async () => { const d = dir(); const big = Buffer.alloc(5 * 1024 * 1024 + 1, 0x20); big.write('', 0); writeFileSync(join(d, 'gross.html'), big); const lines = []; await injectAnalytics(d, A(), { baseUrl: BASE, onLine: (l) => lines.push(l) }); assert.deepEqual(readFileSync(join(d, 'gross.html')), big); assert.ok(lines.some((l) => l.includes('gross.html') && /5 MB/.test(l))); }); test('Datenschutzseite: Platzhalter maskiert, Skript auch dort, Pflichtabschnitte', async () => { const d = dir(); put(d, 'index.html', ''); await injectAnalytics(d, A({ privacy: { company: ' & Co', address: 'A "B" ', email: "x'y@z.de" } }), { baseUrl: BASE }); const p = get(d, 'datenschutz-analyse.html'); assert.ok(p.includes('<script>alert(1)</script> & Co')); assert.ok(!p.includes(''); put(d, 'b.html', ''); put(d, 'c.html', '

x

'); put(d, 'd.html', ''); await injectAnalytics(d, A(), { baseUrl: BASE }); assert.equal(get(d, 'a.html'), `${TAG}`); assert.equal(get(d, 'b.html'), `${TAG}`); assert.equal(get(d, 'c.html'), `${TAG}

x

`); assert.equal(get(d, 'd.html'), `${TAG}`); }); test('alter Tag im Kommentar wird nicht ersetzt; echter wird genau einmal eingesetzt', async () => { const d = dir(); const old = ''; put(d, 'a.html', ``); await injectAnalytics(d, A(), { baseUrl: BASE }); assert.equal(get(d, 'a.html'), `${TAG}`); }); test('5-MB-Grenze: genau 5 MB wird bearbeitet', async () => { const d = dir(); const b = Buffer.alloc(5 * 1024 * 1024, 0x20); b.write('', 0); writeFileSync(join(d, 'g.html'), b); await injectAnalytics(d, A(), { baseUrl: BASE }); assert.ok(get(d, 'g.html').includes('data-wk-analytics')); }); test('validateJob: Adresse nur Leerraum ⇒ leer, sonst getrimmt', () => { const base = { sub: 'demo', repo: 'JUSN/demo', sha: 'abcdef1', branch: 'main' }; const r = validateJob({ ...base, analytics: A({ privacy: { company: 'F', email: 'e@x.de', address: ' ' } }) }); assert.equal(r.job.analytics.privacy.address, ''); const r2 = validateJob({ ...base, analytics: A({ privacy: { company: 'F', email: 'e@x.de', address: ' Weg 1 ' } }) }); assert.equal(r2.job.analytics.privacy.address, 'Weg 1'); }); test('Zusatzseite enthält keine HTML-Kommentare', async () => { const d = dir(); await injectAnalytics(d, A(), { baseUrl: BASE }); assert.ok(!get(d, 'datenschutz-analyse.html').includes('bc'), 'abc'); });