Abo-API: Bestellung je Produkt, Katalog, Speicherstand, Wechsel-Möglichkeit

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Fable 5.1 committed 2026-09-28 09:43:09 +00:00
1 parent 05363d06ad
commit c4607e9c43
6 files changed
+784 -40

No files matched your search

+117
View File
@@ -0,0 +1,117 @@
import http from 'node:http'
import express from 'express'
import cookieParser from 'cookie-parser'
import signature from 'cookie-signature'
import Stripe from 'stripe'
/**
* Express-App für Routen-Tests: wie server/index.js mit cookieParser(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/portal-admin': portalAdminRoutes })
*
* Reihenfolge im Test: Nachbauten starten, useTestEnv(), dann dieses Modul und die Server-Module
* dynamisch importieren (assertTestEnv() weigert sich sonst, weil config.js die Umgebung beim
* ersten Import liest). Die Anfrage-Helfer sprechen die zuletzt gestartete App an.
*/
function assertTestEnv() {
for (const name of ['APPWRITE_ENDPOINT', 'STRIPE_API_BASE']) {
let host = null
try {
host = new URL(process.env[name] || '').hostname
} catch {
host = null
}
if (host !== '127.0.0.1') {
throw new Error(`testApp: ${name} zeigt nicht auf 127.0.0.1 – useTestEnv() vor dem Import aufrufen`)
}
}
}
assertTestEnv()
const cookieName = () => process.env.SESSION_COOKIE_NAME || 'webklar_portal_session'
const sessionSecret = () => process.env.SESSION_SECRET || ''
/**
* 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), sessionSecret())}`
return `${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(cookieParser(sessionSecret()))
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 = process.env.STRIPE_WEBHOOK_SECRET || 'whsec_test' } = {}) {
const payload = JSON.stringify(event)
const stripeSignature = webhookSigner.webhooks.generateTestHeaderString({ payload, secret })
return request('POST', path, { body: payload, headers: { 'stripe-signature': stripeSignature } })
}