Löschung nach 30 Tagen: Abo-Vorprüfung, Funktionsdokument danach auf off

Bei Grund abo_ende prüft die Löschung vor jedem unumkehrbaren Schritt frisch,
ob das Wartungs-Abo wieder läuft (Lesefehler: nichts löschen). Sind alle
Buchungen und die Einstellung weg, wird das Funktionsdokument unter
feature-admin und termine-cfg auf off zurückgesetzt (siteKey bleibt).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-04 17:57:41 +00:00
1 parent 91e7f7d8a3
commit 30701ba277
2 files changed
+408 -47

No files matched your search

+290 -10
View File
@@ -37,6 +37,7 @@ const { speichereEinstellung, resetKontextForTests } = await import('../server/s
const { belegeZellen } = await import('../server/services/termine/zellen.js')
const { bestaetige } = await import('../server/services/termine/ablauf.js')
const { disable } = await import('../server/services/siteFeatures.js')
const { withLocks } = await import('../server/services/keyedLock.js')
const J = await import('../server/services/termine/jobs.js')
const { minutenJob, fuenfMinutenJob, taeglicherJob, tagesbericht, wendeLoeschfristenAn, loescheAbgeschalteteFunktionen, resetJobsForTests } = J
after(() => setMailTransportForTests(null))
@@ -1439,7 +1440,7 @@ test('tagesbericht: das Segment zählt die Schritte aller Bereiche und fehlt bei
assert.equal(mit, ohne.replace(', 9 Fehler', ', Löschläufe am Deckel: 4, Rest folgt an den nächsten Tagen, 9 Fehler'))
})
test('loescheAbgeschalteteFunktionen: disabled länger als 30 Tage ⇒ Buchungen, Zellen und Einstellung weg; alles andere bleibt', async () => {
test('loescheAbgeschalteteFunktionen: disabled länger als 30 Tage ⇒ Buchungen, Zellen und Einstellung weg, das Funktionsdokument danach auf off; alles andere bleibt', async () => {
const seit = (tage, zusatz = 0) => iso(plus(JETZT, -tage * TAG + zusatz))
await richteFunktionEin(F1, { state: 'disabled', disabledAt: seit(31) })
await richteFunktionEin(F2, { state: 'disabled', disabledAt: seit(30, 1) }) // noch knapp unter 30 Tagen
@@ -1447,6 +1448,8 @@ test('loescheAbgeschalteteFunktionen: disabled länger als 30 Tage ⇒ Buchungen
await richteFunktionEin('f_p_projekt04_termine', { state: 'disabled', disabledAt: seit(90), featureKey: 'news' }) // andere Funktion
await richteFunktionEin('f_p_projekt05_termine', { state: 'disabled' }) // kein disabledAt
const funktionen = [F1, F2, F3, 'f_p_projekt04_termine', 'f_p_projekt05_termine']
const andere = funktionen.slice(1)
const vorher = Object.fromEntries(andere.map((id) => [id, structuredClone(aw.docs('portalFeatures').get(id))]))
for (const [i, featureId] of funktionen.entries()) {
await legeBuchung(`b_fest_${i}`, { featureId, status: 'bestaetigt' })
await legeBuchung(`b_alt_${i}`, { featureId, status: 'erledigt', ende: plus(JETZT, -2 * TAG) })
@@ -1462,8 +1465,33 @@ test('loescheAbgeschalteteFunktionen: disabled länger als 30 Tage ⇒ Buchungen
assert.equal(aw.docs('termineConfig').get(F1), undefined, 'Einstellung gelöscht')
assert.equal(buchungen().length, 8, 'die Buchungen der anderen vier Funktionen bleiben')
for (const featureId of [F2, F3, 'f_p_projekt04_termine', 'f_p_projekt05_termine']) assert.ok(aw.docs('termineConfig').get(featureId), `${featureId}: Einstellung bleibt`)
assert.ok(aw.docs('portalFeatures').get(F1), 'das Funktionsdokument selbst bleibt')
assert.deepEqual(await loescheAbgeschalteteFunktionen(JETZT), { funktionen: 0, buchungen: 0, gedeckelt: 0 }, 'zweiter Lauf: nichts mehr zu löschen, die Funktion zählt nicht noch einmal')
// das Funktionsdokument selbst bleibt, steht danach aber auf off (der Entwurf einer Neubestellung); der siteKey und damit das eingebettete Snippet bleiben gültig
const doc = aw.docs('portalFeatures').get(F1)
assert.ok(doc, 'das Funktionsdokument selbst bleibt')
assert.equal(doc.state, 'off')
assert.equal(doc.enabled, false)
assert.equal(doc.unlockedByPurchase, false)
assert.equal(doc.purchaseStatus, 'zurueckgesetzt')
assert.equal(doc.zahlungMeldung, '')
assert.equal(doc.ticketId, '')
assert.equal(doc.activatedAt, null)
assert.equal(doc.disabledAt, null)
assert.equal(doc.disabledReason, null)
assert.equal(doc.aboEndeMailAt, null)
assert.equal(doc.adminNote, 'Daten gelöscht (Löschfrist abgelaufen)')
assert.equal(doc.siteKey, 'abcdefghijklmnop', 'siteKey unverändert')
for (const id of andere) assert.deepEqual(aw.docs('portalFeatures').get(id), vorher[id], `${id}: Dokument unverändert`)
// zweiter Lauf: nichts mehr zu löschen, die Funktion zählt nicht noch einmal, das zurückgesetzte Dokument wird nicht mehr gelistet und nicht mehr gelesen
const leseF1 = new RegExp(`/collections/portalFeatures/documents/${F1}(\\?|$)`)
let zweiter
const zugriffeAufF1 = await zaehleAnfragen(async () => {
zweiter = await loescheAbgeschalteteFunktionen(JETZT)
}, leseF1)
assert.deepEqual(zweiter, { funktionen: 0, buchungen: 0, gedeckelt: 0 })
assert.equal(zugriffeAufF1, 0, 'keine tägliche Neuprüfung des zurückgesetzten Dokuments')
assert.equal(aw.docs('portalFeatures').get(F1).state, 'off')
assert.equal(fehlerLog().length, 0)
})
test('loescheAbgeschalteteFunktionen: scheitert das Löschen einer Buchung, bleibt die Einstellung bis zum nächsten Lauf', async () => {
@@ -1473,9 +1501,12 @@ test('loescheAbgeschalteteFunktionen: scheitert das Löschen einer Buchung, blei
await loescheAbgeschalteteFunktionen(JETZT)
assert.ok(buchung('b_eins'))
assert.ok(aw.docs('termineConfig').get(F1), 'Einstellung bleibt, solange Buchungen übrig sind')
assert.equal(aw.docs('portalFeatures').get(F1).state, 'disabled', 'das Dokument bleibt disabled, solange Buchungen übrig sind')
assert.deepEqual(await loescheAbgeschalteteFunktionen(JETZT), { funktionen: 1, buchungen: 1, gedeckelt: 0 })
assert.equal(buchung('b_eins'), undefined)
assert.equal(aw.docs('termineConfig').get(F1), undefined)
assert.equal(aw.docs('portalFeatures').get(F1).state, 'off', 'der nächste Lauf löscht und setzt zurück')
assert.equal(aw.docs('portalFeatures').get(F1).purchaseStatus, 'zurueckgesetzt')
})
test('loescheAbgeschalteteFunktionen: ein erneutes disable() (Doppelklick im WOMS, Kundenlöschung) verschiebt die 30 Tage nicht', async () => {
@@ -1493,8 +1524,8 @@ test('loescheAbgeschalteteFunktionen: ein erneutes disable() (Doppelklick im WOM
})
/** Eine abgeschaltete Funktion (seit 40 Tagen, Standard F1) mit n Buchungen `<praefix>000…`; mit zellen bekommt jede ihre Zellen. */
async function abgeschaltetMitBuchungen(n, { zellen = true, featureId = F1, praefix = 'b_ab' } = {}) {
await richteFunktionEin(featureId, { state: 'disabled', disabledAt: iso(plus(JETZT, -40 * TAG)) })
async function abgeschaltetMitBuchungen(n, { zellen = true, featureId = F1, praefix = 'b_ab', funktion = {} } = {}) {
await richteFunktionEin(featureId, { state: 'disabled', disabledAt: iso(plus(JETZT, -40 * TAG)), ...funktion })
for (let i = 0; i < n; i++) await legeBuchung(`${praefix}${String(i).padStart(3, '0')}`, { featureId, start: plus(JETZT, TAG + i * 2 * STUNDE), zellen })
}
/** Das Personal schaltet die Funktion wieder ein: siteFeatures.activate sperrt nicht und lässt disabledAt stehen. */
@@ -1544,6 +1575,7 @@ test('loescheAbgeschalteteFunktionen: wird die Funktion nach 50 Buchungen wieder
assert.deepEqual(erg, { funktionen: 1, buchungen: 50, gedeckelt: 0 })
assert.equal(buchungen().length, 10)
assert.ok(aw.docs('termineConfig').get(F1), 'Einstellung bleibt')
assert.equal(aw.docs('portalFeatures').get(F1).state, 'active', 'die wieder eingeschaltete Funktion wird nicht zurückgesetzt')
})
test('loescheAbgeschalteteFunktionen: wird die Funktion nach der letzten Buchung wieder eingeschaltet, bleibt die Einstellung', async () => {
@@ -1553,6 +1585,9 @@ test('loescheAbgeschalteteFunktionen: wird die Funktion nach der letzten Buchung
assert.ok(aw.docs('termineConfig').get(F1), 'Einstellung bleibt')
assert.equal(erg.funktionen, 1)
assert.equal(erg.buchungen, 2, 'beide Buchungen waren in einem Block von 50, vor der nächsten Prüfung gelöscht')
const doc = aw.docs('portalFeatures').get(F1)
assert.equal(doc.state, 'active', 'die wieder eingeschaltete Funktion wird nicht zurückgesetzt')
assert.notEqual(doc.purchaseStatus, 'zurueckgesetzt')
})
test('loescheAbgeschalteteFunktionen: kann die Funktion vor dem Löschen nicht gelesen werden, wird nichts gelöscht (Fehler im Log)', async () => {
@@ -1574,9 +1609,10 @@ test('loescheAbgeschalteteFunktionen: seitenweise mit Deckel – 230 Buchungen,
assert.ok(zellenProBuchung > 0)
const funktion = () => aw.docs('portalFeatures').get(F1)
const vorher = { state: funktion().state, disabledAt: funktion().disabledAt }
const stimmt = (uebrig) => {
const stimmt = (uebrig, { zurueckgesetzt = false } = {}) => {
assert.equal(buchungen().length, uebrig, 'Rest')
assert.equal(aw.docs('termineClaims').size, uebrig * zellenProBuchung, 'Zellen passen zum Rest')
if (zurueckgesetzt) return assert.deepEqual({ state: funktion().state, disabledAt: funktion().disabledAt }, { state: 'off', disabledAt: null }, 'erst der letzte Lauf setzt das Dokument zurück')
assert.deepEqual({ state: funktion().state, disabledAt: funktion().disabledAt }, vorher, 'Zustand und disabledAt bleiben, die nächste Frist beginnt nicht neu')
}
@@ -1597,7 +1633,7 @@ test('loescheAbgeschalteteFunktionen: seitenweise mit Deckel – 230 Buchungen,
log.warn.mock.resetCalls()
erg = await loescheAbgeschalteteFunktionen(JETZT)
assert.deepEqual(erg, { funktionen: 1, buchungen: 30, gedeckelt: 0 }, 'Lauf 3: die letzten 30')
stimmt(0)
stimmt(0, { zurueckgesetzt: true })
assert.equal(aw.docs('termineConfig').get(F1), undefined, 'Einstellung erst jetzt gelöscht')
assert.equal(deckelWarnungen().length, 0)
assert.equal(fehlerLog().length, 0)
@@ -1745,6 +1781,251 @@ test('loescheAbgeschalteteFunktionen: wird die Funktion unmittelbar vor dem Lös
assert.equal(fehlerLog().length, 0)
})
// Löschung nach 30 Tagen (E8): frische Abo-Prüfung vor jedem unumkehrbaren Schritt, danach das Funktionsdokument auf off
/** Laufendes (oder mit `over` beendetes) Wartungs-Abo von KUNDE. */
const wartung = (stand = 'active', over = {}) => aw.seed('subscriptions', subscriptionDoc('sub_w', { ...wartungDoc, customerId: KUNDE, status: stand, ...over }))
/** Alle Anfragen an die Abo-Collections (Spiegel und Ereignisse), die der Abo-Baustein stellt. */
const ABO_ANFRAGEN = /\/collections\/(subscriptions|contractEvents)\//
/** Die Anfrage, die die Einstellung (termineConfig) löscht. */
const einstellungLoeschen = (url, methode) => methode === 'DELETE' && /\/collections\/termineConfig\/documents\//.test(url)
/** Terminplaner seit 31 Tagen abgeschaltet, mit allem, was der Kauf und die Einrichtung am Dokument hinterlassen haben. */
const eingerichtet = (over = {}) => ({
...BEZAHLT,
disabledAt: iso(plus(JETZT, -31 * TAG)),
activatedAt: iso(plus(JETZT, -200 * TAG)),
ticketId: 't_einrichtung',
zahlungMeldung: 'gemeldet',
aboEndeMailAt: iso(plus(JETZT, -31 * TAG)),
adminNote: 'Abgeschaltet: Wartungs-Abo beendet',
stripeSessionId: 'cs_test_alt',
invoiceUrl: 'https://invoice.stripe.example/alt',
...over,
})
test('Löschung nach 30 Tagen, Grund abo_ende: läuft das Wartungs-Abo wieder, wird nichts gelöscht (Warnung im Log, Dokument bleibt disabled)', async () => {
await abgeschaltetMitBuchungen(3, { funktion: eingerichtet({ disabledReason: 'abo_ende' }) })
wartung('active')
const erg = await loescheAbgeschalteteFunktionen(JETZT)
assert.deepEqual(erg, { funktionen: 0, buchungen: 0, gedeckelt: 0 })
assert.equal(buchungen().length, 3, 'Buchungen bleiben')
for (const b of buchungen()) assert.ok(claims(b.$id).length > 0, `${b.$id}: Zellen bleiben`)
assert.ok(aw.docs('termineConfig').get(F1), 'Einstellung bleibt')
const doc = aw.docs('portalFeatures').get(F1)
assert.equal(doc.state, 'disabled')
assert.equal(doc.disabledReason, 'abo_ende')
assert.ok(warnLog().some((zeile) => /Abo läuft wieder, Löschung abgebrochen/.test(zeile)), 'Warnung im Log')
assert.doesNotMatch(warnLog().join('\n'), /\bb_|f_p_|c_kunde|Müller/, 'keine Kennung im Log')
assert.equal(fehlerLog().length, 0, 'kein Fehler, kein Wurf')
})
test('Löschung nach 30 Tagen, Grund abo_ende: endet das Abo wirklich (beendet), wird gelöscht', async () => {
await abgeschaltetMitBuchungen(3, { funktion: eingerichtet({ disabledReason: 'abo_ende' }) })
wartung('canceled', { endedAt: iso(plus(JETZT, -31 * TAG)) })
assert.deepEqual(await loescheAbgeschalteteFunktionen(JETZT), { funktionen: 1, buchungen: 3, gedeckelt: 0 })
assert.equal(buchungen().length, 0)
assert.equal(aw.docs('termineConfig').get(F1), undefined)
assert.equal(aw.docs('portalFeatures').get(F1).state, 'off')
assert.equal(fehlerLog().length, 0)
})
for (const [name, grund] of [
['team', { disabledReason: 'team' }],
['kunde_geloescht', { disabledReason: 'kunde_geloescht' }],
['abo_ende_vor_einrichtung', { disabledReason: 'abo_ende_vor_einrichtung' }],
['Altbestand ohne Grund', {}],
]) {
test(`Löschung nach 30 Tagen, ${name}: gelöscht und zurückgesetzt, ohne eine einzige Abfrage auf das Abo (auch wenn es läuft)`, async () => {
await abgeschaltetMitBuchungen(3, { funktion: eingerichtet(grund) })
wartung('active')
let erg
const aboAnfragen = await zaehleAnfragen(async () => {
erg = await loescheAbgeschalteteFunktionen(JETZT)
}, ABO_ANFRAGEN)
assert.equal(aboAnfragen, 0, 'keine Abfrage auf subscriptions oder contractEvents')
assert.deepEqual(erg, { funktionen: 1, buchungen: 3, gedeckelt: 0 })
assert.equal(buchungen().length, 0)
assert.equal(aw.docs('termineClaims').size, 0)
assert.equal(aw.docs('termineConfig').get(F1), undefined)
const doc = aw.docs('portalFeatures').get(F1)
assert.equal(doc.state, 'off', 'das Zurücksetzen gilt für jeden Grund')
assert.equal(doc.purchaseStatus, 'zurueckgesetzt')
assert.equal(fehlerLog().length, 0)
})
}
test('Löschung nach 30 Tagen: das zurückgesetzte Dokument behält siteKey sowie Bestell- und Rechnungsfelder, alles Übrige ist leer', async () => {
await abgeschaltetMitBuchungen(1, { zellen: false, funktion: eingerichtet({ disabledReason: 'abo_ende' }) })
const vorher = structuredClone(aw.docs('portalFeatures').get(F1))
await loescheAbgeschalteteFunktionen(JETZT)
const doc = aw.docs('portalFeatures').get(F1)
assert.deepEqual(
{ ...doc, $updatedAt: undefined },
{
...vorher,
$updatedAt: undefined,
state: 'off',
enabled: false,
unlockedByPurchase: false,
purchaseStatus: 'zurueckgesetzt',
zahlungMeldung: '',
ticketId: '',
activatedAt: null,
disabledAt: null,
disabledReason: null,
aboEndeMailAt: null,
adminNote: 'Daten gelöscht (Löschfrist abgelaufen)',
},
'genau diese Felder ändern sich; siteKey, stripeSessionId, invoiceUrl und alles andere bleiben',
)
assert.equal(doc.siteKey, 'abcdefghijklmnop')
assert.equal(doc.stripeSessionId, 'cs_test_alt')
})
test('Löschung nach 30 Tagen, Grund abo_ende: kommt das Abo erst nach der 50. Löschung zurück, endet die Löschung dort und die Einstellung bleibt', async () => {
await abgeschaltetMitBuchungen(60, { zellen: false, funktion: { disabledReason: 'abo_ende' } })
const echt = globalThis.fetch
let geloescht = 0
globalThis.fetch = async (url, optionen) => {
const antwort = await echt(url, optionen)
if (buchungLoeschen(String(url), optionen?.method ?? 'GET')) {
geloescht += 1
if (geloescht === 50) wartung('active')
}
return antwort
}
let erg
try {
erg = await loescheAbgeschalteteFunktionen(JETZT)
} finally {
globalThis.fetch = echt
}
assert.equal(geloescht, 50, 'nach der Rückkehr des Abos kommt keine Löschung mehr')
assert.deepEqual(erg, { funktionen: 1, buchungen: 50, gedeckelt: 0 })
assert.equal(buchungen().length, 10)
assert.ok(aw.docs('termineConfig').get(F1), 'Einstellung bleibt')
assert.equal(aw.docs('portalFeatures').get(F1).state, 'disabled')
assert.ok(warnLog().some((zeile) => /Abo läuft wieder, Löschung abgebrochen/.test(zeile)))
assert.equal(fehlerLog().length, 0)
})
test('Löschung nach 30 Tagen, Grund abo_ende: kommt das Abo zurück, nachdem die letzte Buchung weg ist, bleibt die Einstellung (Prüfung vor dem Löschen der Einstellung)', async () => {
await abgeschaltetMitBuchungen(2, { zellen: false, funktion: { disabledReason: 'abo_ende' } })
let listen = 0
const leereSeite = (url, methode) => listeVonBuchungen(/featureId/)(url, methode) && (listen += 1) === 2
const erg = await mitEinwurf(leereSeite, async () => wartung('active'), () => loescheAbgeschalteteFunktionen(JETZT))
assert.deepEqual(erg, { funktionen: 1, buchungen: 2, gedeckelt: 0 })
assert.ok(aw.docs('termineConfig').get(F1), 'Einstellung bleibt')
assert.equal(aw.docs('portalFeatures').get(F1).state, 'disabled', 'kein Zurücksetzen, solange die Einstellung da ist')
assert.ok(warnLog().some((zeile) => /Abo läuft wieder, Löschung abgebrochen/.test(zeile)))
assert.equal(fehlerLog().length, 0)
})
test('Löschung nach 30 Tagen, Grund abo_ende: Lesefehler auf subscriptions ⇒ nichts gelöscht, fehler gezählt; der nächste Lauf löscht', async () => {
await abgeschaltetMitBuchungen(3, { funktion: abgeschaltetBezahlt(40) })
// 1. Lesezugriff auf subscriptions = Abo-Abgleich (Vorauswahl), 2. = Prüfung der Löschung
aw.failNext({ collection: 'subscriptions', method: 'GET', status: 500, message: 'Nachbau kaputt', after: 1 })
let erg = await taeglicherJob(JETZT)
assert.deepEqual(erg.aboAbgleich, { ...ABO_NULL, geprueft: 1 })
assert.deepEqual(erg.abgeschaltet, { funktionen: 0, buchungen: 0 })
assert.equal(erg.fehler, 1, 'der Lesefehler wird gezählt')
assert.equal(fehlerLog().filter((zeile) => /Abo vor dem Löschen nicht gelesen/.test(zeile)).length, 1)
assert.equal(buchungen().length, 3, 'im Zweifel wird nichts gelöscht')
assert.ok(aw.docs('termineConfig').get(F1), 'Einstellung bleibt')
assert.equal(aw.docs('portalFeatures').get(F1).state, 'disabled')
erg = await taeglicherJob(JETZT)
assert.equal(erg.fehler, 0)
assert.deepEqual(erg.abgeschaltet, { funktionen: 1, buchungen: 3 })
assert.equal(aw.docs('termineConfig').get(F1), undefined)
assert.equal(aw.docs('portalFeatures').get(F1).state, 'off')
})
test('Zurücksetzen scheitert (PATCH auf portalFeatures) ⇒ Einstellung ist weg, Dokument noch disabled; der nächste Lauf setzt zurück, auch wenn das Abo inzwischen läuft', async () => {
await abgeschaltetMitBuchungen(2, { zellen: false, funktion: eingerichtet({ disabledReason: 'abo_ende' }) })
aw.failNext({ collection: 'portalFeatures', method: 'PATCH', status: 500, message: 'Nachbau kaputt' })
assert.deepEqual(await loescheAbgeschalteteFunktionen(JETZT), { funktionen: 1, buchungen: 2, gedeckelt: 0 })
assert.equal(aw.docs('termineConfig').get(F1), undefined, 'Einstellung ist weg')
assert.equal(aw.docs('portalFeatures').get(F1).state, 'disabled', 'das Zurücksetzen ist gescheitert')
assert.equal(fehlerLog().length, 1)
wartung('active') // jetzt ist nichts Unumkehrbares mehr offen: das Abo spielt keine Rolle
log.error.mock.resetCalls()
let erg
const aboAnfragen = await zaehleAnfragen(async () => {
erg = await loescheAbgeschalteteFunktionen(JETZT)
}, ABO_ANFRAGEN)
assert.deepEqual(erg, { funktionen: 0, buchungen: 0, gedeckelt: 0 }, 'ein reines Zurücksetzen zählt nicht')
assert.equal(aboAnfragen, 0, 'keine Abo-Prüfung')
const doc = aw.docs('portalFeatures').get(F1)
assert.equal(doc.state, 'off')
assert.equal(doc.purchaseStatus, 'zurueckgesetzt')
assert.equal(fehlerLog().length, 0)
})
test('Zurücksetzen: war die Einstellung schon weg (404) und gibt es keine Buchungen, setzt der Lauf nur das Dokument zurück – ohne Abo-Prüfung, auch bei laufendem Abo', async () => {
await abgeschaltetMitBuchungen(0, { funktion: eingerichtet({ disabledReason: 'abo_ende' }) })
await deleteDocument('termineConfig', F1)
wartung('active')
let erg
const aboAnfragen = await zaehleAnfragen(async () => {
erg = await loescheAbgeschalteteFunktionen(JETZT)
}, ABO_ANFRAGEN)
assert.deepEqual(erg, { funktionen: 0, buchungen: 0, gedeckelt: 0 })
assert.equal(aboAnfragen, 0, 'nichts Unumkehrbares mehr offen: keine Abo-Abfrage')
assert.equal(aw.docs('portalFeatures').get(F1).state, 'off')
assert.equal(aw.docs('portalFeatures').get(F1).purchaseStatus, 'zurueckgesetzt')
assert.equal(fehlerLog().length, 0)
})
test('Zurücksetzen: läuft unter der Sperre feature-admin:<projectId> – solange das Team sie hält, bleibt das Dokument disabled', async () => {
await abgeschaltetMitBuchungen(1, { zellen: false, funktion: eingerichtet({ disabledReason: 'team' }) })
let lauf
await withLocks([`feature-admin:${PROJEKT}`], async () => {
lauf = loescheAbgeschalteteFunktionen(JETZT)
for (let i = 0; i < 200 && aw.docs('termineConfig').get(F1); i += 1) await new Promise((fertig) => setTimeout(fertig, 10)) // bis die Einstellung weg ist
assert.equal(aw.docs('termineConfig').get(F1), undefined, 'Buchungen und Einstellung sind gelöscht')
await new Promise((fertig) => setTimeout(fertig, 100))
assert.equal(aw.docs('portalFeatures').get(F1).state, 'disabled', 'das Zurücksetzen wartet auf die Sperre')
})
assert.deepEqual(await lauf, { funktionen: 1, buchungen: 1, gedeckelt: 0 })
assert.equal(aw.docs('portalFeatures').get(F1).state, 'off')
})
for (const [was, einwurf] of [
['vom Team aktiviert', () => schalteEin()],
['frisch neu abgeschaltet (disabledAt jünger als 30 Tage)', () => aw.seed('portalFeatures', { ...aw.docs('portalFeatures').get(F1), disabledAt: iso(plus(JETZT, -TAG)) })],
]) {
test(`Zurücksetzen: wird das Dokument zwischen dem Löschen der Einstellung und dem Zurücksetzen ${was}, bleibt es so (frisch gelesen, nicht überschrieben)`, async () => {
await abgeschaltetMitBuchungen(1, { zellen: false, funktion: eingerichtet({ disabledReason: 'abo_ende' }) })
const erg = await mitEinwurf(einstellungLoeschen, async () => einwurf(), () => loescheAbgeschalteteFunktionen(JETZT))
assert.deepEqual(erg, { funktionen: 1, buchungen: 1, gedeckelt: 0 })
assert.equal(aw.docs('termineConfig').get(F1), undefined, 'die Einstellung war schon gelöscht')
const doc = aw.docs('portalFeatures').get(F1)
assert.notEqual(doc.state, 'off', 'nicht zurückgesetzt')
assert.equal(doc.unlockedByPurchase, true)
assert.equal(doc.purchaseStatus, 'paid')
assert.equal(doc.ticketId, 't_einrichtung')
assert.equal(doc.adminNote, 'Abgeschaltet: Wartungs-Abo beendet')
assert.equal(fehlerLog().length, 0)
})
}
test('Zurücksetzen: nach dem Zurücksetzen steht das Dokument auf off und ist wieder bestellbar (markOrdered: Test in Block B)', async () => {
await abgeschaltetMitBuchungen(1, { zellen: false, funktion: eingerichtet({ disabledReason: 'team' }) })
await loescheAbgeschalteteFunktionen(JETZT)
const { state, enabled } = aw.docs('portalFeatures').get(F1)
assert.deepEqual({ state, enabled }, { state: 'off', enabled: false })
})
// ---------------------------------------------------------------- Tagesjob
test('taeglicherJob: Löschfristen, Warteschlange, festgefahrene Mails, verwaiste Zellen und abgeschaltete Funktionen in einem Lauf', async () => {
@@ -2051,8 +2332,6 @@ test('taeglicherJob: festgefahrene Mail wird fehler und erst 48 Stunden später
const K22_BETREFF = /^Dein Terminplaner wurde beendet/
const K23_BETREFF = /^Dein Terminplaner läuft wieder/
const kundenMails = (betreff) => sink.sent.filter((mail) => betreff.test(mail.subject))
/** Laufendes (oder mit `over` beendetes) Wartungs-Abo von KUNDE. */
const wartung = (stand = 'active', over = {}) => aw.seed('subscriptions', subscriptionDoc('sub_w', { ...wartungDoc, customerId: KUNDE, status: stand, ...over }))
/** Bezahlter, seit `tage` Tagen wegen des Abo-Endes abgeschalteter Terminplaner; K22 ging schon (Marker), der Abgleich holt nichts nach. */
const abgeschaltetBezahlt = (tage, over = {}) => ({
state: 'disabled',
@@ -2182,7 +2461,8 @@ test('Modus off, bezahlter Terminplaner abgeschaltet seit 31 Tagen: Buchungen, Z
assert.equal(buchung('b_fest'), undefined)
assert.equal(claims('b_fest').length, 0, 'Zellen frei')
assert.equal(aw.docs('termineConfig').get(F1), undefined, 'Einstellung gelöscht')
// (Dokument selbst auf `off` setzen: E8)
assert.equal(aw.docs('portalFeatures').get(F1).state, 'off', 'das Dokument steht danach auf off: der Guard findet keinen bezahlten Terminplaner mehr')
assert.equal(aw.docs('portalFeatures').get(F1).unlockedByPurchase, false)
// die Tagesjob-Meldung und die Statusroute lesen daraus Zahlen, nie undefined
assert.deepEqual(letzterTagesjob(), { at: JETZT.toISOString(), fehler: 0, gedeckelt: 0, geloescht: 1 })