Funktionen/News: Schlussdurchgang (Webhook-Retry, Checkout-Sitzung, Rechnungslink, Widerruf Funktion, UI- und Rechtstext-Korrekturen)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0dc57fe5e8
commit
4b7f73abba
22 files changed
+400
-57
No files matched your search
@@ -1,3 +1,4 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { test, after } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { startAppwriteMock } from './support/appwriteMock.js'
|
||||
@@ -98,16 +99,26 @@ test('checkout_legt_session_an', async () => {
|
||||
assert.equal(doc.consentText, config.funktionen.consentText)
|
||||
assert.ok(doc.consentAt)
|
||||
assert.ok(doc.consentUserAgent)
|
||||
// zweite Bestellung waehrend die erste offen ist: 409, keine neue Sitzung
|
||||
assert.equal((await checkout('p1')).status, 409)
|
||||
// zweite Bestellung waehrend die erste offen ist: dieselbe Zahlungsseite, keine neue Sitzung
|
||||
const again = await checkout('p1')
|
||||
assert.equal(again.status, 200)
|
||||
assert.equal(again.body.url, res.body.url)
|
||||
assert.equal(sessions().length, 1)
|
||||
})
|
||||
|
||||
test('abgelaufene_bestellung_darf_neu_gestartet_werden', async () => {
|
||||
const doc = await sf.getFeatureDoc('c1', 'p1', 'news')
|
||||
aw.seed('portalFeatures', { ...aw.docs('portalFeatures').get(doc.$id), orderedAt: new Date(Date.now() - 25 * 3600 * 1000).toISOString() })
|
||||
assert.equal((await checkout('p1')).status, 200)
|
||||
test('abgelaufene_sitzung_darf_neu_gestartet_werden', async () => {
|
||||
sessions().at(-1).status = 'expired'
|
||||
const res = await checkout('p1')
|
||||
assert.equal(res.status, 200)
|
||||
assert.equal(sessions().length, 2)
|
||||
assert.equal((await sf.getFeatureDoc('c1', 'p1', 'news')).stripeSessionId, sessions().at(-1).id)
|
||||
})
|
||||
|
||||
test('abgeschlossene_sitzung_409', async () => {
|
||||
sessions().at(-1).status = 'complete'
|
||||
const before = sessions().length
|
||||
assert.equal((await checkout('p1')).status, 409)
|
||||
assert.equal(sessions().length, before)
|
||||
})
|
||||
|
||||
test('checkout_bei_setup_409', async () => {
|
||||
@@ -192,7 +203,8 @@ test('parallele_checkouts_eine_sitzung', async () => {
|
||||
aw.seed('websiteProjects', { $id: 'p8', customerId: 'c1', projectName: 'Gärtnerei' })
|
||||
const before = sessions().length
|
||||
const results = await Promise.all([checkout('p8'), checkout('p8')])
|
||||
assert.deepEqual(results.map((r) => r.status).sort(), [200, 409])
|
||||
assert.deepEqual(results.map((r) => r.status), [200, 200])
|
||||
assert.equal(results[0].body.url, results[1].body.url)
|
||||
assert.equal(sessions().length, before + 1)
|
||||
})
|
||||
|
||||
@@ -300,3 +312,86 @@ test('ticket_fehlt_markiert', async () => {
|
||||
assert.equal(missing.ticketMissing, true)
|
||||
assert.equal(missing.ticketWoid, null)
|
||||
})
|
||||
|
||||
// ------------------------------------------------ Schlussdurchgang ---
|
||||
test('webhook_retry_setzt_ticketlose_einrichtung_fort', async () => {
|
||||
aw.seed('websiteProjects', { $id: 'pr1', customerId: 'c1', projectName: 'Retry' })
|
||||
await checkout('pr1')
|
||||
const id = sessions().at(-1).id
|
||||
const wosBefore = workorders().length
|
||||
const mailsBefore = mails.sent.length
|
||||
aw.failNext({ collection: 'workorders', method: 'POST', status: 400 })
|
||||
const first = await postWebhook(event('checkout.session.completed', paidSession(id, 'pr1')))
|
||||
assert.equal(first.status, 500)
|
||||
const stuck = await sf.getFeatureDoc('c1', 'pr1', 'news')
|
||||
assert.equal(stuck.state, 'setup')
|
||||
assert.ok(!stuck.ticketId)
|
||||
assert.equal(mails.sent.length, mailsBefore)
|
||||
assert.equal((await postWebhook(event('checkout.session.completed', paidSession(id, 'pr1')))).status, 200)
|
||||
const fixed = await sf.getFeatureDoc('c1', 'pr1', 'news')
|
||||
assert.ok(fixed.ticketId)
|
||||
assert.equal(workorders().length, wosBefore + 1)
|
||||
assert.deepEqual(subjects().slice(mailsBefore).sort(), ['Deine Bestellung: News-Pinnwand', 'Neue Funktion bestellt: News-Pinnwand'])
|
||||
// weiterer Retry: nichts mehr
|
||||
await postWebhook(event('checkout.session.completed', paidSession(id, 'pr1')))
|
||||
assert.equal(workorders().length, wosBefore + 1)
|
||||
assert.equal(mails.sent.length - mailsBefore, 2)
|
||||
})
|
||||
|
||||
test('rechnungslink_wird_nachgetragen', async () => {
|
||||
aw.seed('websiteProjects', { $id: 'pi1', customerId: 'c1', projectName: 'Rechnung' })
|
||||
await checkout('pi1')
|
||||
const session = sessions().at(-1)
|
||||
await postWebhook(event('checkout.session.completed', paidSession(session.id, 'pi1')))
|
||||
const listed = async () => (await get('/api/funktionen', { cookie })).projects.find((p) => p.projectId === 'pi1').features.news
|
||||
assert.equal((await listed()).invoiceUrl, null)
|
||||
st.invoices.set('in_late', { id: 'in_late', object: 'invoice', hosted_invoice_url: 'https://invoice.stripe.test/late' })
|
||||
session.invoice = 'in_late'
|
||||
assert.equal((await listed()).invoiceUrl, 'https://invoice.stripe.test/late')
|
||||
const doc = await sf.getFeatureDoc('c1', 'pi1', 'news')
|
||||
assert.equal(doc.stripeInvoiceId, 'in_late')
|
||||
// Stripe-Fehler lassen die Liste nicht scheitern
|
||||
aw.seed('portalFeatures', { ...aw.docs('portalFeatures').get(doc.$id), invoiceUrl: '' })
|
||||
st.failNext({ method: 'GET', path: /checkout\/sessions/, status: 400 })
|
||||
assert.equal((await get('/api/funktionen', { cookie })).projects.length > 0, true)
|
||||
})
|
||||
|
||||
test('bezahlt_ohne_passende_bestellung_meldet_dem_team', async () => {
|
||||
aw.seed('websiteProjects', { $id: 'pu1', customerId: 'c1', projectName: 'Unklar' })
|
||||
const mailsBefore = mails.sent.length
|
||||
const res = await postWebhook(event('checkout.session.completed', paidSession('cs_verwaist', 'pu1')))
|
||||
assert.equal(res.status, 200)
|
||||
const notice = mails.sent.slice(mailsBefore)
|
||||
assert.equal(notice.length, 1)
|
||||
assert.equal(notice[0].subject, 'Neue Funktion bestellt: News-Pinnwand')
|
||||
assert.match(notice[0].text, /cs_verwaist/)
|
||||
assert.match(notice[0].text, /nicht zuzuordnen/)
|
||||
assert.doesNotMatch(notice[0].text, /Zahlung fehlgeschlagen/)
|
||||
assert.equal(workorders().filter((t) => t.topic.includes('Unklar')).length, 0)
|
||||
})
|
||||
|
||||
test('abgeschaltete_funktion_nicht_buchbar', async () => {
|
||||
aw.seed('websiteProjects', { $id: 'pd1', customerId: 'c1', projectName: 'Abgeschaltet' })
|
||||
const doc = await sf.adminActivate('c1', 'pd1', 'news', { by: 'test' })
|
||||
await sf.disable(doc.$id, { by: 'test' })
|
||||
const before = sessions().length
|
||||
const res = await checkout('pd1')
|
||||
assert.equal(res.status, 409)
|
||||
assert.equal(res.body.error, 'Die News-Pinnwand wurde für diese Website abgeschaltet. Schreib uns im Chat, wenn du sie wieder möchtest.')
|
||||
assert.equal(sessions().length, before)
|
||||
})
|
||||
|
||||
test('statische_quellen: news_label, minmax_grid, ticket_anleitung', () => {
|
||||
const read = (p) => readFileSync(new URL(`../${p}`, import.meta.url), 'utf8')
|
||||
assert.match(read('public/app.js'), /FEATURE_LABELS = \{[^}]*news: 'News-Pinnwand'/)
|
||||
assert.doesNotMatch(read('public/app.js'), /FEATURE_LABELS\[f\.featureKey\] \|\| f\.featureKey/)
|
||||
const embed = read('public/embed/news.js')
|
||||
assert.ok(embed.includes('grid-template-columns:minmax(0,1.25fr) minmax(0,1fr)'))
|
||||
assert.ok(embed.includes('.wkn-combo-in{grid-template-columns:minmax(0,1fr)}'))
|
||||
assert.ok(!read('public/dashboard.html').includes('id="view-news" aria-live'))
|
||||
})
|
||||
|
||||
test('ticket_details_nennen_die_anleitung', () => {
|
||||
const doc = [...aw.docs('workorders').values()].find((t) => String(t.topic).startsWith('News-Pinnwand einbauen'))
|
||||
assert.ok(doc.details.includes('Anleitung: docs/funktionen/news-einbau.md (Repo Webklar-Kundenbereich)'))
|
||||
})
|
||||
Reference in new issue
Block a user