test: add promptAsync mocks to all test files for promptAsync migration

This commit is contained in:
Peïo Thibault 2026-02-07 14:32:47 +01:00
parent fa77be0daf
commit 414cecd7df
4 changed files with 1168 additions and 1067 deletions

View File

@ -171,6 +171,7 @@ function createBackgroundManager(): BackgroundManager {
const client = { const client = {
session: { session: {
prompt: async () => ({}), prompt: async () => ({}),
promptAsync: async () => ({}),
abort: async () => ({}), abort: async () => ({}),
}, },
} }
@ -880,12 +881,14 @@ describe("BackgroundManager.notifyParentSession - aborted parent", () => {
test("should skip notification when parent session is aborted", async () => { test("should skip notification when parent session is aborted", async () => {
//#given //#given
let promptCalled = false let promptCalled = false
const promptMock = async () => {
promptCalled = true
return {}
}
const client = { const client = {
session: { session: {
prompt: async () => { prompt: promptMock,
promptCalled = true promptAsync: promptMock,
return {}
},
abort: async () => ({}), abort: async () => ({}),
messages: async () => { messages: async () => {
const error = new Error("User aborted") const error = new Error("User aborted")
@ -922,14 +925,16 @@ describe("BackgroundManager.notifyParentSession - aborted parent", () => {
test("should swallow aborted error from prompt", async () => { test("should swallow aborted error from prompt", async () => {
//#given //#given
let promptCalled = false let promptCalled = false
const promptMock = async () => {
promptCalled = true
const error = new Error("User aborted")
error.name = "MessageAbortedError"
throw error
}
const client = { const client = {
session: { session: {
prompt: async () => { prompt: promptMock,
promptCalled = true promptAsync: promptMock,
const error = new Error("User aborted")
error.name = "MessageAbortedError"
throw error
},
abort: async () => ({}), abort: async () => ({}),
messages: async () => ({ data: [] }), messages: async () => ({ data: [] }),
}, },
@ -1054,19 +1059,20 @@ describe("BackgroundManager.tryCompleteTask", () => {
expect(concurrencyManager.getCount(concurrencyKey)).toBe(0) expect(concurrencyManager.getCount(concurrencyKey)).toBe(0)
}) })
test("should abort session on completion", async () => { test("should abort session on completion", async () => {
// #given // #given
const abortedSessionIDs: string[] = [] const abortedSessionIDs: string[] = []
const client = { const client = {
session: { session: {
prompt: async () => ({}), prompt: async () => ({}),
abort: async (args: { path: { id: string } }) => { promptAsync: async () => ({}),
abortedSessionIDs.push(args.path.id) abort: async (args: { path: { id: string } }) => {
return {} abortedSessionIDs.push(args.path.id)
}, return {}
messages: async () => ({ data: [] }), },
}, messages: async () => ({ data: [] }),
} },
}
manager.shutdown() manager.shutdown()
manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput) manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput)
stubNotifyParentSession(manager) stubNotifyParentSession(manager)
@ -1196,24 +1202,26 @@ describe("BackgroundManager.resume concurrency key", () => {
}) })
describe("BackgroundManager.resume model persistence", () => { describe("BackgroundManager.resume model persistence", () => {
let manager: BackgroundManager let manager: BackgroundManager
let promptCalls: Array<{ path: { id: string }; body: Record<string, unknown> }> let promptCalls: Array<{ path: { id: string }; body: Record<string, unknown> }>
beforeEach(() => { beforeEach(() => {
// given // given
promptCalls = [] promptCalls = []
const client = { const promptMock = async (args: { path: { id: string }; body: Record<string, unknown> }) => {
session: { promptCalls.push(args)
prompt: async (args: { path: { id: string }; body: Record<string, unknown> }) => { return {}
promptCalls.push(args) }
return {} const client = {
}, session: {
abort: async () => ({}), prompt: promptMock,
}, promptAsync: promptMock,
} abort: async () => ({}),
manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput) },
stubNotifyParentSession(manager) }
}) manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput)
stubNotifyParentSession(manager)
})
afterEach(() => { afterEach(() => {
manager.shutdown() manager.shutdown()
@ -1311,19 +1319,20 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => {
let manager: BackgroundManager let manager: BackgroundManager
let mockClient: ReturnType<typeof createMockClient> let mockClient: ReturnType<typeof createMockClient>
function createMockClient() { function createMockClient() {
return { return {
session: { session: {
create: async () => ({ data: { id: `ses_${crypto.randomUUID()}` } }), create: async () => ({ data: { id: `ses_${crypto.randomUUID()}` } }),
get: async () => ({ data: { directory: "/test/dir" } }), get: async () => ({ data: { directory: "/test/dir" } }),
prompt: async () => ({}), prompt: async () => ({}),
messages: async () => ({ data: [] }), promptAsync: async () => ({}),
todo: async () => ({ data: [] }), messages: async () => ({ data: [] }),
status: async () => ({ data: {} }), todo: async () => ({ data: [] }),
abort: async () => ({}), status: async () => ({ data: {} }),
}, abort: async () => ({}),
} },
} }
}
beforeEach(() => { beforeEach(() => {
// given // given
@ -1871,13 +1880,14 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => {
}) })
describe("BackgroundManager.checkAndInterruptStaleTasks", () => { describe("BackgroundManager.checkAndInterruptStaleTasks", () => {
test("should NOT interrupt task running less than 30 seconds (min runtime guard)", async () => { test("should NOT interrupt task running less than 30 seconds (min runtime guard)", async () => {
const client = { const client = {
session: { session: {
prompt: async () => ({}), prompt: async () => ({}),
abort: async () => ({}), promptAsync: async () => ({}),
}, abort: async () => ({}),
} },
}
const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput, { staleTimeoutMs: 180_000 }) const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput, { staleTimeoutMs: 180_000 })
const task: BackgroundTask = { const task: BackgroundTask = {
@ -1903,12 +1913,13 @@ describe("BackgroundManager.checkAndInterruptStaleTasks", () => {
expect(task.status).toBe("running") expect(task.status).toBe("running")
}) })
test("should NOT interrupt task with recent lastUpdate", async () => { test("should NOT interrupt task with recent lastUpdate", async () => {
const client = { const client = {
session: { session: {
prompt: async () => ({}), prompt: async () => ({}),
abort: async () => ({}), promptAsync: async () => ({}),
}, abort: async () => ({}),
},
} }
const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput, { staleTimeoutMs: 180_000 }) const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput, { staleTimeoutMs: 180_000 })
@ -1935,11 +1946,12 @@ describe("BackgroundManager.checkAndInterruptStaleTasks", () => {
expect(task.status).toBe("running") expect(task.status).toBe("running")
}) })
test("should interrupt task with stale lastUpdate (> 3min)", async () => { test("should interrupt task with stale lastUpdate (> 3min)", async () => {
const client = { const client = {
session: { session: {
prompt: async () => ({}), prompt: async () => ({}),
abort: async () => ({}), promptAsync: async () => ({}),
abort: async () => ({}),
}, },
} }
const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput, { staleTimeoutMs: 180_000 }) const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput, { staleTimeoutMs: 180_000 })
@ -1971,10 +1983,11 @@ describe("BackgroundManager.checkAndInterruptStaleTasks", () => {
expect(task.completedAt).toBeDefined() expect(task.completedAt).toBeDefined()
}) })
test("should respect custom staleTimeoutMs config", async () => { test("should respect custom staleTimeoutMs config", async () => {
const client = { const client = {
session: { session: {
prompt: async () => ({}), prompt: async () => ({}),
promptAsync: async () => ({}),
abort: async () => ({}), abort: async () => ({}),
}, },
} }
@ -2005,13 +2018,14 @@ describe("BackgroundManager.checkAndInterruptStaleTasks", () => {
expect(task.error).toContain("Stale timeout") expect(task.error).toContain("Stale timeout")
}) })
test("should release concurrency before abort", async () => { test("should release concurrency before abort", async () => {
const client = { const client = {
session: { session: {
prompt: async () => ({}), prompt: async () => ({}),
abort: async () => ({}), promptAsync: async () => ({}),
}, abort: async () => ({}),
} },
}
const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput, { staleTimeoutMs: 180_000 }) const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput, { staleTimeoutMs: 180_000 })
stubNotifyParentSession(manager) stubNotifyParentSession(manager)
@ -2040,13 +2054,14 @@ describe("BackgroundManager.checkAndInterruptStaleTasks", () => {
expect(task.status).toBe("cancelled") expect(task.status).toBe("cancelled")
}) })
test("should handle multiple stale tasks in same poll cycle", async () => { test("should handle multiple stale tasks in same poll cycle", async () => {
const client = { const client = {
session: { session: {
prompt: async () => ({}), prompt: async () => ({}),
abort: async () => ({}), promptAsync: async () => ({}),
}, abort: async () => ({}),
} },
}
const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput, { staleTimeoutMs: 180_000 }) const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput, { staleTimeoutMs: 180_000 })
stubNotifyParentSession(manager) stubNotifyParentSession(manager)
@ -2091,13 +2106,14 @@ describe("BackgroundManager.checkAndInterruptStaleTasks", () => {
expect(task2.status).toBe("cancelled") expect(task2.status).toBe("cancelled")
}) })
test("should use default timeout when config not provided", async () => { test("should use default timeout when config not provided", async () => {
const client = { const client = {
session: { session: {
prompt: async () => ({}), prompt: async () => ({}),
abort: async () => ({}), promptAsync: async () => ({}),
}, abort: async () => ({}),
} },
}
const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput) const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput)
stubNotifyParentSession(manager) stubNotifyParentSession(manager)
@ -2126,18 +2142,19 @@ describe("BackgroundManager.checkAndInterruptStaleTasks", () => {
}) })
describe("BackgroundManager.shutdown session abort", () => { describe("BackgroundManager.shutdown session abort", () => {
test("should call session.abort for all running tasks during shutdown", () => { test("should call session.abort for all running tasks during shutdown", () => {
// given // given
const abortedSessionIDs: string[] = [] const abortedSessionIDs: string[] = []
const client = { const client = {
session: { session: {
prompt: async () => ({}), prompt: async () => ({}),
abort: async (args: { path: { id: string } }) => { promptAsync: async () => ({}),
abortedSessionIDs.push(args.path.id) abort: async (args: { path: { id: string } }) => {
return {} abortedSessionIDs.push(args.path.id)
}, return {}
}, },
} },
}
const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput) const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput)
const task1: BackgroundTask = { const task1: BackgroundTask = {
@ -2175,18 +2192,19 @@ describe("BackgroundManager.shutdown session abort", () => {
expect(abortedSessionIDs).toHaveLength(2) expect(abortedSessionIDs).toHaveLength(2)
}) })
test("should not call session.abort for completed or cancelled tasks", () => { test("should not call session.abort for completed or cancelled tasks", () => {
// given // given
const abortedSessionIDs: string[] = [] const abortedSessionIDs: string[] = []
const client = { const client = {
session: { session: {
prompt: async () => ({}), prompt: async () => ({}),
abort: async (args: { path: { id: string } }) => { promptAsync: async () => ({}),
abortedSessionIDs.push(args.path.id) abort: async (args: { path: { id: string } }) => {
return {} abortedSessionIDs.push(args.path.id)
}, return {}
}, },
} },
}
const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput) const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput)
const completedTask: BackgroundTask = { const completedTask: BackgroundTask = {
@ -2235,15 +2253,16 @@ describe("BackgroundManager.shutdown session abort", () => {
expect(abortedSessionIDs).toHaveLength(0) expect(abortedSessionIDs).toHaveLength(0)
}) })
test("should call onShutdown callback during shutdown", () => { test("should call onShutdown callback during shutdown", () => {
// given // given
let shutdownCalled = false let shutdownCalled = false
const client = { const client = {
session: { session: {
prompt: async () => ({}), prompt: async () => ({}),
abort: async () => ({}), promptAsync: async () => ({}),
}, abort: async () => ({}),
} },
}
const manager = new BackgroundManager( const manager = new BackgroundManager(
{ client, directory: tmpdir() } as unknown as PluginInput, { client, directory: tmpdir() } as unknown as PluginInput,
undefined, undefined,
@ -2261,14 +2280,15 @@ describe("BackgroundManager.shutdown session abort", () => {
expect(shutdownCalled).toBe(true) expect(shutdownCalled).toBe(true)
}) })
test("should not throw when onShutdown callback throws", () => { test("should not throw when onShutdown callback throws", () => {
// given // given
const client = { const client = {
session: { session: {
prompt: async () => ({}), prompt: async () => ({}),
abort: async () => ({}), promptAsync: async () => ({}),
}, abort: async () => ({}),
} },
}
const manager = new BackgroundManager( const manager = new BackgroundManager(
{ client, directory: tmpdir() } as unknown as PluginInput, { client, directory: tmpdir() } as unknown as PluginInput,
undefined, undefined,
@ -2509,19 +2529,20 @@ describe("BackgroundManager.handleEvent - early session.idle deferral", () => {
const realDateNow = Date.now const realDateNow = Date.now
const baseNow = realDateNow() const baseNow = realDateNow()
const client = { const client = {
session: { session: {
prompt: async () => ({}), prompt: async () => ({}),
abort: async () => ({}), promptAsync: async () => ({}),
messages: async (args: { path: { id: string } }) => { abort: async () => ({}),
messagesCalls.push(args.path.id) messages: async (args: { path: { id: string } }) => {
return { messagesCalls.push(args.path.id)
data: [ return {
{ data: [
info: { role: "assistant" }, {
parts: [{ type: "text", text: "ok" }], info: { role: "assistant" },
}, parts: [{ type: "text", text: "ok" }],
], },
],
} }
}, },
todo: async () => ({ data: [] }), todo: async () => ({ data: [] }),
@ -2566,23 +2587,24 @@ describe("BackgroundManager.handleEvent - early session.idle deferral", () => {
}) })
test("should not defer when session.idle fires after MIN_IDLE_TIME_MS", async () => { test("should not defer when session.idle fires after MIN_IDLE_TIME_MS", async () => {
//#given - a running task started more than MIN_IDLE_TIME_MS ago //#given - a running task started more than MIN_IDLE_TIME_MS ago
const sessionID = "session-late-idle" const sessionID = "session-late-idle"
const client = { const client = {
session: { session: {
prompt: async () => ({}), prompt: async () => ({}),
abort: async () => ({}), promptAsync: async () => ({}),
messages: async () => ({ abort: async () => ({}),
data: [ messages: async () => ({
{ data: [
info: { role: "assistant" }, {
parts: [{ type: "text", text: "ok" }], info: { role: "assistant" },
}, parts: [{ type: "text", text: "ok" }],
], },
}), ],
todo: async () => ({ data: [] }), }),
}, todo: async () => ({ data: [] }),
} },
}
const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput) const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput)
stubNotifyParentSession(manager) stubNotifyParentSession(manager)
@ -2618,20 +2640,21 @@ describe("BackgroundManager.handleEvent - early session.idle deferral", () => {
const realDateNow = Date.now const realDateNow = Date.now
const baseNow = realDateNow() const baseNow = realDateNow()
const client = { const client = {
session: { session: {
prompt: async () => ({}), prompt: async () => ({}),
abort: async () => ({}), promptAsync: async () => ({}),
messages: async () => { abort: async () => ({}),
messagesCallCount += 1 messages: async () => {
return { messagesCallCount += 1
data: [ return {
{ data: [
info: { role: "assistant" }, {
parts: [{ type: "text", text: "ok" }], info: { role: "assistant" },
}, parts: [{ type: "text", text: "ok" }],
], },
} ],
}
}, },
todo: async () => ({ data: [] }), todo: async () => ({ data: [] }),
}, },

View File

@ -34,6 +34,7 @@ describe("atlas hook", () => {
client: { client: {
session: { session: {
prompt: promptMock, prompt: promptMock,
promptAsync: promptMock,
}, },
}, },
_promptMock: promptMock, _promptMock: promptMock,

File diff suppressed because it is too large Load Diff

View File

@ -111,17 +111,19 @@ describe("look-at tool", () => {
}) })
describe("createLookAt error handling", () => { describe("createLookAt error handling", () => {
// given JSON parse error occurs in session.prompt // given JSON parse error occurs in session.promptAsync
// when LookAt tool executed // when LookAt tool executed
// then return user-friendly error message // then error propagates (band-aid removed since root cause fixed by promptAsync migration)
test("handles JSON parse error from session.prompt gracefully", async () => { test("propagates JSON parse error from session.promptAsync", async () => {
const throwingMock = async () => {
throw new Error("JSON Parse error: Unexpected EOF")
}
const mockClient = { const mockClient = {
session: { session: {
get: async () => ({ data: { directory: "/project" } }), get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_test_json_error" } }), create: async () => ({ data: { id: "ses_test_json_error" } }),
prompt: async () => { prompt: throwingMock,
throw new Error("JSON Parse error: Unexpected EOF") promptAsync: throwingMock,
},
messages: async () => ({ data: [] }), messages: async () => ({ data: [] }),
}, },
} }
@ -142,28 +144,24 @@ describe("look-at tool", () => {
ask: async () => {}, ask: async () => {},
} }
const result = await tool.execute( await expect(
{ file_path: "/test/file.png", goal: "analyze image" }, tool.execute({ file_path: "/test/file.png", goal: "analyze image" }, toolContext)
toolContext ).rejects.toThrow("JSON Parse error: Unexpected EOF")
)
expect(result).toContain("Error: Failed to analyze")
expect(result).toContain("malformed response")
expect(result).toContain("multimodal-looker")
expect(result).toContain("image/png")
}) })
// given generic error occurs in session.prompt // given generic error occurs in session.promptAsync
// when LookAt tool executed // when LookAt tool executed
// then return error including original error message // then error propagates
test("handles generic prompt error gracefully", async () => { test("propagates generic prompt error", async () => {
const throwingMock = async () => {
throw new Error("Network connection failed")
}
const mockClient = { const mockClient = {
session: { session: {
get: async () => ({ data: { directory: "/project" } }), get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_test_generic_error" } }), create: async () => ({ data: { id: "ses_test_generic_error" } }),
prompt: async () => { prompt: throwingMock,
throw new Error("Network connection failed") promptAsync: throwingMock,
},
messages: async () => ({ data: [] }), messages: async () => ({ data: [] }),
}, },
} }
@ -184,13 +182,9 @@ describe("look-at tool", () => {
ask: async () => {}, ask: async () => {},
} }
const result = await tool.execute( await expect(
{ file_path: "/test/file.pdf", goal: "extract text" }, tool.execute({ file_path: "/test/file.pdf", goal: "extract text" }, toolContext)
toolContext ).rejects.toThrow("Network connection failed")
)
expect(result).toContain("Error: Failed to send prompt")
expect(result).toContain("Network connection failed")
}) })
}) })
@ -220,6 +214,10 @@ describe("look-at tool", () => {
promptBody = input.body promptBody = input.body
return { data: {} } return { data: {} }
}, },
promptAsync: async (input: any) => {
promptBody = input.body
return { data: {} }
},
messages: async () => ({ messages: async () => ({
data: [ data: [
{ info: { role: "assistant", time: { created: 1 } }, parts: [{ type: "text", text: "done" }] }, { info: { role: "assistant", time: { created: 1 } }, parts: [{ type: "text", text: "done" }] },
@ -274,6 +272,10 @@ describe("look-at tool", () => {
promptBody = input.body promptBody = input.body
return { data: {} } return { data: {} }
}, },
promptAsync: async (input: any) => {
promptBody = input.body
return { data: {} }
},
messages: async () => ({ messages: async () => ({
data: [ data: [
{ info: { role: "assistant", time: { created: 1 } }, parts: [{ type: "text", text: "analyzed" }] }, { info: { role: "assistant", time: { created: 1 } }, parts: [{ type: "text", text: "analyzed" }] },
@ -327,6 +329,10 @@ describe("look-at tool", () => {
promptBody = input.body promptBody = input.body
return { data: {} } return { data: {} }
}, },
promptAsync: async (input: any) => {
promptBody = input.body
return { data: {} }
},
messages: async () => ({ messages: async () => ({
data: [ data: [
{ info: { role: "assistant", time: { created: 1 } }, parts: [{ type: "text", text: "analyzed" }] }, { info: { role: "assistant", time: { created: 1 } }, parts: [{ type: "text", text: "analyzed" }] },