Workers P4: Pult – als Mensch antworten, Vorschau freigeben, Antwort des Kunden einfügen

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-18 22:08:48 +00:00
1 parent be2e6786b6
commit 533091c333
7 files changed
+1001 -48

No files matched your search

+39 -6
View File
@@ -3,7 +3,7 @@ import assert from 'node:assert/strict'
import { loadModule } from './load.mjs'
// API-Client der Hustler-Seite: URLs, Methoden, Header, deutsche Fehlertexte, JWT-Wiederholung, Pult-Prüfungen
const { createWorkersApi, REQUEST_TIMEOUT_MS, AUTH_COOLDOWN_MS } = await loadModule('src/lib/workersApi.js')
const { createWorkersApi, REQUEST_TIMEOUT_MS, AUTH_COOLDOWN_MS, ACTION_TIMEOUT_MS, SLOW_PULT_ACTIONS } = await loadModule('src/lib/workersApi.js')
const { createJwtCache } = await loadModule('src/lib/serverpulsAuth.js')
const jwts = []
@@ -118,19 +118,21 @@ test('pult: ein Status, Komma-Liste oder Array (Übersicht §7); unbekannte Stat
})
test('pultAction: nur bekannte Aktionen, Pfad-Tricks abgewiesen', async () => {
const f = fakeFetch(okReplies(7))
const f = fakeFetch(okReplies(8))
const api = createWorkersApi({ getJwt, fetchImpl: f })
for (const bad of ['../x', 'delete', '']) {
for (const bad of ['../x', 'delete', '', 'start-build/../x', 'Start-Build']) {
await assert.rejects(api.pultAction('p1', bad), /Unbekannte Aktion/)
}
assert.equal(f.calls.length, 0)
for (const a of ['claim', 'sent', 'discard', 'done', 'reply', 'approve-preview', 'paste']) {
for (const a of ['claim', 'sent', 'discard', 'done', 'reply', 'approve-preview', 'paste', 'start-build']) {
await api.pultAction('p1', a, a === 'reply' || a === 'paste' ? { text: 'x' } : undefined)
}
assert.deepEqual(f.calls.map((c) => c.url.split('/').pop()), ['claim', 'sent', 'discard', 'done', 'reply', 'approve-preview', 'paste'])
assert.deepEqual(f.calls.map((c) => c.url.split('/').pop()), ['claim', 'sent', 'discard', 'done', 'reply', 'approve-preview', 'paste', 'start-build'])
assert.equal(f.calls[4].init.body, JSON.stringify({ text: 'x' }))
// Plattform-Folge-Antwort einfügen (P4-T34)
assert.deepEqual([f.calls[6].url, f.calls[6].init.method, f.calls[6].init.body], ['/api/workers/pult/p1/paste', 'POST', JSON.stringify({ text: 'x' })])
// Bau starten (P4-T32): POST ohne Body
assert.deepEqual([f.calls[7].url, f.calls[7].init.method, f.calls[7].init.body], ['/api/workers/pult/p1/start-build', 'POST', undefined])
})
test('limit wird geklemmt (Ereignisse 1..200, Spur 1..50)', async () => {
@@ -192,9 +194,40 @@ test('Zeitgrenze: rechtzeitige Antworten bleiben unberührt, ein späterer Abruf
assert.deepEqual(await quick.state(), {})
})
test('Zeitgrenze: Standardwerte 20 s und 2 min', () => {
test('Zeitgrenze: Standardwerte 20 s und 2 min, für langsame Pult-Aktionen 60 s', () => {
assert.equal(REQUEST_TIMEOUT_MS, 20000)
assert.equal(AUTH_COOLDOWN_MS, 120000)
assert.equal(ACTION_TIMEOUT_MS, 60000)
assert.deepEqual(SLOW_PULT_ACTIONS, ['reply', 'approve-preview', 'start-build'])
assert.ok(Object.isFrozen(SLOW_PULT_ACTIONS))
})
// P4-T32 (D14): Antworten, Freigeben und Bauen laufen im Dienst länger (SMTP, Warteschlange); bricht der Client nach 20 s ab, sähe der Mensch einen Fehler,
// obwohl die Mail hinausgeht. Nur diese drei Aktionen bekommen die lange Frist, alles andere bleibt bei der kurzen.
test('Zeitgrenze (D14): reply, approve-preview und start-build warten länger als die übrigen Pult-Aktionen', async () => {
const slowReply = (ms) => async () => {
await new Promise((resolve) => setTimeout(resolve, ms))
return { ok: true, status: 200, json: async () => ({ item: { $id: 'p1' } }) }
}
// kurze Frist 30 ms, lange Frist 400 ms, Antwort nach 120 ms
for (const action of ['reply', 'approve-preview', 'start-build']) {
const api = createWorkersApi({ getJwt, fetchImpl: slowReply(120), timeoutMs: 30, actionTimeoutMs: 400 })
assert.deepEqual(await api.pultAction('p1', action, action === 'reply' ? { text: 'x' } : undefined), { item: { $id: 'p1' } }, action)
}
for (const action of ['claim', 'sent', 'discard', 'done', 'paste']) {
const api = createWorkersApi({ getJwt, fetchImpl: slowReply(120), timeoutMs: 30, actionTimeoutMs: 400 })
await assert.rejects(api.pultAction('p1', action, action === 'paste' ? { text: 'x' } : undefined), { message: DOWN, status: 0 }, action)
}
// Lesen (Liste, Zustand) behält die kurze Frist
await assert.rejects(createWorkersApi({ getJwt, fetchImpl: slowReply(120), timeoutMs: 30, actionTimeoutMs: 400 }).pult(), { message: DOWN, status: 0 })
// auch die lange Frist endet: ein hängender Abruf meldet nach ihr die Dienst-Meldung (status 0) und bricht ab
const signals = []
const hang = (url, init) => { signals.push(init.signal); return new Promise(() => {}) }
const api = createWorkersApi({ getJwt, fetchImpl: hang, timeoutMs: 20, actionTimeoutMs: 150 })
const t0 = Date.now()
await assert.rejects(api.pultAction('p1', 'reply', { text: 'x' }), { message: DOWN, status: 0 })
assert.ok(Date.now() - t0 >= 120, 'wartet die lange Frist ab')
assert.equal(signals[0].aborted, true)
})
test('401-Folge: danach kein frisches JWT mehr, bis die Sperre abgelaufen ist (höchstens ein createJWT je Sperre)', async () => {