From 250a320ff2be144fe649b6c9615e3fd4a97e2946 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Fri, 23 Jan 2026 14:47:37 +0700 Subject: [PATCH] Fix E2E tests: gate on credentials and parse agentId robustly --- .../agent/providers/codex-mcp-agent.test.ts | 2 +- .../daemon-e2e/checkout-ship.e2e.test.ts | 13 +++++- .../daemon-e2e/orchestration.e2e.test.ts | 35 ++++++++++++-- .../daemon-e2e/permissions-codex.e2e.test.ts | 4 +- .../server/daemon-e2e/streaming.e2e.test.ts | 46 +++++++------------ 5 files changed, 62 insertions(+), 38 deletions(-) diff --git a/packages/server/src/server/agent/providers/codex-mcp-agent.test.ts b/packages/server/src/server/agent/providers/codex-mcp-agent.test.ts index a43fda3fa..79a1278c4 100644 --- a/packages/server/src/server/agent/providers/codex-mcp-agent.test.ts +++ b/packages/server/src/server/agent/providers/codex-mcp-agent.test.ts @@ -1133,7 +1133,7 @@ describe("CodexMcpAgentClient (MCP integration)", () => { model: CODEX_TEST_MODEL, reasoningEffort: CODEX_TEST_REASONING_EFFORT, cwd, - modeId: "auto", + modeId: "read-only", approvalPolicy: "on-request", } satisfies AgentSessionConfig; const filePath = path.join(cwd, "permission.txt"); diff --git a/packages/server/src/server/daemon-e2e/checkout-ship.e2e.test.ts b/packages/server/src/server/daemon-e2e/checkout-ship.e2e.test.ts index 6974e3b92..71073687d 100644 --- a/packages/server/src/server/daemon-e2e/checkout-ship.e2e.test.ts +++ b/packages/server/src/server/daemon-e2e/checkout-ship.e2e.test.ts @@ -19,6 +19,17 @@ function tmpCwd(prefix: string): string { return realpathSync(mkdtempSync(path.join(tmpdir(), prefix))); } +function hasGitHubCliAuth(): boolean { + try { + execSync("gh auth status -h github.com", { stdio: "pipe" }); + return true; + } catch { + return false; + } +} + +const testWithGitHubCliAuth = hasGitHubCliAuth() ? test : test.skip; + type McpToolResult = { structuredContent?: Record; content?: Array<{ structuredContent?: Record } | Record>; @@ -137,7 +148,7 @@ describe("daemon checkout ship loop", () => { await ctx.cleanup(); }, 60000); - test( + testWithGitHubCliAuth( "runs the full checkout ship loop via checkout RPCs", async () => { const repoDir = tmpCwd("checkout-ship-"); diff --git a/packages/server/src/server/daemon-e2e/orchestration.e2e.test.ts b/packages/server/src/server/daemon-e2e/orchestration.e2e.test.ts index 37f990381..78dfec102 100644 --- a/packages/server/src/server/daemon-e2e/orchestration.e2e.test.ts +++ b/packages/server/src/server/daemon-e2e/orchestration.e2e.test.ts @@ -120,11 +120,36 @@ describe("daemon E2E", () => { createAgentCall.type === "tool_call" && createAgentCall.output ) { - // The output contains the agentId - const output = createAgentCall.output as { agentId?: string }; - if (output.agentId) { - childAgentId = output.agentId; - } + const output = createAgentCall.output as unknown; + const tryExtract = (value: unknown): string | null => { + if (!value) return null; + if (typeof value === "string") { + try { + return tryExtract(JSON.parse(value)); + } catch { + return null; + } + } + if (typeof value !== "object") return null; + const asObj = value as Record; + const direct = asObj.agentId; + if (typeof direct === "string") return direct; + const structured = asObj.structuredContent; + if (structured && typeof structured === "object") { + const nested = (structured as Record).agentId; + if (typeof nested === "string") return nested; + } + if (typeof structured === "string") { + try { + return tryExtract(JSON.parse(structured)); + } catch { + return null; + } + } + return null; + }; + + childAgentId = tryExtract(output); } // Verify we found the child agent ID diff --git a/packages/server/src/server/daemon-e2e/permissions-codex.e2e.test.ts b/packages/server/src/server/daemon-e2e/permissions-codex.e2e.test.ts index cdfdd2799..89d58a550 100644 --- a/packages/server/src/server/daemon-e2e/permissions-codex.e2e.test.ts +++ b/packages/server/src/server/daemon-e2e/permissions-codex.e2e.test.ts @@ -40,7 +40,7 @@ describe("daemon E2E", () => { provider: "codex", model: CODEX_TEST_MODEL, reasoningEffort: CODEX_TEST_REASONING_EFFORT, cwd, title: "Codex Permission Test", - modeId: "auto", + modeId: "read-only", }); expect(agent.id).toBeTruthy(); @@ -105,7 +105,7 @@ describe("daemon E2E", () => { provider: "codex", model: CODEX_TEST_MODEL, reasoningEffort: CODEX_TEST_REASONING_EFFORT, cwd, title: "Codex Permission Deny Test", - modeId: "auto", + modeId: "read-only", }); expect(agent.id).toBeTruthy(); diff --git a/packages/server/src/server/daemon-e2e/streaming.e2e.test.ts b/packages/server/src/server/daemon-e2e/streaming.e2e.test.ts index 0ed05d292..9d3617a87 100644 --- a/packages/server/src/server/daemon-e2e/streaming.e2e.test.ts +++ b/packages/server/src/server/daemon-e2e/streaming.e2e.test.ts @@ -17,7 +17,12 @@ function tmpCwd(): string { const CODEX_TEST_MODEL = "gpt-5.1-codex-mini"; const CODEX_TEST_REASONING_EFFORT = "low"; -describe("daemon E2E", () => { +const hasClaudeCredentials = + !!process.env.CLAUDE_SESSION_TOKEN || !!process.env.ANTHROPIC_API_KEY; + +const describeWithClaude = hasClaudeCredentials ? describe : describe.skip; + +describeWithClaude("daemon E2E", () => { let ctx: DaemonTestContext; beforeEach(async () => { @@ -389,43 +394,26 @@ describe("daemon E2E", () => { const queue = ctx.client.getMessageQueue(); const assistantChunks: string[] = []; - // Debug: dump all events from queue - - for (let i = 0; i < queue.length; i++) { - const m = queue[i]; - if (m.type === "agent_stream" && m.payload.agentId === agent.id) { - const event = m.payload.event; - if (event.type === "timeline") { - const item = event.item; - - } else { - - } - } else if (m.type === "agent_state" && m.payload.id === agent.id) { - - } - } - - // Find the user_message for message 2 to mark the boundary - let foundMsg2UserMessage = false; + // We only care about Turn 2 ("Hello world ..."), but event ordering can be noisy when + // Turn 1 is still streaming. Anchor on the assistant response content itself. + let startedCollecting = false; for (let i = msg2StartPosition; i < queue.length; i++) { const m = queue[i]; - - // Look for our user message to mark the start of message 2 context if ( m.type === "agent_stream" && m.payload.agentId === agent.id && m.payload.event.type === "timeline" ) { const item = m.payload.event.item; - if (item.type === "user_message" && (item.text as string)?.includes("Hello world")) { - foundMsg2UserMessage = true; - - } - // Collect assistant messages after we found the user message - if (foundMsg2UserMessage && item.type === "assistant_message" && item.text) { - assistantChunks.push(item.text); + if (item.type === "assistant_message" && item.text) { + const text = String(item.text); + if (!startedCollecting && text.includes("Hello")) { + startedCollecting = true; + } + if (startedCollecting) { + assistantChunks.push(text); + } } } }