import { test } from 'node:test' import assert from 'node:assert/strict' import { createWriteLimiter } from '../server/services/writeLimiter.js' // Schreib-Begrenzung je Schluessel im Speicher (News 30/h, Terminplaner 120/h): Fenster, getrennte Schluessel, Middleware. const MINUTE = 60 * 1000 function uhr(start = 1_000_000) { const stand = { jetzt: start } return { stand, now: () => stand.jetzt } } test('writeLimiter: max Aufrufe sind erlaubt, der naechste ist darueber', () => { const { now } = uhr() const limiter = createWriteLimiter({ max: 3, now }) assert.deepEqual([1, 2, 3].map(() => limiter.istUeberschritten('k')), [false, false, false]) assert.equal(limiter.istUeberschritten('k'), true) assert.equal(limiter.istUeberschritten('k'), true, 'weitere Aufrufe bleiben darueber') }) test('writeLimiter: Schluessel sind getrennt', () => { const { now } = uhr() const limiter = createWriteLimiter({ max: 1, now }) assert.equal(limiter.istUeberschritten('a'), false) assert.equal(limiter.istUeberschritten('a'), true) assert.equal(limiter.istUeberschritten('b'), false, 'b hat sein eigenes Limit') }) test('writeLimiter: das Fenster laeuft ab (Standard eine Stunde), ein Aufruf zaehlt genau bis 60 Minuten nach ihm', () => { const { stand, now } = uhr() const limiter = createWriteLimiter({ max: 2, now }) assert.equal(limiter.istUeberschritten('k'), false) // A bei 0 stand.jetzt += 60 * MINUTE - 1 assert.equal(limiter.istUeberschritten('k'), false) // B bei 59:59,999: A zaehlt noch (2 <= 2) stand.jetzt += 1 // 60:00: A ist aus dem Fenster assert.equal(limiter.istUeberschritten('k'), false) // C: B + C = 2 assert.equal(limiter.istUeberschritten('k'), true) // D: B + C + D = 3 > 2 }) test('writeLimiter: auch abgelehnte Aufrufe zaehlen im Fenster mit (wie bisher bei den News)', () => { const { stand, now } = uhr() const limiter = createWriteLimiter({ max: 1, now }) assert.equal(limiter.istUeberschritten('k'), false) // X bei 0 stand.jetzt += MINUTE assert.equal(limiter.istUeberschritten('k'), true) // Y bei 1 min, abgelehnt stand.jetzt += 59 * MINUTE // 60 min: X ist raus, Y (59 min alt) zaehlt noch assert.equal(limiter.istUeberschritten('k'), true) }) test('writeLimiter: eigenes Fenster, abgelaufene Schluessel werden vergessen, reset leert alles', () => { const { stand, now } = uhr() const limiter = createWriteLimiter({ max: 1, fensterMs: 10 * MINUTE, now }) assert.equal(limiter.istUeberschritten('a'), false) assert.equal(limiter.istUeberschritten('a'), true) stand.jetzt += 10 * MINUTE assert.equal(limiter.istUeberschritten('a'), false, 'nach dem Fenster frei') assert.equal(limiter.istUeberschritten('b'), false) limiter.reset() assert.equal(limiter.istUeberschritten('a'), false) assert.equal(limiter.istUeberschritten('b'), false) }) test('writeLimiter: middleware antwortet ab dem Ueberschreiten mit 429 und dem Text, sonst weiter', () => { const { now } = uhr() const limiter = createWriteLimiter({ max: 2, now }) const mw = limiter.middleware((req) => req.kunde, 'Zu viele Änderungen.') const aufruf = (kunde) => { const antwort = { status(code) { this.code = code; return this }, json(body) { this.body = body; return this } } let weiter = 0 mw({ kunde }, antwort, () => { weiter += 1 }) return { weiter, code: antwort.code, body: antwort.body } } assert.deepEqual(aufruf('c1'), { weiter: 1, code: undefined, body: undefined }) assert.equal(aufruf('c1').weiter, 1) assert.deepEqual(aufruf('c1'), { weiter: 0, code: 429, body: { error: 'Zu viele Änderungen.' } }) assert.equal(aufruf('c2').weiter, 1, 'anderer Kunde') }) test('writeLimiter: ungueltige Angaben werfen', () => { for (const max of [0, -1, 1.5, '3', undefined]) assert.throws(() => createWriteLimiter({ max }), TypeError, String(max)) assert.throws(() => createWriteLimiter({ max: 1, fensterMs: 0 }), TypeError) assert.throws(() => createWriteLimiter({ max: 1, now: 5 }), TypeError) })