51 lines · 1.8 KB
import { describe, expect, test } from "bun:test";
import { HttpError } from "~/server/errors";
import { httpErrorAdapter } from "./http-error-adapter";
import { paymentDetail, upsellFromResponse, useUpsell } from "./upsell";

describe("402 payment required", () => {
  test("the detail survives the server-to-client adapter", () => {
    const error = new HttpError(402, "Usage limit reached for AI calls", {
      code: "payment_required",
      meter: "ai.calls",
      reason: "plan_limit",
      manageable: true,
      account: { name: "acme", kind: "org" },
      billingPath: "/orgs/acme/billing",
      accountId: "internal-id",
    });
    const wire = httpErrorAdapter.toSerializable(error);
    const back = httpErrorAdapter.fromSerializable(wire as never);
    expect(paymentDetail(back)).toEqual({
      message: "Usage limit reached for AI calls",
      meter: "ai.calls",
      reason: "plan_limit",
      manageable: true,
      account: { name: "acme", kind: "org" },
      billingPath: "/orgs/acme/billing",
    });
    expect(JSON.stringify(wire)).not.toContain("internal-id");
  });

  test("other errors are not upsells", () => {
    expect(paymentDetail(new HttpError(403, "nope"))).toBeNull();
    expect(paymentDetail(new HttpError(402, "no detail"))).toBeNull();
    expect(paymentDetail(new HttpError(402, "other", { meter: "x" }))).toBeNull();
    expect(paymentDetail(new Error("plain"))).toBeNull();
  });

  test("plain JSON 402 responses open the upsell", () => {
    expect(upsellFromResponse(413, { error: "too big" })).toBe(false);
    expect(
      upsellFromResponse(402, {