Files
cfw-autumn/server/tests/integration/crud/customers/customer-billing-controls.test.ts
2026-03-30 15:25:40 +01:00

475 lines
13 KiB
TypeScript

import { expect, test } from "bun:test";
import type { ApiCustomerV5, CustomerBillingControls } from "@autumn/shared";
import { TestFeature } from "@tests/setup/v2Features.js";
import { expectAutumnError } from "@tests/utils/expectUtils/expectErrUtils.js";
import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js";
import chalk from "chalk";
import { CusService } from "@/internal/customers/CusService.js";
const spendLimitControls: CustomerBillingControls = {
spend_limits: [
{
feature_id: TestFeature.Messages,
enabled: true,
overage_limit: 25,
},
],
};
const autoTopupControls: CustomerBillingControls = {
auto_topups: [
{
feature_id: TestFeature.Credits,
enabled: true,
threshold: 20,
quantity: 100,
},
],
};
const usageAlertControls: CustomerBillingControls = {
usage_alerts: [
{
feature_id: TestFeature.Messages,
threshold: 90,
threshold_type: "usage_percentage",
enabled: true,
},
],
};
const overageAllowedControls: CustomerBillingControls = {
overage_allowed: [
{
feature_id: TestFeature.Messages,
enabled: true,
},
],
};
test.concurrent(`${chalk.yellowBright("customer billing controls: create customer with spend limits")}`, async () => {
const customerId = "customer-billing-controls-1";
const { autumnV2_1, ctx } = await initScenario({
setup: [s.deleteCustomer({ customerId })],
actions: [],
});
await autumnV2_1.customers.create({
id: customerId,
name: "Spend Limit Customer",
email: `${customerId}@example.com`,
billing_controls: spendLimitControls,
});
const cachedCustomer =
await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(cachedCustomer.billing_controls?.spend_limits).toEqual(
spendLimitControls.spend_limits,
);
const uncachedCustomer = await autumnV2_1.customers.get<ApiCustomerV5>(
customerId,
{
skip_cache: "true",
},
);
expect(uncachedCustomer.billing_controls?.spend_limits).toEqual(
spendLimitControls.spend_limits,
);
const fromDb = await CusService.getFull({
ctx,
idOrInternalId: customerId,
});
expect(fromDb.spend_limits).toEqual(spendLimitControls.spend_limits);
});
test.concurrent(`${chalk.yellowBright("customer billing controls: update spend limits without clearing other billing controls")}`, async () => {
const customerId = "customer-billing-controls-2";
const { autumnV2_1 } = await initScenario({
customerId,
setup: [s.customer({})],
actions: [],
});
await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
await autumnV2_1.customers.update(customerId, {
billing_controls: autoTopupControls,
});
await autumnV2_1.customers.update(customerId, {
billing_controls: spendLimitControls,
});
const afterSpendLimitUpdate =
await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(afterSpendLimitUpdate.billing_controls?.auto_topups).toEqual(
autoTopupControls.auto_topups,
);
expect(afterSpendLimitUpdate.billing_controls?.spend_limits).toEqual(
spendLimitControls.spend_limits,
);
await autumnV2_1.customers.update(customerId, {
billing_controls: { spend_limits: [] },
});
const cachedCustomer =
await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(cachedCustomer.billing_controls?.auto_topups).toEqual(
autoTopupControls.auto_topups,
);
expect(cachedCustomer.billing_controls?.spend_limits).toEqual([]);
const uncachedCustomer = await autumnV2_1.customers.get<ApiCustomerV5>(
customerId,
{
skip_cache: "true",
},
);
expect(uncachedCustomer.billing_controls?.auto_topups).toEqual(
autoTopupControls.auto_topups,
);
expect(uncachedCustomer.billing_controls?.spend_limits).toEqual([]);
});
test.concurrent(`${chalk.yellowBright("customer billing controls: reject duplicate spend limit feature ids on create and update")}`, async () => {
const customerId = "customer-billing-controls-3";
const { autumnV2_1 } = await initScenario({
setup: [s.deleteCustomer({ customerId })],
actions: [],
});
await expectAutumnError({
func: async () =>
await autumnV2_1.customers.create({
id: customerId,
name: "Duplicate Spend Limit Create",
email: `${customerId}@example.com`,
billing_controls: {
spend_limits: [
{
feature_id: TestFeature.Messages,
enabled: true,
overage_limit: 10,
},
{
feature_id: TestFeature.Messages,
enabled: false,
overage_limit: 20,
},
],
},
}),
});
await autumnV2_1.customers.create({
id: customerId,
name: "Duplicate Spend Limit Update",
email: `${customerId}@example.com`,
});
await expectAutumnError({
func: async () =>
await autumnV2_1.customers.update(customerId, {
billing_controls: {
spend_limits: [
{
feature_id: TestFeature.Credits,
enabled: true,
overage_limit: 15,
},
{
feature_id: TestFeature.Credits,
enabled: true,
overage_limit: 30,
},
],
},
}),
});
});
test.concurrent(`${chalk.yellowBright("customer billing controls: create customer with usage alerts")}`, async () => {
const customerId = "customer-billing-controls-4";
const { autumnV2_1, ctx } = await initScenario({
setup: [s.deleteCustomer({ customerId })],
actions: [],
});
await autumnV2_1.customers.create({
id: customerId,
name: "Usage Alert Customer",
email: `${customerId}@example.com`,
billing_controls: usageAlertControls,
});
const cachedCustomer =
await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(cachedCustomer.billing_controls?.usage_alerts).toEqual(
usageAlertControls.usage_alerts,
);
const uncachedCustomer = await autumnV2_1.customers.get<ApiCustomerV5>(
customerId,
{ skip_cache: "true" },
);
expect(uncachedCustomer.billing_controls?.usage_alerts).toEqual(
usageAlertControls.usage_alerts,
);
const fromDb = await CusService.getFull({
ctx,
idOrInternalId: customerId,
});
expect(fromDb.usage_alerts).toEqual(usageAlertControls.usage_alerts);
});
test.concurrent(`${chalk.yellowBright("customer billing controls: updating usage alerts does not unset spend limits or auto topups")}`, async () => {
const customerId = "customer-billing-controls-5";
const { autumnV2_1 } = await initScenario({
customerId,
setup: [s.customer({})],
actions: [],
});
// Set spend limits and auto topups first
await autumnV2_1.customers.update(customerId, {
billing_controls: spendLimitControls,
});
await autumnV2_1.customers.update(customerId, {
billing_controls: autoTopupControls,
});
// Now update only usage_alerts
await autumnV2_1.customers.update(customerId, {
billing_controls: usageAlertControls,
});
const cached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(cached.billing_controls?.spend_limits).toEqual(
spendLimitControls.spend_limits,
);
expect(cached.billing_controls?.auto_topups).toEqual(
autoTopupControls.auto_topups,
);
expect(cached.billing_controls?.usage_alerts).toEqual(
usageAlertControls.usage_alerts,
);
const uncached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId, {
skip_cache: "true",
});
expect(uncached.billing_controls?.spend_limits).toEqual(
spendLimitControls.spend_limits,
);
expect(uncached.billing_controls?.auto_topups).toEqual(
autoTopupControls.auto_topups,
);
expect(uncached.billing_controls?.usage_alerts).toEqual(
usageAlertControls.usage_alerts,
);
});
test.concurrent(`${chalk.yellowBright("customer billing controls: updating spend limits does not unset usage alerts")}`, async () => {
const customerId = "customer-billing-controls-6";
const { autumnV2_1 } = await initScenario({
customerId,
setup: [s.customer({})],
actions: [],
});
// Set usage alerts first
await autumnV2_1.customers.update(customerId, {
billing_controls: usageAlertControls,
});
// Now update only spend_limits
await autumnV2_1.customers.update(customerId, {
billing_controls: spendLimitControls,
});
const cached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(cached.billing_controls?.usage_alerts).toEqual(
usageAlertControls.usage_alerts,
);
expect(cached.billing_controls?.spend_limits).toEqual(
spendLimitControls.spend_limits,
);
const uncached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId, {
skip_cache: "true",
});
expect(uncached.billing_controls?.usage_alerts).toEqual(
usageAlertControls.usage_alerts,
);
expect(uncached.billing_controls?.spend_limits).toEqual(
spendLimitControls.spend_limits,
);
});
test.concurrent(`${chalk.yellowBright("customer billing controls: clearing usage alerts with empty array")}`, async () => {
const customerId = "customer-billing-controls-7";
const { autumnV2_1 } = await initScenario({
customerId,
setup: [s.customer({})],
actions: [],
});
// Set both spend limits and usage alerts
await autumnV2_1.customers.update(customerId, {
billing_controls: {
...spendLimitControls,
...usageAlertControls,
},
});
// Clear only usage_alerts
await autumnV2_1.customers.update(customerId, {
billing_controls: { usage_alerts: [] },
});
const cached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(cached.billing_controls?.usage_alerts).toEqual([]);
expect(cached.billing_controls?.spend_limits).toEqual(
spendLimitControls.spend_limits,
);
const uncached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId, {
skip_cache: "true",
});
expect(uncached.billing_controls?.usage_alerts).toEqual([]);
expect(uncached.billing_controls?.spend_limits).toEqual(
spendLimitControls.spend_limits,
);
});
test.concurrent(`${chalk.yellowBright("customer billing controls: create customer with overage_allowed")}`, async () => {
const customerId = "customer-billing-controls-8";
const { autumnV2_1, ctx } = await initScenario({
setup: [s.deleteCustomer({ customerId })],
actions: [],
});
await autumnV2_1.customers.create({
id: customerId,
name: "Overage Allowed Customer",
email: `${customerId}@example.com`,
billing_controls: overageAllowedControls,
});
const cachedCustomer =
await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(cachedCustomer.billing_controls?.overage_allowed).toEqual(
overageAllowedControls.overage_allowed,
);
const uncachedCustomer = await autumnV2_1.customers.get<ApiCustomerV5>(
customerId,
{
skip_cache: "true",
},
);
expect(uncachedCustomer.billing_controls?.overage_allowed).toEqual(
overageAllowedControls.overage_allowed,
);
const fromDb = await CusService.getFull({ ctx, idOrInternalId: customerId });
expect(fromDb.overage_allowed).toEqual(
overageAllowedControls.overage_allowed,
);
});
test.concurrent(`${chalk.yellowBright("customer billing controls: update overage_allowed without clearing other billing controls")}`, async () => {
const customerId = "customer-billing-controls-9";
const { autumnV2_1 } = await initScenario({
customerId,
setup: [s.customer({})],
actions: [],
});
await autumnV2_1.customers.update(customerId, {
billing_controls: spendLimitControls,
});
await autumnV2_1.customers.update(customerId, {
billing_controls: overageAllowedControls,
});
const cached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(cached.billing_controls?.spend_limits).toEqual(
spendLimitControls.spend_limits,
);
expect(cached.billing_controls?.overage_allowed).toEqual(
overageAllowedControls.overage_allowed,
);
const uncached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId, {
skip_cache: "true",
});
expect(uncached.billing_controls?.spend_limits).toEqual(
spendLimitControls.spend_limits,
);
expect(uncached.billing_controls?.overage_allowed).toEqual(
overageAllowedControls.overage_allowed,
);
});
test.concurrent(`${chalk.yellowBright("customer billing controls: reject duplicate overage_allowed feature ids")}`, async () => {
const customerId = "customer-billing-controls-10";
const { autumnV2_1 } = await initScenario({
setup: [s.deleteCustomer({ customerId })],
actions: [],
});
await expectAutumnError({
func: async () =>
await autumnV2_1.customers.create({
id: customerId,
name: "Duplicate Overage Allowed",
email: `${customerId}@example.com`,
billing_controls: {
overage_allowed: [
{ feature_id: TestFeature.Messages, enabled: true },
{ feature_id: TestFeature.Messages, enabled: false },
],
},
}),
});
});
test.concurrent(`${chalk.yellowBright("customer billing controls: clearing overage_allowed with empty array")}`, async () => {
const customerId = "customer-billing-controls-11";
const { autumnV2_1 } = await initScenario({
customerId,
setup: [s.customer({})],
actions: [],
});
await autumnV2_1.customers.update(customerId, {
billing_controls: {
...spendLimitControls,
...overageAllowedControls,
},
});
await autumnV2_1.customers.update(customerId, {
billing_controls: { overage_allowed: [] },
});
const cached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId);
expect(cached.billing_controls?.overage_allowed).toEqual([]);
expect(cached.billing_controls?.spend_limits).toEqual(
spendLimitControls.spend_limits,
);
const uncached = await autumnV2_1.customers.get<ApiCustomerV5>(customerId, {
skip_cache: "true",
});
expect(uncached.billing_controls?.overage_allowed).toEqual([]);
expect(uncached.billing_controls?.spend_limits).toEqual(
spendLimitControls.spend_limits,
);
});