chore: merged from main
This commit is contained in:
@@ -122,7 +122,7 @@ export const runRedisDeduction = async ({
|
||||
overageBehavior,
|
||||
eventInfo,
|
||||
}: RunRedisDeductionParams): Promise<RunRedisDeductionResult> => {
|
||||
const { org, env } = ctx;
|
||||
const { org, env, skipCache } = ctx;
|
||||
|
||||
const hasContUseFeature = featureDeductions.some((deduction) =>
|
||||
isContUseFeature({ feature: deduction.feature }),
|
||||
@@ -144,7 +144,7 @@ export const runRedisDeduction = async ({
|
||||
};
|
||||
}
|
||||
|
||||
if (query.skip_cache) {
|
||||
if (query.skip_cache || skipCache) {
|
||||
return {
|
||||
fallback: true,
|
||||
code: "skip_cache",
|
||||
|
||||
@@ -31,6 +31,7 @@ export const runTrack = async ({
|
||||
statusCode: 400,
|
||||
});
|
||||
}
|
||||
// ctx.skipCache = true;
|
||||
|
||||
const { fallback, balances } = await runRedisDeduction({
|
||||
ctx,
|
||||
|
||||
@@ -103,7 +103,7 @@ export const deductFromCusEnts = async ({
|
||||
const isPaidAllocated = deductions.some((d) =>
|
||||
isPaidContinuousUse({
|
||||
feature: d.feature,
|
||||
fullCus,
|
||||
fullCus: fullCus!,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -374,6 +374,10 @@ export const runDeductionTx = async (
|
||||
|
||||
const result = await db.transaction(
|
||||
async (tx) => {
|
||||
// Set statement timeout to cancel query at database level if it takes too long
|
||||
// This will automatically rollback the transaction and free the connection
|
||||
await tx.execute(sql`SET LOCAL statement_timeout = '1000ms'`);
|
||||
|
||||
// Pass tx as the db connection
|
||||
const txParams = {
|
||||
...params,
|
||||
|
||||
@@ -115,6 +115,5 @@ export const getCustomerAndProducts = async ({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { customer, products };
|
||||
};
|
||||
|
||||
0
server/src/test.ts
Normal file
0
server/src/test.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { ApiVersion } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
|
||||
// const creditsFeature = constructFeatureItem({
|
||||
// featureId: TestFeature.Credits,
|
||||
// includedUsage: 1000,
|
||||
// });
|
||||
const actionFeature = constructArrearItem({
|
||||
featureId: TestFeature.Action1,
|
||||
includedUsage: 0,
|
||||
});
|
||||
|
||||
const proProd = constructProduct({
|
||||
type: "pro",
|
||||
isDefault: false,
|
||||
items: [actionFeature],
|
||||
});
|
||||
|
||||
const testCase = "credit-systems5";
|
||||
const customerId = "credit-systems5";
|
||||
|
||||
describe(`${chalk.yellowBright("credit-systems5: test check works with main feature in credit system (usage allowed true)")}`, () => {
|
||||
const autumnV1: AutumnInt = new AutumnInt({ version: ApiVersion.V1_2 });
|
||||
|
||||
beforeAll(async () => {
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
withTestClock: false,
|
||||
attachPm: "success",
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [proProd],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await autumnV1.attach({
|
||||
customer_id: customerId,
|
||||
product_id: proProd.id,
|
||||
});
|
||||
});
|
||||
|
||||
test("should allow check when feature has usage allowed true and part of a credit system", async () => {
|
||||
const checkResult = await autumnV1.check({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Action1,
|
||||
required_balance: 1,
|
||||
});
|
||||
|
||||
expect(checkResult.allowed).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,277 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { ApiVersion, type LimitedItem } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { timeout } from "@tests/utils/genUtils.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { Decimal } from "decimal.js";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
|
||||
const testCase = "concurrent-track7";
|
||||
|
||||
// Product with both lifetime and monthly Messages features
|
||||
const lifetimeMessagesItem = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 20000,
|
||||
interval: null, // Lifetime
|
||||
}) as LimitedItem;
|
||||
|
||||
const monthlyMessagesItem = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 10000,
|
||||
interval: "month" as any,
|
||||
intervalCount: 1,
|
||||
}) as LimitedItem;
|
||||
|
||||
const pro = constructProduct({
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
items: [lifetimeMessagesItem, monthlyMessagesItem],
|
||||
});
|
||||
|
||||
const NUM_REQUESTS = 500; // Reduced from 10000 to avoid DB parameter limits
|
||||
const NUM_CUSTOMERS = 3;
|
||||
|
||||
// Calculate total included usage dynamically
|
||||
const TOTAL_INCLUDED_USAGE =
|
||||
(lifetimeMessagesItem.included_usage ?? 0) +
|
||||
(monthlyMessagesItem.included_usage ?? 0);
|
||||
|
||||
// Helper to generate random decimal between min and max using Decimal.js
|
||||
const randomDecimal = (min: number, max: number): Decimal => {
|
||||
const value = Math.random() * (max - min) + min;
|
||||
return new Decimal(value).toDecimalPlaces(2);
|
||||
};
|
||||
|
||||
describe(`${chalk.yellowBright(`${testCase}: Stress test with 10k concurrent requests per customer through check (send_event)`)}`, () => {
|
||||
const autumnV1: AutumnInt = new AutumnInt({ version: ApiVersion.V1_2 });
|
||||
const customerIds = Array.from(
|
||||
{ length: NUM_CUSTOMERS },
|
||||
(_, i) => `${testCase}_customer${i + 1}`,
|
||||
);
|
||||
|
||||
// Store expected total usage per customer using Decimal for precision
|
||||
const customerExpectedUsage: Record<string, Decimal> = {};
|
||||
|
||||
beforeAll(async () => {
|
||||
// Initialize all customers
|
||||
for (const customerId of customerIds) {
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
withTestClock: false,
|
||||
});
|
||||
}
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
for (const customerId of customerIds) {
|
||||
await autumnV1.attach({
|
||||
customer_id: customerId,
|
||||
product_id: pro.id,
|
||||
});
|
||||
|
||||
// Initialize expected usage to 0
|
||||
customerExpectedUsage[customerId] = new Decimal(0);
|
||||
}
|
||||
});
|
||||
|
||||
test("should have initial balances for all customers", async () => {
|
||||
for (const customerId of customerIds) {
|
||||
const customer = await autumnV1.customers.get(customerId);
|
||||
|
||||
console.log(`\n🔍 Initial state for ${customerId}:`);
|
||||
console.log(
|
||||
` Balance: ${customer.features[TestFeature.Messages].balance}`,
|
||||
);
|
||||
console.log(` Usage: ${customer.features[TestFeature.Messages].usage}`);
|
||||
|
||||
// Total balance should be lifetime + monthly
|
||||
expect(customer.features[TestFeature.Messages].balance).toBe(
|
||||
TOTAL_INCLUDED_USAGE,
|
||||
);
|
||||
expect(customer.features[TestFeature.Messages].usage).toBe(0);
|
||||
expect(customer.features[TestFeature.Messages].breakdown?.length).toBe(2);
|
||||
}
|
||||
});
|
||||
|
||||
test(`should handle ${NUM_REQUESTS * NUM_CUSTOMERS} concurrent requests across ${NUM_CUSTOMERS} customers`, async () => {
|
||||
console.log(
|
||||
`\n🚀 Starting ${NUM_REQUESTS * NUM_CUSTOMERS} concurrent track requests...`,
|
||||
);
|
||||
console.log(
|
||||
` ${NUM_REQUESTS} requests per customer × ${NUM_CUSTOMERS} customers`,
|
||||
);
|
||||
|
||||
const allPromises: Promise<number>[] = [];
|
||||
|
||||
// Generate requests for each customer
|
||||
for (const customerId of customerIds) {
|
||||
const customerPromises: Promise<number>[] = [];
|
||||
|
||||
for (let i = 0; i < NUM_REQUESTS; i++) {
|
||||
// Generate random value between 0.01 and 2.00 using Decimal
|
||||
const decimalValue = randomDecimal(0.01, 2.0);
|
||||
const value = decimalValue.toDecimalPlaces(5).toNumber();
|
||||
|
||||
// Accumulate expected usage using Decimal for precision
|
||||
customerExpectedUsage[customerId] =
|
||||
customerExpectedUsage[customerId].plus(decimalValue);
|
||||
|
||||
// Create track request for Messages feature with timing
|
||||
const requestStart = Date.now();
|
||||
const promise = autumnV1
|
||||
.check({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
send_event: true,
|
||||
required_balance: value,
|
||||
skip_event: true, // Skip event insertion for stress test
|
||||
})
|
||||
.then(() => Date.now() - requestStart);
|
||||
|
||||
customerPromises.push(promise);
|
||||
}
|
||||
|
||||
allPromises.push(...customerPromises);
|
||||
}
|
||||
|
||||
// Execute all requests concurrently
|
||||
const startTime = Date.now();
|
||||
const durations = await Promise.all(allPromises);
|
||||
const endTime = Date.now();
|
||||
|
||||
// Calculate P99
|
||||
const sortedDurations = durations.sort((a, b) => a - b);
|
||||
const p99Index = Math.floor(sortedDurations.length * 0.99);
|
||||
const p99 = sortedDurations[p99Index];
|
||||
|
||||
console.log(
|
||||
`\n✅ Completed ${NUM_REQUESTS * NUM_CUSTOMERS} requests in ${endTime - startTime}ms`,
|
||||
);
|
||||
console.log(
|
||||
` Average: ${((endTime - startTime) / (NUM_REQUESTS * NUM_CUSTOMERS)).toFixed(2)}ms per request`,
|
||||
);
|
||||
console.log(` P99: ${p99.toFixed(2)}ms`);
|
||||
|
||||
// Log expected totals per customer
|
||||
for (const customerId of customerIds) {
|
||||
console.log(`\n📊 ${customerId}:`);
|
||||
console.log(
|
||||
` Total usage: ${customerExpectedUsage[customerId].toFixed(2)} units`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("should have correct cached balances for all customers", async () => {
|
||||
for (const customerId of customerIds) {
|
||||
const customer = await autumnV1.customers.get(customerId);
|
||||
|
||||
const totalUsage = customerExpectedUsage[customerId];
|
||||
|
||||
// Balance should be capped at 0 (no negative balances without overage_allowed)
|
||||
const expectedBalance = Decimal.max(
|
||||
0,
|
||||
new Decimal(TOTAL_INCLUDED_USAGE).minus(totalUsage),
|
||||
)
|
||||
.toDP(5)
|
||||
.toNumber();
|
||||
const actualBalance = new Decimal(
|
||||
customer.features[TestFeature.Messages].balance ?? 0,
|
||||
)
|
||||
.toDP(5)
|
||||
.toNumber();
|
||||
|
||||
// Usage should be capped at included_usage without overage_allowed
|
||||
const expectedUsage = Decimal.min(totalUsage, TOTAL_INCLUDED_USAGE)
|
||||
.toDP(5)
|
||||
.toNumber();
|
||||
const actualUsage = new Decimal(
|
||||
customer.features[TestFeature.Messages].usage ?? 0,
|
||||
)
|
||||
.toDP(5)
|
||||
.toNumber();
|
||||
|
||||
// Verify balance and usage match expectations
|
||||
expect(actualBalance).toEqual(expectedBalance);
|
||||
expect(actualUsage).toEqual(expectedUsage);
|
||||
|
||||
// Verify breakdown balances sum to top-level balance
|
||||
const breakdown = customer.features[TestFeature.Messages].breakdown;
|
||||
if (breakdown && breakdown.length > 0) {
|
||||
const breakdownBalance = breakdown.reduce(
|
||||
(sum, b) => new Decimal(sum).plus(b.balance || 0).toNumber(),
|
||||
0,
|
||||
);
|
||||
expect(new Decimal(breakdownBalance).toDP(5).toNumber()).toEqual(
|
||||
actualBalance!,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("should have correct non-cached balances for all customers after 2s", async () => {
|
||||
console.log("\n⏳ Waiting 2s for DB sync...");
|
||||
await timeout(5000);
|
||||
|
||||
for (const customerId of customerIds) {
|
||||
const customer = await autumnV1.customers.get(customerId, {
|
||||
skip_cache: "true",
|
||||
});
|
||||
|
||||
const totalUsage = customerExpectedUsage[customerId];
|
||||
|
||||
// Balance should be capped at 0 (no negative balances without overage_allowed)
|
||||
const expectedBalance = Decimal.max(
|
||||
0,
|
||||
new Decimal(TOTAL_INCLUDED_USAGE).minus(totalUsage),
|
||||
)
|
||||
.toDP(5)
|
||||
.toNumber();
|
||||
const actualBalance = new Decimal(
|
||||
customer.features[TestFeature.Messages].balance ?? 0,
|
||||
)
|
||||
.toDP(5)
|
||||
.toNumber();
|
||||
|
||||
// Usage should be capped at included_usage without overage_allowed
|
||||
const expectedUsage = Decimal.min(totalUsage, TOTAL_INCLUDED_USAGE)
|
||||
.toDP(5)
|
||||
.toNumber();
|
||||
const actualUsage = new Decimal(
|
||||
customer.features[TestFeature.Messages].usage ?? 0,
|
||||
)
|
||||
.toDP(5)
|
||||
.toNumber();
|
||||
|
||||
// Use Decimal for precise comparisons - expect exact match
|
||||
expect(actualBalance).toEqual(expectedBalance);
|
||||
|
||||
// Verify usage matches - expect exact match
|
||||
expect(actualUsage).toEqual(expectedUsage);
|
||||
|
||||
// Verify breakdown balances match top-level (lifetime + monthly)
|
||||
const breakdown = customer.features[TestFeature.Messages].breakdown;
|
||||
if (breakdown && breakdown.length > 0) {
|
||||
const breakdownBalance = breakdown.reduce(
|
||||
(sum, b) => new Decimal(sum).plus(b.balance || 0).toNumber(),
|
||||
0,
|
||||
);
|
||||
|
||||
expect(new Decimal(breakdownBalance).toDP(5).toNumber()).toEqual(
|
||||
actualBalance!,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("\n✅ All balances verified successfully!");
|
||||
});
|
||||
});
|
||||
63
server/tests/balances/track/misc/track-misc4.test.ts
Normal file
63
server/tests/balances/track/misc/track-misc4.test.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { ApiVersion, type TrackResponseV2 } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
|
||||
const messagesFeature = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 100,
|
||||
});
|
||||
|
||||
const freeProd = constructProduct({
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
items: [messagesFeature],
|
||||
});
|
||||
|
||||
const testCase = "track-misc4";
|
||||
|
||||
describe(`${chalk.yellowBright("track-misc4: track version 1.2 response")}`, () => {
|
||||
const customerId = "track-misc4";
|
||||
const autumnV1: AutumnInt = new AutumnInt({ version: ApiVersion.V1_2 });
|
||||
|
||||
beforeAll(async () => {
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
withTestClock: false,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [freeProd],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await autumnV1.attach({
|
||||
customer_id: customerId,
|
||||
product_id: freeProd.id,
|
||||
});
|
||||
});
|
||||
|
||||
test("should track version 1.2 response", async () => {
|
||||
const trackRes: TrackResponseV2 = await autumnV1.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: 20,
|
||||
});
|
||||
|
||||
expect(trackRes).toMatchObject({
|
||||
id: "placeholder",
|
||||
code: "event_received",
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
// value: 20,
|
||||
});
|
||||
});
|
||||
});
|
||||
77
server/tests/balances/track/negative/track-negative1.test.ts
Normal file
77
server/tests/balances/track/negative/track-negative1.test.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { ApiVersion, type TrackResponseV2 } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
|
||||
const messagesFeature = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 100,
|
||||
});
|
||||
|
||||
const freeProd = constructProduct({
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
items: [messagesFeature],
|
||||
});
|
||||
|
||||
const testCase = "track-negative1";
|
||||
|
||||
describe(`${chalk.yellowBright("track-negative1: track negative on meterd feature")}`, () => {
|
||||
const customerId = "track-negative1";
|
||||
const autumnV1: AutumnInt = new AutumnInt({ version: ApiVersion.V1_2 });
|
||||
const autumnV2: AutumnInt = new AutumnInt({ version: ApiVersion.V2_0 });
|
||||
|
||||
beforeAll(async () => {
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
withTestClock: false,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [freeProd],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await autumnV1.attach({
|
||||
customer_id: customerId,
|
||||
product_id: freeProd.id,
|
||||
});
|
||||
});
|
||||
|
||||
test("should have initial balance of 100", async () => {
|
||||
const customer = await autumnV1.customers.get(customerId);
|
||||
const balance = customer.features[TestFeature.Messages].balance;
|
||||
|
||||
expect(balance).toBe(100);
|
||||
});
|
||||
|
||||
test("should deduct from messages with negative value", async () => {
|
||||
const deductValue = -37.89;
|
||||
|
||||
const trackRes: TrackResponseV2 = await autumnV2.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Messages,
|
||||
value: deductValue,
|
||||
});
|
||||
|
||||
expect(trackRes.balance).toBeDefined();
|
||||
expect(trackRes.balance?.feature_id).toBe(TestFeature.Messages);
|
||||
expect(trackRes.balance?.current_balance).toBe(100 - deductValue);
|
||||
expect(trackRes.balance?.usage).toBe(deductValue);
|
||||
|
||||
const customer = await autumnV1.customers.get(customerId);
|
||||
const balance = customer.features[TestFeature.Messages].balance;
|
||||
const usage = customer.features[TestFeature.Messages].usage;
|
||||
|
||||
expect(balance).toBe(100 - deductValue);
|
||||
expect(usage).toBe(deductValue);
|
||||
});
|
||||
});
|
||||
122
server/tests/balances/track/negative/track-negative2.test.ts
Normal file
122
server/tests/balances/track/negative/track-negative2.test.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import {
|
||||
type ApiCustomer,
|
||||
ApiVersion,
|
||||
type TrackResponseV2,
|
||||
} from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { timeout } from "../../../utils/genUtils";
|
||||
|
||||
const userItem = constructFeatureItem({
|
||||
featureId: TestFeature.Users,
|
||||
includedUsage: 5,
|
||||
});
|
||||
|
||||
const freeProd = constructProduct({
|
||||
type: "free",
|
||||
isDefault: false,
|
||||
items: [userItem],
|
||||
});
|
||||
|
||||
const testCase = "track-negative2";
|
||||
|
||||
describe(`${chalk.yellowBright("track-negative2: track negative on free allocated feature")}`, () => {
|
||||
const customerId = "track-negative2";
|
||||
const autumnV1: AutumnInt = new AutumnInt({ version: ApiVersion.V1_2 });
|
||||
const autumnV2: AutumnInt = new AutumnInt({ version: ApiVersion.V2_0 });
|
||||
|
||||
beforeAll(async () => {
|
||||
await initCustomerV3({
|
||||
ctx,
|
||||
customerId,
|
||||
withTestClock: false,
|
||||
});
|
||||
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [freeProd],
|
||||
prefix: testCase,
|
||||
});
|
||||
|
||||
await autumnV1.attach({
|
||||
customer_id: customerId,
|
||||
product_id: freeProd.id,
|
||||
});
|
||||
});
|
||||
|
||||
test("should have initial balance of 5", async () => {
|
||||
const customer = await autumnV1.customers.get(customerId);
|
||||
const balance = customer.features[TestFeature.Users].balance;
|
||||
|
||||
expect(balance).toBe(5);
|
||||
});
|
||||
|
||||
test("should track positive into 'overage' ", async () => {
|
||||
const trackValue = 8;
|
||||
|
||||
const trackRes: TrackResponseV2 = await autumnV2.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Users,
|
||||
value: trackValue,
|
||||
});
|
||||
|
||||
expect(trackRes.balance).toBeDefined();
|
||||
expect(trackRes.balance).toMatchObject({
|
||||
current_balance: 0,
|
||||
purchased_balance: 3,
|
||||
usage: trackValue,
|
||||
});
|
||||
});
|
||||
|
||||
test("should track negative and reduce purchased balance first", async () => {
|
||||
const trackValue = -2;
|
||||
|
||||
const trackRes: TrackResponseV2 = await autumnV2.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Users,
|
||||
value: trackValue,
|
||||
});
|
||||
|
||||
expect(trackRes.balance).toMatchObject({
|
||||
current_balance: 0,
|
||||
purchased_balance: 1,
|
||||
usage: 6,
|
||||
});
|
||||
});
|
||||
|
||||
test("should track negative and reduce granted balance second", async () => {
|
||||
const trackValue = -2;
|
||||
|
||||
const trackRes: TrackResponseV2 = await autumnV2.track({
|
||||
customer_id: customerId,
|
||||
feature_id: TestFeature.Users,
|
||||
value: trackValue,
|
||||
});
|
||||
|
||||
expect(trackRes.balance).toMatchObject({
|
||||
current_balance: 1,
|
||||
purchased_balance: 0,
|
||||
usage: 4,
|
||||
});
|
||||
});
|
||||
|
||||
test("non-cached customer should reflect changes", async () => {
|
||||
await timeout(2000);
|
||||
const customer = await autumnV2.customers.get<ApiCustomer>(customerId, {
|
||||
skip_cache: "true",
|
||||
});
|
||||
|
||||
expect(customer.balances[TestFeature.Users]).toMatchObject({
|
||||
current_balance: 1,
|
||||
purchased_balance: 0,
|
||||
usage: 4,
|
||||
});
|
||||
});
|
||||
});
|
||||
62
server/tests/crud/plan/get-plan/get-plan1.test.ts
Normal file
62
server/tests/crud/plan/get-plan/get-plan1.test.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { beforeAll, describe, test } from "bun:test";
|
||||
import { type ApiProduct, ApiVersion } from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import {
|
||||
constructArrearItem,
|
||||
constructFeatureItem,
|
||||
constructPrepaidItem,
|
||||
} from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
|
||||
const messagesFeature = constructFeatureItem({
|
||||
featureId: TestFeature.Messages,
|
||||
includedUsage: 100,
|
||||
});
|
||||
|
||||
const usageFeature = constructArrearItem({
|
||||
featureId: TestFeature.Words,
|
||||
includedUsage: 10,
|
||||
billingUnits: 1,
|
||||
price: 0.5,
|
||||
});
|
||||
|
||||
const prepaidFeature = constructPrepaidItem({
|
||||
featureId: TestFeature.Credits,
|
||||
billingUnits: 150,
|
||||
price: 10,
|
||||
});
|
||||
|
||||
const pro = constructProduct({
|
||||
type: "pro",
|
||||
isDefault: false,
|
||||
items: [messagesFeature, usageFeature, prepaidFeature],
|
||||
});
|
||||
|
||||
const testCase = "get-plan1";
|
||||
|
||||
describe(`${chalk.yellowBright("get-plan1: get plan response v1.2")}`, () => {
|
||||
const autumnV1: AutumnInt = new AutumnInt({ version: ApiVersion.V1_2 });
|
||||
|
||||
beforeAll(async () => {
|
||||
await initProductsV0({
|
||||
ctx,
|
||||
products: [pro],
|
||||
prefix: testCase,
|
||||
});
|
||||
});
|
||||
|
||||
test("should track version 1.2 response", async () => {
|
||||
const plan = (await autumnV1.products.get(pro.id)) as ApiProduct;
|
||||
|
||||
// 1. Check messages product item
|
||||
const msgesResponseItem = plan.items.find(
|
||||
(item) => item.feature_id === TestFeature.Messages,
|
||||
);
|
||||
|
||||
console.log(msgesResponseItem);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user