import { test, after, beforeEach } 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' // Abo-Baustein (server/services/aboStatus.js, B5 Kernversion, E1 ausgebaut): laeuft / bestellbar / aboLage / aboMemo. // Fail-closed: ein Lesefehler wirft, er wird nie zu false bzw. ok:false. Die Umgebung steht vor dem ersten // Import von server/config.js. const aw = await startAppwriteMock({ schema: { subscriptions: schemaKeys('subscriptions'), contractEvents: schemaKeys('contractEvents') } }) const st = await startFakeStripe() after(() => Promise.all([aw.close(), st.close()])) useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url }) const { seedFixtures, subscriptionDoc, wartungDoc, hosting2Doc } = await import('./support/fixtures.js') const { deleteDocument } = await import('../server/services/appwriteAdmin.js') const { laeuft, bestellbar, aboLage, aboMemo } = await import('../server/services/aboStatus.js') const DAY_MS = 86400000 const inTagen = (tage) => new Date(Date.now() + tage * DAY_MS).toISOString() // seedFixtures gibt c1 nur ein Hosting-Abo (sub_h): fuer Wartungsfaelle ueberschreibt der Test es mit einem Wartungs-Abo. const wartung = (patch = {}) => aw.seed('subscriptions', subscriptionDoc('sub_h', { ...wartungDoc, ...patch })) let ereignisNr = 0 // Vertragsereignis des Abos sub_h von c1; $createdAt steigt mit jedem Aufruf, damit "das juengste" eindeutig ist const ereignis = (type, status, patch = {}) => { ereignisNr += 1 aw.seed('contractEvents', { $id: `ev_${ereignisNr}`, $createdAt: new Date(Date.UTC(2026, 8, 1, 0, 0, ereignisNr)).toISOString(), customerId: 'c1', subscriptionId: 'sub_h', type, productKey: 'wartung', details: '{}', status, ...patch, }) } const wechsel = (status, patch) => ereignis('wechsel', status, patch) /** Anzahl der Anfragen an den Appwrite-Nachbau, die fn ausloest (optional nur die, deren URL zu muster passt). */ async function zaehleAnfragen(fn, muster = null) { const echt = globalThis.fetch let n = 0 globalThis.fetch = (url, optionen) => { if (!muster || muster.test(String(url))) n += 1 return echt(url, optionen) } try { await fn() } finally { globalThis.fetch = echt } return n } const ereignisAnfragen = (fn) => zaehleAnfragen(fn, /contractEvents/) // Jeder Test beginnt mit den Standard-Abos (c1 Hosting, c2 Hosting, c3 keins) und ohne Ereignisse; geloescht wird ueber die API beforeEach(async () => { for (const collection of ['subscriptions', 'contractEvents']) { for (const id of aw.docs(collection).keys()) await deleteDocument(collection, id) } seedFixtures(aw) }) test('Wartungs-Abo active: laeuft true, bestellbar { ok: true } ohne Grund', async () => { wartung({ status: 'active' }) assert.equal(await laeuft('c1'), true) assert.deepEqual(await bestellbar('c1'), { ok: true }) }) test('Hosting-Abo (Standard von c1 und c2): laeuft false, bestellbar kein_abo', async () => { for (const customerId of ['c1', 'c2']) { assert.equal(await laeuft(customerId), false, customerId) assert.deepEqual(await bestellbar(customerId), { ok: false, grund: 'kein_abo' }, customerId) } }) test('Dokument ohne productKey zaehlt als Wartungs-Abo (wie routes/abo.js productKeyOf), ein unbekannter Schluessel nicht', async () => { const { productKey: _entfernt, ...ohneSchluessel } = wartungDoc aw.seed('subscriptions', subscriptionDoc('sub_h', ohneSchluessel)) assert.equal(await laeuft('c1'), true, 'Feld fehlt') assert.deepEqual(await bestellbar('c1'), { ok: true }) wartung({ productKey: null }) assert.equal(await laeuft('c1'), true, 'productKey null') assert.deepEqual(await bestellbar('c1'), { ok: true }) wartung({ productKey: 'mailStorage' }) assert.equal(await laeuft('c1'), false, 'anderes Produkt') assert.deepEqual(await bestellbar('c1'), { ok: false, grund: 'kein_abo' }) }) test('canceled: laeuft false, bestellbar kein_abo - auch mit vorgemerkter Kuendigung (kein Abo schlaegt gekuendigt)', async () => { wartung({ status: 'canceled' }) assert.equal(await laeuft('c1'), false) assert.deepEqual(await bestellbar('c1'), { ok: false, grund: 'kein_abo' }) wartung({ status: 'canceled', cancelAtPeriodEnd: true, cancelAt: inTagen(-1) }) assert.equal(await laeuft('c1'), false) assert.deepEqual(await bestellbar('c1'), { ok: false, grund: 'kein_abo' }) }) test('past_due und trialing: laeuft true, bestellbar nicht_aktiv', async () => { for (const status of ['past_due', 'trialing']) { wartung({ status }) assert.equal(await laeuft('c1'), true, status) assert.deepEqual(await bestellbar('c1'), { ok: false, grund: 'nicht_aktiv' }, status) } }) test('Kuendigung vorgemerkt (cancelAtPeriodEnd oder cancelAt): gekuendigt, vor nicht_aktiv und bei jedem laufenden Status', async () => { for (const status of ['active', 'trialing', 'past_due']) { wartung({ status, cancelAtPeriodEnd: true, cancelAt: null }) assert.equal(await laeuft('c1'), true, `${status} laeuft`) assert.deepEqual(await bestellbar('c1'), { ok: false, grund: 'gekuendigt' }, `${status} cancelAtPeriodEnd`) wartung({ status, cancelAtPeriodEnd: false, cancelAt: inTagen(12) }) assert.equal(await laeuft('c1'), true, `${status} laeuft`) assert.deepEqual(await bestellbar('c1'), { ok: false, grund: 'gekuendigt' }, `${status} cancelAt`) } }) test('kein Abo (c3), leere Kunden-ID: laeuft false, bestellbar kein_abo', async () => { assert.equal(await laeuft('c3'), false) assert.deepEqual(await bestellbar('c3'), { ok: false, grund: 'kein_abo' }) assert.equal(await laeuft(''), false) assert.deepEqual(await bestellbar(undefined), { ok: false, grund: 'kein_abo' }) }) test('Lesefehler (500 auf subscriptions): laeuft und bestellbar werfen, nie false und nie ok:false', async () => { wartung({ status: 'active' }) aw.failNext({ collection: 'subscriptions', status: 500 }) await assert.rejects(() => laeuft('c1'), /Nachbau: eingeplanter Fehler 500/) aw.failNext({ collection: 'subscriptions', status: 500 }) await assert.rejects(() => bestellbar('c1'), /Nachbau: eingeplanter Fehler 500/) // der Fehler war einmalig: danach liest dieselbe Frage wieder normal assert.equal(await laeuft('c1'), true) assert.deepEqual(await bestellbar('c1'), { ok: true }) }) test('aboMemo: zweiter Aufruf desselben Kunden kommt aus dem Speicher, ein frischer laeuft liest neu', async () => { wartung({ status: 'active' }) const memo = aboMemo() assert.equal(await memo.laeuft('c1'), true) aw.failNext({ collection: 'subscriptions', status: 500 }) assert.equal(await memo.laeuft('c1'), true, 'aus dem Speicher, das eingeplante Failure trifft ihn nicht') await assert.rejects(() => laeuft('c1'), /eingeplanter Fehler 500/, 'ein frischer laeuft liest neu und trifft den Fehler') }) test('aboMemo: bestellbar wird je Kunde gemerkt; laeuft und bestellbar teilen sich die Abo-Lesung desselben Kunden', async () => { wartung({ status: 'past_due' }) const memo = aboMemo() assert.deepEqual(await memo.bestellbar('c1'), { ok: false, grund: 'nicht_aktiv' }) aw.failNext({ collection: 'subscriptions', status: 500 }) assert.deepEqual(await memo.bestellbar('c1'), { ok: false, grund: 'nicht_aktiv' }, 'zweiter Aufruf aus dem Speicher') assert.equal(await memo.laeuft('c1'), true, 'laeuft nutzt dieselbe gelesene Abo-Zeile') await assert.rejects(() => bestellbar('c1'), /eingeplanter Fehler 500/, 'der eingeplante Fehler war noch frei') }) test('aboMemo: ein anderer Kunde wird neu gelesen, ein anderer aboMemo() ebenfalls', async () => { wartung({ status: 'active' }) const memo = aboMemo() assert.equal(await memo.laeuft('c1'), true) // anderer Kunde im selben Memo: eigene Lesung (trifft den eingeplanten Fehler) aw.failNext({ collection: 'subscriptions', status: 500 }) await assert.rejects(() => memo.laeuft('c3'), /eingeplanter Fehler 500/) assert.equal(await aboMemo().laeuft('c3'), false, 'c3 hat kein Abo (die Ablehnung bleibt in diesem Memo stehen, E1)') // anderer aboMemo() fuer denselben Kunden: neu lesen, der alte Speicher gilt nicht aw.failNext({ collection: 'subscriptions', status: 500 }) await assert.rejects(() => aboMemo().laeuft('c1'), /eingeplanter Fehler 500/) assert.equal(await memo.laeuft('c1'), true, 'der erste Memo behaelt seinen Eintrag') }) test('aboMemo: gleichzeitige Aufrufe desselben Kunden lesen nur einmal', async () => { wartung({ status: 'active' }) aw.resetListCalls() const memo = aboMemo() const antworten = await Promise.all([memo.laeuft('c1'), memo.bestellbar('c1'), memo.laeuft('c1')]) assert.deepEqual(antworten, [true, { ok: true }, true]) assert.equal(aw.listCalls('subscriptions').length, 1) }) // ------------------------------------------------------------------------------------------ E1: ausgebaut --- test('laeuft: Status-Tabelle (active, trialing, past_due laufen; canceled, unpaid, incomplete_expired nicht)', async () => { for (const status of ['active', 'trialing', 'past_due']) { wartung({ status }) assert.equal(await laeuft('c1'), true, status) } for (const status of ['canceled', 'unpaid', 'incomplete_expired']) { wartung({ status }) assert.equal(await laeuft('c1'), false, status) } }) test('laeuft: Hosting aktiv und kein Dokument => false; vorgemerkte Kuendigung zaehlt noch als laufend', async () => { assert.equal(await laeuft('c1'), false, 'Hosting aktiv (Standard von c1)') assert.equal(await laeuft('c3'), false, 'kein Dokument') wartung({ status: 'active', cancelAtPeriodEnd: true, cancelAt: inTagen(12) }) assert.equal(await laeuft('c1'), true) }) test('laeuft: zwei Dokumente (altes canceled, neues active) => true, in beiden Anlegereihenfolgen', async () => { aw.seed('subscriptions', subscriptionDoc('sub_alt', { ...wartungDoc, status: 'canceled', $createdAt: inTagen(-100) })) wartung({ status: 'active', $createdAt: inTagen(-10) }) assert.equal(await laeuft('c1'), true, 'das aktive ist das neuere') aw.seed('subscriptions', subscriptionDoc('sub_alt', { ...wartungDoc, status: 'canceled', $createdAt: inTagen(-1) })) assert.equal(await laeuft('c1'), true, 'das beendete ist das neuere, das laufende gilt trotzdem') }) test('Altbestand-Unterschied: Dokument ohne productKey => laeuft(id) true, laeuft(id, { altbestand: false }) false', async () => { const { productKey: _entfernt, ...ohneSchluessel } = wartungDoc aw.seed('subscriptions', subscriptionDoc('sub_h', ohneSchluessel)) assert.equal(await laeuft('c1'), true) assert.equal(await laeuft('c1', { altbestand: true }), true) assert.equal(await laeuft('c1', { altbestand: false }), false) wartung({ productKey: null }) assert.equal(await laeuft('c1'), true, 'productKey null') assert.equal(await laeuft('c1', { altbestand: false }), false, 'productKey null, strenge Regel') // mit Schluessel 'wartung' urteilen beide Regeln gleich wartung({ status: 'active' }) assert.equal(await laeuft('c1'), true) assert.equal(await laeuft('c1', { altbestand: false }), true) wartung({ status: 'canceled' }) assert.equal(await laeuft('c1', { altbestand: false }), false) }) test('Lesefehler: laeuft, bestellbar und aboLage lehnen ab (GET 500 auf subscriptions), nie false oder { ok: false }', async () => { wartung({ status: 'active' }) for (const frage of [() => laeuft('c1'), () => laeuft('c1', { altbestand: false }), () => bestellbar('c1'), () => aboLage('c1')]) { aw.failNext({ collection: 'subscriptions', method: 'GET', status: 500 }) await assert.rejects(frage, /eingeplanter Fehler 500/) } // der Fehler war einmalig assert.equal(await laeuft('c1'), true) }) test('Lesefehler bei den Ereignissen: nur die grosszuegige Regel liest sie und lehnt dann ab, die strenge Regel nicht', async () => { wartung({ status: 'active' }) aw.failNext({ collection: 'contractEvents', method: 'GET', status: 500 }) assert.equal(await laeuft('c1', { altbestand: false }), true, 'strenge Regel fragt keine Ereignisse') await assert.rejects(() => laeuft('c1'), /eingeplanter Fehler 500/, 'laeuft liest sie und scheitert') aw.failNext({ collection: 'contractEvents', method: 'GET', status: 500 }) await assert.rejects(() => bestellbar('c1'), /eingeplanter Fehler 500/) aw.failNext({ collection: 'contractEvents', method: 'GET', status: 500 }) await assert.rejects(() => aboLage('c1'), /eingeplanter Fehler 500/) assert.deepEqual(await aboLage('c1'), { laeuft: true, art: 'laeuft', endedAt: null }) }) test('bestellbar: Tabelle der Gruende', async () => { const faelle = [ ['active ohne Kuendigung', { status: 'active' }, { ok: true }], ['active mit cancelAtPeriodEnd', { status: 'active', cancelAtPeriodEnd: true }, { ok: false, grund: 'gekuendigt' }], ['active mit cancelAt in der Zukunft', { status: 'active', cancelAt: inTagen(30) }, { ok: false, grund: 'gekuendigt' }], ['trialing', { status: 'trialing' }, { ok: false, grund: 'nicht_aktiv' }], ['past_due', { status: 'past_due' }, { ok: false, grund: 'nicht_aktiv' }], ['trialing mit Kuendigung', { status: 'trialing', cancelAtPeriodEnd: true }, { ok: false, grund: 'gekuendigt' }], ['past_due mit Kuendigung', { status: 'past_due', cancelAt: inTagen(5) }, { ok: false, grund: 'gekuendigt' }], ['canceled', { status: 'canceled' }, { ok: false, grund: 'kein_abo' }], ] for (const [name, patch, erwartet] of faelle) { wartung(patch) assert.deepEqual(await bestellbar('c1'), erwartet, name) } wartung({ productKey: 'hosting' }) assert.deepEqual(await bestellbar('c1'), { ok: false, grund: 'kein_abo' }, 'Hosting') assert.deepEqual(await bestellbar('c3'), { ok: false, grund: 'kein_abo' }, 'kein Abo') }) test('aboLage: art laeuft (endedAt null, auch bei vorgemerkter Kuendigung mit canceledAt)', async () => { wartung({ status: 'active' }) assert.deepEqual(await aboLage('c1'), { laeuft: true, art: 'laeuft', endedAt: null }) wartung({ status: 'past_due', cancelAtPeriodEnd: true, canceledAt: inTagen(-2) }) assert.deepEqual(await aboLage('c1'), { laeuft: true, art: 'laeuft', endedAt: null }) }) test('aboLage: art hosting bei laufendem Hosting-Abo, ohne Ereignis-Abfrage', async () => { // Standard von c1 und c2: Hosting aktiv const n = await ereignisAnfragen(async () => { assert.deepEqual(await aboLage('c1'), { laeuft: false, art: 'hosting', endedAt: null }) assert.deepEqual(await aboLage('c2'), { laeuft: false, art: 'hosting', endedAt: null }) }) assert.equal(n, 0, 'keine Ereignisse gelesen') assert.equal(await ereignisAnfragen(() => laeuft('c1')), 0) assert.equal(await ereignisAnfragen(() => bestellbar('c1')), 0) }) test('aboLage: art beendet mit endedAt (oder canceledAt, oder null), ohne Ereignis-Abfrage', async () => { const ende = inTagen(-3) const gekuendigtAm = inTagen(-20) const n = await ereignisAnfragen(async () => { wartung({ status: 'canceled', endedAt: ende, canceledAt: gekuendigtAm }) assert.deepEqual(await aboLage('c1'), { laeuft: false, art: 'beendet', endedAt: ende }, 'endedAt vor canceledAt') wartung({ status: 'canceled', endedAt: null, canceledAt: gekuendigtAm }) assert.deepEqual(await aboLage('c1'), { laeuft: false, art: 'beendet', endedAt: gekuendigtAm }, 'nur canceledAt') wartung({ status: 'unpaid', endedAt: null, canceledAt: null }) assert.deepEqual(await aboLage('c1'), { laeuft: false, art: 'beendet', endedAt: null }, 'beides leer') assert.deepEqual(await aboLage('c3'), { laeuft: false, art: 'beendet', endedAt: null }, 'kein Abo') wartung({ productKey: 'hosting', status: 'canceled', endedAt: ende }) assert.deepEqual(await aboLage('c1'), { laeuft: false, art: 'beendet', endedAt: ende }, 'beendetes Hosting-Abo') }) assert.equal(n, 0, 'keine Ereignisse gelesen') }) test('aboLage: hat ein Kunde Hosting aktiv und Wartung beendet, gilt das laufende Hosting-Abo', async () => { aw.seed('subscriptions', subscriptionDoc('sub_alt', { ...wartungDoc, status: 'canceled', endedAt: inTagen(-30) })) aw.seed('subscriptions', subscriptionDoc('sub_h', { ...hosting2Doc, customerId: 'c1' })) assert.deepEqual(await aboLage('c1'), { laeuft: false, art: 'hosting', endedAt: null }) }) test('aboLage: Wechsel-Widerruf => laeuft false, art wechsel_widerrufen; ein neueres erledigtes wechsel-Ereignis hebt es auf', async () => { wartung({ status: 'active' }) wechsel('widerrufen') assert.deepEqual(await aboLage('c1'), { laeuft: false, art: 'wechsel_widerrufen', endedAt: null }) wechsel('erledigt') assert.deepEqual(await aboLage('c1'), { laeuft: true, art: 'laeuft', endedAt: null }, 'das juengste wechsel-Ereignis zaehlt') }) test('aboLage: nur das juengste wechsel-Ereignis DIESES Abos zaehlt (andere Typen, andere Abos, offene Wechsel)', async () => { wartung({ status: 'active' }) wechsel('erledigt') wechsel('widerrufen') assert.equal((await aboLage('c1')).art, 'wechsel_widerrufen', 'juengstes widerrufen, aelteres erledigt') ereignis('abschluss', 'widerrufen') ereignis('speicher', 'widerrufen') wechsel('widerrufen', { subscriptionId: 'sub_anderes' }) wechsel('erledigt', { subscriptionId: 'sub_anderes' }) assert.equal((await aboLage('c1')).art, 'wechsel_widerrufen', 'juengere Ereignisse anderer Typen und anderer Abos aendern nichts') wechsel('offen') assert.deepEqual(await aboLage('c1'), { laeuft: true, art: 'laeuft', endedAt: null }, 'ein offener Wechsel ist kein Widerruf') }) test('aboLage: Ereignisse anderer Kunden zaehlen nicht', async () => { wartung({ status: 'active' }) wechsel('widerrufen', { customerId: 'c2' }) assert.equal((await aboLage('c1')).art, 'laeuft') }) test('Wechsel-Regel in allen Praedikaten: laeuft false, bestellbar kein_abo, aboLage wechsel_widerrufen; die strenge Regel bleibt true und fragt keine Ereignisse', async () => { wartung({ status: 'active' }) wechsel('widerrufen') assert.equal(await laeuft('c1'), false) assert.equal(await laeuft('c1', { altbestand: true }), false) assert.deepEqual(await bestellbar('c1'), { ok: false, grund: 'kein_abo' }) assert.deepEqual(await aboLage('c1'), { laeuft: false, art: 'wechsel_widerrufen', endedAt: null }) const memo = aboMemo() assert.equal(await memo.laeuft('c1'), false) assert.deepEqual(await memo.bestellbar('c1'), { ok: false, grund: 'kein_abo' }) assert.deepEqual(await memo.lage('c1'), { laeuft: false, art: 'wechsel_widerrufen', endedAt: null }) let strenge const n = await ereignisAnfragen(async () => { strenge = await laeuft('c1', { altbestand: false }) }) assert.equal(strenge, true, 'Analyse bleibt bei ihrer heutigen Regel') assert.equal(n, 0, 'strenge Regel fragt keine Ereignisse') }) test('Wechsel-Regel: Kuendigung und Status spielen keine Rolle, solange der Spiegel Wartung sagt', async () => { for (const status of ['active', 'trialing', 'past_due']) { wartung({ status, cancelAtPeriodEnd: true }) wechsel('widerrufen') assert.equal(await laeuft('c1'), false, status) assert.deepEqual(await bestellbar('c1'), { ok: false, grund: 'kein_abo' }, status) } }) test('Ereignisse werden nur gelesen, wenn der Spiegel "Wartung laeuft" sagt - und dann einmal je Frage', async () => { wartung({ status: 'active' }) assert.equal(await ereignisAnfragen(() => laeuft('c1')), 1) assert.equal(await ereignisAnfragen(() => bestellbar('c1')), 1) assert.equal(await ereignisAnfragen(() => aboLage('c1')), 1) assert.equal(await ereignisAnfragen(() => laeuft('c1', { altbestand: false })), 0) }) test('aboMemo: drei Aufrufe fuer denselben Kunden => eine Abfrage auf subscriptions (und eine auf contractEvents)', async () => { wartung({ status: 'active' }) aw.resetListCalls() const memo = aboMemo() assert.equal(await memo.laeuft('c1'), true) assert.deepEqual(await memo.bestellbar('c1'), { ok: true }) assert.deepEqual(await memo.lage('c1'), { laeuft: true, art: 'laeuft', endedAt: null }) assert.equal(aw.listCalls('subscriptions').length, 1) assert.equal(aw.listCalls('contractEvents').length, 1) // zweiter Kunde: eine weitere Abfrage assert.equal(await memo.laeuft('c3'), false) assert.deepEqual(await memo.lage('c3'), { laeuft: false, art: 'beendet', endedAt: null }) assert.equal(aw.listCalls('subscriptions').length, 2) // ein neuer aboMemo() liest wieder assert.equal(await aboMemo().laeuft('c1'), true) assert.equal(aw.listCalls('subscriptions').length, 3) }) test('aboMemo: lage liefert je Aufruf ein neues Objekt (Aendern wirkt nicht auf den Speicher)', async () => { wartung({ status: 'active' }) const memo = aboMemo() const erste = await memo.lage('c1') erste.laeuft = false erste.art = 'beendet' assert.deepEqual(await memo.lage('c1'), { laeuft: true, art: 'laeuft', endedAt: null }) assert.equal(await memo.laeuft('c1'), true) }) test('aboMemo: eine Ablehnung bleibt fuer diesen Lauf stehen (alle drei Fragen, keine zusaetzliche Anfrage); ein neuer Memo liest wieder', async () => { wartung({ status: 'active' }) const memo = aboMemo() aw.resetListCalls() aw.failNext({ collection: 'subscriptions', method: 'GET', status: 500 }) await assert.rejects(() => memo.laeuft('c1'), /eingeplanter Fehler 500/) await assert.rejects(() => memo.bestellbar('c1'), /eingeplanter Fehler 500/, 'bestellbar wirft, nie ok:false') await assert.rejects(() => memo.lage('c1'), /eingeplanter Fehler 500/) await assert.rejects(() => memo.laeuft('c1'), /eingeplanter Fehler 500/) assert.equal(aw.listCalls('subscriptions').length, 1, 'nach der Ablehnung wird im selben Memo nicht neu gelesen') // andere Kunden im selben Memo sind nicht betroffen, ein neuer Memo liest den Kunden neu assert.equal(await memo.laeuft('c3'), false) assert.equal(await aboMemo().laeuft('c1'), true) }) test('aboMemo: auch ein Lesefehler bei den Ereignissen bleibt im Memo stehen', async () => { wartung({ status: 'active' }) const memo = aboMemo() aw.resetListCalls() aw.failNext({ collection: 'contractEvents', method: 'GET', status: 500 }) await assert.rejects(() => memo.lage('c1'), /eingeplanter Fehler 500/) await assert.rejects(() => memo.bestellbar('c1'), /eingeplanter Fehler 500/) assert.equal(aw.listCalls('subscriptions').length, 1) assert.equal(aw.listCalls('contractEvents').length, 1) })