import http from 'node:http' import express from 'express' import signature from 'cookie-signature' import Stripe from 'stripe' import { config } from '../../server/config.js' import { sessionMiddleware } from '../../server/middleware/session.js' /** * Express-App für Routen-Tests: wie server/index.js mit sessionMiddleware() (cookieParser mit * SESSION_SECRET) und express.json samt rawBody (Stripe-Webhook), aber nur mit den Routern, die der * Test übergibt: * const app = await startTestApp({ '/api/abo': aboRoutes, '/api/team': teamRoutes }) * * Reihenfolge im Test: Nachbauten starten, useTestEnv(), dann dieses Modul und die Server-Module * dynamisch importieren. config.js liest die Umgebung beim ersten Import; zeigt sie nicht auf die * Nachbauten, weigert sich dieses Modul beim Laden. Die Anfrage-Helfer sprechen die zuletzt * gestartete App an. */ function assertTestEnv() { for (const [name, value] of [ ['APPWRITE_ENDPOINT', config.appwrite.endpoint], ['STRIPE_API_BASE', config.stripe.apiBase], ]) { let host = null try { host = new URL(value || '').hostname } catch { host = null } if (host !== '127.0.0.1') { throw new Error(`testApp: ${name} zeigt nicht auf 127.0.0.1 – useTestEnv() muss vor dem ersten Import von config.js laufen`) } } } assertTestEnv() /** * Signierter Session-Cookie wie setPortalSession (cookie-parser: Praefix "s:" + cookie-signature). * Kunde: { customerId, appwriteUserId }. Admin: { role: 'admin', appwriteUserId, name, email }. */ export function sessionCookie(session) { const value = `s:${signature.sign(JSON.stringify(session), config.sessionSecret)}` return `${config.cookieName}=${encodeURIComponent(value)}` } let baseUrl = null /** App mit den Routern (Pfad -> Router) auf 127.0.0.1 starten; liefert { url, close }. */ export async function startTestApp(routers = {}) { const app = express() app.set('trust proxy', 1) app.use(sessionMiddleware()) app.use( express.json({ limit: '2mb', verify: (req, _res, buf) => { req.rawBody = buf }, }) ) for (const [mountPath, router] of Object.entries(routers)) app.use(mountPath, router) app.use((err, _req, res, _next) => { if (!res.headersSent) res.status(err.status || 500).json({ error: err.message || 'Interner Serverfehler' }) }) const server = http.createServer(app) await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) baseUrl = `http://127.0.0.1:${server.address().port}` return { url: baseUrl, close: () => new Promise((resolve) => server.close(resolve)) } } async function request(method, path, { body, cookie, headers = {} } = {}) { if (!baseUrl) throw new Error('testApp: startTestApp() zuerst aufrufen') const res = await fetch(baseUrl + path, { method, headers: { ...(body !== undefined ? { 'content-type': 'application/json' } : {}), ...(cookie ? { cookie } : {}), ...headers, }, body: body === undefined ? undefined : typeof body === 'string' ? body : JSON.stringify(body), redirect: 'manual', }) const text = await res.text() let parsed = null try { parsed = text ? JSON.parse(text) : null } catch { parsed = text } return { status: res.status, body: parsed, headers: res.headers } } /** GET -> { status, body } */ export const rawGet = (path, opts = {}) => request('GET', path, opts) /** GET -> JSON-Body; ab Status 400 fliegt ein Fehler mit err.status und err.body. */ export async function get(path, opts = {}) { const { status, body } = await rawGet(path, opts) if (status >= 400) { const err = new Error(`GET ${path} → ${status}: ${body?.error || JSON.stringify(body)}`) err.status = status err.body = body throw err } return body } /** POST mit JSON-Body -> { status, body } */ export const post = (path, body, opts = {}) => request('POST', path, { ...opts, body }) // Signiert wie Stripe (whsec_test aus useTestEnv); der Schluessel wird nie fuer Netzaufrufe benutzt const webhookSigner = new Stripe('sk_test_fake') /** Stripe-Ereignis signiert an den Webhook schicken (F25) -> { status, body }. */ export function postWebhook(event, { path = '/api/abo/webhook', secret = config.stripe.webhookSecret || 'whsec_test' } = {}) { const payload = JSON.stringify(event) const stripeSignature = webhookSigner.webhooks.generateTestHeaderString({ payload, secret }) return request('POST', path, { body: payload, headers: { 'stripe-signature': stripeSignature } }) }