test: expand auth fallback coverage
This commit is contained in:
@@ -144,6 +144,26 @@ describe("runWithModelFallback", () => {
|
|||||||
expect(run.mock.calls[1]?.[1]).toBe("claude-haiku-3-5");
|
expect(run.mock.calls[1]?.[1]).toBe("claude-haiku-3-5");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("falls back on lowercase credential errors", async () => {
|
||||||
|
const cfg = makeCfg();
|
||||||
|
const run = vi
|
||||||
|
.fn()
|
||||||
|
.mockRejectedValueOnce(new Error("no api key found for profile openai"))
|
||||||
|
.mockResolvedValueOnce("ok");
|
||||||
|
|
||||||
|
const result = await runWithModelFallback({
|
||||||
|
cfg,
|
||||||
|
provider: "openai",
|
||||||
|
model: "gpt-4.1-mini",
|
||||||
|
run,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.result).toBe("ok");
|
||||||
|
expect(run).toHaveBeenCalledTimes(2);
|
||||||
|
expect(run.mock.calls[1]?.[0]).toBe("anthropic");
|
||||||
|
expect(run.mock.calls[1]?.[1]).toBe("claude-haiku-3-5");
|
||||||
|
});
|
||||||
|
|
||||||
it("appends the configured primary as a last fallback", async () => {
|
it("appends the configured primary as a last fallback", async () => {
|
||||||
const cfg = makeCfg({
|
const cfg = makeCfg({
|
||||||
agents: {
|
agents: {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
buildBootstrapContextFiles,
|
buildBootstrapContextFiles,
|
||||||
classifyFailoverReason,
|
classifyFailoverReason,
|
||||||
formatAssistantErrorText,
|
formatAssistantErrorText,
|
||||||
|
isAuthErrorMessage,
|
||||||
isBillingErrorMessage,
|
isBillingErrorMessage,
|
||||||
isCloudCodeAssistFormatError,
|
isCloudCodeAssistFormatError,
|
||||||
isCompactionFailureError,
|
isCompactionFailureError,
|
||||||
@@ -122,6 +123,23 @@ describe("isBillingErrorMessage", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("isAuthErrorMessage", () => {
|
||||||
|
it("matches credential validation errors", () => {
|
||||||
|
const samples = [
|
||||||
|
'No credentials found for profile "anthropic:claude-cli".',
|
||||||
|
"No API key found for profile openai.",
|
||||||
|
];
|
||||||
|
for (const sample of samples) {
|
||||||
|
expect(isAuthErrorMessage(sample)).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ignores unrelated errors", () => {
|
||||||
|
expect(isAuthErrorMessage("rate limit exceeded")).toBe(false);
|
||||||
|
expect(isAuthErrorMessage("billing issue detected")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("isFailoverErrorMessage", () => {
|
describe("isFailoverErrorMessage", () => {
|
||||||
it("matches auth/rate/billing/timeout", () => {
|
it("matches auth/rate/billing/timeout", () => {
|
||||||
const samples = [
|
const samples = [
|
||||||
@@ -140,6 +158,8 @@ describe("isFailoverErrorMessage", () => {
|
|||||||
describe("classifyFailoverReason", () => {
|
describe("classifyFailoverReason", () => {
|
||||||
it("returns a stable reason", () => {
|
it("returns a stable reason", () => {
|
||||||
expect(classifyFailoverReason("invalid api key")).toBe("auth");
|
expect(classifyFailoverReason("invalid api key")).toBe("auth");
|
||||||
|
expect(classifyFailoverReason("no credentials found")).toBe("auth");
|
||||||
|
expect(classifyFailoverReason("no api key found")).toBe("auth");
|
||||||
expect(classifyFailoverReason("429 too many requests")).toBe("rate_limit");
|
expect(classifyFailoverReason("429 too many requests")).toBe("rate_limit");
|
||||||
expect(classifyFailoverReason("resource has been exhausted")).toBe(
|
expect(classifyFailoverReason("resource has been exhausted")).toBe(
|
||||||
"rate_limit",
|
"rate_limit",
|
||||||
|
|||||||
Reference in New Issue
Block a user