LRU-Speicher mit Ablaufzeit (lru.js), Cache-Instanz der öffentlichen Antwort (cache.js: 500 Einträge, 30 s) und Appwrite-Speicher mit getrennten Versionen für Kundendaten und Team-Einstellungen (speicher.js), alle Schreibzugriffe unter der Sperre zeiten:<projectId>. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
153 lines
6.0 KiB
JavaScript
153 lines
6.0 KiB
JavaScript
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)
|
|
})
|