Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions tests/integration/combo-provider-exhaustion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const {
settingsDb,
} = harness;
const providersDb = await import("../../src/lib/db/providers.ts");
const quotaCache = await import("../../src/domain/quotaCache.ts");

function toPlainHeaders(headers: any): Record<string, string> {
if (!headers) return {};
Expand Down Expand Up @@ -272,11 +273,7 @@ test("cross-provider not affected: different providers both return 429, both are
name: "cross-provider-combo",
strategy: "priority",
config: { maxRetries: 0, retryDelayMs: 0, fallbackDelayMs: 0 },
models: [
"openai/gpt-4o-mini",
"anthropic/claude-sonnet-4-6",
"claude/claude-sonnet-4-6",
],
models: ["openai/gpt-4o-mini", "anthropic/claude-sonnet-4-6", "claude/claude-sonnet-4-6"],
});

let openaiCalls = 0;
Expand Down Expand Up @@ -413,6 +410,11 @@ test("exhaustion does not persist across requests: second request starts fresh (
assert.equal(body1.choices[0].message.content, "anthropic handled first request");
assert.equal(openaiCalls, 1, "first request: openai called once");
assert.equal(anthropicCalls, 1, "first request: anthropic called once");
assert.equal(
quotaCache.isQuotaExhaustedForRequest(openaiConn.id, "openai"),
true,
"the first 429 also records persistent account quota exhaustion"
);

// Clear the connection-level cooldown left by the first 429 — we are testing
// the request-scoped exhaustion sets, not the (persistent-by-design) cooldown.
Expand All @@ -425,6 +427,13 @@ test("exhaustion does not persist across requests: second request starts fresh (
backoffLevel: 0,
});

// Model a real account replenishment, not just the connection cooldown expiring.
// The quota cache is a separate persistent-by-design selection boundary.
quotaCache.setQuotaCache(openaiConn.id, "openai", {
requests: { remainingPercentage: 100, resetAt: null },
});
assert.equal(quotaCache.isQuotaExhaustedForRequest(openaiConn.id, "openai"), false);

// Second request: exhaustedProviders should be reset, openai should be tried again
requestCount = 1;
const response2 = await handleChat(
Expand Down Expand Up @@ -533,10 +542,7 @@ test("allow rate-limited connections after transient 429 on subsequent targets i
name: "rate-limit-reuse-combo",
strategy: "priority",
config: { maxRetries: 0, retryDelayMs: 0, fallbackDelayMs: 0 },
models: [
"openai/gpt-4o-mini",
"openai/gpt-3.5-turbo",
],
models: ["openai/gpt-4o-mini", "openai/gpt-3.5-turbo"],
});

let openaiCalls = 0;
Expand Down Expand Up @@ -655,4 +661,3 @@ test("emergency fallback never sends the failing provider's credentials to anoth
`no unauthenticated emergency call should reach upstream either: ${JSON.stringify(upstreamCalls)}`
);
});

Loading