latest
This commit is contained in:
@@ -8,7 +8,7 @@ BUN_PARALLEL_V2 \
|
||||
'integration/billing/migrations' \
|
||||
'integration/billing/cron' \
|
||||
'integration/crud/customers' \
|
||||
# 'integration/billing/attach' \
|
||||
'integration/billing/attach' \
|
||||
|
||||
|
||||
# 'integration/billing/attach' \
|
||||
|
||||
@@ -4,23 +4,26 @@ source "$(dirname "$0")/config.sh"
|
||||
|
||||
export TEST_FILE_CONCURRENCY=6
|
||||
|
||||
# 'attach/migrations' \
|
||||
BUN_PARALLEL_V2 \
|
||||
'attach/basic' \
|
||||
'attach/upgrade' \
|
||||
'attach/downgrade' \
|
||||
'attach/free' \
|
||||
'attach/addOn' \
|
||||
'attach/checkout' \
|
||||
'attach/others' \
|
||||
'attach/upgradeOld' \
|
||||
'attach/response' \
|
||||
'interval/upgrade' \
|
||||
'interval/multiSub' \
|
||||
# 'billing/new-billing-subscription' \
|
||||
# 'billing/legacy/attach' \
|
||||
# 'server/tests/attach/entities'
|
||||
# --max=6
|
||||
'billing/legacy/attach'
|
||||
|
||||
|
||||
# 'billing/invoice-action-required' \
|
||||
# BUN_PARALLEL_V2 \
|
||||
# 'attach/basic' \
|
||||
# 'attach/upgrade' \
|
||||
# 'attach/downgrade' \
|
||||
# 'attach/free' \
|
||||
# 'attach/addOn' \
|
||||
# 'attach/checkout' \
|
||||
# 'attach/others' \
|
||||
# 'attach/upgradeOld' \
|
||||
# 'attach/response' \
|
||||
# 'interval/upgrade' \
|
||||
# 'interval/multiSub' \
|
||||
# 'server/tests/attach/entities'
|
||||
# # 'billing/new-billing-subscription' \
|
||||
# # 'billing/legacy/attach' \
|
||||
# # --max=6
|
||||
|
||||
|
||||
# # 'billing/invoice-action-required' \
|
||||
@@ -111,6 +111,8 @@ export const handleAttach = createRoute({
|
||||
|
||||
const { products, customer } = attachParams;
|
||||
|
||||
console.log("ATTACH RESPONSE:", response);
|
||||
|
||||
const responseV1 = AttachResponseV1Schema.parse({
|
||||
success: true,
|
||||
product_ids: products.map((p) => p.id),
|
||||
|
||||
@@ -20,7 +20,7 @@ export const buildAutumnLineItems = ({
|
||||
const { logger } = ctx;
|
||||
|
||||
// For now, update subscription doesn't charge for existing usage.
|
||||
const { lineItems: arrearLineItems, updateCustomerEntitlements } =
|
||||
let { lineItems: arrearLineItems, updateCustomerEntitlements } =
|
||||
deletedCustomerProduct && includeArrearLineItems
|
||||
? customerProductToArrearLineItems({
|
||||
ctx,
|
||||
@@ -33,6 +33,8 @@ export const buildAutumnLineItems = ({
|
||||
})
|
||||
: { lineItems: [], updateCustomerEntitlements: [] };
|
||||
|
||||
arrearLineItems = arrearLineItems.filter((lineItem) => lineItem.amount !== 0);
|
||||
|
||||
// Get line items for ongoing cus product
|
||||
const deletedLineItems = deletedCustomerProduct
|
||||
? customerProductToLineItems({
|
||||
|
||||
@@ -32,26 +32,11 @@ export const filterLineItemsForTrialTransition = ({
|
||||
return lineItems;
|
||||
}
|
||||
|
||||
return lineItems
|
||||
.map((lineItem) => {
|
||||
return lineItems.filter((lineItem) => {
|
||||
const { billingTiming, direction, price } = lineItem.context;
|
||||
const isPositive = lineItem.amount > 0;
|
||||
const isRecurringPrice = !isOneOffPrice(price);
|
||||
|
||||
// Starting trial (!isTrialing → willBeTrialing):
|
||||
// Mark in_advance positive recurring items as deferred (will be charged after trial)
|
||||
if (willBeTrialing) {
|
||||
if (billingTiming === "in_advance" && isPositive && isRecurringPrice) {
|
||||
return { ...lineItem, deferredForTrial: true };
|
||||
}
|
||||
}
|
||||
|
||||
return lineItem;
|
||||
})
|
||||
.filter((lineItem) => {
|
||||
const { billingTiming, direction } = lineItem.context;
|
||||
const isPositive = lineItem.amount > 0;
|
||||
|
||||
// Ending trial (isTrialing → !willBeTrialing):
|
||||
// Filter out refunds and in_arrear positive items (no refund for trial period, no arrear charges)
|
||||
if (isTrialing) {
|
||||
@@ -59,6 +44,13 @@ export const filterLineItemsForTrialTransition = ({
|
||||
if (billingTiming === "in_arrear" && isPositive) return false;
|
||||
}
|
||||
|
||||
// Starting trial (!isTrialing → willBeTrialing):
|
||||
// Filter out in_advance positive items (no charge for upcoming trial period)
|
||||
if (willBeTrialing) {
|
||||
if (billingTiming === "in_advance" && isPositive && isRecurringPrice)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -15,10 +15,7 @@ import { attachParamsToMetadata } from "@/internal/billing/attach/utils/attachPa
|
||||
import { billingActions } from "@/internal/billing/v2/actions/index.js";
|
||||
import { createFullCusProduct } from "@/internal/customers/add-product/createFullCusProduct.js";
|
||||
import type { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js";
|
||||
import {
|
||||
attachToInvoiceResponse,
|
||||
insertInvoiceFromAttach,
|
||||
} from "@/internal/invoices/invoiceUtils.js";
|
||||
import { insertInvoiceFromAttach } from "@/internal/invoices/invoiceUtils.js";
|
||||
import { getNextStartOfMonthUnix } from "@/internal/products/prices/billingIntervalUtils.js";
|
||||
import { addIntervalToAnchor } from "@/internal/products/prices/billingIntervalUtils2.js";
|
||||
import { getSmallestInterval } from "@/internal/products/prices/priceUtils/priceIntervalUtils.js";
|
||||
@@ -95,9 +92,7 @@ export const handlePaidProduct = async ({
|
||||
checkout_url: billingResponse?.payment_url ?? undefined,
|
||||
|
||||
invoice: attachParams.invoiceOnly
|
||||
? attachToInvoiceResponse({
|
||||
invoice: billingResult?.stripe?.stripeInvoice || undefined,
|
||||
})
|
||||
? billingResult?.stripe?.stripeInvoice
|
||||
: undefined,
|
||||
});
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@ export const handleUpdateQuantityFunction = async ({
|
||||
attachParams: AttachParams;
|
||||
body: AttachBodyV0;
|
||||
}) => {
|
||||
const { billingResponse } = await billingActions.legacy.updateQuantity({
|
||||
const { billingResponse, billingResult } =
|
||||
await billingActions.legacy.updateQuantity({
|
||||
ctx,
|
||||
body,
|
||||
attachParams,
|
||||
@@ -25,7 +26,7 @@ export const handleUpdateQuantityFunction = async ({
|
||||
return AttachFunctionResponseSchema.parse({
|
||||
code: SuccessCode.FeaturesUpdated,
|
||||
message: `Successfully updated quantity for features`,
|
||||
invoice: billingResponse?.invoice,
|
||||
invoice: billingResult?.stripe?.stripeInvoice,
|
||||
checkout_url: billingResponse?.payment_url,
|
||||
});
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
} from "@autumn/shared";
|
||||
import { billingActions } from "@/internal/billing/v2/actions/index.js";
|
||||
import type { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js";
|
||||
import { attachToInvoiceResponse } from "@/internal/invoices/invoiceUtils.js";
|
||||
import type { AutumnContext } from "../../../../../honoUtils/HonoEnv.js";
|
||||
|
||||
export const handleUpgradeFlow = async ({
|
||||
@@ -268,9 +267,7 @@ export const handleUpgradeFlow = async ({
|
||||
checkout_url: billingResponse?.payment_url,
|
||||
|
||||
invoice: attachParams.invoiceOnly
|
||||
? attachToInvoiceResponse({
|
||||
invoice: billingResult?.stripe?.stripeInvoice || undefined,
|
||||
})
|
||||
? billingResult?.stripe?.stripeInvoice
|
||||
: undefined,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -20,7 +20,7 @@ import { products } from "@tests/utils/fixtures/products.js";
|
||||
import { createTestContext } from "@tests/utils/testInitUtils/createTestContext";
|
||||
import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js";
|
||||
|
||||
const SEED_PREFIX = "seed-cancel-test";
|
||||
const SEED_PREFIX = "seed";
|
||||
|
||||
async function seedScenarios() {
|
||||
// Clear and setup org first
|
||||
@@ -40,7 +40,7 @@ async function seedScenarios() {
|
||||
items.consumableWords({ includedUsage: 100 }), // 100 free words, then $0.05/word overage
|
||||
items.allocatedWorkflows({ includedUsage: 3 }), // 3 free workflows, $10/workflow overage
|
||||
items.prepaidMessages({
|
||||
includedUsage: 50,
|
||||
includedUsage: 100,
|
||||
billingUnits: 100,
|
||||
price: 10,
|
||||
}), // 50 free messages, $10/100 pack
|
||||
@@ -83,12 +83,26 @@ async function seedScenarios() {
|
||||
],
|
||||
});
|
||||
|
||||
const growthProduct = products.growth({
|
||||
id: "growth",
|
||||
items: [
|
||||
items.consumableWords({ includedUsage: 1000 }),
|
||||
items.allocatedWorkflows({ includedUsage: 10 }),
|
||||
items.prepaidMessages({
|
||||
includedUsage: 200,
|
||||
billingUnits: 100,
|
||||
price: 6,
|
||||
}), // 200 free messages, $8/100 pack
|
||||
],
|
||||
});
|
||||
|
||||
const allProducts = [
|
||||
proProduct,
|
||||
recurringAddOn,
|
||||
freeProduct,
|
||||
oneTimeProduct,
|
||||
premiumProduct,
|
||||
growthProduct,
|
||||
];
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import type { ApiCustomerV3 } from "@autumn/shared";
|
||||
import { expectCustomerFeatureCorrect } from "@tests/integration/billing/utils/expectCustomerFeatureCorrect.js";
|
||||
import { expectProductActive } from "@tests/integration/billing/utils/expectCustomerProductCorrect.js";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { items } from "@tests/utils/fixtures/items.js";
|
||||
import { products } from "@tests/utils/fixtures/products.js";
|
||||
import { completeInvoiceCheckout } from "@tests/utils/stripeUtils/completeInvoiceCheckout.js";
|
||||
import { test } from "bun:test";
|
||||
import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js";
|
||||
import chalk from "chalk";
|
||||
|
||||
@@ -13,60 +6,23 @@ import chalk from "chalk";
|
||||
* Test: Attach free default product, then attach pro with invoice mode
|
||||
*/
|
||||
test.concurrent(`${chalk.yellowBright("invoice-mode: free default then pro with invoice checkout")}`, async () => {
|
||||
const messagesItem = items.monthlyMessages({ includedUsage: 50 });
|
||||
|
||||
// Free default product
|
||||
const free = products.base({
|
||||
id: "free",
|
||||
items: [messagesItem],
|
||||
isDefault: true,
|
||||
});
|
||||
|
||||
// Pro product with price
|
||||
const proMessagesItem = items.monthlyMessages({ includedUsage: 200 });
|
||||
const pro = products.pro({
|
||||
id: "pro",
|
||||
items: [proMessagesItem],
|
||||
});
|
||||
|
||||
const customerId = "temp-invoice-free-then-pro";
|
||||
|
||||
// Setup customer with default attached
|
||||
const { autumnV1 } = await initScenario({
|
||||
customerId,
|
||||
customerId: "test",
|
||||
setup: [
|
||||
s.customer({ withDefault: true }),
|
||||
s.products({ list: [free, pro] }),
|
||||
s.customer({ paymentMethod: "success" }),
|
||||
// s.products({ list: [free, pro] }),
|
||||
],
|
||||
actions: [s.attachPaymentMethod({ type: "fail" })],
|
||||
actions: [],
|
||||
});
|
||||
|
||||
// Attach pro with invoice mode
|
||||
const attachResult = await autumnV1.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
// invoice: true,
|
||||
});
|
||||
|
||||
console.log("attachResult", attachResult);
|
||||
return;
|
||||
|
||||
expect(attachResult.checkout_url).toBeDefined();
|
||||
|
||||
// Complete invoice checkout
|
||||
await completeInvoiceCheckout({
|
||||
url: attachResult.checkout_url!,
|
||||
});
|
||||
|
||||
// Verify pro is now active
|
||||
const customerAfter = await autumnV1.customers.get<ApiCustomerV3>(customerId);
|
||||
expectProductActive({
|
||||
customer: customerAfter,
|
||||
productId: pro.id,
|
||||
});
|
||||
expectCustomerFeatureCorrect({
|
||||
customer: customerAfter,
|
||||
featureId: TestFeature.Messages,
|
||||
balance: 200,
|
||||
await autumnV1.attach({
|
||||
customer_id: "test",
|
||||
product_id: "pro_seed-cancel-test",
|
||||
options: [
|
||||
{
|
||||
feature_id: "messages",
|
||||
quantity: 100,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,9 +16,13 @@
|
||||
|
||||
import { expect, test } from "bun:test";
|
||||
import { type ApiCustomerV3, CusExpand } from "@autumn/shared";
|
||||
import { expectCustomerInvoiceCorrect } from "@tests/integration/billing/utils/expectCustomerInvoiceCorrect";
|
||||
import {
|
||||
expectCustomerProducts,
|
||||
expectProductActive,
|
||||
} from "@tests/integration/billing/utils/expectCustomerProductCorrect";
|
||||
import { expectSubCount } from "@tests/merged/mergeUtils/expectSubCorrect";
|
||||
import { TestFeature } from "@tests/setup/v2Features";
|
||||
import { expectProductAttached } from "@tests/utils/expectUtils/expectProductAttached";
|
||||
import { items } from "@tests/utils/fixtures/items";
|
||||
import { products } from "@tests/utils/fixtures/products";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext";
|
||||
@@ -69,13 +73,19 @@ test.concurrent(`${chalk.yellowBright("attach: paid add-on with new_billing_subs
|
||||
],
|
||||
});
|
||||
|
||||
// After first add-on attach: 2 subs
|
||||
// After first add-on attach: 2 subs, both products active
|
||||
await expectSubCount({ ctx, customerId, count: 2 });
|
||||
|
||||
const customer1 = await autumnV1.customers.get<ApiCustomerV3>(customerId);
|
||||
expectProductAttached({ customer: customer1, productId: addOn.id });
|
||||
expect(customer1.invoices.length).toBe(2);
|
||||
expect(customer1.invoices[0].total).toBe(10);
|
||||
await expectCustomerProducts({
|
||||
customer: customer1,
|
||||
active: [pro.id, addOn.id],
|
||||
});
|
||||
expectCustomerInvoiceCorrect({
|
||||
customer: customer1,
|
||||
count: 2,
|
||||
latestTotal: 20, // recurringAddOn uses type: "pro" → $20/month
|
||||
});
|
||||
|
||||
// Attach same add-on again → 3 subs
|
||||
await autumnV1.attach({
|
||||
@@ -89,8 +99,11 @@ test.concurrent(`${chalk.yellowBright("attach: paid add-on with new_billing_subs
|
||||
const customer2 = await autumnV1.customers.get<ApiCustomerV3>(customerId);
|
||||
const addOnProduct = customer2.products.find((p) => p.id === addOn.id);
|
||||
expect(addOnProduct?.quantity).toBe(2);
|
||||
expect(customer2.invoices?.length).toBe(3);
|
||||
expect(customer2.invoices?.[0].total).toBe(10);
|
||||
expectCustomerInvoiceCorrect({
|
||||
customer: customer2,
|
||||
count: 3,
|
||||
latestTotal: 20,
|
||||
});
|
||||
}, 120000);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -143,7 +156,7 @@ test.concurrent(`${chalk.yellowBright("attach: entities with new_billing_subscri
|
||||
await expectSubCount({ ctx, customerId, count: 2 });
|
||||
|
||||
const entity1 = await autumnV1.entities.get(customerId, entities[0].id);
|
||||
expectProductAttached({ customer: entity1, productId: premium.id });
|
||||
await expectProductActive({ customer: entity1, productId: premium.id });
|
||||
|
||||
// Attach premium to entity 2 → 3 subs
|
||||
await autumnV1.attach({
|
||||
@@ -156,30 +169,25 @@ test.concurrent(`${chalk.yellowBright("attach: entities with new_billing_subscri
|
||||
await expectSubCount({ ctx, customerId, count: 3 });
|
||||
|
||||
const entity2 = await autumnV1.entities.get(customerId, entities[1].id);
|
||||
expectProductAttached({ customer: entity2, productId: premium.id });
|
||||
await expectProductActive({ customer: entity2, productId: premium.id });
|
||||
|
||||
// Verify final state
|
||||
// Verify final state: customer pro active, both entity premiums active
|
||||
const customer = await autumnV1.customers.get<ApiCustomerV3>(customerId, {
|
||||
expand: [CusExpand.Invoices],
|
||||
});
|
||||
|
||||
const customerPro = customer.products.find((p) => p.id === pro.id);
|
||||
expect(customerPro).toBeDefined();
|
||||
expect(customerPro?.status).toBe("active");
|
||||
await expectProductActive({ customer, productId: pro.id });
|
||||
|
||||
const entity1Final = await autumnV1.entities.get(customerId, entities[0].id);
|
||||
const e1Premium = entity1Final.products?.find(
|
||||
(p: { id?: string }) => p.id === premium.id,
|
||||
);
|
||||
expect(e1Premium).toBeDefined();
|
||||
expect(e1Premium!.status).toBe("active");
|
||||
await expectProductActive({
|
||||
customer: entity1Final,
|
||||
productId: premium.id,
|
||||
});
|
||||
|
||||
const entity2Final = await autumnV1.entities.get(customerId, entities[1].id);
|
||||
const e2Premium = entity2Final.products?.find(
|
||||
(p: { id?: string }) => p.id === premium.id,
|
||||
);
|
||||
expect(e2Premium).toBeDefined();
|
||||
expect(e2Premium!.status).toBe("active");
|
||||
await expectProductActive({
|
||||
customer: entity2Final,
|
||||
productId: premium.id,
|
||||
});
|
||||
}, 120000);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -229,11 +237,15 @@ test.concurrent(`${chalk.yellowBright("attach: customer upgrade doesn't affect e
|
||||
// Verify initial: customer pro + entity premium = 2 subs
|
||||
await expectSubCount({ ctx, customerId, count: 2 });
|
||||
|
||||
const customerBefore = await autumnV1.customers.get(customerId);
|
||||
expectProductAttached({ customer: customerBefore, productId: pro.id });
|
||||
const customerBefore =
|
||||
await autumnV1.customers.get<ApiCustomerV3>(customerId);
|
||||
await expectProductActive({ customer: customerBefore, productId: pro.id });
|
||||
|
||||
const entityBefore = await autumnV1.entities.get(customerId, entities[0].id);
|
||||
expectProductAttached({ customer: entityBefore, productId: premium.id });
|
||||
await expectProductActive({
|
||||
customer: entityBefore,
|
||||
productId: premium.id,
|
||||
});
|
||||
|
||||
// Upgrade customer from pro → premium
|
||||
await autumnV1.attach({
|
||||
@@ -241,29 +253,25 @@ test.concurrent(`${chalk.yellowBright("attach: customer upgrade doesn't affect e
|
||||
product_id: premium.id,
|
||||
});
|
||||
|
||||
// Customer should have premium active, pro gone
|
||||
const customerAfter = await autumnV1.customers.get<ApiCustomerV3>(customerId);
|
||||
expectProductAttached({ customer: customerAfter, productId: premium.id });
|
||||
|
||||
// Pro should be gone from customer-level products
|
||||
const proProduct = customerAfter.products.find(
|
||||
(p) => p.id === pro.id && !p.entity_id,
|
||||
);
|
||||
expect(proProduct).toBeUndefined();
|
||||
await expectCustomerProducts({
|
||||
customer: customerAfter,
|
||||
active: [premium.id],
|
||||
notPresent: [pro.id],
|
||||
});
|
||||
|
||||
// Still 2 subs (customer premium + entity premium on separate sub)
|
||||
await expectSubCount({ ctx, customerId, count: 2 });
|
||||
|
||||
// Entity should still have premium on its separate sub
|
||||
// Entity should still have premium active on its separate sub
|
||||
const entityAfter = await autumnV1.entities.get(customerId, entities[0].id);
|
||||
const entityProducts = entityAfter.products!;
|
||||
expect(entityProducts.length).toBe(1);
|
||||
const entityPremium = entityProducts.find(
|
||||
(p: { id?: string }) => p.id === premium.id,
|
||||
);
|
||||
expect(entityPremium).toBeDefined();
|
||||
expect(entityPremium!.status).toBe("active");
|
||||
await expectProductActive({
|
||||
customer: entityAfter,
|
||||
productId: premium.id,
|
||||
});
|
||||
expect(entityAfter.products!.length).toBe(1);
|
||||
|
||||
const invoices = customerAfter.invoices;
|
||||
expect(invoices).toBeDefined();
|
||||
expect(invoices!.length).toBeGreaterThanOrEqual(1);
|
||||
}, 120000);
|
||||
expect(customerAfter.invoices).toBeDefined();
|
||||
expect(customerAfter.invoices!.length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
@@ -17,6 +17,8 @@ import {
|
||||
AttachBranch,
|
||||
CusProductStatus,
|
||||
} from "@autumn/shared";
|
||||
import { expectCustomerInvoiceCorrect } from "@tests/integration/billing/utils/expectCustomerInvoiceCorrect";
|
||||
import { expectProductTrialing } from "@tests/integration/billing/utils/expectCustomerProductTrialing";
|
||||
import { expectSubToBeCorrect } from "@tests/merged/mergeUtils/expectSubCorrect";
|
||||
import { expectProductAttached } from "@tests/utils/expectUtils/expectProductAttached";
|
||||
import { items } from "@tests/utils/fixtures/items";
|
||||
@@ -111,11 +113,12 @@ test.concurrent(`${chalk.yellowBright("legacy-trial 1: upgrade during trial (pro
|
||||
});
|
||||
|
||||
// Premium period_end ≈ curUnix + 7 days
|
||||
const product = customer.products.find((p: any) => p.id === premium.id)!;
|
||||
expect(product.current_period_end).toBeDefined();
|
||||
expect(
|
||||
Math.abs(product.current_period_end! - addDays(curUnix, 7).getTime()),
|
||||
).toBeLessThanOrEqual(1000 * 60 * 30); // 30 min tolerance
|
||||
await expectProductTrialing({
|
||||
customer: customer as any,
|
||||
productId: premium.id,
|
||||
trialEndsAt: addDays(curUnix, 7).getTime(),
|
||||
toleranceMs: 1000 * 60 * 30, // 30 min tolerance
|
||||
});
|
||||
|
||||
await expectSubToBeCorrect({
|
||||
db: ctx.db,
|
||||
@@ -185,16 +188,10 @@ test.concurrent(`${chalk.yellowBright("legacy-trial 2: upgrade after trial ends
|
||||
const curUnix = await advanceTestClock({
|
||||
stripeCli: ctx.stripeCli,
|
||||
testClockId: testClockId!,
|
||||
advanceTo: addDays(new Date(), 8).getTime(),
|
||||
advanceTo: addDays(new Date(), 12).getTime(),
|
||||
waitForSeconds: 30,
|
||||
});
|
||||
|
||||
// Preview attach → should be Upgrade branch (trial ended, product is active)
|
||||
const attachPreview = await autumnV1.attachPreview({
|
||||
customer_id: customerId,
|
||||
product_id: premium.id,
|
||||
});
|
||||
expect(attachPreview?.branch).toBe(AttachBranch.Upgrade);
|
||||
|
||||
// Get checkout total for comparison
|
||||
const checkoutRes = await autumnV1.checkout({
|
||||
customer_id: customerId,
|
||||
@@ -217,14 +214,21 @@ test.concurrent(`${chalk.yellowBright("legacy-trial 2: upgrade after trial ends
|
||||
});
|
||||
|
||||
// Premium period_end ≈ curUnix + 7 days
|
||||
const product = customer.products.find((p: any) => p.id === premium.id)!;
|
||||
expect(product.current_period_end).toBeDefined();
|
||||
expect(
|
||||
Math.abs(product.current_period_end! - addDays(curUnix, 7).getTime()),
|
||||
).toBeLessThanOrEqual(1000 * 60 * 30); // 30 min tolerance
|
||||
await expectProductTrialing({
|
||||
customer: customer as any,
|
||||
productId: premium.id,
|
||||
trialEndsAt: addDays(curUnix, 7).getTime(),
|
||||
toleranceMs: 1000 * 60 * 30, // 30 min tolerance
|
||||
});
|
||||
|
||||
await expectCustomerInvoiceCorrect({
|
||||
customer: customer as any,
|
||||
count: 4,
|
||||
latestTotal: 0,
|
||||
});
|
||||
|
||||
// Invoice total should match checkout preview
|
||||
expect(customer.invoices[0].total).toBe(
|
||||
expect(customer.invoices?.[1]?.total).toBe(
|
||||
new Decimal(checkoutRes.total).toDP(2).toNumber(),
|
||||
);
|
||||
|
||||
@@ -235,4 +239,4 @@ test.concurrent(`${chalk.yellowBright("legacy-trial 2: upgrade after trial ends
|
||||
env: ctx.env,
|
||||
shouldBeTrialing: true,
|
||||
});
|
||||
}, 120000);
|
||||
});
|
||||
|
||||
@@ -14,8 +14,9 @@
|
||||
|
||||
import { expect, test } from "bun:test";
|
||||
import type { ApiCustomerV3, CusProductStatus } from "@autumn/shared";
|
||||
import { calculateExpectedInvoiceAmount } from "@tests/integration/billing/utils/calculateExpectedInvoiceAmount";
|
||||
import { expectCustomerInvoiceCorrect } from "@tests/integration/billing/utils/expectCustomerInvoiceCorrect";
|
||||
import { TestFeature } from "@tests/setup/v2Features";
|
||||
import { getExpectedInvoiceTotal } from "@tests/utils/expectUtils/expectInvoiceUtils";
|
||||
import { expectProductAttached } from "@tests/utils/expectUtils/expectProductAttached";
|
||||
import { items } from "@tests/utils/fixtures/items";
|
||||
import { products } from "@tests/utils/fixtures/products";
|
||||
@@ -57,7 +58,7 @@ test.concurrent(`${chalk.yellowBright("legacy-upgrade-merged 1: upgrade entity i
|
||||
],
|
||||
actions: [
|
||||
s.attach({ productId: pro.id, entityIndex: 0 }),
|
||||
s.attach({ productId: pro.id, entityIndex: 1, timeout: 3000 }),
|
||||
s.attach({ productId: pro.id, entityIndex: 1, timeout: 4000 }),
|
||||
s.track({
|
||||
featureId: TestFeature.Words,
|
||||
value: entity1Val,
|
||||
@@ -70,7 +71,7 @@ test.concurrent(`${chalk.yellowBright("legacy-upgrade-merged 1: upgrade entity i
|
||||
entityIndex: 1,
|
||||
timeout: 3000,
|
||||
}),
|
||||
s.advanceTestClock({ weeks: 2 }),
|
||||
// s.advanceTestClock({ weeks: 2 }),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -81,6 +82,24 @@ test.concurrent(`${chalk.yellowBright("legacy-upgrade-merged 1: upgrade entity i
|
||||
entity_id: "ent-1",
|
||||
});
|
||||
|
||||
const entity1Overage = calculateExpectedInvoiceAmount({
|
||||
items: pro.items,
|
||||
usage: [{ featureId: TestFeature.Words, value: entity1Val }],
|
||||
options: { onlyArrear: true },
|
||||
});
|
||||
|
||||
const entity1Base =
|
||||
getBasePrice({ product: premium }) - getBasePrice({ product: pro });
|
||||
|
||||
const customerAfterAttach =
|
||||
await autumnV1.customers.get<ApiCustomerV3>(customerId);
|
||||
|
||||
await expectCustomerInvoiceCorrect({
|
||||
customer: customerAfterAttach,
|
||||
count: 3,
|
||||
latestTotal: entity1Base + entity1Overage,
|
||||
});
|
||||
|
||||
// Advance to next invoice to check usage billing
|
||||
await advanceToNextInvoice({
|
||||
stripeCli: ctx.stripeCli,
|
||||
@@ -89,15 +108,10 @@ test.concurrent(`${chalk.yellowBright("legacy-upgrade-merged 1: upgrade entity i
|
||||
});
|
||||
|
||||
// Entity 2's usage on pro should show up on the invoice
|
||||
const expectedUsageTotal = await getExpectedInvoiceTotal({
|
||||
customerId,
|
||||
productId: pro.id,
|
||||
const expectedUsageTotal = calculateExpectedInvoiceAmount({
|
||||
items: pro.items,
|
||||
usage: [{ featureId: TestFeature.Words, value: entity2Val }],
|
||||
onlyIncludeUsage: true,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
options: { includeFixed: false, onlyArrear: true },
|
||||
});
|
||||
|
||||
const basePrice =
|
||||
@@ -106,7 +120,7 @@ test.concurrent(`${chalk.yellowBright("legacy-upgrade-merged 1: upgrade entity i
|
||||
const customer = await autumnV1.customers.get<ApiCustomerV3>(customerId);
|
||||
const invoice = customer.invoices![0];
|
||||
expect(invoice.total).toBe(basePrice + expectedUsageTotal);
|
||||
}, 120000);
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
// TEST 2: Upgrade cancels scheduled downgrade (single entity)
|
||||
|
||||
@@ -1,196 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import {
|
||||
type AppEnv,
|
||||
CusProductStatus,
|
||||
LegacyVersion,
|
||||
type Organization,
|
||||
} from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "@tests/utils/expectUtils/expectAttach.js";
|
||||
import { getExpectedInvoiceTotal } from "@tests/utils/expectUtils/expectInvoiceUtils.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 { getBasePrice } from "@tests/utils/testProductUtils/testProductUtils.js";
|
||||
import chalk from "chalk";
|
||||
import { addWeeks } from "date-fns";
|
||||
import type { Stripe } from "stripe";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils";
|
||||
import { constructArrearItem } 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";
|
||||
|
||||
const premium = constructProduct({
|
||||
id: "premium",
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "premium",
|
||||
});
|
||||
const premiumAnnual = constructProduct({
|
||||
id: "premiumAnnual",
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "premium",
|
||||
isAnnual: true,
|
||||
});
|
||||
|
||||
const pro = constructProduct({
|
||||
id: "pro",
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const ops = [
|
||||
{
|
||||
entityId: "1",
|
||||
product: pro,
|
||||
results: [{ product: pro, status: CusProductStatus.Active }],
|
||||
},
|
||||
{
|
||||
entityId: "2",
|
||||
product: pro,
|
||||
results: [{ product: pro, status: CusProductStatus.Active }],
|
||||
},
|
||||
];
|
||||
|
||||
const testCase = "mergedUpgrade1";
|
||||
describe(`${chalk.yellowBright("mergedUpgrade1: Testing merged subs, upgrade 1 & 2 to pro, add premium 2")}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
|
||||
let stripeCli: Stripe;
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli;
|
||||
let org: Organization;
|
||||
let env: AppEnv;
|
||||
|
||||
const entities = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Entity 1",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Entity 2",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
];
|
||||
|
||||
beforeAll(async () => {
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro, premium, premiumAnnual],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const res = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
stripeCli = ctx.stripeCli;
|
||||
db = ctx.db;
|
||||
org = ctx.org;
|
||||
env = ctx.env;
|
||||
testClockId = res.testClockId!;
|
||||
|
||||
await autumn.entities.create(customerId, entities);
|
||||
});
|
||||
|
||||
for (let index = 0; index < ops.length; index++) {
|
||||
const op = ops[index];
|
||||
test(`should attach ${op.product.id} to entity ${op.entityId}`, async () => {
|
||||
try {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: op.product,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
entityId: op.entityId,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(
|
||||
`Operation failed: ${op.entityId} ${op.product.id}, index: ${index}`,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const entity1Val = 100000;
|
||||
const entity2Val = 300000;
|
||||
|
||||
test("should advance test clock and upgrade entity 1 to premium, and have correct invoice", async () => {
|
||||
await timeout(3000);
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Words,
|
||||
value: entity1Val,
|
||||
entity_id: "1",
|
||||
});
|
||||
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Words,
|
||||
value: entity2Val,
|
||||
entity_id: "2",
|
||||
});
|
||||
|
||||
await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addWeeks(Date.now(), 2).getTime(),
|
||||
waitForSeconds: 30,
|
||||
});
|
||||
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: premium,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
entityId: "1",
|
||||
});
|
||||
});
|
||||
|
||||
test("should advance to next invoice and have correct invoice", async () => {
|
||||
await advanceToNextInvoice({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
withPause: true,
|
||||
});
|
||||
|
||||
const expectedTotal = await getExpectedInvoiceTotal({
|
||||
org,
|
||||
env,
|
||||
customerId,
|
||||
productId: pro.id,
|
||||
stripeCli,
|
||||
db,
|
||||
onlyIncludeUsage: true,
|
||||
usage: [
|
||||
{
|
||||
featureId: TestFeature.Words,
|
||||
value: entity2Val,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const invoice = customer.invoices[0];
|
||||
const basePrice =
|
||||
getBasePrice({ product: pro }) + getBasePrice({ product: premium });
|
||||
expect(invoice.total).toBe(basePrice + expectedTotal);
|
||||
});
|
||||
});
|
||||
@@ -1,143 +0,0 @@
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import {
|
||||
type AppEnv,
|
||||
CusProductStatus,
|
||||
LegacyVersion,
|
||||
type Organization,
|
||||
} from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "@tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectProductAttached } from "@tests/utils/expectUtils/expectProductAttached.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import type { Stripe } from "stripe";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } 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";
|
||||
|
||||
// UNCOMMENT FROM HERE
|
||||
|
||||
const pro = constructProduct({
|
||||
id: "pro",
|
||||
items: [constructFeatureItem({ featureId: TestFeature.Words })],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const free = constructProduct({
|
||||
id: "free",
|
||||
items: [constructFeatureItem({ featureId: TestFeature.Words })],
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const premium = constructProduct({
|
||||
id: "premium",
|
||||
items: [constructFeatureItem({ featureId: TestFeature.Words })],
|
||||
type: "premium",
|
||||
});
|
||||
const growth = constructProduct({
|
||||
id: "growth",
|
||||
items: [constructFeatureItem({ featureId: TestFeature.Words })],
|
||||
type: "growth",
|
||||
});
|
||||
|
||||
const ops = [
|
||||
{
|
||||
entityId: "1",
|
||||
product: premium,
|
||||
results: [{ product: premium, status: CusProductStatus.Active }],
|
||||
},
|
||||
{
|
||||
entityId: "2",
|
||||
product: premium,
|
||||
results: [{ product: premium, status: CusProductStatus.Active }],
|
||||
},
|
||||
{
|
||||
entityId: "1",
|
||||
product: pro,
|
||||
results: [
|
||||
{ product: premium, status: CusProductStatus.Active },
|
||||
{ product: pro, status: CusProductStatus.Scheduled },
|
||||
],
|
||||
},
|
||||
{
|
||||
entityId: "1",
|
||||
product: growth,
|
||||
results: [{ product: growth, status: CusProductStatus.Active }],
|
||||
},
|
||||
];
|
||||
|
||||
const testCase = "mergedUpgrade2";
|
||||
describe(`${chalk.yellowBright("mergedUpgrade2: Upgrading when there's a scheduled downgrade")}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
|
||||
let stripeCli: Stripe;
|
||||
let db: DrizzleCli;
|
||||
let org: Organization;
|
||||
let env: AppEnv;
|
||||
|
||||
const entities = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Entity 1",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Entity 2",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
];
|
||||
|
||||
beforeAll(async () => {
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro, free, premium, growth],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
stripeCli = ctx.stripeCli;
|
||||
db = ctx.db;
|
||||
org = ctx.org;
|
||||
env = ctx.env;
|
||||
|
||||
await autumn.entities.create(customerId, entities);
|
||||
});
|
||||
|
||||
for (const op of ops) {
|
||||
test(`should attach ${op.product.id} to entity ${op.entityId}`, async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: op.product,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
entities,
|
||||
entityId: op.entityId,
|
||||
});
|
||||
|
||||
for (const result of op.results) {
|
||||
const entity = await autumn.entities.get(customerId, op.entityId);
|
||||
expectProductAttached({
|
||||
customer: entity,
|
||||
product: result.product,
|
||||
status: result.status,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,152 +0,0 @@
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import {
|
||||
type AppEnv,
|
||||
CusProductStatus,
|
||||
LegacyVersion,
|
||||
type Organization,
|
||||
} from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "@tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectProductAttached } from "@tests/utils/expectUtils/expectProductAttached.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import type { Stripe } from "stripe";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } 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";
|
||||
|
||||
// UNCOMMENT FROM HERE
|
||||
|
||||
const pro = constructProduct({
|
||||
id: "pro",
|
||||
items: [constructFeatureItem({ featureId: TestFeature.Words })],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const free = constructProduct({
|
||||
id: "free",
|
||||
items: [constructFeatureItem({ featureId: TestFeature.Words })],
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const premium = constructProduct({
|
||||
id: "premium",
|
||||
items: [constructFeatureItem({ featureId: TestFeature.Words })],
|
||||
type: "premium",
|
||||
});
|
||||
const growth = constructProduct({
|
||||
id: "growth",
|
||||
items: [constructFeatureItem({ featureId: TestFeature.Words })],
|
||||
type: "growth",
|
||||
});
|
||||
|
||||
const ops = [
|
||||
{
|
||||
entityId: "1",
|
||||
product: premium,
|
||||
results: [{ product: premium, status: CusProductStatus.Active }],
|
||||
},
|
||||
{
|
||||
entityId: "2",
|
||||
product: premium,
|
||||
results: [{ product: premium, status: CusProductStatus.Active }],
|
||||
},
|
||||
{
|
||||
entityId: "1",
|
||||
product: pro,
|
||||
results: [
|
||||
{ product: premium, status: CusProductStatus.Active },
|
||||
{ product: pro, status: CusProductStatus.Scheduled },
|
||||
],
|
||||
},
|
||||
{
|
||||
entityId: "2",
|
||||
product: pro,
|
||||
results: [
|
||||
{ product: premium, status: CusProductStatus.Active },
|
||||
{ product: pro, status: CusProductStatus.Scheduled },
|
||||
],
|
||||
},
|
||||
{
|
||||
entityId: "2",
|
||||
product: growth,
|
||||
results: [{ product: growth, status: CusProductStatus.Active }],
|
||||
// waitForInvoice: 5000,
|
||||
},
|
||||
];
|
||||
|
||||
const testCase = "mergedUpgrade3";
|
||||
describe(`${chalk.yellowBright("mergedUpgrade3: Upgrading when there's a scheduled downgrade")}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
|
||||
let stripeCli: Stripe;
|
||||
let db: DrizzleCli;
|
||||
let org: Organization;
|
||||
let env: AppEnv;
|
||||
|
||||
const entities = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Entity 1",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Entity 2",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
];
|
||||
|
||||
beforeAll(async () => {
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro, free, premium, growth],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
stripeCli = ctx.stripeCli;
|
||||
db = ctx.db;
|
||||
org = ctx.org;
|
||||
env = ctx.env;
|
||||
|
||||
await autumn.entities.create(customerId, entities);
|
||||
});
|
||||
|
||||
for (const op of ops) {
|
||||
test(`should attach ${op.product.id} to entity ${op.entityId}`, async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: op.product,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
entities,
|
||||
entityId: op.entityId,
|
||||
});
|
||||
|
||||
for (const result of op.results) {
|
||||
const entity = await autumn.entities.get(customerId, op.entityId);
|
||||
expectProductAttached({
|
||||
customer: entity,
|
||||
product: result.product,
|
||||
status: result.status,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,146 +0,0 @@
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import {
|
||||
type AppEnv,
|
||||
CusProductStatus,
|
||||
LegacyVersion,
|
||||
type Organization,
|
||||
} from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "@tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectProductAttached } from "@tests/utils/expectUtils/expectProductAttached.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import type { Stripe } from "stripe";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } 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";
|
||||
|
||||
// OPERATIONS:
|
||||
// Pro, Pro
|
||||
// Free
|
||||
// Premium
|
||||
|
||||
const pro = constructProduct({
|
||||
id: "pro",
|
||||
items: [constructFeatureItem({ featureId: TestFeature.Words })],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const free = constructProduct({
|
||||
id: "free",
|
||||
items: [constructFeatureItem({ featureId: TestFeature.Words })],
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const premium = constructProduct({
|
||||
id: "premium",
|
||||
items: [constructFeatureItem({ featureId: TestFeature.Words })],
|
||||
type: "premium",
|
||||
});
|
||||
const growth = constructProduct({
|
||||
id: "growth",
|
||||
items: [constructFeatureItem({ featureId: TestFeature.Words })],
|
||||
type: "growth",
|
||||
});
|
||||
|
||||
const ops = [
|
||||
{
|
||||
entityId: "1",
|
||||
product: pro,
|
||||
results: [{ product: pro, status: CusProductStatus.Active }],
|
||||
},
|
||||
{
|
||||
entityId: "2",
|
||||
product: pro,
|
||||
results: [{ product: pro, status: CusProductStatus.Active }],
|
||||
},
|
||||
{
|
||||
entityId: "1",
|
||||
product: free,
|
||||
results: [
|
||||
{ product: pro, status: CusProductStatus.Active },
|
||||
{ product: free, status: CusProductStatus.Scheduled },
|
||||
],
|
||||
},
|
||||
{
|
||||
entityId: "1",
|
||||
product: premium,
|
||||
results: [{ product: premium, status: CusProductStatus.Active }],
|
||||
},
|
||||
];
|
||||
|
||||
const testCase = "mergedUpgrade4";
|
||||
describe(`${chalk.yellowBright("mergedUpgrade4: Upgrading when there's a cancel")}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
|
||||
let stripeCli: Stripe;
|
||||
let db: DrizzleCli;
|
||||
let org: Organization;
|
||||
let env: AppEnv;
|
||||
|
||||
const entities = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Entity 1",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Entity 2",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
];
|
||||
|
||||
beforeAll(async () => {
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro, free, premium, growth],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
stripeCli = ctx.stripeCli;
|
||||
db = ctx.db;
|
||||
org = ctx.org;
|
||||
env = ctx.env;
|
||||
|
||||
await autumn.entities.create(customerId, entities);
|
||||
});
|
||||
|
||||
for (const op of ops) {
|
||||
test(`should attach ${op.product.id} to entity ${op.entityId}`, async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: op.product,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
entities,
|
||||
entityId: op.entityId,
|
||||
});
|
||||
|
||||
for (const result of op.results) {
|
||||
const entity = await autumn.entities.get(customerId, op.entityId);
|
||||
expectProductAttached({
|
||||
customer: entity,
|
||||
product: result.product,
|
||||
status: result.status,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -26,7 +26,7 @@ export const completeCheckoutForm = async (
|
||||
_isLocal?: boolean,
|
||||
) => {
|
||||
const browser = await puppeteer.launch({
|
||||
headless: false,
|
||||
headless: true,
|
||||
executablePath: process.env.TESTS_CHROMIUM_PATH,
|
||||
args: ["--no-sandbox", "--disable-setuid-sandbox"],
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user