import { test, beforeEach } from 'node:test' import assert from 'node:assert/strict' import { neuerLru } from '../server/services/zeiten/lru.js' import { zeitenCache, vergissZeiten, resetZeitenCacheForTests } from '../server/services/zeiten/cache.js' // Zeiten-Speicher im Arbeitsspeicher: echtes LRU mit Ablaufzeit (server/services/zeiten/lru.js) und die Instanz fuer die // oeffentliche Schnittstelle (cache.js: hoechstens 500 Eintraege, 30 Sekunden). Rein, die Uhr kommt als Parameter (Millisekunden). test('LRU verdrängt den ältesten, Treffer erneuern', () => { const lru = neuerLru({ max: 2, ttlMs: 1000 }) lru.setze('a', 1, 0) lru.setze('b', 2, 0) assert.equal(lru.hole('a', 1), 1) // a ist jetzt der jüngste, b der älteste lru.setze('c', 3, 2) assert.equal(lru.hole('b', 3), undefined, 'b war der älteste und muss fehlen') assert.equal(lru.hole('a', 3), 1) assert.equal(lru.hole('c', 3), 3) assert.equal(lru.groesse(), 2) }) test('LRU ohne Treffer verdrängt in Einfügereihenfolge', () => { const lru = neuerLru({ max: 2, ttlMs: 1000 }) lru.setze('a', 1, 0) lru.setze('b', 2, 0) lru.setze('c', 3, 0) assert.equal(lru.hole('a', 0), undefined) assert.equal(lru.hole('b', 0), 2) assert.equal(lru.hole('c', 0), 3) }) test('LRU: erneutes Setzen ersetzt den Wert, erneuert Platz und Ablaufzeit und füllt den Speicher nicht auf', () => { const lru = neuerLru({ max: 2, ttlMs: 1000 }) lru.setze('a', 1, 0) lru.setze('b', 2, 0) lru.setze('a', 11, 900) // a ist neu und jünger, b der älteste assert.equal(lru.groesse(), 2) lru.setze('c', 3, 900) assert.equal(lru.hole('b', 900), undefined) assert.equal(lru.hole('a', 1500), 11, 'die Ablaufzeit läuft ab dem letzten Setzen') assert.equal(lru.hole('a', 1900), undefined, '900 + 1000 = abgelaufen') }) test('LRU-Einträge verfallen nach ttl', () => { const lru = neuerLru({ max: 10, ttlMs: 30000 }) lru.setze('a', 'x', 1000) assert.equal(lru.hole('a', 1000), 'x') assert.equal(lru.hole('a', 30999), 'x', 'eine Millisekunde vor Ablauf') assert.equal(lru.hole('a', 31000), undefined, 'genau nach ttlMs ist der Eintrag weg') assert.equal(lru.groesse(), 0, 'ein abgelaufener Eintrag bleibt nicht liegen') }) test('LRU: ein Treffer verlängert die Ablaufzeit nicht (nur den Platz in der Reihe)', () => { const lru = neuerLru({ max: 10, ttlMs: 1000 }) lru.setze('a', 1, 0) assert.equal(lru.hole('a', 900), 1) assert.equal(lru.hole('a', 1000), undefined) }) test('LRU: vergiss, leere, groesse', () => { const lru = neuerLru({ max: 5, ttlMs: 1000 }) assert.equal(lru.groesse(), 0) lru.setze('a', 1, 0) lru.setze('b', 2, 0) assert.equal(lru.groesse(), 2) lru.vergiss('a') lru.vergiss('gibt-es-nicht') assert.equal(lru.hole('a', 0), undefined) assert.equal(lru.hole('b', 0), 2) assert.equal(lru.groesse(), 1) lru.leere() assert.equal(lru.groesse(), 0) assert.equal(lru.hole('b', 0), undefined) }) test('LRU: Fehlschlag ist undefined, gespeicherte Werte wie null, 0 und leerer Text bleiben unterscheidbar', () => { const lru = neuerLru({ max: 5, ttlMs: 1000 }) assert.equal(lru.hole('fehlt', 0), undefined) lru.setze('n', null, 0) lru.setze('z', 0, 0) lru.setze('s', '', 0) assert.equal(lru.hole('n', 0), null) assert.equal(lru.hole('z', 0), 0) assert.equal(lru.hole('s', 0), '') }) test('LRU: max 1 behält nur den letzten Eintrag, die Größe überschreitet max nie', () => { const lru = neuerLru({ max: 1, ttlMs: 1000 }) lru.setze('a', 1, 0) lru.setze('b', 2, 0) assert.equal(lru.groesse(), 1) assert.equal(lru.hole('a', 0), undefined) assert.equal(lru.hole('b', 0), 2) const viele = neuerLru({ max: 50, ttlMs: 1000 }) for (let i = 0; i < 500; i += 1) viele.setze(`k${i}`, i, i) assert.equal(viele.groesse(), 50) assert.equal(viele.hole('k499', 500), 499) assert.equal(viele.hole('k449', 500), undefined) assert.equal(viele.hole('k450', 500), 450) }) test('LRU: ungültige Angaben werfen', () => { for (const max of [0, -1, 1.5, '3', null, undefined, Number.NaN, Infinity]) { assert.throws(() => neuerLru({ max, ttlMs: 1000 }), TypeError, `max ${String(max)}`) } for (const ttlMs of [0, -5, '1000', null, undefined, Number.NaN, Infinity]) { assert.throws(() => neuerLru({ max: 3, ttlMs }), TypeError, `ttlMs ${String(ttlMs)}`) } assert.throws(() => neuerLru(), TypeError) const lru = neuerLru({ max: 3, ttlMs: 1000 }) for (const jetzt of [undefined, null, '5', Number.NaN, new Date()]) { assert.throws(() => lru.hole('a', jetzt), TypeError, `hole nowMs ${String(jetzt)}`) assert.throws(() => lru.setze('a', 1, jetzt), TypeError, `setze nowMs ${String(jetzt)}`) } }) test('LRU: Instanzen teilen nichts', () => { const a = neuerLru({ max: 2, ttlMs: 1000 }) const b = neuerLru({ max: 2, ttlMs: 1000 }) a.setze('k', 1, 0) assert.equal(b.hole('k', 0), undefined) assert.equal(b.groesse(), 0) }) // ---------------------------------------------------------------- cache.js beforeEach(() => resetZeitenCacheForTests()) test('zeitenCache: 500 Einträge, 30 Sekunden', () => { for (let i = 0; i < 501; i += 1) zeitenCache.setze(`k${i}`, i, 0) assert.equal(zeitenCache.groesse(), 500) assert.equal(zeitenCache.hole('k0', 0), undefined, 'der älteste ist verdrängt') assert.equal(zeitenCache.hole('k1', 0), 1) assert.equal(zeitenCache.hole('k500', 29999), 500) assert.equal(zeitenCache.hole('k500', 30000), undefined) }) test('vergissZeiten entfernt nur den Eintrag dieses siteKeys', () => { zeitenCache.setze('abcdabcdabcdabcd', { aktiv: true }, 0) zeitenCache.setze('wxyzwxyzwxyzwxyz', { aktiv: true }, 0) vergissZeiten('abcdabcdabcdabcd') assert.equal(zeitenCache.hole('abcdabcdabcdabcd', 1), undefined) assert.deepEqual(zeitenCache.hole('wxyzwxyzwxyzwxyz', 1), { aktiv: true }) assert.doesNotThrow(() => vergissZeiten(undefined)) assert.doesNotThrow(() => vergissZeiten('unbekannt')) }) test('resetZeitenCacheForTests leert alles', () => { zeitenCache.setze('a', 1, 0) zeitenCache.setze('b', 2, 0) resetZeitenCacheForTests() assert.equal(zeitenCache.groesse(), 0) })