Checkout je Funktion: Zustimmungsnachweis vor der Weiterleitung, Zustaende aus dem Register
Rest der Route POST /api/funktionen/:key/checkout unter der Sperre feature-order:<Kunde>:<Projekt>: Zustand aus dem Register (setup/active, disabled, ordered mit offener Sitzung, "Zahlung fortsetzen"), Stripe-Sitzung mit der Zeit des Klicks, Zustimmungsnachweis (Serverfassungen und -texte) vor markOrdered und vor der URL. Standard-Haken belehrung mit BELEHRUNG_PLATZHALTER fuer den Terminplaner. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1f6ae0f7e0
commit
7fcb268644
5 files changed
+534
-43
No files matched your search
+401
-13
@@ -1,14 +1,17 @@
|
||||
import { test, after, beforeEach, mock } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { createHash } from 'node:crypto'
|
||||
import { startAppwriteMock } from './support/appwriteMock.js'
|
||||
import { startFakeStripe } from './support/fakeStripe.js'
|
||||
import { useTestEnv } from './support/testEnv.js'
|
||||
import { schemaKeys } from './support/schema.js'
|
||||
|
||||
// Checkout je Funktion, Teil 1 (Task B6): POST /api/funktionen/:key/checkout mit den Vorpruefungen in der
|
||||
// Reihenfolge der Spezifikation (1 Kundenansicht, 2 Schluessel, 3 Zustimmung, 4 Projekt, 5 Schalter, 5b Unterlagen,
|
||||
// 6 Bestellpruefung, 7 Wartungs-Abo). Jede Ablehnung laesst keine Spur zurueck: keine Stripe-Sitzung, kein
|
||||
// featureConsents- und kein portalFeatures-Dokument. Sperre, Zustandspruefung und Nachweis folgen in Task B7.
|
||||
// Checkout je Funktion (Tasks B6 und B7): POST /api/funktionen/:key/checkout.
|
||||
// Teil 1 (B6): Vorpruefungen in der Reihenfolge der Spezifikation (1 Kundenansicht, 2 Schluessel, 3 Zustimmung, 4 Projekt,
|
||||
// 5 Schalter, 5b Unterlagen, 6 Bestellpruefung, 7 Wartungs-Abo). Jede Ablehnung laesst keine Spur zurueck: keine
|
||||
// Stripe-Sitzung, kein featureConsents- und kein portalFeatures-Dokument.
|
||||
// Teil 2 (B7, ab "Sperre, Zustand, Nachweis"): Zustandspruefung, Stripe-Sitzung, Zustimmungsnachweis vor der Weiterleitung,
|
||||
// markOrdered; Wiederverwendung der offenen Sitzung, "Zahlung fortsetzen", parallele Klicks.
|
||||
const aw = await startAppwriteMock({
|
||||
schema: {
|
||||
portalFeatures: schemaKeys('portalFeatures'),
|
||||
@@ -26,6 +29,8 @@ const { default: funktionenRoutes } = await import('../server/routes/funktionen.
|
||||
const { deleteDocument } = await import('../server/services/appwriteAdmin.js')
|
||||
const reg = await import('../server/services/featureRegistry.js')
|
||||
const order = await import('../server/services/featureOrder.js')
|
||||
const consents = await import('../server/services/featureConsents.js')
|
||||
const sf = await import('../server/services/siteFeatures.js')
|
||||
|
||||
const app = await startTestApp({ '/api/funktionen': funktionenRoutes })
|
||||
after(() => Promise.all([app.close(), aw.close(), st.close()]))
|
||||
@@ -514,20 +519,403 @@ test('Abo-Memo: je Anfrage ein aboMemo(), News fragt das Abo nie', async () => {
|
||||
}
|
||||
})
|
||||
|
||||
// Zwischenstand bis Task B7: der Rest der Route (Sperre, Zustand, Stripe-Sitzung, Nachweis, markOrdered) gilt noch nur fuer News.
|
||||
// Eine Termine-Bestellung, die alle Vorpruefungen besteht, darf bis dahin weder Sitzung noch Dokument noch Nachweis anlegen;
|
||||
// B7 ersetzt diesen Test durch den Erfolgsfall.
|
||||
test('Termine-Bestellung nach bestandenen Vorpruefungen legt bis Task B7 noch nichts an (503)', async () => {
|
||||
// ------------------------------------------------------------------------------------------ Sperre, Zustand, Nachweis (B7) ---
|
||||
// Ab hier der Rest der Route: die Vorpruefungen sind bestanden (Fragebogen-Pruefung per Haken uebersprungen, Block C fuellt sie).
|
||||
const bereit = () => {
|
||||
T.bestellpruefung = async () => []
|
||||
}
|
||||
const featureDoc = (state, extra = {}) =>
|
||||
aw.seed('portalFeatures', { $id: 'f_p1_termine', customerId: 'c1', projectId: 'p1', featureKey: 'termine', state, enabled: state === 'active', siteKey: 'abcdefghijklmnop', ...extra })
|
||||
const sitzungen = () => [...st.checkoutSessions.values()]
|
||||
const nachweise = () => [...aw.docs('featureConsents').values()]
|
||||
const termineDoc = () => aw.docs('portalFeatures').get('f_p1_termine')
|
||||
const sha256 = (text) => createHash('sha256').update(text).digest('hex')
|
||||
const belehrungText = async (key) => (await reg.featureHaken(key).belehrung?.()) ?? null
|
||||
const erwarteteTexte = async (key) => consents.nachweisTexte(reg.featureConfig(key), { belehrung: await belehrungText(key) })
|
||||
const hakenTexte = (feature) => feature.consent.items.filter((item) => item.haken).map((item) => item.text).join('\n\n')
|
||||
|
||||
test('Nachweis steht vor der Weiterleitung: Serverfassungen und -texte in Reihenfolge, Hash, ein Zeitpunkt', async () => {
|
||||
bereit()
|
||||
const vor = new Date().toISOString()
|
||||
const res = await checkout('termine', bodyT())
|
||||
assert.equal(res.status, 503)
|
||||
assert.equal(res.body.code, 'bestellung_nicht_freigeschaltet')
|
||||
keineSpuren()
|
||||
const nach = new Date().toISOString()
|
||||
assert.equal(res.status, 200, JSON.stringify(res.body))
|
||||
assert.deepEqual(Object.keys(res.body), ['url'])
|
||||
assert.equal(sitzungen().length, 1)
|
||||
const [session] = sitzungen()
|
||||
assert.equal(res.body.url, session.url)
|
||||
|
||||
assert.equal(nachweise().length, 1, 'genau ein Nachweis')
|
||||
const [n] = nachweise()
|
||||
assert.equal(n.stripeSessionId, session.id)
|
||||
assert.deepEqual([n.customerId, n.projectId, n.featureKey], ['c1', 'p1', 'termine'])
|
||||
assert.deepEqual(JSON.parse(n.texte), await erwarteteTexte('termine'), 'Texte in Anzeigereihenfolge')
|
||||
assert.deepEqual(JSON.parse(n.fassungen), consents.zustimmungsfassungen(T))
|
||||
assert.equal(n.textHash, sha256(n.texte), 'SHA-256 ueber die gespeicherten Texte')
|
||||
assert.equal(n.textHash, consents.texteHash(JSON.parse(n.texte)))
|
||||
assert.ok(n.ip, 'ip')
|
||||
assert.ok(n.userAgent, 'userAgent')
|
||||
assert.equal(n.konto, 'Kunde Eins <k@example.invalid>')
|
||||
|
||||
// ein Zeitpunkt: Nachweis, Stripe-Metadaten und Dokument tragen dieselbe Zeit des Klicks (Eingang der Anfrage)
|
||||
const doc = termineDoc()
|
||||
assert.equal(doc.state, 'ordered')
|
||||
assert.equal(doc.stripeSessionId, session.id)
|
||||
assert.equal(n.acceptedAt, session.metadata.consentAt)
|
||||
assert.equal(n.acceptedAt, doc.consentAt)
|
||||
assert.ok(vor <= n.acceptedAt && n.acceptedAt <= nach, `${vor} <= ${n.acceptedAt} <= ${nach}`)
|
||||
assert.match(n.acceptedAt, /Z$/, 'UTC-Zeit mit Z')
|
||||
assert.equal(doc.consentText, hakenTexte(T).slice(0, 1000), 'consentText aus den Haken-Texten')
|
||||
assert.equal(doc.consentIp, n.ip)
|
||||
assert.equal(doc.consentUserAgent, n.userAgent)
|
||||
})
|
||||
|
||||
test('Belehrung im Nachweis: Eintrag belehrung (haken false) direkt vor dem Knopf, News hat keinen', async () => {
|
||||
bereit()
|
||||
assert.equal(reg.BELEHRUNG_PLATZHALTER, 'ENTWURF (Block F): Widerrufsbelehrung mit Muster-Widerrufsformular.')
|
||||
assert.equal(await reg.featureHaken('termine').belehrung(), reg.BELEHRUNG_PLATZHALTER)
|
||||
assert.equal(reg.featureHaken('news').belehrung, undefined)
|
||||
await checkout('termine', bodyT())
|
||||
const texte = JSON.parse(nachweise()[0].texte)
|
||||
assert.deepEqual(texte.slice(-2), [
|
||||
{ id: 'belehrung', text: reg.BELEHRUNG_PLATZHALTER, haken: false },
|
||||
{ id: 'knopf', text: T.consent.knopf, haken: false },
|
||||
])
|
||||
assert.deepEqual(texte.map((x) => x.id), [...T.consent.items.map((x) => x.id), 'belehrung', 'knopf'])
|
||||
|
||||
const news = await checkout('news', { projectId: 'p3', consent: true }, customerCookie('c3'))
|
||||
assert.equal(news.status, 200)
|
||||
const newsNachweis = nachweise().find((d) => d.featureKey === 'news')
|
||||
assert.deepEqual(JSON.parse(newsNachweis.texte).map((x) => x.id), [...config.features.news.consent.items.map((x) => x.id), 'knopf'])
|
||||
})
|
||||
|
||||
test('Belehrungs-Haken ueberschreibbar: der gelieferte Text steht im Nachweis, null laesst den Eintrag weg', async () => {
|
||||
bereit()
|
||||
T.belehrung = async () => 'Eigene Belehrung.'
|
||||
await checkout('termine', bodyT())
|
||||
assert.deepEqual(JSON.parse(nachweise()[0].texte).at(-2), { id: 'belehrung', text: 'Eigene Belehrung.', haken: false })
|
||||
await deleteDocument('featureConsents', nachweise()[0].$id)
|
||||
await deleteDocument('portalFeatures', 'f_p1_termine')
|
||||
st.checkoutSessions.clear()
|
||||
T.belehrung = async () => null
|
||||
try {
|
||||
assert.equal((await checkout('termine', bodyT())).status, 200)
|
||||
assert.equal(JSON.parse(nachweise()[0].texte).some((x) => x.id === 'belehrung'), false)
|
||||
} finally {
|
||||
delete T.belehrung
|
||||
}
|
||||
})
|
||||
|
||||
test('Client-Texte zaehlen nicht: gespeichert werden die Registertexte und -fassungen', async () => {
|
||||
bereit()
|
||||
const res = await checkout('termine', bodyT({ texte: ['manipuliert'], fassungen: { zustimmung: 'x' }, konto: 'Fremd <f@x.de>', acceptedAt: '2000-01-01T00:00:00Z' }))
|
||||
assert.equal(res.status, 200)
|
||||
const [n] = nachweise()
|
||||
assert.deepEqual(JSON.parse(n.texte), await erwarteteTexte('termine'))
|
||||
assert.deepEqual(JSON.parse(n.fassungen), consents.zustimmungsfassungen(T))
|
||||
assert.equal(n.konto, 'Kunde Eins <k@example.invalid>')
|
||||
assert.ok(n.acceptedAt > '2020')
|
||||
assert.doesNotMatch(JSON.stringify(nachweise()), /manipuliert/)
|
||||
})
|
||||
|
||||
test('Fassungswechsel: alter Body 409, neuer Body 200 mit neuer Fassung im Nachweis', async () => {
|
||||
bereit()
|
||||
const alt = T.consent.version
|
||||
try {
|
||||
T.consent.version = 'ENTWURF-2099-01-01'
|
||||
const veraltet = await checkout('termine', bodyT({ zustimmung: { version: alt, haken: pflichtHaken() } }))
|
||||
assert.equal(veraltet.status, 409)
|
||||
assert.equal(veraltet.body.code, 'fassung_veraltet')
|
||||
keineSpuren()
|
||||
const neu = await checkout('termine', bodyT({ zustimmung: { version: 'ENTWURF-2099-01-01', haken: pflichtHaken() } }))
|
||||
assert.equal(neu.status, 200)
|
||||
assert.equal(JSON.parse(nachweise()[0].fassungen).zustimmung, 'ENTWURF-2099-01-01')
|
||||
} finally {
|
||||
T.consent.version = alt
|
||||
}
|
||||
})
|
||||
|
||||
test('ohne Nachweis keine URL: scheitert das Speichern, gibt es kein ordered, keine Weiterleitung', async () => {
|
||||
bereit()
|
||||
const log = stumm()
|
||||
try {
|
||||
aw.failNext({ collection: 'featureConsents', method: 'POST', status: 500, message: 'GEHEIMER-ROHTEXT interner Fehler' })
|
||||
const res = await checkout('termine', bodyT())
|
||||
assert.equal(res.status, 500)
|
||||
assert.equal(res.body.url, undefined)
|
||||
assert.doesNotMatch(JSON.stringify(res.body), /GEHEIMER-ROHTEXT|https?:/)
|
||||
assert.match(res.body.error, /Bestellung konnte gerade nicht gestartet werden/)
|
||||
assert.equal(nachweise().length, 0)
|
||||
const doc = termineDoc()
|
||||
assert.ok(!doc || doc.state === 'off', 'nicht ordered')
|
||||
assert.ok(!doc?.orderedAt, 'orderedAt leer')
|
||||
// die Stripe-Sitzung ist angelegt, aber nie an den Browser gegangen; sie laeuft nach 24 h ab
|
||||
assert.equal(sitzungen().length, 1)
|
||||
// ein erneuter Klick geht normal durch: neue Sitzung, ein Nachweis zu ihr
|
||||
const noch = await checkout('termine', bodyT())
|
||||
assert.equal(noch.status, 200)
|
||||
assert.equal(sitzungen().length, 2)
|
||||
assert.equal(nachweise().length, 1)
|
||||
assert.equal(nachweise()[0].stripeSessionId, sitzungen()[1].id)
|
||||
} finally {
|
||||
log.mock.restore()
|
||||
}
|
||||
})
|
||||
|
||||
test('Nachweis nicht speicherbar (zu lange Texte): 500 mit allgemeinem Text, kein Rohtext, kein ordered', async () => {
|
||||
bereit()
|
||||
const alt = T.consent.items[0].text
|
||||
const log = stumm()
|
||||
try {
|
||||
T.consent.items[0].text = 'x'.repeat(30000)
|
||||
const res = await checkout('termine', bodyT())
|
||||
assert.equal(res.status, 500)
|
||||
assert.doesNotMatch(JSON.stringify(res.body), /Zustimmungsnachweis|zu lang/)
|
||||
assert.equal(res.body.url, undefined)
|
||||
assert.ok(!termineDoc() || termineDoc().state === 'off')
|
||||
} finally {
|
||||
T.consent.items[0].text = alt
|
||||
log.mock.restore()
|
||||
}
|
||||
})
|
||||
|
||||
test('Datenbankfehler beim markOrdered: 500 ohne Rohtext, der Nachweis steht schon, das Dokument nicht ordered', async () => {
|
||||
bereit()
|
||||
const log = stumm()
|
||||
try {
|
||||
aw.failNext({ collection: 'portalFeatures', method: 'PATCH', status: 400, message: 'Invalid document structure: Attribute "stripeSessionId" has invalid type.' })
|
||||
const res = await checkout('termine', bodyT())
|
||||
assert.equal(res.status, 500, JSON.stringify(res.body))
|
||||
assert.match(res.body.error, /Bestellung konnte gerade nicht gestartet werden/)
|
||||
assert.doesNotMatch(JSON.stringify(res.body), /Invalid document structure|stripeSessionId|https?:/)
|
||||
assert.equal(nachweise().length, 1, 'Nachweis kommt vor markOrdered')
|
||||
assert.equal(termineDoc().state, 'off')
|
||||
} finally {
|
||||
log.mock.restore()
|
||||
}
|
||||
})
|
||||
|
||||
test('Wiederverwendung: zweiter Aufruf bei offener Sitzung gibt dieselbe URL, ohne zweite Sitzung und zweiten Nachweis', async () => {
|
||||
bereit()
|
||||
const erste = await checkout('termine', bodyT())
|
||||
const zweite = await checkout('termine', bodyT())
|
||||
assert.equal(zweite.status, 200)
|
||||
assert.equal(zweite.body.url, erste.body.url)
|
||||
assert.equal(sitzungen().length, 1)
|
||||
assert.equal(nachweise().length, 1)
|
||||
// die Wiederverwendung fasst das Dokument nicht an (orderedAt bleibt der Zeitpunkt der Bestellung)
|
||||
aw.seed('portalFeatures', { ...termineDoc(), orderedAt: '2026-01-01T00:00:00.000Z' })
|
||||
await checkout('termine', bodyT())
|
||||
assert.equal(termineDoc().orderedAt, '2026-01-01T00:00:00.000Z')
|
||||
assert.equal(termineDoc().state, 'ordered')
|
||||
})
|
||||
|
||||
test('Wiederverwendung ohne Nachweis: der Nachweis wird nachgetragen, bevor die URL zurueckkommt', async () => {
|
||||
bereit()
|
||||
const erste = await checkout('termine', bodyT())
|
||||
await deleteDocument('featureConsents', nachweise()[0].$id)
|
||||
assert.equal(nachweise().length, 0)
|
||||
const vor = new Date().toISOString()
|
||||
const zweite = await checkout('termine', bodyT())
|
||||
assert.equal(zweite.status, 200)
|
||||
assert.equal(zweite.body.url, erste.body.url)
|
||||
assert.equal(sitzungen().length, 1, 'keine zweite Stripe-Sitzung')
|
||||
assert.equal(nachweise().length, 1)
|
||||
assert.equal(nachweise()[0].stripeSessionId, sitzungen()[0].id)
|
||||
assert.ok(nachweise()[0].acceptedAt >= vor, 'Zeit des neuen Klicks')
|
||||
assert.deepEqual(JSON.parse(nachweise()[0].texte), await erwarteteTexte('termine'))
|
||||
|
||||
// scheitert das Nachtragen, kommt keine URL zurueck
|
||||
await deleteDocument('featureConsents', nachweise()[0].$id)
|
||||
const log = stumm()
|
||||
try {
|
||||
aw.failNext({ collection: 'featureConsents', method: 'POST', status: 500 })
|
||||
const kaputt = await checkout('termine', bodyT())
|
||||
assert.equal(kaputt.status, 500)
|
||||
assert.equal(kaputt.body.url, undefined)
|
||||
assert.equal(nachweise().length, 0)
|
||||
} finally {
|
||||
log.mock.restore()
|
||||
}
|
||||
})
|
||||
|
||||
test('abgelaufene Sitzung: neue Sitzung und neuer Nachweis (zwei Dokumente, zwei Sitzungs-IDs)', async () => {
|
||||
bereit()
|
||||
const erste = await checkout('termine', bodyT())
|
||||
sitzungen()[0].status = 'expired'
|
||||
const zweite = await checkout('termine', bodyT())
|
||||
assert.equal(zweite.status, 200)
|
||||
assert.notEqual(zweite.body.url, erste.body.url)
|
||||
assert.equal(sitzungen().length, 2)
|
||||
assert.equal(nachweise().length, 2)
|
||||
assert.equal(new Set(nachweise().map((n) => n.stripeSessionId)).size, 2)
|
||||
assert.deepEqual(nachweise().map((n) => n.stripeSessionId).sort(), sitzungen().map((s) => s.id).sort())
|
||||
assert.equal(termineDoc().stripeSessionId, sitzungen()[1].id)
|
||||
assert.equal(termineDoc().consentAt, sitzungen()[1].metadata.consentAt)
|
||||
})
|
||||
|
||||
test('parallele Klicks: eine Sitzung, ein Nachweis, beide 200 mit gleicher URL', async () => {
|
||||
bereit()
|
||||
const [a, b] = await Promise.all([checkout('termine', bodyT()), checkout('termine', bodyT())])
|
||||
assert.deepEqual([a.status, b.status], [200, 200])
|
||||
assert.equal(a.body.url, b.body.url)
|
||||
assert.equal(sitzungen().length, 1)
|
||||
assert.equal(nachweise().length, 1)
|
||||
})
|
||||
|
||||
// ------------------------------------------------------------------------------------------ Zustaende ---
|
||||
test('Zustaende: setup und active sind 409 orderedConflictMessage, disabled 409 disabledMessage, nirgends eine neue Sitzung', async () => {
|
||||
bereit()
|
||||
for (const [state, text] of [
|
||||
['setup', T.orderedConflictMessage],
|
||||
['active', T.orderedConflictMessage],
|
||||
['disabled', T.disabledMessage],
|
||||
]) {
|
||||
featureDoc(state)
|
||||
const vorher = spuren()
|
||||
const res = await checkout('termine', bodyT())
|
||||
assert.equal(res.status, 409, state)
|
||||
assert.equal(res.body.error, text, state)
|
||||
assert.equal(res.body.url, undefined, state)
|
||||
assert.deepEqual(spuren(), vorher, `${state}: nichts angelegt`)
|
||||
assert.equal(termineDoc().state, state, `${state}: unveraendert`)
|
||||
}
|
||||
// News: Meldungen aus dem Register der News
|
||||
const news = config.features.news
|
||||
aw.seed('portalFeatures', { $id: 'f_p3_news', customerId: 'c3', projectId: 'p3', featureKey: 'news', state: 'active', enabled: true, siteKey: 'abcdefghijklmnop' })
|
||||
const nCookie = customerCookie('c3')
|
||||
assert.equal((await checkout('news', { projectId: 'p3', consent: true }, nCookie)).body.error, news.orderedConflictMessage)
|
||||
aw.seed('portalFeatures', { $id: 'f_p3_news', customerId: 'c3', projectId: 'p3', featureKey: 'news', state: 'disabled', enabled: false, siteKey: 'abcdefghijklmnop' })
|
||||
assert.equal((await checkout('news', { projectId: 'p3', consent: true }, nCookie)).body.error, news.disabledMessage)
|
||||
assert.equal(sitzungen().length, 0)
|
||||
})
|
||||
|
||||
test('Zustand complete: 409, keine neue Sitzung, kein neuer Nachweis', async () => {
|
||||
bereit()
|
||||
await checkout('termine', bodyT())
|
||||
sitzungen()[0].status = 'complete'
|
||||
const res = await checkout('termine', bodyT())
|
||||
assert.equal(res.status, 409)
|
||||
assert.equal(res.body.error, T.orderedConflictMessage)
|
||||
assert.equal(sitzungen().length, 1)
|
||||
assert.equal(nachweise().length, 1)
|
||||
// auch "Zahlung fortsetzen" bekommt keine URL
|
||||
const fort = await checkout('termine', { projectId: 'p1', fortsetzen: true })
|
||||
assert.equal(fort.status, 409)
|
||||
assert.equal(fort.body.url, undefined)
|
||||
})
|
||||
|
||||
test('Stripe nicht erreichbar: frische Bestellung (< 24 h) ist 409, aeltere bekommt eine neue Sitzung samt Nachweis', async () => {
|
||||
bereit()
|
||||
const log = mock.method(console, 'warn', () => {})
|
||||
try {
|
||||
featureDoc('ordered', { stripeSessionId: 'cs_weg', orderedAt: new Date().toISOString() })
|
||||
const frisch = await checkout('termine', bodyT())
|
||||
assert.equal(frisch.status, 409)
|
||||
assert.equal(frisch.body.error, T.orderedConflictMessage)
|
||||
assert.deepEqual(spuren(), { sessions: 0, consents: 0, features: 1 }, 'nichts angelegt')
|
||||
featureDoc('ordered', { stripeSessionId: 'cs_weg', orderedAt: new Date(Date.now() - 2 * DAY_MS).toISOString() })
|
||||
const alt = await checkout('termine', bodyT())
|
||||
assert.equal(alt.status, 200)
|
||||
assert.equal(sitzungen().length, 1)
|
||||
assert.equal(nachweise().length, 1)
|
||||
assert.equal(termineDoc().stripeSessionId, sitzungen()[0].id)
|
||||
// ordered ohne gespeicherte Sitzungs-ID: ebenfalls eine neue Sitzung
|
||||
featureDoc('ordered', { stripeSessionId: '' })
|
||||
assert.equal((await checkout('termine', bodyT())).status, 200)
|
||||
assert.equal(sitzungen().length, 2)
|
||||
assert.equal(nachweise().length, 2)
|
||||
} finally {
|
||||
log.mock.restore()
|
||||
}
|
||||
})
|
||||
|
||||
// ------------------------------------------------------------------------------------------ Zahlung fortsetzen ---
|
||||
test('Zahlung fortsetzen: Dokument ordered mit Nachweis gibt dieselbe URL, ohne zweite Sitzung und zweiten Nachweis', async () => {
|
||||
bereit()
|
||||
const erste = await checkout('termine', bodyT())
|
||||
const fort = await checkout('termine', { projectId: 'p1', fortsetzen: true })
|
||||
assert.equal(fort.status, 200, JSON.stringify(fort.body))
|
||||
assert.equal(fort.body.url, erste.body.url)
|
||||
assert.equal(sitzungen().length, 1)
|
||||
assert.equal(nachweise().length, 1)
|
||||
})
|
||||
|
||||
test('Zahlung fortsetzen ohne Nachweis: 409 zustimmung_fehlt ohne URL, danach mit zustimmung 200 und der Nachweis ist wieder da', async () => {
|
||||
bereit()
|
||||
const erste = await checkout('termine', bodyT())
|
||||
await deleteDocument('featureConsents', nachweise()[0].$id)
|
||||
const fort = await checkout('termine', { projectId: 'p1', fortsetzen: true })
|
||||
assert.equal(fort.status, 409)
|
||||
assert.equal(fort.body.code, 'zustimmung_fehlt')
|
||||
assert.equal(fort.body.url, undefined)
|
||||
assert.doesNotMatch(JSON.stringify(fort.body), /https?:/)
|
||||
assert.equal(nachweise().length, 0, 'ohne Zustimmung entsteht kein Nachweis')
|
||||
assert.equal(sitzungen().length, 1)
|
||||
// der Kunde stimmt im Formular erneut zu
|
||||
const neu = await checkout('termine', bodyT())
|
||||
assert.equal(neu.status, 200)
|
||||
assert.equal(neu.body.url, erste.body.url)
|
||||
assert.equal(nachweise().length, 1)
|
||||
assert.equal(nachweise()[0].stripeSessionId, sitzungen()[0].id)
|
||||
assert.equal(sitzungen().length, 1)
|
||||
})
|
||||
|
||||
test('Zahlung fortsetzen bei abgelaufener Sitzung: 409 zustimmung_fehlt, mit zustimmung eine neue Sitzung', async () => {
|
||||
bereit()
|
||||
await checkout('termine', bodyT())
|
||||
sitzungen()[0].status = 'expired'
|
||||
const fort = await checkout('termine', { projectId: 'p1', fortsetzen: true })
|
||||
assert.equal(fort.status, 409)
|
||||
assert.equal(fort.body.code, 'zustimmung_fehlt')
|
||||
assert.equal(fort.body.url, undefined)
|
||||
assert.equal(sitzungen().length, 1, 'fortsetzen legt nie eine Sitzung an')
|
||||
assert.equal(nachweise().length, 1)
|
||||
const neu = await checkout('termine', bodyT())
|
||||
assert.equal(neu.status, 200)
|
||||
assert.equal(sitzungen().length, 2)
|
||||
assert.equal(nachweise().length, 2)
|
||||
})
|
||||
|
||||
test('Zahlung fortsetzen: ein Lesefehler beim Nachweis ist 500 ohne URL (im Zweifel keine Weiterleitung)', async () => {
|
||||
bereit()
|
||||
await checkout('termine', bodyT())
|
||||
const log = stumm()
|
||||
try {
|
||||
aw.failNext({ collection: 'featureConsents', method: 'GET', status: 500, message: 'GEHEIMER-ROHTEXT' })
|
||||
const fort = await checkout('termine', { projectId: 'p1', fortsetzen: true })
|
||||
assert.equal(fort.status, 500)
|
||||
assert.equal(fort.body.url, undefined)
|
||||
assert.doesNotMatch(JSON.stringify(fort.body), /GEHEIMER-ROHTEXT/)
|
||||
} finally {
|
||||
log.mock.restore()
|
||||
}
|
||||
})
|
||||
|
||||
test('Zahlung fortsetzen bei News: gleiche Regeln (Nachweis da, dieselbe URL)', async () => {
|
||||
const erste = await checkout('news', { projectId: 'p3', consent: true }, customerCookie('c3'))
|
||||
const fort = await checkout('news', { projectId: 'p3', fortsetzen: true }, customerCookie('c3'))
|
||||
assert.equal(fort.status, 200)
|
||||
assert.equal(fort.body.url, erste.body.url)
|
||||
assert.equal(nachweise().length, 1)
|
||||
assert.equal(sitzungen().length, 1)
|
||||
})
|
||||
|
||||
// ------------------------------------------------------------------------------------------ Meldungen aus dem Register ---
|
||||
test('markOrdered: Meldungen kommen aus dem Register der Funktion', async () => {
|
||||
for (const [key, feature] of [['news', config.features.news], ['termine', T]]) {
|
||||
for (const [state, text] of [['setup', feature.orderedConflictMessage], ['active', feature.orderedConflictMessage], ['disabled', feature.disabledMessage]]) {
|
||||
aw.seed('portalFeatures', { $id: `f_p1_${key}`, customerId: 'c1', projectId: 'p1', featureKey: key, state, enabled: state === 'active', siteKey: 'abcdefghijklmnop' })
|
||||
await assert.rejects(
|
||||
() => sf.markOrdered({ customerId: 'c1', projectId: 'p1', key, sessionId: 'cs_x', consent: {} }),
|
||||
(err) => err.status === 409 && err.message === text,
|
||||
`${key} ${state}`
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// ------------------------------------------------------------------------------------------ fortsetzen ---
|
||||
const featureDoc = (state, extra = {}) =>
|
||||
aw.seed('portalFeatures', { $id: 'f_p1_termine', customerId: 'c1', projectId: 'p1', featureKey: 'termine', state, enabled: state === 'active', siteKey: 'abcdefghijklmnop', ...extra })
|
||||
|
||||
test('fortsetzen: ohne Dokument oder ausser ordered ist 400 zustimmung_fehlt, es entsteht nichts', async () => {
|
||||
const fall = { projectId: 'p1', fortsetzen: true }
|
||||
|
||||
Reference in new issue
Block a user