Öffnungszeiten: Abo-Abgleich mit 30 Tagen Frist

gleicheZeitenAboAb(now) koppelt die im Wartungs-Abo enthaltenen Zeiten
(unlockedBy abo) an das Abo (Spec 5, Z11): Abo-Ende erkennen (aboEndeAt, K26),
nach 30 Berliner Kalendertagen abschalten (abo_ende, K27, Ersatz-Ticket),
bei Rückkehr des Abos wieder einschalten (Neubau nach dem Abschalten, K24
wieder), 30 Tage nach dem Abschalten Daten löschen und state off.
Lesefehler beim Abo: Kunde überspringen, nie abschalten oder löschen.

R25: activate() leert aboEndeAt und behaltenSessionId; starteBehalten lehnt
bei laufendem Wartungs-Abo mit 409 ab (Lesefehler 503).
R17: k24Mail mit einbau null und wieder schaltet beide Einbau-Blöcke ab,
K24 bekommt dafür den Block weiter um den Kasten "Wie geht es weiter?".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-05 03:16:56 +00:00
1 parent 53253f0451
commit efc6dcf332
8 files changed
+1285 -10

No files matched your search

+43 -1
View File
@@ -31,7 +31,7 @@ const st = await startFakeStripe()
useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url })
const { config } = await import('../server/config.js')
const { startTestApp, post, postWebhook } = await import('./support/testApp.js')
const { customerCookie, kundenansichtCookie, seedFixtures } = await import('./support/fixtures.js')
const { customerCookie, kundenansichtCookie, seedFixtures, subscriptionDoc, wartungDoc } = await import('./support/fixtures.js')
const { deleteDocument } = await import('../server/services/appwriteAdmin.js')
const { withLocks } = await import('../server/services/keyedLock.js')
const { setMailTransportForTests } = await import('../server/services/mailer.js')
@@ -254,6 +254,48 @@ test('Behalten nur nach Abo-Ende, sonst 409', async () => {
}
})
test('Läuft das Wartungs-Abo wieder, ist Behalten gesperrt (409, die Funktion ist enthalten); Lesefehler beim Abo ⇒ 503; beides ohne Spur (R25)', async () => {
const wartungLaeuft = (over = {}) => aw.seed('subscriptions', subscriptionDoc('sub_h', { ...wartungDoc, customerId: 'c1', status: 'active', ...over }))
const LAEUFT = 'Dein Wartungs-Abo läuft wieder – Öffnungszeiten live ist darin enthalten.'
// aktiv mit Abo-Ende, die Zahlung hat schon begonnen (offene Sitzung): kehrt das Abo zurück, gibt es auch die alte Seite nicht mehr heraus
seedeZeiten()
const sessionId = await starteBehalten()
const davor = spuren()
wartungLaeuft()
const res = await behalten(koerper())
assert.equal(res.status, 409, JSON.stringify(res.body))
assert.equal(res.body.error, LAEUFT)
assert.equal(res.body.url, undefined)
assert.deepEqual(spuren(), davor, 'keine neue Sitzung, kein Nachweis')
assert.equal(zeitenDoc().behaltenSessionId, sessionId)
// eine vorgemerkte Kündigung zählt als laufend (bis zum Periodenende)
wartungLaeuft({ cancelAtPeriodEnd: true })
assert.equal((await behalten(koerper())).status, 409)
// abgeschaltet nach dem Abo-Ende, Eingaben noch da: ebenso
for (const id of [...aw.docs('portalFeatures').keys()]) await deleteDocument('portalFeatures', id)
seedeAbgeschaltet()
await mitZeiten()
const aus = await behalten(koerper())
assert.equal(aus.status, 409)
assert.equal(aus.body.error, LAEUFT)
// Abo-Lage nicht lesbar: fail-closed
aw.failNext({ collection: 'subscriptions', method: 'GET', status: 500, message: 'Appwrite down' })
const kaputt = await behalten(koerper())
assert.equal(kaputt.status, 503)
assert.match(kaputt.body.error, /nicht prüfbar/)
assert.equal(JSON.stringify(kaputt.body).includes('Appwrite'), false)
// ohne laufendes Wartungs-Abo (Hosting, beendet) geht es weiter wie gehabt
aw.seed('subscriptions', subscriptionDoc('sub_h', { ...wartungDoc, customerId: 'c1', status: 'canceled', endedAt: ABO_ENDE }))
const frei = await behalten(koerper())
assert.equal(frei.status, 200, JSON.stringify(frei.body))
assert.equal(sessionAufrufe().length, 2, 'die erste Sitzung und diese; die gesperrten Versuche legten keine an')
})
test('Vorprüfungen wie bei der Bestellung: Kundenansicht 403, gleiche Zustimmung (beide Haken), Projekt, Schalter, Unterlagen; alles ohne Spur', async () => {
seedeZeiten()
// Kundenansicht (Mitarbeiter bestellen nicht fuer den Kunden)