fix: update subscription next cycle end floored

This commit is contained in:
John Yeo
2026-01-16 14:37:41 +00:00
parent 71e1295c38
commit 0b6d767f8b
108 changed files with 3583 additions and 2846 deletions

View File

@@ -1,10 +1,13 @@
import { describe, expect, test } from "bun:test";
import type { ExistingUsages } from "@autumn/shared";
import { contexts } from "@tests/utils/fixtures/db/contexts";
import { customerEntitlements } from "@tests/utils/fixtures/db/customerEntitlements";
import { customerProducts } from "@tests/utils/fixtures/db/customerProducts";
import chalk from "chalk";
import { applyExistingUsages } from "@/internal/billing/v2/utils/handleExistingUsages/applyExistingUsages";
const ctx = contexts.create({});
describe(chalk.yellowBright("applyExistingUsages"), () => {
describe("basic usage deduction", () => {
test("deducts existing usage from new entitlement balance", () => {
@@ -29,6 +32,7 @@ describe(chalk.yellowBright("applyExistingUsages"), () => {
// Act
applyExistingUsages({
ctx,
customerProduct: cusProduct,
existingUsages,
entities: [],

View File

@@ -1,11 +1,14 @@
import { describe, expect, test } from "bun:test";
import type { ExistingUsages } from "@autumn/shared";
import { contexts } from "@tests/utils/fixtures/db/contexts";
import { customerEntitlements } from "@tests/utils/fixtures/db/customerEntitlements";
import { customerProducts } from "@tests/utils/fixtures/db/customerProducts";
import { entities } from "@tests/utils/fixtures/db/entities";
import chalk from "chalk";
import { applyExistingUsages } from "@/internal/billing/v2/utils/handleExistingUsages/applyExistingUsages";
const ctx = contexts.create({});
describe(
chalk.yellowBright("applyExistingUsages (testing entities flow)"),
() => {
@@ -67,6 +70,7 @@ describe(
// Act
applyExistingUsages({
ctx,
customerProduct: cusProduct,
existingUsages,
entities: entityList,
@@ -118,7 +122,12 @@ describe(
};
// Act
applyExistingUsages({ customerProduct, existingUsages, entities: entityList });
applyExistingUsages({
ctx,
customerProduct,
existingUsages,
entities: entityList,
});
// Assert: Entity count (2) takes priority, balance = 10 - 2 = 8
const updatedCusEntA = customerProduct.customer_entitlements.find(
@@ -173,7 +182,12 @@ describe(
const existingUsages: ExistingUsages = {};
// Act
applyExistingUsages({ customerProduct, existingUsages, entities: entityList });
applyExistingUsages({
ctx,
customerProduct,
existingUsages,
entities: entityList,
});
// Total usage = 3, distributed: first cusEnt uses 2, second cusEnt uses 1
const updatedCusEnts = customerProduct.customer_entitlements.filter(

View File

@@ -1,10 +1,13 @@
import { describe, expect, test } from "bun:test";
import { EntInterval, type ExistingUsages } from "@autumn/shared";
import { contexts } from "@tests/utils/fixtures/db/contexts";
import { customerEntitlements } from "@tests/utils/fixtures/db/customerEntitlements";
import { customerProducts } from "@tests/utils/fixtures/db/customerProducts";
import chalk from "chalk";
import { applyExistingUsages } from "@/internal/billing/v2/utils/handleExistingUsages/applyExistingUsages";
const ctx = contexts.create({});
describe(
chalk.yellowBright("applyExistingUsages (testing deduction order)"),
() => {
@@ -44,6 +47,7 @@ describe(
// Act
applyExistingUsages({
ctx,
customerProduct: cusProduct,
existingUsages,
entities: [],
@@ -96,7 +100,12 @@ describe(
};
// Act
applyExistingUsages({ customerProduct, existingUsages, entities: [] });
applyExistingUsages({
ctx,
customerProduct,
existingUsages,
entities: [],
});
// Assert: Prepaid should be depleted first (0), then pay-per-use should have 3 remaining
const updatedPrepaid = customerProduct.customer_entitlements.find(
@@ -149,7 +158,12 @@ describe(
};
// Act
applyExistingUsages({ customerProduct, existingUsages, entities: [] });
applyExistingUsages({
ctx,
customerProduct,
existingUsages,
entities: [],
});
const updatedPrepaid = customerProduct.customer_entitlements.find(
(ce) => ce.usage_allowed === false,
@@ -217,7 +231,12 @@ describe(
};
// Act
applyExistingUsages({ customerProduct, existingUsages, entities: [] });
applyExistingUsages({
ctx,
customerProduct,
existingUsages,
entities: [],
});
// Find each cusEnt by their unique characteristics
const findCusEnt = (usageAllowed: boolean, interval: EntInterval) =>

View File

@@ -1,10 +1,13 @@
import { describe, expect, test } from "bun:test";
import type { ExistingUsages } from "@autumn/shared";
import { contexts } from "@tests/utils/fixtures/db/contexts";
import { customerEntitlements } from "@tests/utils/fixtures/db/customerEntitlements";
import { customerProducts } from "@tests/utils/fixtures/db/customerProducts";
import chalk from "chalk";
import { applyExistingUsages } from "@/internal/billing/v2/utils/handleExistingUsages/applyExistingUsages";
const ctx = contexts.create({});
describe(chalk.yellowBright("applyExistingUsages (entity usages)"), () => {
describe("entity usages deduction on entity-scoped cusEnt", () => {
test("each entity's balance is reduced by its respective usage", () => {
@@ -43,6 +46,7 @@ describe(chalk.yellowBright("applyExistingUsages (entity usages)"), () => {
// Act
applyExistingUsages({
ctx,
customerProduct: cusProduct,
existingUsages,
entities: [],
@@ -88,6 +92,7 @@ describe(chalk.yellowBright("applyExistingUsages (entity usages)"), () => {
// Act
applyExistingUsages({
ctx,
customerProduct: cusProduct,
existingUsages,
entities: [],
@@ -134,7 +139,12 @@ describe(chalk.yellowBright("applyExistingUsages (entity usages)"), () => {
};
// Act
applyExistingUsages({ customerProduct, existingUsages, entities: [] });
applyExistingUsages({
ctx,
customerProduct,
existingUsages,
entities: [],
});
// Assert: Deduction should flow through entities
const updatedCusEnt = customerProduct.customer_entitlements[0];
@@ -185,7 +195,12 @@ describe(chalk.yellowBright("applyExistingUsages (entity usages)"), () => {
};
// Act
applyExistingUsages({ customerProduct, existingUsages, entities: [] });
applyExistingUsages({
ctx,
customerProduct,
existingUsages,
entities: [],
});
// Assert: Entity balances should go negative
const updatedCusEnt = customerProduct.customer_entitlements[0];
@@ -194,4 +209,154 @@ describe(chalk.yellowBright("applyExistingUsages (entity usages)"), () => {
expect(updatedCusEnt.entities?.entity2.balance).toBe(-20); // 30 - 50 = -20
});
});
describe("per-entity to per-entity preservation", () => {
test("entity usages are preserved when updating from entity-scoped to entity-scoped cusEnt", () => {
const internalFeatureId = "internal_feature_a";
const entityFeatureId = "entity_feature_id";
// Entity-scoped cusEnt with 3 entities, each with balance 100
const entityScopedCusEnt = customerEntitlements.create({
internalFeatureId,
featureId: "feature_a",
featureName: "Feature A",
allowance: 100,
balance: 0, // Top-level balance not used for entity-scoped
entityFeatureId,
entities: {
entity1: { id: "entity1", balance: 100, adjustment: 0 },
entity2: { id: "entity2", balance: 100, adjustment: 0 },
entity3: { id: "entity3", balance: 100, adjustment: 0 },
},
});
const cusProduct = customerProducts.create({
customerEntitlements: [entityScopedCusEnt],
});
// Existing entity usages: entity1 used 30, entity2 used 50, entity3 used 10
const existingUsages: ExistingUsages = {
[internalFeatureId]: {
usage: 0,
entityUsages: {
entity1: 30,
entity2: 50,
entity3: 10,
},
},
};
// Act
applyExistingUsages({
ctx,
customerProduct: cusProduct,
existingUsages,
entities: [],
});
// Assert: Each entity's balance should be reduced by its usage
const updatedCusEnt = cusProduct.customer_entitlements[0];
expect(updatedCusEnt.entities).not.toBeNull();
expect(updatedCusEnt.entities?.entity1.balance).toBe(70); // 100 - 30
expect(updatedCusEnt.entities?.entity2.balance).toBe(50); // 100 - 50
expect(updatedCusEnt.entities?.entity3.balance).toBe(90); // 100 - 10
});
test("entity usages are preserved even when some entities have no prior usage", () => {
const internalFeatureId = "internal_feature_a";
const entityFeatureId = "entity_feature_id";
// Entity-scoped cusEnt with 3 entities
const entityScopedCusEnt = customerEntitlements.create({
internalFeatureId,
featureId: "feature_a",
featureName: "Feature A",
allowance: 50,
balance: 0,
entityFeatureId,
entities: {
entity1: { id: "entity1", balance: 50, adjustment: 0 },
entity2: { id: "entity2", balance: 50, adjustment: 0 },
entity3: { id: "entity3", balance: 50, adjustment: 0 },
},
});
const cusProduct = customerProducts.create({
customerEntitlements: [entityScopedCusEnt],
});
// Only entity1 has existing usage, entity2/entity3 have none
const existingUsages: ExistingUsages = {
[internalFeatureId]: {
usage: 0,
entityUsages: {
entity1: 25,
// entity2 and entity3 have no entry - should remain at full balance
},
},
};
// Act
applyExistingUsages({
ctx,
customerProduct: cusProduct,
existingUsages,
entities: [],
});
// Assert
const updatedCusEnt = cusProduct.customer_entitlements[0];
expect(updatedCusEnt.entities).not.toBeNull();
expect(updatedCusEnt.entities?.entity1.balance).toBe(25); // 50 - 25
expect(updatedCusEnt.entities?.entity2.balance).toBe(50); // Unchanged
expect(updatedCusEnt.entities?.entity3.balance).toBe(50); // Unchanged
});
});
describe("entity to non-entity conversion", () => {
test("entity usages are NOT summed when applied to non-entity-scoped cusEnt (current behavior)", () => {
const internalFeatureId = "internal_feature_a";
// Non-entity-scoped cusEnt (no entityFeatureId)
const nonEntityScopedCusEnt = customerEntitlements.create({
internalFeatureId,
featureId: "feature_a",
featureName: "Feature A",
allowance: 300,
balance: 300,
// No entityFeatureId - not entity-scoped
});
const cusProduct = customerProducts.create({
customerEntitlements: [nonEntityScopedCusEnt],
});
// Entity usages from previous entity-scoped product
// entity1: 30, entity2: 50, entity3: 10 = total 90
const existingUsages: ExistingUsages = {
[internalFeatureId]: {
usage: 0, // No top-level usage
entityUsages: {
entity1: 30,
entity2: 50,
entity3: 10,
},
},
};
// Act
applyExistingUsages({
ctx,
customerProduct: cusProduct,
existingUsages,
entities: [],
});
// Assert: Entity usages should NOT be applied (cusEnt is non-entity-scoped)
// This is the CURRENT intentional behavior - entity usages don't convert to top-level
const updatedCusEnt = cusProduct.customer_entitlements[0];
expect(updatedCusEnt.balance).toBe(300); // Unchanged - entity usages ignored
expect(updatedCusEnt.entities).toBeNull();
});
});
});

View File

@@ -86,7 +86,7 @@ describe(chalk.yellowBright("cusProductToExistingUsages"), () => {
expect(existingUsages[internalFeatureId].usage).toBe(30);
// Entity usages reflect current balances (not usage)
expect(existingUsages[internalFeatureId].entityUsages).toEqual({
entity1: 40,
entity1: 10,
entity2: 25,
});
});

View File

@@ -0,0 +1,238 @@
import { describe, expect, test } from "bun:test";
import { BillingInterval, getCycleEnd } from "@autumn/shared";
import { fromUnix, toUnix } from "@tests/utils/testIntervalUtils/testUnixUtils";
/**
* Test suite for getCycleEnd floor parameter
*
* The floor parameter sets a minimum allowed result. If the calculated cycle end
* is before the floor, the floor is returned instead.
*
* Use case: Long trial periods. When the billing anchor (trial end) is far in the
* future, getCycleEnd normally finds the next cycle boundary after `now`, which
* "wraps backwards" from the anchor. With floor, we can ensure billing doesn't
* start before the trial ends.
*
* Example: Trial ends 4 Aug (anchor), now is 16 Jan, monthly interval.
* Without floor: returns 4 Feb (next monthly boundary after now).
* With floor=4 Aug: returns 4 Aug (billing can't start before trial ends).
*/
describe("get-cycle-end-floor: floor parameter constrains minimum result", () => {
describe("Monthly interval with floor - long trial scenario", () => {
test("trial ends far in future: anchor 4 Aug, now 16 Jan, floor 4 Aug -> returns 4 Aug", () => {
// Trial ends 4 Aug (anchor), now is 16 Jan
// Without floor: cycle end would be 4 Feb (wraps backwards from anchor)
// With floor: returns 4 Aug (trial end date)
const anchor = toUnix({ year: 2026, month: 8, day: 4 });
const now = toUnix({ year: 2026, month: 1, day: 16 });
const floor = anchor; // Can't bill before trial ends
const result = getCycleEnd({
anchor,
interval: BillingInterval.Month,
intervalCount: 1,
now,
floor,
});
expect(result).toBe(floor);
const { year, month, day } = fromUnix(result);
expect(year).toBe(2026);
expect(month).toBe(8);
expect(day).toBe(4);
});
test("200-day trial: anchor 4 Aug, now 16 Jan, floor 4 Aug -> returns anchor", () => {
// Simulating the exact bug scenario: 200-day trial
const anchor = toUnix({
year: 2026,
month: 8,
day: 4,
hour: 13,
minute: 45,
});
const now = toUnix({
year: 2026,
month: 1,
day: 16,
hour: 13,
minute: 45,
});
const floor = anchor;
const result = getCycleEnd({
anchor,
interval: BillingInterval.Month,
intervalCount: 1,
now,
floor,
});
expect(result).toBe(floor);
});
test("floor not triggered when cycle end is after floor", () => {
// Anchor is 2 Jan, now is 15 Jan, floor is 1 Jan
// Cycle end is 2 Feb, which is after floor, so floor not used
const anchor = toUnix({ year: 2025, month: 1, day: 2 });
const now = toUnix({ year: 2025, month: 1, day: 15 });
const floor = toUnix({ year: 2025, month: 1, day: 1 });
const result = getCycleEnd({
anchor,
interval: BillingInterval.Month,
intervalCount: 1,
now,
floor,
});
const { year, month, day } = fromUnix(result);
expect(year).toBe(2025);
expect(month).toBe(2);
expect(day).toBe(2);
});
test("floor equals calculated end: returns that value", () => {
// Edge case: floor is exactly the calculated cycle end
const anchor = toUnix({ year: 2025, month: 1, day: 15 });
const now = toUnix({ year: 2025, month: 1, day: 20 });
const floor = toUnix({ year: 2025, month: 2, day: 15 });
const result = getCycleEnd({
anchor,
interval: BillingInterval.Month,
intervalCount: 1,
now,
floor,
});
expect(result).toBe(floor);
});
test("anchor in near future (< 1 interval): without floor wraps correctly, with floor constrains", () => {
// Anchor is 28 Feb, now is 15 Jan
// Without floor: returns 28 Jan (next boundary)
// With floor of 28 Feb: returns 28 Feb
const anchor = toUnix({ year: 2025, month: 2, day: 28 });
const now = toUnix({ year: 2025, month: 1, day: 15 });
const floor = anchor;
const result = getCycleEnd({
anchor,
interval: BillingInterval.Month,
intervalCount: 1,
now,
floor,
});
expect(result).toBe(floor);
const { month, day } = fromUnix(result);
expect(month).toBe(2);
expect(day).toBe(28);
});
});
describe("Quarterly interval (intervalCount=3) with floor", () => {
test("long trial with quarterly billing: floor constrains result", () => {
// Trial ends 15 Jul (anchor, quarterly), now is 1 Feb
// Without floor: cycle end would be 15 Apr
// With floor: returns 15 Jul
const anchor = toUnix({ year: 2025, month: 7, day: 15 });
const now = toUnix({ year: 2025, month: 2, day: 1 });
const floor = anchor;
const result = getCycleEnd({
anchor,
interval: BillingInterval.Month,
intervalCount: 3,
now,
floor,
});
expect(result).toBe(floor);
const { month, day } = fromUnix(result);
expect(month).toBe(7);
expect(day).toBe(15);
});
});
describe("Annual interval with floor", () => {
test("long trial with annual billing: floor constrains result", () => {
// Trial ends 15 Dec 2025 (anchor), now is 1 Mar 2025
// Without floor: cycle end would be 15 Dec 2024 (past!)
// Wait, that's not right. Let me recalculate...
// Actually for annual: anchor 15 Dec 2025, now 1 Mar 2025
// Difference is negative (-9 months), cyclesPassed = floor(-9/12) = -1
// Next cycle end = anchor + (-1+1)*12 = 15 Dec 2025
// So floor wouldn't be triggered here. Let's use a different scenario.
// Anchor is 15 Dec 2026, now is 1 Mar 2025
// This is a very long trial (21 months)
const anchor = toUnix({ year: 2026, month: 12, day: 15 });
const now = toUnix({ year: 2025, month: 3, day: 1 });
const floor = anchor;
const result = getCycleEnd({
anchor,
interval: BillingInterval.Year,
intervalCount: 1,
now,
floor,
});
// Without floor: would be 15 Dec 2025
// With floor: returns 15 Dec 2026
expect(result).toBe(floor);
const { year, month, day } = fromUnix(result);
expect(year).toBe(2026);
expect(month).toBe(12);
expect(day).toBe(15);
});
});
describe("Weekly interval with floor", () => {
test("trial ends in 3 weeks: floor constrains result", () => {
// Anchor is 28 Jan (3 weeks from now), now is 7 Jan
// Without floor: cycle end would be 14 Jan (next weekly boundary)
// With floor: returns 28 Jan
const anchor = toUnix({ year: 2025, month: 1, day: 28 });
const now = toUnix({ year: 2025, month: 1, day: 7 });
const floor = anchor;
const result = getCycleEnd({
anchor,
interval: BillingInterval.Week,
intervalCount: 1,
now,
floor,
});
expect(result).toBe(floor);
const { month, day } = fromUnix(result);
expect(month).toBe(1);
expect(day).toBe(28);
});
});
describe("No floor (undefined) maintains original behavior", () => {
test("without floor: returns calculated cycle end even if anchor is far future", () => {
// Same scenario as first test but without floor
const anchor = toUnix({ year: 2026, month: 8, day: 4 });
const now = toUnix({ year: 2026, month: 1, day: 16 });
const result = getCycleEnd({
anchor,
interval: BillingInterval.Month,
intervalCount: 1,
now,
// floor not provided
});
// Without floor, returns 4 Feb (next monthly boundary after now)
const { year, month, day } = fromUnix(result);
expect(year).toBe(2026);
expect(month).toBe(2);
expect(day).toBe(4);
});
});
});

View File

@@ -0,0 +1,164 @@
import { describe, expect, test } from "bun:test";
import { BillingInterval, getCycleStart } from "@autumn/shared";
import { fromUnix, toUnix } from "@tests/utils/testIntervalUtils/testUnixUtils";
/**
* Test suite for getCycleStart floor parameter
*
* The floor parameter sets a minimum allowed result. If the calculated cycle start
* is before the floor, the floor is returned instead.
*
* Use case: Subscription starts mid-cycle. Without floor, getCycleStart would return
* a date before the subscription existed.
*
* Example: Subscription starts 1 Jan, billing anchor is 15 Jan, now is 5 Jan.
* Without floor: returns 15 Dec (previous cycle boundary).
* With floor=1 Jan: returns 1 Jan (subscription start).
*/
describe("get-cycle-start-floor: floor parameter constrains minimum result", () => {
describe("Monthly interval with floor", () => {
test("subscription starts mid-cycle: anchor 15 Jan, now 5 Jan, floor 1 Jan -> returns 1 Jan", () => {
// Subscription created 1 Jan, anchor is 15 Jan, now is 5 Jan
// Without floor: cycle start would be 15 Dec (before subscription existed!)
// With floor: returns 1 Jan (subscription start)
const anchor = toUnix({ year: 2025, month: 1, day: 15 });
const now = toUnix({ year: 2025, month: 1, day: 5 });
const floor = toUnix({ year: 2025, month: 1, day: 1 });
const result = getCycleStart({
anchor,
interval: BillingInterval.Month,
intervalCount: 1,
now,
floor,
});
expect(result).toBe(floor);
const { year, month, day } = fromUnix(result);
expect(year).toBe(2025);
expect(month).toBe(1);
expect(day).toBe(1);
});
test("floor not triggered when cycle start is after floor", () => {
// Anchor is 2 Jan, now is 15 Feb, floor is 1 Jan
// Cycle start is 2 Feb, which is after floor, so floor not used
const anchor = toUnix({ year: 2025, month: 1, day: 2 });
const now = toUnix({ year: 2025, month: 2, day: 15 });
const floor = toUnix({ year: 2025, month: 1, day: 1 });
const result = getCycleStart({
anchor,
interval: BillingInterval.Month,
intervalCount: 1,
now,
floor,
});
const { year, month, day } = fromUnix(result);
expect(year).toBe(2025);
expect(month).toBe(2);
expect(day).toBe(2);
});
test("floor equals calculated start: returns that value", () => {
// Edge case: floor is exactly the calculated cycle start
const anchor = toUnix({ year: 2025, month: 1, day: 15 });
const now = toUnix({ year: 2025, month: 2, day: 20 });
const floor = toUnix({ year: 2025, month: 2, day: 15 });
const result = getCycleStart({
anchor,
interval: BillingInterval.Month,
intervalCount: 1,
now,
floor,
});
expect(result).toBe(floor);
});
test("anchor far in future with floor: returns floor when cycle start would be before subscription", () => {
// Anchor is 15 Aug (7 months in future), now is 10 Jan
// Without floor: cycle start would be 15 Dec (previous year)
// With floor of 1 Jan: returns 1 Jan
const anchor = toUnix({ year: 2025, month: 8, day: 15 });
const now = toUnix({ year: 2025, month: 1, day: 10 });
const floor = toUnix({ year: 2025, month: 1, day: 1 });
const result = getCycleStart({
anchor,
interval: BillingInterval.Month,
intervalCount: 1,
now,
floor,
});
expect(result).toBe(floor);
});
});
describe("Quarterly interval (intervalCount=3) with floor", () => {
test("subscription starts mid-quarter: floor constrains result", () => {
// Anchor is 15 Apr (quarterly), now is 20 Feb, subscription started 1 Feb
// Without floor: cycle start would be 15 Jan
// With floor: returns 1 Feb
const anchor = toUnix({ year: 2025, month: 4, day: 15 });
const now = toUnix({ year: 2025, month: 2, day: 20 });
const floor = toUnix({ year: 2025, month: 2, day: 1 });
const result = getCycleStart({
anchor,
interval: BillingInterval.Month,
intervalCount: 3,
now,
floor,
});
expect(result).toBe(floor);
});
});
describe("Annual interval with floor", () => {
test("subscription starts mid-year: floor constrains result", () => {
// Anchor is 15 Dec 2025, now is 1 Mar 2025, subscription started 1 Feb 2025
// Without floor: cycle start would be 15 Dec 2024
// With floor: returns 1 Feb 2025
const anchor = toUnix({ year: 2025, month: 12, day: 15 });
const now = toUnix({ year: 2025, month: 3, day: 1 });
const floor = toUnix({ year: 2025, month: 2, day: 1 });
const result = getCycleStart({
anchor,
interval: BillingInterval.Year,
intervalCount: 1,
now,
floor,
});
expect(result).toBe(floor);
});
});
describe("No floor (undefined) maintains original behavior", () => {
test("without floor: returns calculated cycle start even if in the past", () => {
// Same scenario as first test but without floor
const anchor = toUnix({ year: 2025, month: 1, day: 15 });
const now = toUnix({ year: 2025, month: 1, day: 5 });
const result = getCycleStart({
anchor,
interval: BillingInterval.Month,
intervalCount: 1,
now,
// floor not provided
});
// Without floor, returns 15 Dec (previous cycle boundary)
const { year, month, day } = fromUnix(result);
expect(year).toBe(2024);
expect(month).toBe(12);
expect(day).toBe(15);
});
});
});

View File

@@ -107,26 +107,5 @@ describe(chalk.yellowBright("computeUpdateSubscriptionIntent"), () => {
expect(result).toBe(UpdateSubscriptionIntent.UpdatePlan);
});
test("returns UpdatePlan with no options or items", () => {
const params: UpdateSubscriptionV0Params = {
...baseParams,
};
const result = computeUpdateSubscriptionIntent(params);
expect(result).toBe(UpdateSubscriptionIntent.UpdatePlan);
});
test("returns UpdatePlan when options is empty array", () => {
const params: UpdateSubscriptionV0Params = {
...baseParams,
options: [],
};
const result = computeUpdateSubscriptionIntent(params);
expect(result).toBe(UpdateSubscriptionIntent.UpdatePlan);
});
});
});