test: 💍 bun

This commit is contained in:
amianthus
2025-10-31 11:08:00 +00:00
parent 3f5ad97675
commit 4b9ce6af70
76 changed files with 4538 additions and 5160 deletions

View File

@@ -269,22 +269,43 @@ After migration, run: `bun test [FILE_PATH]` to verify all tests pass.
### updateQuantity (1 file)
- [x]`tests/attach/updateQuantity/updateQuantity1.test.ts` - Mocha→Bun
### rollovers (6 files)
- [x]`tests/advanced/rollovers/rollover1.test.ts` - Mocha→Bun
- [x]`tests/advanced/rollovers/rollover2.test.ts` - Mocha→Bun
- [x]`tests/advanced/rollovers/rollover3.test.ts` - Mocha→Bun
- [x]`tests/advanced/rollovers/rollover4.test.ts` - Mocha→Bun
- [x]`tests/advanced/rollovers/rollover5.test.ts` - Mocha→Bun
- [x]`tests/advanced/rollovers/rollover6.test.ts` - Mocha→Bun
### customInterval (6 files)
- [x]`tests/advanced/customInterval/customInterval1.test.ts` - Mocha→Bun
- [x]`tests/advanced/customInterval/customInterval2.test.ts` - Mocha→Bun
- [x]`tests/advanced/customInterval/customInterval3.test.ts` - Mocha→Bun
- [x]`tests/advanced/customInterval/customInterval4.test.ts` - Mocha→Bun
- [x]`tests/advanced/customInterval/customInterval5.test.ts` - Mocha→Bun
- [x] 🔕 `tests/advanced/customInterval/customInterval6.ts` - Empty file (skipped)
### usageLimit (4 files)
- [x]`tests/advanced/usageLimit/usageLimit1.test.ts` - Mocha→Bun
- [x]`tests/advanced/usageLimit/usageLimit2.test.ts` - Mocha→Bun
- [x]`tests/advanced/usageLimit/usageLimit3.test.ts` - Mocha→Bun
- [x]`tests/advanced/usageLimit/usageLimit4.test.ts` - Mocha→Bun
### G5 Not Migrated (not in g5.sh script):
- [ ] ⏸️ `tests/advanced/multiFeature/*.ts` (3 files - uses old ProductV1 structure)
- [ ] ⏸️ `tests/advanced/rollovers/*.ts` (not in g5.sh script)
- [ ] ⏸️ `tests/advanced/customInterval/*.ts` (not in g5.sh script)
- [ ] ⏸️ `tests/advanced/usageLimit/*.ts` (not in g5.sh script)
- [ ] ⏸️ `tests/advanced/multiFeature/multiFeature1.ts` (uses old ProductV1 structure)
- [ ] ⏸️ `tests/advanced/multiFeature/multiFeature2.ts` (uses old ProductV1 structure)
- [ ] ⏸️ `tests/advanced/multiFeature/multiFeature3.ts` (uses old ProductV1 structure)
## Final Migration Summary
### Totals:
- **G1:** 48 files ✅
- **G2:** 28 files ✅
- **G1:** 47 files ✅
- **G2:** 39 files ✅ (prepaid6 migrated, prepaid7 commented out)
- **G3:** 19 files ✅
- **G4:** 47 files ✅
- **G5:** 19 files ✅
- **Total Migrated:** 161 files
- **Not in shell scripts:** ~6 files (multiFeature, rollovers, customInterval, usageLimit)
- **G4:** 65 files ✅ (all merged/core tests)
- **G5:** 34 files ✅ (15 duplicates deleted)
- **Total Migrated:** 204 files
- **Not migrated:** 3 files (multiFeature 1-3 - ProductV1 structure)
### Helper Functions Created/Updated:
1.`checkUsageInvoiceAmountV2` - V2 wrapper for usage invoice validation
@@ -306,13 +327,18 @@ After migration, run: `bun test [FILE_PATH]` to verify all tests pass.
-`scripts/testGroups/g5.sh` - Updated to `BUN_PARALLEL_COMPACT` (partial - skips unmigrated tests)
### All before() → beforeAll() Replaced:
- ✅ Verified: 0 test files still using `before()` (all 55 occurrences replaced with `beforeAll()`)
- ✅ Verified: 0 test files still using `before()` (all occurrences replaced with `beforeAll()`)
- ✅ All test files now use proper Bun test syntax
### Cleanup Actions Completed:
- ✅ Deleted 15 Mocha duplicate .ts files where .test.ts versions existed (coupons, referrals, usage)
- ✅ Renamed 1 Bun duplicate to .backup.ts (updateQuantity1.ts)
- ✅ Created backups for all newly migrated files
### Migration Status:
- ✅ All ProductV1→ProductV2 conversions complete (except multiFeature + some G5 unmigrated)
- ✅ All Mocha→Bun framework migrations complete for G1-G4 and partial G5
- ✅ All ProductV1→ProductV2 conversions complete (except 3 multiFeature files)
- ✅ All Mocha→Bun framework migrations complete (except 3 multiFeature files)
- ✅ All global state → isolated migrations complete for migrated files
- ✅ All tests preserve original logic and assertions
- ✅ G1-G4 ready for parallel Bun execution
- ⚠️ Some test failures in G3 (invoice counts) - likely flaky tests, not migration issues
-All test groups (G1-G5) ready for parallel Bun execution
- ⚠️ multiFeature tests (3 files) use ProductV1 `items: {}` object structure - require manual conversion

View File

@@ -1,234 +0,0 @@
import {
type AppEnv,
type Customer,
LegacyVersion,
type Organization,
} from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import { addHours, addMonths } from "date-fns";
import type Stripe from "stripe";
import { setupBefore } from "tests/before.js";
import { rewards } from "tests/global.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
import { getExpectedInvoiceTotal } from "tests/utils/expectUtils/expectInvoiceUtils.js";
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
import { timeout } from "tests/utils/genUtils.js";
import { createProducts } from "tests/utils/productUtils.js";
import {
advanceTestClock,
completeCheckoutForm,
getDiscount,
} from "tests/utils/stripeUtils.js";
import {
addPrefixToProducts,
getBasePrice,
} from "tests/utils/testProductUtils/testProductUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { getOriginalCouponId } from "@/internal/rewards/rewardUtils.js";
import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
const testCase = "coupon1";
const pro = constructProduct({
type: "pro",
items: [constructArrearItem({ featureId: TestFeature.Words })],
});
const simulateOneCycle = async ({
customerId,
db,
org,
env,
stripeCli,
autumn,
testClockId,
couponAmount,
curUnix,
}: {
customerId: string;
db: DrizzleCli;
org: Organization;
env: AppEnv;
stripeCli: Stripe;
autumn: AutumnInt;
testClockId: string;
couponAmount: number;
curUnix: number;
}) => {
const usage = Math.random() * 100000 + 10000;
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Words,
value: usage,
});
// Expected invoice total
const expectedTotal = await getExpectedInvoiceTotal({
usage: [{ featureId: TestFeature.Words, value: usage }],
customerId,
productId: pro.id,
db,
org,
env,
stripeCli,
});
couponAmount -= expectedTotal;
curUnix = await advanceTestClock({
stripeCli,
testClockId,
advanceTo: addHours(
addMonths(curUnix, 1),
hoursToFinalizeInvoice,
).getTime(),
waitForSeconds: 30,
});
const customer = await autumn.customers.get(customerId);
expect(customer.invoices![0].total).to.equal(0);
const cusDiscount = await getDiscount({
stripeCli: stripeCli,
stripeId: customer.stripe_id!,
});
expect(cusDiscount).to.exist;
expect(getOriginalCouponId(cusDiscount.coupon?.id)).to.equal(
rewards.rolloverAll.id,
);
expect(cusDiscount.coupon?.amount_off).to.equal(
Math.round(couponAmount * 100),
`Expected stripe cus to have coupon amount ${couponAmount * 100}`,
);
return {
couponAmount,
curUnix,
};
};
describe(
chalk.yellow(
`${testCase} - Testing invoice credits reward, apply to all product`,
),
() => {
const customerId = "coupon1";
let stripeCli: Stripe;
let customer: Customer;
let testClockId: string;
let db: DrizzleCli;
let org: Organization;
let env: AppEnv;
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
let couponAmount = rewards.rolloverAll.discount_config.discount_value;
let curUnix = new Date().getTime();
before(async function () {
await setupBefore(this);
db = this.db;
org = this.org;
env = this.env;
stripeCli = this.stripeCli;
const res = await initCustomer({
customerId,
org,
env,
db,
autumn: this.autumnJs,
});
addPrefixToProducts({
products: [pro],
prefix: testCase,
});
await createProducts({
products: [pro],
orgId: org.id,
env,
db,
autumn,
});
testClockId = res.testClockId;
customer = res.customer;
});
// CYCLE 0
it("should attach pro", async () => {
const res = await autumn.attach({
customer_id: customerId,
product_id: pro.id,
});
await completeCheckoutForm(
res.checkout_url,
undefined,
rewards.rolloverAll.id,
);
await timeout(10000);
couponAmount -= getBasePrice({ product: pro });
const customer = await autumn.customers.get(customerId);
expectProductAttached({ customer, product: pro });
expect(customer.invoices![0].total).to.equal(0);
const cusDiscount = await getDiscount({
stripeCli,
stripeId: customer.stripe_id!,
});
expect(cusDiscount).to.exist;
expect(getOriginalCouponId(cusDiscount.coupon?.id)).to.equal(
rewards.rolloverAll.id,
);
expect(cusDiscount.coupon?.amount_off).to.equal(couponAmount * 100);
});
it("should run one cycle and have correct invoice + coupon amount", async () => {
const res = await simulateOneCycle({
customerId,
db,
org,
env,
stripeCli,
autumn,
testClockId,
couponAmount,
curUnix: new Date().getTime(),
});
couponAmount = res.couponAmount;
curUnix = res.curUnix;
});
// CYCLE 1
it("should run another cycle and have correct invoice + coupon amount", async () => {
const res = await simulateOneCycle({
customerId,
db,
org,
env,
stripeCli,
autumn,
testClockId,
couponAmount,
curUnix,
});
});
},
);

View File

@@ -1,197 +0,0 @@
import {
type AppEnv,
CouponDurationType,
type CreateReward,
LegacyVersion,
type Organization,
RewardType,
} from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import { addHours, addMonths } from "date-fns";
import { Decimal } from "decimal.js";
import type Stripe from "stripe";
import { setupBefore } from "tests/before.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
import { getExpectedInvoiceTotal } from "tests/utils/expectUtils/expectInvoiceUtils.js";
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
import { timeout } from "tests/utils/genUtils.js";
import { createProducts, createReward } from "tests/utils/productUtils.js";
import { completeCheckoutForm, getDiscount } from "tests/utils/stripeUtils.js";
import {
addPrefixToProducts,
getBasePrice,
} from "tests/utils/testProductUtils/testProductUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { getOriginalCouponId } from "@/internal/rewards/rewardUtils.js";
import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
const pro = constructProduct({
type: "pro",
items: [constructArrearItem({ featureId: TestFeature.Words })],
});
const testCase = "coupon2";
// Create reward input
const reward: CreateReward = {
id: "usage",
name: "usage",
promo_codes: [{ code: "usage" }],
type: RewardType.InvoiceCredits,
discount_config: {
discount_value: 10000,
duration_type: CouponDurationType.Forever,
duration_value: 1,
should_rollover: true,
apply_to_all: false,
price_ids: [],
},
};
describe(
chalk.yellow(`${testCase} - Testing one-off rollover, apply to usage only`),
() => {
const customerId = testCase;
let stripeCli: Stripe;
let testClockId: string;
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
let org: Organization;
let env: AppEnv;
let db: DrizzleCli;
let couponAmount = reward.discount_config?.discount_value ?? 0;
before(async function () {
await setupBefore(this);
org = this.org;
env = this.env;
db = this.db;
stripeCli = this.stripeCli;
const { testClockId: testClockId1 } = await initCustomer({
customerId,
org: this.org,
env: this.env,
db: this.db,
autumn: this.autumnJs,
});
testClockId = testClockId1;
addPrefixToProducts({
products: [pro],
prefix: testCase,
});
await createProducts({
orgId: this.org.id,
env: this.env,
db: this.db,
autumn,
products: [pro],
});
await createReward({
orgId: org.id,
env,
db,
autumn,
reward,
productId: pro.id,
onlyUsage: true,
});
});
// CYCLE 0
it("should attach pro with promo code", async () => {
const res = await autumn.attach({
customer_id: customerId,
product_id: pro.id,
});
await completeCheckoutForm(res.checkout_url, undefined, reward.id);
await timeout(10000);
const customer = await autumn.customers.get(customerId);
expectProductAttached({
customer,
product: pro,
});
});
it("should have fixed price invoice and correct remaining coupon amount", async () => {
const customer = await autumn.customers.get(customerId);
const fixedPrice = getBasePrice({ product: pro });
expect(customer.invoices![0].total).to.equal(fixedPrice);
const cusDiscount = await getDiscount({
stripeCli,
stripeId: customer.stripe_id!,
});
expect(getOriginalCouponId(cusDiscount.coupon?.id)).to.equal(reward.id);
expect(cusDiscount.coupon?.amount_off).to.equal(couponAmount * 100);
});
// CYCLE 1
it("should track usage and have correct invoice amount", async () => {
const usage = new Decimal(Math.random() * 1250120 + 10000)
.toDecimalPlaces(2)
.toNumber();
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Words,
value: usage,
});
const usageTotal = await getExpectedInvoiceTotal({
org,
env,
db,
customerId,
productId: pro.id,
usage: [{ featureId: TestFeature.Words, value: usage }],
stripeCli,
onlyIncludeUsage: true,
});
const basePrice = getBasePrice({ product: pro });
couponAmount = couponAmount - usageTotal;
await advanceTestClock({
stripeCli,
testClockId,
advanceTo: addHours(
addMonths(new Date(), 1),
hoursToFinalizeInvoice,
).getTime(),
waitForSeconds: 20,
});
const customer = await autumn.customers.get(customerId);
expect(customer.invoices![0].total).to.equal(basePrice);
const cusDiscount = await getDiscount({
stripeCli,
stripeId: customer.stripe_id!,
});
expect(getOriginalCouponId(cusDiscount.coupon?.id)).to.equal(reward.id);
expect(cusDiscount.coupon?.amount_off).to.equal(
Math.round(couponAmount * 100),
);
});
},
);

View File

@@ -1,176 +0,0 @@
import {
type AppEnv,
CouponDurationType,
type CreateReward,
LegacyVersion,
type Organization,
RewardType,
} from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import type Stripe from "stripe";
import { setupBefore } from "tests/before.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { expectAttachCorrect } from "tests/utils/expectUtils/expectAttach.js";
import { createProducts, createReward } from "tests/utils/productUtils.js";
import {
addPrefixToProducts,
getBasePrice,
} from "tests/utils/testProductUtils/testProductUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import {
constructArrearItem,
constructFeatureItem,
} from "@/utils/scriptUtils/constructItem.js";
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
const pro = constructProduct({
type: "pro",
items: [constructArrearItem({ featureId: TestFeature.Words })],
});
const oneOff = constructProduct({
type: "one_off",
items: [
constructFeatureItem({
featureId: TestFeature.Words,
includedUsage: 500,
}),
],
});
// Create reward input
const rewardId = "attach_coupon";
const promoCode = "attach_coupon_code";
const reward: CreateReward = {
id: rewardId,
name: "attach_coupon",
promo_codes: [{ code: promoCode }],
type: RewardType.FixedDiscount,
discount_config: {
discount_value: 5,
duration_type: CouponDurationType.OneOff,
duration_value: 1,
should_rollover: true,
apply_to_all: true,
price_ids: [],
},
};
const testCase = "coupon3";
describe(chalk.yellow(`${testCase} - Testing attach coupon`), () => {
const customerId = testCase;
let stripeCli: Stripe;
let testClockId: string;
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
let org: Organization;
let env: AppEnv;
let db: DrizzleCli;
const couponAmount = reward.discount_config!.discount_value;
before(async function () {
await setupBefore(this);
org = this.org;
env = this.env;
db = this.db;
stripeCli = this.stripeCli;
const { testClockId: testClockId1 } = await initCustomer({
customerId,
org: this.org,
env: this.env,
db: this.db,
autumn: this.autumnJs,
attachPm: "success",
});
testClockId = testClockId1;
addPrefixToProducts({
products: [pro, oneOff],
prefix: testCase,
});
await createProducts({
orgId: this.org.id,
env: this.env,
db: this.db,
autumn,
products: [pro, oneOff],
});
await createReward({
orgId: org.id,
env,
db,
autumn,
reward,
productId: pro.id,
});
});
// CYCLE 0
it("should attach pro with reward ID", async () => {
const res = await autumn.attach({
customer_id: customerId,
product_id: pro.id,
reward: rewardId,
});
const customer = await autumn.customers.get(customerId);
expectAttachCorrect({
customer,
product: pro,
});
const invoice = customer.invoices![0];
const basePrice = getBasePrice({ product: pro });
expect(invoice.total).to.equal(basePrice - couponAmount);
});
it("should attach one off with reward ID", async () => {
await autumn.attach({
customer_id: customerId,
product_id: oneOff.id,
reward: rewardId,
});
const customer = await autumn.customers.get(customerId);
expectAttachCorrect({
customer,
product: oneOff,
});
const invoice = customer.invoices![0];
const basePrice = getBasePrice({ product: oneOff });
expect(invoice.total).to.equal(basePrice - couponAmount);
expect(invoice.product_ids).to.include(oneOff.id);
});
it("should attach one off with promo code", async () => {
await autumn.attach({
customer_id: customerId,
product_id: oneOff.id,
reward: promoCode,
});
const customer = await autumn.customers.get(customerId);
expectAttachCorrect({
customer,
product: oneOff,
});
expect(customer.invoices!.length).to.equal(3);
const basePrice = getBasePrice({ product: oneOff });
for (let i = 0; i < 2; i++) {
const invoice = customer.invoices![i];
expect(invoice.total).to.equal(basePrice - couponAmount);
expect(invoice.product_ids).to.include(oneOff.id);
}
});
});

View File

