import { test, before, after } from 'node:test' import assert from 'node:assert/strict' import http from 'node:http' import { createAnalyticsClient } from '../server/services/analyticsClient.js' import { verifyHeatToken } from '../analytics/src/heat.js' let server let url let calls let respond before(async () => { server = http.createServer((req, res) => { let body = '' req.on('data', (c) => { body += c }) req.on('end', () => { calls.push({ method: req.method, url: req.url, auth: req.headers.authorization, body }) const r = respond(req) res.writeHead(r.status, { 'Content-Type': 'application/json' }) res.end(JSON.stringify(r.body ?? {})) }) }) await new Promise((r) => server.listen(0, '127.0.0.1', r)) url = `http://127.0.0.1:${server.address().port}` }) after(() => server.close()) const mk = (over = {}) => createAnalyticsClient({ url, adminToken: 'tok-secret', heatSecret: 'heat-secret', ...over }) const reset = (r = () => ({ status: 200, body: {} })) => { calls = []; respond = r } test('putSite: PUT mit Bearer und JSON', async () => { reset() await mk().putSite('abcdefghij123456', { projectId: 'p1', domains: ['a.project.webklar.com'], active: true }) assert.equal(calls[0].method, 'PUT') assert.equal(calls[0].url, '/a/admin/sites/abcdefghij123456') assert.equal(calls[0].auth, 'Bearer tok-secret') assert.deepEqual(JSON.parse(calls[0].body), { projectId: 'p1', domains: ['a.project.webklar.com'], active: true }) }) test('deleteSite und getStats', async () => { reset(() => ({ status: 200, body: { pv: 3 } })) const c = mk() await c.deleteSite('abcdefghij123456') assert.equal(calls[0].method, 'DELETE') const s = await c.getStats('abcdefghij123456', '7') assert.deepEqual(s, { pv: 3 }) assert.equal(calls[1].url, '/a/admin/stats/abcdefghij123456?range=7') }) test('Fehler tragen status, ohne Token in der Meldung', async () => { reset(() => ({ status: 500, body: { error: 'kaputt' } })) await assert.rejects(mk().getStats('abcdefghij123456', '7'), (e) => { assert.equal(e.status, 500) assert.ok(!e.message.includes('tok-secret')) return true }) }) test('fehlender adminToken => 503', async () => { reset() await assert.rejects(mk({ adminToken: '' }).putSite('abcdefghij123456', {}), (e) => { assert.equal(e.status, 503) assert.equal(e.message, 'Auswertung nicht eingerichtet') return true }) assert.equal(calls.length, 0) }) test('Timeout wird als Fehler gemeldet', async () => { reset(() => ({ status: 200 })) const slow = http.createServer(() => {}) await new Promise((r) => slow.listen(0, '127.0.0.1', r)) const c = mk({ url: `http://127.0.0.1:${slow.address().port}`, timeoutMs: 50 }) await assert.rejects(c.getStats('abcdefghij123456', '7'), (e) => { assert.ok(e.status >= 500) assert.ok(!e.message.includes('tok-secret')) return true }) slow.closeAllConnections?.() slow.close() }) test('heatToken: von der Analytics-Verifikation akzeptiert, 30 Min gueltig', () => { const now = Date.UTC(2026, 9, 1, 12, 0, 0) const token = mk().heatToken({ siteKey: 'abcdefghij123456', path: '/preise', vp: 'm', now }) const v = verifyHeatToken(token, 'heat-secret', now) assert.deepEqual(v, { siteKey: 'abcdefghij123456', path: '/preise', vp: 'm', exp: now / 1000 + 1800 }) assert.equal(verifyHeatToken(token, 'heat-secret', now + 1801 * 1000), null) assert.equal(verifyHeatToken(token, 'anderes', now), null) }) test('heatToken ohne heatSecret => 503', () => { assert.throws(() => mk({ heatSecret: '' }).heatToken({ siteKey: 'abcdefghij123456', path: '/', vp: 'd' }), (e) => e.status === 503) }) test('stopSite: POST /off mit Bearer, 404 zaehlt als erledigt, andere Fehler werfen', async () => { reset() await mk().stopSite('abcdefghij123456') assert.equal(calls[0].method, 'POST') assert.equal(calls[0].url, '/a/admin/sites/abcdefghij123456/off') assert.equal(calls[0].auth, 'Bearer tok-secret') reset(() => ({ status: 404, body: { error: 'x' } })) assert.deepEqual(await mk().stopSite('abcdefghij123456'), { ok: true, missing: true }) reset(() => ({ status: 500, body: {} })) await assert.rejects(mk().stopSite('abcdefghij123456'), (e) => e.status === 500) })