Files
cfw-autumn/server/tests/integration/billing/update-subscription/errors/update-one-off-errors.test.ts

289 lines
8.1 KiB
TypeScript

import { test } from "bun:test";
import { FreeTrialDuration } from "@autumn/shared";
import { TestFeature } from "@tests/setup/v2Features.js";
import { expectAutumnError } from "@tests/utils/expectUtils/expectErrUtils.js";
import { items } from "@tests/utils/fixtures/items.js";
import { products } from "@tests/utils/fixtures/products.js";
import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js";
import chalk from "chalk";
// ═══════════════════════════════════════════════════════════════════════════════
// ONE-OFF ERRORS - Price/Billing Changes Not Allowed
// ═══════════════════════════════════════════════════════════════════════════════
// 1. Error: changing base price on one-off product
test.concurrent(`${chalk.yellowBright("error: one-off changing base price")}`, async () => {
const oneOffPriceItem = items.oneOffPrice({ price: 50 });
const oneOffProd = products.base({ items: [oneOffPriceItem], id: "oneoff" });
const { customerId, autumnV1 } = await initScenario({
customerId: "err-oneoff-base-price",
setup: [
s.customer({ paymentMethod: "success" }),
s.products({ list: [oneOffProd] }),
],
actions: [s.attach({ productId: "oneoff" })],
});
// Try to change base price - should fail
const newPriceItem = items.oneOffPrice({ price: 100 });
const updateParams = {
customer_id: customerId,
product_id: oneOffProd.id,
items: [newPriceItem],
};
await expectAutumnError({
func: async () => {
await autumnV1.subscriptions.update(updateParams);
},
});
});
// 2. Error: changing feature price on one-off prepaid product
test.concurrent(`${chalk.yellowBright("error: one-off changing feature price")}`, async () => {
const oneOffMessagesItem = items.oneOffMessages({
price: 10,
billingUnits: 100,
});
const oneOffProd = products.base({
items: [oneOffMessagesItem],
id: "oneoff-feature",
});
const { customerId, autumnV1 } = await initScenario({
customerId: "err-oneoff-feature-price",
setup: [
s.customer({ paymentMethod: "success" }),
s.products({ list: [oneOffProd] }),
],
actions: [
s.attach({
productId: "oneoff-feature",
options: [{ feature_id: TestFeature.Messages, quantity: 1 }],
}),
],
});
// Try to change feature price - should fail
const newMessagesItem = items.oneOffMessages({
price: 20,
billingUnits: 100,
});
const updateParams = {
customer_id: customerId,
product_id: oneOffProd.id,
items: [newMessagesItem],
};
await expectAutumnError({
func: async () => {
await autumnV1.subscriptions.update(updateParams);
},
});
});
// 3. Error: changing billing units on one-off prepaid product
test.concurrent(`${chalk.yellowBright("error: one-off changing billing units")}`, async () => {
const oneOffMessagesItem = items.oneOffMessages({
price: 10,
billingUnits: 100,
});
const oneOffProd = products.base({
items: [oneOffMessagesItem],
id: "oneoff-billing",
});
const { customerId, autumnV1 } = await initScenario({
customerId: "err-oneoff-billing-units",
setup: [
s.customer({ paymentMethod: "success" }),
s.products({ list: [oneOffProd] }),
],
actions: [
s.attach({
productId: "oneoff-billing",
options: [{ feature_id: TestFeature.Messages, quantity: 1 }],
}),
],
});
// Try to change billing units - should fail
const newMessagesItem = items.oneOffMessages({
price: 10,
billingUnits: 200,
});
const updateParams = {
customer_id: customerId,
product_id: oneOffProd.id,
items: [newMessagesItem],
};
await expectAutumnError({
func: async () => {
await autumnV1.subscriptions.update(updateParams);
},
});
});
// 4. Error: changing both price and billing units on one-off
test.concurrent(`${chalk.yellowBright("error: one-off changing price and billing units")}`, async () => {
const oneOffMessagesItem = items.oneOffMessages({
price: 10,
billingUnits: 100,
});
const oneOffProd = products.base({
items: [oneOffMessagesItem],
id: "oneoff-both",
});
const { customerId, autumnV1 } = await initScenario({
customerId: "err-oneoff-both",
setup: [
s.customer({ paymentMethod: "success" }),
s.products({ list: [oneOffProd] }),
],
actions: [
s.attach({
productId: "oneoff-both",
options: [{ feature_id: TestFeature.Messages, quantity: 1 }],
}),
],
});
// Try to change both price and billing units - should fail
const newMessagesItem = items.oneOffMessages({
price: 20,
billingUnits: 200,
});
const updateParams = {
customer_id: customerId,
product_id: oneOffProd.id,
items: [newMessagesItem],
};
await expectAutumnError({
func: async () => {
await autumnV1.subscriptions.update(updateParams);
},
});
});
// 5. Error: updating quantity of one-off item on recurring product
test.concurrent(`${chalk.yellowBright("error: one-off item quantity update on recurring product")}`, async () => {
const billingUnits = 100;
const oneOffMessagesItem = items.oneOffMessages({
price: 10,
billingUnits,
});
const recurringProduct = products.base({
items: [items.monthlyPrice({ price: 20 }), oneOffMessagesItem],
id: "recurring-with-oneoff",
});
const { customerId, autumnV1 } = await initScenario({
customerId: "err-oneoff-qty-recurring",
setup: [
s.customer({ paymentMethod: "success" }),
s.products({ list: [recurringProduct] }),
],
actions: [
s.attach({
productId: recurringProduct.id,
options: [
{ feature_id: TestFeature.Messages, quantity: 1 * billingUnits },
],
}),
],
});
// Try to update quantity of one-off messages - should fail
await expectAutumnError({
func: async () => {
await autumnV1.subscriptions.update({
customer_id: customerId,
product_id: recurringProduct.id,
options: [
{ feature_id: TestFeature.Messages, quantity: 2 * billingUnits },
],
});
},
});
});
// 6. Error: adding a free trial to a one-off product
test.concurrent(`${chalk.yellowBright("error: one-off adding free trial")}`, async () => {
const oneOffPriceItem = items.oneOffPrice({ price: 50 });
const oneOffProd = products.base({
items: [oneOffPriceItem],
id: "oneoff-add-trial",
});
const { customerId, autumnV1 } = await initScenario({
customerId: "err-oneoff-add-trial",
setup: [
s.customer({ paymentMethod: "success" }),
s.products({ list: [oneOffProd] }),
],
actions: [s.attach({ productId: "oneoff-add-trial" })],
});
// Try to add a free trial to a one-off product - should fail
await expectAutumnError({
func: async () => {
await autumnV1.subscriptions.update({
customer_id: customerId,
product_id: oneOffProd.id,
free_trial: {
length: 7,
duration: FreeTrialDuration.Day,
unique_fingerprint: false,
card_required: true,
},
});
},
});
});
// 8. Error: removing trial and adding one-off item
test.concurrent(`${chalk.yellowBright("error: remove trial and add one-off item")}`, async () => {
const dashboardItem = items.dashboard();
const monthlyBasePrice = items.monthlyPrice({ price: 20 });
const proTrial = products.base({
items: [dashboardItem, monthlyBasePrice],
id: "pro-trial",
trialDays: 7,
});
const { customerId, autumnV1 } = await initScenario({
customerId: "err-trial-remove-add-oneoff",
setup: [
s.customer({ testClock: true, paymentMethod: "success" }),
s.products({ list: [proTrial] }),
],
actions: [s.attach({ productId: proTrial.id })],
});
// Try to remove trial and add one-off prepaid messages - should fail
const oneOffMessagesItem = items.oneOffMessages({
includedUsage: 0,
billingUnits: 100,
price: 10,
});
const quantity = 300;
await expectAutumnError({
func: async () => {
await autumnV1.subscriptions.update({
customer_id: customerId,
product_id: proTrial.id,
free_trial: null,
items: [dashboardItem, monthlyBasePrice, oneOffMessagesItem],
options: [{ feature_id: TestFeature.Messages, quantity }],
});
},
});
});