@@ -0,0 +1,145 @@
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import { addHours, addMonths } from "date-fns";
import type Stripe from "stripe";
import { addPrefixToProducts } from "tests/attach/utils.js";
import { setupBefore } from "tests/before.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
import { createProducts } from "tests/utils/productUtils.js";
import { advanceTestClock } from "tests/utils/stripeUtils.js";
import { getBasePrice } from "tests/utils/testProductUtils/testProductUtils.js";
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 { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
const testCase = "customInterval1";
export const pro = constructProduct({
items: [
constructFeatureItem({
featureId: TestFeature.Words,
intervalCount: 2,
includedUsage: 500,
}),
],
intervalCount: 2,
type: "pro",
});
export const premium = constructProduct({
items: [
constructFeatureItem({
featureId: TestFeature.Words,
intervalCount: 2,
}),
// constructArrearItem({ featureId: TestFeature.Words }),
// constructArrearProratedItem({
// featureId: TestFeature.Users,
// pricePerUnit: 30,
// }),
],
intervalCount: 2,
type: "premium",
});
describe(`${chalk.yellowBright(`${testCase}: Testing custom interval and interval count`)}`, () => {
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;
before(async function () {
await setupBefore(this);
const { autumnJs } = this;
db = this.db;
org = this.org;
env = this.env;
stripeCli = this.stripeCli;
const { testClockId: testClockId1 } = await initCustomer({
autumn: autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
addPrefixToProducts({
products: [pro, premium],
prefix: testCase,
});
await createProducts({
autumn,
products: [pro, premium],
db,
orgId: org.id,
env,
});
testClockId = testClockId1!;
});
it("should attach pro product", async () => {
await attachAndExpectCorrect({
autumn,
customerId,
product: pro,
stripeCli,
db,
org,
env,
});
});
const usage = 100012;
it("should upgrade to premium product and have correct invoice next cycle", async () => {
const curUnix = await advanceTestClock({
stripeCli,
testClockId,
advanceTo: addMonths(new Date(), 1).getTime(),
waitForSeconds: 15,
});
await attachAndExpectCorrect({
autumn,
customerId,
product: premium,
stripeCli,
db,
org,
env,
});
const customer = await autumn.customers.get(customerId);
expect(customer.invoices.length).to.equal(2);
const nextUnix = await advanceTestClock({
stripeCli,
testClockId,
advanceTo: addHours(
addMonths(new Date(curUnix), 1),
hoursToFinalizeInvoice,
).getTime(),
waitForSeconds: 30,
});
const customer2 = await autumn.customers.get(customerId);
const invoices = customer2.invoices;
expect(invoices.length).to.equal(3);
expect(invoices[0].product_ids).to.include(premium.id);
expect(invoices[0].total).to.equal(getBasePrice({ product: premium }));
const wordsFeature = customer2.features[TestFeature.Words];
// @ts-expect-error
expect(wordsFeature.interval_count).to.equal(2);
});
});

View File

@@ -0,0 +1,129 @@
import { LegacyVersion } from "@autumn/shared";
import { beforeAll, describe, expect, test } from "bun:test";
import chalk from "chalk";
import { addHours, addMonths } from "date-fns";
import type Stripe from "stripe";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
import { getBasePrice } from "tests/utils/testProductUtils/testProductUtils.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";
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
const testCase = "customInterval1";
export const pro = constructProduct({
items: [
constructFeatureItem({
featureId: TestFeature.Words,
intervalCount: 2,
includedUsage: 500,
}),
],
intervalCount: 2,
type: "pro",
});
export const premium = constructProduct({
items: [
constructFeatureItem({
featureId: TestFeature.Words,
intervalCount: 2,
}),
// constructArrearItem({ featureId: TestFeature.Words }),
// constructArrearProratedItem({
// featureId: TestFeature.Users,
// pricePerUnit: 30,
// }),
],
intervalCount: 2,
type: "premium",
});
describe(`${chalk.yellowBright(`${testCase}: Testing custom interval and interval count`)}`, () => {
const customerId = testCase;
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
let testClockId: string;
let stripeCli: Stripe;
beforeAll(async () => {
stripeCli = ctx.stripeCli;
const { testClockId: testClockId1 } = await initCustomerV3({
ctx,
customerId,
customerData: {},
attachPm: "success",
withTestClock: true,
});
await initProductsV0({
ctx,
products: [pro, premium],
prefix: testCase,
customerId,
});
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 usage = 100012;
test("should upgrade to premium product and have correct invoice next cycle", async () => {
const curUnix = await advanceTestClock({
stripeCli,
testClockId,
advanceTo: addMonths(new Date(), 1).getTime(),
waitForSeconds: 15,
});
await attachAndExpectCorrect({
autumn,
customerId,
product: premium,
stripeCli: ctx.stripeCli,
db: ctx.db,
org: ctx.org,
env: ctx.env,
});
const customer = await autumn.customers.get(customerId);
expect(customer.invoices.length).toBe(2);
const nextUnix = await advanceTestClock({
stripeCli,
testClockId,
advanceTo: addHours(
addMonths(new Date(curUnix), 1),
hoursToFinalizeInvoice,
).getTime(),
waitForSeconds: 30,
});
const customer2 = await autumn.customers.get(customerId);
const invoices = customer2.invoices;
expect(invoices.length).toBe(3);
expect(invoices[0].product_ids).toContain(premium.id);
expect(invoices[0].total).toBe(getBasePrice({ product: premium }));
const wordsFeature = customer2.features[TestFeature.Words];
// @ts-expect-error
expect(wordsFeature.interval_count).toBe(2);
});
});

View File

@@ -0,0 +1,118 @@
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import { addHours, addMonths } from "date-fns";
import type Stripe from "stripe";
import { addPrefixToProducts } from "tests/attach/utils.js";
import { setupBefore } from "tests/before.js";
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 { createProducts } from "tests/utils/productUtils.js";
import { advanceTestClock } from "tests/utils/stripeUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
import { constructRawProduct } from "@/utils/scriptUtils/createTestProducts.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
const testCase = "customInterval2";
export const pro = constructRawProduct({
id: "pro",
items: [
constructArrearItem({
includedUsage: 0,
featureId: TestFeature.Words,
intervalCount: 2,
}),
],
});
describe(`${chalk.yellowBright(`${testCase}: Testing custom interval on arrear prorated price`)}`, () => {
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;
before(async function () {
await setupBefore(this);
const { autumnJs } = this;
db = this.db;
org = this.org;
env = this.env;
stripeCli = this.stripeCli;
const { testClockId: testClockId1 } = await initCustomer({
autumn: autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
addPrefixToProducts({
products: [pro],
prefix: testCase,
});
await createProducts({
autumn,
products: [pro],
db,
orgId: org.id,
env,
});
testClockId = testClockId1!;
});
it("should attach pro product", async () => {
await attachAndExpectCorrect({
autumn,
customerId,
product: pro,
stripeCli,
db,
org,
env,
});
});
const usage = 100012;
it("should upgrade to premium product and have correct invoice next cycle", async () => {
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Words,
value: usage,
});
const curUnix = await advanceTestClock({
stripeCli,
testClockId,
advanceTo: addHours(
addMonths(new Date(), 2),
hoursToFinalizeInvoice,
).getTime(),
waitForSeconds: 30,
});
const invoiceAmount = await getExpectedInvoiceTotal({
customerId,
productId: pro.id,
usage: [{ featureId: TestFeature.Words, value: usage }],
stripeCli,
db,
org,
env,
});
const customer = await autumn.customers.get(customerId);
expect(customer.invoices.length).to.equal(2);
expect(invoiceAmount).to.equal(customer.invoices[0].total);
});
});

View File

@@ -0,0 +1,102 @@
import { LegacyVersion } from "@autumn/shared";
import { beforeAll, describe, expect, test } from "bun:test";
import chalk from "chalk";
import { addHours, addMonths } from "date-fns";
import type Stripe from "stripe";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
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 { AutumnInt } from "@/external/autumn/autumnCli.js";
import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
import { constructRawProduct } from "@/utils/scriptUtils/createTestProducts.js";
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
const testCase = "customInterval2";
export const pro = constructRawProduct({
id: "pro",
items: [
constructArrearItem({
includedUsage: 0,
featureId: TestFeature.Words,
intervalCount: 2,
}),
],
});
describe(`${chalk.yellowBright(`${testCase}: Testing custom interval on arrear prorated price`)}`, () => {
const customerId = testCase;
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
let testClockId: string;
let stripeCli: Stripe;
beforeAll(async () => {
stripeCli = ctx.stripeCli;
const { testClockId: testClockId1 } = await initCustomerV3({
ctx,
customerId,
customerData: {},
attachPm: "success",
withTestClock: true,
});
await initProductsV0({
ctx,
products: [pro],
prefix: testCase,
customerId,
});
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 usage = 100012;
test("should upgrade to premium product and have correct invoice next cycle", async () => {
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Words,
value: usage,
});
const curUnix = await advanceTestClock({
stripeCli,
testClockId,
advanceTo: addHours(
addMonths(new Date(), 2),
hoursToFinalizeInvoice,
).getTime(),
waitForSeconds: 30,
});
const invoiceAmount = await getExpectedInvoiceTotal({
customerId,
productId: pro.id,
usage: [{ featureId: TestFeature.Words, value: usage }],
stripeCli: ctx.stripeCli,
db: ctx.db,
org: ctx.org,
env: ctx.env,
});
const customer = await autumn.customers.get(customerId);
expect(customer.invoices.length).toBe(2);
expect(invoiceAmount).toBe(customer.invoices[0].total);
});
});

View File

@@ -0,0 +1,160 @@
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import { addDays, addMonths } from "date-fns";
import type Stripe from "stripe";
import { addPrefixToProducts } from "tests/attach/utils.js";
import { setupBefore } from "tests/before.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
import { createProducts } from "tests/utils/productUtils.js";
import { advanceTestClock } from "tests/utils/stripeUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { calculateProrationAmount } from "@/internal/invoices/prorationUtils.js";
import {
constructFeatureItem,
constructPrepaidItem,
} from "@/utils/scriptUtils/constructItem.js";
import {
constructProduct,
constructRawProduct,
} from "@/utils/scriptUtils/createTestProducts.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
const testCase = "customInterval3";
export const pro = constructProduct({
type: "pro",
items: [
constructFeatureItem({
featureId: TestFeature.Words,
intervalCount: 2,
}),
],
intervalCount: 2,
});
const prepaidWordsItem = constructPrepaidItem({
featureId: TestFeature.Words,
price: 10,
billingUnits: 1,
includedUsage: 0,
intervalCount: 2,
});
export const addOn = constructRawProduct({
id: "addOn",
items: [prepaidWordsItem],
isAddOn: true,
});
describe(`${chalk.yellowBright(`${testCase}: Testing custom interval on add on merged product`)}`, () => {
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;
before(async function () {
await setupBefore(this);
const { autumnJs } = this;
db = this.db;
org = this.org;
env = this.env;
stripeCli = this.stripeCli;
const { testClockId: testClockId1 } = await initCustomer({
autumn: autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
addPrefixToProducts({
products: [pro, addOn],
prefix: testCase,
});
await createProducts({
autumn,
products: [pro, addOn],
db,
orgId: org.id,
env,
});
testClockId = testClockId1!;
});
it("should attach pro product", async () => {
await attachAndExpectCorrect({
autumn,
customerId,
product: pro,
stripeCli,
db,
org,
env,
});
});
it("should upgrade to attached add on and have correct invoice next cycle", async () => {
const curUnix = await advanceTestClock({
stripeCli,
testClockId,
advanceTo: addDays(new Date(), 20).getTime(),
waitForSeconds: 15,
});
const wordBillingSets = 2;
const wordsBillingUnits = prepaidWordsItem.billing_units! * wordBillingSets;
await autumn.attach({
customer_id: customerId,
product_id: addOn.id,
options: [
{
feature_id: TestFeature.Words,
quantity: wordsBillingUnits,
},
],
});
const customer = await autumn.customers.get(customerId);
const proProduct = customer.products.find((p) => p.id === pro.id);
const invoices = customer.invoices;
expectProductAttached({
customer,
product: pro,
});
expectProductAttached({
customer,
product: addOn,
});
const expectedPrice = wordsBillingUnits * prepaidWordsItem.price!;
const proratedPrice = calculateProrationAmount({
amount: expectedPrice,
periodStart: new Date().getTime(),
periodEnd: addMonths(new Date(), 2).getTime(),
now: curUnix!,
});
expect(invoices[0].product_ids).to.include(addOn.id);
expect(invoices[0].total).to.approximately(proratedPrice, 0.1);
const expectedAddonEnd = addMonths(new Date(), 2);
const approximate = 1000 * 60 * 60 * 24; // +- 1 day
const addOnProduct = customer.products.find((p) => p.id === addOn.id);
expect(addOnProduct?.current_period_end).to.be.approximately(
expectedAddonEnd.getTime(),
approximate,
);
});
});

View File

@@ -0,0 +1,144 @@
import { LegacyVersion } from "@autumn/shared";
import { beforeAll, describe, expect, test } from "bun:test";
import chalk from "chalk";
import { addDays, addMonths } from "date-fns";
import type Stripe from "stripe";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { calculateProrationAmount } from "@/internal/invoices/prorationUtils.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";
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
const testCase = "customInterval3";
export const pro = constructProduct({
type: "pro",
items: [
constructFeatureItem({
featureId: TestFeature.Words,
intervalCount: 2,
}),
],
intervalCount: 2,
});
const prepaidWordsItem = constructPrepaidItem({
featureId: TestFeature.Words,
price: 10,
billingUnits: 1,
includedUsage: 0,
intervalCount: 2,
});
export const addOn = constructRawProduct({
id: "addOn",
items: [prepaidWordsItem],
isAddOn: true,
});
describe(`${chalk.yellowBright(`${testCase}: Testing custom interval on add on merged product`)}`, () => {
const customerId = testCase;
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
let testClockId: string;
let stripeCli: Stripe;
beforeAll(async () => {
stripeCli = ctx.stripeCli;
const { testClockId: testClockId1 } = await initCustomerV3({
ctx,
customerId,
customerData: {},
attachPm: "success",
withTestClock: true,
});
await initProductsV0({
ctx,
products: [pro, addOn],
prefix: testCase,
customerId,
});
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,
});
});
test("should upgrade to attached add on and have correct invoice next cycle", async () => {
const curUnix = await advanceTestClock({
stripeCli,
testClockId,
advanceTo: addDays(new Date(), 20).getTime(),
waitForSeconds: 15,
});
const wordBillingSets = 2;
const wordsBillingUnits = prepaidWordsItem.billing_units! * wordBillingSets;
await autumn.attach({
customer_id: customerId,
product_id: addOn.id,
options: [
{
feature_id: TestFeature.Words,
quantity: wordsBillingUnits,
},
],
});
const customer = await autumn.customers.get(customerId);
const proProduct = customer.products.find((p) => p.id === pro.id);
const invoices = customer.invoices;
expectProductAttached({
customer,
product: pro,
});
expectProductAttached({
customer,
product: addOn,
});
const expectedPrice = wordsBillingUnits * prepaidWordsItem.price!;
const proratedPrice = calculateProrationAmount({
amount: expectedPrice,
periodStart: new Date().getTime(),
periodEnd: addMonths(new Date(), 2).getTime(),
now: curUnix!,
});
expect(invoices[0].product_ids).toContain(addOn.id);
expect(invoices[0].total).toBeCloseTo(proratedPrice, 1);
const expectedAddonEnd = addMonths(new Date(), 2);
const approximate = 1000 * 60 * 60 * 24; // +- 1 day
const addOnProduct = customer.products.find((p) => p.id === addOn.id);
expect(addOnProduct?.current_period_end).toBeCloseTo(
expectedAddonEnd.getTime(),
-Math.log10(approximate),
);
});
});

View File

@@ -0,0 +1,149 @@
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import { addMonths } from "date-fns";
import type Stripe from "stripe";
import { addPrefixToProducts } from "tests/attach/utils.js";
import { setupBefore } from "tests/before.js";
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 { createProducts } from "tests/utils/productUtils.js";
import { getBasePrice } from "tests/utils/testProductUtils/testProductUtils.js";
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 { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
const testCase = "customInterval4";
export const pro = constructProduct({
items: [
constructFeatureItem({
featureId: TestFeature.Words,
includedUsage: 500,
}),
],
intervalCount: 2,
type: "pro",
});
export const premium = constructProduct({
id: "premium",
items: [
constructFeatureItem({
featureId: TestFeature.Words,
includedUsage: 500,
}),
],
intervalCount: 2,
type: "premium",
});
describe(`${chalk.yellowBright(`${testCase}: Testing downgrades for custom intervals`)}`, () => {
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;
before(async function () {
await setupBefore(this);
const { autumnJs } = this;
db = this.db;
org = this.org;
env = this.env;
stripeCli = this.stripeCli;
const { testClockId: testClockId1 } = await initCustomer({
autumn: autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
addPrefixToProducts({
products: [pro, premium],
prefix: testCase,
});
await createProducts({
autumn,
products: [pro, premium],
db,
orgId: org.id,
env,
});
testClockId = testClockId1!;
});
it("should attach premium product", async () => {
await attachAndExpectCorrect({
autumn,
customerId,
product: premium,
stripeCli,
db,
org,
env,
});
});
it("should have correct next cycle at on checkout", async () => {
const checkout = await autumn.checkout({
customer_id: customerId,
product_id: pro.id,
});
const expectedNextCycle = addMonths(new Date(), 2);
expect(checkout.next_cycle?.starts_at).to.be.approximately(
expectedNextCycle.getTime(),
1000 * 60 * 60 * 24,
);
expect(checkout.total).to.equal(0);
});
let preview: any;
it("should downgrade to pro", async () => {
const { preview: preview_ } = await expectDowngradeCorrect({
autumn,
customerId,
curProduct: premium,
newProduct: pro,
stripeCli,
db,
org,
env,
});
preview = preview_;
});
it("should have pro attached on next cycle", async () => {
await expectNextCycleCorrect({
preview: preview!,
autumn,
stripeCli,
customerId,
testClockId,
product: pro,
db,
org,
env,
});
const customer = await autumn.customers.get(customerId);
const invoices = customer.invoices;
expect(invoices.length).to.equal(2);
expect(invoices[0].total).to.equal(getBasePrice({ product: pro }));
});
});

View File

@@ -0,0 +1,133 @@
import { LegacyVersion } from "@autumn/shared";
import { beforeAll, describe, expect, test } from "bun:test";
import chalk from "chalk";
import { addMonths } from "date-fns";
import type Stripe from "stripe";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
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 { getBasePrice } from "tests/utils/testProductUtils/testProductUtils.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";
const testCase = "customInterval4";
export const pro = constructProduct({
items: [
constructFeatureItem({
featureId: TestFeature.Words,
includedUsage: 500,
}),
],
intervalCount: 2,
type: "pro",
});
export const premium = constructProduct({
id: "premium",
items: [
constructFeatureItem({
featureId: TestFeature.Words,
includedUsage: 500,
}),
],
intervalCount: 2,
type: "premium",
});
describe(`${chalk.yellowBright(`${testCase}: Testing downgrades for custom intervals`)}`, () => {
const customerId = testCase;
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
let testClockId: string;
let stripeCli: Stripe;
beforeAll(async () => {
stripeCli = ctx.stripeCli;
const { testClockId: testClockId1 } = await initCustomerV3({
ctx,
customerId,
customerData: {},
attachPm: "success",
withTestClock: true,
});
await initProductsV0({
ctx,
products: [pro, premium],
prefix: testCase,
customerId,
});
testClockId = testClockId1!;
});
test("should attach premium product", async () => {
await attachAndExpectCorrect({
autumn,
customerId,
product: premium,
stripeCli: ctx.stripeCli,
db: ctx.db,
org: ctx.org,
env: ctx.env,
});
});
test("should have correct next cycle at on checkout", async () => {
const checkout = await autumn.checkout({
customer_id: customerId,
product_id: pro.id,
});
const expectedNextCycle = addMonths(new Date(), 2);
expect(checkout.next_cycle?.starts_at).toBeCloseTo(
expectedNextCycle.getTime(),
-Math.log10(1000 * 60 * 60 * 24),
);
expect(checkout.total).toBe(0);
});
let preview: any;
test("should downgrade to pro", async () => {
const { preview: preview_ } = await expectDowngradeCorrect({
autumn,
customerId,
curProduct: premium,
newProduct: pro,
stripeCli: ctx.stripeCli,
db: ctx.db,
org: ctx.org,
env: ctx.env,
});
preview = preview_;
});
test("should have pro attached on next cycle", async () => {
await expectNextCycleCorrect({
preview: preview!,
autumn,
stripeCli: ctx.stripeCli,
customerId,
testClockId,
product: pro,
db: ctx.db,
org: ctx.org,
env: ctx.env,
});
const customer = await autumn.customers.get(customerId);
const invoices = customer.invoices;
expect(invoices.length).toBe(2);
expect(invoices[0].total).toBe(getBasePrice({ product: pro }));
});
});

View File

@@ -0,0 +1,161 @@
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
import type { Customer } from "autumn-js";
import { expect } from "chai";
import chalk from "chalk";
import type Stripe from "stripe";
import { addPrefixToProducts } from "tests/attach/utils.js";
import { setupBefore } from "tests/before.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
import { createProducts } from "tests/utils/productUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
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 { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
const testCase = "customInterval5";
const includedUsage = 500;
const monthlyWords = constructFeatureItem({
featureId: TestFeature.Words,
includedUsage,
});
const biMonthlyWords = constructFeatureItem({
featureId: TestFeature.Words,
intervalCount: 2,
includedUsage,
});
export const pro = constructProduct({
items: [monthlyWords, biMonthlyWords],
intervalCount: 2,
type: "pro",
});
const getBreakdown = ({
customer,
intervalCount,
}: {
customer: Customer;
intervalCount: number;
}) => {
const wordsFeature = customer.features[TestFeature.Words];
return wordsFeature.breakdown?.find(
(b: any) => b.interval_count === intervalCount,
);
};
describe(`${chalk.yellowBright(`${testCase}: Testing multi interval features with custom intervals`)}`, () => {
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;
before(async function () {
await setupBefore(this);
const { autumnJs } = this;
db = this.db;
org = this.org;
env = this.env;
stripeCli = this.stripeCli;
const { testClockId: testClockId1 } = await initCustomer({
autumn: autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
addPrefixToProducts({
products: [pro],
prefix: testCase,
});
await createProducts({
autumn,
products: [pro],
db,
orgId: org.id,
env,
});
testClockId = testClockId1!;
});
it("should attach pro product", async () => {
await attachAndExpectCorrect({
autumn,
customerId,
product: pro,
stripeCli,
db,
org,
env,
});
const customer = await autumn.customers.get(customerId);
const wordsFeature = customer.features[TestFeature.Words];
// @ts-expect-error
expect(wordsFeature.interval_count).to.equal(null);
expect(wordsFeature.breakdown?.length).to.equal(2);
expect(
wordsFeature.breakdown?.some(
(b: any) => b.interval_count === 1 && b.interval === "month",
),
).to.equal(true);
expect(
wordsFeature.breakdown?.some(
(b: any) => b.interval_count === 2 && b.interval === "month",
),
).to.equal(true);
});
const trackVal = 300;
it("should have correct breakdown after usage", async () => {
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Words,
value: trackVal,
});
await timeout(3000);
const customer = await autumn.customers.get(customerId);
// Should deduct
const monthlyBreakdown = getBreakdown({ customer, intervalCount: 1 });
const biMonthlyBreakdown = getBreakdown({ customer, intervalCount: 2 });
expect(monthlyBreakdown?.balance).to.equal(includedUsage - trackVal);
expect(biMonthlyBreakdown?.balance).to.equal(includedUsage);
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Words,
value: trackVal,
});
await timeout(3000);
const customer2 = await autumn.customers.get(customerId);
const monthlyBreakdown2 = getBreakdown({
customer: customer2,
intervalCount: 1,
});
const biMonthlyBreakdown2 = getBreakdown({
customer: customer2,
intervalCount: 2,
});
expect(monthlyBreakdown2?.balance).to.equal(0);
expect(biMonthlyBreakdown2?.balance).to.equal(includedUsage - 100);
});
});

View File

@@ -0,0 +1,145 @@
import { LegacyVersion } from "@autumn/shared";
import type { Customer } from "autumn-js";
import { beforeAll, describe, expect, test } from "bun:test";
import chalk from "chalk";
import type Stripe from "stripe";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
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";
const testCase = "customInterval5";
const includedUsage = 500;
const monthlyWords = constructFeatureItem({
featureId: TestFeature.Words,
includedUsage,
});
const biMonthlyWords = constructFeatureItem({
featureId: TestFeature.Words,
intervalCount: 2,
includedUsage,
});
export const pro = constructProduct({
items: [monthlyWords, biMonthlyWords],
intervalCount: 2,
type: "pro",
});
const getBreakdown = ({
customer,
intervalCount,
}: {
customer: Customer;
intervalCount: number;
}) => {
const wordsFeature = customer.features[TestFeature.Words];
return wordsFeature.breakdown?.find(
(b: any) => b.interval_count === intervalCount,
);
};
describe(`${chalk.yellowBright(`${testCase}: Testing multi interval features with custom intervals`)}`, () => {
const customerId = testCase;
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
let testClockId: string;
let stripeCli: Stripe;
beforeAll(async () => {
stripeCli = ctx.stripeCli;
const { testClockId: testClockId1 } = await initCustomerV3({
ctx,
customerId,
customerData: {},
attachPm: "success",
withTestClock: true,
});
await initProductsV0({
ctx,
products: [pro],
prefix: testCase,
customerId,
});
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 customer = await autumn.customers.get(customerId);
const wordsFeature = customer.features[TestFeature.Words];
// @ts-expect-error
expect(wordsFeature.interval_count).toBe(null);
expect(wordsFeature.breakdown?.length).toBe(2);
expect(
wordsFeature.breakdown?.some(
(b: any) => b.interval_count === 1 && b.interval === "month",
),
).toBe(true);
expect(
wordsFeature.breakdown?.some(
(b: any) => b.interval_count === 2 && b.interval === "month",
),
).toBe(true);
});
const trackVal = 300;
test("should have correct breakdown after usage", async () => {
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Words,
value: trackVal,
});
await timeout(3000);
const customer = await autumn.customers.get(customerId);
// Should deduct
const monthlyBreakdown = getBreakdown({ customer, intervalCount: 1 });
const biMonthlyBreakdown = getBreakdown({ customer, intervalCount: 2 });
expect(monthlyBreakdown?.balance).toBe(includedUsage - trackVal);
expect(biMonthlyBreakdown?.balance).toBe(includedUsage);
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Words,
value: trackVal,
});
await timeout(3000);
const customer2 = await autumn.customers.get(customerId);
const monthlyBreakdown2 = getBreakdown({
customer: customer2,
intervalCount: 1,
});
const biMonthlyBreakdown2 = getBreakdown({
customer: customer2,
intervalCount: 2,
});
expect(monthlyBreakdown2?.balance).toBe(0);
expect(biMonthlyBreakdown2?.balance).toBe(includedUsage - 100);
});
});

View File

@@ -1,255 +0,0 @@
import {
type AppEnv,
CusExpand,
CusProductStatus,
ErrCode,
type Organization,
type ReferralCode,
type RewardRedemption,
} from "@autumn/shared";
import { assert } from "chai";
import chalk from "chalk";
import type { Stripe } from "stripe";
import { setupBefore } from "tests/before.js";
import { expectProductV1Attached } from "tests/utils/expectUtils/expectProductAttached.js";
import { advanceTestClock } from "tests/utils/stripeUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import AutumnError, { AutumnInt } from "@/external/autumn/autumnCli.js";
import { CusService } from "@/internal/customers/CusService.js";
import { RewardRedemptionService } from "@/internal/rewards/RewardRedemptionService.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { products, referralPrograms } from "../../../global.js";
export const group = "referrals13";
describe(`${chalk.yellowBright(
"referrals13: Testing referrals - referrer on Pro, gets discount on next cycle - coupon-based",
)}`, () => {
const mainCustomerId = "main-referral-13";
const redeemer = "referral13-r1";
const redeemerPM = "success";
const autumn: AutumnInt = new AutumnInt();
let stripeCli: Stripe;
const testClockIds: string[] = [];
let referralCode: ReferralCode;
let redemption: RewardRedemption;
let db: DrizzleCli;
let org: Organization;
let env: AppEnv;
before(async function () {
await setupBefore(this);
stripeCli = this.stripeCli;
db = this.db;
org = this.org;
env = this.env;
try {
await Promise.all([
autumn.customers.delete(mainCustomerId),
autumn.customers.delete(redeemer),
RewardRedemptionService._resetCustomerRedemptions({
db,
internalCustomerId: [mainCustomerId, redeemer],
}),
]);
} catch {}
// Initialize main customer with Pro product already attached
const res = await initCustomer({
autumn: this.autumnJs,
customerId: mainCustomerId,
db,
org,
env,
attachPm: "success",
});
testClockIds.push(res.testClockId);
// Attach Pro product to main customer first
await autumn.attach({
customer_id: mainCustomerId,
product_id: products.pro.id,
});
const redeemerRes = await initCustomer({
autumn: this.autumnJs,
customerId: redeemer,
db: this.db,
org: this.org,
env: this.env,
attachPm: redeemerPM,
withTestClock: true,
});
testClockIds.push(redeemerRes.testClockId);
});
it("should advance clock 10 days before redeeming", async () => {
// Advance 10 days after Pro is attached
await Promise.all(
testClockIds.map((x) =>
advanceTestClock({
testClockId: x,
numberOfDays: 10,
waitForSeconds: 10,
stripeCli,
}),
),
);
});
it("should create code once", async () => {
referralCode = await autumn.referrals.createCode({
customerId: mainCustomerId,
referralId: referralPrograms.paidProductImmediateReferrer.id,
});
assert.exists(referralCode.code);
});
it("should create redemption for redeemer and fail if redeemed again", async () => {
redemption = await autumn.referrals.redeem({
customerId: redeemer,
code: referralCode.code,
});
// Try redeem for redeemer again
try {
await autumn.referrals.redeem({
customerId: redeemer,
code: referralCode.code,
});
assert.fail("Should not be able to redeem again");
} catch (error) {
assert.instanceOf(error, AutumnError);
assert.equal(error.code, ErrCode.CustomerAlreadyRedeemedReferralCode);
}
});
it("should have referrer already on Pro, and redeemer gets free product", async () => {
const redemptionResult = await autumn.redemptions.get(redemption.id);
assert.equal(redemptionResult.redeemer_applied, true);
const mainProds = (await autumn.customers.get(mainCustomerId)).products;
const redeemerProds = (await autumn.customers.get(redeemer)).products;
// Main customer (referrer) should have the pro product (already attached)
assert.equal(mainProds.length, 1);
assert.equal(mainProds[0].id, products.pro.id);
// Redeemer should only have the free product (no pro product given in referrer-only program)
assert.equal(redeemerProds.length, 1);
assert.equal(redeemerProds[0].id, products.free.id);
expectProductV1Attached({
customer: await autumn.customers.get(mainCustomerId),
product: products.pro,
status: CusProductStatus.Active,
});
// Verify redeemer only has free product
expectProductV1Attached({
customer: await autumn.customers.get(redeemer),
product: products.free,
status: CusProductStatus.Active,
});
});
it("should advance test clock and verify referrer gets discount on next Pro cycle", async () => {
// Advance 31 days from current time to trigger next billing cycle
// Coupon was applied on day 10, lasts 30 days, so should still be active on day 31
await Promise.all(
testClockIds.map((x) =>
advanceTestClock({
testClockId: x,
numberOfDays: 31,
waitForSeconds: 25,
stripeCli,
}),
),
);
// Test that main customer's Pro invoice has discount applied
const mainCustomerWithInvoices = await autumn.customers.get(
mainCustomerId,
{
expand: [CusExpand.Invoices, CusExpand.Rewards],
},
);
const proInvoice = mainCustomerWithInvoices.invoices.find((x) =>
x.product_ids.includes(products.pro.id),
);
const expectedTotal = products.pro.prices[0].config.amount;
const actualTotal = proInvoice?.total;
if (proInvoice) {
// Should have a discount applied - invoice total should be less than full Pro price ($10)
assert.isBelow(
actualTotal!,
expectedTotal, // $10 in cents
"Pro invoice should have discount applied, making it less than full price",
);
// For referrer-only reward, the discount should make it significantly cheaper or free
assert.isAtMost(
actualTotal!,
expectedTotal / 2, // $5 or less in cents - assuming at least 50% discount
"Referrer should get substantial discount on Pro product",
);
}
const dbCustomers = await Promise.all(
[mainCustomerId, redeemer].map((x) =>
CusService.getFull({
db,
idOrInternalId: x,
orgId: org.id,
env,
inStatuses: [
CusProductStatus.Active,
CusProductStatus.PastDue,
CusProductStatus.Expired,
],
}),
),
);
const expectedProducts = [
[
// Main referrer - keeps Pro with discount applied
{ name: "Free", status: CusProductStatus.Expired },
{ name: "Pro", status: CusProductStatus.Active },
],
[
// Redeemer - only has free product (no reward in referrer-only program)
{ name: "Free", status: CusProductStatus.Active },
],
];
dbCustomers.forEach((customer, index) => {
const expectedProductsForCustomer = expectedProducts[index];
expectedProductsForCustomer.forEach((expectedProduct) => {
const matchingProduct = customer.customer_products.find(
(cp) =>
cp.product.name === expectedProduct.name &&
cp.status === expectedProduct.status,
);
const unMatchedProduct = customer.customer_products.find(
(cp) => cp.product.name === expectedProduct.name,
);
assert.exists(
matchingProduct,
`Customer ${customer.name} should have ${expectedProduct.name} product with status ${expectedProduct.status}. ${unMatchedProduct ? `However ${unMatchedProduct.product.name} with status ${unMatchedProduct.status} was found instead` : ""}`,
);
});
});
});
});

View File

@@ -1,264 +0,0 @@
import {
type AppEnv,
CusExpand,
CusProductStatus,
ErrCode,
type Organization,
type ReferralCode,
type RewardRedemption,
} from "@autumn/shared";
import { assert } from "chai";
import chalk from "chalk";
import type { Stripe } from "stripe";
import { setupBefore } from "tests/before.js";
import { expectProductV1Attached } from "tests/utils/expectUtils/expectProductAttached.js";
import { advanceTestClock } from "tests/utils/stripeUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import AutumnError, { AutumnInt } from "@/external/autumn/autumnCli.js";
import { CusService } from "@/internal/customers/CusService.js";
import { RewardRedemptionService } from "@/internal/rewards/RewardRedemptionService.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { products, referralPrograms } from "../../../global.js";
export const group = "referrals14";
describe(`${chalk.yellowBright(
"referrals14: Testing referrals - referrer on Premium (higher tier), gets pro_amount discount - coupon-based",
)}`, () => {
const mainCustomerId = "main-referral-14";
const redeemer = "referral14-r1";
const redeemerPM = "success";
const autumn: AutumnInt = new AutumnInt();
let stripeCli: Stripe;
const testClockIds: string[] = [];
let referralCode: ReferralCode;
let redemption: RewardRedemption;
let db: DrizzleCli;
let org: Organization;
let env: AppEnv;
before(async function () {
await setupBefore(this);
stripeCli = this.stripeCli;
db = this.db;
org = this.org;
env = this.env;
try {
await Promise.all([
autumn.customers.delete(mainCustomerId, { deleteInStripe: true }),
autumn.customers.delete(redeemer, { deleteInStripe: true }),
RewardRedemptionService._resetCustomerRedemptions({
db,
internalCustomerId: [mainCustomerId, redeemer],
}),
]);
} catch {}
// Initialize main customer with Premium product already attached
const res = await initCustomer({
autumn: this.autumnJs,
customerId: mainCustomerId,
db,
org,
env,
attachPm: "success",
});
testClockIds.push(res.testClockId);
// Attach Premium product to main customer first (higher tier than Pro)
await autumn.attach({
customer_id: mainCustomerId,
product_id: products.premium.id,
});
const redeemerRes = await initCustomer({
autumn: this.autumnJs,
customerId: redeemer,
db: this.db,
org: this.org,
env: this.env,
attachPm: redeemerPM,
withTestClock: true,
});
testClockIds.push(redeemerRes.testClockId);
// Advance 10 days after Premium is attached, then redeem the code
await Promise.all(
testClockIds.map((x) =>
advanceTestClock({
testClockId: x,
numberOfDays: 10,
waitForSeconds: 5,
stripeCli,
}),
),
);
});
it("should create code once", async () => {
referralCode = await autumn.referrals.createCode({
customerId: mainCustomerId,
referralId: referralPrograms.paidProductImmediateReferrer.id,
});
assert.exists(referralCode.code);
// Get referral code again
const referralCode2 = await autumn.referrals.createCode({
customerId: mainCustomerId,
referralId: referralPrograms.paidProductImmediateReferrer.id,
});
assert.equal(referralCode2.code, referralCode.code);
});
it("should create redemption for redeemer and fail if redeemed again", async () => {
redemption = await autumn.referrals.redeem({
customerId: redeemer,
code: referralCode.code,
});
// Try redeem for redeemer again
try {
await autumn.referrals.redeem({
customerId: redeemer,
code: referralCode.code,
});
assert.fail("Should not be able to redeem again");
} catch (error) {
assert.instanceOf(error, AutumnError);
assert.equal(error.code, ErrCode.CustomerAlreadyRedeemedReferralCode);
}
});
it("should have referrer already on Premium, and redeemer gets free product", async () => {
const redemptionResult = await autumn.redemptions.get(redemption.id);
assert.equal(redemptionResult.redeemer_applied, true);
const mainCus = await autumn.customers.get(mainCustomerId);
const redeemerCus = await autumn.customers.get(redeemer);
const mainProds = mainCus.products;
const redeemerProds = redeemerCus.products;
// Main customer (referrer) should have the premium product (already attached)
assert.equal(mainProds.length, 1);
assert.equal(mainProds[0].id, products.premium.id);
// Redeemer should only have the free product (no pro product given in referrer-only program)
assert.equal(redeemerProds.length, 1);
assert.equal(redeemerProds[0].id, products.free.id);
expectProductV1Attached({
customer: mainCus,
product: products.premium,
status: CusProductStatus.Active,
});
// Verify redeemer only has free product
expectProductV1Attached({
customer: redeemerCus,
product: products.free,
status: CusProductStatus.Active,
});
});
it("should advance test clock and verify referrer gets pro_amount discount on Premium cycle", async () => {
// Advance 21 more days (total 31 days from start) to trigger next billing cycle
// Coupon was applied on day 10, lasts 30 days, so should still be active on day 31
await Promise.all(
testClockIds.map((x) =>
advanceTestClock({
testClockId: x,
numberOfDays: 31,
waitForSeconds: 25,
stripeCli,
}),
),
);
// Test that main customer's Premium invoice has pro_amount discount applied
const mainCustomerWithInvoices = await autumn.customers.get(
mainCustomerId,
{
expand: [CusExpand.Invoices],
},
);
const premiumInvoice = mainCustomerWithInvoices.invoices.find((x) =>
x.product_ids.includes(products.premium.id),
);
if (premiumInvoice) {
// Premium costs $50, Pro costs $10 - so referrer should get $10 discount on Premium
// Expected: Premium ($50) - Pro amount ($10) = $40
console.log(products.premium.prices);
const premiumPrice = products.premium.prices[0].config.amount; // $50
const proAmount = products.pro.prices[0].config.amount; // $10 (pro_amount discount)
const expectedTotal = premiumPrice - proAmount; // $40
// The invoice total should be exactly Premium price minus pro_amount
assert.equal(
premiumInvoice.total,
expectedTotal,
`Premium invoice should be $40 (Premium $50 - Pro amount $10 discount). Got $${premiumInvoice.total}`,
);
// Verify that the discount was applied (total is less than full Premium price)
assert.isBelow(
premiumInvoice.total,
premiumPrice,
"Referrer on Premium should get pro_amount discount, making it less than full Premium price",
);
}
const dbCustomers = await Promise.all(
[mainCustomerId, redeemer].map((x) =>
CusService.getFull({
db,
idOrInternalId: x,
orgId: org.id,
env,
inStatuses: [
CusProductStatus.Active,
CusProductStatus.PastDue,
CusProductStatus.Expired,
],
}),
),
);
const expectedProducts = [
[
// Main referrer - keeps Premium with pro_amount discount applied
{ name: "Free", status: CusProductStatus.Expired },
{ name: "Premium", status: CusProductStatus.Active },
],
[
// Redeemer - only has free product (no reward in referrer-only program)
{ name: "Free", status: CusProductStatus.Active },
],
];
dbCustomers.forEach((customer, index) => {
const expectedProductsForCustomer = expectedProducts[index];
expectedProductsForCustomer.forEach((expectedProduct) => {
const matchingProduct = customer.customer_products.find(
(cp) =>
cp.product.name === expectedProduct.name &&
cp.status === expectedProduct.status,
);
const unMatchedProduct = customer.customer_products.find(
(cp) => cp.product.name === expectedProduct.name,
);
assert.exists(
matchingProduct,
`Customer ${customer.name} should have ${expectedProduct.name} product with status ${expectedProduct.status}. ${unMatchedProduct ? `However ${unMatchedProduct.product.name} with status ${unMatchedProduct.status} was found instead` : ""}`,
);
});
});
});
});

View File

@@ -1,296 +0,0 @@
import {
type AppEnv,
CusExpand,
CusProductStatus,
ErrCode,
type Organization,
type ReferralCode,
type RewardRedemption,
} from "@autumn/shared";
import { assert } from "chai";
import chalk from "chalk";
import type { Stripe } from "stripe";
import { setupBefore } from "tests/before.js";
import { expectProductV1Attached } from "tests/utils/expectUtils/expectProductAttached.js";
import { advanceTestClock } from "tests/utils/stripeUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import AutumnError, { AutumnInt } from "@/external/autumn/autumnCli.js";
import { CusService } from "@/internal/customers/CusService.js";
import { RewardRedemptionService } from "@/internal/rewards/RewardRedemptionService.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { products, referralPrograms } from "../../../global.js";
export const group = "referrals15";
describe(`${chalk.yellowBright(
"referrals15: Testing referrals - referrer starts with no product, gets pro_amount discount - immediate, both - coupon-based",
)}`, () => {
const mainCustomerId = "main-referral-15";
const redeemer = "referral15-r1";
const redeemerPM = "success";
const autumn: AutumnInt = new AutumnInt();
let stripeCli: Stripe;
const testClockIds: string[] = [];
let referralCode: ReferralCode;
let redemption: RewardRedemption;
let db: DrizzleCli;
let org: Organization;
let env: AppEnv;
before(async function () {
await setupBefore(this);
stripeCli = this.stripeCli;
db = this.db;
org = this.org;
env = this.env;
try {
await Promise.all([
autumn.customers.delete(mainCustomerId, { deleteInStripe: true }),
autumn.customers.delete(redeemer, { deleteInStripe: true }),
RewardRedemptionService._resetCustomerRedemptions({
db,
internalCustomerId: [mainCustomerId, redeemer],
}),
]);
} catch {}
// Initialize main customer with NO paid product (just free tier)
const res = await initCustomer({
autumn: this.autumnJs,
customerId: mainCustomerId,
db,
org,
env,
attachPm: "success",
});
testClockIds.push(res.testClockId);
const redeemerRes = await initCustomer({
autumn: this.autumnJs,
customerId: redeemer,
db: this.db,
org: this.org,
env: this.env,
attachPm: redeemerPM,
withTestClock: true,
});
testClockIds.push(redeemerRes.testClockId);
});
it("should advance clock 10 days before redeeming", async () => {
// Advance 10 days after setup
await Promise.all(
testClockIds.map((x) =>
advanceTestClock({
testClockId: x,
numberOfDays: 10,
waitForSeconds: 10,
stripeCli,
}),
),
);
});
it("should create code once", async () => {
referralCode = await autumn.referrals.createCode({
customerId: mainCustomerId,
referralId: referralPrograms.paidProductImmediateAll.id,
});
assert.exists(referralCode.code);
// Get referral code again
const referralCode2 = await autumn.referrals.createCode({
customerId: mainCustomerId,
referralId: referralPrograms.paidProductImmediateAll.id,
});
assert.equal(referralCode2.code, referralCode.code);
});
it("should create redemption for redeemer and fail if redeemed again", async () => {
redemption = await autumn.referrals.redeem({
customerId: redeemer,
code: referralCode.code,
});
// Try redeem for redeemer again
try {
await autumn.referrals.redeem({
customerId: redeemer,
code: referralCode.code,
});
assert.fail("Should not be able to redeem again");
} catch (error) {
assert.instanceOf(error, AutumnError);
assert.equal(error.code, ErrCode.CustomerAlreadyRedeemedReferralCode);
}
});
it("should have both referrer and redeemer get pro product", async () => {
const redemptionResult = await autumn.redemptions.get(redemption.id);
assert.equal(redemptionResult.redeemer_applied, true);
const mainCus = await autumn.customers.get(mainCustomerId);
const redeemerCus = await autumn.customers.get(redeemer);
const mainProds = mainCus.products;
const redeemerProds = redeemerCus.products;
// Main customer (referrer) should now have the pro product
assert.equal(mainProds.length, 1);
assert.equal(mainProds[0].id, products.pro.id);
// Redeemer should also have the pro product (both get reward)
assert.equal(redeemerProds.length, 1);
assert.equal(redeemerProds[0].id, products.pro.id);
expectProductV1Attached({
customer: mainCus,
product: products.pro,
status: CusProductStatus.Active,
});
expectProductV1Attached({
customer: redeemerCus,
product: products.pro,
status: CusProductStatus.Active,
});
});
it("should advance test clock and verify both customers get pro_amount discount on Pro cycle", async () => {
// Advance 31 days from current time to trigger next billing cycle
// Coupon was applied on day 10, lasts 30 days, so should still be active on day 31
await Promise.all(
testClockIds.map((x) =>
advanceTestClock({
testClockId: x,
numberOfDays: 31,
waitForSeconds: 25,
stripeCli,
}),
),
);
// Test that both customers' Pro invoices have pro_amount discount applied
const [mainCustomerWithInvoices, redeemerWithInvoices] = await Promise.all([
autumn.customers.get(mainCustomerId, {
expand: [CusExpand.Invoices],
}),
autumn.customers.get(redeemer, {
expand: [CusExpand.Invoices],
}),
]);
// console.log(
// "Main Customer Invoices:\n",
// mainCustomerWithInvoices.invoices
// .map(
// (x) =>
// `${x.product_ids.join(", ")}: ${x.total} | ${new Date(x.created_at).toLocaleDateString()}`,
// )
// .join("\n"),
// );
// console.log(
// "Redeemer Invoices:\n",
// redeemerWithInvoices.invoices
// .map(
// (x) =>
// `${x.product_ids.join(", ")}: ${x.total} | ${new Date(x.created_at).toLocaleDateString()}`,
// )
// .join("\n"),
// );
// Check main customer (referrer) invoice
const mainProInvoice = mainCustomerWithInvoices.invoices.find((x) =>
x.product_ids.includes(products.pro.id),
);
if (mainProInvoice) {
// Pro costs $10, so with pro_amount discount it should be $0 (Pro - Pro amount = $0)
const proPrice = products.pro.prices[0].config.amount; // $10
const proAmount = products.pro.prices[0].config.amount; // $10 (pro_amount discount)
const expectedTotal = proPrice - proAmount; // $0
// console.log("Main customer expected total:", expectedTotal);
// console.log("Main customer Pro invoice total:", mainProInvoice.total);
assert.equal(
mainProInvoice.total,
expectedTotal,
`Main customer Pro invoice should be $0 (Pro $10 - Pro amount $10 discount). Got $${mainProInvoice.total}`,
);
}
// Check redeemer invoice
const redeemerProInvoice = redeemerWithInvoices.invoices.find((x) =>
x.product_ids.includes(products.pro.id),
);
if (redeemerProInvoice) {
// Pro costs $10, so with pro_amount discount it should be $0 (Pro - Pro amount = $0)
const proPrice = products.pro.prices[0].config.amount; // $10
const proAmount = products.pro.prices[0].config.amount; // $10 (pro_amount discount)
const expectedTotal = proPrice - proAmount; // $0
// console.log("Redeemer expected total:", expectedTotal);
// console.log("Redeemer Pro invoice total:", redeemerProInvoice.total);
assert.equal(
redeemerProInvoice.total,
expectedTotal,
`Redeemer Pro invoice should be $0 (Pro $10 - Pro amount $10 discount). Got $${redeemerProInvoice.total}`,
);
}
const dbCustomers = await Promise.all(
[mainCustomerId, redeemer].map((x) =>
CusService.getFull({
db,
idOrInternalId: x,
orgId: org.id,
env,
inStatuses: [
CusProductStatus.Active,
CusProductStatus.PastDue,
CusProductStatus.Expired,
],
}),
),
);
const expectedProducts = [
[
// Main referrer - has Pro with pro_amount discount applied
{ name: "Free", status: CusProductStatus.Expired },
{ name: "Pro", status: CusProductStatus.Active },
],
[
// Redeemer - also has Pro with pro_amount discount applied
{ name: "Free", status: CusProductStatus.Expired },
{ name: "Pro", status: CusProductStatus.Active },
],
];
dbCustomers.forEach((customer, index) => {
const expectedProductsForCustomer = expectedProducts[index];
expectedProductsForCustomer.forEach((expectedProduct) => {
const matchingProduct = customer.customer_products.find(
(cp) =>
cp.product.name === expectedProduct.name &&
cp.status === expectedProduct.status,
);
const unMatchedProduct = customer.customer_products.find(
(cp) => cp.product.name === expectedProduct.name,
);
assert.exists(
matchingProduct,
`Customer ${customer.name} should have ${expectedProduct.name} product with status ${expectedProduct.status}. ${unMatchedProduct ? `However ${unMatchedProduct.product.name} with status ${unMatchedProduct.status} was found instead` : ""}`,
);
});
});
});
});

View File

@@ -1,351 +0,0 @@
// import {
// type AppEnv,
// CusExpand,
// CusProductStatus,
// ErrCode,
// type Organization,
// type ReferralCode,
// type RewardRedemption,
// } from "@autumn/shared";
// import { assert } from "chai";
// import chalk from "chalk";
// import type { Stripe } from "stripe";
// import { setupBefore } from "tests/before.js";
// import { expectProductV1Attached } from "tests/utils/expectUtils/expectProductAttached.js";
// import {
// advanceTestClock,
// completeCheckoutForm,
// } from "tests/utils/stripeUtils.js";
// import type { DrizzleCli } from "@/db/initDrizzle.js";
// import AutumnError, { AutumnInt } from "@/external/autumn/autumnCli.js";
// import { CusService } from "@/internal/customers/CusService.js";
// import { RewardRedemptionService } from "@/internal/rewards/RewardRedemptionService.js";
// import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
// import { products, referralPrograms, rewards } from "../../../global.js";
// export const group = "referrals16";
// describe(`${chalk.yellowBright(
// "referrals16: Testing referrals - referrer starts with no product, gets pro_amount discount - checkout, both - coupon-based"
// )}`, () => {
// const mainCustomerId = "main-referral-16";
// const redeemer = "referral16-r1";
// const redeemerPM = "success";
// const autumn: AutumnInt = new AutumnInt();
// let stripeCli: Stripe;
// const testClockIds: string[] = [];
// let referralCode: ReferralCode;
// let redemption: RewardRedemption;
// let db: DrizzleCli;
// let org: Organization;
// let env: AppEnv;
// before(async function () {
// await setupBefore(this);
// stripeCli = this.stripeCli;
// db = this.db;
// org = this.org;
// env = this.env;
// try {
// await Promise.all([
// autumn.customers.delete(mainCustomerId, { deleteInStripe: true }),
// autumn.customers.delete(redeemer, { deleteInStripe: true }),
// RewardRedemptionService._resetCustomerRedemptions({
// db,
// internalCustomerId: [mainCustomerId, redeemer],
// }),
// ]);
// } catch {}
// // Initialize main customer with NO paid product (just free tier)
// const res = await initCustomer({
// autumn: this.autumnJs,
// customerId: mainCustomerId,
// db,
// org,
// env,
// attachPm: "success",
// });
// testClockIds.push(res.testClockId);
// const redeemerRes = await initCustomer({
// autumn: this.autumnJs,
// customerId: redeemer,
// db: this.db,
// org: this.org,
// env: this.env,
// attachPm: redeemerPM,
// withTestClock: true,
// });
// testClockIds.push(redeemerRes.testClockId);
// });
// it("should advance clock 10 days before redeeming", async () => {
// // Advance 10 days after setup
// await Promise.all(
// testClockIds.map((x) =>
// advanceTestClock({
// testClockId: x,
// numberOfDays: 10,
// waitForSeconds: 10,
// stripeCli,
// })
// )
// );
// });
// it("should create code once", async () => {
// referralCode = await autumn.referrals.createCode({
// customerId: mainCustomerId,
// referralId: referralPrograms.paidProductCheckoutAll.id,
// });
// assert.exists(referralCode.code);
// // Get referral code again
// const referralCode2 = await autumn.referrals.createCode({
// customerId: mainCustomerId,
// referralId: referralPrograms.paidProductCheckoutAll.id,
// });
// assert.equal(referralCode2.code, referralCode.code);
// });
// it("should create redemption for redeemer and fail if redeemed again", async () => {
// redemption = await autumn.referrals.redeem({
// customerId: redeemer,
// code: referralCode.code,
// });
// // Try redeem for redeemer again
// try {
// await autumn.referrals.redeem({
// customerId: redeemer,
// code: referralCode.code,
// });
// assert.fail("Should not be able to redeem again");
// } catch (error) {
// assert.instanceOf(error, AutumnError);
// assert.equal(error.code, ErrCode.CustomerAlreadyRedeemedReferralCode);
// }
// });
// it("should have referrer and redeemer still on free tier (reward not triggered yet)", async () => {
// const redemptionResult = await autumn.redemptions.get(redemption.id);
// assert.equal(redemptionResult.triggered, false); // Checkout trigger not fired yet
// const mainCus = await autumn.customers.get(mainCustomerId);
// const redeemerCus = await autumn.customers.get(redeemer);
// const mainProds = mainCus.products;
// const redeemerProds = redeemerCus.products;
// // Both customers should still only have free product
// assert.equal(mainProds.length, 1);
// assert.equal(mainProds[0].id, products.free.id);
// assert.equal(redeemerProds.length, 1);
// assert.equal(redeemerProds[0].id, products.free.id);
// expectProductV1Attached({
// customer: mainCus,
// product: products.free,
// status: CusProductStatus.Active,
// });
// expectProductV1Attached({
// customer: redeemerCus,
// product: products.free,
// status: CusProductStatus.Active,
// });
// });
// it("should trigger reward when redeemer checks out with Premium", async () => {
// // Redeemer purchases Premium product (triggers checkout reward)
// const checkoutRes = await autumn.attach({
// customer_id: redeemer,
// product_id: products.premium.id,
// force_checkout: true,
// });
// await completeCheckoutForm(checkoutRes.checkout_url);
// // Wait a bit for webhook processing
// await new Promise((resolve) => setTimeout(resolve, 10000));
// // Now both customers should have the reward applied
// const redemptionResult = await autumn.redemptions.get(redemption.id);
// assert.equal(redemptionResult.applied, true);
// const mainCus = await autumn.customers.get(mainCustomerId);
// const redeemerCus = await autumn.customers.get(redeemer);
// const mainProds = mainCus.products;
// const redeemerProds = redeemerCus.products;
// // Main customer (referrer) should now have the pro product
// assert.equal(mainProds.length, 1);
// assert.equal(mainProds[0].id, products.pro.id);
// // Redeemer should have both Premium (purchased) and the Pro price discount
// assert.equal(redeemerProds.length, 1);
// const redeemerStripeDiscounts = await stripeCli.subscriptions.retrieve(
// redeemerProds.find((x) => x.id === products.premium.id)
// ?.subscription_ids?.[0]!,
// {
// expand: ["discounts"],
// }
// );
// const parsedDiscountID = redeemerStripeDiscounts.discounts.find((x) => {
// if (typeof x === "string") {
// return x;
// } else if (typeof x === "object") {
// return x.coupon.id;
// } else return null;
// })!;
// assert.equal(
// redeemerStripeDiscounts.discounts.length,
// 1,
// `Redeemer Stripe Discounts: ${JSON.stringify(redeemerStripeDiscounts.discounts, null, 4)}`
// );
// assert.equal(
// typeof parsedDiscountID === "object"
// ? parsedDiscountID.coupon.id
// : parsedDiscountID,
// rewards.paidProductWithConfig.id,
// `Parsed Discount ID: ${parsedDiscountID}`
// );
// assert.exists(
// redeemerProds.find((x) => x.id === products.premium.id),
// `Redeemer must have Premium product`
// );
// expectProductV1Attached({
// customer: mainCus,
// product: products.pro,
// status: CusProductStatus.Active,
// });
// expectProductV1Attached({
// customer: redeemerCus,
// product: products.premium,
// status: CusProductStatus.Active,
// });
// });
// it("should advance test clock and verify both customers get pro_amount discount on their cycles", async () => {
// // Advance 31 days from current time to trigger next billing cycle
// await Promise.all(
// testClockIds.map((x) =>
// advanceTestClock({
// testClockId: x,
// numberOfDays: 31,
// waitForSeconds: 25,
// stripeCli,
// })
// )
// );
// // Test that both customers' invoices have pro_amount discount applied
// const [mainCustomerWithInvoices, redeemerWithInvoices] = await Promise.all([
// autumn.customers.get(mainCustomerId, {
// expand: [CusExpand.Invoices],
// }),
// autumn.customers.get(redeemer, {
// expand: [CusExpand.Invoices],
// }),
// ]);
// // Check main customer (referrer) Pro invoice
// const mainProInvoice = mainCustomerWithInvoices.invoices.find((x) =>
// x.product_ids.includes(products.pro.id)
// );
// if (mainProInvoice) {
// // Pro costs $10, so with pro_amount discount it should be $0 (Pro - Pro amount = $0)
// const proPrice = products.pro.prices[0].config.amount; // $10
// const proAmount = products.pro.prices[0].config.amount; // $10 (pro_amount discount)
// const expectedTotal = proPrice - proAmount; // $0
// // console.log("Main customer expected total:", expectedTotal);
// // console.log("Main customer Pro invoice total:", mainProInvoice.total);
// assert.equal(
// mainProInvoice.total,
// expectedTotal,
// `Main customer Pro invoice should be $0 (Pro $10 - Pro amount $10 discount). Got $${mainProInvoice.total}`
// );
// }
// // Check redeemer Premium invoice (should have $10 off)
// const redeemerPremiumInvoice = redeemerWithInvoices.invoices.find((x) =>
// x.product_ids.includes(products.premium.id)
// );
// if (redeemerPremiumInvoice) {
// // Premium costs $50, so with pro_amount discount it should be $40 (Premium - Pro amount = $40)
// const premiumPrice = products.premium.prices[0].config.amount; // $50
// const proAmount = products.pro.prices[0].config.amount; // $10 (pro_amount discount)
// const expectedTotal = premiumPrice - proAmount; // $40
// assert.equal(
// redeemerPremiumInvoice.total,
// expectedTotal,
// `Redeemer Premium invoice should be $40 (Premium $50 - Pro amount $10 discount). Got $${redeemerPremiumInvoice.total}`
// );
// }
// const dbCustomers = await Promise.all(
// [mainCustomerId, redeemer].map((x) =>
// CusService.getFull({
// db,
// idOrInternalId: x,
// orgId: org.id,
// env,
// inStatuses: [
// CusProductStatus.Active,
// CusProductStatus.PastDue,
// CusProductStatus.Expired,
// ],
// })
// )
// );
// const expectedProducts = [
// [
// // Main referrer - has Pro with pro_amount discount applied
// { name: "Free", status: CusProductStatus.Expired },
// { name: "Pro", status: CusProductStatus.Active },
// ],
// [
// // Redeemer - has both Premium (purchased) and Pro (reward) with discounts
// { name: "Free", status: CusProductStatus.Expired },
// { name: "Pro", status: CusProductStatus.Active },
// { name: "Premium", status: CusProductStatus.Active },
// ],
// ];
// dbCustomers.forEach((customer, index) => {
// const expectedProductsForCustomer = expectedProducts[index];
// expectedProductsForCustomer.forEach((expectedProduct) => {
// const matchingProduct = customer.customer_products.find(
// (cp) =>
// cp.product.name === expectedProduct.name &&
// cp.status === expectedProduct.status
// );
// const unMatchedProduct = customer.customer_products.find(
// (cp) => cp.product.name === expectedProduct.name
// );
// assert.exists(
// matchingProduct,
// `Customer ${customer.name} should have ${expectedProduct.name} product with status ${expectedProduct.status}. ${unMatchedProduct ? `However ${unMatchedProduct.product.name} with status ${unMatchedProduct.status} was found instead` : ""}`
// );
// });
// });
// });
// });

View File

@@ -1,292 +0,0 @@
import {
type AppEnv,
ErrCode,
type Organization,
type ReferralCode,
type RewardRedemption,
} from "@autumn/shared";
import { assert } from "chai";
import chalk from "chalk";
import { addDays } from "date-fns";
import type { Stripe } from "stripe";
import { addPrefixToProducts } from "tests/attach/utils.js";
import { setupBefore } from "tests/before.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { timeout } from "tests/utils/genUtils.js";
import { createProducts } from "tests/utils/productUtils.js";
import { advanceTestClock } from "tests/utils/stripeUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import AutumnError, { AutumnInt } from "@/external/autumn/autumnCli.js";
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { products, referralPrograms } from "../../global.js";
const pro = constructProduct({
id: "pro",
items: [constructFeatureItem({ featureId: TestFeature.Words })],
type: "pro",
trial: true,
});
// UNCOMMENT FROM HERE
describe(`${chalk.yellowBright(
"referrals1: Testing referrals (on checkout)",
)}`, () => {
const mainCustomerId = "main-referral-1";
const alternateCustomerId = "alternate-referral-1";
const redeemers = ["referral1-r1", "referral1-r2", "referral1-r3"];
const autumn: AutumnInt = new AutumnInt();
let stripeCli: Stripe;
let testClockId: string;
let referralCode: ReferralCode;
const redemptions: RewardRedemption[] = [];
let mainCustomer: any;
let db: DrizzleCli;
let org: Organization;
let env: AppEnv;
before(async function () {
await setupBefore(this);
stripeCli = this.stripeCli;
db = this.db;
org = this.org;
env = this.env;
addPrefixToProducts({
products: [pro],
prefix: mainCustomerId,
});
await createProducts({
autumn: this.autumnJs,
products: [pro],
db,
orgId: org.id,
env,
customerId: mainCustomerId,
});
const res = await initCustomer({
autumn: this.autumnJs,
customerId: mainCustomerId,
fingerprint: "main-referral-1",
db,
org,
env,
attachPm: "success",
});
mainCustomer = res.customer;
testClockId = res.testClockId;
await autumn.attach({
customer_id: mainCustomerId,
product_id: pro.id,
});
const batchCreate = [];
for (const redeemer of redeemers) {
batchCreate.push(
initCustomer({
autumn: this.autumnJs,
customerId: redeemer,
db: this.db,
org: this.org,
env: this.env,
attachPm: "success",
}),
);
}
batchCreate.push(
initCustomer({
autumn: this.autumnJs,
customerId: alternateCustomerId,
fingerprint: "main-referral-1",
db: this.db,
org: this.org,
env: this.env,
attachPm: "success",
}),
);
await Promise.all(batchCreate);
});
it("should create code once", async () => {
referralCode = await autumn.referrals.createCode({
customerId: mainCustomerId,
referralId: referralPrograms.onCheckout.id,
});
assert.exists(referralCode.code);
// Get referral code again
const referralCode2 = await autumn.referrals.createCode({
customerId: mainCustomerId,
referralId: referralPrograms.onCheckout.id,
});
assert.equal(referralCode2.code, referralCode.code);
});
it("should fail if same customer tries to redeem code again", async () => {
try {
await autumn.referrals.redeem({
customerId: mainCustomerId,
code: referralCode.code,
});
assert.fail("Own customer should not be able to redeem code");
} catch (error) {
assert.instanceOf(error, AutumnError);
assert.equal(error.code, ErrCode.CustomerCannotRedeemOwnCode);
}
try {
await autumn.referrals.redeem({
customerId: alternateCustomerId,
code: referralCode.code,
});
assert.fail(
"Own customer (same fingerprint) should not be able to redeem code",
);
} catch (error) {
assert.instanceOf(error, AutumnError);
assert.equal(error.code, ErrCode.CustomerCannotRedeemOwnCode);
}
});
it("should create redemption for each redeemer and fail if redeemed again", async () => {
for (const redeemer of redeemers) {
const redemption: RewardRedemption = await autumn.referrals.redeem({
customerId: redeemer,
code: referralCode.code,
});
redemptions.push(redemption);
}
// Try redeem for redeemer1 again
try {
const redemption1 = await autumn.referrals.redeem({
customerId: redeemers[0],
code: referralCode.code,
});
assert.fail("Should not be able to redeem again");
} catch (error) {
assert.instanceOf(error, AutumnError);
assert.equal(error.code, ErrCode.CustomerAlreadyRedeemedReferralCode);
}
});
// return;
it("should be triggered (and applied) when redeemers check out", async () => {
for (let i = 0; i < redeemers.length; i++) {
const redeemer = redeemers[i];
await autumn.attach({
customer_id: redeemer,
product_id: products.pro.id,
});
await timeout(3000);
// Get redemption object
const redemption = await autumn.redemptions.get(redemptions[i].id);
// Check if redemption is triggered
const count = i + 1;
if (count > referralPrograms.onCheckout.max_redemptions) {
assert.equal(redemption.triggered, false);
assert.equal(redemption.applied, false);
} else {
assert.equal(redemption.triggered, true);
assert.equal(redemption.applied, i === 0);
}
// Check stripe customer
const stripeCus = (await stripeCli.customers.retrieve(
mainCustomer.processor?.id,
)) as Stripe.Customer;
assert.notEqual(stripeCus.discount, null);
}
});
let curTime = new Date();
it("customer should have discount for first purchase", async () => {
curTime = addDays(addDays(curTime, 7), 4);
await advanceTestClock({
testClockId,
advanceTo: curTime.getTime(),
stripeCli,
});
// 1. Get invoice
const { invoices } = await autumn.customers.get(mainCustomerId);
assert.equal(invoices.length, 2);
assert.equal(invoices[0].total, 0);
});
// it("customer should have discount for second purchase", async function () {
// // 2. Check that customer has another discount
// let stripeCus = (await stripeCli.customers.retrieve(
// mainCustomer.processor?.id,
// )) as Stripe.Customer;
// assert.notEqual(stripeCus.discount, null);
// // 2. Advance test clock to 1 month from start (trigger discount.deleted event)
// curTime = addHours(addMonths(new Date(), 1), 2);
// await advanceTestClock({
// testClockId,
// advanceTo: curTime.getTime(),
// stripeCli,
// });
// // 3. Advance test clock to 1 month + 12 days from start (trigger new invoice)
// curTime = addDays(curTime, 12);
// await advanceTestClock({
// testClockId,
// advanceTo: curTime.getTime(),
// stripeCli,
// });
// // // 3. Get invoice again
// let { invoices: invoices2 } = await autumn.customers.get(mainCustomerId);
// assert.equal(invoices2.length, 3);
// assert.equal(invoices2[0].total, 0);
// });
});
// const { testClockId: testClockId1, customer } =
// await initCustomerWithTestClock({
// customerId: mainCustomerId,
// db: this.db,
// org: this.org,
// env: this.env,
// fingerprint: "main-referral-1",
// });
// testClockId = testClockId1;
// mainCustomer = customer;
// await autumn.attach({
// customer_id: mainCustomerId,
// product_id: products.proWithTrial.id,
// });
// initCustomer({
// customer_data: {
// id: alternateCustomerId,
// name: "Alternate Referral 1",
// email: "alternate-referral-1@example.com",
// fingerprint: "main-referral-1",
// },
// db: this.db,
// org: this.org,
// env: this.env,
// })

View File

@@ -1,174 +0,0 @@
import {
type AppEnv,
type Customer,
ErrCode,
type Organization,
type ReferralCode,
type RewardRedemption,
} from "@autumn/shared";
import { assert } from "chai";
import chalk from "chalk";
import { addDays } from "date-fns";
import type { Stripe } from "stripe";
import { setupBefore } from "tests/before.js";
import { timeout } from "tests/utils/genUtils.js";
import { initCustomer } from "tests/utils/init.js";
import { advanceTestClock } from "tests/utils/stripeUtils.js";
import AutumnError, { AutumnInt } from "@/external/autumn/autumnCli.js";
import { createStripeCli } from "@/external/connect/createStripeCli.js";
import { initCustomerV2 } from "@/utils/scriptUtils/initCustomer.js";
import { products, referralPrograms } from "../../global.js";
// UNCOMMENT FROM HERE
describe(`${chalk.yellowBright(
"referrals2: Testing referrals (immediate redemption)",
)}`, () => {
const mainCustomerId = "main-referral-2";
const redeemers = ["referral2-r1", "referral2-r2", "referral2-r3"];
const autumn: AutumnInt = new AutumnInt();
let stripeCli: Stripe;
let testClockId: string;
let referralCode: ReferralCode;
const redemptions: RewardRedemption[] = [];
let mainCustomer: Customer;
let org: Organization;
let env: AppEnv;
before(async function () {
await setupBefore(this);
stripeCli = this.stripeCli;
org = this.org;
env = this.env;
const { testClockId: testClockId1, customer } = await initCustomerV2({
customerId: mainCustomerId,
db: this.db,
org: this.org,
env: this.env,
autumn,
});
testClockId = testClockId1;
mainCustomer = customer;
const batchCreate = [];
for (const redeemer of redeemers) {
batchCreate.push(
initCustomer({
customerId: redeemer,
db: this.db,
org: this.org,
env: this.env,
attachPm: true,
}),
);
}
await Promise.all(batchCreate);
});
it("should create code once", async () => {
referralCode = await autumn.referrals.createCode({
customerId: mainCustomerId,
referralId: referralPrograms.immediate.id,
});
assert.exists(referralCode.code);
});
it("should create redemption for each redeemer and fail if redeemed again", async () => {
for (let i = 0; i < redeemers.length; i++) {
const redeemer = redeemers[i];
const count = i + 1;
try {
const redemption: RewardRedemption = await autumn.referrals.redeem({
customerId: redeemer,
code: referralCode.code,
});
redemptions.push(redemption);
if (count > referralPrograms.immediate.max_redemptions) {
assert.equal(redemption.triggered, false);
assert.equal(redemption.applied, false);
} else {
assert.fail("Should not be able to redeem again");
}
} catch (error) {
if (count > referralPrograms.immediate.max_redemptions) {
assert.instanceOf(error, AutumnError);
assert.equal(error.code, ErrCode.ReferralCodeMaxRedemptionsReached);
}
}
}
// Check stripe customer
const legacyStripe = createStripeCli({
org: org,
env: env,
legacyVersion: true,
});
const stripeCus = (await legacyStripe.customers.retrieve(
mainCustomer.processor?.id,
{
expand: ["discount"],
},
)) as Stripe.Customer;
assert.notEqual(stripeCus.discount, null);
});
let curTime = new Date();
it("customer should have discount for first purchase", async () => {
await autumn.attach({
customer_id: mainCustomerId,
product_id: products.proWithTrial.id,
});
await timeout(3000);
curTime = addDays(addDays(curTime, 7), 4);
await advanceTestClock({
testClockId,
advanceTo: curTime.getTime(),
stripeCli,
waitForSeconds: 30,
});
// 1. Get invoice
const { invoices } = await autumn.customers.get(mainCustomerId);
assert.equal(invoices!.length, 2);
assert.equal(invoices![0].total, 0);
});
// it("customer should have discount for second purchase", async function () {
// // 2. Check that customer has another discount
// let stripeCus = (await stripeCli.customers.retrieve(
// mainCustomer.processor?.id,
// )) as Stripe.Customer;
// assert.notEqual(stripeCus.discount, null);
// // 2. Advance test clock to 1 month from start (trigger discount.deleted event)
// curTime = addHours(addMonths(new Date(), 1), 2);
// await advanceTestClock({
// testClockId,
// advanceTo: curTime.getTime(),
// stripeCli,
// });
// // 3. Advance test clock to 1 month + 7 days from start (trigger new invoice)
// curTime = addDays(curTime, 8);
// await advanceTestClock({
// testClockId,
// advanceTo: curTime.getTime(),
// stripeCli,
// });
// // // 3. Get invoice again
// let { invoices: invoices2 } = await autumn.customers.get(mainCustomerId);
// assert.equal(invoices2!.length, 3);
// assert.equal(invoices2![0].total, 0);
// });
});

View File

@@ -1,141 +0,0 @@
import {
type Customer,
ErrCode,
type ReferralCode,
type RewardRedemption,
} from "@autumn/shared";
import { assert } from "chai";
import chalk from "chalk";
import type { Stripe } from "stripe";
import { setupBefore } from "tests/before.js";
import { compareProductEntitlements } from "tests/utils/compare.js";
import { timeout } from "tests/utils/genUtils.js";
import { initCustomer } from "tests/utils/init.js";
import { initCustomerWithTestClock } from "tests/utils/testInitUtils.js";
import AutumnError, { AutumnInt } from "@/external/autumn/autumnCli.js";
import { features, products, referralPrograms } from "../../global.js";
// UNCOMMENT FROM HERE
describe(`${chalk.yellowBright(
"referrals3: Testing free product referrals",
)}`, () => {
const mainCustomerId = "main-referral-3";
const redeemers = ["referral3-r1", "referral3-r2", "referral3-r3"];
let autumn: AutumnInt = new AutumnInt();
let stripeCli: Stripe;
let testClockId: string;
let referralCode: ReferralCode;
const redemptions: RewardRedemption[] = [];
let mainCustomer: Customer;
before(async function () {
await setupBefore(this);
autumn = this.autumn;
stripeCli = this.stripeCli;
const { testClockId: testClockId1, customer } =
await initCustomerWithTestClock({
customerId: mainCustomerId,
db: this.db,
org: this.org,
env: this.env,
fingerprint: "main-referral-3",
});
testClockId = testClockId1;
mainCustomer = customer;
await autumn.attach({
customer_id: mainCustomerId,
product_id: products.proWithTrial.id,
});
const batchCreate = [];
for (const redeemer of redeemers) {
batchCreate.push(
initCustomer({
customerId: redeemer,
db: this.db,
org: this.org,
env: this.env,
attachPm: true,
}),
);
}
await Promise.all(batchCreate);
});
it("should create code once", async () => {
referralCode = await autumn.referrals.createCode({
customerId: mainCustomerId,
referralId: referralPrograms.freeProduct.id,
});
assert.exists(referralCode.code);
});
it("should create redemption for each redeemer and fail if redeemed again", async () => {
for (const redeemer of redeemers) {
const redemption: RewardRedemption = await autumn.referrals.redeem({
customerId: redeemer,
code: referralCode.code,
});
redemptions.push(redemption);
// assert.equal(redemption.triggered, false);
// assert.equal(redemption.applied, false);
}
// Try redeem for redeemer1 again
try {
const redemption1 = await autumn.referrals.redeem({
customerId: redeemers[0],
code: referralCode.code,
});
assert.fail("Should not be able to redeem again");
} catch (error) {
assert.instanceOf(error, AutumnError);
assert.equal(error.code, ErrCode.CustomerAlreadyRedeemedReferralCode);
}
});
it("should be triggered (and applied) when redeemers check out", async () => {
for (let i = 0; i < redeemers.length; i++) {
const redeemer = redeemers[i];
await autumn.attach({
customer_id: redeemer,
product_id: products.pro.id,
});
await timeout(3000);
// Get redemption object
const redemption = await autumn.redemptions.get(redemptions[i].id);
// Check if redemption is triggered
const count = i + 1;
if (count > referralPrograms.freeProduct.max_redemptions) {
assert.equal(redemption.triggered, false);
assert.equal(redemption.applied, false);
} else {
// 1. Check that main customer has free add on
compareProductEntitlements({
customerId: mainCustomerId,
product: products.freeAddOn,
features,
quantity: count,
});
compareProductEntitlements({
customerId: redeemer,
product: products.freeAddOn,
features,
});
}
}
});
});

View File

@@ -1,125 +0,0 @@
import type { Customer, ReferralCode, RewardRedemption } from "@autumn/shared";
import { assert } from "chai";
import chalk from "chalk";
import { addDays, addHours } from "date-fns";
import type { Stripe } from "stripe";
import { setupBefore } from "tests/before.js";
import { compareProductEntitlements } from "tests/utils/compare.js";
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
import { timeout } from "tests/utils/genUtils.js";
import { initCustomer } from "tests/utils/init.js";
import { advanceTestClock } from "tests/utils/stripeUtils.js";
import { initCustomerWithTestClock } from "tests/utils/testInitUtils.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { features, products, referralPrograms } from "../../global.js";
// UNCOMMENT FROM HERE
describe(`${chalk.yellowBright(
"referrals4: Testing free product referrals with trial",
)}`, () => {
const mainCustomerId = "main-referral-4";
// let redeemers = ["referral4-r1", "referral4-r2"];
const redeemerId = "referral4-r1";
let autumn: AutumnInt = new AutumnInt();
let stripeCli: Stripe;
let referralCode: ReferralCode;
const redemptions: RewardRedemption[] = [];
let mainCustomer: Customer;
let redeemer: Customer;
let testClockId: string;
before(async function () {
await setupBefore(this);
autumn = this.autumn;
stripeCli = this.stripeCli;
await initCustomer({
customerId: mainCustomerId,
db: this.db,
org: this.org,
env: this.env,
attachPm: true,
});
await autumn.attach({
customer_id: mainCustomerId,
product_id: products.proWithTrial.id,
});
const { testClockId: testClockId1, customer } =
await initCustomerWithTestClock({
customerId: redeemerId,
db: this.db,
org: this.org,
env: this.env,
});
testClockId = testClockId1;
redeemer = customer;
});
it("should create referral code", async () => {
referralCode = await autumn.referrals.createCode({
customerId: mainCustomerId,
referralId: referralPrograms.freeProduct.id,
});
assert.exists(referralCode.code);
});
it("should create redemption for each redeemer and fail if redeemed again", async () => {
const redemption: RewardRedemption = await autumn.referrals.redeem({
customerId: redeemerId,
code: referralCode.code,
});
redemptions.push(redemption);
});
it("should not be triggered because of trial", async () => {
await autumn.attach({
customer_id: redeemerId,
product_id: products.proWithTrial.id,
});
await timeout(3000);
// Get redemption object
const redemption = await autumn.redemptions.get(redemptions[0].id);
assert.equal(redemption.triggered, false);
});
it("should be triggered after trial ends", async () => {
const advanceTo = addHours(
addDays(new Date(), 7),
hoursToFinalizeInvoice,
).getTime();
await advanceTestClock({
stripeCli,
testClockId,
advanceTo,
waitForSeconds: 30,
});
const redemption = await autumn.redemptions.get(redemptions[0].id);
assert.equal(redemption.triggered, true);
compareProductEntitlements({
customerId: mainCustomerId,
product: products.freeAddOn,
features,
quantity: 1,
});
compareProductEntitlements({
customerId: redeemerId,
product: products.freeAddOn,
features,
quantity: 1,
});
});
});

View File

@@ -0,0 +1,198 @@
import {
type AppEnv,
type Customer,
LegacyVersion,
type LimitedItem,
type Organization,
ProductItemInterval,
RolloverDuration,
} from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import type Stripe from "stripe";
import { addPrefixToProducts } from "tests/attach/utils.js";
import { setupBefore } from "tests/before.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { createProducts } from "tests/utils/productUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
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 { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { resetAndGetCusEnt } from "./rolloverTestUtils.js";
const rolloverConfig = {
max: 500,
length: 1,
duration: RolloverDuration.Month,
};
const messagesItem = constructFeatureItem({
featureId: TestFeature.Messages,
includedUsage: 400,
interval: ProductItemInterval.Month,
rolloverConfig,
}) as LimitedItem;
export const free = constructProduct({
items: [messagesItem],
type: "free",
isDefault: false,
});
const testCase = "rollover1";
// , per entity and regular
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for feature item`)}`, () => {
const customerId = testCase;
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
let testClockId: string;
let db: DrizzleCli, org: Organization, env: AppEnv;
let customer: Customer;
let stripeCli: Stripe;
const curUnix = new Date().getTime();
before(async function () {
await setupBefore(this);
const { autumnJs } = this;
db = this.db;
org = this.org;
env = this.env;
stripeCli = this.stripeCli;
addPrefixToProducts({
products: [free],
prefix: testCase,
});
await createProducts({
autumn,
products: [free],
customerId,
db,
orgId: org.id,
env,
});
const res = await initCustomer({
autumn: autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
testClockId = res.testClockId!;
customer = res.customer;
});
it("should attach free product", async () => {
await autumn.attach({
customer_id: customerId,
product_id: free.id,
});
});
const messageUsage = 250;
let curBalance = messagesItem.included_usage;
it("should create track messages, reset, and have correct rollover", async () => {
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Messages,
value: messageUsage,
});
await timeout(3000);
await resetAndGetCusEnt({
db,
customer,
productGroup: free.group,
featureId: TestFeature.Messages,
});
const cus = await autumn.customers.get(customerId);
const msgesFeature = cus.features[TestFeature.Messages];
const expectedRollover = Math.min(
messagesItem.included_usage - messageUsage,
rolloverConfig.max,
);
const expectedBalance = messagesItem.included_usage + expectedRollover;
expect(msgesFeature).to.exist;
expect(msgesFeature?.balance).to.equal(expectedBalance);
// @ts-expect-error
expect(msgesFeature?.rollovers[0].balance).to.equal(expectedRollover);
curBalance = expectedBalance;
});
// let usage2 = 50;
it("should reset again and have correct rollover", async () => {
await resetAndGetCusEnt({
db,
customer,
productGroup: free.group,
featureId: TestFeature.Messages,
});
const expectedRollover = Math.min(curBalance, rolloverConfig.max);
const expectedBalance = messagesItem.included_usage + expectedRollover;
const cus = await autumn.customers.get(customerId);
const msgesFeature = cus.features[TestFeature.Messages];
expect(msgesFeature).to.exist;
expect(msgesFeature?.balance).to.equal(expectedBalance);
// @ts-expect-error (oldest rollover should be 100 (150 - 50))
expect(msgesFeature?.rollovers[0].balance).to.equal(100);
// @ts-expect-error (newest rollover should be 400 (msges.included_usage))
expect(msgesFeature?.rollovers[1].balance).to.equal(400);
});
it("should track messages and deduct from rollovers first", async () => {
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Messages,
value: 150,
});
await timeout(3000);
const cus = await autumn.customers.get(customerId);
const msgesFeature = cus.features[TestFeature.Messages];
// @ts-expect-error
const rollover1 = msgesFeature?.rollovers[0];
// @ts-expect-error
const rollover2 = msgesFeature?.rollovers[1];
expect(rollover1.balance).to.equal(0);
expect(rollover2.balance).to.equal(350);
});
it("should track and deduct from rollover + original balance", async () => {
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Messages,
value: 400,
});
await timeout(3000);
const cus = await autumn.customers.get(customerId);
const msgesFeature = cus.features[TestFeature.Messages];
// @ts-expect-error
const rollovers = msgesFeature.rollovers;
expect(rollovers![0].balance).to.equal(0);
expect(rollovers![1].balance).to.equal(0);
expect(msgesFeature.balance).to.equal(messagesItem.included_usage - 50);
});
});

