Retention: abhaengige Daten vor dem Abo loeschen, Fehlschlag haelt den Kunden fuer den naechsten Lauf
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bade4edb74
commit
9a2a70b491
2 files changed
+172
-29
No files matched your search
+117
-1
@@ -389,7 +389,8 @@ test('retention_loescht_featureConsents: Ein Fehler beim Löschen eines Nachweis
|
||||
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
|
||||
// ein zweiter Aufruf mit demselben Plan holt den Rest nach (in einem Lauf mit Abo-Dokument plant ihn findDueDeletions erneut, siehe die Tests
|
||||
// „Fehlschlag … hält den Kunden“); 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)
|
||||
@@ -398,3 +399,118 @@ test('retention_loescht_featureConsents: Ein Fehler beim Löschen eines Nachweis
|
||||
assert.equal(alt.deleted.featureConsents, 0)
|
||||
assert.deepEqual(alt.failed, [])
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------- Reihenfolge: abhängige Daten vor dem Abo (Fix Runde 1, R119)
|
||||
|
||||
/** Kunde mit beendetem Abo (2022), einem Vertragsereignis und einem Zustimmungsnachweis, ohne Beiträge und Funktionen. */
|
||||
function seedFaelligMitNachweis(id) {
|
||||
sub(`${id}_sub`, id, { status: 'canceled', endedAt: '2022-03-15T10:00:00.000Z' })
|
||||
event(`${id}_ev`, id, `${id}_sub`)
|
||||
stripeSub(`${id}_sub`, id)
|
||||
aw.seed('featureConsents', {
|
||||
$id: `${id}_c1`, customerId: id, projectId: `${id}p`, featureKey: 'termine', 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',
|
||||
})
|
||||
}
|
||||
const planVon = (plan, customerId) => ({
|
||||
...plan,
|
||||
customers: plan.customers.filter((c) => c.customerId === customerId),
|
||||
news: plan.news.filter((n) => n.customerId === customerId),
|
||||
takeover: [],
|
||||
})
|
||||
|
||||
test('Fehlschlag beim Löschen eines Zustimmungsnachweises hält den Kunden: Abo und Vertragsereignisse bleiben, der nächste Lauf plant den Nachweis erneut', async () => {
|
||||
seedFaelligMitNachweis('ro')
|
||||
const plan = await findDueDeletions(JAN_2026)
|
||||
assert.ok(dueIds(plan).includes('ro'))
|
||||
aw.failNext({ collection: 'featureConsents', method: 'DELETE', status: 500, message: 'kaputt' })
|
||||
const result = await applyDeletions(planVon(plan, 'ro'))
|
||||
assert.equal(result.failed.length, 1)
|
||||
assert.deepEqual([result.failed[0].what, result.failed[0].id], ['featureConsents', 'ro_c1'])
|
||||
assert.match(result.failed[0].reason, /kaputt/)
|
||||
assert.deepEqual(result.held, ['ro'], 'der Kunde wird als gehalten gemeldet')
|
||||
assert.equal(result.deleted.featureConsents, 0)
|
||||
assert.equal(result.deleted.subscriptions, 0)
|
||||
assert.equal(result.deleted.contractEvents, 0)
|
||||
assert.ok(aw.docs('subscriptions').has('ro_sub') && aw.docs('contractEvents').has('ro_ev') && aw.docs('featureConsents').has('ro_c1'), 'nichts davon ist weg')
|
||||
assert.match(formatReport(planVon(plan, 'ro'), result), /Abo-Daten bleiben[\s\S]*ro/)
|
||||
// der nächste Lauf findet den Kunden und den Nachweis wieder (das Abo-Dokument ist ja noch da) ...
|
||||
const zweiter = await findDueDeletions(JAN_2026)
|
||||
assert.ok(dueIds(zweiter).includes('ro'))
|
||||
assert.deepEqual(zweiter.news.find((n) => n.customerId === 'ro').consentIds, ['ro_c1'])
|
||||
// ... und löscht jetzt alles
|
||||
const fertig = await applyDeletions(planVon(zweiter, 'ro'))
|
||||
assert.deepEqual(fertig.failed, [])
|
||||
assert.deepEqual(fertig.held, [])
|
||||
assert.equal(fertig.deleted.featureConsents, 1)
|
||||
assert.equal(fertig.deleted.subscriptions, 1)
|
||||
assert.ok(!aw.docs('subscriptions').has('ro_sub') && !aw.docs('contractEvents').has('ro_ev') && !aw.docs('featureConsents').has('ro_c1'))
|
||||
assert.ok(!dueIds(await findDueDeletions(JAN_2026)).includes('ro'))
|
||||
})
|
||||
|
||||
test('Fehlschlag beim Löschen eines News-Beitrags hält den Kunden ebenso (dasselbe Muster wie bei den Nachweisen)', async () => {
|
||||
sub('rp_sub', 'rp', { status: 'canceled', endedAt: '2022-03-15T10:00:00.000Z' })
|
||||
event('rp_ev', 'rp', 'rp_sub')
|
||||
stripeSub('rp_sub', 'rp')
|
||||
aw.seed('newsPosts', { $id: 'rp_post', customerId: 'rp', projectId: 'rpp', type: 'note', imageId: null, title: 'Hi', text: 'Hallo' })
|
||||
const plan = await findDueDeletions(JAN_2026)
|
||||
assert.deepEqual(plan.news.find((n) => n.customerId === 'rp').posts.map((p) => p.id), ['rp_post'])
|
||||
aw.failNext({ collection: 'newsPosts', method: 'DELETE', status: 500, message: 'kaputt' })
|
||||
const result = await applyDeletions(planVon(plan, 'rp'))
|
||||
assert.deepEqual(result.failed.map((f) => [f.what, f.id]), [['newsPosts', 'rp_post']])
|
||||
assert.deepEqual(result.held, ['rp'])
|
||||
assert.ok(aw.docs('subscriptions').has('rp_sub') && aw.docs('contractEvents').has('rp_ev') && aw.docs('newsPosts').has('rp_post'))
|
||||
const zweiter = await findDueDeletions(JAN_2026)
|
||||
assert.ok(dueIds(zweiter).includes('rp'))
|
||||
assert.deepEqual(zweiter.news.find((n) => n.customerId === 'rp').posts.map((p) => p.id), ['rp_post'])
|
||||
const fertig = await applyDeletions(planVon(zweiter, 'rp'))
|
||||
assert.deepEqual(fertig.failed, [])
|
||||
assert.ok(!aw.docs('subscriptions').has('rp_sub') && !aw.docs('newsPosts').has('rp_post'))
|
||||
})
|
||||
|
||||
test('Fehlschlag beim Löschen eines Vertragsereignisses hält das Abo ebenfalls (sonst bliebe das Ereignis als Waise liegen)', async () => {
|
||||
sub('rq_sub', 'rq', { status: 'canceled', endedAt: '2022-03-15T10:00:00.000Z' })
|
||||
event('rq_ev', 'rq', 'rq_sub')
|
||||
stripeSub('rq_sub', 'rq')
|
||||
const plan = await findDueDeletions(JAN_2026)
|
||||
aw.failNext({ collection: 'contractEvents', method: 'DELETE', status: 500, message: 'kaputt' })
|
||||
const result = await applyDeletions(planVon(plan, 'rq'))
|
||||
assert.deepEqual(result.failed.map((f) => [f.what, f.id]), [['contractEvents', 'rq_ev']])
|
||||
assert.deepEqual(result.held, ['rq'])
|
||||
assert.ok(aw.docs('subscriptions').has('rq_sub'), 'das Abo-Dokument bleibt, damit der nächste Lauf den Kunden wieder findet')
|
||||
assert.ok(dueIds(await findDueDeletions(JAN_2026)).includes('rq'))
|
||||
const fertig = await applyDeletions(planVon(await findDueDeletions(JAN_2026), 'rq'))
|
||||
assert.deepEqual(fertig.failed, [])
|
||||
assert.ok(!aw.docs('subscriptions').has('rq_sub') && !aw.docs('contractEvents').has('rq_ev'))
|
||||
})
|
||||
|
||||
test('Reihenfolge: Beiträge, Funktionen und Nachweise gehen vor den Vertragsereignissen und Abo-Dokumenten', async () => {
|
||||
seedFaelligMitNachweis('rr')
|
||||
const plan = await findDueDeletions(JAN_2026)
|
||||
const reihenfolge = []
|
||||
const echt = globalThis.fetch
|
||||
globalThis.fetch = async (url, optionen) => {
|
||||
if (optionen?.method === 'DELETE') reihenfolge.push(String(url).match(/collections\/([^/]+)\/documents/)?.[1])
|
||||
return echt(url, optionen)
|
||||
}
|
||||
try {
|
||||
await applyDeletions(planVon(plan, 'rr'))
|
||||
} finally {
|
||||
globalThis.fetch = echt
|
||||
}
|
||||
assert.deepEqual(reihenfolge, ['featureConsents', 'contractEvents', 'subscriptions'])
|
||||
})
|
||||
|
||||
test('Nachweis-Liste an der Grenze: findDueDeletions bricht ab, bevor etwas geplant wird (fail-closed)', async () => {
|
||||
sub('rs_sub', 'rs', { status: 'canceled', endedAt: '2022-03-15T10:00:00.000Z' })
|
||||
stripeSub('rs_sub', 'rs')
|
||||
const grenze = Math.max(...['subscriptions', 'contractEvents', 'newsPosts', 'portalFeatures', 'takeoverCredits'].map((c) => aw.docs(c).size)) + 1
|
||||
for (let i = 0; i < grenze; i += 1) {
|
||||
aw.seed('featureConsents', {
|
||||
$id: `rs_c${i}`, customerId: 'rs', projectId: 'rsp', featureKey: 'termine', stripeSessionId: '', acceptedAt: '2022-02-01T10:00:00.000+00:00',
|
||||
fassungen: '{}', texte: '[]', textHash: 'x', ip: '', userAgent: '', konto: 'k',
|
||||
})
|
||||
}
|
||||
await assert.rejects(findDueDeletions(JAN_2026, { limit: grenze }), /Liste abgeschnitten, Paginierung nötig \(featureConsents/)
|
||||
assert.equal([...aw.docs('featureConsents').values()].filter((c) => c.customerId === 'rs').length, grenze, 'nichts gelöscht')
|
||||
})
|
||||
Reference in new issue
Block a user