- Eltern-Ordner ist jetzt EIN Git-Repo (statt getrennter Repos). - root .gitignore haelt Secrets (.env), node_modules, DB und Build-Artefakte raus. - release.ps1: manueller Release (ZIP bauen + ans Backend laden). - root README mit Struktur und Release-Ablauf. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
51 lines
2.5 KiB
JavaScript
51 lines
2.5 KiB
JavaScript
import { generateKey, normalizeDomain, safeEqual, isExpired, compareVersions, signToken, verifyToken } from '../src/util.js';
|
|
|
|
let fail = 0;
|
|
const ok = (name, cond) => { console.log((cond ? '[PASS] ' : '[FAIL] ') + name); if (!cond) fail++; };
|
|
|
|
// Key format
|
|
const k = generateKey();
|
|
ok('key format XXXX-XXXX-XXXX-XXXX', /^[A-Z2-9]{4}-[A-Z2-9]{4}-[A-Z2-9]{4}-[A-Z2-9]{4}$/.test(k));
|
|
ok('no ambiguous chars (0OI1L)', !/[0OI1L]/.test(k));
|
|
const keys = new Set(Array.from({ length: 1000 }, () => generateKey()));
|
|
ok('1000 keys unique', keys.size === 1000);
|
|
|
|
// Domain normalization
|
|
ok('strip https + www', normalizeDomain('https://www.Example.com/') === 'example.com');
|
|
ok('strip path', normalizeDomain('http://example.com/foo/bar') === 'example.com');
|
|
ok('strip port', normalizeDomain('example.com:8080') === 'example.com');
|
|
ok('subdomain kept', normalizeDomain('https://shop.example.com/x') === 'shop.example.com');
|
|
ok('empty stays empty', normalizeDomain('') === '');
|
|
ok('null safe', normalizeDomain(null) === '');
|
|
|
|
// safeEqual
|
|
ok('safeEqual match', safeEqual('abc123', 'abc123') === true);
|
|
ok('safeEqual mismatch', safeEqual('abc', 'abd') === false);
|
|
ok('safeEqual length diff', safeEqual('abc', 'abcd') === false);
|
|
|
|
// isExpired
|
|
ok('no expiry = not expired', isExpired(null) === false);
|
|
ok('past = expired', isExpired('2000-01-01T00:00:00Z') === true);
|
|
ok('future = not expired', isExpired('2999-01-01T00:00:00Z') === false);
|
|
ok('garbage = not expired', isExpired('not-a-date') === false);
|
|
|
|
// compareVersions
|
|
ok('1.1.0 > 1.0.0', compareVersions('1.1.0', '1.0.0') === 1);
|
|
ok('1.0.0 < 1.0.1', compareVersions('1.0.0', '1.0.1') === -1);
|
|
ok('1.0 == 1.0.0', compareVersions('1.0', '1.0.0') === 0);
|
|
ok('2.0.0 > 1.9.9', compareVersions('2.0.0', '1.9.9') === 1);
|
|
ok('10.0 > 9.0 (numeric not lexical)', compareVersions('10.0', '9.0') === 1);
|
|
|
|
// signToken / verifyToken
|
|
const secret = 'top-secret';
|
|
const tok = signToken({ k: 'KEY', p: 'gdpr-content-blocker', v: '1.1.0', exp: Date.now() + 10000 }, secret);
|
|
const payload = verifyToken(tok, secret);
|
|
ok('token round-trips', payload && payload.k === 'KEY' && payload.v === '1.1.0');
|
|
ok('wrong secret → null', verifyToken(tok, 'other') === null);
|
|
ok('tampered body → null', verifyToken('x' + tok, secret) === null);
|
|
ok('expired token → null', verifyToken(signToken({ exp: Date.now() - 1 }, secret), secret) === null);
|
|
ok('garbage token → null', verifyToken('not-a-token', secret) === null);
|
|
|
|
console.log('\n' + (fail === 0 ? 'ALL PASSED' : fail + ' FAILED'));
|
|
process.exit(fail === 0 ? 0 : 1);
|