Skript-API: botleads({ room, q, page, id }) antwortet wie GET /botleads des Dienstes
(items, total, pageSize 50; Telefon nie bei Heilberufen, gekürzte Stubs nur mit id).
60 sichtbare Einträge in vier Räumen (zwei Seiten), 3 gekürzte Stubs, Heilberufe mit
Altbestand-Telefon, eine http-Quelle, zwei feindliche Quellen, lange Namen und Gründe.
Schalter: faults.botleads, botleadsDelayMs, botleadsEmpty (?botleads=leer), botleadCalls.
Fetch-Weiche und entry.jsx (?bot=<Id>) um die Route und den Deep-Link ergänzt.
Szenarien: botleads (Reiter, Liste, Raumfilter, Blättern mit Fokus, Suche, Leerzustände,
Fehlertexte, Deep-Link, Quellen-Links, Telefon) und handy-botleads (390 x 844: kein
seitliches Scrollen, Knopfhöhe, Kontrast der gekürzten Zeile, Wortumbruch).
Der Pin in workersHarness.test.mjs prüft nun die neue Antwortform statt der leeren Liste.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
60 lines
4.1 KiB
JavaScript
60 lines
4.1 KiB
JavaScript
// 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/<Aktion>) 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/<Aktion>. 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.
|
||
// Seit P6 auch Bot-Leads (GET /botleads?room=&q=&page=&id=): die Route liest die Angaben wie der Dienst (fehlend = leer, page als Zahl, unlesbar = NaN).
|
||
// 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))
|
||
if (method === 'GET' && path === '/botleads') {
|
||
const query = parsed.searchParams
|
||
return reply(200, await api.botleads({ room: query.get('room') ?? '', q: query.get('q') ?? '', page: Number(query.get('page') ?? ''), id: query.get('id') ?? '' }))
|
||
}
|
||
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.' })
|
||
}
|
||
}
|
||
}
|