View File

@@ -0,0 +1,179 @@
import {
type Customer,
LegacyVersion,
type LimitedItem,
ProductItemInterval,
RolloverDuration,
} from "@autumn/shared";
import { beforeAll, describe, expect, test } from "bun:test";
import chalk from "chalk";
import type Stripe from "stripe";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "tests/setup/v2Features.js";
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";
import { resetAndGetCusEnt } from "./rolloverTestUtils.js";
const rolloverConfig = {
max: 500,
length: 1,
duration: RolloverDuration.Month,
};
const messagesItem = constructFeatureItem({
featureId: TestFeature.Messages,
includedUsage: 400,
interval: ProductItemInterval.Month,
rolloverConfig,
}) as LimitedItem;
export const free = constructProduct({
items: [messagesItem],
type: "free",
isDefault: false,
});
const testCase = "rollover1";
// , per entity and regular
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for feature item`)}`, () => {
const customerId = testCase;
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
let testClockId: string;
let customer: Customer;
let stripeCli: Stripe;
const curUnix = new Date().getTime();
beforeAll(async () => {
stripeCli = ctx.stripeCli;
await initProductsV0({
ctx,
products: [free],
prefix: testCase,
customerId,
});
const res = await initCustomerV3({
ctx,
customerId,
customerData: {},
attachPm: "success",
withTestClock: true,
});
testClockId = res.testClockId!;
customer = res.customer;
});
test("should attach free product", async () => {
await autumn.attach({
customer_id: customerId,
product_id: free.id,
});
});
const messageUsage = 250;
let curBalance = messagesItem.included_usage;
test("should create track messages, reset, and have correct rollover", async () => {
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Messages,
value: messageUsage,
});
await timeout(3000);
await resetAndGetCusEnt({
db: ctx.db,
customer,
productGroup: free.group,
featureId: TestFeature.Messages,
});
const cus = await autumn.customers.get(customerId);
const msgesFeature = cus.features[TestFeature.Messages];
const expectedRollover = Math.min(
messagesItem.included_usage - messageUsage,
rolloverConfig.max,
);
const expectedBalance = messagesItem.included_usage + expectedRollover;
expect(msgesFeature).toBeDefined();
expect(msgesFeature?.balance).toBe(expectedBalance);
// @ts-expect-error
expect(msgesFeature?.rollovers[0].balance).toBe(expectedRollover);
curBalance = expectedBalance;
});
// let usage2 = 50;
test("should reset again and have correct rollover", async () => {
await resetAndGetCusEnt({
db: ctx.db,
customer,
productGroup: free.group,
featureId: TestFeature.Messages,
});
const expectedRollover = Math.min(curBalance, rolloverConfig.max);
const expectedBalance = messagesItem.included_usage + expectedRollover;
const cus = await autumn.customers.get(customerId);
const msgesFeature = cus.features[TestFeature.Messages];
expect(msgesFeature).toBeDefined();
expect(msgesFeature?.balance).toBe(expectedBalance);
// @ts-expect-error (oldest rollover should be 100 (150 - 50))
expect(msgesFeature?.rollovers[0].balance).toBe(100);
// @ts-expect-error (newest rollover should be 400 (msges.included_usage))
expect(msgesFeature?.rollovers[1].balance).toBe(400);
});
test("should track messages and deduct from rollovers first", async () => {
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Messages,
value: 150,
});
await timeout(3000);
const cus = await autumn.customers.get(customerId);
const msgesFeature = cus.features[TestFeature.Messages];
// @ts-expect-error
const rollover1 = msgesFeature?.rollovers[0];
// @ts-expect-error
const rollover2 = msgesFeature?.rollovers[1];
expect(rollover1.balance).toBe(0);
expect(rollover2.balance).toBe(350);
});
test("should track and deduct from rollover + original balance", async () => {
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Messages,
value: 400,
});
await timeout(3000);
const cus = await autumn.customers.get(customerId);
const msgesFeature = cus.features[TestFeature.Messages];
// @ts-expect-error
const rollovers = msgesFeature.rollovers;
expect(rollovers![0].balance).toBe(0);
expect(rollovers![1].balance).toBe(0);
expect(msgesFeature.balance).toBe(messagesItem.included_usage - 50);
});
});

