fix: tests
This commit is contained in:
@@ -5,9 +5,9 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/config.sh"
|
||||
|
||||
# If contains setup then run $MOCHA_SETUP
|
||||
if [[ "$1" == *"setup"* ]]; then
|
||||
MOCHA_PARALLEL=true $MOCHA_SETUP
|
||||
fi
|
||||
# if [[ "$1" == *"setup"* ]]; then
|
||||
# MOCHA_PARALLEL=true $MOCHA_SETUP
|
||||
# fi
|
||||
|
||||
$MOCHA_CMD \
|
||||
'tests/attach/basic/*.ts' \
|
||||
|
||||
@@ -7,20 +7,19 @@ source "$(dirname "$0")/config.sh"
|
||||
if [[ "$1" == *"setup"* ]]; then
|
||||
MOCHA_PARALLEL=true $MOCHA_SETUP
|
||||
fi
|
||||
# $MOCHA_CMD 'tests/advanced/referrals/*.ts' 'tests/advanced/coupons/*.ts'
|
||||
|
||||
# $MOCHA_CMD 'tests/advanced/multiFeature/*.ts' \
|
||||
# 'tests/advanced/coupons/*.ts' \
|
||||
# 'tests/attach/updateQuantity/*.ts' \
|
||||
# 'tests/advanced/referrals/*.ts' \
|
||||
# 'tests/advanced/referrals/paid/*.ts' \
|
||||
# 'tests/advanced/rollovers/*.ts' \
|
||||
# 'tests/advanced/customInterval/*.ts'
|
||||
$MOCHA_CMD 'tests/advanced/multiFeature/*.ts' \
|
||||
'tests/advanced/coupons/*.ts' \
|
||||
'tests/attach/updateQuantity/*.ts' \
|
||||
'tests/advanced/referrals/*.ts' \
|
||||
'tests/advanced/referrals/paid/*.ts' \
|
||||
'tests/advanced/rollovers/*.ts' \
|
||||
'tests/advanced/customInterval/*.ts'
|
||||
|
||||
# $MOCHA_CMD 'tests/attach/multiProduct/*.ts' \
|
||||
# 'tests/advanced/usageLimit/*.ts'
|
||||
$MOCHA_CMD 'tests/attach/multiProduct/*.ts' \
|
||||
'tests/advanced/usageLimit/*.ts'
|
||||
|
||||
# $MOCHA_CMD 'tests/advanced/usage/*.ts'
|
||||
$MOCHA_CMD 'tests/advanced/usage/*.ts'
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type AppEnv, type Organization } from "@autumn/shared";
|
||||
import type { AppEnv, Organization } from "@autumn/shared";
|
||||
import chalk from "chalk";
|
||||
import { Stripe } from "stripe";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
@@ -266,6 +266,7 @@ export const handleStripeWebhookEvent = async ({
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Stripe webhook, error refreshing cache!`, { error });
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
|
||||
@@ -41,7 +41,10 @@ export const handleConnectWebhook = async (c: Context<HonoEnv>) => {
|
||||
}
|
||||
|
||||
const accountId = event.account;
|
||||
if (!accountId) return c.json({ error: "Account ID not found" }, 200);
|
||||
if (!accountId) {
|
||||
logger.error(`Account ID not found in webhook event`);
|
||||
return c.json({ error: "Account ID not found" }, 200);
|
||||
}
|
||||
|
||||
const { org, features } = await OrgService.getByAccountId({
|
||||
db,
|
||||
@@ -78,6 +81,6 @@ export const handleConnectWebhook = async (c: Context<HonoEnv>) => {
|
||||
return c.json({ message: "Webhook received" }, 200);
|
||||
} catch (error) {
|
||||
logger.error(`Stripe webhook, error: ${error}`, { error });
|
||||
return c.json({ message: "Internal server error" }, 500);
|
||||
return c.json({ message: "Webhook received, internal server error" }, 200); // 200 to avoid retries / shutdown of webhook...
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import {
|
||||
type ApiPlatformOrg,
|
||||
type ListPlatformOrgsQuery,
|
||||
ListPlatformOrgsQuerySchema,
|
||||
organizations,
|
||||
} from "@autumn/shared";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { createRoute } from "@/honoMiddlewares/routeHandler.js";
|
||||
import { toPlatformOrg } from "./platformOrgUtils.js";
|
||||
|
||||
/**
|
||||
* Route: GET /platform/orgs - List organizations created by master org
|
||||
*/
|
||||
export const handleListPlatformOrgs = createRoute({
|
||||
query: ListPlatformOrgsQuerySchema,
|
||||
handler: async (c) => {
|
||||
const query = c.req.valid("query") as ListPlatformOrgsQuery;
|
||||
const ctx = c.get("ctx");
|
||||
const { db, org: masterOrg } = ctx;
|
||||
|
||||
const orgs = await db
|
||||
.select()
|
||||
.from(organizations)
|
||||
.where(eq(organizations.created_by, masterOrg.id))
|
||||
.limit(query.limit)
|
||||
.offset(query.offset);
|
||||
|
||||
const orgsList: ApiPlatformOrg[] = orgs.map((org) =>
|
||||
toPlatformOrg({
|
||||
org: {
|
||||
slug: org.slug,
|
||||
name: org.name,
|
||||
createdAt: org.createdAt,
|
||||
},
|
||||
masterOrgId: masterOrg.id,
|
||||
}),
|
||||
);
|
||||
|
||||
return c.json({
|
||||
list: orgsList,
|
||||
total: orgs.length,
|
||||
limit: query.limit,
|
||||
offset: query.offset,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
import { eq } from "drizzle-orm";
|
||||
import { cte } from "@/db/cteUtils/buildCte.js";
|
||||
import { createRoute } from "@/honoMiddlewares/routeHandler.js";
|
||||
import { toPlatformOrg } from "./platformOrgUtils.js";
|
||||
|
||||
/**
|
||||
* Route: GET /platform/users - List users created by master org
|
||||
@@ -19,7 +20,7 @@ export const listPlatformUsers = createRoute({
|
||||
const query = c.req.valid("query") as ListPlatformUsersQuery;
|
||||
const ctx = c.get("ctx");
|
||||
|
||||
const { db, org, logger } = ctx;
|
||||
const { db, org } = ctx;
|
||||
|
||||
const shouldExpandOrgs = query.expand?.includes("organizations");
|
||||
|
||||
@@ -54,11 +55,16 @@ export const listPlatformUsers = createRoute({
|
||||
created_at: new Date(user.created_at).getTime(),
|
||||
...(shouldExpandOrgs &&
|
||||
user.organizations && {
|
||||
organizations: user.organizations.map((org: any) => ({
|
||||
slug: cleanOrgSlug(org.slug, org.id),
|
||||
organizations: user.organizations.map((org: any) =>
|
||||
toPlatformOrg({
|
||||
org: {
|
||||
slug: org.slug,
|
||||
name: org.name,
|
||||
created_at: new Date(org.createdAt).getTime(),
|
||||
})),
|
||||
createdAt: org.createdAt,
|
||||
},
|
||||
masterOrgId: ctx.org?.id || "",
|
||||
}),
|
||||
),
|
||||
}),
|
||||
}));
|
||||
|
||||
@@ -70,23 +76,3 @@ export const listPlatformUsers = createRoute({
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Remove master org ID prefix from organization slug
|
||||
*/
|
||||
function cleanOrgSlug(slug: string, orgId: string): string {
|
||||
let cleanedSlug = slug;
|
||||
const prefix = `${orgId}_`;
|
||||
if (cleanedSlug.startsWith(prefix)) {
|
||||
cleanedSlug = cleanedSlug.slice(prefix.length);
|
||||
}
|
||||
// Handle the case where slug is prepended with "slug_orgId"
|
||||
const altPrefix1 = `_${orgId}`;
|
||||
const altPrefix2 = `|${orgId}`;
|
||||
if (cleanedSlug.endsWith(altPrefix1)) {
|
||||
cleanedSlug = cleanedSlug.slice(0, -altPrefix1.length);
|
||||
} else if (cleanedSlug.endsWith(altPrefix2)) {
|
||||
cleanedSlug = cleanedSlug = cleanedSlug.slice(0, -altPrefix2.length);
|
||||
}
|
||||
return cleanedSlug;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import type { ApiPlatformOrg } from "@autumn/shared";
|
||||
|
||||
/**
|
||||
* Remove master org ID prefix from organization slug
|
||||
*/
|
||||
function cleanOrgSlug({
|
||||
slug,
|
||||
orgId,
|
||||
}: {
|
||||
slug: string;
|
||||
orgId: string;
|
||||
}): string {
|
||||
let cleanedSlug = slug;
|
||||
const prefix = `${orgId}_`;
|
||||
if (cleanedSlug.startsWith(prefix)) {
|
||||
cleanedSlug = cleanedSlug.slice(prefix.length);
|
||||
}
|
||||
// Handle the case where slug is prepended with "slug_orgId"
|
||||
const altPrefix1 = `_${orgId}`;
|
||||
const altPrefix2 = `|${orgId}`;
|
||||
if (cleanedSlug.endsWith(altPrefix1)) {
|
||||
cleanedSlug = cleanedSlug.slice(0, -altPrefix1.length);
|
||||
} else if (cleanedSlug.endsWith(altPrefix2)) {
|
||||
cleanedSlug = cleanedSlug = cleanedSlug.slice(0, -altPrefix2.length);
|
||||
}
|
||||
return cleanedSlug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert raw org data to ApiPlatformOrg format
|
||||
*/
|
||||
export function toPlatformOrg({
|
||||
org,
|
||||
masterOrgId,
|
||||
}: {
|
||||
org: { slug: string; name: string; createdAt: string | Date };
|
||||
masterOrgId: string;
|
||||
}): ApiPlatformOrg {
|
||||
return {
|
||||
slug: cleanOrgSlug({ slug: org.slug, orgId: masterOrgId }),
|
||||
name: org.name,
|
||||
created_at: new Date(org.createdAt).getTime(),
|
||||
};
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Autumn } from "autumn-js";
|
||||
import { Hono } from "hono";
|
||||
import type { HonoEnv } from "@/honoUtils/HonoEnv.js";
|
||||
import { listPlatformUsers } from "../platformLegacy/handlers/handleListPlatformUsers.js";
|
||||
import { handleCreatePlatformOrg } from "./handlers/handleCreatePlatformOrg.js";
|
||||
import { handleGetPlatformOAuth } from "./handlers/handleGetPlatformOAuth.js";
|
||||
import { handleListPlatformOrgs } from "./handlers/handleListPlatformOrgs.js";
|
||||
import { listPlatformUsers } from "./handlers/handleListPlatformUsers.js";
|
||||
import { handleUpdateOrganizationStripe } from "./handlers/handleUpdateOrganizationStripe.js";
|
||||
|
||||
const platformBetaRouter = new Hono<HonoEnv>();
|
||||
@@ -61,7 +62,7 @@ platformBetaRouter.use("*", async (c, next) => {
|
||||
* POST /organization
|
||||
* Creates a new organization for platform users
|
||||
*/
|
||||
platformBetaRouter.post("/organization", ...handleCreatePlatformOrg);
|
||||
platformBetaRouter.post("/organizations", ...handleCreatePlatformOrg);
|
||||
|
||||
/**
|
||||
* POST /oauth_url
|
||||
@@ -80,4 +81,5 @@ platformBetaRouter.post(
|
||||
|
||||
platformBetaRouter.get("/users", ...listPlatformUsers);
|
||||
|
||||
platformBetaRouter.get("/organizations", ...handleListPlatformOrgs);
|
||||
export { platformBetaRouter };
|
||||
|
||||
@@ -57,7 +57,6 @@ const reward: CreateReward = {
|
||||
describe(
|
||||
chalk.yellow(`${testCase} - Testing one-off rollover, apply to usage only`),
|
||||
() => {
|
||||
let logger: any;
|
||||
const customerId = testCase;
|
||||
let stripeCli: Stripe;
|
||||
let testClockId: string;
|
||||
@@ -67,7 +66,7 @@ describe(
|
||||
let env: AppEnv;
|
||||
let db: DrizzleCli;
|
||||
|
||||
let couponAmount = reward.discount_config!.discount_value;
|
||||
let couponAmount = reward.discount_config?.discount_value ?? 0;
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
|
||||
@@ -1,35 +1,34 @@
|
||||
import chalk from "chalk";
|
||||
import { expect } from "chai";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { features } from "tests/global.js";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
|
||||
/** biome-ignore-all lint/suspicious/noExportsInTest: needed */
|
||||
import {
|
||||
AppEnv,
|
||||
type AppEnv,
|
||||
BillingInterval,
|
||||
EntInterval,
|
||||
ProductItemFeatureType,
|
||||
UsageModel,
|
||||
} from "@autumn/shared";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import { getMainCusProduct } from "tests/utils/cusProductUtils/cusProductUtils.js";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import { addMonths } from "date-fns";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { features } from "tests/global.js";
|
||||
import {
|
||||
getLifetimeFreeCusEnt,
|
||||
getUsageCusEnt,
|
||||
} from "tests/utils/cusProductUtils/cusEntSearchUtils.js";
|
||||
|
||||
import { getMainCusProduct } from "tests/utils/cusProductUtils/cusProductUtils.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import { advanceTestClock } from "tests/utils/stripeUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import {
|
||||
constructFeatureItem,
|
||||
constructFeaturePriceItem,
|
||||
} from "@/internal/products/product-items/productItemUtils.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import { advanceTestClock } from "tests/utils/stripeUtils.js";
|
||||
import { initCustomerWithTestClock } from "tests/utils/testInitUtils.js";
|
||||
import { addMonths } from "date-fns";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { initCustomerV2 } from "@/utils/scriptUtils/initCustomer.js";
|
||||
|
||||
// Scenario 1: prepaid + pay per use monthly -> prepaid + pay per use monthly
|
||||
let pro = {
|
||||
const pro = {
|
||||
id: "multiFeature3Pro",
|
||||
name: "Multi Feature 3 Pro",
|
||||
items: {
|
||||
@@ -62,19 +61,19 @@ export const getLifetimeAndUsageCusEnts = async ({
|
||||
env: AppEnv;
|
||||
featureId: string;
|
||||
}) => {
|
||||
let mainCusProduct = await getMainCusProduct({
|
||||
const mainCusProduct = await getMainCusProduct({
|
||||
customerId,
|
||||
db,
|
||||
orgId,
|
||||
env,
|
||||
});
|
||||
|
||||
let lifetimeCusEnt = getLifetimeFreeCusEnt({
|
||||
const lifetimeCusEnt = getLifetimeFreeCusEnt({
|
||||
cusProduct: mainCusProduct!,
|
||||
featureId,
|
||||
});
|
||||
|
||||
let usageCusEnt = getUsageCusEnt({
|
||||
const usageCusEnt = getUsageCusEnt({
|
||||
cusProduct: mainCusProduct!,
|
||||
featureId,
|
||||
});
|
||||
@@ -86,8 +85,8 @@ export const getLifetimeAndUsageCusEnts = async ({
|
||||
describe(`${chalk.yellowBright(
|
||||
"multi-feature/multi_feature3: Testing lifetime + pay per use, advance test clock",
|
||||
)}`, () => {
|
||||
let autumn: AutumnInt = new AutumnInt();
|
||||
let customerId = "multiFeature3Customer";
|
||||
const autumn: AutumnInt = new AutumnInt();
|
||||
const customerId = "multiFeature3Customer";
|
||||
|
||||
let totalUsage = 0;
|
||||
|
||||
@@ -95,17 +94,16 @@ describe(`${chalk.yellowBright(
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
|
||||
let { customer, testClockId: _testClockId } =
|
||||
await initCustomerWithTestClock({
|
||||
const res = await initCustomerV2({
|
||||
autumn,
|
||||
customerId,
|
||||
db: this.db,
|
||||
org: this.org,
|
||||
env: this.env,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
testClockId = _testClockId;
|
||||
|
||||
autumn = this.autumn;
|
||||
testClockId = res.testClockId;
|
||||
|
||||
await createProducts({
|
||||
autumn,
|
||||
@@ -122,7 +120,7 @@ describe(`${chalk.yellowBright(
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
let { lifetimeCusEnt, usageCusEnt } = await getLifetimeAndUsageCusEnts({
|
||||
const { lifetimeCusEnt, usageCusEnt } = await getLifetimeAndUsageCusEnts({
|
||||
customerId,
|
||||
db: this.db,
|
||||
orgId: this.org.id,
|
||||
@@ -135,7 +133,7 @@ describe(`${chalk.yellowBright(
|
||||
expect(usageCusEnt?.balance).to.equal(pro.items.payPerUse.included_usage);
|
||||
});
|
||||
|
||||
let overageValue = 30;
|
||||
const overageValue = 30;
|
||||
it("should use lifetime allowance + overage", async function () {
|
||||
let value = pro.items.lifetime.included_usage as number;
|
||||
value += overageValue;
|
||||
@@ -150,7 +148,7 @@ describe(`${chalk.yellowBright(
|
||||
|
||||
await timeout(3000);
|
||||
|
||||
let { lifetimeCusEnt, usageCusEnt } = await getLifetimeAndUsageCusEnts({
|
||||
const { lifetimeCusEnt, usageCusEnt } = await getLifetimeAndUsageCusEnts({
|
||||
customerId,
|
||||
db: this.db,
|
||||
orgId: this.org.id,
|
||||
@@ -163,14 +161,14 @@ describe(`${chalk.yellowBright(
|
||||
});
|
||||
|
||||
it("cycle 1:should have correct usage after first cycle", async function () {
|
||||
let advanceTo = addMonths(new Date(), 1).getTime();
|
||||
const advanceTo = addMonths(new Date(), 1).getTime();
|
||||
await advanceTestClock({
|
||||
stripeCli: this.stripeCli,
|
||||
testClockId,
|
||||
advanceTo,
|
||||
});
|
||||
|
||||
let { lifetimeCusEnt, usageCusEnt } = await getLifetimeAndUsageCusEnts({
|
||||
const { lifetimeCusEnt, usageCusEnt } = await getLifetimeAndUsageCusEnts({
|
||||
customerId,
|
||||
db: this.db,
|
||||
orgId: this.org.id,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
type Customer,
|
||||
ErrCode,
|
||||
type Organization,
|
||||
type ReferralCode,
|
||||
type RewardRedemption,
|
||||
} from "@autumn/shared";
|
||||
@@ -12,8 +14,9 @@ import { setupBefore } from "tests/before.js";
|
||||
import { timeout } from "tests/utils/genUtils.js";
|
||||
import { initCustomer } from "tests/utils/init.js";
|
||||
import { advanceTestClock } from "tests/utils/stripeUtils.js";
|
||||
import { initCustomerWithTestClock } from "tests/utils/testInitUtils.js";
|
||||
import AutumnError, { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { initCustomerV2 } from "@/utils/scriptUtils/initCustomer.js";
|
||||
import { products, referralPrograms } from "../../global.js";
|
||||
|
||||
// UNCOMMENT FROM HERE
|
||||
@@ -29,17 +32,20 @@ describe(`${chalk.yellowBright(
|
||||
|
||||
const redemptions: RewardRedemption[] = [];
|
||||
let mainCustomer: Customer;
|
||||
|
||||
let org: Organization;
|
||||
let env: AppEnv;
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
stripeCli = this.stripeCli;
|
||||
org = this.org;
|
||||
env = this.env;
|
||||
|
||||
const { testClockId: testClockId1, customer } =
|
||||
await initCustomerWithTestClock({
|
||||
const { testClockId: testClockId1, customer } = await initCustomerV2({
|
||||
customerId: mainCustomerId,
|
||||
db: this.db,
|
||||
org: this.org,
|
||||
env: this.env,
|
||||
autumn,
|
||||
});
|
||||
testClockId = testClockId1;
|
||||
mainCustomer = customer;
|
||||
@@ -95,8 +101,17 @@ describe(`${chalk.yellowBright(
|
||||
}
|
||||
|
||||
// Check stripe customer
|
||||
const stripeCus = (await stripeCli.customers.retrieve(
|
||||
const legacyStripe = createStripeCli({
|
||||
org: org,
|
||||
env: env,
|
||||
legacyVersion: true,
|
||||
});
|
||||
|
||||
const stripeCus = (await legacyStripe.customers.retrieve(
|
||||
mainCustomer.processor?.id,
|
||||
{
|
||||
expand: ["discount"],
|
||||
},
|
||||
)) as Stripe.Customer;
|
||||
|
||||
assert.notEqual(stripeCus.discount, null);
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import { features, products, referralPrograms } from "../../global.js";
|
||||
import type { Customer, ReferralCode, RewardRedemption } from "@autumn/shared";
|
||||
import { assert } from "chai";
|
||||
import chalk from "chalk";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
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";
|
||||
import { initCustomer } from "tests/utils/init.js";
|
||||
import { compareProductEntitlements } from "tests/utils/compare.js";
|
||||
import { addDays, addHours } from "date-fns";
|
||||
import { advanceTestClock } from "tests/utils/stripeUtils.js";
|
||||
import type { Stripe } from "stripe";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
import { compareProductEntitlements } from "tests/utils/compare.js";
|
||||
import { hoursToFinalizeInvoice } from "tests/utils/constants.js";
|
||||
import { timeout } from "tests/utils/genUtils.js";
|
||||
import { initCustomer } from "tests/utils/init.js";
|
||||
import { advanceTestClock } from "tests/utils/stripeUtils.js";
|
||||
import { initCustomerWithTestClock } from "tests/utils/testInitUtils.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { features, products, referralPrograms } from "../../global.js";
|
||||
|
||||
// UNCOMMENT FROM HERE
|
||||
describe(`${chalk.yellowBright(
|
||||
"referrals4: Testing free product referrals with trial",
|
||||
)}`, () => {
|
||||
let mainCustomerId = "main-referral-4";
|
||||
const mainCustomerId = "main-referral-4";
|
||||
// let redeemers = ["referral4-r1", "referral4-r2"];
|
||||
let redeemerId = "referral4-r1";
|
||||
const redeemerId = "referral4-r1";
|
||||
|
||||
let autumn: AutumnInt = new AutumnInt();
|
||||
let stripeCli: Stripe;
|
||||
let referralCode: ReferralCode;
|
||||
|
||||
let redemptions: RewardRedemption[] = [];
|
||||
const redemptions: RewardRedemption[] = [];
|
||||
let mainCustomer: Customer;
|
||||
let redeemer: Customer;
|
||||
|
||||
@@ -48,7 +48,7 @@ describe(`${chalk.yellowBright(
|
||||
product_id: products.proWithTrial.id,
|
||||
});
|
||||
|
||||
let { testClockId: testClockId1, customer } =
|
||||
const { testClockId: testClockId1, customer } =
|
||||
await initCustomerWithTestClock({
|
||||
customerId: redeemerId,
|
||||
db: this.db,
|
||||
@@ -60,7 +60,7 @@ describe(`${chalk.yellowBright(
|
||||
redeemer = customer;
|
||||
});
|
||||
|
||||
it("should create referral code", async function () {
|
||||
it("should create referral code", async () => {
|
||||
referralCode = await autumn.referrals.createCode({
|
||||
customerId: mainCustomerId,
|
||||
referralId: referralPrograms.freeProduct.id,
|
||||
@@ -69,8 +69,8 @@ describe(`${chalk.yellowBright(
|
||||
assert.exists(referralCode.code);
|
||||
});
|
||||
|
||||
it("should create redemption for each redeemer and fail if redeemed again", async function () {
|
||||
let redemption: RewardRedemption = await autumn.referrals.redeem({
|
||||
it("should create redemption for each redeemer and fail if redeemed again", async () => {
|
||||
const redemption: RewardRedemption = await autumn.referrals.redeem({
|
||||
customerId: redeemerId,
|
||||
code: referralCode.code,
|
||||
});
|
||||
@@ -78,7 +78,7 @@ describe(`${chalk.yellowBright(
|
||||
redemptions.push(redemption);
|
||||
});
|
||||
|
||||
it("should not be triggered because of trial", async function () {
|
||||
it("should not be triggered because of trial", async () => {
|
||||
await autumn.attach({
|
||||
customer_id: redeemerId,
|
||||
product_id: products.proWithTrial.id,
|
||||
@@ -87,13 +87,13 @@ describe(`${chalk.yellowBright(
|
||||
await timeout(3000);
|
||||
|
||||
// Get redemption object
|
||||
let redemption = await autumn.redemptions.get(redemptions[0].id);
|
||||
const redemption = await autumn.redemptions.get(redemptions[0].id);
|
||||
|
||||
assert.equal(redemption.triggered, false);
|
||||
});
|
||||
|
||||
it("should be triggered after trial ends", async function () {
|
||||
let advanceTo = addHours(
|
||||
it("should be triggered after trial ends", async () => {
|
||||
const advanceTo = addHours(
|
||||
addDays(new Date(), 7),
|
||||
hoursToFinalizeInvoice,
|
||||
).getTime();
|
||||
@@ -104,7 +104,7 @@ describe(`${chalk.yellowBright(
|
||||
waitForSeconds: 30,
|
||||
});
|
||||
|
||||
let redemption = await autumn.redemptions.get(redemptions[0].id);
|
||||
const redemption = await autumn.redemptions.get(redemptions[0].id);
|
||||
|
||||
assert.equal(redemption.triggered, true);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { AppEnv, Organization } from "@autumn/shared";
|
||||
import { expect } from "chai";
|
||||
import chalk from "chalk";
|
||||
import { setupBefore } from "tests/before.js";
|
||||
@@ -7,6 +8,7 @@ import { compareMainProduct } from "tests/utils/compare.js";
|
||||
import { timeout } from "tests/utils/genUtils.js";
|
||||
import { createProducts } from "tests/utils/productUtils.js";
|
||||
import { completeCheckoutForm } from "tests/utils/stripeUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructPrepaidItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructRawProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
@@ -51,7 +53,7 @@ const testCase = "basic3";
|
||||
describe(`${chalk.yellowBright("basic3: Testing attach one time / monthly add ons")}`, () => {
|
||||
const customerId = testCase;
|
||||
const autumn: AutumnInt = new AutumnInt();
|
||||
let db, org, env;
|
||||
let db: DrizzleCli, org: Organization, env: AppEnv;
|
||||
|
||||
before(async function () {
|
||||
await setupBefore(this);
|
||||
|
||||
@@ -38,19 +38,6 @@ const pro = constructProduct({
|
||||
trial: true,
|
||||
});
|
||||
|
||||
const ops = [
|
||||
{
|
||||
entityId: "1",
|
||||
product: pro,
|
||||
results: [{ product: pro, status: CusProductStatus.Active }],
|
||||
},
|
||||
{
|
||||
entityId: "2",
|
||||
product: pro,
|
||||
results: [{ product: pro, status: CusProductStatus.Active }],
|
||||
},
|
||||
];
|
||||
|
||||
const testCase = "multiInvoice1";
|
||||
describe(`${chalk.yellowBright("multiInvoice1: Testing multi attach through invoice flow")}`, () => {
|
||||
const customerId = testCase;
|
||||
@@ -98,7 +85,7 @@ describe(`${chalk.yellowBright("multiInvoice1: Testing multi attach through invo
|
||||
testClockId = testClockId1!;
|
||||
});
|
||||
|
||||
it("should run multi attach through checkout and have correct sub", async () => {
|
||||
it("should run multi attach through invoice checkout flow", async () => {
|
||||
const productsList = [
|
||||
{
|
||||
product_id: pro.id,
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
type ProductOptions,
|
||||
type ProductV2,
|
||||
} from "@autumn/shared";
|
||||
import type { Customer, Entity } from "autumn-js";
|
||||
import { expect } from "chai";
|
||||
import { expectSubToBeCorrect } from "tests/merged/mergeUtils/expectSubCorrect.js";
|
||||
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
|
||||
@@ -75,8 +76,10 @@ export const expectMultiAttachCorrect = async ({
|
||||
await timeout(5000);
|
||||
}
|
||||
|
||||
await timeout(2500);
|
||||
|
||||
for (const result of results) {
|
||||
let customer;
|
||||
let customer: Customer | Entity;
|
||||
if (result.entityId) {
|
||||
customer = await autumn.entities.get(customerId, result.entityId);
|
||||
} else {
|
||||
@@ -84,7 +87,7 @@ export const expectMultiAttachCorrect = async ({
|
||||
}
|
||||
|
||||
expectProductAttached({
|
||||
customer,
|
||||
customer: customer as Customer,
|
||||
product: result.product,
|
||||
status: result.status,
|
||||
entityId: result.entityId,
|
||||
|
||||
@@ -27,3 +27,48 @@ export function queryStringArray<T extends z.ZodTypeAny>(schema: T) {
|
||||
return val;
|
||||
}, z.array(schema));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to handle query string integers that come in as strings and need to be converted to numbers.
|
||||
* Query parameters are always strings, so this helper parses them to integers for validation.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const schema = z.object({
|
||||
* limit: queryInteger({ min: 1, max: 100 }).default(10),
|
||||
* offset: queryInteger({ min: 0 }).default(0),
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function queryInteger(options?: {
|
||||
min?: number;
|
||||
max?: number;
|
||||
error?: string;
|
||||
}) {
|
||||
let schema = z.number().int({ message: options?.error });
|
||||
|
||||
if (options?.min !== undefined) {
|
||||
schema = schema.min(options.min, {
|
||||
message: options?.error || `must be at least ${options.min}`,
|
||||
});
|
||||
}
|
||||
|
||||
if (options?.max !== undefined) {
|
||||
schema = schema.max(options.max, {
|
||||
message: options?.error || `must be at most ${options.max}`,
|
||||
});
|
||||
}
|
||||
|
||||
return z.preprocess((val) => {
|
||||
// If already a number, return as-is
|
||||
if (typeof val === "number") {
|
||||
return val;
|
||||
}
|
||||
// Parse string to integer
|
||||
if (typeof val === "string") {
|
||||
const parsed = Number.parseInt(val, 10);
|
||||
return Number.isNaN(parsed) ? val : parsed;
|
||||
}
|
||||
return val;
|
||||
}, schema);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,13 @@
|
||||
import { queryStringArray } from "@api/common/queryHelpers.js";
|
||||
import { queryInteger, queryStringArray } from "@api/common/queryHelpers.js";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
/**
|
||||
* Query params for GET /platform/users endpoint
|
||||
*/
|
||||
export const ListPlatformUsersQuerySchema = z.object({
|
||||
limit: z
|
||||
.number()
|
||||
.int({ error: "limit must be an integer" })
|
||||
.min(1, { error: "limit must be at least 1" })
|
||||
.max(100, { error: "limit must be at most 100" })
|
||||
.default(10),
|
||||
limit: queryInteger({ min: 1, max: 100 }).default(10),
|
||||
|
||||
offset: z
|
||||
.number({ error: "offset must be a number" })
|
||||
.int({ error: "offset must be an integer" })
|
||||
.min(0, { error: "offset must be at least 0" })
|
||||
.default(0),
|
||||
offset: queryInteger({ min: 0 }).default(0),
|
||||
|
||||
expand: queryStringArray(z.enum(["organizations"]))
|
||||
.optional()
|
||||
@@ -72,3 +63,28 @@ export const ListPlatformUsersResponseSchema = z.object({
|
||||
export type ListPlatformUsersResponse = z.infer<
|
||||
typeof ListPlatformUsersResponseSchema
|
||||
>;
|
||||
|
||||
/**
|
||||
* Query params for GET /platform/orgs endpoint
|
||||
*/
|
||||
export const ListPlatformOrgsQuerySchema = z.object({
|
||||
limit: queryInteger({ min: 1, max: 100 }).default(10),
|
||||
|
||||
offset: queryInteger({ min: 0 }).default(0),
|
||||
});
|
||||
|
||||
export type ListPlatformOrgsQuery = z.infer<typeof ListPlatformOrgsQuerySchema>;
|
||||
|
||||
/**
|
||||
* Response schema for GET /platform/orgs
|
||||
*/
|
||||
export const ListPlatformOrgsResponseSchema = z.object({
|
||||
list: z.array(ApiPlatformOrgSchema),
|
||||
total: z.number().describe("Total number of organizations returned"),
|
||||
limit: z.number().describe("Limit used in the query"),
|
||||
offset: z.number().describe("Offset used in the query"),
|
||||
});
|
||||
|
||||
export type ListPlatformOrgsResponse = z.infer<
|
||||
typeof ListPlatformOrgsResponseSchema
|
||||
>;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { ApiVersion, API_VERSIONS } from "./ApiVersion.js";
|
||||
import { API_VERSIONS, type ApiVersion } from "./ApiVersion.js";
|
||||
import type { VersionMetadata } from "./versionRegistry.js";
|
||||
import { getVersionMetadata, getVersionsSorted } from "./versionRegistryUtils.js";
|
||||
import {
|
||||
getVersionMetadata,
|
||||
getVersionsSorted,
|
||||
} from "./versionRegistryUtils.js";
|
||||
|
||||
/**
|
||||
* ApiVersionClass - Encapsulates version comparison logic
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { API_VERSIONS, ApiVersion } from "./ApiVersion.js";
|
||||
import { API_VERSIONS, type ApiVersion } from "./ApiVersion.js";
|
||||
import { VERSION_REGISTRY, type VersionMetadata } from "./versionRegistry.js";
|
||||
|
||||
/**
|
||||
@@ -24,7 +24,9 @@ export function getVersionMetadata({
|
||||
return VERSION_REGISTRY[version];
|
||||
}
|
||||
|
||||
export function isValidVersion(params: { version: string }): params is { version: ApiVersion } {
|
||||
export function isValidVersion(params: {
|
||||
version: string;
|
||||
}): params is { version: ApiVersion } {
|
||||
return API_VERSIONS.includes(params.version as ApiVersion);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user