|
1 | 1 | import { describe, expect, it } from "vitest"; |
2 | 2 | import { |
3 | 3 | classifyFailoverReason, |
| 4 | + classifyFailoverReasonFromHttpStatus, |
4 | 5 | isAuthErrorMessage, |
5 | 6 | isAuthPermanentErrorMessage, |
6 | 7 | isBillingErrorMessage, |
@@ -505,6 +506,87 @@ describe("image dimension errors", () => { |
505 | 506 | }); |
506 | 507 | }); |
507 | 508 |
|
| 509 | +describe("classifyFailoverReasonFromHttpStatus – 402 temporary limits", () => { |
| 510 | + it("reclassifies periodic usage limits as rate_limit", () => { |
| 511 | + const samples = [ |
| 512 | + "Monthly spend limit reached.", |
| 513 | + "Weekly usage limit exhausted.", |
| 514 | + "Daily limit reached, resets tomorrow.", |
| 515 | + ]; |
| 516 | + for (const sample of samples) { |
| 517 | + expect(classifyFailoverReasonFromHttpStatus(402, sample)).toBe("rate_limit"); |
| 518 | + } |
| 519 | + }); |
| 520 | + |
| 521 | + it("reclassifies org/workspace spend limits as rate_limit", () => { |
| 522 | + const samples = [ |
| 523 | + "Organization spending limit exceeded.", |
| 524 | + "Workspace spend limit reached.", |
| 525 | + "Organization limit exceeded for this billing period.", |
| 526 | + ]; |
| 527 | + for (const sample of samples) { |
| 528 | + expect(classifyFailoverReasonFromHttpStatus(402, sample)).toBe("rate_limit"); |
| 529 | + } |
| 530 | + }); |
| 531 | + |
| 532 | + it("keeps 402 as billing when explicit billing signals are present", () => { |
| 533 | + expect( |
| 534 | + classifyFailoverReasonFromHttpStatus( |
| 535 | + 402, |
| 536 | + "Your credit balance is too low. Monthly limit exceeded.", |
| 537 | + ), |
| 538 | + ).toBe("billing"); |
| 539 | + expect( |
| 540 | + classifyFailoverReasonFromHttpStatus( |
| 541 | + 402, |
| 542 | + "Insufficient credits. Organization limit reached.", |
| 543 | + ), |
| 544 | + ).toBe("billing"); |
| 545 | + expect( |
| 546 | + classifyFailoverReasonFromHttpStatus( |
| 547 | + 402, |
| 548 | + "The account associated with this API key has reached its maximum allowed monthly spending limit.", |
| 549 | + ), |
| 550 | + ).toBe("billing"); |
| 551 | + }); |
| 552 | + |
| 553 | + it("keeps long 402 payloads with explicit billing text as billing", () => { |
| 554 | + const longBillingPayload = `${"x".repeat(520)} insufficient credits. Monthly spend limit reached.`; |
| 555 | + expect(classifyFailoverReasonFromHttpStatus(402, longBillingPayload)).toBe("billing"); |
| 556 | + }); |
| 557 | + |
| 558 | + it("keeps 402 as billing without message or with generic message", () => { |
| 559 | + expect(classifyFailoverReasonFromHttpStatus(402, undefined)).toBe("billing"); |
| 560 | + expect(classifyFailoverReasonFromHttpStatus(402, "")).toBe("billing"); |
| 561 | + expect(classifyFailoverReasonFromHttpStatus(402, "Payment required")).toBe("billing"); |
| 562 | + }); |
| 563 | + |
| 564 | + it("matches raw 402 wrappers and status-split payloads for the same message", () => { |
| 565 | + const transientMessage = "Monthly spend limit reached. Please visit your billing settings."; |
| 566 | + expect(classifyFailoverReason(`402 Payment Required: ${transientMessage}`)).toBe("rate_limit"); |
| 567 | + expect(classifyFailoverReasonFromHttpStatus(402, transientMessage)).toBe("rate_limit"); |
| 568 | + |
| 569 | + const billingMessage = |
| 570 | + "The account associated with this API key has reached its maximum allowed monthly spending limit."; |
| 571 | + expect(classifyFailoverReason(`402 Payment Required: ${billingMessage}`)).toBe("billing"); |
| 572 | + expect(classifyFailoverReasonFromHttpStatus(402, billingMessage)).toBe("billing"); |
| 573 | + }); |
| 574 | + |
| 575 | + it("keeps explicit 402 rate-limit messages in the rate_limit lane", () => { |
| 576 | + const transientMessage = "rate limit exceeded"; |
| 577 | + expect(classifyFailoverReason(`HTTP 402 Payment Required: ${transientMessage}`)).toBe( |
| 578 | + "rate_limit", |
| 579 | + ); |
| 580 | + expect(classifyFailoverReasonFromHttpStatus(402, transientMessage)).toBe("rate_limit"); |
| 581 | + }); |
| 582 | + |
| 583 | + it("keeps plan-upgrade 402 limit messages in billing", () => { |
| 584 | + const billingMessage = "Your usage limit has been reached. Please upgrade your plan."; |
| 585 | + expect(classifyFailoverReason(`HTTP 402 Payment Required: ${billingMessage}`)).toBe("billing"); |
| 586 | + expect(classifyFailoverReasonFromHttpStatus(402, billingMessage)).toBe("billing"); |
| 587 | + }); |
| 588 | +}); |
| 589 | + |
508 | 590 | describe("classifyFailoverReason", () => { |
509 | 591 | it("classifies documented provider error messages", () => { |
510 | 592 | expect(classifyFailoverReason(OPENAI_RATE_LIMIT_MESSAGE)).toBe("rate_limit"); |
|
0 commit comments