chore: migrated legacy tests to legacy/attach folder
This commit is contained in:
14
server/src/external/autumn/autumnCli.ts
vendored
14
server/src/external/autumn/autumnCli.ts
vendored
@@ -264,7 +264,12 @@ export class AutumnInt {
|
||||
{
|
||||
skipWebhooks,
|
||||
idempotencyKey,
|
||||
}: { skipWebhooks?: boolean; idempotencyKey?: string } = {},
|
||||
timeout,
|
||||
}: {
|
||||
skipWebhooks?: boolean;
|
||||
idempotencyKey?: string;
|
||||
timeout?: number;
|
||||
} = {},
|
||||
): Promise<any> {
|
||||
const headers: Record<string, string> = {};
|
||||
if (skipWebhooks !== undefined) {
|
||||
@@ -280,6 +285,13 @@ export class AutumnInt {
|
||||
Object.keys(headers).length > 0 ? headers : undefined,
|
||||
);
|
||||
|
||||
const concurrency = Number(process.env.TEST_FILE_CONCURRENCY || "0");
|
||||
const defaultTimeout = concurrency > 1 ? 5000 : 4000;
|
||||
const finalTimeout = timeout ?? defaultTimeout;
|
||||
if (finalTimeout) {
|
||||
await new Promise((resolve) => setTimeout(resolve, finalTimeout));
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
39
server/tests/_groups/core.ts
Normal file
39
server/tests/_groups/core.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import type { TestGroup } from "./types";
|
||||
|
||||
export const core: TestGroup = {
|
||||
name: "core",
|
||||
description:
|
||||
"Critical flows that must pass: basic attach, balance operations, CRUD, unit tests",
|
||||
tier: "core",
|
||||
paths: [
|
||||
// ── Balance: Check ──
|
||||
"integration/balances/check/check-basic.test.ts",
|
||||
"balances/check/credit-systems",
|
||||
"balances/check/send-event",
|
||||
|
||||
// ── Balance: Track ──
|
||||
"balances/track/basic",
|
||||
"balances/track/concurrency",
|
||||
"balances/track/credit-systems",
|
||||
"balances/track/entity-balances",
|
||||
"balances/track/entity-products",
|
||||
"balances/track/negative",
|
||||
"balances/track/paid-allocated",
|
||||
"integration/balances/track/track-misc.test.ts",
|
||||
|
||||
// ── Balance: Update ──
|
||||
"integration/balances/update/balance/update-balance-basic.test.ts",
|
||||
|
||||
// ── Legacy Attach ──
|
||||
"integration/billing/legacy/attach/attach-new-billing-subscription.test.ts",
|
||||
"integration/billing/legacy/attach/attach-misc.test.ts",
|
||||
"integration/billing/legacy/attach/downgrade/legacy-downgrade-merged-schedule.test.ts",
|
||||
"integration/billing/legacy/attach/group/legacy-group-merged.test.ts",
|
||||
"integration/billing/legacy/attach/invoice/legacy-attach-invoice-mode.test.ts",
|
||||
"integration/billing/legacy/attach/new/legacy-new-merged.test.ts",
|
||||
"integration/billing/legacy/attach/separate/legacy-separate.test.ts",
|
||||
"integration/billing/legacy/attach/trial/legacy-trial.test.ts",
|
||||
"integration/billing/legacy/attach/update-quantity/legacy-update-quantity.test.ts",
|
||||
"integration/billing/legacy/attach/upgrade/legacy-upgrade.test.ts",
|
||||
],
|
||||
};
|
||||
9
server/tests/_groups/domains/advanced.ts
Normal file
9
server/tests/_groups/domains/advanced.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { TestGroup } from "../types";
|
||||
|
||||
export const advanced: TestGroup = {
|
||||
name: "advanced",
|
||||
description:
|
||||
"Coupons, referrals, custom intervals, usage limits, multi-feature",
|
||||
tier: "domain",
|
||||
paths: [],
|
||||
};
|
||||
8
server/tests/_groups/domains/balances.ts
Normal file
8
server/tests/_groups/domains/balances.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { TestGroup } from "../types";
|
||||
|
||||
export const balances: TestGroup = {
|
||||
name: "balances",
|
||||
description: "All balance check, track, set-usage, and cron tests",
|
||||
tier: "domain",
|
||||
paths: [],
|
||||
};
|
||||
9
server/tests/_groups/domains/billing.ts
Normal file
9
server/tests/_groups/domains/billing.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { TestGroup } from "../types";
|
||||
|
||||
export const billing: TestGroup = {
|
||||
name: "billing",
|
||||
description:
|
||||
"All attach, upgrade, downgrade, checkout, invoice, subscription, and legacy billing tests",
|
||||
tier: "domain",
|
||||
paths: [],
|
||||
};
|
||||
8
server/tests/_groups/domains/crud.ts
Normal file
8
server/tests/_groups/domains/crud.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { TestGroup } from "../types";
|
||||
|
||||
export const crud: TestGroup = {
|
||||
name: "crud",
|
||||
description: "Customer, plan, entity, and feature CRUD operations",
|
||||
tier: "domain",
|
||||
paths: [],
|
||||
};
|
||||
9
server/tests/_groups/domains/misc.ts
Normal file
9
server/tests/_groups/domains/misc.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { TestGroup } from "../types";
|
||||
|
||||
export const misc: TestGroup = {
|
||||
name: "misc",
|
||||
description:
|
||||
"Cron jobs, external PSPs, scenarios, rate limits, archived tests",
|
||||
tier: "domain",
|
||||
paths: [],
|
||||
};
|
||||
9
server/tests/_groups/domains/webhooks.ts
Normal file
9
server/tests/_groups/domains/webhooks.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { TestGroup } from "../types";
|
||||
|
||||
export const webhooks: TestGroup = {
|
||||
name: "webhooks",
|
||||
description: "Stripe and Autumn webhook handlers",
|
||||
tier: "domain",
|
||||
maxConcurrency: 3,
|
||||
paths: [],
|
||||
};
|
||||
96
server/tests/_groups/index.ts
Normal file
96
server/tests/_groups/index.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { readdir } from "node:fs/promises";
|
||||
import { join, relative } from "node:path";
|
||||
import { core } from "./core";
|
||||
import { advanced } from "./domains/advanced";
|
||||
import { balances } from "./domains/balances";
|
||||
import { billing } from "./domains/billing";
|
||||
import { crud } from "./domains/crud";
|
||||
import { misc } from "./domains/misc";
|
||||
import { webhooks } from "./domains/webhooks";
|
||||
import { suites } from "./suites";
|
||||
import type { TestGroup, TestSuite } from "./types";
|
||||
|
||||
export type { TestGroup, TestSuite, TestTier } from "./types";
|
||||
|
||||
const allGroups: TestGroup[] = [
|
||||
core,
|
||||
balances,
|
||||
billing,
|
||||
crud,
|
||||
webhooks,
|
||||
advanced,
|
||||
misc,
|
||||
];
|
||||
|
||||
export const getAllGroups = (): TestGroup[] => allGroups;
|
||||
|
||||
export const getGroup = ({ name }: { name: string }): TestGroup | undefined =>
|
||||
allGroups.find((g) => g.name === name);
|
||||
|
||||
export const getAllSuites = (): TestSuite[] => suites;
|
||||
|
||||
export const getSuite = ({ name }: { name: string }): TestSuite | undefined =>
|
||||
suites.find((s) => s.name === name);
|
||||
|
||||
/** Resolve a suite to its constituent test groups. */
|
||||
export const resolveSuite = ({
|
||||
name,
|
||||
}: {
|
||||
name: string;
|
||||
}): TestGroup[] | undefined => {
|
||||
const suite = getSuite({ name });
|
||||
if (!suite) return undefined;
|
||||
|
||||
return suite.groups
|
||||
.map((groupName) => getGroup({ name: groupName }))
|
||||
.filter((g): g is TestGroup => g !== undefined);
|
||||
};
|
||||
|
||||
/** Resolve a name to test paths -- checks groups first, then suites. */
|
||||
export const resolveTestPaths = ({
|
||||
name,
|
||||
}: {
|
||||
name: string;
|
||||
}): string[] | undefined => {
|
||||
const group = getGroup({ name });
|
||||
if (group) return group.paths;
|
||||
|
||||
const suiteGroups = resolveSuite({ name });
|
||||
if (suiteGroups) {
|
||||
const paths = suiteGroups.flatMap((g) => g.paths);
|
||||
return [...new Set(paths)];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/** Recursively discover all *.test.ts files under a directory. */
|
||||
export const discoverAllTestFiles = async ({
|
||||
testsDir,
|
||||
}: {
|
||||
testsDir: string;
|
||||
}): Promise<string[]> => {
|
||||
const results: string[] = [];
|
||||
|
||||
const walk = async ({ dir }: { dir: string }) => {
|
||||
const entries = await readdir(dir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const fullPath = join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
if (
|
||||
entry.name.startsWith(".") ||
|
||||
entry.name.startsWith("_") ||
|
||||
entry.name === "node_modules"
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
await walk({ dir: fullPath });
|
||||
} else if (entry.name.endsWith(".test.ts")) {
|
||||
results.push(relative(testsDir, fullPath));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await walk({ dir: testsDir });
|
||||
return results.sort();
|
||||
};
|
||||
14
server/tests/_groups/suites.ts
Normal file
14
server/tests/_groups/suites.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { TestSuite } from "./types";
|
||||
|
||||
export const suites: TestSuite[] = [
|
||||
{
|
||||
name: "pre-merge",
|
||||
description: "Run before merging any PR",
|
||||
groups: ["core"],
|
||||
},
|
||||
{
|
||||
name: "all-domain",
|
||||
description: "All domain-level test groups",
|
||||
groups: ["balances", "billing", "crud", "webhooks", "advanced", "misc"],
|
||||
},
|
||||
];
|
||||
18
server/tests/_groups/types.ts
Normal file
18
server/tests/_groups/types.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export type TestTier = "core" | "domain";
|
||||
|
||||
export type TestGroup = {
|
||||
name: string;
|
||||
description: string;
|
||||
tier: TestTier;
|
||||
/** Directory paths relative to server/tests/. Resolved recursively for all *.test.ts files. */
|
||||
paths: string[];
|
||||
/** Override the default concurrency for this group. */
|
||||
maxConcurrency?: number;
|
||||
};
|
||||
|
||||
export type TestSuite = {
|
||||
name: string;
|
||||
description: string;
|
||||
/** Group names to include when running this suite. */
|
||||
groups: string[];
|
||||
};
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
ProductItemInterval,
|
||||
} from "@autumn/shared";
|
||||
import { resetAndGetCusEnt } from "@tests/balances/track/rollovers/rolloverTestUtils.js";
|
||||
import { addPrefixToProducts } from "@tests/attach/utils.js";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
@@ -60,11 +59,6 @@ describe(`${chalk.yellowBright(`${testCase}: Testing custom reset intervals`)}`,
|
||||
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [free],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [free],
|
||||
@@ -1,137 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { LegacyVersion } 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 { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import {
|
||||
constructProduct,
|
||||
constructRawProduct,
|
||||
} from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { expectFeaturesCorrect } from "../../utils/expectUtils/expectFeaturesCorrect.js";
|
||||
import { replaceItems } from "../utils.js";
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 100,
|
||||
}),
|
||||
],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
export const addOn = constructRawProduct({
|
||||
id: "add_on_1",
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 200,
|
||||
}),
|
||||
],
|
||||
isAddOn: true,
|
||||
});
|
||||
|
||||
const testCase = "addOn1";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing free add on, and updating free add on`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_2 });
|
||||
|
||||
beforeAll(async () => {
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro, addOn],
|
||||
prefix: testCase,
|
||||
});
|
||||
});
|
||||
|
||||
test("should should attach pro product, then add on product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
stripeCli: ctx.stripeCli,
|
||||
customerId,
|
||||
product: pro,
|
||||
});
|
||||
});
|
||||
|
||||
test("should should attach add on product", async () => {
|
||||
const preview = await autumn.attachPreview({
|
||||
customer_id: customerId,
|
||||
product_id: addOn.id,
|
||||
});
|
||||
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: addOn.id,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
|
||||
expect(customer.products.length).toBe(2);
|
||||
expectProductAttached({
|
||||
customer,
|
||||
product: addOn,
|
||||
});
|
||||
expectProductAttached({
|
||||
customer,
|
||||
product: pro,
|
||||
});
|
||||
});
|
||||
|
||||
const customItems = replaceItems({
|
||||
items: addOn.items,
|
||||
featureId: TestFeature.Messages,
|
||||
newItem: constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 400,
|
||||
}),
|
||||
});
|
||||
|
||||
test("should attach new free add on product", async () => {
|
||||
const preview = await autumn.attachPreview({
|
||||
customer_id: customerId,
|
||||
product_id: addOn.id,
|
||||
is_custom: true,
|
||||
items: customItems,
|
||||
});
|
||||
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: addOn.id,
|
||||
is_custom: true,
|
||||
items: customItems,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
|
||||
expect(customer.products.length).toBe(2);
|
||||
expectProductAttached({
|
||||
customer,
|
||||
product: addOn,
|
||||
});
|
||||
|
||||
expectFeaturesCorrect({
|
||||
customer,
|
||||
product: {
|
||||
...addOn,
|
||||
items: customItems,
|
||||
},
|
||||
otherProducts: [pro, addOn],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,92 +0,0 @@
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "@tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectFeaturesCorrect } from "@tests/utils/expectUtils/expectFeaturesCorrect.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,
|
||||
constructRawProduct,
|
||||
} from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
|
||||
export const pro = constructProduct({
|
||||
type: "pro",
|
||||
items: [],
|
||||
});
|
||||
|
||||
export const addOn = constructRawProduct({
|
||||
id: "addOn",
|
||||
isAddOn: true,
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Credits,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const testCase = "addOn2";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing attach free add on twice (should be treated as one off?)`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
beforeAll(async () => {
|
||||
db = ctx.db;
|
||||
org = ctx.org;
|
||||
env = ctx.env;
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: { fingerprint: "test" },
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro, addOn],
|
||||
prefix: testCase,
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach pro product and free add on", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
skipSubCheck: true,
|
||||
});
|
||||
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: addOn.id,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
expectProductAttached({
|
||||
customer,
|
||||
product: addOn,
|
||||
});
|
||||
|
||||
expectFeaturesCorrect({
|
||||
customer,
|
||||
product: addOn,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,150 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { ApiVersion, ProductItemInterval } from "@autumn/shared";
|
||||
import type { ApiCustomerV1 } from "@shared/api/customers/previousVersions/apiCustomerV1.js";
|
||||
import { AutumnCli } from "@tests/cli/AutumnCli.js";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { expectCustomerV0Correct } from "@tests/utils/expectUtils/expectCustomerV0Correct.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import {
|
||||
constructFeatureItem,
|
||||
constructPrepaidItem,
|
||||
} from "@/utils/scriptUtils/constructItem.js";
|
||||
import {
|
||||
constructProduct,
|
||||
constructRawProduct,
|
||||
} from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
|
||||
// Pro product (matches global products.pro)
|
||||
const proProd = constructProduct({
|
||||
type: "pro",
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Dashboard,
|
||||
isBoolean: true,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 10,
|
||||
interval: ProductItemInterval.Month,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Admin,
|
||||
unlimited: true,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
// Monthly add-on product (matches global products.monthlyAddOnMetered1)
|
||||
// - Prepaid monthly add-on
|
||||
// - 0 base allowance, customer specifies quantity
|
||||
const monthlyAddOn = constructRawProduct({
|
||||
id: "monthly-add-on-metered-1",
|
||||
isAddOn: true,
|
||||
items: [
|
||||
constructPrepaidItem({
|
||||
featureId: TestFeature.Messages,
|
||||
price: 9,
|
||||
billingUnits: 250,
|
||||
includedUsage: 0,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const testCase = "basic2";
|
||||
const customerId = testCase;
|
||||
|
||||
describe(`${chalk.yellowBright("basic2: Testing attach monthly add on")}`, () => {
|
||||
const autumnV1 = new AutumnInt({
|
||||
secretKey: ctx.orgSecretKey,
|
||||
version: ApiVersion.V1_2,
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
// Create products FIRST before customer creation
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [proProd, monthlyAddOn],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
// Then create customer with payment method
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach pro", async () => {
|
||||
await autumnV1.attach({
|
||||
customer_id: customerId,
|
||||
product_id: proProd.id,
|
||||
});
|
||||
|
||||
const res = await AutumnCli.getCustomer(customerId);
|
||||
await expectCustomerV0Correct({
|
||||
sent: proProd,
|
||||
cusRes: res,
|
||||
});
|
||||
});
|
||||
|
||||
const monthlyQuantity = 500;
|
||||
|
||||
test("should attach monthly add on", async () => {
|
||||
await AutumnCli.attach({
|
||||
customerId: customerId,
|
||||
productId: monthlyAddOn.id,
|
||||
forceCheckout: false,
|
||||
options: [
|
||||
{
|
||||
feature_id: TestFeature.Messages,
|
||||
quantity: monthlyQuantity,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test("should have correct product & entitlements", async () => {
|
||||
const cusRes = await AutumnCli.getCustomer(customerId);
|
||||
|
||||
// Pro gives 10 Messages
|
||||
const proMetered1 = 10;
|
||||
|
||||
const monthlyMetered1Balance = cusRes.entitlements.find(
|
||||
(e: ApiCustomerV1["entitlements"][number]) =>
|
||||
e.feature_id === TestFeature.Messages && e.interval === "month",
|
||||
);
|
||||
|
||||
expect(monthlyMetered1Balance?.balance).toBe(proMetered1 + monthlyQuantity);
|
||||
|
||||
expect(cusRes.add_ons).toHaveLength(1);
|
||||
const monthlyAddOnId = cusRes.add_ons.find(
|
||||
(a: any) => a.id === monthlyAddOn.id,
|
||||
);
|
||||
|
||||
expect(monthlyAddOnId).toBeDefined();
|
||||
expect(cusRes.invoices.length).toBe(2);
|
||||
});
|
||||
|
||||
test("should have correct /check result for metered1", async () => {
|
||||
const res: any = await AutumnCli.entitled(customerId, TestFeature.Messages);
|
||||
|
||||
const metered1Balance = res!.balances.find(
|
||||
(b: any) => b.feature_id === TestFeature.Messages,
|
||||
);
|
||||
|
||||
// Pro gives 10, monthly add-on gives monthlyQuantity
|
||||
const proMetered1Amt = 10;
|
||||
const monthlyAddOnMetered1Amt = monthlyQuantity;
|
||||
|
||||
expect(metered1Balance!.balance).toBe(
|
||||
proMetered1Amt + monthlyAddOnMetered1Amt,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,101 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import {
|
||||
ApiVersion,
|
||||
CusProductStatus,
|
||||
type Customer,
|
||||
ProductItemInterval,
|
||||
} from "@autumn/shared";
|
||||
import { AutumnCli } from "@tests/cli/AutumnCli.js";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { advanceToNextInvoice } from "@tests/utils/testAttachUtils/testAttachUtils";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { attachFailedPaymentMethod } from "@/external/stripe/stripeCusUtils";
|
||||
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";
|
||||
|
||||
// Pro product (matches global products.pro)
|
||||
const proProd = constructProduct({
|
||||
type: "pro",
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Dashboard,
|
||||
isBoolean: true,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 10,
|
||||
interval: ProductItemInterval.Month,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Admin,
|
||||
unlimited: true,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const testCase = "basic6";
|
||||
const customerId = testCase;
|
||||
|
||||
describe(`${chalk.yellowBright("basic6: Testing subscription past_due")}`, () => {
|
||||
const autumnV1 = new AutumnInt({
|
||||
secretKey: ctx.orgSecretKey,
|
||||
version: ApiVersion.V1_2,
|
||||
});
|
||||
|
||||
let stripeCli: Stripe;
|
||||
let testClockId: string;
|
||||
let customer: Customer;
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
// Create products FIRST before customer creation
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [proProd],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
// Then create customer with payment method
|
||||
const result = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
testClockId = result.testClockId;
|
||||
customer = result.customer;
|
||||
});
|
||||
|
||||
test("should attach pro product and switch to failed payment method", async () => {
|
||||
const res = await autumnV1.attach({
|
||||
customer_id: customerId,
|
||||
product_id: proProd.id,
|
||||
});
|
||||
|
||||
await attachFailedPaymentMethod({
|
||||
stripeCli,
|
||||
customer,
|
||||
});
|
||||
});
|
||||
|
||||
test("should advance to next cycle", async () => {
|
||||
await advanceToNextInvoice({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
});
|
||||
});
|
||||
|
||||
test("should have pro product in past due status", async () => {
|
||||
const cusRes: any = await AutumnCli.getCustomer(customerId);
|
||||
const proProduct = cusRes.products.find((p: any) => p.id === proProd.id);
|
||||
expect(proProduct).toBeDefined();
|
||||
expect(proProduct.status).toBe(CusProductStatus.PastDue);
|
||||
});
|
||||
});
|
||||
@@ -1,123 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import {
|
||||
ApiVersion,
|
||||
CusProductStatus,
|
||||
FreeTrialDuration,
|
||||
ProductItemInterval,
|
||||
} from "@autumn/shared";
|
||||
import { AutumnCli } from "@tests/cli/AutumnCli.js";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { expectCustomerV0Correct } from "@tests/utils/expectUtils/expectCustomerV0Correct.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
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";
|
||||
|
||||
// Pro product with trial (matches global products.proWithTrial)
|
||||
const proWithTrial = constructProduct({
|
||||
type: "pro",
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Dashboard,
|
||||
isBoolean: true,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 10,
|
||||
interval: ProductItemInterval.Month,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Admin,
|
||||
unlimited: true,
|
||||
}),
|
||||
],
|
||||
freeTrial: {
|
||||
length: 7,
|
||||
duration: FreeTrialDuration.Day,
|
||||
unique_fingerprint: true,
|
||||
card_required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const testCase = "basic8";
|
||||
const customerId = testCase;
|
||||
const customerId2 = `${testCase}2`;
|
||||
|
||||
describe(`${chalk.yellowBright("basic8: Testing trial duplicates (same fingerprint)")}`, () => {
|
||||
const autumnV1 = new AutumnInt({
|
||||
secretKey: ctx.orgSecretKey,
|
||||
version: ApiVersion.V1_2,
|
||||
});
|
||||
|
||||
const randFingerprint = Math.random().toString(36).substring(2, 15);
|
||||
|
||||
beforeAll(async () => {
|
||||
// Create products FIRST before customer creation
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [proWithTrial],
|
||||
prefix: testCase,
|
||||
customerIds: [customerId, customerId2],
|
||||
});
|
||||
|
||||
// Create first customer with fingerprint
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: { fingerprint: randFingerprint },
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
// Create second customer with same fingerprint
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId: customerId2,
|
||||
customerData: { fingerprint: randFingerprint },
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach pro with trial and have correct product & invoice", async () => {
|
||||
await autumnV1.attach({
|
||||
customer_id: customerId,
|
||||
product_id: proWithTrial.id,
|
||||
});
|
||||
|
||||
const customer = await AutumnCli.getCustomer(customerId);
|
||||
|
||||
await expectCustomerV0Correct({
|
||||
sent: proWithTrial,
|
||||
cusRes: customer,
|
||||
status: CusProductStatus.Trialing,
|
||||
});
|
||||
|
||||
const invoices = customer.invoices;
|
||||
expect(invoices.length).toBe(1);
|
||||
expect(invoices[0].total).toBe(0);
|
||||
});
|
||||
|
||||
test("should attach pro with trial to second customer and have correct product & invoice (pro with trial, full price)", async () => {
|
||||
await autumnV1.attach({
|
||||
customer_id: customerId2,
|
||||
product_id: proWithTrial.id,
|
||||
});
|
||||
|
||||
const customer = await AutumnCli.getCustomer(customerId2);
|
||||
console.log(JSON.stringify(customer, null, 2));
|
||||
|
||||
await expectCustomerV0Correct({
|
||||
sent: proWithTrial,
|
||||
cusRes: customer,
|
||||
status: CusProductStatus.Active,
|
||||
});
|
||||
|
||||
const invoices = customer.invoices;
|
||||
expect(invoices.length).toBe(1);
|
||||
expect(invoices[0].total).toBe(20);
|
||||
});
|
||||
});
|
||||
@@ -1,74 +0,0 @@
|
||||
import { ProductItemInterval } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { createSharedProducts } from "@/utils/scriptUtils/testUtils/createSharedProduct.js";
|
||||
/**
|
||||
* Shared default product for basic test group
|
||||
* Used by multiple tests (basic1, basic3) to avoid conflicts
|
||||
* ID is NOT prefixed - shared across all tests in this group
|
||||
*/
|
||||
export const sharedDefaultFree = constructProduct({
|
||||
id: "shared-default-free",
|
||||
type: "free",
|
||||
isDefault: true,
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 5,
|
||||
interval: ProductItemInterval.Month,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
export const sharedProProduct = constructProduct({
|
||||
id: "shared-pro-product",
|
||||
isDefault: false,
|
||||
type: "pro",
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Dashboard,
|
||||
isBoolean: true,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 10,
|
||||
interval: ProductItemInterval.Month,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Admin,
|
||||
unlimited: true,
|
||||
}),
|
||||
],
|
||||
});
|
||||
export const sharedPremiumProduct = constructProduct({
|
||||
id: "shared-premium-product",
|
||||
isDefault: false,
|
||||
type: "premium",
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Dashboard,
|
||||
isBoolean: true,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 100,
|
||||
interval: ProductItemInterval.Month,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Admin,
|
||||
unlimited: true,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
export const initBasicSharedProducts = async () => {
|
||||
await createSharedProducts({
|
||||
ctx,
|
||||
products: [sharedDefaultFree, sharedProProduct],
|
||||
});
|
||||
};
|
||||
|
||||
// Auto-init on import (backwards compat)
|
||||
await initBasicSharedProducts();
|
||||
@@ -1,128 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import {
|
||||
ApiVersion,
|
||||
type CheckResponseV0,
|
||||
type Entitlement,
|
||||
ProductItemInterval,
|
||||
} from "@autumn/shared";
|
||||
import { AutumnCli } from "@tests/cli/AutumnCli.js";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { expectCustomerV0Correct } from "@tests/utils/expectUtils/expectCustomerV0Correct.js";
|
||||
import { timeout } from "@tests/utils/genUtils.js";
|
||||
import { completeStripeCheckoutFormV2 as completeCheckoutForm } from "@tests/utils/browserPool/completeStripeCheckoutFormV2";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { convertProductV2ToV1 } from "@/internal/products/productUtils/productV2Utils/convertProductV2ToV1.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";
|
||||
|
||||
// Pro product with boolean, metered, and unlimited features
|
||||
const proProd = constructProduct({
|
||||
type: "pro",
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Dashboard,
|
||||
isBoolean: true,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 10,
|
||||
interval: ProductItemInterval.Month,
|
||||
}),
|
||||
// Unlimited feature (maps to global products.pro.infinite1)
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Admin,
|
||||
unlimited: true,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const testCase = "checkout1";
|
||||
const customerId = testCase;
|
||||
|
||||
describe(`${chalk.yellowBright("checkout1: Testing attach basic product through checkout")}`, () => {
|
||||
const autumnV1 = new AutumnInt({
|
||||
secretKey: ctx.orgSecretKey,
|
||||
version: ApiVersion.V1_2,
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [proProd],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
// Then create customer
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: { fingerprint: "test" },
|
||||
withTestClock: true,
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach pro through checkout", async () => {
|
||||
const { checkout_url } = await autumnV1.attach({
|
||||
customer_id: customerId,
|
||||
product_id: proProd.id,
|
||||
});
|
||||
|
||||
await completeCheckoutForm({ url: checkout_url });
|
||||
await timeout(12000);
|
||||
});
|
||||
|
||||
test("should have correct product & entitlements", async () => {
|
||||
const res = await AutumnCli.getCustomer(customerId);
|
||||
|
||||
await expectCustomerV0Correct({
|
||||
sent: proProd,
|
||||
cusRes: res,
|
||||
});
|
||||
expect(res.invoices.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test("should have correct result when calling /check", async () => {
|
||||
// Convert ProductV2 to V1 to get reference entitlements (what we SENT)
|
||||
const proProdV1 = convertProductV2ToV1({
|
||||
productV2: proProd,
|
||||
orgId: ctx.org.id,
|
||||
features: ctx.features,
|
||||
});
|
||||
const proEntitlements = proProdV1.entitlements;
|
||||
|
||||
// Iterate through reference product's entitlements and verify check responses
|
||||
for (const entitlement of Object.values(proEntitlements) as Entitlement[]) {
|
||||
const allowance = entitlement.allowance;
|
||||
|
||||
const res = (await AutumnCli.entitled(
|
||||
customerId,
|
||||
entitlement.feature_id!,
|
||||
)) as CheckResponseV0;
|
||||
|
||||
const entBalance = res.balances.find(
|
||||
(b) => b.feature_id === entitlement.feature_id,
|
||||
);
|
||||
|
||||
expect(
|
||||
res.allowed,
|
||||
`Allowed for ${entitlement.feature_id} is not true`,
|
||||
).toBe(true);
|
||||
expect(
|
||||
entBalance,
|
||||
`Entitlement ${entitlement.feature_id} balance not found`,
|
||||
).toBeDefined();
|
||||
if (entitlement.allowance) {
|
||||
expect(
|
||||
entBalance?.balance,
|
||||
`Entitlement ${entitlement.feature_id} balance does not match expected balance.`,
|
||||
).toBe(allowance);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,159 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import {
|
||||
ApiVersion,
|
||||
type CheckResponseV0,
|
||||
type LimitedItem,
|
||||
ProductItemInterval,
|
||||
} from "@autumn/shared";
|
||||
import type { ApiCustomerV1 } from "@shared/api/customers/previousVersions/apiCustomerV1.js";
|
||||
import { AutumnCli } from "@tests/cli/AutumnCli.js";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { expectCustomerV0Correct } from "@tests/utils/expectUtils/expectCustomerV0Correct.js";
|
||||
import { timeout } from "@tests/utils/genUtils.js";
|
||||
import { completeStripeCheckoutFormV2 as completeCheckoutForm } from "@tests/utils/browserPool/completeStripeCheckoutFormV2";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import {
|
||||
constructFeatureItem,
|
||||
constructPrepaidItem,
|
||||
} from "@/utils/scriptUtils/constructItem.js";
|
||||
import {
|
||||
constructProduct,
|
||||
constructRawProduct,
|
||||
} from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
|
||||
// Pro product (matches global products.pro)
|
||||
const proProd = constructProduct({
|
||||
type: "pro",
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Dashboard,
|
||||
isBoolean: true,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 10,
|
||||
interval: ProductItemInterval.Month,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Admin,
|
||||
unlimited: true,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
// One-time add-on product
|
||||
const oneTimeItem = constructPrepaidItem({
|
||||
featureId: TestFeature.Messages,
|
||||
price: 9,
|
||||
billingUnits: 250,
|
||||
isOneOff: true,
|
||||
}) as LimitedItem;
|
||||
|
||||
const oneTime = constructRawProduct({
|
||||
id: "one_off",
|
||||
items: [oneTimeItem],
|
||||
isAddOn: true,
|
||||
});
|
||||
|
||||
const testCase = "checkout2";
|
||||
const customerId = testCase;
|
||||
|
||||
describe(`${chalk.yellowBright("checkout2: Testing attach one time add ons (through checkout)")}`, () => {
|
||||
const autumnV1 = new AutumnInt({
|
||||
secretKey: ctx.orgSecretKey,
|
||||
version: ApiVersion.V1_2,
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
// Create products FIRST before customer creation
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [proProd, oneTime],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
// Then create customer with payment method
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach pro", async () => {
|
||||
await autumnV1.attach({
|
||||
customer_id: customerId,
|
||||
product_id: proProd.id,
|
||||
});
|
||||
|
||||
const res = await AutumnCli.getCustomer(customerId);
|
||||
await expectCustomerV0Correct({
|
||||
sent: proProd,
|
||||
cusRes: res,
|
||||
});
|
||||
});
|
||||
|
||||
const oneTimeQuantity = 500;
|
||||
const oneTimeBillingUnits = oneTimeItem.billing_units;
|
||||
const oneTimePurchaseCount = 2;
|
||||
|
||||
test("should attach one time add on twice, force checkout", async () => {
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const res = await autumnV1.attach({
|
||||
customer_id: customerId,
|
||||
product_id: oneTime.id,
|
||||
force_checkout: true,
|
||||
});
|
||||
|
||||
await completeCheckoutForm({
|
||||
url: res.checkout_url,
|
||||
overrideQuantity: oneTimeQuantity / (oneTimeBillingUnits ?? 1),
|
||||
});
|
||||
await timeout(15000);
|
||||
}
|
||||
});
|
||||
|
||||
test("should have correct product & entitlements", async () => {
|
||||
const cusRes = await AutumnCli.getCustomer(customerId);
|
||||
|
||||
// Find the add-on balance for Messages with lifetime interval (one-time purchase)
|
||||
const addOnBalance = cusRes.entitlements.find(
|
||||
(e: ApiCustomerV1["entitlements"][number]) =>
|
||||
e.feature_id === TestFeature.Messages && e.interval === "lifetime",
|
||||
);
|
||||
|
||||
const expectedAmt = oneTimeQuantity * oneTimePurchaseCount;
|
||||
|
||||
expect(addOnBalance?.balance).toBe(expectedAmt);
|
||||
|
||||
expect(cusRes.add_ons).toHaveLength(1);
|
||||
expect(cusRes.add_ons[0].id).toBe(oneTime.id);
|
||||
expect(cusRes.invoices.length).toBe(1 + oneTimePurchaseCount);
|
||||
});
|
||||
|
||||
test("should have correct /check result for metered1", async () => {
|
||||
const res = (await AutumnCli.entitled(
|
||||
customerId,
|
||||
TestFeature.Messages,
|
||||
)) as CheckResponseV0;
|
||||
|
||||
expect(res.allowed).toBe(true);
|
||||
|
||||
// Pro product gives 10 Messages per month
|
||||
const proMetered1Amt = 10;
|
||||
const addOnBalance = res.balances.find(
|
||||
(b: CheckResponseV0["balances"][number]) =>
|
||||
b.feature_id === TestFeature.Messages,
|
||||
);
|
||||
|
||||
expect(addOnBalance?.balance).toBe(
|
||||
proMetered1Amt + oneTimeQuantity * oneTimePurchaseCount,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,86 +0,0 @@
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import { LegacyVersion } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { expectFeaturesCorrect } from "@tests/utils/expectUtils/expectFeaturesCorrect.js";
|
||||
import { expectProductAttached } from "@tests/utils/expectUtils/expectProductAttached.js";
|
||||
import { completeStripeCheckoutFormV2 as completeCheckoutForm } from "@tests/utils/browserPool/completeStripeCheckoutFormV2";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.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";
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 100,
|
||||
}),
|
||||
],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
export const oneOff = constructProduct({
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Users,
|
||||
includedUsage: 5,
|
||||
}),
|
||||
],
|
||||
type: "one_off",
|
||||
isAddOn: true,
|
||||
});
|
||||
|
||||
const testCase = "checkout3";
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing multi attach checkout, pro + one off`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
|
||||
beforeAll(async () => {
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro, oneOff],
|
||||
prefix: testCase,
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach pro and one off product", async () => {
|
||||
const res = await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_ids: [pro.id, oneOff.id],
|
||||
});
|
||||
|
||||
await completeCheckoutForm({ url: res.checkout_url });
|
||||
await timeout(10000);
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
|
||||
expectProductAttached({
|
||||
customer,
|
||||
product: pro,
|
||||
});
|
||||
expectProductAttached({
|
||||
customer,
|
||||
product: oneOff,
|
||||
});
|
||||
|
||||
expectFeaturesCorrect({
|
||||
customer,
|
||||
product: pro,
|
||||
});
|
||||
|
||||
expectFeaturesCorrect({
|
||||
customer,
|
||||
product: oneOff,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,86 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { RewardType } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { completeStripeCheckoutFormV2 as completeStripeCheckoutForm } from "@tests/utils/browserPool/completeStripeCheckoutFormV2";
|
||||
import { expectProductAttached } from "@tests/utils/expectUtils/expectProductAttached.js";
|
||||
import { createReward } from "@tests/utils/productUtils.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import { getBasePrice } from "@tests/utils/testProductUtils/testProductUtils.js";
|
||||
import chalk from "chalk";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import {
|
||||
constructCoupon,
|
||||
constructProduct,
|
||||
} from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
|
||||
const pro = constructProduct({
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 100,
|
||||
}),
|
||||
],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const reward = constructCoupon({
|
||||
id: "checkout4",
|
||||
promoCode: "checkout4code",
|
||||
discountType: RewardType.PercentageDiscount,
|
||||
discountValue: 50,
|
||||
});
|
||||
|
||||
const testCase = "checkout4";
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing attach coupon`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt();
|
||||
|
||||
beforeAll(async () => {
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createReward({
|
||||
orgId: ctx.org.id,
|
||||
env: ctx.env,
|
||||
db: ctx.db,
|
||||
autumn,
|
||||
reward,
|
||||
productId: pro.id,
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach pro and one off product", async () => {
|
||||
const res = await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
reward: reward.id,
|
||||
});
|
||||
|
||||
await completeStripeCheckoutForm({ url: res.checkout_url });
|
||||
await timeout(10000);
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
|
||||
expectProductAttached({
|
||||
customer,
|
||||
product: pro,
|
||||
});
|
||||
|
||||
expect(customer.invoices.length).toBe(1);
|
||||
const totalPrice = getBasePrice({ product: pro });
|
||||
expect(customer.invoices[0].total).toBe(totalPrice * 0.5);
|
||||
});
|
||||
});
|
||||
@@ -1,84 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { LegacyVersion } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { expectFeaturesCorrect } from "@tests/utils/expectUtils/expectFeaturesCorrect.js";
|
||||
import { expectProductAttached } from "@tests/utils/expectUtils/expectProductAttached.js";
|
||||
import { completeInvoiceCheckoutV2 as completeInvoiceCheckout } from "@tests/utils/browserPool/completeInvoiceCheckoutV2";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import { getBasePrice } from "@tests/utils/testProductUtils/testProductUtils.js";
|
||||
import chalk from "chalk";
|
||||
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";
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 100,
|
||||
}),
|
||||
],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const testCase = "checkout5";
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing invoice checkout, no product till paid`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_2 });
|
||||
|
||||
beforeAll(async () => {
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
withTestClock: true,
|
||||
});
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach pro product", async () => {
|
||||
const res = await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
invoice: true,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
|
||||
const invoice = customer.invoices?.[0];
|
||||
|
||||
expect(invoice).toBeDefined();
|
||||
expect(invoice.total).toBe(getBasePrice({ product: pro }));
|
||||
expect(invoice.status).toBe("open");
|
||||
|
||||
const product = customer.products.find((p) => p.id === pro.id);
|
||||
expect(product).toBeUndefined();
|
||||
|
||||
await completeInvoiceCheckout({
|
||||
url: res.checkout_url,
|
||||
});
|
||||
|
||||
const customer2 = await autumn.customers.get(customerId);
|
||||
|
||||
const invoice2 = customer2.invoices?.[0];
|
||||
|
||||
expect(customer2.invoices.length).toBe(1);
|
||||
expect(invoice2).toBeDefined();
|
||||
expect(invoice2.status).toBe("paid");
|
||||
|
||||
expectProductAttached({
|
||||
customer: customer2,
|
||||
product: pro,
|
||||
});
|
||||
|
||||
expectFeaturesCorrect({
|
||||
customer: customer2,
|
||||
product: pro,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,120 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { LegacyVersion } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { expectFeaturesCorrect } from "@tests/utils/expectUtils/expectFeaturesCorrect.js";
|
||||
import { expectProductAttached } from "@tests/utils/expectUtils/expectProductAttached.js";
|
||||
import { completeInvoiceCheckoutV2 as completeInvoiceCheckout } from "@tests/utils/browserPool/completeInvoiceCheckoutV2";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import { getBasePrice } from "@tests/utils/testProductUtils/testProductUtils.js";
|
||||
import chalk from "chalk";
|
||||
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";
|
||||
|
||||
const pro = constructProduct({
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 100,
|
||||
}),
|
||||
],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const premium = constructProduct({
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 250,
|
||||
}),
|
||||
],
|
||||
type: "premium",
|
||||
});
|
||||
|
||||
const testCase = "checkout6";
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing invoice checkout via checkout endpoint`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_2 });
|
||||
|
||||
beforeAll(async () => {
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro, premium],
|
||||
prefix: testCase,
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach pro product via invoice checkout", async () => {
|
||||
const res = await autumn.checkout({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
invoice: true,
|
||||
});
|
||||
|
||||
expect(res.url).toBeDefined();
|
||||
|
||||
await completeInvoiceCheckout({
|
||||
url: res.url!,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
|
||||
expectProductAttached({
|
||||
customer,
|
||||
product: pro,
|
||||
});
|
||||
|
||||
expectFeaturesCorrect({
|
||||
customer,
|
||||
product: pro,
|
||||
});
|
||||
});
|
||||
|
||||
// test("should have no URL returned if try to attach premium (with invoice true)", async () => {
|
||||
// const res = await autumn.attach({
|
||||
// customer_id: customerId,
|
||||
// product_id: premium.id,
|
||||
// invoice: true,
|
||||
// });
|
||||
|
||||
// expect(res.url).toBeUndefined();
|
||||
// });
|
||||
|
||||
test("should attach premium product via invoice enable immediately", async () => {
|
||||
const res = await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: premium.id,
|
||||
invoice: true,
|
||||
enable_product_immediately: true,
|
||||
finalize_invoice: false,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
|
||||
expectProductAttached({
|
||||
customer,
|
||||
product: premium,
|
||||
});
|
||||
|
||||
expectFeaturesCorrect({
|
||||
customer,
|
||||
product: premium,
|
||||
});
|
||||
|
||||
const invoices = customer.invoices;
|
||||
expect(invoices.length).toBe(2);
|
||||
expect(invoices[0].status).toBe("draft");
|
||||
expect(invoices[0].total).toBe(
|
||||
getBasePrice({ product: premium }) - getBasePrice({ product: pro }),
|
||||
); // proration...
|
||||
});
|
||||
});
|
||||
@@ -1,113 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { LegacyVersion } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { expectFeaturesCorrect } from "@tests/utils/expectUtils/expectFeaturesCorrect.js";
|
||||
import { expectProductAttached } from "@tests/utils/expectUtils/expectProductAttached.js";
|
||||
import { completeInvoiceCheckoutV2 as completeInvoiceCheckout } from "@tests/utils/browserPool/completeInvoiceCheckoutV2";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import {
|
||||
constructFeatureItem,
|
||||
constructPrepaidItem,
|
||||
} from "@/utils/scriptUtils/constructItem.js";
|
||||
import {
|
||||
constructProduct,
|
||||
constructRawProduct,
|
||||
} from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 100,
|
||||
}),
|
||||
],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
export const addOn = constructRawProduct({
|
||||
id: "addOn",
|
||||
items: [
|
||||
constructPrepaidItem({
|
||||
featureId: TestFeature.Messages,
|
||||
billingUnits: 100,
|
||||
price: 10,
|
||||
isOneOff: true,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const testCase = "checkout7";
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing invoice checkout with one off product`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_2 });
|
||||
|
||||
beforeAll(async () => {
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro, addOn],
|
||||
prefix: testCase,
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach pro product, then add on product via invoice checkout", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
const options = [
|
||||
{
|
||||
quantity: 200,
|
||||
feature_id: TestFeature.Messages,
|
||||
},
|
||||
];
|
||||
|
||||
const res2 = await autumn.checkout({
|
||||
customer_id: customerId,
|
||||
product_id: addOn.id,
|
||||
invoice: true,
|
||||
options,
|
||||
});
|
||||
|
||||
expect(res2.url).toBeDefined();
|
||||
|
||||
await completeInvoiceCheckout({
|
||||
url: res2.url!,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
|
||||
expectProductAttached({
|
||||
customer,
|
||||
product: addOn,
|
||||
});
|
||||
|
||||
expectFeaturesCorrect({
|
||||
customer,
|
||||
product: addOn,
|
||||
otherProducts: [pro],
|
||||
options,
|
||||
});
|
||||
});
|
||||
|
||||
// it("should have no URL returned if try to attach add on (with invoice true)", async function () {
|
||||
// const res = await autumn.checkout({
|
||||
// customer_id: customerId,
|
||||
// product_id: addOn.id,
|
||||
// invoice: true,
|
||||
// });
|
||||
|
||||
// expect(res.url).to.not.exist;
|
||||
// });
|
||||
});
|
||||
@@ -1,95 +0,0 @@
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import { ApiVersion } from "@autumn/shared";
|
||||
import { AutumnCli } from "@tests/cli/AutumnCli.js";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { completeStripeCheckoutFormV2 as completeStripeCheckoutForm } from "@tests/utils/browserPool/completeStripeCheckoutFormV2";
|
||||
import { expectCustomerV0Correct } from "@tests/utils/expectUtils/expectCustomerV0Correct.js";
|
||||
import { timeout } from "@tests/utils/genUtils.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructPrepaidItem } 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";
|
||||
|
||||
// Monthly with one-time prepaid product (matches global products.monthlyWithOneTime)
|
||||
// Has both monthly and one-time prepaid items
|
||||
const monthlyWithOneTime = constructProduct({
|
||||
type: "pro",
|
||||
items: [
|
||||
constructPrepaidItem({
|
||||
featureId: TestFeature.Messages,
|
||||
price: 5,
|
||||
billingUnits: 100,
|
||||
includedUsage: 0,
|
||||
isOneOff: true,
|
||||
}),
|
||||
constructPrepaidItem({
|
||||
featureId: TestFeature.Words,
|
||||
price: 10,
|
||||
billingUnits: 100,
|
||||
includedUsage: 0,
|
||||
isOneOff: true,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const testCase = "checkout8";
|
||||
const customerId = testCase;
|
||||
|
||||
describe(`${chalk.yellowBright("checkout8: attach monthly with one time prepaid, and quantity = 0")}`, () => {
|
||||
const autumnV1 = new AutumnInt({
|
||||
secretKey: ctx.orgSecretKey,
|
||||
version: ApiVersion.V1_2,
|
||||
});
|
||||
|
||||
const options = [
|
||||
{
|
||||
feature_id: TestFeature.Messages,
|
||||
quantity: 0,
|
||||
},
|
||||
{
|
||||
feature_id: TestFeature.Words,
|
||||
quantity: 4,
|
||||
},
|
||||
];
|
||||
|
||||
beforeAll(async () => {
|
||||
// Create products FIRST before customer creation
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [monthlyWithOneTime],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
// Then create customer
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
withTestClock: true,
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach monthly with one time", async () => {
|
||||
const res = await autumnV1.attach({
|
||||
customer_id: customerId,
|
||||
product_id: monthlyWithOneTime.id,
|
||||
options,
|
||||
});
|
||||
|
||||
await completeStripeCheckoutForm({ url: res.checkout_url });
|
||||
await timeout(12000);
|
||||
});
|
||||
|
||||
test("should have correct main product and entitlements", async () => {
|
||||
const cusRes = await AutumnCli.getCustomer(customerId);
|
||||
|
||||
await expectCustomerV0Correct({
|
||||
sent: monthlyWithOneTime,
|
||||
cusRes,
|
||||
optionsList: options,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,104 +0,0 @@
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "@tests/utils/expectUtils/expectAttach.js";
|
||||
import {
|
||||
expectDowngradeCorrect,
|
||||
expectNextCycleCorrect,
|
||||
} from "@tests/utils/expectUtils/expectScheduleUtils.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 { 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 testCase = "downgrade1";
|
||||
|
||||
const pro = constructProduct({
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const premium = constructProduct({
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "premium",
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing downgrade from premium -> pro`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
beforeAll(async () => {
|
||||
db = ctx.db;
|
||||
org = ctx.org;
|
||||
env = ctx.env;
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro, premium],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
test("should attach premium product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: premium,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
});
|
||||
|
||||
// let nextCycle = Date.now();
|
||||
let preview = null;
|
||||
|
||||
test("should downgrade to pro", async () => {
|
||||
const { preview: preview_ } = await expectDowngradeCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
curProduct: premium,
|
||||
newProduct: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
|
||||
preview = preview_;
|
||||
});
|
||||
|
||||
test("should have pro attached on next cycle", async () => {
|
||||
await expectNextCycleCorrect({
|
||||
preview: preview!,
|
||||
autumn,
|
||||
stripeCli,
|
||||
customerId,
|
||||
testClockId,
|
||||
product: pro,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,112 +0,0 @@
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "@tests/utils/expectUtils/expectAttach.js";
|
||||
import {
|
||||
expectDowngradeCorrect,
|
||||
expectNextCycleCorrect,
|
||||
} from "@tests/utils/expectUtils/expectScheduleUtils.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 "@/internal/products/product-items/productItemUtils.js";
|
||||
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 testCase = "downgrade2";
|
||||
|
||||
const free = constructProduct({
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
feature_id: TestFeature.Words,
|
||||
included_usage: 100,
|
||||
}),
|
||||
],
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const premium = constructProduct({
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "premium",
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing downgrade from premium -> free`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
beforeAll(async () => {
|
||||
db = ctx.db;
|
||||
org = ctx.org;
|
||||
env = ctx.env;
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [free, premium],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
test("should attach premium product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: premium,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
});
|
||||
|
||||
// let nextCycle = Date.now();
|
||||
let preview = null;
|
||||
test("should downgrade to free", async () => {
|
||||
const { preview: preview_ } = await expectDowngradeCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
curProduct: premium,
|
||||
newProduct: free,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
|
||||
preview = preview_;
|
||||
});
|
||||
|
||||
test("should have pro attached on next cycle", async () => {
|
||||
await expectNextCycleCorrect({
|
||||
preview: preview!,
|
||||
autumn,
|
||||
stripeCli,
|
||||
customerId,
|
||||
testClockId,
|
||||
product: free,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,150 +0,0 @@
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import {
|
||||
type AppEnv,
|
||||
type Customer,
|
||||
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 { expectDowngradeCorrect } from "@tests/utils/expectUtils/expectScheduleUtils.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 "@/internal/products/product-items/productItemUtils.js";
|
||||
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 testCase = "downgrade3";
|
||||
|
||||
const free = constructProduct({
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
feature_id: TestFeature.Words,
|
||||
included_usage: 100,
|
||||
}),
|
||||
],
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const pro = constructProduct({
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const premium = constructProduct({
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "premium",
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing downgrade: premium -> pro -> free -> pro -> premium`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let customer: Customer;
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
beforeAll(async () => {
|
||||
db = ctx.db;
|
||||
org = ctx.org;
|
||||
env = ctx.env;
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1, customer: customer_ } =
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [free, pro, premium],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
customer = customer_!;
|
||||
});
|
||||
|
||||
test("should attach premium product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: premium,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
});
|
||||
|
||||
// let nextCycle = Date.now();
|
||||
let preview = null;
|
||||
test("should downgrade to pro", async () => {
|
||||
const { preview: preview_ } = await expectDowngradeCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
curProduct: premium,
|
||||
newProduct: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
|
||||
preview = preview_;
|
||||
});
|
||||
|
||||
test("should downgrade to free", async () => {
|
||||
const { preview: preview_ } = await expectDowngradeCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
curProduct: premium,
|
||||
newProduct: free,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
|
||||
preview = preview_;
|
||||
});
|
||||
|
||||
test("should change downgrade to pro", async () => {
|
||||
const { preview: preview_ } = await expectDowngradeCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
curProduct: premium,
|
||||
newProduct: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
});
|
||||
|
||||
test("should renew premium", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: premium.id,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
expectProductAttached({
|
||||
customer,
|
||||
product: premium,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,135 +0,0 @@
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import {
|
||||
type AppEnv,
|
||||
BillingInterval,
|
||||
LegacyVersion,
|
||||
type Organization,
|
||||
} from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "@tests/utils/expectUtils/expectAttach.js";
|
||||
import {
|
||||
expectDowngradeCorrect,
|
||||
expectNextCycleCorrect,
|
||||
} from "@tests/utils/expectUtils/expectScheduleUtils.js";
|
||||
import { advanceMonths } from "@tests/utils/stripeUtils.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 { timeout } from "@/utils/genUtils.js";
|
||||
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 testCase = "downgrade4";
|
||||
|
||||
const proQuarter = constructProduct({
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "pro",
|
||||
interval: BillingInterval.Quarter,
|
||||
});
|
||||
|
||||
const pro = constructProduct({
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const premium = constructProduct({
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "premium",
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing downgrade: pro-quarter -> premium -> pro`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
beforeAll(async () => {
|
||||
db = ctx.db;
|
||||
org = ctx.org;
|
||||
env = ctx.env;
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [proQuarter, pro, premium],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
test("should attach pro quarterly product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: proQuarter,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
});
|
||||
|
||||
test("should downgrade to premium", async () => {
|
||||
await expectDowngradeCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
curProduct: proQuarter,
|
||||
newProduct: premium,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
});
|
||||
|
||||
let preview = null;
|
||||
|
||||
test("should downgrade to pro", async () => {
|
||||
const { preview: preview_ } = await expectDowngradeCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
curProduct: proQuarter,
|
||||
newProduct: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
|
||||
preview = preview_;
|
||||
});
|
||||
|
||||
test("should have correct invoice after cycle", async () => {
|
||||
await advanceMonths({ stripeCli, testClockId, numberOfMonths: 3 });
|
||||
|
||||
await timeout(10000);
|
||||
|
||||
await expectNextCycleCorrect({
|
||||
preview: preview!,
|
||||
autumn,
|
||||
stripeCli,
|
||||
customerId,
|
||||
testClockId,
|
||||
product: pro,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
});
|
||||
|
||||
return;
|
||||
});
|
||||
@@ -1,153 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { CusProductStatus, ProductItemInterval } from "@autumn/shared";
|
||||
import { AutumnCli } from "@tests/cli/AutumnCli.js";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { hoursToFinalizeInvoice } from "@tests/utils/constants.js";
|
||||
import { expectCustomerV0Correct } from "@tests/utils/expectUtils/expectCustomerV0Correct.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { addHours, addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
|
||||
// Inline product definitions for downgrade5 test
|
||||
const proProduct = constructProduct({
|
||||
type: "pro",
|
||||
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Dashboard,
|
||||
isBoolean: true,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 10,
|
||||
interval: ProductItemInterval.Month,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Admin,
|
||||
unlimited: true,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const premiumProduct = constructProduct({
|
||||
type: "premium",
|
||||
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 100,
|
||||
interval: ProductItemInterval.Month,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const testCase = "downgrade5";
|
||||
describe(`${chalk.yellowBright(`${testCase}: testing basic downgrade (paid to paid)`)}`, () => {
|
||||
const customerId = testCase;
|
||||
let testClockId: string;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
// Initialize products for this test
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [proProduct, premiumProduct],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId_ } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = testClockId_;
|
||||
});
|
||||
|
||||
test("should attach premium", async () => {
|
||||
await AutumnCli.attach({
|
||||
customerId: customerId,
|
||||
productId: premiumProduct.id,
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach pro", async () => {
|
||||
await AutumnCli.attach({
|
||||
customerId: customerId,
|
||||
productId: proProduct.id,
|
||||
});
|
||||
});
|
||||
|
||||
test("should have correct product and entitlements for scheduled pro", async () => {
|
||||
const res = await AutumnCli.getCustomer(customerId);
|
||||
|
||||
expectCustomerV0Correct({
|
||||
sent: premiumProduct,
|
||||
cusRes: res,
|
||||
});
|
||||
|
||||
const { products: resProducts } = res;
|
||||
|
||||
const resPro = resProducts.find(
|
||||
(p: any) =>
|
||||
p.id === proProduct.id && p.status === CusProductStatus.Scheduled,
|
||||
);
|
||||
|
||||
expect(resPro).toBeDefined();
|
||||
});
|
||||
|
||||
test("should attach premium and remove scheduled pro", async () => {
|
||||
await AutumnCli.attach({
|
||||
customerId: customerId,
|
||||
productId: premiumProduct.id,
|
||||
});
|
||||
|
||||
const res = await AutumnCli.getCustomer(customerId);
|
||||
const resPro = res.products.find(
|
||||
(p: any) =>
|
||||
p.id === proProduct.id && p.status === CusProductStatus.Scheduled,
|
||||
);
|
||||
|
||||
expect(resPro).toBeUndefined();
|
||||
|
||||
expectCustomerV0Correct({
|
||||
sent: premiumProduct,
|
||||
cusRes: res,
|
||||
});
|
||||
});
|
||||
|
||||
// Advance time 1 month
|
||||
test("should attach pro, advance stripe clock and have pro is attached", async () => {
|
||||
await AutumnCli.attach({
|
||||
customerId: customerId,
|
||||
productId: proProduct.id,
|
||||
});
|
||||
|
||||
await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addHours(
|
||||
addMonths(new Date(), 1),
|
||||
hoursToFinalizeInvoice,
|
||||
).getTime(),
|
||||
waitForSeconds: 15,
|
||||
});
|
||||
|
||||
const res = await AutumnCli.getCustomer(customerId);
|
||||
expectCustomerV0Correct({
|
||||
sent: proProduct,
|
||||
cusRes: res,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,78 +0,0 @@
|
||||
// import { BillingInterval, ProductItemInterval } from "@autumn/shared";
|
||||
// import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
// import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
// import { constructPriceItem } from "@/internal/products/product-items/productItemUtils.js";
|
||||
// import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
// import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
// import { createSharedProducts } from "@/utils/scriptUtils/testUtils/createSharedProduct.js";
|
||||
|
||||
// /**
|
||||
// * Shared products for downgrade test group
|
||||
// * Matches global products.free, products.pro, products.premium
|
||||
// */
|
||||
|
||||
// export const sharedFreeProduct = constructProduct({
|
||||
// id: "shared-downgrade-free",
|
||||
// type: "free",
|
||||
// isDefault: true,
|
||||
// excludeBase: true,
|
||||
// items: [
|
||||
// constructFeatureItem({
|
||||
// featureId: TestFeature.Messages,
|
||||
// includedUsage: 5,
|
||||
// interval: ProductItemInterval.Month,
|
||||
// }),
|
||||
// ],
|
||||
// });
|
||||
|
||||
// export const sharedProProduct = constructProduct({
|
||||
// id: "shared-downgrade-pro",
|
||||
// type: "pro",
|
||||
// excludeBase: true,
|
||||
// items: [
|
||||
// constructFeatureItem({
|
||||
// featureId: TestFeature.Dashboard,
|
||||
// isBoolean: true,
|
||||
// }),
|
||||
// constructFeatureItem({
|
||||
// featureId: TestFeature.Messages,
|
||||
// includedUsage: 10,
|
||||
// interval: ProductItemInterval.Month,
|
||||
// }),
|
||||
// constructFeatureItem({
|
||||
// featureId: TestFeature.Admin,
|
||||
// unlimited: true,
|
||||
// }),
|
||||
// constructPriceItem({
|
||||
// price: 2000,
|
||||
// interval: BillingInterval.Month,
|
||||
// }),
|
||||
// ],
|
||||
// });
|
||||
|
||||
// export const sharedPremiumProduct = constructProduct({
|
||||
// id: "shared-downgrade-premium",
|
||||
// type: "premium",
|
||||
// excludeBase: true,
|
||||
// items: [
|
||||
// constructFeatureItem({
|
||||
// featureId: TestFeature.Messages,
|
||||
// includedUsage: 100,
|
||||
// interval: ProductItemInterval.Month,
|
||||
// }),
|
||||
// constructPriceItem({
|
||||
// price: 5000,
|
||||
// interval: BillingInterval.Month,
|
||||
// }),
|
||||
// ],
|
||||
// });
|
||||
|
||||
// export const initDowngradeSharedProducts = async () => {
|
||||
// await createSharedProducts({
|
||||
// ctx,
|
||||
// products: [sharedFreeProduct, sharedProProduct, sharedPremiumProduct],
|
||||
// });
|
||||
// };
|
||||
|
||||
// // Auto-init on import (backwards compat)
|
||||
// await initDowngradeSharedProducts();
|
||||
@@ -1,76 +0,0 @@
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import { LegacyVersion } 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 { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
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 testCase = "aentity1";
|
||||
|
||||
const pro = constructProduct({
|
||||
items: [
|
||||
constructArrearItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 1500,
|
||||
}),
|
||||
],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`attach/${testCase}: Testing attach to entity via checkout`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
|
||||
beforeAll(async () => {
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
});
|
||||
|
||||
const newEntities = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Entity 1",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
];
|
||||
|
||||
test("should attach pro product to entity 1", async () => {
|
||||
await autumn.entities.create(customerId, newEntities);
|
||||
const entityId = newEntities[0].id;
|
||||
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
entityId,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
// console.log("customer products:", customer.products);
|
||||
|
||||
expectProductAttached({
|
||||
customer,
|
||||
product: pro,
|
||||
entityId,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,138 +0,0 @@
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import { defaultApiVersion } from "@tests/constants.js";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "@tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectFeaturesCorrect } from "@tests/utils/expectUtils/expectFeaturesCorrect.js";
|
||||
import { expectInvoiceAfterUsage } from "@tests/utils/expectUtils/expectSingleUse/expectUsageInvoice.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
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";
|
||||
import { advanceToNextInvoice } from "../../utils/testAttachUtils/testAttachUtils";
|
||||
|
||||
const testCase = "aentity2";
|
||||
|
||||
export const proAnnual = constructProduct({
|
||||
items: [
|
||||
constructArrearItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 1500,
|
||||
}),
|
||||
],
|
||||
type: "pro",
|
||||
isAnnual: true,
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`attach/${testCase}: Testing attach pro annual to entity via checkout`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: defaultApiVersion });
|
||||
let testClockId: string;
|
||||
|
||||
let curUnix = Date.now();
|
||||
|
||||
beforeAll(async () => {
|
||||
const result = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = result.testClockId!;
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [proAnnual],
|
||||
prefix: testCase,
|
||||
});
|
||||
});
|
||||
|
||||
const newEntities = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Entity 1",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
];
|
||||
|
||||
let entityId = newEntities[0].id;
|
||||
test("should attach pro annual product to entity 2", async () => {
|
||||
await autumn.entities.create(customerId, newEntities);
|
||||
entityId = newEntities[0].id;
|
||||
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: proAnnual,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
entityId,
|
||||
});
|
||||
});
|
||||
|
||||
const usage = 1250130;
|
||||
test("should track usage", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
entity_id: entityId,
|
||||
feature_id: TestFeature.Words,
|
||||
value: usage,
|
||||
});
|
||||
|
||||
const entity = await autumn.entities.get(customerId, entityId);
|
||||
|
||||
expectFeaturesCorrect({
|
||||
customer: entity,
|
||||
product: proAnnual,
|
||||
usage: [
|
||||
{
|
||||
featureId: TestFeature.Words,
|
||||
value: usage,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await timeout(2000);
|
||||
const nonCachedEntity = await autumn.entities.get(customerId, entityId, {
|
||||
skip_cache: "true",
|
||||
});
|
||||
|
||||
expectFeaturesCorrect({
|
||||
customer: nonCachedEntity,
|
||||
product: proAnnual,
|
||||
usage: [
|
||||
{
|
||||
featureId: TestFeature.Words,
|
||||
value: usage,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test("should have correct invoice after cycle", async () => {
|
||||
curUnix = await advanceToNextInvoice({
|
||||
stripeCli: ctx.stripeCli,
|
||||
testClockId,
|
||||
withPause: true,
|
||||
});
|
||||
|
||||
await expectInvoiceAfterUsage({
|
||||
autumn,
|
||||
customerId,
|
||||
entityId,
|
||||
featureId: TestFeature.Words,
|
||||
product: proAnnual,
|
||||
usage,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
numInvoices: 2,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,120 +0,0 @@
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import { defaultApiVersion } from "@tests/constants.js";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { hoursToFinalizeInvoice } from "@tests/utils/constants.js";
|
||||
import { attachAndExpectCorrect } from "@tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectInvoiceAfterUsage } from "@tests/utils/expectUtils/expectSingleUse/expectUsageInvoice.js";
|
||||
import { advanceTestClock } from "@tests/utils/stripeUtils.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { addHours, addMonths } from "date-fns";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
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 testCase = "aentity3";
|
||||
|
||||
export const proAnnual = constructProduct({
|
||||
items: [
|
||||
constructArrearItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 1500,
|
||||
}),
|
||||
],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`attach/${testCase}: Attach pro annual to entity and cancel`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: defaultApiVersion });
|
||||
let testClockId: string;
|
||||
|
||||
let curUnix = Date.now();
|
||||
|
||||
beforeAll(async () => {
|
||||
const result = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = result.testClockId!;
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [proAnnual],
|
||||
prefix: testCase,
|
||||
});
|
||||
});
|
||||
|
||||
const newEntities = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Entity 1",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
];
|
||||
|
||||
let entityId = newEntities[0].id;
|
||||
test("should attach pro annual to entity", async () => {
|
||||
await autumn.entities.create(customerId, newEntities);
|
||||
entityId = newEntities[0].id;
|
||||
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: proAnnual,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
entityId,
|
||||
});
|
||||
});
|
||||
|
||||
const nextUsage = 1032100;
|
||||
test("should cancel and have correct final invoice", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
entity_id: entityId,
|
||||
feature_id: TestFeature.Words,
|
||||
value: nextUsage,
|
||||
});
|
||||
|
||||
await autumn.cancel({
|
||||
customer_id: customerId,
|
||||
product_id: proAnnual.id,
|
||||
entity_id: entityId,
|
||||
});
|
||||
|
||||
await timeout(5000);
|
||||
|
||||
curUnix = await advanceTestClock({
|
||||
stripeCli: ctx.stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addHours(
|
||||
addMonths(curUnix, 1),
|
||||
hoursToFinalizeInvoice,
|
||||
).getTime(),
|
||||
});
|
||||
|
||||
await expectInvoiceAfterUsage({
|
||||
autumn,
|
||||
customerId,
|
||||
entityId,
|
||||
featureId: TestFeature.Words,
|
||||
product: proAnnual,
|
||||
usage: nextUsage,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
numInvoices: 2,
|
||||
expectExpired: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,190 +0,0 @@
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import { defaultApiVersion } from "@tests/constants.js";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "@tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectFeaturesCorrect } from "@tests/utils/expectUtils/expectFeaturesCorrect.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
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";
|
||||
import { timeout } from "../../utils/genUtils.js";
|
||||
|
||||
const testCase = "aentity4";
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [
|
||||
constructArrearItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 1500,
|
||||
}),
|
||||
],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`attach/${testCase}: Testing attach pro diff entities and testing track / check`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: defaultApiVersion });
|
||||
|
||||
beforeAll(async () => {
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
});
|
||||
|
||||
const newEntities = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Entity 1",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Entity 2",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
];
|
||||
|
||||
const entity1 = newEntities[0];
|
||||
const entity2 = newEntities[1];
|
||||
|
||||
test("should attach pro product to entity 1", async () => {
|
||||
await autumn.entities.create(customerId, newEntities);
|
||||
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
entityId: entity1.id,
|
||||
numSubs: 1,
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach pro product to entity 2", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
entityId: entity2.id,
|
||||
numSubs: 2,
|
||||
});
|
||||
|
||||
// wait for webhooks to clear cache
|
||||
await timeout(4000);
|
||||
});
|
||||
|
||||
const entity1Usage = Math.random() * 1000000;
|
||||
test("should track usage on entity 1", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
entity_id: entity1.id,
|
||||
feature_id: TestFeature.Words,
|
||||
value: entity1Usage,
|
||||
});
|
||||
|
||||
const entity1Res = await autumn.entities.get(customerId, entity1.id);
|
||||
const entity2Res = await autumn.entities.get(customerId, entity2.id);
|
||||
|
||||
expectFeaturesCorrect({
|
||||
customer: entity1Res,
|
||||
product: pro,
|
||||
usage: [
|
||||
{
|
||||
featureId: TestFeature.Words,
|
||||
value: entity1Usage,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expectFeaturesCorrect({
|
||||
customer: entity2Res,
|
||||
product: pro,
|
||||
});
|
||||
|
||||
await timeout(2000);
|
||||
const entity1ResUncached = await autumn.entities.get(
|
||||
customerId,
|
||||
entity1.id,
|
||||
{
|
||||
skip_cache: "true",
|
||||
},
|
||||
);
|
||||
const entity2ResUncached = await autumn.entities.get(
|
||||
customerId,
|
||||
entity2.id,
|
||||
{
|
||||
skip_cache: "true",
|
||||
},
|
||||
);
|
||||
|
||||
expectFeaturesCorrect({
|
||||
customer: entity1ResUncached,
|
||||
product: pro,
|
||||
usage: [
|
||||
{
|
||||
featureId: TestFeature.Words,
|
||||
value: entity1Usage,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expectFeaturesCorrect({
|
||||
customer: entity2ResUncached,
|
||||
product: pro,
|
||||
});
|
||||
});
|
||||
|
||||
const entity2Usage = Math.random() * 1000000;
|
||||
test("should track usage on entity 2", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
entity_id: entity2.id,
|
||||
feature_id: TestFeature.Words,
|
||||
value: entity2Usage,
|
||||
});
|
||||
|
||||
const entity1Res = await autumn.entities.get(customerId, entity1.id);
|
||||
const entity2Res = await autumn.entities.get(customerId, entity2.id);
|
||||
|
||||
expectFeaturesCorrect({
|
||||
customer: entity1Res,
|
||||
product: pro,
|
||||
usage: [
|
||||
{
|
||||
featureId: TestFeature.Words,
|
||||
value: entity1Usage,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expectFeaturesCorrect({
|
||||
customer: entity2Res,
|
||||
product: pro,
|
||||
usage: [
|
||||
{
|
||||
featureId: TestFeature.Words,
|
||||
value: entity2Usage,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,161 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { CusProductStatus } from "@autumn/shared";
|
||||
import { defaultApiVersion } from "@tests/constants.js";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { hoursToFinalizeInvoice } from "@tests/utils/constants.js";
|
||||
import { attachAndExpectCorrect } from "@tests/utils/expectUtils/expectAttach.js";
|
||||
import { advanceTestClock } from "@tests/utils/stripeUtils.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { addHours, addMonths } from "date-fns";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
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";
|
||||
import {
|
||||
expectProductAttached,
|
||||
expectScheduledApiSub,
|
||||
} from "../../utils/expectUtils/expectProductAttached";
|
||||
|
||||
const testCase = "aentity5";
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [
|
||||
constructArrearItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 1500,
|
||||
}),
|
||||
],
|
||||
type: "pro",
|
||||
});
|
||||
export const premium = constructProduct({
|
||||
items: [
|
||||
constructArrearItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 1500,
|
||||
}),
|
||||
],
|
||||
type: "premium",
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`attach/${testCase}: Testing downgrade entity product`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: defaultApiVersion });
|
||||
let testClockId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const result = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = result.testClockId!;
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro, premium],
|
||||
prefix: testCase,
|
||||
});
|
||||
});
|
||||
|
||||
const newEntities = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Entity 1",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Entity 2",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
];
|
||||
|
||||
const entity1 = newEntities[0];
|
||||
const entity2 = newEntities[1];
|
||||
|
||||
test("should attach premium product to entity 1", async () => {
|
||||
await autumn.entities.create(customerId, newEntities);
|
||||
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: premium,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
entityId: entity1.id,
|
||||
numSubs: 1,
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach premium product to entity 2", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: premium,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
entityId: entity2.id,
|
||||
numSubs: 2,
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach pro product to entity 1", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
entity_id: entity1.id,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
const entity = await autumn.entities.get(customerId, entity1.id);
|
||||
|
||||
expectProductAttached({
|
||||
customer: entity,
|
||||
product: pro,
|
||||
status: CusProductStatus.Scheduled,
|
||||
});
|
||||
|
||||
await expectScheduledApiSub({
|
||||
customerId,
|
||||
entityId: entity1.id,
|
||||
productId: pro.id,
|
||||
});
|
||||
// const entity = await autumn.entities.get(customerId, entity1.id);
|
||||
// const proProd = entity.products.find((p: any) => p.id === pro.id);
|
||||
// expect(proProd).toBeDefined();
|
||||
// expect(proProd.status).toBe(CusProductStatus.Scheduled);
|
||||
});
|
||||
|
||||
test("should advance test clock and have pro attached to entity 1", async () => {
|
||||
await advanceTestClock({
|
||||
stripeCli: ctx.stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addHours(
|
||||
addMonths(new Date(), 1),
|
||||
hoursToFinalizeInvoice,
|
||||
).getTime(),
|
||||
waitForSeconds: 30,
|
||||
});
|
||||
|
||||
const entity = await autumn.entities.get(customerId, entity1.id);
|
||||
const proProd = entity.products?.find((p: any) => p.id === pro.id);
|
||||
expect(proProd).toBeDefined();
|
||||
expect(proProd?.status).toBe(CusProductStatus.Active);
|
||||
expect(entity.products?.length).toBe(1);
|
||||
|
||||
const entity2Res = await autumn.entities.get(customerId, entity2.id);
|
||||
const premiumProd = entity2Res.products?.find(
|
||||
(p: any) => p.id === premium.id,
|
||||
);
|
||||
expect(premiumProd).toBeDefined();
|
||||
expect(premiumProd?.status).toBe(CusProductStatus.Active);
|
||||
expect(entity2Res.products?.length).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -1,90 +0,0 @@
|
||||
// import { beforeAll, describe, expect } from "bun:test";
|
||||
// import {
|
||||
// ApiVersion,
|
||||
// CusProductStatus,
|
||||
// FreeTrialDuration,
|
||||
// } from "@autumn/shared";
|
||||
// import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
// import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
// import chalk from "chalk";
|
||||
// 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";
|
||||
// import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
|
||||
// const pro = constructProduct({
|
||||
// type: "pro",
|
||||
// freeTrial: {
|
||||
// length: 7,
|
||||
// duration: FreeTrialDuration.Day,
|
||||
// unique_fingerprint: true,
|
||||
// card_required: false,
|
||||
// },
|
||||
// items: [
|
||||
// constructFeatureItem({
|
||||
// featureId: TestFeature.Messages,
|
||||
// includedUsage: 300,
|
||||
// entityFeatureId: TestFeature.Users,
|
||||
// }),
|
||||
// ],
|
||||
// });
|
||||
|
||||
// const testCase = "entity6";
|
||||
|
||||
// describe(`${chalk.yellowBright("entity6: Testing two entities with a free trial")}`, () => {
|
||||
// const customerId = testCase;
|
||||
|
||||
// const autumnV1: AutumnInt = new AutumnInt({ version: ApiVersion.V1_2 });
|
||||
|
||||
// beforeAll(async () => {
|
||||
// await initCustomerV3({
|
||||
// ctx,
|
||||
// customerId,
|
||||
// withTestClock: true,
|
||||
// attachPm: "success",
|
||||
// });
|
||||
|
||||
// await autumnV1.entities.create(customerId, [
|
||||
// {
|
||||
// id: "entity1",
|
||||
// name: "Entity 1",
|
||||
// feature_id: TestFeature.Users,
|
||||
// },
|
||||
// {
|
||||
// id: "entity2",
|
||||
// name: "Entity 2",
|
||||
// feature_id: TestFeature.Users,
|
||||
// },
|
||||
// ]);
|
||||
|
||||
// await initProductsV0({
|
||||
// ctx,
|
||||
// products: [pro],
|
||||
// prefix: testCase,
|
||||
// });
|
||||
|
||||
// await autumnV1.attach({
|
||||
// customer_id: customerId,
|
||||
// product_id: pro.id,
|
||||
// entity_id: "entity1",
|
||||
// });
|
||||
|
||||
// const entity1 = await autumnV1.entities.get(customerId, "entity1");
|
||||
|
||||
// await autumnV1.attach({
|
||||
// customer_id: customerId,
|
||||
// product_id: pro.id,
|
||||
// entity_id: "entity2",
|
||||
// });
|
||||
|
||||
// const entity2 = await autumnV1.entities.get(customerId, "entity2");
|
||||
|
||||
// expect(entity1.products.length).toBe(1);
|
||||
// expect(entity2.products.length).toBe(1);
|
||||
// expect(entity1.products[0].id).toBe(pro.id);
|
||||
// expect(entity2.products[0].id).toBe(pro.id);
|
||||
// expect(entity1.products[0].status).toBe(CusProductStatus.Trialing);
|
||||
// expect(entity2.products[0].status).toBe(CusProductStatus.Trialing);
|
||||
// });
|
||||
// });
|
||||
@@ -1,157 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import {
|
||||
type ApiCusProductV3,
|
||||
type ApiCustomerV3,
|
||||
CreateFreeTrialSchema,
|
||||
CusProductStatus,
|
||||
FreeTrialDuration,
|
||||
LegacyVersion,
|
||||
} from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { addDays } from "date-fns";
|
||||
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";
|
||||
|
||||
const testCase = "free1";
|
||||
|
||||
const trial1 = CreateFreeTrialSchema.parse({
|
||||
length: 7,
|
||||
duration: FreeTrialDuration.Day,
|
||||
});
|
||||
|
||||
export const free = constructProduct({
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 100,
|
||||
}),
|
||||
],
|
||||
isDefault: false,
|
||||
freeTrial: trial1,
|
||||
type: "free",
|
||||
id: "enterprise_trial",
|
||||
});
|
||||
export const addOn = constructProduct({
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Credits,
|
||||
includedUsage: 1000,
|
||||
}),
|
||||
],
|
||||
isDefault: false,
|
||||
type: "free",
|
||||
isAddOn: true,
|
||||
id: "add_on",
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing free product with trial and attaching add on`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({
|
||||
version: LegacyVersion.v1_4,
|
||||
orgConfig: { multiple_trials: true },
|
||||
});
|
||||
let testClockId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const result = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = result.testClockId!;
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [free, addOn],
|
||||
prefix: testCase,
|
||||
});
|
||||
});
|
||||
|
||||
const approximateDiff = 1000 * 60 * 80; // 60 minutes
|
||||
test("should attach free product with trial", async () => {
|
||||
const attachPreview = await autumn.attachPreview({
|
||||
customer_id: customerId,
|
||||
product_id: free.id,
|
||||
});
|
||||
|
||||
const attach = await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: free.id,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const freeProduct = customer.products.find(
|
||||
(p) => p.id === free.id,
|
||||
) as ApiCusProductV3;
|
||||
|
||||
expect(freeProduct).toBeDefined();
|
||||
expect(freeProduct?.status).toBe(CusProductStatus.Trialing);
|
||||
expect(freeProduct?.current_period_end).toBeGreaterThanOrEqual(
|
||||
addDays(Date.now(), trial1.length).getTime() - approximateDiff,
|
||||
);
|
||||
expect(freeProduct?.current_period_end).toBeLessThanOrEqual(
|
||||
addDays(Date.now(), trial1.length).getTime() + approximateDiff,
|
||||
);
|
||||
});
|
||||
|
||||
const trial2 = CreateFreeTrialSchema.parse({
|
||||
length: 14,
|
||||
duration: FreeTrialDuration.Day,
|
||||
});
|
||||
|
||||
test("should update free product's trial end date", async () => {
|
||||
const attachPreview = await autumn.attachPreview({
|
||||
customer_id: customerId,
|
||||
product_id: free.id,
|
||||
free_trial: trial2,
|
||||
is_custom: true,
|
||||
});
|
||||
|
||||
const attach = await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: free.id,
|
||||
free_trial: trial2,
|
||||
is_custom: true,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const freeProduct = customer.products.find(
|
||||
(p) => p.id === free.id,
|
||||
) as ApiCusProductV3;
|
||||
|
||||
expect(freeProduct?.status).toBe(CusProductStatus.Trialing);
|
||||
expect(freeProduct?.current_period_end).toBeGreaterThanOrEqual(
|
||||
addDays(Date.now(), trial2.length).getTime() - approximateDiff,
|
||||
);
|
||||
expect(freeProduct?.current_period_end).toBeLessThanOrEqual(
|
||||
addDays(Date.now(), trial2.length).getTime() + approximateDiff,
|
||||
);
|
||||
});
|
||||
|
||||
test("should attach add on product", async () => {
|
||||
const attachPreview = await autumn.attachPreview({
|
||||
customer_id: customerId,
|
||||
product_id: addOn.id,
|
||||
});
|
||||
|
||||
const attach = await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: addOn.id,
|
||||
});
|
||||
|
||||
const customer = (await autumn.customers.get(customerId)) as ApiCustomerV3;
|
||||
const addOnProduct = customer.products.find((p) => p.id === addOn.id);
|
||||
const freeProduct = customer.products.find((p) => p.id === free.id);
|
||||
|
||||
expect(addOnProduct).toBeDefined();
|
||||
expect(addOnProduct?.status).toBe("active");
|
||||
expect(freeProduct?.status).toBe("trialing");
|
||||
});
|
||||
});
|
||||
@@ -1,133 +0,0 @@
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import { AutumnCli } from "@tests/cli/AutumnCli.js";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { expectCustomerV0Correct } from "@tests/utils/expectUtils/expectCustomerV0Correct.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
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";
|
||||
|
||||
/*
|
||||
FLOW:
|
||||
1. Attach pro group 1 & pro group 2 at once -> should have both products as main
|
||||
2. Upgrade pro group 1 -> premium group 1
|
||||
3. Upgrade pro group 2 -> premium group 2
|
||||
*/
|
||||
|
||||
const testCase = "multiProduct1";
|
||||
|
||||
// Group 1 products (use Messages feature)
|
||||
const proGroup1 = constructProduct({
|
||||
id: "proGroup1",
|
||||
group: `${testCase}-g1`,
|
||||
type: "pro",
|
||||
items: [
|
||||
constructArrearItem({
|
||||
includedUsage: 10,
|
||||
featureId: TestFeature.Messages,
|
||||
price: 100, // $1.00 per unit (100 cents per billing unit of 1)
|
||||
billingUnits: 1,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const premiumGroup1 = constructProduct({
|
||||
id: "premiumGroup1",
|
||||
group: `${testCase}-g1`,
|
||||
type: "premium",
|
||||
items: [
|
||||
constructArrearItem({
|
||||
includedUsage: 100,
|
||||
featureId: TestFeature.Messages,
|
||||
price: 200, // $2.00 per unit (200 cents per billing unit of 1)
|
||||
billingUnits: 1,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
// Group 2 products (use Words feature)
|
||||
const proGroup2 = constructProduct({
|
||||
id: "proGroup2",
|
||||
group: `${testCase}-g2`,
|
||||
type: "pro",
|
||||
items: [
|
||||
constructArrearItem({
|
||||
includedUsage: 10,
|
||||
featureId: TestFeature.Words,
|
||||
price: 60, // $0.60 per unit (60 cents per billing unit of 1)
|
||||
billingUnits: 1,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const premiumGroup2 = constructProduct({
|
||||
id: "premiumGroup2",
|
||||
group: `${testCase}-g2`,
|
||||
type: "premium",
|
||||
items: [
|
||||
constructArrearItem({
|
||||
includedUsage: 10,
|
||||
featureId: TestFeature.Words,
|
||||
price: 90, // $0.90 per unit (90 cents per billing unit of 1)
|
||||
billingUnits: 1,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
describe(
|
||||
chalk.yellowBright(`${testCase}: Testing multi product attach, and upgrade`),
|
||||
() => {
|
||||
const customerId = testCase;
|
||||
beforeAll(async () => {
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [proGroup1, proGroup2, premiumGroup1, premiumGroup2],
|
||||
// prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach pro group 1 and pro group 2", async () => {
|
||||
await AutumnCli.attach({
|
||||
customerId: customerId,
|
||||
productIds: [proGroup1.id, proGroup2.id],
|
||||
});
|
||||
|
||||
const cusRes = await AutumnCli.getCustomer(customerId);
|
||||
await expectCustomerV0Correct({ sent: proGroup1, cusRes });
|
||||
await expectCustomerV0Correct({ sent: proGroup2, cusRes });
|
||||
});
|
||||
|
||||
test("should upgrade to premium group 1", async () => {
|
||||
await AutumnCli.attach({
|
||||
customerId: customerId,
|
||||
productId: premiumGroup1.id,
|
||||
});
|
||||
|
||||
// 1. Compare main product
|
||||
const cusRes = await AutumnCli.getCustomer(customerId);
|
||||
await expectCustomerV0Correct({ sent: premiumGroup1, cusRes });
|
||||
});
|
||||
|
||||
test("should upgrade to premium group 2", async () => {
|
||||
await AutumnCli.attach({
|
||||
customerId: customerId,
|
||||
productId: premiumGroup2.id,
|
||||
});
|
||||
|
||||
// 1. Compare main product
|
||||
const cusRes = await AutumnCli.getCustomer(customerId);
|
||||
await expectCustomerV0Correct({ sent: premiumGroup2, cusRes });
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -1,108 +0,0 @@
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import { LegacyVersion } from "@autumn/shared";
|
||||
import { attachAndExpectCorrect } from "@tests/utils/expectUtils/expectAttach.js";
|
||||
import {
|
||||
expectDowngradeCorrect,
|
||||
expectNextCycleCorrect,
|
||||
} from "@tests/utils/expectUtils/expectScheduleUtils.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.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 testCase = "others1";
|
||||
|
||||
export const free = constructProduct({
|
||||
items: [],
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [],
|
||||
type: "pro",
|
||||
trial: true,
|
||||
});
|
||||
|
||||
export const premium = constructProduct({
|
||||
items: [],
|
||||
type: "premium",
|
||||
trial: true,
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing trials: pro with trial -> premium with trial -> free`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
beforeAll(async () => {
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [free, pro, premium],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
test("should attach pro product (with trial)", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
stripeCli: ctx.stripeCli,
|
||||
customerId,
|
||||
product: pro,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach premium product (with trial)", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
stripeCli: ctx.stripeCli,
|
||||
customerId,
|
||||
product: premium,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach free product at the end of the trial", async () => {
|
||||
const { preview } = await expectDowngradeCorrect({
|
||||
autumn,
|
||||
stripeCli: ctx.stripeCli,
|
||||
customerId,
|
||||
curProduct: premium,
|
||||
newProduct: free,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
expectNextCycleCorrect({
|
||||
autumn,
|
||||
preview,
|
||||
stripeCli: ctx.stripeCli,
|
||||
customerId,
|
||||
testClockId,
|
||||
product: free,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,94 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { ApiVersion, ErrCode } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import AutumnError, { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { generateId } from "@/utils/genUtils";
|
||||
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";
|
||||
|
||||
const pro = constructProduct({
|
||||
type: "pro",
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Credits,
|
||||
includedUsage: 500,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const testCase = "others10";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}/idempotency: idempotency key already exists`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumnV1: AutumnInt = new AutumnInt({ version: ApiVersion.V1_2 });
|
||||
|
||||
let results: PromiseSettledResult<
|
||||
Awaited<ReturnType<typeof autumnV1.attach>>
|
||||
>[];
|
||||
|
||||
beforeAll(async () => {
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
withTestClock: true,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
const idempotencyKey = generateId("it");
|
||||
|
||||
results = await Promise.allSettled([
|
||||
autumnV1.attach(
|
||||
{
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
},
|
||||
{
|
||||
idempotencyKey,
|
||||
},
|
||||
),
|
||||
autumnV1.attach(
|
||||
{
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
},
|
||||
{
|
||||
idempotencyKey,
|
||||
},
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
test("should reject duplicate idempotency key with 409", async () => {
|
||||
// Exactly one request should succeed
|
||||
const fulfilled = results.filter((r) => r.status === "fulfilled");
|
||||
const rejected = results.filter((r) => r.status === "rejected");
|
||||
|
||||
expect(fulfilled).toHaveLength(1);
|
||||
expect(rejected).toHaveLength(1);
|
||||
|
||||
// The successful request should have attached the product
|
||||
const successResult = fulfilled[0] as PromiseFulfilledResult<
|
||||
Awaited<ReturnType<typeof autumnV1.attach>>
|
||||
>;
|
||||
expect(successResult.value.success).toBe(true);
|
||||
expect(successResult.value.customer_id).toBe(customerId);
|
||||
expect(successResult.value.product_ids).toContain(pro.id);
|
||||
|
||||
// The rejected request should have the duplicate idempotency key error
|
||||
const rejectedResult = rejected[0] as PromiseRejectedResult;
|
||||
expect(rejectedResult.reason).toBeInstanceOf(AutumnError);
|
||||
expect((rejectedResult.reason as AutumnError).code).toBe(
|
||||
ErrCode.DuplicateIdempotencyKey,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,69 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
|
||||
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 { attachFailedPaymentMethod } from "@/external/stripe/stripeCusUtils.js";
|
||||
import { CusService } from "@/internal/customers/CusService.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 testCase = "others3";
|
||||
|
||||
export const pro = constructProduct({
|
||||
type: "pro",
|
||||
items: [],
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing attach payment failure`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
beforeAll(async () => {
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
// Payment failure
|
||||
test("should handle payment failure", async () => {
|
||||
const customer = await CusService.get({
|
||||
db: ctx.db,
|
||||
idOrInternalId: customerId,
|
||||
orgId: ctx.org.id,
|
||||
env: ctx.env,
|
||||
});
|
||||
|
||||
await attachFailedPaymentMethod({
|
||||
stripeCli: ctx.stripeCli,
|
||||
customer: customer!,
|
||||
});
|
||||
|
||||
const res = await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
// console.log(res);
|
||||
|
||||
expect(res.checkout_url).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,122 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { LegacyVersion } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { expectAttachCorrect } from "@tests/utils/expectUtils/expectAttach.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { timeout } from "../../utils/genUtils";
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const testCase = "others6";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing attach with customer ID and entity ID null`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
|
||||
const email = `${customerId}@test.com`;
|
||||
beforeAll(async () => {
|
||||
const customer = await CusService.getByEmail({
|
||||
db: ctx.db,
|
||||
orgId: ctx.org.id,
|
||||
env: ctx.env,
|
||||
email,
|
||||
});
|
||||
|
||||
if (customer.length > 0) {
|
||||
await autumn.customers.delete(customer[0].internal_id);
|
||||
}
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
});
|
||||
|
||||
let internalCustomerId = "";
|
||||
let internalEntityId = "";
|
||||
const entityId = "1";
|
||||
test("should attach create customer with no ID", async () => {
|
||||
const customer = await autumn.customers.create({
|
||||
id: null,
|
||||
email: `${customerId}@test.com`,
|
||||
name: customerId,
|
||||
withAutumnId: true,
|
||||
});
|
||||
|
||||
expect(customer.autumn_id).toBeDefined();
|
||||
|
||||
internalCustomerId = customer.autumn_id;
|
||||
|
||||
const data = await autumn.entities.create(internalCustomerId, {
|
||||
id: null,
|
||||
feature_id: TestFeature.Users,
|
||||
});
|
||||
|
||||
internalEntityId = data.autumn_id;
|
||||
|
||||
expect(internalEntityId).toBeDefined();
|
||||
});
|
||||
|
||||
test("should be able to attach pro product, invoice only", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: internalCustomerId,
|
||||
entity_id: internalEntityId,
|
||||
product_id: pro.id,
|
||||
invoice: true,
|
||||
enable_product_immediately: true,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(internalCustomerId);
|
||||
|
||||
expectAttachCorrect({
|
||||
customer,
|
||||
product: pro,
|
||||
});
|
||||
|
||||
expect(customer.invoices!.length).toBe(1);
|
||||
expect(customer.invoices![0].status).toBe("draft");
|
||||
});
|
||||
|
||||
test("should create customer with ID, and attach pro product", async () => {
|
||||
const customer = await autumn.customers.create({
|
||||
id: customerId,
|
||||
email: `${customerId}@test.com`,
|
||||
});
|
||||
|
||||
expect(customer.autumn_id).toBe(internalCustomerId);
|
||||
|
||||
const entity = await autumn.entities.create(customer.id, {
|
||||
id: entityId,
|
||||
feature_id: TestFeature.Users,
|
||||
});
|
||||
|
||||
internalEntityId = entity.autumn_id;
|
||||
|
||||
await timeout(2000);
|
||||
|
||||
const customer2 = await autumn.customers.get(customerId);
|
||||
|
||||
expectAttachCorrect({
|
||||
customer: customer2,
|
||||
product: pro,
|
||||
});
|
||||
|
||||
const entity2 = await autumn.entities.get(customerId, entityId);
|
||||
|
||||
expectAttachCorrect({
|
||||
customer: entity2,
|
||||
product: pro,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,61 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { expectAttachCorrect } from "@tests/utils/expectUtils/expectAttach.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import { getBasePrice } from "@tests/utils/testProductUtils/testProductUtils.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 { 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";
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "pro",
|
||||
trial: true,
|
||||
});
|
||||
|
||||
const testCase = "others7";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing attach with free_trial=False`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
|
||||
beforeAll(async () => {
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: { fingerprint: "test" },
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach pro product with free_trial=False", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
free_trial: false,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
|
||||
expectAttachCorrect({
|
||||
customer,
|
||||
product: pro,
|
||||
});
|
||||
|
||||
expect(customer.invoices.length).toBe(1);
|
||||
expect(customer.invoices[0].total).toBe(getBasePrice({ product: pro }));
|
||||
});
|
||||
});
|
||||
@@ -1,86 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import { getBasePrice } from "@tests/utils/testProductUtils/testProductUtils.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,
|
||||
constructPrepaidItem,
|
||||
} 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";
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
}),
|
||||
constructPrepaidItem({
|
||||
isOneOff: true,
|
||||
featureId: TestFeature.Users,
|
||||
billingUnits: 1,
|
||||
price: 100,
|
||||
}),
|
||||
],
|
||||
isAnnual: true,
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const testCase = "others8";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing annual pro with one off prepaid`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
|
||||
beforeAll(async () => {
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: { fingerprint: "test" },
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach annual pro product with one off prepaid", async () => {
|
||||
const options = [
|
||||
{
|
||||
feature_id: TestFeature.Users,
|
||||
quantity: 1,
|
||||
},
|
||||
];
|
||||
|
||||
const preview = await autumn.attachPreview({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
options,
|
||||
});
|
||||
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
options,
|
||||
});
|
||||
|
||||
console.log(preview);
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
|
||||
const invoice = customer.invoices[0];
|
||||
// expect(preview.total).toBe(invoice.total);
|
||||
expect(invoice.total).toBe(
|
||||
getBasePrice({ product: pro }) + options[0].quantity * 100,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,74 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "@tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectAutumnError } from "@tests/utils/expectUtils/expectErrUtils.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";
|
||||
|
||||
export const free = constructProduct({
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
}),
|
||||
],
|
||||
isAnnual: false,
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
// Pro trial
|
||||
|
||||
// Pro
|
||||
|
||||
const testCase = "others9";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing attach free product again`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
|
||||
beforeAll(async () => {
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: { fingerprint: "test" },
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [free],
|
||||
prefix: testCase,
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach free product, then try again and hit error", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: free,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
skipSubCheck: true,
|
||||
});
|
||||
|
||||
await expectAutumnError({
|
||||
func: async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: free.id,
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,62 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { ApiVersion } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
|
||||
const testCase = "attach-response1";
|
||||
|
||||
const pro = constructProduct({
|
||||
items: [
|
||||
constructArrearItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 1000,
|
||||
}),
|
||||
],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing v0.2 response for attach, scenario: new`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: ApiVersion.V0_2 });
|
||||
|
||||
beforeAll(async () => {
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
});
|
||||
|
||||
test("should return v0.2 responses for attach", async () => {
|
||||
const attachResponse = await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
expect(attachResponse.checkout_url).toBeDefined();
|
||||
expect(Object.keys(attachResponse)).toEqual(["checkout_url"]);
|
||||
});
|
||||
|
||||
test("should return correct v1.2 responses for attach", async () => {
|
||||
const autumnV1 = new AutumnInt({ version: ApiVersion.V1_2 });
|
||||
const attachResponse = await autumnV1.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
expect(attachResponse).toMatchObject({
|
||||
customer_id: customerId,
|
||||
product_ids: [pro.id],
|
||||
checkout_url: expect.any(String),
|
||||
});
|
||||
expect(attachResponse.code).toBeDefined();
|
||||
expect(attachResponse.message).toBeDefined();
|
||||
expect(attachResponse.message).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,98 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { ApiVersion } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { initCustomerV3 } from "../../../src/utils/scriptUtils/testUtils/initCustomerV3";
|
||||
|
||||
const testCase = "attach-response2";
|
||||
|
||||
const pro = constructProduct({
|
||||
type: "pro",
|
||||
isDefault: false,
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 1000,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const premium = constructProduct({
|
||||
type: "premium",
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 1000,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing v0.2 / v1.2 response for attach, scenario: upgrade`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: ApiVersion.V0_2 });
|
||||
|
||||
beforeAll(async () => {
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro, premium],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
});
|
||||
|
||||
test("should return v0.2 responses for attach", async () => {
|
||||
const attachResponse = await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: premium.id,
|
||||
});
|
||||
|
||||
expect(Object.keys(attachResponse)).toEqual(["success", "message"]);
|
||||
});
|
||||
|
||||
test("should return correct v1.2 responses for attach", async () => {
|
||||
const autumnV1 = new AutumnInt({ version: ApiVersion.V1_2 });
|
||||
|
||||
await autumnV1.cancel({
|
||||
customer_id: customerId,
|
||||
product_id: premium.id,
|
||||
cancel_immediately: true,
|
||||
});
|
||||
|
||||
await autumnV1.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
const attachResponse = await autumnV1.attach({
|
||||
customer_id: customerId,
|
||||
product_id: premium.id,
|
||||
});
|
||||
|
||||
expect(attachResponse).toMatchObject({
|
||||
customer_id: customerId,
|
||||
product_ids: [premium.id],
|
||||
code: expect.any(String),
|
||||
message: expect.any(String),
|
||||
});
|
||||
// expect(attachResponse.code).toBeDefined();
|
||||
// expect(attachResponse.message).toBeDefined();
|
||||
// expect(attachResponse.message).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,90 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { ApiVersion } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { initCustomerV3 } from "../../../src/utils/scriptUtils/testUtils/initCustomerV3";
|
||||
|
||||
const testCase = "attach-response3";
|
||||
|
||||
const pro = constructProduct({
|
||||
type: "pro",
|
||||
isDefault: false,
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 1000,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const premium = constructProduct({
|
||||
type: "premium",
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 1000,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing v0.2 / v1.2 response for attach, scenario: downgrade`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: ApiVersion.V0_2 });
|
||||
|
||||
beforeAll(async () => {
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro, premium],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: premium.id,
|
||||
});
|
||||
});
|
||||
|
||||
test("should return v0.2 responses for attach", async () => {
|
||||
const attachResponse = await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
expect(Object.keys(attachResponse)).toEqual(["success", "message"]);
|
||||
});
|
||||
|
||||
test("should return correct v1.2 responses for attach", async () => {
|
||||
const autumnV1 = new AutumnInt({ version: ApiVersion.V1_2 });
|
||||
|
||||
await autumnV1.subscriptions.update({
|
||||
customer_id: customerId,
|
||||
product_id: premium.id,
|
||||
cancel_action: "uncancel",
|
||||
});
|
||||
|
||||
const attachResponse = await autumnV1.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
expect(attachResponse).toMatchObject({
|
||||
customer_id: customerId,
|
||||
product_ids: [pro.id],
|
||||
code: expect.any(String),
|
||||
message: expect.any(String),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,75 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { ApiVersion } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { initCustomerV3 } from "../../../src/utils/scriptUtils/testUtils/initCustomerV3";
|
||||
|
||||
const testCase = "attach-response4";
|
||||
|
||||
const pro = constructProduct({
|
||||
type: "pro",
|
||||
isDefault: false,
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 1000,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing v0.2 / v1.2 response for attach, scenario: new (card on file)`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: ApiVersion.V0_2 });
|
||||
|
||||
beforeAll(async () => {
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
});
|
||||
|
||||
test("should return v0.2 responses for attach", async () => {
|
||||
const attachResponse = await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
expect(Object.keys(attachResponse)).toEqual(["success", "message"]);
|
||||
});
|
||||
|
||||
test("should return correct v1.2 responses for attach", async () => {
|
||||
const autumnV1 = new AutumnInt({ version: ApiVersion.V1_2 });
|
||||
|
||||
await autumnV1.cancel({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
cancel_immediately: true,
|
||||
});
|
||||
|
||||
const attachResponse = await autumnV1.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
expect(attachResponse).toMatchObject({
|
||||
customer_id: customerId,
|
||||
product_ids: [pro.id],
|
||||
code: expect.any(String),
|
||||
message: expect.any(String),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,77 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { ApiVersion } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { initCustomerV3 } from "../../../src/utils/scriptUtils/testUtils/initCustomerV3";
|
||||
|
||||
const testCase = "attach-response5";
|
||||
|
||||
const oneOff = constructProduct({
|
||||
type: "one_off",
|
||||
isDefault: false,
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 1000,
|
||||
interval: null,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing v0.2 / v1.2 response for attach, scenario: one off (card on file)`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: ApiVersion.V0_2 });
|
||||
|
||||
beforeAll(async () => {
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [oneOff],
|
||||
prefix: testCase,
|
||||
});
|
||||
});
|
||||
|
||||
test("should return v0.2 responses for attach", async () => {
|
||||
const attachResponse = await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: oneOff.id,
|
||||
});
|
||||
|
||||
expect(attachResponse).toMatchObject({
|
||||
// customer_id: customerId,
|
||||
// product_ids: [oneOff.id],
|
||||
// code: expect.any(String),
|
||||
success: true,
|
||||
message: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
test("should return correct v1.2 responses for attach", async () => {
|
||||
const autumnV1 = new AutumnInt({ version: ApiVersion.V1_2 });
|
||||
|
||||
const attachResponse = await autumnV1.attach({
|
||||
customer_id: customerId,
|
||||
product_id: oneOff.id,
|
||||
});
|
||||
|
||||
expect(attachResponse).toMatchObject({
|
||||
success: true,
|
||||
customer_id: customerId,
|
||||
product_ids: [oneOff.id],
|
||||
code: expect.any(String),
|
||||
message: expect.any(String),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,130 +0,0 @@
|
||||
import { expect } from "bun:test";
|
||||
import {
|
||||
type AppEnv,
|
||||
AttachBranch,
|
||||
type Organization,
|
||||
type ProductItem,
|
||||
type ProductV2,
|
||||
} from "@autumn/shared";
|
||||
import { expectSubToBeCorrect } from "@tests/merged/mergeUtils/expectSubCorrect.js";
|
||||
import { expectFeaturesCorrect } from "@tests/utils/expectUtils/expectFeaturesCorrect.js";
|
||||
import {
|
||||
expectSubItemsCorrect,
|
||||
getSubsFromCusId,
|
||||
} from "@tests/utils/expectUtils/expectSubUtils.js";
|
||||
import type Stripe from "stripe";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import type { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
|
||||
const runUpdateEntsTest = async ({
|
||||
autumn,
|
||||
stripeCli,
|
||||
customerId,
|
||||
customProduct,
|
||||
newVersion,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
customItems,
|
||||
usage,
|
||||
}: {
|
||||
autumn: AutumnInt;
|
||||
stripeCli: Stripe;
|
||||
customerId: string;
|
||||
customProduct: ProductV2;
|
||||
newVersion?: number;
|
||||
db: DrizzleCli;
|
||||
org: Organization;
|
||||
env: AppEnv;
|
||||
customItems?: ProductItem[];
|
||||
usage?: {
|
||||
featureId: string;
|
||||
value: number;
|
||||
}[];
|
||||
}) => {
|
||||
// 1. Get subs before
|
||||
|
||||
const { subs: subsBefore } = await getSubsFromCusId({
|
||||
stripeCli,
|
||||
customerId,
|
||||
productId: customProduct.id,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
|
||||
const preview = await autumn.attachPreview({
|
||||
customer_id: customerId,
|
||||
product_id: customProduct.id,
|
||||
version: newVersion,
|
||||
is_custom: customItems ? true : undefined,
|
||||
items: customItems,
|
||||
});
|
||||
|
||||
if (newVersion) {
|
||||
expect(preview.branch).toBe(AttachBranch.NewVersion);
|
||||
} else {
|
||||
expect(preview.branch).toBe(AttachBranch.SameCustomEnts);
|
||||
expect(preview.due_today).toBeUndefined();
|
||||
}
|
||||
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: customProduct.id,
|
||||
version: newVersion,
|
||||
is_custom: customItems ? true : undefined,
|
||||
items: customItems,
|
||||
});
|
||||
|
||||
// 1. Ensure no new invoices created
|
||||
const { subs: subsAfter, cusProduct } = await getSubsFromCusId({
|
||||
stripeCli,
|
||||
customerId,
|
||||
productId: customProduct.id,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
|
||||
const invoicesBefore = subsBefore.map((sub) => sub.latest_invoice);
|
||||
const invoicesAfter = subsAfter.map((sub) => sub.latest_invoice);
|
||||
const subIdsBefore = subsBefore.map((sub) => sub.id);
|
||||
const subIdsAfter = subsAfter.map((sub) => sub.id);
|
||||
|
||||
// let periodEndsBefore = subsBefore.map((sub) => sub.current_period_end);
|
||||
// let periodEndsAfter = subsAfter.map((sub) => sub.current_period_end);
|
||||
|
||||
expect(invoicesAfter).toStrictEqual(invoicesBefore);
|
||||
expect(subIdsAfter).toStrictEqual(subIdsBefore);
|
||||
// expect(periodEndsAfter).toStrictEqual(periodEndsBefore);
|
||||
|
||||
if (customItems) {
|
||||
expect(cusProduct.is_custom).toBe(true);
|
||||
}
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
expectFeaturesCorrect({
|
||||
customer,
|
||||
product: customProduct,
|
||||
usage,
|
||||
});
|
||||
|
||||
// 2. Expect product attached
|
||||
await expectSubItemsCorrect({
|
||||
stripeCli,
|
||||
customerId,
|
||||
product: customProduct,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
|
||||
await expectSubToBeCorrect({
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
};
|
||||
|
||||
export default runUpdateEntsTest;
|
||||
@@ -1,155 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { LegacyVersion } 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 ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
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";
|
||||
import { advanceToNextInvoice } from "../../utils/testAttachUtils/testAttachUtils.js";
|
||||
import { replaceItems } from "../utils.js";
|
||||
import runUpdateEntsTest from "./expectUpdateEnts.js";
|
||||
|
||||
const testCase = "updateEnts1";
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [
|
||||
constructArrearItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 10000,
|
||||
}),
|
||||
],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing update ents (changing included usage)`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
const numUsers = 0;
|
||||
|
||||
beforeAll(async () => {
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
test("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
});
|
||||
|
||||
const newItem = constructArrearItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 20000,
|
||||
});
|
||||
|
||||
const customItems = replaceItems({
|
||||
items: pro.items,
|
||||
featureId: TestFeature.Words,
|
||||
newItem,
|
||||
});
|
||||
|
||||
const usage = 50000;
|
||||
const overage = 50000 - (newItem.included_usage as number);
|
||||
|
||||
test("should update overage item to have new included usage", async () => {
|
||||
const customProduct = {
|
||||
...pro,
|
||||
items: customItems,
|
||||
};
|
||||
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
value: usage,
|
||||
feature_id: TestFeature.Words,
|
||||
});
|
||||
|
||||
await timeout(5000);
|
||||
|
||||
await runUpdateEntsTest({
|
||||
autumn,
|
||||
stripeCli: ctx.stripeCli,
|
||||
customerId,
|
||||
customProduct,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
customItems,
|
||||
usage: [
|
||||
{
|
||||
featureId: TestFeature.Words,
|
||||
value: usage,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test("should have correct invoice next cycle", async () => {
|
||||
const invoiceTotal = await getExpectedInvoiceTotal({
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
customerId,
|
||||
productId: pro.id,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
usage: [
|
||||
{
|
||||
featureId: TestFeature.Words,
|
||||
value: usage,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await advanceToNextInvoice({
|
||||
stripeCli: ctx.stripeCli,
|
||||
testClockId,
|
||||
withPause: true,
|
||||
});
|
||||
|
||||
// let curUnix = Date.now();
|
||||
// curUnix = await advanceTestClock({
|
||||
// stripeCli: ctx.stripeCli,
|
||||
// testClockId,
|
||||
// advanceTo: addMonths(curUnix, 1).getTime(),
|
||||
// });
|
||||
|
||||
// await advanceTestClock({
|
||||
// stripeCli: ctx.stripeCli,
|
||||
// testClockId,
|
||||
// advanceTo: addHours(curUnix, hoursToFinalizeInvoice).getTime(),
|
||||
// waitForSeconds: 10,
|
||||
// });
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const invoice = customer.invoices![0];
|
||||
expect(invoice.total).toBe(invoiceTotal);
|
||||
});
|
||||
});
|
||||
@@ -1,170 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { LegacyVersion } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { hoursToFinalizeInvoice } from "@tests/utils/constants.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 ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { addHours, addMonths, addWeeks } from "date-fns";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
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";
|
||||
import { replaceItems } from "../utils.js";
|
||||
import runUpdateEntsTest from "./expectUpdateEnts.js";
|
||||
|
||||
const testCase = "updateEnts2";
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [
|
||||
constructArrearItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 10000,
|
||||
}),
|
||||
],
|
||||
type: "pro",
|
||||
isAnnual: true,
|
||||
});
|
||||
|
||||
/**
|
||||
* updateEnts2:
|
||||
* Testing updating entitlements for annual plans
|
||||
* 1. Start with pro annual plan (usage-based)
|
||||
* 2. Update included usage amount
|
||||
* 3. Verify features and usage are updated correctly
|
||||
* 4. Verify invoice total is correct in next billing cycle
|
||||
*
|
||||
* Verifies that updating entitlements works correctly for annual plans
|
||||
* and that usage/billing is calculated properly
|
||||
*/
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing update ents (changing included usage) for annual plan`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
const numUsers = 0;
|
||||
|
||||
beforeAll(async () => {
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
test("should attach pro annual product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
});
|
||||
|
||||
const newItem = constructArrearItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 5000,
|
||||
});
|
||||
|
||||
const customItems = replaceItems({
|
||||
items: pro.items,
|
||||
featureId: TestFeature.Words,
|
||||
newItem,
|
||||
});
|
||||
|
||||
const usage = 1200500;
|
||||
|
||||
test("should attach custom pro product", async () => {
|
||||
await advanceTestClock({
|
||||
stripeCli: ctx.stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addWeeks(curUnix, 2).getTime(),
|
||||
waitForSeconds: 30,
|
||||
});
|
||||
|
||||
const customProduct = {
|
||||
...pro,
|
||||
items: customItems,
|
||||
};
|
||||
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
value: usage,
|
||||
feature_id: TestFeature.Words,
|
||||
});
|
||||
|
||||
await timeout(5000);
|
||||
|
||||
await runUpdateEntsTest({
|
||||
autumn,
|
||||
stripeCli: ctx.stripeCli,
|
||||
customerId,
|
||||
customProduct,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
customItems,
|
||||
usage: [
|
||||
{
|
||||
featureId: TestFeature.Words,
|
||||
value: usage,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test("should have correct invoice usage next cycle", async () => {
|
||||
const invoiceTotal = await getExpectedInvoiceTotal({
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
customerId,
|
||||
productId: pro.id,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
usage: [
|
||||
{
|
||||
featureId: TestFeature.Words,
|
||||
value: usage,
|
||||
},
|
||||
],
|
||||
onlyIncludeMonthly: true,
|
||||
});
|
||||
|
||||
let curUnix = Date.now();
|
||||
curUnix = await advanceTestClock({
|
||||
stripeCli: ctx.stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addMonths(curUnix, 1).getTime(),
|
||||
});
|
||||
|
||||
await advanceTestClock({
|
||||
stripeCli: ctx.stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addHours(curUnix, hoursToFinalizeInvoice).getTime(),
|
||||
waitForSeconds: 10,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const invoice = customer.invoices![0];
|
||||
expect(invoice.total).toBe(invoiceTotal);
|
||||
});
|
||||
});
|
||||
@@ -1,186 +0,0 @@
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import { LegacyVersion } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "@tests/utils/expectUtils/expectAttach.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 { constructFeatureItem } from "@/internal/products/product-items/productItemUtils.js";
|
||||
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";
|
||||
import { replaceItems } from "../utils.js";
|
||||
import runUpdateEntsTest from "./expectUpdateEnts.js";
|
||||
|
||||
const testCase = "updateEnts3";
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [
|
||||
constructArrearItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 10000,
|
||||
}),
|
||||
],
|
||||
type: "pro",
|
||||
isAnnual: true,
|
||||
});
|
||||
|
||||
/**
|
||||
* updateEnts2:
|
||||
* Testing updating entitlements for annual plans
|
||||
* 1. Start with pro annual plan (usage-based)
|
||||
* 2. Update included usage amount
|
||||
* 3. Verify features and usage are updated correctly
|
||||
* 4. Verify invoice total is correct in next billing cycle
|
||||
*
|
||||
* Verifies that updating entitlements works correctly for annual plans
|
||||
* and that usage/billing is calculated properly
|
||||
*/
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing update ents (changing feature items)`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
beforeAll(async () => {
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
test("should attach pro annual product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
});
|
||||
|
||||
const newFeatureItem = constructFeatureItem({
|
||||
feature_id: TestFeature.Messages,
|
||||
included_usage: 500,
|
||||
});
|
||||
|
||||
const usage = 1200500;
|
||||
|
||||
const customItems = [...pro.items, newFeatureItem];
|
||||
|
||||
test("should attach custom pro product with new feature item", async () => {
|
||||
const customProduct = {
|
||||
...pro,
|
||||
items: customItems,
|
||||
};
|
||||
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
value: usage,
|
||||
feature_id: TestFeature.Words,
|
||||
});
|
||||
|
||||
await advanceTestClock({
|
||||
stripeCli: ctx.stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addWeeks(curUnix, 2).getTime(),
|
||||
waitForSeconds: 10,
|
||||
});
|
||||
|
||||
await runUpdateEntsTest({
|
||||
autumn,
|
||||
stripeCli: ctx.stripeCli,
|
||||
customerId,
|
||||
customProduct,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
customItems,
|
||||
usage: [
|
||||
{
|
||||
featureId: TestFeature.Words,
|
||||
value: usage,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach custom pro product with updated feature item", async () => {
|
||||
const customItems2 = replaceItems({
|
||||
items: customItems,
|
||||
featureId: TestFeature.Messages,
|
||||
newItem: constructFeatureItem({
|
||||
feature_id: TestFeature.Messages,
|
||||
included_usage: 1000,
|
||||
}),
|
||||
});
|
||||
|
||||
const customProduct = {
|
||||
...pro,
|
||||
items: customItems2,
|
||||
};
|
||||
|
||||
await runUpdateEntsTest({
|
||||
autumn,
|
||||
stripeCli: ctx.stripeCli,
|
||||
customerId,
|
||||
customProduct,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
customItems: customItems2,
|
||||
usage: [
|
||||
{
|
||||
featureId: TestFeature.Words,
|
||||
value: usage,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach custom pro product with removed feature item", async () => {
|
||||
const customItems2 = customItems.filter(
|
||||
(item) => item.feature_id !== TestFeature.Messages,
|
||||
);
|
||||
|
||||
const customProduct = {
|
||||
...pro,
|
||||
items: customItems2,
|
||||
};
|
||||
|
||||
await runUpdateEntsTest({
|
||||
autumn,
|
||||
stripeCli: ctx.stripeCli,
|
||||
customerId,
|
||||
customProduct,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
customItems: customItems2,
|
||||
usage: [
|
||||
{
|
||||
featureId: TestFeature.Words,
|
||||
value: usage,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,85 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { AttachBranch, BillingInterval, LegacyVersion } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "@tests/utils/expectUtils/expectAttach.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructPriceItem } from "@/internal/products/product-items/productItemUtils.js";
|
||||
import { nullish } from "@/utils/genUtils.js";
|
||||
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 testCase = "updateEnts4";
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [
|
||||
constructArrearItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 10000,
|
||||
}),
|
||||
],
|
||||
type: "pro",
|
||||
isAnnual: true,
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Checking price changes don't result in update ents func`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
beforeAll(async () => {
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
test("should attach pro annual product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
});
|
||||
|
||||
test("branch should not be same custom ents if base price updated", async () => {
|
||||
let customItems = pro.items.filter((item) => !nullish(item.feature_id));
|
||||
|
||||
customItems = [
|
||||
...customItems,
|
||||
constructPriceItem({
|
||||
price: 10,
|
||||
interval: BillingInterval.Year,
|
||||
}),
|
||||
];
|
||||
|
||||
const preview = await autumn.attachPreview({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
is_custom: true,
|
||||
items: customItems,
|
||||
});
|
||||
|
||||
expect(preview.branch).toBe(AttachBranch.SameCustom);
|
||||
});
|
||||
});
|
||||
@@ -1,134 +0,0 @@
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "@tests/utils/expectUtils/expectAttach.js";
|
||||
import { advanceTestClock } from "@tests/utils/stripeUtils.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 { 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";
|
||||
|
||||
// UNCOMMENT FROM HERE
|
||||
const pro = constructProduct({
|
||||
id: "pro",
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "pro",
|
||||
});
|
||||
const premium = constructProduct({
|
||||
id: "premium",
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "premium",
|
||||
});
|
||||
const growth = constructProduct({
|
||||
id: "growth",
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "growth",
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright("upgrade1: Testing usage upgrades")}`, () => {
|
||||
const customerId = "upgrade1";
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
|
||||
let stripeCli: Stripe;
|
||||
let testClockId: string;
|
||||
let curUnix: number;
|
||||
let db: DrizzleCli;
|
||||
let org: Organization;
|
||||
let env: AppEnv;
|
||||
|
||||
beforeAll(async () => {
|
||||
db = ctx.db;
|
||||
org = ctx.org;
|
||||
env = ctx.env;
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro, premium, growth],
|
||||
prefix: customerId,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
test("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach premium product", async () => {
|
||||
const wordsUsage = 100000;
|
||||
await timeout(4000);
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Words,
|
||||
value: wordsUsage,
|
||||
});
|
||||
|
||||
// curUnix = await advanceTestClock({
|
||||
// stripeCli,
|
||||
// testClockId,
|
||||
// advanceTo: addWeeks(new Date(), 2).getTime(),
|
||||
// waitForSeconds: 10,
|
||||
// });
|
||||
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: premium,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
|
||||
await timeout(2000);
|
||||
});
|
||||
|
||||
test("should attach growth product", async () => {
|
||||
const wordsUsage = 200000;
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Words,
|
||||
value: wordsUsage,
|
||||
});
|
||||
|
||||
curUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
numberOfWeeks: 1,
|
||||
waitForSeconds: 30,
|
||||
});
|
||||
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: growth,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,140 +0,0 @@
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "@tests/utils/expectUtils/expectAttach.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 type Stripe from "stripe";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
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";
|
||||
|
||||
// Shared products for attach tests
|
||||
const testCase = "upgrade2";
|
||||
export const pro = constructProduct({
|
||||
id: "pro",
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
export const proAnnual = constructProduct({
|
||||
id: "pro_annual",
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "pro",
|
||||
isAnnual: true,
|
||||
});
|
||||
|
||||
export const premiumAnnual = constructProduct({
|
||||
id: "premium_annual",
|
||||
items: [constructArrearItem({ featureId: TestFeature.Words })],
|
||||
type: "premium",
|
||||
isAnnual: true,
|
||||
});
|
||||
|
||||
/**
|
||||
* upgrade2:
|
||||
* 1. Start with pro monthly plan (usage-based)
|
||||
* 2. Upgrade to pro annual plan (usage-based)
|
||||
* 3. Upgrade to premium annual plan (usage-based)
|
||||
*
|
||||
* Verifies subscription items and anchors are correct after each upgrade
|
||||
*/
|
||||
|
||||
describe(`${chalk.yellowBright("upgrade2: Testing usage upgrades with monthly -> annual")}`, () => {
|
||||
const customerId = "upgrade2";
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let stripeCli: Stripe;
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
|
||||
let curUnix = new Date().getTime();
|
||||
|
||||
beforeAll(async () => {
|
||||
db = ctx.db;
|
||||
org = ctx.org;
|
||||
env = ctx.env;
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro, proAnnual, premiumAnnual],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
test("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach pro annual product", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Words,
|
||||
value: 100000,
|
||||
});
|
||||
|
||||
curUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addWeeks(curUnix, 2).getTime(),
|
||||
});
|
||||
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: proAnnual,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach premium annual product", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Words,
|
||||
value: 5000000,
|
||||
});
|
||||
|
||||
await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addWeeks(curUnix, 1).getTime(),
|
||||
waitForSeconds: 10,
|
||||
});
|
||||
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: premiumAnnual,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,187 +0,0 @@
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "@tests/utils/expectUtils/expectAttach.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 type Stripe from "stripe";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.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";
|
||||
|
||||
const testCase = "upgrade3";
|
||||
|
||||
const pro = constructProduct({
|
||||
items: [
|
||||
constructArrearProratedItem({
|
||||
featureId: TestFeature.Users,
|
||||
pricePerUnit: 12,
|
||||
}),
|
||||
],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const premium = constructProduct({
|
||||
items: [
|
||||
constructArrearProratedItem({
|
||||
featureId: TestFeature.Users,
|
||||
pricePerUnit: 20,
|
||||
}),
|
||||
],
|
||||
type: "premium",
|
||||
});
|
||||
|
||||
const proAnnual = constructProduct({
|
||||
items: [
|
||||
constructArrearProratedItem({
|
||||
featureId: TestFeature.Users,
|
||||
pricePerUnit: 12,
|
||||
}),
|
||||
],
|
||||
type: "pro",
|
||||
isAnnual: true,
|
||||
});
|
||||
|
||||
/**
|
||||
* upgrade3:
|
||||
* Testing upgrades for arrear prorated
|
||||
* 1. Start with pro monthly plan (usage-based)
|
||||
* 2. Upgrade to pro annual plan (usage-based)
|
||||
* 3. Upgrade to premium annual plan (usage-based)
|
||||
*
|
||||
* Verifies subscription items and anchors are correct after each upgrade
|
||||
* with arrear prorated billing
|
||||
*/
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing upgrades with arrear prorated`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
let curUnix = new Date().getTime();
|
||||
let numUsers = 0;
|
||||
|
||||
beforeAll(async () => {
|
||||
db = ctx.db;
|
||||
org = ctx.org;
|
||||
env = ctx.env;
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro, premium, proAnnual],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
test("should attach pro product (arrear prorated)", async () => {
|
||||
// 1. Create multiple entities
|
||||
const entities = await autumn.entities.create(customerId, [
|
||||
{
|
||||
id: "entity1",
|
||||
name: "entity1",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "entity2",
|
||||
name: "entity2",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
]);
|
||||
numUsers = 2;
|
||||
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
usage: [
|
||||
{
|
||||
featureId: TestFeature.Users,
|
||||
value: 2,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test("should create entity, then upgrade to premium product (arrear prorated)", async () => {
|
||||
curUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addWeeks(curUnix, 1).getTime(),
|
||||
});
|
||||
|
||||
// TODO: Check price paid for entity3
|
||||
await autumn.entities.create(customerId, [
|
||||
{
|
||||
id: "entity3",
|
||||
name: "entity3",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
]);
|
||||
numUsers += 1;
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: premium,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
usage: [
|
||||
{
|
||||
featureId: TestFeature.Users,
|
||||
value: numUsers,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test("should upgrade to pro-annual product (arrear prorated)", async () => {
|
||||
curUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addWeeks(curUnix, 1).getTime(),
|
||||
});
|
||||
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: proAnnual,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
usage: [
|
||||
{
|
||||
featureId: TestFeature.Users,
|
||||
value: numUsers,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,168 +0,0 @@
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "@tests/utils/expectUtils/expectAttach.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 type Stripe from "stripe";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructPrepaidItem } 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 testCase = "upgrade4";
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [
|
||||
constructPrepaidItem({
|
||||
featureId: TestFeature.Users,
|
||||
price: 12,
|
||||
billingUnits: 1,
|
||||
}),
|
||||
],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
export const premium = constructProduct({
|
||||
items: [
|
||||
constructPrepaidItem({
|
||||
featureId: TestFeature.Users,
|
||||
price: 20,
|
||||
billingUnits: 1,
|
||||
}),
|
||||
],
|
||||
type: "premium",
|
||||
});
|
||||
|
||||
export const proAnnual = constructProduct({
|
||||
items: [
|
||||
constructPrepaidItem({
|
||||
featureId: TestFeature.Users,
|
||||
price: 12,
|
||||
billingUnits: 1,
|
||||
}),
|
||||
],
|
||||
type: "pro",
|
||||
isAnnual: true,
|
||||
});
|
||||
|
||||
/**
|
||||
* upgrade3:
|
||||
* Testing upgrades for arrear prorated
|
||||
* 1. Start with pro monthly plan (usage-based)
|
||||
* 2. Upgrade to pro annual plan (usage-based)
|
||||
* 3. Upgrade to premium annual plan (usage-based)
|
||||
*
|
||||
* Verifies subscription items and anchors are correct after each upgrade
|
||||
* with arrear prorated billing
|
||||
*/
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing upgrades with prepaid continuous use`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
let curUnix = new Date().getTime();
|
||||
const numUsers = 0;
|
||||
|
||||
beforeAll(async () => {
|
||||
db = ctx.db;
|
||||
org = ctx.org;
|
||||
env = ctx.env;
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro, premium, proAnnual],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
const proOpts = [
|
||||
{
|
||||
feature_id: TestFeature.Users,
|
||||
quantity: 4,
|
||||
},
|
||||
];
|
||||
|
||||
test("should attach pro product (arrear prorated)", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
options: proOpts,
|
||||
});
|
||||
});
|
||||
|
||||
const premiumOpts = [
|
||||
{
|
||||
feature_id: TestFeature.Users,
|
||||
quantity: 6,
|
||||
},
|
||||
];
|
||||
|
||||
test("should create entity, then upgrade to premium product (arrear prorated)", async () => {
|
||||
curUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addWeeks(curUnix, 1).getTime(),
|
||||
});
|
||||
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: premium,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
options: premiumOpts,
|
||||
});
|
||||
});
|
||||
|
||||
const proAnnualOpts = [
|
||||
{
|
||||
feature_id: TestFeature.Users,
|
||||
quantity: 3,
|
||||
},
|
||||
];
|
||||
|
||||
test("should upgrade to pro-annual product (arrear prorated)", async () => {
|
||||
curUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addWeeks(curUnix, 1).getTime(),
|
||||
});
|
||||
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: proAnnual,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
options: proAnnualOpts,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,122 +0,0 @@
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "@tests/utils/expectUtils/expectAttach.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 type Stripe from "stripe";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructPrepaidItem } 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 testCase = "upgrade5";
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [
|
||||
constructPrepaidItem({
|
||||
featureId: TestFeature.Messages,
|
||||
price: 12,
|
||||
billingUnits: 100,
|
||||
}),
|
||||
],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
export const premium = constructProduct({
|
||||
items: [
|
||||
constructPrepaidItem({
|
||||
featureId: TestFeature.Messages,
|
||||
price: 8,
|
||||
billingUnits: 100,
|
||||
}),
|
||||
],
|
||||
type: "premium",
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing upgrades with prepaid single use`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
let curUnix = new Date().getTime();
|
||||
const numUsers = 0;
|
||||
|
||||
beforeAll(async () => {
|
||||
db = ctx.db;
|
||||
org = ctx.org;
|
||||
env = ctx.env;
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro, premium],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
const proOpts = [
|
||||
{
|
||||
feature_id: TestFeature.Messages,
|
||||
quantity: 300,
|
||||
},
|
||||
];
|
||||
|
||||
test("should attach pro product (prepaid single use)", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
options: proOpts,
|
||||
});
|
||||
});
|
||||
|
||||
const premiumOpts = [
|
||||
{
|
||||
feature_id: TestFeature.Messages,
|
||||
quantity: 600,
|
||||
},
|
||||
];
|
||||
|
||||
test("should upgrade to premium product (prepaid single use)", async () => {
|
||||
curUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addWeeks(curUnix, 1).getTime(),
|
||||
waitForSeconds: 20,
|
||||
});
|
||||
|
||||
return;
|
||||
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: premium,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
options: premiumOpts,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,80 +0,0 @@
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
|
||||
import { attachAndExpectCorrect } from "@tests/utils/expectUtils/expectAttach.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 { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
|
||||
const testCase = "upgrade7";
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
export const premium = constructProduct({
|
||||
items: [],
|
||||
type: "premium",
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing upgrade via cancel + attach`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
beforeAll(async () => {
|
||||
db = ctx.db;
|
||||
org = ctx.org;
|
||||
env = ctx.env;
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro, premium],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
test("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
});
|
||||
|
||||
test("should cancel than attach premium product", async () => {
|
||||
await autumn.cancel({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
cancel_immediately: true,
|
||||
});
|
||||
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: premium.id,
|
||||
force_checkout: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,128 +0,0 @@
|
||||
import {
|
||||
BillingInterval,
|
||||
FreeTrialDuration,
|
||||
ProductItemInterval,
|
||||
} from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import { constructPriceItem } from "@/internal/products/product-items/productItemUtils.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { createSharedProducts } from "@/utils/scriptUtils/testUtils/createSharedProduct.js";
|
||||
|
||||
/**
|
||||
* Shared products for upgradeOld test group
|
||||
* Matches global products.pro, products.proWithTrial, products.premium, products.premiumWithTrial
|
||||
*/
|
||||
|
||||
export const sharedProProduct = constructProduct({
|
||||
id: "shared-upgradeold-pro",
|
||||
type: "pro",
|
||||
excludeBase: true,
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Dashboard,
|
||||
isBoolean: true,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 10,
|
||||
interval: ProductItemInterval.Month,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Admin,
|
||||
unlimited: true,
|
||||
}),
|
||||
constructPriceItem({
|
||||
price: 2000,
|
||||
interval: BillingInterval.Month,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
export const sharedProWithTrialProduct = constructProduct({
|
||||
id: "shared-upgradeold-pro-trial",
|
||||
type: "pro",
|
||||
excludeBase: true,
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Dashboard,
|
||||
isBoolean: true,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 10,
|
||||
interval: ProductItemInterval.Month,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Admin,
|
||||
unlimited: true,
|
||||
}),
|
||||
constructPriceItem({
|
||||
price: 2000,
|
||||
interval: BillingInterval.Month,
|
||||
}),
|
||||
],
|
||||
freeTrial: {
|
||||
length: 7,
|
||||
duration: FreeTrialDuration.Day,
|
||||
unique_fingerprint: true,
|
||||
card_required: true,
|
||||
},
|
||||
});
|
||||
|
||||
export const sharedPremiumProduct = constructProduct({
|
||||
id: "shared-upgradeold-premium",
|
||||
type: "premium",
|
||||
excludeBase: true,
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 100,
|
||||
interval: ProductItemInterval.Month,
|
||||
}),
|
||||
constructPriceItem({
|
||||
price: 5000,
|
||||
interval: BillingInterval.Month,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
export const sharedPremiumWithTrialProduct = constructProduct({
|
||||
id: "shared-upgradeold-premium-trial",
|
||||
type: "premium",
|
||||
excludeBase: true,
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 100,
|
||||
interval: ProductItemInterval.Month,
|
||||
}),
|
||||
constructPriceItem({
|
||||
price: 5000,
|
||||
interval: BillingInterval.Month,
|
||||
}),
|
||||
],
|
||||
freeTrial: {
|
||||
length: 7,
|
||||
duration: FreeTrialDuration.Day,
|
||||
unique_fingerprint: true,
|
||||
card_required: true,
|
||||
},
|
||||
});
|
||||
|
||||
export const initUpgradeOldSharedProducts = async () => {
|
||||
await createSharedProducts({
|
||||
ctx,
|
||||
products: [
|
||||
sharedProProduct,
|
||||
sharedProWithTrialProduct,
|
||||
sharedPremiumProduct,
|
||||
sharedPremiumWithTrialProduct,
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
// Auto-init removed to prevent conflicts when running tests in parallel
|
||||
// Each test file now initializes its own products with unique prefixes
|
||||
// await initUpgradeOldSharedProducts();
|
||||
@@ -1,101 +0,0 @@
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import {
|
||||
BillingInterval,
|
||||
FreeTrialDuration,
|
||||
ProductItemInterval,
|
||||
} from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructPriceItem } from "@/internal/products/product-items/productItemUtils.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";
|
||||
|
||||
const testCase = "upgradeOld2";
|
||||
|
||||
const proProduct = constructProduct({
|
||||
type: "pro",
|
||||
excludeBase: true,
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Dashboard,
|
||||
isBoolean: true,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 10,
|
||||
interval: ProductItemInterval.Month,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Admin,
|
||||
unlimited: true,
|
||||
}),
|
||||
constructPriceItem({
|
||||
price: 2000,
|
||||
interval: BillingInterval.Month,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const premiumWithTrialProduct = constructProduct({
|
||||
type: "premium",
|
||||
excludeBase: true,
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 100,
|
||||
interval: ProductItemInterval.Month,
|
||||
}),
|
||||
constructPriceItem({
|
||||
price: 5000,
|
||||
interval: BillingInterval.Month,
|
||||
}),
|
||||
],
|
||||
freeTrial: {
|
||||
length: 7,
|
||||
duration: FreeTrialDuration.Day,
|
||||
unique_fingerprint: true,
|
||||
card_required: true,
|
||||
},
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(
|
||||
"upgradeOld2: Testing upgrade (paid to trial)",
|
||||
)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt();
|
||||
|
||||
beforeAll(async () => {
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [proProduct, premiumWithTrialProduct],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach pro", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: proProduct.id,
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach premium with trial and have trial", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: premiumWithTrialProduct.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,139 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import {
|
||||
BillingInterval,
|
||||
CusProductStatus,
|
||||
FreeTrialDuration,
|
||||
ProductItemInterval,
|
||||
} from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { expectCustomerV0Correct } from "@tests/utils/expectUtils/expectCustomerV0Correct.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { addDays } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructPriceItem } from "@/internal/products/product-items/productItemUtils.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
|
||||
const testCase = "upgradeOld3";
|
||||
|
||||
const proWithTrialProduct = constructProduct({
|
||||
type: "pro",
|
||||
excludeBase: true,
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Dashboard,
|
||||
isBoolean: true,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 10,
|
||||
interval: ProductItemInterval.Month,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Admin,
|
||||
unlimited: true,
|
||||
}),
|
||||
constructPriceItem({
|
||||
price: 2000,
|
||||
interval: BillingInterval.Month,
|
||||
}),
|
||||
],
|
||||
freeTrial: {
|
||||
length: 7,
|
||||
duration: FreeTrialDuration.Day,
|
||||
unique_fingerprint: true,
|
||||
card_required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const premiumWithTrialProduct = constructProduct({
|
||||
type: "premium",
|
||||
excludeBase: true,
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 100,
|
||||
interval: ProductItemInterval.Month,
|
||||
}),
|
||||
constructPriceItem({
|
||||
price: 5000,
|
||||
interval: BillingInterval.Month,
|
||||
}),
|
||||
],
|
||||
freeTrial: {
|
||||
length: 7,
|
||||
duration: FreeTrialDuration.Day,
|
||||
unique_fingerprint: true,
|
||||
card_required: true,
|
||||
},
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright("upgradeOld3: Testing upgrade (trial to trial)")}`, () => {
|
||||
const customerId = testCase;
|
||||
let testClockId: string;
|
||||
const autumn: AutumnInt = new AutumnInt();
|
||||
let stripeCli: Stripe;
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [proWithTrialProduct, premiumWithTrialProduct],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId_ } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = testClockId_;
|
||||
});
|
||||
|
||||
test("should attach pro with trial", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: proWithTrialProduct.id,
|
||||
});
|
||||
|
||||
console.log(` ${chalk.greenBright("Attached pro with trial")}`);
|
||||
});
|
||||
|
||||
test("should attach premium with trial", async () => {
|
||||
const advanceTo = addDays(new Date(), 3).getTime();
|
||||
|
||||
await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo,
|
||||
waitForSeconds: 10,
|
||||
});
|
||||
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: premiumWithTrialProduct.id,
|
||||
});
|
||||
});
|
||||
|
||||
test("should check product and ents", async () => {
|
||||
const res = await autumn.customers.get(customerId);
|
||||
expectCustomerV0Correct({
|
||||
sent: premiumWithTrialProduct,
|
||||
cusRes: res,
|
||||
status: CusProductStatus.Trialing,
|
||||
});
|
||||
|
||||
const invoices = res.invoices;
|
||||
|
||||
expect(invoices![0].total).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -1,163 +0,0 @@
|
||||
// TESTING UPGRADES
|
||||
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import {
|
||||
BillingInterval,
|
||||
type Customer,
|
||||
ProductItemInterval,
|
||||
} from "@autumn/shared";
|
||||
import { AutumnCli } from "@tests/cli/AutumnCli.js";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { expectCustomerV0Correct } from "@tests/utils/expectUtils/expectCustomerV0Correct.js";
|
||||
import { expectAutumnError } from "@tests/utils/expectUtils/expectErrUtils.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import {
|
||||
attachFailedPaymentMethod,
|
||||
attachPmToCus,
|
||||
} from "@/external/stripe/stripeCusUtils.js";
|
||||
import { constructPriceItem } from "@/internal/products/product-items/productItemUtils.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";
|
||||
|
||||
const testCase = "upgradeOld4";
|
||||
|
||||
const proProduct = constructProduct({
|
||||
type: "pro",
|
||||
excludeBase: true,
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Dashboard,
|
||||
isBoolean: true,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 10,
|
||||
interval: ProductItemInterval.Month,
|
||||
}),
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Admin,
|
||||
unlimited: true,
|
||||
}),
|
||||
constructPriceItem({
|
||||
price: 2000,
|
||||
interval: BillingInterval.Month,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const premiumProduct = constructProduct({
|
||||
type: "premium",
|
||||
excludeBase: true,
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 100,
|
||||
interval: ProductItemInterval.Month,
|
||||
}),
|
||||
constructPriceItem({
|
||||
price: 5000,
|
||||
interval: BillingInterval.Month,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright("upgradeOld4: Testing upgrade from pro -> premium")}`, () => {
|
||||
let customer: Customer;
|
||||
const customerId = testCase;
|
||||
|
||||
let stripeCli: Stripe;
|
||||
const autumn: AutumnInt = new AutumnInt();
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [proProduct, premiumProduct],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const { customer: customer_ } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
customer = customer_;
|
||||
});
|
||||
|
||||
test("should attach pro (trial)", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: proProduct.id,
|
||||
});
|
||||
|
||||
const res = await autumn.customers.get(customerId);
|
||||
expectCustomerV0Correct({
|
||||
sent: proProduct,
|
||||
cusRes: res,
|
||||
});
|
||||
});
|
||||
|
||||
// 1. Try force checkout...
|
||||
test("should attach premium and not be able to force checkout", async () => {
|
||||
expectAutumnError({
|
||||
func: async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: premiumProduct.id,
|
||||
force_checkout: true,
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach premium and not be able to upgrade (without payment method)", async () => {
|
||||
await attachFailedPaymentMethod({
|
||||
stripeCli: stripeCli,
|
||||
customer: customer,
|
||||
});
|
||||
|
||||
await expectAutumnError({
|
||||
func: async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: premiumProduct.id,
|
||||
force_checkout: true,
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
return;
|
||||
|
||||
// Attach payment method
|
||||
test("should attach successful payment method", async () => {
|
||||
await attachPmToCus({
|
||||
db: ctx.db,
|
||||
customer: customer,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
});
|
||||
|
||||
test("should attach premium and have correct product and entitlements", async () => {
|
||||
await AutumnCli.attach({
|
||||
customerId: customerId,
|
||||
productId: premiumProduct.id,
|
||||
});
|
||||
|
||||
const res = await AutumnCli.getCustomer(customerId);
|
||||
expectCustomerV0Correct({
|
||||
sent: premiumProduct,
|
||||
cusRes: res,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,185 +0,0 @@
|
||||
import type { BillingInterval, ProductItem, ProductV2 } from "@autumn/shared";
|
||||
import { nullish } from "@/utils/genUtils.js";
|
||||
|
||||
// export const runAttachTest = async ({
|
||||
// autumn,
|
||||
// customerId,
|
||||
// entityId,
|
||||
// product,
|
||||
// options,
|
||||
// stripeCli,
|
||||
// db,
|
||||
// org,
|
||||
// env,
|
||||
// usage,
|
||||
// waitForInvoice = 0,
|
||||
// isCanceled = false,
|
||||
// skipFeatureCheck = false,
|
||||
// singleInvoice = false,
|
||||
// skipSubCheck = false,
|
||||
// entities,
|
||||
// }: {
|
||||
// autumn: AutumnInt;
|
||||
// customerId: string;
|
||||
// entityId?: string;
|
||||
// product: ProductV2;
|
||||
// options?: FeatureOptions[];
|
||||
// stripeCli: Stripe;
|
||||
// db: DrizzleCli;
|
||||
// org: Organization;
|
||||
// env: AppEnv;
|
||||
// usage?: {
|
||||
// featureId: string;
|
||||
// value: number;
|
||||
// }[];
|
||||
// waitForInvoice?: number;
|
||||
// isCanceled?: boolean;
|
||||
// skipFeatureCheck?: boolean;
|
||||
// singleInvoice?: boolean;
|
||||
// skipSubCheck?: boolean;
|
||||
// entities?: CreateEntity[];
|
||||
// }) => {
|
||||
// const preview = await autumn.attachPreview({
|
||||
// customer_id: customerId,
|
||||
// product_id: product.id,
|
||||
// entity_id: entityId,
|
||||
// });
|
||||
|
||||
// const total = getAttachTotal({
|
||||
// preview,
|
||||
// options,
|
||||
// });
|
||||
|
||||
// await autumn.attach({
|
||||
// customer_id: customerId,
|
||||
// product_id: product.id,
|
||||
// entity_id: entityId,
|
||||
// options: toSnakeCase(options),
|
||||
// });
|
||||
|
||||
// if (waitForInvoice) {
|
||||
// await timeout(waitForInvoice);
|
||||
// }
|
||||
|
||||
// const customer = await autumn.customers.get(customerId);
|
||||
|
||||
// const productCount = customer.products.reduce((acc: number, p: any) => {
|
||||
// if (product.group == p.group) {
|
||||
// return acc + 1;
|
||||
// } else return acc;
|
||||
// }, 0);
|
||||
|
||||
// expect(
|
||||
// productCount,
|
||||
// `customer should only have 1 product (from this group: ${product.group})`
|
||||
// ).to.equal(1);
|
||||
|
||||
// expectProductAttached({
|
||||
// customer,
|
||||
// product,
|
||||
// entityId,
|
||||
// });
|
||||
|
||||
// let intervals = Array.from(
|
||||
// new Set(product.items.map((item) => item.interval))
|
||||
// ).filter(notNullish);
|
||||
// const multiInterval = intervals.length > 1;
|
||||
|
||||
// const freeProduct = isFreeProductV2({ product });
|
||||
// if (!freeProduct) {
|
||||
// let multiInvoice = !singleInvoice && multiInterval;
|
||||
// expectInvoicesCorrect({
|
||||
// customer,
|
||||
// first: multiInvoice ? undefined : { productId: product.id, total },
|
||||
// second: multiInvoice ? { productId: product.id, total } : undefined,
|
||||
// });
|
||||
// }
|
||||
|
||||
// if (!skipFeatureCheck) {
|
||||
// expectFeaturesCorrect({
|
||||
// customer,
|
||||
// product,
|
||||
// usage,
|
||||
// options,
|
||||
// entities,
|
||||
// });
|
||||
// }
|
||||
|
||||
// const branch = preview.branch;
|
||||
// if (branch == AttachBranch.OneOff || freeProduct) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if (skipSubCheck) return;
|
||||
|
||||
// await expectSubItemsCorrect({
|
||||
// stripeCli,
|
||||
// customerId,
|
||||
// product,
|
||||
// db,
|
||||
// org,
|
||||
// env,
|
||||
// isCanceled,
|
||||
// });
|
||||
|
||||
// const stripeSubs = await stripeCli.subscriptions.list({
|
||||
// customer: customer.stripe_id!,
|
||||
// });
|
||||
// if (multiInterval) {
|
||||
// expect(stripeSubs.data.length).to.equal(2, "should have 2 subscriptions");
|
||||
// } else {
|
||||
// expect(stripeSubs.data.length).to.equal(
|
||||
// 1,
|
||||
// "should only have 1 subscription"
|
||||
// );
|
||||
// }
|
||||
// };
|
||||
|
||||
export const addPrefixToProducts = ({
|
||||
products,
|
||||
prefix,
|
||||
}: {
|
||||
products: ProductV2[];
|
||||
prefix: string;
|
||||
}) => {
|
||||
for (const product of products) {
|
||||
product.id = `${prefix}_${product.id}`;
|
||||
product.name = `${product.name} ${prefix}`;
|
||||
product.group = prefix;
|
||||
}
|
||||
|
||||
return products;
|
||||
};
|
||||
|
||||
export const replaceItems = ({
|
||||
featureId,
|
||||
interval,
|
||||
newItem,
|
||||
items,
|
||||
}: {
|
||||
featureId?: string;
|
||||
interval?: BillingInterval;
|
||||
newItem: ProductItem;
|
||||
items: ProductItem[];
|
||||
}) => {
|
||||
const newItems = structuredClone(items);
|
||||
|
||||
let index;
|
||||
if (featureId) {
|
||||
index = newItems.findIndex((item) => item.feature_id === featureId);
|
||||
}
|
||||
|
||||
if (interval) {
|
||||
index = newItems.findIndex(
|
||||
(item) => item.interval === (interval as any) && nullish(item.feature_id),
|
||||
);
|
||||
}
|
||||
|
||||
if (index === -1) {
|
||||
throw new Error("Item not found");
|
||||
}
|
||||
|
||||
newItems[index!] = newItem;
|
||||
|
||||
return newItems;
|
||||
};
|
||||
@@ -1,211 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import {
|
||||
ApiVersion,
|
||||
AppEnv,
|
||||
CusProductStatus,
|
||||
customers,
|
||||
ProcessorType,
|
||||
} from "@autumn/shared";
|
||||
import { replaceItems } from "@tests/attach/utils.js";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { RCMappingService } from "@/external/revenueCat/misc/RCMappingService.js";
|
||||
import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
|
||||
import { OrgService } from "@/internal/orgs/OrgService.js";
|
||||
import { encryptData } from "@/utils/encryptUtils.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";
|
||||
import { expectFeaturesCorrect } from "../../utils/expectUtils/expectFeaturesCorrect.js";
|
||||
import { timeout } from "../../utils/genUtils.js";
|
||||
import {
|
||||
expectWebhookSuccess,
|
||||
RevenueCatWebhookClient,
|
||||
} from "./utils/revenue-cat-webhook-client.js";
|
||||
|
||||
const testCase = "rcMigration1";
|
||||
const RC_WEBHOOK_SECRET = "test_rc_webhook_secret_migration";
|
||||
|
||||
// RevenueCat product ID
|
||||
const RC_PRO_MONTHLY_ID = "com.app.migration_pro_monthly";
|
||||
|
||||
// Autumn product definitions
|
||||
const messagesFeature = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 1000,
|
||||
});
|
||||
|
||||
const proMonthlyV1 = constructProduct({
|
||||
id: `${testCase}-pro-monthly`,
|
||||
type: "pro",
|
||||
items: [messagesFeature],
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const proMonthlyV2 = {
|
||||
...proMonthlyV1,
|
||||
version: 2,
|
||||
items: replaceItems({
|
||||
items: proMonthlyV1.items,
|
||||
featureId: TestFeature.Messages,
|
||||
newItem: constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 2000,
|
||||
}),
|
||||
}),
|
||||
};
|
||||
|
||||
describe(
|
||||
chalk.yellowBright("rcMigration1: RevenueCat customer migration"),
|
||||
() => {
|
||||
const customerId = `${testCase}-customer`;
|
||||
const autumnV1 = new AutumnInt({ version: ApiVersion.V1_2 });
|
||||
let internalCustomerId: string | null = null;
|
||||
let rcClient: RevenueCatWebhookClient;
|
||||
|
||||
beforeAll(async () => {
|
||||
// 1. Configure org with RevenueCat processor config
|
||||
if (
|
||||
ctx.org.processor_configs?.revenuecat?.sandbox_webhook_secret !==
|
||||
RC_WEBHOOK_SECRET
|
||||
) {
|
||||
await OrgService.update({
|
||||
db: ctx.db,
|
||||
orgId: ctx.org.id,
|
||||
updates: {
|
||||
processor_configs: {
|
||||
...ctx.org.processor_configs,
|
||||
revenuecat: {
|
||||
api_key: encryptData("mock_rc_api_key_live"),
|
||||
sandbox_api_key: encryptData("mock_rc_api_key_sandbox"),
|
||||
project_id: "mock_project_live",
|
||||
sandbox_project_id: "mock_project_sandbox",
|
||||
webhook_secret: RC_WEBHOOK_SECRET,
|
||||
sandbox_webhook_secret: RC_WEBHOOK_SECRET,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Create product and mappings
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [proMonthlyV1],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
await RCMappingService.upsert({
|
||||
db: ctx.db,
|
||||
data: {
|
||||
org_id: ctx.org.id,
|
||||
env: AppEnv.Sandbox,
|
||||
autumn_product_id: proMonthlyV1.id,
|
||||
revenuecat_product_ids: [RC_PRO_MONTHLY_ID],
|
||||
},
|
||||
});
|
||||
|
||||
// 3. Create customer
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
withTestClock: false,
|
||||
});
|
||||
|
||||
const dbCustomer = await ctx.db.query.customers.findFirst({
|
||||
where: eq(customers.id, customerId),
|
||||
});
|
||||
expect(dbCustomer).toBeDefined();
|
||||
internalCustomerId = dbCustomer!.internal_id;
|
||||
|
||||
// Initialize RevenueCat webhook client
|
||||
rcClient = new RevenueCatWebhookClient({
|
||||
orgId: ctx.org.id,
|
||||
env: ctx.env,
|
||||
webhookSecret: RC_WEBHOOK_SECRET,
|
||||
});
|
||||
});
|
||||
|
||||
test("should create customer with pro monthly v1 product via initial purchase", async () => {
|
||||
const { response, data } = await rcClient.initialPurchase({
|
||||
productId: RC_PRO_MONTHLY_ID,
|
||||
appUserId: customerId,
|
||||
originalTransactionId: "migration_tx_12345",
|
||||
});
|
||||
|
||||
expectWebhookSuccess({ response, data });
|
||||
|
||||
const customer = await autumnV1.customers.get(customerId);
|
||||
expect(customer).toBeDefined();
|
||||
expect(customer.products).toHaveLength(1);
|
||||
expect(customer.products[0].id).toBe(proMonthlyV1.id);
|
||||
|
||||
// Verify cus_product has RevenueCat processor
|
||||
const cusProducts = await CusProductService.list({
|
||||
db: ctx.db,
|
||||
internalCustomerId: internalCustomerId!,
|
||||
inStatuses: [CusProductStatus.Active],
|
||||
});
|
||||
expect(cusProducts).toHaveLength(1);
|
||||
expect(cusProducts[0].processor?.type).toBe(ProcessorType.RevenueCat);
|
||||
});
|
||||
|
||||
test("should create v2 of the product with updated features", async () => {
|
||||
// Create v2 with increased usage
|
||||
const newItems = replaceItems({
|
||||
items: proMonthlyV1.items,
|
||||
featureId: TestFeature.Messages,
|
||||
newItem: constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 2000, // Increased from 1000
|
||||
}),
|
||||
});
|
||||
|
||||
await autumnV1.products.update(proMonthlyV1.id, {
|
||||
items: newItems,
|
||||
});
|
||||
});
|
||||
|
||||
test("should migrate customer from v1 to v2", async () => {
|
||||
await autumnV1.track({
|
||||
customer_id: customerId,
|
||||
value: 500,
|
||||
feature_id: TestFeature.Messages,
|
||||
});
|
||||
|
||||
await timeout(2000);
|
||||
|
||||
// Run migration via API
|
||||
await autumnV1.migrate({
|
||||
from_product_id: proMonthlyV1.id,
|
||||
to_product_id: proMonthlyV1.id,
|
||||
from_version: 1,
|
||||
to_version: 2,
|
||||
});
|
||||
|
||||
await timeout(5000);
|
||||
|
||||
const customer = await autumnV1.customers.get(customerId);
|
||||
expect(customer).toBeDefined();
|
||||
expect(customer.products).toHaveLength(1);
|
||||
expect(customer.products[0].id).toBe(proMonthlyV1.id);
|
||||
expect(customer.products[0].version).toBe(2);
|
||||
|
||||
expectFeaturesCorrect({
|
||||
customer,
|
||||
product: proMonthlyV2,
|
||||
usage: [
|
||||
{
|
||||
featureId: TestFeature.Messages,
|
||||
value: 500,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -1,395 +0,0 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import {
|
||||
ApiVersion,
|
||||
AppEnv,
|
||||
CusProductStatus,
|
||||
customers,
|
||||
} from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { RCMappingService } from "@/external/revenueCat/misc/RCMappingService.js";
|
||||
import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
|
||||
import { OrgService } from "@/internal/orgs/OrgService.js";
|
||||
import { encryptData } from "@/utils/encryptUtils.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";
|
||||
import {
|
||||
expectWebhookSuccess,
|
||||
RevenueCatWebhookClient,
|
||||
} from "./utils/revenue-cat-webhook-client.js";
|
||||
|
||||
const testCase = "rc1";
|
||||
const RC_WEBHOOK_SECRET = "test_rc_webhook_secret_12345";
|
||||
|
||||
// RevenueCat product IDs (what RC sends in webhooks)
|
||||
const RC_PRO_MONTHLY_ID = "com.app.pro_monthly";
|
||||
const RC_PRO_YEARLY_ID = "com.app.pro_yearly";
|
||||
const RC_ADD_ON_ID = "com.app.add_on_pack";
|
||||
|
||||
// Autumn product definitions
|
||||
const messagesFeature = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 1000,
|
||||
});
|
||||
|
||||
const proMonthly = constructProduct({
|
||||
id: `${testCase}-pro-monthly`,
|
||||
type: "pro",
|
||||
items: [messagesFeature],
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const proYearly = constructProduct({
|
||||
id: `${testCase}-pro-yearly`,
|
||||
type: "pro",
|
||||
isAnnual: true,
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 1000,
|
||||
}),
|
||||
],
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const addOnPack = constructProduct({
|
||||
id: `${testCase}-add-on`,
|
||||
type: "one_off",
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 100,
|
||||
}),
|
||||
],
|
||||
isAddOn: true,
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
describe(chalk.yellowBright("rc1: RevenueCat webhook integration"), () => {
|
||||
const customerId = `${testCase}-customer`;
|
||||
const autumnV1 = new AutumnInt({ version: ApiVersion.V1_2 });
|
||||
let proMonthlyCusProductId: string | null = null;
|
||||
let internalCustomerId: string | null = null;
|
||||
let rcClient: RevenueCatWebhookClient;
|
||||
|
||||
const fetchLatestActiveCusProductId = async () => {
|
||||
if (!internalCustomerId) {
|
||||
throw new Error("internalCustomerId not set");
|
||||
}
|
||||
|
||||
const cusProducts = await CusProductService.list({
|
||||
db: ctx.db,
|
||||
internalCustomerId,
|
||||
inStatuses: [
|
||||
CusProductStatus.Active,
|
||||
CusProductStatus.PastDue,
|
||||
CusProductStatus.Scheduled,
|
||||
],
|
||||
});
|
||||
|
||||
const activeSorted = cusProducts
|
||||
.filter(
|
||||
(cp) =>
|
||||
cp.status === CusProductStatus.Active ||
|
||||
cp.status === CusProductStatus.PastDue,
|
||||
)
|
||||
.sort((a, b) => (a.created_at ?? 0) - (b.created_at ?? 0));
|
||||
|
||||
expect(
|
||||
activeSorted.length > 0,
|
||||
"Expected at least one active cus_product for customer",
|
||||
).toBe(true);
|
||||
|
||||
// Return the latest active cus_product id for the customer
|
||||
return activeSorted[activeSorted.length - 1]!.id;
|
||||
};
|
||||
|
||||
const fetchLatestCusProductIdAnyStatus = async () => {
|
||||
if (!internalCustomerId) {
|
||||
throw new Error("internalCustomerId not set");
|
||||
}
|
||||
|
||||
const cusProducts = await CusProductService.list({
|
||||
db: ctx.db,
|
||||
internalCustomerId,
|
||||
inStatuses: undefined,
|
||||
});
|
||||
|
||||
if (cusProducts.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const sorted = [...cusProducts].sort(
|
||||
(a, b) => (a.created_at ?? 0) - (b.created_at ?? 0),
|
||||
);
|
||||
|
||||
return sorted[sorted.length - 1]!.id;
|
||||
};
|
||||
|
||||
const getBaselineCusProductId = () => {
|
||||
if (!proMonthlyCusProductId) {
|
||||
throw new Error(
|
||||
"Baseline CusProduct ID was not set from initial purchase",
|
||||
);
|
||||
}
|
||||
|
||||
return proMonthlyCusProductId;
|
||||
};
|
||||
const updateBaselineCusProductId = (cusProductId: string) => {
|
||||
proMonthlyCusProductId = cusProductId;
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
// 1. Configure org with RevenueCat processor config
|
||||
if (
|
||||
ctx.org.processor_configs?.revenuecat?.sandbox_webhook_secret !==
|
||||
RC_WEBHOOK_SECRET
|
||||
) {
|
||||
await OrgService.update({
|
||||
db: ctx.db,
|
||||
orgId: ctx.org.id,
|
||||
updates: {
|
||||
processor_configs: {
|
||||
...ctx.org.processor_configs,
|
||||
revenuecat: {
|
||||
api_key: encryptData("mock_rc_api_key_live"),
|
||||
sandbox_api_key: encryptData("mock_rc_api_key_sandbox"),
|
||||
project_id: "mock_project_live",
|
||||
sandbox_project_id: "mock_project_sandbox",
|
||||
webhook_secret: RC_WEBHOOK_SECRET,
|
||||
sandbox_webhook_secret: RC_WEBHOOK_SECRET,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize RevenueCat webhook client
|
||||
rcClient = new RevenueCatWebhookClient({
|
||||
orgId: ctx.org.id,
|
||||
env: ctx.env,
|
||||
webhookSecret: RC_WEBHOOK_SECRET,
|
||||
});
|
||||
|
||||
// 2-4. Create products, mappings, and customer concurrently
|
||||
await Promise.all([
|
||||
initProductsV0({
|
||||
ctx,
|
||||
products: [proMonthly, proYearly, addOnPack],
|
||||
prefix: testCase,
|
||||
}),
|
||||
RCMappingService.upsert({
|
||||
db: ctx.db,
|
||||
data: {
|
||||
org_id: ctx.org.id,
|
||||
env: AppEnv.Sandbox,
|
||||
autumn_product_id: proMonthly.id,
|
||||
revenuecat_product_ids: [RC_PRO_MONTHLY_ID],
|
||||
},
|
||||
}),
|
||||
RCMappingService.upsert({
|
||||
db: ctx.db,
|
||||
data: {
|
||||
org_id: ctx.org.id,
|
||||
env: AppEnv.Sandbox,
|
||||
autumn_product_id: addOnPack.id,
|
||||
revenuecat_product_ids: [RC_ADD_ON_ID],
|
||||
},
|
||||
}),
|
||||
RCMappingService.upsert({
|
||||
db: ctx.db,
|
||||
data: {
|
||||
org_id: ctx.org.id,
|
||||
env: AppEnv.Sandbox,
|
||||
autumn_product_id: proYearly.id,
|
||||
revenuecat_product_ids: [RC_PRO_YEARLY_ID],
|
||||
},
|
||||
}),
|
||||
initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
withTestClock: false,
|
||||
}),
|
||||
]);
|
||||
|
||||
const dbCustomer = await ctx.db.query.customers.findFirst({
|
||||
where: eq(customers.id, customerId),
|
||||
});
|
||||
expect(dbCustomer).toBeDefined();
|
||||
internalCustomerId = dbCustomer!.internal_id;
|
||||
});
|
||||
|
||||
test("should create customer with pro monthly product", async () => {
|
||||
const result = await rcClient.initialPurchase({
|
||||
productId: RC_PRO_MONTHLY_ID,
|
||||
appUserId: customerId,
|
||||
originalTransactionId: "1234567890",
|
||||
});
|
||||
expectWebhookSuccess(result);
|
||||
|
||||
const customer = await autumnV1.customers.get(customerId);
|
||||
expect(customer).toBeDefined();
|
||||
expect(customer.id).toBe(customerId);
|
||||
expect(customer.products).toHaveLength(1);
|
||||
expect(customer.products[0].id).toBe(proMonthly.id);
|
||||
|
||||
proMonthlyCusProductId = await fetchLatestActiveCusProductId();
|
||||
});
|
||||
|
||||
test("should upgrade customer to pro yearly product upon renewal", async () => {
|
||||
const result = await rcClient.renewal({
|
||||
productId: RC_PRO_YEARLY_ID,
|
||||
appUserId: customerId,
|
||||
originalTransactionId: "1234567890",
|
||||
});
|
||||
expectWebhookSuccess(result);
|
||||
|
||||
await fetchLatestActiveCusProductId();
|
||||
const customer = await autumnV1.customers.get(customerId);
|
||||
expect(customer).toBeDefined();
|
||||
expect(customer.id).toBe(customerId);
|
||||
expect(customer.products).toHaveLength(1);
|
||||
expect(customer.products[0].id).toBe(proYearly.id);
|
||||
});
|
||||
|
||||
test("should downgrade customer to pro monthly product upon initial purchase", async () => {
|
||||
const result = await rcClient.initialPurchase({
|
||||
productId: RC_PRO_MONTHLY_ID,
|
||||
appUserId: customerId,
|
||||
originalTransactionId: "1234567890",
|
||||
});
|
||||
expectWebhookSuccess(result);
|
||||
|
||||
const customer = await autumnV1.customers.get(customerId);
|
||||
expect(customer).toBeDefined();
|
||||
expect(customer.id).toBe(customerId);
|
||||
expect(customer.products).toHaveLength(1);
|
||||
expect(customer.products[0].id).toBe(proMonthly.id);
|
||||
|
||||
const currentCusProductId = await fetchLatestActiveCusProductId();
|
||||
console.log("currentCusProductId", currentCusProductId);
|
||||
expect(currentCusProductId).not.toBe(getBaselineCusProductId());
|
||||
updateBaselineCusProductId(currentCusProductId);
|
||||
});
|
||||
|
||||
test("should go to cancelling state upon cancellation", async () => {
|
||||
const result = await rcClient.cancellation({
|
||||
productId: RC_PRO_MONTHLY_ID,
|
||||
appUserId: customerId,
|
||||
originalTransactionId: "1234567890",
|
||||
expirationAtMs: Date.now() + 1000 * 60 * 60 * 24 * 30,
|
||||
});
|
||||
expectWebhookSuccess(result);
|
||||
|
||||
const customer = await autumnV1.customers.get(customerId);
|
||||
expect(customer).toBeDefined();
|
||||
expect(customer.id).toBe(customerId);
|
||||
expect(customer.products).toHaveLength(1);
|
||||
expect(customer.products[0].id).toBe(proMonthly.id);
|
||||
const canceledAt = customer.products[0].canceled_at ?? 0;
|
||||
expect(typeof canceledAt).toBe("number");
|
||||
expect(Math.abs(Date.now() - canceledAt)).toBeLessThanOrEqual(3000);
|
||||
|
||||
const currentCusProductId = await fetchLatestActiveCusProductId();
|
||||
expect(currentCusProductId).toBe(getBaselineCusProductId());
|
||||
});
|
||||
|
||||
test("should uncancel customer after cancellation event", async () => {
|
||||
const result = await rcClient.uncancellation({
|
||||
productId: RC_PRO_MONTHLY_ID,
|
||||
appUserId: customerId,
|
||||
});
|
||||
expectWebhookSuccess(result);
|
||||
|
||||
const customer = await autumnV1.customers.get(customerId);
|
||||
expect(customer).toBeDefined();
|
||||
expect(customer.id).toBe(customerId);
|
||||
expect(customer.products).toHaveLength(1);
|
||||
expect(customer.products[0].id).toBe(proMonthly.id);
|
||||
expect(customer.products[0].canceled_at).toBeNull();
|
||||
|
||||
const currentCusProductId = await fetchLatestActiveCusProductId();
|
||||
expect(currentCusProductId).toBe(getBaselineCusProductId());
|
||||
});
|
||||
|
||||
test("should mark product as past due upon billing issue", async () => {
|
||||
const result = await rcClient.billingIssue({
|
||||
productId: RC_PRO_MONTHLY_ID,
|
||||
appUserId: customerId,
|
||||
originalTransactionId: "1234567890",
|
||||
});
|
||||
expectWebhookSuccess(result);
|
||||
|
||||
const customer = await autumnV1.customers.get(customerId);
|
||||
expect(customer).toBeDefined();
|
||||
expect(customer.id).toBe(customerId);
|
||||
expect(customer.products).toHaveLength(1);
|
||||
expect(customer.products[0].id).toBe(proMonthly.id);
|
||||
expect(String(customer.products[0].status)).toBe("past_due");
|
||||
|
||||
const currentCusProductId = await fetchLatestActiveCusProductId();
|
||||
expect(currentCusProductId).toBe(getBaselineCusProductId());
|
||||
});
|
||||
|
||||
test("should go to expired state upon expiration", async () => {
|
||||
const result = await rcClient.expiration({
|
||||
productId: RC_PRO_MONTHLY_ID,
|
||||
appUserId: customerId,
|
||||
originalTransactionId: "1234567890",
|
||||
});
|
||||
expectWebhookSuccess(result);
|
||||
|
||||
const customer = await autumnV1.customers.get(customerId);
|
||||
expect(customer).toBeDefined();
|
||||
expect(customer.id).toBe(customerId);
|
||||
expect(customer.products).toHaveLength(0);
|
||||
|
||||
const latestCusProductId = await fetchLatestCusProductIdAnyStatus();
|
||||
// After expiration, there may no longer be a cus_product row at all. In that
|
||||
// case, we just assert there are no cus_products for this customer anymore.
|
||||
if (latestCusProductId === null) {
|
||||
const allCusProducts = await CusProductService.list({
|
||||
db: ctx.db,
|
||||
internalCustomerId: internalCustomerId!,
|
||||
inStatuses: undefined,
|
||||
});
|
||||
expect(allCusProducts.length).toBe(0);
|
||||
} else {
|
||||
expect(latestCusProductId).toBe(getBaselineCusProductId());
|
||||
}
|
||||
});
|
||||
|
||||
test("should attach add-on product after expiration via non-renewing purchase", async () => {
|
||||
const result = await rcClient.nonRenewingPurchase({
|
||||
productId: RC_ADD_ON_ID,
|
||||
appUserId: customerId,
|
||||
originalTransactionId: "add_on_tx_12345",
|
||||
});
|
||||
expectWebhookSuccess(result);
|
||||
|
||||
const customer = await autumnV1.customers.get(customerId);
|
||||
expect(customer).toBeDefined();
|
||||
expect(customer.id).toBe(customerId);
|
||||
expect(customer.products).toHaveLength(1);
|
||||
expect(customer.products[0].id).toBe(addOnPack.id);
|
||||
|
||||
const addOnCusProducts = await CusProductService.getByProductId({
|
||||
db: ctx.db,
|
||||
productId: addOnPack.id,
|
||||
orgId: ctx.org.id,
|
||||
env: ctx.env,
|
||||
limit: 1,
|
||||
});
|
||||
expect(
|
||||
addOnCusProducts.length > 0,
|
||||
`CusProduct for add-on product ${addOnPack.id} should exist`,
|
||||
).toBe(true);
|
||||
const addOnCusProductId = addOnCusProducts[0]!.id;
|
||||
expect(typeof addOnCusProductId).toBe("string");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,244 @@
|
||||
/**
|
||||
* Legacy Add-on Attach Tests
|
||||
*
|
||||
* Migrated from:
|
||||
* - server/tests/attach/addOn/addOn1.test.ts (attach pro then free add-on)
|
||||
* - server/tests/attach/addOn/addOn2.test.ts (attach pro then free add-on with credits)
|
||||
* - server/tests/attach/basic/basic2.test.ts (attach pro then monthly prepaid add-on with quantity)
|
||||
*
|
||||
* Tests V1 attach (s.attach) behavior for:
|
||||
* - Attaching a free add-on after a base product
|
||||
* - Verifying both products appear as active
|
||||
* - Verifying feature balances after add-on attachment
|
||||
* - Attaching prepaid add-ons with quantity options
|
||||
*/
|
||||
|
||||
import { expect, test } from "bun:test";
|
||||
import type { ApiCustomerV1, ApiCustomerV3 } from "@autumn/shared";
|
||||
import { AutumnCli } from "@tests/cli/AutumnCli";
|
||||
import { expectCustomerFeatureCorrect } from "@tests/integration/billing/utils/expectCustomerFeatureCorrect";
|
||||
import { expectCustomerInvoiceCorrect } from "@tests/integration/billing/utils/expectCustomerInvoiceCorrect";
|
||||
import { expectCustomerProducts } from "@tests/integration/billing/utils/expectCustomerProductCorrect";
|
||||
import { TestFeature } from "@tests/setup/v2Features";
|
||||
import { items } from "@tests/utils/fixtures/items";
|
||||
import { products } from "@tests/utils/fixtures/products";
|
||||
import { initScenario, s } from "@tests/utils/testInitUtils/initScenario";
|
||||
import chalk from "chalk";
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
// TEST 1: Attach pro then free add-on with messages
|
||||
// (from addOn1)
|
||||
//
|
||||
// Scenario:
|
||||
// - Pro product ($20/month) with Messages feature (100 included)
|
||||
// - Free add-on with Messages feature (200 included)
|
||||
// - Attach Pro, then attach add-on
|
||||
//
|
||||
// Expected:
|
||||
// - Customer has both Pro and add-on active
|
||||
// - 1 invoice for $20 (Pro subscription only, add-on is free)
|
||||
// - Messages balance = 100 (Pro) + 200 (add-on) = 300
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
test.concurrent(`${chalk.yellowBright("legacy-addon 1: attach pro then free add-on with messages")}`, async () => {
|
||||
const customerId = "legacy-addon-1";
|
||||
|
||||
const messagesItem = items.monthlyMessages({ includedUsage: 100 });
|
||||
const pro = products.pro({ id: "pro", items: [messagesItem] });
|
||||
|
||||
const addOnMessagesItem = items.monthlyMessages({ includedUsage: 200 });
|
||||
const addOn = products.base({
|
||||
id: "addon",
|
||||
items: [addOnMessagesItem],
|
||||
isAddOn: true,
|
||||
});
|
||||
|
||||
const { autumnV1 } = await initScenario({
|
||||
customerId,
|
||||
setup: [
|
||||
s.customer({ paymentMethod: "success", testClock: true }),
|
||||
s.products({ list: [pro, addOn] }),
|
||||
],
|
||||
actions: [
|
||||
s.attach({ productId: pro.id }),
|
||||
s.attach({ productId: addOn.id }),
|
||||
],
|
||||
});
|
||||
|
||||
const customer = await autumnV1.customers.get<ApiCustomerV3>(customerId);
|
||||
|
||||
await expectCustomerProducts({
|
||||
customer,
|
||||
active: [pro.id, addOn.id],
|
||||
});
|
||||
|
||||
await expectCustomerInvoiceCorrect({
|
||||
customer,
|
||||
count: 1,
|
||||
latestTotal: 20,
|
||||
});
|
||||
|
||||
expectCustomerFeatureCorrect({
|
||||
customer,
|
||||
featureId: TestFeature.Messages,
|
||||
balance: 300,
|
||||
usage: 0,
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
// TEST 2: Attach pro then free add-on twice
|
||||
// (from addOn2)
|
||||
//
|
||||
// Scenario:
|
||||
// - Pro product ($20/month) with no feature items
|
||||
// - Free add-on with Credits feature (100 included)
|
||||
// - Attach Pro, then attach add-on, then attach add-on again
|
||||
//
|
||||
// Expected:
|
||||
// - Customer has both Pro and add-on active after each attachment
|
||||
// - Credits feature balance remains 100 after second attach
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
test.concurrent(`${chalk.yellowBright("legacy-addon 2: attach pro then free add-on twice")}`, async () => {
|
||||
const customerId = "legacy-addon-2";
|
||||
|
||||
const pro = products.pro({ id: "pro", items: [] });
|
||||
|
||||
const creditsItem = items.monthlyCredits();
|
||||
const addOn = products.base({
|
||||
id: "addon",
|
||||
items: [creditsItem],
|
||||
isAddOn: true,
|
||||
});
|
||||
|
||||
const { autumnV1 } = await initScenario({
|
||||
customerId,
|
||||
setup: [
|
||||
s.customer({ paymentMethod: "success", testClock: true }),
|
||||
s.products({ list: [pro, addOn] }),
|
||||
],
|
||||
actions: [
|
||||
s.attach({ productId: pro.id }),
|
||||
s.attach({ productId: addOn.id }),
|
||||
],
|
||||
});
|
||||
|
||||
const customer = await autumnV1.customers.get<ApiCustomerV3>(customerId);
|
||||
|
||||
await expectCustomerProducts({
|
||||
customer,
|
||||
active: [pro.id, addOn.id],
|
||||
});
|
||||
|
||||
expectCustomerFeatureCorrect({
|
||||
customer,
|
||||
featureId: TestFeature.Credits,
|
||||
balance: 100,
|
||||
usage: 0,
|
||||
});
|
||||
|
||||
// Attach the same free add-on a second time
|
||||
await autumnV1.attach({
|
||||
customer_id: customerId,
|
||||
product_id: `${addOn.id}_${customerId}`,
|
||||
});
|
||||
|
||||
const customerAfter = await autumnV1.customers.get<ApiCustomerV3>(customerId);
|
||||
|
||||
await expectCustomerProducts({
|
||||
customer: customerAfter,
|
||||
active: [pro.id, addOn.id],
|
||||
});
|
||||
|
||||
expectCustomerFeatureCorrect({
|
||||
customer: customerAfter,
|
||||
featureId: TestFeature.Credits,
|
||||
balance: 100,
|
||||
usage: 0,
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
// TEST 3: Attach pro then monthly prepaid add-on with quantity
|
||||
// (from basic2)
|
||||
//
|
||||
// Scenario:
|
||||
// - Pro product ($20/month) with Messages feature (10 included)
|
||||
// - Monthly prepaid add-on for Messages ($9/250 units, 0 included)
|
||||
// - Attach Pro, then attach add-on with quantity 500 (2 packs)
|
||||
//
|
||||
// Expected:
|
||||
// - Customer has both Pro and add-on active
|
||||
// - 2 invoices (Pro $20, add-on $18)
|
||||
// - Messages balance = 10 (Pro) + 500 (add-on) = 510
|
||||
// - /check returns correct combined balance (using V0 entitled endpoint)
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
test.concurrent(`${chalk.yellowBright("legacy-addon 3: attach pro then monthly prepaid add-on with quantity")}`, async () => {
|
||||
const customerId = "legacy-addon-3";
|
||||
const monthlyQuantity = 500;
|
||||
|
||||
const messagesItem = items.monthlyMessages({ includedUsage: 10 });
|
||||
const pro = products.pro({ id: "pro", items: [messagesItem] });
|
||||
|
||||
const prepaidMessagesItem = items.prepaidMessages({
|
||||
price: 9,
|
||||
billingUnits: 250,
|
||||
includedUsage: 0,
|
||||
});
|
||||
const addOn = products.base({
|
||||
id: "monthly-add-on",
|
||||
items: [prepaidMessagesItem],
|
||||
isAddOn: true,
|
||||
});
|
||||
|
||||
await initScenario({
|
||||
customerId,
|
||||
setup: [
|
||||
s.customer({ paymentMethod: "success", testClock: true }),
|
||||
s.products({ list: [pro, addOn] }),
|
||||
],
|
||||
actions: [s.attach({ productId: pro.id })],
|
||||
});
|
||||
|
||||
// Attach the prepaid add-on with quantity options (using AutumnCli like original)
|
||||
await AutumnCli.attach({
|
||||
customerId,
|
||||
productId: addOn.id,
|
||||
forceCheckout: false,
|
||||
options: [
|
||||
{
|
||||
feature_id: TestFeature.Messages,
|
||||
quantity: monthlyQuantity,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Use AutumnCli.getCustomer for V1 response format (entitlements, add_ons)
|
||||
const cusRes = await AutumnCli.getCustomer(customerId) as ApiCustomerV1;
|
||||
|
||||
// Pro gives 10 Messages, add-on gives 500
|
||||
const expectedBalance = 10 + monthlyQuantity;
|
||||
|
||||
const monthlyMessagesBalance = cusRes.entitlements.find(
|
||||
(e) => e.feature_id === TestFeature.Messages && e.interval === "month",
|
||||
);
|
||||
expect(monthlyMessagesBalance?.balance).toBe(expectedBalance);
|
||||
|
||||
expect(cusRes.add_ons).toHaveLength(1);
|
||||
const monthlyAddOnFound = cusRes.add_ons.find((a) => a.id === addOn.id);
|
||||
expect(monthlyAddOnFound).toBeDefined();
|
||||
|
||||
expect(cusRes.invoices.length).toBe(2);
|
||||
|
||||
// Verify /entitled returns correct balance (V0 endpoint with balances array)
|
||||
const entitledRes = await AutumnCli.entitled(customerId, TestFeature.Messages) as {
|
||||
allowed: boolean;
|
||||
balances: { feature_id: string; balance: number }[];
|
||||
};
|
||||
const messagesBalance = entitledRes.balances.find(
|
||||
(b) => b.feature_id === TestFeature.Messages,
|
||||
);
|
||||
expect(messagesBalance?.balance).toBe(expectedBalance);
|
||||
});
|
||||
@@ -1,10 +1,16 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import type { ApiCustomerV3 } from "@autumn/shared";
|
||||
import { expectCustomerProducts } from "@tests/integration/billing/utils/expectCustomerProductCorrect";
|
||||
import { TestFeature } from "@tests/setup/v2Features";
|
||||
import { completeInvoiceCheckoutV2 as completeInvoiceCheckout } from "@tests/utils/browserPool/completeInvoiceCheckoutV2";
|
||||
import { expectAutumnError } from "@tests/utils/expectUtils/expectErrUtils";
|
||||
import { expectProductAttached } from "@tests/utils/expectUtils/expectProductAttached.js";
|
||||
import { items } from "@tests/utils/fixtures/items.js";
|
||||
import { products } from "@tests/utils/fixtures/products.js";
|
||||
import { completeInvoiceCheckoutV2 as completeInvoiceCheckout } from "@tests/utils/browserPool/completeInvoiceCheckoutV2";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext";
|
||||
import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js";
|
||||
import chalk from "chalk";
|
||||
import { CusService } from "@/internal/customers/CusService";
|
||||
import { removeAllPaymentMethods } from "@/external/stripe/customers/paymentMethods/operations/removeAllPaymentMethods.js";
|
||||
import { attachPaymentMethod } from "@/utils/scriptUtils/initCustomer.js";
|
||||
|
||||
@@ -107,3 +113,159 @@ test.concurrent(`${chalk.yellowBright("attach: pro then upgrade to premium with
|
||||
product: premium,
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
// TEST 3: Create customer and entity with null IDs (autumn_id generation)
|
||||
// (from others6)
|
||||
//
|
||||
// Scenario:
|
||||
// - Create customer with id=null → gets autumn_id
|
||||
// - Create entity with id=null → gets autumn_id
|
||||
// - Attach pro product with invoice option
|
||||
// - Then assign external ID to the same customer
|
||||
//
|
||||
// Expected:
|
||||
// - Customer and entity get autumn_id when created with null
|
||||
// - Can attach product using autumn_id
|
||||
// - Can later assign external ID to same customer
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
test.concurrent(`${chalk.yellowBright("attach-edge-case 3: null customer_id and entity_id (autumn_id generation)")}`, async () => {
|
||||
const customerId = "edge-case-null-ids";
|
||||
const email = `${customerId}@test.com`;
|
||||
|
||||
const wordsItem = items.consumableWords();
|
||||
const pro = products.pro({
|
||||
id: "pro",
|
||||
items: [wordsItem],
|
||||
});
|
||||
|
||||
// Clean up any existing customer with this email
|
||||
const existingCustomers = await CusService.getByEmail({
|
||||
db: ctx.db,
|
||||
orgId: ctx.org.id,
|
||||
env: ctx.env,
|
||||
email,
|
||||
});
|
||||
|
||||
const { autumnV1 } = await initScenario({
|
||||
setup: [s.products({ list: [pro], prefix: customerId })],
|
||||
actions: [],
|
||||
});
|
||||
|
||||
if (existingCustomers.length > 0) {
|
||||
await autumnV1.customers.delete(existingCustomers[0].internal_id);
|
||||
}
|
||||
|
||||
// Create customer with id=null → should get autumn_id
|
||||
const customer = await autumnV1.customers.create({
|
||||
id: null,
|
||||
email,
|
||||
name: customerId,
|
||||
withAutumnId: true,
|
||||
});
|
||||
|
||||
expect(customer.autumn_id).toBeDefined();
|
||||
const internalCustomerId = customer.autumn_id;
|
||||
|
||||
// Create entity with id=null → should get autumn_id
|
||||
const entity = await autumnV1.entities.create(internalCustomerId, {
|
||||
id: null,
|
||||
feature_id: TestFeature.Users,
|
||||
});
|
||||
|
||||
expect(entity.autumn_id).toBeDefined();
|
||||
const internalEntityId = entity.autumn_id;
|
||||
|
||||
// Attach pro product using autumn_ids, with invoice option
|
||||
await autumnV1.attach({
|
||||
customer_id: internalCustomerId,
|
||||
entity_id: internalEntityId,
|
||||
product_id: pro.id,
|
||||
invoice: true,
|
||||
enable_product_immediately: true,
|
||||
});
|
||||
|
||||
const customerAfterAttach = await autumnV1.customers.get<ApiCustomerV3>(internalCustomerId);
|
||||
|
||||
await expectCustomerProducts({
|
||||
customer: customerAfterAttach,
|
||||
active: [pro.id],
|
||||
});
|
||||
|
||||
expect(customerAfterAttach.invoices?.length).toBe(1);
|
||||
expect(customerAfterAttach.invoices?.[0].status).toBe("draft");
|
||||
|
||||
// Now assign external ID to the same customer
|
||||
const customerWithId = await autumnV1.customers.create({
|
||||
id: customerId,
|
||||
email,
|
||||
});
|
||||
|
||||
// Should be the same customer (same autumn_id)
|
||||
expect(customerWithId.autumn_id).toBe(internalCustomerId);
|
||||
|
||||
// Can now fetch by external ID
|
||||
const customerById = await autumnV1.customers.get<ApiCustomerV3>(customerId);
|
||||
|
||||
await expectCustomerProducts({
|
||||
customer: customerById,
|
||||
active: [pro.id],
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
// TEST 4: Duplicate attach error (attach same product twice)
|
||||
// (from others9)
|
||||
//
|
||||
// Scenario:
|
||||
// - Free product with Words feature
|
||||
// - Attach free product → success
|
||||
// - Attach same free product again → error
|
||||
//
|
||||
// Expected:
|
||||
// - First attach succeeds
|
||||
// - Second attach throws AutumnError
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
test.concurrent(`${chalk.yellowBright("attach-edge-case 4: duplicate attach error")}`, async () => {
|
||||
const customerId = "edge-case-duplicate-attach";
|
||||
|
||||
const wordsItem = items.monthlyWords({ includedUsage: 100 });
|
||||
const free = products.base({
|
||||
id: "free",
|
||||
items: [wordsItem],
|
||||
});
|
||||
|
||||
const { autumnV1 } = await initScenario({
|
||||
customerId,
|
||||
setup: [
|
||||
s.customer({ paymentMethod: "success", testClock: true }),
|
||||
s.products({ list: [free] }),
|
||||
],
|
||||
actions: [],
|
||||
});
|
||||
|
||||
// First attach should succeed
|
||||
await autumnV1.attach({
|
||||
customer_id: customerId,
|
||||
product_id: free.id,
|
||||
});
|
||||
|
||||
const customerAfterFirstAttach = await autumnV1.customers.get<ApiCustomerV3>(customerId);
|
||||
|
||||
await expectCustomerProducts({
|
||||
customer: customerAfterFirstAttach,
|
||||
active: [free.id],
|
||||
});
|
||||
|
||||
// Second attach should fail
|
||||
await expectAutumnError({
|
||||
func: async () => {
|
||||
await autumnV1.attach({
|
||||
customer_id: customerId,
|
||||
product_id: free.id,
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import type { AppEnv } from "@autumn/shared";
|
||||
import { type AppEnv, ErrCode } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { getMainCusProduct } from "@tests/utils/cusProductUtils/cusProductUtils.js";
|
||||
import { expectProductAttached } from "@tests/utils/expectUtils/expectProductAttached.js";
|
||||
@@ -8,8 +8,9 @@ import { products } from "@tests/utils/fixtures/products.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js";
|
||||
import chalk from "chalk";
|
||||
import AutumnError from "@/external/autumn/autumnCli";
|
||||
import { cusProductToSub } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import { generateId, timeout } from "@/utils/genUtils.js";
|
||||
|
||||
// ============================================================================
|
||||
// Test 1: Auto-create customer and entity via attach
|
||||
@@ -160,3 +161,72 @@ test.skip(`${chalk.yellowBright("attach-misc: attach race condition - concurrent
|
||||
const successes = responses.filter((r) => r.status === "fulfilled");
|
||||
expect(successes.length).toEqual(1);
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Test 3: Idempotency key - duplicate requests rejected
|
||||
// (from others10)
|
||||
// ============================================================================
|
||||
test.concurrent(`${chalk.yellowBright("attach-misc: idempotency key duplicate rejected")}`, async () => {
|
||||
const customerId = "attach-misc-idempotency";
|
||||
|
||||
const creditsItem = items.monthlyMessages({ includedUsage: 500 });
|
||||
const pro = products.pro({
|
||||
id: "pro",
|
||||
items: [creditsItem],
|
||||
});
|
||||
|
||||
const { autumnV1 } = await initScenario({
|
||||
customerId,
|
||||
setup: [
|
||||
s.customer({ paymentMethod: "success", testClock: true }),
|
||||
s.products({ list: [pro] }),
|
||||
],
|
||||
actions: [],
|
||||
});
|
||||
|
||||
const idempotencyKey = generateId("it");
|
||||
|
||||
// Send two concurrent attach requests with the same idempotency key
|
||||
const results = await Promise.allSettled([
|
||||
autumnV1.attach(
|
||||
{
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
},
|
||||
{
|
||||
idempotencyKey,
|
||||
},
|
||||
),
|
||||
autumnV1.attach(
|
||||
{
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
},
|
||||
{
|
||||
idempotencyKey,
|
||||
},
|
||||
),
|
||||
]);
|
||||
|
||||
// Exactly one request should succeed
|
||||
const fulfilled = results.filter((r) => r.status === "fulfilled");
|
||||
const rejected = results.filter((r) => r.status === "rejected");
|
||||
|
||||
expect(fulfilled).toHaveLength(1);
|
||||
expect(rejected).toHaveLength(1);
|
||||
|
||||
// The successful request should have attached the product
|
||||
const successResult = fulfilled[0] as PromiseFulfilledResult<
|
||||
Awaited<ReturnType<typeof autumnV1.attach>>
|
||||
>;
|
||||
expect(successResult.value.success).toBe(true);
|
||||
expect(successResult.value.customer_id).toBe(customerId);
|
||||
expect(successResult.value.product_ids).toContain(pro.id);
|
||||
|
||||
// The rejected request should have the duplicate idempotency key error
|
||||
const rejectedResult = rejected[0] as PromiseRejectedResult;
|
||||
expect(rejectedResult.reason).toBeInstanceOf(AutumnError);
|
||||
expect((rejectedResult.reason as AutumnError).code).toBe(
|
||||
ErrCode.DuplicateIdempotencyKey,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,313 @@
|
||||
/**
|
||||
* Legacy Attach Response Format Tests
|
||||
*
|
||||
* Migrated from:
|
||||
* - server/tests/attach/response/attach-response1.test.ts (new, no card → checkout_url)
|
||||
* - server/tests/attach/response/attach-response2.test.ts (upgrade response)
|
||||
* - server/tests/attach/response/attach-response3.test.ts (downgrade response)
|
||||
* - server/tests/attach/response/attach-response4.test.ts (new, card on file)
|
||||
* - server/tests/attach/response/attach-response5.test.ts (one-off, card on file)
|
||||
*
|
||||
* Tests that attach responses have the correct shape for v0.2 and v1.2 API versions
|
||||
* across different attach scenarios (new, upgrade, downgrade, one-off).
|
||||
*/
|
||||
|
||||
/** biome-ignore-all lint/suspicious/noExplicitAny: test file */
|
||||
|
||||
import { expect, test } from "bun:test";
|
||||
import { ApiVersion } from "@autumn/shared";
|
||||
import { items } from "@tests/utils/fixtures/items";
|
||||
import { products } from "@tests/utils/fixtures/products";
|
||||
import { initScenario, s } from "@tests/utils/testInitUtils/initScenario";
|
||||
import chalk from "chalk";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli";
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
// TEST 1: New attach with no card on file → returns checkout_url
|
||||
// (from attach-response1)
|
||||
//
|
||||
// Scenario:
|
||||
// - Pro product ($20/month) with consumable Words (1000 included)
|
||||
// - Customer with NO payment method
|
||||
// - v0.2: attach returns only { checkout_url }
|
||||
// - v1.2: attach returns { customer_id, product_ids, checkout_url, code, message }
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
test.concurrent(`${chalk.yellowBright("attach-response 1: new attach, no card → checkout_url")}`, async () => {
|
||||
const customerId = "attach-response-1";
|
||||
|
||||
const pro = products.pro({
|
||||
id: "pro",
|
||||
items: [items.consumableWords({ includedUsage: 1000 })],
|
||||
});
|
||||
|
||||
await initScenario({
|
||||
customerId,
|
||||
setup: [s.customer({}), s.products({ list: [pro] })],
|
||||
actions: [],
|
||||
});
|
||||
|
||||
const autumnV0 = new AutumnInt({ version: ApiVersion.V0_2 });
|
||||
const autumnV1 = new AutumnInt({ version: ApiVersion.V1_2 });
|
||||
|
||||
// v0.2 response: only checkout_url
|
||||
const v0Response = await autumnV0.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
expect(v0Response.checkout_url).toBeDefined();
|
||||
expect(Object.keys(v0Response)).toEqual(["checkout_url"]);
|
||||
|
||||
// v1.2 response: richer object
|
||||
const v1Response = await autumnV1.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
expect(v1Response).toMatchObject({
|
||||
customer_id: customerId,
|
||||
product_ids: [pro.id],
|
||||
checkout_url: expect.any(String),
|
||||
});
|
||||
expect(v1Response.code).toBeDefined();
|
||||
expect(v1Response.message).toBeDefined();
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
// TEST 2: Upgrade response
|
||||
// (from attach-response2)
|
||||
//
|
||||
// Scenario:
|
||||
// - Pro ($20/month) and Premium ($50/month) with Words (1000 included)
|
||||
// - Customer with payment method, attach Pro first
|
||||
// - Upgrade to Premium
|
||||
// - v0.2: returns { success, message }
|
||||
// - v1.2: cancel Premium, re-attach Pro, then upgrade to Premium →
|
||||
// returns { customer_id, product_ids, code, message }
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
test.concurrent(`${chalk.yellowBright("attach-response 2: upgrade response")}`, async () => {
|
||||
const customerId1 = "attach-response-2-1";
|
||||
const customerId2 = "attach-response-2-2";
|
||||
|
||||
const wordsItem = items.monthlyWords({ includedUsage: 1000 });
|
||||
const pro = products.pro({ id: "pro", items: [wordsItem] });
|
||||
const premium = products.premium({ id: "premium", items: [wordsItem] });
|
||||
|
||||
const { autumnV1 } = await initScenario({
|
||||
customerId: customerId1,
|
||||
setup: [
|
||||
s.customer({ paymentMethod: "success", testClock: true }),
|
||||
s.products({ list: [pro, premium] }),
|
||||
s.otherCustomers([{ id: customerId2, paymentMethod: "success" }]),
|
||||
],
|
||||
actions: [s.attach({ productId: pro.id })],
|
||||
});
|
||||
|
||||
const autumnV0 = new AutumnInt({ version: ApiVersion.V0_2 });
|
||||
|
||||
// v0.2 upgrade response
|
||||
const v0Response = await autumnV0.attach({
|
||||
customer_id: customerId1,
|
||||
product_id: premium.id,
|
||||
});
|
||||
|
||||
expect(Object.keys(v0Response)).toEqual(["success", "message"]);
|
||||
|
||||
await autumnV1.attach({
|
||||
customer_id: customerId2,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
// v1.2 upgrade response
|
||||
const v1Response = await autumnV1.attach({
|
||||
customer_id: customerId2,
|
||||
product_id: premium.id,
|
||||
});
|
||||
|
||||
expect(v1Response).toMatchObject({
|
||||
customer_id: customerId2,
|
||||
product_ids: [premium.id],
|
||||
code: expect.any(String),
|
||||
message: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
// TEST 3: Downgrade response
|
||||
// (from attach-response3)
|
||||
//
|
||||
// Scenario:
|
||||
// - Pro ($20/month) and Premium ($50/month) with Words (1000 included)
|
||||
// - Customer with payment method, attach Premium first
|
||||
// - Downgrade to Pro
|
||||
// - v0.2: returns { success, message }
|
||||
// - v1.2: uncancel Premium, then downgrade to Pro →
|
||||
// returns { customer_id, product_ids, code, message }
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
test.concurrent(`${chalk.yellowBright("attach-response 3: downgrade response")}`, async () => {
|
||||
const customerId = "attach-response-3";
|
||||
|
||||
const wordsItem = items.monthlyWords({ includedUsage: 1000 });
|
||||
const pro = products.pro({ id: "pro", items: [wordsItem] });
|
||||
const premium = products.premium({ id: "premium", items: [wordsItem] });
|
||||
|
||||
const { autumnV1 } = await initScenario({
|
||||
customerId,
|
||||
setup: [
|
||||
s.customer({ paymentMethod: "success", testClock: true }),
|
||||
s.products({ list: [pro, premium] }),
|
||||
],
|
||||
actions: [s.attach({ productId: premium.id })],
|
||||
});
|
||||
|
||||
const autumnV0 = new AutumnInt({ version: ApiVersion.V0_2 });
|
||||
|
||||
// v0.2 downgrade response
|
||||
const v0Response = await autumnV0.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
expect(Object.keys(v0Response)).toEqual(["success", "message"]);
|
||||
|
||||
// Reset: uncancel Premium for v1.2 test
|
||||
await autumnV1.subscriptions.update({
|
||||
customer_id: customerId,
|
||||
product_id: premium.id,
|
||||
cancel_action: "uncancel",
|
||||
});
|
||||
|
||||
// v1.2 downgrade response
|
||||
const v1Response = await autumnV1.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
expect(v1Response).toMatchObject({
|
||||
customer_id: customerId,
|
||||
product_ids: [pro.id],
|
||||
code: expect.any(String),
|
||||
message: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
// TEST 4: New attach with card on file → immediate success
|
||||
// (from attach-response4)
|
||||
//
|
||||
// Scenario:
|
||||
// - Pro ($20/month) with Words (1000 included)
|
||||
// - Customer with payment method
|
||||
// - v0.2: attach returns { success, message }
|
||||
// - v1.2: cancel Pro, re-attach →
|
||||
// returns { customer_id, product_ids, code, message }
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
test.concurrent(`${chalk.yellowBright("attach-response 4: new attach, card on file")}`, async () => {
|
||||
const customerId = "attach-response-4";
|
||||
|
||||
const pro = products.pro({
|
||||
id: "pro",
|
||||
items: [items.monthlyWords({ includedUsage: 1000 })],
|
||||
});
|
||||
|
||||
const { autumnV1 } = await initScenario({
|
||||
customerId,
|
||||
setup: [
|
||||
s.customer({ paymentMethod: "success", testClock: true }),
|
||||
s.products({ list: [pro] }),
|
||||
],
|
||||
actions: [],
|
||||
});
|
||||
|
||||
const autumnV0 = new AutumnInt({ version: ApiVersion.V0_2 });
|
||||
|
||||
// v0.2 response
|
||||
const v0Response = await autumnV0.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
expect(Object.keys(v0Response)).toEqual(["success", "message"]);
|
||||
|
||||
// Reset: cancel Pro for v1.2 test
|
||||
await autumnV1.cancel({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
cancel_immediately: true,
|
||||
});
|
||||
|
||||
// v1.2 response
|
||||
const v1Response = await autumnV1.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
expect(v1Response).toMatchObject({
|
||||
customer_id: customerId,
|
||||
product_ids: [pro.id],
|
||||
code: expect.any(String),
|
||||
message: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
// TEST 5: One-off attach with card on file
|
||||
// (from attach-response5)
|
||||
//
|
||||
// Scenario:
|
||||
// - One-off product ($10) with lifetime Words (1000 included)
|
||||
// - Customer with payment method
|
||||
// - v0.2: attach returns { success, message }
|
||||
// - v1.2: attach again (one-off can be attached multiple times) →
|
||||
// returns { success, customer_id, product_ids, code, message }
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
test.concurrent(`${chalk.yellowBright("attach-response 5: one-off attach, card on file")}`, async () => {
|
||||
const customerId = "attach-response-5";
|
||||
|
||||
const oneOff = products.oneOff({
|
||||
id: "one-off",
|
||||
items: [items.lifetimeMessages({ includedUsage: 1000 })],
|
||||
});
|
||||
|
||||
await initScenario({
|
||||
customerId,
|
||||
setup: [
|
||||
s.customer({ paymentMethod: "success", testClock: true }),
|
||||
s.products({ list: [oneOff] }),
|
||||
],
|
||||
actions: [],
|
||||
});
|
||||
|
||||
const autumnV0 = new AutumnInt({ version: ApiVersion.V0_2 });
|
||||
const autumnV1 = new AutumnInt({ version: ApiVersion.V1_2 });
|
||||
|
||||
// v0.2 response
|
||||
const v0Response = await autumnV0.attach({
|
||||
customer_id: customerId,
|
||||
product_id: oneOff.id,
|
||||
});
|
||||
|
||||
expect(v0Response).toMatchObject({
|
||||
success: true,
|
||||
message: expect.any(String),
|
||||
});
|
||||
|
||||
// v1.2 response (attach again — one-off can accumulate)
|
||||
const v1Response = await autumnV1.attach({
|
||||
customer_id: customerId,
|
||||
product_id: oneOff.id,
|
||||
});
|
||||
|
||||
expect(v1Response).toMatchObject({
|
||||
success: true,
|
||||
customer_id: customerId,
|
||||
product_ids: [oneOff.id],
|
||||
code: expect.any(String),
|
||||
message: expect.any(String),
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user