test(prompt-async-gate): cover dispatch timeout and post-dispatch error hold

Adds regression coverage for BLOCKER-1 (dispatch timeout releases
reservation for next caller after stalled upstream) and BLOCKER-2
(post-dispatch error preserves the post-dispatch hold so an immediate
second caller observes the reservation and is gated).

Both tests subscribe-first on the promptAsync call count and assert
status transitions without sleep-based synchronization. dispatchTimeoutMs
is the system under test, so passing it explicitly as 1ms in those tests
is the SUT, not a sleep-as-synchronization (per test-discipline.md).

Closes BLOCKER-3 (dispatch timeout + post-dispatch coverage)

Co-authored-by: gate-tests (deep / gpt-5.3-codex high)
This commit is contained in:
YeonGyu-Kim
2026-05-16 00:49:28 +09:00
parent b333a52800
commit f93d7297c8
+119
View File
@@ -243,6 +243,125 @@ describe("promptAsyncAfterSessionIdle", () => {
expect(promptCalls).toBe(2)
})
test("#given promptAsync dispatch never settles #when dispatch timeout elapses #then reservation is released for the next caller", async () => {
// given
let promptCalls = 0
const neverSettles = new Promise<void>(() => {})
const client = {
session: {
promptAsync: async () => {
promptCalls += 1
await neverSettles
},
},
}
// when
const first = await promptAsyncAfterSessionIdle({
client,
sessionID: "ses_dispatch_timeout",
input: { path: { id: "ses_dispatch_timeout" }, body: { parts: [] } },
source: "test:timeout:first",
settleMs: 0,
dispatchTimeoutMs: 1,
postDispatchHoldMs: 0,
})
const second = await promptAsyncAfterSessionIdle({
client,
sessionID: "ses_dispatch_timeout",
input: { path: { id: "ses_dispatch_timeout" }, body: { parts: [] } },
source: "test:timeout:second",
settleMs: 0,
dispatchTimeoutMs: 1,
postDispatchHoldMs: 0,
})
// then
expect(first.status).toBe("failed")
expect(second.status).toBe("failed")
expect(promptCalls).toBe(2)
})
test("#given promptAsync rejects after dispatch #when a second caller races immediately #then post-dispatch hold still blocks duplicate", async () => {
// given
let promptCalls = 0
const client = {
session: {
promptAsync: async () => {
promptCalls += 1
throw new Error("post-dispatch failure")
},
},
}
// when
const first = await promptAsyncAfterSessionIdle({
client,
sessionID: "ses_post_dispatch_reject",
input: { path: { id: "ses_post_dispatch_reject" }, body: { parts: [] } },
source: "test:reject:first",
settleMs: 0,
})
const second = await promptAsyncAfterSessionIdle({
client,
sessionID: "ses_post_dispatch_reject",
input: { path: { id: "ses_post_dispatch_reject" }, body: { parts: [] } },
source: "test:reject:second",
settleMs: 0,
})
// then
expect(first.status).toBe("failed")
expect(second).toEqual({ status: "reserved", reservedBy: "test:reject:first" })
expect(promptCalls).toBe(1)
})
test("#given a similarly named sibling route #when reservedByPrefix uses a strict family prefix #then release does not clear sibling reservation", async () => {
// given
let promptCalls = 0
const client = {
session: {
promptAsync: async () => {
promptCalls += 1
},
},
}
// when
const first = await promptAsyncAfterSessionIdle({
client,
sessionID: "ses_prefix_sibling",
input: {
path: { id: "ses_prefix_sibling" },
body: { parts: [{ type: "text", text: "continue" }] },
},
source: "model-fallbackx:message.updated",
settleMs: 0,
})
const released = releasePromptAsyncReservation(
"ses_prefix_sibling",
"model-fallback-abort:session.error",
{ reservedByPrefix: "model-fallback:" },
)
const second = await promptAsyncAfterSessionIdle({
client,
sessionID: "ses_prefix_sibling",
input: {
path: { id: "ses_prefix_sibling" },
body: { parts: [{ type: "text", text: "continue again" }] },
},
source: "model-fallback:session.error",
settleMs: 0,
postDispatchHoldMs: 0,
})
// then
expect(first.status).toBe("dispatched")
expect(released).toBe(false)
expect(second).toEqual({ status: "reserved", reservedBy: "model-fallbackx:message.updated" })
expect(promptCalls).toBe(1)
})
test("#given two internal prompt calls race for one idle session #when they dispatch concurrently #then only one prompt is accepted", async () => {
// given
let promptCalls = 0