test: 💍 bun
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -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),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
145
server/tests/advanced/customInterval/customInterval1.backup.ts
Normal file
145
server/tests/advanced/customInterval/customInterval1.backup.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import { addHours, addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import { advanceTestClock } from "tests/utils/stripeUtils.js";
|
||||
import { getBasePrice } from "tests/utils/testProductUtils/testProductUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
|
||||
const testCase = "customInterval1";
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
intervalCount: 2,
|
||||
includedUsage: 500,
|
||||
}),
|
||||
],
|
||||
intervalCount: 2,
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
export const premium = constructProduct({
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
intervalCount: 2,
|
||||
}),
|
||||
// constructArrearItem({ featureId: TestFeature.Words }),
|
||||
// constructArrearProratedItem({
|
||||
// featureId: TestFeature.Users,
|
||||
// pricePerUnit: 30,
|
||||
// }),
|
||||
],
|
||||
intervalCount: 2,
|
||||
type: "premium",
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing custom interval and interval count`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [pro, premium],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [pro, premium],
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
it("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
});
|
||||
|
||||
const usage = 100012;
|
||||
it("should upgrade to premium product and have correct invoice next cycle", async () => {
|
||||
const curUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addMonths(new Date(), 1).getTime(),
|
||||
waitForSeconds: 15,
|
||||
});
|
||||
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: premium,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
expect(customer.invoices.length).to.equal(2);
|
||||
|
||||
const nextUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addHours(
|
||||
addMonths(new Date(curUnix), 1),
|
||||
hoursToFinalizeInvoice,
|
||||
).getTime(),
|
||||
waitForSeconds: 30,
|
||||
});
|
||||
|
||||
const customer2 = await autumn.customers.get(customerId);
|
||||
const invoices = customer2.invoices;
|
||||
expect(invoices.length).to.equal(3);
|
||||
expect(invoices[0].product_ids).to.include(premium.id);
|
||||
expect(invoices[0].total).to.equal(getBasePrice({ product: premium }));
|
||||
|
||||
const wordsFeature = customer2.features[TestFeature.Words];
|
||||
// @ts-expect-error
|
||||
expect(wordsFeature.interval_count).to.equal(2);
|
||||
});
|
||||
});
|
||||
129
server/tests/advanced/customInterval/customInterval1.test.ts
Normal file
129
server/tests/advanced/customInterval/customInterval1.test.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { LegacyVersion } from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import { addHours, addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { getBasePrice } from "tests/utils/testProductUtils/testProductUtils.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
|
||||
const testCase = "customInterval1";
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
intervalCount: 2,
|
||||
includedUsage: 500,
|
||||
}),
|
||||
],
|
||||
intervalCount: 2,
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
export const premium = constructProduct({
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
intervalCount: 2,
|
||||
}),
|
||||
// constructArrearItem({ featureId: TestFeature.Words }),
|
||||
// constructArrearProratedItem({
|
||||
// featureId: TestFeature.Users,
|
||||
// pricePerUnit: 30,
|
||||
// }),
|
||||
],
|
||||
intervalCount: 2,
|
||||
type: "premium",
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing custom interval and interval count`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro, premium],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
test("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
});
|
||||
|
||||
const usage = 100012;
|
||||
test("should upgrade to premium product and have correct invoice next cycle", async () => {
|
||||
const curUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addMonths(new Date(), 1).getTime(),
|
||||
waitForSeconds: 15,
|
||||
});
|
||||
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: premium,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
expect(customer.invoices.length).toBe(2);
|
||||
|
||||
const nextUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addHours(
|
||||
addMonths(new Date(curUnix), 1),
|
||||
hoursToFinalizeInvoice,
|
||||
).getTime(),
|
||||
waitForSeconds: 30,
|
||||
});
|
||||
|
||||
const customer2 = await autumn.customers.get(customerId);
|
||||
const invoices = customer2.invoices;
|
||||
expect(invoices.length).toBe(3);
|
||||
expect(invoices[0].product_ids).toContain(premium.id);
|
||||
expect(invoices[0].total).toBe(getBasePrice({ product: premium }));
|
||||
|
||||
const wordsFeature = customer2.features[TestFeature.Words];
|
||||
// @ts-expect-error
|
||||
expect(wordsFeature.interval_count).toBe(2);
|
||||
});
|
||||
});
|
||||
118
server/tests/advanced/customInterval/customInterval2.backup.ts
Normal file
118
server/tests/advanced/customInterval/customInterval2.backup.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import { addHours, addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { getExpectedInvoiceTotal } from "tests/utils/expectUtils/expectInvoiceUtils.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import { advanceTestClock } from "tests/utils/stripeUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructRawProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
|
||||
const testCase = "customInterval2";
|
||||
|
||||
export const pro = constructRawProduct({
|
||||
id: "pro",
|
||||
items: [
|
||||
constructArrearItem({
|
||||
includedUsage: 0,
|
||||
featureId: TestFeature.Words,
|
||||
intervalCount: 2,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing custom interval on arrear prorated price`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [pro],
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
it("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
});
|
||||
|
||||
const usage = 100012;
|
||||
it("should upgrade to premium product and have correct invoice next cycle", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Words,
|
||||
value: usage,
|
||||
});
|
||||
|
||||
const curUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addHours(
|
||||
addMonths(new Date(), 2),
|
||||
hoursToFinalizeInvoice,
|
||||
).getTime(),
|
||||
waitForSeconds: 30,
|
||||
});
|
||||
|
||||
const invoiceAmount = await getExpectedInvoiceTotal({
|
||||
customerId,
|
||||
productId: pro.id,
|
||||
usage: [{ featureId: TestFeature.Words, value: usage }],
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
expect(customer.invoices.length).to.equal(2);
|
||||
expect(invoiceAmount).to.equal(customer.invoices[0].total);
|
||||
});
|
||||
});
|
||||
102
server/tests/advanced/customInterval/customInterval2.test.ts
Normal file
102
server/tests/advanced/customInterval/customInterval2.test.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { LegacyVersion } from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import { addHours, addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { getExpectedInvoiceTotal } from "tests/utils/expectUtils/expectInvoiceUtils.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructRawProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
|
||||
const testCase = "customInterval2";
|
||||
|
||||
export const pro = constructRawProduct({
|
||||
id: "pro",
|
||||
items: [
|
||||
constructArrearItem({
|
||||
includedUsage: 0,
|
||||
featureId: TestFeature.Words,
|
||||
intervalCount: 2,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing custom interval on arrear prorated price`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
test("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
});
|
||||
|
||||
const usage = 100012;
|
||||
test("should upgrade to premium product and have correct invoice next cycle", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Words,
|
||||
value: usage,
|
||||
});
|
||||
|
||||
const curUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addHours(
|
||||
addMonths(new Date(), 2),
|
||||
hoursToFinalizeInvoice,
|
||||
).getTime(),
|
||||
waitForSeconds: 30,
|
||||
});
|
||||
|
||||
const invoiceAmount = await getExpectedInvoiceTotal({
|
||||
customerId,
|
||||
productId: pro.id,
|
||||
usage: [{ featureId: TestFeature.Words, value: usage }],
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
expect(customer.invoices.length).toBe(2);
|
||||
expect(invoiceAmount).toBe(customer.invoices[0].total);
|
||||
});
|
||||
});
|
||||
160
server/tests/advanced/customInterval/customInterval3.backup.ts
Normal file
160
server/tests/advanced/customInterval/customInterval3.backup.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import { addDays, addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import { advanceTestClock } from "tests/utils/stripeUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { calculateProrationAmount } from "@/internal/invoices/prorationUtils.js";
|
||||
import {
|
||||
constructFeatureItem,
|
||||
constructPrepaidItem,
|
||||
} from "@/utils/scriptUtils/constructItem.js";
|
||||
import {
|
||||
constructProduct,
|
||||
constructRawProduct,
|
||||
} from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
|
||||
const testCase = "customInterval3";
|
||||
|
||||
export const pro = constructProduct({
|
||||
type: "pro",
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
intervalCount: 2,
|
||||
}),
|
||||
],
|
||||
intervalCount: 2,
|
||||
});
|
||||
|
||||
const prepaidWordsItem = constructPrepaidItem({
|
||||
featureId: TestFeature.Words,
|
||||
price: 10,
|
||||
billingUnits: 1,
|
||||
includedUsage: 0,
|
||||
intervalCount: 2,
|
||||
});
|
||||
|
||||
export const addOn = constructRawProduct({
|
||||
id: "addOn",
|
||||
items: [prepaidWordsItem],
|
||||
isAddOn: true,
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing custom interval on add on merged product`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [pro, addOn],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [pro, addOn],
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
it("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
});
|
||||
|
||||
it("should upgrade to attached add on and have correct invoice next cycle", async () => {
|
||||
const curUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addDays(new Date(), 20).getTime(),
|
||||
waitForSeconds: 15,
|
||||
});
|
||||
|
||||
const wordBillingSets = 2;
|
||||
const wordsBillingUnits = prepaidWordsItem.billing_units! * wordBillingSets;
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: addOn.id,
|
||||
options: [
|
||||
{
|
||||
feature_id: TestFeature.Words,
|
||||
quantity: wordsBillingUnits,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const proProduct = customer.products.find((p) => p.id === pro.id);
|
||||
const invoices = customer.invoices;
|
||||
expectProductAttached({
|
||||
customer,
|
||||
product: pro,
|
||||
});
|
||||
|
||||
expectProductAttached({
|
||||
customer,
|
||||
product: addOn,
|
||||
});
|
||||
|
||||
const expectedPrice = wordsBillingUnits * prepaidWordsItem.price!;
|
||||
const proratedPrice = calculateProrationAmount({
|
||||
amount: expectedPrice,
|
||||
periodStart: new Date().getTime(),
|
||||
periodEnd: addMonths(new Date(), 2).getTime(),
|
||||
now: curUnix!,
|
||||
});
|
||||
|
||||
expect(invoices[0].product_ids).to.include(addOn.id);
|
||||
expect(invoices[0].total).to.approximately(proratedPrice, 0.1);
|
||||
|
||||
const expectedAddonEnd = addMonths(new Date(), 2);
|
||||
const approximate = 1000 * 60 * 60 * 24; // +- 1 day
|
||||
const addOnProduct = customer.products.find((p) => p.id === addOn.id);
|
||||
|
||||
expect(addOnProduct?.current_period_end).to.be.approximately(
|
||||
expectedAddonEnd.getTime(),
|
||||
approximate,
|
||||
);
|
||||
});
|
||||
});
|
||||
144
server/tests/advanced/customInterval/customInterval3.test.ts
Normal file
144
server/tests/advanced/customInterval/customInterval3.test.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { LegacyVersion } from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import { addDays, addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { calculateProrationAmount } from "@/internal/invoices/prorationUtils.js";
|
||||
import {
|
||||
constructFeatureItem,
|
||||
constructPrepaidItem,
|
||||
} from "@/utils/scriptUtils/constructItem.js";
|
||||
import {
|
||||
constructProduct,
|
||||
constructRawProduct,
|
||||
} from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
|
||||
const testCase = "customInterval3";
|
||||
|
||||
export const pro = constructProduct({
|
||||
type: "pro",
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
intervalCount: 2,
|
||||
}),
|
||||
],
|
||||
intervalCount: 2,
|
||||
});
|
||||
|
||||
const prepaidWordsItem = constructPrepaidItem({
|
||||
featureId: TestFeature.Words,
|
||||
price: 10,
|
||||
billingUnits: 1,
|
||||
includedUsage: 0,
|
||||
intervalCount: 2,
|
||||
});
|
||||
|
||||
export const addOn = constructRawProduct({
|
||||
id: "addOn",
|
||||
items: [prepaidWordsItem],
|
||||
isAddOn: true,
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing custom interval on add on merged product`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro, addOn],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
test("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
});
|
||||
|
||||
test("should upgrade to attached add on and have correct invoice next cycle", async () => {
|
||||
const curUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addDays(new Date(), 20).getTime(),
|
||||
waitForSeconds: 15,
|
||||
});
|
||||
|
||||
const wordBillingSets = 2;
|
||||
const wordsBillingUnits = prepaidWordsItem.billing_units! * wordBillingSets;
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: addOn.id,
|
||||
options: [
|
||||
{
|
||||
feature_id: TestFeature.Words,
|
||||
quantity: wordsBillingUnits,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const proProduct = customer.products.find((p) => p.id === pro.id);
|
||||
const invoices = customer.invoices;
|
||||
expectProductAttached({
|
||||
customer,
|
||||
product: pro,
|
||||
});
|
||||
|
||||
expectProductAttached({
|
||||
customer,
|
||||
product: addOn,
|
||||
});
|
||||
|
||||
const expectedPrice = wordsBillingUnits * prepaidWordsItem.price!;
|
||||
const proratedPrice = calculateProrationAmount({
|
||||
amount: expectedPrice,
|
||||
periodStart: new Date().getTime(),
|
||||
periodEnd: addMonths(new Date(), 2).getTime(),
|
||||
now: curUnix!,
|
||||
});
|
||||
|
||||
expect(invoices[0].product_ids).toContain(addOn.id);
|
||||
expect(invoices[0].total).toBeCloseTo(proratedPrice, 1);
|
||||
|
||||
const expectedAddonEnd = addMonths(new Date(), 2);
|
||||
const approximate = 1000 * 60 * 60 * 24; // +- 1 day
|
||||
const addOnProduct = customer.products.find((p) => p.id === addOn.id);
|
||||
|
||||
expect(addOnProduct?.current_period_end).toBeCloseTo(
|
||||
expectedAddonEnd.getTime(),
|
||||
-Math.log10(approximate),
|
||||
);
|
||||
});
|
||||
});
|
||||
149
server/tests/advanced/customInterval/customInterval4.backup.ts
Normal file
149
server/tests/advanced/customInterval/customInterval4.backup.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import { addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import {
|
||||
expectDowngradeCorrect,
|
||||
expectNextCycleCorrect,
|
||||
} from "tests/utils/expectUtils/expectScheduleUtils.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import { getBasePrice } from "tests/utils/testProductUtils/testProductUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
|
||||
const testCase = "customInterval4";
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 500,
|
||||
}),
|
||||
],
|
||||
intervalCount: 2,
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
export const premium = constructProduct({
|
||||
id: "premium",
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 500,
|
||||
}),
|
||||
],
|
||||
intervalCount: 2,
|
||||
type: "premium",
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing downgrades for custom intervals`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [pro, premium],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [pro, premium],
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
it("should attach premium product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: premium,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
});
|
||||
|
||||
it("should have correct next cycle at on checkout", async () => {
|
||||
const checkout = await autumn.checkout({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
const expectedNextCycle = addMonths(new Date(), 2);
|
||||
expect(checkout.next_cycle?.starts_at).to.be.approximately(
|
||||
expectedNextCycle.getTime(),
|
||||
1000 * 60 * 60 * 24,
|
||||
);
|
||||
|
||||
expect(checkout.total).to.equal(0);
|
||||
});
|
||||
|
||||
let preview: any;
|
||||
it("should downgrade to pro", async () => {
|
||||
const { preview: preview_ } = await expectDowngradeCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
curProduct: premium,
|
||||
newProduct: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
|
||||
preview = preview_;
|
||||
});
|
||||
|
||||
it("should have pro attached on next cycle", async () => {
|
||||
await expectNextCycleCorrect({
|
||||
preview: preview!,
|
||||
autumn,
|
||||
stripeCli,
|
||||
customerId,
|
||||
testClockId,
|
||||
product: pro,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const invoices = customer.invoices;
|
||||
expect(invoices.length).to.equal(2);
|
||||
expect(invoices[0].total).to.equal(getBasePrice({ product: pro }));
|
||||
});
|
||||
});
|
||||
133
server/tests/advanced/customInterval/customInterval4.test.ts
Normal file
133
server/tests/advanced/customInterval/customInterval4.test.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { LegacyVersion } from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import { addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import {
|
||||
expectDowngradeCorrect,
|
||||
expectNextCycleCorrect,
|
||||
} from "tests/utils/expectUtils/expectScheduleUtils.js";
|
||||
import { getBasePrice } from "tests/utils/testProductUtils/testProductUtils.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
|
||||
const testCase = "customInterval4";
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 500,
|
||||
}),
|
||||
],
|
||||
intervalCount: 2,
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
export const premium = constructProduct({
|
||||
id: "premium",
|
||||
items: [
|
||||
constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 500,
|
||||
}),
|
||||
],
|
||||
intervalCount: 2,
|
||||
type: "premium",
|
||||
});
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing downgrades for custom intervals`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro, premium],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
test("should attach premium product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: premium,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
});
|
||||
|
||||
test("should have correct next cycle at on checkout", async () => {
|
||||
const checkout = await autumn.checkout({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
const expectedNextCycle = addMonths(new Date(), 2);
|
||||
expect(checkout.next_cycle?.starts_at).toBeCloseTo(
|
||||
expectedNextCycle.getTime(),
|
||||
-Math.log10(1000 * 60 * 60 * 24),
|
||||
);
|
||||
|
||||
expect(checkout.total).toBe(0);
|
||||
});
|
||||
|
||||
let preview: any;
|
||||
test("should downgrade to pro", async () => {
|
||||
const { preview: preview_ } = await expectDowngradeCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
curProduct: premium,
|
||||
newProduct: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
|
||||
preview = preview_;
|
||||
});
|
||||
|
||||
test("should have pro attached on next cycle", async () => {
|
||||
await expectNextCycleCorrect({
|
||||
preview: preview!,
|
||||
autumn,
|
||||
stripeCli: ctx.stripeCli,
|
||||
customerId,
|
||||
testClockId,
|
||||
product: pro,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const invoices = customer.invoices;
|
||||
expect(invoices.length).toBe(2);
|
||||
expect(invoices[0].total).toBe(getBasePrice({ product: pro }));
|
||||
});
|
||||
});
|
||||
161
server/tests/advanced/customInterval/customInterval5.backup.ts
Normal file
161
server/tests/advanced/customInterval/customInterval5.backup.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared";
|
||||
import type { Customer } from "autumn-js";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
|
||||
const testCase = "customInterval5";
|
||||
|
||||
const includedUsage = 500;
|
||||
const monthlyWords = constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage,
|
||||
});
|
||||
|
||||
const biMonthlyWords = constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
intervalCount: 2,
|
||||
includedUsage,
|
||||
});
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [monthlyWords, biMonthlyWords],
|
||||
intervalCount: 2,
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const getBreakdown = ({
|
||||
customer,
|
||||
intervalCount,
|
||||
}: {
|
||||
customer: Customer;
|
||||
intervalCount: number;
|
||||
}) => {
|
||||
const wordsFeature = customer.features[TestFeature.Words];
|
||||
return wordsFeature.breakdown?.find(
|
||||
(b: any) => b.interval_count === intervalCount,
|
||||
);
|
||||
};
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing multi interval features with custom intervals`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [pro],
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
it("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const wordsFeature = customer.features[TestFeature.Words];
|
||||
// @ts-expect-error
|
||||
expect(wordsFeature.interval_count).to.equal(null);
|
||||
expect(wordsFeature.breakdown?.length).to.equal(2);
|
||||
|
||||
expect(
|
||||
wordsFeature.breakdown?.some(
|
||||
(b: any) => b.interval_count === 1 && b.interval === "month",
|
||||
),
|
||||
).to.equal(true);
|
||||
expect(
|
||||
wordsFeature.breakdown?.some(
|
||||
(b: any) => b.interval_count === 2 && b.interval === "month",
|
||||
),
|
||||
).to.equal(true);
|
||||
});
|
||||
|
||||
const trackVal = 300;
|
||||
it("should have correct breakdown after usage", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Words,
|
||||
value: trackVal,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
|
||||
// Should deduct
|
||||
const monthlyBreakdown = getBreakdown({ customer, intervalCount: 1 });
|
||||
const biMonthlyBreakdown = getBreakdown({ customer, intervalCount: 2 });
|
||||
|
||||
expect(monthlyBreakdown?.balance).to.equal(includedUsage - trackVal);
|
||||
expect(biMonthlyBreakdown?.balance).to.equal(includedUsage);
|
||||
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Words,
|
||||
value: trackVal,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
const customer2 = await autumn.customers.get(customerId);
|
||||
const monthlyBreakdown2 = getBreakdown({
|
||||
customer: customer2,
|
||||
intervalCount: 1,
|
||||
});
|
||||
const biMonthlyBreakdown2 = getBreakdown({
|
||||
customer: customer2,
|
||||
intervalCount: 2,
|
||||
});
|
||||
|
||||
expect(monthlyBreakdown2?.balance).to.equal(0);
|
||||
expect(biMonthlyBreakdown2?.balance).to.equal(includedUsage - 100);
|
||||
});
|
||||
});
|
||||
145
server/tests/advanced/customInterval/customInterval5.test.ts
Normal file
145
server/tests/advanced/customInterval/customInterval5.test.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import { LegacyVersion } from "@autumn/shared";
|
||||
import type { Customer } from "autumn-js";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
|
||||
const testCase = "customInterval5";
|
||||
|
||||
const includedUsage = 500;
|
||||
const monthlyWords = constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage,
|
||||
});
|
||||
|
||||
const biMonthlyWords = constructFeatureItem({
|
||||
featureId: TestFeature.Words,
|
||||
intervalCount: 2,
|
||||
includedUsage,
|
||||
});
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [monthlyWords, biMonthlyWords],
|
||||
intervalCount: 2,
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const getBreakdown = ({
|
||||
customer,
|
||||
intervalCount,
|
||||
}: {
|
||||
customer: Customer;
|
||||
intervalCount: number;
|
||||
}) => {
|
||||
const wordsFeature = customer.features[TestFeature.Words];
|
||||
return wordsFeature.breakdown?.find(
|
||||
(b: any) => b.interval_count === intervalCount,
|
||||
);
|
||||
};
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing multi interval features with custom intervals`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
test("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const wordsFeature = customer.features[TestFeature.Words];
|
||||
// @ts-expect-error
|
||||
expect(wordsFeature.interval_count).toBe(null);
|
||||
expect(wordsFeature.breakdown?.length).toBe(2);
|
||||
|
||||
expect(
|
||||
wordsFeature.breakdown?.some(
|
||||
(b: any) => b.interval_count === 1 && b.interval === "month",
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
wordsFeature.breakdown?.some(
|
||||
(b: any) => b.interval_count === 2 && b.interval === "month",
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
const trackVal = 300;
|
||||
test("should have correct breakdown after usage", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Words,
|
||||
value: trackVal,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
|
||||
// Should deduct
|
||||
const monthlyBreakdown = getBreakdown({ customer, intervalCount: 1 });
|
||||
const biMonthlyBreakdown = getBreakdown({ customer, intervalCount: 2 });
|
||||
|
||||
expect(monthlyBreakdown?.balance).toBe(includedUsage - trackVal);
|
||||
expect(biMonthlyBreakdown?.balance).toBe(includedUsage);
|
||||
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Words,
|
||||
value: trackVal,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
const customer2 = await autumn.customers.get(customerId);
|
||||
const monthlyBreakdown2 = getBreakdown({
|
||||
customer: customer2,
|
||||
intervalCount: 1,
|
||||
});
|
||||
const biMonthlyBreakdown2 = getBreakdown({
|
||||
customer: customer2,
|
||||
intervalCount: 2,
|
||||
});
|
||||
|
||||
expect(monthlyBreakdown2?.balance).toBe(0);
|
||||
expect(biMonthlyBreakdown2?.balance).toBe(includedUsage - 100);
|
||||
});
|
||||
});
|
||||
@@ -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` : ""}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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` : ""}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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` : ""}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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` : ""}`
|
||||
// );
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
@@ -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,
|
||||
// })
|
||||
@@ -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);
|
||||
// });
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
198
server/tests/advanced/rollovers/rollover1.backup.ts
Normal file
198
server/tests/advanced/rollovers/rollover1.backup.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
type Customer,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
type Organization,
|
||||
ProductItemInterval,
|
||||
RolloverDuration,
|
||||
} from "@autumn/shared";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import { resetAndGetCusEnt } from "./rolloverTestUtils.js";
|
||||
|
||||
const rolloverConfig = {
|
||||
max: 500,
|
||||
length: 1,
|
||||
duration: RolloverDuration.Month,
|
||||
};
|
||||
const messagesItem = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 400,
|
||||
interval: ProductItemInterval.Month,
|
||||
rolloverConfig,
|
||||
}) as LimitedItem;
|
||||
|
||||
export const free = constructProduct({
|
||||
items: [messagesItem],
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const testCase = "rollover1";
|
||||
// , per entity and regular
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for feature item`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let customer: Customer;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [free],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [free],
|
||||
customerId,
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
const res = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = res.testClockId!;
|
||||
customer = res.customer;
|
||||
});
|
||||
|
||||
it("should attach free product", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: free.id,
|
||||
});
|
||||
});
|
||||
|
||||
const messageUsage = 250;
|
||||
let curBalance = messagesItem.included_usage;
|
||||
|
||||
it("should create track messages, reset, and have correct rollover", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: messageUsage,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
await resetAndGetCusEnt({
|
||||
db,
|
||||
customer,
|
||||
productGroup: free.group,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
|
||||
const expectedRollover = Math.min(
|
||||
messagesItem.included_usage - messageUsage,
|
||||
rolloverConfig.max,
|
||||
);
|
||||
|
||||
const expectedBalance = messagesItem.included_usage + expectedRollover;
|
||||
|
||||
expect(msgesFeature).to.exist;
|
||||
expect(msgesFeature?.balance).to.equal(expectedBalance);
|
||||
// @ts-expect-error
|
||||
expect(msgesFeature?.rollovers[0].balance).to.equal(expectedRollover);
|
||||
curBalance = expectedBalance;
|
||||
});
|
||||
|
||||
// let usage2 = 50;
|
||||
it("should reset again and have correct rollover", async () => {
|
||||
await resetAndGetCusEnt({
|
||||
db,
|
||||
customer,
|
||||
productGroup: free.group,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
|
||||
const expectedRollover = Math.min(curBalance, rolloverConfig.max);
|
||||
const expectedBalance = messagesItem.included_usage + expectedRollover;
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
|
||||
expect(msgesFeature).to.exist;
|
||||
expect(msgesFeature?.balance).to.equal(expectedBalance);
|
||||
|
||||
// @ts-expect-error (oldest rollover should be 100 (150 - 50))
|
||||
expect(msgesFeature?.rollovers[0].balance).to.equal(100);
|
||||
// @ts-expect-error (newest rollover should be 400 (msges.included_usage))
|
||||
expect(msgesFeature?.rollovers[1].balance).to.equal(400);
|
||||
});
|
||||
|
||||
it("should track messages and deduct from rollovers first", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: 150,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
|
||||
// @ts-expect-error
|
||||
const rollover1 = msgesFeature?.rollovers[0];
|
||||
// @ts-expect-error
|
||||
const rollover2 = msgesFeature?.rollovers[1];
|
||||
|
||||
expect(rollover1.balance).to.equal(0);
|
||||
expect(rollover2.balance).to.equal(350);
|
||||
});
|
||||
|
||||
it("should track and deduct from rollover + original balance", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: 400,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
|
||||
// @ts-expect-error
|
||||
const rollovers = msgesFeature.rollovers;
|
||||
expect(rollovers![0].balance).to.equal(0);
|
||||
expect(rollovers![1].balance).to.equal(0);
|
||||
expect(msgesFeature.balance).to.equal(messagesItem.included_usage - 50);
|
||||
});
|
||||
});
|
||||
179
server/tests/advanced/rollovers/rollover1.test.ts
Normal file
179
server/tests/advanced/rollovers/rollover1.test.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
import {
|
||||
type Customer,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
ProductItemInterval,
|
||||
RolloverDuration,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { resetAndGetCusEnt } from "./rolloverTestUtils.js";
|
||||
|
||||
const rolloverConfig = {
|
||||
max: 500,
|
||||
length: 1,
|
||||
duration: RolloverDuration.Month,
|
||||
};
|
||||
const messagesItem = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 400,
|
||||
interval: ProductItemInterval.Month,
|
||||
rolloverConfig,
|
||||
}) as LimitedItem;
|
||||
|
||||
export const free = constructProduct({
|
||||
items: [messagesItem],
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const testCase = "rollover1";
|
||||
// , per entity and regular
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for feature item`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let customer: Customer;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [free],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const res = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = res.testClockId!;
|
||||
customer = res.customer;
|
||||
});
|
||||
|
||||
test("should attach free product", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: free.id,
|
||||
});
|
||||
});
|
||||
|
||||
const messageUsage = 250;
|
||||
let curBalance = messagesItem.included_usage;
|
||||
|
||||
test("should create track messages, reset, and have correct rollover", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: messageUsage,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
await resetAndGetCusEnt({
|
||||
db: ctx.db,
|
||||
customer,
|
||||
productGroup: free.group,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
|
||||
const expectedRollover = Math.min(
|
||||
messagesItem.included_usage - messageUsage,
|
||||
rolloverConfig.max,
|
||||
);
|
||||
|
||||
const expectedBalance = messagesItem.included_usage + expectedRollover;
|
||||
|
||||
expect(msgesFeature).toBeDefined();
|
||||
expect(msgesFeature?.balance).toBe(expectedBalance);
|
||||
// @ts-expect-error
|
||||
expect(msgesFeature?.rollovers[0].balance).toBe(expectedRollover);
|
||||
curBalance = expectedBalance;
|
||||
});
|
||||
|
||||
// let usage2 = 50;
|
||||
test("should reset again and have correct rollover", async () => {
|
||||
await resetAndGetCusEnt({
|
||||
db: ctx.db,
|
||||
customer,
|
||||
productGroup: free.group,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
|
||||
const expectedRollover = Math.min(curBalance, rolloverConfig.max);
|
||||
const expectedBalance = messagesItem.included_usage + expectedRollover;
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
|
||||
expect(msgesFeature).toBeDefined();
|
||||
expect(msgesFeature?.balance).toBe(expectedBalance);
|
||||
|
||||
// @ts-expect-error (oldest rollover should be 100 (150 - 50))
|
||||
expect(msgesFeature?.rollovers[0].balance).toBe(100);
|
||||
// @ts-expect-error (newest rollover should be 400 (msges.included_usage))
|
||||
expect(msgesFeature?.rollovers[1].balance).toBe(400);
|
||||
});
|
||||
|
||||
test("should track messages and deduct from rollovers first", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: 150,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
|
||||
// @ts-expect-error
|
||||
const rollover1 = msgesFeature?.rollovers[0];
|
||||
// @ts-expect-error
|
||||
const rollover2 = msgesFeature?.rollovers[1];
|
||||
|
||||
expect(rollover1.balance).toBe(0);
|
||||
expect(rollover2.balance).toBe(350);
|
||||
});
|
||||
|
||||
test("should track and deduct from rollover + original balance", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: 400,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
|
||||
// @ts-expect-error
|
||||
const rollovers = msgesFeature.rollovers;
|
||||
expect(rollovers![0].balance).toBe(0);
|
||||
expect(rollovers![1].balance).toBe(0);
|
||||
expect(msgesFeature.balance).toBe(messagesItem.included_usage - 50);
|
||||
});
|
||||
});
|
||||
225
server/tests/advanced/rollovers/rollover2.backup.ts
Normal file
225
server/tests/advanced/rollovers/rollover2.backup.ts
Normal file
@@ -0,0 +1,225 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
type Customer,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
type Organization,
|
||||
ProductItemInterval,
|
||||
RolloverDuration,
|
||||
} from "@autumn/shared";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import { resetAndGetCusEnt } from "./rolloverTestUtils.js";
|
||||
|
||||
const rolloverConfig = {
|
||||
max: 500,
|
||||
length: 1,
|
||||
duration: RolloverDuration.Month,
|
||||
};
|
||||
|
||||
const msgesItem = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 400,
|
||||
interval: ProductItemInterval.Month,
|
||||
rolloverConfig,
|
||||
entityFeatureId: TestFeature.Users,
|
||||
}) as LimitedItem;
|
||||
|
||||
export const free = constructProduct({
|
||||
items: [msgesItem],
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const testCase = "rollover2";
|
||||
// , per entity and regular
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for feature item (per entity)`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let customer: Customer;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [free],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [free],
|
||||
customerId,
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
const res = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = res.testClockId!;
|
||||
customer = res.customer;
|
||||
});
|
||||
|
||||
const entities: any[] = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Entity 1",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Entity 2",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
];
|
||||
|
||||
it("should attach pro product", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: free.id,
|
||||
});
|
||||
|
||||
await autumn.entities.create(customerId, entities);
|
||||
});
|
||||
|
||||
const entity1Id = entities[0].id;
|
||||
const entity2Id = entities[1].id;
|
||||
const newEntity1Balance = 300;
|
||||
const newEntity2Balance = 200;
|
||||
const includedUsage = msgesItem.included_usage;
|
||||
const usages = [
|
||||
{
|
||||
entityId: entity1Id,
|
||||
usage: includedUsage - newEntity1Balance,
|
||||
rollover: newEntity1Balance,
|
||||
},
|
||||
{
|
||||
entityId: entity2Id,
|
||||
usage: includedUsage - newEntity2Balance,
|
||||
rollover: newEntity2Balance,
|
||||
},
|
||||
];
|
||||
|
||||
it("should create track messages, reset, and have correct rollover", async () => {
|
||||
for (const usage of usages) {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: usage.usage,
|
||||
entity_id: usage.entityId,
|
||||
});
|
||||
}
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
// Run reset cusEnt on ...
|
||||
await resetAndGetCusEnt({
|
||||
db,
|
||||
customer,
|
||||
productGroup: free.group,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
|
||||
for (const usage of usages) {
|
||||
const entity = await autumn.entities.get(customerId, usage.entityId);
|
||||
const msgesFeature = entity.features[TestFeature.Messages];
|
||||
const expectedRollover = Math.min(usage.rollover, rolloverConfig.max);
|
||||
|
||||
expect(msgesFeature.rollovers.length).to.equal(1);
|
||||
expect(msgesFeature.balance).to.equal(includedUsage + expectedRollover);
|
||||
expect(msgesFeature.rollovers[0].balance).to.equal(expectedRollover);
|
||||
}
|
||||
});
|
||||
|
||||
it("should reset again and have correct rollovers", async () => {
|
||||
await resetAndGetCusEnt({
|
||||
db,
|
||||
customer,
|
||||
productGroup: free.group,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
|
||||
const entity1 = await autumn.entities.get(customerId, entity1Id);
|
||||
const entity1Msges = entity1.features[TestFeature.Messages];
|
||||
// 400, 300 -> 400, 100 (max is 500)
|
||||
const rollovers = entity1Msges.rollovers;
|
||||
expect(rollovers[0].balance).to.equal(100);
|
||||
expect(rollovers[1].balance).to.equal(400);
|
||||
|
||||
const entity2 = await autumn.entities.get(customerId, entity2Id);
|
||||
const entity2Msges = entity2.features[TestFeature.Messages];
|
||||
// 400, 200 -> 400, 0 (max is 500)
|
||||
const rollovers2 = entity2Msges.rollovers;
|
||||
expect(rollovers2[0].balance).to.equal(100);
|
||||
expect(rollovers2[1].balance).to.equal(400);
|
||||
});
|
||||
|
||||
it("should track and deduct from oldest rollovers first", async () => {
|
||||
for (const entity of entities) {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: 150,
|
||||
entity_id: entity.id,
|
||||
});
|
||||
|
||||
await timeout(2000);
|
||||
const entRes = await autumn.entities.get(customerId, entity.id);
|
||||
const msgesFeature = entRes.features[TestFeature.Messages];
|
||||
const rollovers = msgesFeature.rollovers;
|
||||
expect(rollovers[0].balance).to.equal(0);
|
||||
expect(rollovers[1].balance).to.equal(350);
|
||||
expect(msgesFeature.balance).to.equal(includedUsage + 350);
|
||||
}
|
||||
});
|
||||
|
||||
it("should track past rollovers and deduct from original balance", async () => {
|
||||
for (const entity of entities) {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: 400,
|
||||
entity_id: entity.id,
|
||||
});
|
||||
await timeout(2000);
|
||||
|
||||
const entRes = await autumn.entities.get(customerId, entity.id);
|
||||
const msgesFeature = entRes.features[TestFeature.Messages];
|
||||
const rollovers = msgesFeature.rollovers;
|
||||
expect(rollovers[0].balance).to.equal(0);
|
||||
expect(rollovers[1].balance).to.equal(0);
|
||||
expect(msgesFeature.balance).to.equal(includedUsage - 50);
|
||||
}
|
||||
});
|
||||
});
|
||||
206
server/tests/advanced/rollovers/rollover2.test.ts
Normal file
206
server/tests/advanced/rollovers/rollover2.test.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
import {
|
||||
type Customer,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
ProductItemInterval,
|
||||
RolloverDuration,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { resetAndGetCusEnt } from "./rolloverTestUtils.js";
|
||||
|
||||
const rolloverConfig = {
|
||||
max: 500,
|
||||
length: 1,
|
||||
duration: RolloverDuration.Month,
|
||||
};
|
||||
|
||||
const msgesItem = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 400,
|
||||
interval: ProductItemInterval.Month,
|
||||
rolloverConfig,
|
||||
entityFeatureId: TestFeature.Users,
|
||||
}) as LimitedItem;
|
||||
|
||||
export const free = constructProduct({
|
||||
items: [msgesItem],
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const testCase = "rollover2";
|
||||
// , per entity and regular
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for feature item (per entity)`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let customer: Customer;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [free],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const res = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = res.testClockId!;
|
||||
customer = res.customer;
|
||||
});
|
||||
|
||||
const entities: any[] = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Entity 1",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Entity 2",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
];
|
||||
|
||||
test("should attach pro product", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: free.id,
|
||||
});
|
||||
|
||||
await autumn.entities.create(customerId, entities);
|
||||
});
|
||||
|
||||
const entity1Id = entities[0].id;
|
||||
const entity2Id = entities[1].id;
|
||||
const newEntity1Balance = 300;
|
||||
const newEntity2Balance = 200;
|
||||
const includedUsage = msgesItem.included_usage;
|
||||
const usages = [
|
||||
{
|
||||
entityId: entity1Id,
|
||||
usage: includedUsage - newEntity1Balance,
|
||||
rollover: newEntity1Balance,
|
||||
},
|
||||
{
|
||||
entityId: entity2Id,
|
||||
usage: includedUsage - newEntity2Balance,
|
||||
rollover: newEntity2Balance,
|
||||
},
|
||||
];
|
||||
|
||||
test("should create track messages, reset, and have correct rollover", async () => {
|
||||
for (const usage of usages) {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: usage.usage,
|
||||
entity_id: usage.entityId,
|
||||
});
|
||||
}
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
// Run reset cusEnt on ...
|
||||
await resetAndGetCusEnt({
|
||||
db: ctx.db,
|
||||
customer,
|
||||
productGroup: free.group,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
|
||||
for (const usage of usages) {
|
||||
const entity = await autumn.entities.get(customerId, usage.entityId);
|
||||
const msgesFeature = entity.features[TestFeature.Messages];
|
||||
const expectedRollover = Math.min(usage.rollover, rolloverConfig.max);
|
||||
|
||||
expect(msgesFeature.rollovers.length).toBe(1);
|
||||
expect(msgesFeature.balance).toBe(includedUsage + expectedRollover);
|
||||
expect(msgesFeature.rollovers[0].balance).toBe(expectedRollover);
|
||||
}
|
||||
});
|
||||
|
||||
test("should reset again and have correct rollovers", async () => {
|
||||
await resetAndGetCusEnt({
|
||||
db: ctx.db,
|
||||
customer,
|
||||
productGroup: free.group,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
|
||||
const entity1 = await autumn.entities.get(customerId, entity1Id);
|
||||
const entity1Msges = entity1.features[TestFeature.Messages];
|
||||
// 400, 300 -> 400, 100 (max is 500)
|
||||
const rollovers = entity1Msges.rollovers;
|
||||
expect(rollovers[0].balance).toBe(100);
|
||||
expect(rollovers[1].balance).toBe(400);
|
||||
|
||||
const entity2 = await autumn.entities.get(customerId, entity2Id);
|
||||
const entity2Msges = entity2.features[TestFeature.Messages];
|
||||
// 400, 200 -> 400, 0 (max is 500)
|
||||
const rollovers2 = entity2Msges.rollovers;
|
||||
expect(rollovers2[0].balance).toBe(100);
|
||||
expect(rollovers2[1].balance).toBe(400);
|
||||
});
|
||||
|
||||
test("should track and deduct from oldest rollovers first", async () => {
|
||||
for (const entity of entities) {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: 150,
|
||||
entity_id: entity.id,
|
||||
});
|
||||
|
||||
await timeout(2000);
|
||||
const entRes = await autumn.entities.get(customerId, entity.id);
|
||||
const msgesFeature = entRes.features[TestFeature.Messages];
|
||||
const rollovers = msgesFeature.rollovers;
|
||||
expect(rollovers[0].balance).toBe(0);
|
||||
expect(rollovers[1].balance).toBe(350);
|
||||
expect(msgesFeature.balance).toBe(includedUsage + 350);
|
||||
}
|
||||
});
|
||||
|
||||
test("should track past rollovers and deduct from original balance", async () => {
|
||||
for (const entity of entities) {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: 400,
|
||||
entity_id: entity.id,
|
||||
});
|
||||
await timeout(2000);
|
||||
|
||||
const entRes = await autumn.entities.get(customerId, entity.id);
|
||||
const msgesFeature = entRes.features[TestFeature.Messages];
|
||||
const rollovers = msgesFeature.rollovers;
|
||||
expect(rollovers[0].balance).toBe(0);
|
||||
expect(rollovers[1].balance).toBe(0);
|
||||
expect(msgesFeature.balance).toBe(includedUsage - 50);
|
||||
}
|
||||
});
|
||||
});
|
||||
127
server/tests/advanced/rollovers/rollover3.backup.ts
Normal file
127
server/tests/advanced/rollovers/rollover3.backup.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
type Customer,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
type Organization,
|
||||
RolloverDuration,
|
||||
} from "@autumn/shared";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import { addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import { constructArrearProratedItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
|
||||
const rolloverConfig = {
|
||||
max: 500,
|
||||
length: 1,
|
||||
duration: RolloverDuration.Month,
|
||||
};
|
||||
const messagesItem = constructArrearProratedItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 400,
|
||||
rolloverConfig,
|
||||
}) as LimitedItem;
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [messagesItem],
|
||||
type: "pro",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const testCase = "rollover3";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for usage price feature`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let customer: Customer;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [pro],
|
||||
customerId,
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
const res = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = res.testClockId!;
|
||||
customer = res.customer;
|
||||
});
|
||||
|
||||
it("should attach pro product", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
});
|
||||
|
||||
const rollover = 250;
|
||||
let curBalance = messagesItem.included_usage;
|
||||
|
||||
it("should create track messages, reset, and have correct rollover", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: messagesItem.included_usage - rollover,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addMonths(new Date(), 1).getTime(),
|
||||
waitForSeconds: 20,
|
||||
});
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
|
||||
const expectedBalance = messagesItem.included_usage + rollover;
|
||||
|
||||
expect(msgesFeature).to.exist;
|
||||
expect(msgesFeature?.balance).to.equal(expectedBalance);
|
||||
// @ts-expect-error
|
||||
expect(msgesFeature?.rollovers[0].balance).to.equal(rollover);
|
||||
curBalance = expectedBalance;
|
||||
});
|
||||
});
|
||||
108
server/tests/advanced/rollovers/rollover3.test.ts
Normal file
108
server/tests/advanced/rollovers/rollover3.test.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import {
|
||||
type Customer,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
RolloverDuration,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import { addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import { constructArrearProratedItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
|
||||
const rolloverConfig = {
|
||||
max: 500,
|
||||
length: 1,
|
||||
duration: RolloverDuration.Month,
|
||||
};
|
||||
const messagesItem = constructArrearProratedItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 400,
|
||||
rolloverConfig,
|
||||
}) as LimitedItem;
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [messagesItem],
|
||||
type: "pro",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const testCase = "rollover3";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for usage price feature`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let customer: Customer;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const res = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = res.testClockId!;
|
||||
customer = res.customer;
|
||||
});
|
||||
|
||||
test("should attach pro product", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
});
|
||||
|
||||
const rollover = 250;
|
||||
let curBalance = messagesItem.included_usage;
|
||||
|
||||
test("should create track messages, reset, and have correct rollover", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: messagesItem.included_usage - rollover,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addMonths(new Date(), 1).getTime(),
|
||||
waitForSeconds: 20,
|
||||
});
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
|
||||
const expectedBalance = messagesItem.included_usage + rollover;
|
||||
|
||||
expect(msgesFeature).toBeDefined();
|
||||
expect(msgesFeature?.balance).toBe(expectedBalance);
|
||||
// @ts-expect-error
|
||||
expect(msgesFeature?.rollovers[0].balance).toBe(rollover);
|
||||
curBalance = expectedBalance;
|
||||
});
|
||||
});
|
||||
157
server/tests/advanced/rollovers/rollover4.backup.ts
Normal file
157
server/tests/advanced/rollovers/rollover4.backup.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
type Customer,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
type Organization,
|
||||
RolloverDuration,
|
||||
} from "@autumn/shared";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import { addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import { constructPrepaidItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
|
||||
const rolloverConfig = {
|
||||
max: 400,
|
||||
length: 1,
|
||||
duration: RolloverDuration.Month,
|
||||
};
|
||||
const messagesItem = constructPrepaidItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 100,
|
||||
billingUnits: 300,
|
||||
price: 10,
|
||||
rolloverConfig,
|
||||
}) as LimitedItem;
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [messagesItem],
|
||||
type: "pro",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const testCase = "rollover4";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for usage price feature`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let customer: Customer;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
let curUnix = new Date().getTime();
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [pro],
|
||||
customerId,
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
const res = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = res.testClockId!;
|
||||
customer = res.customer;
|
||||
});
|
||||
|
||||
const paidQuantity = 300;
|
||||
const balance = paidQuantity + messagesItem.included_usage;
|
||||
const options = [
|
||||
{
|
||||
feature_id: TestFeature.Messages,
|
||||
quantity: paidQuantity,
|
||||
},
|
||||
];
|
||||
|
||||
it("should attach pro product", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
options,
|
||||
});
|
||||
});
|
||||
|
||||
const rollover = 50;
|
||||
it("should create track messages, reset, and have correct rollover", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: balance - rollover,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
curUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addMonths(new Date(), 1).getTime(),
|
||||
waitForSeconds: 20,
|
||||
});
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
|
||||
// @ts-expect-error
|
||||
const rollovers = msgesFeature?.rollovers;
|
||||
|
||||
expect(msgesFeature).to.exist;
|
||||
expect(msgesFeature?.balance).to.equal(balance + rollover);
|
||||
expect(rollovers[0].balance).to.equal(rollover);
|
||||
});
|
||||
|
||||
// let usage2 = 50;
|
||||
it("should reset again and have correct rollover", async () => {
|
||||
await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addMonths(curUnix, 1).getTime(),
|
||||
waitForSeconds: 20,
|
||||
});
|
||||
|
||||
const newRollover = Math.min(balance + rollover, rolloverConfig.max);
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
// @ts-expect-error
|
||||
const rollovers = msgesFeature?.rollovers;
|
||||
|
||||
expect(msgesFeature).to.exist;
|
||||
expect(msgesFeature?.balance).to.equal(balance + newRollover);
|
||||
expect(rollovers[0].balance).to.equal(0);
|
||||
expect(rollovers[1].balance).to.equal(400);
|
||||
});
|
||||
});
|
||||
138
server/tests/advanced/rollovers/rollover4.test.ts
Normal file
138
server/tests/advanced/rollovers/rollover4.test.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
import {
|
||||
type Customer,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
RolloverDuration,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import { addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import { constructPrepaidItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
|
||||
const rolloverConfig = {
|
||||
max: 400,
|
||||
length: 1,
|
||||
duration: RolloverDuration.Month,
|
||||
};
|
||||
const messagesItem = constructPrepaidItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 100,
|
||||
billingUnits: 300,
|
||||
price: 10,
|
||||
rolloverConfig,
|
||||
}) as LimitedItem;
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [messagesItem],
|
||||
type: "pro",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const testCase = "rollover4";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for usage price feature`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let customer: Customer;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
let curUnix = new Date().getTime();
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const res = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = res.testClockId!;
|
||||
customer = res.customer;
|
||||
});
|
||||
|
||||
const paidQuantity = 300;
|
||||
const balance = paidQuantity + messagesItem.included_usage;
|
||||
const options = [
|
||||
{
|
||||
feature_id: TestFeature.Messages,
|
||||
quantity: paidQuantity,
|
||||
},
|
||||
];
|
||||
|
||||
test("should attach pro product", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
options,
|
||||
});
|
||||
});
|
||||
|
||||
const rollover = 50;
|
||||
test("should create track messages, reset, and have correct rollover", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: balance - rollover,
|
||||
});
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
curUnix = await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addMonths(new Date(), 1).getTime(),
|
||||
waitForSeconds: 20,
|
||||
});
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
|
||||
// @ts-expect-error
|
||||
const rollovers = msgesFeature?.rollovers;
|
||||
|
||||
expect(msgesFeature).toBeDefined();
|
||||
expect(msgesFeature?.balance).toBe(balance + rollover);
|
||||
expect(rollovers[0].balance).toBe(rollover);
|
||||
});
|
||||
|
||||
// let usage2 = 50;
|
||||
test("should reset again and have correct rollover", async () => {
|
||||
await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addMonths(curUnix, 1).getTime(),
|
||||
waitForSeconds: 20,
|
||||
});
|
||||
|
||||
const newRollover = Math.min(balance + rollover, rolloverConfig.max);
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
// @ts-expect-error
|
||||
const rollovers = msgesFeature?.rollovers;
|
||||
|
||||
expect(msgesFeature).toBeDefined();
|
||||
expect(msgesFeature?.balance).toBe(balance + newRollover);
|
||||
expect(rollovers[0].balance).toBe(0);
|
||||
expect(rollovers[1].balance).toBe(400);
|
||||
});
|
||||
});
|
||||
137
server/tests/advanced/rollovers/rollover5.backup.ts
Normal file
137
server/tests/advanced/rollovers/rollover5.backup.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
type Customer,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
type Organization,
|
||||
RolloverDuration,
|
||||
} from "@autumn/shared";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import { resetAndGetCusEnt } from "./rolloverTestUtils.js";
|
||||
|
||||
const freeRollover = { max: 1000, length: 1, duration: RolloverDuration.Month };
|
||||
const proRollover = { max: 600, length: 1, duration: RolloverDuration.Month };
|
||||
|
||||
const freeMsges = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 500,
|
||||
rolloverConfig: freeRollover,
|
||||
}) as LimitedItem;
|
||||
|
||||
const proMsges = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 500,
|
||||
rolloverConfig: proRollover,
|
||||
}) as LimitedItem;
|
||||
|
||||
const free = constructProduct({
|
||||
items: [freeMsges],
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const pro = constructProduct({
|
||||
items: [proMsges],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const testCase = "rollover5";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for upgrade`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let customer: Customer;
|
||||
let stripeCli: Stripe;
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [free, pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [free, pro],
|
||||
customerId,
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
const res = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = res.testClockId!;
|
||||
customer = res.customer;
|
||||
});
|
||||
|
||||
it("should attach free product", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: free.id,
|
||||
});
|
||||
});
|
||||
|
||||
it("should create rollovers", async () => {
|
||||
await resetAndGetCusEnt({
|
||||
customer,
|
||||
db,
|
||||
productGroup: testCase,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
await resetAndGetCusEnt({
|
||||
customer,
|
||||
db,
|
||||
productGroup: testCase,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
|
||||
// Attach pro
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
const freeRolloverBalance = freeMsges.included_usage * 2;
|
||||
const proRolloverBalance = Math.min(proRollover.max, freeRolloverBalance);
|
||||
|
||||
expect(msgesFeature).to.exist;
|
||||
expect(msgesFeature?.balance).to.equal(
|
||||
proMsges.included_usage + proRolloverBalance,
|
||||
);
|
||||
// @ts-expect-error
|
||||
const rollovers = msgesFeature?.rollovers;
|
||||
expect(rollovers[0].balance).to.equal(100);
|
||||
expect(rollovers[1].balance).to.equal(500);
|
||||
});
|
||||
});
|
||||
118
server/tests/advanced/rollovers/rollover5.test.ts
Normal file
118
server/tests/advanced/rollovers/rollover5.test.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import {
|
||||
type Customer,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
RolloverDuration,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { resetAndGetCusEnt } from "./rolloverTestUtils.js";
|
||||
|
||||
const freeRollover = { max: 1000, length: 1, duration: RolloverDuration.Month };
|
||||
const proRollover = { max: 600, length: 1, duration: RolloverDuration.Month };
|
||||
|
||||
const freeMsges = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 500,
|
||||
rolloverConfig: freeRollover,
|
||||
}) as LimitedItem;
|
||||
|
||||
const proMsges = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 500,
|
||||
rolloverConfig: proRollover,
|
||||
}) as LimitedItem;
|
||||
|
||||
const free = constructProduct({
|
||||
items: [freeMsges],
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const pro = constructProduct({
|
||||
items: [proMsges],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const testCase = "rollover5";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for upgrade`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let customer: Customer;
|
||||
let stripeCli: Stripe;
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [free, pro],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const res = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = res.testClockId!;
|
||||
customer = res.customer;
|
||||
});
|
||||
|
||||
test("should attach free product", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: free.id,
|
||||
});
|
||||
});
|
||||
|
||||
test("should create rollovers", async () => {
|
||||
await resetAndGetCusEnt({
|
||||
customer,
|
||||
db: ctx.db,
|
||||
productGroup: testCase,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
await resetAndGetCusEnt({
|
||||
customer,
|
||||
db: ctx.db,
|
||||
productGroup: testCase,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
|
||||
// Attach pro
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
const freeRolloverBalance = freeMsges.included_usage * 2;
|
||||
const proRolloverBalance = Math.min(proRollover.max, freeRolloverBalance);
|
||||
|
||||
expect(msgesFeature).toBeDefined();
|
||||
expect(msgesFeature?.balance).toBe(
|
||||
proMsges.included_usage + proRolloverBalance,
|
||||
);
|
||||
// @ts-expect-error
|
||||
const rollovers = msgesFeature?.rollovers;
|
||||
expect(rollovers[0].balance).toBe(100);
|
||||
expect(rollovers[1].balance).toBe(500);
|
||||
});
|
||||
});
|
||||
151
server/tests/advanced/rollovers/rollover6.backup.ts
Normal file
151
server/tests/advanced/rollovers/rollover6.backup.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
type Customer,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
type Organization,
|
||||
RolloverDuration,
|
||||
} from "@autumn/shared";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import { addHours, addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
import { resetAndGetCusEnt } from "./rolloverTestUtils.js";
|
||||
|
||||
const freeRollover = { max: 600, length: 1, duration: RolloverDuration.Month };
|
||||
const proRollover = { max: 1000, length: 1, duration: RolloverDuration.Month };
|
||||
|
||||
const freeMsges = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 500,
|
||||
rolloverConfig: freeRollover,
|
||||
}) as LimitedItem;
|
||||
|
||||
const proMsges = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 500,
|
||||
rolloverConfig: proRollover,
|
||||
}) as LimitedItem;
|
||||
|
||||
const free = constructProduct({
|
||||
items: [freeMsges],
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const pro = constructProduct({
|
||||
items: [proMsges],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const testCase = "rollover6";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for upgrade`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let customer: Customer;
|
||||
let stripeCli: Stripe;
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [free, pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [free, pro],
|
||||
customerId,
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
const res = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = res.testClockId!;
|
||||
customer = res.customer;
|
||||
});
|
||||
|
||||
it("should attach free product", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
});
|
||||
|
||||
it("should create rollovers", async () => {
|
||||
await resetAndGetCusEnt({
|
||||
customer,
|
||||
db,
|
||||
productGroup: testCase,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
await resetAndGetCusEnt({
|
||||
customer,
|
||||
db,
|
||||
productGroup: testCase,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
|
||||
// Attach pro
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: free.id,
|
||||
});
|
||||
|
||||
await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addHours(
|
||||
addMonths(curUnix, 1),
|
||||
hoursToFinalizeInvoice,
|
||||
).getTime(),
|
||||
waitForSeconds: 20,
|
||||
});
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
const proRolloverBalance = proMsges.included_usage * 2;
|
||||
const freeRolloverBalance = Math.min(freeRollover.max, proRolloverBalance);
|
||||
|
||||
expect(msgesFeature).to.exist;
|
||||
expect(msgesFeature?.balance).to.equal(
|
||||
freeMsges.included_usage + freeRolloverBalance,
|
||||
);
|
||||
|
||||
// @ts-expect-error
|
||||
const rollovers = msgesFeature?.rollovers;
|
||||
expect(rollovers[0].balance).to.equal(100);
|
||||
expect(rollovers[1].balance).to.equal(500);
|
||||
});
|
||||
});
|
||||
132
server/tests/advanced/rollovers/rollover6.test.ts
Normal file
132
server/tests/advanced/rollovers/rollover6.test.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import {
|
||||
type Customer,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
RolloverDuration,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import { addHours, addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
import { resetAndGetCusEnt } from "./rolloverTestUtils.js";
|
||||
|
||||
const freeRollover = { max: 600, length: 1, duration: RolloverDuration.Month };
|
||||
const proRollover = { max: 1000, length: 1, duration: RolloverDuration.Month };
|
||||
|
||||
const freeMsges = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 500,
|
||||
rolloverConfig: freeRollover,
|
||||
}) as LimitedItem;
|
||||
|
||||
const proMsges = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 500,
|
||||
rolloverConfig: proRollover,
|
||||
}) as LimitedItem;
|
||||
|
||||
const free = constructProduct({
|
||||
items: [freeMsges],
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const pro = constructProduct({
|
||||
items: [proMsges],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const testCase = "rollover6";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for upgrade`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let customer: Customer;
|
||||
let stripeCli: Stripe;
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [free, pro],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const res = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = res.testClockId!;
|
||||
customer = res.customer;
|
||||
});
|
||||
|
||||
test("should attach free product", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
});
|
||||
|
||||
test("should create rollovers", async () => {
|
||||
await resetAndGetCusEnt({
|
||||
customer,
|
||||
db: ctx.db,
|
||||
productGroup: testCase,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
await resetAndGetCusEnt({
|
||||
customer,
|
||||
db: ctx.db,
|
||||
productGroup: testCase,
|
||||
featureId: TestFeature.Messages,
|
||||
});
|
||||
|
||||
// Attach pro
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: free.id,
|
||||
});
|
||||
|
||||
await advanceTestClock({
|
||||
stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addHours(
|
||||
addMonths(curUnix, 1),
|
||||
hoursToFinalizeInvoice,
|
||||
).getTime(),
|
||||
waitForSeconds: 20,
|
||||
});
|
||||
|
||||
const cus = await autumn.customers.get(customerId);
|
||||
const msgesFeature = cus.features[TestFeature.Messages];
|
||||
const proRolloverBalance = proMsges.included_usage * 2;
|
||||
const freeRolloverBalance = Math.min(freeRollover.max, proRolloverBalance);
|
||||
|
||||
expect(msgesFeature).toBeDefined();
|
||||
expect(msgesFeature?.balance).toBe(
|
||||
freeMsges.included_usage + freeRolloverBalance,
|
||||
);
|
||||
|
||||
// @ts-expect-error
|
||||
const rollovers = msgesFeature?.rollovers;
|
||||
expect(rollovers[0].balance).toBe(100);
|
||||
expect(rollovers[1].balance).toBe(500);
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
|
||||
@@ -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",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
// }
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
// }
|
||||
// });
|
||||
151
server/tests/advanced/usageLimit/usageLimit1.backup.ts
Normal file
151
server/tests/advanced/usageLimit/usageLimit1.backup.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
ErrCode,
|
||||
LegacyVersion,
|
||||
type Organization,
|
||||
} from "@autumn/shared";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectAutumnError } from "tests/utils/expectUtils/expectErrUtils.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructArrearProratedItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
|
||||
const userItem = constructArrearProratedItem({
|
||||
featureId: TestFeature.Users,
|
||||
pricePerUnit: 50,
|
||||
includedUsage: 0,
|
||||
usageLimit: 2,
|
||||
});
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [userItem],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const testCase = "usageLimit1";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing usage limits for entities`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [pro],
|
||||
customerId,
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
const entities = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Entity 1",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Entity 2",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "Entity 3",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
name: "Entity 4",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
];
|
||||
|
||||
it("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
});
|
||||
it("should create more entities than the limit and hit error", async () => {
|
||||
await expectAutumnError({
|
||||
errCode: ErrCode.FeatureLimitReached,
|
||||
func: async () => {
|
||||
await autumn.entities.create(customerId, entities);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should create entities one by one, then hit usage limit", async () => {
|
||||
await autumn.entities.create(customerId, entities[0]);
|
||||
await autumn.entities.create(customerId, entities[1]);
|
||||
|
||||
await expectAutumnError({
|
||||
errCode: ErrCode.FeatureLimitReached,
|
||||
func: async () => {
|
||||
await autumn.entities.create(customerId, entities[2]);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should have correct check and get customer value", async () => {
|
||||
const check = await autumn.check({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Users,
|
||||
});
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
|
||||
expect(check.balance).to.equal(-2);
|
||||
// @ts-expect-error
|
||||
expect(check.usage_limit).to.equal(userItem.usage_limit);
|
||||
|
||||
// @ts-expect-error
|
||||
expect(customer.features[TestFeature.Users].usage_limit).to.equal(
|
||||
userItem.usage_limit,
|
||||
);
|
||||
});
|
||||
});
|
||||
132
server/tests/advanced/usageLimit/usageLimit1.test.ts
Normal file
132
server/tests/advanced/usageLimit/usageLimit1.test.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import {
|
||||
ErrCode,
|
||||
LegacyVersion,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectAutumnError } from "tests/utils/expectUtils/expectErrUtils.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructArrearProratedItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
|
||||
const userItem = constructArrearProratedItem({
|
||||
featureId: TestFeature.Users,
|
||||
pricePerUnit: 50,
|
||||
includedUsage: 0,
|
||||
usageLimit: 2,
|
||||
});
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [userItem],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const testCase = "usageLimit1";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing usage limits for entities`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
const entities = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Entity 1",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Entity 2",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "Entity 3",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
name: "Entity 4",
|
||||
feature_id: TestFeature.Users,
|
||||
},
|
||||
];
|
||||
|
||||
test("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
});
|
||||
test("should create more entities than the limit and hit error", async () => {
|
||||
await expectAutumnError({
|
||||
errCode: ErrCode.FeatureLimitReached,
|
||||
func: async () => {
|
||||
await autumn.entities.create(customerId, entities);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("should create entities one by one, then hit usage limit", async () => {
|
||||
await autumn.entities.create(customerId, entities[0]);
|
||||
await autumn.entities.create(customerId, entities[1]);
|
||||
|
||||
await expectAutumnError({
|
||||
errCode: ErrCode.FeatureLimitReached,
|
||||
func: async () => {
|
||||
await autumn.entities.create(customerId, entities[2]);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("should have correct check and get customer value", async () => {
|
||||
const check = await autumn.check({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Users,
|
||||
});
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
|
||||
expect(check.balance).toBe(-2);
|
||||
// @ts-expect-error
|
||||
expect(check.usage_limit).toBe(userItem.usage_limit);
|
||||
|
||||
// @ts-expect-error
|
||||
expect(customer.features[TestFeature.Users].usage_limit).toBe(
|
||||
userItem.usage_limit,
|
||||
);
|
||||
});
|
||||
});
|
||||
195
server/tests/advanced/usageLimit/usageLimit2.backup.ts
Normal file
195
server/tests/advanced/usageLimit/usageLimit2.backup.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
type Organization,
|
||||
} from "@autumn/shared";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import {
|
||||
constructArrearItem,
|
||||
constructFeatureItem,
|
||||
} from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
|
||||
const messageItem = constructArrearItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 100,
|
||||
billingUnits: 1,
|
||||
price: 0.5,
|
||||
usageLimit: 500,
|
||||
}) as LimitedItem;
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [messageItem],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const addOnMessages = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
interval: null,
|
||||
includedUsage: 250,
|
||||
}) as LimitedItem;
|
||||
|
||||
const messageAddOn = constructProduct({
|
||||
type: "one_off",
|
||||
items: [addOnMessages],
|
||||
});
|
||||
|
||||
const testCase = "usageLimit2";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing usage limits, usage prices`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [pro, messageAddOn],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [pro, messageAddOn],
|
||||
customerId,
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
it("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
});
|
||||
|
||||
const initialUsage =
|
||||
messageItem.included_usage + messageItem.usage_limit! + 1000;
|
||||
|
||||
it("should track more messages than limit and not surpass", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: initialUsage,
|
||||
});
|
||||
|
||||
await timeout(2000);
|
||||
|
||||
const check = await autumn.check({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
});
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
|
||||
const expectedBalance =
|
||||
messageItem.included_usage - messageItem.usage_limit!;
|
||||
|
||||
expect(check.balance).to.equal(expectedBalance);
|
||||
expect(check.allowed).to.equal(false);
|
||||
// @ts-expect-error
|
||||
expect(check.usage_limit!).to.equal(messageItem.usage_limit!);
|
||||
// @ts-expect-error
|
||||
expect(customer.features[TestFeature.Messages].usage_limit).to.equal(
|
||||
messageItem.usage_limit!,
|
||||
);
|
||||
});
|
||||
|
||||
it("should purchase add ons and have correct check results", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: messageAddOn.id,
|
||||
});
|
||||
|
||||
const check = await autumn.check({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
});
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const expectedBalance =
|
||||
messageItem.included_usage -
|
||||
messageItem.usage_limit! +
|
||||
addOnMessages.included_usage;
|
||||
|
||||
expect(check.balance).to.equal(expectedBalance);
|
||||
expect(check.allowed).to.equal(true);
|
||||
|
||||
// @ts-expect-error
|
||||
expect(check.usage_limit!).to.equal(
|
||||
messageItem.usage_limit! + addOnMessages.included_usage,
|
||||
);
|
||||
// @ts-expect-error
|
||||
expect(customer.features[TestFeature.Messages].usage_limit).to.equal(
|
||||
messageItem.usage_limit! + addOnMessages.included_usage,
|
||||
);
|
||||
});
|
||||
|
||||
it("should use up all add ons and have correct check results", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: addOnMessages.included_usage + 500,
|
||||
});
|
||||
|
||||
await timeout(2000);
|
||||
|
||||
const check = await autumn.check({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
});
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
|
||||
const expectedBalance =
|
||||
messageItem.included_usage - messageItem.usage_limit!;
|
||||
expect(check.balance).to.equal(expectedBalance);
|
||||
expect(check.allowed).to.equal(false);
|
||||
// @ts-expect-error
|
||||
expect(check.usage_limit!).to.equal(
|
||||
messageItem.usage_limit! + addOnMessages.included_usage,
|
||||
);
|
||||
// @ts-expect-error
|
||||
expect(customer.features[TestFeature.Messages].usage_limit).to.equal(
|
||||
messageItem.usage_limit! + addOnMessages.included_usage,
|
||||
);
|
||||
});
|
||||
});
|
||||
176
server/tests/advanced/usageLimit/usageLimit2.test.ts
Normal file
176
server/tests/advanced/usageLimit/usageLimit2.test.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
import {
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import {
|
||||
constructArrearItem,
|
||||
constructFeatureItem,
|
||||
} from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
|
||||
const messageItem = constructArrearItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 100,
|
||||
billingUnits: 1,
|
||||
price: 0.5,
|
||||
usageLimit: 500,
|
||||
}) as LimitedItem;
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [messageItem],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const addOnMessages = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
interval: null,
|
||||
includedUsage: 250,
|
||||
}) as LimitedItem;
|
||||
|
||||
const messageAddOn = constructProduct({
|
||||
type: "one_off",
|
||||
items: [addOnMessages],
|
||||
});
|
||||
|
||||
const testCase = "usageLimit2";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing usage limits, usage prices`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro, messageAddOn],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
test("should attach pro product", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
});
|
||||
|
||||
const initialUsage =
|
||||
messageItem.included_usage + messageItem.usage_limit! + 1000;
|
||||
|
||||
test("should track more messages than limit and not surpass", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: initialUsage,
|
||||
});
|
||||
|
||||
await timeout(2000);
|
||||
|
||||
const check = await autumn.check({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
});
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
|
||||
const expectedBalance =
|
||||
messageItem.included_usage - messageItem.usage_limit!;
|
||||
|
||||
expect(check.balance).toBe(expectedBalance);
|
||||
expect(check.allowed).toBe(false);
|
||||
// @ts-expect-error
|
||||
expect(check.usage_limit!).toBe(messageItem.usage_limit!);
|
||||
// @ts-expect-error
|
||||
expect(customer.features[TestFeature.Messages].usage_limit).toBe(
|
||||
messageItem.usage_limit!,
|
||||
);
|
||||
});
|
||||
|
||||
test("should purchase add ons and have correct check results", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: messageAddOn.id,
|
||||
});
|
||||
|
||||
const check = await autumn.check({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
});
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
const expectedBalance =
|
||||
messageItem.included_usage -
|
||||
messageItem.usage_limit! +
|
||||
addOnMessages.included_usage;
|
||||
|
||||
expect(check.balance).toBe(expectedBalance);
|
||||
expect(check.allowed).toBe(true);
|
||||
|
||||
// @ts-expect-error
|
||||
expect(check.usage_limit!).toBe(
|
||||
messageItem.usage_limit! + addOnMessages.included_usage,
|
||||
);
|
||||
// @ts-expect-error
|
||||
expect(customer.features[TestFeature.Messages].usage_limit).toBe(
|
||||
messageItem.usage_limit! + addOnMessages.included_usage,
|
||||
);
|
||||
});
|
||||
|
||||
test("should use up all add ons and have correct check results", async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: addOnMessages.included_usage + 500,
|
||||
});
|
||||
|
||||
await timeout(2000);
|
||||
|
||||
const check = await autumn.check({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
});
|
||||
const customer = await autumn.customers.get(customerId);
|
||||
|
||||
const expectedBalance =
|
||||
messageItem.included_usage - messageItem.usage_limit!;
|
||||
expect(check.balance).toBe(expectedBalance);
|
||||
expect(check.allowed).toBe(false);
|
||||
// @ts-expect-error
|
||||
expect(check.usage_limit!).toBe(
|
||||
messageItem.usage_limit! + addOnMessages.included_usage,
|
||||
);
|
||||
// @ts-expect-error
|
||||
expect(customer.features[TestFeature.Messages].usage_limit).toBe(
|
||||
messageItem.usage_limit! + addOnMessages.included_usage,
|
||||
);
|
||||
});
|
||||
});
|
||||
147
server/tests/advanced/usageLimit/usageLimit3.backup.ts
Normal file
147
server/tests/advanced/usageLimit/usageLimit3.backup.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
ErrCode,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
type Organization,
|
||||
} from "@autumn/shared";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import { addPrefixToProducts } from "tests/attach/utils.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectAutumnError } from "tests/utils/expectUtils/expectErrUtils.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructPrepaidItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
|
||||
|
||||
const messageItem = constructPrepaidItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 50,
|
||||
billingUnits: 100,
|
||||
price: 8,
|
||||
usageLimit: 500,
|
||||
}) as LimitedItem;
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [messageItem],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
// const addOnMessages = constructFeatureItem({
|
||||
// featureId: TestFeature.Messages,
|
||||
// interval: null,
|
||||
// includedUsage: 250,
|
||||
// }) as LimitedItem;
|
||||
|
||||
// const messageAddOn = constructProduct({
|
||||
// type: "one_off",
|
||||
// items: [addOnMessages],
|
||||
// });
|
||||
|
||||
const testCase = "usageLimit3";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing usage limits for prepaid`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
const { autumnJs } = this;
|
||||
db = this.db;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
stripeCli = this.stripeCli;
|
||||
|
||||
addPrefixToProducts({
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
products: [pro],
|
||||
customerId,
|
||||
db,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomer({
|
||||
autumn: autumnJs,
|
||||
customerId,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
it("should attach pro product with quantity exceeding usage limit and get an error", async () => {
|
||||
expectAutumnError({
|
||||
errCode: ErrCode.InvalidOptions,
|
||||
func: async () => {
|
||||
return await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
options: [
|
||||
{
|
||||
feature_id: TestFeature.Messages,
|
||||
quantity: 600,
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
it("should attach pro product and update quantity with quantity exceeding usage limit and get an error", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
options: [
|
||||
{
|
||||
feature_id: TestFeature.Messages,
|
||||
quantity: 100,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expectAutumnError({
|
||||
errCode: ErrCode.InvalidOptions,
|
||||
func: async () => {
|
||||
return await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
options: [
|
||||
{
|
||||
feature_id: TestFeature.Messages,
|
||||
quantity: 600,
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
129
server/tests/advanced/usageLimit/usageLimit3.test.ts
Normal file
129
server/tests/advanced/usageLimit/usageLimit3.test.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import {
|
||||
ErrCode,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectAutumnError } from "tests/utils/expectUtils/expectErrUtils.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructPrepaidItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
|
||||
const messageItem = constructPrepaidItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 50,
|
||||
billingUnits: 100,
|
||||
price: 8,
|
||||
usageLimit: 500,
|
||||
}) as LimitedItem;
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [messageItem],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
// const addOnMessages = constructFeatureItem({
|
||||
// featureId: TestFeature.Messages,
|
||||
// interval: null,
|
||||
// includedUsage: 250,
|
||||
// }) as LimitedItem;
|
||||
|
||||
// const messageAddOn = constructProduct({
|
||||
// type: "one_off",
|
||||
// items: [addOnMessages],
|
||||
// });
|
||||
|
||||
const testCase = "usageLimit3";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing usage limits for prepaid`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
test("should attach pro product with quantity exceeding usage limit and get an error", async () => {
|
||||
expectAutumnError({
|
||||
errCode: ErrCode.InvalidOptions,
|
||||
func: async () => {
|
||||
return await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
options: [
|
||||
{
|
||||
feature_id: TestFeature.Messages,
|
||||
quantity: 600,
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
test("should attach pro product and update quantity with quantity exceeding usage limit and get an error", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
options: [
|
||||
{
|
||||
feature_id: TestFeature.Messages,
|
||||
quantity: 100,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expectAutumnError({
|
||||
errCode: ErrCode.InvalidOptions,
|
||||
func: async () => {
|
||||
return await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
options: [
|
||||
{
|
||||
feature_id: TestFeature.Messages,
|
||||
quantity: 600,
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
93
server/tests/advanced/usageLimit/usageLimit4.test.ts
Normal file
93
server/tests/advanced/usageLimit/usageLimit4.test.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import {
|
||||
ErrCode,
|
||||
LegacyVersion,
|
||||
type LimitedItem,
|
||||
} from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "tests/utils/testInitUtils/createTestContext.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectAutumnError } from "tests/utils/expectUtils/expectErrUtils.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructArrearProratedItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
|
||||
const messageItem = constructArrearProratedItem({
|
||||
featureId: TestFeature.Users,
|
||||
includedUsage: 1,
|
||||
pricePerUnit: 10,
|
||||
usageLimit: 3,
|
||||
}) as LimitedItem;
|
||||
|
||||
export const pro = constructProduct({
|
||||
items: [messageItem],
|
||||
type: "pro",
|
||||
});
|
||||
|
||||
const testCase = "usageLimit4";
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Testing usage limits for cont use item`)}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 });
|
||||
let testClockId: string;
|
||||
let stripeCli: Stripe;
|
||||
|
||||
const curUnix = new Date().getTime();
|
||||
|
||||
beforeAll(async () => {
|
||||
stripeCli = ctx.stripeCli;
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
customerId,
|
||||
});
|
||||
|
||||
const { testClockId: testClockId1 } = await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
customerData: {},
|
||||
attachPm: "success",
|
||||
withTestClock: true,
|
||||
});
|
||||
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
test("should attach pro product with quantity exceeding usage limit and get an error", async () => {
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
product: pro,
|
||||
stripeCli: ctx.stripeCli,
|
||||
db: ctx.db,
|
||||
org: ctx.org,
|
||||
env: ctx.env,
|
||||
});
|
||||
});
|
||||
test("should attach pro product and update quantity with quantity exceeding usage limit and get an error", async () => {
|
||||
await expectAutumnError({
|
||||
errCode: ErrCode.InvalidInputs,
|
||||
func: async () => {
|
||||
await autumn.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Users,
|
||||
value: messageItem.usage_limit! + 1,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const check = await autumn.check({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Users,
|
||||
});
|
||||
|
||||
expect(check.balance).toBe(0);
|
||||
expect(check.allowed).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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!;
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -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!;
|
||||
});
|
||||
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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!;
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -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!;
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -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!;
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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!;
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -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!;
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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!;
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user