import { expect, test } from "@playwright/test"; import { getUserFacingImageGenerationErrorMessage, getUserFacingRequestErrorMessage, isProviderBalanceError, isRateLimitError, } from "@/lib/request-error"; import { isImageFallbackModelBalanceCoolingDown } from "@/lib/ai/runtime/image-fallback-balance-cooldown"; test("a bare HTTP 429 is rate limiting, not insufficient balance", () => { const error = Object.assign(new Error("Too many requests (HTTP 429)"), { status: 429 }); expect(isProviderBalanceError(error)).toBe(false); expect(isRateLimitError(error)).toBe(true); expect(getUserFacingImageGenerationErrorMessage(error)).toContain("请求过于频繁"); expect(getUserFacingRequestErrorMessage(error)).toContain("请求过于频繁"); }); test("an explicit provider balance error takes precedence over HTTP 429", () => { const error = Object.assign(new Error("余额不足,请点击充值页面续费(HTTP 429)"), { status: 429 }); expect(isProviderBalanceError(error)).toBe(true); expect(isRateLimitError(error)).toBe(false); expect(getUserFacingImageGenerationErrorMessage(error)).toContain("服务商线路余额"); expect(getUserFacingImageGenerationErrorMessage(error)).toContain("与您的积分余额无关"); expect(getUserFacingImageGenerationErrorMessage(error)).not.toMatch(/^积分不足/); }); test("nested billing details and generic quota limits are kept distinct", () => { const nested = { status: 429, message: "request failed", data: { error: { message: "insufficient_balance" } } }; expect(isProviderBalanceError(nested)).toBe(true); expect(isRateLimitError(nested)).toBe(false); const usageLimit = new Error("Rate quota exceeded (HTTP 429)"); expect(isProviderBalanceError(usageLimit)).toBe(false); expect(isRateLimitError(usageLimit)).toBe(true); expect(isProviderBalanceError(new Error("该模型额度不足(HTTP 429)"))).toBe(true); }); test("a recent confirmed provider balance failure skips only fallback selection", () => { const now = Date.now(); const model = { healthLastEventType: "quota", healthLastError: "余额不足(HTTP 429)", healthLastFailureAt: new Date(now - 30_000), }; expect(isImageFallbackModelBalanceCoolingDown(model, now)).toBe(true); expect(isImageFallbackModelBalanceCoolingDown({ ...model, healthLastFailureAt: new Date(now - 3 * 60 * 60 * 1000) }, now)).toBe(false); expect(isImageFallbackModelBalanceCoolingDown({ ...model, healthLastEventType: "success" }, now)).toBe(false); });