Files
Webklar-Kundenbereich/test/termineEntwuerfe.test.js
ClaudeandClaude Sonnet 5.5 91b1a3bcd8 Terminplaner TP2: Entwuerfe nach 90 Tagen und bei Kundenloeschung entfernen
Neu: server/services/termine/entwuerfe.js. Ein Terminplaner im Zustand off ist ein
Entwurf (Fragebogen, Einstellung). loescheEntwuerfe(now) im Tagesjob (nach den
abgeschalteten Funktionen, nur im vollen Lauf): letzte Aenderung = juengerer Zeitpunkt
aus $updatedAt des Funktionsdokuments und termineConfig.updatedAt, aelter als 90 Tage
=> Einstellung weg, das Funktionsdokument nur, wenn es nie in Betrieb oder bezahlt war
(sonst bleibt es wegen des siteKeys; ein von E8 zurueckgesetztes Dokument nie).
Vor jedem Loeschen unter feature-order, feature-admin und termine-cfg frisch lesen
(noch off, Frist, keine Buchung). Seiten zu 100 per cursorAfter, hoechstens 2000
geprueft und 100 geloescht je Lauf, der Rest folgt an Folgetagen; Einzelfehler zaehlen
in fehler. Tagesbericht: Segment "Entwuerfe n geloescht (m offen)".
loescheEntwuerfeVonKunde: Kundenloeschung entfernt alle off-Entwuerfe vollstaendig,
Fehler werden nur protokolliert.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-04 20:33:33 +00:00

