Strengthen sync executor test coverage
Cover metadata output and prompt failure branches so the sync executor is verified by its returned contract, not only tool flag plumbing. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -1,137 +1,252 @@
|
||||
const { describe, test, expect, mock } = require("bun:test")
|
||||
|
||||
describe("executeSync", () => {
|
||||
test("passes question=false via tools parameter to block question tool", async () => {
|
||||
//#given
|
||||
const { executeSync } = require("./sync-executor")
|
||||
type ExecuteSync = typeof import("./sync-executor").executeSync
|
||||
|
||||
const deps = {
|
||||
createOrGetSession: mock(async () => ({ sessionID: "ses-test-123", isNew: true })),
|
||||
waitForCompletion: mock(async () => {}),
|
||||
processMessages: mock(async () => "agent response"),
|
||||
setSessionFallbackChain: mock(() => {}),
|
||||
type PromptAsyncInput = {
|
||||
path: { id: string }
|
||||
body: {
|
||||
agent: string
|
||||
tools: Record<string, boolean>
|
||||
parts: Array<{ type: string; text: string }>
|
||||
}
|
||||
}
|
||||
|
||||
type ToolContext = {
|
||||
sessionID: string
|
||||
messageID: string
|
||||
agent: string
|
||||
abort: AbortSignal
|
||||
metadata: ReturnType<typeof mock>
|
||||
}
|
||||
|
||||
type Dependencies = {
|
||||
createOrGetSession: ReturnType<typeof mock>
|
||||
waitForCompletion: ReturnType<typeof mock>
|
||||
processMessages: ReturnType<typeof mock>
|
||||
setSessionFallbackChain: ReturnType<typeof mock>
|
||||
}
|
||||
|
||||
async function importExecuteSync(): Promise<ExecuteSync> {
|
||||
const module = await import("./sync-executor")
|
||||
return module.executeSync
|
||||
}
|
||||
|
||||
function createDependencies(overrides?: Partial<Dependencies>): Dependencies {
|
||||
return {
|
||||
createOrGetSession: mock(async () => ({ sessionID: "ses-test-123", isNew: true })),
|
||||
waitForCompletion: mock(async () => {}),
|
||||
processMessages: mock(async () => "agent response"),
|
||||
setSessionFallbackChain: mock(() => {}),
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
function createPromptAsyncRecorder(implementation?: (input: PromptAsyncInput) => Promise<unknown>) {
|
||||
let capturedInput: PromptAsyncInput | undefined
|
||||
|
||||
const promptAsync = mock(async (input: PromptAsyncInput) => {
|
||||
capturedInput = input
|
||||
if (implementation) {
|
||||
return implementation(input)
|
||||
}
|
||||
|
||||
let promptArgs: any
|
||||
const promptAsync = mock(async (input: any) => {
|
||||
promptArgs = input
|
||||
return { data: {} }
|
||||
})
|
||||
return { data: {} }
|
||||
})
|
||||
|
||||
return {
|
||||
promptAsync,
|
||||
getCapturedInput(): PromptAsyncInput | undefined {
|
||||
return capturedInput
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function createToolContext(): ToolContext {
|
||||
return {
|
||||
sessionID: "parent-session",
|
||||
messageID: "msg-1",
|
||||
agent: "sisyphus",
|
||||
abort: new AbortController().signal,
|
||||
metadata: mock(async () => {}),
|
||||
}
|
||||
}
|
||||
|
||||
function createContext(promptAsync: ReturnType<typeof mock>) {
|
||||
return {
|
||||
client: {
|
||||
session: {
|
||||
promptAsync,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
describe("executeSync", () => {
|
||||
test("sends sync prompt with question and task tools disabled", async () => {
|
||||
//#given
|
||||
const executeSync = await importExecuteSync()
|
||||
const deps = createDependencies()
|
||||
const toolContext = createToolContext()
|
||||
const recorder = createPromptAsyncRecorder()
|
||||
const args = {
|
||||
subagent_type: "explore",
|
||||
description: "test task",
|
||||
prompt: "find something",
|
||||
}
|
||||
|
||||
const toolContext = {
|
||||
sessionID: "parent-session",
|
||||
messageID: "msg-1",
|
||||
agent: "sisyphus",
|
||||
abort: new AbortController().signal,
|
||||
metadata: mock(async () => {}),
|
||||
}
|
||||
|
||||
const ctx = {
|
||||
client: {
|
||||
session: { promptAsync },
|
||||
},
|
||||
run_in_background: false,
|
||||
}
|
||||
|
||||
//#when
|
||||
await executeSync(args, toolContext, ctx as any, deps)
|
||||
await executeSync(args, toolContext, createContext(recorder.promptAsync) as never, deps)
|
||||
|
||||
//#then
|
||||
expect(promptAsync).toHaveBeenCalled()
|
||||
expect(promptArgs.body.tools.question).toBe(false)
|
||||
const promptInput = recorder.getCapturedInput()
|
||||
expect(promptInput).toBeDefined()
|
||||
expect(promptInput?.path.id).toBe("ses-test-123")
|
||||
expect(promptInput?.body.agent).toBe("explore")
|
||||
expect(promptInput?.body.tools.question).toBe(false)
|
||||
expect(promptInput?.body.tools.task).toBe(false)
|
||||
expect(promptInput?.body.parts).toEqual([{ type: "text", text: "find something" }])
|
||||
})
|
||||
|
||||
test("passes task=false via tools parameter", async () => {
|
||||
test("returns processed response with task metadata footer", async () => {
|
||||
//#given
|
||||
const { executeSync } = require("./sync-executor")
|
||||
|
||||
const deps = {
|
||||
createOrGetSession: mock(async () => ({ sessionID: "ses-test-123", isNew: true })),
|
||||
waitForCompletion: mock(async () => {}),
|
||||
processMessages: mock(async () => "agent response"),
|
||||
setSessionFallbackChain: mock(() => {}),
|
||||
}
|
||||
|
||||
let promptArgs: any
|
||||
const promptAsync = mock(async (input: any) => {
|
||||
promptArgs = input
|
||||
return { data: {} }
|
||||
const executeSync = await importExecuteSync()
|
||||
const deps = createDependencies({
|
||||
createOrGetSession: mock(async () => ({ sessionID: "ses-test-456", isNew: true })),
|
||||
processMessages: mock(async () => "final answer"),
|
||||
})
|
||||
|
||||
const toolContext = createToolContext()
|
||||
const recorder = createPromptAsyncRecorder()
|
||||
const args = {
|
||||
subagent_type: "librarian",
|
||||
description: "search docs",
|
||||
prompt: "find docs",
|
||||
}
|
||||
|
||||
const toolContext = {
|
||||
sessionID: "parent-session",
|
||||
messageID: "msg-2",
|
||||
agent: "sisyphus",
|
||||
abort: new AbortController().signal,
|
||||
metadata: mock(async () => {}),
|
||||
}
|
||||
|
||||
const ctx = {
|
||||
client: {
|
||||
session: { promptAsync },
|
||||
},
|
||||
run_in_background: false,
|
||||
}
|
||||
|
||||
//#when
|
||||
await executeSync(args, toolContext, ctx as any, deps)
|
||||
const result = await executeSync(args, toolContext, createContext(recorder.promptAsync) as never, deps)
|
||||
|
||||
//#then
|
||||
expect(promptAsync).toHaveBeenCalled()
|
||||
expect(promptArgs.body.tools.task).toBe(false)
|
||||
expect(result).toContain("final answer")
|
||||
expect(result).toContain("<task_metadata>")
|
||||
expect(result).toContain("session_id: ses-test-456")
|
||||
expect(result).toContain("</task_metadata>")
|
||||
expect(deps.waitForCompletion).toHaveBeenCalledWith(
|
||||
"ses-test-456",
|
||||
toolContext,
|
||||
expect.objectContaining({ client: expect.anything() })
|
||||
)
|
||||
})
|
||||
|
||||
test("applies fallbackChain to sync sessions", async () => {
|
||||
test("records metadata with description and created session id", async () => {
|
||||
//#given
|
||||
const { executeSync } = require("./sync-executor")
|
||||
|
||||
const setSessionFallbackChain = mock(() => {})
|
||||
const deps = {
|
||||
createOrGetSession: mock(async () => ({ sessionID: "ses-test-456", isNew: true })),
|
||||
waitForCompletion: mock(async () => {}),
|
||||
processMessages: mock(async () => "agent response"),
|
||||
setSessionFallbackChain,
|
||||
const executeSync = await importExecuteSync()
|
||||
const deps = createDependencies({
|
||||
createOrGetSession: mock(async () => ({ sessionID: "ses-metadata", isNew: true })),
|
||||
})
|
||||
const toolContext = createToolContext()
|
||||
const recorder = createPromptAsyncRecorder()
|
||||
const args = {
|
||||
subagent_type: "explore",
|
||||
description: "metadata title",
|
||||
prompt: "collect evidence",
|
||||
run_in_background: false,
|
||||
}
|
||||
|
||||
//#when
|
||||
await executeSync(args, toolContext, createContext(recorder.promptAsync) as never, deps)
|
||||
|
||||
//#then
|
||||
expect(toolContext.metadata).toHaveBeenCalledWith({
|
||||
title: "metadata title",
|
||||
metadata: { sessionId: "ses-metadata" },
|
||||
})
|
||||
})
|
||||
|
||||
test("applies fallback chain to sync sessions before completion polling", async () => {
|
||||
//#given
|
||||
const executeSync = await importExecuteSync()
|
||||
const deps = createDependencies({
|
||||
createOrGetSession: mock(async () => ({ sessionID: "ses-fallback", isNew: true })),
|
||||
})
|
||||
const toolContext = createToolContext()
|
||||
const recorder = createPromptAsyncRecorder()
|
||||
const args = {
|
||||
subagent_type: "explore",
|
||||
description: "test task",
|
||||
prompt: "find something",
|
||||
run_in_background: false,
|
||||
}
|
||||
|
||||
const toolContext = {
|
||||
sessionID: "parent-session",
|
||||
messageID: "msg-3",
|
||||
agent: "sisyphus",
|
||||
abort: new AbortController().signal,
|
||||
metadata: mock(async () => {}),
|
||||
}
|
||||
|
||||
const ctx = {
|
||||
client: {
|
||||
session: { promptAsync: mock(async () => ({ data: {} })) },
|
||||
},
|
||||
}
|
||||
|
||||
const fallbackChain = [
|
||||
{ providers: ["quotio"], model: "kimi-k2.5", variant: undefined },
|
||||
{ providers: ["openai"], model: "gpt-5.2", variant: "high" },
|
||||
]
|
||||
|
||||
//#when
|
||||
await executeSync(args, toolContext, ctx as any, deps, fallbackChain)
|
||||
await executeSync(
|
||||
args,
|
||||
toolContext,
|
||||
createContext(recorder.promptAsync) as never,
|
||||
deps,
|
||||
fallbackChain
|
||||
)
|
||||
|
||||
//#then
|
||||
expect(setSessionFallbackChain).toHaveBeenCalledWith("ses-test-456", fallbackChain)
|
||||
expect(deps.setSessionFallbackChain).toHaveBeenCalledWith("ses-fallback", fallbackChain)
|
||||
})
|
||||
|
||||
test("returns dedicated agent-not-found error with task metadata", async () => {
|
||||
//#given
|
||||
const executeSync = await importExecuteSync()
|
||||
const deps = createDependencies({
|
||||
createOrGetSession: mock(async () => ({ sessionID: "ses-missing-agent", isNew: true })),
|
||||
})
|
||||
const toolContext = createToolContext()
|
||||
const recorder = createPromptAsyncRecorder(async () => {
|
||||
throw new Error("agent.name is undefined")
|
||||
})
|
||||
const args = {
|
||||
subagent_type: "explore",
|
||||
description: "missing agent",
|
||||
prompt: "find something",
|
||||
run_in_background: false,
|
||||
}
|
||||
|
||||
//#when
|
||||
const result = await executeSync(args, toolContext, createContext(recorder.promptAsync) as never, deps)
|
||||
|
||||
//#then
|
||||
expect(result).toContain('Error: Agent "explore" not found')
|
||||
expect(result).toContain("session_id: ses-missing-agent")
|
||||
expect(deps.waitForCompletion).not.toHaveBeenCalled()
|
||||
expect(deps.processMessages).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("returns generic prompt failure with task metadata", async () => {
|
||||
//#given
|
||||
const executeSync = await importExecuteSync()
|
||||
const deps = createDependencies({
|
||||
createOrGetSession: mock(async () => ({ sessionID: "ses-prompt-error", isNew: true })),
|
||||
})
|
||||
const toolContext = createToolContext()
|
||||
const recorder = createPromptAsyncRecorder(async () => {
|
||||
throw new Error("network exploded")
|
||||
})
|
||||
const args = {
|
||||
subagent_type: "librarian",
|
||||
description: "generic failure",
|
||||
prompt: "find docs",
|
||||
run_in_background: false,
|
||||
}
|
||||
|
||||
//#when
|
||||
const result = await executeSync(args, toolContext, createContext(recorder.promptAsync) as never, deps)
|
||||
|
||||
//#then
|
||||
expect(result).toContain("Error: Failed to send prompt: network exploded")
|
||||
expect(result).toContain("session_id: ses-prompt-error")
|
||||
expect(deps.waitForCompletion).not.toHaveBeenCalled()
|
||||
expect(deps.processMessages).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user