Ctrl K
ComunidadGuías…

tests/e2e/billing-settings.spec.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { expect, test } from "@playwright/test";
import {
  buildE2EAuthHeaders,
  enableScopedE2EAuth,
  scopedPath,
} from "./helpers/auth";
import { capacitySummary } from "./helpers/billing";
import {
  fulfillJson,
  mockSettingsShell,
} from "./helpers/billing-settings-fixtures";

const TEAM_ID = "00000000-0000-4000-8000-000000000002";
const TEAM_SLUG = "acme";

test("personal Billing shows capacity and reviews an add-on change", async ({
  page,
}) => {
  await enableScopedE2EAuth(page);
  await mockSettingsShell(page);
  await page.route("**/api/billing/capacity", (route) =>
    fulfillJson(route, {
      ...capacitySummary({
        concurrencyPurchasesEnabled: true,
        hostedUsage: {
          includedRemainingCents: 0,
          purchasedRemainingCents: 1_000,
          openReservationsCents: 1_000,
          spendableCents: 0,
          grantResetsAt: "2026-09-17T12:00:00.000Z",
          purchasesFrozen: false,
        },
      }),
      billingDetails: {
        hasMoreInvoices: true,
        paymentMethod: {
          brand: "visa",
          last4: "4242",
          expMonth: 12,
          expYear: 2028,
        },
        invoices: [
          {
            id: "in_credit",
            number: "MPX-002",
            description: "$25 inference credit",
            status: "paid",
            amountCents: 2_500,
            currency: "usd",
            createdAt: "2026-08-17T12:00:00.000Z",
            hostedInvoiceUrl: "https://invoice.stripe.test/in_credit",
            invoicePdfUrl: "https://invoice.stripe.test/in_credit.pdf",
          },
          {
            id: "in_plan",
            number: "MPX-001",
            description: "Mogplex Plus",
            status: "paid",
            amountCents: 10_000,
            currency: "usd",
            createdAt: "2026-07-17T12:00:00.000Z",
            hostedInvoiceUrl: "https://invoice.stripe.test/in_plan",
            invoicePdfUrl: "https://invoice.stripe.test/in_plan.pdf",
          },
        ],
      },
    })
  );

  await page.route("**/api/billing/capacity/preview", async (route) => {
    expect(route.request().postDataJSON()).toEqual({
      lookupKey: "capacity_v2_concurrency_10_monthly",
      quantity: 2,
      effectiveAction: "increase",
    });
    await fulfillJson(route, {
      resource: "concurrency",
      lookupKey: "capacity_v2_concurrency_10_monthly",
      name: "Parallel agent runs +10",
      action: "increase",
      currentQuantity: 1,
      resultingQuantity: 2,
      currentAllowance: "35",
      resultingAllowance: "45",
      currentRecurringAmountCents: 500,
      resultingRecurringAmountCents: 1_000,
      recurringChangeCents: 500,
      amountDueNowCents: 250,
      currency: "usd",
      taxStatus: "calculated",
      effectiveAt: "2026-08-17T12:00:00.000Z",
      effectiveTiming: "after_payment",
      previewToken: "preview-token",
      expiresAt: "2026-08-17T12:10:00.000Z",
    });
  });
  await page.route("**/api/billing/capacity/checkout", (route) =>
    fulfillJson(route, {
      status: "submitted",
      paymentUrl: null,
      entitlementStatus: "pending_webhook",
    })
  );
  await page.route("**/api/billing/hosted-usage/checkout", (route) =>
    fulfillJson(route, {
      status: "checkout_created",
      url: scopedPath("settings?tab=billing&billing=hosted-usage-submitted"),
      creditCents: 1_000,
      subtotalCents: 1_000,
      currency: "usd",
      balanceStatus: "pending_webhook",
    })
  );
  await page.route("**/api/stripe/portal", (route) =>
    fulfillJson(route, { url: scopedPath("settings?tab=billing") })
  );

  await page.goto(scopedPath("settings?tab=billing"));

  await expect(page.getByRole("heading", { name: "Plus" })).toBeVisible();
  await expect(
    page.getByRole("heading", { name: "Add inference credit" })
  ).toBeVisible();
  const creditChoices = page.getByRole("group", {
    name: "Choose credit amount",
  });
  await expect(creditChoices.getByRole("button")).toHaveCount(7);
  await expect(
    creditChoices.getByRole("button", {
      name: "Pay $1, get $1 credit",
    })
  ).toBeVisible();
  await expect(page.getByText("Monthly plan cost")).toBeVisible();
  await expect(page.getByText("$100.00 / month")).toBeVisible();
  await expect(page.getByText("35 parallel agent runs")).toBeVisible();
  await expect(page.getByText("25 with plan, 10 add-on")).toBeVisible();
  await expect(page.getByText("Next due date")).toBeVisible();
  await expect(page.getByText("Sep 17, 2026")).toBeVisible();
  await expect(page.getByText("Visa ending in 4242")).toBeVisible();
  await expect(page.getByText("Expires 12/2028")).toBeVisible();
  await expect(
    page.getByRole("button", { name: "Update payment method" })
  ).toBeVisible();
  await expect(page.getByText("$25 inference credit")).toBeVisible();
  await expect(page.getByText("Mogplex Plus")).toBeVisible();
  await expect(
    page.getByRole("link", { name: "Invoice MPX-002" })
  ).toHaveAttribute("href", "https://invoice.stripe.test/in_credit");
  await expect(
    page.getByRole("button", { name: "View all invoices" })
  ).toBeVisible();
  await expect(
    page.getByRole("link", { name: "support@mogplex.com" })
  ).toHaveAttribute("href", "mailto:support@mogplex.com");
  await expect(
    page.getByRole("heading", { name: "Parallel agent runs" })
  ).toBeVisible();
  await expect(page.getByRole("heading", { name: "Storage" })).toBeVisible();
  await expect(page.getByText("Hosted usage", { exact: true })).toHaveCount(0);
  await expect(page.getByText("Retained data", { exact: true })).toHaveCount(0);
  await expect(page.getByText("Automatic burst", { exact: true })).toHaveCount(
    0
  );
  await expect(page.getByText("Not enforced", { exact: true })).toHaveCount(0);
  const headings = await page.locator("h2").allTextContents();
  expect(headings.slice(0, 2)).toEqual(["Plus", "Add inference credit"]);
  await expect(page.getByText("7 of 35")).toHaveCount(0);
  await expect(page.getByText("2.3 GB of 5 GB")).toHaveCount(0);
  await page.getByRole("tab", { name: "Usage", exact: true }).click();
  await expect(page.getByText(/Code review .*#285/)).toBeVisible();
  await page
    .getByRole("tab", { name: "Billing Settings", exact: true })
    .click();

  const inferenceCheckout = page.waitForRequest(
    "**/api/billing/hosted-usage/checkout"
  );
  await page.getByRole("button", { name: "Pay $10, get $10 credit" }).click();
  expect((await inferenceCheckout).postDataJSON()).toMatchObject({
    preset: "capacity_v2_hosted_usage_credit_10",
    returnPath: scopedPath("settings/billing"),
  });
  await expect(
    page.getByText("Payment submitted. Stripe will add the full credit amount.")
  ).toBeVisible();

  await page
    .getByRole("button", { name: "Manage Parallel agent runs +10" })
    .click();
  await page.getByRole("button", { name: "Increase quantity" }).click();
  await page.getByRole("button", { name: "Review change" }).click();
  await expect(page.getByText("35 to 45")).toBeVisible();
  await expect(page.getByText("$2.50")).toBeVisible();

  const confirmRequest = page.waitForRequest(
    "**/api/billing/capacity/checkout"
  );
  await page.getByRole("button", { name: "Confirm change" }).click();
  expect((await confirmRequest).postDataJSON()).toMatchObject({
    previewToken: "preview-token",
  });
  await expect(page.getByRole("dialog")).toHaveCount(0);

  const portalRequest = page.waitForRequest("**/api/stripe/portal");
  await page.getByRole("button", { name: "Update payment method" }).click();
  await portalRequest;
});

test("ordinary Billing hides plan-included capacity meters without an upsell", async ({
  page,
}) => {
  await enableScopedE2EAuth(page);
  await mockSettingsShell(page);
  await page.route("**/api/billing/capacity", (route) =>
    fulfillJson(
      route,
      capacitySummary({
        concurrencyPurchasesEnabled: false,
        concurrency: {
          active: 7,
          included: 25,
          addOn: 0,
          limit: 25,
          wouldBlock: false,
        },
        addOns: [],
      })
    )
  );

  await page.goto(scopedPath("settings?tab=billing"));

  await expect(page.getByText("Automatic burst", { exact: true })).toHaveCount(
    0
  );
  await expect(page.getByText("7 of 25")).toHaveCount(0);
  await expect(page.getByText("25 parallel agent runs")).toBeVisible();
  await expect(
    page.getByRole("heading", { name: "Storage add-ons" })
  ).toBeVisible();
  await expect(
    page.getByRole("button", { name: /Parallel agent runs \+/ })
  ).toHaveCount(0);
  await expect(
    page.getByRole("button", { name: "Add Storage +1 GB" })
  ).toBeVisible();
});

test("existing concurrency customers can reduce but not increase outside the pilot", async ({
  page,
}) => {
  await enableScopedE2EAuth(page);
  await mockSettingsShell(page);
  await page.route("**/api/billing/capacity", (route) =>
    fulfillJson(route, capacitySummary({ concurrencyPurchasesEnabled: false }))
  );

  await page.goto(scopedPath("settings?tab=billing"));
  await page
    .getByRole("button", { name: "Manage Parallel agent runs +10" })
    .click();

  await expect(
    page.getByText(
      "Extra parallel agent runs are not available for this account. You can keep or reduce your current quantity."
    )
  ).toBeVisible();
  await expect(
    page.getByRole("button", { name: "Increase quantity" })
  ).toBeDisabled();
  await expect(
    page.getByRole("button", { name: "Decrease quantity" })
  ).toBeEnabled();
});

test("company Billing omits capacity add-ons for a member", async ({
  page,
}) => {
  await page.context().setExtraHTTPHeaders({
    ...buildE2EAuthHeaders("00000000-0000-4000-8000-000000000001"),
    "x-mogplex-scope-kind": "team",
    "x-mogplex-scope-slug": TEAM_SLUG,
    "x-mogplex-scope-id": TEAM_ID,
  });
  await mockSettingsShell(page);
  await page.route(`**/api/teams/${TEAM_ID}/members`, (route) =>
    fulfillJson(route, {
      team: { id: TEAM_ID, slug: TEAM_SLUG, name: "Acme", icon_url: null },
      members: [],
      invites: [],
      viewer: { userId: "member-1", role: "developer", canManage: false },
    })
  );
  await page.route(`**/api/teams/${TEAM_ID}/keys`, (route) =>
    fulfillJson(route, {
      keys: [],
      viewer: { role: "developer", canManage: false },
    })
  );
  await page.route(`**/api/teams/${TEAM_ID}/models`, (route) =>
    fulfillJson(route, {
      modelAllowlist: null,
      viewer: { role: "developer", canManage: false },
    })
  );
  await page.route("**/api/billing/capacity", (route) =>
    fulfillJson(
      route,
      capacitySummary({
        account: {
          id: "billing-account-2",
          eventSequence: "4",
          enforcementMode: "shadow",
          scope: "team",
          displayName: "Acme",
          status: "read_only",
          canManageBilling: false,
          hasSubscription: true,
          hasBillingHistory: true,
        },
        plan: {
          ref: "business",
          name: "Business",
          offerKind: "contract",
          interval: "contract",
          recurringAmountCents: null,
          renewsAt: null,
          cancelsAt: null,
          namedUserLimit: null,
        },
      })
    )
  );

  await page.goto(`/${TEAM_SLUG}/settings?tab=billing`);

  await expect(page.getByRole("heading", { name: "Business" })).toBeVisible();
  await expect(
    page.getByRole("heading", { name: /Capacity add-ons|Storage add-ons/ })
  ).toHaveCount(0);
  await expect(page.getByRole("button", { name: /Add|Manage/ })).toHaveCount(0);
});

test("Billing explains when purchase operations are unavailable", async ({
  page,
}) => {
  await enableScopedE2EAuth(page);
  await mockSettingsShell(page);
  await page.route("**/api/billing/capacity", (route) =>
    fulfillJson(
      route,
      capacitySummary({
        billingOperationsEnabled: false,
      })
    )
  );

  await page.goto(scopedPath("settings?tab=billing"));

  await expect(
    page.getByText(
      "Inference purchases are unavailable on this deployment. Ask the deployment administrator to configure billing."
    )
  ).toBeVisible();
  await expect(
    page.getByText(
      "Capacity purchases are unavailable on this deployment. Ask the deployment administrator to configure billing."
    )
  ).toBeVisible();
  await expect(
    page.getByRole("button", { name: "$10", exact: true })
  ).toHaveCount(0);
  await expect(
    page.getByRole("button", { name: "Add Storage +1 GB" })
  ).toHaveCount(0);
});

test("legacy subscribers can manage their plan and buy inference credit", async ({
  page,
}) => {
  await enableScopedE2EAuth(page);
  await mockSettingsShell(page);
  await page.route("**/api/billing/capacity", (route) =>
    fulfillJson(
      route,
      capacitySummary({
        billingOperationsEnabled: false,
        concurrency: {
          active: 0,
          included: 0,
          addOn: 0,
          limit: 0,
          wouldBlock: false,
        },
        plan: {
          ref: "legacy",
          name: "Mog Mode",
          offerKind: "legacy",
          interval: "contract",
          recurringAmountCents: null,
          renewsAt: "2026-09-17T12:00:00.000Z",
          cancelsAt: null,
          namedUserLimit: null,
        },
      })
    )
  );
  await page.route("**/api/stripe/checkout", (route) =>
    fulfillJson(route, {
      url: scopedPath("settings?tab=billing&billing=topup"),
    })
  );

  await page.goto(scopedPath("settings?tab=billing"));

  await expect(page.getByRole("heading", { name: "Mog Mode" })).toBeVisible();
  await expect(page.getByRole("button", { name: "Manage plan" })).toBeEnabled();
  await expect(page.getByText("0 parallel agent runs")).toHaveCount(0);

  const topupCheckout = page.waitForRequest("**/api/stripe/checkout");
  await page.getByRole("button", { name: "Pay $10, get $10 credit" }).click();
  expect((await topupCheckout).postDataJSON()).toMatchObject({
    kind: "topup",
    preset: "topup_10",
  });
});

test("top-up customers can open billing history without a subscription", async ({
  page,
}) => {
  await enableScopedE2EAuth(page);
  await mockSettingsShell(page);
  await page.route("**/api/billing/capacity", (route) =>
    fulfillJson(
      route,
      capacitySummary({
        account: {
          ...capacitySummary().account,
          hasSubscription: false,
          hasBillingHistory: true,
        },
      })
    )
  );
  await page.route("**/api/stripe/portal", (route) =>
    fulfillJson(route, { url: scopedPath("settings?tab=billing") })
  );
  await page.goto(scopedPath("settings?tab=billing"));
  const portalRequest = page.waitForRequest("**/api/stripe/portal");
  await page.getByRole("button", { name: "Manage billing" }).click();
  await portalRequest;
});