restructured tests

This commit is contained in:
John Yeo
2025-06-10 19:54:05 +01:00
parent 519752ff44
commit ff7f46d273
127 changed files with 4980 additions and 3443 deletions

View File

@@ -3,8 +3,11 @@
# npx tsx scripts/alex.ts
filename=$1
# Check if the file path contains "/tests/"
if [[ $filename == *"/tests/"* ]]; then
# Check if the file path contains "shell"
if [[ $filename == *"shell"* ]]; then
$filename
elif [[ $filename == *"/tests/"* ]]; then
# Extract everything after "/tests/"
path_after_tests=$(echo "$filename" | sed 's/.*\/tests\///')
# Remove .ts extension if present

4
server/shell/config.sh Normal file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
MOCHA_SETUP="npx mocha tests/00_setup.ts"
MOCHA_CMD="npx mocha --parallel --timeout 10000000 --ignore tests/00_setup.ts"

10
server/shell/g1.sh Executable file
View File

@@ -0,0 +1,10 @@
#!/bin/bash
# Source shared configuration
source "$(dirname "$0")/config.sh"
MOCHA_PARALLEL=true $MOCHA_SETUP \
&& $MOCHA_CMD \
'tests/attach/basic/*.ts' \
'tests/attach/upgrade/*.ts' \
'tests/attach/downgrade/*.ts'

14
server/shell/g2.sh Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/bash
# Source shared configuration
source "$(dirname "$0")/config.sh"
MOCHA_PARALLEL=true $MOCHA_SETUP && $MOCHA_CMD \
'tests/attach/upgradeOld/*.ts' \
'tests/attach/entities/*.ts' \
'tests/attach/migrations/*.ts' \
'tests/attach/newVersion/*.ts' \
'tests/attach/others/*.ts' \
'tests/attach/updateEnts/*.ts' \

12
server/shell/g3.sh Executable file
View File

@@ -0,0 +1,12 @@
#!/bin/bash
# Source shared configuration
source "$(dirname "$0")/config.sh"
MOCHA_PARALLEL=true $MOCHA_SETUP
$MOCHA_CMD 'tests/contUse/entities/*.ts'
$MOCHA_CMD 'tests/contUse/update/*.ts'
$MOCHA_CMD 'tests/contUse/track/*.ts'

16
server/shell/g4.sh Executable file
View File

@@ -0,0 +1,16 @@
#!/bin/bash
# Source shared configuration
source "$(dirname "$0")/config.sh"
MOCHA_PARALLEL=true $MOCHA_SETUP
$MOCHA_CMD 'tests/advanced/multiFeature/*.ts' \
'tests/advanced/coupons/*.ts' \
'tests/attach/updateQuantity/*.ts' \
'tests/advanced/referrals/*.ts'
# $MOCHA_CMD 'tests/attach/multiProduct/*.ts'
# $MOCHA_CMD 'tests/advanced/usage/*.ts'

4
server/shell/g5.sh Executable file
View File

@@ -0,0 +1,4 @@
MOCHA_PARALLEL=true npx mocha 'tests/alex/00_setup.ts' && npx mocha --parallel --timeout 10000000 \
'tests/alex/01_free.ts' 'tests/alex/02_pro.ts' 'tests/alex/03_premium.ts' \
'tests/alex/04_topups.ts' 'tests/alex/05_cancel.ts' 'tests/alex/06_switch.ts' \
--ignore 'tests/alex/00_setup.ts'

View File

