test: share codex app-server client fixtures

This commit is contained in:
Peter Steinberger
2026-04-20 16:46:00 +01:00
parent 9fe066b37a
commit 68b7666d7c
2 changed files with 41 additions and 103 deletions

View File

@@ -1,5 +1,5 @@
import { EventEmitter } from "node:events";
import { PassThrough, Writable } from "node:stream";
import { PassThrough } from "node:stream";
import { afterEach, describe, expect, it, vi } from "vitest";
import {
__testing,
@@ -9,42 +9,23 @@ import {
readCodexVersionFromUserAgent,
} from "./client.js";
import { resetSharedCodexAppServerClientForTests } from "./shared-client.js";
function createClientHarness() {
const stdout = new PassThrough();
const stderr = new PassThrough();
const writes: string[] = [];
const stdin = new Writable({
write(chunk, _encoding, callback) {
writes.push(chunk.toString());
callback();
},
});
const process = Object.assign(new EventEmitter(), {
stdin,
stdout,
stderr,
killed: false,
kill: vi.fn(() => {
process.killed = true;
}),
});
// fromTransportForTests speaks the same newline-delimited JSON-RPC as the
// spawned app-server, but keeps the process lifecycle fully observable.
const client = CodexAppServerClient.fromTransportForTests(process);
return {
client,
process,
writes,
send(message: unknown) {
stdout.write(`${JSON.stringify(message)}\n`);
},
};
}
import { createClientHarness } from "./test-support.js";
describe("CodexAppServerClient", () => {
const clients: CodexAppServerClient[] = [];
function startInitialize() {
const harness = createClientHarness();
clients.push(harness.client);
const initializing = harness.client.initialize();
const outbound = JSON.parse(harness.writes[0] ?? "{}") as {
id?: number;
method?: string;
params?: { clientInfo?: { name?: string; title?: string; version?: string } };
};
return { harness, initializing, outbound };
}
afterEach(() => {
resetSharedCodexAppServerClientForTests();
vi.useRealTimers();
@@ -115,15 +96,7 @@ describe("CodexAppServerClient", () => {
});
it("initializes with the required client version", async () => {
const harness = createClientHarness();
clients.push(harness.client);
const initializing = harness.client.initialize();
const outbound = JSON.parse(harness.writes[0] ?? "{}") as {
id?: number;
method?: string;
params?: { clientInfo?: { name?: string; title?: string; version?: string } };
};
const { harness, initializing, outbound } = startInitialize();
harness.send({
id: outbound.id,
result: { userAgent: "openclaw/0.118.0 (macOS; test)" },
@@ -145,11 +118,7 @@ describe("CodexAppServerClient", () => {
});
it("blocks unsupported app-server versions during initialize", async () => {
const harness = createClientHarness();
clients.push(harness.client);
const initializing = harness.client.initialize();
const outbound = JSON.parse(harness.writes[0] ?? "{}") as { id?: number };
const { harness, initializing, outbound } = startInitialize();
harness.send({
id: outbound.id,
result: { userAgent: "openclaw/0.117.9 (macOS; test)" },
@@ -162,11 +131,7 @@ describe("CodexAppServerClient", () => {
});
it("blocks app-server initialize responses without a version", async () => {
const harness = createClientHarness();
clients.push(harness.client);
const initializing = harness.client.initialize();
const outbound = JSON.parse(harness.writes[0] ?? "{}") as { id?: number };
const { harness, initializing, outbound } = startInitialize();
harness.send({ id: outbound.id, result: {} });
await expect(initializing).rejects.toThrow(

View File

@@ -1,11 +1,32 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import type { AgentMessage } from "@mariozechner/pi-agent-core";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { mirrorCodexAppServerTranscript } from "./transcript-mirror.js";
let tempDir: string;
function assistantMessage(text: string, timestamp: number): AgentMessage {
return {
role: "assistant",
content: [{ type: "text", text }],
api: "openai-codex-responses",
provider: "openai-codex",
model: "gpt-5.4-codex",
usage: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: "stop",
timestamp,
};
}
describe("mirrorCodexAppServerTranscript", () => {
beforeEach(async () => {
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-transcript-"));
@@ -23,40 +44,8 @@ describe("mirrorCodexAppServerTranscript", () => {
sessionKey: "agent:main:session-1",
messages: [
{ role: "user", content: "hello", timestamp: 1 },
{
role: "assistant",
content: [{ type: "text", text: "Codex plan:\ninspect" }],
api: "openai-codex-responses",
provider: "openai-codex",
model: "gpt-5.4-codex",
usage: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: "stop",
timestamp: 2,
},
{
role: "assistant",
content: [{ type: "text", text: "hi" }],
api: "openai-codex-responses",
provider: "openai-codex",
model: "gpt-5.4-codex",
usage: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: "stop",
timestamp: 3,
},
assistantMessage("Codex plan:\ninspect", 2),
assistantMessage("hi", 3),
],
});
@@ -76,23 +65,7 @@ describe("mirrorCodexAppServerTranscript", () => {
const sessionFile = path.join(tempDir, "session.jsonl");
const messages = [
{ role: "user" as const, content: "hello", timestamp: 1 },
{
role: "assistant" as const,
content: [{ type: "text" as const, text: "hi" }],
api: "openai-codex-responses",
provider: "openai-codex",
model: "gpt-5.4-codex",
usage: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: "stop" as const,
timestamp: 2,
},
assistantMessage("hi", 2),
];
await mirrorCodexAppServerTranscript({