mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-18 01:01:36 +00:00
* fix(crestodian): keep onboarding RPCs restart-safe * fix(profiles): isolate approval state migrations * fix(crestodian): bypass configured gateway setup * test(crestodian): type onboarding mocks * fix(onboarding): require inference before Crestodian * fix(onboarding): enforce verified inference handoff * fix(macos): reset setup on gateway endpoint edits * chore(i18n): refresh native source inventory * fix(gateway): keep socket on request cancellation * test(packaging): require workspace templates * fix(onboarding): bind setup to verified inference * fix(onboarding): align inference gate contracts * fix(crestodian): classify concurrent policy rejection * test(crestodian): expect registry restoration * fix(onboarding): bind setup to configured gateways * fix(codex): preserve startup phase deadlines * test(crestodian): match fail-closed policy ordering * test(onboarding): assert bound gateway handoff * fix(codex): bind runtime resolution to spawn cwd * test(crestodian): assert policy rejection order * fix(cli): preserve gateway routing across restarts * fix(macos): fail closed during gateway edits * test(macos): cover gateway route generation races * chore: keep release notes out of onboarding PR * fix(ci): refresh onboarding generated checks * style(swift): align gateway channel formatting * fix(ci): refresh plugin SDK surface budgets * fix(ci): resync native string inventory * refactor(swift): split gateway channel support * test(doctor): isolate plugin compatibility registry * test(macos): isolate gateway onboarding fixtures * test(macos): assert gateway lease health ordering * fix(codex): reconcile computer-use startup changes
257 lines
9.1 KiB
TypeScript
257 lines
9.1 KiB
TypeScript
// Codex tests cover models plugin behavior.
|
|
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
|
|
import { CodexAppServerClient } from "./client.js";
|
|
import { createClientHarness } from "./test-support.js";
|
|
|
|
const mocks = vi.hoisted(() => {
|
|
const authBridge = {
|
|
applyAuthProfile: vi.fn(async () => undefined),
|
|
authProfileId: vi.fn((params?: { authProfileId?: string }) => params?.authProfileId),
|
|
fallbackApiKeyCacheKey: vi.fn(() => undefined),
|
|
startOptions: vi.fn(async ({ startOptions }) => startOptions),
|
|
};
|
|
const managedBinary = {
|
|
nativeCommand: vi.fn(() => undefined),
|
|
startOptions: vi.fn(async (startOptions) => startOptions),
|
|
};
|
|
const providerAuth = {
|
|
agentDir: vi.fn(() => "/tmp/openclaw-agent"),
|
|
};
|
|
return { authBridge, managedBinary, providerAuth };
|
|
});
|
|
|
|
vi.mock("./auth-bridge.js", () => ({
|
|
applyCodexAppServerAuthProfile: mocks.authBridge.applyAuthProfile,
|
|
bridgeCodexAppServerStartOptions: mocks.authBridge.startOptions,
|
|
resolveCodexAppServerFallbackApiKeyCacheKey: mocks.authBridge.fallbackApiKeyCacheKey,
|
|
resolveCodexAppServerAuthProfileIdForAgent: mocks.authBridge.authProfileId,
|
|
resolveCodexAppServerHomeDir: (agentDir: string) => `${agentDir}/codex-home`,
|
|
}));
|
|
|
|
vi.mock("./managed-binary.js", () => ({
|
|
resolveManagedCodexAppServerStartOptions: mocks.managedBinary.startOptions,
|
|
resolveManagedCodexNativeCommand: mocks.managedBinary.nativeCommand,
|
|
}));
|
|
|
|
vi.mock("openclaw/plugin-sdk/agent-runtime", () => ({
|
|
resolveDefaultAgentDir: mocks.providerAuth.agentDir,
|
|
}));
|
|
|
|
let listCodexAppServerModels: typeof import("./models.js").listCodexAppServerModels;
|
|
let listAllCodexAppServerModels: typeof import("./models.js").listAllCodexAppServerModels;
|
|
let resetSharedCodexAppServerClientForTests: typeof import("./shared-client.js").resetSharedCodexAppServerClientForTests;
|
|
|
|
describe("listCodexAppServerModels", () => {
|
|
beforeAll(async () => {
|
|
({ listCodexAppServerModels } = await import("./models.js"));
|
|
({ listAllCodexAppServerModels } = await import("./models.js"));
|
|
({ resetSharedCodexAppServerClientForTests } = await import("./shared-client.js"));
|
|
});
|
|
|
|
afterEach(() => {
|
|
resetSharedCodexAppServerClientForTests();
|
|
vi.restoreAllMocks();
|
|
mocks.authBridge.applyAuthProfile.mockClear();
|
|
mocks.authBridge.authProfileId.mockClear();
|
|
mocks.authBridge.authProfileId.mockImplementation(
|
|
(params?: { authProfileId?: string }) => params?.authProfileId,
|
|
);
|
|
mocks.authBridge.fallbackApiKeyCacheKey.mockClear();
|
|
mocks.authBridge.fallbackApiKeyCacheKey.mockReturnValue(undefined);
|
|
mocks.authBridge.startOptions.mockClear();
|
|
mocks.managedBinary.startOptions.mockClear();
|
|
mocks.managedBinary.startOptions.mockImplementation(async (startOptions) => startOptions);
|
|
mocks.managedBinary.nativeCommand.mockClear();
|
|
mocks.managedBinary.nativeCommand.mockReturnValue(undefined);
|
|
mocks.providerAuth.agentDir.mockClear();
|
|
});
|
|
|
|
it("lists app-server models through the typed helper", async () => {
|
|
const harness = createClientHarness();
|
|
const startSpy = vi.spyOn(CodexAppServerClient, "start").mockReturnValue(harness.client);
|
|
|
|
const listPromise = listCodexAppServerModels({ limit: 12, timeoutMs: 1000 });
|
|
await vi.waitFor(() => expect(harness.writes.length).toBeGreaterThanOrEqual(1));
|
|
const initialize = JSON.parse(harness.writes[0] ?? "{}") as { id?: number };
|
|
harness.send({
|
|
id: initialize.id,
|
|
result: { userAgent: "openclaw/0.143.0 (macOS; test)" },
|
|
});
|
|
await vi.waitFor(() => expect(harness.writes.length).toBeGreaterThanOrEqual(3));
|
|
const list = JSON.parse(harness.writes[2] ?? "{}") as { id?: number; method?: string };
|
|
expect(list.method).toBe("model/list");
|
|
|
|
harness.send({
|
|
id: list.id,
|
|
result: {
|
|
data: [
|
|
{
|
|
id: "gpt-5.4",
|
|
model: "gpt-5.4",
|
|
upgrade: null,
|
|
upgradeInfo: null,
|
|
availabilityNux: null,
|
|
displayName: "gpt-5.4",
|
|
description: "GPT-5.4",
|
|
hidden: false,
|
|
inputModalities: ["text", "image"],
|
|
supportedReasoningEfforts: [
|
|
{ reasoningEffort: "low", description: "fast" },
|
|
{ reasoningEffort: "xhigh", description: "deep" },
|
|
],
|
|
defaultReasoningEffort: "medium",
|
|
supportsPersonality: false,
|
|
additionalSpeedTiers: [],
|
|
isDefault: true,
|
|
},
|
|
],
|
|
nextCursor: null,
|
|
},
|
|
});
|
|
|
|
await expect(listPromise).resolves.toEqual({
|
|
models: [
|
|
{
|
|
id: "gpt-5.4",
|
|
model: "gpt-5.4",
|
|
displayName: "gpt-5.4",
|
|
description: "GPT-5.4",
|
|
hidden: false,
|
|
inputModalities: ["text", "image"],
|
|
supportedReasoningEfforts: ["low", "xhigh"],
|
|
defaultReasoningEffort: "medium",
|
|
isDefault: true,
|
|
},
|
|
],
|
|
});
|
|
harness.client.close();
|
|
startSpy.mockRestore();
|
|
});
|
|
|
|
it("lists all app-server model pages through one client", async () => {
|
|
const harness = createClientHarness();
|
|
const startSpy = vi.spyOn(CodexAppServerClient, "start").mockReturnValue(harness.client);
|
|
|
|
const listPromise = listAllCodexAppServerModels({ limit: 1, timeoutMs: 1000 });
|
|
await vi.waitFor(() => expect(harness.writes.length).toBeGreaterThanOrEqual(1));
|
|
const initialize = JSON.parse(harness.writes[0] ?? "{}") as { id?: number };
|
|
harness.send({
|
|
id: initialize.id,
|
|
result: { userAgent: "openclaw/0.143.0 (macOS; test)" },
|
|
});
|
|
await vi.waitFor(() => expect(harness.writes.length).toBeGreaterThanOrEqual(3));
|
|
const firstList = JSON.parse(harness.writes[2] ?? "{}") as {
|
|
id?: number;
|
|
params?: { cursor?: string | null };
|
|
};
|
|
expect(firstList.params?.cursor).toBeNull();
|
|
|
|
harness.send({
|
|
id: firstList.id,
|
|
result: {
|
|
data: [
|
|
{
|
|
id: "gpt-5.4",
|
|
model: "gpt-5.4",
|
|
upgrade: null,
|
|
upgradeInfo: null,
|
|
availabilityNux: null,
|
|
displayName: "gpt-5.4",
|
|
description: "GPT-5.4",
|
|
hidden: false,
|
|
inputModalities: ["text"],
|
|
supportedReasoningEfforts: [],
|
|
defaultReasoningEffort: "medium",
|
|
supportsPersonality: false,
|
|
additionalSpeedTiers: [],
|
|
isDefault: false,
|
|
},
|
|
],
|
|
nextCursor: "page-2",
|
|
},
|
|
});
|
|
await vi.waitFor(() => expect(harness.writes.length).toBeGreaterThanOrEqual(4));
|
|
const secondList = JSON.parse(harness.writes[3] ?? "{}") as {
|
|
id?: number;
|
|
params?: { cursor?: string | null };
|
|
};
|
|
expect(secondList.params?.cursor).toBe("page-2");
|
|
|
|
harness.send({
|
|
id: secondList.id,
|
|
result: {
|
|
data: [
|
|
{
|
|
id: "gpt-5.5",
|
|
model: "gpt-5.5",
|
|
upgrade: null,
|
|
upgradeInfo: null,
|
|
availabilityNux: null,
|
|
displayName: "gpt-5.5",
|
|
description: "GPT-5.5",
|
|
hidden: false,
|
|
inputModalities: ["text", "image"],
|
|
supportedReasoningEfforts: [],
|
|
defaultReasoningEffort: "medium",
|
|
supportsPersonality: false,
|
|
additionalSpeedTiers: [],
|
|
isDefault: false,
|
|
},
|
|
],
|
|
nextCursor: null,
|
|
},
|
|
});
|
|
|
|
const list = await listPromise;
|
|
expect(list.models.map((model) => model.id)).toEqual(["gpt-5.4", "gpt-5.5"]);
|
|
harness.client.close();
|
|
startSpy.mockRestore();
|
|
});
|
|
|
|
it("marks all-model listing truncated after the page cap", async () => {
|
|
const harness = createClientHarness();
|
|
const startSpy = vi.spyOn(CodexAppServerClient, "start").mockReturnValue(harness.client);
|
|
|
|
const listPromise = listAllCodexAppServerModels({ limit: 1, timeoutMs: 1000, maxPages: 1 });
|
|
await vi.waitFor(() => expect(harness.writes.length).toBeGreaterThanOrEqual(1));
|
|
const initialize = JSON.parse(harness.writes[0] ?? "{}") as { id?: number };
|
|
harness.send({
|
|
id: initialize.id,
|
|
result: { userAgent: "openclaw/0.143.0 (macOS; test)" },
|
|
});
|
|
await vi.waitFor(() => expect(harness.writes.length).toBeGreaterThanOrEqual(3));
|
|
const firstList = JSON.parse(harness.writes[2] ?? "{}") as { id?: number };
|
|
harness.send({
|
|
id: firstList.id,
|
|
result: {
|
|
data: [
|
|
{
|
|
id: "gpt-5.4",
|
|
model: "gpt-5.4",
|
|
upgrade: null,
|
|
upgradeInfo: null,
|
|
availabilityNux: null,
|
|
displayName: "gpt-5.4",
|
|
description: "GPT-5.4",
|
|
hidden: false,
|
|
inputModalities: ["text"],
|
|
supportedReasoningEfforts: [],
|
|
defaultReasoningEffort: "medium",
|
|
supportsPersonality: false,
|
|
additionalSpeedTiers: [],
|
|
isDefault: false,
|
|
},
|
|
],
|
|
nextCursor: "page-2",
|
|
},
|
|
});
|
|
|
|
const list = await listPromise;
|
|
expect(list.models.map((model) => model.id)).toEqual(["gpt-5.4"]);
|
|
expect(list.nextCursor).toBe("page-2");
|
|
expect(list.truncated).toBe(true);
|
|
harness.client.close();
|
|
startSpy.mockRestore();
|
|
});
|
|
});
|