Files
ClaudeandClaude Opus 5.5 097036c9a5 Öffnungszeiten: Speicher mit Versionen und Cache
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>
2026-10-05 00:50:41 +00:00

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