2026-04-06 17:38:37 +09:00
|
|
|
import { describe, expect, test } from "bun:test"
|
|
|
|
|
|
|
|
|
|
import { classifyErrorType, isRetryableError } from "./error-classifier"
|
|
|
|
|
|
|
|
|
|
describe("runtime-fallback quota error regressions", () => {
|
2026-04-21 18:17:22 +09:00
|
|
|
test("classifies subscription quota errors as quota_exceeded and triggers fallback", () => {
|
2026-04-06 17:38:37 +09:00
|
|
|
//#given
|
|
|
|
|
const error = {
|
|
|
|
|
name: "AI_APICallError",
|
|
|
|
|
message: "Subscription quota exceeded. You can continue using free models.",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const errorType = classifyErrorType(error)
|
|
|
|
|
const retryable = isRetryableError(error, [429, 500, 502, 503, 504])
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(errorType).toBe("quota_exceeded")
|
2026-04-21 18:17:22 +09:00
|
|
|
// quota exhaustion should trigger fallback to the next model
|
|
|
|
|
expect(retryable).toBe(true)
|
2026-04-06 17:38:37 +09:00
|
|
|
})
|
|
|
|
|
|
2026-04-21 18:17:22 +09:00
|
|
|
test("treats HTTP 402 payment required as fallback-eligible", () => {
|
2026-04-06 17:38:37 +09:00
|
|
|
//#given
|
|
|
|
|
const error = { statusCode: 402, message: "Payment Required" }
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const retryable = isRetryableError(error, [429, 500, 502, 503, 504])
|
|
|
|
|
|
|
|
|
|
//#then
|
2026-04-21 18:17:22 +09:00
|
|
|
// payment failure triggers fallback to a different provider/model
|
|
|
|
|
expect(retryable).toBe(true)
|
2026-04-06 17:38:37 +09:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test("keeps HTTP 429 rate limit retryable", () => {
|
|
|
|
|
//#given
|
|
|
|
|
const error = { statusCode: 429, message: "Too Many Requests: rate limit reached" }
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const retryable = isRetryableError(error, [429, 500, 502, 503, 504])
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(retryable).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
2026-04-21 18:17:22 +09:00
|
|
|
test("classifies quota error names as quota_exceeded and triggers fallback", () => {
|
2026-04-06 17:38:37 +09:00
|
|
|
//#given
|
|
|
|
|
const error = { name: "QuotaExceededError", message: "Request failed." }
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const errorType = classifyErrorType(error)
|
|
|
|
|
const retryable = isRetryableError(error, [429, 500, 502, 503, 504])
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(errorType).toBe("quota_exceeded")
|
2026-04-21 18:17:22 +09:00
|
|
|
// quota errors trigger fallback to next configured model
|
|
|
|
|
expect(retryable).toBe(true)
|
2026-04-06 17:38:37 +09:00
|
|
|
})
|
2026-05-11 00:43:58 +08:00
|
|
|
|
|
|
|
|
test("classifies Volcano Engine 'exceeded the usage quota' as retryable", () => {
|
|
|
|
|
//#given
|
|
|
|
|
const error = {
|
|
|
|
|
name: "SessionRetry",
|
|
|
|
|
message: "You have exceeded the 5-hour usage quota. It will reset at 2026-05-11 01:20:12 +0800 CST. We recommend using a different model.",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const retryable = isRetryableError(error, [429, 500, 502, 503, 504])
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
// Volcano Engine quota errors trigger fallback to the next model
|
|
|
|
|
expect(retryable).toBe(true)
|
|
|
|
|
})
|
2026-04-06 17:38:37 +09:00
|
|
|
})
|