fix allocated tests
This commit is contained in:
@@ -47,7 +47,10 @@ export const computeUpdateCustomerEntitlementPlan = ({
|
||||
// Downgrade case
|
||||
if (previousOverage <= 0) {
|
||||
// Just return
|
||||
return undefined;
|
||||
return {
|
||||
customerEntitlement,
|
||||
balanceChange: 0,
|
||||
};
|
||||
} else {
|
||||
// Plan for downgrade
|
||||
const customerPrice = cusEntToCusPrice({
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import {
|
||||
ApiVersion,
|
||||
LegacyVersion,
|
||||
OnDecrease,
|
||||
OnIncrease,
|
||||
} from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "@tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectSubQuantityCorrect } from "@tests/utils/expectUtils/expectContUseUtils.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructArrearProratedItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { getV2Balance } from "../../testBalanceUtils";
|
||||
|
||||
const userItem = constructArrearProratedItem({
|
||||
featureId: TestFeature.Users,
|
||||
pricePerUnit: 50,
|
||||
includedUsage: 1,
|
||||
config: {
|
||||
on_increase: OnIncrease.BillImmediately,
|
||||
on_decrease: OnDecrease.None,
|
||||
},
|
||||
});
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [userItem],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const testCase = "track-paid-allocated4";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing track usage for cont use (without overage)`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
const autumnV2: AutumnInt = new AutumnInt({ version: ApiVersion.V2_0 });
|
||||
|
||||
beforeAll(async () => {
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
});
|
||||
|
||||
let usage = 0;
|
||||
test("should attach pro", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
});
|
||||
|
||||
test("should track +1 and have no new invoice", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Users,
|
||||
value: 1,
|
||||
});
|
||||
|
||||
usage += 1;
|
||||
|
||||
await expectSubQuantityCorrect({
|
||||
stripeCli: ctx.stripeCli,
|
||||
productId: pro.id,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
customerId,
|
||||
usage,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const invoices = customer.invoices;
|
||||
expect(invoices.length).toBe(1);
|
||||
});
|
||||
|
||||
test("should track -1 and have no new invoice", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Users,
|
||||
value: -1,
|
||||
});
|
||||
|
||||
usage -= 1;
|
||||
|
||||
await expectSubQuantityCorrect({
|
||||
stripeCli: ctx.stripeCli,
|
||||
productId: pro.id,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
customerId,
|
||||
usage,
|
||||
});
|
||||
|
||||
// Verify balance values reflect the replaceable (unused) logic
|
||||
const v2Balance = await getV2Balance({
|
||||
customerId,
|
||||
featureId: TestFeature.Users,
|
||||
});
|
||||
|
||||
expect(v2Balance).toMatchObject({
|
||||
granted_balance: 1,
|
||||
purchased_balance: 0,
|
||||
current_balance: 1,
|
||||
usage: 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,227 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { LegacyVersion, OnDecrease, OnIncrease } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "@tests/utils/expectUtils/expectAttach.js";
|
||||
import {
|
||||
expectSubQuantityCorrect,
|
||||
expectUpcomingItemsCorrect,
|
||||
} from "@tests/utils/expectUtils/expectContUseUtils.js";
|
||||
import { advanceTestClock } from "@tests/utils/stripeUtils.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { addWeeks } from "date-fns";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructArrearProratedItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { getV2Balance } from "../../testBalanceUtils";
|
||||
|
||||
const userItem = constructArrearProratedItem({
|
||||
featureId: TestFeature.Users,
|
||||
pricePerUnit: 50,
|
||||
includedUsage: 1,
|
||||
config: {
|
||||
on_increase: OnIncrease.ProrateNextCycle,
|
||||
on_decrease: OnDecrease.ProrateNextCycle,
|
||||
},
|
||||
});
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [userItem],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const testCase = "track-paid-allocated5";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing track usage for cont use, prorate next cycle`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let curUnix = Date.now();
|
||||
|
||||
beforeAll(async () => {
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
let usage = 0;
|
||||
test("should attach pro", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
});
|
||||
|
||||
test("should create track +3 usage and have correct invoice", async () => {
|
||||
curUnix = await advanceTestClock({
|
||||
stripeCli: ctx.stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addWeeks(new Date(), 2).getTime(),
|
||||
waitForSeconds: 30,
|
||||
});
|
||||
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Users,
|
||||
value: 3,
|
||||
});
|
||||
|
||||
usage += 3;
|
||||
|
||||
const { stripeSubs, fullCus } = await expectSubQuantityCorrect({
|
||||
stripeCli: ctx.stripeCli,
|
||||
productId: pro.id,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
customerId,
|
||||
usage,
|
||||
});
|
||||
|
||||
await expectUpcomingItemsCorrect({
|
||||
stripeCli: ctx.stripeCli,
|
||||
fullCus,
|
||||
stripeSubs,
|
||||
curUnix,
|
||||
expectedNumItems: 1,
|
||||
unitPrice: userItem.price!,
|
||||
quantity: 2,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const invoices = customer.invoices;
|
||||
expect(invoices.length).toBe(1);
|
||||
|
||||
const v2Balance = await getV2Balance({
|
||||
customerId,
|
||||
featureId: TestFeature.Users,
|
||||
});
|
||||
|
||||
expect(v2Balance).toMatchObject({
|
||||
granted_balance: 1,
|
||||
purchased_balance: 2,
|
||||
current_balance: 0,
|
||||
usage: 3,
|
||||
});
|
||||
});
|
||||
|
||||
test("should track -1 and have no new invoice", async () => {
|
||||
curUnix = await advanceTestClock({
|
||||
stripeCli: ctx.stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addWeeks(curUnix, 1).getTime(),
|
||||
waitForSeconds: 30,
|
||||
});
|
||||
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Users,
|
||||
value: -1,
|
||||
});
|
||||
|
||||
usage -= 1;
|
||||
|
||||
const { stripeSubs, fullCus } = await expectSubQuantityCorrect({
|
||||
stripeCli: ctx.stripeCli,
|
||||
productId: pro.id,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
customerId,
|
||||
usage,
|
||||
});
|
||||
|
||||
await expectUpcomingItemsCorrect({
|
||||
stripeCli: ctx.stripeCli,
|
||||
fullCus,
|
||||
stripeSubs,
|
||||
unitPrice: userItem.price!,
|
||||
curUnix,
|
||||
expectedNumItems: 2,
|
||||
quantity: -1,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const invoices = customer.invoices;
|
||||
expect(invoices.length).toBe(1);
|
||||
|
||||
const v2Balance = await getV2Balance({
|
||||
customerId,
|
||||
featureId: TestFeature.Users,
|
||||
});
|
||||
|
||||
expect(v2Balance).toMatchObject({
|
||||
granted_balance: 1,
|
||||
purchased_balance: 1,
|
||||
current_balance: 0,
|
||||
usage: 2,
|
||||
});
|
||||
});
|
||||
|
||||
test("should track -1 and have no new invoice", async () => {
|
||||
const quantity = -1;
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Users,
|
||||
value: quantity,
|
||||
});
|
||||
|
||||
usage += quantity;
|
||||
|
||||
const { stripeSubs, cusProduct, fullCus } = await expectSubQuantityCorrect({
|
||||
stripeCli: ctx.stripeCli,
|
||||
productId: pro.id,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
customerId,
|
||||
usage,
|
||||
});
|
||||
|
||||
await expectUpcomingItemsCorrect({
|
||||
stripeCli: ctx.stripeCli,
|
||||
fullCus,
|
||||
stripeSubs,
|
||||
unitPrice: userItem.price!,
|
||||
curUnix,
|
||||
expectedNumItems: 3,
|
||||
quantity,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const invoices = customer.invoices;
|
||||
expect(invoices.length).toBe(1);
|
||||
|
||||
const v2Balance = await getV2Balance({
|
||||
customerId,
|
||||
featureId: TestFeature.Users,
|
||||
});
|
||||
|
||||
expect(v2Balance).toMatchObject({
|
||||
granted_balance: 1,
|
||||
purchased_balance: 0,
|
||||
current_balance: 0,
|
||||
usage: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -5,12 +5,10 @@ import {
|
||||
OnIncrease,
|
||||
ProductItemFeatureType,
|
||||
} from "@autumn/shared";
|
||||
import { calculateProratedDiff } from "@tests/integration/billing/utils/proration/calculateProratedDiff.js";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { hoursToFinalizeInvoice } from "@tests/utils/constants.js";
|
||||
import {
|
||||
expectSubQuantityCorrect,
|
||||
expectUpcomingItemsCorrect,
|
||||
} from "@tests/utils/expectUtils/expectContUseUtils.js";
|
||||
import { expectSubQuantityCorrect } from "@tests/utils/expectUtils/expectContUseUtils.js";
|
||||
import { getSubsFromCusId } from "@tests/utils/expectUtils/expectSubUtils.js";
|
||||
import { timeout } from "@tests/utils/genUtils.js";
|
||||
import { advanceTestClock } from "@tests/utils/stripeUtils.js";
|
||||
@@ -211,28 +209,36 @@ test.concurrent(`${chalk.yellowBright("legacy-set-usage2: ProrateNextCycle sub q
|
||||
|
||||
await timeout(15000);
|
||||
|
||||
let usage = 3;
|
||||
const usage1 = 3;
|
||||
|
||||
const { stripeSubs, fullCus } = await expectSubQuantityCorrect({
|
||||
const { stripeSubs } = await expectSubQuantityCorrect({
|
||||
stripeCli: ctx.stripeCli,
|
||||
productId: pro.id,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
customerId,
|
||||
usage,
|
||||
usage: usage1,
|
||||
});
|
||||
|
||||
await expectUpcomingItemsCorrect({
|
||||
stripeCli: ctx.stripeCli,
|
||||
fullCus,
|
||||
stripeSubs,
|
||||
curUnix,
|
||||
expectedNumItems: 1,
|
||||
unitPrice: userItem.price!,
|
||||
quantity: 2, // 3 usage - 1 included = 2 overage
|
||||
const stripeCustomerId = stripeSubs[0].customer as string;
|
||||
|
||||
// Step 1: overage went 0 → 2. Credit for $0 old is filtered; 1 deferred invoice item created.
|
||||
const proratedCharge1 = await calculateProratedDiff({
|
||||
customerId,
|
||||
advancedTo: curUnix,
|
||||
oldAmount: 0,
|
||||
newAmount: 2 * userItem.price!,
|
||||
});
|
||||
|
||||
const items1 = await ctx.stripeCli.invoiceItems.list({
|
||||
customer: stripeCustomerId,
|
||||
});
|
||||
expect(items1.data.length).toBe(1);
|
||||
expect(
|
||||
Math.abs(items1.data[0].amount - Math.round(proratedCharge1 * 100)),
|
||||
).toBeLessThanOrEqual(1);
|
||||
|
||||
const customer1 = await autumnV1.customers.get<ApiCustomerV3>(customerId);
|
||||
expect(customer1.invoices!.length).toBe(1);
|
||||
|
||||
@@ -250,7 +256,7 @@ test.concurrent(`${chalk.yellowBright("legacy-set-usage2: ProrateNextCycle sub q
|
||||
value: 2,
|
||||
});
|
||||
|
||||
const result2 = await expectSubQuantityCorrect({
|
||||
await expectSubQuantityCorrect({
|
||||
stripeCli: ctx.stripeCli,
|
||||
productId: pro.id,
|
||||
db: ctx.db,
|
||||
@@ -260,16 +266,25 @@ test.concurrent(`${chalk.yellowBright("legacy-set-usage2: ProrateNextCycle sub q
|
||||
usage: 2,
|
||||
});
|
||||
|
||||
await expectUpcomingItemsCorrect({
|
||||
stripeCli: ctx.stripeCli,
|
||||
fullCus: result2.fullCus,
|
||||
stripeSubs: result2.stripeSubs,
|
||||
unitPrice: userItem.price!,
|
||||
curUnix,
|
||||
expectedNumItems: 2,
|
||||
quantity: -1, // decrease by 1 seat
|
||||
// Step 2: overage went 2 → 1. Two deferred items: credit for old (2 overage) + charge for new (1 overage).
|
||||
// Net of the 2 newest items = prorated diff from 2×$50 → 1×$50.
|
||||
const proratedDiff2 = await calculateProratedDiff({
|
||||
customerId,
|
||||
advancedTo: curUnix,
|
||||
oldAmount: 2 * userItem.price!,
|
||||
newAmount: 1 * userItem.price!,
|
||||
});
|
||||
|
||||
const items2 = await ctx.stripeCli.invoiceItems.list({
|
||||
customer: stripeCustomerId,
|
||||
});
|
||||
expect(items2.data.length).toBe(3);
|
||||
|
||||
const netStep2Cents = items2.data[0].amount + items2.data[1].amount;
|
||||
expect(
|
||||
Math.abs(netStep2Cents - Math.round(proratedDiff2 * 100)),
|
||||
).toBeLessThanOrEqual(1);
|
||||
|
||||
const customer2 = await autumnV1.customers.get<ApiCustomerV3>(customerId);
|
||||
expect(customer2.invoices!.length).toBe(1);
|
||||
|
||||
@@ -280,28 +295,35 @@ test.concurrent(`${chalk.yellowBright("legacy-set-usage2: ProrateNextCycle sub q
|
||||
value: 4,
|
||||
});
|
||||
|
||||
usage = 4;
|
||||
|
||||
const result3 = await expectSubQuantityCorrect({
|
||||
await expectSubQuantityCorrect({
|
||||
stripeCli: ctx.stripeCli,
|
||||
productId: pro.id,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
customerId,
|
||||
usage,
|
||||
usage: 4,
|
||||
});
|
||||
|
||||
await expectUpcomingItemsCorrect({
|
||||
stripeCli: ctx.stripeCli,
|
||||
fullCus: result3.fullCus,
|
||||
stripeSubs: result3.stripeSubs,
|
||||
unitPrice: userItem.price!,
|
||||
curUnix,
|
||||
expectedNumItems: 3,
|
||||
quantity: 2, // +2 seats from usage=2 to usage=4
|
||||
// Step 3: overage went 1 → 3. Two deferred items: credit for old (1 overage) + charge for new (3 overage).
|
||||
// Net of the 2 newest items = prorated diff from 1×$50 → 3×$50.
|
||||
const proratedDiff3 = await calculateProratedDiff({
|
||||
customerId,
|
||||
advancedTo: curUnix,
|
||||
oldAmount: 1 * userItem.price!,
|
||||
newAmount: 3 * userItem.price!,
|
||||
});
|
||||
|
||||
const items3 = await ctx.stripeCli.invoiceItems.list({
|
||||
customer: stripeCustomerId,
|
||||
});
|
||||
expect(items3.data.length).toBe(5);
|
||||
|
||||
const netStep3Cents = items3.data[0].amount + items3.data[1].amount;
|
||||
expect(
|
||||
Math.abs(netStep3Cents - Math.round(proratedDiff3 * 100)),
|
||||
).toBeLessThanOrEqual(1);
|
||||
|
||||
const customer3 = await autumnV1.customers.get<ApiCustomerV3>(customerId);
|
||||
expect(customer3.invoices!.length).toBe(1);
|
||||
}, 300_000); // Longer timeout for Stripe test clock operations
|
||||
}); // Longer timeout for Stripe test clock operations
|
||||
|
||||
@@ -75,6 +75,31 @@ test(`${chalk.yellowBright("bill-imm1: track within included usage creates no in
|
||||
await expectCustomerInvoiceCorrect({ customerId, count: 1 });
|
||||
|
||||
await expectStripeSubscriptionCorrect({ ctx, customerId });
|
||||
|
||||
await autumnV2.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Users,
|
||||
value: -1,
|
||||
});
|
||||
|
||||
expect(trackRes.balance).toMatchObject({
|
||||
granted_balance: 1,
|
||||
purchased_balance: 0,
|
||||
current_balance: 1,
|
||||
usage: 0,
|
||||
});
|
||||
|
||||
await expectFeatureCachedAndDb({
|
||||
autumn: autumnV1,
|
||||
customerId,
|
||||
featureId: TestFeature.Users,
|
||||
balance: 1,
|
||||
usage: 0,
|
||||
});
|
||||
|
||||
await expectCustomerInvoiceCorrect({ customerId, count: 1 });
|
||||
|
||||
await expectStripeSubscriptionCorrect({ ctx, customerId });
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -10,8 +10,10 @@ import { expectCustomerInvoiceCorrect } from "@tests/integration/billing/utils/e
|
||||
import { expectFeatureCachedAndDb } from "@tests/integration/billing/utils/expectFeatureCachedAndDb.js";
|
||||
import { expectStripeSubscriptionCorrect } from "@tests/integration/billing/utils/expectStripeSubCorrect/expectStripeSubscriptionCorrect.js";
|
||||
import { calculateProratedDiff } from "@tests/integration/billing/utils/proration/calculateProratedDiff.js";
|
||||
import { getStripeSubscription } from "@tests/integration/billing/utils/stripeSubscriptionUtils.js";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { products } from "@tests/utils/fixtures/products.js";
|
||||
import { advanceTestClock } from "@tests/utils/stripeUtils.js";
|
||||
import { advanceToNextInvoice } from "@tests/utils/testAttachUtils/testAttachUtils.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js";
|
||||
@@ -193,3 +195,135 @@ test(`${chalk.yellowBright("prorate-nc2: mid-cycle decrease creates no immediate
|
||||
|
||||
await expectStripeSubscriptionCorrect({ ctx, customerId });
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// prorate-nc3: Track +3 then -1 — verify pending invoice items
|
||||
//
|
||||
// Checks that each track creates correct prorated pending invoice
|
||||
// items on the Stripe subscription. A decrease creates two items
|
||||
// (credit for old allocation + charge for new allocation).
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
test(`${chalk.yellowBright("prorate-nc3: pending invoice items match prorated amounts after increase and decrease")}`, async () => {
|
||||
const pro = products.pro({ id: "pro", items: [userItem] });
|
||||
|
||||
const { customerId, autumnV1, autumnV2, testClockId, advancedTo } =
|
||||
await initScenario({
|
||||
customerId: "prorate-nc3",
|
||||
setup: [
|
||||
s.customer({ testClock: true, paymentMethod: "success" }),
|
||||
s.products({ list: [pro] }),
|
||||
],
|
||||
actions: [
|
||||
s.attach({ productId: pro.id }),
|
||||
s.advanceTestClock({ weeks: 2 }),
|
||||
],
|
||||
});
|
||||
|
||||
// ─── Step 1: Track +3 (1 included + 2 overage) ───
|
||||
|
||||
const trackRes1: TrackResponseV2 = await autumnV2.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Users,
|
||||
value: 3,
|
||||
});
|
||||
|
||||
expect(trackRes1.balance).toMatchObject({
|
||||
granted_balance: 1,
|
||||
purchased_balance: 2,
|
||||
current_balance: 0,
|
||||
usage: 3,
|
||||
});
|
||||
|
||||
await expectFeatureCachedAndDb({
|
||||
autumn: autumnV1,
|
||||
customerId,
|
||||
featureId: TestFeature.Users,
|
||||
balance: -2,
|
||||
usage: 3,
|
||||
});
|
||||
|
||||
await expectCustomerInvoiceCorrect({ customerId, count: 1 });
|
||||
await expectStripeSubscriptionCorrect({ ctx, customerId });
|
||||
|
||||
const proratedOverage = await calculateProratedDiff({
|
||||
customerId,
|
||||
advancedTo,
|
||||
oldAmount: 0,
|
||||
newAmount: 2 * PRICE_PER_SEAT,
|
||||
});
|
||||
|
||||
const { stripeCli, stripeCustomerId } = await getStripeSubscription({
|
||||
customerId,
|
||||
});
|
||||
|
||||
const itemsAfterIncrease = await stripeCli.invoiceItems.list({
|
||||
customer: stripeCustomerId,
|
||||
});
|
||||
|
||||
expect(itemsAfterIncrease.data.length).toBe(1);
|
||||
expect(itemsAfterIncrease.data[0].amount).toBe(
|
||||
Math.round(proratedOverage * 100),
|
||||
);
|
||||
|
||||
// ─── Step 2: Advance 1 week, then track -1 ───
|
||||
|
||||
const advancedTo2 = await advanceTestClock({
|
||||
stripeCli: ctx.stripeCli,
|
||||
testClockId: testClockId!,
|
||||
startingFrom: new Date(advancedTo),
|
||||
numberOfWeeks: 1,
|
||||
waitForSeconds: 30,
|
||||
});
|
||||
|
||||
const trackRes2: TrackResponseV2 = await autumnV2.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Users,
|
||||
value: -1,
|
||||
});
|
||||
|
||||
expect(trackRes2.balance).toMatchObject({
|
||||
granted_balance: 1,
|
||||
purchased_balance: 1,
|
||||
current_balance: 0,
|
||||
usage: 2,
|
||||
});
|
||||
|
||||
await expectFeatureCachedAndDb({
|
||||
autumn: autumnV1,
|
||||
customerId,
|
||||
featureId: TestFeature.Users,
|
||||
balance: -1,
|
||||
usage: 2,
|
||||
});
|
||||
|
||||
await expectCustomerInvoiceCorrect({ customerId, count: 1 });
|
||||
await expectStripeSubscriptionCorrect({ ctx, customerId });
|
||||
|
||||
const proratedDecrease = await calculateProratedDiff({
|
||||
customerId,
|
||||
advancedTo: advancedTo2,
|
||||
oldAmount: 2 * PRICE_PER_SEAT,
|
||||
newAmount: 1 * PRICE_PER_SEAT,
|
||||
});
|
||||
|
||||
const itemsAfterDecrease = await stripeCli.invoiceItems.list({
|
||||
customer: stripeCustomerId,
|
||||
});
|
||||
|
||||
// 3 total: 1 from the increase + 2 from the decrease (credit + charge)
|
||||
expect(itemsAfterDecrease.data.length).toBe(3);
|
||||
|
||||
// Newest 2 items are from the decrease (Stripe returns newest first)
|
||||
const decreaseItems = itemsAfterDecrease.data.slice(0, 2);
|
||||
const hasCredit = decreaseItems.some((item) => item.amount < 0);
|
||||
const hasCharge = decreaseItems.some((item) => item.amount > 0);
|
||||
expect(hasCredit).toBe(true);
|
||||
expect(hasCharge).toBe(true);
|
||||
|
||||
const decreaseNetCents = decreaseItems.reduce(
|
||||
(sum, item) => sum + item.amount,
|
||||
0,
|
||||
);
|
||||
expect(decreaseNetCents).toBe(Math.round(proratedDecrease * 100));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user