Files
Webklar-Kundenbereich/test/support/testApp.js
T
ClaudeandClaude Sonnet 5.5 4465ac7b94 Terminplaner: Einbau-Skript – Nachbesserungen
429 ohne Schleife (Meldung und Erneut-versuchen-Knopf, Formulare bleiben stehen), Schriftfarbe
auf der Betriebsfarbe mit mindestens 4,5 : 1, AGB-Link aus der oeffentlichen Konfiguration
(betrieb.agbUrl), Haken starten bei geaenderter Auswahl ohne Haken, Fehler ohne Feld nicht
verschluckt, Konfiguration fail closed, ladeNeu fuer Neuladen und neu_laden, Tastatur und
Fokus (Modifikatoren, Monatspfeile, Belehrungstext), Aufraeumen. UI-Testserver: Timer,
offene Verbindungen, Hinweis auf sessionCookie.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-03 08:17:44 +00:00

122 lines
4.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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, server, close } (server: der http.Server, z. B. fuer closeAllConnections). */
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, server, close: () => new Promise((resolve) => server.close(resolve)) }
}
/** Anfrage an die zuletzt gestartete App (beliebige Methode) -> { status, body, headers }; body ist geparstes JSON, sonst Text. */
export async function send(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 = {}) => send('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 = {}) => send('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 send('POST', path, { body: payload, headers: { 'stripe-signature': stripeSignature } })
}