// Fetch-Weiche des Prüfstands: nimmt die Anfragen des echten API-Clients (createWorkersApi, URLs unter /api/workers) entgegen und // beantwortet sie mit der Skript-API. Damit läuft im Prüfstand dieselbe Kette wie im Betrieb (API-Client, Feed, Board), und die // Ausfälle lassen sich schalten: control.fault = null (gesund) | 'down' (503 ohne JSON wie eine Fehlerseite des Proxys) | // 'session' (401) | 'hang' (die Antwort kommt nie, wie auf einer toten Verbindung). Kein Netz, kein Appwrite. // Seit P3 auch Pult (GET /pult, POST /pult/:id/) und Interessent (GET /prospect/:id): Fehler der Skript-API (status + deutscher // Satz) kommen als { error } mit diesem Status an, wie vom Dienst (auch 413 „Anfrage zu groß.“). Seit P4 laufen reply, approve-preview, // start-build und paste über dieselbe Route POST /pult/:id/. Ein Fehler mit status 0 aus der Skript-API bricht die Verbindung ab // (fetch wirft), damit die Meldung „Kann gesendet sein – Pult neu laden.“ (Zeitüberschreitung, D14) sichtbar wird. // control.calls sammelt jede Anfrage ({ method, path, jwt }), damit der Prüfstand zählen kann. const reply = (status, body) => ({ ok: status >= 200 && status < 300, status, json: async () => { if (body === undefined) throw new SyntaxError('kein JSON') return body }, }) export function createScriptedFetch(api, control) { return async function scriptedFetch(url, init = {}) { const method = init.method || 'GET' const parsed = new URL(url, 'http://harness.invalid') const path = parsed.pathname.replace(/^\/api\/workers/, '') control.calls.push({ method, path, jwt: init.headers ? init.headers['X-Appwrite-JWT'] : undefined }) if (control.fault === 'hang') return new Promise(() => {}) if (control.fault === 'down') return reply(503, undefined) if (control.fault === 'session') return reply(401, {}) try { const body = init.body === undefined ? undefined : JSON.parse(init.body) if (method === 'GET' && path === '/state') return reply(200, await api.state()) if (method === 'GET' && path === '/events') { const limit = parsed.searchParams.get('limit') return reply(200, await api.events(parsed.searchParams.get('after'), limit === null ? undefined : Number(limit))) } const trace = /^\/worker\/([^/]+)\/trace$/.exec(path) if (method === 'GET' && trace) return reply(200, await api.trace(decodeURIComponent(trace[1]))) if (method === 'POST' && path === '/pause') return reply(200, await api.pause(body.paused)) if (method === 'GET' && path === '/settings') return reply(200, await api.settings()) if (method === 'PUT' && path === '/settings') return reply(200, await api.saveSettings(body)) // Freigabe-Pult und Interessent (P3); ohne ?status= gilt wie im Dienst offen,in_arbeit if (method === 'GET' && path === '/pult') return reply(200, await api.pult(parsed.searchParams.get('status') ?? undefined)) const action = /^\/pult\/([^/]+)\/([^/]+)$/.exec(path) if (method === 'POST' && action) return reply(200, await api.pultAction(decodeURIComponent(action[1]), action[2], body)) const prospect = /^\/prospect\/([^/]+)$/.exec(path) if (method === 'GET' && prospect) return reply(200, await api.prospect(decodeURIComponent(prospect[1]))) return reply(404, { error: 'Unbekannter Pfad im Prüfstand.' }) } catch (err) { // Status 0 aus der Skript-API = die Verbindung bricht ab, bevor eine Antwort da ist (fetch wirft wie bei einem Netzfehler) if (err && err.status === 0) throw new TypeError('Failed to fetch') return reply(err && err.status ? err.status : 500, { error: err && err.message ? err.message : 'Fehler im Prüfstand.' }) } } }