Files
2026-07-13 13:39:12 +08:00

111 lines
4.9 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
// ─── Unit tests for anti-thundering herd logic ──────────────────────────────
// These tests verify the accountFallback layer (provider profiles + backoff).
// The auth.js mutex is an integration-level concern (DB-dependent) and is covered
// by its own manual verification.
const { checkFallbackError, getProviderProfile } =
await import("../../open-sse/services/accountFallback.ts");
const { BACKOFF_CONFIG, PROVIDER_PROFILES, DEFAULT_API_LIMITS, COOLDOWN_MS, RateLimitReason } =
await import("../../open-sse/config/constants.ts");
const { getProviderCategory } = await import("../../open-sse/config/providerRegistry.ts");
// ─── OAuth vs API Profile Differentiation ───────────────────────────────────
test("OAuth profile has stricter circuit breaker (lower threshold)", () => {
const oauth = PROVIDER_PROFILES.oauth;
const apikey = PROVIDER_PROFILES.apikey;
assert.ok(
oauth.circuitBreakerThreshold < apikey.circuitBreakerThreshold,
"OAuth should open circuit faster"
);
});
test("API profile has shorter transient cooldown", () => {
const apikey = PROVIDER_PROFILES.apikey;
const oauth = PROVIDER_PROFILES.oauth;
assert.ok(
apikey.transientCooldown < oauth.transientCooldown,
"API providers should have shorter base cooldown"
);
});
// ─── Backoff Ceiling Tests (prevents infinite growth) ───────────────────────
test("Exponential backoff clamps to the configured maxBackoffLevel", () => {
const result = checkFallbackError(502, "", 20, null, null);
assert.equal(result.newBackoffLevel, BACKOFF_CONFIG.maxLevel);
assert.equal(
result.cooldownMs,
COOLDOWN_MS.transientInitial * Math.pow(2, BACKOFF_CONFIG.maxLevel)
);
});
test("API provider backoff level caps at profile maxBackoffLevel", () => {
const maxLevel = PROVIDER_PROFILES.apikey.maxBackoffLevel;
const result = checkFallbackError(502, "", maxLevel, null, "groq");
assert.equal(result.newBackoffLevel, maxLevel, "Should not exceed max level");
});
test("OAuth provider backoff level caps at profile maxBackoffLevel", () => {
const maxLevel = PROVIDER_PROFILES.oauth.maxBackoffLevel;
const result = checkFallbackError(502, "", maxLevel, null, "claude");
assert.equal(result.newBackoffLevel, maxLevel, "Should not exceed max level");
});
// ─── Thundering Herd Simulation (unit level) ────────────────────────────────
// This tests that 5 concurrent 502 calls to checkFallbackError with the same
// backoff level produce CONSISTENT results (same cooldown, same new level).
test("5 concurrent 502 calls at same backoff level produce identical results", () => {
const results = [];
for (let i = 0; i < 5; i++) {
results.push(checkFallbackError(502, "Bad Gateway", 0, null, "groq"));
}
// All should have same cooldown and new backoff level
const first = results[0];
for (const r of results) {
assert.equal(r.cooldownMs, first.cooldownMs, "Cooldowns should be identical");
assert.equal(r.newBackoffLevel, first.newBackoffLevel, "Backoff levels should be identical");
assert.equal(r.reason, RateLimitReason.SERVER_ERROR);
}
});
test("Progressive backoff: Level 0→1→2 produce increasing cooldowns", () => {
const r0 = checkFallbackError(502, "", 0, null, "groq");
const r1 = checkFallbackError(502, "", 1, null, "groq");
const r2 = checkFallbackError(502, "", 2, null, "groq");
assert.ok(r1.cooldownMs > r0.cooldownMs, "Level 1 should be longer than level 0");
assert.ok(r2.cooldownMs > r1.cooldownMs, "Level 2 should be longer than level 1");
assert.equal(r0.newBackoffLevel, 1);
assert.equal(r1.newBackoffLevel, 2);
assert.equal(r2.newBackoffLevel, 3);
});
// ─── DEFAULT_API_LIMITS Configuration ───────────────────────────────────────
test("DEFAULT_API_LIMITS has reasonable values", () => {
assert.ok(DEFAULT_API_LIMITS.requestsPerMinute >= 60, "RPM should be at least 60");
assert.ok(DEFAULT_API_LIMITS.minTimeBetweenRequests >= 100, "minTime should be at least 100ms");
assert.ok(DEFAULT_API_LIMITS.concurrentRequests >= 5, "maxConcurrent should be at least 5");
});
test("API providers auto-enrolled are grouped correctly", () => {
// Spot check: these should all be apikey
const apiProviders = ["groq", "fireworks", "cerebras", "openai", "anthropic"];
const oauthProviders = ["claude", "codex", "github", "antigravity"];
for (const p of apiProviders) {
assert.equal(getProviderCategory(p), "apikey", `${p} should be apikey`);
}
for (const p of oauthProviders) {
assert.equal(getProviderCategory(p), "oauth", `${p} should be oauth`);
}
});