063eb09750
Co-authored-by: Danny Kim <temeddix@gmail.com> Co-committed-by: Danny Kim <temeddix@gmail.com>
256 lines
7.1 KiB
TypeScript
256 lines
7.1 KiB
TypeScript
import { expect, test } from "vite-plus/test";
|
|
|
|
import {
|
|
AgentAuthError,
|
|
agentEnvironment,
|
|
failureFooter,
|
|
type ProfileFailure,
|
|
profileFooter,
|
|
runProfiles,
|
|
sanitizedEnvironment,
|
|
} from "./profiles.ts";
|
|
|
|
test("formats profile attribution as a Markdown quote", () => {
|
|
expect(
|
|
profileFooter({ type: "OPENCODE", model: "openrouter/qwen/qwen3.8-27b" }),
|
|
).toBe("> Profile: `opencode` · `openrouter/qwen/qwen3.8-27b`");
|
|
});
|
|
|
|
test("falls through only after usage exhaustion", async () => {
|
|
const visited: string[] = [];
|
|
const result = await runProfiles(
|
|
[
|
|
{ type: "CLAUDE", model: "claude-sonnet-5", token: "first" },
|
|
{ type: "CODEX", model: "gpt-5.6-terra" },
|
|
],
|
|
async (profile) => {
|
|
visited.push(profile.type);
|
|
if (profile.type === "CLAUDE") {
|
|
throw new Error("You've hit your usage limit");
|
|
}
|
|
const resolved = await Promise.resolve("done");
|
|
return resolved;
|
|
},
|
|
);
|
|
|
|
expect(result).toBe("done");
|
|
expect(visited).toStrictEqual(["CLAUDE", "CODEX"]);
|
|
});
|
|
|
|
test("recognizes Claude's weekly limit message", async () => {
|
|
const result = await runProfiles(
|
|
[
|
|
{ type: "CLAUDE", model: "claude-sonnet-5", token: "first" },
|
|
{ type: "CODEX", model: "gpt-5.6-terra" },
|
|
],
|
|
async (profile) => {
|
|
if (profile.type === "CLAUDE") {
|
|
throw new Error("You've hit your weekly limit · resets 12am (UTC)");
|
|
}
|
|
const resolved = await Promise.resolve("fallback");
|
|
return resolved;
|
|
},
|
|
);
|
|
|
|
expect(result).toBe("fallback");
|
|
});
|
|
|
|
test("recognizes provider API exhaustion status", async () => {
|
|
const results = await Promise.all(
|
|
["API Error: 402 Insufficient funds", "HTTP error: 429"].map(
|
|
async (message) => {
|
|
const result = await runProfiles(
|
|
[
|
|
{ type: "CLAUDE", model: "claude-sonnet-5", token: "first" },
|
|
{ type: "CODEX", model: "gpt-5.6-terra" },
|
|
],
|
|
async (profile) => {
|
|
if (profile.type === "CLAUDE") {
|
|
throw new Error(message);
|
|
}
|
|
const resolved = await Promise.resolve("fallback");
|
|
return resolved;
|
|
},
|
|
);
|
|
return result;
|
|
},
|
|
),
|
|
);
|
|
|
|
expect(results).toStrictEqual(["fallback", "fallback"]);
|
|
});
|
|
|
|
test("falls through after a sign-in failure and records each try", async () => {
|
|
const failures: ProfileFailure[] = [];
|
|
const result = await runProfiles(
|
|
[
|
|
{ type: "CODEX", model: "gpt-5.6-terra" },
|
|
{ type: "CLAUDE", model: "claude-sonnet-5", token: "first" },
|
|
{ type: "OPENCODE", model: "openrouter/qwen/qwen3.8-27b" },
|
|
],
|
|
async (profile) => {
|
|
if (profile.type === "CODEX") {
|
|
throw new AgentAuthError("codex sign-in failed");
|
|
}
|
|
if (profile.type === "CLAUDE") {
|
|
throw new Error("API Error: 429");
|
|
}
|
|
const resolved = await Promise.resolve("fallback");
|
|
return resolved;
|
|
},
|
|
failures,
|
|
);
|
|
|
|
expect(result).toBe("fallback");
|
|
expect(failures).toStrictEqual([
|
|
{ type: "CODEX", model: "gpt-5.6-terra", reason: "SIGN_IN_FAILED" },
|
|
{ type: "CLAUDE", model: "claude-sonnet-5", reason: "USAGE_EXHAUSTED" },
|
|
]);
|
|
expect(failureFooter(failures)).toStrictEqual(
|
|
"> Tried: `codex` · `gpt-5.6-terra` (sign-in failed), " +
|
|
"`claude` · `claude-sonnet-5` (usage exhausted)",
|
|
);
|
|
});
|
|
|
|
test("throws the last sign-in failure when every profile fails", async () => {
|
|
const failures: ProfileFailure[] = [];
|
|
await expect(
|
|
runProfiles(
|
|
[
|
|
{ type: "CODEX", model: "gpt-5.6-terra" },
|
|
{ type: "CLAUDE", model: "claude-sonnet-5", token: "first" },
|
|
],
|
|
(profile) => {
|
|
throw new AgentAuthError(`${profile.type} sign-in failed`);
|
|
},
|
|
failures,
|
|
),
|
|
).rejects.toThrow(
|
|
expect.toSatisfy(
|
|
(error: unknown) =>
|
|
error instanceof AgentAuthError &&
|
|
error.message.includes("CLAUDE sign-in failed"),
|
|
),
|
|
);
|
|
expect(failures.map(({ reason }) => reason)).toStrictEqual([
|
|
"SIGN_IN_FAILED",
|
|
"SIGN_IN_FAILED",
|
|
]);
|
|
});
|
|
|
|
test("preserves an ordinary agent failure", async () => {
|
|
const visited: string[] = [];
|
|
await expect(
|
|
runProfiles(
|
|
[
|
|
{ type: "CLAUDE", model: "claude-sonnet-5", token: "first" },
|
|
{ type: "CODEX", model: "gpt-5.6-terra" },
|
|
],
|
|
(profile) => {
|
|
visited.push(profile.type);
|
|
throw new Error("tests failed");
|
|
},
|
|
),
|
|
).rejects.toThrow("tests failed");
|
|
expect(visited).toStrictEqual(["CLAUDE"]);
|
|
});
|
|
|
|
test("does not mistake incidental quota text for exhaustion", async () => {
|
|
const visited: string[] = [];
|
|
await expect(
|
|
runProfiles(
|
|
[
|
|
{ type: "CLAUDE", model: "claude-sonnet-5", token: "first" },
|
|
{ type: "CODEX", model: "gpt-5.6-terra" },
|
|
],
|
|
(profile) => {
|
|
visited.push(profile.type);
|
|
throw new Error("quota parser test failed at line 429");
|
|
},
|
|
),
|
|
).rejects.toThrow("quota parser test failed");
|
|
expect(visited).toStrictEqual(["CLAUDE"]);
|
|
});
|
|
|
|
test("exposes only the selected profile credential to the agent", () => {
|
|
const base = new Map([
|
|
["PATH", "/usr/bin"],
|
|
["HOME", "/home/bot"],
|
|
["UNRELATED_SECRET", "must-not-reach-agent"],
|
|
["BOT_AGENTS_MASTER_KEY", "master"],
|
|
["PROFILES", "contains every secret"],
|
|
["REVIEWER_TOKEN", "reviewer"],
|
|
["CLAUDE_CODE_OAUTH_TOKEN", "old-claude"],
|
|
["ANTHROPIC_API_KEY", "old-anthropic-key"],
|
|
["ANTHROPIC_AUTH_TOKEN", "old-anthropic-token"],
|
|
["OPENAI_API_KEY", "old-openai"],
|
|
["CODEX_API_KEY", "old-codex-key"],
|
|
["CODEX_ACCESS_TOKEN", "old-codex-token"],
|
|
]);
|
|
const profile = {
|
|
index: 0,
|
|
type: "CLAUDE" as const,
|
|
model: "claude-sonnet-5",
|
|
uid: 20_000,
|
|
gid: 20_000,
|
|
home: "/runtime/profile-0",
|
|
tokenFile: "/runtime/profile-0/token",
|
|
};
|
|
|
|
expect(agentEnvironment(base, profile, "selected")).toStrictEqual(
|
|
new Map([
|
|
["PATH", "/usr/bin"],
|
|
["HOME", "/runtime/profile-0"],
|
|
["CLAUDE_CODE_OAUTH_TOKEN", "selected"],
|
|
]),
|
|
);
|
|
expect(sanitizedEnvironment(base)).toStrictEqual(
|
|
new Map([
|
|
["PATH", "/usr/bin"],
|
|
["HOME", "/home/bot"],
|
|
]),
|
|
);
|
|
});
|
|
|
|
test("uses a private Codex home without inventing an API credential", () => {
|
|
const profile = {
|
|
index: 1,
|
|
type: "CODEX" as const,
|
|
model: "gpt-5.6-terra",
|
|
uid: 20_001,
|
|
gid: 20_001,
|
|
home: "/runtime/profile-1",
|
|
tokenFile: null,
|
|
};
|
|
|
|
expect(agentEnvironment(new Map([["PATH", "/bin"]]), profile)).toStrictEqual(
|
|
new Map([
|
|
["PATH", "/bin"],
|
|
["HOME", "/runtime/profile-1"],
|
|
["CODEX_HOME", "/runtime/profile-1/.codex"],
|
|
]),
|
|
);
|
|
});
|
|
|
|
test("exposes an OpenRouter key only to the OpenCode profile", () => {
|
|
const profile = {
|
|
index: 2,
|
|
type: "OPENCODE" as const,
|
|
model: "openrouter/qwen/qwen3.8-27b",
|
|
uid: 20_002,
|
|
gid: 20_002,
|
|
home: "/runtime/profile-2",
|
|
tokenFile: "/runtime/profile-2/token",
|
|
};
|
|
|
|
expect(
|
|
agentEnvironment(new Map([["PATH", "/bin"]]), profile, "selected"),
|
|
).toStrictEqual(
|
|
new Map([
|
|
["PATH", "/bin"],
|
|
["HOME", "/runtime/profile-2"],
|
|
["OPENROUTER_API_KEY", "selected"],
|
|
]),
|
|
);
|
|
});
|