TP2 Abschlussprüfung: Zahlung/Team (Wiederzustellung, K16-Warnung, Sperrschlüssel)

- adoptPaidSession übernimmt ein erneut zugestelltes checkout.session.completed nicht mehr, wenn das Dokument nach 30 Tagen zurückgesetzt wurde
  (off, purchaseStatus zurueckgesetzt, gleiche Sitzung): kein Setup, kein Ticket, keine K15/T16
- meldeFreigeschaltet liefert true/false; die Staff-Aktivierung in Textform vermerkt eine nicht gesendete K16 mit Unterlagen in adminNote und
  antwortet mit warnung: true
- disable() nutzt die Sperre feature-activate:<id> statt feature-disable:<id> (Freischaltung und Abschalten überholen sich nicht)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-05 06:18:34 +00:00
1 parent 4dc447ed33
commit b974b3a3ec
7 files changed
+153 -6

No files matched your search

+13
View File
@@ -370,6 +370,19 @@ test('Der Haken bekommt das unter der Sperre frisch gelesene Dokument; ein zwisc
assert.deepEqual(gesehen, [{ state: 'setup', marker: 'frisch gelesen' }], 'der Haken sieht nur das frische, noch offene Dokument')
})
test('A3: disable() teilt die Sperre feature-activate:<id> mit der Freischaltung - wer die Freischaltung haelt, laesst das Abschalten warten', async () => {
const id = einrichtung('termine', { einstellung: true })
let abschalten
await withLocks([`feature-activate:${id}`], async () => {
abschalten = sf.disable(id, { reason: 'team' })
await new Promise((r) => setTimeout(r, 80))
assert.equal(zustand(id), 'setup', 'solange die Freischaltung die Sperre haelt, schaltet disable() nicht ab')
})
const doc = await abschalten
assert.equal(doc.state, 'disabled')
assert.equal(zustand(id), 'disabled')
})
// ------------------------------------------------------------------------------- Tickets, Kunden ---
test('Ticket Cancelled (oder Open) => bleibt setup, der Haken wird gar nicht gefragt', async () => {
let gefragt = 0
+31
View File
@@ -372,6 +372,37 @@ test('(7) Stripe-Sitzung laeuft ab, die Zahlung kommt doch: Uebernahme, setup, e
assert.equal(mails.sent.length, 2)
})
test('(7) A1: erneut zugestelltes checkout.session.completed nach der 30-Tage-Loeschung (off, zurueckgesetzt, gleiche Sitzung) wird nicht uebernommen', async (t) => {
const sessionId = await bestelle()
assert.equal((await webhook('checkout.session.completed', bezahlt(sessionId))).status, 200)
// der 30-Tage-Lauf hat das Dokument zurueckgesetzt (siehe setzeFunktionZurueck); die Sitzungs-ID bleibt als Marke stehen
const d0 = await doc()
aw.seed('portalFeatures', {
...d0,
state: 'off',
enabled: false,
unlockedByPurchase: false,
purchaseStatus: 'zurueckgesetzt',
zahlungMeldung: '',
ticketId: '',
adminNote: 'Daten gelöscht (Löschfrist abgelaufen)',
})
const tickets0 = tickets().length
const mails0 = mails.sent.length
const stumm = STUMM(t)
const warn = t.mock.method(console, 'warn', () => {})
// "Resend event" im Stripe-Dashboard: dasselbe Ereignis noch einmal
assert.equal((await webhook('checkout.session.completed', bezahlt(sessionId))).status, 200)
const d = await doc()
assert.deepEqual([d.state, d.purchaseStatus, d.stripeSessionId, d.ticketId], ['off', 'zurueckgesetzt', sessionId, ''], 'nicht uebernommen')
assert.equal(tickets().length, tickets0, 'kein neues Ticket')
assert.equal(mails.sent.length, mails0, 'weder K15 noch T16')
const log = geloggt(stumm) + warn.mock.calls.map((c) => c.arguments.join(' ')).join('\n')
assert.match(log, new RegExp(`Sitzung ${sessionId} .*zurueckgesetzt`))
assert.doesNotMatch(log, /k@example\.invalid|Salon/, 'keine personenbezogenen Daten im Log')
})
// ------------------------------------------------------------------------------------ (8) Neubestellung nach Ablauf ---
test('(8) Neubestellung nach Ablauf: die alte Sitzung zahlt (Uebernahme, ein Ticket), die neue danach ungeklaert, kein zweites Ticket', async (t) => {
const alt = await bestelle()
+38
View File
@@ -1444,6 +1444,44 @@ test('textform_pflicht_modus_on: ohne Angaben 400, mit gültigen Angaben 200, Na
assert.equal(mails.sent.length, mailsVorher + 2, 'je eine K16 für den Kunden und den Chat-Kunden, sonst nichts')
})
const K16_NICHT_GESENDET = 'K16 mit Unterlagen NICHT gesendet – von Hand nachsenden'
test('textform A2: scheitert der Versand von K16 mit den Unterlagen, bleibt die Funktion freigeschaltet, adminNote und warnung:true sagen es; Erfolg und Kunde ohne Adresse', async (t) => {
modusMerken(t)
termineMit('on', [])
t.mock.method(console, 'error', () => {})
// Erfolg: keine Warnung, keine Notiz
const gut = legeKundeF13()
const ok = await aktivierenF13(gut, feldF13(gut))
assert.equal(ok.status, 200, JSON.stringify(ok.body))
assert.equal(ok.body.warnung, undefined, 'ohne Fehler kein Feld warnung')
assert.doesNotMatch((await sf.getFeatureDoc(gut.customerId, gut.projectId, 'termine')).adminNote, /NICHT gesendet/)
// Versandfehler: Freischaltung und Nachweis stehen, die Warnung kommt zurueck
const k = legeKundeF13()
setMailTransportForTests({ sendMail: async () => { throw new Error('SMTP weg') } })
t.after(() => setMailTransportForTests(mails))
const res = await aktivierenF13(k, feldF13(k))
assert.equal(res.status, 200, JSON.stringify(res.body))
assert.equal(res.body.warnung, true)
const doc = await sf.getFeatureDoc(k.customerId, k.projectId, 'termine')
assert.equal(doc.state, 'active', 'die Freischaltung bleibt')
assert.equal(consentsDesF13(k.projectId).length, 1, 'der Nachweis bleibt')
assert.ok(doc.adminNote.includes(K16_NICHT_GESENDET), doc.adminNote)
assert.match(doc.adminNote, /^\d{4}-\d\d-\d\dT[\d:.]+Z System: K16 mit Unterlagen NICHT gesendet – von Hand nachsenden \| .*Andrej: activate/)
assert.equal(featuresOf(res, k.projectId).termine.adminNote, doc.adminNote, 'das Ticketsystem liest die Notiz mit der Antwort')
// Kunde ohne E-Mail-Adresse: K16 kann nicht raus, auch das ist eine Warnung
setMailTransportForTests(mails)
const leer = legeKundeF13({ email: '' })
aw.docs('customers').get(leer.customerId).email = ''
const ohneAdresse = await aktivierenF13(leer, feldF13(leer))
assert.equal(ohneAdresse.status, 200, JSON.stringify(ohneAdresse.body))
assert.equal(ohneAdresse.body.warnung, true)
assert.ok((await sf.getFeatureDoc(leer.customerId, leer.projectId, 'termine')).adminNote.includes(K16_NICHT_GESENDET))
})
test('textform_entfaellt: Modus off und pilot, Modus on mit Pilotadresse, News und Abschalten brauchen keine Angaben und schreiben keinen Nachweis', async (t) => {
modusMerken(t)
const mailsVorher = mails.sent.length
+25
View File
@@ -315,6 +315,31 @@ test('meldeFreigeschaltet: Kunde nicht lesbar oder Funktion nicht im Register =>
assert.equal(mails.sent.length, 0)
})
test('meldeFreigeschaltet liefert true nur, wenn K16 rausging: Erfolg true; Versandfehler, nicht lesbarer Kunde, unbekannte Funktion, Kunde ohne oder unbekannt false (A2)', async (t) => {
STUMM(t)
const doc = einrichtung('termine', { einstellung: true })
assert.equal(await meldeFreigeschaltet(doc), true)
assert.equal(mails.sent.length, 1)
setMailTransportForTests({
sendMail: async () => {
throw new Error('SMTP: Postfach voll')
},
})
try {
assert.equal(await meldeFreigeschaltet(doc), false, 'Versandfehler')
} finally {
setMailTransportForTests(mails)
}
aw.failNext({ collection: 'customers', method: 'GET', status: 500, message: 'Nachbau: Kunden kaputt' })
assert.equal(await meldeFreigeschaltet(doc), false, 'Kunde nicht lesbar')
assert.equal(await meldeFreigeschaltet(einrichtung('gibtsnicht')), false, 'Funktion nicht im Register')
aw.seed('customers', { $id: 'c_leer2', appwriteUserId: 'u_leer2', email: '', name: 'Ohne Adresse', status: 'active' })
assert.equal(await meldeFreigeschaltet(einrichtung('termine', { customerId: 'c_leer2' })), false, 'Kunde ohne Adresse')
assert.equal(await meldeFreigeschaltet(einrichtung('termine', { customerId: 'c_gibtsnicht' })), false, 'Kunde unbekannt')
assert.equal(mails.sent.length, 1, 'nur die erste Mail ging raus')
})
// -------------------------------------------------------------------------------------------- runActivation ---
test('runActivation sendet fuer ein aktiviertes Termine-Dokument genau ein K16 (ueber meldeFreigeschaltet), ein zweiter Lauf keins', async () => {
const doc = einrichtung('termine', { einstellung: true })