428 lines
21 KiB
JavaScript
Raw Permalink 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 { startAppwriteMock } from './support/appwriteMock.js'
import { schemaKeys } from './support/schema.js'
import { useTestEnv } from './support/testEnv.js'
import { TERMINE_DATETIMES } from '../server/services/termine/schema.js'
// Entwuerfe des Terminplaners (server/services/termine/entwuerfe.js, Terminplaner TP2 C6): ein Funktionsdokument im Zustand `off`
// samt Einstellung (termineConfig) ist ein Entwurf des Fragebogens. Er wird 90 Tage nach der letzten Aenderung geloescht, bei der
// Kundenloeschung sofort. Aufbau wie termineJobs.test.js: Nachbau von Appwrite mit Schema und Standard-Limit 25, feste Zeit JETZT.
const aw = await startAppwriteMock({
schema: {
portalFeatures: schemaKeys('portalFeatures'),
termineConfig: schemaKeys('termineConfig'),
termineBookings: schemaKeys('termineBookings'),
},
datetimes: TERMINE_DATETIMES,
defaultLimit: 25,
})
after(() => aw.close())
useTestEnv({ appwriteUrl: aw.url, stripeUrl: 'http://127.0.0.1:1' })
const { deleteDocument } = await import('../server/services/appwriteAdmin.js')
const { withLocks } = await import('../server/services/keyedLock.js')
const { speichereFragebogen, resetKontextForTests } = await import('../server/services/termine/kontext.js')
const { ENTWURF_TAGE, MAX_GEPRUEFT, MAX_GELOESCHT, loescheEntwuerfe, loescheEntwuerfeVonKunde } = await import('../server/services/termine/entwuerfe.js')
const TAG = 24 * 60 * 60 * 1000
const JETZT = new Date('2026-10-05T08:00:00.000Z') // Montag, 10:00 Berlin
const KUNDE = 'c_kunde01'
const ANDERER = 'c_kunde02'
const SITE_KEY = 'abcdefghijklmnop'
const vor = (tage) => new Date(JETZT.getTime() - tage * TAG).toISOString()
const fid = (nr) => `f_p_e${nr}_termine`
const pid = (nr) => `p_e${nr}`
const dok = (nr) => aw.docs('portalFeatures').get(fid(nr))
const einstellung = (nr) => aw.docs('termineConfig').get(fid(nr))
/** Entwurf: Funktionsdokument im Zustand off (`tage` seit der letzten Aenderung) und, wenn config, die Einstellung (`cfgTage`). */
function legeEntwurf(nr, { tage = 91, cfgTage = tage, config = true, kunde = KUNDE, key = 'termine', doc = {} } = {}) {
aw.seed('portalFeatures', {
$id: key === 'termine' ? fid(nr) : `f_p_e${nr}_${key}`,
customerId: kunde,
projectId: pid(nr),
featureKey: key,
state: 'off',
enabled: false,
siteKey: SITE_KEY,
$updatedAt: vor(tage),
...doc,
})
if (config) {
aw.seed('termineConfig', {
$id: key === 'termine' ? fid(nr) : `f_p_e${nr}_${key}`,
customerId: kunde,
projectId: pid(nr),
einstellung: '{}',
version: 1,
updatedAt: vor(cfgTage),
updatedBy: 'kunde',
$updatedAt: vor(cfgTage),
})
}
}
/** n Entwuerfe mit den Nummern von..bis (nach $id sortiert und damit in dieser Reihenfolge gelesen: Nummern mit gleicher Stellenzahl). */
function legeViele(von, bis, optionen) {
for (let nr = von; nr <= bis; nr += 1) legeEntwurf(nr, optionen)
}
const warteBis = async (bedingung) => {
for (let i = 0; i < 400 && !bedingung(); i += 1) await new Promise((fertig) => setTimeout(fertig, 5))
assert.ok(bedingung(), 'Bedingung wurde nicht erfuellt')
}
let log
const logText = () => [...log.error.mock.calls, ...log.warn.mock.calls].map((aufruf) => aufruf.arguments.map(String).join(' ')).join('\n')
beforeEach(async () => {
for (const collection of ['portalFeatures', 'termineConfig', 'termineBookings']) {
const ids = [...aw.docs(collection).keys()]
for (let i = 0; i < ids.length; i += 50) await Promise.all(ids.slice(i, i + 50).map((id) => deleteDocument(collection, id)))
}
resetKontextForTests()
aw.resetListCalls()
log = { error: mock.method(console, 'error', () => {}), warn: mock.method(console, 'warn', () => {}) }
})
afterEach(() => {
// das Protokoll nennt nie Kunden-, Projekt- oder Funktions-IDs und keinen siteKey
const text = logText()
mock.restoreAll()
assert.doesNotMatch(text, /f_p_e|p_e\d|c_kunde|abcdefghijklmnop/, 'keine Kennungen im Log')
})
// ---------------------------------------------------------------- Konstanten
test('Konstanten: 90 Tage, höchstens 2000 geprüft, höchstens 100 gelöscht', () => {
assert.deepEqual([ENTWURF_TAGE, MAX_GEPRUEFT, MAX_GELOESCHT], [90, 2000, 100])
})
// ---------------------------------------------------------------- Frist
test('Frist: 89 und 90 Tage alt bleiben, 91 Tage: Einstellung und Funktionsdokument sind weg', async () => {
legeEntwurf(1, { tage: 89 })
legeEntwurf(2, { tage: 90 })
legeEntwurf(3, { tage: 91 })
// dieselbe Grenze für die Einstellung allein (das Funktionsdokument ist längst älter)
legeEntwurf(4, { tage: 300, cfgTage: 89 })
legeEntwurf(5, { tage: 300, cfgTage: 90 })
legeEntwurf(6, { tage: 300, cfgTage: 91 })
const erg = await loescheEntwuerfe(JETZT)
assert.deepEqual(erg, { geprueft: 6, geloescht: 2, uebrig: 0, fehler: 0 })
for (const nr of [1, 2, 4, 5]) {
assert.ok(dok(nr), `Funktionsdokument ${nr} bleibt`)
assert.ok(einstellung(nr), `Einstellung ${nr} bleibt`)
}
for (const nr of [3, 6]) {
assert.equal(dok(nr), undefined, `Funktionsdokument ${nr}`)
assert.equal(einstellung(nr), undefined, `Einstellung ${nr}`)
}
})
test('Frist: der jüngere Zeitpunkt zählt – ein Entwurf, den der Kunde frisch bearbeitet hat (alte $updatedAt, neues termineConfig.updatedAt), bleibt', async () => {
legeEntwurf(1, { tage: 200, cfgTage: 200 })
// der Schreibweg von PUT /api/termine/fragebogen: aendert nur das termineConfig-Dokument, nie das Funktionsdokument
await speichereFragebogen(dok(1), (bisher) => bisher, { now: new Date(JETZT.getTime() - 10 * TAG), by: 'kunde' })
assert.equal(Date.parse(dok(1).$updatedAt), Date.parse(vor(200)), 'das Funktionsdokument blieb unberührt')
assert.equal(Date.parse(einstellung(1).updatedAt), Date.parse(vor(10)))
const erg = await loescheEntwuerfe(JETZT)
assert.deepEqual(erg, { geprueft: 1, geloescht: 0, uebrig: 0, fehler: 0 })
assert.ok(dok(1))
assert.ok(einstellung(1))
// 91 Tage nach der Bearbeitung ist auch dieser Entwurf fällig
const spaeter = await loescheEntwuerfe(new Date(JETZT.getTime() + 81 * TAG + 1000))
assert.deepEqual(spaeter, { geprueft: 1, geloescht: 1, uebrig: 0, fehler: 0 })
assert.equal(dok(1), undefined)
})
test('Frist: ein frisch geändertes Funktionsdokument (z. B. nach abgebrochener Zahlung wieder auf off) bleibt, auch wenn die Einstellung alt ist', async () => {
legeEntwurf(1, { tage: 3, cfgTage: 200, doc: { purchaseStatus: 'failed' } })
const erg = await loescheEntwuerfe(JETZT)
assert.deepEqual(erg, { geprueft: 1, geloescht: 0, uebrig: 0, fehler: 0 })
assert.ok(dok(1))
assert.ok(einstellung(1))
})
test('Funktionsdokument ohne Einstellung und älter als 90 Tage: das Dokument geht, ohne Einstellung wird nichts gelesen oder gelöscht', async () => {
legeEntwurf(1, { tage: 100, config: false })
legeEntwurf(2, { tage: 20, config: false })
const erg = await loescheEntwuerfe(JETZT)
assert.deepEqual(erg, { geprueft: 2, geloescht: 1, uebrig: 0, fehler: 0 })
assert.equal(dok(1), undefined)
assert.ok(dok(2))
})
test('Nur Termine im Zustand off: ordered, setup, active und disabled bleiben unabhängig vom Alter, ebenso andere Funktionen', async () => {
for (const [nr, state] of [[1, 'ordered'], [2, 'setup'], [3, 'active'], [4, 'disabled']]) {
legeEntwurf(nr, { tage: 400, doc: { state, enabled: state === 'active', ...(state === 'disabled' ? { disabledAt: vor(400), disabledReason: 'team' } : {}) } })
}
legeEntwurf(5, { tage: 400, key: 'news' })
legeEntwurf(6, { tage: 400, key: 'analyse', config: false })
const vorher = [...aw.docs('portalFeatures').values()].map((d) => ({ ...d }))
const erg = await loescheEntwuerfe(JETZT)
assert.deepEqual(erg, { geprueft: 0, geloescht: 0, uebrig: 0, fehler: 0 })
assert.deepEqual([...aw.docs('portalFeatures').values()], vorher)
assert.equal(aw.docs('termineConfig').size, 5, 'keine Einstellung wurde gelöscht')
// die Abfrage nennt Funktion und Zustand
const abfrage = aw.listCalls('portalFeatures')[0].queries
assert.deepEqual(abfrage.filter((q) => q.method === 'equal').map((q) => [q.attribute, q.values]), [['featureKey', ['termine']], ['state', ['off']]])
})
// ---------------------------------------------------------------- Spuren von Betrieb und Zahlung
const SPUREN = [
['activatedAt', { activatedAt: vor(200) }],
['ticketId', { ticketId: 'wo_12345' }],
['disabledAt', { disabledAt: vor(150) }],
['unlockedByPurchase', { unlockedByPurchase: true }],
['purchaseStatus paid', { purchaseStatus: 'paid' }],
]
test('Ein Funktionsdokument, das in Betrieb oder bezahlt war, verliert nur die Einstellung (der siteKey bleibt gültig)', async () => {
SPUREN.forEach(([, felder], i) => legeEntwurf(i + 1, { doc: felder }))
const vorher = SPUREN.map((_, i) => ({ ...dok(i + 1) }))
const erg = await loescheEntwuerfe(JETZT)
assert.deepEqual(erg, { geprueft: 5, geloescht: 5, uebrig: 0, fehler: 0 }, 'die Einstellung ist gelöscht, das zählt als Löschung')
SPUREN.forEach(([name], i) => {
assert.deepEqual(dok(i + 1), vorher[i], `${name}: Funktionsdokument unverändert`)
assert.equal(dok(i + 1).siteKey, SITE_KEY)
assert.equal(einstellung(i + 1), undefined, `${name}: Einstellung gelöscht`)
})
// am nächsten Tag ist nichts mehr zu tun
assert.deepEqual(await loescheEntwuerfe(new Date(JETZT.getTime() + TAG)), { geprueft: 5, geloescht: 0, uebrig: 0, fehler: 0 })
})
test('Von E8 zurückgesetztes Dokument (off, purchaseStatus zurueckgesetzt): nach 91 Tagen bleiben Dokument und siteKey unverändert, eine vorhandene Einstellung wird gelöscht', async () => {
const zurueckgesetzt = { purchaseStatus: 'zurueckgesetzt', unlockedByPurchase: false, ticketId: '', activatedAt: null, disabledAt: null, disabledReason: null, zahlungMeldung: '', adminNote: 'Daten gelöscht (Löschfrist abgelaufen)' }
legeEntwurf(1, { tage: 91, doc: zurueckgesetzt }) // mit neuem Entwurf
legeEntwurf(2, { tage: 400, config: false, doc: zurueckgesetzt }) // ohne Einstellung
const vorher = [{ ...dok(1) }, { ...dok(2) }]
const erg = await loescheEntwuerfe(JETZT)
assert.deepEqual(erg, { geprueft: 2, geloescht: 1, uebrig: 0, fehler: 0 })
assert.deepEqual(dok(1), vorher[0])
assert.deepEqual(dok(2), vorher[1])
assert.equal(dok(1).siteKey, SITE_KEY)
assert.equal(einstellung(1), undefined)
})
test('Nach fehlgeschlagener Zahlung (purchaseStatus failed) ist der Entwurf nach 90 Tagen weg, davor nicht', async () => {
legeEntwurf(1, { tage: 89, doc: { purchaseStatus: 'failed', stripeSessionId: 'cs_test_1', orderedAt: vor(89) } })
legeEntwurf(2, { tage: 91, doc: { purchaseStatus: 'failed', stripeSessionId: 'cs_test_2', orderedAt: vor(91) } })
const erg = await loescheEntwuerfe(JETZT)
assert.deepEqual(erg, { geprueft: 2, geloescht: 1, uebrig: 0, fehler: 0 })
assert.ok(dok(1))
assert.equal(dok(2), undefined)
assert.equal(einstellung(2), undefined)
})
// ---------------------------------------------------------------- frisch lesen unter den Sperren
test('Rennen: wird der Entwurf bestellt, während der Lauf auf die Sperre feature-order wartet, liest der Lauf frisch und löscht nichts', async () => {
legeEntwurf(1)
let freigeben
const gehalten = new Promise((fertig) => (freigeben = fertig))
const sperre = withLocks([`feature-order:${KUNDE}:${pid(1)}`], () => gehalten)
const lauf = loescheEntwuerfe(JETZT)
await warteBis(() => aw.listCalls('portalFeatures').length > 0) // der Kandidat steht in der Liste, jetzt wartet der Lauf
// gleich alt wie vorher (seed behält $updatedAt): nur das frische Lesen des Zustands kann das Löschen noch verhindern
aw.seed('portalFeatures', { ...dok(1), state: 'ordered', stripeSessionId: 'cs_test_9' })
freigeben()
await sperre
const erg = await lauf
assert.deepEqual(erg, { geprueft: 1, geloescht: 0, uebrig: 0, fehler: 0 })
assert.equal(dok(1).state, 'ordered')
assert.ok(einstellung(1), 'die Einstellung der Bestellung bleibt')
})
test('Rennen: dieselbe Lage unter feature-admin und termine-cfg (Team schaltet ein, Kunde speichert)', async () => {
legeEntwurf(1)
legeEntwurf(2)
let freigeben
const gehalten = new Promise((fertig) => (freigeben = fertig))
const sperre = withLocks([`feature-admin:${pid(1)}`, `termine-cfg:${fid(2)}`], () => gehalten)
const lauf = loescheEntwuerfe(JETZT)
await warteBis(() => aw.listCalls('portalFeatures').length > 0)
aw.seed('portalFeatures', { ...dok(1), state: 'active', enabled: true })
aw.seed('termineConfig', { ...einstellung(2), updatedAt: vor(1) }) // der Kunde hat den Entwurf gerade bearbeitet
freigeben()
await sperre
const erg = await lauf
assert.deepEqual(erg, { geprueft: 2, geloescht: 0, uebrig: 0, fehler: 0 })
assert.equal(dok(1).state, 'active')
assert.ok(einstellung(1))
assert.ok(dok(2))
assert.ok(einstellung(2))
})
test('Buchungen zur Funktion: nichts wird gelöscht, weder Einstellung noch Dokument', async () => {
legeEntwurf(1)
legeEntwurf(2)
aw.seed('termineBookings', { $id: 'b_aaaaaaaa1', featureId: fid(1), status: 'bestaetigt' })
const erg = await loescheEntwuerfe(JETZT)
assert.deepEqual(erg, { geprueft: 2, geloescht: 1, uebrig: 0, fehler: 0 })
assert.ok(dok(1))
assert.ok(einstellung(1))
assert.equal(dok(2), undefined)
})
// ---------------------------------------------------------------- Deckel
test('130 Kandidaten: der erste Lauf löscht 100 und lässt 30 übrig, der zweite löscht die 30; die Liste wird seitenweise gelesen', async () => {
legeViele(100, 229)
let erg = await loescheEntwuerfe(JETZT)
assert.deepEqual(erg, { geprueft: 130, geloescht: 100, uebrig: 30, fehler: 0 })
assert.equal(aw.docs('portalFeatures').size, 30)
assert.equal(aw.docs('termineConfig').size, 30)
const abfragen = aw.listCalls('portalFeatures')
assert.equal(abfragen.length, 2, 'zwei Seiten')
for (const abfrage of abfragen) {
assert.ok(abfrage.queries.some((q) => q.method === 'limit' && q.values[0] === 100), 'Seite zu 100')
assert.ok(abfrage.queries.some((q) => q.method === 'orderAsc' && q.attribute === '$id'))
}
assert.equal(abfragen[0].queries.some((q) => q.method === 'cursorAfter'), false)
assert.equal(abfragen[1].queries.find((q) => q.method === 'cursorAfter').values[0], fid(199), 'die zweite Seite beginnt nach dem 100. Dokument')
assert.match(logText(), /Rest folgt/, 'der Deckel steht im Log')
erg = await loescheEntwuerfe(new Date(JETZT.getTime() + TAG))
assert.deepEqual(erg, { geprueft: 30, geloescht: 30, uebrig: 0, fehler: 0 })
assert.equal(aw.docs('portalFeatures').size, 0)
assert.equal(aw.docs('termineConfig').size, 0)
})
test('Übrig zählt nur Kandidaten, die der Deckel aufhält: junge Entwürfe in der Liste sind nie „offen“', async () => {
legeViele(100, 219) // 120 fällige
legeViele(220, 239, { tage: 10 }) // 20 junge
const erg = await loescheEntwuerfe(JETZT)
assert.deepEqual(erg, { geprueft: 140, geloescht: 100, uebrig: 20, fehler: 0 })
})
test('Nach 2000 geprüften Dokumenten endet der Lauf mit einer Logzeile ohne Kennungen; genau 2000 enden ohne', async () => {
legeViele(1000, 2999, { tage: 10 }) // 2000 junge: nichts zu tun
let erg = await loescheEntwuerfe(JETZT)
assert.deepEqual(erg, { geprueft: 2000, geloescht: 0, uebrig: 0, fehler: 0 })
assert.equal(log.warn.mock.calls.length, 0, 'genau 2000: der Lauf ist vollständig')
legeEntwurf(3000, { tage: 10 })
aw.resetListCalls()
erg = await loescheEntwuerfe(JETZT)
assert.deepEqual(erg, { geprueft: 2000, geloescht: 0, uebrig: 0, fehler: 0 })
assert.equal(log.warn.mock.calls.length, 1)
assert.match(String(log.warn.mock.calls[0].arguments[0]), /^\[termine-entwuerfe\] .*2000.*nächsten Tagen/)
assert.equal(aw.listCalls('portalFeatures').length, 21, '21 Seiten: die 21. zeigt, dass es weitergeht')
})
// ---------------------------------------------------------------- Fehler
test('Einzelfehler beim Löschen: fehler 1, die übrigen werden gelöscht, der Rest folgt am nächsten Tag', async () => {
legeViele(1, 3)
aw.failNext({ collection: 'termineConfig', method: 'DELETE', status: 500, message: 'Nachbau kaputt' }) // trifft den ersten Kandidaten
const erg = await loescheEntwuerfe(JETZT)
assert.deepEqual(erg, { geprueft: 3, geloescht: 2, uebrig: 0, fehler: 1 })
assert.ok(dok(1), 'bei einem Fehler an der Einstellung bleibt auch das Dokument')
assert.ok(einstellung(1))
assert.equal(dok(2), undefined)
assert.equal(dok(3), undefined)
assert.equal(log.error.mock.calls.length, 1)
assert.match(String(log.error.mock.calls[0].arguments[0]), /^\[termine-entwuerfe\] .*Nachbau kaputt/)
const naechster = await loescheEntwuerfe(new Date(JETZT.getTime() + TAG))
assert.deepEqual(naechster, { geprueft: 1, geloescht: 1, uebrig: 0, fehler: 0 })
assert.equal(dok(1), undefined)
})
test('Scheitert nur das Löschen des Funktionsdokuments, ist die Einstellung weg und der nächste Lauf löscht das Dokument', async () => {
legeEntwurf(1)
aw.failNext({ collection: 'portalFeatures', method: 'DELETE', status: 500, message: 'Nachbau kaputt' })
const erg = await loescheEntwuerfe(JETZT)
assert.deepEqual(erg, { geprueft: 1, geloescht: 0, uebrig: 0, fehler: 1 })
assert.ok(dok(1))
assert.equal(einstellung(1), undefined)
assert.deepEqual(await loescheEntwuerfe(new Date(JETZT.getTime() + TAG)), { geprueft: 1, geloescht: 1, uebrig: 0, fehler: 0 })
assert.equal(dok(1), undefined)
})
test('Ein Lesefehler beim Prüfen eines Kandidaten zählt als Fehler und löscht nichts; die übrigen Kandidaten laufen weiter', async () => {
legeViele(1, 2)
// 1. GET auf portalFeatures = die Liste, 2. = frisches Lesen des ersten Kandidaten
aw.failNext({ collection: 'portalFeatures', method: 'GET', status: 500, message: 'Nachbau kaputt', after: 1 })
const erg = await loescheEntwuerfe(JETZT)
assert.deepEqual(erg, { geprueft: 2, geloescht: 1, uebrig: 0, fehler: 1 })
assert.ok(dok(1))
assert.ok(einstellung(1))
assert.equal(dok(2), undefined)
})
test('Ist die Liste nicht lesbar, gibt es keinen Wurf: fehler 1, nichts geprüft', async () => {
legeEntwurf(1)
aw.failNext({ collection: 'portalFeatures', method: 'GET', status: 500, message: 'Nachbau kaputt' })
assert.deepEqual(await loescheEntwuerfe(JETZT), { geprueft: 0, geloescht: 0, uebrig: 0, fehler: 1 })
assert.ok(dok(1))
})
test('Die Zeit muss ein gültiges Date sein', async () => {
await assert.rejects(() => loescheEntwuerfe(), TypeError)
await assert.rejects(() => loescheEntwuerfe('heute'), TypeError)
})
// ---------------------------------------------------------------- Kundenloeschung
test('Kunde löschen: loescheEntwuerfeVonKunde entfernt alle off-Entwürfe des Kunden vollständig, unabhängig vom Alter und von Spuren', async () => {
legeEntwurf(1, { tage: 1 }) // frisch
legeEntwurf(2, { tage: 400, doc: { purchaseStatus: 'zurueckgesetzt' } }) // zurückgesetzt, der siteKey fällt mit dem Kunden
legeEntwurf(3, { tage: 5, doc: { activatedAt: vor(300), unlockedByPurchase: true } })
legeEntwurf(4, { tage: 5, config: false })
// nicht betroffen: andere Zustände, News, anderer Kunde
legeEntwurf(5, { tage: 400, doc: { state: 'active', enabled: true } })
legeEntwurf(6, { tage: 400, doc: { state: 'ordered' } })
legeEntwurf(7, { tage: 400, doc: { state: 'disabled', disabledAt: vor(5), disabledReason: 'kunde_geloescht' } })
legeEntwurf(8, { tage: 400, key: 'news' })
legeEntwurf(9, { tage: 400, kunde: ANDERER })
assert.deepEqual(await loescheEntwuerfeVonKunde(KUNDE), { geloescht: 4 })
for (const nr of [1, 2, 3, 4]) {
assert.equal(dok(nr), undefined, `Funktionsdokument ${nr}`)
assert.equal(einstellung(nr), undefined, `Einstellung ${nr}`)
}
for (const nr of [5, 6, 7, 9]) {
assert.ok(dok(nr), `Funktionsdokument ${nr} bleibt`)
assert.ok(einstellung(nr), `Einstellung ${nr} bleibt`)
}
assert.ok(aw.docs('portalFeatures').get('f_p_e8_news'))
assert.deepEqual(await loescheEntwuerfeVonKunde(KUNDE), { geloescht: 0 }, 'ein zweiter Aufruf findet nichts mehr')
assert.deepEqual(await loescheEntwuerfeVonKunde('c_unbekannt'), { geloescht: 0 })
})
test('Kunde löschen: Buchungen zur Funktion halten den Entwurf zurück, der Zustand wird unter den Sperren frisch gelesen', async () => {
legeEntwurf(1, { tage: 1 })
legeEntwurf(2, { tage: 1 })
aw.seed('termineBookings', { $id: 'b_aaaaaaaa1', featureId: fid(1), status: 'bestaetigt' })
let freigeben
const gehalten = new Promise((fertig) => (freigeben = fertig))
const sperre = withLocks([`feature-order:${KUNDE}:${pid(2)}`], () => gehalten)
aw.resetListCalls()
const lauf = loescheEntwuerfeVonKunde(KUNDE)
await warteBis(() => aw.listCalls('portalFeatures').length > 0)
aw.seed('portalFeatures', { ...dok(2), state: 'ordered', stripeSessionId: 'cs_test_9' })
freigeben()
await sperre
assert.deepEqual(await lauf, { geloescht: 0 })
assert.ok(dok(1))
assert.ok(einstellung(1))
assert.equal(dok(2).state, 'ordered')
assert.ok(einstellung(2))
})
test('Kunde löschen: ein Fehler bei einem Entwurf hält die übrigen nicht auf und wird danach weitergereicht', async () => {
legeViele(1, 3, { tage: 1 })
aw.failNext({ collection: 'termineConfig', method: 'DELETE', status: 500, message: 'Nachbau kaputt' })
await assert.rejects(() => loescheEntwuerfeVonKunde(KUNDE), /Nachbau kaputt/)
assert.ok(dok(1), 'der Entwurf mit Fehler bleibt')
assert.equal(dok(2), undefined)
assert.equal(dok(3), undefined)
assert.deepEqual(await loescheEntwuerfeVonKunde(KUNDE), { geloescht: 1 }, 'ein zweiter Versuch holt ihn nach')
})