| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import { describe, test, expect, vi, beforeEach } from "vitest"; |
|
|
| const validateBearerToken = vi.fn(); |
| vi.mock("../auth-session", () => ({ |
| validateBearerToken: (...a: unknown[]) => validateBearerToken(...a), |
| })); |
|
|
| const getEntitlements = vi.fn(); |
| vi.mock("../_shared/entitlement-check", async (importActual) => { |
| const actual = await importActual<typeof import("../_shared/entitlement-check")>(); |
| return { |
| ...actual, |
| getEntitlements: (...a: unknown[]) => getEntitlements(...a), |
| }; |
| }); |
|
|
| |
| process.env.WIDGET_AGENT_KEY = "server-widget-key"; |
| process.env.PRO_WIDGET_KEY = "server-pro-key"; |
|
|
| const { default: handler } = await import("../../api/widget-agent"); |
|
|
| const FREE_FEATURES = { |
| tier: 0, |
| apiAccess: false, |
| apiRateLimit: 0, |
| maxDashboards: 1, |
| prioritySupport: false, |
| exportFormats: [] as string[], |
| }; |
|
|
| function bearerRequest(): Request { |
| return new Request("https://www.worldmonitor.app/api/widget-agent", { |
| method: "POST", |
| headers: { |
| Origin: "https://www.worldmonitor.app", |
| "Content-Type": "application/json", |
| Authorization: "Bearer test-session-token", |
| }, |
| body: JSON.stringify({ prompt: "Build a widget", mode: "create", tier: "basic" }), |
| }); |
| } |
|
|
| beforeEach(() => { |
| validateBearerToken.mockReset(); |
| getEntitlements.mockReset(); |
| validateBearerToken.mockResolvedValue({ valid: true, userId: "user_wa_billing", role: "free" }); |
| }); |
|
|
| describe("widget-agent billing-verification denial (#4771)", () => { |
| test("renewal_verification_pending: 503 + code + marker header + Retry-After", async () => { |
| getEntitlements.mockResolvedValue({ |
| planKey: "pro_monthly", |
| features: FREE_FEATURES, |
| validUntil: 0, |
| billingStatus: "renewal_verification_pending", |
| retryAfterSeconds: 60, |
| }); |
|
|
| const res = await handler(bearerRequest()); |
| expect(res.status).toBe(503); |
| expect(res.headers.get("X-Billing-Verification")).toBe("renewal_verification_pending"); |
| expect(res.headers.get("Retry-After")).toBeTruthy(); |
| expect(res.headers.get("Access-Control-Allow-Origin")).toBe("https://www.worldmonitor.app"); |
| const body = await res.json(); |
| expect(body.code).toBe("renewal_verification_pending"); |
| }); |
|
|
| test("subscription_lapsed: confirmed denial stays a 403 with the stable code", async () => { |
| getEntitlements.mockResolvedValue({ |
| planKey: "pro_monthly", |
| features: FREE_FEATURES, |
| validUntil: 0, |
| billingStatus: "subscription_lapsed", |
| }); |
|
|
| const res = await handler(bearerRequest()); |
| expect(res.status).toBe(403); |
| expect(res.headers.get("X-Billing-Verification")).toBe("subscription_lapsed"); |
| const body = await res.json(); |
| expect(body.code).toBe("subscription_lapsed"); |
| }); |
|
|
| test("verificationUnavailable marker: retryable 503, not a hard denial", async () => { |
| getEntitlements.mockResolvedValue({ |
| planKey: "free", |
| features: FREE_FEATURES, |
| validUntil: 0, |
| verificationUnavailable: true, |
| }); |
|
|
| const res = await handler(bearerRequest()); |
| expect(res.status).toBe(503); |
| expect(res.headers.get("X-Billing-Verification")).toBe("entitlement_verification_unavailable"); |
| expect(res.headers.get("Retry-After")).toBeTruthy(); |
| const body = await res.json(); |
| expect(body.code).toBe("entitlement_verification_unavailable"); |
| }); |
|
|
| test("plain free-tier row: legacy generic 403, no billing marker", async () => { |
| getEntitlements.mockResolvedValue({ |
| planKey: "free", |
| features: FREE_FEATURES, |
| validUntil: 0, |
| }); |
|
|
| const res = await handler(bearerRequest()); |
| expect(res.status).toBe(403); |
| expect(res.headers.get("X-Billing-Verification")).toBeNull(); |
| const body = await res.json(); |
| expect(body.error).toBe("Pro subscription required"); |
| expect(body.code).toBeUndefined(); |
| }); |
|
|
| test("null entitlement lookup: fail-closed generic 403, no billing marker", async () => { |
| getEntitlements.mockResolvedValue(null); |
|
|
| const res = await handler(bearerRequest()); |
| expect(res.status).toBe(403); |
| expect(res.headers.get("X-Billing-Verification")).toBeNull(); |
| const body = await res.json(); |
| expect(body.error).toBe("Pro subscription required"); |
| }); |
| }); |
|
|