@@ -0,0 +1,156 @@
|
||||
import { assertEquals, assertRejects, assertThrows } from "jsr:@std/assert@1";
|
||||
import {
|
||||
agentEnvironment,
|
||||
parseProfiles,
|
||||
runProfiles,
|
||||
sanitizedEnvironment,
|
||||
} from "./profiles.ts";
|
||||
|
||||
Deno.test("parses profiles in fallback order with model defaults", () => {
|
||||
assertEquals(
|
||||
parseProfiles(`
|
||||
- type: claude
|
||||
token: claude-token
|
||||
- type: codex
|
||||
model: gpt-custom
|
||||
`),
|
||||
[
|
||||
{ type: "claude", model: "claude-sonnet-5", token: "claude-token" },
|
||||
{ type: "codex", model: "gpt-custom" },
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("rejects an empty profile list", () => {
|
||||
assertThrows(() => parseProfiles("[]"), Error, "at least one profile");
|
||||
});
|
||||
|
||||
Deno.test("rejects a Claude profile without its setup token", () => {
|
||||
assertThrows(
|
||||
() => parseProfiles("- type: claude"),
|
||||
Error,
|
||||
"profiles[0].token",
|
||||
);
|
||||
});
|
||||
|
||||
Deno.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");
|
||||
},
|
||||
);
|
||||
|
||||
assertEquals(result, "done");
|
||||
assertEquals(visited, ["claude", "codex"]);
|
||||
});
|
||||
|
||||
Deno.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 limit · resets Monday");
|
||||
}
|
||||
return Promise.resolve("fallback");
|
||||
},
|
||||
);
|
||||
|
||||
assertEquals(result, "fallback");
|
||||
});
|
||||
|
||||
Deno.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");
|
||||
},
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
assertEquals(results, ["fallback", "fallback"]);
|
||||
});
|
||||
|
||||
Deno.test("preserves an ordinary agent failure", async () => {
|
||||
const visited: string[] = [];
|
||||
await assertRejects(
|
||||
() =>
|
||||
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");
|
||||
},
|
||||
),
|
||||
Error,
|
||||
"tests failed",
|
||||
);
|
||||
assertEquals(visited, ["claude"]);
|
||||
});
|
||||
|
||||
Deno.test("does not mistake incidental quota text for exhaustion", async () => {
|
||||
const visited: string[] = [];
|
||||
await assertRejects(
|
||||
() =>
|
||||
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");
|
||||
},
|
||||
),
|
||||
Error,
|
||||
"quota parser test failed",
|
||||
);
|
||||
assertEquals(visited, ["claude"]);
|
||||
});
|
||||
|
||||
Deno.test("exposes only the selected profile credential to the agent", () => {
|
||||
const base = {
|
||||
HOME: "/home/bot",
|
||||
PROFILES: "contains every secret",
|
||||
REVIEWER_TOKEN: "reviewer",
|
||||
BOT_TOKEN: "legacy",
|
||||
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",
|
||||
};
|
||||
|
||||
assertEquals(
|
||||
agentEnvironment(base, {
|
||||
type: "claude",
|
||||
model: "claude-sonnet-5",
|
||||
token: "selected",
|
||||
}),
|
||||
{ HOME: "/home/bot", CLAUDE_CODE_OAUTH_TOKEN: "selected" },
|
||||
);
|
||||
assertEquals(sanitizedEnvironment(base), { HOME: "/home/bot" });
|
||||
});
|
||||
Reference in New Issue
Block a user