Kundenansicht P6: News-Löschschritt und Zurücksetzen nach der Löschung im Abo-Ende-Abgleich

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-16 16:44:08 +00:00
1 parent 92ac61dffd
commit d8c952ac2a
2 files changed
+116 -11

No files matched your search

+34 -4
View File
@@ -14,12 +14,13 @@ const aw = await startAppwriteMock({
subscriptions: schemaKeys('subscriptions'),
contractEvents: schemaKeys('contractEvents'),
termineConfig: schemaKeys('termineConfig'),
newsPosts: schemaKeys('newsPosts'),
},
})
const st = await startFakeStripe()
useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url })
const { customers, hostingDoc, seedFixtures, subscriptionDoc, wartungDoc } = await import('./support/fixtures.js')
const { deleteDocument } = await import('../server/services/appwriteAdmin.js')
const { deleteDocument, updateDocument } = await import('../server/services/appwriteAdmin.js')
const { setMailTransportForTests } = await import('../server/services/mailer.js')
const { datumKurz } = await import('../server/services/termine/zeit.js')
const { gleicheAboFunktionenAb } = await import('../server/services/aboFunktionenAbgleich.js')
@@ -44,7 +45,7 @@ const leere = async (...collections) => {
seedFixtures(aw)
aw.seed('websiteProjects', { $id: 'p1', customerId: 'c1', projectName: 'Bäckerei' })
beforeEach(async () => {
await leere('portalFeatures', 'subscriptions', 'contractEvents', 'termineConfig')
await leere('portalFeatures', 'subscriptions', 'contractEvents', 'termineConfig', 'newsPosts')
mails.sent.length = 0
setMailTransportForTests(mails)
aw.seed('customers', customers.c1)
@@ -76,10 +77,10 @@ test('Rückkehr: nur solange die Daten da sind; ohne activatedAt zurück auf set
assert.deepEqual([f('news').state, f('news').disabledReason, f('news').aboEndeMailAt, f('shop').state, f('unterkunft').state, f('termine').state], ['active', null, null, 'active', 'active', 'disabled'])
aw.seed('termineConfig', { $id: 'f_p1_termine', einstellung: '{}', version: 1 }); await gleicheAboFunktionenAb(JETZT); assert.equal(f('termine').state, 'setup') // ohne activatedAt: setup, nicht active
})
test('Rückkehr des Webshops und des Buchungskalenders nicht nach der Löschung der Daten', async () => {
test('Rückkehr des Webshops und des Buchungskalenders nie nach der Löschung der Daten: das Dokument wird off (Task 9)', async () => {
abo(wartungDoc); const aus = { state: 'disabled', enabled: false, disabledReason: 'abo_ende', disabledAt: '2026-07-10T09:00:00.000Z', aboEndeMailAt: '2026-07-10T09:05:00.000Z' }
seed('shop', { ...aus, adminNote: '2026-10-10T09:00:00.000Z System: Shopdaten gelöscht am 2026-10-10' }); seed('unterkunft', { ...aus, adminNote: 'Buchungsdaten gelöscht am 2026-10-10' })
const r = await gleicheAboFunktionenAb(JETZT); assert.deepEqual([r.wiederEin, f('shop').state, f('unterkunft').state], [0, 'disabled', 'disabled'])
const r = await gleicheAboFunktionenAb(JETZT); assert.deepEqual([r.wiederEin, r.geloescht, f('shop').state, f('unterkunft').state], [0, 2, 'off', 'off'])
})
test('Alte Dokumente bleiben unberührt; Kunde ohne Adresse setzt nur den Marker; Mailfehler: Marker leer, nächster Lauf sendet, disabledAt bleibt', async (t) => {
t.mock.method(console, 'error', () => {}); t.mock.method(console, 'warn', () => {})
@@ -101,3 +102,32 @@ test('Projektstart-Funktionen (projekt): ohne jedes Abo unberührt (Aufbauphase)
test('abo-Dokumente kennen keine Aufbauphase: ohne jedes Abo-Dokument werden sie wie bisher abgeschaltet', async () => {
seed('news'); const r = await gleicheAboFunktionenAb(JETZT); assert.deepEqual([r.abgeschaltet, f('news').state], [1, 'disabled'])
})
// Task 9: News-Löschschritt und Zurücksetzen nach der Löschung
test('News: nach 30 Tagen werden Beiträge samt Bild gelöscht und das Dokument ist off; vorher und mit laufendem Abo nicht', async () => {
abo({ ...wartungDoc, status: 'canceled' }); const aus = { state: 'disabled', enabled: false, disabledReason: 'abo_ende', aboEndeMailAt: '2026-09-01T00:00:00.000Z' }
seed('news', { ...aus, disabledAt: new Date(JETZT.getTime() - 29 * TAG).toISOString() })
aw.seed('newsPosts', { $id: 'np1', customerId: 'c1', projectId: 'p1', title: 'A', imageId: null, visibleFrom: '2026-09-01T00:00:00.000Z', visibleUntil: null }); aw.seed('newsPosts', { $id: 'np2', customerId: 'c1', projectId: 'p_anders', title: 'B', imageId: null, visibleFrom: '2026-09-01T00:00:00.000Z', visibleUntil: null })
assert.equal((await gleicheAboFunktionenAb(JETZT)).geloescht, 0); assert.equal(aw.docs('newsPosts').size, 2)
await updateDocument('portalFeatures', 'f_p1_news', { disabledAt: new Date(JETZT.getTime() - 31 * TAG).toISOString() })
assert.equal((await gleicheAboFunktionenAb(JETZT)).geloescht, 1); assert.deepEqual([...aw.docs('newsPosts').keys()], ['np2'])
assert.deepEqual([f('news').state, f('news').disabledAt, f('news').aboEndeMailAt], ['off', null, null])
seed('news', { ...aus, disabledAt: new Date(JETZT.getTime() - 40 * TAG).toISOString() }); aw.seed('subscriptions', subscriptionDoc('sub_h', wartungDoc)) // Abo kehrt zurück: Rückkehr statt Löschung
aw.seed('newsPosts', { $id: 'np3', customerId: 'c1', projectId: 'p1', title: 'C', imageId: null, visibleFrom: '2026-09-01T00:00:00.000Z', visibleUntil: null })
await gleicheAboFunktionenAb(JETZT); assert.deepEqual([f('news').state, aw.docs('newsPosts').has('np3')], ['active', true])
})
test('News: ein Fehler beim Löschen lässt das Dokument disabled und zählt fehler', async (t) => {
abo({ ...wartungDoc, status: 'canceled' }); seed('news', { state: 'disabled', enabled: false, disabledReason: 'abo_ende', aboEndeMailAt: 'x', disabledAt: new Date(JETZT.getTime() - 31 * TAG).toISOString() })
aw.seed('newsPosts', { $id: 'np1', customerId: 'c1', projectId: 'p1', title: 'A', imageId: null, visibleFrom: '2026-09-01T00:00:00.000Z', visibleUntil: null }); t.mock.method(console, 'error', () => {}); aw.failNext({ collection: 'newsPosts', method: 'DELETE', status: 500 })
const r = await gleicheAboFunktionenAb(JETZT); assert.deepEqual([r.fehler, f('news').state], [1, 'disabled']); assert.equal((await gleicheAboFunktionenAb(JETZT)).geloescht, 1)
})
test('Shop und Unterkunft: nach der Löschung durch die bestehenden Jobs wird das Dokument off (auch bei laufendem Abo); ohne Marker nichts', async () => {
abo(wartungDoc); const aus = { state: 'disabled', enabled: false, disabledReason: 'abo_ende', disabledAt: '2026-06-01T00:00:00.000Z', aboEndeMailAt: '2026-06-01T00:05:00.000Z' }
seed('shop', { ...aus, adminNote: 'Shopdaten gelöscht am 2026-09-01' }); seed('unterkunft', { ...aus, adminNote: 'Buchungsdaten gelöscht am 2026-09-01' }); seed('termine', { ...aus })
const r = await gleicheAboFunktionenAb(JETZT); assert.equal(r.geloescht, 2); assert.deepEqual([f('shop').state, f('unterkunft').state, f('shop').aboEndeMailAt], ['off', 'off', null])
assert.deepEqual([f('termine').state, f('shop').adminNote], ['disabled', '']) // Terminplaner: setzt termine/jobs.js selbst auf off; der Löschvermerk endet mit dem Zurücksetzen
})
test('News: ohne Marker und nach Ablauf der Frist wird gelöscht, ohne noch einmal K39 zu senden', async () => {
abo({ ...wartungDoc, status: 'canceled' }); seed('news', { state: 'disabled', enabled: false, disabledReason: 'abo_ende', disabledAt: new Date(JETZT.getTime() - 35 * TAG).toISOString(), aboEndeMailAt: null })
const r = await gleicheAboFunktionenAb(JETZT); assert.deepEqual([r.geloescht, r.mails, f('news').state, mails.sent.length], [1, 0, 'off', 0])
})