Analytics: Heatmap-Endpunkt nachgeschaerft
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
28c7a90d8b
commit
add7fa4add
4 files changed
+97
-13
No files matched your search
@@ -100,3 +100,39 @@ test('/a/heat nur auf dem öffentlichen Listener', async () => {
|
||||
assert.equal(r.status, 404);
|
||||
server.close();
|
||||
});
|
||||
|
||||
test('403 bei ausgeschalteter Website (active = 0), mit CORS für die eigene Domain', async () => {
|
||||
const s = await setup();
|
||||
s.db.prepare('UPDATE sites SET active = 0 WHERE siteKey = ?').run(KEY);
|
||||
const r = await s.get(tok(), { Origin: 'https://kunde.de' });
|
||||
assert.equal(r.status, 403);
|
||||
assert.equal(r.headers.get('access-control-allow-origin'), 'https://kunde.de');
|
||||
s.close();
|
||||
});
|
||||
|
||||
test('403 mit CORS-Kopf, wenn die Website bekannt ist (abgelaufen, aus); sonst ohne', async () => {
|
||||
const s = await setup();
|
||||
const origin = { Origin: 'https://www.kunde.de' };
|
||||
const abgelaufen = await s.get(tok({ exp: exp(-1) }), origin);
|
||||
assert.equal(abgelaufen.status, 403);
|
||||
assert.equal(abgelaufen.headers.get('access-control-allow-origin'), 'https://www.kunde.de');
|
||||
// fremder Origin, falsche Signatur, unbekannte Website: kein CORS-Kopf
|
||||
assert.equal((await s.get(tok({ exp: exp(-1) }), { Origin: 'https://evil.example' })).headers.get('access-control-allow-origin'), null);
|
||||
assert.equal((await s.get(tok({}, 'anderes-secret'), origin)).headers.get('access-control-allow-origin'), null);
|
||||
assert.equal((await s.get(tok({ siteKey: 'nichtvorhanden0123456' }), origin)).headers.get('access-control-allow-origin'), null);
|
||||
assert.equal((await s.get('abc', origin)).headers.get('access-control-allow-origin'), null);
|
||||
s.close();
|
||||
});
|
||||
|
||||
test('OPTIONS-Preflight: CORS-Köpfe nur für erlaubte Origins bekannter Websites', async () => {
|
||||
const s = await setup();
|
||||
const pre = (token, origin) => fetch(`${s.base}/a/heat?token=${encodeURIComponent(token)}`, { method: 'OPTIONS', headers: { Origin: origin, 'Access-Control-Request-Method': 'GET' } });
|
||||
const ok = await pre(tok(), 'https://kunde.de');
|
||||
assert.equal(ok.status, 204);
|
||||
assert.equal(ok.headers.get('access-control-allow-origin'), 'https://kunde.de');
|
||||
assert.match(ok.headers.get('access-control-allow-methods'), /GET/);
|
||||
assert.equal((await pre(tok({ exp: exp(-1) }), 'https://kunde.de')).headers.get('access-control-allow-origin'), 'https://kunde.de');
|
||||
assert.equal((await pre(tok(), 'https://evil.example')).headers.get('access-control-allow-origin'), null);
|
||||
assert.equal((await pre('abc', 'https://kunde.de')).headers.get('access-control-allow-origin'), null);
|
||||
s.close();
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { openDb } from '../src/db.js';
|
||||
import { runNightly, nextRunAt, dayOf } from '../src/jobs.js';
|
||||
import { runNightly, nextRunAt, dayOf, scheduleNightly } from '../src/jobs.js';
|
||||
import { createAdminServer } from '../src/server.js';
|
||||
|
||||
const KEY = 'abcdef0123456789abcd';
|
||||
@@ -177,3 +177,30 @@ test('Stats: Top 20 absteigend, consentRate 0 ohne Entscheidungen, Fehlerfälle'
|
||||
assert.equal((await a.get(`/a/admin/stats/${KEY}?range=7`, 'Bearer nope')).status, 401);
|
||||
a.close();
|
||||
});
|
||||
|
||||
test('scheduleNightly: früh feuernder Timer läuft nicht vorzeitig und nie doppelt für dasselbe 02:30', () => {
|
||||
let t = Date.UTC(2026, 9, 10, 10, 0, 0);
|
||||
const pending = [];
|
||||
const timers = { setTimeout: (fn, ms) => { const h = { fn, at: t + ms }; pending.push(h); return h; }, clearTimeout: (h) => { const i = pending.indexOf(h); if (i >= 0) pending.splice(i, 1); } };
|
||||
const runs = [];
|
||||
const quiet = { log() {}, error() {} };
|
||||
const s = scheduleNightly({}, { startDelayMs: 1000, log: quiet, now: () => t, timers, job: (db, o) => { runs.push(o.now); return { aggregated: 0, sitesDeleted: 0 }; } });
|
||||
const fire = (clock) => { const h = pending.shift(); t = clock ?? h.at; h.fn(); };
|
||||
fire(); // Startlauf
|
||||
assert.equal(runs.length, 1);
|
||||
const target = nextRunAt(t);
|
||||
assert.equal(pending.length, 1);
|
||||
assert.equal(pending[0].at, target);
|
||||
fire(target - 500); // Timer feuert 500 ms zu früh
|
||||
assert.equal(runs.length, 1, 'kein vorzeitiger Lauf');
|
||||
assert.equal(pending.length, 1);
|
||||
assert.equal(pending[0].at, target, 'neu auf das Ziel gesetzt');
|
||||
fire(target); // pünktlich
|
||||
assert.equal(runs.length, 2);
|
||||
assert.equal(pending[0].at, nextRunAt(target + 1000));
|
||||
assert.ok(pending[0].at > target + 20 * 3600000, 'nächster Lauf am Folgetag');
|
||||
fire(pending[0].at - 1); // wieder zu früh
|
||||
assert.equal(runs.length, 2);
|
||||
s.stop();
|
||||
assert.equal(pending.length, 0);
|
||||
});
|
||||
Reference in new issue
Block a user