Mails K22, K23 und Hinweis auf das Ende der Funktionen in K02/K03

Terminplaner TP2 E3. K22 "Terminplaner beendet" (Blöcke abo_ende, hosting,
vor_einrichtung, daten) und K23 "Terminplaner läuft wieder" im Layout von K19,
Du-Form, Texte als Entwurf für die Mail-Überarbeitung. K02 und K03 bekommen den
Block funktionen (in K03 innerhalb von abo); routes/abo.js setzt ihn nur beim
Wartungs-Abo und nie bei der Speicher-Kündigung.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-04 14:09:05 +00:00
1 parent eae6eee03c
commit 973135d2de
7 files changed
+282 -8

No files matched your search

+47 -1
View File
@@ -22,7 +22,7 @@ const aw = await startAppwriteMock({
const st = await startFakeStripe()
useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url })
const { config } = await import('../server/config.js')
const { startTestApp, post } = await import('./support/testApp.js')
const { startTestApp, post, postWebhook } = await import('./support/testApp.js')
const { hostingDoc, consent, seedFixtures, stripeSubscription } = await import('./support/fixtures.js')
const { default: aboRoutes, resetPublicRateLimit, publicRateLimitState, purgeRateLimit } = await import('../server/routes/abo.js')
const { listContractEvents, recordContractEvent } = await import('../server/services/contractEvents.js')
@@ -147,6 +147,52 @@ test('K03/K04/T08/T09: Produkt des Kunden, Vertragsende nach § 11 der Fassung 4
assert.ok(k03u.text.includes('§ 11 der Vertragsbedingungen'), k03u.text)
})
// Terminplaner TP2 (E3): Website-Analyse und Terminplaner gehören zum Wartungs-Abo; K03 und K02 sagen, dass sie mit ihm enden
const FUNKTIONEN_SATZ = 'Die Website-Analyse und der Terminplaner gehören zum Wartungs-Abo und enden mit ihm.'
const WARTUNG_V4 = { productKey: 'wartung', termsVersion: config.abo.termsVersion }
const k02 = () => bySubject('Bestätigung Ihrer Kündigung')
test('K03: Hinweis auf das Ende der Funktionen nur bei der Kündigung eines Wartungs-Abos', async () => {
await resetSubH({ patch: WARTUNG_V4, stripe: { productKey: 'wartung', metadata: { termsVersion: config.abo.termsVersion } } })
assert.equal((await cancel({ contract: 'abo', kind: 'ordentlich' })).status, 200)
assert.ok(k03().text.includes(FUNKTIONEN_SATZ), k03().text)
assert.ok(k03().text.includes('Die Termindaten löschen wir 30 Tage nach dem Abschalten; vorher können Sie Ihre Termine im Kundenportal ansehen und exportieren.'), k03().text)
// Hosting-Abo: weder Analyse noch Terminplaner
await resetSubH()
assert.equal((await cancel({ contract: 'abo', kind: 'ordentlich' })).status, 200)
assert.doesNotMatch(k03().text, /Terminplaner/)
// nur der Zusatzspeicher eines Wartungs-Abos gekündigt: das Abo läuft weiter, kein Hinweis
await resetSubH({ units: 3, patch: WARTUNG_V4, stripe: { productKey: 'wartung', metadata: { termsVersion: config.abo.termsVersion } } })
assert.equal((await cancel({ contract: 'zusatzspeicher' })).status, 200)
assert.doesNotMatch(k03().text, /Terminplaner/)
// unbekannte E-Mail (kein Abo gefunden): kein Hinweis
await resetSubH({ patch: WARTUNG_V4, stripe: { productKey: 'wartung', metadata: { termsVersion: config.abo.termsVersion } } })
assert.equal((await cancel({ contract: 'abo', kind: 'ordentlich', email: 'niemand@example.invalid' })).status, 200)
assert.doesNotMatch(bySubject('Eingangsbestätigung Ihrer Kündigung').text, /Terminplaner/)
})
test('K02: Kündigung im Stripe-Kundenportal, Hinweis auf das Ende der Funktionen nur beim Wartungs-Abo', async () => {
const gekuendigt = (id) => ({
id,
object: 'event',
type: 'customer.subscription.updated',
created: Math.floor(Date.now() / 1000),
data: { object: { id: 'sub_h', object: 'subscription', cancel_at_period_end: true, metadata: {} }, previous_attributes: { cancel_at_period_end: false } },
})
for (const [productKey, mitHinweis] of [['wartung', true], ['hosting', false]]) {
const patch = productKey === 'wartung' ? WARTUNG_V4 : {}
await resetSubH({ patch: { ...patch, cancelAtPeriodEnd: true }, stripe: { productKey, cancel_at_period_end: true, metadata: productKey === 'wartung' ? { termsVersion: config.abo.termsVersion } : {} } })
const res = await postWebhook(gekuendigt(`evt_k02_${productKey}`))
assert.equal(res.status, 200, JSON.stringify(res.body))
assert.ok(k02(), `${productKey}: K02`)
assert.equal(k02().text.includes(FUNKTIONEN_SATZ), mitHinweis, `${productKey}\n${k02().text}`)
assert.equal(/Terminplaner/.test(k02().text), mitHinweis, productKey)
}
})
test('2 von 3 Paketen ergibt Menge 1', async () => {
await resetSubH({ units: 3 })
const res = await cancel({ contract: 'zusatzspeicher', packages: '2' })