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' import { mailSink } from './support/mailSink.js' // Zusatzspeicher-Plus in Fassung 6: Nachweis mit Kundenart aus dem Dokument vor der Änderung, das Minus bleibt ohne Nachweis const aw = await startAppwriteMock({ schema: { subscriptions: schemaKeys('subscriptions'), contractEvents: schemaKeys('contractEvents'), hourCredits: schemaKeys('hourCredits'), featureConsents: schemaKeys('featureConsents'), }, }) const st = await startFakeStripe() useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url }) Object.assign(process.env, { KUNDENANSICHT_NEU: 'pilot', KUNDENANSICHT_NEU_PILOT_EMAILS: 'K@example.invalid, k3@example.invalid' }) const { config } = await import('../server/config.js') const { startTestApp, post } = await import('./support/testApp.js') const { hostingDoc, customerCookie, customers, kundenansichtCookie, seedFixtures, stripeSubscription } = await import('./support/fixtures.js') const { default: aboRoutes } = await import('../server/routes/abo.js') const { listContractEvents } = await import('../server/services/contractEvents.js') const { deleteDocument } = await import('../server/services/appwriteAdmin.js') const { setMailTransportForTests } = await import('../server/services/mailer.js') const { FEHLER_BEDINGUNGEN } = await import('../server/services/bestelltexte.js') const app = await startTestApp({ '/api/abo': aboRoutes }) const mails = mailSink() setMailTransportForTests(mails) after(() => { setMailTransportForTests(null) return Promise.all([app.close(), aw.close(), st.close()]) }) seedFixtures(aw) const price = (id, lookup_key, unit_amount) => ({ id, object: 'price', active: true, lookup_key, unit_amount, recurring: { interval: 'month' } }) st.prices.set('price_mailStorage', price('price_mailStorage', config.mailStorage.lookupKey, config.mailStorage.amountCents)) st.prices.set('price_hosting', price('price_hosting', config.products.hosting.lookupKey, config.products.hosting.amountCents)) st.prices.set('price_wartung', price('price_wartung', config.products.wartung.lookupKey, config.products.wartung.amountCents)) const setzeKundenart = (id, wert) => aw.seed('customers', { ...customers[id], ...(wert === undefined ? {} : { kundenart: wert }) }) async function clearEvents(customerId) { for (const e of await listContractEvents(customerId)) await deleteDocument(config.collections.contractEvents, e.$id) } async function resetSubH({ units = 0 } = {}) { await clearEvents('c1') mails.sent.length = 0 aw.seed('subscriptions', { ...hostingDoc, storageUnits: units, storagePaidUnits: 0, storagePaidPeriodEnd: null }) st.subscriptions.set('sub_h', stripeSubscription({ id: 'sub_h', customerId: 'c1', productKey: 'hosting', storageUnits: units })) } const NEU = () => config.abo.termsVersionNeu const zeilen = () => [...aw.docs('featureConsents').values()].filter((d) => d.featureKey === 'speicher') const stripeAenderungen = (von) => st.calls.slice(von).filter((c) => c.method === 'POST' && c.path === '/v1/subscriptions/sub_h') test('Plus in Fassung 6: Nachweis mit Kundenart aus dem Dokument, Ereignis mit der neuen Fassung', async () => { setzeKundenart('c1', 'unternehmen') await resetSubH({ units: 0 }) const n0 = zeilen().length const res = await post('/api/abo/storage', { units: 2, acceptTerms: true, termsVersion: NEU(), kundenart: 'privat' }, { cookie: customerCookie() }) assert.equal(res.status, 200, JSON.stringify(res.body)) const [z] = zeilen().slice(n0) assert.deepEqual([z.kundenart, JSON.parse(z.fassungen).bezug, z.projectId], ['unternehmen', 'units=2', '']) assert.deepEqual(JSON.parse(z.texte).map((t) => t.id), ['bestellung', 'linkzeile', 'knopf', 'kundenart']) assert.equal(JSON.parse(z.texte)[0].text, 'Zusatzspeicher: 2 Pakete (+10 GB), 2,00 € pro Monat (gemäß § 19 UStG ohne Umsatzsteuer), monatlich kündbar') assert.equal((await listContractEvents('c1'))[0].termsVersion, NEU()) }) test('Privat und leer: Nachweis mit privat, nie mit dem Wert aus dem Body', async () => { for (const art of ['privat', undefined, 'firma']) { setzeKundenart('c1', art) await resetSubH({ units: 0 }) const res = await post('/api/abo/storage', { units: 1, acceptTerms: true, termsVersion: NEU(), kundenart: 'unternehmen' }, { cookie: customerCookie() }) assert.equal(res.status, 200, JSON.stringify(res.body)) assert.equal(zeilen().at(-1).kundenart, 'privat', String(art)) assert.match(JSON.parse(zeilen().at(-1).texte)[0].text, /^Zusatzspeicher: 1 Paket \(\+5 GB\), 1,00 € pro Monat/) } }) test('Minus, Fassung 5, falsche Fassung, ungültige Menge und Nachweisfehler', async () => { setzeKundenart('c1', 'unternehmen') await resetSubH({ units: 2 }) const n0 = zeilen().length assert.equal((await post('/api/abo/storage', { units: 1 }, { cookie: customerCookie() })).status, 200) assert.equal(zeilen().length, n0) // Minus: kein Nachweis await resetSubH({ units: 0 }) assert.equal((await post('/api/abo/storage', { units: 1, acceptTerms: true, termsVersion: config.abo.termsVersion }, { cookie: customerCookie() })).status, 200) assert.equal(zeilen().length, n0) // Fassung 5: alter Weg ohne Nachweis const fremd = await post('/api/abo/storage', { units: 2, acceptTerms: true, termsVersion: NEU() }, { cookie: customerCookie('c2') }) assert.equal(fremd.status, 409) const v3 = await post('/api/abo/storage', { units: 2, acceptTerms: true, termsVersion: '2026-09-25-v3' }, { cookie: customerCookie() }) assert.deepEqual([v3.status, v3.body.error], [409, FEHLER_BEDINGUNGEN]) for (const units of [11, -1, 1.5, '2', undefined, 0]) { const r = await post('/api/abo/storage', { units, acceptTerms: true, termsVersion: NEU() }, { cookie: customerCookie() }) assert.ok(r.status === 400 || r.status === 200, `${String(units)}: ${r.status}`) } assert.equal(zeilen().length, n0, 'ungültige Mengen und 0 schreiben keinen Nachweis') await resetSubH({ units: 0 }) aw.failNext({ collection: config.collections.featureConsents, method: 'POST' }) const vor = st.calls.length const f = await post('/api/abo/storage', { units: 2, acceptTerms: true, termsVersion: NEU() }, { cookie: customerCookie() }) assert.deepEqual([f.status, stripeAenderungen(vor).length], [500, 0]) }) test('Mitarbeiter-Kundenansicht ändert nichts und schreibt nichts', async () => { await resetSubH({ units: 0 }) const n0 = zeilen().length const vor = st.calls.length const r = await post('/api/abo/storage', { units: 2, acceptTerms: true, termsVersion: NEU() }, { cookie: kundenansichtCookie({ customerId: 'c1' }) }) assert.deepEqual([r.status, zeilen().length, stripeAenderungen(vor).length], [403, n0, 0]) }) test('Kunde nicht lesbar: 500 statt Rückfall, nichts geändert', async () => { setzeKundenart('c1', 'unternehmen') await resetSubH({ units: 0 }) const n0 = zeilen().length const vor = st.calls.length aw.failNext({ collection: config.collections.customers, method: 'GET', status: 500 }) const r = await post('/api/abo/storage', { units: 2, acceptTerms: true, termsVersion: NEU() }, { cookie: customerCookie() }) assert.deepEqual([r.status, zeilen().length, stripeAenderungen(vor).length], [500, n0, 0]) })