Files
oh-my-opencode/src/hooks/session-recovery/hook.test.ts
T
YeonGyu-Kim 8e9dea949b fix(session-recovery): persist dedupe across stale repeated session.error
processingErrors was emptied in a finally block, so a second
session.error fired for the same assistant message id after the
first recovery resolved would re-run abort, history fetch, the
recovery toast, and any auto-resume promptAsync (resumeSession),
producing duplicate internal prompt injections during stale event
re-emission or polling-driven retries.

Drop the in-flight delete and keep the dedupe permanent for the
plugin lifetime. A genuinely new failure starts a new assistant
message with a different id, so this never blocks future legitimate
errors. Same-id duplicates collapse into a single recovery attempt.

Add hook.test.ts asserting that two sequential handleSessionRecovery
calls for the same recoverable info trigger session.abort, the
recovery toast, and any internal promptAsync at most once.
2026-05-15 18:43:53 +09:00

87 lines
2.5 KiB
TypeScript

import { describe, expect, test } from "bun:test"
import { createSessionRecoveryHook } from "./hook"
type RecoverableInfo = Parameters<ReturnType<typeof createSessionRecoveryHook>["handleSessionRecovery"]>[0]
function createPrefillErrorInfo(): RecoverableInfo {
return {
id: "msg_failed_prefill",
role: "assistant",
sessionID: "ses_recovery_dedupe",
error: { message: "This model does not support assistant message prefill." },
}
}
function createCountingCtx() {
const counts = { abort: 0, messages: 0, promptAsync: 0, toast: 0 }
const info = createPrefillErrorInfo()
const ctx = {
client: {
session: {
abort: async () => {
counts.abort++
return {}
},
messages: async () => {
counts.messages++
return {
data: [
{
info: {
id: info.id,
role: "assistant",
error: info.error,
},
},
],
}
},
promptAsync: async () => {
counts.promptAsync++
return {}
},
},
tui: {
showToast: async () => {
counts.toast++
return {}
},
},
},
directory: "/tmp/session-recovery-dedupe-test",
}
return { ctx, counts, info }
}
describe("session-recovery hook persistent dedupe", () => {
test("#given the same recoverable session.error fires twice for the same assistant message id #when handleSessionRecovery is called twice in sequence #then recovery side effects run only once", async () => {
// given
const { ctx, counts, info } = createCountingCtx()
const hook = createSessionRecoveryHook(ctx as never)
// when
await hook.handleSessionRecovery(info)
await hook.handleSessionRecovery(info)
// then
expect(counts.abort).toBe(1)
expect(counts.toast).toBe(1)
expect(counts.promptAsync).toBe(0)
})
test("#given a recovered assistant message id is later reused by a stale duplicate session.error #when handleSessionRecovery is called for that stale duplicate #then recovery is suppressed", async () => {
// given
const { ctx, counts, info } = createCountingCtx()
const hook = createSessionRecoveryHook(ctx as never)
// when
await hook.handleSessionRecovery(info)
await Promise.resolve()
const result = await hook.handleSessionRecovery(info)
// then
expect(result).toBe(false)
expect(counts.abort).toBe(1)
})
})