Retention: Zustimmungsnachweise der Funktionsbestellung drei Jahre nach Abo-Ende loeschen
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a629b2519b
commit
5b5b4b90eb
4 files changed
+106
-15
No files matched your search
@@ -17,6 +17,7 @@ const aw = await startAppwriteMock({
|
||||
takeoverCredits: schemaKeys('takeoverCredits'),
|
||||
newsPosts: schemaKeys('newsPosts'),
|
||||
portalFeatures: schemaKeys('portalFeatures'),
|
||||
featureConsents: schemaKeys('featureConsents'),
|
||||
},
|
||||
})
|
||||
const st = await startFakeStripe()
|
||||
@@ -327,3 +328,73 @@ test('retention: Analyse-Dokument schaltet auch den Sammeldienst ab', async () =
|
||||
analyseDeps.client = ok
|
||||
}
|
||||
})
|
||||
|
||||
test('retention_loescht_featureConsents: Zustimmungsnachweise fälliger Kunden werden geplant, gelöscht und berichtet; andere bleiben', async () => {
|
||||
const consent = (id, customerId, projectId, featureKey) => aw.seed('featureConsents', {
|
||||
$id: id, customerId, projectId, featureKey, stripeSessionId: `cs_${id}`, acceptedAt: '2022-02-01T10:00:00.000+00:00',
|
||||
fassungen: '{}', texte: '[]', textHash: 'x', ip: '10.0.0.1', userAgent: 'Browser', konto: 'k@example.com',
|
||||
})
|
||||
// fälliger Kunde: Abo endete 2022, zwei Funktionsbestellungen (Terminplaner und News) in zwei Projekten, sonst nichts
|
||||
sub('fc_sub', 'fc', { status: 'canceled', endedAt: '2022-03-15T10:00:00.000Z' })
|
||||
stripeSub('fc_sub', 'fc')
|
||||
consent('fc_c1', 'fc', 'fcp1', 'termine')
|
||||
consent('fc_c2', 'fc', 'fcp2', 'news')
|
||||
// nicht fälliger Kunde (laufendes Abo) mit Nachweis, und ein Kunde ohne Abo (nur News) mit Nachweis: beide bleiben wie bisher unberührt
|
||||
sub('fc2_sub', 'fc2', { status: 'active' })
|
||||
consent('fc2_c1', 'fc2', 'fc2p1', 'termine')
|
||||
consent('fc3_c1', 'fc3', 'fc3p1', 'news')
|
||||
aw.seed('newsPosts', { $id: 'fc3_post', customerId: 'fc3', projectId: 'fc3p1', type: 'note', imageId: null, title: 'Hi', text: 'Hallo' })
|
||||
|
||||
// die Abfrage nach customerId hat im Schema (scripts/feature-setup.mjs) einen Index
|
||||
const { FEATURE_CONSENT_INDEXES } = await import('../scripts/feature-setup.mjs')
|
||||
assert.ok(FEATURE_CONSENT_INDEXES.some((i) => i.attributes.length === 1 && i.attributes[0] === 'customerId'), 'Index auf featureConsents.customerId fehlt')
|
||||
|
||||
const plan = await findDueDeletions(JAN_2026)
|
||||
const eintrag = plan.news.find((n) => n.customerId === 'fc')
|
||||
assert.ok(eintrag, 'ein fälliger Kunde nur mit Nachweisen (ohne Beiträge und Funktionen) steht im Plan')
|
||||
assert.deepEqual(eintrag.consentIds.sort(), ['fc_c1', 'fc_c2'])
|
||||
assert.deepEqual(eintrag.posts, [])
|
||||
assert.deepEqual(eintrag.featureIds, [])
|
||||
assert.ok(!plan.news.some((n) => n.customerId === 'fc2' || n.customerId === 'fc3'), 'nicht fälliger Kunde und Kunde ohne Abo bleiben außerhalb')
|
||||
assert.match(formatReport(plan), /Kunde fc: 0 News-Beitrag\/-Beiträge mit 0 Foto\(s\) zu löschen, 0 Funktion\(en\) abzuschalten, 2 Zustimmungsnachweis\(e\) zu löschen/)
|
||||
|
||||
// Probelauf löscht nichts
|
||||
const probe = await run({ mode: '', now: JAN_2026, log: () => {} })
|
||||
assert.equal(probe.applied, false)
|
||||
assert.ok(aw.docs('featureConsents').has('fc_c1') && aw.docs('featureConsents').has('fc_c2'))
|
||||
|
||||
const result = await applyDeletions({ ...plan, customers: plan.customers.filter((c) => c.customerId === 'fc'), news: plan.news.filter((n) => n.customerId === 'fc') })
|
||||
assert.deepEqual(result.failed, [])
|
||||
assert.equal(result.deleted.featureConsents, 2)
|
||||
assert.ok(!aw.docs('featureConsents').has('fc_c1') && !aw.docs('featureConsents').has('fc_c2'))
|
||||
assert.ok(aw.docs('featureConsents').has('fc2_c1') && aw.docs('featureConsents').has('fc3_c1'), 'Nachweise anderer Kunden bleiben')
|
||||
assert.match(formatReport(plan, result), /Zustimmungsnachweise zu Funktionsbestellungen: 2 gelöscht\./)
|
||||
// ein zweiter Lauf findet nichts mehr für diesen Kunden
|
||||
const danach = await findDueDeletions(JAN_2026)
|
||||
assert.ok(!danach.news.some((n) => n.customerId === 'fc'))
|
||||
})
|
||||
|
||||
test('retention_loescht_featureConsents: Ein Fehler beim Löschen eines Nachweises stoppt den Rest nicht und landet in failed; ältere Pläne ohne consentIds laufen weiter', async () => {
|
||||
const consent = (id, customerId) => aw.seed('featureConsents', {
|
||||
$id: id, customerId, projectId: 'fcxp', featureKey: 'termine', stripeSessionId: '', acceptedAt: '2021-02-01T10:00:00.000+00:00',
|
||||
fassungen: '{}', texte: '[]', textHash: 'x', ip: '', userAgent: '', konto: 'Mitarbeiter: Test',
|
||||
})
|
||||
consent('fcx_c1', 'fcx')
|
||||
consent('fcx_c2', 'fcx')
|
||||
aw.failNext({ collection: 'featureConsents', method: 'DELETE', status: 500, message: 'kaputt' })
|
||||
const result = await applyDeletions({ customers: [], takeover: [], news: [{ customerId: 'fcx', posts: [], featureIds: [], consentIds: ['fcx_c1', 'fcx_c2'] }] })
|
||||
assert.equal(result.failed.length, 1)
|
||||
assert.equal(result.failed[0].what, 'featureConsents')
|
||||
assert.equal(result.failed[0].id, 'fcx_c1')
|
||||
assert.match(result.failed[0].reason, /kaputt/)
|
||||
assert.equal(result.deleted.featureConsents, 1)
|
||||
assert.ok(aw.docs('featureConsents').has('fcx_c1') && !aw.docs('featureConsents').has('fcx_c2'))
|
||||
// der nächste Lauf holt den Rest nach; ein Eintrag, der inzwischen weg ist (404), zählt nicht
|
||||
const nochmal = await applyDeletions({ customers: [], takeover: [], news: [{ customerId: 'fcx', posts: [], featureIds: [], consentIds: ['fcx_c1', 'fcx_c2'] }] })
|
||||
assert.deepEqual(nochmal.failed, [])
|
||||
assert.equal(nochmal.deleted.featureConsents, 1)
|
||||
// Pläne ohne consentIds (ältere Form) bleiben gültig
|
||||
const alt = await applyDeletions({ customers: [], takeover: [], news: [{ customerId: 'fcx', posts: [], featureIds: [] }] })
|
||||
assert.equal(alt.deleted.featureConsents, 0)
|
||||
assert.deepEqual(alt.failed, [])
|
||||
})
|
||||
Reference in new issue
Block a user