Analytics: Geruest, Datenbank, Website-Register

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-01 09:23:27 +00:00
1 parent a1ce46672a
commit f7da79e4b3
6 files changed
+318

No files matched your search

+138
View File
@@ -0,0 +1,138 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { loadConfig } from '../src/config.js';
import { openDb } from '../src/db.js';
import { createServer } from '../src/server.js';
const KEY = 'abcdef0123456789abcd';
async function setup(adminToken = 'geheim') {
const db = openDb(':memory:');
const server = createServer({ config: { adminToken }, db });
await new Promise((r) => server.listen(0, '127.0.0.1', r));
const base = `http://127.0.0.1:${server.address().port}`;
const call = async (method, path, body, token = 'geheim', raw) => {
const headers = {};
if (token !== null) headers.Authorization = `Bearer ${token}`;
if (body !== undefined || raw !== undefined) headers['Content-Type'] = 'application/json';
const res = await fetch(base + path, { method, headers, body: raw ?? (body === undefined ? undefined : JSON.stringify(body)) });
return { status: res.status, body: await res.json() };
};
return { db, call, close: () => server.close() };
}
const row = (db, k = KEY) => db.prepare('SELECT * FROM sites WHERE siteKey = ?').get(k);
const valid = { projectId: 'p1', domains: ['abc.project.webklar.com'], active: true };
test('loadConfig: Vorgaben und Umgebung', () => {
const c = loadConfig({});
assert.deepEqual(c, { port: 8090, dataDir: '/srv/webklar-analytics', adminToken: '', heatSecret: '', geoFile: '', baseHost: 'project.webklar.com' });
const d = loadConfig({ ANALYTICS_PORT: '9000', ANALYTICS_ADMIN_TOKEN: 't', ANALYTICS_HEAT_SECRET: 'h' });
assert.equal(d.port, 9000);
assert.equal(d.adminToken, 't');
assert.equal(d.heatSecret, 'h');
});
test('openDb ist idempotent und legt Tabellen an', () => {
const db = openDb(':memory:');
const names = db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all().map((r) => r.name);
for (const n of ['sites', 'events_raw', 'clicks', 'daily']) assert.ok(names.includes(n), n);
});
test('GET /a/health ohne Token', async () => {
const t = await setup();
const r = await t.call('GET', '/a/health', undefined, null);
assert.equal(r.status, 200);
assert.deepEqual(r.body, { ok: true });
t.close();
});
test('Admin: 401 ohne, mit falschem Token und bei leerem adminToken', async () => {
const t = await setup();
assert.equal((await t.call('PUT', `/a/admin/sites/${KEY}`, valid, null)).status, 401);
assert.equal((await t.call('PUT', `/a/admin/sites/${KEY}`, valid, 'falsch')).status, 401);
assert.equal((await t.call('DELETE', `/a/admin/sites/${KEY}`, undefined, 'falsch')).status, 401);
assert.equal(row(t.db), undefined);
t.close();
const e = await setup('');
assert.equal((await e.call('PUT', `/a/admin/sites/${KEY}`, valid, '')).status, 401);
assert.equal((await e.call('PUT', `/a/admin/sites/${KEY}`, valid, 'x')).status, 401);
e.close();
});
test('PUT legt an und ändert', async () => {
const t = await setup();
const r = await t.call('PUT', `/a/admin/sites/${KEY}`, valid);
assert.equal(r.status, 200);
assert.equal(r.body.ok, true);
let s = row(t.db);
assert.equal(s.projectId, 'p1');
assert.deepEqual(JSON.parse(s.domains), ['abc.project.webklar.com']);
assert.equal(s.active, 1);
assert.equal(s.offSince, null);
await t.call('PUT', `/a/admin/sites/${KEY}`, { projectId: 'p2', domains: ['a.de', 'b.de'], active: true });
s = row(t.db);
assert.equal(s.projectId, 'p2');
assert.deepEqual(JSON.parse(s.domains), ['a.de', 'b.de']);
assert.equal(t.db.prepare('SELECT COUNT(*) c FROM sites').get().c, 1);
t.close();
});
test('active=false setzt offSince einmalig, active=true löscht es', async () => {
const t = await setup();
await t.call('PUT', `/a/admin/sites/${KEY}`, valid);
await t.call('PUT', `/a/admin/sites/${KEY}`, { ...valid, active: false });
const s1 = row(t.db);
assert.equal(s1.active, 0);
assert.ok(!Number.isNaN(Date.parse(s1.offSince)));
assert.match(s1.offSince, /^\d{4}-\d\d-\d\dT/);
t.db.prepare('UPDATE sites SET offSince = ? WHERE siteKey = ?').run('2026-01-01T00:00:00.000Z', KEY);
await t.call('PUT', `/a/admin/sites/${KEY}`, { ...valid, active: false });
assert.equal(row(t.db).offSince, '2026-01-01T00:00:00.000Z');
await t.call('PUT', `/a/admin/sites/${KEY}`, valid);
assert.equal(row(t.db).offSince, null);
t.close();
});
test('PUT: ungültiger siteKey 400', async () => {
const t = await setup();
for (const k of ['kurz', 'ABCDEF0123456789ABCD', 'abcdef0123456789abc!', 'a'.repeat(41)]) {
assert.equal((await t.call('PUT', `/a/admin/sites/${k}`, valid)).status, 400, k);
}
assert.equal((await t.call('DELETE', '/a/admin/sites/kurz')).status, 400);
t.close();
});
test('PUT: Domains nur Hostnamen, max 5; projectId/active geprüft', async () => {
const t = await setup();
const bad = [
['a.de:8080'], ['a.de/pfad'], ['https://a.de'], ['A.de'], ['localhost'], [''], ['-a.de'], [1], 'a.de', [],
['a.de', 'b.de', 'c.de', 'd.de', 'e.de', 'f.de'],
];
for (const domains of bad) {
const r = await t.call('PUT', `/a/admin/sites/${KEY}`, { ...valid, domains });
assert.equal(r.status, 400, JSON.stringify(domains));
assert.equal(typeof r.body.error, 'string');
}
assert.equal((await t.call('PUT', `/a/admin/sites/${KEY}`, { ...valid, projectId: '' })).status, 400);
assert.equal((await t.call('PUT', `/a/admin/sites/${KEY}`, { ...valid, active: 'ja' })).status, 400);
assert.equal((await t.call('PUT', `/a/admin/sites/${KEY}`, undefined, 'geheim', '{kaputt')).status, 400);
assert.equal(row(t.db), undefined);
assert.equal((await t.call('PUT', `/a/admin/sites/${KEY}`, { ...valid, domains: ['a.de', 'b.de', 'c.de', 'd.de', 'e.de'] })).status, 200);
t.close();
});
test('PUT: zu großer Body 413', async () => {
const t = await setup();
const r = await t.call('PUT', `/a/admin/sites/${KEY}`, { ...valid, projectId: 'x'.repeat(70000) });
assert.equal(r.status, 413);
t.close();
});
test('DELETE entfernt Website, unbekannte Route 404', async () => {
const t = await setup();
await t.call('PUT', `/a/admin/sites/${KEY}`, valid);
assert.equal((await t.call('DELETE', `/a/admin/sites/${KEY}`)).status, 200);
assert.equal(row(t.db), undefined);
assert.equal((await t.call('GET', '/a/nichts', undefined, null)).status, 404);
t.close();
});