View File

@@ -0,0 +1,225 @@
import {
type AppEnv,
type Customer,
LegacyVersion,
type LimitedItem,
type Organization,
ProductItemInterval,
RolloverDuration,
} from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import type Stripe from "stripe";
import { addPrefixToProducts } from "tests/attach/utils.js";
import { setupBefore } from "tests/before.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { createProducts } from "tests/utils/productUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
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 { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { resetAndGetCusEnt } from "./rolloverTestUtils.js";
const rolloverConfig = {
max: 500,
length: 1,
duration: RolloverDuration.Month,
};
const msgesItem = constructFeatureItem({
featureId: TestFeature.Messages,
includedUsage: 400,
interval: ProductItemInterval.Month,
rolloverConfig,
entityFeatureId: TestFeature.Users,
}) as LimitedItem;
export const free = constructProduct({
items: [msgesItem],
type: "free",
isDefault: false,
});
const testCase = "rollover2";
// , per entity and regular
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for feature item (per entity)`)}`, () => {
const customerId = testCase;
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
let testClockId: string;
let db: DrizzleCli, org: Organization, env: AppEnv;
let customer: Customer;
let stripeCli: Stripe;
const curUnix = new Date().getTime();
before(async function () {
await setupBefore(this);
const { autumnJs } = this;
db = this.db;
org = this.org;
env = this.env;
stripeCli = this.stripeCli;
addPrefixToProducts({
products: [free],
prefix: testCase,
});
await createProducts({
autumn,
products: [free],
customerId,
db,
orgId: org.id,
env,
});
const res = await initCustomer({
autumn: autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
testClockId = res.testClockId!;
customer = res.customer;
});
const entities: any[] = [
{
id: "1",
name: "Entity 1",
feature_id: TestFeature.Users,
},
{
id: "2",
name: "Entity 2",
feature_id: TestFeature.Users,
},
];
it("should attach pro product", async () => {
await autumn.attach({
customer_id: customerId,
product_id: free.id,
});
await autumn.entities.create(customerId, entities);
});
const entity1Id = entities[0].id;
const entity2Id = entities[1].id;
const newEntity1Balance = 300;
const newEntity2Balance = 200;
const includedUsage = msgesItem.included_usage;
const usages = [
{
entityId: entity1Id,
usage: includedUsage - newEntity1Balance,
rollover: newEntity1Balance,
},
{
entityId: entity2Id,
usage: includedUsage - newEntity2Balance,
rollover: newEntity2Balance,
},
];
it("should create track messages, reset, and have correct rollover", async () => {
for (const usage of usages) {
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Messages,
value: usage.usage,
entity_id: usage.entityId,
});
}
await timeout(3000);
// Run reset cusEnt on ...
await resetAndGetCusEnt({
db,
customer,
productGroup: free.group,
featureId: TestFeature.Messages,
});
for (const usage of usages) {
const entity = await autumn.entities.get(customerId, usage.entityId);
const msgesFeature = entity.features[TestFeature.Messages];
const expectedRollover = Math.min(usage.rollover, rolloverConfig.max);
expect(msgesFeature.rollovers.length).to.equal(1);
expect(msgesFeature.balance).to.equal(includedUsage + expectedRollover);
expect(msgesFeature.rollovers[0].balance).to.equal(expectedRollover);
}
});
it("should reset again and have correct rollovers", async () => {
await resetAndGetCusEnt({
db,
customer,
productGroup: free.group,
featureId: TestFeature.Messages,
});
const entity1 = await autumn.entities.get(customerId, entity1Id);
const entity1Msges = entity1.features[TestFeature.Messages];
// 400, 300 -> 400, 100 (max is 500)
const rollovers = entity1Msges.rollovers;
expect(rollovers[0].balance).to.equal(100);
expect(rollovers[1].balance).to.equal(400);
const entity2 = await autumn.entities.get(customerId, entity2Id);
const entity2Msges = entity2.features[TestFeature.Messages];
// 400, 200 -> 400, 0 (max is 500)
const rollovers2 = entity2Msges.rollovers;
expect(rollovers2[0].balance).to.equal(100);
expect(rollovers2[1].balance).to.equal(400);
});
it("should track and deduct from oldest rollovers first", async () => {
for (const entity of entities) {
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Messages,
value: 150,
entity_id: entity.id,
});
await timeout(2000);
const entRes = await autumn.entities.get(customerId, entity.id);
const msgesFeature = entRes.features[TestFeature.Messages];
const rollovers = msgesFeature.rollovers;
expect(rollovers[0].balance).to.equal(0);
expect(rollovers[1].balance).to.equal(350);
expect(msgesFeature.balance).to.equal(includedUsage + 350);
}
});
it("should track past rollovers and deduct from original balance", async () => {
for (const entity of entities) {
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Messages,
value: 400,
entity_id: entity.id,
});
await timeout(2000);
const entRes = await autumn.entities.get(customerId, entity.id);
const msgesFeature = entRes.features[TestFeature.Messages];
const rollovers = msgesFeature.rollovers;
expect(rollovers[0].balance).to.equal(0);
expect(rollovers[1].balance).to.equal(0);
expect(msgesFeature.balance).to.equal(includedUsage - 50);
}
});
});

View File

@@ -0,0 +1,206 @@
import {
type Customer,
LegacyVersion,
type LimitedItem,
ProductItemInterval,
RolloverDuration,
} from "@autumn/shared";
import { beforeAll, describe, expect, test } from "bun:test";
import chalk from "chalk";
import type Stripe from "stripe";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "tests/setup/v2Features.js";
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";
import { resetAndGetCusEnt } from "./rolloverTestUtils.js";
const rolloverConfig = {
max: 500,
length: 1,
duration: RolloverDuration.Month,
};
const msgesItem = constructFeatureItem({
featureId: TestFeature.Messages,
includedUsage: 400,
interval: ProductItemInterval.Month,
rolloverConfig,
entityFeatureId: TestFeature.Users,
}) as LimitedItem;
export const free = constructProduct({
items: [msgesItem],
type: "free",
isDefault: false,
});
const testCase = "rollover2";
// , per entity and regular
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for feature item (per entity)`)}`, () => {
const customerId = testCase;
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
let testClockId: string;
let customer: Customer;
let stripeCli: Stripe;
const curUnix = new Date().getTime();
beforeAll(async () => {
stripeCli = ctx.stripeCli;
await initProductsV0({
ctx,
products: [free],
prefix: testCase,
customerId,
});
const res = await initCustomerV3({
ctx,
customerId,
customerData: {},
attachPm: "success",
withTestClock: true,
});
testClockId = res.testClockId!;
customer = res.customer;
});
const entities: any[] = [
{
id: "1",
name: "Entity 1",
feature_id: TestFeature.Users,
},
{
id: "2",
name: "Entity 2",
feature_id: TestFeature.Users,
},
];
test("should attach pro product", async () => {
await autumn.attach({
customer_id: customerId,
product_id: free.id,
});
await autumn.entities.create(customerId, entities);
});
const entity1Id = entities[0].id;
const entity2Id = entities[1].id;
const newEntity1Balance = 300;
const newEntity2Balance = 200;
const includedUsage = msgesItem.included_usage;
const usages = [
{
entityId: entity1Id,
usage: includedUsage - newEntity1Balance,
rollover: newEntity1Balance,
},
{
entityId: entity2Id,
usage: includedUsage - newEntity2Balance,
rollover: newEntity2Balance,
},
];
test("should create track messages, reset, and have correct rollover", async () => {
for (const usage of usages) {
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Messages,
value: usage.usage,
entity_id: usage.entityId,
});
}
await timeout(3000);
// Run reset cusEnt on ...
await resetAndGetCusEnt({
db: ctx.db,
customer,
productGroup: free.group,
featureId: TestFeature.Messages,
});
for (const usage of usages) {
const entity = await autumn.entities.get(customerId, usage.entityId);
const msgesFeature = entity.features[TestFeature.Messages];
const expectedRollover = Math.min(usage.rollover, rolloverConfig.max);
expect(msgesFeature.rollovers.length).toBe(1);
expect(msgesFeature.balance).toBe(includedUsage + expectedRollover);
expect(msgesFeature.rollovers[0].balance).toBe(expectedRollover);
}
});
test("should reset again and have correct rollovers", async () => {
await resetAndGetCusEnt({
db: ctx.db,
customer,
productGroup: free.group,
featureId: TestFeature.Messages,
});
const entity1 = await autumn.entities.get(customerId, entity1Id);
const entity1Msges = entity1.features[TestFeature.Messages];
// 400, 300 -> 400, 100 (max is 500)
const rollovers = entity1Msges.rollovers;
expect(rollovers[0].balance).toBe(100);
expect(rollovers[1].balance).toBe(400);
const entity2 = await autumn.entities.get(customerId, entity2Id);
const entity2Msges = entity2.features[TestFeature.Messages];
// 400, 200 -> 400, 0 (max is 500)
const rollovers2 = entity2Msges.rollovers;
expect(rollovers2[0].balance).toBe(100);
expect(rollovers2[1].balance).toBe(400);
});
test("should track and deduct from oldest rollovers first", async () => {
for (const entity of entities) {
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Messages,
value: 150,
entity_id: entity.id,
});
await timeout(2000);
const entRes = await autumn.entities.get(customerId, entity.id);
const msgesFeature = entRes.features[TestFeature.Messages];
const rollovers = msgesFeature.rollovers;
expect(rollovers[0].balance).toBe(0);
expect(rollovers[1].balance).toBe(350);
expect(msgesFeature.balance).toBe(includedUsage + 350);
}
});
test("should track past rollovers and deduct from original balance", async () => {
for (const entity of entities) {
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Messages,
value: 400,
entity_id: entity.id,
});
await timeout(2000);
const entRes = await autumn.entities.get(customerId, entity.id);
const msgesFeature = entRes.features[TestFeature.Messages];
const rollovers = msgesFeature.rollovers;
expect(rollovers[0].balance).toBe(0);
expect(rollovers[1].balance).toBe(0);
expect(msgesFeature.balance).toBe(includedUsage - 50);
}
});
});

View File

@@ -0,0 +1,127 @@
import {
type AppEnv,
type Customer,
LegacyVersion,
type LimitedItem,
type Organization,
RolloverDuration,
} from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import { addMonths } from "date-fns";
import type Stripe from "stripe";
import { addPrefixToProducts } from "tests/attach/utils.js";
import { setupBefore } from "tests/before.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { createProducts } from "tests/utils/productUtils.js";
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 { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
const rolloverConfig = {
max: 500,
length: 1,
duration: RolloverDuration.Month,
};
const messagesItem = constructArrearProratedItem({
featureId: TestFeature.Messages,
includedUsage: 400,
rolloverConfig,
}) as LimitedItem;
export const pro = constructProduct({
items: [messagesItem],
type: "pro",
isDefault: false,
});
const testCase = "rollover3";
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for usage price feature`)}`, () => {
const customerId = testCase;
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
let testClockId: string;
let db: DrizzleCli, org: Organization, env: AppEnv;
let customer: Customer;
let stripeCli: Stripe;
const curUnix = new Date().getTime();
before(async function () {
await setupBefore(this);
const { autumnJs } = this;
db = this.db;
org = this.org;
env = this.env;
stripeCli = this.stripeCli;
addPrefixToProducts({
products: [pro],
prefix: testCase,
});
await createProducts({
autumn,
products: [pro],
customerId,
db,
orgId: org.id,
env,
});
const res = await initCustomer({
autumn: autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
testClockId = res.testClockId!;
customer = res.customer;
});
it("should attach pro product", async () => {
await autumn.attach({
customer_id: customerId,
product_id: pro.id,
});
});
const rollover = 250;
let curBalance = messagesItem.included_usage;
it("should create track messages, reset, and have correct rollover", async () => {
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Messages,
value: messagesItem.included_usage - rollover,
});
await timeout(3000);
await advanceTestClock({
stripeCli,
testClockId,
advanceTo: addMonths(new Date(), 1).getTime(),
waitForSeconds: 20,
});
const cus = await autumn.customers.get(customerId);
const msgesFeature = cus.features[TestFeature.Messages];
const expectedBalance = messagesItem.included_usage + rollover;
expect(msgesFeature).to.exist;
expect(msgesFeature?.balance).to.equal(expectedBalance);
// @ts-expect-error
expect(msgesFeature?.rollovers[0].balance).to.equal(rollover);
curBalance = expectedBalance;
});
});

