import { test, after } from 'node:test' import assert from 'node:assert/strict' import { startAppwriteMock } from './support/appwriteMock.js' import { startFakeStripe } from './support/fakeStripe.js' import { useTestEnv } from './support/testEnv.js' import { schemaKeys } from './support/schema.js' const aw = await startAppwriteMock({ schema: { contractEvents: schemaKeys('contractEvents'), subscriptions: schemaKeys('subscriptions') }, }) const st = await startFakeStripe() after(() => Promise.all([aw.close(), st.close()])) useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url }) const { recordContractEvent, listContractEvents, openWithdrawals, listOpenStorageTasks, completeContractEvent, updateContractEvent, } = await import('../server/services/contractEvents.js') test('Ereignis speichern und lesen (details als JSON)', async () => { await recordContractEvent({ customerId: 'c1', subscriptionId: 'sub_1', type: 'speicher', productKey: 'hosting', details: { fromUnits: 0, toUnits: 2 }, consent: { termsVersion: '2026-10-05-v4', consentAt: '2026-10-05T10:00:00.000Z', consentIp: '1.2.3.4', consentUserAgent: 'UA', }, status: 'offen', dueAt: '2026-10-05T10:00:00.000Z', }) const [e] = await listContractEvents('c1') assert.deepEqual(e.details, { fromUnits: 0, toUnits: 2 }) assert.equal((await listOpenStorageTasks()).length, 1) }) test('Widerrufsfristen je Bestellung', () => { const ev = (type, consentAt, details = {}, status = 'erledigt') => ({ $id: type, type, consentAt, details, status }) const now = new Date('2026-10-10T12:00:00Z') const open = openWithdrawals( [ ev('abschluss', '2026-09-01T10:00:00Z'), ev('wechsel', '2026-10-05T10:00:00Z'), ev('speicher', '2026-10-06T10:00:00Z', { fromUnits: 2, toUnits: 1 }), ev('speicher', '2026-10-07T10:00:00Z', { fromUnits: 0, toUnits: 1 }), ], now ) assert.deepEqual(open.map((o) => o.type), ['wechsel', 'speicher']) assert.equal(openWithdrawals([ev('wechsel', '2026-10-05T10:00:00Z', {}, 'widerrufen')], now).length, 0) // F02: ein Wechsel ohne Zahlung (gescheitert) hat wie ein widerrufener kein Widerrufsrecht mehr assert.equal(openWithdrawals([ev('wechsel', '2026-10-05T10:00:00Z', {}, 'gescheitert')], now).length, 0) }) // F04: die Frist zaehlt ab dem Vertragsschluss bei Stripe (details.contractAt), nicht ab dem fruehereren // Portal-Klick (consentAt). Waere hier consentAt massgeblich, waere die Frist laengst abgelaufen. test('Widerrufsfrist zaehlt ab dem Vertragsschluss (details.contractAt), nicht ab dem Portal-Klick', () => { const now = new Date('2026-10-10T12:00:00Z') const ev = { $id: 'e1', type: 'abschluss', status: 'erledigt', consentAt: '2026-08-01T10:00:00Z', details: { contractAt: '2026-10-08T10:00:00Z' }, } assert.deepEqual(openWithdrawals([ev], now).map((o) => o.eventId), ['e1']) }) test('Aufgabe erledigen', async () => { const [t] = await listOpenStorageTasks() const done = await completeContractEvent(t.$id, 'kenso@webklar.com') assert.equal(done.status, 'erledigt') assert.equal(done.doneBy, 'kenso@webklar.com') }) // F01: Status setzen und details zusammenfuehren (nicht ersetzen) in einem Aufruf test('Ereignis aktualisieren: Status setzen, details zusammenfuehren', async () => { const created = await recordContractEvent({ customerId: 'c2', subscriptionId: 'sub_2', type: 'wechsel', productKey: 'hosting', details: { von: 'wartung', zu: 'hosting' }, status: 'offen', }) const updated = await updateContractEvent(created.$id, { status: 'gescheitert', details: { grund: 'Zahlung fehlgeschlagen' }, }) assert.equal(updated.status, 'gescheitert') assert.deepEqual(JSON.parse(updated.details), { von: 'wartung', zu: 'hosting', grund: 'Zahlung fehlgeschlagen' }) }) // F05: dieselbe id zweimal speichern (z. B. Webhook-Wiederholung) ist kein Fehler, sondern liefert // das schon vorhandene Dokument zurueck, ohne ein zweites anzulegen test('Ereignis mit vorhandener id (409): liefert das schon gespeicherte Dokument', async () => { const first = await recordContractEvent({ customerId: 'c3', subscriptionId: 'sub_3', type: 'abschluss', productKey: 'hosting', details: { contractAt: '2026-10-01T09:00:00.000Z' }, id: 'abschluss-sub_3', }) const again = await recordContractEvent({ customerId: 'c3', subscriptionId: 'sub_3', type: 'abschluss', productKey: 'hosting', details: { contractAt: '2026-10-01T09:00:00.000Z' }, id: 'abschluss-sub_3', }) assert.equal(again.$id, 'abschluss-sub_3') assert.equal(again.$id, first.$id) assert.equal((await listContractEvents('c3')).length, 1) }) // Rechtsrunde 28.09.2026: eine Speicher-Verringerung hat kein Pflicht-Haekchen und damit keine // Zustimmungsdaten - recordContractEvent muss mit consent: null funktionieren und darf dann keine // consentIp/consentUserAgent speichern test('Speicher-Verringerung ohne Zustimmung (consent: null) speichert keine Zustimmungsdaten', async () => { const doc = await recordContractEvent({ customerId: 'c4', subscriptionId: 'sub_4', type: 'speicher', productKey: 'hosting', details: { fromUnits: 2, toUnits: 1 }, consent: null, }) assert.equal(doc.consentIp ?? null, null) assert.equal(doc.consentUserAgent ?? null, null) assert.equal(doc.termsVersion ?? null, null) })