Zusatzspeicher: Plan nach Stripe-Abgleich erneut prüfen, Aufgabenliste unter Sperre, K13 ohne Kosten

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Fable 5.1 committed 2026-09-29 09:31:28 +00:00
1 parent 662f8d8144
commit f66a713531
4 files changed
+153 -25

No files matched your search

+93 -5
View File
@@ -413,22 +413,81 @@ test('Plus in zwei Schritten: erst die bezahlte Menge ohne Verrechnung, dann ant
test('Plus bis zur bezahlten Menge: ein Schritt ohne Verrechnung, Aufgabe zum Periodenende, kein Einrichten', async () => {
await resetSubH({ units: 1, paidUnits: 3 })
const before = st.calls.length
const result = await changeStorage('c1', 3, consent)
const result = await changeStorage('c1', 2, consent)
const upd = subscriptionUpdates(before)
assert.equal(upd.length, 1)
assert.equal(upd[0].body['items[0][quantity]'], '3')
assert.equal(upd[0].body['items[0][quantity]'], '2')
assert.equal(upd[0].body['proration_behavior'], 'none')
assert.equal(doc().storageUnits, 3)
assert.equal(doc().storageUnits, 2)
assert.equal(doc().storagePaidUnits, 3)
const [ev] = await listContractEvents('c1')
assert.equal(ev.status, 'offen')
assert.equal(ev.details.kind, 'periodenende')
assert.equal(ev.details.toUnits, 3)
assert.equal(ev.details.toUnits, 2)
assert.equal(ev.dueAt, hostingDoc.currentPeriodEnd)
assert.equal(ev.termsVersion, consent.termsVersion, 'Bestellung mit Zustimmung')
assert.equal(result.change.kind, 'periodenende')
assert.ok(Math.abs(new Date(result.change.effectiveAt).getTime() - Date.now()) < 5000, 'fuer den Kunden sofort wirksam')
assert.equal(t13().subject, `[Speicher] reduzieren zum ${PERIOD_END_LABEL}: k@example.invalid`)
assert.ok(k13().attachments.length === 1, 'K13 mit PDF (Bestellung)')
// Fix 2 (3): K13 als Bestellung (PDF, Widerruf), aber ohne Einrichtung/anteilige Berechnung - schon bezahlt
const customerMail = k13()
assert.equal(customerMail.attachments.length, 1, 'K13 mit PDF (Bestellung)')
for (const text of ['schon bezahlt', 'keine zusätzlichen Kosten', '2 Pakete', '14 Tagen widerrufen', 'Vertragsunterlagen im Anhang']) {
assert.ok(customerMail.text.includes(text), `K13 ohne "${text}":\n${customerMail.text}`)
}
assert.doesNotMatch(customerMail.text, /wird eingerichtet|anteilig|ohne anteilige Erstattung/)
})
// Fix 2 (5): ein Plus genau zurueck auf die bezahlte Menge - konsoleH steht schon darauf, zum Periodenende ist
// nichts zu tun: die offene Verringerung wird geschlossen, keine neue Aufgabe, das Ereignis bleibt als Nachweis
test('Plus genau zurück auf die bezahlte Menge: Verringerung aufgehoben, keine neue Aufgabe, Nachweis bleibt', async () => {
await resetSubH({ units: 3, paidUnits: 3 })
await changeStorage('c1', 1, null)
const [minusEvent] = await listContractEvents('c1')
mails.sent.length = 0
const before = st.calls.length
const result = await changeStorage('c1', 3, consent)
assert.equal(subscriptionUpdates(before)[0].body['proration_behavior'], 'none')
assert.equal(doc().storageUnits, 3)
assert.deepEqual(await openTasks(), [])
const events = await listContractEvents('c1')
const superseded = events.find((e) => e.$id === minusEvent.$id)
assert.equal(superseded.status, 'erledigt')
assert.equal(superseded.doneBy, 'ersetzt')
const plusEvent = events.find((e) => e.$id === result.event.$id)
assert.equal(plusEvent.status, 'erledigt')
assert.equal(plusEvent.doneBy, 'keine Aufgabe')
assert.ok(plusEvent.doneAt)
assert.equal(plusEvent.details.kind, 'periodenende')
assert.equal(plusEvent.details.toUnits, 3)
assert.equal(plusEvent.termsVersion, consent.termsVersion, 'Nachweis der Bestellung')
assert.equal(result.change.task, false)
// K13 bestaetigt die Bestellung (nichts berechnet), T13 entfaellt: nichts zu tun
assert.equal(mails.sent.length, 1)
assert.ok(k13().text.includes('keine zusätzlichen Kosten'))
assert.equal(t13(), null)
})
// Fix 2 (1): weichen Spiegel und Stripe ab, wird der Plan nach dem Abgleich erneut geprueft - ein Plus ohne
// Zustimmung oder ueber das Kuendigungsformular darf nicht durchrutschen
test('Spiegel und Stripe weichen ab: Plan nach dem Abgleich erneut geprüft (400/409), nichts angelegt', async () => {
await resetSubH({ units: 3 })
// Spiegel sagt 3 Pakete, Stripe nur 1: aus dem vermeintlichen Minus auf 2 wird ein Plus
st.subscriptions.set('sub_h', stripeSubscription({ id: 'sub_h', customerId: 'c1', productKey: 'hosting', storageUnits: 1 }))
const before = st.calls.length
await assert.rejects(changeStorage('c1', 2, null), (e) => e.status === 400 && /zustimmen/.test(e.message))
assert.deepEqual(await listContractEvents('c1'), [], 'kein Ereignis')
assert.deepEqual(subscriptionUpdates(before), [], 'keine Stripe-Aenderung')
assert.deepEqual(mails.sent, [])
assert.equal(storageItem().quantity, 1)
await assert.rejects(changeStorage('c1', 2, null, { viaCancellation: true }), (e) => e.status === 409)
assert.deepEqual(await listContractEvents('c1'), [])
assert.deepEqual(subscriptionUpdates(before), [])
// Mit Zustimmung geht das Plus, mit dem frischen Stand aus Stripe
const result = await changeStorage('c1', 2, consent)
assert.equal(result.change.fromUnits, 1)
assert.equal(result.change.kind, 'einrichten')
assert.equal(doc().storageUnits, 2)
})
test('Plus ohne Zustimmung: 400; gekündigt: 409', async () => {
@@ -704,6 +763,35 @@ test('Spiegel nicht aktualisierbar nach der Stripe-Änderung: Aufruf gelingt, Er
}
})
// Fix 2 (4): aeltere offene Aufgaben werden nur ersetzt, wenn die neue Aufgabe existiert und Stripe die Aenderung
// uebernommen hat - sonst bliebe das Team ohne Aufgabe
test('Ältere offene Aufgabe bleibt, wenn das Ereignis nicht speicherbar ist oder Stripe Schritt 1 ablehnt', async () => {
await resetSubH({ units: 0 })
await changeStorage('c1', 2, consent)
const [first] = await listContractEvents('c1')
const original = console.error
console.error = () => {}
try {
aw.failNext({ collection: config.collections.contractEvents, method: 'POST', status: 500 })
await changeStorage('c1', 3, consent)
let events = await listContractEvents('c1')
assert.equal(events.length, 1)
assert.equal(events[0].$id, first.$id)
assert.equal(events[0].status, 'offen', 'aeltere Aufgabe bleibt offen')
assert.equal(storageItem().quantity, 3)
st.failNext({ method: 'POST', path: '/v1/subscriptions/sub_h', status: 400, message: 'Nachbau: abgelehnt' })
await assert.rejects(changeStorage('c1', 4, consent), (e) => e.status === 502)
events = await listContractEvents('c1')
assert.equal(events.length, 2)
assert.equal(events.find((e) => e.$id === first.$id).status, 'offen', 'aeltere Aufgabe bleibt offen')
assert.equal(events.find((e) => e.$id !== first.$id).status, 'gescheitert')
assert.equal((await openTasks()).length, 1)
} finally {
console.error = original
}
})
test('Ereignis nicht speicherbar: Stripe-Änderung läuft, T13 mit Block nachweis_fehlt', async () => {
await resetSubH({ units: 0 })
const errors = []