From 4b9ce6af701ae880949a3f91d4b0f910474b3174 Mon Sep 17 00:00:00 2001 From: amianthus <49116958+SirTenzin@users.noreply.github.com> Date: Fri, 31 Oct 2025 11:08:00 +0000 Subject: [PATCH] =?UTF-8?q?test:=20=F0=9F=92=8D=20bun?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/tests/MIGRATION_TRACKER.md | 56 ++- server/tests/advanced/coupons/coupon1.ts | 234 ------------ server/tests/advanced/coupons/coupon2.ts | 197 ---------- server/tests/advanced/coupons/coupon3.ts | 176 --------- .../customInterval/customInterval1.backup.ts | 145 ++++++++ .../customInterval/customInterval1.test.ts | 129 +++++++ .../customInterval/customInterval2.backup.ts | 118 ++++++ .../customInterval/customInterval2.test.ts | 102 +++++ .../customInterval/customInterval3.backup.ts | 160 ++++++++ .../customInterval/customInterval3.test.ts | 144 +++++++ .../customInterval/customInterval4.backup.ts | 149 ++++++++ .../customInterval/customInterval4.test.ts | 133 +++++++ .../customInterval/customInterval5.backup.ts | 161 ++++++++ .../customInterval/customInterval5.test.ts | 145 ++++++++ .../customInterval/customInterval6.backup.ts | 0 .../advanced/referrals/paid/referrals13.ts | 255 ------------- .../advanced/referrals/paid/referrals14.ts | 264 ------------- .../advanced/referrals/paid/referrals15.ts | 296 --------------- .../advanced/referrals/paid/referrals16.ts | 351 ------------------ server/tests/advanced/referrals/referrals1.ts | 292 --------------- server/tests/advanced/referrals/referrals2.ts | 174 --------- server/tests/advanced/referrals/referrals3.ts | 141 ------- server/tests/advanced/referrals/referrals4.ts | 125 ------- .../advanced/rollovers/rollover1.backup.ts | 198 ++++++++++ .../advanced/rollovers/rollover1.test.ts | 179 +++++++++ .../advanced/rollovers/rollover2.backup.ts | 225 +++++++++++ .../advanced/rollovers/rollover2.test.ts | 206 ++++++++++ .../advanced/rollovers/rollover3.backup.ts | 127 +++++++ .../advanced/rollovers/rollover3.test.ts | 108 ++++++ .../advanced/rollovers/rollover4.backup.ts | 157 ++++++++ .../advanced/rollovers/rollover4.test.ts | 138 +++++++ .../advanced/rollovers/rollover5.backup.ts | 137 +++++++ .../advanced/rollovers/rollover5.test.ts | 118 ++++++ .../advanced/rollovers/rollover6.backup.ts | 151 ++++++++ .../advanced/rollovers/rollover6.test.ts | 132 +++++++ server/tests/advanced/usage/sharedProducts.ts | 7 +- server/tests/advanced/usage/usage1.ts | 125 ------- server/tests/advanced/usage/usage2.ts | 136 ------- server/tests/advanced/usage/usage3.ts | 140 ------- server/tests/advanced/usage/usage4.ts | 172 --------- .../advanced/usageLimit/usageLimit1.backup.ts | 151 ++++++++ .../advanced/usageLimit/usageLimit1.test.ts | 132 +++++++ .../advanced/usageLimit/usageLimit2.backup.ts | 195 ++++++++++ .../advanced/usageLimit/usageLimit2.test.ts | 176 +++++++++ .../advanced/usageLimit/usageLimit3.backup.ts | 147 ++++++++ .../advanced/usageLimit/usageLimit3.test.ts | 129 +++++++ .../usageLimit/usageLimit4.backup.ts} | 120 ++---- .../advanced/usageLimit/usageLimit4.test.ts | 93 +++++ server/tests/attach/basic/basic3.test.ts | 5 +- server/tests/attach/basic/sharedProducts.ts | 7 +- .../tests/attach/downgrade/downgrade5.test.ts | 5 +- .../tests/attach/downgrade/downgrade6.test.ts | 5 +- .../tests/attach/downgrade/downgrade7.test.ts | 5 +- .../tests/attach/downgrade/sharedProducts.ts | 10 +- .../attach/multiProduct/sharedProducts.ts | 7 +- .../prepaid/{prepaid6.ts => prepaid6.test.ts} | 88 ++--- .../tests/attach/upgradeOld/sharedProducts.ts | 11 +- .../attach/upgradeOld/upgradeOld1.test.ts | 30 +- .../merged/downgrade/mergedDowngrade1.test.ts | 21 +- .../merged/downgrade/mergedDowngrade1.ts | 206 ---------- .../merged/downgrade/mergedDowngrade2.test.ts | 21 +- .../merged/downgrade/mergedDowngrade2.ts | 228 ------------ .../merged/downgrade/mergedDowngrade3.test.ts | 21 +- .../merged/downgrade/mergedDowngrade3.ts | 172 --------- .../merged/downgrade/mergedDowngrade4.test.ts | 21 +- .../merged/downgrade/mergedDowngrade4.ts | 196 ---------- .../merged/downgrade/mergedDowngrade8.test.ts | 21 +- .../merged/downgrade/mergedDowngrade8.ts | 184 --------- .../merged/downgrade/mergedDowngrade9.test.ts | 23 +- .../merged/downgrade/mergedDowngrade9.ts | 232 ------------ .../merged/prepaid/mergedPrepaid1.test.ts | 21 +- server/tests/merged/prepaid/mergedPrepaid1.ts | 175 --------- .../merged/prepaid/mergedPrepaid2.test.ts | 21 +- server/tests/merged/prepaid/mergedPrepaid2.ts | 200 ---------- .../merged/prepaid/mergedPrepaid3.test.ts | 21 +- server/tests/merged/prepaid/mergedPrepaid3.ts | 195 ---------- 76 files changed, 4538 insertions(+), 5160 deletions(-) delete mode 100644 server/tests/advanced/coupons/coupon1.ts delete mode 100644 server/tests/advanced/coupons/coupon2.ts delete mode 100644 server/tests/advanced/coupons/coupon3.ts create mode 100644 server/tests/advanced/customInterval/customInterval1.backup.ts create mode 100644 server/tests/advanced/customInterval/customInterval1.test.ts create mode 100644 server/tests/advanced/customInterval/customInterval2.backup.ts create mode 100644 server/tests/advanced/customInterval/customInterval2.test.ts create mode 100644 server/tests/advanced/customInterval/customInterval3.backup.ts create mode 100644 server/tests/advanced/customInterval/customInterval3.test.ts create mode 100644 server/tests/advanced/customInterval/customInterval4.backup.ts create mode 100644 server/tests/advanced/customInterval/customInterval4.test.ts create mode 100644 server/tests/advanced/customInterval/customInterval5.backup.ts create mode 100644 server/tests/advanced/customInterval/customInterval5.test.ts create mode 100644 server/tests/advanced/customInterval/customInterval6.backup.ts delete mode 100644 server/tests/advanced/referrals/paid/referrals13.ts delete mode 100644 server/tests/advanced/referrals/paid/referrals14.ts delete mode 100644 server/tests/advanced/referrals/paid/referrals15.ts delete mode 100644 server/tests/advanced/referrals/paid/referrals16.ts delete mode 100644 server/tests/advanced/referrals/referrals1.ts delete mode 100644 server/tests/advanced/referrals/referrals2.ts delete mode 100644 server/tests/advanced/referrals/referrals3.ts delete mode 100644 server/tests/advanced/referrals/referrals4.ts create mode 100644 server/tests/advanced/rollovers/rollover1.backup.ts create mode 100644 server/tests/advanced/rollovers/rollover1.test.ts create mode 100644 server/tests/advanced/rollovers/rollover2.backup.ts create mode 100644 server/tests/advanced/rollovers/rollover2.test.ts create mode 100644 server/tests/advanced/rollovers/rollover3.backup.ts create mode 100644 server/tests/advanced/rollovers/rollover3.test.ts create mode 100644 server/tests/advanced/rollovers/rollover4.backup.ts create mode 100644 server/tests/advanced/rollovers/rollover4.test.ts create mode 100644 server/tests/advanced/rollovers/rollover5.backup.ts create mode 100644 server/tests/advanced/rollovers/rollover5.test.ts create mode 100644 server/tests/advanced/rollovers/rollover6.backup.ts create mode 100644 server/tests/advanced/rollovers/rollover6.test.ts delete mode 100644 server/tests/advanced/usage/usage1.ts delete mode 100644 server/tests/advanced/usage/usage2.ts delete mode 100644 server/tests/advanced/usage/usage3.ts delete mode 100644 server/tests/advanced/usage/usage4.ts create mode 100644 server/tests/advanced/usageLimit/usageLimit1.backup.ts create mode 100644 server/tests/advanced/usageLimit/usageLimit1.test.ts create mode 100644 server/tests/advanced/usageLimit/usageLimit2.backup.ts create mode 100644 server/tests/advanced/usageLimit/usageLimit2.test.ts create mode 100644 server/tests/advanced/usageLimit/usageLimit3.backup.ts create mode 100644 server/tests/advanced/usageLimit/usageLimit3.test.ts rename server/tests/{attach/updateQuantity/updateQuantity1.ts => advanced/usageLimit/usageLimit4.backup.ts} (53%) create mode 100644 server/tests/advanced/usageLimit/usageLimit4.test.ts rename server/tests/attach/prepaid/{prepaid6.ts => prepaid6.test.ts} (65%) delete mode 100644 server/tests/merged/downgrade/mergedDowngrade1.ts delete mode 100644 server/tests/merged/downgrade/mergedDowngrade2.ts delete mode 100644 server/tests/merged/downgrade/mergedDowngrade3.ts delete mode 100644 server/tests/merged/downgrade/mergedDowngrade4.ts delete mode 100644 server/tests/merged/downgrade/mergedDowngrade8.ts delete mode 100644 server/tests/merged/downgrade/mergedDowngrade9.ts delete mode 100644 server/tests/merged/prepaid/mergedPrepaid1.ts delete mode 100644 server/tests/merged/prepaid/mergedPrepaid2.ts delete mode 100644 server/tests/merged/prepaid/mergedPrepaid3.ts diff --git a/server/tests/MIGRATION_TRACKER.md b/server/tests/MIGRATION_TRACKER.md index 712880ffb..9421317f5 100644 --- a/server/tests/MIGRATION_TRACKER.md +++ b/server/tests/MIGRATION_TRACKER.md @@ -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 diff --git a/server/tests/advanced/coupons/coupon1.ts b/server/tests/advanced/coupons/coupon1.ts deleted file mode 100644 index 2e97e1924..000000000 --- a/server/tests/advanced/coupons/coupon1.ts +++ /dev/null @@ -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, - }); - }); - }, -); diff --git a/server/tests/advanced/coupons/coupon2.ts b/server/tests/advanced/coupons/coupon2.ts deleted file mode 100644 index cadd28dd8..000000000 --- a/server/tests/advanced/coupons/coupon2.ts +++ /dev/null @@ -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), - ); - }); - }, -); diff --git a/server/tests/advanced/coupons/coupon3.ts b/server/tests/advanced/coupons/coupon3.ts deleted file mode 100644 index ce9c002c5..000000000 --- a/server/tests/advanced/coupons/coupon3.ts +++ /dev/null @@ -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); - } - }); -}); diff --git a/server/tests/advanced/customInterval/customInterval1.backup.ts b/server/tests/advanced/customInterval/customInterval1.backup.ts new file mode 100644 index 000000000..c864b9417 --- /dev/null +++ b/server/tests/advanced/customInterval/customInterval1.backup.ts @@ -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); + }); +}); diff --git a/server/tests/advanced/customInterval/customInterval1.test.ts b/server/tests/advanced/customInterval/customInterval1.test.ts new file mode 100644 index 000000000..342aa775a --- /dev/null +++ b/server/tests/advanced/customInterval/customInterval1.test.ts @@ -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); + }); +}); diff --git a/server/tests/advanced/customInterval/customInterval2.backup.ts b/server/tests/advanced/customInterval/customInterval2.backup.ts new file mode 100644 index 000000000..9cf54f547 --- /dev/null +++ b/server/tests/advanced/customInterval/customInterval2.backup.ts @@ -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); + }); +}); diff --git a/server/tests/advanced/customInterval/customInterval2.test.ts b/server/tests/advanced/customInterval/customInterval2.test.ts new file mode 100644 index 000000000..724a48a4f --- /dev/null +++ b/server/tests/advanced/customInterval/customInterval2.test.ts @@ -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); + }); +}); diff --git a/server/tests/advanced/customInterval/customInterval3.backup.ts b/server/tests/advanced/customInterval/customInterval3.backup.ts new file mode 100644 index 000000000..424ab4ead --- /dev/null +++ b/server/tests/advanced/customInterval/customInterval3.backup.ts @@ -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, + ); + }); +}); diff --git a/server/tests/advanced/customInterval/customInterval3.test.ts b/server/tests/advanced/customInterval/customInterval3.test.ts new file mode 100644 index 000000000..36aca4beb --- /dev/null +++ b/server/tests/advanced/customInterval/customInterval3.test.ts @@ -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), + ); + }); +}); diff --git a/server/tests/advanced/customInterval/customInterval4.backup.ts b/server/tests/advanced/customInterval/customInterval4.backup.ts new file mode 100644 index 000000000..b68f29764 --- /dev/null +++ b/server/tests/advanced/customInterval/customInterval4.backup.ts @@ -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 })); + }); +}); diff --git a/server/tests/advanced/customInterval/customInterval4.test.ts b/server/tests/advanced/customInterval/customInterval4.test.ts new file mode 100644 index 000000000..c2525a0cf --- /dev/null +++ b/server/tests/advanced/customInterval/customInterval4.test.ts @@ -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 })); + }); +}); diff --git a/server/tests/advanced/customInterval/customInterval5.backup.ts b/server/tests/advanced/customInterval/customInterval5.backup.ts new file mode 100644 index 000000000..cca80a257 --- /dev/null +++ b/server/tests/advanced/customInterval/customInterval5.backup.ts @@ -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); + }); +}); diff --git a/server/tests/advanced/customInterval/customInterval5.test.ts b/server/tests/advanced/customInterval/customInterval5.test.ts new file mode 100644 index 000000000..bb3af2919 --- /dev/null +++ b/server/tests/advanced/customInterval/customInterval5.test.ts @@ -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); + }); +}); diff --git a/server/tests/advanced/customInterval/customInterval6.backup.ts b/server/tests/advanced/customInterval/customInterval6.backup.ts new file mode 100644 index 000000000..e69de29bb diff --git a/server/tests/advanced/referrals/paid/referrals13.ts b/server/tests/advanced/referrals/paid/referrals13.ts deleted file mode 100644 index 14dcc6704..000000000 --- a/server/tests/advanced/referrals/paid/referrals13.ts +++ /dev/null @@ -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` : ""}`, - ); - }); - }); - }); -}); diff --git a/server/tests/advanced/referrals/paid/referrals14.ts b/server/tests/advanced/referrals/paid/referrals14.ts deleted file mode 100644 index 6b12ddf22..000000000 --- a/server/tests/advanced/referrals/paid/referrals14.ts +++ /dev/null @@ -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` : ""}`, - ); - }); - }); - }); -}); diff --git a/server/tests/advanced/referrals/paid/referrals15.ts b/server/tests/advanced/referrals/paid/referrals15.ts deleted file mode 100644 index 1f8c15c82..000000000 --- a/server/tests/advanced/referrals/paid/referrals15.ts +++ /dev/null @@ -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` : ""}`, - ); - }); - }); - }); -}); diff --git a/server/tests/advanced/referrals/paid/referrals16.ts b/server/tests/advanced/referrals/paid/referrals16.ts deleted file mode 100644 index 0b56bcab2..000000000 --- a/server/tests/advanced/referrals/paid/referrals16.ts +++ /dev/null @@ -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` : ""}` -// ); -// }); -// }); -// }); -// }); diff --git a/server/tests/advanced/referrals/referrals1.ts b/server/tests/advanced/referrals/referrals1.ts deleted file mode 100644 index 2ed5f38ad..000000000 --- a/server/tests/advanced/referrals/referrals1.ts +++ /dev/null @@ -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, -// }) diff --git a/server/tests/advanced/referrals/referrals2.ts b/server/tests/advanced/referrals/referrals2.ts deleted file mode 100644 index 1aa238e1c..000000000 --- a/server/tests/advanced/referrals/referrals2.ts +++ /dev/null @@ -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); - // }); -}); diff --git a/server/tests/advanced/referrals/referrals3.ts b/server/tests/advanced/referrals/referrals3.ts deleted file mode 100644 index 500f5294c..000000000 --- a/server/tests/advanced/referrals/referrals3.ts +++ /dev/null @@ -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, - }); - } - } - }); -}); diff --git a/server/tests/advanced/referrals/referrals4.ts b/server/tests/advanced/referrals/referrals4.ts deleted file mode 100644 index 2de7fc1a4..000000000 --- a/server/tests/advanced/referrals/referrals4.ts +++ /dev/null @@ -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, - }); - }); -}); diff --git a/server/tests/advanced/rollovers/rollover1.backup.ts b/server/tests/advanced/rollovers/rollover1.backup.ts new file mode 100644 index 000000000..9b81e8ecb --- /dev/null +++ b/server/tests/advanced/rollovers/rollover1.backup.ts @@ -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); + }); +}); diff --git a/server/tests/advanced/rollovers/rollover1.test.ts b/server/tests/advanced/rollovers/rollover1.test.ts new file mode 100644 index 000000000..16ede8a51 --- /dev/null +++ b/server/tests/advanced/rollovers/rollover1.test.ts @@ -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); + }); +}); diff --git a/server/tests/advanced/rollovers/rollover2.backup.ts b/server/tests/advanced/rollovers/rollover2.backup.ts new file mode 100644 index 000000000..bdd212140 --- /dev/null +++ b/server/tests/advanced/rollovers/rollover2.backup.ts @@ -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); + } + }); +}); diff --git a/server/tests/advanced/rollovers/rollover2.test.ts b/server/tests/advanced/rollovers/rollover2.test.ts new file mode 100644 index 000000000..3da0251f7 --- /dev/null +++ b/server/tests/advanced/rollovers/rollover2.test.ts @@ -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); + } + }); +}); diff --git a/server/tests/advanced/rollovers/rollover3.backup.ts b/server/tests/advanced/rollovers/rollover3.backup.ts new file mode 100644 index 000000000..37b0c7590 --- /dev/null +++ b/server/tests/advanced/rollovers/rollover3.backup.ts @@ -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; + }); +}); diff --git a/server/tests/advanced/rollovers/rollover3.test.ts b/server/tests/advanced/rollovers/rollover3.test.ts new file mode 100644 index 000000000..13cf4c32f --- /dev/null +++ b/server/tests/advanced/rollovers/rollover3.test.ts @@ -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; + }); +}); diff --git a/server/tests/advanced/rollovers/rollover4.backup.ts b/server/tests/advanced/rollovers/rollover4.backup.ts new file mode 100644 index 000000000..13e38b224 --- /dev/null +++ b/server/tests/advanced/rollovers/rollover4.backup.ts @@ -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); + }); +}); diff --git a/server/tests/advanced/rollovers/rollover4.test.ts b/server/tests/advanced/rollovers/rollover4.test.ts new file mode 100644 index 000000000..6677c9ab7 --- /dev/null +++ b/server/tests/advanced/rollovers/rollover4.test.ts @@ -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); + }); +}); diff --git a/server/tests/advanced/rollovers/rollover5.backup.ts b/server/tests/advanced/rollovers/rollover5.backup.ts new file mode 100644 index 000000000..13a77c758 --- /dev/null +++ b/server/tests/advanced/rollovers/rollover5.backup.ts @@ -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); + }); +}); diff --git a/server/tests/advanced/rollovers/rollover5.test.ts b/server/tests/advanced/rollovers/rollover5.test.ts new file mode 100644 index 000000000..cb24c58b5 --- /dev/null +++ b/server/tests/advanced/rollovers/rollover5.test.ts @@ -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); + }); +}); diff --git a/server/tests/advanced/rollovers/rollover6.backup.ts b/server/tests/advanced/rollovers/rollover6.backup.ts new file mode 100644 index 000000000..f7c1051d0 --- /dev/null +++ b/server/tests/advanced/rollovers/rollover6.backup.ts @@ -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); + }); +}); diff --git a/server/tests/advanced/rollovers/rollover6.test.ts b/server/tests/advanced/rollovers/rollover6.test.ts new file mode 100644 index 000000000..11e6b8d6d --- /dev/null +++ b/server/tests/advanced/rollovers/rollover6.test.ts @@ -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); + }); +}); diff --git a/server/tests/advanced/usage/sharedProducts.ts b/server/tests/advanced/usage/sharedProducts.ts index 5003e889a..7d25fbf8a 100644 --- a/server/tests/advanced/usage/sharedProducts.ts +++ b/server/tests/advanced/usage/sharedProducts.ts @@ -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(); diff --git a/server/tests/advanced/usage/usage1.ts b/server/tests/advanced/usage/usage1.ts deleted file mode 100644 index 489c0ea1d..000000000 --- a/server/tests/advanced/usage/usage1.ts +++ /dev/null @@ -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", - ); - }); -}); diff --git a/server/tests/advanced/usage/usage2.ts b/server/tests/advanced/usage/usage2.ts deleted file mode 100644 index 3c5152fb7..000000000 --- a/server/tests/advanced/usage/usage2.ts +++ /dev/null @@ -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); - // } - }); -}); diff --git a/server/tests/advanced/usage/usage3.ts b/server/tests/advanced/usage/usage3.ts deleted file mode 100644 index 21d4c76c5..000000000 --- a/server/tests/advanced/usage/usage3.ts +++ /dev/null @@ -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); - }); -}); diff --git a/server/tests/advanced/usage/usage4.ts b/server/tests/advanced/usage/usage4.ts deleted file mode 100644 index 181e1cd24..000000000 --- a/server/tests/advanced/usage/usage4.ts +++ /dev/null @@ -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; -// } -// }); diff --git a/server/tests/advanced/usageLimit/usageLimit1.backup.ts b/server/tests/advanced/usageLimit/usageLimit1.backup.ts new file mode 100644 index 000000000..417433e12 --- /dev/null +++ b/server/tests/advanced/usageLimit/usageLimit1.backup.ts @@ -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, + ); + }); +}); diff --git a/server/tests/advanced/usageLimit/usageLimit1.test.ts b/server/tests/advanced/usageLimit/usageLimit1.test.ts new file mode 100644 index 000000000..b93560245 --- /dev/null +++ b/server/tests/advanced/usageLimit/usageLimit1.test.ts @@ -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, + ); + }); +}); diff --git a/server/tests/advanced/usageLimit/usageLimit2.backup.ts b/server/tests/advanced/usageLimit/usageLimit2.backup.ts new file mode 100644 index 000000000..58e2dc733 --- /dev/null +++ b/server/tests/advanced/usageLimit/usageLimit2.backup.ts @@ -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, + ); + }); +}); diff --git a/server/tests/advanced/usageLimit/usageLimit2.test.ts b/server/tests/advanced/usageLimit/usageLimit2.test.ts new file mode 100644 index 000000000..ba709b8cd --- /dev/null +++ b/server/tests/advanced/usageLimit/usageLimit2.test.ts @@ -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, + ); + }); +}); diff --git a/server/tests/advanced/usageLimit/usageLimit3.backup.ts b/server/tests/advanced/usageLimit/usageLimit3.backup.ts new file mode 100644 index 000000000..fc48ebe43 --- /dev/null +++ b/server/tests/advanced/usageLimit/usageLimit3.backup.ts @@ -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, + }, + ], + }); + }, + }); + }); +}); diff --git a/server/tests/advanced/usageLimit/usageLimit3.test.ts b/server/tests/advanced/usageLimit/usageLimit3.test.ts new file mode 100644 index 000000000..e90b45d6b --- /dev/null +++ b/server/tests/advanced/usageLimit/usageLimit3.test.ts @@ -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, + }, + ], + }); + }, + }); + }); +}); diff --git a/server/tests/attach/updateQuantity/updateQuantity1.ts b/server/tests/advanced/usageLimit/usageLimit4.backup.ts similarity index 53% rename from server/tests/attach/updateQuantity/updateQuantity1.ts rename to server/tests/advanced/usageLimit/usageLimit4.backup.ts index 8d769f826..0938b1a32 100644 --- a/server/tests/attach/updateQuantity/updateQuantity1.ts +++ b/server/tests/advanced/usageLimit/usageLimit4.backup.ts @@ -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); }); }); diff --git a/server/tests/advanced/usageLimit/usageLimit4.test.ts b/server/tests/advanced/usageLimit/usageLimit4.test.ts new file mode 100644 index 000000000..06ec1e7f0 --- /dev/null +++ b/server/tests/advanced/usageLimit/usageLimit4.test.ts @@ -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); + }); +}); diff --git a/server/tests/attach/basic/basic3.test.ts b/server/tests/attach/basic/basic3.test.ts index 2e6e84b15..504ab03b1 100644 --- a/server/tests/attach/basic/basic3.test.ts +++ b/server/tests/attach/basic/basic3.test.ts @@ -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, diff --git a/server/tests/attach/basic/sharedProducts.ts b/server/tests/attach/basic/sharedProducts.ts index 50f6f0203..2ad42211f 100644 --- a/server/tests/attach/basic/sharedProducts.ts +++ b/server/tests/attach/basic/sharedProducts.ts @@ -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(); diff --git a/server/tests/attach/downgrade/downgrade5.test.ts b/server/tests/attach/downgrade/downgrade5.test.ts index a52518db6..905c345f7 100644 --- a/server/tests/attach/downgrade/downgrade5.test.ts +++ b/server/tests/attach/downgrade/downgrade5.test.ts @@ -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, }); }); }); diff --git a/server/tests/attach/downgrade/downgrade6.test.ts b/server/tests/attach/downgrade/downgrade6.test.ts index ba80aa380..06e8ec128 100644 --- a/server/tests/attach/downgrade/downgrade6.test.ts +++ b/server/tests/attach/downgrade/downgrade6.test.ts @@ -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, }); }); }); diff --git a/server/tests/attach/downgrade/downgrade7.test.ts b/server/tests/attach/downgrade/downgrade7.test.ts index e56ea7a57..3640e511d 100644 --- a/server/tests/attach/downgrade/downgrade7.test.ts +++ b/server/tests/attach/downgrade/downgrade7.test.ts @@ -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({ diff --git a/server/tests/attach/downgrade/sharedProducts.ts b/server/tests/attach/downgrade/sharedProducts.ts index 6656cdc4c..b6bc7fba7 100644 --- a/server/tests/attach/downgrade/sharedProducts.ts +++ b/server/tests/attach/downgrade/sharedProducts.ts @@ -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(); diff --git a/server/tests/attach/multiProduct/sharedProducts.ts b/server/tests/attach/multiProduct/sharedProducts.ts index 1f48d7242..b4deee1bc 100644 --- a/server/tests/attach/multiProduct/sharedProducts.ts +++ b/server/tests/attach/multiProduct/sharedProducts.ts @@ -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(); diff --git a/server/tests/attach/prepaid/prepaid6.ts b/server/tests/attach/prepaid/prepaid6.test.ts similarity index 65% rename from server/tests/attach/prepaid/prepaid6.ts rename to server/tests/attach/prepaid/prepaid6.test.ts index cf50e6202..0b13600d4 100644 --- a/server/tests/attach/prepaid/prepaid6.ts +++ b/server/tests/attach/prepaid/prepaid6.test.ts @@ -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); }); }); diff --git a/server/tests/attach/upgradeOld/sharedProducts.ts b/server/tests/attach/upgradeOld/sharedProducts.ts index f7f589a48..6231ee4ab 100644 --- a/server/tests/attach/upgradeOld/sharedProducts.ts +++ b/server/tests/attach/upgradeOld/sharedProducts.ts @@ -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(); diff --git a/server/tests/attach/upgradeOld/upgradeOld1.test.ts b/server/tests/attach/upgradeOld/upgradeOld1.test.ts index 49b6e125a..315bc0543 100644 --- a/server/tests/attach/upgradeOld/upgradeOld1.test.ts +++ b/server/tests/attach/upgradeOld/upgradeOld1.test.ts @@ -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; diff --git a/server/tests/merged/downgrade/mergedDowngrade1.test.ts b/server/tests/merged/downgrade/mergedDowngrade1.test.ts index 9eedd2886..90b94d11d 100644 --- a/server/tests/merged/downgrade/mergedDowngrade1.test.ts +++ b/server/tests/merged/downgrade/mergedDowngrade1.test.ts @@ -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!; }); diff --git a/server/tests/merged/downgrade/mergedDowngrade1.ts b/server/tests/merged/downgrade/mergedDowngrade1.ts deleted file mode 100644 index 66306eff2..000000000 --- a/server/tests/merged/downgrade/mergedDowngrade1.ts +++ /dev/null @@ -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, - }); - } - }); -}); diff --git a/server/tests/merged/downgrade/mergedDowngrade2.test.ts b/server/tests/merged/downgrade/mergedDowngrade2.test.ts index af5eb6360..2c0e97349 100644 --- a/server/tests/merged/downgrade/mergedDowngrade2.test.ts +++ b/server/tests/merged/downgrade/mergedDowngrade2.test.ts @@ -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!; }); diff --git a/server/tests/merged/downgrade/mergedDowngrade2.ts b/server/tests/merged/downgrade/mergedDowngrade2.ts deleted file mode 100644 index 32fc26dea..000000000 --- a/server/tests/merged/downgrade/mergedDowngrade2.ts +++ /dev/null @@ -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", - }); - }); -}); diff --git a/server/tests/merged/downgrade/mergedDowngrade3.test.ts b/server/tests/merged/downgrade/mergedDowngrade3.test.ts index cdce2a5dc..d9ec317de 100644 --- a/server/tests/merged/downgrade/mergedDowngrade3.test.ts +++ b/server/tests/merged/downgrade/mergedDowngrade3.test.ts @@ -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!; }); diff --git a/server/tests/merged/downgrade/mergedDowngrade3.ts b/server/tests/merged/downgrade/mergedDowngrade3.ts deleted file mode 100644 index f9ff2e621..000000000 --- a/server/tests/merged/downgrade/mergedDowngrade3.ts +++ /dev/null @@ -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; - } - } - }); -}); diff --git a/server/tests/merged/downgrade/mergedDowngrade4.test.ts b/server/tests/merged/downgrade/mergedDowngrade4.test.ts index 1b9f153ec..133e27df6 100644 --- a/server/tests/merged/downgrade/mergedDowngrade4.test.ts +++ b/server/tests/merged/downgrade/mergedDowngrade4.test.ts @@ -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!; }); diff --git a/server/tests/merged/downgrade/mergedDowngrade4.ts b/server/tests/merged/downgrade/mergedDowngrade4.ts deleted file mode 100644 index 557484a01..000000000 --- a/server/tests/merged/downgrade/mergedDowngrade4.ts +++ /dev/null @@ -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); - } - }); -}); diff --git a/server/tests/merged/downgrade/mergedDowngrade8.test.ts b/server/tests/merged/downgrade/mergedDowngrade8.test.ts index 06c1a1461..b1afa32dc 100644 --- a/server/tests/merged/downgrade/mergedDowngrade8.test.ts +++ b/server/tests/merged/downgrade/mergedDowngrade8.test.ts @@ -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!; }); diff --git a/server/tests/merged/downgrade/mergedDowngrade8.ts b/server/tests/merged/downgrade/mergedDowngrade8.ts deleted file mode 100644 index b9ea55b4a..000000000 --- a/server/tests/merged/downgrade/mergedDowngrade8.ts +++ /dev/null @@ -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; - } - } - }); -}); diff --git a/server/tests/merged/downgrade/mergedDowngrade9.test.ts b/server/tests/merged/downgrade/mergedDowngrade9.test.ts index 2bc0d91a7..d91335610 100644 --- a/server/tests/merged/downgrade/mergedDowngrade9.test.ts +++ b/server/tests/merged/downgrade/mergedDowngrade9.test.ts @@ -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, diff --git a/server/tests/merged/downgrade/mergedDowngrade9.ts b/server/tests/merged/downgrade/mergedDowngrade9.ts deleted file mode 100644 index e249ef7eb..000000000 --- a/server/tests/merged/downgrade/mergedDowngrade9.ts +++ /dev/null @@ -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", - }); - }); -}); diff --git a/server/tests/merged/prepaid/mergedPrepaid1.test.ts b/server/tests/merged/prepaid/mergedPrepaid1.test.ts index 44a51e900..3831af414 100644 --- a/server/tests/merged/prepaid/mergedPrepaid1.test.ts +++ b/server/tests/merged/prepaid/mergedPrepaid1.test.ts @@ -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!; }); diff --git a/server/tests/merged/prepaid/mergedPrepaid1.ts b/server/tests/merged/prepaid/mergedPrepaid1.ts deleted file mode 100644 index b0c3aa5bb..000000000 --- a/server/tests/merged/prepaid/mergedPrepaid1.ts +++ /dev/null @@ -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; - } - } - }); -}); diff --git a/server/tests/merged/prepaid/mergedPrepaid2.test.ts b/server/tests/merged/prepaid/mergedPrepaid2.test.ts index a45457f07..b11a127af 100644 --- a/server/tests/merged/prepaid/mergedPrepaid2.test.ts +++ b/server/tests/merged/prepaid/mergedPrepaid2.test.ts @@ -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!; }); diff --git a/server/tests/merged/prepaid/mergedPrepaid2.ts b/server/tests/merged/prepaid/mergedPrepaid2.ts deleted file mode 100644 index 9c630682a..000000000 --- a/server/tests/merged/prepaid/mergedPrepaid2.ts +++ /dev/null @@ -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, - }); - }); -}); diff --git a/server/tests/merged/prepaid/mergedPrepaid3.test.ts b/server/tests/merged/prepaid/mergedPrepaid3.test.ts index bb940afa0..0a7d7317e 100644 --- a/server/tests/merged/prepaid/mergedPrepaid3.test.ts +++ b/server/tests/merged/prepaid/mergedPrepaid3.test.ts @@ -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!; }); diff --git a/server/tests/merged/prepaid/mergedPrepaid3.ts b/server/tests/merged/prepaid/mergedPrepaid3.ts deleted file mode 100644 index f7cb0221c..000000000 --- a/server/tests/merged/prepaid/mergedPrepaid3.ts +++ /dev/null @@ -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, - }); - }); -});