Files
cfw-autumn/server/tests/integration/billing/multi-attach/customize/multi-attach-customize.test.ts
2026-03-02 14:18:02 +00:00

219 lines
6.5 KiB
TypeScript

/**
* Multi-Attach Customize Tests
*
* Tests for the per-plan `customize` field in billing.multi_attach.
* Each plan can independently override its base price or items.
*
* Key behaviors:
* - Different custom prices per plan
* - Different custom items per plan (add/change features)
*/
import { expect, test } from "bun:test";
import type { ApiCustomerV3 } from "@autumn/shared";
import {
expectCustomerFeatureCorrect,
expectCustomerFeatureExists,
} from "@tests/integration/billing/utils/expectCustomerFeatureCorrect";
import { expectCustomerInvoiceCorrect } from "@tests/integration/billing/utils/expectCustomerInvoiceCorrect";
import { expectCustomerProducts } from "@tests/integration/billing/utils/expectCustomerProductCorrect";
import { expectSubToBeCorrect } from "@tests/merged/mergeUtils/expectSubCorrect";
import { TestFeature } from "@tests/setup/v2Features";
import { items } from "@tests/utils/fixtures/items";
import { itemsV2 } from "@tests/utils/fixtures/itemsV2";
import { products } from "@tests/utils/fixtures/products";
import { initScenario, s } from "@tests/utils/testInitUtils/initScenario";
import chalk from "chalk";
import { expectStripeSubscriptionCorrect } from "../../utils/expectStripeSubCorrect";
// ═══════════════════════════════════════════════════════════════════
// Test 1: Different custom prices per plan
//
// Scenario:
// - planA: Pro ($20/mo) with messages — customized to $35/mo
// - planB: Base ($30/mo) with users in group-b — customized to $50/mo
//
// Expected:
// - Preview total = $85 ($35 + $50)
// - Both products active
// - Features correct
// - Invoice total = $85
// ═══════════════════════════════════════════════════════════════════
test.concurrent(`${chalk.yellowBright("multi-attach customize 1: different custom prices per plan")}`, async () => {
const messagesItem = items.monthlyMessages({ includedUsage: 500 });
const usersItem = items.monthlyUsers({ includedUsage: 10 });
const planA = products.pro({ id: "plan-a", items: [messagesItem] });
const planB = products.base({
id: "plan-b",
items: [usersItem, items.monthlyPrice({ price: 30 })],
group: "group-b",
});
const { customerId, autumnV1, ctx } = await initScenario({
customerId: "ma-customize-prices",
setup: [
s.customer({ paymentMethod: "success" }),
s.products({ list: [planA, planB] }),
],
actions: [],
});
const multiAttachParams = {
customer_id: customerId,
plans: [
{
plan_id: planA.id,
customize: {
price: itemsV2.monthlyPrice({ amount: 35 }),
},
},
{
plan_id: planB.id,
customize: {
price: itemsV2.monthlyPrice({ amount: 50 }),
},
},
],
};
// 1. Preview — $35 (planA custom) + $50 (planB custom) = $85
const preview = await autumnV1.billing.previewMultiAttach(multiAttachParams);
expect(preview.total).toBeCloseTo(85, 0);
// 2. Attach
await autumnV1.billing.multiAttach(multiAttachParams);
// 3. Verify
const customer = await autumnV1.customers.get<ApiCustomerV3>(customerId);
await expectCustomerProducts({
customer,
active: [planA.id, planB.id],
});
expectCustomerFeatureCorrect({
customer,
featureId: TestFeature.Messages,
balance: 500,
});
expectCustomerFeatureCorrect({
customer,
featureId: TestFeature.Users,
balance: 10,
});
await expectCustomerInvoiceCorrect({
customer,
count: 1,
latestTotal: 85,
});
await expectSubToBeCorrect({
db: ctx.db,
customerId,
org: ctx.org,
env: ctx.env,
flags: { checkNotTrialing: true },
});
});
// ═══════════════════════════════════════════════════════════════════
// Test 2: Custom items per plan (add feature + change included usage)
//
// Scenario:
// - planA: Base ($20/mo) with messages (100 included) —
// customized to $50/mo, messages (500 included) + dashboard boolean
// - planB: Base ($30/mo) with words (10 included) in group-b —
// customized to free (price null), words (50 included)
//
// Expected:
// - Preview total = $50 ($50 + $0)
// - Both products active
// - Messages balance = 500, dashboard exists, words balance = 50
// - Invoice total = $50
// ═══════════════════════════════════════════════════════════════════
test.concurrent(`${chalk.yellowBright("multi-attach customize 2: custom items per plan")}`, async () => {
const messagesItem = items.monthlyMessages({ includedUsage: 100 });
const addon = products.base({
id: "addon",
items: [messagesItem, items.monthlyPrice({ price: 20 })],
});
const { customerId, autumnV1, ctx } = await initScenario({
customerId: "ma-customize-items",
setup: [
s.customer({ paymentMethod: "success" }),
s.products({ list: [addon] }),
],
actions: [],
});
const multiAttachParams = {
customer_id: customerId,
plans: [
{
plan_id: addon.id,
customize: {
price: null,
items: [
itemsV2.prepaidMessages({ included: 100, amount: 20 }),
itemsV2.dashboard(),
],
},
feature_quantities: [
{ feature_id: TestFeature.Messages, quantity: 300 },
],
},
{
plan_id: addon.id,
customize: {
price: null,
items: [itemsV2.prepaidMessages({ included: 100, amount: 30 })],
},
feature_quantities: [
{ feature_id: TestFeature.Messages, quantity: 500 },
],
},
],
};
// 1. Preview — $50 (planA custom price) + $0 (planB price removed) = $50
const preview = await autumnV1.billing.previewMultiAttach(multiAttachParams);
expect(preview.total).toEqual(40 + 120);
// 2. Attach
await autumnV1.billing.multiAttach(multiAttachParams);
// 3. Verify
const customer = await autumnV1.customers.get<ApiCustomerV3>(customerId);
await expectCustomerProducts({
customer,
active: [addon.id],
});
// Messages: 500 included (customized up from 100)
expectCustomerFeatureCorrect({
customer,
featureId: TestFeature.Messages,
balance: 800,
});
// Dashboard: added via customize items
await expectCustomerFeatureExists({
customer,
featureId: TestFeature.Dashboard,
});
await expectCustomerInvoiceCorrect({
customer,
count: 1,
latestTotal: 40 + 120,
});
await expectStripeSubscriptionCorrect({ ctx, customerId });
});