Fix E2E tests: gate on credentials and parse agentId robustly
This commit is contained in:
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user