Files
Webklar-Kundenbereich/test/writeLimiter.test.js
T
ClaudeandClaude Sonnet 5.5 bb45d35ec7 Terminplaner: Kunden-Schnittstelle – Nachbesserungen
- GET /zeiten?ohne=<Buchung>: Leistung, Dauer und Person der Buchung wie beim
  Verschieben (verschiebeZeiten exportiert), unbekannte/fremde/Sperre 404 (R42)
- Schreib-Begrenzung als gemeinsamer Baustein (writeLimiter.js) fuer News und
  Terminplaner statt kopierter Funktion (R43)
- Unlesbares Einstellungs-JSON: Kontext traegt die echte Version, Speichern
  ueberschreibt den Schaden statt dauerhaft 409
- U+FEFF als Escape statt als rohes Zeichen
- Tests: Mandantentrennung mit echtem zweitem Kunden, R42, Limiter, kaputtes JSON

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-03 08:17:43 +00:00

84 lines
3.9 KiB
JavaScript

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)
})