Endprüfung Nachrunde R7: Test der Zeitgrenze (D14) für reply, approve-preview und start-build wiederhergestellt
Der Commit 1536a3b hatte den Test „reply, approve-preview und start-build warten länger als die übrigen
Pult-Aktionen“ gelöscht. Der neue Test prüft gegen den heutigen Code (ACTION_TIMEOUT_MS 65 s,
REQUEST_TIMEOUT_MS 20 s, Optionen timeoutMs / actionTimeoutMs) mit den Uhren von node:test statt echter
Wartezeit: keine Abhängigkeit von der Last, Grenzen auf die Millisekunde (eine Millisekunde vor der Frist
läuft der Abruf, mit ihr endet er). Die drei langsamen Aktionen nehmen eine Antwort zwischen kurzer und langer
Frist an, claim/sent/discard/done/paste und das Lesen enden mit der kurzen Frist (DOWN_TEXT, status 0, Signal
abgebrochen), ein hängender Abruf endet nach der langen Frist. Derselbe Ablauf mit Standard- und mit
eingespeisten Fristen; ein zweiter Test pinnt, dass auch der Wiederholungsversuch nach einer 401 die Frist
der Aktion behält.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c48d1834f4
commit
41ae7ffd3f
1 file changed
+106
@@ -207,6 +207,112 @@ test('Zeitgrenze: Standardwerte 20 s und 2 min, für langsame Pult-Aktionen 65 s
|
||||
assert.ok(Object.isFrozen(UNCERTAIN_STATUS))
|
||||
})
|
||||
|
||||
// P4-T32 (D14), Nachrunde R7: 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 (65 s), alles andere bleibt bei der kurzen (20 s).
|
||||
// Mit den Uhren von node:test statt echter Wartezeit: kein Warten, keine Abhängigkeit von der Last des Rechners, und die Grenzen sind auf die Millisekunde
|
||||
// genau prüfbar (eine Millisekunde vor der Frist läuft der Abruf noch, mit ihr endet er). setImmediate bleibt echt und lässt die Promise-Ketten durchlaufen.
|
||||
const flush = () => new Promise((resolve) => setImmediate(resolve))
|
||||
const bodyFor = (action) => (action === 'reply' || action === 'paste' ? { text: 'x' } : undefined)
|
||||
|
||||
// Dienst, der erst nach ms antwortet (Uhr von node:test); merkt sich die Signale der Abrufe
|
||||
function slowService(ms, signals = []) {
|
||||
return (url, init) => {
|
||||
signals.push(init.signal)
|
||||
return new Promise((resolve) => setTimeout(() => resolve({ ok: true, status: 200, json: async () => ({ item: { $id: 'p1' } }) }), ms))
|
||||
}
|
||||
}
|
||||
|
||||
// Ergebnis eines Abrufs, der noch läuft, ohne auf ihn zu warten: 'offen', 'ok' oder der Fehler
|
||||
function watch(promise) {
|
||||
const w = { state: 'offen', error: null }
|
||||
promise.then(() => { w.state = 'ok' }, (e) => { w.state = 'fehler'; w.error = e })
|
||||
return w
|
||||
}
|
||||
|
||||
// Standardfristen (20 s / 65 s) und eingespeiste Fristen (timeoutMs / actionTimeoutMs) müssen sich gleich verhalten
|
||||
for (const [label, options] of [
|
||||
['Standardfristen', {}],
|
||||
['eingespeiste Fristen', { timeoutMs: 300, actionTimeoutMs: 4000 }],
|
||||
]) {
|
||||
test(`Zeitgrenze (D14): reply, approve-preview und start-build warten länger als die übrigen Pult-Aktionen (${label})`, async (t) => {
|
||||
t.mock.timers.enable({ apis: ['setTimeout'] })
|
||||
const short = options.timeoutMs ?? REQUEST_TIMEOUT_MS
|
||||
const long = options.actionTimeoutMs ?? ACTION_TIMEOUT_MS
|
||||
assert.ok(short < long)
|
||||
const between = Math.floor((short + long) / 2) // die Antwort kommt nach der kurzen, aber vor der langen Frist
|
||||
const run = async (action, { service = slowService(between) }) => {
|
||||
const api = createWorkersApi({ getJwt, fetchImpl: service, ...options })
|
||||
const w = watch(action === 'read' ? api.pult() : api.pultAction('p1', action, bodyFor(action)))
|
||||
await flush() // JWT geholt, Anfrage gesendet, Frist und Antwort stehen auf der Uhr
|
||||
return { w, api, tick: async (ms) => { t.mock.timers.tick(ms); await flush() } }
|
||||
}
|
||||
|
||||
// die drei langsamen Aktionen: die Antwort nach der kurzen Frist kommt noch an
|
||||
for (const action of ['reply', 'approve-preview', 'start-build']) {
|
||||
const { w, tick } = await run(action, {})
|
||||
await tick(short) // die kurze Frist ist erreicht: der Abruf läuft weiter
|
||||
assert.equal(w.state, 'offen', `${action}: nach der kurzen Frist noch offen`)
|
||||
await tick(between - short) // die Antwort trifft ein
|
||||
assert.equal(w.state, 'ok', `${action}: Antwort nach der kurzen Frist, vor der langen`)
|
||||
}
|
||||
|
||||
// alle übrigen Aktionen und das Lesen behalten die kurze Frist: eine Millisekunde davor offen, mit ihr „nicht erreichbar“ (status 0), Signal abgebrochen
|
||||
for (const action of ['claim', 'sent', 'discard', 'done', 'paste', 'read']) {
|
||||
const signals = []
|
||||
const { w, tick } = await run(action, { service: slowService(between, signals) })
|
||||
await tick(short - 1)
|
||||
assert.equal(w.state, 'offen', `${action}: eine Millisekunde vor der kurzen Frist`)
|
||||
assert.equal(signals[0].aborted, false, action)
|
||||
await tick(1)
|
||||
assert.equal(w.state, 'fehler', `${action}: mit der kurzen Frist`)
|
||||
assert.equal(w.error.message, DOWN, action)
|
||||
assert.equal(w.error.status, 0, action)
|
||||
assert.equal(signals[0].aborted, true, `${action}: das Signal ist abgebrochen`)
|
||||
}
|
||||
|
||||
// auch die lange Frist endet: ein hängender Abruf meldet nach ihr die Dienst-Meldung (status 0) und bricht ab, keine Millisekunde früher
|
||||
for (const action of ['reply', 'approve-preview', 'start-build']) {
|
||||
const signals = []
|
||||
const hang = (url, init) => { signals.push(init.signal); return new Promise(() => {}) }
|
||||
const { w, tick } = await run(action, { service: hang })
|
||||
await tick(long - 1)
|
||||
assert.equal(w.state, 'offen', `${action}: eine Millisekunde vor der langen Frist`)
|
||||
assert.equal(signals[0].aborted, false, action)
|
||||
await tick(1)
|
||||
assert.equal(w.state, 'fehler', `${action}: mit der langen Frist`)
|
||||
assert.deepEqual([w.error.message, w.error.status], [DOWN, 0], action)
|
||||
assert.equal(signals[0].aborted, true, `${action}: das Signal ist abgebrochen`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
test('Zeitgrenze (D14): auch der Wiederholungsversuch nach einer 401 behält die lange Frist bei den langsamen Aktionen, die kurze bei den übrigen', async (t) => {
|
||||
t.mock.timers.enable({ apis: ['setTimeout'] })
|
||||
const MIDDLE = Math.floor((REQUEST_TIMEOUT_MS + ACTION_TIMEOUT_MS) / 2)
|
||||
const slowAfter401 = () => {
|
||||
let n = 0
|
||||
return (url, init) => {
|
||||
n += 1
|
||||
if (n === 1) return Promise.resolve({ ok: false, status: 401, json: async () => ({}) })
|
||||
return slowService(MIDDLE)(url, init) // zweiter Versuch: Antwort nach der kurzen (20 s), vor der langen Frist (65 s)
|
||||
}
|
||||
}
|
||||
const tick = async (ms) => { t.mock.timers.tick(ms); await flush() }
|
||||
for (const action of ['reply', 'approve-preview', 'start-build']) {
|
||||
const w = watch(createWorkersApi({ getJwt, fetchImpl: slowAfter401() }).pultAction('p1', action, bodyFor(action)))
|
||||
await flush()
|
||||
await tick(MIDDLE)
|
||||
assert.equal(w.state, 'ok', action)
|
||||
}
|
||||
for (const action of ['claim', 'paste']) {
|
||||
const w = watch(createWorkersApi({ getJwt, fetchImpl: slowAfter401() }).pultAction('p1', action, bodyFor(action)))
|
||||
await flush()
|
||||
await tick(REQUEST_TIMEOUT_MS)
|
||||
assert.equal(w.state, 'fehler', action)
|
||||
assert.deepEqual([w.error.message, w.error.status], [DOWN, 0], action)
|
||||
}
|
||||
})
|
||||
|
||||
// Endprüfung P4 (W1, D14): eine Fehlerseite des Proxys (502/504) bei reply, approve-preview und start-build heißt nicht „nicht erreichbar“:
|
||||
// der Dienst arbeitet weiter, wenn nginx aufgibt, die Mail kann schon hinausgegangen sein. Der Client selbst (read) bleibt bei DOWN_TEXT
|
||||
// und dem echten Status; die Aktions-Meldung (actionErrorText) entscheidet nach Aktion und Status.
|
||||
|
||||
Reference in new issue
Block a user