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, { error: "Storage full", code: "payment_required", meter: "storage.r2", reason: "plan_limit", billingPath: "/settings/billing", }), ).toBe(true); expect(typeof useUpsell).toBe("function"); }); });