View File

@@ -0,0 +1,108 @@
import {
type Customer,
LegacyVersion,
type LimitedItem,
RolloverDuration,
} from "@autumn/shared";
import { beforeAll, describe, expect, test } from "bun:test";
import chalk from "chalk";
import { addMonths } from "date-fns";
import type Stripe from "stripe";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "tests/setup/v2Features.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";
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
const rolloverConfig = {
max: 500,
length: 1,
duration: RolloverDuration.Month,
};
const messagesItem = constructArrearProratedItem({
featureId: TestFeature.Messages,
includedUsage: 400,
rolloverConfig,
}) as LimitedItem;
export const pro = constructProduct({
items: [messagesItem],
type: "pro",
isDefault: false,
});
const testCase = "rollover3";
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for usage price feature`)}`, () => {
const customerId = testCase;
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
let testClockId: string;
let customer: Customer;
let stripeCli: Stripe;
const curUnix = new Date().getTime();
beforeAll(async () => {
stripeCli = ctx.stripeCli;
await initProductsV0({
ctx,
products: [pro],
prefix: testCase,
customerId,
});
const res = await initCustomerV3({
ctx,
customerId,
customerData: {},
attachPm: "success",
withTestClock: true,
});
testClockId = res.testClockId!;
customer = res.customer;
});
test("should attach pro product", async () => {
await autumn.attach({
customer_id: customerId,
product_id: pro.id,
});
});
const rollover = 250;
let curBalance = messagesItem.included_usage;
test("should create track messages, reset, and have correct rollover", async () => {
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Messages,
value: messagesItem.included_usage - rollover,
});
await timeout(3000);
await advanceTestClock({
stripeCli,
testClockId,
advanceTo: addMonths(new Date(), 1).getTime(),
waitForSeconds: 20,
});
const cus = await autumn.customers.get(customerId);
const msgesFeature = cus.features[TestFeature.Messages];
const expectedBalance = messagesItem.included_usage + rollover;
expect(msgesFeature).toBeDefined();
expect(msgesFeature?.balance).toBe(expectedBalance);
// @ts-expect-error
expect(msgesFeature?.rollovers[0].balance).toBe(rollover);
curBalance = expectedBalance;
});
});

View File

@@ -0,0 +1,157 @@
import {
type AppEnv,
type Customer,
LegacyVersion,
type LimitedItem,
type Organization,
RolloverDuration,
} from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import { addMonths } from "date-fns";
import type Stripe from "stripe";
import { addPrefixToProducts } from "tests/attach/utils.js";
import { setupBefore } from "tests/before.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { createProducts } from "tests/utils/productUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { timeout } from "@/utils/genUtils.js";
import { constructPrepaidItem } from "@/utils/scriptUtils/constructItem.js";
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
const rolloverConfig = {
max: 400,
length: 1,
duration: RolloverDuration.Month,
};
const messagesItem = constructPrepaidItem({
featureId: TestFeature.Messages,
includedUsage: 100,
billingUnits: 300,
price: 10,
rolloverConfig,
}) as LimitedItem;
export const pro = constructProduct({
items: [messagesItem],
type: "pro",
isDefault: false,
});
const testCase = "rollover4";
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for usage price feature`)}`, () => {
const customerId = testCase;
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
let testClockId: string;
let db: DrizzleCli, org: Organization, env: AppEnv;
let customer: Customer;
let stripeCli: Stripe;
let curUnix = new Date().getTime();
before(async function () {
await setupBefore(this);
const { autumnJs } = this;
db = this.db;
org = this.org;
env = this.env;
stripeCli = this.stripeCli;
addPrefixToProducts({
products: [pro],
prefix: testCase,
});
await createProducts({
autumn,
products: [pro],
customerId,
db,
orgId: org.id,
env,
});
const res = await initCustomer({
autumn: autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
testClockId = res.testClockId!;
customer = res.customer;
});
const paidQuantity = 300;
const balance = paidQuantity + messagesItem.included_usage;
const options = [
{
feature_id: TestFeature.Messages,
quantity: paidQuantity,
},
];
it("should attach pro product", async () => {
await autumn.attach({
customer_id: customerId,
product_id: pro.id,
options,
});
});
const rollover = 50;
it("should create track messages, reset, and have correct rollover", async () => {
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Messages,
value: balance - rollover,
});
await timeout(3000);
curUnix = await advanceTestClock({
stripeCli,
testClockId,
advanceTo: addMonths(new Date(), 1).getTime(),
waitForSeconds: 20,
});
const cus = await autumn.customers.get(customerId);
const msgesFeature = cus.features[TestFeature.Messages];
// @ts-expect-error
const rollovers = msgesFeature?.rollovers;
expect(msgesFeature).to.exist;
expect(msgesFeature?.balance).to.equal(balance + rollover);
expect(rollovers[0].balance).to.equal(rollover);
});
// let usage2 = 50;
it("should reset again and have correct rollover", async () => {
await advanceTestClock({
stripeCli,
testClockId,
advanceTo: addMonths(curUnix, 1).getTime(),
waitForSeconds: 20,
});
const newRollover = Math.min(balance + rollover, rolloverConfig.max);
const cus = await autumn.customers.get(customerId);
const msgesFeature = cus.features[TestFeature.Messages];
// @ts-expect-error
const rollovers = msgesFeature?.rollovers;
expect(msgesFeature).to.exist;
expect(msgesFeature?.balance).to.equal(balance + newRollover);
expect(rollovers[0].balance).to.equal(0);
expect(rollovers[1].balance).to.equal(400);
});
});

View File

@@ -0,0 +1,138 @@
import {
type Customer,
LegacyVersion,
type LimitedItem,
RolloverDuration,
} from "@autumn/shared";
import { beforeAll, describe, expect, test } from "bun:test";
import chalk from "chalk";
import { addMonths } from "date-fns";
import type Stripe from "stripe";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { timeout } from "@/utils/genUtils.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";
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
const rolloverConfig = {
max: 400,
length: 1,
duration: RolloverDuration.Month,
};
const messagesItem = constructPrepaidItem({
featureId: TestFeature.Messages,
includedUsage: 100,
billingUnits: 300,
price: 10,
rolloverConfig,
}) as LimitedItem;
export const pro = constructProduct({
items: [messagesItem],
type: "pro",
isDefault: false,
});
const testCase = "rollover4";
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for usage price feature`)}`, () => {
const customerId = testCase;
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
let testClockId: string;
let customer: Customer;
let stripeCli: Stripe;
let curUnix = new Date().getTime();
beforeAll(async () => {
stripeCli = ctx.stripeCli;
await initProductsV0({
ctx,
products: [pro],
prefix: testCase,
customerId,
});
const res = await initCustomerV3({
ctx,
customerId,
customerData: {},
attachPm: "success",
withTestClock: true,
});
testClockId = res.testClockId!;
customer = res.customer;
});
const paidQuantity = 300;
const balance = paidQuantity + messagesItem.included_usage;
const options = [
{
feature_id: TestFeature.Messages,
quantity: paidQuantity,
},
];
test("should attach pro product", async () => {
await autumn.attach({
customer_id: customerId,
product_id: pro.id,
options,
});
});
const rollover = 50;
test("should create track messages, reset, and have correct rollover", async () => {
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Messages,
value: balance - rollover,
});
await timeout(3000);
curUnix = await advanceTestClock({
stripeCli,
testClockId,
advanceTo: addMonths(new Date(), 1).getTime(),
waitForSeconds: 20,
});
const cus = await autumn.customers.get(customerId);
const msgesFeature = cus.features[TestFeature.Messages];
// @ts-expect-error
const rollovers = msgesFeature?.rollovers;
expect(msgesFeature).toBeDefined();
expect(msgesFeature?.balance).toBe(balance + rollover);
expect(rollovers[0].balance).toBe(rollover);
});
// let usage2 = 50;
test("should reset again and have correct rollover", async () => {
await advanceTestClock({
stripeCli,
testClockId,
advanceTo: addMonths(curUnix, 1).getTime(),
waitForSeconds: 20,
});
const newRollover = Math.min(balance + rollover, rolloverConfig.max);
const cus = await autumn.customers.get(customerId);
const msgesFeature = cus.features[TestFeature.Messages];
// @ts-expect-error
const rollovers = msgesFeature?.rollovers;
expect(msgesFeature).toBeDefined();
expect(msgesFeature?.balance).toBe(balance + newRollover);
expect(rollovers[0].balance).toBe(0);
expect(rollovers[1].balance).toBe(400);
});
});

View File

@@ -0,0 +1,137 @@
import {
type AppEnv,
type Customer,
LegacyVersion,
type LimitedItem,
type Organization,
RolloverDuration,
} from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import type Stripe from "stripe";
import { addPrefixToProducts } from "tests/attach/utils.js";
import { setupBefore } from "tests/before.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { createProducts } from "tests/utils/productUtils.js";
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 { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { resetAndGetCusEnt } from "./rolloverTestUtils.js";
const freeRollover = { max: 1000, length: 1, duration: RolloverDuration.Month };
const proRollover = { max: 600, length: 1, duration: RolloverDuration.Month };
const freeMsges = constructFeatureItem({
featureId: TestFeature.Messages,
includedUsage: 500,
rolloverConfig: freeRollover,
}) as LimitedItem;
const proMsges = constructFeatureItem({
featureId: TestFeature.Messages,
includedUsage: 500,
rolloverConfig: proRollover,
}) as LimitedItem;
const free = constructProduct({
items: [freeMsges],
type: "free",
isDefault: false,
});
const pro = constructProduct({
items: [proMsges],
type: "pro",
});
const testCase = "rollover5";
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for upgrade`)}`, () => {
const customerId = testCase;
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
let testClockId: string;
let db: DrizzleCli, org: Organization, env: AppEnv;
let customer: Customer;
let stripeCli: Stripe;
const curUnix = new Date().getTime();
before(async function () {
await setupBefore(this);
const { autumnJs } = this;
db = this.db;
org = this.org;
env = this.env;
stripeCli = this.stripeCli;
addPrefixToProducts({
products: [free, pro],
prefix: testCase,
});
await createProducts({
autumn,
products: [free, pro],
customerId,
db,
orgId: org.id,
env,
});
const res = await initCustomer({
autumn: autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
testClockId = res.testClockId!;
customer = res.customer;
});
it("should attach free product", async () => {
await autumn.attach({
customer_id: customerId,
product_id: free.id,
});
});
it("should create rollovers", async () => {
await resetAndGetCusEnt({
customer,
db,
productGroup: testCase,
featureId: TestFeature.Messages,
});
await resetAndGetCusEnt({
customer,
db,
productGroup: testCase,
featureId: TestFeature.Messages,
});
// Attach pro
await autumn.attach({
customer_id: customerId,
product_id: pro.id,
});
const cus = await autumn.customers.get(customerId);
const msgesFeature = cus.features[TestFeature.Messages];
const freeRolloverBalance = freeMsges.included_usage * 2;
const proRolloverBalance = Math.min(proRollover.max, freeRolloverBalance);
expect(msgesFeature).to.exist;
expect(msgesFeature?.balance).to.equal(
proMsges.included_usage + proRolloverBalance,
);
// @ts-expect-error
const rollovers = msgesFeature?.rollovers;
expect(rollovers[0].balance).to.equal(100);
expect(rollovers[1].balance).to.equal(500);
});
});

View File

@@ -0,0 +1,118 @@
import {
type Customer,
LegacyVersion,
type LimitedItem,
RolloverDuration,
} from "@autumn/shared";
import { beforeAll, describe, expect, test } from "bun:test";
import chalk from "chalk";
import type Stripe from "stripe";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "tests/setup/v2Features.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";
import { resetAndGetCusEnt } from "./rolloverTestUtils.js";
const freeRollover = { max: 1000, length: 1, duration: RolloverDuration.Month };
const proRollover = { max: 600, length: 1, duration: RolloverDuration.Month };
const freeMsges = constructFeatureItem({
featureId: TestFeature.Messages,
includedUsage: 500,
rolloverConfig: freeRollover,
}) as LimitedItem;
const proMsges = constructFeatureItem({
featureId: TestFeature.Messages,
includedUsage: 500,
rolloverConfig: proRollover,
}) as LimitedItem;
const free = constructProduct({
items: [freeMsges],
type: "free",
isDefault: false,
});
const pro = constructProduct({
items: [proMsges],
type: "pro",
});
const testCase = "rollover5";
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for upgrade`)}`, () => {
const customerId = testCase;
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
let testClockId: string;
let customer: Customer;
let stripeCli: Stripe;
const curUnix = new Date().getTime();
beforeAll(async () => {
stripeCli = ctx.stripeCli;
await initProductsV0({
ctx,
products: [free, pro],
prefix: testCase,
customerId,
});
const res = await initCustomerV3({
ctx,
customerId,
customerData: {},
attachPm: "success",
withTestClock: true,
});
testClockId = res.testClockId!;
customer = res.customer;
});
test("should attach free product", async () => {
await autumn.attach({
customer_id: customerId,
product_id: free.id,
});
});
test("should create rollovers", async () => {
await resetAndGetCusEnt({
customer,
db: ctx.db,
productGroup: testCase,
featureId: TestFeature.Messages,
});
await resetAndGetCusEnt({
customer,
db: ctx.db,
productGroup: testCase,
featureId: TestFeature.Messages,
});
// Attach pro
await autumn.attach({
customer_id: customerId,
product_id: pro.id,
});
const cus = await autumn.customers.get(customerId);
const msgesFeature = cus.features[TestFeature.Messages];
const freeRolloverBalance = freeMsges.included_usage * 2;
const proRolloverBalance = Math.min(proRollover.max, freeRolloverBalance);
expect(msgesFeature).toBeDefined();
expect(msgesFeature?.balance).toBe(
proMsges.included_usage + proRolloverBalance,
);
// @ts-expect-error
const rollovers = msgesFeature?.rollovers;
expect(rollovers[0].balance).toBe(100);
expect(rollovers[1].balance).toBe(500);
});
});

View File

@@ -0,0 +1,151 @@
import {
type AppEnv,
type Customer,
LegacyVersion,
type LimitedItem,
type Organization,
RolloverDuration,
} from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import { addHours, addMonths } from "date-fns";
import type Stripe from "stripe";
import { addPrefixToProducts } from "tests/attach/utils.js";
import { setupBefore } from "tests/before.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
import { createProducts } from "tests/utils/productUtils.js";
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 { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
import { resetAndGetCusEnt } from "./rolloverTestUtils.js";
const freeRollover = { max: 600, length: 1, duration: RolloverDuration.Month };
const proRollover = { max: 1000, length: 1, duration: RolloverDuration.Month };
const freeMsges = constructFeatureItem({
featureId: TestFeature.Messages,
includedUsage: 500,
rolloverConfig: freeRollover,
}) as LimitedItem;
const proMsges = constructFeatureItem({
featureId: TestFeature.Messages,
includedUsage: 500,
rolloverConfig: proRollover,
}) as LimitedItem;
const free = constructProduct({
items: [freeMsges],
type: "free",
isDefault: false,
});
const pro = constructProduct({
items: [proMsges],
type: "pro",
});
const testCase = "rollover6";
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for upgrade`)}`, () => {
const customerId = testCase;
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
let testClockId: string;
let db: DrizzleCli, org: Organization, env: AppEnv;
let customer: Customer;
let stripeCli: Stripe;
const curUnix = new Date().getTime();
before(async function () {
await setupBefore(this);
const { autumnJs } = this;
db = this.db;
org = this.org;
env = this.env;
stripeCli = this.stripeCli;
addPrefixToProducts({
products: [free, pro],
prefix: testCase,
});
await createProducts({
autumn,
products: [free, pro],
customerId,
db,
orgId: org.id,
env,
});
const res = await initCustomer({
autumn: autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
testClockId = res.testClockId!;
customer = res.customer;
});
it("should attach free product", async () => {
await autumn.attach({
customer_id: customerId,
product_id: pro.id,
});
});
it("should create rollovers", async () => {
await resetAndGetCusEnt({
customer,
db,
productGroup: testCase,
featureId: TestFeature.Messages,
});
await resetAndGetCusEnt({
customer,
db,
productGroup: testCase,
featureId: TestFeature.Messages,
});
// Attach pro
await autumn.attach({
customer_id: customerId,
product_id: free.id,
});
await advanceTestClock({
stripeCli,
testClockId,
advanceTo: addHours(
addMonths(curUnix, 1),
hoursToFinalizeInvoice,
).getTime(),
waitForSeconds: 20,
});
const cus = await autumn.customers.get(customerId);
const msgesFeature = cus.features[TestFeature.Messages];
const proRolloverBalance = proMsges.included_usage * 2;
const freeRolloverBalance = Math.min(freeRollover.max, proRolloverBalance);
expect(msgesFeature).to.exist;
expect(msgesFeature?.balance).to.equal(
freeMsges.included_usage + freeRolloverBalance,
);
// @ts-expect-error
const rollovers = msgesFeature?.rollovers;
expect(rollovers[0].balance).to.equal(100);
expect(rollovers[1].balance).to.equal(500);
});
});

View File

@@ -0,0 +1,132 @@
import {
type Customer,
LegacyVersion,
type LimitedItem,
RolloverDuration,
} from "@autumn/shared";
import { beforeAll, describe, expect, test } from "bun:test";
import chalk from "chalk";
import { addHours, addMonths } from "date-fns";
import type Stripe from "stripe";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { hoursToFinalizeInvoice } from "tests/utils/constants.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";
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
import { resetAndGetCusEnt } from "./rolloverTestUtils.js";
const freeRollover = { max: 600, length: 1, duration: RolloverDuration.Month };
const proRollover = { max: 1000, length: 1, duration: RolloverDuration.Month };
const freeMsges = constructFeatureItem({
featureId: TestFeature.Messages,
includedUsage: 500,
rolloverConfig: freeRollover,
}) as LimitedItem;
const proMsges = constructFeatureItem({
featureId: TestFeature.Messages,
includedUsage: 500,
rolloverConfig: proRollover,
}) as LimitedItem;
const free = constructProduct({
items: [freeMsges],
type: "free",
isDefault: false,
});
const pro = constructProduct({
items: [proMsges],
type: "pro",
});
const testCase = "rollover6";
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for upgrade`)}`, () => {
const customerId = testCase;
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
let testClockId: string;
let customer: Customer;
let stripeCli: Stripe;
const curUnix = new Date().getTime();
beforeAll(async () => {
stripeCli = ctx.stripeCli;
await initProductsV0({
ctx,
products: [free, pro],
prefix: testCase,
customerId,
});
const res = await initCustomerV3({
ctx,
customerId,
customerData: {},
attachPm: "success",
withTestClock: true,
});
testClockId = res.testClockId!;
customer = res.customer;
});
test("should attach free product", async () => {
await autumn.attach({
customer_id: customerId,
product_id: pro.id,
});
});
test("should create rollovers", async () => {
await resetAndGetCusEnt({
customer,
db: ctx.db,
productGroup: testCase,
featureId: TestFeature.Messages,
});
await resetAndGetCusEnt({
customer,
db: ctx.db,
productGroup: testCase,
featureId: TestFeature.Messages,
});
// Attach pro
await autumn.attach({
customer_id: customerId,
product_id: free.id,
});
await advanceTestClock({
stripeCli,
testClockId,
advanceTo: addHours(
addMonths(curUnix, 1),
hoursToFinalizeInvoice,
).getTime(),
waitForSeconds: 20,
});
const cus = await autumn.customers.get(customerId);
const msgesFeature = cus.features[TestFeature.Messages];
const proRolloverBalance = proMsges.included_usage * 2;
const freeRolloverBalance = Math.min(freeRollover.max, proRolloverBalance);
expect(msgesFeature).toBeDefined();
expect(msgesFeature?.balance).toBe(
freeMsges.included_usage + freeRolloverBalance,
);
// @ts-expect-error
const rollovers = msgesFeature?.rollovers;
expect(rollovers[0].balance).toBe(100);
expect(rollovers[1].balance).toBe(500);
});
});

View File

@@ -34,9 +34,12 @@ export const sharedProWithOverage = constructProduct({
],
});
await (async () => {
export const initUsageSharedProducts = async () => {
await createSharedProducts({
ctx,
products: [sharedProWithOverage],
});
})();
};
// Auto-init on import (backwards compat)
await initUsageSharedProducts();

View File

@@ -1,125 +0,0 @@
import type { Customer } from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import type Stripe from "stripe";
import { setupBefore } from "tests/before.js";
import { v1ProductToBasePrice } from "tests/utils/testProductUtils/testProductUtils.js";
import { calculateMetered1Price } from "@/external/stripe/utils.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { AutumnCli } from "../../cli/AutumnCli.js";
import { features, products } from "../../global.js";
import { compareMainProduct } from "../../utils/compare.js";
import { timeout } from "../../utils/genUtils.js";
import { advanceClockForInvoice } from "../../utils/stripeUtils.js";
const testCase = "usage1";
describe(`${chalk.yellowBright("usage1: Testing basic usage product")}`, () => {
const NUM_EVENTS = 50;
const customerId = testCase;
let testClockId: string;
let customer: Customer;
let stripeCli: Stripe;
before(async function () {
await setupBefore(this);
stripeCli = this.stripeCli;
const { customer: customer_, testClockId: testClockId_ } =
await initCustomer({
customerId,
org: this.org,
env: this.env,
db: this.db,
autumn: this.autumnJs,
attachPm: "success",
});
customer = customer_;
testClockId = testClockId_;
});
it("should attach usage based product", async () => {
await AutumnCli.attach({
customerId: customerId,
productId: products.proWithOverage.id,
});
const res = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: products.proWithOverage,
cusRes: res,
});
});
it("usage1: should send metered1 events", async () => {
const batchUpdates = [];
for (let i = 0; i < NUM_EVENTS; i++) {
batchUpdates.push(
AutumnCli.sendEvent({
customerId: customerId,
eventName: features.metered1.eventName,
}),
);
}
await Promise.all(batchUpdates);
await timeout(25000);
});
it("should have correct metered1 balance after sending events", async () => {
const res: any = await AutumnCli.entitled(customerId, features.metered1.id);
expect(res!.allowed).to.be.true;
const balance = res!.balances.find(
(balance: any) => balance.feature_id === features.metered1.id,
);
const proOverageAmt =
products.proWithOverage.entitlements.metered1.allowance;
expect(res!.allowed, "should be allowed").to.be.true;
expect(balance?.balance, "should have correct metered1 balance").to.equal(
proOverageAmt! - NUM_EVENTS,
);
expect(balance?.usage_allowed, "should have usage_allowed").to.be.true;
});
// Check invoice
it("should advance stripe test clock and wait for event", async () => {
await advanceClockForInvoice({
stripeCli,
testClockId,
waitForMeterUpdate: true,
});
});
it("should have correct invoice amount", async () => {
const cusRes = await AutumnCli.getCustomer(customerId);
const invoices = cusRes!.invoices;
// calculate price
const price = calculateMetered1Price({
product: products.proWithOverage,
numEvents: NUM_EVENTS,
metered1Feature: features.metered1,
});
expect(invoices.length).to.equal(2);
const invoice = invoices[0];
const basePrice = v1ProductToBasePrice({
prices: products.proWithOverage.prices,
});
expect(invoice.total).to.equal(
price + basePrice,
"invoice total should be usage price + base price",
);
});
});

View File

@@ -1,136 +0,0 @@
import { expect } from "chai";
import chalk from "chalk";
import { Decimal } from "decimal.js";
import type Stripe from "stripe";
import { setupBefore } from "tests/before.js";
import { advanceClockForInvoice } from "tests/utils/stripeUtils.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { AutumnCli } from "../../cli/AutumnCli.js";
import { advanceProducts, creditSystems, features } from "../../global.js";
import { getCreditsUsed } from "../../utils/advancedUsageUtils.js";
import { compareMainProduct } from "../../utils/compare.js";
import { timeout } from "../../utils/genUtils.js";
// FIRST, REGULAR CHECK GPU STARTER MONTHLY
const testCase = "usage2";
describe(`${chalk.yellowBright("usage2: Testing basic usage product")}`, () => {
const customerId = testCase;
const PRECISION = 10;
const ASSERT_INVOICE_AMOUNT = true;
const CREDIT_MULTIPLIER = 100000;
let testClockId = "";
let totalCreditsUsed = 0;
let stripeCli: Stripe;
before(async function () {
await setupBefore(this);
const { testClockId: createdTestClockId } = await initCustomer({
customerId,
org: this.org,
env: this.env,
db: this.db,
autumn: this.autumnJs,
attachPm: "success",
});
testClockId = createdTestClockId;
stripeCli = this.stripeCli;
});
it("should attach gpu system starter", async () => {
await AutumnCli.attach({
customerId: customerId,
productId: advanceProducts.gpuSystemStarter.id,
});
const res = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: advanceProducts.gpuSystemStarter,
cusRes: res,
});
});
// Use up events
it("should send events and have correct balance (up to 10 DP)", async () => {
const eventCount = 20;
const batchEvents = [];
for (let i = 0; i < eventCount; i++) {
const randomVal = new Decimal(Math.random().toFixed(PRECISION))
.mul(CREDIT_MULTIPLIER)
.mul(Math.random() > 0.2 ? 1 : -1)
.toNumber();
const gpuId = i % 2 === 0 ? features.gpu1.id : features.gpu2.id;
const creditsUsed = getCreditsUsed(
creditSystems.gpuCredits,
gpuId,
randomVal,
);
totalCreditsUsed = new Decimal(totalCreditsUsed)
.plus(creditsUsed)
.toNumber();
batchEvents.push(
AutumnCli.sendEvent({
customerId: customerId,
eventName: gpuId,
properties: { value: randomVal },
}),
);
}
await Promise.all(batchEvents);
await timeout(10000);
const { allowed, balanceObj }: any = await AutumnCli.entitled(
customerId,
creditSystems.gpuCredits.id,
true,
);
const creditAllowance =
advanceProducts.gpuSystemStarter.entitlements.gpuCredits.allowance!;
expect(allowed).to.be.true;
expect(balanceObj!.balance).to.equal(
new Decimal(creditAllowance).minus(totalCreditsUsed).toNumber(),
);
// console.log(" - Total credits used: ", totalCreditsUsed);
// console.log(" - Balance: ", balanceObj!.balance);
});
// Check invoice.created event
it("should have correct invoice amount / updated meter balance", async () => {
await advanceClockForInvoice({
stripeCli,
testClockId,
waitForMeterUpdate: ASSERT_INVOICE_AMOUNT,
});
// const res = await AutumnCli.getCustomer(customerId);
// const invoices = res!.invoices;
// if (ASSERT_INVOICE_AMOUNT) {
// await checkUsageInvoiceAmount({
// invoices,
// totalUsage: totalCreditsUsed,
// product: advanceProducts.gpuSystemStarter,
// featureId: creditSystems.gpuCredits.id,
// });
// } else {
// const { allowed, balanceObj }: any = await AutumnCli.entitled(
// customerId,
// creditSystems.gpuCredits.id,
// true,
// );
// const allowance =
// advanceProducts.gpuSystemStarter.entitlements.gpuCredits.allowance!;
// assert.equal(balanceObj.balance, allowance);
// }
});
});

View File

@@ -1,140 +0,0 @@
import chalk from "chalk";
import { advanceProducts } from "../../global.js";
import { AutumnCli } from "../../cli/AutumnCli.js";
import { sendGPUEvents } from "../../utils/advancedUsageUtils.js";
import { advanceTestClock } from "../../utils/stripeUtils.js";
import { assert, expect } from "chai";
import { Decimal } from "decimal.js";
import { compareMainProduct } from "../../utils/compare.js";
import { checkSubscriptionContainsProducts } from "tests/utils/scheduleCheckUtils.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { setupBefore } from "tests/before.js";
import Stripe from "stripe";
import { priceToInvoiceAmount } from "@/internal/products/prices/priceUtils/priceToInvoiceAmount.js";
import { calculateProrationAmount } from "@/internal/invoices/prorationUtils.js";
import { getSubsFromCusId } from "tests/utils/expectUtils/expectSubUtils.js";
import { subToPeriodStartEnd } from "@/external/stripe/stripeSubUtils/convertSubUtils.js";
const testCase = "usage3";
const ASSERT_INVOICE_AMOUNT = true;
describe(`${chalk.yellowBright(
"usage3: upgrade from GPU starter monthly to GPU pro monthly",
)}`, () => {
const customerId = "usage3";
let testClockId = "";
let totalCreditsUsed = 0;
let stripeCli: Stripe;
let curUnix = 0;
before(async function () {
await setupBefore(this);
let { testClockId: insertedTestClockId } = await initCustomer({
customerId,
org: this.org,
env: this.env,
db: this.db,
autumn: this.autumnJs,
attachPm: "success",
});
testClockId = insertedTestClockId;
stripeCli = this.stripeCli;
});
// 1. Attach GPU starter monthly
it("usage3: should attach GPU starter monthly", async function () {
await AutumnCli.attach({
customerId: customerId,
productId: advanceProducts.gpuSystemStarter.id,
});
});
// 2. Send 20 events
it("usage3: should send 20 events", async function () {
let eventCount = 20;
const { creditsUsed } = await sendGPUEvents({
customerId,
eventCount,
});
totalCreditsUsed = creditsUsed;
});
// 3. Advance test clock by 15 days and upgrade
it("should advance test clock by 15 days and upgrade to GPU pro monthly", async function () {
curUnix = await advanceTestClock({
stripeCli,
testClockId,
numberOfDays: 15,
});
await AutumnCli.attach({
customerId: customerId,
productId: advanceProducts.gpuSystemPro.id,
});
// MAKE SURE STRIPE SUB ONLY HAS GPU PRO
const res = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: advanceProducts.gpuSystemPro,
cusRes: res,
});
let subscriptionId = res.products[0].subscription_ids![0]!;
await checkSubscriptionContainsProducts({
db: this.db,
org: this.org,
env: this.env,
subscriptionId,
productIds: [advanceProducts.gpuSystemPro.id],
});
});
// 4. Check invoice for 15 days of starter usage
it("should have invoice for 15 days of starter usage", async function () {
const res = await AutumnCli.getCustomer(customerId);
const invoices = res!.invoices;
let basePrice1 = advanceProducts.gpuSystemStarter.prices[0].config.amount;
let basePrice2 = advanceProducts.gpuSystemPro.prices[0].config.amount;
let { subs } = await getSubsFromCusId({
db: this.db,
org: this.org,
env: this.env,
customerId,
stripeCli,
productId: advanceProducts.gpuSystemPro.id,
});
let sub = subs[0];
const { start, end } = subToPeriodStartEnd({ sub });
let baseDiff = calculateProrationAmount({
periodStart: start * 1000,
periodEnd: end * 1000,
now: curUnix,
amount: basePrice2 - basePrice1,
allowNegative: true,
});
let usagePrice = advanceProducts.gpuSystemStarter.prices[1];
let overage =
totalCreditsUsed -
advanceProducts.gpuSystemStarter.entitlements.gpuCredits.allowance!;
let overagePrice = priceToInvoiceAmount({
price: usagePrice,
overage,
});
let calculatedTotal = new Decimal(baseDiff)
.plus(overagePrice)
.toDecimalPlaces(2)
.toNumber();
expect(invoices[0].total).to.equal(calculatedTotal);
});
});

