From bf1e968a833c89eff96bf449f6675fddf4c2f7dd Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 19:59:43 +0000 Subject: [PATCH] Kundenansicht neu P7: GET /api/abo liefert Fassung 6 und Wortlaut nur der neuen Ansicht Co-Authored-By: Claude Sonnet 5.5 --- server/routes/abo.js | 8 ++++- test/knAboFassung6.test.js | 69 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 test/knAboFassung6.test.js diff --git a/server/routes/abo.js b/server/routes/abo.js index 0cf1751..9b7ec0b 100644 --- a/server/routes/abo.js +++ b/server/routes/abo.js @@ -26,6 +26,7 @@ import { featureConfig } from '../services/featureRegistry.js' import { istAboModell } from '../services/aboModell.js' import { ENTHALTEN_IN, aboModellFuerKunde } from '../services/aboFunktionen.js' import { neueAnsicht } from '../services/kundenansicht/schalter.js' +import { bestelltexteFuerClient } from '../services/bestelltexte.js' import { PRODUCT_KEYS, contractAttachmentFor, describeItems, isTermsV4OrNewer, productLabel, productView, termsDocFor } from '../services/products.js' import { ACTIVE_STATUSES, @@ -214,10 +215,15 @@ router.get('/', requireSession, async (req, res) => { active ? null : getOpenOffer(customerId, 'hosting').catch(() => null), active ? null : getOpenOffer(customerId, 'wartung').catch(() => null), ]) + // Fassung 6 (P7): nur in der neuen Ansicht und mit gesetzter Neu-Fassung; sonst bleibt die Antwort wie sie war (neun Schluessel) + const fassung6 = neueAnsicht(req) && Boolean(config.abo.termsVersionNeu) return res.json({ available: isStripeConfigured() && isPilotAllowed(customer, hostingOffer || wartungOffer), testAccount: config.abo.testEmails.includes(String(customer?.email || '').toLowerCase()), - termsVersion: config.abo.termsVersion, + termsVersion: fassung6 ? config.abo.termsVersionNeu : config.abo.termsVersion, + ...(fassung6 + ? { bestelltexte: bestelltexteFuerClient({ fassung: config.abo.termsVersionNeu, termsUrl: termsDocFor(config.abo.termsVersionNeu).url }) } + : {}), // Leistungsbeginn bei "Nach Ablauf der Widerrufsfrist" für die Bestellseite lateStartAt: lateStartAt().toISOString(), products: [productView('hosting'), productView('wartung')], diff --git a/test/knAboFassung6.test.js b/test/knAboFassung6.test.js new file mode 100644 index 0000000..3bdb5c4 --- /dev/null +++ b/test/knAboFassung6.test.js @@ -0,0 +1,69 @@ +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' + +// GET /api/abo: Fassung 6 und Wortlaut nur für die neue Ansicht (Pilot), sonst die bisherige Antwort mit neun Schlüsseln +const aw = await startAppwriteMock({ + schema: { + subscriptions: schemaKeys('subscriptions'), + contractEvents: schemaKeys('contractEvents'), + aboOffers: schemaKeys('aboOffers'), + 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, get } = await import('./support/testApp.js') +const { customerCookie, kundenansichtCookie, seedFixtures } = await import('./support/fixtures.js') +const { default: aboRoutes } = await import('../server/routes/abo.js') +const { termsDocFor } = await import('../server/services/products.js') + +const app = await startTestApp({ '/api/abo': aboRoutes }) +after(() => Promise.all([app.close(), aw.close(), st.close()])) +seedFixtures(aw) + +test('Pilot-Kunde bekommt Fassung 6 und den Wortlaut', async () => { + const neu = await get('/api/abo', { cookie: customerCookie('c1') }) + assert.equal(neu.termsVersion, config.abo.termsVersionNeu) + assert.deepEqual([neu.bestelltexte.status, neu.bestelltexte.fassung, neu.bestelltexte.knopf], ['entwurf', config.abo.termsVersionNeu, 'Zahlungspflichtig bestellen']) + assert.equal(neu.bestelltexte.abo.linkzeile.urls[0], termsDocFor(config.abo.termsVersionNeu).url) + assert.match(neu.bestelltexte.abo.linkzeile.text, new RegExp(`Fassung ${config.abo.termsVersionNeu}\\)`)) +}) + +test('Kundenansicht der Mitarbeiter sieht dasselbe, Nicht-Pilot die alte Antwort', async () => { + assert.ok((await get('/api/abo', { cookie: kundenansichtCookie() })).bestelltexte) + const alt = await get('/api/abo', { cookie: customerCookie('c2') }) + assert.equal(alt.termsVersion, config.abo.termsVersion) + assert.deepEqual(Object.keys(alt).sort(), ['available', 'lateStartAt', 'ledger', 'mailStorage', 'offers', 'products', 'subscription', 'termsVersion', 'testAccount']) +}) + +test('Ohne Neu-Fassung in der Konfiguration bleibt auch der Pilot bei der alten Antwort', async () => { + const gemerkt = config.abo.termsVersionNeu + for (const leer of ['', undefined]) { + config.abo.termsVersionNeu = leer + try { + const r = await get('/api/abo', { cookie: customerCookie('c1') }) + assert.equal('bestelltexte' in r, false) + assert.equal(r.termsVersion, config.abo.termsVersion) + } finally { + config.abo.termsVersionNeu = gemerkt + } + } +}) + +test('Schalter off: auch ein Pilot-Kunde bekommt die alte Antwort', async () => { + const modus = config.kundenansichtNeu.mode + config.kundenansichtNeu.mode = 'off' + try { + const r = await get('/api/abo', { cookie: customerCookie('c1') }) + assert.deepEqual([r.termsVersion, 'bestelltexte' in r], [config.abo.termsVersion, false]) + } finally { + config.kundenansichtNeu.mode = modus + } +})