loeschKandidaten liest eine Seite (Standard 100, aelteste Bezugszeit zuerst, optional je Funktion) ohne Gastfelder; die Frist steht in der Abfrage. arbeiteSeiten (Seite lesen, bearbeiten, neu lesen) endet bei leerer Seite, am Deckel (config.termine.loeschDeckel, 2000 je Status und Lauf, Rest an den Folgetagen, gezaehlt als gedeckelt) oder bei einer Seite ohne Fortschritt. verfallen, abgesagt und abgelehnt laufen darueber, unter der Buchungssperre werden Status und Bezugszeit frisch geprueft. taeglicherJob liefert gedeckelt getrennt vom Loeschzaehler. Neue Indizes idx_status_ends und idx_status_updated auf termineBookings (scripts/termine-setup.mjs legt sie an). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
157 lines
6.1 KiB
JavaScript
157 lines
6.1 KiB
JavaScript
import { test, beforeEach, afterEach } from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { config } from '../server/config.js'
|
|
import { termineAus, termineFuerKunde, termineModus } from '../server/services/termine/schalter.js'
|
|
|
|
const TERMINE_ENV = [
|
|
'TERMINE_ENABLED',
|
|
'TERMINE_PILOT_EMAILS',
|
|
'TERMINE_FORM_MIN_SECONDS',
|
|
'TERMINE_MIN_ANTWORT_MS',
|
|
'APPWRITE_COLLECTION_TERMINE_CONFIG',
|
|
'APPWRITE_COLLECTION_TERMINE_BOOKINGS',
|
|
'APPWRITE_COLLECTION_TERMINE_CLAIMS',
|
|
'APPWRITE_COLLECTION_TERMINE_OUTBOX',
|
|
]
|
|
|
|
let original
|
|
beforeEach(() => {
|
|
original = { ...config.termine }
|
|
})
|
|
afterEach(() => {
|
|
Object.assign(config.termine, original)
|
|
})
|
|
|
|
/** Frische config.js-Instanz mit genau diesen Termine-Umgebungsvariablen (alle anderen entfernt). */
|
|
let freshCounter = 0
|
|
async function freshConfig(env = {}) {
|
|
const saved = Object.fromEntries(TERMINE_ENV.map((key) => [key, process.env[key]]))
|
|
for (const key of TERMINE_ENV) delete process.env[key]
|
|
Object.assign(process.env, env)
|
|
try {
|
|
freshCounter += 1
|
|
return (await import(`../server/config.js?termine-test=${freshCounter}`)).config
|
|
} finally {
|
|
for (const key of TERMINE_ENV) {
|
|
if (saved[key] === undefined) delete process.env[key]
|
|
else process.env[key] = saved[key]
|
|
}
|
|
}
|
|
}
|
|
|
|
test('Konfiguration: Vorgaben ohne Umgebungsvariablen', async () => {
|
|
const fresh = await freshConfig()
|
|
assert.deepEqual(fresh.termine, {
|
|
mode: 'off',
|
|
pilotEmails: [],
|
|
holdMinutes: 30,
|
|
zelleMin: 5,
|
|
mailsProStunde: 200,
|
|
loeschDeckel: 2000,
|
|
formMinSekunden: 3,
|
|
minAntwortMs: 300,
|
|
})
|
|
assert.equal(fresh.collections.termineConfig, 'termineConfig')
|
|
assert.equal(fresh.collections.termineBookings, 'termineBookings')
|
|
assert.equal(fresh.collections.termineClaims, 'termineClaims')
|
|
assert.equal(fresh.collections.termineOutbox, 'termineOutbox')
|
|
})
|
|
|
|
test('Konfiguration: Umgebungsvariablen werden gelesen und bereinigt', async () => {
|
|
const fresh = await freshConfig({
|
|
TERMINE_ENABLED: ' Pilot ',
|
|
TERMINE_PILOT_EMAILS: ' Kenso.Gri@Gmail.com, ,justin@webklar.com ',
|
|
TERMINE_FORM_MIN_SECONDS: '0',
|
|
TERMINE_MIN_ANTWORT_MS: '0',
|
|
APPWRITE_COLLECTION_TERMINE_CONFIG: 'tConfig',
|
|
APPWRITE_COLLECTION_TERMINE_BOOKINGS: 'tBookings',
|
|
APPWRITE_COLLECTION_TERMINE_CLAIMS: 'tClaims',
|
|
APPWRITE_COLLECTION_TERMINE_OUTBOX: 'tOutbox',
|
|
})
|
|
assert.equal(fresh.termine.mode, 'pilot')
|
|
assert.deepEqual(fresh.termine.pilotEmails, ['kenso.gri@gmail.com', 'justin@webklar.com'])
|
|
assert.equal(fresh.termine.formMinSekunden, 0)
|
|
assert.equal(fresh.termine.minAntwortMs, 0)
|
|
assert.deepEqual(
|
|
[fresh.collections.termineConfig, fresh.collections.termineBookings, fresh.collections.termineClaims, fresh.collections.termineOutbox],
|
|
['tConfig', 'tBookings', 'tClaims', 'tOutbox'],
|
|
)
|
|
})
|
|
|
|
test('Konfiguration: leerer oder unbekannter Schalter ist off, ungueltige Sekunden fallen auf 3', async () => {
|
|
for (const value of ['', ' ', 'banane', 'true', '1', 'ON ']) {
|
|
const fresh = await freshConfig({ TERMINE_ENABLED: value })
|
|
assert.equal(fresh.termine.mode, value.trim().toLowerCase() === 'on' ? 'on' : 'off', `TERMINE_ENABLED=${JSON.stringify(value)}`)
|
|
}
|
|
for (const value of ['', 'abc', '-1', 'NaN']) {
|
|
const fresh = await freshConfig({ TERMINE_FORM_MIN_SECONDS: value })
|
|
assert.equal(fresh.termine.formMinSekunden, 3, `TERMINE_FORM_MIN_SECONDS=${JSON.stringify(value)}`)
|
|
}
|
|
assert.equal((await freshConfig({ TERMINE_FORM_MIN_SECONDS: '7' })).termine.formMinSekunden, 7)
|
|
for (const value of ['', 'abc', '-1', 'NaN']) {
|
|
const fresh = await freshConfig({ TERMINE_MIN_ANTWORT_MS: value })
|
|
assert.equal(fresh.termine.minAntwortMs, 300, `TERMINE_MIN_ANTWORT_MS=${JSON.stringify(value)}`)
|
|
}
|
|
assert.equal((await freshConfig({ TERMINE_MIN_ANTWORT_MS: '750' })).termine.minAntwortMs, 750)
|
|
})
|
|
|
|
test('termineModus: leer und unbekannt sind off, pilot und on bleiben', () => {
|
|
for (const value of [undefined, null, '', 'banane', 'PILOT', 'true']) {
|
|
config.termine.mode = value
|
|
assert.equal(termineModus(), 'off', `mode=${JSON.stringify(value)}`)
|
|
assert.equal(termineAus(), true)
|
|
}
|
|
config.termine.mode = 'pilot'
|
|
assert.equal(termineModus(), 'pilot')
|
|
assert.equal(termineAus(), false)
|
|
config.termine.mode = 'on'
|
|
assert.equal(termineModus(), 'on')
|
|
assert.equal(termineAus(), false)
|
|
})
|
|
|
|
test('termineModus: Aenderung von config.termine.mode wirkt sofort', () => {
|
|
config.termine.mode = 'off'
|
|
assert.equal(termineModus(), 'off')
|
|
config.termine.mode = 'on'
|
|
assert.equal(termineModus(), 'on')
|
|
config.termine.mode = 'off'
|
|
assert.equal(termineAus(), true)
|
|
})
|
|
|
|
test('termineFuerKunde: off sperrt alle, auch mit Stern in der Liste', () => {
|
|
config.termine.mode = 'off'
|
|
config.termine.pilotEmails = ['*', 'kenso.gri@gmail.com']
|
|
assert.equal(termineFuerKunde('kenso.gri@gmail.com'), false)
|
|
assert.equal(termineFuerKunde('jemand@example.com'), false)
|
|
})
|
|
|
|
test('termineFuerKunde: pilot erlaubt nur Adressen der Liste, Schreibweise egal', () => {
|
|
config.termine.mode = 'pilot'
|
|
config.termine.pilotEmails = ['kenso.gri@gmail.com']
|
|
assert.equal(termineFuerKunde('KENSO.gri@gmail.com '), true)
|
|
assert.equal(termineFuerKunde('kenso.gri@gmail.com'), true)
|
|
assert.equal(termineFuerKunde('jemand@example.com'), false)
|
|
assert.equal(termineFuerKunde('kenso.gri@gmail.com.evil.de'), false)
|
|
})
|
|
|
|
test('termineFuerKunde: pilot mit Stern erlaubt alle, mit leerer Liste niemanden', () => {
|
|
config.termine.mode = 'pilot'
|
|
config.termine.pilotEmails = ['*']
|
|
assert.equal(termineFuerKunde('jemand@example.com'), true)
|
|
config.termine.pilotEmails = []
|
|
assert.equal(termineFuerKunde('kenso.gri@gmail.com'), false)
|
|
})
|
|
|
|
test('termineFuerKunde: pilot ohne E-Mail-Adresse ist false', () => {
|
|
config.termine.mode = 'pilot'
|
|
config.termine.pilotEmails = ['kenso.gri@gmail.com']
|
|
for (const email of [undefined, null, '', ' ']) assert.equal(termineFuerKunde(email), false)
|
|
})
|
|
|
|
test('termineFuerKunde: on gilt fuer alle ohne E-Mail-Filter', () => {
|
|
config.termine.mode = 'on'
|
|
config.termine.pilotEmails = ['kenso.gri@gmail.com']
|
|
assert.equal(termineFuerKunde('jemand@example.com'), true)
|
|
assert.equal(termineFuerKunde(undefined), true)
|
|
})
|