Move the action from Deno to Node.js and Vite+
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
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" },
|
||||
],
|
||||
(profile) => {
|
||||
visited.push(profile.type);
|
||||
if (profile.type === "claude") {
|
||||
throw new Error("You've hit your usage limit");
|
||||
}
|
||||
return Promise.resolve("done");
|
||||
},
|
||||
);
|
||||
|
||||
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" },
|
||||
],
|
||||
(profile) => {
|
||||
if (profile.type === "claude") {
|
||||
throw new Error("You've hit your weekly limit · resets 12am (UTC)");
|
||||
}
|
||||
return Promise.resolve("fallback");
|
||||
},
|
||||
);
|
||||
|
||||
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((message) =>
|
||||
runProfiles(
|
||||
[
|
||||
{ type: "claude", model: "claude-sonnet-5", token: "first" },
|
||||
{ type: "codex", model: "gpt-5.6-terra" },
|
||||
],
|
||||
(profile) => {
|
||||
if (profile.type === "claude") throw new Error(message);
|
||||
return Promise.resolve("fallback");
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
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" },
|
||||
],
|
||||
(profile) => {
|
||||
if (profile.type === "codex") {
|
||||
throw new AgentAuthError("codex sign-in failed");
|
||||
}
|
||||
if (profile.type === "claude") throw new Error("API Error: 429");
|
||||
return Promise.resolve("fallback");
|
||||
},
|
||||
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(
|
||||
(e: unknown) => e instanceof AgentAuthError && e.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 = {
|
||||
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({
|
||||
PATH: "/usr/bin",
|
||||
HOME: "/runtime/profile-0",
|
||||
CLAUDE_CODE_OAUTH_TOKEN: "selected",
|
||||
});
|
||||
expect(sanitizedEnvironment(base)).toStrictEqual({
|
||||
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({ PATH: "/bin" }, profile)).toStrictEqual({
|
||||
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({ PATH: "/bin" }, profile, "selected")).toStrictEqual({
|
||||
PATH: "/bin",
|
||||
HOME: "/runtime/profile-2",
|
||||
OPENROUTER_API_KEY: "selected",
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user