View File

@@ -1,172 +0,0 @@
import type { Customer } from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import type Stripe from "stripe";
import { setupBefore } from "tests/before.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { AutumnCli } from "../../cli/AutumnCli.js";
import { advanceProducts, creditSystems } from "../../global.js";
import {
checkCreditBalance,
checkUsageInvoiceAmount,
sendGPUEvents,
} from "../../utils/advancedUsageUtils.js";
import { compareMainProduct } from "../../utils/compare.js";
import { advanceClockForInvoice } from "../../utils/stripeUtils.js";
// THIRD, TEST GPU PRO ANNUAL
const testCase = "usage4";
describe(`${chalk.yellowBright("usage4: GPU starter annual")}`, () => {
const customerId = testCase;
let totalCreditsUsed = 0;
let testClockId = "";
let customer: Customer;
let stripeCli: Stripe;
before(async function () {
await setupBefore(this);
const res = await initCustomer({
customerId,
org: this.org,
env: this.env,
db: this.db,
autumn: this.autumnJs,
attachPm: "success",
});
testClockId = res.testClockId;
customer = res.customer;
stripeCli = this.stripeCli;
});
it("should attach GPU starter annual", async () => {
await AutumnCli.attach({
customerId: customerId,
productId: advanceProducts.gpuStarterAnnual.id,
});
const res = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: advanceProducts.gpuStarterAnnual,
cusRes: res,
});
expect(res!.invoices.length).to.equal(1);
});
it("should send 20 events and have correct balance", async () => {
const eventCount = 20;
const { creditsUsed } = await sendGPUEvents({
customerId,
eventCount,
});
totalCreditsUsed = creditsUsed;
await checkCreditBalance({
customerId,
featureId: creditSystems.gpuCredits.id,
totalCreditsUsed,
originalAllowance:
advanceProducts.gpuStarterAnnual.entitlements.gpuCredits.allowance!,
});
});
it("should have invoice after a month and correct balance", async () => {
await advanceClockForInvoice({
stripeCli,
testClockId,
waitForMeterUpdate: true,
});
const res = await AutumnCli.getCustomer(customerId);
const invoices = res!.invoices;
const invoiceIndex = invoices.findIndex((invoice: any) =>
invoice.product_ids.includes(advanceProducts.gpuStarterAnnual.id),
);
await checkUsageInvoiceAmount({
invoices,
totalUsage: totalCreditsUsed,
product: advanceProducts.gpuStarterAnnual,
featureId: creditSystems.gpuCredits.id,
invoiceIndex,
includeBase: false,
});
await checkCreditBalance({
customerId,
featureId: creditSystems.gpuCredits.id,
totalCreditsUsed: 0,
originalAllowance:
advanceProducts.gpuStarterAnnual.entitlements.gpuCredits.allowance!,
});
});
});
// // Advance by 1 year and check if latest invoice is correct
// it.skip("should have correct invoice after 1 year", async function () {
// const stripeCli = createStripeCli({ org: this.org, env: this.env });
// // 1. Advance by 11 months
// let numberOfMonths = 11;
// await advanceMonths({
// stripeCli,
// testClockId,
// numberOfMonths,
// });
// // 2. Send 20 events
// let eventCount = 20;
// const { creditsUsed } = await sendGPUEvents({
// customerId,
// eventCount,
// });
// let totalCreditsUsed = creditsUsed;
// console.log(" - Total credits used: ", totalCreditsUsed);
// // Advance by a month and check for usage
// await advanceClockForInvoice({
// stripeCli,
// testClockId,
// waitForMeterUpdate: true,
// startingFrom: addMonths(new Date(), numberOfMonths),
// });
// const res = await AutumnCli.getCustomer(customerId);
// const invoices = res!.invoices;
// let usagePrice = await getUsageInArrearPrice({
// org: this.org,
// env: this.env,
// productId: advanceProducts.gpuStarterAnnual.id,
// });
// // Get billing meter event summary
// let eventSummary = await checkBillingMeterEventSummary({
// stripeCli,
// startTime: addMonths(new Date(), 11),
// stripeMeterId: usagePrice?.config?.stripe_meter_id,
// stripeCustomerId: customer.processor.id,
// });
// try {
// assert.exists(eventSummary);
// assert.equal(
// eventSummary?.aggregated_value,
// Math.round(totalCreditsUsed),
// );
// assert.equal(invoices.length, 13 + 2);
// } catch (error) {
// console.group();
// console.log(" - Event summary: ", eventSummary);
// console.log(" - Total credits used: ", totalCreditsUsed);
// console.log(" - Last 3 invoices: ", invoices.slice(-3));
// console.groupEnd();
// throw error;
// }
// });

View File

@@ -0,0 +1,151 @@
import {
type AppEnv,
ErrCode,
LegacyVersion,
type Organization,
} from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import type Stripe from "stripe";
import { addPrefixToProducts } from "tests/attach/utils.js";
import { setupBefore } from "tests/before.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
import { expectAutumnError } from "tests/utils/expectUtils/expectErrUtils.js";
import { createProducts } from "tests/utils/productUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { constructArrearProratedItem } from "@/utils/scriptUtils/constructItem.js";
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
const userItem = constructArrearProratedItem({
featureId: TestFeature.Users,
pricePerUnit: 50,
includedUsage: 0,
usageLimit: 2,
});
export const pro = constructProduct({
items: [userItem],
type: "pro",
});
const testCase = "usageLimit1";
describe(`${chalk.yellowBright(`${testCase}: Testing usage limits for entities`)}`, () => {
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();
before(async function () {
await setupBefore(this);
const { autumnJs } = this;
db = this.db;
org = this.org;
env = this.env;
stripeCli = this.stripeCli;
addPrefixToProducts({
products: [pro],
prefix: testCase,
});
await createProducts({
autumn,
products: [pro],
customerId,
db,
orgId: org.id,
env,
});
const { testClockId: testClockId1 } = await initCustomer({
autumn: autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
testClockId = testClockId1!;
});
const entities = [
{
id: "1",
name: "Entity 1",
feature_id: TestFeature.Users,
},
{
id: "2",
name: "Entity 2",
feature_id: TestFeature.Users,
},
{
id: "3",
name: "Entity 3",
feature_id: TestFeature.Users,
},
{
id: "4",
name: "Entity 4",
feature_id: TestFeature.Users,
},
];
it("should attach pro product", async () => {
await attachAndExpectCorrect({
autumn,
customerId,
product: pro,
stripeCli,
db,
org,
env,
});
});
it("should create more entities than the limit and hit error", async () => {
await expectAutumnError({
errCode: ErrCode.FeatureLimitReached,
func: async () => {
await autumn.entities.create(customerId, entities);
},
});
});
it("should create entities one by one, then hit usage limit", async () => {
await autumn.entities.create(customerId, entities[0]);
await autumn.entities.create(customerId, entities[1]);
await expectAutumnError({
errCode: ErrCode.FeatureLimitReached,
func: async () => {
await autumn.entities.create(customerId, entities[2]);
},
});
});
it("should have correct check and get customer value", async () => {
const check = await autumn.check({
customer_id: customerId,
feature_id: TestFeature.Users,
});
const customer = await autumn.customers.get(customerId);
expect(check.balance).to.equal(-2);
// @ts-expect-error
expect(check.usage_limit).to.equal(userItem.usage_limit);
// @ts-expect-error
expect(customer.features[TestFeature.Users].usage_limit).to.equal(
userItem.usage_limit,
);
});
});

View File

@@ -0,0 +1,132 @@
import {
ErrCode,
LegacyVersion,
} from "@autumn/shared";
import { beforeAll, describe, expect, test } from "bun:test";
import chalk from "chalk";
import type Stripe from "stripe";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
import { expectAutumnError } from "tests/utils/expectUtils/expectErrUtils.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { constructArrearProratedItem } from "@/utils/scriptUtils/constructItem.js";
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
const userItem = constructArrearProratedItem({
featureId: TestFeature.Users,
pricePerUnit: 50,
includedUsage: 0,
usageLimit: 2,
});
export const pro = constructProduct({
items: [userItem],
type: "pro",
});
const testCase = "usageLimit1";
describe(`${chalk.yellowBright(`${testCase}: Testing usage limits for entities`)}`, () => {
const customerId = testCase;
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
let testClockId: string;
let stripeCli: Stripe;
const curUnix = new Date().getTime();
beforeAll(async () => {
stripeCli = ctx.stripeCli;
await initProductsV0({
ctx,
products: [pro],
prefix: testCase,
customerId,
});
const { testClockId: testClockId1 } = await initCustomerV3({
ctx,
customerId,
customerData: {},
attachPm: "success",
withTestClock: true,
});
testClockId = testClockId1!;
});
const entities = [
{
id: "1",
name: "Entity 1",
feature_id: TestFeature.Users,
},
{
id: "2",
name: "Entity 2",
feature_id: TestFeature.Users,
},
{
id: "3",
name: "Entity 3",
feature_id: TestFeature.Users,
},
{
id: "4",
name: "Entity 4",
feature_id: TestFeature.Users,
},
];
test("should attach pro product", async () => {
await attachAndExpectCorrect({
autumn,
customerId,
product: pro,
stripeCli: ctx.stripeCli,
db: ctx.db,
org: ctx.org,
env: ctx.env,
});
});
test("should create more entities than the limit and hit error", async () => {
await expectAutumnError({
errCode: ErrCode.FeatureLimitReached,
func: async () => {
await autumn.entities.create(customerId, entities);
},
});
});
test("should create entities one by one, then hit usage limit", async () => {
await autumn.entities.create(customerId, entities[0]);
await autumn.entities.create(customerId, entities[1]);
await expectAutumnError({
errCode: ErrCode.FeatureLimitReached,
func: async () => {
await autumn.entities.create(customerId, entities[2]);
},
});
});
test("should have correct check and get customer value", async () => {
const check = await autumn.check({
customer_id: customerId,
feature_id: TestFeature.Users,
});
const customer = await autumn.customers.get(customerId);
expect(check.balance).toBe(-2);
// @ts-expect-error
expect(check.usage_limit).toBe(userItem.usage_limit);
// @ts-expect-error
expect(customer.features[TestFeature.Users].usage_limit).toBe(
userItem.usage_limit,
);
});
});

View File

@@ -0,0 +1,195 @@
import {
type AppEnv,
LegacyVersion,
type LimitedItem,
type Organization,
} from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import type Stripe from "stripe";
import { addPrefixToProducts } from "tests/attach/utils.js";
import { setupBefore } from "tests/before.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
import { createProducts } from "tests/utils/productUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { timeout } from "@/utils/genUtils.js";
import {
constructArrearItem,
constructFeatureItem,
} from "@/utils/scriptUtils/constructItem.js";
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
const messageItem = constructArrearItem({
featureId: TestFeature.Messages,
includedUsage: 100,
billingUnits: 1,
price: 0.5,
usageLimit: 500,
}) as LimitedItem;
export const pro = constructProduct({
items: [messageItem],
type: "pro",
});
const addOnMessages = constructFeatureItem({
featureId: TestFeature.Messages,
interval: null,
includedUsage: 250,
}) as LimitedItem;
const messageAddOn = constructProduct({
type: "one_off",
items: [addOnMessages],
});
const testCase = "usageLimit2";
describe(`${chalk.yellowBright(`${testCase}: Testing usage limits, usage prices`)}`, () => {
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();
before(async function () {
await setupBefore(this);
const { autumnJs } = this;
db = this.db;
org = this.org;
env = this.env;
stripeCli = this.stripeCli;
addPrefixToProducts({
products: [pro, messageAddOn],
prefix: testCase,
});
await createProducts({
autumn,
products: [pro, messageAddOn],
customerId,
db,
orgId: org.id,
env,
});
const { testClockId: testClockId1 } = await initCustomer({
autumn: autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
testClockId = testClockId1!;
});
it("should attach pro product", async () => {
await attachAndExpectCorrect({
autumn,
customerId,
product: pro,
stripeCli,
db,
org,
env,
});
});
const initialUsage =
messageItem.included_usage + messageItem.usage_limit! + 1000;
it("should track more messages than limit and not surpass", async () => {
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Messages,
value: initialUsage,
});
await timeout(2000);
const check = await autumn.check({
customer_id: customerId,
feature_id: TestFeature.Messages,
});
const customer = await autumn.customers.get(customerId);
const expectedBalance =
messageItem.included_usage - messageItem.usage_limit!;
expect(check.balance).to.equal(expectedBalance);
expect(check.allowed).to.equal(false);
// @ts-expect-error
expect(check.usage_limit!).to.equal(messageItem.usage_limit!);
// @ts-expect-error
expect(customer.features[TestFeature.Messages].usage_limit).to.equal(
messageItem.usage_limit!,
);
});
it("should purchase add ons and have correct check results", async () => {
await autumn.attach({
customer_id: customerId,
product_id: messageAddOn.id,
});
const check = await autumn.check({
customer_id: customerId,
feature_id: TestFeature.Messages,
});
const customer = await autumn.customers.get(customerId);
const expectedBalance =
messageItem.included_usage -
messageItem.usage_limit! +
addOnMessages.included_usage;
expect(check.balance).to.equal(expectedBalance);
expect(check.allowed).to.equal(true);
// @ts-expect-error
expect(check.usage_limit!).to.equal(
messageItem.usage_limit! + addOnMessages.included_usage,
);
// @ts-expect-error
expect(customer.features[TestFeature.Messages].usage_limit).to.equal(
messageItem.usage_limit! + addOnMessages.included_usage,
);
});
it("should use up all add ons and have correct check results", async () => {
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Messages,
value: addOnMessages.included_usage + 500,
});
await timeout(2000);
const check = await autumn.check({
customer_id: customerId,
feature_id: TestFeature.Messages,
});
const customer = await autumn.customers.get(customerId);
const expectedBalance =
messageItem.included_usage - messageItem.usage_limit!;
expect(check.balance).to.equal(expectedBalance);
expect(check.allowed).to.equal(false);
// @ts-expect-error
expect(check.usage_limit!).to.equal(
messageItem.usage_limit! + addOnMessages.included_usage,
);
// @ts-expect-error
expect(customer.features[TestFeature.Messages].usage_limit).to.equal(
messageItem.usage_limit! + addOnMessages.included_usage,
);
});
});

View File

