import { test, after } from 'node:test' import assert from 'node:assert/strict' import express from 'express' import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' import { fileURLToPath } from 'node:url' import os from 'node:os' import path from 'node:path' import { startAppwriteMock } from './support/appwriteMock.js' import { startFakeStripe } from './support/fakeStripe.js' import { useTestEnv } from './support/testEnv.js' import { schemaKeys } from './support/schema.js' // Dashboard-Weiche: je Sitzung die neue Platzhalter-Seite oder bytegleich die alte (Schalter KUNDENANSICHT_NEU) const aw = await startAppwriteMock({ schema: { customerViewLog: schemaKeys('customerViewLog') } }) const st = await startFakeStripe() useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url }) const { config } = await import('../server/config.js') const { startTestApp, rawGet } = await import('./support/testApp.js') const { customerCookie, kundenansichtCookie, adminCookie, viewAsCookie } = await import('./support/fixtures.js') const { dashboardWeiche, neuDirektWeiche, oeffentlicheSeiteWeiche } = await import('../server/services/kundenansicht/dashboardWeiche.js') const publicDir = fileURLToPath(new URL('../public/', import.meta.url)) const router = express.Router() // gleiche Reihenfolge wie server/index.js router.get('/dashboard.html', dashboardWeiche({ publicDir })) router.get('/neu/dashboard.html', neuDirektWeiche()) router.get(['/kuendigen.html', '/widerrufen.html'], oeffentlicheSeiteWeiche({ publicDir })) router.use(express.static(publicDir)) // Nachtrag N39: dieselbe Weiche mit einem Nachbau von public/ (Datei fuer Datei erfunden), weil public/neu/oeffentlich/ erst mit P2 entsteht const nachbau = mkdtempSync(path.join(os.tmpdir(), 'kn-weiche-')) mkdirSync(path.join(nachbau, 'neu', 'oeffentlich'), { recursive: true }) writeFileSync(path.join(nachbau, 'neu', 'oeffentlich', 'kuendigen.html'), 'Nachbau') const nachbauRouter = express.Router() nachbauRouter.get(['/kuendigen.html', '/widerrufen.html'], oeffentlicheSeiteWeiche({ publicDir: nachbau })) nachbauRouter.use(express.static(publicDir)) const app = await startTestApp({ '/nachbau': nachbauRouter, '/': router }) after(() => { if (nachbau.startsWith(path.join(os.tmpdir(), 'kn-weiche-'))) rmSync(nachbau, { recursive: true, force: true }) return Promise.all([app.close(), aw.close(), st.close()]) }) const stelle = (mode, pilotEmails = []) => Object.assign(config.kundenansichtNeu, { mode, pilotEmails }) const ALT = readFileSync(path.join(publicDir, 'dashboard.html'), 'utf8') const istNeu = (r) => r.status === 200 && String(r.body).includes('') // N5: gepinnt wird nur die Meta-Zeile const istAlt = (r) => r.status === 200 && r.body === ALT const MAIL = 'k@example.invalid' // E-Mail von c1; c2 hat k2@example.invalid const UNSIGNIERT = `${config.cookieName}=${encodeURIComponent(JSON.stringify({ customerId: 'c1', appwriteUserId: 'u1', email: MAIL }))}` const FAELLE = [ // [Name, Modus, Liste, Cookie, Ergebnis] ['Pilot, Kunde auf der Liste', 'pilot', [MAIL], customerCookie('c1'), 'neu'], ['Pilot, Kunde daneben', 'pilot', [MAIL], customerCookie('c2'), 'alt'], ['Pilot, Mitarbeiter-Kundenansicht', 'pilot', [], kundenansichtCookie(), 'neu'], ['off mit Stern, Kunde', 'off', ['*'], customerCookie('c1'), 'alt'], ['off mit Stern, Kundenansicht', 'off', ['*'], kundenansichtCookie(), 'alt'], ['on, Kunde', 'on', [], customerCookie('c1'), 'neu'], ['on, alte Mitarbeiter-Sitzung', 'on', [], adminCookie(), 'alt'], ['on, Kundenansicht ohne viewId', 'on', [], viewAsCookie('c1'), 'alt'], ['Pilot mit Stern, ohne Sitzung (200 wie heute, kein Redirect)', 'pilot', ['*'], undefined, 'alt'], ['Pilot mit Stern, unsigniertes Cookie', 'pilot', ['*'], UNSIGNIERT, 'alt'], ] for (const [name, modus, liste, cookie, soll] of FAELLE) test(name, async () => { stelle(modus, liste) const r = await rawGet('/dashboard.html', { cookie }) assert.ok(soll === 'neu' ? istNeu(r) : istAlt(r), `${r.status} ${String(r.body).slice(0, 80)}`) }) test('neue Seite: HTML und Cache-Control no-cache; Query bleibt erhalten', async () => { stelle('pilot', [MAIL]) const r = await rawGet('/dashboard.html?abo=success', { cookie: customerCookie('c1') }) assert.ok(istNeu(r)); assert.equal(r.headers.get('cache-control'), 'no-cache'); assert.match(r.headers.get('content-type'), /^text\/html/) }) test('Platzhalter trägt beide Marker (N5) und die Hülle des Briefs', () => { const html = readFileSync(path.join(publicDir, 'neu', 'dashboard.html'), 'utf8') for (const teil of ['', '', 'data-kn-platzhalter', 'id="kn-platzhalter-ansicht"', 'Kundenansicht neu', 'Platzhalter bis P1', '/api/auth/me']) assert.ok(html.includes(teil), teil) assert.ok(!/]*\btype=["']module/.test(html), 'klassisches Skript, kein Modul') }) test('Direktaufruf /neu/dashboard.html: Pilot ja, sonst 302 auf /dashboard.html', async () => { stelle('pilot', [MAIL]) assert.ok(istNeu(await rawGet('/neu/dashboard.html', { cookie: customerCookie('c1') }))) for (const cookie of [customerCookie('c2'), undefined]) { const r = await rawGet('/neu/dashboard.html', { cookie }) assert.deepEqual([r.status, r.headers.get('location')], [302, '/dashboard.html']) } }) test('andere Seiten bleiben unberührt', async () => { stelle('pilot', ['*']) for (const p of ['/login.html', '/manifest.webmanifest']) assert.equal((await rawGet(p, { cookie: customerCookie('c1') })).status, 200, p) }) test('server/index.js hängt die Weiche zwischen /kalkulation.html und express.static ein', () => { const index = readFileSync(new URL('../server/index.js', import.meta.url), 'utf8') const stelle_ = (s) => index.indexOf(s) const reihe = ["app.use('/kalkulation.html'", "app.get('/dashboard.html', dashboardWeiche({ publicDir }))", "app.get('/neu/dashboard.html', neuDirektWeiche())", "app.get(['/kuendigen.html', '/widerrufen.html'], oeffentlicheSeiteWeiche({ publicDir }))", 'app.use(express.static(publicDir))'].map(stelle_) assert.ok(reihe[0] > -1 && reihe.every((n, i) => i === 0 || n > reihe[i - 1]), reihe.join(',')) assert.match(index, /import \{[^}]*\bdashboardWeiche\b[^}]*\bneuDirektWeiche\b[^}]*\boeffentlicheSeiteWeiche\b[^}]*\} from '\.\/services\/kundenansicht\/dashboardWeiche\.js'/) }) // Nachtrag N39: /kuendigen.html und /widerrufen.html aus public/neu/oeffentlich/, aber nur für Pilot-Sitzungen und nur wenn die Datei da ist const bytes = (datei) => readFileSync(path.join(publicDir, datei), 'utf8') test('N39: ohne Datei in public/neu/oeffentlich/ liefern alle Modi bytegleich die alten Seiten', async () => { for (const [modus, liste] of [['off', ['*']], ['pilot', ['*']], ['on', []]]) { stelle(modus, liste) for (const seite of ['kuendigen.html', 'widerrufen.html']) { const r = await rawGet(`/${seite}`, { cookie: customerCookie('c1') }) assert.equal(r.status, 200, `${modus} ${seite}`); assert.equal(r.body, bytes(seite), `${modus} ${seite}`) assert.notEqual(r.headers.get('cache-control'), 'no-cache', `${modus} ${seite}`) } } }) test('N39: mit Datei bekommen nur angemeldete Pilot-Sitzungen die neue Seite, alle anderen bytegleich die alte', async () => { const neu = async (cookie) => { const r = await rawGet('/nachbau/kuendigen.html', { cookie }); return r.status === 200 && String(r.body).includes('content="neu-oeffentlich"') ? r : null } stelle('pilot', [MAIL]) const r = await neu(customerCookie('c1')) assert.ok(r, 'Pilot-Kunde'); assert.equal(r.headers.get('cache-control'), 'no-cache'); assert.match(r.headers.get('content-type'), /^text\/html/) assert.ok(await neu(kundenansichtCookie()), 'Mitarbeiter-Kundenansicht') for (const [name, cookie] of [['Kunde daneben', customerCookie('c2')], ['ohne Sitzung', undefined], ['unsigniertes Cookie', UNSIGNIERT], ['alte Mitarbeiter-Sitzung', adminCookie()]]) { const alt = await rawGet('/nachbau/kuendigen.html', { cookie }) assert.equal(alt.status, 200, name); assert.equal(alt.body, bytes('kuendigen.html'), name); assert.notEqual(alt.headers.get('cache-control'), 'no-cache', name) } stelle('off', ['*']); assert.equal(await neu(customerCookie('c1')), null, 'off schlaegt die Liste') stelle('on', []); assert.ok(await neu(customerCookie('c1')), 'on, Kunde'); assert.equal(await neu(undefined), null, 'on, ohne Sitzung bleibt alt (N39)') }) test('N39: nur Seiten mit eigener Datei wechseln; die andere, der Login und Offline bleiben alt', async () => { stelle('pilot', ['*']) const wid = await rawGet('/nachbau/widerrufen.html', { cookie: customerCookie('c1') }) assert.deepEqual([wid.status, wid.body], [200, bytes('widerrufen.html')]) for (const seite of ['login.html', 'offline.html']) { const r = await rawGet(`/${seite}`, { cookie: customerCookie('c1') }) assert.deepEqual([r.status, r.body], [200, bytes(seite)], seite) } })