import { describe, it, expect } from "vitest"; import { checkFallbackError, getQuotaCooldown } from "../../open-sse/services/accountFallback.js"; describe("checkFallbackError — request-scoped errors (P2)", () => { it("classifies context-overflow messages as request-scoped: no fallback, no cooldown", () => { const msgs = [ "This model's maximum context length is 16385 tokens", "prompt is too long: 250000 tokens > 200000 maximum", "input is too long for requested model", "Invalid parameter: max_tokens exceed model limit", "context_length_exceeded", "Please reduce the length of the messages", "Your request is too large for the context window", ]; for (const message of msgs) { const r = checkFallbackError(400, message); expect(r.shouldFallback, message).toBe(false); expect(r.requestScoped, message).toBe(true); expect(r.cooldownMs).toBe(0); } }); it("does NOT classify generic 4xx as request-scoped (legacy transient path preserved)", () => { const r = checkFallbackError(400, "Invalid value for 'temperature'"); expect(r.requestScoped).toBeUndefined(); expect(r.shouldFallback).toBe(true); expect(r.cooldownMs).toBeGreaterThan(0); }); it("rate-limit text still backs off with fallback", () => { const r = checkFallbackError(429, "rate limit exceeded", 0); expect(r.shouldFallback).toBe(true); expect(r.cooldownMs).toBe(getQuotaCooldown(1)); }); it("no credentials still falls back with long cooldown", () => { const r = checkFallbackError(403, "no credentials found for account"); expect(r.shouldFallback).toBe(true); expect(r.cooldownMs).toBe(2 * 60 * 1000); }); });