@@ -0,0 +1,176 @@
import {
LegacyVersion,
type LimitedItem,
} from "@autumn/shared";
import { beforeAll, describe, expect, test } from "bun:test";
import chalk from "chalk";
import type Stripe from "stripe";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { timeout } from "@/utils/genUtils.js";
import {
constructArrearItem,
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 messageItem = constructArrearItem({
featureId: TestFeature.Messages,
includedUsage: 100,
billingUnits: 1,
price: 0.5,
usageLimit: 500,
}) as LimitedItem;
export const pro = constructProduct({
items: [messageItem],
type: "pro",
});
const addOnMessages = constructFeatureItem({
featureId: TestFeature.Messages,
interval: null,
includedUsage: 250,
}) as LimitedItem;
const messageAddOn = constructProduct({
type: "one_off",
items: [addOnMessages],
});
const testCase = "usageLimit2";
describe(`${chalk.yellowBright(`${testCase}: Testing usage limits, usage prices`)}`, () => {
const customerId = testCase;
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
let testClockId: string;
let stripeCli: Stripe;
const curUnix = new Date().getTime();
beforeAll(async () => {
stripeCli = ctx.stripeCli;
await initProductsV0({
ctx,
products: [pro, messageAddOn],
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 initialUsage =
messageItem.included_usage + messageItem.usage_limit! + 1000;
test("should track more messages than limit and not surpass", async () => {
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Messages,
value: initialUsage,
});
await timeout(2000);
const check = await autumn.check({
customer_id: customerId,
feature_id: TestFeature.Messages,
});
const customer = await autumn.customers.get(customerId);
const expectedBalance =
messageItem.included_usage - messageItem.usage_limit!;
expect(check.balance).toBe(expectedBalance);
expect(check.allowed).toBe(false);
// @ts-expect-error
expect(check.usage_limit!).toBe(messageItem.usage_limit!);
// @ts-expect-error
expect(customer.features[TestFeature.Messages].usage_limit).toBe(
messageItem.usage_limit!,
);
});
test("should purchase add ons and have correct check results", async () => {
await autumn.attach({
customer_id: customerId,
product_id: messageAddOn.id,
});
const check = await autumn.check({
customer_id: customerId,
feature_id: TestFeature.Messages,
});
const customer = await autumn.customers.get(customerId);
const expectedBalance =
messageItem.included_usage -
messageItem.usage_limit! +
addOnMessages.included_usage;
expect(check.balance).toBe(expectedBalance);
expect(check.allowed).toBe(true);
// @ts-expect-error
expect(check.usage_limit!).toBe(
messageItem.usage_limit! + addOnMessages.included_usage,
);
// @ts-expect-error
expect(customer.features[TestFeature.Messages].usage_limit).toBe(
messageItem.usage_limit! + addOnMessages.included_usage,
);
});
test("should use up all add ons and have correct check results", async () => {
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Messages,
value: addOnMessages.included_usage + 500,
});
await timeout(2000);
const check = await autumn.check({
customer_id: customerId,
feature_id: TestFeature.Messages,
});
const customer = await autumn.customers.get(customerId);
const expectedBalance =
messageItem.included_usage - messageItem.usage_limit!;
expect(check.balance).toBe(expectedBalance);
expect(check.allowed).toBe(false);
// @ts-expect-error
expect(check.usage_limit!).toBe(
messageItem.usage_limit! + addOnMessages.included_usage,
);
// @ts-expect-error
expect(customer.features[TestFeature.Messages].usage_limit).toBe(
messageItem.usage_limit! + addOnMessages.included_usage,
);
});
});

View File

@@ -0,0 +1,147 @@
import {
type AppEnv,
ErrCode,
LegacyVersion,
type LimitedItem,
type Organization,
} from "@autumn/shared";
import chalk from "chalk";
import type Stripe from "stripe";
import { addPrefixToProducts } from "tests/attach/utils.js";
import { setupBefore } from "tests/before.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
import { expectAutumnError } from "tests/utils/expectUtils/expectErrUtils.js";
import { createProducts } from "tests/utils/productUtils.js";
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 { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
const messageItem = constructPrepaidItem({
featureId: TestFeature.Messages,
includedUsage: 50,
billingUnits: 100,
price: 8,
usageLimit: 500,
}) as LimitedItem;
export const pro = constructProduct({
items: [messageItem],
type: "pro",
});
// const addOnMessages = constructFeatureItem({
// featureId: TestFeature.Messages,
// interval: null,
// includedUsage: 250,
// }) as LimitedItem;
// const messageAddOn = constructProduct({
// type: "one_off",
// items: [addOnMessages],
// });
const testCase = "usageLimit3";
describe(`${chalk.yellowBright(`${testCase}: Testing usage limits for prepaid`)}`, () => {
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();
before(async function () {
await setupBefore(this);
const { autumnJs } = this;
db = this.db;
org = this.org;
env = this.env;
stripeCli = this.stripeCli;
addPrefixToProducts({
products: [pro],
prefix: testCase,
});
await createProducts({
autumn,
products: [pro],
customerId,
db,
orgId: org.id,
env,
});
const { testClockId: testClockId1 } = await initCustomer({
autumn: autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
testClockId = testClockId1!;
});
it("should attach pro product with quantity exceeding usage limit and get an error", async () => {
expectAutumnError({
errCode: ErrCode.InvalidOptions,
func: async () => {
return await attachAndExpectCorrect({
autumn,
customerId,
product: pro,
stripeCli,
db,
org,
env,
options: [
{
feature_id: TestFeature.Messages,
quantity: 600,
},
],
});
},
});
});
it("should attach pro product and update quantity with quantity exceeding usage limit and get an error", async () => {
await autumn.attach({
customer_id: customerId,
product_id: pro.id,
options: [
{
feature_id: TestFeature.Messages,
quantity: 100,
},
],
});
expectAutumnError({
errCode: ErrCode.InvalidOptions,
func: async () => {
return await attachAndExpectCorrect({
autumn,
customerId,
product: pro,
stripeCli,
db,
org,
env,
options: [
{
feature_id: TestFeature.Messages,
quantity: 600,
},
],
});
},
});
});
});

View File

@@ -0,0 +1,129 @@
import {
ErrCode,
LegacyVersion,
type LimitedItem,
} from "@autumn/shared";
import { beforeAll, describe, test } from "bun:test";
import chalk from "chalk";
import type Stripe from "stripe";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
import { expectAutumnError } from "tests/utils/expectUtils/expectErrUtils.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 messageItem = constructPrepaidItem({
featureId: TestFeature.Messages,
includedUsage: 50,
billingUnits: 100,
price: 8,
usageLimit: 500,
}) as LimitedItem;
export const pro = constructProduct({
items: [messageItem],
type: "pro",
});
// const addOnMessages = constructFeatureItem({
// featureId: TestFeature.Messages,
// interval: null,
// includedUsage: 250,
// }) as LimitedItem;
// const messageAddOn = constructProduct({
// type: "one_off",
// items: [addOnMessages],
// });
const testCase = "usageLimit3";
describe(`${chalk.yellowBright(`${testCase}: Testing usage limits for prepaid`)}`, () => {
const customerId = testCase;
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
let testClockId: string;
let stripeCli: Stripe;
const curUnix = new Date().getTime();
beforeAll(async () => {
stripeCli = ctx.stripeCli;
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 with quantity exceeding usage limit and get an error", async () => {
expectAutumnError({
errCode: ErrCode.InvalidOptions,
func: async () => {
return await attachAndExpectCorrect({
autumn,
customerId,
product: pro,
stripeCli: ctx.stripeCli,
db: ctx.db,
org: ctx.org,
env: ctx.env,
options: [
{
feature_id: TestFeature.Messages,
quantity: 600,
},
],
});
},
});
});
test("should attach pro product and update quantity with quantity exceeding usage limit and get an error", async () => {
await autumn.attach({
customer_id: customerId,
product_id: pro.id,
options: [
{
feature_id: TestFeature.Messages,
quantity: 100,
},
],
});
expectAutumnError({
errCode: ErrCode.InvalidOptions,
func: async () => {
return await attachAndExpectCorrect({
autumn,
customerId,
product: pro,
stripeCli: ctx.stripeCli,
db: ctx.db,
org: ctx.org,
env: ctx.env,
options: [
{
feature_id: TestFeature.Messages,
quantity: 600,
},
],
});
},
});
});
});

View File

@@ -1,48 +1,47 @@
import {
type AppEnv,
AttachErrCode,
ErrCode,
LegacyVersion,
type LimitedItem,
type Organization,
} from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import { addWeeks } from "date-fns";
import type Stripe from "stripe";
import { addPrefixToProducts } from "tests/attach/utils.js";
import { setupBefore } from "tests/before.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
import { expectAutumnError } from "tests/utils/expectUtils/expectErrUtils.js";
import { createProducts } from "tests/utils/productUtils.js";
import { advanceTestClock } from "tests/utils/stripeUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { timeout } from "@/utils/genUtils.js";
import { constructPrepaidItem } from "@/utils/scriptUtils/constructItem.js";
import { constructArrearProratedItem } from "@/utils/scriptUtils/constructItem.js";
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { addPrefixToProducts } from "../utils.js";
const testCase = "updateQuantity1";
const messageItem = constructArrearProratedItem({
featureId: TestFeature.Users,
includedUsage: 1,
pricePerUnit: 10,
usageLimit: 3,
}) as LimitedItem;
export const pro = constructProduct({
items: [
constructPrepaidItem({
featureId: TestFeature.Users,
price: 12,
billingUnits: 1,
}),
],
items: [messageItem],
type: "pro",
});
describe(`${chalk.yellowBright(`${testCase}: Testing upgrades with prepaid single use`)}`, () => {
const testCase = "usageLimit4";
describe(`${chalk.yellowBright(`${testCase}: Testing usage limits for cont use item`)}`, () => {
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;
const curUnix = new Date().getTime();
before(async function () {
await setupBefore(this);
@@ -53,6 +52,20 @@ describe(`${chalk.yellowBright(`${testCase}: Testing upgrades with prepaid singl
stripeCli = this.stripeCli;
addPrefixToProducts({
products: [pro],
prefix: testCase,
});
await createProducts({
autumn,
products: [pro],
customerId,
db,
orgId: org.id,
env,
});
const { testClockId: testClockId1 } = await initCustomer({
autumn: autumnJs,
customerId,
@@ -62,30 +75,10 @@ describe(`${chalk.yellowBright(`${testCase}: Testing upgrades with prepaid singl
attachPm: "success",
});
addPrefixToProducts({
products: [pro],
prefix: testCase,
});
await createProducts({
autumn,
products: [pro],
db,
orgId: org.id,
env,
});
testClockId = testClockId1!;
});
const proOpts = [
{
feature_id: TestFeature.Users,
quantity: 2,
},
];
it("should attach pro product (arrear prorated)", async () => {
it("should attach pro product with quantity exceeding usage limit and get an error", async () => {
await attachAndExpectCorrect({
autumn,
customerId,
@@ -94,61 +87,26 @@ describe(`${chalk.yellowBright(`${testCase}: Testing upgrades with prepaid singl
db,
org,
env,
options: proOpts,
});
});
it("should throw error if try to attach same options", async () => {
it("should attach pro product and update quantity with quantity exceeding usage limit and get an error", async () => {
await expectAutumnError({
errCode: AttachErrCode.ProductAlreadyAttached,
errCode: ErrCode.InvalidInputs,
func: async () => {
await autumn.attach({
await autumn.track({
customer_id: customerId,
product_id: pro.id,
options: proOpts,
feature_id: TestFeature.Users,
value: messageItem.usage_limit! + 1,
});
},
});
});
const updatedOpts = [
{
feature_id: TestFeature.Users,
quantity: 4,
},
];
it("should update quantity to 4 users and have usage stay the same", async () => {
await autumn.track({
const check = await autumn.check({
customer_id: customerId,
feature_id: TestFeature.Users,
value: 2,
});
await timeout(3000);
curUnix = await advanceTestClock({
stripeCli,
testClockId,
advanceTo: addWeeks(curUnix, 1).getTime(),
waitForSeconds: 30,
});
await attachAndExpectCorrect({
autumn,
customerId,
product: pro,
stripeCli,
db,
org,
env,
options: updatedOpts,
usage: [
{
featureId: TestFeature.Users,
value: 2,
},
],
waitForInvoice: 15000,
});
expect(check.balance).to.equal(0);
expect(check.allowed).to.equal(true);
});
});

View File

@@ -0,0 +1,93 @@
import {
ErrCode,
LegacyVersion,
type LimitedItem,
} from "@autumn/shared";
import { beforeAll, describe, expect, test } from "bun:test";
import chalk from "chalk";
import type Stripe from "stripe";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
import { expectAutumnError } from "tests/utils/expectUtils/expectErrUtils.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { constructArrearProratedItem } from "@/utils/scriptUtils/constructItem.js";
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
const messageItem = constructArrearProratedItem({
featureId: TestFeature.Users,
includedUsage: 1,
pricePerUnit: 10,
usageLimit: 3,
}) as LimitedItem;
export const pro = constructProduct({
items: [messageItem],
type: "pro",
});
const testCase = "usageLimit4";
describe(`${chalk.yellowBright(`${testCase}: Testing usage limits for cont use item`)}`, () => {
const customerId = testCase;
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
let testClockId: string;
let stripeCli: Stripe;
const curUnix = new Date().getTime();
beforeAll(async () => {
stripeCli = ctx.stripeCli;
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 with quantity exceeding usage limit and get an error", async () => {
await attachAndExpectCorrect({
autumn,
customerId,
product: pro,
stripeCli: ctx.stripeCli,
db: ctx.db,
org: ctx.org,
env: ctx.env,
});
});
test("should attach pro product and update quantity with quantity exceeding usage limit and get an error", async () => {
await expectAutumnError({
errCode: ErrCode.InvalidInputs,
func: async () => {
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Users,
value: messageItem.usage_limit! + 1,
});
},
});
const check = await autumn.check({
customer_id: customerId,
feature_id: TestFeature.Users,
});
expect(check.balance).toBe(0);
expect(check.allowed).toBe(true);
});
});

View File

@@ -9,7 +9,7 @@ import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { createStripeCli } from "@/external/connect/createStripeCli.js";
import { timeout } from "@/utils/genUtils.js";
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
import { sharedDefaultFree, sharedProProduct } from "./sharedProducts.js";
import { sharedDefaultFree, sharedProProduct, initBasicSharedProducts } from "./sharedProducts.js";
const testCase = "basic3";
const customerId = testCase;
@@ -25,6 +25,9 @@ describe(`${chalk.yellowBright("basic3: Testing cancel through Stripe at period
beforeAll(async () => {
stripeCli = createStripeCli({ org: ctx.org, env: ctx.env });
// Explicitly ensure shared products exist
await initBasicSharedProducts();
// Then create customer with payment method
await initCustomerV3({
ctx,

View File

@@ -43,9 +43,12 @@ export const sharedProProduct = constructProduct({
],
});
await (async () => {
export const initBasicSharedProducts = async () => {
await createSharedProducts({
ctx,
products: [sharedDefaultFree, sharedProProduct],
});
})();
};
// Auto-init on import (backwards compat)
await initBasicSharedProducts();

View File

@@ -13,6 +13,7 @@ import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js"
import {
sharedProProduct,
sharedPremiumProduct,
initDowngradeSharedProducts,
} from "./sharedProducts.js";
const testCase = "downgrade5";
@@ -25,6 +26,9 @@ describe(`${chalk.yellowBright(`${testCase}: testing basic downgrade (paid to pa
beforeAll(async () => {
stripeCli = ctx.stripeCli;
// Explicitly ensure shared products exist
await initDowngradeSharedProducts();
const { testClockId: testClockId_ } = await initCustomerV3({
ctx,
customerId,
@@ -111,7 +115,6 @@ describe(`${chalk.yellowBright(`${testCase}: testing basic downgrade (paid to pa
expectCustomerV0Correct({
sent: sharedProProduct,
cusRes: res,
ctx,
});
});
});

View File

@@ -9,6 +9,7 @@ import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js"
import {
sharedFreeProduct,
sharedPremiumProduct,
initDowngradeSharedProducts,
} from "./sharedProducts.js";
const testCase = "downgrade6";
@@ -19,6 +20,9 @@ describe(`${chalk.yellowBright(`${testCase}: testing expire button`)}`, () => {
let customer: Customer;
beforeAll(async () => {
// Explicitly ensure shared products exist
await initDowngradeSharedProducts();
const { testClockId: testClockId_, customer: customer_ } =
await initCustomerV3({
ctx,
@@ -59,7 +63,6 @@ describe(`${chalk.yellowBright(`${testCase}: testing expire button`)}`, () => {
expectCustomerV0Correct({
sent: sharedFreeProduct,
cusRes: res,
ctx,
});
});
});

View File

@@ -11,6 +11,7 @@ import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js"
import {
sharedProProduct,
sharedPremiumProduct,
initDowngradeSharedProducts,
} from "./sharedProducts.js";
const testCase = "downgrade7";
@@ -24,6 +25,9 @@ describe(`${chalk.yellowBright(`${testCase}: testing expire scheduled product`)}
beforeAll(async () => {
stripeCli = ctx.stripeCli;
// Explicitly ensure shared products exist
await initDowngradeSharedProducts();
const { testClockId: testClockId_, customer: customer_ } =
await initCustomerV3({
ctx,
@@ -72,7 +76,6 @@ describe(`${chalk.yellowBright(`${testCase}: testing expire scheduled product`)}
expectCustomerV0Correct({
sent: sharedPremiumProduct,
cusRes: res,
ctx,
});
const { subs } = await getSubsFromCusId({

View File

@@ -15,6 +15,7 @@ export const sharedFreeProduct = constructProduct({
id: "shared-downgrade-free",
type: "free",
isDefault: true,
excludeBase: true,
items: [
constructFeatureItem({
featureId: TestFeature.Messages,
@@ -27,6 +28,7 @@ export const sharedFreeProduct = constructProduct({
export const sharedProProduct = constructProduct({
id: "shared-downgrade-pro",
type: "pro",
excludeBase: true,
items: [
constructFeatureItem({
featureId: TestFeature.Dashboard,
@@ -51,6 +53,7 @@ export const sharedProProduct = constructProduct({
export const sharedPremiumProduct = constructProduct({
id: "shared-downgrade-premium",
type: "premium",
excludeBase: true,
items: [
constructFeatureItem({
featureId: TestFeature.Messages,
@@ -64,9 +67,12 @@ export const sharedPremiumProduct = constructProduct({
],
});
await (async () => {
export const initDowngradeSharedProducts = async () => {
await createSharedProducts({
ctx,
products: [sharedFreeProduct, sharedProProduct, sharedPremiumProduct],
});
})();
};
// Auto-init on import (backwards compat)
await initDowngradeSharedProducts();

View File

@@ -154,7 +154,7 @@ export const sharedFreeGroup2 = constructProduct({
],
});
await (async () => {
export const initMultiProductSharedProducts = async () => {
await createSharedProducts({
ctx,
products: [
@@ -167,4 +167,7 @@ await (async () => {
sharedFreeGroup2,
],
});
})();
};
// Auto-init on import (backwards compat)
await initMultiProductSharedProducts();

View File

@@ -1,29 +1,20 @@
import {
type AppEnv,
type Customer,
LegacyVersion,
OnDecrease,
OnIncrease,
type Organization,
} from "@autumn/shared";
import { expect } from "chai";
import { type Customer, LegacyVersion, OnDecrease, OnIncrease } from "@autumn/shared";
import { beforeAll, describe, expect, test } from "bun:test";
import chalk from "chalk";
import { addHours, addMonths } from "date-fns";
import type Stripe from "stripe";
import { setupBefore } from "tests/before.js";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
import { createProducts } from "tests/utils/productUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { timeout } from "@/utils/genUtils.js";
import { constructPrepaidItem } from "@/utils/scriptUtils/constructItem.js";
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
import { addPrefixToProducts } from "../utils.js";
const userItem = constructPrepaidItem({
featureId: TestFeature.Users,
@@ -46,41 +37,24 @@ describe(`${chalk.yellowBright(`attach/${testCase}: update quantity, no proratio
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();
let customer: Customer;
before(async function () {
await setupBefore(this);
const { autumnJs } = this;
db = this.db;
org = this.org;
env = this.env;
stripeCli = this.stripeCli;
const res = await initCustomer({
autumn: autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
addPrefixToProducts({
beforeAll(async () => {
await initProductsV0({
ctx,
products: [pro],
prefix: testCase,
customerId,
});
await createProducts({
autumn,
products: [pro],
db,
orgId: org.id,
env,
const res = await initCustomerV3({
ctx,
customerId,
customerData: {},
attachPm: "success",
withTestClock: true,
});
customer = res.customer;
@@ -95,15 +69,15 @@ describe(`${chalk.yellowBright(`attach/${testCase}: update quantity, no proratio
];
const originalQuantity = 4;
it("should attach pro product to customer", async () => {
test("should attach pro product to customer", async () => {
await attachAndExpectCorrect({
autumn,
customerId,
product: pro,
stripeCli,
db,
org,
env,
stripeCli: ctx.stripeCli,
db: ctx.db,
org: ctx.org,
env: ctx.env,
options,
});
@@ -116,7 +90,7 @@ describe(`${chalk.yellowBright(`attach/${testCase}: update quantity, no proratio
const usage = 3;
const newQuantity = 3;
it("should use 3 users, then downgrade to 3 seats", async () => {
test("should use 3 users, then downgrade to 3 seats", async () => {
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Users,
@@ -129,10 +103,10 @@ describe(`${chalk.yellowBright(`attach/${testCase}: update quantity, no proratio
autumn,
customerId,
product: pro,
stripeCli,
db,
org,
env,
stripeCli: ctx.stripeCli,
db: ctx.db,
org: ctx.org,
env: ctx.env,
options: [
{
feature_id: TestFeature.Users,
@@ -147,9 +121,9 @@ describe(`${chalk.yellowBright(`attach/${testCase}: update quantity, no proratio
],
});
});
it("should have correct balance (0) next cycle", async () => {
test("should have correct balance (0) next cycle", async () => {
await advanceTestClock({
stripeCli,
stripeCli: ctx.stripeCli,
testClockId,
advanceTo: addHours(
addMonths(new Date(), 1),
@@ -160,14 +134,14 @@ describe(`${chalk.yellowBright(`attach/${testCase}: update quantity, no proratio
const autumnCus = await autumn.customers.get(customerId);
expect(autumnCus.features[TestFeature.Users].balance).to.equal(0);
expect(autumnCus.features[TestFeature.Users].balance).toBe(0);
const product = autumnCus.products.find((p: any) => p.id == pro.id) as any;
const userItem = product.items.find(
(i: any) => i.feature_id == TestFeature.Users,
);
expect(userItem?.quantity).to.equal(newQuantity);
expect(userItem?.upcoming_quantity).to.not.exist;
expect(autumnCus.invoices[0].total).to.equal(newQuantity * userItem.price);
expect(userItem?.quantity).toBe(newQuantity);
expect(userItem?.upcoming_quantity).toBeUndefined();
expect(autumnCus.invoices[0].total).toBe(newQuantity * userItem.price);
});
});

View File

@@ -18,6 +18,7 @@ import { createSharedProducts } from "@/utils/scriptUtils/testUtils/createShared
export const sharedProProduct = constructProduct({
id: "shared-upgradeold-pro",
type: "pro",
excludeBase: true,
items: [
constructFeatureItem({
featureId: TestFeature.Dashboard,
@@ -42,6 +43,7 @@ export const sharedProProduct = constructProduct({
export const sharedProWithTrialProduct = constructProduct({
id: "shared-upgradeold-pro-trial",
type: "pro",
excludeBase: true,
items: [
constructFeatureItem({
featureId: TestFeature.Dashboard,
@@ -72,6 +74,7 @@ export const sharedProWithTrialProduct = constructProduct({
export const sharedPremiumProduct = constructProduct({
id: "shared-upgradeold-premium",
type: "premium",
excludeBase: true,
items: [
constructFeatureItem({
featureId: TestFeature.Messages,
@@ -88,6 +91,7 @@ export const sharedPremiumProduct = constructProduct({
export const sharedPremiumWithTrialProduct = constructProduct({
id: "shared-upgradeold-premium-trial",
type: "premium",
excludeBase: true,
items: [
constructFeatureItem({
featureId: TestFeature.Messages,
@@ -107,7 +111,7 @@ export const sharedPremiumWithTrialProduct = constructProduct({
},
});
await (async () => {
export const initUpgradeOldSharedProducts = async () => {
await createSharedProducts({
ctx,
products: [
@@ -117,4 +121,7 @@ await (async () => {
sharedPremiumWithTrialProduct,
],
});
})();
};
// Auto-init on import (backwards compat)
await initUpgradeOldSharedProducts();

View File

@@ -1,16 +1,17 @@
import chalk from "chalk";
import { beforeAll, describe, expect, test } from "bun:test";
import { Customer } from "@autumn/shared";
import { expectCustomerV0Correct } from "tests/utils/expectUtils/expectCustomerV0Correct.js";
import type { Customer } from "@autumn/shared";
import chalk from "chalk";
import { addDays } from "date-fns";
import type Stripe from "stripe";
import { expectCustomerV0Correct } from "tests/utils/expectUtils/expectCustomerV0Correct.js";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
import type Stripe from "stripe";
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
import {
sharedProWithTrialProduct,
initUpgradeOldSharedProducts,
sharedPremiumProduct,
sharedProWithTrialProduct,
} from "./sharedProducts.js";
describe(`${chalk.yellowBright(
@@ -19,11 +20,23 @@ describe(`${chalk.yellowBright(
const customerId = "upgradeOld1";
let testClockId: string;
let customer: Customer;
const autumn: AutumnInt = new AutumnInt();
let stripeCli: Stripe;
const autumn = new AutumnInt({
secretKey: ctx.orgSecretKey,
});
const autumnV1 = new AutumnInt({
secretKey: ctx.orgSecretKey,
version: "0.1",
});
beforeAll(async () => {
stripeCli = ctx.stripeCli;
// Explicitly ensure shared products exist
await initUpgradeOldSharedProducts();
const { customer: customer_, testClockId: testClockId_ } =
await initCustomerV3({
ctx,
@@ -59,11 +72,10 @@ describe(`${chalk.yellowBright(
});
test("should check product, ents and invoices", async () => {
const res = await autumn.customers.get(customerId);
const res = await autumnV1.customers.get(customerId);
expectCustomerV0Correct({
sent: sharedPremiumProduct,
cusRes: res,
ctx,
});
const invoices = await res.invoices;

View File

@@ -1,15 +1,15 @@
import { beforeAll, describe, expect, test } from "bun:test";
import {
type AppEnv,
CusProductStatus,
LegacyVersion,
type Organization,
} from "@autumn/shared";
import { beforeAll, describe, expect, test } from "bun:test";
import chalk from "chalk";
import type { Stripe } from "stripe";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { addPrefixToProducts } from "tests/utils/testProductUtils/testProductUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
@@ -87,17 +87,6 @@ describe(`${chalk.yellowBright("mergedDowngrade1: Testing merged subs, downgrade
let env: AppEnv;
beforeAll(async () => {
db = ctx.db;
org = ctx.org;
env = ctx.env;
stripeCli = ctx.stripeCli;
addPrefixToProducts({
products: [pro, premium],
prefix: customerId,
});
await initProductsV0({
ctx,
products: [pro, premium],
@@ -108,9 +97,15 @@ describe(`${chalk.yellowBright("mergedDowngrade1: Testing merged subs, downgrade
const res = await initCustomerV3({
ctx,
customerId,
customerData: {},
attachPm: "success",
withTestClock: true,
});
stripeCli = ctx.stripeCli;
db = ctx.db;
org = ctx.org;
env = ctx.env;
testClockId = res.testClockId!;
});

View File

@@ -1,206 +0,0 @@
import {
type AppEnv,
CusProductStatus,
LegacyVersion,
type Organization,
} from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import type { Stripe } from "stripe";
import { setupBefore } from "tests/before.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
import { createProducts } from "tests/utils/productUtils.js";
import { addPrefixToProducts } from "tests/utils/testProductUtils/testProductUtils.js";
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 { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { expectSubToBeCorrect } from "../mergeUtils/expectSubCorrect.js";
// OPERATIONS:
// Premium, Premium
// Pro, Pro
// Premium, Premium
// UNCOMMENT FROM HERE
const premium = constructProduct({
id: "premium",
items: [constructArrearItem({ featureId: TestFeature.Words })],
type: "premium",
});
const pro = constructProduct({
id: "pro",
items: [constructArrearItem({ featureId: TestFeature.Words })],
type: "pro",
});
const init = [
{ entityId: "1", product: premium }, // upgrade to premium
{ entityId: "2", product: premium }, // upgrade to premium
];
const ops1 = [
{
entityId: "1",
product: pro,
results: [
{ product: premium, status: CusProductStatus.Active },
{ product: pro, status: CusProductStatus.Scheduled },
],
},
{
entityId: "2",
product: pro,
results: [
{ product: premium, status: CusProductStatus.Active },
{ product: pro, status: CusProductStatus.Scheduled },
],
},
];
// Renew
const ops2 = [
{
entityId: "1",
product: premium,
results: [{ product: premium, status: CusProductStatus.Active }],
},
{
entityId: "2",
product: premium,
results: [{ product: premium, status: CusProductStatus.Active }],
},
];
describe(`${chalk.yellowBright("mergedDowngrade1: Testing merged subs, downgrade 2 pros")}`, () => {
const customerId = "mergedDowngrade1";
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;
before(async function () {
await setupBefore(this);
const { autumnJs } = this;
db = this.db;
org = this.org;
env = this.env;
stripeCli = this.stripeCli;
addPrefixToProducts({
products: [pro, premium],
prefix: customerId,
});
await createProducts({
autumn: autumnJs,
products: [pro, premium],
db,
orgId: org.id,
env,
customerId,
});
const { testClockId: testClockId1 } = await initCustomer({
autumn: autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
testClockId = testClockId1!;
});
const entities = [
{
id: "1",
name: "Entity 1",
feature_id: TestFeature.Users,
},
{
id: "2",
name: "Entity 2",
feature_id: TestFeature.Users,
},
];
it("should attach pro product to both entities", async () => {
await autumn.entities.create(customerId, entities);
for (const op of init) {
await autumn.attach({
customer_id: customerId,
product_id: op.product.id,
entity_id: op.entityId,
});
}
});
it("should downgrade both entities to pro and have correct sub + schedule", async () => {
for (const op of ops1) {
await autumn.attach({
customer_id: customerId,
product_id: pro.id,
entity_id: op.entityId,
});
const entity = await autumn.entities.get(customerId, op.entityId);
for (const result of op.results) {
expectProductAttached({
customer: entity,
product: result.product,
entityId: op.entityId,
});
}
expect(
entity.products.filter((p: any) => p.group == premium.group).length,
).to.equal(op.results.length);
await expectSubToBeCorrect({
db,
customerId,
org,
env,
});
}
});
it("should renew both entities and have correct sub + schedule", async () => {
for (const op of ops2) {
await autumn.attach({
customer_id: customerId,
product_id: op.product.id,
entity_id: op.entityId,
});
const entity = await autumn.entities.get(customerId, op.entityId);
for (const result of op.results) {
expectProductAttached({
customer: entity,
product: result.product,
entityId: op.entityId,
});
}
expect(
entity.products.filter((p: any) => p.group == premium.group).length,
).to.equal(op.results.length);
await expectSubToBeCorrect({
db,
customerId,
org,
env,
});
}
});
});

View File

@@ -1,17 +1,17 @@
import { beforeAll, describe, expect, test } from "bun:test";
import {
type AppEnv,
CusProductStatus,
LegacyVersion,
type Organization,
} from "@autumn/shared";
import { beforeAll, describe, expect, test } from "bun:test";
import chalk from "chalk";
import type { Stripe } from "stripe";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
import { advanceToNextInvoice } from "tests/utils/testAttachUtils/testAttachUtils.js";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { addPrefixToProducts } from "tests/utils/testProductUtils/testProductUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
@@ -92,17 +92,6 @@ describe(`${chalk.yellowBright("mergedDowngrade2: Testing merged subs, downgrade
let env: AppEnv;
beforeAll(async () => {
db = ctx.db;
org = ctx.org;
env = ctx.env;
stripeCli = ctx.stripeCli;
addPrefixToProducts({
products: [pro, premium, free],
prefix: testCase,
});
await initProductsV0({
ctx,
products: [pro, premium, free],
@@ -113,9 +102,15 @@ describe(`${chalk.yellowBright("mergedDowngrade2: Testing merged subs, downgrade
const res = await initCustomerV3({
ctx,
customerId,
customerData: {},
attachPm: "success",
withTestClock: true,
});
stripeCli = ctx.stripeCli;
db = ctx.db;
org = ctx.org;
env = ctx.env;
testClockId = res.testClockId!;
});

View File

@@ -1,228 +0,0 @@
import {
type AppEnv,
CusProductStatus,
LegacyVersion,
type Organization,
} from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import type { Stripe } from "stripe";
import { setupBefore } from "tests/before.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
import { createProducts } from "tests/utils/productUtils.js";
import { advanceToNextInvoice } from "tests/utils/testAttachUtils/testAttachUtils.js";
import { addPrefixToProducts } from "tests/utils/testProductUtils/testProductUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import {
constructArrearItem,
constructFeatureItem,
} from "@/utils/scriptUtils/constructItem.js";
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { expectSubToBeCorrect } from "../mergeUtils/expectSubCorrect.js";
// OPERATIONS:
// Premium
// Free
// Free, Premium
// Free, Pro
const free = constructProduct({
id: "free",
items: [constructFeatureItem({ featureId: TestFeature.Words })],
type: "free",
isDefault: false,
});
const premium = constructProduct({
id: "premium",
items: [constructArrearItem({ featureId: TestFeature.Words })],
type: "premium",
});
const pro = constructProduct({
id: "pro",
items: [constructArrearItem({ featureId: TestFeature.Words })],
type: "pro",
});
const ops = [
{
entityId: "1",
product: premium,
results: [{ product: premium, status: CusProductStatus.Active }],
},
{
entityId: "1",
product: free,
results: [
{ product: premium, status: CusProductStatus.Active },
{ product: free, status: CusProductStatus.Scheduled },
],
shouldBeCanceled: true,
},
{
entityId: "2",
product: premium,
results: [{ product: premium, status: CusProductStatus.Active }],
},
{
entityId: "2",
product: pro,
results: [
{ product: premium, status: CusProductStatus.Active },
{ product: pro, status: CusProductStatus.Scheduled },
],
},
];
const testCase = "mergedDowngrade2";
describe(`${chalk.yellowBright("mergedDowngrade2: Testing merged subs, downgrade free 1, add premium 2")}`, () => {
const customerId = testCase;
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;
before(async function () {
await setupBefore(this);
const { autumnJs } = this;
db = this.db;
org = this.org;
env = this.env;
stripeCli = this.stripeCli;
addPrefixToProducts({
products: [pro, premium, free],
prefix: testCase,
});
await createProducts({
autumn: autumnJs,
products: [pro, premium, free],
db,
orgId: org.id,
env,
customerId,
});
const { testClockId: testClockId1 } = await initCustomer({
autumn: autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
testClockId = testClockId1!;
});
const entities = [
{
id: "1",
name: "Entity 1",
feature_id: TestFeature.Users,
},
{
id: "2",
name: "Entity 2",
feature_id: TestFeature.Users,
},
];
it("should run operations", async () => {
await autumn.entities.create(customerId, entities);
for (let index = 0; index < ops.length; index++) {
const op = ops[index];
try {
await autumn.attach({
customer_id: customerId,
product_id: op.product.id,
entity_id: op.entityId,
});
const entity = await autumn.entities.get(customerId, op.entityId);
for (const result of op.results) {
expectProductAttached({
customer: entity,
product: result.product,
entityId: op.entityId,
});
}
expect(
entity.products.filter((p: any) => p.group == premium.group).length,
).to.equal(op.results.length);
await expectSubToBeCorrect({
db,
customerId,
org,
env,
shouldBeCanceled: op.shouldBeCanceled,
});
} catch (error) {
console.log(
`Operation failed: ${op.entityId} ${op.product.id}, index: ${index}`,
);
throw error;
}
}
});
// return;
it("should advance test clock and have correct products for entity 1 & 2", async () => {
await advanceToNextInvoice({
stripeCli,
testClockId,
});
const results = [
{ entityId: "1", product: free, status: CusProductStatus.Active },
{ entityId: "2", product: pro, status: CusProductStatus.Active },
];
for (const result of results) {
const entity = await autumn.entities.get(customerId, result.entityId);
expectProductAttached({
customer: entity,
product: result.product,
status: result.status,
});
const products = entity.products.filter(
(p: any) => p.group == result.product.group,
);
expect(products.length).to.equal(1);
}
await expectSubToBeCorrect({
db,
customerId,
org,
env,
});
});
it("should attach premium to entity 1 (which is free) and have correct products", async () => {
await attachAndExpectCorrect({
autumn,
customerId,
product: premium,
stripeCli,
db,
org,
env,
entityId: "1",
});
});
});

View File

@@ -1,15 +1,15 @@
import { beforeAll, describe, expect, test } from "bun:test";
import {
type AppEnv,
CusProductStatus,
LegacyVersion,
type Organization,
} from "@autumn/shared";
import { beforeAll, describe, expect, test } from "bun:test";
import chalk from "chalk";
import type { Stripe } from "stripe";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { addPrefixToProducts } from "tests/utils/testProductUtils/testProductUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
@@ -84,17 +84,6 @@ describe(`${chalk.yellowBright("mergedDowngrade3: Testing merged subs, pro 1, pr
let env: AppEnv;
beforeAll(async () => {
db = ctx.db;
org = ctx.org;
env = ctx.env;
stripeCli = ctx.stripeCli;
addPrefixToProducts({
products: [pro, premium, free],
prefix: testCase,
});
await initProductsV0({
ctx,
products: [pro, premium, free],
@@ -105,9 +94,15 @@ describe(`${chalk.yellowBright("mergedDowngrade3: Testing merged subs, pro 1, pr
const res = await initCustomerV3({
ctx,
customerId,
customerData: {},
attachPm: "success",
withTestClock: true,
});
stripeCli = ctx.stripeCli;
db = ctx.db;
org = ctx.org;
env = ctx.env;
testClockId = res.testClockId!;
});

View File

@@ -1,172 +0,0 @@
import {
type AppEnv,
CusProductStatus,
LegacyVersion,
type Organization,
} from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import type { Stripe } from "stripe";
import { setupBefore } from "tests/before.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
import { createProducts } from "tests/utils/productUtils.js";
import { addPrefixToProducts } from "tests/utils/testProductUtils/testProductUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import {
constructArrearItem,
constructFeatureItem,
} from "@/utils/scriptUtils/constructItem.js";
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { expectSubToBeCorrect } from "../mergeUtils/expectSubCorrect.js";
// OPERATIONS:
// Pro, Pro
// Free, Premium
const free = constructProduct({
id: "free",
items: [constructFeatureItem({ featureId: TestFeature.Words })],
type: "free",
isDefault: false,
});
const premium = constructProduct({
id: "premium",
items: [constructArrearItem({ featureId: TestFeature.Words })],
type: "premium",
});
const pro = constructProduct({
id: "pro",
items: [constructArrearItem({ featureId: TestFeature.Words })],
type: "pro",
});
const ops = [
{
entityId: "1",
product: pro,
results: [{ product: pro, status: CusProductStatus.Active }],
},
{
entityId: "2",
product: pro,
results: [{ product: pro, status: CusProductStatus.Active }],
},
{
entityId: "1",
product: free,
results: [
{ product: pro, status: CusProductStatus.Active },
{ product: free, status: CusProductStatus.Scheduled },
],
},
{
entityId: "2",
product: premium,
results: [{ product: premium, status: CusProductStatus.Active }],
},
];
const testCase = "mergedDowngrade3";
describe(`${chalk.yellowBright("mergedDowngrade3: Testing merged subs, pro 1, pro 2, downgrade free pro 1, upgrade pro 2 ")}`, () => {
const customerId = testCase;
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;
before(async function () {
await setupBefore(this);
const { autumnJs } = this;
db = this.db;
org = this.org;
env = this.env;
stripeCli = this.stripeCli;
addPrefixToProducts({
products: [pro, premium, free],
prefix: testCase,
});
await createProducts({
autumn: autumnJs,
products: [pro, premium, free],
db,
orgId: org.id,
env,
customerId,
});
const { testClockId: testClockId1 } = await initCustomer({
autumn: autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
testClockId = testClockId1!;
});
const entities = [
{
id: "1",
name: "Entity 1",
feature_id: TestFeature.Users,
},
{
id: "2",
name: "Entity 2",
feature_id: TestFeature.Users,
},
];
it("should run operations", async () => {
await autumn.entities.create(customerId, entities);
for (let index = 0; index < ops.length; index++) {
const op = ops[index];
try {
await autumn.attach({
customer_id: customerId,
product_id: op.product.id,
entity_id: op.entityId,
});
const entity = await autumn.entities.get(customerId, op.entityId);
for (const result of op.results) {
expectProductAttached({
customer: entity,
product: result.product,
entityId: op.entityId,
});
}
expect(
entity.products.filter((p: any) => p.group == premium.group).length,
).to.equal(op.results.length);
await expectSubToBeCorrect({
db,
customerId,
org,
env,
});
} catch (error) {
console.log(
`Operation failed: ${op.entityId} ${op.product.id}, index: ${index}`,
);
throw error;
}
}
});
});

View File

@@ -1,16 +1,16 @@
import { beforeAll, describe, expect, test } from "bun:test";
import {
type AppEnv,
CusProductStatus,
LegacyVersion,
type Organization,
} from "@autumn/shared";
import { beforeAll, describe, expect, test } from "bun:test";
import chalk from "chalk";
import type { Stripe } from "stripe";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
import { advanceToNextInvoice } from "tests/utils/testAttachUtils/testAttachUtils.js";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { addPrefixToProducts } from "tests/utils/testProductUtils/testProductUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
@@ -77,17 +77,6 @@ describe(`${chalk.yellowBright("mergedDowngrade4: Testing advance clock, schedul
let env: AppEnv;
beforeAll(async () => {
db = ctx.db;
org = ctx.org;
env = ctx.env;
stripeCli = ctx.stripeCli;
addPrefixToProducts({
products: [pro, premium, premiumAnnual],
prefix: testCase,
});
await initProductsV0({
ctx,
products: [pro, premium, premiumAnnual],
@@ -98,9 +87,15 @@ describe(`${chalk.yellowBright("mergedDowngrade4: Testing advance clock, schedul
const res = await initCustomerV3({
ctx,
customerId,
customerData: {},
attachPm: "success",
withTestClock: true,
});
stripeCli = ctx.stripeCli;
db = ctx.db;
org = ctx.org;
env = ctx.env;
testClockId = res.testClockId!;
});

View File

@@ -1,196 +0,0 @@
import {
type AppEnv,
CusProductStatus,
LegacyVersion,
type Organization,
} from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import type { Stripe } from "stripe";
import { setupBefore } from "tests/before.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
import { createProducts } from "tests/utils/productUtils.js";
import { advanceToNextInvoice } from "tests/utils/testAttachUtils/testAttachUtils.js";
import { addPrefixToProducts } from "tests/utils/testProductUtils/testProductUtils.js";
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 { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { expectSubToBeCorrect } from "../mergeUtils/expectSubCorrect.js";
// OPERATIONS:
// PremiumAnnual, Premium
// PremiumAnnual, Pro
const premiumAnnual = constructProduct({
id: "premiumAnnual",
items: [constructArrearItem({ featureId: TestFeature.Words })],
type: "premium",
isAnnual: true,
});
const premium = constructProduct({
id: "premium",
items: [constructArrearItem({ featureId: TestFeature.Words })],
type: "premium",
});
const pro = constructProduct({
id: "pro",
items: [constructArrearItem({ featureId: TestFeature.Words })],
type: "pro",
});
const ops = [
{
entityId: "1",
product: premiumAnnual,
results: [{ product: premiumAnnual, status: CusProductStatus.Active }],
},
{
entityId: "2",
product: premium,
results: [{ product: premium, status: CusProductStatus.Active }],
},
{
entityId: "2",
product: pro,
results: [
{ product: premium, status: CusProductStatus.Active },
{ product: pro, status: CusProductStatus.Scheduled },
],
},
];
const testCase = "mergedDowngrade4";
describe(`${chalk.yellowBright("mergedDowngrade4: Testing advance clock, schedule activates")}`, () => {
const customerId = testCase;
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;
before(async function () {
await setupBefore(this);
const { autumnJs } = this;
db = this.db;
org = this.org;
env = this.env;
stripeCli = this.stripeCli;
addPrefixToProducts({
products: [pro, premium, premiumAnnual],
prefix: testCase,
});
await createProducts({
autumn: autumnJs,
products: [pro, premium, premiumAnnual],
db,
orgId: org.id,
env,
customerId,
});
const { testClockId: testClockId1 } = await initCustomer({
autumn: autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
testClockId = testClockId1!;
});
const entities = [
{
id: "1",
name: "Entity 1",
feature_id: TestFeature.Users,
},
{
id: "2",
name: "Entity 2",
feature_id: TestFeature.Users,
},
];
it("should run operations", async () => {
await autumn.entities.create(customerId, entities);
for (let index = 0; index < ops.length; index++) {
const op = ops[index];
try {
await autumn.attach({
customer_id: customerId,
product_id: op.product.id,
entity_id: op.entityId,
});
const entity = await autumn.entities.get(customerId, op.entityId);
for (const result of op.results) {
expectProductAttached({
customer: entity,
product: result.product,
entityId: op.entityId,
});
}
expect(
entity.products.filter((p: any) => p.group == premium.group).length,
).to.equal(op.results.length);
await expectSubToBeCorrect({
db,
customerId,
org,
env,
});
} catch (error) {
console.log(
`Operation failed: ${op.entityId} ${op.product.id}, index: ${index}`,
);
throw error;
}
}
});
it("should advance test clock and have correct premium downgraded for entity 2", async () => {
await advanceToNextInvoice({
stripeCli,
testClockId,
});
// 1. Check that only
const results = [
{
entityId: "1",
product: premiumAnnual,
status: CusProductStatus.Active,
},
{ entityId: "2", product: pro, status: CusProductStatus.Active },
];
for (const result of results) {
const entity = await autumn.entities.get(customerId, result.entityId);
expectProductAttached({
customer: entity,
product: result.product,
status: result.status,
});
const products = entity.products.filter(
(p: any) => p.group == result.product.group,
);
expect(products.length).to.equal(1);
}
});
});

View File

@@ -1,15 +1,15 @@
import { beforeAll, describe, expect, test } from "bun:test";
import {
type AppEnv,
CusProductStatus,
LegacyVersion,
type Organization,
} from "@autumn/shared";
import { beforeAll, describe, expect, test } from "bun:test";
import chalk from "chalk";
import type { Stripe } from "stripe";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { addPrefixToProducts } from "tests/utils/testProductUtils/testProductUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
@@ -96,17 +96,6 @@ describe(`${chalk.yellowBright("mergedDowngrade8: Testing merged subs, downgrade
let env: AppEnv;
beforeAll(async () => {
db = ctx.db;
org = ctx.org;
env = ctx.env;
stripeCli = ctx.stripeCli;
addPrefixToProducts({
products: [pro, premium, premiumAnnual],
prefix: customerId,
});
await initProductsV0({
ctx,
products: [pro, premium, premiumAnnual],
@@ -117,9 +106,15 @@ describe(`${chalk.yellowBright("mergedDowngrade8: Testing merged subs, downgrade
const res = await initCustomerV3({
ctx,
customerId,
customerData: {},
attachPm: "success",
withTestClock: true,
});
stripeCli = ctx.stripeCli;
db = ctx.db;
org = ctx.org;
env = ctx.env;
testClockId = res.testClockId!;
});

View File

@@ -1,184 +0,0 @@
import {
type AppEnv,
CusProductStatus,
LegacyVersion,
type Organization,
} from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import type { Stripe } from "stripe";
import { setupBefore } from "tests/before.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
import { createProducts } from "tests/utils/productUtils.js";
import { addPrefixToProducts } from "tests/utils/testProductUtils/testProductUtils.js";
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 { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { expectSubToBeCorrect } from "../mergeUtils/expectSubCorrect.js";
// UNCOMMENT FROM HERE
const premium = constructProduct({
id: "premium",
items: [constructArrearItem({ featureId: TestFeature.Words })],
type: "premium",
});
const premiumAnnual = constructProduct({
id: "premiumAnnual",
items: [constructArrearItem({ featureId: TestFeature.Words })],
type: "premium",
isAnnual: true,
});
const pro = constructProduct({
id: "pro",
items: [constructArrearItem({ featureId: TestFeature.Words })],
type: "pro",
});
// const init = [
// { entityId: "1", product: premiumAnnual }, // upgrade to premium
// { entityId: "2", product: premium }, // upgrade to premium
// ];
const ops = [
{
entityId: "1",
product: premiumAnnual,
results: [{ product: premiumAnnual, status: CusProductStatus.Active }],
},
{
entityId: "2",
product: premium,
results: [{ product: premium, status: CusProductStatus.Active }],
},
{
entityId: "1",
product: pro,
results: [
{ product: premiumAnnual, status: CusProductStatus.Active },
{ product: pro, status: CusProductStatus.Scheduled },
],
},
{
entityId: "2",
product: pro,
results: [
{ product: premium, status: CusProductStatus.Active },
{ product: pro, status: CusProductStatus.Scheduled },
],
},
{
entityId: "1",
product: premiumAnnual,
results: [{ product: premiumAnnual, status: CusProductStatus.Active }],
},
{
entityId: "2",
product: premium,
results: [{ product: premium, status: CusProductStatus.Active }],
},
];
const testCase = "mergedDowngrade8";
describe(`${chalk.yellowBright("mergedDowngrade8: Testing merged subs, downgrade 2 monthly + annual")}`, () => {
const customerId = testCase;
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;
before(async function () {
await setupBefore(this);
const { autumnJs } = this;
db = this.db;
org = this.org;
env = this.env;
stripeCli = this.stripeCli;
addPrefixToProducts({
products: [pro, premium, premiumAnnual],
prefix: customerId,
});
await createProducts({
autumn: autumnJs,
products: [pro, premium, premiumAnnual],
db,
orgId: org.id,
env,
customerId,
});
const { testClockId: testClockId1 } = await initCustomer({
autumn: autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
testClockId = testClockId1!;
});
const entities = [
{
id: "1",
name: "Entity 1",
feature_id: TestFeature.Users,
},
{
id: "2",
name: "Entity 2",
feature_id: TestFeature.Users,
},
];
it("should run operations", async () => {
await autumn.entities.create(customerId, entities);
for (let index = 0; index < ops.length; index++) {
const op = ops[index];
try {
await autumn.attach({
customer_id: customerId,
product_id: op.product.id,
entity_id: op.entityId,
});
const entity = await autumn.entities.get(customerId, op.entityId);
for (const result of op.results) {
expectProductAttached({
customer: entity,
product: result.product,
entityId: op.entityId,
});
}
expect(
entity.products.filter((p: any) => p.group == premium.group).length,
).to.equal(op.results.length);
await expectSubToBeCorrect({
db,
customerId,
org,
env,
});
} catch (error) {
console.log(
`Operation failed: ${op.entityId} ${op.product.id}, index: ${index}`,
);
throw error;
}
}
});
});

View File

@@ -1,17 +1,17 @@
import { beforeAll, describe, expect, test } from "bun:test";
import {
type AppEnv,
CusProductStatus,
LegacyVersion,
type Organization,
} from "@autumn/shared";
import { beforeAll, describe, expect, test } from "bun:test";
import chalk from "chalk";
import type { Stripe } from "stripe";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
import { advanceToNextInvoice } from "tests/utils/testAttachUtils/testAttachUtils.js";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { addPrefixToProducts } from "tests/utils/testProductUtils/testProductUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
@@ -87,17 +87,6 @@ describe(`${chalk.yellowBright("mergedDowngrade9: Testing merged subs, downgrade
let env: AppEnv;
beforeAll(async () => {
db = ctx.db;
org = ctx.org;
env = ctx.env;
stripeCli = ctx.stripeCli;
addPrefixToProducts({
products: [pro, premium, premiumAnnual],
prefix: customerId,
});
await initProductsV0({
ctx,
products: [pro, premium, premiumAnnual],
@@ -108,9 +97,15 @@ describe(`${chalk.yellowBright("mergedDowngrade9: Testing merged subs, downgrade
const res = await initCustomerV3({
ctx,
customerId,
customerData: {},
attachPm: "success",
withTestClock: true,
});
stripeCli = ctx.stripeCli;
db = ctx.db;
org = ctx.org;
env = ctx.env;
testClockId = res.testClockId!;
});
@@ -158,7 +153,7 @@ describe(`${chalk.yellowBright("mergedDowngrade9: Testing merged subs, downgrade
// }
// expect(
// entity.products.filter((p: any) => p.group == premium.group).length
// ).toBe(op.results.length);
// ).to.equal(op.results.length);
// await expectSubToBeCorrect({
// db,
// customerId,

View File

@@ -1,232 +0,0 @@
import {
type AppEnv,
CusProductStatus,
LegacyVersion,
type Organization,
} from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import type { Stripe } from "stripe";
import { setupBefore } from "tests/before.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
import { createProducts } from "tests/utils/productUtils.js";
import { advanceToNextInvoice } from "tests/utils/testAttachUtils/testAttachUtils.js";
import { addPrefixToProducts } from "tests/utils/testProductUtils/testProductUtils.js";
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 { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
// UNCOMMENT FROM HERE
const premium = constructProduct({
id: "premium",
items: [constructArrearItem({ featureId: TestFeature.Words })],
type: "premium",
});
const premiumAnnual = constructProduct({
id: "premiumAnnual",
items: [constructArrearItem({ featureId: TestFeature.Words })],
type: "premium",
isAnnual: true,
});
const pro = constructProduct({
id: "pro",
items: [constructArrearItem({ featureId: TestFeature.Words })],
type: "pro",
});
// const init = [
// { entityId: "1", product: premiumAnnual }, // upgrade to premium
// { entityId: "2", product: premium }, // upgrade to premium
// ];
const ops = [
{
entityId: "1",
product: premiumAnnual,
results: [{ product: premiumAnnual, status: CusProductStatus.Active }],
},
{
entityId: "2",
product: premium,
results: [{ product: premium, status: CusProductStatus.Active }],
},
{
entityId: "1",
product: pro,
results: [
{ product: premiumAnnual, status: CusProductStatus.Active },
{ product: pro, status: CusProductStatus.Scheduled },
],
},
{
entityId: "2",
product: pro,
results: [
{ product: premium, status: CusProductStatus.Active },
{ product: pro, status: CusProductStatus.Scheduled },
],
},
];
const testCase = "mergedDowngrade9";
describe(`${chalk.yellowBright("mergedDowngrade9: Testing merged subs, downgrade 2 monthly + annual & advance test clock")}`, () => {
const customerId = testCase;
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;
before(async function () {
await setupBefore(this);
const { autumnJs } = this;
db = this.db;
org = this.org;
env = this.env;
stripeCli = this.stripeCli;
addPrefixToProducts({
products: [pro, premium, premiumAnnual],
prefix: customerId,
});
await createProducts({
autumn: autumnJs,
products: [pro, premium, premiumAnnual],
db,
orgId: org.id,
env,
customerId,
});
const { testClockId: testClockId1 } = await initCustomer({
autumn: autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
testClockId = testClockId1!;
});
const entities = [
{
id: "1",
name: "Entity 1",
feature_id: TestFeature.Users,
},
{
id: "2",
name: "Entity 2",
feature_id: TestFeature.Users,
},
];
it("should run operations", async () => {
await autumn.entities.create(customerId, entities);
for (let index = 0; index < ops.length; index++) {
const op = ops[index];
try {
await attachAndExpectCorrect({
autumn,
customerId,
product: op.product,
stripeCli,
db,
org,
env,
entityId: op.entityId,
});
// await autumn.attach({
// customer_id: customerId,
// product_id: op.product.id,
// entity_id: op.entityId,
// });
// const entity = await autumn.entities.get(customerId, op.entityId);
// for (const result of op.results) {
// expectProductAttached({
// customer: entity,
// product: result.product,
// entityId: op.entityId,
// });
// }
// expect(
// entity.products.filter((p: any) => p.group == premium.group).length
// ).to.equal(op.results.length);
// await expectSubToBeCorrect({
// db,
// customerId,
// org,
// env,
// });
} catch (error) {
console.log(
`Operation failed: ${op.entityId} ${op.product.id}, index: ${index}`,
);
throw error;
}
}
});
it("should advance test clock and have correct products for entity 1 & 2", async () => {
const results = [
{
entityId: "1",
products: [
{ product: premiumAnnual, status: CusProductStatus.Active },
{ product: pro, status: CusProductStatus.Scheduled },
],
},
{
entityId: "2",
products: [{ product: pro, status: CusProductStatus.Active }],
},
];
await advanceToNextInvoice({
stripeCli,
testClockId,
});
for (const result of results) {
const entity = await autumn.entities.get(customerId, result.entityId);
for (const product of result.products) {
expectProductAttached({
customer: entity,
product: product.product,
status: product.status,
});
}
const products = entity.products.filter(
(p: any) => p.group == premium.group,
);
expect(products.length).to.equal(result.products.length);
}
});
it("should attach premium to entity 2 and have correct products", async () => {
await attachAndExpectCorrect({
autumn,
customerId,
product: premium,
stripeCli,
db,
org,
env,
entityId: "2",
});
});
});

View File

@@ -1,15 +1,15 @@
import { beforeAll, describe, test } from "bun:test";
import {
type AppEnv,
CusProductStatus,
LegacyVersion,
type Organization,
} from "@autumn/shared";
import { beforeAll, describe, expect, test } from "bun:test";
import chalk from "chalk";
import type { Stripe } from "stripe";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { addPrefixToProducts } from "tests/utils/testProductUtils/testProductUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
@@ -101,17 +101,6 @@ describe(`${chalk.yellowBright("mergedPrepaid1: Testing merged subs, upgrade 1 &
let env: AppEnv;
beforeAll(async () => {
db = ctx.db;
org = ctx.org;
env = ctx.env;
stripeCli = ctx.stripeCli;
addPrefixToProducts({
products: [pro, premium],
prefix: testCase,
});
await initProductsV0({
ctx,
products: [pro, premium],
@@ -122,9 +111,15 @@ describe(`${chalk.yellowBright("mergedPrepaid1: Testing merged subs, upgrade 1 &
const res = await initCustomerV3({
ctx,
customerId,
customerData: {},
attachPm: "success",
withTestClock: true,
});
stripeCli = ctx.stripeCli;
db = ctx.db;
org = ctx.org;
env = ctx.env;
testClockId = res.testClockId!;
});

View File

@@ -1,175 +0,0 @@
import {
type AppEnv,
CusProductStatus,
LegacyVersion,
type Organization,
} from "@autumn/shared";
import chalk from "chalk";
import type { Stripe } from "stripe";
import { setupBefore } from "tests/before.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
import { createProducts } from "tests/utils/productUtils.js";
import { addPrefixToProducts } from "tests/utils/testProductUtils/testProductUtils.js";
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 { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
const billingUnits = 100;
const creditItem = constructPrepaidItem({
featureId: TestFeature.Credits,
includedUsage: 100,
price: 10,
billingUnits,
});
const premium = constructProduct({
id: "premium",
items: [creditItem],
type: "premium",
});
const pro = constructProduct({
id: "pro",
items: [creditItem],
type: "pro",
});
const ops = [
{
entityId: "1",
product: pro,
results: [{ product: pro, status: CusProductStatus.Active }],
options: [
{
feature_id: TestFeature.Credits,
quantity: billingUnits * 4,
},
],
},
{
entityId: "2",
product: pro,
results: [{ product: pro, status: CusProductStatus.Active }],
options: [
{
feature_id: TestFeature.Credits,
quantity: billingUnits * 3,
},
],
},
// Update prepaid quantity (increase)
{
entityId: "1",
product: pro,
results: [{ product: pro, status: CusProductStatus.Active }],
options: [
{
feature_id: TestFeature.Credits,
quantity: billingUnits * 5,
},
],
},
// Update prepaid quantity (decrease)
{
entityId: "2",
product: pro,
results: [{ product: pro, status: CusProductStatus.Active }],
options: [
{
feature_id: TestFeature.Credits,
quantity: billingUnits * 1,
},
],
},
];
const testCase = "mergedPrepaid1";
describe(`${chalk.yellowBright("mergedPrepaid1: Testing merged subs, upgrade 1 & 2 to pro, add premium 2")}`, () => {
const customerId = testCase;
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
let stripeCli: Stripe;
let testClockId: string;
let curUnix: number;
let db: DrizzleCli;
let org: Organization;
let env: AppEnv;
before(async function () {
await setupBefore(this);
const { autumnJs } = this;
db = this.db;
org = this.org;
env = this.env;
stripeCli = this.stripeCli;
addPrefixToProducts({
products: [pro, premium],
prefix: testCase,
});
await createProducts({
autumn: autumnJs,
products: [pro, premium],
db,
orgId: org.id,
env,
customerId,
});
const { testClockId: testClockId1 } = await initCustomer({
autumn: autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
testClockId = testClockId1!;
});
const entities = [
{
id: "1",
name: "Entity 1",
feature_id: TestFeature.Users,
},
{
id: "2",
name: "Entity 2",
feature_id: TestFeature.Users,
},
];
it("should run operations", async () => {
await autumn.entities.create(customerId, entities);
for (let index = 0; index < ops.length; index++) {
const op = ops[index];
try {
await attachAndExpectCorrect({
autumn,
customerId,
product: op.product,
stripeCli,
db,
org,
env,
entityId: op.entityId,
options: op.options,
});
} catch (error) {
console.log(
`Operation failed: ${op.entityId} ${op.product.id}, index: ${index}`,
);
throw error;
}
}
});
});

View File

@@ -1,4 +1,3 @@
import { beforeAll, describe, test } from "bun:test";
import {
type AppEnv,
CusProductStatus,
@@ -7,12 +6,13 @@ import {
OnIncrease,
type Organization,
} from "@autumn/shared";
import { beforeAll, describe, expect, test } from "bun:test";
import chalk from "chalk";
import type { Stripe } from "stripe";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
import { advanceToNextInvoice } from "tests/utils/testAttachUtils/testAttachUtils.js";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { addPrefixToProducts } from "tests/utils/testProductUtils/testProductUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
@@ -119,17 +119,6 @@ describe(`${chalk.yellowBright("mergedPrepaid2: Testing merged subs, upgrade 1 &
let env: AppEnv;
beforeAll(async () => {
db = ctx.db;
org = ctx.org;
env = ctx.env;
stripeCli = ctx.stripeCli;
addPrefixToProducts({
products: [pro, premium],
prefix: testCase,
});
await initProductsV0({
ctx,
products: [pro, premium],
@@ -140,9 +129,15 @@ describe(`${chalk.yellowBright("mergedPrepaid2: Testing merged subs, upgrade 1 &
const res = await initCustomerV3({
ctx,
customerId,
customerData: {},
attachPm: "success",
withTestClock: true,
});
stripeCli = ctx.stripeCli;
db = ctx.db;
org = ctx.org;
env = ctx.env;
testClockId = res.testClockId!;
});

View File

@@ -1,200 +0,0 @@
import {
type AppEnv,
CusProductStatus,
LegacyVersion,
OnDecrease,
OnIncrease,
type Organization,
} from "@autumn/shared";
import chalk from "chalk";
import type { Stripe } from "stripe";
import { setupBefore } from "tests/before.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
import { createProducts } from "tests/utils/productUtils.js";
import { advanceToNextInvoice } from "tests/utils/testAttachUtils/testAttachUtils.js";
import { addPrefixToProducts } from "tests/utils/testProductUtils/testProductUtils.js";
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 { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
const billingUnits = 100;
const creditItem = constructPrepaidItem({
featureId: TestFeature.Credits,
includedUsage: 100,
price: 10,
billingUnits,
config: {
on_increase: OnIncrease.ProrateImmediately,
on_decrease: OnDecrease.None,
},
});
const premium = constructProduct({
id: "premium",
items: [creditItem],
type: "premium",
});
const pro = constructProduct({
id: "pro",
items: [creditItem],
type: "pro",
});
const ops = [
{
entityId: "1",
product: pro,
results: [{ product: pro, status: CusProductStatus.Active }],
options: [
{
feature_id: TestFeature.Credits,
quantity: billingUnits * 4,
},
],
},
{
entityId: "2",
product: pro,
results: [{ product: pro, status: CusProductStatus.Active }],
options: [
{
feature_id: TestFeature.Credits,
quantity: billingUnits * 3,
},
],
},
// Update prepaid quantity (increase)
{
entityId: "1",
product: pro,
results: [{ product: pro, status: CusProductStatus.Active }],
options: [
{
feature_id: TestFeature.Credits,
quantity: billingUnits * 2,
},
],
},
{
entityId: "2",
product: pro,
results: [{ product: pro, status: CusProductStatus.Active }],
options: [
{
feature_id: TestFeature.Credits,
quantity: billingUnits * 1,
},
],
},
// // Update prepaid quantity (decrease)
// {
// entityId: "2",
// product: pro,
// results: [{ product: pro, status: CusProductStatus.Active }],
// options: [
// {
// feature_id: TestFeature.Credits,
// quantity: billingUnits * 1,
// },
// ],
// },
];
const testCase = "mergedPrepaid2";
describe(`${chalk.yellowBright("mergedPrepaid2: Testing merged subs, upgrade 1 & 2 to pro, add premium 2")}`, () => {
const customerId = testCase;
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
let stripeCli: Stripe;
let testClockId: string;
let curUnix: number;
let db: DrizzleCli;
let org: Organization;
let env: AppEnv;
before(async function () {
await setupBefore(this);
const { autumnJs } = this;
db = this.db;
org = this.org;
env = this.env;
stripeCli = this.stripeCli;
addPrefixToProducts({
products: [pro, premium],
prefix: testCase,
});
await createProducts({
autumn: autumnJs,
products: [pro, premium],
db,
orgId: org.id,
env,
customerId,
});
const { testClockId: testClockId1 } = await initCustomer({
autumn: autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
testClockId = testClockId1!;
});
const entities = [
{
id: "1",
name: "Entity 1",
feature_id: TestFeature.Users,
},
{
id: "2",
name: "Entity 2",
feature_id: TestFeature.Users,
},
];
it("should run operations", async () => {
await autumn.entities.create(customerId, entities);
for (let index = 0; index < ops.length; index++) {
const op = ops[index];
try {
await attachAndExpectCorrect({
autumn,
customerId,
product: op.product,
stripeCli,
db,
org,
env,
entityId: op.entityId,
options: op.options,
});
} catch (error) {
console.log(
`Operation failed: ${op.entityId} ${op.product.id}, index: ${index}`,
);
throw error;
}
}
});
it("should have correct balances after update", async () => {
await advanceToNextInvoice({
stripeCli,
testClockId,
});
});
});

View File

@@ -1,6 +1,5 @@
// PREPAID WITH DOWNGRADE (SCHEDULED...)
import { beforeAll, describe, test } from "bun:test";
import {
type AppEnv,
CusProductStatus,
@@ -9,13 +8,14 @@ import {
OnIncrease,
type Organization,
} from "@autumn/shared";
import { beforeAll, describe, expect, test } from "bun:test";
import chalk from "chalk";
import type { Stripe } from "stripe";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
import { advanceToNextInvoice } from "tests/utils/testAttachUtils/testAttachUtils.js";
import ctx from "tests/utils/testInitUtils/createTestContext.js";
import { addPrefixToProducts } from "tests/utils/testProductUtils/testProductUtils.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
@@ -100,17 +100,6 @@ describe(`${chalk.yellowBright("mergedPrepaid3: Testing merged subs, upgrade 1 &
let env: AppEnv;
beforeAll(async () => {
db = ctx.db;
org = ctx.org;
env = ctx.env;
stripeCli = ctx.stripeCli;
addPrefixToProducts({
products: [pro, premium],
prefix: testCase,
});
await initProductsV0({
ctx,
products: [pro, premium],
@@ -121,9 +110,15 @@ describe(`${chalk.yellowBright("mergedPrepaid3: Testing merged subs, upgrade 1 &
const res = await initCustomerV3({
ctx,
customerId,
customerData: {},
attachPm: "success",
withTestClock: true,
});
stripeCli = ctx.stripeCli;
db = ctx.db;
org = ctx.org;
env = ctx.env;
testClockId = res.testClockId!;
});

View File

@@ -1,195 +0,0 @@
// PREPAID WITH DOWNGRADE (SCHEDULED...)
import {
type AppEnv,
CusProductStatus,
LegacyVersion,
OnDecrease,
OnIncrease,
type Organization,
} from "@autumn/shared";
import chalk from "chalk";
import type { Stripe } from "stripe";
import { setupBefore } from "tests/before.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
import { createProducts } from "tests/utils/productUtils.js";
import { advanceToNextInvoice } from "tests/utils/testAttachUtils/testAttachUtils.js";
import { addPrefixToProducts } from "tests/utils/testProductUtils/testProductUtils.js";
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 { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { expectSubToBeCorrect } from "../mergeUtils/expectSubCorrect.js";
const billingUnits = 100;
const creditItem = constructPrepaidItem({
featureId: TestFeature.Credits,
includedUsage: 100,
price: 10,
billingUnits,
config: {
on_increase: OnIncrease.ProrateImmediately,
on_decrease: OnDecrease.ProrateImmediately,
},
});
const premium = constructProduct({
id: "premium",
items: [creditItem],
type: "premium",
});
const pro = constructProduct({
id: "pro",
items: [creditItem],
type: "pro",
});
const ops = [
{
entityId: "1",
product: premium,
results: [{ product: premium, status: CusProductStatus.Active }],
options: [
{
feature_id: TestFeature.Credits,
quantity: billingUnits * 4,
},
],
},
{
entityId: "2",
product: premium,
results: [{ product: premium, status: CusProductStatus.Active }],
options: [
{
feature_id: TestFeature.Credits,
quantity: billingUnits * 3,
},
],
},
// Update prepaid quantity (increase)
{
entityId: "1",
product: pro,
results: [{ product: pro, status: CusProductStatus.Active }],
options: [
{
feature_id: TestFeature.Credits,
quantity: billingUnits * 2,
},
],
},
];
const testCase = "mergedPrepaid3";
describe(`${chalk.yellowBright("mergedPrepaid3: Testing merged subs, upgrade 1 & 2 to premium, downgrade 1 to pro")}`, () => {
const customerId = testCase;
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;
before(async function () {
await setupBefore(this);
const { autumnJs } = this;
db = this.db;
org = this.org;
env = this.env;
stripeCli = this.stripeCli;
addPrefixToProducts({
products: [pro, premium],
prefix: testCase,
});
await createProducts({
autumn: autumnJs,
products: [pro, premium],
db,
orgId: org.id,
env,
customerId,
});
const { testClockId: testClockId1 } = await initCustomer({
autumn: autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
testClockId = testClockId1!;
});
const entities = [
{
id: "1",
name: "Entity 1",
feature_id: TestFeature.Users,
},
{
id: "2",
name: "Entity 2",
feature_id: TestFeature.Users,
},
];
it("should run operations", async () => {
await autumn.entities.create(customerId, entities);
for (let index = 0; index < ops.length; index++) {
const op = ops[index];
try {
await attachAndExpectCorrect({
autumn,
customerId,
product: op.product,
stripeCli,
db,
org,
env,
entityId: op.entityId,
options: op.options,
});
} catch (error) {
console.log(
`Operation failed: ${op.entityId} ${op.product.id}, index: ${index}`,
);
throw error;
}
}
});
it("should have correct products after update", async () => {
await advanceToNextInvoice({
stripeCli,
testClockId,
});
const entity1 = await autumn.entities.get(customerId, "1");
expectProductAttached({
customer: entity1,
product: pro,
entityId: "1",
});
await expectSubToBeCorrect({
db,
customerId,
org,
env,
});
});
});