import { test } from 'node:test' import assert from 'node:assert/strict' import { zeigeWechsel, gemerkteFunktion } from '../public/neu/ansicht-wechsel.js' import { nimmHinweis } from '../public/neu/ansicht-hilfen.js' import { sichtbarerText, ohneKopf, ladeAlteApp } from './support/knWortlautOrakel.js' import { mit, me, abo, aboAntwort, neueWurzel, VORSCHAU } from './support/knDaten.js' const alt = ladeAlteApp() const HOSTING = aboAntwort({ subscription: abo({ produkt: 'hosting', canSwitch: true, storage: { units: 2, pendingSetup: false, pendingReduction: null } }) }) const zeigeNeu = async (fx, daten = HOSTING, m = me(), titel = []) => { const w = neueWurzel(fx); w.addEventListener('kn:titel', (e) => titel.push(e.detail.text)); await zeigeWechsel(w, { route: { bereich: 'vertraege', etage: 'wechsel', query: '' }, me: m, nav: 'push', daten }); await fx.ruhe(); return w } test('Wechsel: sichtbarer Text wie in app.js, Titel wird gemeldet', mit({ 'POST /api/abo/switch/preview': VORSCHAU }, async (fx) => { const titel = []; assert.equal(sichtbarerText(await zeigeNeu(fx, HOSTING, me(), titel)), sichtbarerText(ohneKopf(await alt.wechsel(HOSTING, VORSCHAU)))); assert.deepEqual(titel, ['Wechsel ins Wartungs-Abo']) })) test('Wechsel nicht möglich: Satz und Rückweg zu Verträgen', mit({}, async (fx) => { fx.window.history.replaceState = (_s, _t, url) => { fx.window.location.hash = `#${String(url).split('#')[1] ?? ''}` } const w = await zeigeNeu(fx, aboAntwort({ subscription: abo({ produkt: 'wartung' }) })) assert.match(fx.text(w), /Ein Wechsel ist derzeit nicht möglich\. Details finden Sie unter Verträge\./); assert.equal(fx.window.location.hash, '#vertraege') })) test('Wechsel: Prüfungen wörtlich, Body mit gemerkter Funktion, Schlüssel danach weg', mit({ 'POST /api/abo/switch/preview': VORSCHAU, 'POST /api/abo/switch': () => ({ body: { paymentUrl: 'https://pay.stripe.com/x', subscription: abo() } }) }, async (fx) => { const speicher = new Map(); globalThis.sessionStorage = { getItem: (k) => speicher.get(k) ?? null, setItem: (k, v) => speicher.set(k, v), removeItem: (k) => speicher.delete(k) } try { const w = await zeigeNeu(fx) await fx.klick(fx.finde(w, '#switch-buy')); assert.match(fx.text(w), /Bitte stimmen Sie den Vertragsbedingungen zu\./) await fx.klick(fx.finde(w, '#switch-terms')); await fx.klick(fx.finde(w, '#switch-buy')); assert.match(fx.text(w), /Bitte bestätigen Sie den Sofortbeginn, sonst können wir den Wechsel nicht ausführen\./) await fx.klick(fx.finde(w, '#switch-early')); speicher.set('kn-wechsel-funktion', JSON.stringify({ key: 'termine', projectId: 'p1' })) await fx.klick(fx.finde(w, '#switch-buy')); await fx.ruhe() assert.deepEqual(fx.fetchLog.find((a) => a.url === '/api/abo/switch').body, { acceptTerms: true, earlyStart: true, termsVersion: '2026-09-30-v5', prorationDate: 1760000000, amountDueCents: 3000, funktion: { key: 'termine', projectId: 'p1' } }) assert.equal(speicher.has('kn-wechsel-funktion'), false); assert.equal(fx.window.location.href, 'https://pay.stripe.com/x'); assert.match(fx.text(w), /Ihr Wechsel wartet auf die Zahlung\. Wir leiten Sie zur Zahlung weiter\./) } finally { delete globalThis.sessionStorage } })) test('gemerkteFunktion: kaputte oder fremde Werte ergeben null', () => { const wert = (v) => { globalThis.sessionStorage = { getItem: () => v }; try { return gemerkteFunktion() } finally { delete globalThis.sessionStorage } } assert.deepEqual(wert('{"key":"shop","projectId":"p2"}'), { key: 'shop', projectId: 'p2' }) for (const v of ['kaputt', '{"key":"shop"}', '{"key":1,"projectId":"p"}', null]) assert.equal(wert(v), null) assert.equal(gemerkteFunktion(), null) // ohne sessionStorage }) const FUNKTION = { key: 'termine', projectId: 'p1' } const WOHIN = (fx) => { fx.window.history.replaceState = (_s, _t, url) => { fx.window.location.hash = `#${String(url).split('#')[1] ?? ''}` }; fx.window.history.pushState = fx.window.history.replaceState } for (const [name, gemerkt, antwort, nachgeladen, ziel] of [ ['gemerkt, Funktion steht in der Antwort', true, { wechselFunktion: FUNKTION }, undefined, '#funktionen'], ['gemerkt, Funktion erst im GET /api/abo', true, {}, { wechselFunktion: FUNKTION }, '#funktionen'], ['gemerkt, Wechsel noch nicht erledigt', true, {}, { wechselFunktion: null }, '#vertraege'], ['nicht gemerkt, der Server meldet trotzdem eine Funktion', false, { wechselFunktion: FUNKTION }, { wechselFunktion: FUNKTION }, '#vertraege'], ]) { test(`Wechsel ohne Zahlungsseite (${name}): Ziel ${ziel} (E14, C5)`, mit({ 'POST /api/abo/switch/preview': VORSCHAU, 'POST /api/abo/switch': () => ({ body: { subscription: abo(antwort) } }), 'GET /api/abo': aboAntwort({ subscription: abo({ produkt: 'wartung', ...nachgeladen }) }), }, async (fx) => { WOHIN(fx); const speicher = new Map(gemerkt ? [['kn-wechsel-funktion', JSON.stringify(FUNKTION)]] : []) globalThis.sessionStorage = { getItem: (k) => speicher.get(k) ?? null, setItem: (k, v) => speicher.set(k, v), removeItem: (k) => speicher.delete(k) } try { nimmHinweis() const w = await zeigeNeu(fx); await fx.klick(fx.finde(w, '#switch-terms')); await fx.klick(fx.finde(w, '#switch-early')); await fx.klick(fx.finde(w, '#switch-buy')); await fx.ruhe() assert.equal(fx.window.location.hash, ziel); assert.equal(speicher.has('kn-wechsel-funktion'), false) assert.equal(nimmHinweis() !== '', ziel === '#vertraege') // bei #funktionen bleibt kein Hinweis für später liegen assert.equal(fx.fetchLog.filter((a) => a.url === '/api/abo').length, gemerkt && !antwort.wechselFunktion ? 1 : 0) // höchstens ein Nachladen, nur mit gemerkter Funktion } finally { delete globalThis.sessionStorage } })) } test('Wechsel: 409 lädt die Vorschau neu und behält die Meldung; Ansichtsmodus sperrt', mit({ 'POST /api/abo/switch/preview': VORSCHAU, 'POST /api/abo/switch': () => ({ status: 409, body: { error: 'Betrag hat sich geändert.' } }) }, async (fx) => { const w = await zeigeNeu(fx); await fx.klick(fx.finde(w, '#switch-terms')); await fx.klick(fx.finde(w, '#switch-early')); await fx.klick(fx.finde(w, '#switch-buy')); await fx.ruhe() assert.match(fx.text(w), /Betrag hat sich geändert\./); assert.equal(fx.fetchLog.filter((a) => a.url === '/api/abo/switch/preview').length, 2) const v = await zeigeNeu(fx, HOSTING, me({ viewAs: true })); assert.equal(fx.finde(v, '#switch-buy').hasAttribute('disabled'), true); assert.match(fx.text(v), /Im Ansichtsmodus nicht möglich\./) })) test('Wechsel: Vorschau-Fehler, hängende Bestellung nach pageshow, SEPA-Hinweis; im Ansichtsmodus geht kein POST raus', mit({ 'POST /api/abo/switch/preview': () => ({ status: 500, body: { error: 'Vorschau kaputt.' } }) }, async (fx) => { const w = await zeigeNeu(fx); assert.match(fx.text(w), /Vorschau kaputt\./); assert.equal(fx.finde(w, '#switch-buy').hasAttribute('disabled'), true) fx.tabelle['POST /api/abo/switch/preview'] = VORSCHAU; fx.tabelle['POST /api/abo/switch'] = { paymentUrl: 'https://pay.stripe.com/y', subscription: abo() } const v = await zeigeNeu(fx); await fx.klick(fx.finde(v, '#switch-terms')); await fx.klick(fx.finde(v, '#switch-early')); await fx.klick(fx.finde(v, '#switch-buy')); await fx.ruhe() assert.equal(fx.finde(v, '#switch-buy').hasAttribute('disabled'), true) // wartet auf die Zahlung fx.window.dispatchEvent({ type: 'pageshow', persisted: true }); await fx.ruhe() assert.equal(fx.finde(v, '#switch-buy').hasAttribute('disabled'), false); assert.equal(fx.finde(v, '#switch-buy').textContent, 'Zahlungspflichtig bestellen') const vorher = fx.fetchLog.length; const a = await zeigeNeu(fx, HOSTING, me({ viewAs: true })); await fx.klick(fx.finde(a, '#switch-buy')) assert.deepEqual(fx.fetchLog.slice(vorher).filter((x) => x.url === '/api/abo/switch'), []) })) test('Wechsel: unsichere Zahlungsadresse wird nicht geöffnet und nicht als Erfolg gezeigt', mit({ 'POST /api/abo/switch/preview': VORSCHAU, 'POST /api/abo/switch': () => ({ body: { paymentUrl: 'javascript:alert(1)', subscription: abo() } }) }, async (fx) => { nimmHinweis(); const w = await zeigeNeu(fx); await fx.klick(fx.finde(w, '#switch-terms')); await fx.klick(fx.finde(w, '#switch-early')); await fx.klick(fx.finde(w, '#switch-buy')); await fx.ruhe() assert.notEqual(fx.window.location.href, 'javascript:alert(1)'); assert.match(fx.text(w), /Die Zahlungsseite konnte nicht geöffnet werden\./); assert.equal(nimmHinweis(), '') }))