import { test } from 'node:test'; import assert from 'node:assert/strict'; import { neuerSlug, tageLesen, geraet, gleich, nachweisBauen, nachweisGilt, nachweisAusCookie, nachweisName, MAX_TITEL, MAX_NOTIZ } from '../src/lib/shares.ts'; process.env.SESSION_SECRET ||= 'test-geheimnis-fuer-die-tests'; test('Slugs sind lang genug, gut vorlesbar und verwechslungsfrei', () => { const gesehen = new Set(); for (let i = 0; i < 5000; i++) { const s = neuerSlug(); assert.equal(s.length, 12, `falsche Länge: ${s}`); // Kein i, l, 0, O, 1 — sonst verhört man sich am Telefon. assert.match(s, /^[abcdefghjkmnpqrstuvwxyz23456789]+$/, `verbotenes Zeichen: ${s}`); assert.ok(!gesehen.has(s), `Slug doppelt nach ${i} Zügen: ${s}`); gesehen.add(s); } }); test('Die Zeichen sind gleichmäßig verteilt — sonst schrumpft der Suchraum', () => { const zaehler = new Map(); for (let i = 0; i < 4000; i++) for (const c of neuerSlug()) zaehler.set(c, (zaehler.get(c) || 0) + 1); const werte = [...zaehler.values()]; const schnitt = werte.reduce((a, b) => a + b, 0) / werte.length; for (const [c, n] of zaehler) { assert.ok(Math.abs(n - schnitt) / schnitt < 0.25, `${c} kommt ${n}× statt ~${Math.round(schnitt)}× vor`); } }); test('Unsinnige Gültigkeiten werden abgelehnt, nicht als „unbegrenzt" gedeutet', () => { // Genau der Fehler, den das Review gefunden hat: Number('abc') ist NaN, // NaN > 0 ist false — und der Link galt dann für immer. for (const schlecht of ['abc', -5, 1e9, Infinity, NaN, {}, [], 'morgen']) { assert.throws(() => tageLesen(schlecht, 30), /Gültigkeit/, `durchgerutscht: ${JSON.stringify(schlecht)}`); } }); test('Sinnvolle Gültigkeiten kommen durch, Voreinstellung greift bei Weglassen', () => { assert.equal(tageLesen(7, 30), 7); assert.equal(tageLesen('30', 30), 30); assert.equal(tageLesen(0, 30), 0); // 0 = bewusst unbegrenzt assert.equal(tageLesen(3650, 30), 3650); assert.equal(tageLesen(undefined, 30), 30); assert.equal(tageLesen(null, 30), 30); assert.equal(tageLesen('', 30), 30); assert.equal(tageLesen(7.9, 30), 7); // abgeschnitten, nicht gerundet }); test('Aus dem User-Agent bleibt nur Browser und System übrig', () => { const f = [ ['Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 Version/17.0 Mobile/15E148 Safari/604.1', 'Safari · iPhone'], ['Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0 Safari/537.36', 'Chrome · Windows'], ['Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Gecko/20100101 Firefox/121.0', 'Firefox · Mac'], ['Mozilla/5.0 (Linux; Android 14) Chrome/120.0 Mobile Safari/537.36', 'Chrome · Android'], ] as const; for (const [ua, soll] of f) assert.equal(geraet(ua), soll, ua.slice(0, 40)); assert.equal(geraet(''), null); assert.equal(geraet(null), null); // Der Fingerabdruck darf nicht mehr hergeben als Browser und System. for (const [ua] of f) assert.ok((geraet(ua) || '').length < 25, 'zu viel Detail behalten'); }); test('Der Nachweis fürs Zugangswort gilt nur für seinen Link', () => { const a = 'aaaaaaaa-0000-0000-0000-000000000001'; const b = 'bbbbbbbb-0000-0000-0000-000000000002'; const n = nachweisBauen(a); assert.ok(nachweisGilt(a, n), 'eigener Link abgelehnt'); assert.ok(!nachweisGilt(b, n), 'Nachweis gilt für einen fremden Link'); assert.ok(!nachweisGilt(a, 'quatsch'), 'Unsinn akzeptiert'); assert.ok(!nachweisGilt(a, ''), 'Leerwert akzeptiert'); assert.ok(!nachweisGilt(a, null), 'null akzeptiert'); }); test('Ein abgelaufener oder gefälschter Nachweis wird abgelehnt', () => { const id = 'aaaaaaaa-0000-0000-0000-000000000001'; const [, sig] = nachweisBauen(id).split('.'); const vergangen = Math.floor(Date.now() / 1000) - 10; assert.ok(!nachweisGilt(id, `${vergangen}.${sig}`), 'abgelaufener Nachweis kam durch'); // Ablaufzeit hochsetzen, ohne die Signatur zu kennen → muss scheitern. const zukunft = Math.floor(Date.now() / 1000) + 999999; assert.ok(!nachweisGilt(id, `${zukunft}.${sig}`), 'manipulierte Laufzeit kam durch'); }); test('Der Nachweis wird aus dem richtigen Cookie gelesen', () => { const kopf = `andere=x; ${nachweisName('abc123def456')}=wert.sig; noch=y`; assert.equal(nachweisAusCookie(kopf, 'abc123def456'), 'wert.sig'); assert.equal(nachweisAusCookie(kopf, 'einanderer12'), null); assert.equal(nachweisAusCookie(null, 'abc123def456'), null); }); test('gleich() vergleicht zeitkonstant und stolpert nicht über Längen', () => { assert.ok(gleich('abc', 'abc')); assert.ok(!gleich('abc', 'abd')); assert.ok(!gleich('abc', 'abcd')); assert.ok(!gleich('', 'x')); assert.ok(gleich('', '')); }); test('Die Längengrenzen sind gesetzt', () => { assert.ok(MAX_TITEL > 0 && MAX_TITEL <= 500); assert.ok(MAX_NOTIZ > MAX_TITEL); });