Files
Webklar-Kundenbereich/test/zeitenErinnerung.test.js
T
ClaudeandClaude Opus 5.5 c7cd436ec2 Öffnungszeiten: Mails K24–K27 heißen jetzt K27–K30, Feld k24At heißt einschaltMailAt
K24–K26 sind seit dem Webshop-Livegang (main) vergeben; der Mail-Lader sucht die Vorlage über den
Dateianfang, doppelte Nummern hätten die falsche Mail geschickt. Das Feld ist noch nicht in der
Produktion angelegt (zeiten-setup.mjs lief noch nicht), daher jetzt ein sprechender Name.
Spec und Plan behalten die alten Nummern (Hinweis oben im Dokument).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 06:00:17 +00:00

467 lines
21 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test, after, beforeEach, afterEach, mock } from 'node:test'
import assert from 'node:assert/strict'
import { createHash } from 'node:crypto'
import { startAppwriteMock } from './support/appwriteMock.js'
import { mailSink } from './support/mailSink.js'
import { schemaKeys } from './support/schema.js'
import { useTestEnv } from './support/testEnv.js'
import { ZEITEN_DATETIMES } from '../server/services/zeiten/schema.js'
// Feiertags-Erinnerung von Öffnungszeiten live (Aufgabe 23, Spec 7 "Feiertags-Erinnerung", Z18): erinnereFeiertage(now) schickt je Funktion
// (setup/active) eine Mail K28 mit allen neuen Tagen in 1–7 Tagen, die Feiertag oder "bitte prüfen" sind. Je Tag zuerst der Merker
// sha1(featureId|datum) (409 = schon erinnert), danach erst die Mail: lieber einmal zu wenig als doppelt. Nur Schalter pilot/on (pilot: nur
// freigegebene Kunden), nur 08:00 bis 20:00 Berliner Zeit, kein BCC ans Team, Merker älter als 30 Tage werden gelöscht.
// Aufbau wie zeitenAboAbgleich.test.js: Appwrite-Nachbau mit Schema, Mails in der Senke, feste Zeit als Parameter.
const DATETIMES = {
...ZEITEN_DATETIMES,
portalFeatures: ['orderedAt', 'activatedAt', 'disabledAt', 'consentAt', 'aboEndeMailAt', 'aboEndeAt', 'einschaltMailAt'],
}
const aw = await startAppwriteMock({
schema: {
portalFeatures: schemaKeys('portalFeatures'),
oeffnungszeiten: schemaKeys('oeffnungszeiten'),
zeitenErinnerungen: schemaKeys('zeitenErinnerungen'),
},
datetimes: DATETIMES,
defaultLimit: 25,
})
after(() => aw.close())
useTestEnv({ appwriteUrl: aw.url, stripeUrl: 'http://127.0.0.1:1' })
const { config } = await import('../server/config.js')
const { deleteDocument } = await import('../server/services/appwriteAdmin.js')
const { setMailTransportForTests } = await import('../server/services/mailer.js')
const { speichereZeiten } = await import('../server/services/zeiten/speicher.js')
const { resetZeitenCacheForTests } = await import('../server/services/zeiten/cache.js')
const { erinnereFeiertage } = await import('../server/services/zeiten/erinnerung.js')
after(() => setMailTransportForTests(null))
const EIN = new Date('2026-09-28T07:00:00.000Z') // Montag 28.09.2026, 09:00 Berlin (Sommerzeit): in 7 Tagen liegt Sa 03.10. (Einheit)
const ZWEI = new Date('2027-03-22T08:00:00.000Z') // Montag 22.03.2027, 09:00 Berlin: Karfreitag 26.03. und Ostermontag 29.03.
const KUNDE = 'c1'
const MODUS_VORHER = config.zeiten.mode
const PILOT_VORHER = [...config.zeiten.pilotEmails]
const BASIS_VORHER = config.publicBaseUrl
const plusMs = (von, ms) => new Date(von.getTime() + ms)
const STUNDE = 3600 * 1000
const sha36 = (featureId, datum) => createHash('sha1').update(`${featureId}|${datum}`).digest('hex').slice(0, 36)
let sink
let log
async function raeumeAuf() {
for (const collection of ['portalFeatures', 'oeffnungszeiten', 'zeitenErinnerungen', 'customers', 'websiteProjects']) {
for (const id of [...aw.docs(collection).keys()]) await deleteDocument(collection, id)
}
resetZeitenCacheForTests()
aw.seed('customers', { $id: KUNDE, name: 'Silke Müller', email: 'silke@example.com' })
}
beforeEach(async () => {
await raeumeAuf()
aw.resetListCalls()
config.zeiten.mode = 'on'
config.zeiten.pilotEmails = []
config.publicBaseUrl = 'https://project.webklar.com'
sink = mailSink()
setMailTransportForTests(sink)
log = { error: mock.method(console, 'error', () => {}), warn: mock.method(console, 'warn', () => {}), log: mock.method(console, 'log', () => {}) }
})
afterEach(() => {
// Das Log enthält nie Adresse oder Namen des Kunden
const text = Object.values(log).flatMap((m) => m.mock.calls.map((aufruf) => aufruf.arguments.map(String).join(' '))).join('\n')
mock.restoreAll()
config.zeiten.mode = MODUS_VORHER
config.zeiten.pilotEmails = PILOT_VORHER
config.publicBaseUrl = BASIS_VORHER
assert.doesNotMatch(text, /silke@example\.com|Silke|Müller|salon\.example/i, 'keine Adresse und kein Name des Kunden im Log')
})
// ---------------------------------------------------------------- Hilfen
const funktionsId = (nr = 1) => `f_p${nr}_zeiten`
/** Wochenplan: jeden Tag (auch Samstag und Sonntag) 09:00–18:00, damit jeder Feiertag eine Erinnerung auslöst. */
const OFFEN = () => {
const t = {}
for (let d = 1; d <= 7; d += 1) t[d] = { modus: 'zeiten', fenster: [['09:00', '18:00']] }
return t
}
const eingabe = (tage = OFFEN()) => ({
land: 'RP',
plaene: [{ id: 'p_aaaaaaa1', name: 'Standard', haupt: true, tage }],
feiertage: {},
ausnahmen: [],
hinweise: [],
kontakt: { an: false, telefon: '', adresse: '', whatsapp: null },
})
/** Funktion samt gespeicherten Zeiten (zeiten false: Dokument ohne Eingaben des Kunden). */
async function legeFunktion(nr = 1, over = {}, { kunde = KUNDE, zeiten = true, tage = OFFEN() } = {}) {
aw.seed('websiteProjects', { $id: `p${nr}`, customerId: kunde, projectName: `Praxis ${nr}`, subdomain: `praxis${nr}` })
aw.seed('portalFeatures', {
$id: funktionsId(nr),
customerId: kunde,
projectId: `p${nr}`,
featureKey: 'zeiten',
state: 'active',
enabled: true,
unlockedBy: 'abo',
siteKey: `abcdefghijklmno${'pqrstuv'[nr]}`,
...over,
})
const doc = aw.docs('portalFeatures').get(funktionsId(nr))
if (zeiten) await speichereZeiten({ feature: doc, eingabe: eingabe(tage), version: 0, now: new Date('2026-09-20T10:00:00.000Z') })
return doc
}
const merker = () => [...aw.docs('zeitenErinnerungen').values()]
const mails = () => sink.sent
const logText = () => Object.values(log).flatMap((m) => m.mock.calls.map((aufruf) => aufruf.arguments.map(String).join(' '))).join('\n')
/** Ein Merker wie ihn ein früherer Lauf angelegt hat. */
const legeMerker = (featureId, datum, createdAt = '2026-09-27T10:00:00.000Z') =>
aw.seed('zeitenErinnerungen', { $id: sha36(featureId, datum), featureId, datum, createdAt })
const ergebnis = (over) => ({ geprueft: 0, mails: 0, merker: 0, fehler: 0, ...over })
// ---------------------------------------------------------------- Mail und Merker
test('eine Mail für zwei Feiertage im Fenster', async () => {
await legeFunktion(1)
assert.deepEqual(await erinnereFeiertage(ZWEI), ergebnis({ geprueft: 1, mails: 1, merker: 2 }))
assert.equal(mails().length, 1, 'eine Mail, nicht eine je Tag')
const mail = mails()[0]
assert.deepEqual(mail.to.map((a) => a.address), ['silke@example.com'])
assert.equal(mail.subject, 'Feiertage stehen an – stimmen Deine Zeiten?')
assert.match(mail.text, /Hallo Silke Müller,/)
assert.match(mail.text, /Fr 26\.03\. – Karfreitag\nMo 29\.03\. – Ostermontag/, 'beide Tage, in Datumsfolge')
assert.match(mail.text, /Praxis 1/, 'der Projektname steht in der Mail')
assert.match(mail.text, /https:\/\/project\.webklar\.com\/dashboard\.html#zeiten/, 'Knopf zum Editor')
assert.ok(!mail.bcc?.length, 'K28 geht ohne Kopie ans Team (R17c)')
assert.deepEqual(merker().map((m) => m.datum).sort(), ['2027-03-26', '2027-03-29'])
})
test('Merker: Dokument-ID sha1(featureId|datum), 36 Zeichen, mit featureId, Datum und createdAt', async () => {
await legeFunktion(1)
await erinnereFeiertage(EIN)
assert.equal(merker().length, 1)
const m = merker()[0]
assert.equal(m.$id, sha36(funktionsId(1), '2026-10-03'))
assert.match(m.$id, /^[0-9a-f]{36}$/)
assert.equal(m.featureId, funktionsId(1))
assert.equal(m.datum, '2026-10-03')
assert.equal(new Date(m.createdAt).getTime(), EIN.getTime(), 'createdAt = Zeitpunkt des Laufs')
})
test('Sa 03.10. wird erinnert, wenn der Samstag im Hauptplan offen ist (Tag der Deutschen Einheit)', async () => {
await legeFunktion(1)
assert.deepEqual(await erinnereFeiertage(EIN), ergebnis({ geprueft: 1, mails: 1, merker: 1 }))
assert.match(mails()[0].text, /Sa 03\.10\. – Tag der Deutschen Einheit/)
assert.doesNotMatch(mails()[0].text, /mehrere Tage/, 'ein Tag: Einzahl')
})
test('Tag, an dem der Hauptplan ohnehin geschlossen ist: keine Mail, kein Merker', async () => {
const tage = OFFEN()
tage[6] = { modus: 'zu', fenster: [] } // Samstag zu
await legeFunktion(1, {}, { tage })
assert.deepEqual(await erinnereFeiertage(EIN), ergebnis({ geprueft: 1 }))
assert.equal(mails().length, 0)
assert.equal(merker().length, 0)
})
test('zweiter Lauf: keine zweite Mail (Merker), auch eine Stunde und einen Tag später', async () => {
await legeFunktion(1)
assert.equal((await erinnereFeiertage(EIN)).mails, 1)
assert.deepEqual(await erinnereFeiertage(EIN), ergebnis({ geprueft: 1 }))
assert.deepEqual(await erinnereFeiertage(plusMs(EIN, STUNDE)), ergebnis({ geprueft: 1 }))
assert.deepEqual(await erinnereFeiertage(plusMs(EIN, 24 * STUNDE)), ergebnis({ geprueft: 1 }))
assert.equal(mails().length, 1)
assert.equal(merker().length, 1)
})
test('Neustart zwischen Merker und Mail: Merker existiert, Mail fehlt ⇒ nichts senden', async () => {
await legeFunktion(1)
legeMerker(funktionsId(1), '2026-10-03')
assert.deepEqual(await erinnereFeiertage(EIN), ergebnis({ geprueft: 1 }))
assert.equal(mails().length, 0, 'lieber einmal zu wenig als doppelt')
assert.equal(merker().length, 1)
})
test('nur die neuen Tage stehen in der Mail: ein Tag war schon vorgemerkt', async () => {
await legeFunktion(1)
legeMerker(funktionsId(1), '2027-03-26') // Karfreitag war schon dran
assert.deepEqual(await erinnereFeiertage(ZWEI), ergebnis({ geprueft: 1, mails: 1, merker: 1 }))
assert.equal(mails().length, 1)
assert.match(mails()[0].text, /Mo 29\.03\. – Ostermontag/)
assert.doesNotMatch(mails()[0].text, /Karfreitag/)
assert.deepEqual(merker().map((m) => m.datum).sort(), ['2027-03-26', '2027-03-29'])
})
test('Heiligabend ist „bitte prüfen“: mit Hinweis in der Mail, alle drei Tage in einer Mail', async () => {
await legeFunktion(1)
assert.deepEqual(await erinnereFeiertage(new Date('2026-12-22T08:00:00.000Z')), ergebnis({ geprueft: 1, mails: 1, merker: 3 }))
assert.match(mails()[0].text, /Do 24\.12\. – Heiligabend \(bitte prüfen\)\nFr 25\.12\. – 1\. Weihnachtstag\nSa 26\.12\. – 2\. Weihnachtstag/)
})
test('zwei Läufe gleichzeitig: genau eine Mail (der Merker entscheidet, wer sendet)', async () => {
await legeFunktion(1)
const [a, b] = await Promise.all([erinnereFeiertage(EIN), erinnereFeiertage(EIN)])
assert.equal(a.mails + b.mails, 1)
assert.equal(a.merker + b.merker, 1)
assert.equal(mails().length, 1)
})
test('zwei Funktionen eines Kunden: je Dokument eine Mail mit dem eigenen Projekt', async () => {
await legeFunktion(1)
await legeFunktion(2)
assert.deepEqual(await erinnereFeiertage(EIN), ergebnis({ geprueft: 2, mails: 2, merker: 2 }))
assert.equal(mails().length, 2)
assert.deepEqual(mails().map((m) => /Praxis (\d)/.exec(m.text)[1]).sort(), ['1', '2'])
assert.deepEqual(merker().map((m) => m.featureId).sort(), [funktionsId(1), funktionsId(2)])
})
// ---------------------------------------------------------------- Fenster und Schalter
test('außerhalb 08–20 Uhr Berliner Zeit: nichts (keine Abfrage, kein Merker, keine Mail)', async () => {
await legeFunktion(1)
for (const [uhr, utc] of [['07:59', '05:59'], ['20:00', '18:00'], ['23:30', '21:30'], ['03:00', '01:00']]) {
aw.resetListCalls()
const jetzt = new Date(`2026-09-28T${utc}:00.000Z`)
assert.deepEqual(await erinnereFeiertage(jetzt), ergebnis(), `${uhr} Berlin`)
assert.equal(aw.listCalls().length, 0, `${uhr}: nicht einmal eine Abfrage`)
}
assert.equal(mails().length, 0)
assert.equal(merker().length, 0)
// Grenzen: 08:00 und 19:59 gehören noch zum Fenster
assert.equal((await erinnereFeiertage(new Date('2026-09-28T06:00:00.000Z'))).mails, 1, '08:00 Berlin')
await raeumeAuf()
await legeFunktion(1)
assert.equal((await erinnereFeiertage(new Date('2026-09-28T17:59:00.000Z'))).mails, 1, '19:59 Berlin')
})
test('Schalter off: nichts, auch keine Abfrage', async () => {
await legeFunktion(1)
config.zeiten.mode = 'off'
assert.deepEqual(await erinnereFeiertage(EIN), ergebnis())
assert.equal(aw.listCalls().length, 0)
assert.equal(mails().length, 0)
assert.equal(merker().length, 0)
config.zeiten.mode = 'unbekannt'
assert.deepEqual(await erinnereFeiertage(EIN), ergebnis(), 'ein unbekannter Wert gilt als off')
assert.equal(mails().length, 0)
})
test('pilot: nur Kunden der Pilotliste; die anderen bekommen weder Mail noch Merker', async () => {
aw.seed('customers', { $id: 'c2', name: 'Peter Fremd', email: 'peter@salon.example' })
await legeFunktion(1)
await legeFunktion(2, {}, { kunde: 'c2' })
config.zeiten.mode = 'pilot'
config.zeiten.pilotEmails = ['silke@example.com']
assert.deepEqual(await erinnereFeiertage(EIN), ergebnis({ geprueft: 1, mails: 1, merker: 1 }))
assert.deepEqual(mails()[0].to.map((a) => a.address), ['silke@example.com'])
assert.deepEqual(merker().map((m) => m.featureId), [funktionsId(1)])
// Pilotliste leer: niemand
await raeumeAuf()
aw.seed('customers', { $id: 'c2', name: 'Peter Fremd', email: 'peter@salon.example' })
await legeFunktion(1)
config.zeiten.pilotEmails = []
const vorher = mails().length
assert.deepEqual(await erinnereFeiertage(EIN), ergebnis())
assert.equal(mails().length, vorher)
// '*' = alle
config.zeiten.pilotEmails = ['*']
assert.equal((await erinnereFeiertage(EIN)).mails, 1)
})
test('Schalter on: alle Kunden', async () => {
aw.seed('customers', { $id: 'c2', name: 'Peter Fremd', email: 'peter@salon.example' })
await legeFunktion(1)
await legeFunktion(2, {}, { kunde: 'c2' })
config.zeiten.pilotEmails = [] // die Pilotliste zählt bei on nicht
assert.deepEqual(await erinnereFeiertage(EIN), ergebnis({ geprueft: 2, mails: 2, merker: 2 }))
assert.deepEqual(mails().flatMap((m) => m.to.map((a) => a.address)).sort(), ['peter@salon.example', 'silke@example.com'])
})
// ---------------------------------------------------------------- Welche Funktionen
test('nur Zustand setup und active; setup zählt, andere Zustände und andere Funktionen nicht', async () => {
await legeFunktion(1, { state: 'setup' })
await legeFunktion(2, { state: 'active' })
await legeFunktion(3, { state: 'disabled', disabledReason: 'abo_ende' })
await legeFunktion(4, { state: 'ordered' })
await legeFunktion(5, { state: 'off' })
await legeFunktion(6, { featureKey: 'termine' })
await legeFunktion(7, { featureKey: 'news' })
assert.deepEqual(await erinnereFeiertage(EIN), ergebnis({ geprueft: 2, mails: 2, merker: 2 }))
assert.deepEqual(merker().map((m) => m.featureId).sort(), [funktionsId(1), funktionsId(2)])
})
test('Funktion ohne gespeicherte Zeiten: übersprungen (nichts zu erinnern)', async () => {
await legeFunktion(1, {}, { zeiten: false })
assert.deepEqual(await erinnereFeiertage(EIN), ergebnis())
// Dokument vom Team angelegt, aber noch ohne Eingaben des Kunden (daten null)
aw.seed('oeffnungszeiten', { $id: funktionsId(1), customerId: KUNDE, projectId: 'p1', featureId: funktionsId(1), version: 0, teamVersion: 1, team: '{}', updatedAt: EIN.toISOString(), updatedBy: 'team:s1' })
assert.deepEqual(await erinnereFeiertage(EIN), ergebnis())
assert.equal(mails().length, 0)
assert.equal(merker().length, 0)
})
test('Kunde fehlt oder ohne E-Mail-Adresse: übersprungen, kein Merker, Log ohne Daten', async () => {
await legeFunktion(1, {}, { kunde: 'c_weg' }) // Kundendokument fehlt
aw.seed('customers', { $id: 'c_leer', name: 'Ohne Adresse', email: '' })
await legeFunktion(2, {}, { kunde: 'c_leer' })
const erg = await erinnereFeiertage(EIN)
assert.equal(erg.mails, 0)
assert.equal(erg.merker, 0, 'ohne Adresse wird kein Merker verbraucht: der Tag kann später erinnert werden')
assert.equal(erg.fehler, 0, 'kein Fehler des Laufs')
assert.equal(mails().length, 0)
assert.equal(merker().length, 0)
assert.match(logText(), /Funktion f_p1_zeiten/)
assert.match(logText(), /Funktion f_p2_zeiten/)
// Adresse nachgetragen: der nächste Lauf erinnert
aw.seed('customers', { $id: 'c_leer', name: 'Ohne Adresse', email: 'neu@example.com' })
assert.deepEqual(await erinnereFeiertage(EIN), ergebnis({ geprueft: 2, mails: 1, merker: 1 }))
assert.deepEqual(mails()[0].to.map((a) => a.address), ['neu@example.com'])
})
// ---------------------------------------------------------------- Fehler
test('Mail scheitert: Merker bleibt (höchstens einmal), Fehler gezählt, der Lauf geht weiter', async () => {
aw.seed('customers', { $id: 'c2', name: 'Peter Fremd', email: 'peter@salon.example' })
await legeFunktion(1)
await legeFunktion(2, {}, { kunde: 'c2' })
// der erste Versand scheitert, der zweite geht durch
const roh = sink.sendMail.bind(sink)
let aufrufe = 0
sink.sendMail = async (mail) => {
aufrufe += 1
if (aufrufe === 1) throw new Error('SMTP down für silke@example.com')
return roh(mail)
}
assert.deepEqual(await erinnereFeiertage(EIN), ergebnis({ geprueft: 2, mails: 1, merker: 2, fehler: 1 }))
assert.equal(mails().length, 1)
assert.equal(merker().length, 2, 'beide Merker stehen, auch der der gescheiterten Mail')
// der nächste Lauf wiederholt nicht
assert.deepEqual(await erinnereFeiertage(EIN), ergebnis({ geprueft: 2 }))
assert.equal(mails().length, 1)
})
test('Lesefehler bei einem Dokument stoppt weder die anderen noch den Lauf', async () => {
await legeFunktion(1)
await legeFunktion(2)
// die erste Abfrage der Zeiten (Dokument f_p1_zeiten, Reihenfolge nach $id) scheitert
aw.failNext({ collection: 'oeffnungszeiten', method: 'GET', status: 500, message: 'kaputt' })
const erg = await erinnereFeiertage(EIN)
assert.equal(erg.fehler, 1)
assert.equal(erg.mails, 1, 'das zweite Dokument wird trotzdem erinnert')
assert.equal(merker().length, 1)
})
test('Merker lässt sich nicht anlegen (500): keine Mail für den Tag, Fehler gezählt, nächster Lauf versucht es wieder', async () => {
await legeFunktion(1)
aw.failNext({ collection: 'zeitenErinnerungen', method: 'POST', status: 500, message: 'db down' })
assert.deepEqual(await erinnereFeiertage(EIN), ergebnis({ geprueft: 1, fehler: 1 }))
assert.equal(mails().length, 0, 'ohne Merker keine Mail')
assert.equal(merker().length, 0)
assert.deepEqual(await erinnereFeiertage(plusMs(EIN, STUNDE)), ergebnis({ geprueft: 1, mails: 1, merker: 1 }))
})
test('Funktionen nicht lesbar: kein Wurf, Fehler gezählt', async () => {
await legeFunktion(1)
aw.failNext({ collection: 'portalFeatures', method: 'GET', status: 500, message: 'db down' })
assert.deepEqual(await erinnereFeiertage(EIN), ergebnis({ fehler: 1 }))
assert.equal(mails().length, 0)
})
test('ohne gültiges now: TypeError (wie der Abo-Abgleich, die Jobs fangen ihn)', async () => {
await assert.rejects(() => erinnereFeiertage(undefined), TypeError)
await assert.rejects(() => erinnereFeiertage(new Date('kaputt')), TypeError)
})
// ---------------------------------------------------------------- Aufräumen
test('alte Merker werden aufgeräumt: Datum mehr als 30 Tage her', async () => {
await legeFunktion(1)
// heute (Berlin) = 2026-09-28; Grenze: 2026-08-29 bleibt (genau 30 Tage), 2026-08-28 und älter geht
legeMerker('f_alt', '2026-08-28')
legeMerker('f_alt', '2026-01-02')
legeMerker('f_alt', '2026-08-29')
legeMerker('f_alt', '2026-09-01')
legeMerker('f_neu', '2026-10-03')
await erinnereFeiertage(EIN)
assert.deepEqual(merker().map((m) => m.datum).sort(), ['2026-08-29', '2026-09-01', '2026-10-03', '2026-10-03'])
assert.ok(!aw.docs('zeitenErinnerungen').has(sha36('f_alt', '2026-08-28')))
assert.ok(!aw.docs('zeitenErinnerungen').has(sha36('f_alt', '2026-01-02')))
assert.ok(aw.docs('zeitenErinnerungen').has(sha36('f_alt', '2026-08-29')))
})
test('Aufräumen auch ohne Funktionen, aber nur im Fenster und nur bei Schalter pilot/on', async () => {
legeMerker('f_alt', '2026-01-02')
config.zeiten.mode = 'off'
await erinnereFeiertage(EIN)
assert.equal(merker().length, 1, 'Schalter off: nichts angefasst')
config.zeiten.mode = 'on'
await erinnereFeiertage(new Date('2026-09-28T05:00:00.000Z')) // 07:00 Berlin
assert.equal(merker().length, 1, 'außerhalb des Fensters: nichts angefasst')
await erinnereFeiertage(EIN)
assert.equal(merker().length, 0)
})
test('mehr alte Merker als eine Seite: alle werden gelöscht', async () => {
for (let i = 0; i < 130; i += 1) legeMerker(`f_alt${i}`, '2026-01-02')
legeMerker('f_neu', '2026-10-03')
await erinnereFeiertage(EIN)
assert.deepEqual(merker().map((m) => m.datum), ['2026-10-03'])
})
test('Löschen eines alten Merkers scheitert: Fehler gezählt, die Erinnerung läuft trotzdem', async () => {
await legeFunktion(1)
legeMerker('f_alt', '2026-01-02')
aw.failNext({ collection: 'zeitenErinnerungen', method: 'DELETE', status: 500, message: 'db down' })
const erg = await erinnereFeiertage(EIN)
assert.equal(erg.mails, 1)
assert.equal(erg.fehler, 1)
assert.ok(aw.docs('zeitenErinnerungen').has(sha36('f_alt', '2026-01-02')), 'bleibt für den nächsten Lauf')
assert.equal((await erinnereFeiertage(plusMs(EIN, STUNDE))).fehler, 0)
assert.ok(!aw.docs('zeitenErinnerungen').has(sha36('f_alt', '2026-01-02')))
})