fix(tests): iteration 1
This commit is contained in:
@@ -1,8 +1,17 @@
|
|||||||
import { describe, expect, test } from "vitest";
|
import { describe, expect, test } from "vitest";
|
||||||
import { execFileSync } from "node:child_process";
|
import { execFileSync } from "node:child_process";
|
||||||
import { mkdtempSync, readFileSync, rmSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
import {
|
||||||
|
copyFileSync,
|
||||||
|
existsSync,
|
||||||
|
mkdirSync,
|
||||||
|
mkdtempSync,
|
||||||
|
readFileSync,
|
||||||
|
rmSync,
|
||||||
|
writeFileSync,
|
||||||
|
} from "node:fs";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
__codexAppServerInternals,
|
__codexAppServerInternals,
|
||||||
@@ -17,6 +26,7 @@ import type {
|
|||||||
AgentPermissionRequest,
|
AgentPermissionRequest,
|
||||||
AgentPromptContentBlock,
|
AgentPromptContentBlock,
|
||||||
AgentStreamEvent,
|
AgentStreamEvent,
|
||||||
|
AgentRunResult,
|
||||||
AgentTimelineItem,
|
AgentTimelineItem,
|
||||||
} from "../agent-sdk-types.js";
|
} from "../agent-sdk-types.js";
|
||||||
|
|
||||||
@@ -24,6 +34,7 @@ const CODEX_TEST_MODEL = agentConfigs.codex.model;
|
|||||||
const CODEX_TEST_THINKING_OPTION_ID = agentConfigs.codex.thinkingOptionId;
|
const CODEX_TEST_THINKING_OPTION_ID = agentConfigs.codex.thinkingOptionId;
|
||||||
const ONE_BY_ONE_PNG_BASE64 =
|
const ONE_BY_ONE_PNG_BASE64 =
|
||||||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+X1r0AAAAASUVORK5CYII=";
|
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+X1r0AAAAASUVORK5CYII=";
|
||||||
|
const TEST_FILE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
function isCodexInstalled(): boolean {
|
function isCodexInstalled(): boolean {
|
||||||
try {
|
try {
|
||||||
@@ -52,6 +63,42 @@ function useTempCodexSessionDir(): () => void {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function useTempCodexHome(prefix = "codex-home-"): { codexHome: string; cleanup: () => void } {
|
||||||
|
const codexHome = tmpCwd(prefix);
|
||||||
|
const prevCodexHome = process.env.CODEX_HOME;
|
||||||
|
const sharedCodexHome = process.env.CODEX_HOME ?? path.join(os.homedir(), ".codex");
|
||||||
|
const sharedAuthPath = path.join(sharedCodexHome, "auth.json");
|
||||||
|
if (!existsSync(sharedAuthPath)) {
|
||||||
|
throw new Error(`Codex auth file not found at ${sharedAuthPath}`);
|
||||||
|
}
|
||||||
|
copyFileSync(sharedAuthPath, path.join(codexHome, "auth.json"));
|
||||||
|
writeFileSync(
|
||||||
|
path.join(codexHome, "config.toml"),
|
||||||
|
[
|
||||||
|
'model = "gpt-5.2-codex"',
|
||||||
|
'model_reasoning_effort = "medium"',
|
||||||
|
`[projects."${process.cwd()}"]`,
|
||||||
|
'trust_level = "trusted"',
|
||||||
|
"[features]",
|
||||||
|
"unified_exec = true",
|
||||||
|
"shell_snapshot = true",
|
||||||
|
].join("\n"),
|
||||||
|
"utf8"
|
||||||
|
);
|
||||||
|
process.env.CODEX_HOME = codexHome;
|
||||||
|
return {
|
||||||
|
codexHome,
|
||||||
|
cleanup: () => {
|
||||||
|
if (prevCodexHome === undefined) {
|
||||||
|
delete process.env.CODEX_HOME;
|
||||||
|
} else {
|
||||||
|
process.env.CODEX_HOME = prevCodexHome;
|
||||||
|
}
|
||||||
|
rmSync(codexHome, { recursive: true, force: true });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function hasShellCommand(item: AgentTimelineItem, commandFragment: string): boolean {
|
function hasShellCommand(item: AgentTimelineItem, commandFragment: string): boolean {
|
||||||
if (item.type !== "tool_call") return false;
|
if (item.type !== "tool_call") return false;
|
||||||
if (item.detail.type === "shell") {
|
if (item.detail.type === "shell") {
|
||||||
@@ -198,6 +245,17 @@ function deferred<T>(): Deferred<T> {
|
|||||||
return { promise, resolve, reject };
|
return { promise, resolve, reject };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function expectSuccessfulAssistantTurn(
|
||||||
|
result: Pick<AgentRunResult, "finalText" | "timeline">,
|
||||||
|
options?: { forbiddenText?: string[] }
|
||||||
|
): void {
|
||||||
|
expect(result.finalText.trim().length).toBeGreaterThan(0);
|
||||||
|
expect(result.timeline.some((item) => item.type === "assistant_message")).toBe(true);
|
||||||
|
for (const fragment of options?.forbiddenText ?? []) {
|
||||||
|
expect(result.finalText.toLowerCase()).not.toContain(fragment.toLowerCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
describe("Codex app-server provider (integration)", () => {
|
describe("Codex app-server provider (integration)", () => {
|
||||||
const logger = createTestLogger();
|
const logger = createTestLogger();
|
||||||
|
|
||||||
@@ -375,12 +433,17 @@ describe("Codex app-server provider (integration)", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const result = await session.run([
|
const result = await session.run([
|
||||||
{ type: "text", text: "Reply with exactly: OK." },
|
{
|
||||||
|
type: "text",
|
||||||
|
text: "Confirm in one short sentence that you received the attached image.",
|
||||||
|
},
|
||||||
{ type: "image", mimeType: "image/png", data: ONE_BY_ONE_PNG_BASE64 },
|
{ type: "image", mimeType: "image/png", data: ONE_BY_ONE_PNG_BASE64 },
|
||||||
] satisfies AgentPromptContentBlock[]);
|
] satisfies AgentPromptContentBlock[]);
|
||||||
await session.close();
|
await session.close();
|
||||||
|
|
||||||
expect(result.finalText).toContain("OK");
|
expectSuccessfulAssistantTurn(result, {
|
||||||
|
forbiddenText: ["validation error", "invalid request", "schema"],
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
cleanup();
|
cleanup();
|
||||||
rmSync(cwd, { recursive: true, force: true });
|
rmSync(cwd, { recursive: true, force: true });
|
||||||
@@ -487,9 +550,10 @@ describe("Codex app-server provider (integration)", () => {
|
|||||||
|
|
||||||
test.runIf(isCodexInstalled())("round-trips a stdio MCP tool call", async () => {
|
test.runIf(isCodexInstalled())("round-trips a stdio MCP tool call", async () => {
|
||||||
const cleanup = useTempCodexSessionDir();
|
const cleanup = useTempCodexSessionDir();
|
||||||
|
const { cleanup: cleanupCodexHome } = useTempCodexHome("codex-mcp-home-");
|
||||||
const cwd = tmpCwd("codex-mcp-roundtrip-");
|
const cwd = tmpCwd("codex-mcp-roundtrip-");
|
||||||
const token = `MCP_ROUNDTRIP_${Date.now()}`;
|
const token = `MCP_ROUNDTRIP_${Date.now()}`;
|
||||||
const mcpScriptPath = path.resolve(process.cwd(), "scripts", "mcp-echo-test-server.mjs");
|
const mcpScriptPath = path.resolve(TEST_FILE_DIR, "../../../../scripts/mcp-echo-test-server.mjs");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const client = new CodexAppServerAgentClient(logger);
|
const client = new CodexAppServerAgentClient(logger);
|
||||||
@@ -497,8 +561,8 @@ describe("Codex app-server provider (integration)", () => {
|
|||||||
provider: "codex",
|
provider: "codex",
|
||||||
cwd,
|
cwd,
|
||||||
modeId: "read-only",
|
modeId: "read-only",
|
||||||
model: CODEX_TEST_MODEL,
|
model: "gpt-5.2-codex",
|
||||||
thinkingOptionId: CODEX_TEST_THINKING_OPTION_ID,
|
thinkingOptionId: "medium",
|
||||||
extra: {
|
extra: {
|
||||||
codex: {
|
codex: {
|
||||||
tools: {
|
tools: {
|
||||||
@@ -519,6 +583,7 @@ describe("Codex app-server provider (integration)", () => {
|
|||||||
|
|
||||||
const result = await session.run(
|
const result = await session.run(
|
||||||
[
|
[
|
||||||
|
"Use the MCP tool-calling interface, not shell commands or plain text.",
|
||||||
"You must call the MCP tool named paseo_test.paseo_roundtrip_text exactly once.",
|
"You must call the MCP tool named paseo_test.paseo_roundtrip_text exactly once.",
|
||||||
`Call it with text: ${token}`,
|
`Call it with text: ${token}`,
|
||||||
"Do not use shell or any non-MCP tools.",
|
"Do not use shell or any non-MCP tools.",
|
||||||
@@ -573,6 +638,7 @@ describe("Codex app-server provider (integration)", () => {
|
|||||||
expect(JSON.stringify(mcpDetail?.output ?? {})).toContain(`ECHO:${token}`);
|
expect(JSON.stringify(mcpDetail?.output ?? {})).toContain(`ECHO:${token}`);
|
||||||
expect(result.finalText).toContain(`ECHO:${token}`);
|
expect(result.finalText).toContain(`ECHO:${token}`);
|
||||||
} finally {
|
} finally {
|
||||||
|
cleanupCodexHome();
|
||||||
cleanup();
|
cleanup();
|
||||||
rmSync(cwd, { recursive: true, force: true });
|
rmSync(cwd, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
@@ -582,7 +648,7 @@ describe("Codex app-server provider (integration)", () => {
|
|||||||
"listCommands includes custom prompts and run('/prompts:*') expands them",
|
"listCommands includes custom prompts and run('/prompts:*') expands them",
|
||||||
async () => {
|
async () => {
|
||||||
const cleanup = useTempCodexSessionDir();
|
const cleanup = useTempCodexSessionDir();
|
||||||
const codexHome = process.env.CODEX_HOME ?? path.join(os.homedir(), ".codex");
|
const { codexHome, cleanup: cleanupCodexHome } = useTempCodexHome("codex-prompts-home-");
|
||||||
const promptsDir = path.join(codexHome, "prompts");
|
const promptsDir = path.join(codexHome, "prompts");
|
||||||
const promptName = `paseo-test-${process.pid}-${Date.now().toString(36)}`;
|
const promptName = `paseo-test-${process.pid}-${Date.now().toString(36)}`;
|
||||||
const promptPath = path.join(promptsDir, `${promptName}.md`);
|
const promptPath = path.join(promptsDir, `${promptName}.md`);
|
||||||
@@ -648,6 +714,7 @@ describe("Codex app-server provider (integration)", () => {
|
|||||||
await session.close();
|
await session.close();
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
cleanupCodexHome();
|
||||||
cleanup();
|
cleanup();
|
||||||
rmSync(cwd, { recursive: true, force: true });
|
rmSync(cwd, { recursive: true, force: true });
|
||||||
rmSync(promptPath, { force: true });
|
rmSync(promptPath, { force: true });
|
||||||
@@ -660,7 +727,9 @@ describe("Codex app-server provider (integration)", () => {
|
|||||||
"slash prompt run streams live turn events (turn_started/turn_completed)",
|
"slash prompt run streams live turn events (turn_started/turn_completed)",
|
||||||
async () => {
|
async () => {
|
||||||
const cleanup = useTempCodexSessionDir();
|
const cleanup = useTempCodexSessionDir();
|
||||||
const codexHome = process.env.CODEX_HOME ?? path.join(os.homedir(), ".codex");
|
const { codexHome, cleanup: cleanupCodexHome } = useTempCodexHome(
|
||||||
|
"codex-stream-prompts-home-"
|
||||||
|
);
|
||||||
const promptsDir = path.join(codexHome, "prompts");
|
const promptsDir = path.join(codexHome, "prompts");
|
||||||
const promptName = `paseo-stream-${process.pid}-${Date.now().toString(36)}`;
|
const promptName = `paseo-stream-${process.pid}-${Date.now().toString(36)}`;
|
||||||
const promptPath = path.join(promptsDir, `${promptName}.md`);
|
const promptPath = path.join(promptsDir, `${promptName}.md`);
|
||||||
@@ -706,6 +775,7 @@ describe("Codex app-server provider (integration)", () => {
|
|||||||
await session.close();
|
await session.close();
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
cleanupCodexHome();
|
||||||
cleanup();
|
cleanup();
|
||||||
rmSync(cwd, { recursive: true, force: true });
|
rmSync(cwd, { recursive: true, force: true });
|
||||||
rmSync(promptPath, { force: true });
|
rmSync(promptPath, { force: true });
|
||||||
|
|||||||
Reference in New Issue
Block a user