Löschfristen: bei abgeschnittenen Listen abbrechen, Teilfehler melden, Randfälle
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6c8e7703ae
commit
36b54f6dab
2 files changed
+126
-16
No files matched your search
+60
-1
@@ -21,7 +21,7 @@ const st = await startFakeStripe()
|
||||
const uploadDir = mkdtempSync(path.join(os.tmpdir(), 'loeschfristen-'))
|
||||
process.env.TAKEOVER_UPLOAD_DIR = uploadDir
|
||||
useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url })
|
||||
const { findDueDeletions, applyDeletions, CONSENT_METADATA_KEYS } = await import('../server/services/retention.js')
|
||||
const { findDueDeletions, applyDeletions, formatReport, CONSENT_METADATA_KEYS } = await import('../server/services/retention.js')
|
||||
const { run } = await import('../scripts/loeschfristen.mjs')
|
||||
const { stripeSubscription } = await import('./support/fixtures.js')
|
||||
const { setMailTransportForTests } = await import('../server/services/mailer.js')
|
||||
@@ -205,3 +205,62 @@ test('Skript: LOESCHFRISTEN=apply löscht', async () => {
|
||||
test('Skript: unbekannter Modus wird abgelehnt', async () => {
|
||||
await assert.rejects(run({ mode: 'loeschen', now: JAN_2026, log: () => {} }), /LOESCHFRISTEN/)
|
||||
})
|
||||
|
||||
test('abgeschnittene Liste: Abbruch vor jeder Planung', async () => {
|
||||
sub('l1_a', 'l1', { status: 'canceled', endedAt: '2019-01-01T00:00:00.000Z' })
|
||||
sub('l1_b', 'l1', { status: 'canceled', endedAt: '2019-01-01T00:00:00.000Z' })
|
||||
await assert.rejects(findDueDeletions(JAN_2026, { limit: 2 }), /Liste abgeschnitten, Paginierung nötig/)
|
||||
// mit ausreichendem Limit läuft es
|
||||
assert.ok(Array.isArray((await findDueDeletions(JAN_2026)).customers))
|
||||
})
|
||||
|
||||
test('applyDeletions: Fehler bei einem Dokument stoppen den Rest nicht und landen in failed', async () => {
|
||||
sub('f1_sub', 'f1', { status: 'canceled', endedAt: '2019-01-01T00:00:00.000Z' })
|
||||
sub('f2_sub', 'f2', { status: 'canceled', endedAt: '2019-01-01T00:00:00.000Z' })
|
||||
event('f1_ev', 'f1', 'f1_sub')
|
||||
event('f1_ev2', 'f1', 'f1_sub')
|
||||
stripeSub('f1_sub', 'f1')
|
||||
stripeSub('f2_sub', 'f2')
|
||||
aw.failNext({ collection: 'contractEvents', method: 'DELETE', status: 500, message: 'kaputt' })
|
||||
const plan = {
|
||||
customers: [
|
||||
{ customerId: 'f1', lastEndAt: '2019-01-01T00:00:00.000Z', subscriptionIds: ['f1_sub'], eventIds: ['f1_ev', 'f1_ev2'] },
|
||||
{ customerId: 'f2', lastEndAt: '2019-01-01T00:00:00.000Z', subscriptionIds: ['f2_sub'], eventIds: [] },
|
||||
],
|
||||
takeover: [],
|
||||
}
|
||||
const result = await applyDeletions(plan)
|
||||
assert.equal(result.failed.length, 1)
|
||||
assert.equal(result.failed[0].id, 'f1_ev')
|
||||
assert.match(result.failed[0].reason, /kaputt/)
|
||||
assert.equal(result.deleted.contractEvents, 1)
|
||||
assert.ok(!aw.docs('subscriptions').has('f2_sub'), 'zweiter Kunde wurde trotzdem bearbeitet')
|
||||
assert.match(formatReport(plan, result), /Fehlgeschlagen[\s\S]*f1_ev/)
|
||||
})
|
||||
|
||||
test('incomplete_expired zählt als beendet, incomplete hält auf und steht im Bericht', async () => {
|
||||
sub('i1_sub', 'i1', { status: 'incomplete_expired', endedAt: null, canceledAt: null, startedAt: '2020-01-01T00:00:00.000Z', $updatedAt: '2021-05-05T00:00:00.000Z' })
|
||||
sub('i2_sub', 'i2', { status: 'incomplete_expired', endedAt: null, canceledAt: '2020-02-02T00:00:00.000Z' })
|
||||
sub('i3_old', 'i3', { status: 'canceled', endedAt: '2018-01-01T00:00:00.000Z' })
|
||||
sub('i3_inc', 'i3', { status: 'incomplete', endedAt: null })
|
||||
const plan = await findDueDeletions(JAN_2026)
|
||||
assert.ok(dueIds(plan).includes('i1'))
|
||||
assert.ok(dueIds(plan).includes('i2'))
|
||||
assert.equal(plan.customers.find((c) => c.customerId === 'i2').lastEndAt, '2020-02-02T00:00:00.000Z')
|
||||
assert.ok(!dueIds(plan).includes('i3'))
|
||||
assert.ok(plan.pending.some((p) => p.customerId === 'i3' && p.subscriptionIds.includes('i3_inc')))
|
||||
assert.match(formatReport(plan), /Hält Löschung auf:[\s\S]*i3_inc/)
|
||||
})
|
||||
|
||||
test('aktives Abo mit cancelAtPeriodEnd und altem canceledAt zählt nicht als beendet', async () => {
|
||||
sub('c1_sub', 'c1', { status: 'active', cancelAtPeriodEnd: true, canceledAt: '2010-01-01T00:00:00.000Z', endedAt: null, currentPeriodEnd: '2010-02-01T00:00:00.000Z' })
|
||||
assert.ok(!dueIds(await findDueDeletions(JAN_2026)).includes('c1'))
|
||||
})
|
||||
|
||||
test('genehmigter Antrag ohne decidedAt: übersprungen und gemeldet, kein Rückgriff auf appliedAt', async () => {
|
||||
aw.seed('takeoverCredits', { $id: 'nd_tk', customerId: 'nd', status: 'genehmigt', appliedAt: '2010-01-01T00:00:00.000Z', files: '[]' })
|
||||
const plan = await findDueDeletions(JAN_2026)
|
||||
assert.ok(!plan.takeover.some((t) => t.id === 'nd_tk'))
|
||||
assert.ok(plan.skipped.some((t) => t.id === 'nd_tk'))
|
||||
assert.match(formatReport(plan), /Übersprungen[\s\S]*nd_tk/)
|
||||
})
|
||||
Reference in new issue
Block a user