Zusatzspeicher: Vorschau im flexiblen Abrechnungsmodus, Aufgaben nach Zeitpunkt, keine stillen Abbrüche

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Fable 5.1 committed 2026-09-29 09:16:07 +00:00
1 parent 188db4b3c1
commit bc20ce0673
9 files changed
+922 -228

No files matched your search

+25 -1
View File
@@ -38,8 +38,24 @@ export async function startFakeStripe() {
let seq = 0
let nextSubscriptionUpdate = null
let invoicePreview = null
// Eingeplanter Fehler (failNext, F41d): der naechste passende Aufruf scheitert einmalig
let pendingFailure = null
const nextId = (prefix) => `${prefix}_test_${(seq += 1)}`
const matchesPath = (path, pathname) => (path instanceof RegExp ? path.test(pathname) : path === pathname)
const plannedFailure = (method, pathname) => {
if (!pendingFailure) return null
if (pendingFailure.method && pendingFailure.method !== method) return null
if (pendingFailure.path && !matchesPath(pendingFailure.path, pathname)) return null
if (pendingFailure.after > 0) {
pendingFailure.after -= 1
return null
}
const { status, message } = pendingFailure
pendingFailure = null
return [status, { error: { type: status >= 500 ? 'api_error' : 'invalid_request_error', message: message || `Nachbau: eingeplanter Fehler ${status}` } }]
}
const flat = (params) => Object.fromEntries(params.entries())
const noSuch = (what, id) => [404, { error: { type: 'invalid_request_error', code: 'resource_missing', message: `No such ${what}: '${id}'` } }]
const invalid = (param, message) => [400, { error: { type: 'invalid_request_error', param, message } }]
@@ -337,7 +353,7 @@ export async function startFakeStripe() {
const url = new URL(req.url, 'http://127.0.0.1')
const body = await readBody(req)
calls.push({ method: req.method, path: url.pathname, query: flat(url.searchParams), body: flat(body) })
const [status, payload] = route(req.method, url.pathname, url.searchParams, body)
const [status, payload] = plannedFailure(req.method, url.pathname) || route(req.method, url.pathname, url.searchParams, body)
res.writeHead(status, { 'Content-Type': 'application/json', 'Request-Id': `req_test_${calls.length}` })
res.end(JSON.stringify(payload))
})
@@ -380,6 +396,14 @@ export async function startFakeStripe() {
set invoicePreview(value) {
invoicePreview = value
},
/**
* Naechster passender Aufruf (method, path als String oder RegExp) scheitert einmalig mit status; after
* passende Aufrufe gehen vorher durch. Der Aufruf steht trotzdem in calls. 4xx wiederholt stripe-node
* nicht, 5xx schon (maxNetworkRetries) - fuer "Stripe lehnt ab" also 4xx einplanen.
*/
failNext: ({ method = null, path = null, status = 500, message = '', after = 0 }) => {
pendingFailure = { method, path, status, message, after }
},
close: () => new Promise((resolve) => server.close(resolve)),
}
}