@@ -7,11 +7,13 @@ import {
CusExpand,
EntityExpand,
ErrCode,
Invoice,
} from "@autumn/shared";
import {
CancelParams,
CheckParams,
CheckResult,
Customer,
TrackParams,
UsageParams,
} from "autumn-js";
@@ -214,7 +216,11 @@ export class AutumnInt {
params?: {
expand?: CusExpand[];
},
) => {
): Promise<
Customer & {
invoices: any[];
}
> => {
const queryParams = new URLSearchParams();
const defaultParams = {
expand: [CusExpand.Invoices],
@@ -335,6 +341,11 @@ export class AutumnInt {
const data = await this.post(`/rewards`, reward);
return data;
},
delete: async (rewardId: string) => {
const data = await this.delete(`/rewards/${rewardId}`);
return data;
},
};
rewardPrograms = {
@@ -422,7 +433,7 @@ export class AutumnInt {
return data;
};
cancel = async (params: CancelParams) => {
cancel = async (params: CancelParams & { expire_immediately?: boolean }) => {
const data = await this.post(`/cancel`, params);
return data;
};

View File

@@ -60,6 +60,8 @@ export const priceToUsageInAdvance = ({
let optionsQuantity = options?.quantity;
let finalQuantity = optionsQuantity;
console.log("options", options);
// 1. If adjustable quantity is set, use that, else if quantity is undefined, adjustable is true, else false
let adjustable = !nullish(options?.adjustable_quantity)
? options!.adjustable_quantity

View File

@@ -7,7 +7,7 @@ import { StatusCodes } from "http-status-codes";
export const createStripeProduct = async (
org: Organization,
env: AppEnv,
product: Product
product: Product,
) => {
try {
const stripe = createStripeCli({ org, env });
@@ -33,7 +33,7 @@ export const createStripeProduct = async (
export const deleteStripeProduct = async (
org: Organization,
env: AppEnv,
product: Product
product: Product,
) => {
const stripe = createStripeCli({ org, env });
@@ -85,14 +85,14 @@ export const deactivateStripeMeters = async ({
}
}
const batchSize = 10;
const batchSize = 40;
for (let i = 0; i < allStripeMeters.length; i += batchSize) {
const batch = allStripeMeters.slice(i, i + batchSize);
await Promise.all(
batch.map((meter) => stripeCli.billing.meters.deactivate(meter.id))
batch.map((meter) => stripeCli.billing.meters.deactivate(meter.id)),
);
console.log(
`Deactivated ${i + batch.length}/${allStripeMeters.length} meters`
`Deactivated ${i + batch.length}/${allStripeMeters.length} meters`,
);
await new Promise((resolve) => setTimeout(resolve, 1000));
}
@@ -125,7 +125,7 @@ export const deleteAllStripeProducts = async ({
});
}
let batchSize = 10;
let batchSize = 50;
for (let i = 0; i < stripeProducts.data.length; i += batchSize) {
let batch = stripeProducts.data.slice(i, i + batchSize);
await Promise.all(
@@ -137,10 +137,10 @@ export const deleteAllStripeProducts = async ({
active: false,
});
}
})
}),
);
console.log(
`Deleted ${i + batch.length}/${stripeProducts.data.length} products`
`Deleted ${i + batch.length}/${stripeProducts.data.length} products`,
);
await new Promise((resolve) => setTimeout(resolve, 1000));
}

View File

@@ -40,6 +40,10 @@ export const handleCusProductDeleted = async ({
prematurelyCanceled: boolean;
}) => {
const { org, env } = req;
// const customerId = cusProduct.customer!.id;
// const orgId = org.id;
// const lockKey = `attach_${customerId}_${orgId}_${env}`;
const { scheduled_ids } = cusProduct;
const customer = await CusService.getFull({
@@ -84,6 +88,7 @@ export const handleCusProductDeleted = async ({
}
if (cusProduct.status === CusProductStatus.Expired) {
// When attaching eg. main is trial, canceled in attach function, don't handle...
return;
}

View File

@@ -1,19 +1,13 @@
import { AttachScenario } from "@autumn/shared";
import { addProductsUpdatedWebhookTask } from "@/internal/analytics/handlers/handleProductsUpdated.js";
import { CusProductStatus, FullCusProduct } from "@autumn/shared";
import Stripe from "stripe";
import { AttachScenario } from "@autumn/shared";
import { addProductsUpdatedWebhookTask } from "@/internal/analytics/handlers/handleProductsUpdated.js";
import { CusProductStatus, FullCusProduct } from "@autumn/shared";
import { formatUnixToDateTime, nullish } from "@/utils/genUtils.js";
import { createFullCusProduct } from "@/internal/customers/add-product/createFullCusProduct.js";
import { CusService } from "@/internal/customers/CusService.js";
import { ProductService } from "@/internal/products/ProductService.js";
import { ExtendedRequest } from "@/utils/models/Request.js";
import {
webhookToAttachParams,
webhookToInsertParams,
} from "../../webhookUtils/webhookUtils.js";
import { attachToInsertParams } from "@/internal/products/productUtils.js";
import { productToInsertParams } from "@/internal/customers/attach/attachUtils/attachParams/convertToParams.js";
export const handleSubCanceled = async ({
req,
@@ -36,7 +30,7 @@ export const handleSubCanceled = async ({
const canceledFromPortal = isCanceled && !isAutumnDowngrade;
const { db, org, env, features, logtail: logger } = req;
const { db, org, env, logtail: logger } = req;
if (!canceledFromPortal || updatedCusProducts.length == 0) {
return;
@@ -86,10 +80,10 @@ export const handleSubCanceled = async ({
continue;
}
let insertParams = webhookToInsertParams({
let insertParams = productToInsertParams({
req,
cusProduct: updatedCusProducts[0],
fullCus,
newProduct: product,
entities,
});

View File

@@ -48,35 +48,3 @@ export const webhookToAttachParams = ({
return params;
};
export const webhookToInsertParams = ({
req,
cusProduct,
fullCus,
entities,
}: {
req: ExtendedRequest;
cusProduct: FullCusProduct;
fullCus: FullCustomer;
entities?: Entity[];
}): InsertCusProductParams => {
const fullProduct = cusProductToProduct({ cusProduct });
const params: InsertCusProductParams = {
customer: fullCus,
org: req.org,
product: fullProduct,
prices: cusProductToPrices({ cusProduct }),
entitlements: cusProductToEnts({ cusProduct }),
features: req.features,
freeTrial: cusProduct.free_trial || null,
optionsList: cusProduct.options,
cusProducts: [cusProduct],
internalEntityId: cusProduct.internal_entity_id || undefined,
entities: entities || [],
replaceables: [],
};
return params;
};

View File

@@ -55,7 +55,7 @@ export class EventService {
event_name: events.event_name,
value: events.value,
created_at: events.created_at,
// timestamp: events.timestamp,
timestamp: events.timestamp,
idempotency_key: events.idempotency_key,
properties: events.properties,
set_usage: events.set_usage,

View File

@@ -44,12 +44,16 @@ productApiRouter.post("/all/init_stripe", async (req: any, res) => {
OrgService.getFromReq(req),
]);
console.log(
"fullProducts",
fullProducts.map((p) => p.id),
);
const stripeCli = createStripeCli({
org,
env,
});
const batchProductInit: Promise<any>[] = [];
const productBatchSize = 5;
for (let i = 0; i < fullProducts.length; i += productBatchSize) {
const batch = fullProducts.slice(i, i + productBatchSize);

View File

@@ -1,17 +1,14 @@
import { createStripeCli } from "@/external/stripe/utils.js";
import { pricesContainRecurring } from "@/internal/products/prices/priceUtils.js";
import { createCheckoutMetadata } from "@/internal/metadata/metadataUtils.js";
import RecaseError from "@/utils/errorUtils.js";
import {
AttachParams,
AttachResultSchema,
} from "../cusProducts/AttachParams.js";
import { createStripeCli } from "@/external/stripe/utils.js";
import { pricesContainRecurring } from "@/internal/products/prices/priceUtils.js";
import { createCheckoutMetadata } from "@/internal/metadata/metadataUtils.js";
import { freeTrialToStripeTimestamp } from "@/internal/products/free-trials/freeTrialUtils.js";
import { getStripeSubItems } from "@/external/stripe/stripeSubUtils/getStripeSubItems.js";
import { ErrCode } from "@/errors/errCodes.js";
import RecaseError from "@/utils/errorUtils.js";
import { getNextStartOfMonthUnix } from "@/internal/products/prices/billingIntervalUtils.js";
import { APIVersion } from "@autumn/shared";
import { SuccessCode } from "@autumn/shared";
@@ -49,6 +46,7 @@ export const handleCreateCheckout = async ({
// Handle first item set
const { items } = itemSets[0];
attachParams.itemSets = itemSets;
const isRecurring = pricesContainRecurring(attachParams.prices);

View File

@@ -29,6 +29,7 @@ export const handleAddProduct = async ({
const defaultConfig: AttachConfig = getDefaultAttachConfig();
// 1. If paid product
if (prices.length > 0) {
await handlePaidProduct({
req,

View File

@@ -1,3 +1,4 @@
import Stripe from "stripe";
import { getStripeSubItems } from "@/external/stripe/stripeSubUtils/getStripeSubItems.js";
import { cancelCurSubs } from "@/internal/customers/change-product/handleDowngrade/cancelCurSubs.js";
import { updateScheduledSubWithNewItems } from "@/internal/customers/change-product/scheduleUtils/updateScheduleWithNewItems.js";
@@ -13,9 +14,7 @@ import {
isFreeProduct,
} from "@/internal/products/productUtils.js";
import { createFullCusProduct } from "@/internal/customers/add-product/createFullCusProduct.js";
import { cusProductsToSchedules } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js";
import Stripe from "stripe";
import { attachParamToCusProducts } from "../../attachUtils/convertAttachParams.js";
import { getStripeSubs } from "@/external/stripe/stripeSubUtils.js";

View File

@@ -71,10 +71,12 @@ export const scheduleStripeSub = async ({
export const updateOtherCusProdsWithNewSchedule = async ({
db,
attachParams,
newSchedule,
otherSub,
}: {
db: DrizzleCli;
attachParams: AttachParams;
newSchedule: Stripe.SubscriptionSchedule;
otherSub: Stripe.Subscription;
}) => {
const otherCusProducts = getCusProductsWithStripeSubId({
@@ -86,7 +88,7 @@ export const updateOtherCusProdsWithNewSchedule = async ({
for (const otherCusProduct of otherCusProducts) {
let newScheduledIds = [
...(otherCusProduct.scheduled_ids || []),
otherSub.id,
newSchedule.id,
];
await CusProductService.update({
@@ -138,6 +140,7 @@ export const handleNewScheduleForItemSet = async ({
await updateOtherCusProdsWithNewSchedule({
db,
attachParams,
newSchedule: stripeSchedule,
otherSub,
});

View File

@@ -198,10 +198,7 @@ export const createStripePrices = async ({
req: any;
logger: any;
}) => {
const { prices, entitlements, products, org, internalEntityId } =
attachParams;
const stripeCli = createStripeCli({ org, env: attachParams.customer.env });
const { prices, entitlements, products, org, stripeCli } = attachParams;
const batchPriceUpdates = [];
for (const price of prices) {

View File

@@ -56,31 +56,28 @@ export const webhookToAttachParams = ({
return params;
};
export const webhookToInsertParams = ({
export const productToInsertParams = ({
req,
cusProduct,
fullCus,
newProduct,
entities,
}: {
req: ExtendedRequest;
cusProduct: FullCusProduct;
fullCus: FullCustomer;
newProduct: FullProduct;
entities?: Entity[];
}): InsertCusProductParams => {
const fullProduct = cusProductToProduct({ cusProduct });
const params: InsertCusProductParams = {
customer: fullCus,
org: req.org,
product: fullProduct,
prices: cusProductToPrices({ cusProduct }),
entitlements: cusProductToEnts({ cusProduct }),
product: newProduct,
prices: newProduct.prices,
entitlements: newProduct.entitlements,
features: req.features,
freeTrial: cusProduct.free_trial || null,
optionsList: cusProduct.options,
cusProducts: [cusProduct],
internalEntityId: cusProduct.internal_entity_id || undefined,
cusProducts: fullCus.customer_products,
freeTrial: null,
optionsList: [],
internalEntityId: undefined,
entities: entities || [],
replaceables: [],
};

View File

@@ -28,6 +28,7 @@ import { getEntsWithFeature } from "@/internal/products/entitlements/entitlement
import { isMainProduct } from "@/internal/products/productUtils/classifyProduct.js";
import { createStripeCli } from "@/external/stripe/utils.js";
import { getStripeCusData } from "./attachParamsUtils/getStripeCusData.js";
import { isOneOff } from "@/internal/products/productUtils.js";
const getProductsForAttach = async ({
req,
@@ -60,7 +61,8 @@ const getProductsForAttach = async ({
let otherProd = products.find(
(p) => p.group === prod.group && !p.is_add_on && p.id !== prod.id,
);
if (otherProd && !otherProd.is_add_on) {
if (otherProd && !otherProd.is_add_on && !isOneOff(prod.prices)) {
throw new RecaseError({
message:
"Can't attach multiple products from the same group that are not add-ons",
@@ -145,8 +147,7 @@ const getPricesAndEnts = async ({
optionsInput,
features,
prices,
// to check if it fails for multi prod attach...
curCusProduct: prodIsMain ? curMainProduct : curSameProduct,
curCusProduct: curMainProduct,
}),
prices,
entitlements,
@@ -205,7 +206,7 @@ const getPricesAndEnts = async ({
optionsInput,
features,
prices,
curCusProduct: prodIsMain ? curMainProduct : curSameProduct,
curCusProduct: curMainProduct,
}),
prices,
entitlements: getEntsWithFeature({

View File

@@ -229,21 +229,34 @@ const getChangeProductBranch = async ({
return AttachBranch.MainIsFree;
}
if (isTrialing(curMainProduct!)) {
if (isFreeProduct(attachParams.prices)) {
return AttachBranch.Downgrade;
}
return AttachBranch.MainIsTrial;
}
// 2. If main product is paid, check if upgrade or downgrade
// Check if upgrade or downgrade
let curPrices = cusProductToPrices({ cusProduct: curMainProduct! });
let newPrices = attachParams.prices;
// if (isTrialing(curMainProduct!)) {
// if (isFreeProduct(attachParams.prices)) {
// return AttachBranch.Downgrade;
// }
// let isUpgrade = isProductUpgrade({
// prices1: curPrices,
// prices2: newPrices,
// });
// if (!isUpgrade) {
// return AttachBranch.Downgrade;
// }
// return AttachBranch.MainIsTrial;
// }
let isUpgrade = isProductUpgrade({ prices1: curPrices, prices2: newPrices });
if (isUpgrade) {
if (isTrialing(curMainProduct!)) {
return AttachBranch.MainIsTrial;
}
return AttachBranch.Upgrade;
}

View File

@@ -8,6 +8,7 @@ import { ProrationBehavior } from "@autumn/shared";
import { attachParamsToProduct } from "./convertAttachParams.js";
import { attachParamToCusProducts } from "./convertAttachParams.js";
import { cusProductToPrices } from "../../cusProducts/cusProductUtils/convertCusProduct.js";
import { insertInvoiceFromAttach } from "@/internal/invoices/invoiceUtils.js";
export const intervalsAreSame = ({
attachParams,
@@ -80,15 +81,21 @@ export const getAttachConfig = async ({
let sameIntervals = intervalsAreSame({ attachParams });
let disableMerge =
branch == AttachBranch.MainIsTrial ||
org.config.merge_billing_cycles === false;
const onlyCheckout =
isPublic || forceCheckout || (noPaymentMethod && !invoiceOnly && !isFree);
let config: AttachConfig = {
branch,
onlyCheckout:
(isPublic || forceCheckout || noPaymentMethod) && !invoiceOnly && !isFree,
onlyCheckout,
carryUsage,
proration,
disableTrial,
invoiceOnly: flags.invoiceOnly,
disableMerge: org.config.merge_billing_cycles === false,
disableMerge,
sameIntervals,
carryTrial,
};

View File

@@ -3,7 +3,7 @@ import {
AttachParams,
AttachResultSchema,
} from "../../cusProducts/AttachParams.js";
import { AttachBranch, AttachFunction } from "@autumn/shared";
import { AttachBranch, AttachFunction, CusProductStatus } from "@autumn/shared";
import { handleUpgradeDiffInterval } from "../attachFunctions/upgradeDiffIntFlow/handleUpgradeDiffInt.js";
import { handleCreateCheckout } from "../../add-product/handleCreateCheckout.js";
import { handleAddProduct } from "../attachFunctions/addProductFlow/handleAddProduct.js";
@@ -16,6 +16,7 @@ import { attachParamToCusProducts } from "./convertAttachParams.js";
import { deleteCurrentScheduledProduct } from "./deleteCurrentScheduledProduct.js";
import { handleOneOffFunction } from "../attachFunctions/addProductFlow/handleOneOffFunction.js";
import { handleUpgradeSameInterval } from "../attachFunctions/upgradeSameIntFlow/handleUpgradeSameInt.js";
import { CusProductService } from "../../cusProducts/CusProductService.js";
/*
1. If from new version, free trial should just carry over
@@ -103,7 +104,7 @@ export const runAttachFunction = async ({
attachBody: AttachBody;
config: AttachConfig;
}) => {
const { logtail: logger } = req;
const { logtail: logger, db } = req;
const { stripeCli } = attachParams;
const attachFunction = await getAttachFunction({
@@ -160,8 +161,20 @@ export const runAttachFunction = async ({
// 2. If main is trial, cancel it...
if (branch == AttachBranch.MainIsTrial) {
await CusProductService.update({
db,
cusProductId: curMainProduct!.id,
updates: {
status: CusProductStatus.Expired,
},
});
for (const subId of curMainProduct?.subscription_ids || []) {
await stripeCli.subscriptions.cancel(subId);
await stripeCli.subscriptions.cancel(subId, {
cancellation_details: {
comment: "autumn_downgrade",
},
});
}
}

View File

@@ -2,12 +2,7 @@ import RecaseError from "@/utils/errorUtils.js";
import { ErrCode } from "@/errors/errCodes.js";
import { StatusCodes } from "http-status-codes";
import { AttachParams } from "../../cusProducts/AttachParams.js";
import {
AttachBranch,
AttachErrCode,
entitlements,
UsagePriceConfig,
} from "@autumn/shared";
import { AttachBranch, AttachErrCode, UsagePriceConfig } from "@autumn/shared";
import { AttachBody } from "../models/AttachBody.js";
import { AttachConfig, AttachFlags } from "../models/AttachFlags.js";
import {
@@ -233,7 +228,7 @@ export const handleAttachErrors = async ({
useCheckout: onlyCheckout,
});
await handleUpdateQuantityErrors({
attachParams,
});
// await handleUpdateQuantityErrors({
// attachParams,
// });
};

View File

@@ -15,6 +15,7 @@ import {
AttachBranch,
BillingInterval,
FreeTrial,
PreviewLineItem,
Price,
UsageModel,
} from "@autumn/shared";
@@ -179,12 +180,23 @@ export const getUpgradeProductPreview = async ({
);
}
return {
currency: attachParams.org.default_currency,
due_today: {
let dueToday:
| {
line_items: PreviewLineItem[];
total: number;
}
| undefined = {
line_items: items,
total: dueTodayAmt,
},
};
if (branch == AttachBranch.SameCustomEnts) {
dueToday = undefined;
}
return {
currency: attachParams.org.default_currency,
due_today: dueToday,
due_next_cycle: {
line_items: nextCycleItems,
due_at: nextCycleAt.next_cycle_at,

View File

@@ -3,14 +3,11 @@ import {
AppEnv,
AttachScenario,
CusProductResponseSchema,
CusProductSchema,
CusProductStatus,
Customer,
Entity,
FixedPriceConfig,
FullCusProduct,
FullCustomerEntitlement,
FullCustomerPrice,
Organization,
PriceType,
Subscription,
@@ -31,7 +28,6 @@ import {
getStripeSubs,
subIsPrematurelyCanceled,
} from "@/external/stripe/stripeSubUtils.js";
import { sortCusEntsForDeduction } from "./cusEnts/cusEntUtils.js";
import { getRelatedCusEnt } from "./cusPrices/cusPriceUtils.js";
import { notNullish } from "@/utils/genUtils.js";
import { BREAK_API_VERSION } from "@/utils/constants.js";
@@ -39,7 +35,6 @@ import { addProductsUpdatedWebhookTask } from "@/internal/analytics/handlers/han
import { DrizzleCli } from "@/db/initDrizzle.js";
import { getExistingCusProducts } from "./cusProductUtils/getExistingCusProducts.js";
import { ExtendedRequest } from "@/utils/models/Request.js";
import { webhookToInsertParams } from "@/external/stripe/webhookUtils/webhookUtils.js";
export const isActiveStatus = (status: CusProductStatus) => {
return (

View File

@@ -0,0 +1,22 @@
import { DrizzleCli } from "@/db/initDrizzle.js";
import { CusProductStatus, FullCusProduct } from "@autumn/shared";
import { CusProductService } from "../CusProductService.js";
export const findCusProductById = async ({
db,
internalCustomerId,
productId,
}: {
db: DrizzleCli;
internalCustomerId: string;
productId: string;
}) => {
let cusProducts = await CusProductService.list({
db,
internalCustomerId,
});
return cusProducts.find(
(cusProduct: FullCusProduct) => cusProduct.product.id === productId,
);
};

View File

@@ -13,7 +13,6 @@ import {
ErrCode,
BillingInterval,
AttachScenario,
AttachBranch,
} from "@autumn/shared";
import { AppEnv, Customer } from "@autumn/shared";
import { createFullCusProduct } from "../add-product/createFullCusProduct.js";
@@ -21,8 +20,7 @@ import { handleAddProduct } from "../attach/attachFunctions/addProductFlow/handl
import { CusService } from "../CusService.js";
import { initStripeCusAndProducts } from "../handlers/handleCreateCustomer.js";
import { generateId } from "@/utils/genUtils.js";
import { newCusToFullCus } from "./cusUtils.js";
import { webhookToInsertParams } from "@/external/stripe/webhookUtils/webhookUtils.js";
import {
newCusToAttachParams,
newCusToInsertParams,

View File

@@ -323,7 +323,7 @@ export class ProductService {
internalId: string;
update: any;
}) {
const data = await db
await db
.update(products)
.set(update)
.where(eq(products.internal_id, internalId));

View File

@@ -227,8 +227,6 @@ export const handleNewProductItems = async ({
features,
});
console.log("Updated price", updatedPrice);
if (newPrice) {
newPrices.push(newPrice);
}

View File

@@ -220,7 +220,6 @@ export const toFeatureAndPrice = ({
on_increase: onIncrease,
on_decrease: onDecrease,
};
console.log("Proration config", prorationConfig);
}
let price: Price = {

View File

@@ -239,6 +239,7 @@ export const checkStripeProductExists = async ({
let stripeProduct = await stripeCli.products.retrieve(
product.processor!.id,
);
if (!stripeProduct.active) {
createNew = true;
}
@@ -261,6 +262,10 @@ export const checkStripeProductExists = async ({
},
});
console.log(
`Updated product ${product.name} with stripe product ${stripeProduct.id}`,
);
product.processor = {
id: stripeProduct.id,
type: ProcessorType.Stripe,

View File

@@ -61,13 +61,25 @@ export const initFeature = ({
}
};
// enum TestItemType {
// Prepaid = "prepaid",
// Arrear = "arrear",
// ArrearProrated = "arrear_prorated",
// FixedPrice = "fixed_price",
// }
export const constructRawProduct = ({
id,
isAddOn = false,
items,
}: {
id: string;
isAddOn?: boolean;
items: ProductItem[];
}) => {
return {
id,
name: keyToTitle(id),
items,
is_add_on: isAddOn,
is_default: false,
version: 1,
group: "",
};
};
export const constructProduct = ({
id,
items,

View File

@@ -90,12 +90,13 @@ export const initCustomer = async ({
})) as Customer;
const stripeCli = createStripeCli({ org: org, env: env });
// if (withTestClock) {
let testClockId = "";
if (withTestClock) {
const testClock = await stripeCli.testHelpers.testClocks.create({
frozen_time: Math.floor(Date.now() / 1000),
});
testClockId = testClock.id;
// }
}
if (attachPm) {
await attachPmToCus({

View File

@@ -7,6 +7,7 @@ import {
format,
} from "date-fns";
import { Stripe } from "stripe";
import { timeout } from "../genUtils.js";
export const getStripeNow = async ({
stripeCli,
@@ -57,6 +58,7 @@ export const advanceTestClock = async ({
numberOfHours,
numberOfMonths,
advanceTo,
waitForSeconds,
}: {
stripeCli: Stripe;
testClockId: string;
@@ -66,6 +68,7 @@ export const advanceTestClock = async ({
numberOfHours?: number;
numberOfMonths?: number;
advanceTo?: number;
waitForSeconds?: number;
}) => {
if (!startingFrom) {
startingFrom = new Date();
@@ -96,6 +99,10 @@ export const advanceTestClock = async ({
frozen_time: Math.floor(advanceTo / 1000),
});
if (waitForSeconds) {
await timeout(waitForSeconds * 1000);
}
return advanceTo;
// await timeout(

View File

@@ -2,25 +2,53 @@
MOCHA_SETUP="npx mocha tests/00_setup.ts"
MOCHA_CMD="npx mocha --parallel --timeout 10000000 --ignore tests/00_setup.ts"
# TEST PARALLEL
if [ "$1" == "basic-parallel" ]; then
MOCHA_PARALLEL=true $MOCHA_SETUP && $MOCHA_CMD \
tests/basic/*.ts \
tests/basic/multi-feature/*.ts \
tests/basic/entities/*.ts \
# && $MOCHA_CMD \
# 'tests/basic/referrals/*.ts' 'tests/attach/**/*.ts' \
# 'tests/basic/referrals/*.ts' 'tests/attach/**/*.ts' \
elif [ "$1" == "advanced-parallel" ]; then
MOCHA_PARALLEL=true \
# Group 1
if [ "$1" == "group1" ]; then
$MOCHA_SETUP \
&& $MOCHA_CMD 'tests/advanced/usage/*.ts' \
&& $MOCHA_CMD 'tests/advanced/arrear_prorated/*.ts' 'tests/advanced/coupons/*.ts'\
# && $MOCHA_CMD 'tests/advanced/coupons/*.ts'\
&& $MOCHA_CMD \
'tests/attach/basic/*.ts' \
'tests/attach/upgrade/*.ts' \
'tests/attach/downgrade/*.ts'
fi
# Group 2
if [ "$1" == "g2" ]; then
$MOCHA_SETUP && $MOCHA_CMD \
'tests/attach/upgradeOld/*.ts' \
'tests/attach/entities/*.ts' \
'tests/attach/migrations/*.ts' \
'tests/attach/newVersion/*.ts' \
'tests/attach/others/*.ts' \
'tests/attach/updateEnts/*.ts' \
exit 0
fi
if [ "$1" == "g3" ]; then
$MOCHA_SETUP \
&& $MOCHA_CMD 'tests/contUse/entities/*.ts' \
&& $MOCHA_CMD 'tests/contUse/update/*.ts'\
&& $MOCHA_CMD 'tests/contUse/track/*.ts' \
exit 0
fi
if [ "$1" == "g4" ]; then
$MOCHA_SETUP && $MOCHA_CMD \
'tests/attach/updateQuantity/*.ts' \
'tests/attach/multiProduct/*.ts' \
'tests/advanced/multiFeature/*.ts' \
'tests/advanced/referrals/*.ts' \
'tests/advanced/coupons/*.ts'
fi
# Group 4
if [ "$1" == "g4" ]; then
$MOCHA_SETUP && $MOCHA_CMD \
'tests/advanced/usage/*.ts'
fi
elif [ "$1" == "alex-parallel" ]; then
if [ "$1" == "alex-parallel" ]; then
MOCHA_PARALLEL=true npx mocha 'tests/alex/00_setup.ts' && npx mocha --parallel --timeout 10000000 \
'tests/alex/01_free.ts' 'tests/alex/02_pro.ts' 'tests/alex/03_premium.ts' \
'tests/alex/04_topups.ts' 'tests/alex/05_cancel.ts' 'tests/alex/06_switch.ts' \
@@ -54,3 +82,39 @@ fi
# # TEST PARALLEL
# if [ "$1" == "basic-parallel" ]; then
# MOCHA_PARALLEL=true $MOCHA_SETUP \
# && $MOCHA_CMD \
# 'tests/attach/basic/*.ts' \
# 'tests/attach/upgrade/*.ts' \
# 'tests/attach/downgrade/*.ts' \
# && $MOCHA_CMD \
# 'tests/attach/upgradeOld/*.ts' \
# 'tests/attach/entities/*.ts' \
# 'tests/attach/migrations/*.ts' \
# 'tests/attach/multiProduct/*.ts' \
# 'tests/attach/newVersion/*.ts' \
# 'tests/attach/others/*.ts' \
# 'tests/attach/updateEnts/*.ts' \
# 'tests/attach/updateQuantity/*.ts' \
# 'tests/contUse/entities/*.ts' \
# 'tests/contUse/track/*.ts' \
# 'tests/contUse/update/*.ts' \
# && $MOCHA_CMD \
# 'tests/advanced/multiFeature/*.ts' \
# 'tests/advanced/referrals/*.ts' \
# 'tests/advanced/coupons/*.ts' \
# elif [ "$1" == "advanced-parallel" ]; then
# MOCHA_PARALLEL=true \
# $MOCHA_SETUP \
# && $MOCHA_CMD 'tests/advanced/arrear_prorated/*.ts' 'tests/advanced/coupons/*.ts'\
# # && $MOCHA_CMD 'tests/advanced/usage/*.ts' \
# # && $MOCHA_CMD 'tests/advanced/coupons/*.ts'\

View File

@@ -1,13 +1,12 @@
import chalk from "chalk";
import Stripe from "stripe";
import { Customer } from "@autumn/shared";
import { APIVersion, AppEnv, Customer, Organization } from "@autumn/shared";
import { createStripeCli } from "@/external/stripe/utils.js";
import { getOriginalCouponId } from "@/internal/rewards/rewardUtils.js";
import { getPriceForOverage } from "@/internal/products/prices/priceUtils.js";
import { expect } from "chai";
import { addHours, addMonths } from "date-fns";
import { features, products, rewards } from "tests/global.js";
import { initCustomerWithTestClock } from "tests/utils/testInitUtils.js";
import { getFixedPriceAmount, timeout } from "tests/utils/genUtils.js";
import { AutumnCli } from "tests/cli/AutumnCli.js";
import { compareMainProduct } from "tests/utils/compare.js";
@@ -18,42 +17,161 @@ import {
completeCheckoutForm,
getDiscount,
} from "tests/utils/stripeUtils.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { setupBefore } from "tests/before.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
import { TestFeature } from "tests/setup/v2Features.js";
import {
addPrefixToProducts,
getBasePrice,
} from "tests/utils/testProductUtils/testProductUtils.js";
import { createProducts } from "tests/utils/productUtils.js";
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
import { DrizzleCli } from "@/db/initDrizzle.js";
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
import { getExpectedInvoiceTotal } from "tests/utils/expectUtils/expectInvoiceUtils.js";
import { hoursToFinalizeInvoice } from "tests/utils/constants.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
let 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("coupon1 -- Testing one-off rollover, apply to all"),
chalk.yellow(
`${testCase} - Testing invoice credits reward, apply to all product`,
),
() => {
let customerId = "coupon1";
let stripeCli: Stripe;
let customer: Customer;
let testClockId: string;
let db: DrizzleCli;
let org: Organization;
let env: AppEnv;
let autumn: AutumnInt = new AutumnInt({ version: APIVersion.v1_4 });
let couponAmount = rewards.rolloverAll.discount_config.discount_value;
let curUnix = new Date().getTime();
before(async function () {
const { testClockId: testClockId1, customer: customer1 } =
await initCustomerWithTestClock({
customerId,
org: this.org,
env: this.env,
db: this.db,
});
testClockId = testClockId1;
customer = customer1;
await setupBefore(this);
db = this.db;
org = this.org;
env = this.env;
stripeCli = this.stripeCli;
stripeCli = createStripeCli({
org: this.org,
env: this.env,
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("CYCLE 0: should attach pro with overage (through checkout)", async () => {
couponAmount -= getFixedPriceAmount(products.proWithOverage);
const res = await AutumnCli.attach({
customerId,
productId: products.proWithOverage.id,
forceCheckout: true,
it("should attach pro", async () => {
const res = await autumn.attach({
customer_id: customerId,
product_id: pro.id,
});
await completeCheckoutForm(
@@ -62,117 +180,57 @@ describe(
rewards.rolloverAll.id,
);
await timeout(20000);
await timeout(10000);
const cusRes = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: products.proWithOverage,
cusRes,
});
});
couponAmount -= getBasePrice({ product: pro });
it("CYCLE 0: should have $0 invoice and correct remaining coupon amount", async () => {
const cusRes = await AutumnCli.getCustomer(customerId);
expect(cusRes.invoices[0].total).to.equal(0);
const customer = await autumn.customers.get(customerId);
expectProductAttached({ customer, product: pro });
expect(customer.invoices![0].total).to.equal(0);
const cusDiscount = await getDiscount({
stripeCli: stripeCli,
customer: cusRes.customer,
stripeCli,
stripeId: customer.stripe_id!,
});
try {
expect(cusDiscount).to.exist;
expect(getOriginalCouponId(cusDiscount.coupon?.id)).to.equal(
rewards.rolloverAll.id,
);
// Expect amount to be original amount - pro price
expect(cusDiscount.coupon?.amount_off).to.equal(couponAmount * 100);
} catch (error) {
console.error("--------------------------------");
console.error(
"Expected stripe cus to have coupon",
rewards.rolloverAll,
);
console.error("Actual stripe cus discount", cusDiscount);
throw error;
}
});
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("CYCLE 1: should set usage to -100 and advance clock by 1 month", async () => {
const usage = 100;
const res = await AutumnCli.usage({
it("should run another cycle and have correct invoice + coupon amount", async () => {
const res = await simulateOneCycle({
customerId,
featureId: features.metered1.id,
value: usage,
});
// Price
const price = getPriceForOverage(
products.proWithOverage.prices[1],
-(products.proWithOverage.entitlements.metered1.allowance! - usage),
);
couponAmount =
couponAmount - (price + getFixedPriceAmount(products.proWithOverage));
await advanceClockForInvoice({
db,
org,
env,
stripeCli,
autumn,
testClockId,
waitForMeterUpdate: true,
couponAmount,
curUnix,
});
});
it("CYCLE 1: should have $0 invoice and correct new coupon amount", async () => {
const cusRes = await AutumnCli.getCustomer(customerId);
expect(cusRes.invoices[0].total).to.equal(0);
const cusDiscount = await getDiscount({
stripeCli: stripeCli,
customer,
});
try {
expect(getOriginalCouponId(cusDiscount.coupon?.id)).to.equal(
rewards.rolloverAll.id,
);
expect(cusDiscount.coupon?.amount_off).to.equal(couponAmount * 100);
} catch (error) {
console.log("--------------------------------");
console.log("coupon1, cycle 1 failed");
console.log("Expected stripe cus to have coupon", rewards.rolloverAll);
console.log("Actual stripe cus discount", cusDiscount);
throw error;
}
});
// CYCLE 2
it("CYCLE 2: should have $0 invoice and correct new coupon amount after 2nd cycle", async () => {
await timeout(20000);
let advanceTo = addHours(addMonths(new Date(), 2), 2);
await advanceTestClock({
stripeCli,
testClockId,
advanceTo: advanceTo.getTime(),
});
const cusDiscount = await getDiscount({
stripeCli: stripeCli,
customer,
});
const newCouponAmount =
couponAmount - getFixedPriceAmount(products.proWithOverage);
try {
expect(cusDiscount.coupon?.amount_off).to.equal(newCouponAmount * 100);
} catch (error) {
console.log("--------------------------------");
console.log("coupon1, cycle 2 failed");
console.log("Expected coupon amount", newCouponAmount * 100);
console.log("Stripe cus discount", cusDiscount);
throw error;
}
});
},
);

View File

@@ -1,156 +1,206 @@
import { createLogtailWithContext } from "@/external/logtail/logtailUtils.js";
import { createStripeCli } from "@/external/stripe/utils.js";
import { getOriginalCouponId } from "@/internal/rewards/rewardUtils.js";
import { getPriceForOverage } from "@/internal/products/prices/priceUtils.js";
import { Customer } from "@autumn/shared";
import { expect } from "chai";
import chalk from "chalk";
import Stripe from "stripe";
import { AutumnCli } from "tests/cli/AutumnCli.js";
import { features, products, rewards } from "tests/global.js";
import { compareMainProduct } from "tests/utils/compare.js";
import { getFixedPriceAmount, timeout } from "tests/utils/genUtils.js";
import {
advanceClockForInvoice,
completeCheckoutForm,
getDiscount,
} from "tests/utils/stripeUtils.js";
import { initCustomerWithTestClock } from "tests/utils/testInitUtils.js";
import chalk from "chalk";
import Stripe from "stripe";
import { expect } from "chai";
import {
APIVersion,
AppEnv,
CouponDurationType,
CreateReward,
Organization,
RewardType,
} from "@autumn/shared";
import { getOriginalCouponId } from "@/internal/rewards/rewardUtils.js";
import { getPriceForOverage } from "@/internal/products/prices/priceUtils.js";
import { timeout } from "tests/utils/genUtils.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { DrizzleCli } from "@/db/initDrizzle.js";
import { setupBefore } from "tests/before.js";
import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
import {
addPrefixToProducts,
getBasePrice,
} from "tests/utils/testProductUtils/testProductUtils.js";
import { createProducts, createReward } from "tests/utils/productUtils.js";
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
import { Decimal } from "decimal.js";
import { getExpectedInvoiceTotal } from "tests/utils/expectUtils/expectInvoiceUtils.js";
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
import { addHours, addMonths } from "date-fns";
import { hoursToFinalizeInvoice } from "tests/utils/constants.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("coupon2 -- Testing one-off rollover, apply to usage only"),
chalk.yellow(`${testCase} - Testing one-off rollover, apply to usage only`),
() => {
let logger: any;
let customerId = "coupon2";
let customerId = testCase;
let stripeCli: Stripe;
let customer: Customer;
let testClockId: string;
let couponAmount = rewards.rolloverUsage.discount_config.discount_value;
let autumn: AutumnInt = new AutumnInt({ version: APIVersion.v1_4 });
let org: Organization;
let env: AppEnv;
let db: DrizzleCli;
let couponAmount = reward.discount_config!.discount_value;
before(async function () {
const { testClockId: testClockId1, customer: customer1 } =
await initCustomerWithTestClock({
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;
customer = customer1;
logger = createLogtailWithContext({
test: "coupon2 -- Testing one-off rollover, apply to usage only",
customerId,
testClockId = testClockId1;
addPrefixToProducts({
products: [pro],
prefix: testCase,
});
stripeCli = createStripeCli({
org: this.org,
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 overage (through checkout)", async () => {
const res = await AutumnCli.attach({
customerId,
productId: products.proWithOverage.id,
forceCheckout: true,
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,
rewards.rolloverUsage.id,
);
await completeCheckoutForm(res.checkout_url, undefined, reward.id);
await timeout(10000);
const cusRes = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: products.proWithOverage,
cusRes,
const customer = await autumn.customers.get(customerId);
expectProductAttached({
customer,
product: pro,
});
});
it("should have fixed price invoice and correct remaining coupon amount", async () => {
const cusRes = await AutumnCli.getCustomer(customerId);
const fixedPrice = getFixedPriceAmount(products.proWithOverage);
expect(cusRes.invoices[0].total).to.equal(fixedPrice);
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: stripeCli,
customer: cusRes.customer,
stripeCli,
stripeId: customer.stripe_id!,
});
try {
expect(getOriginalCouponId(cusDiscount.coupon?.id)).to.equal(
rewards.rolloverUsage.id,
);
// Expect amount to be original amount - pro price
expect(getOriginalCouponId(cusDiscount.coupon?.id)).to.equal(reward.id);
expect(cusDiscount.coupon?.amount_off).to.equal(couponAmount * 100);
} catch (error) {
logger.error("--------------------------------");
logger.error(
"Expected stripe cus to have coupon",
rewards.rolloverUsage,
);
logger.error("Actual stripe cus discount", cusDiscount);
throw error;
}
});
// CYCLE 1
it("should set usage to -100 and advance clock by 1 month", async () => {
const usage = Math.min(Math.floor(Math.random() * 1000), 100);
it("should track usage and have correct invoice amount", async () => {
const usage = new Decimal(Math.random() * 1250120 + 10000)
.toDecimalPlaces(2)
.toNumber();
const res = await AutumnCli.usage({
customerId,
featureId: features.metered1.id,
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Words,
value: usage,
});
// Price
const price = getPriceForOverage(
products.proWithOverage.prices[1],
-(products.proWithOverage.entitlements.metered1.allowance! - usage),
);
let usageTotal = await getExpectedInvoiceTotal({
org,
env,
db,
customerId,
productId: pro.id,
usage: [{ featureId: TestFeature.Words, value: usage }],
stripeCli,
onlyIncludeUsage: true,
});
couponAmount = couponAmount - price;
let basePrice = getBasePrice({ product: pro });
await advanceClockForInvoice({
couponAmount = couponAmount - usageTotal;
await advanceTestClock({
stripeCli,
testClockId,
waitForMeterUpdate: true,
});
advanceTo: addHours(
addMonths(new Date(), 1),
hoursToFinalizeInvoice,
).getTime(),
waitForSeconds: 20,
});
it("should have $0 invoice and correct new coupon amount", async () => {
const cusRes = await AutumnCli.getCustomer(customerId);
expect(cusRes.invoices[0].total).to.equal(
getFixedPriceAmount(products.proWithOverage),
);
const customer = await autumn.customers.get(customerId);
expect(customer.invoices![0].total).to.equal(basePrice);
const cusDiscount = await getDiscount({
stripeCli: stripeCli,
customer: cusRes.customer,
stripeCli,
stripeId: customer.stripe_id!,
});
try {
expect(getOriginalCouponId(cusDiscount.coupon?.id)).to.equal(
rewards.rolloverUsage.id,
expect(getOriginalCouponId(cusDiscount.coupon?.id)).to.equal(reward.id);
expect(cusDiscount.coupon?.amount_off).to.equal(
Math.round(couponAmount * 100),
);
expect(cusDiscount.coupon?.amount_off).to.equal(couponAmount * 100);
} catch (error) {
logger.error("--------------------------------");
logger.error("coupon2, cycle 1 failed");
logger.error(
"Expected stripe cus to have coupon",
rewards.rolloverUsage,
);
logger.error("Actual stripe cus discount", cusDiscount);
throw error;
}
});
},
);

View File

@@ -1,23 +1,22 @@
import { expect } from "chai";
import chalk from "chalk";
import { Autumn } from "@/external/autumn/autumnCli.js";
import { features } from "tests/global.js";
import { setupBefore } from "tests/before.js";
import { initCustomer } from "tests/utils/init.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import {
AppEnv,
BillingInterval,
ProductItemFeatureType,
UsageModel,
} from "@autumn/shared";
import { createProduct } from "tests/utils/productUtils.js";
import { createProducts } from "tests/utils/productUtils.js";
import { getMainCusProduct } from "tests/utils/cusProductUtils/cusProductUtils.js";
import { getUsageCusEnt } from "tests/utils/cusProductUtils/cusEntSearchUtils.js";
import { getPrepaidCusEnt } from "tests/utils/cusProductUtils/cusEntSearchUtils.js";
import { constructFeaturePriceItem } from "@/internal/products/product-items/productItemUtils.js";
import { SupabaseClient } from "@supabase/supabase-js";
import { timeout } from "@/utils/genUtils.js";
import { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
// Scenario 1: prepaid + pay per use monthly -> prepaid + pay per use monthly
let pro = {
@@ -102,15 +101,15 @@ export const getPrepaidAndUsageCusEnts = async ({
return { prepaidCusEnt, usageCusEnt };
};
// UNCOMMENT FROM HERE
const testCase = "multiFeature1";
describe(`${chalk.yellowBright(
"multi-feature/multi_feature1: Testing prepaid + pay per use -> prepaid + pay per use",
"multiFeature1: Testing prepaid + pay per use -> prepaid + pay per use",
)}`, () => {
let autumn: Autumn;
let customerId = "multiFeature1Customer";
let autumn: AutumnInt = new AutumnInt();
let customerId = testCase;
let prepaidQuantity = 10;
let prepaidAllowance = pro.items.prepaid.included_usage + prepaidQuantity;
let totalUsage = 0;
let premiumPrepaidAllowance =
@@ -127,30 +126,29 @@ describe(`${chalk.yellowBright(
await setupBefore(this);
await initCustomer({
autumn: this.autumnJs,
customerId,
db: this.db,
org: this.org,
env: this.env,
attachPm: true,
attachPm: "success",
});
autumn = this.autumn;
await createProduct({
await createProducts({
autumn,
product: pro,
});
await createProduct({
autumn,
product: premium,
products: [pro, premium],
db: this.db,
orgId: this.org.id,
env: this.env,
});
});
it("should attach pro product to customer", async function () {
await autumn.attach({
customerId,
productId: pro.id,
customer_id: customerId,
product_id: pro.id,
options: optionsList,
});
@@ -172,10 +170,10 @@ describe(`${chalk.yellowBright(
it("should use prepaid allowance first", async function () {
let value = 60;
await autumn.events.send({
customerId,
await autumn.track({
customer_id: customerId,
value,
featureId: features.metered1.id,
feature_id: features.metered1.id,
});
totalUsage += value;
@@ -196,15 +194,15 @@ describe(`${chalk.yellowBright(
it("should have correct usage / invoice after upgrade", async function () {
let value = 60;
await autumn.events.send({
customerId,
await autumn.track({
customer_id: customerId,
value,
featureId: features.metered1.id,
feature_id: features.metered1.id,
});
totalUsage += value;
await timeout(3000);
await timeout(5000);
let { usageCusEnt } = await getPrepaidAndUsageCusEnts({
customerId,
@@ -214,11 +212,9 @@ describe(`${chalk.yellowBright(
featureId: features.metered1.id,
});
// totalUsage = totalUsage + value - (usageCusEnt?.balance ?? 0);
await autumn.attach({
customerId,
productId: premium.id,
customer_id: customerId,
product_id: premium.id,
options: optionsList,
});
@@ -233,8 +229,6 @@ describe(`${chalk.yellowBright(
// Check invoice too
let { invoices } = await autumn.customers.get(customerId);
// 1. Let invoices[1] be 10 * premium prepaid price - pro prepaid price
// 2. Let invoices[0] be value * pro pay per use price
let invoice1Amount =
(premium.items.prepaid.price ?? 0) * prepaidQuantity -
@@ -242,16 +236,12 @@ describe(`${chalk.yellowBright(
let invoice0Amount = value * (pro.items.payPerUse.price ?? 0);
expect(invoices[1].total).to.equal(invoice1Amount);
expect(invoices[0].total).to.equal(invoice0Amount);
let totalAmount = invoice1Amount + invoice0Amount;
// console.log("Total usage", totalUsage);
// console.log("Premium prepaid allowance", premiumPrepaidAllowance);
// console.log("Value", value);
expect(invoices![0].total).to.equal(totalAmount);
let leftover = premiumPrepaidAllowance - totalUsage + value;
expect(prepaidCusEnt?.balance).to.equal(Math.max(0, leftover));
expect(newUsageCusEnt?.balance).to.equal(0);
});
});

View File

@@ -1,9 +1,8 @@
import { expect } from "chai";
import chalk from "chalk";
import { Autumn } from "@/external/autumn/autumnCli.js";
import { features } from "tests/global.js";
import { setupBefore } from "tests/before.js";
import { initCustomer } from "tests/utils/init.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import {
AppEnv,
BillingInterval,
@@ -22,9 +21,10 @@ import {
constructFeatureItem,
constructFeaturePriceItem,
} from "@/internal/products/product-items/productItemUtils.js";
import { SupabaseClient } from "@supabase/supabase-js";
import { timeout } from "@/utils/genUtils.js";
import { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
// Scenario 1: prepaid + pay per use monthly -> prepaid + pay per use monthly
let pro = {
@@ -51,15 +51,6 @@ let premium = {
id: "multiFeature2Premium",
name: "Multi Feature 2 Premium",
items: {
// // Prepaid
// prepaid: constructFeaturePriceItem({
// feature_id: features.metered1.id,
// included_usage: 100,
// amount: 15,
// interval: BillingInterval.Month,
// usage_model: UsageModel.Prepaid,
// }),
// Pay per use
payPerUse: constructFeaturePriceItem({
feature_id: features.metered1.id,
@@ -86,7 +77,7 @@ export const getLifetimeAndUsageCusEnts = async ({
featureId: string;
}) => {
let mainCusProduct = await getMainCusProduct({
customerId,
customerId: customerId,
db,
orgId,
env,
@@ -105,12 +96,12 @@ export const getLifetimeAndUsageCusEnts = async ({
return { lifetimeCusEnt, usageCusEnt };
};
// UNCOMMENT FROM HERE
const testCase = "multiFeature2";
describe(`${chalk.yellowBright(
"multi-feature/multi_feature2: Testing lifetime + pay per use -> pay per use",
"multiFeature2: Testing lifetime + pay per use -> pay per use",
)}`, () => {
let autumn: Autumn;
let customerId = "multiFeature2Customer";
let autumn: AutumnInt = new AutumnInt();
let customerId = testCase;
let totalUsage = 0;
@@ -118,11 +109,12 @@ describe(`${chalk.yellowBright(
await setupBefore(this);
await initCustomer({
autumn: this.autumnJs,
customerId,
db: this.db,
org: this.org,
env: this.env,
attachPm: true,
attachPm: "success",
});
autumn = this.autumn;
@@ -130,18 +122,24 @@ describe(`${chalk.yellowBright(
await createProduct({
autumn,
product: pro,
db: this.db,
orgId: this.org.id,
env: this.env,
});
await createProduct({
autumn,
product: premium,
db: this.db,
orgId: this.org.id,
env: this.env,
});
});
it("should attach pro product to customer", async function () {
await autumn.attach({
customerId,
productId: pro.id,
customer_id: customerId,
product_id: pro.id,
});
let { lifetimeCusEnt, usageCusEnt } = await getLifetimeAndUsageCusEnts({
@@ -187,16 +185,17 @@ describe(`${chalk.yellowBright(
it("should have correct usage after upgrade", async function () {
let value = 20;
await autumn.events.send({
customerId,
await autumn.track({
customer_id: customerId,
value,
featureId: features.metered1.id,
feature_id: features.metered1.id,
});
await timeout(3000);
await autumn.attach({
customerId,
productId: premium.id,
customer_id: customerId,
product_id: premium.id,
});
let { lifetimeCusEnt, usageCusEnt: newUsageCusEnt } =
@@ -208,18 +207,18 @@ describe(`${chalk.yellowBright(
featureId: features.metered1.id,
});
// Check invoice too
let { invoices } = await autumn.customers.get(customerId);
// 1. Let invoices[1] be 10 * premium prepaid price - pro prepaid price
// 2. Let invoices[0] be value * pro pay per use price
let invoice0Amount = value * (pro.items.payPerUse.price ?? 0);
expect(invoices[0].total).to.equal(invoice0Amount);
expect(lifetimeCusEnt).to.not.exist;
expect(newUsageCusEnt?.balance).to.equal(
premium.items.payPerUse.included_usage,
);
// Check invoice too
let res = await autumn.customers.get(customerId);
let invoices = res.invoices;
let invoice0Amount = value * (pro.items.payPerUse.price ?? 0);
expect(invoices![0].total).to.equal(
invoice0Amount,
"Invoice 0 should be 0",
);
});
});

View File

@@ -1,6 +1,6 @@
import { expect } from "chai";
import chalk from "chalk";
import { Autumn } from "@/external/autumn/autumnCli.js";
import { expect } from "chai";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { features } from "tests/global.js";
import { setupBefore } from "tests/before.js";
@@ -11,7 +11,7 @@ import {
ProductItemFeatureType,
UsageModel,
} from "@autumn/shared";
import { createProduct } from "tests/utils/productUtils.js";
import { createProducts } from "tests/utils/productUtils.js";
import { getMainCusProduct } from "tests/utils/cusProductUtils/cusProductUtils.js";
import {
getLifetimeFreeCusEnt,
@@ -22,11 +22,10 @@ import {
constructFeatureItem,
constructFeaturePriceItem,
} from "@/internal/products/product-items/productItemUtils.js";
import { SupabaseClient } from "@supabase/supabase-js";
import { timeout } from "@/utils/genUtils.js";
import { advanceTestClock } from "tests/utils/stripeUtils.js";
import { initCustomerWithTestClock } from "tests/utils/testInitUtils.js";
import { addDays, addMonths } from "date-fns";
import { addMonths } from "date-fns";
import { DrizzleCli } from "@/db/initDrizzle.js";
// Scenario 1: prepaid + pay per use monthly -> prepaid + pay per use monthly
@@ -87,7 +86,7 @@ export const getLifetimeAndUsageCusEnts = async ({
describe(`${chalk.yellowBright(
"multi-feature/multi_feature3: Testing lifetime + pay per use, advance test clock",
)}`, () => {
let autumn: Autumn;
let autumn: AutumnInt = new AutumnInt();
let customerId = "multiFeature3Customer";
let totalUsage = 0;
@@ -108,16 +107,19 @@ describe(`${chalk.yellowBright(
autumn = this.autumn;
await createProduct({
await createProducts({
autumn,
product: pro,
products: [pro],
db: this.db,
orgId: this.org.id,
env: this.env,
});
});
it("should attach pro product to customer", async function () {
await autumn.attach({
customerId,
productId: pro.id,
customer_id: customerId,
product_id: pro.id,
});
let { lifetimeCusEnt, usageCusEnt } = await getLifetimeAndUsageCusEnts({
@@ -138,10 +140,10 @@ describe(`${chalk.yellowBright(
let value = pro.items.lifetime.included_usage as number;
value += overageValue;
await autumn.events.send({
customerId,
await autumn.track({
customer_id: customerId,
value,
featureId: features.metered1.id,
feature_id: features.metered1.id,
});
totalUsage += value;

View File

@@ -1,7 +1,7 @@
import { products, referralPrograms } from "../../global.js";
import { assert } from "chai";
import chalk from "chalk";
import AutumnError, { Autumn } from "@/external/autumn/autumnCli.js";
import AutumnError, { AutumnInt } from "@/external/autumn/autumnCli.js";
import { setupBefore } from "tests/before.js";
import {
Customer,
@@ -23,7 +23,7 @@ describe(`${chalk.yellowBright(
let mainCustomerId = "main-referral-1";
let alternateCustomerId = "alternate-referral-1";
let redeemers = ["referral1-r1", "referral1-r2", "referral1-r3"];
let autumn: Autumn;
let autumn: AutumnInt = new AutumnInt();
let stripeCli: Stripe;
let testClockId: string;
let referralCode: ReferralCode;
@@ -33,7 +33,6 @@ describe(`${chalk.yellowBright(
before(async function () {
await setupBefore(this);
autumn = this.autumn;
stripeCli = this.stripeCli;
const { testClockId: testClockId1, customer } =
@@ -48,8 +47,8 @@ describe(`${chalk.yellowBright(
mainCustomer = customer;
await autumn.attach({
customerId: mainCustomerId,
productId: products.proWithTrial.id,
customer_id: mainCustomerId,
product_id: products.proWithTrial.id,
});
let batchCreate = [];
@@ -155,8 +154,8 @@ describe(`${chalk.yellowBright(
let redeemer = redeemers[i];
await autumn.attach({
customerId: redeemer,
productId: products.pro.id,
customer_id: redeemer,
product_id: products.pro.id,
});
await timeout(3000);

View File

@@ -1,7 +1,7 @@
import { products, referralPrograms } from "../../global.js";
import { assert } from "chai";
import chalk from "chalk";
import AutumnError, { Autumn } from "@/external/autumn/autumnCli.js";
import AutumnError, { AutumnInt } from "@/external/autumn/autumnCli.js";
import { setupBefore } from "tests/before.js";
import {
Customer,
@@ -22,7 +22,7 @@ describe(`${chalk.yellowBright(
)}`, () => {
let mainCustomerId = "main-referral-2";
let redeemers = ["referral2-r1", "referral2-r2", "referral2-r3"];
let autumn: Autumn;
let autumn: AutumnInt = new AutumnInt();
let stripeCli: Stripe;
let testClockId: string;
let referralCode: ReferralCode;
@@ -32,7 +32,6 @@ describe(`${chalk.yellowBright(
before(async function () {
await setupBefore(this);
autumn = this.autumn;
stripeCli = this.stripeCli;
const { testClockId: testClockId1, customer } =
@@ -118,8 +117,8 @@ describe(`${chalk.yellowBright(
let curTime = new Date();
it("customer should have discount for first purchase", async function () {
await autumn.attach({
customerId: mainCustomerId,
productId: products.proWithTrial.id,
customer_id: mainCustomerId,
product_id: products.proWithTrial.id,
});
await timeout(3000);
@@ -134,8 +133,8 @@ describe(`${chalk.yellowBright(
// 1. Get invoice
let { invoices } = await autumn.customers.get(mainCustomerId);
assert.equal(invoices.length, 2);
assert.equal(invoices[0].total, 0);
assert.equal(invoices!.length, 2);
assert.equal(invoices![0].total, 0);
});
it("customer should have discount for second purchase", async function () {
@@ -165,7 +164,7 @@ describe(`${chalk.yellowBright(
// // 3. Get invoice again
let { invoices: invoices2 } = await autumn.customers.get(mainCustomerId);
assert.equal(invoices2.length, 3);
assert.equal(invoices2[0].total, 0);
assert.equal(invoices2!.length, 3);
assert.equal(invoices2![0].total, 0);
});
});

View File

@@ -1,7 +1,7 @@
import { features, products, referralPrograms } from "../../global.js";
import { assert } from "chai";
import chalk from "chalk";
import AutumnError, { Autumn } from "@/external/autumn/autumnCli.js";
import AutumnError, { AutumnInt } from "@/external/autumn/autumnCli.js";
import { setupBefore } from "tests/before.js";
import {
Customer,
@@ -21,7 +21,7 @@ describe(`${chalk.yellowBright(
)}`, () => {
let mainCustomerId = "main-referral-3";
let redeemers = ["referral3-r1", "referral3-r2", "referral3-r3"];
let autumn: Autumn;
let autumn: AutumnInt = new AutumnInt();
let stripeCli: Stripe;
let testClockId: string;
let referralCode: ReferralCode;
@@ -46,8 +46,8 @@ describe(`${chalk.yellowBright(
mainCustomer = customer;
await autumn.attach({
customerId: mainCustomerId,
productId: products.proWithTrial.id,
customer_id: mainCustomerId,
product_id: products.proWithTrial.id,
});
let batchCreate = [];
@@ -106,8 +106,8 @@ describe(`${chalk.yellowBright(
let redeemer = redeemers[i];
await autumn.attach({
customerId: redeemer,
productId: products.pro.id,
customer_id: redeemer,
product_id: products.pro.id,
});
await timeout(3000);

View File

@@ -1,14 +1,8 @@
import { features, products, referralPrograms } from "../../global.js";
import { assert } from "chai";
import chalk from "chalk";
import AutumnError, { Autumn } from "@/external/autumn/autumnCli.js";
import { setupBefore } from "tests/before.js";
import {
Customer,
ErrCode,
ReferralCode,
RewardRedemption,
} from "@autumn/shared";
import { Customer, ReferralCode, RewardRedemption } from "@autumn/shared";
import { timeout } from "tests/utils/genUtils.js";
import { initCustomerWithTestClock } from "tests/utils/testInitUtils.js";
import { Stripe } from "stripe";
@@ -17,6 +11,7 @@ import { compareProductEntitlements } from "tests/utils/compare.js";
import { addDays, addHours } from "date-fns";
import { advanceTestClock } from "tests/utils/stripeUtils.js";
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
// UNCOMMENT FROM HERE
describe(`${chalk.yellowBright(
@@ -26,7 +21,7 @@ describe(`${chalk.yellowBright(
// let redeemers = ["referral4-r1", "referral4-r2"];
let redeemerId = "referral4-r1";
let autumn: Autumn;
let autumn: AutumnInt = new AutumnInt();
let stripeCli: Stripe;
let referralCode: ReferralCode;
@@ -49,8 +44,8 @@ describe(`${chalk.yellowBright(
});
await autumn.attach({
customerId: mainCustomerId,
productId: products.proWithTrial.id,
customer_id: mainCustomerId,
product_id: products.proWithTrial.id,
});
let { testClockId: testClockId1, customer } =
@@ -85,8 +80,8 @@ describe(`${chalk.yellowBright(
it("should not be triggered because of trial", async function () {
await autumn.attach({
customerId: redeemerId,
productId: products.proWithTrial.id,
customer_id: redeemerId,
product_id: products.proWithTrial.id,
});
await timeout(3000);

View File

@@ -1,188 +0,0 @@
import { expect } from "chai";
import { initCustomer } from "../../utils/init.js";
import { AutumnCli } from "../../cli/AutumnCli.js";
import {
advanceProducts,
creditSystems,
features,
products,
} from "../../global.js";
import { compareMainProduct } from "../../utils/compare.js";
import { timeout } from "../../utils/genUtils.js";
import { Decimal } from "decimal.js";
import { sendGPUEvents } from "../../utils/advancedUsageUtils.js";
import chalk from "chalk";
const PRECISION = 12;
describe.skip(`${chalk.yellowBright(
"Testing group by -- regular metered1 feature",
)}`, () => {
let customerId = "group-by-basic-metered";
before(async function () {
await initCustomer({
customer_data: {
id: customerId,
name: "Group by basic metered",
email: "group-by-basic-metered@example.com",
},
attachPm: true,
db: this.db,
org: this.org,
env: this.env,
});
});
it("should attach pro product to customer", async () => {
await AutumnCli.attach({
customerId,
productId: products.pro.id,
});
const res = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: products.pro,
cusRes: res,
});
});
it("should send events for three different groups", async () => {
// const users = ["null", "123", "abc"];
const users = ["null", "123", "abc"];
let results: any = {};
let numEventsPerUser = 5;
let groupProperty = features.metered1.config.group_by.property;
let multiplier = 1.5;
for (const user of users) {
let batchEvents = [];
let totalValue = 0;
for (let i = 0; i < numEventsPerUser; i++) {
let randomVal = new Decimal(Math.random().toFixed(PRECISION))
.mul(multiplier)
.toNumber();
totalValue = new Decimal(totalValue).plus(randomVal).toNumber();
batchEvents.push(
AutumnCli.sendEvent({
customerId,
eventName: features.metered1.eventName,
properties: {
[groupProperty]: user == "null" ? null : user,
value: randomVal,
},
}),
);
}
await Promise.all(batchEvents);
await timeout(3000);
results[user] = totalValue;
}
let metered1Allowance = products.pro.entitlements.metered1.allowance!;
for (const user of users) {
const { allowed, balanceObj }: any = await AutumnCli.entitled(
customerId,
features.metered1.id,
true,
user == "null" ? undefined : user,
);
let expectedBalance = new Decimal(metered1Allowance)
.minus(results[user])
.toNumber();
if (expectedBalance < 1) {
expect(allowed).to.be.false;
} else {
expect(allowed).to.be.true;
}
expect(balanceObj!.balance).to.equal(expectedBalance);
// Check balance for GET /customers/:customerId
const res = await AutumnCli.getCustomer(customerId, {
[groupProperty]: user == "null" ? undefined : user,
});
const entitlements = res.entitlements;
const metered1 = entitlements.find(
(e: any) => e.feature_id === features.metered1.id,
);
expect(metered1.balance).to.equal(expectedBalance);
}
});
});
// TO ADD SUPPORT FOR IN THE FUTURE
describe.skip(`${chalk.yellowBright(
"Testing group by -- advanced GPU usage",
)}`, () => {
let customerId = "group-by-advanced-gpu-usage-metered";
before(async function () {
await initCustomer({
customer_data: {
id: customerId,
name: "Group by advanced GPU usage",
email: "group-by-advanced-gpu-usage@example.com",
},
attachPm: true,
db: this.db,
org: this.org,
env: this.env,
});
});
it("should attach GPU system starter product to customer", async () => {
await AutumnCli.attach({
customerId,
productId: advanceProducts.gpuSystemStarter.id,
});
const res = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: advanceProducts.gpuSystemStarter,
cusRes: res,
});
});
it("should send events (advanced GPU usage) for three different groups", async () => {
const users = ["null", "123", "abc"];
let numEventsPerUser = 20;
let groupProperty = features.gpu1.config.group_by.property;
const results: any = {};
for (const user of users) {
let { creditsUsed } = await sendGPUEvents({
customerId,
eventCount: numEventsPerUser,
groupObj: user == "null" ? undefined : { [groupProperty]: user },
});
results[user] = creditsUsed;
}
let creditAllowance =
advanceProducts.gpuSystemStarter.entitlements.gpuCredits.allowance!;
console.log("Results", results);
console.log("Starting allowance", creditAllowance);
for (const user of users) {
const { allowed, balanceObj }: any = await AutumnCli.entitled(
customerId,
creditSystems.gpuCredits.id,
true,
user == "null" ? undefined : user,
);
let expectedCreditAllowance = new Decimal(creditAllowance)
.minus(results[user])
.toNumber();
expect(balanceObj.balance).to.equal(expectedCreditAllowance);
}
});
});

View File

@@ -1,187 +0,0 @@
import { Customer } from "@autumn/shared";
import chalk from "chalk";
import { initCustomerWithTestClock } from "../../utils/testInitUtils.js";
import { compareMainProduct } from "../../utils/compare.js";
import { AutumnCli } from "../../cli/AutumnCli.js";
import { advanceProducts, creditSystems } from "../../global.js";
import { timeout } from "../../utils/genUtils.js";
import { assert, expect } from "chai";
import { createStripeCli } from "@/external/stripe/utils.js";
import {
advanceClockForInvoice,
advanceMonths,
checkBillingMeterEventSummary,
getUsageInArrearPrice,
} from "../../utils/stripeUtils.js";
import { addMonths } from "date-fns";
import {
sendGPUEvents,
checkCreditBalance,
checkUsageInvoiceAmount,
} from "../../utils/advancedUsageUtils.js";
// THIRD, TEST GPU PRO ANNUAL
describe(`${chalk.yellowBright("multi_interval1: GPU starter annual")}`, () => {
const customerId = "advancedUsageAnnual";
let testClockId = "";
let totalCreditsUsed = 0;
let customer: Customer;
before(async function () {
this.timeout(30000);
let { testClockId: insertedTestClockId, customer: insertedCustomer } =
await initCustomerWithTestClock({
customerId,
org: this.org,
env: this.env,
db: this.db,
});
testClockId = insertedTestClockId;
customer = insertedCustomer;
console.log("Testing multi interval 1");
});
it("should attach GPU starter annual", async function () {
this.timeout(30000);
const res = await AutumnCli.attach({
customerId: customerId,
productId: advanceProducts.gpuStarterAnnual.id,
});
await timeout(3000);
});
it("should have GPU starter annual product", async function () {
this.timeout(30000);
const res = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: advanceProducts.gpuStarterAnnual,
cusRes: res,
});
// Should have 2 invoices (one for annual, one for monthly)
expect(res!.invoices.length).to.equal(2);
});
it("should send 20 events and have correct balance", async function () {
this.timeout(30000);
let eventCount = 20;
const { creditsUsed } = await sendGPUEvents({
customerId,
eventCount,
});
totalCreditsUsed = creditsUsed;
console.log(" - Total credits used: ", totalCreditsUsed);
await checkCreditBalance({
customerId,
featureId: creditSystems.gpuCredits.id,
totalCreditsUsed,
originalAllowance:
advanceProducts.gpuStarterAnnual.entitlements.gpuCredits.allowance!,
});
});
// Advance by a month and check if latest invoice is correct
it("should have invoice after a month and correct balance", async function () {
const stripeCli = createStripeCli({ org: this.org, env: this.env });
await advanceClockForInvoice({
stripeCli,
testClockId,
waitForMeterUpdate: true,
});
const res = await AutumnCli.getCustomer(customerId);
const invoices = res!.invoices;
let 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,
sb: this.sb,
env: this.env,
productId: advanceProducts.gpuStarterAnnual.id,
});
// Get billing meter event summary
let eventSummary = await checkBillingMeterEventSummary({
stripeCli,
startTime: addMonths(new Date(), 11),
stripeMeterId: usagePrice?.config?.stripe_meter_id,
stripeCustomerId: customer.processor.id,
});
try {
assert.exists(eventSummary);
assert.equal(
eventSummary?.aggregated_value,
Math.round(totalCreditsUsed),
);
assert.equal(invoices.length, 13 + 2);
} catch (error) {
console.group();
console.log(" - Event summary: ", eventSummary);
console.log(" - Total credits used: ", totalCreditsUsed);
console.log(" - Last 3 invoices: ", invoices.slice(-3));
console.groupEnd();
throw error;
}
});
});

View File

@@ -1,206 +0,0 @@
import { Customer } from "@autumn/shared";
import chalk from "chalk";
import { initCustomerWithTestClock } from "../../utils/testInitUtils.js";
import { compareMainProduct } from "../../utils/compare.js";
import { AutumnCli } from "../../cli/AutumnCli.js";
import { advanceProducts, creditSystems } from "../../global.js";
import { timeout } from "../../utils/genUtils.js";
import { assert, expect } from "chai";
import { createStripeCli } from "@/external/stripe/utils.js";
import {
advanceClockForInvoice,
advanceMonths,
advanceTestClock,
checkBillingMeterEventSummary,
getUsageInArrearPrice,
} from "../../utils/stripeUtils.js";
import { addMonths } from "date-fns";
import {
sendGPUEvents,
checkUsageInvoiceAmount,
} from "../../utils/advancedUsageUtils.js";
import { Decimal } from "decimal.js";
// FOURTH, TEST GPU STARTER ANNUAL UPGRADE TO GPU PRO
describe(`${chalk.yellowBright(
"multi_interval2: GPU starter annual upgrade to GPU pro annual",
)}`, () => {
const customerId = "multi_interval2";
let testClockId = "";
let totalCreditsUsed = 0;
let customer: Customer;
let curTime = new Date();
before(async function () {
let { testClockId: insertedTestClockId, customer: insertedCustomer } =
await initCustomerWithTestClock({
customerId,
org: this.org,
env: this.env,
db: this.db,
});
testClockId = insertedTestClockId;
customer = insertedCustomer;
// console.log("MULTI INTERVAL 2, INITIALIZED CUSTOMER");
});
it("should attach GPU starter annual", async function () {
const res = await AutumnCli.attach({
customerId: customerId,
productId: advanceProducts.gpuStarterAnnual.id,
});
await timeout(5000);
});
it("should have GPU starter annual product", async function () {
const res = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: advanceProducts.gpuStarterAnnual,
cusRes: res,
});
});
let numberOfMonths = 0;
it(`should advance ${numberOfMonths} months and upgrade to GPU pro monthly`, async function () {
const stripeCli = createStripeCli({ org: this.org, env: this.env });
await advanceMonths({
stripeCli,
testClockId,
numberOfMonths,
});
curTime = addMonths(curTime, numberOfMonths);
// Send 20 events
let eventCount = 20;
const { creditsUsed } = await sendGPUEvents({
customerId,
eventCount,
});
totalCreditsUsed = creditsUsed;
console.log(" - Total credits used: ", creditsUsed);
await AutumnCli.attach({
customerId: customerId,
productId: advanceProducts.gpuProAnnual.id,
});
await advanceTestClock({
stripeCli,
testClockId,
numberOfDays: 10,
startingFrom: curTime,
});
});
it("should have GPU pro annual product and 2 Stripe subscriptions", async function () {
const res = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: advanceProducts.gpuProAnnual,
cusRes: res,
});
// Should have 2 subscriptions
const stripeCli = createStripeCli({ org: this.org, env: this.env });
const subs = await stripeCli.subscriptions.list({
customer: customer.processor.id,
});
await timeout(5000);
expect(subs.data.length).to.equal(2);
});
it("should have correct invoice for GPU starter annual (bill for remaining usages)", async function () {
const res = await AutumnCli.getCustomer(customerId);
const invoices = res!.invoices;
let 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,
});
});
it("should send 20 events (on GPU pro annual)", async function () {
const stripeCli = createStripeCli({ org: this.org, env: this.env });
// Send 20 events
let eventCount = 20;
const { creditsUsed } = await sendGPUEvents({
customerId,
eventCount,
});
totalCreditsUsed = creditsUsed;
console.log(" - Total credits used: ", totalCreditsUsed);
await advanceClockForInvoice({
stripeCli,
testClockId,
waitForMeterUpdate: true,
startingFrom: curTime,
});
});
it("should have correct billing meter event summary for GPU pro annual", async function () {
const res = await AutumnCli.getCustomer(customerId);
const invoices = res!.invoices;
// Think I have to use Stripe metered event summary to check this
let usagePrice = await getUsageInArrearPrice({
org: this.org,
sb: this.sb,
env: this.env,
productId: advanceProducts.gpuProAnnual.id,
});
const stripeCli = createStripeCli({ org: this.org, env: this.env });
// console.log(" - Usage price: ", usagePrice);
// console.log(" - Config: ", usagePrice?.config);
let eventSummary = await checkBillingMeterEventSummary({
stripeCli,
startTime: curTime, // Wrong date?
stripeMeterId: usagePrice?.config?.stripe_meter_id,
stripeCustomerId: customer.processor.id,
});
let roundedFirst = Math.ceil(
new Decimal(totalCreditsUsed)
.div(usagePrice?.config?.billing_units!)
.toNumber(),
);
let roundedTotalCreditsUsed = new Decimal(roundedFirst)
.mul(usagePrice?.config?.billing_units!)
.toNumber();
try {
assert.exists(eventSummary);
assert.equal(eventSummary?.aggregated_value, roundedTotalCreditsUsed);
} catch (error) {
console.group();
console.log(" - Event summary: ", eventSummary);
console.log(" - Total credits used: ", totalCreditsUsed);
console.groupEnd();
throw error;
}
// await checkUsageInvoiceAmount({
// invoices,
// totalUsage: totalCreditsUsed,
// product: advanceProducts.gpuProAnnual,
// featureId: creditSystems.gpuCredits.id,
// invoiceIndex: 0,
// includeBase: false,
// });
});
});

View File

@@ -1,50 +1,50 @@
import { expect } from "chai";
import { v1ProductToBasePrice } from "tests/utils/testProductUtils/testProductUtils.js";
import { AutumnCli } from "../../cli/AutumnCli.js";
import { features, products } from "../../global.js";
import { compareMainProduct } from "../../utils/compare.js";
import { initCustomer } from "../../utils/init.js";
import {
advanceClockForInvoice,
completeCheckoutForm,
} from "../../utils/stripeUtils.js";
import { advanceClockForInvoice } from "../../utils/stripeUtils.js";
import { timeout } from "../../utils/genUtils.js";
import {
calculateMetered1Price,
createStripeCli,
} from "@/external/stripe/utils.js";
import { calculateMetered1Price } from "@/external/stripe/utils.js";
import chalk from "chalk";
import { initCustomerWithTestClock } from "tests/utils/testInitUtils.js";
import { Customer } from "@autumn/shared";
describe(`${chalk.yellowBright("usage1: Pro with overage")}`, () => {
import { setupBefore } from "tests/before.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import Stripe from "stripe";
const testCase = "usage1";
describe(`${chalk.yellowBright("usage1: Testing basic usage product")}`, () => {
const NUM_EVENTS = 50;
const customerId = "usage1";
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 initCustomerWithTestClock({
await initCustomer({
customerId,
org: this.org,
env: this.env,
db: this.db,
autumn: this.autumnJs,
attachPm: "success",
});
customer = customer_;
testClockId = testClockId_;
});
it("usage1: should create a usage based entitlement", async function () {
const res = await AutumnCli.attach({
it("should attach usage based product", async function () {
await AutumnCli.attach({
customerId: customerId,
productId: products.proWithOverage.id,
forceCheckout: true,
});
await completeCheckoutForm(res.checkout_url);
await timeout(10000);
});
it("usage1: should have correct product", async function () {
const res = await AutumnCli.getCustomer(customerId);
compareMainProduct({
@@ -68,7 +68,7 @@ describe(`${chalk.yellowBright("usage1: Pro with overage")}`, () => {
await timeout(10000);
});
it("usage1: should have correct metered1 balance after sending events", async function () {
it("should have correct metered1 balance after sending events", async function () {
const res: any = await AutumnCli.entitled(customerId, features.metered1.id);
expect(res!.allowed).to.be.true;
@@ -80,23 +80,17 @@ describe(`${chalk.yellowBright("usage1: Pro with overage")}`, () => {
const proOverageAmt =
products.proWithOverage.entitlements.metered1.allowance;
try {
expect(res!.allowed).to.be.true;
expect(balance?.balance).to.equal(proOverageAmt! - NUM_EVENTS);
expect(balance?.usage_allowed).to.be.true;
} catch (error) {
console.group();
console.log("Entitled res", res);
console.group();
throw error;
}
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("usage1: advance stripe test clock and wait for event", async function () {
// this.timeout(1000 * 60 * 10);
const stripeCli = createStripeCli({ org: this.org, env: this.env });
it("should advance stripe test clock and wait for event", async function () {
await advanceClockForInvoice({
stripeCli,
testClockId,
@@ -104,7 +98,7 @@ describe(`${chalk.yellowBright("usage1: Pro with overage")}`, () => {
});
});
it("usage1: should have correct invoice amount", async function () {
it("should have correct invoice amount", async function () {
const cusRes = await AutumnCli.getCustomer(customerId);
const invoices = cusRes!.invoices;
@@ -115,22 +109,17 @@ describe(`${chalk.yellowBright("usage1: Pro with overage")}`, () => {
metered1Feature: features.metered1,
});
try {
expect(invoices.length).to.equal(2);
const invoice2 = invoices[0];
const basePrice = v1ProductToBasePrice({
prices: products.proWithOverage.prices,
});
expect(invoice2.total).to.equal(
price + products.proWithOverage.prices[0].config.amount,
price + basePrice,
"invoice total should be usage price + base price",
);
} catch (error) {
console.group();
console.log(
"Expected invoices[0] to have total of: ",
price + products.proWithOverage.prices[0].config.amount,
);
console.log("Invoices", invoices);
console.group();
throw error;
}
});
});

View File

@@ -2,7 +2,6 @@ import { assert, expect } from "chai";
import { AutumnCli } from "../../cli/AutumnCli.js";
import { advanceProducts, creditSystems, features } from "../../global.js";
import { compareMainProduct } from "../../utils/compare.js";
import { initCustomer } from "../../utils/init.js";
import { advanceClockForInvoice } from "../../utils/stripeUtils.js";
import { timeout } from "../../utils/genUtils.js";
import { createStripeCli } from "@/external/stripe/utils.js";
@@ -14,11 +13,15 @@ import {
} from "../../utils/advancedUsageUtils.js";
import chalk from "chalk";
import { initCustomerWithTestClock } from "tests/utils/testInitUtils.js";
import { setupBefore } from "tests/before.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import Stripe from "stripe";
// FIRST, REGULAR CHECK GPU STARTER MONTHLY
describe(`${chalk.yellowBright("usage2: GPU starter monthly")}`, () => {
const customerId = "usage2";
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;
@@ -26,34 +29,30 @@ describe(`${chalk.yellowBright("usage2: GPU starter monthly")}`, () => {
let testClockId = "";
let totalCreditsUsed = 0;
let stripeCli: Stripe;
before(async function () {
const { testClockId: createdTestClockId } = await initCustomerWithTestClock(
{
await setupBefore(this);
const { testClockId: createdTestClockId } = await initCustomer({
customerId,
org: this.org,
env: this.env,
db: this.db,
},
);
testClockId = createdTestClockId;
autumn: this.autumnJs,
attachPm: "success",
});
it("usage2: should attach monthly starter", async function () {
this.timeout(30000);
testClockId = createdTestClockId;
const res = await AutumnCli.attach({
stripeCli = this.stripeCli;
});
it("should attach gpu system starter", async function () {
await AutumnCli.attach({
customerId: customerId,
productId: advanceProducts.gpuSystemStarter.id,
});
await timeout(3000);
console.log(" - Attached product");
});
it("usage2: should have monthly starter product", async function () {
this.timeout(30000);
const res = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: advanceProducts.gpuSystemStarter,
@@ -62,9 +61,7 @@ describe(`${chalk.yellowBright("usage2: GPU starter monthly")}`, () => {
});
// Use up events
it("usage2: should have correct balance after events (up to 10 DP)", async function () {
this.timeout(30000);
it("should send events and have correct balance (up to 10 DP)", async function () {
let eventCount = 20;
const batchEvents = [];
@@ -107,28 +104,16 @@ describe(`${chalk.yellowBright("usage2: GPU starter monthly")}`, () => {
let creditAllowance =
advanceProducts.gpuSystemStarter.entitlements.gpuCredits.allowance!;
try {
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);
} catch (error) {
console.log("Total credits used: ", totalCreditsUsed);
console.log("Credit allowance: ", creditAllowance);
console.log("Expected balance: ", creditAllowance - totalCreditsUsed);
console.log("Received balance: ", balanceObj!.balance);
console.group();
throw error;
}
// console.log(" - Total credits used: ", totalCreditsUsed);
// console.log(" - Balance: ", balanceObj!.balance);
});
// Check invoice.created event
it("usage2: should have correct invoice amount / updated meter balance", async function () {
this.timeout(100 * 60 * 1000);
const stripeCli = createStripeCli({ org: this.org, env: this.env });
it("should have correct invoice amount / updated meter balance", async function () {
await advanceClockForInvoice({
stripeCli,
testClockId,

View File

@@ -1,6 +1,4 @@
import chalk from "chalk";
import { addDays, addMonths, differenceInDays } from "date-fns";
import { initCustomerWithTestClock } from "../../utils/testInitUtils.js";
import { advanceProducts, creditSystems } from "../../global.js";
import { AutumnCli } from "../../cli/AutumnCli.js";
import {
@@ -17,63 +15,62 @@ import { assert, expect } from "chai";
import { Decimal } from "decimal.js";
import { compareMainProduct } from "../../utils/compare.js";
import { checkSubscriptionContainsProducts } from "tests/utils/scheduleCheckUtils.js";
import { getPriceForOverage } from "@/internal/products/prices/priceUtils.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";
const testCase = "usage3";
const ASSERT_INVOICE_AMOUNT = true;
// SECOND, UPGRADE TO GPU PRO MONTHLY
describe(`${chalk.yellowBright(
"usage3: upgrade from GPU starter monthly to GPU pro monthly",
)}`, () => {
const customerId = "usage3";
let testClockId = "";
let totalCreditsUsed = 0;
let daysAdd1 = 15;
// let daysAdd2 = differenceInDays(
// addMonths(new Date(), 1),
// addDays(new Date(), daysAdd1)
// );
let stripeCli: Stripe;
let curUnix = 0;
before(async function () {
let { testClockId: insertedTestClockId } = await initCustomerWithTestClock({
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 () {
const res = await AutumnCli.attach({
await AutumnCli.attach({
customerId: customerId,
productId: advanceProducts.gpuSystemStarter.id,
});
await timeout(3000);
});
// 2. Send 20 events
it("usage3: should send 20 events", async function () {
this.timeout(30000);
// console.log(" Sending 20 events");
let eventCount = 20;
const { creditsUsed } = await sendGPUEvents({
customerId,
eventCount,
});
console.log(" - Total credits used: ", creditsUsed);
totalCreditsUsed = creditsUsed;
});
// 3. Advance test clock by 15 days and upgrade
it("usage3: should advance test clock by 15 days and upgrade to GPU pro monthly", async function () {
const stripeCli = createStripeCli({ org: this.org, env: this.env });
await advanceTestClock({
it("should advance test clock by 15 days and upgrade to GPU pro monthly", async function () {
curUnix = await advanceTestClock({
stripeCli,
testClockId,
numberOfDays: 15,
@@ -84,12 +81,7 @@ describe(`${chalk.yellowBright(
productId: advanceProducts.gpuSystemPro.id,
});
await timeout(3000);
});
// 4. Check product attached
it("usage3: should have correct product attached (GPU pro monthly)", async function () {
this.timeout(30000);
// MAKE SURE STRIPE SUB ONLY HAS GPU PRO
const res = await AutumnCli.getCustomer(customerId);
compareMainProduct({
@@ -97,10 +89,7 @@ describe(`${chalk.yellowBright(
cusRes: res,
});
// MAKE SURE STRIPE SUB ONLY HAS GPU PRO
const stripeCli = createStripeCli({ org: this.org, env: this.env });
let subscriptionId = res.products[0].subscription_ids![0]!;
await checkSubscriptionContainsProducts({
db: this.db,
org: this.org,
@@ -110,82 +99,48 @@ describe(`${chalk.yellowBright(
});
});
// 5. Check invoice for 15 days of starter usage
it("usage3: should have invoice for 15 days of starter usage", async function () {
// 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 invoiceIndex = invoices.findIndex((invoice: any) =>
invoice.product_ids.includes(advanceProducts.gpuSystemStarter.id),
);
let basePrice1 = advanceProducts.gpuSystemStarter.prices[0].config.amount;
let basePrice2 = advanceProducts.gpuSystemPro.prices[0].config.amount;
// console.log("Total usage: ", totalCreditsUsed);
await checkUsageInvoiceAmount({
invoices,
totalUsage: totalCreditsUsed,
product: advanceProducts.gpuSystemStarter,
featureId: creditSystems.gpuCredits.id,
invoiceIndex,
includeBase: false,
});
});
return;
// 6. Advance another 15 days and check invoice for pro usage
it("usage3: should 20 send events (GPU pro monthly)", async function () {
let eventCount = 20;
const { creditsUsed } = await sendGPUEvents({
let { subs } = await getSubsFromCusId({
db: this.db,
org: this.org,
env: this.env,
customerId,
eventCount,
});
totalCreditsUsed = creditsUsed;
console.log(" - Total credits used: ", totalCreditsUsed);
// Check entitled
const { allowed, balanceObj }: any = await AutumnCli.entitled(
customerId,
creditSystems.gpuCredits.id,
true,
);
let proAllowance =
advanceProducts.gpuSystemPro.entitlements.gpuCredits.allowance!;
try {
assert.equal(allowed, true);
assert.equal(
balanceObj.balance,
new Decimal(proAllowance).minus(totalCreditsUsed).toNumber(),
);
} catch (error) {
console.group();
console.log(" - Total credits used: ", totalCreditsUsed);
console.log(" - Pro allowance: ", proAllowance);
console.log(" - Balance: ", balanceObj.balance);
console.groupEnd();
throw error;
}
});
// 7. Advance another 15 days and check invoice for pro usage
it("usage3: should have invoice for 15 days of pro usage", async function () {
const stripeCli = createStripeCli({ org: this.org, env: this.env });
await advanceClockForInvoice({
stripeCli,
testClockId,
waitForMeterUpdate: ASSERT_INVOICE_AMOUNT,
productId: advanceProducts.gpuSystemPro.id,
});
const res = await AutumnCli.getCustomer(customerId);
const invoices = res!.invoices;
let sub = subs[0];
await checkUsageInvoiceAmount({
invoices,
totalUsage: totalCreditsUsed,
product: advanceProducts.gpuSystemPro,
featureId: creditSystems.gpuCredits.id,
let baseDiff = calculateProrationAmount({
periodStart: sub.current_period_start * 1000,
periodEnd: sub.current_period_end * 1000,
now: curUnix,
amount: basePrice2 - basePrice1,
allowNegative: true,
});
let usagePrice = advanceProducts.gpuSystemStarter.prices[1];
let overage =
totalCreditsUsed -
advanceProducts.gpuSystemStarter.entitlements.gpuCredits.allowance!;
let overagePrice = priceToInvoiceAmount({
price: usagePrice,
overage,
});
let calculatedTotal = new Decimal(baseDiff)
.plus(overagePrice)
.toDecimalPlaces(2)
.toNumber();
expect(invoices[0].total).to.equal(calculatedTotal);
});
});

View File

@@ -0,0 +1,175 @@
import { Customer } from "@autumn/shared";
import chalk from "chalk";
import { compareMainProduct } from "../../utils/compare.js";
import { AutumnCli } from "../../cli/AutumnCli.js";
import { advanceProducts, creditSystems } from "../../global.js";
import { expect } from "chai";
import { advanceClockForInvoice } from "../../utils/stripeUtils.js";
import {
sendGPUEvents,
checkCreditBalance,
checkUsageInvoiceAmount,
} from "../../utils/advancedUsageUtils.js";
import Stripe from "stripe";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { setupBefore } from "tests/before.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);
let 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 function () {
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(2);
});
it("should send 20 events and have correct balance", async function () {
let 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 function () {
await advanceClockForInvoice({
stripeCli,
testClockId,
waitForMeterUpdate: true,
});
const res = await AutumnCli.getCustomer(customerId);
const invoices = res!.invoices;
let 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,
// sb: this.sb,
// env: this.env,
// productId: advanceProducts.gpuStarterAnnual.id,
// });
// // Get billing meter event summary
// let eventSummary = await checkBillingMeterEventSummary({
// stripeCli,
// startTime: addMonths(new Date(), 11),
// stripeMeterId: usagePrice?.config?.stripe_meter_id,
// stripeCustomerId: customer.processor.id,
// });
// try {
// assert.exists(eventSummary);
// assert.equal(
// eventSummary?.aggregated_value,
// Math.round(totalCreditsUsed),
// );
// assert.equal(invoices.length, 13 + 2);
// } catch (error) {
// console.group();
// console.log(" - Event summary: ", eventSummary);
// console.log(" - Total credits used: ", totalCreditsUsed);
// console.log(" - Last 3 invoices: ", invoices.slice(-3));
// console.groupEnd();
// throw error;
// }
// });

View File

@@ -173,35 +173,35 @@ describe(chalk.yellowBright("05_cancel"), () => {
await timeout(5000);
});
// TODO: Edit so that it doesn't auto cancel for unit test org
it("should have expired / past_dued pro product", async function () {
const cusRes = await AutumnCli.getCustomer(customerId);
// // TODO: Edit so that it doesn't auto cancel for unit test org
// it("should have expired / past_dued pro product", async function () {
// const cusRes = await AutumnCli.getCustomer(customerId);
let org = this.org;
// let org = this.org;
console.log("Cancel on past due:", org.config.cancel_on_past_due);
if (org.config.cancel_on_past_due) {
const proProduct = getProductFromCusRes({
cusRes,
productId: alexProducts.pro.id,
});
// console.log("Cancel on past due:", org.config.cancel_on_past_due);
// if (org.config.cancel_on_past_due) {
// const proProduct = getProductFromCusRes({
// cusRes,
// productId: alexProducts.pro.id,
// });
expect(proProduct).to.not.exist;
// expect(proProduct).to.not.exist;
const freeProduct = getProductFromCusRes({
cusRes,
productId: alexProducts.free.id,
});
// const freeProduct = getProductFromCusRes({
// cusRes,
// productId: alexProducts.free.id,
// });
expect(freeProduct).to.exist;
expect(freeProduct.status).to.equal(CusProductStatus.Active);
} else {
compareMainProduct({
sent: alexProducts.pro,
cusRes,
status: CusProductStatus.PastDue,
});
}
});
// expect(freeProduct).to.exist;
// expect(freeProduct.status).to.equal(CusProductStatus.Active);
// } else {
// compareMainProduct({
// sent: alexProducts.pro,
// cusRes,
// status: CusProductStatus.PastDue,
// });
// }
// });
});
});

View File

@@ -0,0 +1,210 @@
// import { createStripeCli } from "@/external/stripe/utils.js";
// import { AutumnCli } from "../cli/AutumnCli.js";
// import { features, products } from "../global.js";
// import { initCustomer } from "../utils/init.js";
// import { timeout } from "../utils/genUtils.js";
// import chalk from "chalk";
// import {
// checkFeatureHasCorrectBalance,
// compareMainProduct,
// } from "../utils/compare.js";
// import { expect } from "chai";
// import {
// advanceTestClock,
// completeCheckoutForm,
// } from "../utils/stripeUtils.js";
// import { CusProductStatus } from "@autumn/shared";
// import { attachFailedPaymentMethod } from "@/external/stripe/stripeCusUtils.js";
// import { addDays, addMonths } from "date-fns";
// describe(`${chalk.yellowBright(
// "03_cancel: Testing cancel (at period end and now)",
// )}`, () => {
// const customerId = "cancelCustomer";
// before(async function () {
// this.timeout(30000);
// await initCustomer({
// customer_data: {
// id: customerId,
// name: "Test Customer",
// email: "test@test.com",
// },
// db: this.db,
// org: this.org,
// env: this.env,
// });
// });
// it("should attach pro product", async function () {
// this.timeout(30000);
// const res: any = await AutumnCli.attach({
// customerId: customerId,
// productId: products.pro.id,
// });
// await completeCheckoutForm(res.checkout_url);
// await timeout(5000);
// console.log(` ${chalk.greenBright("Attached pro product")}`);
// });
// // 1. Cancel pro product
// it("should cancel pro product (at period end)", async function () {
// this.timeout(10000);
// const stripeCli = createStripeCli({ org: this.org, env: this.env });
// // 1. Cancel pro product
// const cusRes: any = await AutumnCli.getCustomer(customerId);
// const proProduct = cusRes.products.find(
// (p: any) => p.id === products.pro.id,
// );
// for (const subId of proProduct.subscription_ids) {
// await stripeCli.subscriptions.update(subId, {
// cancel_at_period_end: true,
// });
// }
// await timeout(3000);
// console.log(` ${chalk.greenBright("Cancelled pro product")}`);
// });
// it("should have pro product active, and canceled_at != null", async function () {
// this.timeout(10000);
// const cusRes: any = await AutumnCli.getCustomer(customerId);
// compareMainProduct({
// sent: products.pro,
// cusRes: cusRes,
// });
// const proProduct = cusRes.products.find(
// (p: any) => p.id === products.pro.id,
// );
// expect(proProduct.canceled_at).to.not.equal(null);
// expect(proProduct.status).to.equal(CusProductStatus.Active);
// });
// // CANCEL SUB NOW, SUBSCRIPTION.DELETED WEBHOOK
// it("should cancel pro product (now)", async function () {
// this.timeout(10000);
// const stripeCli = createStripeCli({ org: this.org, env: this.env });
// const cusRes: any = await AutumnCli.getCustomer(customerId);
// const proProduct = cusRes.products.find(
// (p: any) => p.id === products.pro.id,
// );
// for (const subId of proProduct.subscription_ids) {
// await stripeCli.subscriptions.cancel(subId);
// }
// await timeout(3000);
// console.log(` ${chalk.greenBright("Cancelled pro product now")}`);
// });
// it("should have free product active, and pro product not returned", async function () {
// this.timeout(10000);
// const cusRes: any = await AutumnCli.getCustomer(customerId);
// compareMainProduct({
// sent: products.free,
// cusRes: cusRes,
// });
// });
// it("should have correct entitlements (for free)", async function () {
// for (const entitlement of Object.values(products.free.entitlements)) {
// let feature = features[entitlement.feature_id!];
// await checkFeatureHasCorrectBalance({
// customerId,
// feature: feature,
// entitlement,
// expectedBalance: entitlement.allowance || 0,
// });
// }
// });
// });
// describe(`${chalk.yellowBright(
// "03_cancel: Testing subscription past_due",
// )}`, () => {
// const customerId = "03_cancel_past_due";
// before(async function () {
// this.timeout(30000);
// const stripeCli = createStripeCli({ org: this.org, env: this.env });
// const testClock = await stripeCli.testHelpers.testClocks.create({
// frozen_time: Math.round(Date.now() / 1000),
// });
// this.testClockId = testClock.id;
// await initCustomer({
// customer_data: {
// id: customerId,
// name: "Test Customer",
// email: "test@test.com",
// },
// db: this.db,
// org: this.org,
// env: this.env,
// attachPm: true,
// testClockId: testClock.id,
// });
// });
// it("should attach pro product", async function () {
// this.timeout(10000);
// await AutumnCli.attach({
// customerId: customerId,
// productId: products.pro.id,
// });
// });
// it("should attach failed payment method and advance to next billing date", async function () {
// // 1. Swap customer's card
// const stripeCli = createStripeCli({ org: this.org, env: this.env });
// const cusRes: any = await AutumnCli.getCustomer(customerId);
// await attachFailedPaymentMethod({
// stripeCli,
// customer: cusRes.customer,
// });
// // const advanceDate = addDays(addMonths(new Date(), 1), 1);
// // await stripeCli.testHelpers.testClocks.advance(this.testClockId, {
// // frozen_time: Math.round(advanceDate.getTime() / 1000),
// // });
// await advanceTestClock({
// stripeCli,
// testClockId: this.testClockId,
// advanceTo: addDays(addMonths(new Date(), 1), 1).getTime(),
// });
// });
// it("should have free product active and correct entitlements", async function () {
// const cusRes: any = await AutumnCli.getCustomer(customerId);
// // compareMainProduct({
// // sent: products.free,
// // cusRes: cusRes,
// // });
// // TODO: Check why this line messes up the test
// // compareProductEntitlements({
// // customerId,
// // product: products.free,
// // features,
// // });
// });
// });

View File

@@ -9,7 +9,7 @@ import { compareMainProduct } from "../utils/compare.js";
import { AutumnCli } from "../cli/AutumnCli.js";
import chalk from "chalk";
describe(`${chalk.yellowBright("Testing pkey")}`, () => {
describe(`${chalk.yellowBright("08_pkey: Testing publishable key")}`, () => {
// 1. Initialize customer with card
let customerId = "pkeyTestCustomer";
const bearerPublicAxios = getPublicAxiosInstance({
@@ -99,8 +99,6 @@ describe(`${chalk.yellowBright("Testing pkey")}`, () => {
});
it("should return error if try to downgrade to free", async function () {
this.timeout(30000);
try {
await bearerPublicAxios.post("/v1/attach", {
customer_id: customerId,
@@ -116,7 +114,6 @@ describe(`${chalk.yellowBright("Testing pkey")}`, () => {
// Next, check entitled for pro
it("should return correct metered1 amount for pro", async function () {
this.timeout(30000);
const { data } = await bearerPublicAxios.post("/v1/entitled", {
customer_id: customerId,
feature_id: features.metered1.id,
@@ -133,7 +130,6 @@ describe(`${chalk.yellowBright("Testing pkey")}`, () => {
});
it("should return same balance for entitled with bearer and x-publishable-key", async function () {
this.timeout(30000);
const { data } = await bearerPublicAxios.post("/v1/entitled", {
customer_id: customerId,
feature_id: features.metered1.id,
@@ -150,7 +146,6 @@ describe(`${chalk.yellowBright("Testing pkey")}`, () => {
});
it("should return error when try to send event", async function () {
this.timeout(30000);
try {
await bearerPublicAxios.post("/v1/events", {
customer_id: customerId,

View File

@@ -275,7 +275,4 @@ describe(`${chalk.yellowBright(
startingBalance: balance,
});
});
// TODO: Test reset at for in arrear prorated with Ent Interval = Lifetime
// TODO: Test in arrear prorated for entitlements with billing units > 1
});

View File

@@ -0,0 +1,188 @@
import chalk from "chalk";
import Stripe from "stripe";
import { APIVersion, Customer } from "@autumn/shared";
import { createStripeCli } from "@/external/stripe/utils.js";
import { getOriginalCouponId } from "@/internal/rewards/rewardUtils.js";
import { getPriceForOverage } from "@/internal/products/prices/priceUtils.js";
import { expect } from "chai";
import { addHours, addMonths } from "date-fns";
import { features, products, rewards } from "tests/global.js";
import { getFixedPriceAmount, timeout } from "tests/utils/genUtils.js";
import { AutumnCli } from "tests/cli/AutumnCli.js";
import { compareMainProduct } from "tests/utils/compare.js";
import {
advanceClockForInvoice,
advanceTestClock,
completeCheckoutForm,
getDiscount,
} from "tests/utils/stripeUtils.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { setupBefore } from "tests/before.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
const testCase = "coupon1";
describe(
chalk.yellow(`${testCase} -- Testing one-off rollover, apply to all`),
() => {
let customerId = "coupon1";
let stripeCli: Stripe;
let customer: Customer;
let testClockId: string;
let db, org, env;
let autumn: AutumnInt = new AutumnInt({ version: APIVersion.v1_4 });
let couponAmount = rewards.rolloverAll.discount_config.discount_value;
before(async function () {
await setupBefore(this);
db = this.db;
org = this.org;
env = this.env;
autumn = this.autumnJs;
stripeCli = this.stripeCli;
const res = await initCustomer({
customerId,
org: this.org,
env: this.env,
db: this.db,
autumn: this.autumnJs,
});
testClockId = res.testClockId;
customer = res.customer;
});
// CYCLE 0
it("CYCLE 0: should attach pro with overage (through checkout)", async () => {
couponAmount -= getFixedPriceAmount(products.proWithOverage);
const res = await AutumnCli.attach({
customerId,
productId: products.proWithOverage.id,
forceCheckout: true,
});
await completeCheckoutForm(
res.checkout_url,
undefined,
rewards.rolloverAll.id,
);
await timeout(20000);
const cusRes = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: products.proWithOverage,
cusRes,
});
});
it("CYCLE 0: should have $0 invoice and correct remaining coupon amount", async () => {
const cusRes = await AutumnCli.getCustomer(customerId);
expect(cusRes.invoices[0].total).to.equal(0);
const cusDiscount = await getDiscount({
stripeCli: stripeCli,
customer: cusRes.customer,
});
try {
expect(getOriginalCouponId(cusDiscount.coupon?.id)).to.equal(
rewards.rolloverAll.id,
);
// Expect amount to be original amount - pro price
expect(cusDiscount.coupon?.amount_off).to.equal(couponAmount * 100);
} catch (error) {
console.error("--------------------------------");
console.error(
"Expected stripe cus to have coupon",
rewards.rolloverAll,
);
console.error("Actual stripe cus discount", cusDiscount);
throw error;
}
});
// CYCLE 1
it("CYCLE 1: should set usage to -100 and advance clock by 1 month", async () => {
const usage = 100;
const res = await AutumnCli.usage({
customerId,
featureId: features.metered1.id,
value: usage,
});
// Price
const price = getPriceForOverage(
products.proWithOverage.prices[1],
-(products.proWithOverage.entitlements.metered1.allowance! - usage),
);
couponAmount =
couponAmount - (price + getFixedPriceAmount(products.proWithOverage));
await advanceClockForInvoice({
stripeCli,
testClockId,
waitForMeterUpdate: true,
});
});
it("CYCLE 1: should have $0 invoice and correct new coupon amount", async () => {
const cusRes = await AutumnCli.getCustomer(customerId);
expect(cusRes.invoices[0].total).to.equal(0);
const cusDiscount = await getDiscount({
stripeCli: stripeCli,
customer,
});
try {
expect(getOriginalCouponId(cusDiscount.coupon?.id)).to.equal(
rewards.rolloverAll.id,
);
expect(cusDiscount.coupon?.amount_off).to.equal(couponAmount * 100);
} catch (error) {
console.log("--------------------------------");
console.log("coupon1, cycle 1 failed");
console.log("Expected stripe cus to have coupon", rewards.rolloverAll);
console.log("Actual stripe cus discount", cusDiscount);
throw error;
}
});
// CYCLE 2
it("CYCLE 2: should have $0 invoice and correct new coupon amount after 2nd cycle", async () => {
await timeout(20000);
let advanceTo = addHours(addMonths(new Date(), 2), 2);
await advanceTestClock({
stripeCli,
testClockId,
advanceTo: advanceTo.getTime(),
});
const cusDiscount = await getDiscount({
stripeCli: stripeCli,
customer,
});
const newCouponAmount =
couponAmount - getFixedPriceAmount(products.proWithOverage);
try {
expect(cusDiscount.coupon?.amount_off).to.equal(newCouponAmount * 100);
} catch (error) {
console.log("--------------------------------");
console.log("coupon1, cycle 2 failed");
console.log("Expected coupon amount", newCouponAmount * 100);
console.log("Stripe cus discount", cusDiscount);
throw error;
}
});
},
);

View File

@@ -0,0 +1,381 @@
// import { Autumn } from "@/external/autumn/autumnCli.js";
// import { setupBefore } from "tests/before.js";
// import { CusProductStatus, organizations } from "@autumn/shared";
// import { getFeaturePrice, getUsagePriceTiers } from "tests/utils/genUtils.js";
// import { entityProducts, features } from "../global.js";
// import { Stripe } from "stripe";
// import { checkBalance } from "tests/utils/autumnUtils.js";
// import { initCustomerWithTestClock } from "tests/utils/testInitUtils.js";
// import { advanceTestClock } from "tests/utils/stripeUtils.js";
// import { addDays, addHours, addMonths } from "date-fns";
// import { CacheManager } from "@/external/caching/CacheManager.js";
// import { CacheType } from "@/external/caching/cacheActions.js";
// import { hashApiKey } from "@/internal/dev/api-keys/apiKeyUtils.js";
// import { compareMainProduct } from "../utils/compare.js";
// import { assert, expect } from "chai";
// import chalk from "chalk";
// import { DrizzleCli } from "@/db/initDrizzle.js";
// import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
// import { eq } from "drizzle-orm";
// // Check balance and stripe quantity
// const checkEntAndStripeQuantity = async ({
// db,
// autumn,
// stripeCli,
// featureId,
// customerId,
// expectedBalance,
// expectedUsage,
// expectedStripeQuantity,
// }: {
// db: DrizzleCli;
// autumn: Autumn;
// stripeCli: Stripe;
// featureId: string;
// customerId: string;
// expectedBalance: number;
// expectedUsage?: number;
// expectedStripeQuantity: number;
// }) => {
// let { customer, entitlements, products } =
// await autumn.customers.get(customerId);
// let cusProducts = await CusProductService.list({
// db,
// internalCustomerId: customer.internal_id,
// inStatuses: [CusProductStatus.Active],
// });
// let entitlement = entitlements.find((e: any) => e.feature_id == featureId);
// expect(entitlement.balance).to.equal(expectedBalance);
// if (expectedUsage) {
// expect(entitlement.used).to.equal(
// expectedUsage,
// `Get customer ${customerId} returned incorrect "used" for feature ${featureId}`,
// );
// }
// if (products.length == 0) {
// assert.fail(`Get customer ${customerId} returned no products`);
// }
// // 2. Get stripe quantity
// let mainProduct = products[0];
// if (mainProduct.subscription_ids.length == 0) {
// assert.fail(`Get customer ${customerId} returned no subscriptions`);
// }
// let price = getFeaturePrice({
// product: mainProduct,
// featureId: featureId,
// cusProducts,
// });
// if (!price) {
// assert.fail(
// `Get customer ${customerId} returned no price for feature ${featureId}`,
// );
// }
// let stripeSub = await stripeCli.subscriptions.retrieve(
// mainProduct.subscription_ids[0],
// );
// let subItem = stripeSub.items.data.find(
// (item: any) => item.price.id == price.config!.stripe_price_id,
// );
// if (!subItem) {
// assert.fail(
// `Get customer ${customerId} returned no sub item for feature ${featureId}`,
// );
// }
// expect(subItem.quantity).to.equal(
// expectedStripeQuantity,
// `Get customer ${customerId} returned incorrect stripe quantity for feature ${featureId}`,
// );
// };
// // UNCOMMENT FROM HERE
// describe(`${chalk.yellowBright("entities1: Testing entities")}`, () => {
// let customerId = "entity1";
// let autumn: Autumn;
// let stripeCli: Stripe;
// let usageTiers = getUsagePriceTiers({
// product: entityProducts.entityPro,
// featureId: features.seats.id,
// });
// let testClockId: string;
// before(async function () {
// await setupBefore(this);
// autumn = this.autumn;
// stripeCli = this.stripeCli;
// const { testClockId: testClockId1 } = await initCustomerWithTestClock({
// customerId,
// db: this.db,
// org: this.org,
// env: this.env,
// });
// testClockId = testClockId1;
// // Update org config
// await this.db
// .update(organizations)
// .set({
// config: {
// ...this.org.config,
// prorate_unused: false,
// },
// })
// .where(eq(organizations.id, this.org.id));
// await CacheManager.invalidate({
// action: CacheType.SecretKey,
// value: hashApiKey(process.env.UNIT_TEST_AUTUMN_SECRET_KEY!),
// });
// await CacheManager.disconnect();
// });
// let firstEntityId = "1";
// it("should attach entityFree product", async function () {
// await this.autumn.attach({
// customerId,
// productId: entityProducts.entityFree.id,
// });
// await this.autumn.entities.create(customerId, {
// id: firstEntityId,
// name: "1@gmail.com",
// featureId: features.seats.id,
// });
// // Check if entity is created
// let res = await this.autumn.entities.list(customerId);
// let entities = res.data;
// expect(entities).to.have.lengthOf(1);
// expect(entities[0].id).to.equal(firstEntityId);
// });
// it("should successfully remove created entity", async function () {
// await this.autumn.entities.delete(customerId, firstEntityId);
// let res = await this.autumn.entities.list(customerId);
// let entities = res.data;
// expect(entities).to.have.lengthOf(0);
// });
// it("should create first entity, then attach entityPro product", async function () {
// await this.autumn.entities.create(customerId, {
// id: firstEntityId,
// name: "1@gmail.com",
// featureId: features.seats.id,
// });
// await this.autumn.attach({
// customerId,
// productId: entityProducts.entityPro.id,
// });
// // Check product is attached correctly
// let cusRes = await autumn.customers.get(customerId);
// compareMainProduct({
// sent: entityProducts.entityPro,
// cusRes: cusRes,
// });
// let { invoices } = cusRes;
// let usageTiers = getUsagePriceTiers({
// product: entityProducts.entityPro,
// featureId: features.seats.id,
// });
// // Check invoice is created correctly
// expect(invoices).to.have.lengthOf(1);
// expect(invoices[0].total).to.equal(usageTiers[0].amount);
// // Check balance and stripe quantity
// await checkEntAndStripeQuantity({
// db: this.db,
// autumn,
// stripeCli,
// featureId: features.seats.id,
// customerId,
// expectedBalance: -1,
// expectedStripeQuantity: 1,
// });
// await checkBalance({
// autumn,
// featureId: features.metered1.id,
// customerId,
// expectedBalance:
// entityProducts.entityPro.entitlements.metered1.allowance!,
// });
// });
// let newEntities = [
// {
// id: "2",
// name: "2@gmail.com",
// featureId: features.seats.id,
// },
// {
// id: "3",
// name: "3@gmail.com",
// featureId: features.seats.id,
// },
// {
// id: "4",
// name: "4@gmail.com",
// featureId: features.seats.id,
// },
// ];
// it("should create 3 additional entities and be charged immediately", async function () {
// let advanceToDay = addDays(new Date(), 1).getTime();
// await advanceTestClock({
// stripeCli,
// testClockId,
// advanceTo: advanceToDay,
// waitForSeconds: 20,
// });
// await this.autumn.entities.create(customerId, newEntities);
// let entitiesRes = await this.autumn.entities.list(customerId);
// let entities = entitiesRes.data;
// expect(entities).to.have.lengthOf(newEntities.length + 1);
// let cusRes = await autumn.customers.get(customerId);
// let { invoices } = cusRes;
// expect(invoices[0].total).to.equal(
// usageTiers[0].amount * newEntities.length,
// );
// });
// it("should remove 2 entities, and have correct balance / stripe quantity", async function () {
// await this.autumn.entities.delete(customerId, newEntities[0].id);
// await checkEntAndStripeQuantity({
// db: this.db,
// autumn,
// stripeCli,
// featureId: features.seats.id,
// customerId,
// expectedBalance: -(newEntities.length + 1),
// expectedStripeQuantity: newEntities.length + 1 - 1,
// expectedUsage: newEntities.length + 1 - 1,
// });
// await this.autumn.entities.delete(customerId, newEntities[1].id);
// await checkEntAndStripeQuantity({
// db: this.db,
// autumn,
// stripeCli,
// featureId: features.seats.id,
// customerId,
// expectedBalance: -(newEntities.length + 1),
// expectedStripeQuantity: newEntities.length + 1 - 2,
// expectedUsage: newEntities.length + 1 - 2,
// });
// });
// let newEntities2 = [
// {
// id: "5",
// name: "5@gmail.com",
// featureId: features.seats.id,
// },
// {
// id: "6",
// name: "6@gmail.com",
// featureId: features.seats.id,
// },
// {
// id: "7",
// name: "7@gmail.com",
// featureId: features.seats.id,
// },
// ];
// it("should create three additional seats, and be charged for only one", async function () {
// await this.autumn.entities.create(customerId, newEntities2);
// let totalSeats = newEntities2.length + 2;
// await checkEntAndStripeQuantity({
// db: this.db,
// autumn,
// stripeCli,
// featureId: features.seats.id,
// customerId,
// expectedBalance: -totalSeats,
// expectedStripeQuantity: totalSeats,
// expectedUsage: totalSeats,
// });
// let cusRes = await autumn.customers.get(customerId);
// let { invoices } = cusRes;
// expect(invoices[0].total).to.equal(usageTiers[0].amount);
// });
// // return;
// it("should remove one entity, and have correct balance / stripe quantity after advancing test clock", async function () {
// await this.autumn.entities.delete(customerId, newEntities2[0].id);
// let totalSeats = newEntities2.length + 1;
// let advanceTo = addHours(addMonths(new Date(), 1), 4).getTime();
// await advanceTestClock({ stripeCli, testClockId, advanceTo });
// // Get entities
// let { data: entities } = await this.autumn.entities.list(customerId);
// expect(entities).to.have.lengthOf(totalSeats);
// await checkEntAndStripeQuantity({
// db: this.db,
// autumn,
// stripeCli,
// featureId: features.seats.id,
// customerId,
// expectedBalance: -totalSeats,
// expectedStripeQuantity: totalSeats,
// expectedUsage: totalSeats,
// });
// await checkBalance({
// autumn,
// featureId: features.metered1.id,
// customerId,
// expectedBalance:
// totalSeats * entityProducts.entityPro.entitlements.metered1.allowance!,
// });
// });
// after(async function () {
// await this.db
// .update(organizations)
// .set({
// config: {
// ...this.org.config,
// prorate_unused: true,
// },
// })
// .where(eq(organizations.id, this.org.id));
// void CacheManager.invalidate({
// action: CacheType.SecretKey,
// value: hashApiKey(process.env.UNIT_TEST_AUTUMN_SECRET_KEY!),
// });
// });
// });

View File

@@ -0,0 +1,199 @@
// THIS TEST CASE IS COVERED UNDER UPGRADE2.TS
// import { Customer } from "@autumn/shared";
// import chalk from "chalk";
// import { compareMainProduct } from "../../utils/compare.js";
// import { AutumnCli } from "../../cli/AutumnCli.js";
// import { advanceProducts, creditSystems } from "../../global.js";
// import { timeout } from "../../utils/genUtils.js";
// import { assert, expect } from "chai";
// import { createStripeCli } from "@/external/stripe/utils.js";
// import {
// advanceClockForInvoice,
// advanceMonths,
// advanceTestClock,
// checkBillingMeterEventSummary,
// getUsageInArrearPrice,
// } from "../../utils/stripeUtils.js";
// import { addMonths } from "date-fns";
// import {
// sendGPUEvents,
// checkUsageInvoiceAmount,
// } from "../../utils/advancedUsageUtils.js";
// import { Decimal } from "decimal.js";
// import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
// import { setupBefore } from "tests/before.js";
// import Stripe from "stripe";
// // FOURTH, TEST GPU STARTER ANNUAL UPGRADE TO GPU PRO
// const testCase = "usage5";
// describe(`${chalk.yellowBright("usage5: multi interval upgrade, GPU starter annual -> GPU pro annual")}`, () => {
// const customerId = testCase;
// let testClockId = "";
// let totalCreditsUsed = 0;
// let customer: Customer;
// let stripeCli: Stripe;
// let curTime = new Date();
// before(async function () {
// await setupBefore(this);
// let 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 function () {
// await AutumnCli.attach({
// customerId: customerId,
// productId: advanceProducts.gpuStarterAnnual.id,
// });
// const res = await AutumnCli.getCustomer(customerId);
// compareMainProduct({
// sent: advanceProducts.gpuStarterAnnual,
// cusRes: res,
// });
// });
// it(`should advance 1 month and upgrade to GPU pro monthly`, async function () {
// let numberOfMonths = 1;
// await advanceMonths({
// stripeCli,
// testClockId,
// numberOfMonths,
// });
// curTime = addMonths(curTime, numberOfMonths);
// // Send 20 events
// let eventCount = 20;
// const { creditsUsed } = await sendGPUEvents({
// customerId,
// eventCount,
// });
// totalCreditsUsed = creditsUsed;
// await AutumnCli.attach({
// customerId: customerId,
// productId: advanceProducts.gpuProAnnual.id,
// });
// await advanceTestClock({
// stripeCli,
// testClockId,
// numberOfDays: 10,
// startingFrom: curTime,
// });
// });
// it("should have GPU pro annual product and 2 Stripe subscriptions", async function () {
// const res = await AutumnCli.getCustomer(customerId);
// compareMainProduct({
// sent: advanceProducts.gpuProAnnual,
// cusRes: res,
// });
// // Should have 2 subscriptions
// const subs = await stripeCli.subscriptions.list({
// customer: customer.processor.id,
// });
// expect(subs.data.length).to.equal(2);
// });
// it("should have correct invoice for GPU starter annual (bill for remaining usages)", async function () {
// const res = await AutumnCli.getCustomer(customerId);
// const invoices = res!.invoices;
// let 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,
// });
// });
// it("should send 20 events (on GPU pro annual)", async function () {
// const stripeCli = createStripeCli({ org: this.org, env: this.env });
// // Send 20 events
// let eventCount = 20;
// const { creditsUsed } = await sendGPUEvents({
// customerId,
// eventCount,
// });
// totalCreditsUsed = creditsUsed;
// await advanceClockForInvoice({
// stripeCli,
// testClockId,
// waitForMeterUpdate: true,
// startingFrom: curTime,
// });
// });
// it("should have correct billing meter event summary for GPU pro annual", async function () {
// const res = await AutumnCli.getCustomer(customerId);
// const invoices = res!.invoices;
// // Think I have to use Stripe metered event summary to check this
// let usagePrice = await getUsageInArrearPrice({
// org: this.org,
// sb: this.sb,
// env: this.env,
// productId: advanceProducts.gpuProAnnual.id,
// });
// let eventSummary = await checkBillingMeterEventSummary({
// stripeCli,
// startTime: curTime, // Wrong date?
// stripeMeterId: usagePrice?.config?.stripe_meter_id,
// stripeCustomerId: customer.processor.id,
// });
// let roundedFirst = Math.ceil(
// new Decimal(totalCreditsUsed)
// .div(usagePrice?.config?.billing_units!)
// .toNumber(),
// );
// let roundedTotalCreditsUsed = new Decimal(roundedFirst)
// .mul(usagePrice?.config?.billing_units!)
// .toNumber();
// try {
// assert.exists(eventSummary);
// assert.equal(eventSummary?.aggregated_value, roundedTotalCreditsUsed);
// } catch (error) {
// console.group();
// console.log(" - Event summary: ", eventSummary);
// console.log(" - Total credits used: ", totalCreditsUsed);
// console.groupEnd();
// throw error;
// }
// // await checkUsageInvoiceAmount({
// // invoices,
// // totalUsage: totalCreditsUsed,
// // product: advanceProducts.gpuProAnnual,
// // featureId: creditSystems.gpuCredits.id,
// // invoiceIndex: 0,
// // includeBase: false,
// // });
// });
// });

View File

@@ -0,0 +1,332 @@
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { assert, expect } from "chai";
import chalk from "chalk";
import { setupBefore } from "tests/before.js";
import { AutumnCli } from "tests/cli/AutumnCli.js";
import { features, products } from "tests/global.js";
import { compareMainProduct } from "tests/utils/compare.js";
import { completeCheckoutForm } from "tests/utils/stripeUtils.js";
import { timeout } from "tests/utils/genUtils.js";
const oneTimeQuantity = 2;
const oneTimePurchaseCount = 2;
const oneTimeOverrideQuantity = 4;
const monthlyQuantity = 2;
// UNCOMMENT FROM HERE
const testCase = "basic1";
describe(`${chalk.yellowBright(
"basic1: Testing attach -- free, pro & one-time / monthly add on",
)}`, () => {
let customerId = testCase;
let autumn: AutumnInt = new AutumnInt();
let db, org, env;
before(async function () {
await setupBefore(this);
db = this.db;
org = this.org;
env = this.env;
await initCustomer({
autumn: this.autumnJs,
customerId,
db,
org,
env,
fingerprint: "test",
});
});
it("should create customer and have default free active", async function () {
const data = await autumn.customers.get(customerId);
compareMainProduct({
sent: products.free,
cusRes: data,
});
});
it("should have correct entitlements", async function () {
const data = await autumn.customers.get(customerId);
const expectedEntitlement = products.free.entitlements.metered1;
const entitled = (await autumn.check({
customer_id: customerId,
feature_id: features.metered1.id,
})) as any;
const metered1Balance = entitled.balances.find(
(balance: any) => balance.feature_id === features.metered1.id,
);
expect(entitled.allowed).to.be.true;
expect(metered1Balance).to.exist;
expect(metered1Balance.balance).to.equal(expectedEntitlement.allowance);
expect(metered1Balance.unlimited).to.not.exist;
});
it("should have correct boolean1 entitlement", async function () {
const entitled = await AutumnCli.entitled(customerId, features.boolean1.id);
expect(entitled!.allowed).to.be.false;
});
});
// describe("Attach pro -- check products & entitlements", () => {
// it("POST /attach -- attaching pro (force checkout)", async function () {
// const { checkout_url } = await AutumnCli.attach({
// customerId: customerId,
// productId: products.pro.id,
// });
// await completeCheckoutForm(checkout_url);
// await timeout(10000); // for webhook to be processed
// console.log(` ${chalk.greenBright("Attached pro")}`);
// });
// it("GET /customers/:id -- checking product & entitlements (pro)", async function () {
// const res = await AutumnCli.getCustomer(customerId);
// // console.log("Res: ", res);
// compareMainProduct({
// sent: products.pro,
// cusRes: res,
// });
// expect(res.invoices.length).to.be.greaterThan(0);
// });
// // return;
// it("GET /entitled -- checking entitlements for metered1 && boolean1", async function () {
// const proEntitlements = products.pro.entitlements;
// for (const entitlement of Object.values(proEntitlements)) {
// const allowance = entitlement.allowance;
// const res: any = await AutumnCli.entitled(
// customerId,
// entitlement.feature_id!,
// );
// const entBalance = res!.balances.find(
// (b: any) => b.feature_id === entitlement.feature_id,
// );
// try {
// expect(res!.allowed).to.be.true;
// expect(entBalance).to.exist;
// if (entitlement.allowance) {
// expect(entBalance!.balance).to.equal(allowance);
// }
// // console.log(` - ${entitlement.feature_id} -- Passed`);
// } catch (error) {
// console.group();
// console.group();
// console.log("Looking for: ", entitlement);
// console.log("Received: ", res);
// console.groupEnd();
// console.groupEnd();
// throw error;
// }
// }
// });
// });
// const oneTimeBillingUnits =
// products.oneTimeAddOnMetered1.prices[0].config.billing_units;
// const monthlyBillingUnits =
// products.monthlyAddOnMetered1.prices[0].config.billing_units;
// describe("One time add on (force checkout)", () => {
// // PURCHASE ONE TIME ADD ON
// it("POST /attach -- attaching one time add on (force checkout) [no quantity passed in]", async function () {
// try {
// for (let i = 0; i < oneTimePurchaseCount; i++) {
// const res = await AutumnCli.attach({
// customerId: customerId,
// productId: products.oneTimeAddOnMetered1.id,
// forceCheckout: true,
// });
// await completeCheckoutForm(res.checkout_url, oneTimeOverrideQuantity);
// await timeout(10000); // for webhook to be processed
// console.log(` ${chalk.greenBright("Attached one time add on")}`);
// }
// } catch (error) {
// console.group();
// console.group();
// console.log("Failed to attach one time add on");
// console.log("Error data:", error);
// console.groupEnd();
// console.groupEnd();
// process.exit(1);
// }
// });
// // TODO: Attach one time add on again (with quantity?)
// it("GET /customers/:id -- checking product & entitlements (one time add on)", async function () {
// const cusRes = await AutumnCli.getCustomer(customerId);
// // 1. Metered1 balance should be pro + one time add on
// // Fetch balance
// const addOnBalance = cusRes.entitlements.find(
// (e: any) =>
// e.feature_id === features.metered1.id &&
// e.interval ==
// products.oneTimeAddOnMetered1.entitlements.metered1.interval,
// );
// const expectedAmt =
// (oneTimeOverrideQuantity || oneTimeQuantity) *
// oneTimeBillingUnits *
// oneTimePurchaseCount;
// try {
// assert.equal(addOnBalance!.balance, expectedAmt);
// assert.equal(cusRes.add_ons.length, 1);
// assert.equal(cusRes.add_ons[0].id, products.oneTimeAddOnMetered1.id);
// assert.equal(cusRes.invoices.length, 1 + oneTimePurchaseCount);
// } catch (error) {
// console.group();
// console.group();
// console.log("GET customer, balances failed");
// console.log(
// "Add on entitlement:",
// products.oneTimeAddOnMetered1.entitlements.metered1,
// );
// console.log("Customer entitlements:", cusRes.entitlements);
// console.groupEnd();
// console.groupEnd();
// throw error;
// }
// });
// it("GET /entitled -- checking entitled for metered1", async function () {
// const res: any = await AutumnCli.entitled(
// customerId,
// features.metered1.id,
// );
// expect(res!.allowed).to.be.true;
// // pro metered1
// const proMetered1Amt = products.pro.entitlements.metered1.allowance;
// const addOnBalance = res!.balances.find(
// (b: any) => b.feature_id === features.metered1.id,
// );
// expect(res!.allowed).to.be.true;
// expect(addOnBalance!.balance).to.equal(
// proMetered1Amt! +
// (oneTimeOverrideQuantity || oneTimeQuantity) *
// oneTimeBillingUnits *
// oneTimePurchaseCount,
// );
// });
// });
// // PURCHASE MONTHLY ADD ON
// describe("Monthly add on", () => {
// it("POST /attach -- attaching monthly add on", async function () {
// await AutumnCli.attach({
// customerId: customerId,
// productId: products.monthlyAddOnMetered1.id,
// forceCheckout: false,
// options: [
// {
// feature_id: features.metered1.id,
// quantity: monthlyQuantity * monthlyBillingUnits,
// },
// ],
// });
// await timeout(10000);
// console.log(` ${chalk.greenBright("Attached monthly top up")}`);
// });
// it("GET /customers/:id -- checking product & entitlements (monthly add on)", async function () {
// const cusRes = await AutumnCli.getCustomer(customerId);
// // 1. Metered1 balance should be pro + one time add on
// const proMetered1 = products.pro.entitlements.metered1.allowance;
// // Fetch balance
// const monthlyMetered1Balance = cusRes.entitlements.find(
// (e: any) =>
// e.feature_id === features.metered1.id &&
// e.interval ==
// products.monthlyAddOnMetered1.entitlements.metered1.interval,
// );
// try {
// assert.equal(
// monthlyMetered1Balance!.balance,
// proMetered1! + monthlyQuantity * monthlyBillingUnits,
// );
// assert.equal(cusRes.add_ons.length, 2);
// const monthlyAddOnId = cusRes.add_ons.find(
// (a: any) => a.id === products.monthlyAddOnMetered1.id,
// );
// assert.exists(monthlyAddOnId);
// expect(cusRes.invoices.length).to.equal(2 + oneTimePurchaseCount);
// } catch (error) {
// console.group();
// console.group();
// console.log("GET customer, balances failed");
// console.log(
// "Add on entitlement:",
// products.monthlyAddOnMetered1.entitlements.metered1,
// );
// console.log("Customer entitlements:", cusRes.entitlements);
// console.groupEnd();
// console.groupEnd();
// throw error;
// }
// });
// it("GET /entitled -- checking entitlements (monthly add on)", async function () {
// const res: any = await AutumnCli.entitled(
// customerId,
// features.metered1.id,
// );
// const metered1Balance = res!.balances.find(
// (b: any) => b.feature_id === features.metered1.id,
// );
// const proMetered1Amt = products.pro.entitlements.metered1.allowance;
// const monthlyAddOnMetered1Amt = monthlyQuantity * monthlyBillingUnits;
// const oneTimeAddOnMetered1Amt =
// (oneTimeOverrideQuantity || oneTimeQuantity) *
// oneTimeBillingUnits *
// oneTimePurchaseCount;
// try {
// expect(metered1Balance!.balance).to.equal(
// proMetered1Amt! + monthlyAddOnMetered1Amt + oneTimeAddOnMetered1Amt,
// );
// } catch (error) {
// console.group();
// console.group();
// console.log("GET entitled, balances failed");
// console.log("/entitled response:", res);
// console.log("Pro metered1 amt:", proMetered1Amt);
// console.log("Monthly add on metered1 amt:", monthlyAddOnMetered1Amt);
// console.log("One time add on metered1 amt:", oneTimeAddOnMetered1Amt);
// console.groupEnd();
// console.groupEnd();
// throw error;
// }
// });
// });

View File

@@ -14,14 +14,11 @@ import { completeCheckoutForm } from "tests/utils/stripeUtils.js";
import { Decimal } from "decimal.js";
import { expect } from "chai";
describe(`${chalk.yellowBright("attach3: Multi attach, all one off")}`, () => {
let customerId = "attach3";
const testCase = "basic10";
// let billingUnits =
// oneTimeProducts.oneTimeMetered2.prices[0].config.billing_units;
// let quantity = 1000 * billingUnits;
describe(`${chalk.yellowBright("basic10: Multi attach, all one off")}`, () => {
let customerId = testCase;
let quantity = 1000;
let options = [
{
feature_id: features.metered2.id,
@@ -48,7 +45,7 @@ describe(`${chalk.yellowBright("attach3: Multi attach, all one off")}`, () => {
});
await completeCheckoutForm(res.checkout_url);
await timeout(15000);
await timeout(20000);
});
it("should have correct main product and entitlements", async function () {
@@ -65,7 +62,6 @@ describe(`${chalk.yellowBright("attach3: Multi attach, all one off")}`, () => {
optionsList: options,
});
// Check invoices
const invoices = cusRes.invoices;
const metered1Amount = getFixedPriceAmount(oneTimeProducts.oneTimeMetered1);
const metered2Tiers = getUsagePriceTiers({
@@ -79,13 +75,11 @@ describe(`${chalk.yellowBright("attach3: Multi attach, all one off")}`, () => {
oneTimeProducts.oneTimeMetered2.prices[0].config.billing_units,
);
// console.log("Num billing units: ", numBillingUnits);
expect(invoices[0].total).to.equal(
new Decimal(metered2Amount)
const expectedTotal = new Decimal(metered2Amount)
.mul(numBillingUnits)
.add(metered1Amount)
.toNumber(),
);
.toNumber();
expect(invoices[0].total).to.equal(expectedTotal);
});
});

View File

@@ -0,0 +1,295 @@
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { assert, expect } from "chai";
import chalk from "chalk";
import { setupBefore } from "tests/before.js";
import { AutumnCli } from "tests/cli/AutumnCli.js";
import { features, products } from "tests/global.js";
import { compareMainProduct } from "tests/utils/compare.js";
import { completeCheckoutForm } from "tests/utils/stripeUtils.js";
import { timeout } from "tests/utils/genUtils.js";
const oneTimeQuantity = 2;
const oneTimePurchaseCount = 2;
const oneTimeOverrideQuantity = 4;
const monthlyQuantity = 2;
// UNCOMMENT FROM HERE
const testCase = "basic2";
describe(`${chalk.yellowBright("basic2: Testing attach pro")}`, () => {
let customerId = testCase;
let autumn: AutumnInt = new AutumnInt();
let db, org, env;
before(async function () {
await setupBefore(this);
db = this.db;
org = this.org;
env = this.env;
await initCustomer({
autumn: this.autumnJs,
customerId,
db,
org,
env,
fingerprint: "test",
});
});
it("should attach pro through checkout", async function () {
const { checkout_url } = await autumn.attach({
customer_id: customerId,
product_id: products.pro.id,
});
await completeCheckoutForm(checkout_url);
await timeout(10000);
});
it("should have correct product & entitlements", async function () {
const res = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: products.pro,
cusRes: res,
});
expect(res.invoices.length).to.be.greaterThan(0);
});
// return;
it("should have correct result when calling /check", async function () {
const proEntitlements = products.pro.entitlements;
for (const entitlement of Object.values(proEntitlements)) {
const allowance = entitlement.allowance;
const res: any = await AutumnCli.entitled(
customerId,
entitlement.feature_id!,
);
const entBalance = res!.balances.find(
(b: any) => b.feature_id === entitlement.feature_id,
);
try {
expect(res!.allowed).to.be.true;
expect(entBalance).to.exist;
if (entitlement.allowance) {
expect(entBalance!.balance).to.equal(allowance);
}
// console.log(` - ${entitlement.feature_id} -- Passed`);
} catch (error) {
console.group();
console.group();
console.log("Looking for: ", entitlement);
console.log("Received: ", res);
console.groupEnd();
console.groupEnd();
throw error;
}
}
});
return;
const oneTimeBillingUnits =
products.oneTimeAddOnMetered1.prices[0].config.billing_units;
const monthlyBillingUnits =
products.monthlyAddOnMetered1.prices[0].config.billing_units;
describe("One time add on (force checkout)", () => {
// PURCHASE ONE TIME ADD ON
it("POST /attach -- attaching one time add on (force checkout) [no quantity passed in]", async function () {
try {
for (let i = 0; i < oneTimePurchaseCount; i++) {
const res = await AutumnCli.attach({
customerId: customerId,
productId: products.oneTimeAddOnMetered1.id,
forceCheckout: true,
});
await completeCheckoutForm(res.checkout_url, oneTimeOverrideQuantity);
await timeout(10000); // for webhook to be processed
console.log(` ${chalk.greenBright("Attached one time add on")}`);
}
} catch (error) {
console.group();
console.group();
console.log("Failed to attach one time add on");
console.log("Error data:", error);
console.groupEnd();
console.groupEnd();
process.exit(1);
}
});
// TODO: Attach one time add on again (with quantity?)
it("GET /customers/:id -- checking product & entitlements (one time add on)", async function () {
const cusRes = await AutumnCli.getCustomer(customerId);
// 1. Metered1 balance should be pro + one time add on
// Fetch balance
const addOnBalance = cusRes.entitlements.find(
(e: any) =>
e.feature_id === features.metered1.id &&
e.interval ==
products.oneTimeAddOnMetered1.entitlements.metered1.interval,
);
const expectedAmt =
(oneTimeOverrideQuantity || oneTimeQuantity) *
oneTimeBillingUnits *
oneTimePurchaseCount;
try {
assert.equal(addOnBalance!.balance, expectedAmt);
assert.equal(cusRes.add_ons.length, 1);
assert.equal(cusRes.add_ons[0].id, products.oneTimeAddOnMetered1.id);
assert.equal(cusRes.invoices.length, 1 + oneTimePurchaseCount);
} catch (error) {
console.group();
console.group();
console.log("GET customer, balances failed");
console.log(
"Add on entitlement:",
products.oneTimeAddOnMetered1.entitlements.metered1,
);
console.log("Customer entitlements:", cusRes.entitlements);
console.groupEnd();
console.groupEnd();
throw error;
}
});
it("GET /entitled -- checking entitled for metered1", async function () {
const res: any = await AutumnCli.entitled(
customerId,
features.metered1.id,
);
expect(res!.allowed).to.be.true;
// pro metered1
const proMetered1Amt = products.pro.entitlements.metered1.allowance;
const addOnBalance = res!.balances.find(
(b: any) => b.feature_id === features.metered1.id,
);
expect(res!.allowed).to.be.true;
expect(addOnBalance!.balance).to.equal(
proMetered1Amt! +
(oneTimeOverrideQuantity || oneTimeQuantity) *
oneTimeBillingUnits *
oneTimePurchaseCount,
);
});
});
// PURCHASE MONTHLY ADD ON
describe("Monthly add on", () => {
it("POST /attach -- attaching monthly add on", async function () {
await AutumnCli.attach({
customerId: customerId,
productId: products.monthlyAddOnMetered1.id,
forceCheckout: false,
options: [
{
feature_id: features.metered1.id,
quantity: monthlyQuantity * monthlyBillingUnits,
},
],
});
await timeout(10000);
console.log(` ${chalk.greenBright("Attached monthly top up")}`);
});
it("GET /customers/:id -- checking product & entitlements (monthly add on)", async function () {
const cusRes = await AutumnCli.getCustomer(customerId);
// 1. Metered1 balance should be pro + one time add on
const proMetered1 = products.pro.entitlements.metered1.allowance;
// Fetch balance
const monthlyMetered1Balance = cusRes.entitlements.find(
(e: any) =>
e.feature_id === features.metered1.id &&
e.interval ==
products.monthlyAddOnMetered1.entitlements.metered1.interval,
);
try {
assert.equal(
monthlyMetered1Balance!.balance,
proMetered1! + monthlyQuantity * monthlyBillingUnits,
);
assert.equal(cusRes.add_ons.length, 2);
const monthlyAddOnId = cusRes.add_ons.find(
(a: any) => a.id === products.monthlyAddOnMetered1.id,
);
assert.exists(monthlyAddOnId);
expect(cusRes.invoices.length).to.equal(2 + oneTimePurchaseCount);
} catch (error) {
console.group();
console.group();
console.log("GET customer, balances failed");
console.log(
"Add on entitlement:",
products.monthlyAddOnMetered1.entitlements.metered1,
);
console.log("Customer entitlements:", cusRes.entitlements);
console.groupEnd();
console.groupEnd();
throw error;
}
});
it("GET /entitled -- checking entitlements (monthly add on)", async function () {
const res: any = await AutumnCli.entitled(
customerId,
features.metered1.id,
);
const metered1Balance = res!.balances.find(
(b: any) => b.feature_id === features.metered1.id,
);
const proMetered1Amt = products.pro.entitlements.metered1.allowance;
const monthlyAddOnMetered1Amt = monthlyQuantity * monthlyBillingUnits;
const oneTimeAddOnMetered1Amt =
(oneTimeOverrideQuantity || oneTimeQuantity) *
oneTimeBillingUnits *
oneTimePurchaseCount;
try {
expect(metered1Balance!.balance).to.equal(
proMetered1Amt! + monthlyAddOnMetered1Amt + oneTimeAddOnMetered1Amt,
);
} catch (error) {
console.group();
console.group();
console.log("GET entitled, balances failed");
console.log("/entitled response:", res);
console.log("Pro metered1 amt:", proMetered1Amt);
console.log("Monthly add on metered1 amt:", monthlyAddOnMetered1Amt);
console.log("One time add on metered1 amt:", oneTimeAddOnMetered1Amt);
console.groupEnd();
console.groupEnd();
throw error;
}
});
});
});

View File

@@ -0,0 +1,161 @@
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { assert, expect } from "chai";
import chalk from "chalk";
import { setupBefore } from "tests/before.js";
import { AutumnCli } from "tests/cli/AutumnCli.js";
import { features, products } from "tests/global.js";
import { compareMainProduct } from "tests/utils/compare.js";
import { completeCheckoutForm } from "tests/utils/stripeUtils.js";
import { timeout } from "tests/utils/genUtils.js";
import {
constructProduct,
constructRawProduct,
} from "@/utils/scriptUtils/createTestProducts.js";
import { constructPrepaidItem } from "@/utils/scriptUtils/constructItem.js";
import { createProducts } from "tests/utils/productUtils.js";
// const oneTimeQuantity = 2;
// const oneTimePurchaseCount = 2;
// const oneTimeOverrideQuantity = 4;
// const monthlyQuantity = 2;
let oneTimeItem = constructPrepaidItem({
featureId: features.metered1.id,
price: 9,
billingUnits: 250,
isOneOff: true,
});
let oneTime = constructRawProduct({
id: "basic3_one_off",
items: [oneTimeItem],
isAddOn: true,
});
let monthlyItem = constructPrepaidItem({
featureId: features.metered1.id,
price: 9,
billingUnits: 250,
});
let monthly = constructRawProduct({
id: "basic3_monthly",
items: [
constructPrepaidItem({
featureId: features.metered1.id,
price: 9,
billingUnits: 250,
}),
],
});
// UNCOMMENT FROM HERE
const testCase = "basic3";
describe(`${chalk.yellowBright("basic3: Testing attach one time / monthly add ons")}`, () => {
let customerId = testCase;
let autumn: AutumnInt = new AutumnInt();
let db, org, env;
before(async function () {
await setupBefore(this);
db = this.db;
org = this.org;
env = this.env;
await initCustomer({
autumn: this.autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
await createProducts({
autumn: this.autumnJs,
db,
orgId: org.id,
env,
products: [oneTime, monthly],
});
});
it("should attach pro", async function () {
await autumn.attach({
customer_id: customerId,
product_id: products.pro.id,
});
const res = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: products.pro,
cusRes: res,
});
});
const oneTimeQuantity = 500;
const oneTimeBillingUnits = oneTimeItem.billing_units;
const oneTimePurchaseCount = 2;
it("should attach one time add on twice, force checkout", async function () {
for (let i = 0; i < 2; i++) {
const res = await autumn.attach({
customer_id: customerId,
product_id: oneTime.id,
force_checkout: true,
});
await completeCheckoutForm(
res.checkout_url,
oneTimeQuantity / oneTimeBillingUnits!,
);
await timeout(20000);
}
});
it("should have correct product & entitlements", async function () {
const cusRes = await AutumnCli.getCustomer(customerId);
const addOnBalance = cusRes.entitlements.find(
(e: any) =>
e.feature_id === features.metered1.id &&
e.interval ==
products.oneTimeAddOnMetered1.entitlements.metered1.interval,
);
const expectedAmt = oneTimeQuantity * oneTimePurchaseCount;
expect(addOnBalance!.balance).to.equal(
expectedAmt,
"add on balance should be correct",
);
expect(cusRes.add_ons).to.have.lengthOf(
1,
"should only have one add on product after two purchases (since they combine)",
);
expect(cusRes.add_ons[0].id).to.equal(
oneTime.id,
"add on product should exist",
);
expect(cusRes.invoices.length).to.equal(
1 + oneTimePurchaseCount,
"invoices should be correct",
);
});
it("should have correct /check result for metered1", async function () {
const res: any = await AutumnCli.entitled(customerId, features.metered1.id);
expect(res!.allowed).to.be.true;
const proMetered1Amt = products.pro.entitlements.metered1.allowance;
const addOnBalance = res!.balances.find(
(b: any) => b.feature_id === features.metered1.id,
);
expect(res!.allowed).to.be.true;
expect(addOnBalance!.balance).to.equal(
proMetered1Amt! + oneTimeQuantity * oneTimePurchaseCount,
);
});
});

View File

@@ -0,0 +1,121 @@
import chalk from "chalk";
import { expect } from "chai";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { setupBefore } from "tests/before.js";
import { AutumnCli } from "tests/cli/AutumnCli.js";
import { features, products } from "tests/global.js";
import { compareMainProduct } from "tests/utils/compare.js";
import { constructRawProduct } from "@/utils/scriptUtils/createTestProducts.js";
import { constructPrepaidItem } from "@/utils/scriptUtils/constructItem.js";
import { createProducts } from "tests/utils/productUtils.js";
let monthlyItem = constructPrepaidItem({
featureId: features.metered1.id,
price: 9,
billingUnits: 250,
});
let monthly = constructRawProduct({
id: "basic4_monthly",
items: [monthlyItem],
});
const testCase = "basic4";
describe(`${chalk.yellowBright("basic4: Testing attach monthly add on")}`, () => {
let customerId = testCase;
let autumn: AutumnInt = new AutumnInt();
let db, org, env;
before(async function () {
await setupBefore(this);
db = this.db;
org = this.org;
env = this.env;
await initCustomer({
autumn: this.autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
await createProducts({
autumn: this.autumnJs,
db,
orgId: org.id,
env,
products: [monthly],
});
});
it("should attach pro", async function () {
await autumn.attach({
customer_id: customerId,
product_id: products.pro.id,
});
const res = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: products.pro,
cusRes: res,
});
});
const monthlyQuantity = 500;
it("should attach monthly add on", async function () {
await AutumnCli.attach({
customerId: customerId,
productId: products.monthlyAddOnMetered1.id,
forceCheckout: false,
options: [
{
feature_id: features.metered1.id,
quantity: monthlyQuantity,
},
],
});
});
it("should have correct product & entitlements", async function () {
const cusRes = await AutumnCli.getCustomer(customerId);
const proMetered1 = products.pro.entitlements.metered1.allowance;
const monthlyMetered1Balance = cusRes.entitlements.find(
(e: any) =>
e.feature_id === features.metered1.id &&
e.interval ==
products.monthlyAddOnMetered1.entitlements.metered1.interval,
);
expect(monthlyMetered1Balance!.balance).to.equal(
proMetered1! + monthlyQuantity,
);
expect(cusRes.add_ons).to.have.lengthOf(1);
const monthlyAddOnId = cusRes.add_ons.find(
(a: any) => a.id === products.monthlyAddOnMetered1.id,
);
expect(monthlyAddOnId).to.exist;
expect(cusRes.invoices.length).to.equal(2);
});
it("should have correct /check result for metered1", async function () {
const res: any = await AutumnCli.entitled(customerId, features.metered1.id);
const metered1Balance = res!.balances.find(
(b: any) => b.feature_id === features.metered1.id,
);
const proMetered1Amt = products.pro.entitlements.metered1.allowance;
const monthlyAddOnMetered1Amt = monthlyQuantity;
expect(metered1Balance!.balance).to.equal(
proMetered1Amt! + monthlyAddOnMetered1Amt,
);
});
});

View File

@@ -0,0 +1,98 @@
import { createStripeCli } from "@/external/stripe/utils.js";
import { AutumnCli } from "tests/cli/AutumnCli.js";
import { features, products } from "tests/global.js";
import chalk from "chalk";
import {
checkFeatureHasCorrectBalance,
compareMainProduct,
} from "tests/utils/compare.js";
import { expect } from "chai";
import { CusProductStatus } from "@autumn/shared";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { setupBefore } from "tests/before.js";
import Stripe from "stripe";
import { timeout } from "@/utils/genUtils.js";
const testCase = "basic5";
describe(`${chalk.yellowBright(
"basic5: Testing cancel through Stripe at period end and now",
)}`, () => {
const customerId = testCase;
let stripeCli: Stripe;
before(async function () {
await setupBefore(this);
stripeCli = this.stripeCli;
await initCustomer({
customerId,
db: this.db,
org: this.org,
env: this.env,
autumn: this.autumnJs,
attachPm: "success",
});
});
it("should attach pro product", async function () {
const res: any = await AutumnCli.attach({
customerId: customerId,
productId: products.pro.id,
});
});
it("should cancel pro product (at period end)", async function () {
const stripeCli = createStripeCli({ org: this.org, env: this.env });
const cusRes: any = await AutumnCli.getCustomer(customerId);
const proProduct = cusRes.products.find(
(p: any) => p.id === products.pro.id,
);
for (const subId of proProduct.subscription_ids) {
await stripeCli.subscriptions.update(subId, {
cancel_at_period_end: true,
});
}
await timeout(5000);
});
it("should have pro product active, and canceled_at != null, and free scheduled", async function () {
const cusRes: any = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: products.pro,
cusRes: cusRes,
});
const proProduct = cusRes.products.find(
(p: any) => p.id === products.pro.id,
);
expect(proProduct.canceled_at).to.not.equal(null);
expect(proProduct.status).to.equal(CusProductStatus.Active);
const freeProduct = cusRes.products.find(
(p: any) => p.id === products.free.id,
);
expect(freeProduct).to.exist;
expect(freeProduct.status).to.equal(CusProductStatus.Scheduled);
});
it("should cancel pro product (now)", async function () {
const cusRes: any = await AutumnCli.getCustomer(customerId);
const proProduct = cusRes.products.find(
(p: any) => p.id === products.pro.id,
);
for (const subId of proProduct.subscription_ids) {
await stripeCli.subscriptions.cancel(subId);
}
await timeout(5000);
});
it("should have free product active, and no pro product", async function () {
const cusRes: any = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: products.free,
cusRes: cusRes,
});
});
});

View File

@@ -0,0 +1,74 @@
import { createStripeCli } from "@/external/stripe/utils.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import chalk from "chalk";
import { setupBefore } from "tests/before.js";
import Stripe from "stripe";
import { advanceTestClock } from "tests/utils/stripeUtils.js";
import { AutumnCli } from "tests/cli/AutumnCli.js";
import { products } from "tests/global.js";
import { attachFailedPaymentMethod } from "@/external/stripe/stripeCusUtils.js";
import { CusProductStatus, Customer } from "@autumn/shared";
import { addDays, addHours, addMonths } from "date-fns";
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
import { compareMainProduct } from "tests/utils/compare.js";
import { expect } from "chai";
const testCase = "basic6";
describe(`${chalk.yellowBright(
"basic6: Testing subscription past_due",
)}`, () => {
const customerId = testCase;
let stripeCli: Stripe;
let testClockId: string;
let customer: Customer;
before(async function () {
await setupBefore(this);
stripeCli = this.stripeCli;
const { testClockId: testClockId_, customer: customer_ } =
await initCustomer({
customerId,
db: this.db,
org: this.org,
env: this.env,
autumn: this.autumnJs,
attachPm: "success",
});
testClockId = testClockId_;
customer = customer_;
});
it("should attach pro product and switch to failed payment method", async function () {
await AutumnCli.attach({
customerId: customerId,
productId: products.pro.id,
});
await attachFailedPaymentMethod({
stripeCli,
customer,
});
});
it("should advance to next cycle", async function () {
await advanceTestClock({
stripeCli,
testClockId,
advanceTo: addHours(
addMonths(new Date(), 1),
hoursToFinalizeInvoice,
).getTime(),
waitForSeconds: 30,
});
});
it("should have pro product in past due status", async function () {
const cusRes: any = await AutumnCli.getCustomer(customerId);
const proProduct = cusRes.products.find(
(p: any) => p.id === products.pro.id,
);
expect(proProduct).to.exist;
expect(proProduct.status).to.equal(CusProductStatus.PastDue);
});
});

View File

@@ -0,0 +1,80 @@
import chalk from "chalk";
import { compareMainProduct } from "tests/utils/compare.js";
import { products } from "tests/global.js";
import { AutumnCli } from "tests/cli/AutumnCli.js";
import { assert, expect } from "chai";
import { timeout } from "tests/utils/genUtils.js";
import { CusProductStatus } from "@autumn/shared";
import { setupBefore } from "tests/before.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
const testCase = "basic7";
describe(`${chalk.yellowBright("basic7: Testing trial duplicates (same customer)")}`, () => {
const customerId = testCase;
let customerId2 = testCase + "2";
const autumn = new AutumnInt();
before(async function () {
await setupBefore(this);
await initCustomer({
customerId,
db: this.db,
org: this.org,
env: this.env,
autumn: this.autumnJs,
attachPm: "success",
});
});
it("should attach pro with trial and have correct product & invoice", async function () {
await AutumnCli.attach({
customerId: customerId,
productId: products.proWithTrial.id,
});
const customer = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: products.proWithTrial,
cusRes: customer,
status: CusProductStatus.Trialing,
});
const invoices = customer.invoices;
expect(invoices.length).to.equal(1, "Invoice length should be 1");
expect(invoices[0].total).to.equal(0, "Invoice total should be 0");
});
it("should cancel pro with trial", async function () {
await autumn.cancel({
customer_id: customerId,
product_id: products.proWithTrial.id,
expire_immediately: true,
});
await timeout(5000); // for webhook to be processed
});
it("should be able to attach pro with trial again (renewal flow)", async function () {
await AutumnCli.attach({
customerId: customerId,
productId: products.proWithTrial.id,
});
const customer = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: products.proWithTrial,
cusRes: customer,
status: CusProductStatus.Trialing,
});
const invoices = customer.invoices;
expect(invoices.length).to.equal(1, "Invoice length should be 1");
expect(invoices[0].amount).to.equal(
products.proWithTrial.prices[0].amount,
"should have paid full amount (trial already used once)",
);
});
});

View File

@@ -0,0 +1,85 @@
import chalk from "chalk";
import { setupBefore } from "tests/before.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { AutumnCli } from "tests/cli/AutumnCli.js";
import { products } from "tests/global.js";
import { compareMainProduct } from "tests/utils/compare.js";
import { CusProductStatus } from "@autumn/shared";
import { expect } from "chai";
import { timeout } from "@/utils/genUtils.js";
const testCase = "basic8";
describe(`${chalk.yellowBright("basic8: Testing trial duplicates (same fingerprint)")}`, () => {
const customerId = testCase;
let customerId2 = testCase + "2";
const autumn = new AutumnInt();
before(async function () {
const randFingerprint = Math.random().toString(36).substring(2, 15);
await setupBefore(this);
await initCustomer({
customerId,
db: this.db,
org: this.org,
env: this.env,
autumn: this.autumnJs,
fingerprint: randFingerprint,
attachPm: "success",
});
await initCustomer({
customerId: customerId2,
db: this.db,
org: this.org,
env: this.env,
autumn: this.autumnJs,
fingerprint: randFingerprint,
attachPm: "success",
});
});
it("should attach pro with trial and have correct product & invoice", async function () {
await AutumnCli.attach({
customerId: customerId,
productId: products.proWithTrial.id,
});
const customer = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: products.proWithTrial,
cusRes: customer,
status: CusProductStatus.Trialing,
});
const invoices = customer.invoices;
expect(invoices.length).to.equal(1, "Invoice length should be 1");
expect(invoices[0].total).to.equal(0, "Invoice total should be 0");
});
it("should attach pro with trial to second customer and have correct product & invoice (pro with trial, full price)", async function () {
await autumn.attach({
customer_id: customerId2,
product_id: products.proWithTrial.id,
});
// await timeout(5000); // for webhook to be processed
const customer = await AutumnCli.getCustomer(customerId2);
compareMainProduct({
sent: products.proWithTrial,
cusRes: customer,
status: CusProductStatus.Active,
});
// Check invoice is equal monthly price
const invoices = customer.invoices;
expect(invoices.length).to.equal(1, "Invoice length should be 1");
expect(invoices[0].total).to.equal(
10,
"Invoice total should be full price",
);
});
});

View File

@@ -7,10 +7,11 @@ import { initCustomer } from "tests/utils/init.js";
import { completeCheckoutForm } from "tests/utils/stripeUtils.js";
import { compareMainProduct } from "tests/utils/compare.js";
const testCase = "basic9";
describe(`${chalk.yellowBright(
"attach2: Testing monthly with one time prepaid, quantity = 0",
"basic9: attach monthly with one time prepaid, and quantity = 0",
)}`, () => {
let customerId = "attach2";
let customerId = testCase;
let options = [
{

View File

@@ -12,7 +12,6 @@ import { addPrefixToProducts, runAttachTest } from "../utils.js";
import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
import { constructFeatureItem } from "@/internal/products/product-items/productItemUtils.js";
import { expectDowngradeCorrect } from "tests/utils/expectUtils/expectScheduleUtils.js";
import { expectNextCycleCorrect } from "tests/utils/expectUtils/expectScheduleUtils.js";

View File

@@ -0,0 +1,120 @@
import chalk from "chalk";
import Stripe from "stripe";
import { CusProductStatus } from "@autumn/shared";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { AutumnCli } from "tests/cli/AutumnCli.js";
import { products } from "tests/global.js";
import { expect } from "chai";
import { compareMainProduct } from "tests/utils/compare.js";
import { addHours, addMonths } from "date-fns";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { setupBefore } from "tests/before.js";
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
const testCase = "downgrade5";
describe(`${chalk.yellowBright(
"downgrade5: testing basic downgrade (paid to paid)",
)}`, () => {
let customerId = testCase;
let autumn: AutumnInt = new AutumnInt();
let testClockId: string;
let stripeCli: Stripe;
before(async function () {
await setupBefore(this);
const { autumnJs } = this;
stripeCli = this.stripeCli;
const { customer: customer_, testClockId: testClockId_ } =
await initCustomer({
customerId,
db: this.db,
org: this.org,
env: this.env,
attachPm: "success",
autumn: autumnJs,
});
testClockId = testClockId_;
});
it("should attach premium", async function () {
await AutumnCli.attach({
customerId: customerId,
productId: products.premium.id,
});
});
it("should attach pro", async function () {
await AutumnCli.attach({
customerId: customerId,
productId: products.pro.id,
});
});
it("should have correct product and entitlements for scheduled pro", async function () {
const res = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: products.premium,
cusRes: res,
});
const { products: resProducts } = res;
const resPro = resProducts.find(
(p: any) =>
p.id === products.pro.id && p.status === CusProductStatus.Scheduled,
);
expect(resPro).to.exist;
});
it("should attach premium and remove scheduled pro", async function () {
await AutumnCli.attach({
customerId: customerId,
productId: products.premium.id,
});
const res = await AutumnCli.getCustomer(customerId);
const resPro = res.products.find(
(p: any) =>
p.id === products.pro.id && p.status === CusProductStatus.Scheduled,
);
expect(resPro).to.not.exist;
compareMainProduct({
sent: products.premium,
cusRes: res,
});
});
// Advance time 1 month
it("should attach pro, advance stripe clock and have pro is attached", async function () {
await AutumnCli.attach({
customerId: customerId,
productId: products.pro.id,
});
await advanceTestClock({
stripeCli,
testClockId,
advanceTo: addHours(
addMonths(new Date(), 1),
hoursToFinalizeInvoice,
).getTime(),
waitForSeconds: 15,
});
const res = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: products.pro,
cusRes: res,
});
});
});

View File

@@ -0,0 +1,114 @@
import { Customer } from "@autumn/shared";
import { AutumnCli } from "tests/cli/AutumnCli.js";
import { products } from "tests/global.js";
import chalk from "chalk";
import { compareMainProduct } from "tests/utils/compare.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { setupBefore } from "tests/before.js";
import { getMainCusProduct } from "@/internal/customers/cusProducts/cusProductUtils.js";
const testCase = "downgrade6";
describe(`${chalk.yellowBright("downgrade6: testing expire button")}`, () => {
let customerId = testCase;
let testClockId: string;
let autumn: AutumnInt = new AutumnInt();
let customer: Customer;
before(async function () {
await setupBefore(this);
const { testClockId: testClockId_, customer: customer_ } =
await initCustomer({
customerId,
db: this.db,
org: this.org,
env: this.env,
autumn: this.autumnJs,
});
customer = customer_;
testClockId = testClockId_;
});
it("should attach premium", async function () {
await autumn.attach({
customer_id: customerId,
product_id: products.premium.id,
});
});
it("should expire premium", async function () {
const cusProduct = await getMainCusProduct({
db: this.db,
internalCustomerId: customer.internal_id,
});
await AutumnCli.expire(cusProduct!.id);
});
it("should have correct product and entitlements after expiration", async function () {
const res = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: products.free,
cusRes: res,
});
});
// // 2. Get premium
// it("POST /attach -- attaching premium, then attach pro", async function () {
// this.timeout(30000);
// await AutumnCli.attach({
// customerId: customerId,
// productId: products.premium.id,
// });
// await AutumnCli.attach({
// customerId: customerId,
// productId: products.pro.id,
// });
// });
// it("Expiring pro product (should re-attach premium)", async function () {
// this.timeout(30000);
// // Expire pro product
// const customerProduct = await getCusProduct(
// this.sb,
// customer.internal_id,
// products.pro.id,
// );
// await AutumnCli.expire(customerProduct.id);
// await timeout(5000);
// });
// it("GET /customers/:customer_id -- checking product and ents", async function () {
// this.timeout(30000);
// // Check that free is attached
// const res = await AutumnCli.getCustomer(customerId);
// compareMainProduct({
// sent: products.premium,
// cusRes: res,
// });
// // Get stripe subscription (ensure canceled is null)
// const stripeCli = createStripeCli({
// org: this.org,
// env: this.env,
// });
// const premiumCusProduct = await getCusProduct(
// this.sb,
// customer.internal_id,
// products.premium.id,
// );
// const stripeSub = await stripeCli.subscriptions.retrieve(
// premiumCusProduct.processor.subscription_id,
// );
// // Check that canceled is null
// assert.isNull(stripeSub.canceled_at);
// });
});

View File

@@ -0,0 +1,80 @@
import { Customer } from "@autumn/shared";
import { AutumnCli } from "tests/cli/AutumnCli.js";
import { products } from "tests/global.js";
import chalk from "chalk";
import { compareMainProduct } from "tests/utils/compare.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { setupBefore } from "tests/before.js";
import { findCusProductById } from "@/internal/customers/cusProducts/cusProductUtils/findCusProduct.js";
import { expect } from "chai";
import { getSubsFromCusId } from "tests/utils/expectUtils/expectSubUtils.js";
const testCase = "downgrade7";
describe(`${chalk.yellowBright("downgrade7: testing expire scheduled product")}`, () => {
let customerId = testCase;
let testClockId: string;
let customer: Customer;
before(async function () {
await setupBefore(this);
const { testClockId: testClockId_, customer: customer_ } =
await initCustomer({
customerId,
db: this.db,
org: this.org,
env: this.env,
autumn: this.autumnJs,
attachPm: "success",
});
customer = customer_;
testClockId = testClockId_;
});
// 2. Get premium
it("should attach premium, then attach pro", async function () {
await AutumnCli.attach({
customerId: customerId,
productId: products.premium.id,
});
await AutumnCli.attach({
customerId: customerId,
productId: products.pro.id,
});
});
it("should expire scheduled product (pro)", async function () {
const cusProduct = await findCusProductById({
db: this.db,
internalCustomerId: customer.internal_id,
productId: products.pro.id,
});
expect(cusProduct).to.exist;
await AutumnCli.expire(cusProduct!.id);
});
it("should have correct product and entitlements (premium)", async function () {
this.timeout(30000);
// Check that free is attached
const res = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: products.premium,
cusRes: res,
});
const { subs } = await getSubsFromCusId({
stripeCli: this.stripeCli,
customerId: customerId,
productId: products.premium.id,
db: this.db,
org: this.org,
env: this.env,
});
expect(subs).to.have.lengthOf(1);
expect(subs[0].canceled_at).to.be.null;
});
});

View File

@@ -0,0 +1,118 @@
import { createStripeCli } from "@/external/stripe/utils.js";
import chalk from "chalk";
import Stripe from "stripe";
import { AutumnCli } from "tests/cli/AutumnCli.js";
import { advanceProducts } from "tests/global.js";
import {
checkProductIsScheduled,
compareMainProduct,
} from "tests/utils/compare.js";
import { setupBefore } from "tests/before.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
// TEST MULTI INTERVAL DOWNGRADE
//
/*
CASE 1: Annual pro -> Annual starter
- If attach annual starter, should schedule correctly [DONE]
- If advance test clock, should downgrade correctly (to monthly starter) [DONE]
- If cancel active subscription (on Stripe), should remove scheduled correctly [DONE]
- If cancel scheduled subscription (on Stripe), should remove scheduled correctly [DONE]
- If expire on dashboard, should remove scheduled correctly
- If upgrade back to annual pro, should remove scheduled correctly [DONE]
- If downgrade to monthly pro (switch downgrade), should be correct [DONE]
- If downgrade to free (switch downgrade), should be correct
*/
const testCase = "downgrade9";
describe(`${chalk.yellowBright("downgrade9: Multi interval downgrade -- Annual pro -> Annual starter")}`, () => {
let customerId = testCase;
before(async function () {
await setupBefore(this);
await initCustomer({
customerId,
db: this.db,
org: this.org,
env: this.env,
autumn: this.autumnJs,
attachPm: "success",
withTestClock: false,
});
});
it("should attach annual pro", async function () {
await AutumnCli.attach({
customerId: customerId,
productId: advanceProducts.gpuProAnnual.id,
});
let cusRes = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: advanceProducts.gpuProAnnual,
cusRes,
});
});
it("should attach downgrade to annual starter", async function () {
await AutumnCli.attach({
customerId: customerId,
productId: advanceProducts.gpuStarterAnnual.id,
});
let cusRes = await AutumnCli.getCustomer(customerId);
checkProductIsScheduled({
cusRes,
product: advanceProducts.gpuStarterAnnual,
});
});
});
// describe(`${chalk.yellowBright("downgrade9: Multi interval downgrade -- Quarterly pro -> Monthly pro")}`, () => {
// let customerId = testCase;
// let stripeCli: Stripe;
// let testClockId: string;
// before(async function () {
// const { testClockId: insertedTestClockId } =
// await initCustomerWithTestClock({
// customerId,
// org: this.org,
// env: this.env,
// db: this.db,
// });
// testClockId = insertedTestClockId;
// stripeCli = createStripeCli({
// org: this.org,
// env: this.env,
// });
// });
// it("should attach quarterly pro", async function () {
// let res = await AutumnCli.attach({
// customerId: customerId,
// productId: advanceProducts.gpuProQuarter.id,
// });
// let cusRes = await AutumnCli.getCustomer(customerId);
// compareMainProduct({
// sent: advanceProducts.gpuProQuarter,
// cusRes,
// });
// });
// it("should attach downgrade to monthly pro", async function () {
// let res = await AutumnCli.attach({
// customerId: customerId,
// productId: advanceProducts.gpuSystemPro.id,
// });
// let cusRes = await AutumnCli.getCustomer(customerId);
// checkProductIsScheduled({
// cusRes,
// product: advanceProducts.gpuSystemPro,
// });
// });
// });

View File

@@ -147,38 +147,4 @@ describe(`${chalk.yellowBright(`attach/${testCase}: Testing attach pro annual to
env,
});
});
// let nextUsage = 123123912;
// it("should cancel and have a final invoice", async function () {
// await autumn.cancel({
// customer_id: customerId,
// product_id: proAnnual.id,
// entity_id: entityId,
// });
// await timeout(5000);
// curUnix = await advanceTestClock({
// stripeCli,
// testClockId,
// advanceTo: addHours(
// addMonths(curUnix, 1),
// hoursToFinalizeInvoice,
// ).getTime(),
// });
// await expectInvoiceAfterUsage({
// autumn,
// customerId,
// entityId,
// featureId: TestFeature.Words,
// product: proAnnual,
// usage: nextUsage,
// stripeCli,
// db,
// org,
// env,
// numInvoices: 3,
// });
// });
});

View File

@@ -1,8 +1,10 @@
import { AutumnCli } from "tests/cli/AutumnCli.js";
import { attachProducts } from "tests/global.js";
import { compareMainProduct } from "tests/utils/compare.js";
import { initCustomer } from "tests/utils/init.js";
import chalk from "chalk";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { setupBefore } from "tests/before.js";
import { Customer } from "@autumn/shared";
/*
FLOW:
@@ -10,24 +12,24 @@ FLOW:
2. Upgrade pro group 1 -> premium group 1
3. Upgrade pro group 2 -> premium group 2
*/
const testCase = "multiProduct1";
describe(
chalk.yellowBright(
"01_multi_product1: Testing multi product attach, and upgrade",
),
chalk.yellowBright(`${testCase}: Testing multi product attach, and upgrade`),
() => {
let customerId = "multi-product-attach-upgrade";
let customerId = testCase;
let customer: Customer;
before(async function () {
this.customer = await initCustomer({
await setupBefore(this);
const res = await initCustomer({
customerId,
db: this.db,
org: this.org,
customer_data: {
id: customerId,
name: customerId,
email: "multi-product-attach-upgrade@example.com",
},
env: this.env,
attachPm: true,
autumn: this.autumnJs,
attachPm: "success",
});
customer = res.customer;
});
it("should attach pro group 1 and pro group 2", async function () {
@@ -50,8 +52,6 @@ describe(
// 1. Compare main product
const cusRes = await AutumnCli.getCustomer(customerId);
compareMainProduct({ sent: attachProducts.premiumGroup1, cusRes });
// 2. Check latest invoice
});
it("should upgrade to premium group 2", async function () {

View File

@@ -1,17 +1,20 @@
import chalk from "chalk";
import { Stripe } from "stripe";
import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
import { CusProductStatus, Customer } from "@autumn/shared";
import { expect } from "chai";
import { AutumnCli } from "tests/cli/AutumnCli.js";
import { advanceProducts, attachProducts } from "tests/global.js";
import { attachProducts } from "tests/global.js";
import {
checkProductIsScheduled,
compareMainProduct,
} from "tests/utils/compare.js";
import { initCustomer } from "tests/utils/init.js";
import { searchCusProducts, timeout } from "tests/utils/genUtils.js";
import { createStripeCli } from "@/external/stripe/utils.js";
import { Stripe } from "stripe";
import { searchCusProducts } from "tests/utils/genUtils.js";
import { checkScheduleContainsProducts } from "tests/utils/scheduleCheckUtils.js";
import { setupBefore } from "tests/before.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
/*
FLOW:
@@ -20,26 +23,27 @@ FLOW:
3. Downgrade to starter group 2
4. Change downgrade to pro group 2
*/
describe("Multi Product 3: premium1->starter1, premium2->starter2, then premium2->pro2, then premium2->free", () => {
let customerId = "multi-side-free-downgrade";
const testCase = "multiProduct2";
describe(`${chalk.yellowBright(
"multiProduct2: premium1->starter1, premium2->starter2, then premium2->pro2, then premium2->free",
)}`, () => {
let customerId = testCase;
let customer: Customer;
let stripeCli: Stripe;
before(async function () {
customer = await initCustomer({
await setupBefore(this);
stripeCli = this.stripeCli;
const res = await initCustomer({
db: this.db,
org: this.org,
customer_data: {
id: customerId,
name: customerId,
email: "multi-side-free-downgrade@example.com",
},
env: this.env,
attachPm: true,
});
stripeCli = createStripeCli({
org: this.org,
customerId,
env: this.env,
autumn: this.autumnJs,
attachPm: "success",
});
customer = res.customer;
});
it("should attach premium group 1 and premium group 2", async function () {
@@ -121,14 +125,6 @@ describe("Multi Product 3: premium1->starter1, premium2->starter2, then premium2
});
});
// it("should upgrade to premium group 1 again, then downgrade back to starter group 1", async function () {
// await AutumnCli.attach({
// customerId: customerId,
// productId: attachProducts.premiumGroup1.id,
// });
// });
it("should downgrade to free", async function () {
await AutumnCli.attach({
customerId: customerId,
@@ -160,15 +156,3 @@ describe("Multi Product 3: premium1->starter1, premium2->starter2, then premium2
});
});
});
// Advance test clock
// 1. Premium 1 -> Starter 1, Premium 2 -> Starter 2, Advance Test Clock [OK]
// 2. Premium 1 -> Starter 1, Premium 2, Advance Test Clock
// 3. Premium 1 -> Starter 1, Premium 2 -> Free, Advance Test Clock
// EXPIRE BUTTON
// 1. Premium 1 -> Starter 1, Premium 2 -> Starter 2, Expire Starter 1, Expire Starter 2
// 2. Premium 1 -> Starter 1, Premium 2 -> Starter 2, Cancel Premium 1, or 2 immediately...
// UPGRADES
// 1. Premium 1 -> Starter 1, Premium 2 -> Starter 2, Upgrade to Starter 1

View File

@@ -1,51 +1,47 @@
import { createStripeCli } from "@/external/stripe/utils.js";
import { getStripeSubs } from "@/external/stripe/stripeSubUtils.js";
import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
import chalk from "chalk";
import { expect } from "chai";
import { AutumnCli } from "tests/cli/AutumnCli.js";
import { attachProducts } from "tests/global.js";
import { compareMainProduct } from "tests/utils/compare.js";
import { initCustomer } from "tests/utils/init.js";
import { searchCusProducts, timeout } from "tests/utils/genUtils.js";
import chalk from "chalk";
import { createStripeCli } from "@/external/stripe/utils.js";
import { getStripeSubs } from "@/external/stripe/stripeSubUtils.js";
import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { setupBefore } from "tests/before.js";
import Stripe from "stripe";
// TESTING DOWNGRADE DOWNGRADE THEN
// 1. UPGRADE FIRST PRODUCT BACK -- SHOULD REPLACE SCHEDULE WITH OLD FIRST PRODUCT
// 2. UPGRADE SECOND PRODUCT BACK -- SHOULD CANCEL SCHEDULE
const testCase = "multiProduct3";
describe(
chalk.yellowBright(
"Multi Product 4: double downgrade, double upgrade (back)",
),
chalk.yellowBright(`${testCase}: double downgrade, double upgrade (back)`),
() => {
let customerId = "multi-double-downgrade-upgrade";
let customerId = testCase;
let customer;
let stripeCli;
let stripeCli: Stripe;
before(async function () {
customer = await initCustomer({
await setupBefore(this);
stripeCli = this.stripeCli;
const res = await initCustomer({
db: this.db,
org: this.org,
env: this.env,
customer_data: {
id: customerId,
name: customerId,
email: "multi-product-upgrade-test@example.com",
},
attachPm: true,
customerId,
autumn: this.autumnJs,
attachPm: "success",
});
stripeCli = createStripeCli({
org: this.org,
env: this.env,
});
customer = res.customer;
});
it("should attach premium group 1 and premium group 2", async function () {
let res = await AutumnCli.attach({
await AutumnCli.attach({
customerId: customerId,
productIds: [
attachProducts.premiumGroup1.id,
@@ -59,7 +55,7 @@ describe(
});
it("should attach starter group 1, then starter group 2", async function () {
let res = await AutumnCli.attach({
await AutumnCli.attach({
customerId: customerId,
productId: attachProducts.starterGroup1.id,
});
@@ -71,20 +67,18 @@ describe(
});
it("should reattach premium group 1", async function () {
await timeout(3000);
await AutumnCli.attach({
customerId: customerId,
productId: attachProducts.premiumGroup1.id,
});
// Check that schedule contains premium group 1, and starter group 2...
await timeout(10000);
const cusProducts = await CusProductService.list({
db: this.db,
internalCustomerId: customer!.internal_id,
});
// 1. Check that premium group 1 is active, and had scheduled_ids
let premiumGroup1 = searchCusProducts({
cusProducts,
productId: attachProducts.premiumGroup1.id,
@@ -119,7 +113,7 @@ describe(
productId: attachProducts.premiumGroup2.id,
});
await timeout(5000);
await timeout(10000);
const cusProducts = await CusProductService.list({
db: this.db,
@@ -157,12 +151,5 @@ describe(
expect(sub.cancel_at).to.equal(null);
expect(sub.status).to.equal("active");
});
// it("should reattach premium group 2", async function () {
// let res = await AutumnCli.attach({
// customerId: this.customer.id,
// productId: attachProducts.premiumGroup2.id,
// });
// });
},
);

View File

@@ -0,0 +1,161 @@
import { assert } from "chai";
import { features, products } from "tests/global.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { getPublicAxiosInstance } from "tests/utils/setup.js";
import { completeCheckoutForm } from "tests/utils/stripeUtils.js";
import { timeout } from "tests/utils/genUtils.js";
import { ErrCode } from "@autumn/shared";
import { compareMainProduct } from "tests/utils/compare.js";
import { AutumnCli } from "tests/cli/AutumnCli.js";
import chalk from "chalk";
import { setupBefore } from "tests/before.js";
const testCase = "others4";
describe(`${chalk.yellowBright("others4: Testing publishable key")}`, () => {
// 1. Initialize customer with card
let customerId = testCase;
const bearerPublicAxios = getPublicAxiosInstance({
withBearer: true,
});
before(async function () {
await setupBefore(this);
await initCustomer({
customerId,
db: this.db,
org: this.org,
env: this.env,
autumn: this.autumnJs,
});
});
it("should return a 401 if the pkey is invalid", async function () {
this.timeout(30000);
const axiosInstance = getPublicAxiosInstance({
withBearer: true,
pkey: "am_pk_test_invalid",
});
try {
const { data } = await axiosInstance.post("/v1/attach", {
customer_id: customerId,
product_id: products.pro.id,
});
throw new Error("Should not be able to attach");
} catch (error: any) {
assert.equal(error.response.status, 401);
}
});
it("should return checkout URL for both bearer key", async function () {
this.timeout(30000);
const axiosInstanceBearer = getPublicAxiosInstance({
withBearer: true,
});
// 1. Should be able to upgrade to pro
const { data } = await axiosInstanceBearer.post("/v1/attach", {
customer_id: customerId,
product_id: products.pro.id,
});
assert.exists(data.checkout_url);
await completeCheckoutForm(data.checkout_url);
await timeout(5000);
});
it("should have customer with product", async function () {
this.timeout(30000);
const res = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: products.pro,
cusRes: res,
});
});
it("should return error if try to upgrade or downgrade without pkey", async function () {
this.timeout(30000);
const axiosInstance = getPublicAxiosInstance({
withBearer: true,
});
try {
await axiosInstance.post("/v1/attach", {
customer_id: customerId,
product_id: products.premium.id,
});
throw new Error("Should not be able to attach");
} catch (error: any) {
assert.equal(error.response.status, 400);
assert.equal(error.response.data.code, ErrCode.InvalidRequest);
}
});
it("should return error if try to downgrade to free", async function () {
try {
await bearerPublicAxios.post("/v1/attach", {
customer_id: customerId,
product_id: products.free.id,
});
throw new Error("Should not be able to attach");
} catch (error: any) {
assert.equal(error.response.status, 400);
assert.equal(error.response.data.code, ErrCode.InvalidRequest);
}
});
// Next, check entitled for pro
it("should return correct metered1 amount for pro", async function () {
const { data } = await bearerPublicAxios.post("/v1/entitled", {
customer_id: customerId,
feature_id: features.metered1.id,
});
assert.equal(data.allowed, true);
const metered1Balance = data.balances.find(
(b: any) => b.feature_id === features.metered1.id,
);
assert.equal(
metered1Balance.balance,
products.pro.entitlements.metered1.allowance,
);
});
it("should return same balance for entitled with bearer and x-publishable-key", async function () {
const { data } = await bearerPublicAxios.post("/v1/entitled", {
customer_id: customerId,
feature_id: features.metered1.id,
});
assert.equal(data.allowed, true);
const metered1Balance = data.balances.find(
(b: any) => b.feature_id === features.metered1.id,
);
assert.equal(
metered1Balance.balance,
products.pro.entitlements.metered1.allowance,
);
});
it("should return error when try to send event", async function () {
try {
await bearerPublicAxios.post("/v1/events", {
customer_id: customerId,
event_name: features.metered1.id,
properties: {
value: 10,
},
});
throw new Error("Should not be able to send event");
} catch (error: any) {
assert.equal(error.response.status, 401);
assert.equal(error.response.data.code, ErrCode.EndpointNotPublic);
}
});
});

View File

@@ -0,0 +1,243 @@
import chalk from "chalk";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { features, products } from "tests/global.js";
import { AutumnCli } from "tests/cli/AutumnCli.js";
import { timeout } from "../../utils/genUtils.js";
import { expect } from "chai";
import { setupBefore } from "tests/before.js";
const checkEntitledOnProduct = async ({
customerId,
product,
totalAllowance,
finish = false,
usageBased = false,
}: {
customerId: string;
product: any;
totalAllowance?: number;
finish?: boolean;
usageBased?: boolean;
}) => {
// 1. Send events
const allowance = totalAllowance || product.entitlements.metered1.allowance;
// const randomNum = Math.floor(Math.random() * (allowance - 1));
const randomNum = 3;
const batchUpdates = [];
for (let i = 0; i < randomNum; i++) {
batchUpdates.push(
AutumnCli.sendEvent({
customerId: customerId,
eventName: features.metered1.eventName,
}),
);
}
await Promise.all(batchUpdates);
await timeout(8000);
let used = randomNum;
// 2. Check entitled
const { allowed, balanceObj }: any = await AutumnCli.entitled(
customerId,
features.metered1.id,
true,
);
try {
expect(allowed).to.be.true;
expect(balanceObj!.balance).to.equal(allowance - randomNum);
if (!finish) {
return used;
}
} catch (error) {
console.group();
console.group();
console.log("Allowance: ", allowance, "Random num: ", randomNum);
console.log("Expected balance to be: ", allowance - randomNum);
console.log("Entitled res: ", { allowed, balanceObj });
console.groupEnd();
console.groupEnd();
throw error;
}
// Finish up
const batchUpdates2 = [];
for (let i = 0; i < allowance - randomNum; i++) {
batchUpdates2.push(
AutumnCli.sendEvent({
customerId: customerId,
eventName: features.metered1.eventName,
}),
);
}
await Promise.all(batchUpdates2);
await timeout(8000);
used += allowance - randomNum;
// 3. Check entitled again
const { allowed: allowed2, balanceObj: balanceObj2 }: any =
await AutumnCli.entitled(customerId, features.metered1.id, true);
try {
if (usageBased) {
expect(allowed2).to.be.true;
} else {
expect(allowed2).to.be.false;
}
expect(balanceObj2!.balance).to.equal(0);
return used;
} catch (error) {
console.group();
console.group();
console.log("Expected balance to be: ", 0);
console.log("Entitled res: ", { allowed2, balanceObj2 });
console.groupEnd();
console.groupEnd();
throw error;
}
};
// TODO: Add test case for unlimited feature
const testCase = "others5";
describe(`${chalk.yellowBright(
"others5: Testing /events and /entitled, for pro, one time top up",
)}`, () => {
const customerId = testCase;
let curAllowance = 0;
const oneTimeBillingUnits =
products.oneTimeAddOnMetered1.prices[0].config.billing_units!;
let oneTimeQuantity = 2 * oneTimeBillingUnits;
before(async function () {
await setupBefore(this);
await initCustomer({
customerId,
db: this.db,
org: this.org,
env: this.env,
autumn: this.autumnJs,
attachPm: "success",
});
});
it("should have correct entitlements (free)", async function () {
await checkEntitledOnProduct({
customerId: customerId,
product: products.free,
finish: true,
});
});
it("should attach pro", async function () {
await AutumnCli.attach({
customerId: customerId,
productId: products.pro.id,
});
});
it("should have correct entitlements (pro)", async function () {
const used = await checkEntitledOnProduct({
customerId: customerId,
product: products.pro,
finish: false,
});
curAllowance = products.pro.entitlements.metered1.allowance! - used;
});
it("should attach one time top up", async function () {
await AutumnCli.attach({
customerId: customerId,
productId: products.oneTimeAddOnMetered1.id,
options: [
{
feature_id: features.metered1.id,
quantity: oneTimeQuantity,
},
],
});
});
it("should have correct entitlements (one time top up)", async function () {
// const oneTimeAmt = oneTimeBillingUnits * oneTimeQuantity;
await checkEntitledOnProduct({
customerId: customerId,
product: products.oneTimeAddOnMetered1,
finish: true,
totalAllowance: curAllowance + oneTimeQuantity,
});
});
});
describe(`${chalk.yellowBright(
"others5: Testing /entitled & /events, for pro with overage",
)}`, () => {
const customerId = testCase;
before(async function () {
await setupBefore(this);
await initCustomer({
customerId,
db: this.db,
org: this.org,
env: this.env,
autumn: this.autumnJs,
attachPm: "success",
});
});
// PRO WITH OVERAGE
it("should attach pro (with overage)", async function () {
await AutumnCli.attach({
customerId: customerId,
productId: products.proWithOverage.id,
});
});
it("should have correct entitlements (pro with overage)", async function () {
await checkEntitledOnProduct({
customerId: customerId,
product: products.proWithOverage,
finish: true,
totalAllowance: products.proWithOverage.entitlements.metered1.allowance!,
usageBased: true,
});
});
it("should have correct usage-based balance (balance < 0)", async function () {
const { allowed, balanceObj }: any = await AutumnCli.entitled(
customerId,
features.metered1.id,
true,
);
expect(allowed).to.be.true;
expect(balanceObj!.balance).to.equal(0);
// Sent 5 events
const batchUpdates = [];
for (let i = 0; i < 5; i++) {
batchUpdates.push(
AutumnCli.sendEvent({
customerId: customerId,
eventName: features.metered1.eventName,
}),
);
}
await Promise.all(batchUpdates);
await timeout(5000);
const { allowed: allowed2, balanceObj: balanceObj2 }: any =
await AutumnCli.entitled(customerId, features.metered1.id, true);
expect(allowed2).to.be.true;
expect(balanceObj2!.balance).to.equal(-5);
expect(balanceObj2!.usage_allowed).to.be.true;
});
});

View File

@@ -65,6 +65,7 @@ const runUpdateEntsTest = async ({
expect(preview.branch).to.equal(AttachBranch.NewVersion);
} else {
expect(preview.branch).to.equal(AttachBranch.SameCustomEnts);
expect(preview.due_today).to.be.undefined;
}
await autumn.attach({

View File

@@ -30,17 +30,6 @@ export let pro = constructProduct({
type: "pro",
});
/**
* upgrade3:
* Testing upgrades for arrear prorated
* 1. Start with pro monthly plan (usage-based)
* 2. Upgrade to pro annual plan (usage-based)
* 3. Upgrade to premium annual plan (usage-based)
*
* Verifies subscription items and anchors are correct after each upgrade
* with arrear prorated billing
*/
describe(`${chalk.yellowBright(`${testCase}: Testing update ents (changing included usage)`)}`, () => {
let customerId = testCase;
let autumn: AutumnInt = new AutumnInt({ version: APIVersion.v1_4 });
@@ -60,15 +49,6 @@ describe(`${chalk.yellowBright(`${testCase}: Testing update ents (changing inclu
stripeCli = this.stripeCli;
const { testClockId: testClockId1 } = await initCustomer({
autumn: autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
addPrefixToProducts({
products: [pro],
prefix: testCase,
@@ -83,10 +63,19 @@ describe(`${chalk.yellowBright(`${testCase}: Testing update ents (changing inclu
customerId,
});
const { testClockId: testClockId1 } = await initCustomer({
autumn: autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
testClockId = testClockId1!;
});
it("should attach pro product (prepaid single use)", async function () {
it("should attach pro product", async function () {
await runAttachTest({
autumn,
customerId,
@@ -112,7 +101,7 @@ describe(`${chalk.yellowBright(`${testCase}: Testing update ents (changing inclu
let usage = 50000;
let overage = 50000 - (newItem.included_usage as number);
it("should attach custom pro product", async function () {
it("should update overage item to have new included usage", async function () {
const customProduct = {
...pro,
items: customItems,
@@ -144,7 +133,7 @@ describe(`${chalk.yellowBright(`${testCase}: Testing update ents (changing inclu
});
});
it("should have correct invoice usage next cycle", async function () {
it("should have correct invoice next cycle", async function () {
const invoiceTotal = await getExpectedInvoiceTotal({
org,
env,
@@ -175,7 +164,7 @@ describe(`${chalk.yellowBright(`${testCase}: Testing update ents (changing inclu
});
const customer = await autumn.customers.get(customerId);
const invoice = customer.invoices[0];
const invoice = customer.invoices![0];
expect(invoice.total).to.equal(
invoiceTotal,
"invoice total after 1 cycle should be correct",

View File

@@ -183,7 +183,7 @@ describe(`${chalk.yellowBright(`${testCase}: Testing update ents (changing inclu
});
const customer = await autumn.customers.get(customerId);
const invoice = customer.invoices[0];
const invoice = customer.invoices![0];
expect(invoice.total).to.equal(
invoiceTotal,
"invoice total after 1 cycle should be correct",

View File

@@ -1,5 +1,5 @@
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import {
APIVersion,
AppEnv,
@@ -19,6 +19,7 @@ import { advanceTestClock } from "tests/utils/stripeUtils.js";
import { addWeeks } from "date-fns";
import { expectAutumnError } from "tests/utils/expectUtils/expectErrUtils.js";
import { timeout } from "@/utils/genUtils.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
const testCase = "updateQuantity1";
@@ -33,17 +34,6 @@ export let pro = constructProduct({
type: "pro",
});
/**
* upgrade3:
* Testing upgrades for arrear prorated
* 1. Start with pro monthly plan (usage-based)
* 2. Upgrade to pro annual plan (usage-based)
* 3. Upgrade to premium annual plan (usage-based)
*
* Verifies subscription items and anchors are correct after each upgrade
* with arrear prorated billing
*/
describe(`${chalk.yellowBright(`${testCase}: Testing upgrades with prepaid single use`)}`, () => {
let customerId = testCase;
let autumn: AutumnInt = new AutumnInt({ version: APIVersion.v1_4 });
@@ -80,6 +70,9 @@ describe(`${chalk.yellowBright(`${testCase}: Testing upgrades with prepaid singl
await createProducts({
autumn,
products: [pro],
db,
orgId: org.id,
env,
});
testClockId = testClockId1!;
@@ -110,40 +103,40 @@ describe(`${chalk.yellowBright(`${testCase}: Testing upgrades with prepaid singl
errCode: AttachErrCode.ProductAlreadyAttached,
func: async () => {
await autumn.attach({
customerId,
productId: pro.id,
customer_id: customerId,
product_id: pro.id,
options: proOpts,
});
},
});
});
const newOpts = [
{
feature_id: TestFeature.Users,
quantity: 1,
},
];
it("should throw error if try to reduce seats to less than current usage", async function () {
await autumn.track({
customer_id: customerId,
feature_id: TestFeature.Users,
value: 2,
});
// const newOpts = [
// {
// feature_id: TestFeature.Users,
// quantity: 1,
// },
// ];
// it("should throw error if try to reduce seats to less than current usage", async function () {
// await autumn.track({
// customer_id: customerId,
// feature_id: TestFeature.Users,
// value: 2,
// });
await timeout(1000);
// await timeout(1000);
await expectAutumnError({
errCode: AttachErrCode.InvalidOptions,
func: async () => {
await autumn.attach({
customerId,
productId: pro.id,
options: newOpts,
});
},
});
});
// await expectAutumnError({
// errCode: AttachErrCode.InvalidOptions,
// func: async () => {
// await autumn.attach({
// customer_id: customerId,
// product_id: pro.id,
// options: newOpts,
// });
// },
// });
// });
const updatedOpts = [
{

View File

@@ -32,7 +32,7 @@ let growth = constructProduct({
type: "growth",
});
describe(`${chalk.yellowBright("attach/upgrade1: Testing usage upgrades")}`, () => {
describe(`${chalk.yellowBright("upgrade1: Testing usage upgrades")}`, () => {
let customerId = "upgrade1";
let autumn: AutumnInt = new AutumnInt({ version: APIVersion.v1_4 });

View File

@@ -6,17 +6,11 @@ import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import {
APIVersion,
AppEnv,
FullCusProduct,
Organization,
} from "@autumn/shared";
import { APIVersion, AppEnv, Organization } from "@autumn/shared";
import { addPrefixToProducts, runAttachTest } from "../utils.js";
import { advanceTestClock } from "tests/utils/stripeUtils.js";
import { addWeeks, getDate } from "date-fns";
import { addWeeks } from "date-fns";
import { DrizzleCli } from "@/db/initDrizzle.js";
@@ -51,7 +45,7 @@ export let premiumAnnual = constructProduct({
* Verifies subscription items and anchors are correct after each upgrade
*/
describe(`${chalk.yellowBright("attach/upgrade2: Testing usage upgrades with monthly -> annual")}`, () => {
describe(`${chalk.yellowBright("upgrade2: Testing usage upgrades with monthly -> annual")}`, () => {
let customerId = "upgrade2";
let autumn: AutumnInt = new AutumnInt({ version: APIVersion.v1_4 });
let stripeCli: Stripe;
@@ -153,6 +147,7 @@ describe(`${chalk.yellowBright("attach/upgrade2: Testing usage upgrades with mon
db,
org,
env,
singleInvoice: true,
});
});
});

View File

@@ -29,7 +29,7 @@ export let pro = constructProduct({
export let premium = constructProduct({
items: [
constructPrepaidItem({
featureId: TestFeature.Users,
featureId: TestFeature.Messages,
price: 8,
billingUnits: 100,
}),
@@ -37,29 +37,6 @@ export let premium = constructProduct({
type: "premium",
});
export let proAnnual = constructProduct({
items: [
constructPrepaidItem({
featureId: TestFeature.Users,
price: 12,
billingUnits: 1,
}),
],
type: "pro",
isAnnual: true,
});
/**
* upgrade3:
* Testing upgrades for arrear prorated
* 1. Start with pro monthly plan (usage-based)
* 2. Upgrade to pro annual plan (usage-based)
* 3. Upgrade to premium annual plan (usage-based)
*
* Verifies subscription items and anchors are correct after each upgrade
* with arrear prorated billing
*/
describe(`${chalk.yellowBright(`${testCase}: Testing upgrades with prepaid single use`)}`, () => {
let customerId = testCase;
let autumn: AutumnInt = new AutumnInt({ version: APIVersion.v1_4 });
@@ -89,13 +66,13 @@ describe(`${chalk.yellowBright(`${testCase}: Testing upgrades with prepaid singl
});
addPrefixToProducts({
products: [pro, premium, proAnnual],
products: [pro, premium],
prefix: testCase,
});
await createProducts({
autumn,
products: [pro, premium, proAnnual],
products: [pro, premium],
db,
orgId: org.id,
env,
@@ -136,7 +113,9 @@ describe(`${chalk.yellowBright(`${testCase}: Testing upgrades with prepaid singl
stripeCli,
testClockId,
advanceTo: addWeeks(curUnix, 1).getTime(),
waitForSeconds: 20,
});
await runAttachTest({
autumn,
customerId,

View File

@@ -10,12 +10,10 @@ import { addPrefixToProducts, runAttachTest } from "../utils.js";
import {
constructArrearItem,
constructArrearProratedItem,
constructPrepaidItem,
} from "@/utils/scriptUtils/constructItem.js";
import { TestFeature } from "tests/setup/v2Features.js";
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
import { advanceTestClock } from "tests/utils/stripeUtils.js";
import { addWeeks } from "date-fns";
import { expectAutumnError } from "tests/utils/expectUtils/expectErrUtils.js";
import { attachFailedPaymentMethod } from "@/external/stripe/stripeCusUtils.js";
import { CusService } from "@/internal/customers/CusService.js";
@@ -55,9 +53,6 @@ describe(`${chalk.yellowBright(`${testCase}: Testing failed upgrades`)}`, () =>
let db: DrizzleCli, org: Organization, env: AppEnv;
let stripeCli: Stripe;
let curUnix = new Date().getTime();
let numUsers = 0;
before(async function () {
await setupBefore(this);
const { autumnJs } = this;
@@ -121,6 +116,7 @@ describe(`${chalk.yellowBright(`${testCase}: Testing failed upgrades`)}`, () =>
});
await attachFailedPaymentMethod({ stripeCli, customer: cus! });
await timeout(2000);
await expectAutumnError({
func: async () => {

View File

@@ -0,0 +1,75 @@
import chalk from "chalk";
import { products } from "tests/global.js";
import { assert } from "chai";
import { Customer } from "@autumn/shared";
import { compareMainProduct } from "tests/utils/compare.js";
import { addDays } from "date-fns";
import { setupBefore } from "tests/before.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
import Stripe from "stripe";
describe(`${chalk.yellowBright(
"upgradeOld1: Testing upgrade (trial to paid)",
)}`, () => {
const customerId = "upgradeOld1";
let testClockId: string;
let customer: Customer;
let autumn: AutumnInt = new AutumnInt();
let stripeCli: Stripe;
before(async function () {
await setupBefore(this);
stripeCli = this.stripeCli;
const { customer: customer_, testClockId: testClockId_ } =
await initCustomer({
autumn: this.autumnJs,
customerId,
db: this.db,
org: this.org,
env: this.env,
attachPm: "success",
});
customer = customer_;
testClockId = testClockId_;
});
it("should attach pro with trial", async function () {
await autumn.attach({
customer_id: customerId,
product_id: products.proWithTrial.id,
});
});
it("should attach premium", async function () {
await advanceTestClock({
stripeCli,
testClockId,
advanceTo: addDays(new Date(), 3).getTime(),
waitForSeconds: 10,
});
await autumn.attach({
customer_id: customerId,
product_id: products.premium.id,
});
});
it("should check product, ents and invoices", async function () {
const res = await autumn.customers.get(customerId);
compareMainProduct({
sent: products.premium,
cusRes: res,
});
const invoices = await res.invoices;
assert.equal(
invoices[0].total,
products.premium.prices[0].config.amount,
"Invoice should be for 50.00",
);
});
});

View File

@@ -0,0 +1,52 @@
import Stripe from "stripe";
import chalk from "chalk";
import { Customer } from "@autumn/shared";
import { setupBefore } from "tests/before.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { products } from "tests/global.js";
import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
describe(`${chalk.yellowBright(
"upgradeOld2: Testing upgrade (paid to trial)",
)}`, () => {
const customerId = "upgradeOld2";
let testClockId: string;
let customer: Customer;
let autumn: AutumnInt = new AutumnInt();
let stripeCli: Stripe;
before(async function () {
await setupBefore(this);
stripeCli = this.stripeCli;
const { customer: customer_, testClockId: testClockId_ } =
await initCustomer({
autumn: this.autumnJs,
customerId,
db: this.db,
org: this.org,
env: this.env,
attachPm: "success",
});
customer = customer_;
testClockId = testClockId_;
});
it("should attach pro", async function () {
this.timeout(30000);
await autumn.attach({
customer_id: customerId,
product_id: products.pro.id,
});
});
it("should attach premium with trial and have trial", async function () {
this.timeout(30000);
await autumn.attach({
customer_id: customerId,
product_id: products.premiumWithTrial.id,
});
});
});

View File

@@ -0,0 +1,72 @@
import assert from "assert";
import chalk from "chalk";
import { CusProductStatus } from "@autumn/shared";
import { addDays } from "date-fns";
import { setupBefore } from "tests/before.js";
import { compareMainProduct } from "tests/utils/compare.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { products } from "tests/global.js";
import Stripe from "stripe";
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
describe(`${chalk.yellowBright("upgradeOld3: Testing upgrade (trial to trial)")}`, () => {
const customerId = "upgradeOld3";
let testClockId: string;
let autumn: AutumnInt = new AutumnInt();
let stripeCli: Stripe;
before(async function () {
await setupBefore(this);
stripeCli = this.stripeCli;
const { customer: customer_, testClockId: testClockId_ } =
await initCustomer({
autumn: this.autumnJs,
customerId,
db: this.db,
org: this.org,
env: this.env,
attachPm: "success",
});
testClockId = testClockId_;
});
it("should attach pro with trial", async function () {
this.timeout(30000);
await autumn.attach({
customer_id: customerId,
product_id: products.proWithTrial.id,
});
console.log(` ${chalk.greenBright("Attached pro with trial")}`);
});
it("should attach premium with trial", async function () {
const advanceTo = addDays(new Date(), 3).getTime();
await advanceTestClock({
stripeCli,
testClockId,
advanceTo,
waitForSeconds: 10,
});
await autumn.attach({
customer_id: customerId,
product_id: products.premiumWithTrial.id,
});
});
it("should check product and ents", async function () {
const res = await autumn.customers.get(customerId);
compareMainProduct({
sent: products.premiumWithTrial,
cusRes: res,
status: CusProductStatus.Trialing,
});
const invoices = res.invoices;
assert.equal(invoices![0].total, 0, "Invoice should be 0");
});
});

View File

@@ -0,0 +1,116 @@
// TESTING UPGRADES
import chalk from "chalk";
import { AppEnv, Organization } from "@autumn/shared";
import { AutumnCli } from "tests/cli/AutumnCli.js";
import { products } from "tests/global.js";
import {
attachFailedPaymentMethod,
attachPmToCus,
} from "@/external/stripe/stripeCusUtils.js";
import { Customer } from "@autumn/shared";
import { compareMainProduct } from "tests/utils/compare.js";
import { expectAutumnError } from "tests/utils/expectUtils/expectErrUtils.js";
import { DrizzleCli } from "@/db/initDrizzle.js";
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import Stripe from "stripe";
import { setupBefore } from "tests/before.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
const testCase = "upgradeOld4";
describe(`${chalk.yellowBright("upgradeOld4: Testing upgrade from pro -> premium")}`, () => {
let customer: Customer;
let customerId = testCase;
let stripeCli: Stripe;
let db: DrizzleCli;
let org: Organization;
let env: AppEnv;
let autumn: AutumnInt = new AutumnInt();
before(async function () {
await setupBefore(this);
stripeCli = this.stripeCli;
db = this.db;
org = this.org;
env = this.env;
let { customer: customer_ } = await initCustomer({
autumn: this.autumnJs,
customerId,
db,
org,
env,
attachPm: "success",
});
customer = customer_;
});
it("should attach pro (trial)", async function () {
await autumn.attach({
customer_id: customerId,
product_id: products.pro.id,
});
let res = await autumn.customers.get(customerId);
compareMainProduct({
sent: products.pro,
cusRes: res,
});
});
// 1. Try force checkout...
it("should attach premium and not be able to force checkout", async function () {
expectAutumnError({
func: async () => {
await autumn.attach({
customer_id: customerId,
product_id: products.premium.id,
force_checkout: true,
});
},
});
});
it("should attach premium and not be able to upgrade (without payment method)", async function () {
await attachFailedPaymentMethod({
stripeCli: stripeCli,
customer: customer,
});
await expectAutumnError({
func: async () => {
await autumn.attach({
customer_id: customerId,
product_id: products.premium.id,
force_checkout: true,
});
},
});
});
// Attach payment method
it("should attach successful payment method", async function () {
await attachPmToCus({
db: this.db,
customer: customer,
org: this.org,
env: this.env,
});
});
it("should attach premium and have correct product and entitlements", async function () {
await AutumnCli.attach({
customerId: customerId,
productId: products.premium.id,
});
const res = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: products.premium,
cusRes: res,
});
});
});

View File

@@ -44,6 +44,7 @@ export const runAttachTest = async ({
waitForInvoice = 0,
isCanceled = false,
skipFeatureCheck = false,
singleInvoice = false,
}: {
autumn: AutumnInt;
customerId: string;
@@ -61,6 +62,7 @@ export const runAttachTest = async ({
waitForInvoice?: number;
isCanceled?: boolean;
skipFeatureCheck?: boolean;
singleInvoice?: boolean;
}) => {
const preview = await autumn.attachPreview({
customer_id: customerId,
@@ -110,10 +112,11 @@ export const runAttachTest = async ({
const freeProduct = isFreeProductV2({ product });
if (!freeProduct) {
let multiInvoice = !singleInvoice && multiInterval;
expectInvoicesCorrect({
customer,
first: multiInterval ? undefined : { productId: product.id, total },
second: multiInterval ? { productId: product.id, total } : undefined,
first: multiInvoice ? undefined : { productId: product.id, total },
second: multiInvoice ? { productId: product.id, total } : undefined,
});
}
@@ -141,7 +144,7 @@ export const runAttachTest = async ({
});
const stripeSubs = await stripeCli.subscriptions.list({
customer: customer.stripe_id,
customer: customer.stripe_id!,
});
if (multiInterval) {
expect(stripeSubs.data.length).to.equal(2, "should have 2 subscriptions");

View File

@@ -1,131 +0,0 @@
import { createStripeCli } from "@/external/stripe/utils.js";
import { addMonths } from "date-fns";
import Stripe from "stripe";
import { AutumnCli } from "tests/cli/AutumnCli.js";
import { advanceProducts } from "tests/global.js";
import {
checkProductIsScheduled,
compareMainProduct,
} from "tests/utils/compare.js";
import { initCustomer } from "tests/utils/init.js";
import { advanceClockForInvoice } from "tests/utils/stripeUtils.js";
import { advanceMonths } from "tests/utils/stripeUtils.js";
import { initCustomerWithTestClock } from "tests/utils/testInitUtils.js";
// TEST MULTI INTERVAL DOWNGRADE
//
/*
CASE 1: Annual pro -> Annual starter
- If attach annual starter, should schedule correctly [DONE]
- If advance test clock, should downgrade correctly (to monthly starter) [DONE]
- If cancel active subscription (on Stripe), should remove scheduled correctly [DONE]
- If cancel scheduled subscription (on Stripe), should remove scheduled correctly [DONE]
- If expire on dashboard, should remove scheduled correctly
- If upgrade back to annual pro, should remove scheduled correctly [DONE]
- If downgrade to monthly pro (switch downgrade), should be correct [DONE]
- If downgrade to free (switch downgrade), should be correct
*/
describe.skip("Multi interval downgrade -- Quarterly pro -> Monthly pro", () => {
let customerId = "multi-interval-downgrade";
let stripeCli: Stripe;
let testClockId: string;
before(async function () {
const { testClockId: insertedTestClockId } =
await initCustomerWithTestClock({
customerId,
org: this.org,
env: this.env,
db: this.db,
});
testClockId = insertedTestClockId;
stripeCli = createStripeCli({
org: this.org,
env: this.env,
});
});
it("should attach quarterly pro", async function () {
let res = await AutumnCli.attach({
customerId: customerId,
productId: advanceProducts.gpuProQuarter.id,
});
let cusRes = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: advanceProducts.gpuProQuarter,
cusRes,
});
});
it("should attach downgrade to monthly pro", async function () {
let res = await AutumnCli.attach({
customerId: customerId,
productId: advanceProducts.gpuSystemPro.id,
});
let cusRes = await AutumnCli.getCustomer(customerId);
checkProductIsScheduled({
cusRes,
product: advanceProducts.gpuSystemPro,
});
});
it("should advance clock by a year", async function () {
// let numberOfMonths = 2;
// await advanceMonths({
// stripeCli,
// testClockId,
// numberOfMonths,
// });
});
});
describe("Multi interval downgrade -- Annual pro -> Annual starter", () => {
let customerId = "multi-interval-downgrade";
let customer;
let stripeCli;
let testClockId: string;
before(async function () {
await initCustomer({
customer_data: {
id: customerId,
name: customerId,
email: "multi-interval-downgrade@example.com",
},
attachPm: true,
org: this.org,
env: this.env,
db: this.db,
});
});
it("should attach annual pro", async function () {
let res = await AutumnCli.attach({
customerId: customerId,
productId: advanceProducts.gpuProAnnual.id,
});
let cusRes = await AutumnCli.getCustomer(customerId);
compareMainProduct({
sent: advanceProducts.gpuProAnnual,
cusRes,
});
});
it("should attach downgrade to annual starter", async function () {
let res = await AutumnCli.attach({
customerId: customerId,
productId: advanceProducts.gpuStarterAnnual.id,
});
let cusRes = await AutumnCli.getCustomer(customerId);
checkProductIsScheduled({
cusRes,
product: advanceProducts.gpuStarterAnnual,
});
});
});

Some files were not shown because too many files have changed in this diff Show More