Terminplaner TP2: Bestell-Gate und Gesundheits-Stichwortpruefung

fragebogen.js ersetzt den Stub aus Block B: bestellpruefung (Kriterien (a) bis (h),
je Punkt ein Eintrag, nach Schrittfolge sortiert, Abo-Punkt nur bei mitAbo !== false),
SCHRITT_TITEL, gesundheitsWarnungen (Stichworte nur am Wortanfang, hoechstens 10
Meldungen) und bestellungAnsicht fuer die Bestellseite. einstellungen.js exportiert
sichtbareLeistungen und MELDUNG_PFLICHT. Testhelfer bestellbarerStand und entwurfDoc;
der Stub-Test entfaellt, die zwei B-Tests mit dem Stub laufen gegen das echte Gate.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-04 19:00:12 +00:00
1 parent ab47fabcb2
commit 008ad4fb1e
7 files changed
+869 -47

No files matched your search

+73 -8
View File
@@ -27,6 +27,7 @@ const { config } = await import('../server/config.js')
const { startTestApp, post } = await import('./support/testApp.js')
const { customerCookie, kundenansichtCookie, seedFixtures, subscriptionDoc, wartungDoc } = await import('./support/fixtures.js')
const { default: funktionenRoutes } = await import('../server/routes/funktionen.js')
const { bestellbarerStand, entwurfDoc } = await import('./support/fragebogenStand.js')
const { deleteDocument } = await import('../server/services/appwriteAdmin.js')
const reg = await import('../server/services/featureRegistry.js')
const order = await import('../server/services/featureOrder.js')
@@ -349,9 +350,10 @@ test('Bestellpruefung: kaputter Haken (keine Liste) lehnt ab, 500 ohne Rohtext (
test('Standard-Haken liest den Entwurf nur, wenn es ein Dokument gibt, und ruft die Bestellpruefung ohne Abo', async () => {
const haken = reg.featureHaken('termine')
const arg = { customerId: 'c1', projectId: 'p1', feature: T }
// ohne Dokument: keine Abfrage von termineConfig, der Platzhalter meldet nichts offen
// ohne Dokument: keine Abfrage von termineConfig, das echte Gate meldet nur die Branche offen (kein Abo-Punkt)
aw.failNext({ collection: 'termineConfig', status: 500 })
assert.deepEqual(await haken.bestellpruefung(arg), [])
const ohneDoc = await haken.bestellpruefung(arg)
assert.deepEqual(ohneDoc.map((p) => p.schritt), ['branche'])
// mit Dokument: der Entwurf wird geladen (der eingeplante Lesefehler trifft)
aw.seed('portalFeatures', { $id: 'f_p1_termine', customerId: 'c1', projectId: 'p1', featureKey: 'termine', state: 'off', enabled: false, siteKey: 'abcdefghijklmnop' })
const log = stumm()
@@ -360,10 +362,21 @@ test('Standard-Haken liest den Entwurf nur, wenn es ein Dokument gibt, und ruft
} finally {
log.mock.restore()
}
assert.deepEqual(await haken.bestellpruefung(arg), [], 'danach wieder normal')
// Dokument ohne gespeicherten Entwurf: wie ohne Dokument
assert.deepEqual((await haken.bestellpruefung(arg)).map((p) => p.schritt), ['branche'], 'danach wieder normal')
// bestellbarer Entwurf: leer; offener Fragebogen: Punkte der Schritte, nie der Abo-Punkt (mitAbo: false)
aw.seed('termineConfig', entwurfDoc('f_p1_termine', { customerId: 'c1', projectId: 'p1' }))
assert.deepEqual(await haken.bestellpruefung(arg), [])
const offen = bestellbarerStand()
offen.fragebogen.bestaetigt = []
aw.seed('termineConfig', entwurfDoc('f_p1_termine', { customerId: 'c1', projectId: 'p1', stand: offen }))
const punkte = await haken.bestellpruefung(arg)
assert.equal(punkte.length, 8)
assert.ok(punkte.every((p) => p.schritt !== 'bestellen'))
})
// ------------------------------------------------------------------------------------------ 7 Abo ---
const SCHRITTE_OHNE_BRANCHE = ['wer', 'leistungen', 'zeiten', 'regeln', 'gastfelder', 'benachrichtigung', 'website']
const ABO_TEXT = {
kein_abo: 'Für den Terminplaner brauchst du ein laufendes Wartungs-Abo.',
gekuendigt: 'Dein Wartungs-Abo ist gekündigt. Für den Terminplaner brauchst du ein laufendes Abo.',
@@ -443,15 +456,67 @@ test('Abo-Lesefehler: 500 ohne Rohtext, fail-closed ohne Spur', async () => {
}
})
test('Abo-Stufe mit leerer Pruefung (Stub und Standard-Gate): ohne Abo 409 kein_abo, nie bestellung_offen', async () => {
test('Abo-Stufe mit dem echten Gate: ohne Abo und bestellbarem Entwurf 409 kein_abo, mit offenem Fragebogen 409 bestellung_offen (ohne bestellen)', async () => {
const c3 = customerCookie('c3')
// Standard-Haken (Platzhalter von Block B meldet nichts offen)
aboAbgelehnt(await checkout('termine', { projectId: 'p3', zustimmung: zustimmung() }, c3), 'kein_abo')
// ueberschriebener Haken mit leerer Liste
const entwurf = (stand) => {
aw.seed('portalFeatures', { $id: 'f_p3_termine', customerId: 'c3', projectId: 'p3', featureKey: 'termine', state: 'off', enabled: false, siteKey: 'abcdefghijklmnop' })
aw.seed('termineConfig', entwurfDoc('f_p3_termine', { customerId: 'c3', projectId: 'p3', stand }))
}
// Standard-Haken, Kunde ohne Abo, bestellbarer Entwurf: das Gate ist leer, die Abo-Stufe lehnt ab
entwurf(bestellbarerStand())
const featuresVorher = aw.docs('portalFeatures').size
const abgelehnt = await checkout('termine', { projectId: 'p3', zustimmung: zustimmung() }, c3)
assert.equal(abgelehnt.status, 409)
assert.equal(abgelehnt.body.code, 'kein_abo')
assert.notEqual(abgelehnt.body.code, 'bestellung_offen')
assert.equal(abgelehnt.body.error, ABO_TEXT.kein_abo)
assert.deepEqual(spuren(), { sessions: 0, consents: 0, features: featuresVorher }, 'keine Sitzung, kein Nachweis, kein neues Dokument')
// Kunde ohne Abo, Fragebogen offen: bestellung_offen, und die Liste enthaelt keinen Abo-Punkt (mitAbo: false)
const offen = bestellbarerStand()
offen.fragebogen.bestaetigt = ['branche']
entwurf(offen)
const res = await checkout('termine', { projectId: 'p3', zustimmung: zustimmung() }, c3)
assert.equal(res.status, 409)
assert.equal(res.body.code, 'bestellung_offen')
assert.equal(res.body.offen.length, 7)
assert.ok(res.body.offen.every((p) => p.schritt !== 'bestellen'), 'kein Eintrag bestellen')
assert.deepEqual(res.body.offen.map((p) => p.schritt), SCHRITTE_OHNE_BRANCHE)
assert.deepEqual(spuren(), { sessions: 0, consents: 0, features: featuresVorher })
// ohne Entwurf (gar kein Dokument): ebenfalls bestellung_offen mit der Branche, nie ein Abo-Punkt
for (const col of ['portalFeatures', 'termineConfig']) for (const id of aw.docs(col).keys()) await deleteDocument(col, id)
const leer = await checkout('termine', { projectId: 'p3', zustimmung: zustimmung() }, c3)
assert.equal(leer.status, 409)
assert.equal(leer.body.code, 'bestellung_offen')
assert.deepEqual(leer.body.offen.map((p) => p.schritt), ['branche'])
// ueberschriebener Haken mit leerer Liste: weiter die Abo-Stufe
T.bestellpruefung = async () => []
aboAbgelehnt(await checkout('termine', { projectId: 'p3', zustimmung: zustimmung() }, c3), 'kein_abo')
})
test('Echtes Gate bis zur Stripe-Sitzung: Kunde mit Wartungs-Abo und bestellbarem Entwurf kommt durch, mit offenem Punkt nicht', async () => {
const entwurf = (stand) => {
aw.seed('portalFeatures', { $id: 'f_p1_termine', customerId: 'c1', projectId: 'p1', featureKey: 'termine', state: 'off', enabled: false, siteKey: 'abcdefghijklmnop' })
aw.seed('termineConfig', entwurfDoc('f_p1_termine', { customerId: 'c1', projectId: 'p1', stand }))
}
// ein offener Punkt (Impressum fehlt): 409 bestellung_offen, keine Sitzung, kein Nachweis
const luecke = bestellbarerStand()
luecke.einstellung.betrieb.impressumUrl = null
entwurf(luecke)
const offen = await checkout('termine', bodyT())
assert.equal(offen.status, 409)
assert.equal(offen.body.code, 'bestellung_offen')
assert.deepEqual(offen.body.offen.map((p) => p.schritt), ['website'])
assert.deepEqual({ sessions: sitzungen().length, consents: nachweise().length }, { sessions: 0, consents: 0 })
// bestellbarer Entwurf: Gate leer, Abo laeuft, die Stripe-Sitzung entsteht
entwurf(bestellbarerStand())
const ok = await checkout('termine', bodyT())
assert.equal(ok.status, 200, JSON.stringify(ok.body))
assert.match(ok.body.url, /^https?:\/\//)
assert.equal(sitzungen().length, 1)
assert.equal(nachweise().length, 1)
assert.equal(termineDoc().state, 'ordered')
})
test('aboMeldung: Wortlaute aus dem Register, Akkusativ aus mails.formen.akk, unbekannter Grund wie unbekannt', () => {
for (const grund of ['kein_abo', 'gekuendigt', 'nicht_aktiv']) assert.equal(order.aboMeldung(T, grund), ABO_TEXT[grund], grund)
assert.equal(order.aboMeldung(T, 'unbekannt'), 'Das Abo konnte gerade nicht geprüft werden.')
@@ -544,7 +609,7 @@ test('Abo-Memo: je Anfrage ein aboMemo(), News fragt das Abo nie', async () => {
})
// ------------------------------------------------------------------------------------------ Sperre, Zustand, Nachweis (B7) ---
// Ab hier der Rest der Route: die Vorpruefungen sind bestanden (Fragebogen-Pruefung per Haken uebersprungen, Block C fuellt sie).
// Ab hier der Rest der Route: die Vorpruefungen sind bestanden (Fragebogen-Pruefung per Haken uebersprungen; das echte Gate pruefen die Tests der Abschnitte 6 und 7).
const bereit = () => {
T.bestellpruefung = async () => []
}