Fix E2E tests: gate on credentials and parse agentId robustly

This commit is contained in:
Mohamed Boudra
2026-01-23 14:47:37 +07:00
parent 3459faabc4
commit 250a320ff2
5 changed files with 62 additions and 38 deletions
@@ -1133,7 +1133,7 @@ describe("CodexMcpAgentClient (MCP integration)", () => {
model: CODEX_TEST_MODEL, model: CODEX_TEST_MODEL,
reasoningEffort: CODEX_TEST_REASONING_EFFORT, reasoningEffort: CODEX_TEST_REASONING_EFFORT,
cwd, cwd,
modeId: "auto", modeId: "read-only",
approvalPolicy: "on-request", approvalPolicy: "on-request",
} satisfies AgentSessionConfig; } satisfies AgentSessionConfig;
const filePath = path.join(cwd, "permission.txt"); const filePath = path.join(cwd, "permission.txt");
@@ -19,6 +19,17 @@ function tmpCwd(prefix: string): string {
return realpathSync(mkdtempSync(path.join(tmpdir(), prefix))); 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 = { type McpToolResult = {
structuredContent?: Record<string, unknown>; structuredContent?: Record<string, unknown>;
content?: Array<{ structuredContent?: Record<string, unknown> } | Record<string, unknown>>; content?: Array<{ structuredContent?: Record<string, unknown> } | Record<string, unknown>>;
@@ -137,7 +148,7 @@ describe("daemon checkout ship loop", () => {
await ctx.cleanup(); await ctx.cleanup();
}, 60000); }, 60000);
test( testWithGitHubCliAuth(
"runs the full checkout ship loop via checkout RPCs", "runs the full checkout ship loop via checkout RPCs",
async () => { async () => {
const repoDir = tmpCwd("checkout-ship-"); const repoDir = tmpCwd("checkout-ship-");
@@ -120,11 +120,36 @@ describe("daemon E2E", () => {
createAgentCall.type === "tool_call" && createAgentCall.type === "tool_call" &&
createAgentCall.output createAgentCall.output
) { ) {
// The output contains the agentId const output = createAgentCall.output as unknown;
const output = createAgentCall.output as { agentId?: string }; const tryExtract = (value: unknown): string | null => {
if (output.agentId) { if (!value) return null;
childAgentId = output.agentId; if (typeof value === "string") {
} try {
return tryExtract(JSON.parse(value));
} catch {
return null;
}
}
if (typeof value !== "object") return null;
const asObj = value as Record<string, unknown>;
const direct = asObj.agentId;
if (typeof direct === "string") return direct;
const structured = asObj.structuredContent;
if (structured && typeof structured === "object") {
const nested = (structured as Record<string, unknown>).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 // Verify we found the child agent ID
@@ -40,7 +40,7 @@ describe("daemon E2E", () => {
provider: "codex", model: CODEX_TEST_MODEL, reasoningEffort: CODEX_TEST_REASONING_EFFORT, provider: "codex", model: CODEX_TEST_MODEL, reasoningEffort: CODEX_TEST_REASONING_EFFORT,
cwd, cwd,
title: "Codex Permission Test", title: "Codex Permission Test",
modeId: "auto", modeId: "read-only",
}); });
expect(agent.id).toBeTruthy(); expect(agent.id).toBeTruthy();
@@ -105,7 +105,7 @@ describe("daemon E2E", () => {
provider: "codex", model: CODEX_TEST_MODEL, reasoningEffort: CODEX_TEST_REASONING_EFFORT, provider: "codex", model: CODEX_TEST_MODEL, reasoningEffort: CODEX_TEST_REASONING_EFFORT,
cwd, cwd,
title: "Codex Permission Deny Test", title: "Codex Permission Deny Test",
modeId: "auto", modeId: "read-only",
}); });
expect(agent.id).toBeTruthy(); expect(agent.id).toBeTruthy();
@@ -17,7 +17,12 @@ function tmpCwd(): string {
const CODEX_TEST_MODEL = "gpt-5.1-codex-mini"; const CODEX_TEST_MODEL = "gpt-5.1-codex-mini";
const CODEX_TEST_REASONING_EFFORT = "low"; 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; let ctx: DaemonTestContext;
beforeEach(async () => { beforeEach(async () => {
@@ -389,43 +394,26 @@ describe("daemon E2E", () => {
const queue = ctx.client.getMessageQueue(); const queue = ctx.client.getMessageQueue();
const assistantChunks: string[] = []; const assistantChunks: string[] = [];
// Debug: dump all events from queue // 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.
for (let i = 0; i < queue.length; i++) { let startedCollecting = false;
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;
for (let i = msg2StartPosition; i < queue.length; i++) { for (let i = msg2StartPosition; i < queue.length; i++) {
const m = queue[i]; const m = queue[i];
// Look for our user message to mark the start of message 2 context
if ( if (
m.type === "agent_stream" && m.type === "agent_stream" &&
m.payload.agentId === agent.id && m.payload.agentId === agent.id &&
m.payload.event.type === "timeline" m.payload.event.type === "timeline"
) { ) {
const item = m.payload.event.item; const item = m.payload.event.item;
if (item.type === "user_message" && (item.text as string)?.includes("Hello world")) { if (item.type === "assistant_message" && item.text) {
foundMsg2UserMessage = true; const text = String(item.text);
if (!startedCollecting && text.includes("Hello")) {
} startedCollecting = true;
// Collect assistant messages after we found the user message }
if (foundMsg2UserMessage && item.type === "assistant_message" && item.text) { if (startedCollecting) {
assistantChunks.push(item.text); assistantChunks.push(text);
}
} }
} }
} }