feat: set up test and run tests easier

This commit is contained in:
John Yeo
2025-10-20 10:32:26 +01:00
parent 54aafa5734
commit dc90d1dab4
21 changed files with 1165 additions and 216 deletions

View File

@@ -1,3 +0,0 @@
{
"terminals": []
}

View File

@@ -1,12 +0,0 @@
{
"name": "Autumn dev container",
"image": "mcr.microsoft.com/devcontainers/node:18",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
"forwardPorts": [8080],
// "postCreateCommand": "pnpm install",
"postCreateCommand": "apt update && apt install -y zsh",
"settings": {
"terminal.integrated.shell.linux": "/bin/zsh"
},
"extensions": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
}

View File

@@ -1,79 +0,0 @@
# name: Benchmark Server PR
# on:
# pull_request:
# types: [opened, synchronize, reopened]
# paths:
# - 'server/**'
# - 'pnpm-lock.yaml'
# - 'package.json'
# permissions:
# contents: read
# pull-requests: write
# jobs:
# benchmark:
# runs-on: ubuntu-latest
# steps:
# - name: Checkout code
# uses: actions/checkout@v4
# - name: Setup pnpm
# uses: pnpm/action-setup@v2
# with:
# version: latest
# - name: Setup Node.js
# uses: actions/setup-node@v4
# with:
# node-version: '20'
# cache: 'pnpm'
# - name: Install dependencies
# run: pnpm i --no-frozen-lockfile
# - name: Run benchmark
# id: benchmark
# working-directory: ./server
# run: |
# echo "BENCHMARK_OUTPUT<<EOF" >> $GITHUB_OUTPUT
# FULL_OUTPUT=$(pnpm run benchmark 2>&1)
# FILTERED_OUTPUT=$(echo "$FULL_OUTPUT" | grep -v "^> @.*benchmark" | grep -v "^> tsx benchmarks" | grep -v "Benchmark completed successfully")
# echo "$FILTERED_OUTPUT" >> $GITHUB_OUTPUT
# echo "EOF" >> $GITHUB_OUTPUT
# if echo "$FULL_OUTPUT" | grep -q "Benchmark completed successfully"; then
# echo "BENCHMARK_STATUS=✅ Passed" >> $GITHUB_OUTPUT
# else
# echo "BENCHMARK_STATUS=❌ Failed" >> $GITHUB_OUTPUT
# fi
# - name: Comment PR
# uses: actions/github-script@v7
# with:
# script: |
# const output = `${{ steps.benchmark.outputs.BENCHMARK_OUTPUT }}`;
# const status = `${{ steps.benchmark.outputs.BENCHMARK_STATUS }}`;
# const body = `## 📊 Benchmark Results
# **Benchmark CI:** ${status}
# <details>
# <summary>Click to view benchmark results</summary>
# \`\`\`javascript
# ${output}
# \`\`\`
# </details>
# *Benchmark run for commit ${{ github.sha }}*`;
# github.rest.issues.createComment({
# issue_number: context.issue.number,
# owner: context.repo.owner,
# repo: context.repo.repo,
# body: body
# });

View File

@@ -18,6 +18,21 @@
"inquirer": "^12.6.3", "inquirer": "^12.6.3",
}, },
}, },
"scripts": {
"name": "@autumn/scripts",
"version": "1.0.0",
"dependencies": {
"@autumn/shared": "workspace:*",
"chalk": "^5.3.0",
"dotenv": "^16.5.0",
"inquirer": "^12.6.3",
},
"devDependencies": {
"@types/inquirer": "^9.0.7",
"tsx": "^4.19.2",
"typescript": "^5.7.3",
},
},
"server": { "server": {
"name": "@autumn/server", "name": "@autumn/server",
"version": "1.0.0", "version": "1.0.0",
@@ -318,6 +333,8 @@
"@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.32.1", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-U9JwTrDvdQ9iWuABVsMLj8nJVwAyQz6QXvgLsVhryhCEPkLsbcP/MXxm+jYcAwLoV8ESbaTTjnD4kuAFa+Hyjg=="], "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.32.1", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-U9JwTrDvdQ9iWuABVsMLj8nJVwAyQz6QXvgLsVhryhCEPkLsbcP/MXxm+jYcAwLoV8ESbaTTjnD4kuAFa+Hyjg=="],
"@autumn/scripts": ["@autumn/scripts@workspace:scripts"],
"@autumn/server": ["@autumn/server@workspace:server"], "@autumn/server": ["@autumn/server@workspace:server"],
"@autumn/shared": ["@autumn/shared@workspace:shared"], "@autumn/shared": ["@autumn/shared@workspace:shared"],
@@ -1264,6 +1281,8 @@
"@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="], "@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="],
"@types/inquirer": ["@types/inquirer@9.0.9", "", { "dependencies": { "@types/through": "*", "rxjs": "^7.2.0" } }, "sha512-/mWx5136gts2Z2e5izdoRCo46lPp5TMs9R15GTSsgg/XnZyxDWVqoVU3R9lWnccKpqwsJLvRoxbCjoJtZB7DSw=="],
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/lodash": ["@types/lodash@4.17.20", "", {}, "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA=="], "@types/lodash": ["@types/lodash@4.17.20", "", {}, "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA=="],
@@ -1328,6 +1347,8 @@
"@types/tedious": ["@types/tedious@4.0.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw=="], "@types/tedious": ["@types/tedious@4.0.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw=="],
"@types/through": ["@types/through@0.0.33", "", { "dependencies": { "@types/node": "*" } }, "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ=="],
"@types/triple-beam": ["@types/triple-beam@1.3.5", "", {}, "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw=="], "@types/triple-beam": ["@types/triple-beam@1.3.5", "", {}, "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw=="],
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],

View File

@@ -4,7 +4,8 @@
"workspaces": [ "workspaces": [
"server", "server",
"shared", "shared",
"vite" "vite",
"scripts"
], ],
"type": "module", "type": "module",
"scripts": { "scripts": {
@@ -12,6 +13,7 @@
"dev:simple": "concurrently \"cd shared && bun dev:watch\" \"cd server && bun dev\" \"cd server && bun workers:dev\" \"cd vite && bun dev\"", "dev:simple": "concurrently \"cd shared && bun dev:watch\" \"cd server && bun dev\" \"cd server && bun workers:dev\" \"cd vite && bun dev\"",
"vite:build": "bun -F @autumn/shared build && bun -F @autumn/vite build:bun", "vite:build": "bun -F @autumn/shared build && bun -F @autumn/vite build:bun",
"vite:start": "bun -F @autumn/vite start:bun", "vite:start": "bun -F @autumn/vite start:bun",
"shared": "bun -F @autumn/shared build", "shared": "bun -F @autumn/shared build",
"server": "bun -F @autumn/shared build && bun -F @autumn/server start", "server": "bun -F @autumn/shared build && bun -F @autumn/server start",
"workers": "bun -F @autumn/shared build && bun -F @autumn/server workers", "workers": "bun -F @autumn/shared build && bun -F @autumn/server workers",
@@ -20,14 +22,19 @@
"server:cron": "pnpm -F server cron:start", "server:cron": "pnpm -F server cron:start",
"server:check": "NODE_ENV=production pnpm -F server check", "server:check": "NODE_ENV=production pnpm -F server check",
"setup": "node scripts/setup.js", "setup": "node scripts/setup.js",
"setup:test": "bun scripts/setup-test.ts",
"tests": "bun scripts/test.ts",
"setupci": "node scripts/setupci.js", "setupci": "node scripts/setupci.js",
"db:push": " bun -F @autumn/shared db:push", "db:push": " bun -F @autumn/shared db:push",
"db:generate": "bun -F @autumn/shared db:generate", "db:generate": "bun -F @autumn/shared db:generate",
"db:migrate": " bun -F @autumn/shared db:migrate", "db:migrate": " bun -F @autumn/shared db:migrate",
"docker:up": "docker compose -f docker-compose.dev.yml up --build", "docker:up": "docker compose -f docker-compose.dev.yml up --build",
"docker:up:unix": "docker compose -f docker-compose.unix.yml up --build", "docker:up:unix": "docker compose -f docker-compose.unix.yml up --build",
"docker:up:ci": "docker compose -f docker-compose.ci.yml up --build", "docker:up:ci": "docker compose -f docker-compose.ci.yml up --build",
"build:all": "pnpm -F shared build && pnpm -F server prod:build && pnpm -F vite build",
"vite:build:bun": "bun -F @autumn/shared build && bun -F @autumn/vite build:bun", "vite:build:bun": "bun -F @autumn/shared build && bun -F @autumn/vite build:bun",
"vite:start:bun": "bun -F @autumn/shared build && bun -F @autumn/vite start:bun" "vite:start:bun": "bun -F @autumn/shared build && bun -F @autumn/vite start:bun"
}, },

21
scripts/package.json Normal file
View File

@@ -0,0 +1,21 @@
{
"name": "@autumn/scripts",
"version": "1.0.0",
"type": "module",
"private": true,
"scripts": {
"setup": "tsx setup.js",
"setup-test": "tsx setup-test.ts"
},
"dependencies": {
"@autumn/shared": "workspace:*",
"chalk": "^5.3.0",
"dotenv": "^16.5.0",
"inquirer": "^12.6.3"
},
"devDependencies": {
"@types/inquirer": "^9.0.7",
"tsx": "^4.19.2",
"typescript": "^5.7.3"
}
}

145
scripts/setup-test.ts Normal file
View File

@@ -0,0 +1,145 @@
#!/usr/bin/env node
import chalk from "chalk";
import inquirer from "inquirer";
import { createTestOrg, TEST_ORG_CONFIG } from "./setupTestUtils/createTestOrg.js";
import {
setupStripeTestKey,
setupTunnelUrl,
setupUpstash,
} from "./setupTestUtils/setupPrompts.js";
import { updateEnvFile } from "./setupTestUtils/updateEnvFile.js";
import {
updateSingleEnvVar,
updateMultipleEnvVars,
} from "./setupTestUtils/incrementalEnvUpdate.js";
async function showPreparationChecklist() {
console.log(
chalk.magentaBright("\n================ Autumn Test Setup ================\n"),
);
console.log(
chalk.cyan("This script will set up a test organization for development.\n"),
);
console.log(chalk.yellowBright("Before you begin, please have the following ready:\n"));
console.log(chalk.whiteBright("1. Stripe Test API Key (sk_test_...)"));
console.log(
chalk.gray(" → Used to link Stripe to your test account for payment processing\n"),
);
console.log(chalk.whiteBright("2. Upstash Redis REST URL and Token"));
console.log(
chalk.gray(" → Used for caching customer objects and testing race conditions\n"),
);
console.log(chalk.whiteBright("3. Tunnel URL (e.g., ngrok URL)"));
console.log(
chalk.gray(
" → Points to localhost:8080 so Stripe webhooks can reach your server\n",
),
);
// Prompt user to continue
const { ready } = await inquirer.prompt([
{
type: "confirm",
name: "ready",
message: chalk.cyan("Ready to begin setup?"),
default: true,
},
]);
if (!ready) {
console.log(chalk.yellow("\nSetup cancelled. Run the script again when you're ready!\n"));
process.exit(0);
}
}
async function main() {
// Show preparation checklist
await showPreparationChecklist();
try {
// Import db from server
const { db } = await import("../server/src/db/initDrizzle.js");
// Step 1: Create test organization in database and get API key
const autumnSecretKey = await createTestOrg({ db });
// Save org details immediately
updateMultipleEnvVars({
TESTS_ORG: TEST_ORG_CONFIG.slug,
TESTS_ORG_ID: TEST_ORG_CONFIG.id,
...(autumnSecretKey && { UNIT_TEST_AUTUMN_SECRET_KEY: autumnSecretKey }),
});
// Step 2: Get Stripe test key
const stripeTestKey = await setupStripeTestKey();
// Save Stripe key immediately
updateSingleEnvVar({ key: "STRIPE_TEST_KEY", value: stripeTestKey });
// Step 3: Get Upstash configuration
const { upstashUrl, upstashToken } = await setupUpstash();
// Save Upstash credentials immediately
updateMultipleEnvVars({
UPSTASH_REDIS_REST_URL: upstashUrl,
UPSTASH_REDIS_REST_TOKEN: upstashToken,
});
// Step 4: Get tunnel URL
const tunnelUrl = await setupTunnelUrl();
// Save tunnel URL immediately
updateSingleEnvVar({ key: "STRIPE_WEBHOOK_URL", value: tunnelUrl });
// Step 5: Final update to ensure proper formatting
updateEnvFile({
testOrgSlug: TEST_ORG_CONFIG.slug,
testOrgId: TEST_ORG_CONFIG.id,
autumnSecretKey,
stripeTestKey,
upstashUrl,
upstashToken,
tunnelUrl,
});
console.log(
chalk.magentaBright(
"\n================ Setup Complete! ================\n",
),
);
console.log(chalk.greenBright("🎉 Test organization setup complete! 🎉\n"));
console.log(chalk.cyan("Test Organization Details:"));
console.log(chalk.whiteBright(` Organization: ${TEST_ORG_CONFIG.slug}`));
console.log(chalk.whiteBright(` ID: ${TEST_ORG_CONFIG.id}`));
if (autumnSecretKey) {
console.log(chalk.whiteBright(` Secret Key: ${autumnSecretKey}\n`));
} else {
console.log(
chalk.whiteBright(" Secret Key: (using existing key from .env)\n"),
);
}
console.log(chalk.cyan("Next steps:"));
console.log(
chalk.whiteBright("1. Start your tunnel (e.g., ngrok http 8080)"),
);
console.log(chalk.whiteBright("2. Start your development server"));
console.log(
chalk.whiteBright("3. Run tests with your new test organization!\n"),
);
process.exit(0);
} catch (error) {
console.error(
chalk.red("\n❌ Setup failed:"),
error instanceof Error ? error.message : error,
);
process.exit(1);
}
}
main();

View File

@@ -0,0 +1,128 @@
import { type OrgConfig, member, organizations, user } from "@autumn/shared";
import chalk from "chalk";
import { eq } from "drizzle-orm";
import type { DrizzleCli } from "@server/db/initDrizzle.js";
import { createKey } from "@server/internal/dev/api-keys/apiKeyUtils.js";
const TEST_ORG_CONFIG = {
id: "org_2sWv2S8LJ9iaTjLI6UtNsfL88Kt",
slug: "unit-test-org",
name: "Unit Test Org",
createdAt: new Date(1738583937426).toISOString(),
created_at: 1738583937426,
};
/**
* Creates a test organization in the database and generates an API key
*/
export async function createTestOrg({
db,
}: {
db: DrizzleCli;
}): Promise<string | null> {
console.log(
chalk.magentaBright(
"\n================ Creating Test Organization ================\n",
),
);
// Check if org already exists
const existingOrg = await db.query.organizations.findFirst({
where: eq(organizations.id, TEST_ORG_CONFIG.id),
});
if (existingOrg) {
console.log(
chalk.yellowBright(
`Test organization '${TEST_ORG_CONFIG.slug}' already exists. Creating new API key.`,
),
);
// Always create a new API key for existing org
const apiKey = await createKey({
db,
env: "sandbox" as any,
name: "Unit Test Key",
orgId: TEST_ORG_CONFIG.id,
prefix: "am_sk_test",
meta: {
createdBy: "setup-test-script",
createdAt: new Date().toISOString(),
},
userId: undefined,
});
console.log(chalk.greenBright("✅ Created API key for existing organization"));
return apiKey;
}
// Create the test organization
const org = {
id: TEST_ORG_CONFIG.id,
slug: TEST_ORG_CONFIG.slug,
name: TEST_ORG_CONFIG.name,
createdAt: new Date(TEST_ORG_CONFIG.created_at),
created_at: TEST_ORG_CONFIG.created_at,
stripe_connected: false,
default_currency: "usd",
config: {} as OrgConfig,
onboarded: true,
};
await db.insert(organizations).values(org);
console.log(
chalk.greenBright(
`✅ Created test organization: ${TEST_ORG_CONFIG.slug} (${TEST_ORG_CONFIG.id})`,
),
);
// Get first 5 users from database and create memberships
const users = await db.select().from(user).limit(5);
if (users.length > 0) {
const { generateId } = await import("@server/utils/genUtils.js");
const memberships = users.map((u) => ({
id: generateId("mem"),
organizationId: TEST_ORG_CONFIG.id,
userId: u.id,
role: "owner",
createdAt: new Date(),
}));
await db.insert(member).values(memberships);
console.log(
chalk.greenBright(
`✅ Created ${memberships.length} membership(s) for test organization`,
),
);
} else {
console.log(
chalk.yellowBright(
"⚠ No users found in database. Skipping membership creation.",
),
);
}
// Create API key for the new org
const apiKey = await createKey({
db,
env: "sandbox" as any,
name: "Unit Test Key",
orgId: TEST_ORG_CONFIG.id,
prefix: "am_sk_test",
meta: {
createdBy: "setup-test-script",
createdAt: new Date().toISOString(),
},
userId: undefined,
});
console.log(chalk.greenBright("✅ Created API key for test organization"));
return apiKey;
}
export { TEST_ORG_CONFIG };

View File

@@ -0,0 +1,87 @@
import { readFileSync, writeFileSync } from "node:fs";
import chalk from "chalk";
import { envPath } from "./updateEnvFile.js";
/**
* Incrementally updates a single env variable in the .env file
*/
export function updateSingleEnvVar({
key,
value,
}: {
key: string;
value: string;
}) {
try {
const envContent = readFileSync(envPath, "utf-8");
const lines = envContent.split("\n");
// Check if the key already exists
let found = false;
const updatedLines = lines.map((line) => {
const trimmed = line.trim();
if (trimmed.startsWith(`${key}=`)) {
found = true;
return `${key}=${value}`;
}
return line;
});
// If not found, add it to the end
if (!found) {
updatedLines.push(`${key}=${value}`);
}
writeFileSync(envPath, updatedLines.join("\n"));
console.log(chalk.gray(` ✓ Saved ${key} to .env`));
} catch (error) {
console.log(
chalk.red(
` ⚠ Warning: Could not save ${key} to .env. You may need to add it manually.`,
),
);
}
}
/**
* Updates multiple env variables at once
*/
export function updateMultipleEnvVars(vars: Record<string, string>) {
try {
const envContent = readFileSync(envPath, "utf-8");
const lines = envContent.split("\n");
const keysToUpdate = Object.keys(vars);
const foundKeys = new Set<string>();
// Update existing keys
const updatedLines = lines.map((line) => {
const trimmed = line.trim();
for (const key of keysToUpdate) {
if (trimmed.startsWith(`${key}=`)) {
foundKeys.add(key);
return `${key}=${vars[key]}`;
}
}
return line;
});
// Add new keys that weren't found
for (const key of keysToUpdate) {
if (!foundKeys.has(key)) {
updatedLines.push(`${key}=${vars[key]}`);
}
}
writeFileSync(envPath, updatedLines.join("\n"));
for (const key of keysToUpdate) {
console.log(chalk.gray(` ✓ Saved ${key} to .env`));
}
} catch (error) {
console.log(
chalk.red(
" ⚠ Warning: Could not save variables to .env. You may need to add them manually.",
),
);
}
}

View File

@@ -0,0 +1,136 @@
import inquirer from "inquirer";
import chalk from "chalk";
/**
* Prompts user for Stripe test API key
*/
export async function setupStripeTestKey(): Promise<string> {
console.log(
chalk.magentaBright(
"\n================ Stripe Test API Key Setup ================\n",
),
);
console.log(
chalk.cyan(
"This Stripe test API key will be used to link Stripe to your test account.",
),
);
console.log(
chalk.cyan(
"You can find this in your Stripe Dashboard under Developers > API Keys (Test Mode).\n",
),
);
const { stripeTestKey } = await inquirer.prompt([
{
type: "input",
name: "stripeTestKey",
message: chalk.cyan("Enter your Stripe test secret key (sk_test_...):"),
validate: (input: string) => {
if (!input || input.length < 10) {
return "Please enter a valid Stripe test key";
}
if (!input.startsWith("sk_test_")) {
return "Stripe test keys should start with 'sk_test_'";
}
return true;
},
},
]);
return stripeTestKey;
}
/**
* Prompts user for Upstash configuration
*/
export async function setupUpstash(): Promise<{
upstashUrl: string;
upstashToken: string;
}> {
console.log(
chalk.magentaBright("\n================ Upstash Setup ================\n"),
);
console.log(
chalk.cyan(
"Upstash is used for caching the customer object and is important for testing race conditions.",
),
);
console.log(
chalk.cyan(
"You can create a free Upstash Redis instance at https://upstash.com/\n",
),
);
const { upstashUrl } = await inquirer.prompt([
{
type: "input",
name: "upstashUrl",
message: chalk.cyan("Enter your Upstash Redis REST URL:"),
validate: (input: string) => {
if (!input || input.length < 10) {
return "Please enter a valid Upstash URL";
}
if (!input.startsWith("https://")) {
return "Upstash URL should start with 'https://'";
}
return true;
},
},
]);
const { upstashToken } = await inquirer.prompt([
{
type: "input",
name: "upstashToken",
message: chalk.cyan("Enter your Upstash Redis REST token:"),
validate: (input: string) => {
if (!input || input.length < 10) {
return "Please enter a valid Upstash token";
}
return true;
},
},
]);
return { upstashUrl, upstashToken };
}
/**
* Prompts user for tunnel URL
*/
export async function setupTunnelUrl(): Promise<string> {
console.log(
chalk.magentaBright(
"\n================ Tunnel URL Setup ================\n",
),
);
console.log(
chalk.cyan(
"You need a tunnel that points to localhost:8080 (your server URL) to receive Stripe webhooks.",
),
);
console.log(
chalk.cyan("You can use tools like ngrok, localtunnel, or Cloudflare Tunnel."),
);
console.log(chalk.cyan("Example: https://your-subdomain.ngrok.io\n"));
const { tunnelUrl } = await inquirer.prompt([
{
type: "input",
name: "tunnelUrl",
message: chalk.cyan("Enter your tunnel URL:"),
validate: (input: string) => {
if (!input || input.length < 10) {
return "Please enter a valid tunnel URL";
}
if (!input.startsWith("https://") && !input.startsWith("http://")) {
return "Tunnel URL should start with 'http://' or 'https://'";
}
return true;
},
},
]);
return tunnelUrl;
}

View File

@@ -0,0 +1,183 @@
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import chalk from "chalk";
import { config } from "dotenv";
// Get the directory of this script file
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
/**
* Find server/.env file robustly - works whether running from root or scripts dir
*/
function findEnvPath(): string {
// Try from script directory (scripts/setup-test.ts -> ../server/.env)
const fromScriptDir = resolve(__dirname, "../../server/.env");
if (existsSync(fromScriptDir)) {
return fromScriptDir;
}
// Try from current working directory
const fromCwd = resolve(process.cwd(), "server/.env");
if (existsSync(fromCwd)) {
return fromCwd;
}
// If neither exists, return the path from script dir (will fail later with clear error)
return fromScriptDir;
}
export const envPath = findEnvPath();
// Load existing env vars
config({ path: envPath });
/**
* Updates server/.env with new test configuration
*/
export function updateEnvFile({
testOrgSlug,
testOrgId,
autumnSecretKey,
stripeTestKey,
upstashUrl,
upstashToken,
tunnelUrl,
}: {
testOrgSlug: string;
testOrgId: string;
autumnSecretKey: string | null;
stripeTestKey: string;
upstashUrl: string;
upstashToken: string;
tunnelUrl: string;
}) {
console.log(
chalk.magentaBright(
"\n================ Updating Environment Variables ================\n",
),
);
// Read existing .env file
let envContent = "";
try {
envContent = readFileSync(envPath, "utf-8");
} catch {
console.log(
chalk.red(
`❌ Could not read server/.env file at ${envPath}. Make sure it exists.`,
),
);
process.exit(1);
}
// Parse existing env vars
const envVars = new Map<string, string>();
const lines = envContent.split("\n");
for (const line of lines) {
const trimmed = line.trim();
// Skip empty lines and comments
if (!trimmed || trimmed.startsWith("#")) {
continue;
}
const eqIndex = trimmed.indexOf("=");
if (eqIndex > 0) {
const key = trimmed.substring(0, eqIndex);
const value = trimmed.substring(eqIndex + 1);
envVars.set(key, value);
}
}
// Update with new test variables
envVars.set("TESTS_ORG", testOrgSlug);
envVars.set("TESTS_ORG_ID", testOrgId);
// Only update the secret key if a new one was generated
if (autumnSecretKey) {
envVars.set("UNIT_TEST_AUTUMN_SECRET_KEY", autumnSecretKey);
}
envVars.set("STRIPE_TEST_KEY", stripeTestKey);
envVars.set("UPSTASH_REDIS_REST_URL", upstashUrl);
envVars.set("UPSTASH_REDIS_REST_TOKEN", upstashToken);
envVars.set("STRIPE_WEBHOOK_URL", tunnelUrl);
// Build new env content, preserving structure
const sections: string[][] = [];
let currentSection: string[] = [];
let inTestSection = false;
for (const line of lines) {
const trimmed = line.trim();
// Check if this is a section header
if (trimmed.startsWith("#")) {
if (currentSection.length > 0) {
sections.push(currentSection);
currentSection = [];
}
currentSection.push(line);
inTestSection = trimmed.toLowerCase().includes("test");
continue;
}
// Skip test-related vars from existing content - we'll add them fresh
if (
trimmed.startsWith("TESTS_ORG") ||
trimmed.startsWith("UNIT_TEST_AUTUMN_SECRET_KEY") ||
trimmed.startsWith("STRIPE_TEST_KEY") ||
trimmed.startsWith("UPSTASH_REDIS_REST") ||
(trimmed.startsWith("STRIPE_WEBHOOK_URL") && inTestSection)
) {
continue;
}
currentSection.push(line);
}
if (currentSection.length > 0) {
sections.push(currentSection);
}
// Add test configuration section
const testSection = [
"",
"# Test Configuration",
`TESTS_ORG=${testOrgSlug}`,
`TESTS_ORG_ID=${testOrgId}`,
];
// Only add secret key if it was generated/updated
if (autumnSecretKey) {
testSection.push(`UNIT_TEST_AUTUMN_SECRET_KEY=${autumnSecretKey}`);
} else if (envVars.has("UNIT_TEST_AUTUMN_SECRET_KEY")) {
testSection.push(
`UNIT_TEST_AUTUMN_SECRET_KEY=${envVars.get("UNIT_TEST_AUTUMN_SECRET_KEY")}`,
);
}
testSection.push(
`STRIPE_TEST_KEY=${stripeTestKey}`,
"",
"# Upstash (for caching)",
`UPSTASH_REDIS_REST_URL=${upstashUrl}`,
`UPSTASH_REDIS_REST_TOKEN=${upstashToken}`,
"",
"# Tunnel URL (for Stripe webhooks)",
`STRIPE_WEBHOOK_URL=${tunnelUrl}`,
"",
);
sections.push(testSection);
// Write back to file
const newContent = sections.map((s) => s.join("\n")).join("\n");
writeFileSync(envPath, newContent);
console.log(
chalk.greenBright(`✅ Environment variables updated in ${envPath}`),
);
}

279
scripts/test.ts Normal file
View File

@@ -0,0 +1,279 @@
#!/usr/bin/env node
import { spawn } from "node:child_process";
import { existsSync, readdirSync, statSync } from "node:fs";
import { join, relative, resolve } from "node:path";
import chalk from "chalk";
/**
* Recursively finds all test files in a directory
*/
function findTestFiles({ dir }: { dir: string }): string[] {
const files: string[] = [];
const entries = readdirSync(dir);
for (const entry of entries) {
const fullPath = join(dir, entry);
const stat = statSync(fullPath);
if (stat.isDirectory()) {
files.push(...findTestFiles({ dir: fullPath }));
} else if (
entry.endsWith(".ts") &&
!entry.includes("Utils") &&
!entry.includes("utils")
) {
files.push(fullPath);
}
}
return files;
}
/**
* Fuzzy matches a search term against test file paths and returns a score
*/
function fuzzyMatchScore({
search,
filePath,
}: {
search: string;
filePath: string;
}): number {
const searchLower = search.toLowerCase();
const pathLower = filePath.toLowerCase();
const fileName = pathLower.split("/").pop() || "";
// Check if search matches exactly in filename (highest priority)
if (fileName === `${searchLower}.ts`) {
return 1000;
}
// Check if filename starts with search (high priority)
if (fileName.startsWith(searchLower)) {
return 500;
}
// Check if search is contained in filename
if (fileName.includes(searchLower)) {
return 100;
}
// Simple fuzzy match - check if all characters appear in order
let searchIndex = 0;
let score = 0;
for (
let i = 0;
i < pathLower.length && searchIndex < searchLower.length;
i++
) {
if (pathLower[i] === searchLower[searchIndex]) {
searchIndex++;
score++;
}
}
// Return 0 if not all characters matched
if (searchIndex !== searchLower.length) {
return 0;
}
return score;
}
/**
* Runs shell test scripts from server/shell/ directory or individual test files
*/
async function runTest() {
const scriptName = process.argv[2];
const additionalArgs = process.argv.slice(3);
if (!scriptName) {
console.log(
chalk.red("❌ Please provide a shell script or test file name"),
);
console.log(
chalk.cyan("\nUsage: bun tests <script-name|test-name> [args...]"),
);
console.log(chalk.gray("Examples:"));
console.log(chalk.gray(" bun tests g1"));
console.log(chalk.gray(" bun tests g1 setup"));
console.log(
chalk.gray(" bun tests basic1 # fuzzy matches test file"),
);
console.log(
chalk.gray(" bun tests attach/basic1 # matches path pattern\n"),
);
process.exit(1);
}
const serverDir = resolve(process.cwd(), "server");
const shellScript = resolve(serverDir, "shell", `${scriptName}.sh`);
// First try to find a shell script
if (existsSync(shellScript)) {
const argsDisplay =
additionalArgs.length > 0 ? ` ${additionalArgs.join(" ")}` : "";
console.log(
chalk.cyan(`🧪 Running shell script: ${scriptName}.sh${argsDisplay}\n`),
);
// Create a new process group by spawning with detached: true
const child = spawn("bash", [shellScript, ...additionalArgs], {
cwd: serverDir,
stdio: "inherit",
env: { ...process.env, NODE_ENV: "production" },
detached: true,
});
// Store the process group ID
const pgid = child.pid;
// Forward termination signals to entire process group
const killProcessGroup = () => {
if (pgid) {
try {
// Kill the entire process group with SIGKILL (force kill)
process.kill(-pgid, "SIGKILL");
} catch (_err) {
// Process group might already be dead
}
}
};
process.on("SIGINT", () => {
console.log(chalk.yellow("\n⚠ Received SIGINT, killing all test processes...\n"));
killProcessGroup();
process.exit(130);
});
process.on("SIGTERM", () => {
console.log(chalk.yellow("\n⚠ Received SIGTERM, killing all test processes...\n"));
killProcessGroup();
process.exit(143);
});
process.on("exit", () => {
killProcessGroup();
});
child.on("exit", (code) => {
if (code === 0) {
console.log(
chalk.green(`\n✅ Test ${scriptName} completed successfully`),
);
} else {
console.log(
chalk.red(`\n❌ Test ${scriptName} failed with code ${code}`),
);
process.exit(code || 1);
}
});
child.on("error", (error) => {
console.log(chalk.red(`\n❌ Error running test: ${error.message}`));
process.exit(1);
});
return;
}
// If not a shell script, try fuzzy matching test files
console.log(
chalk.cyan(`🔍 Searching for test file matching: ${scriptName}\n`),
);
const testsDir = resolve(serverDir, "tests");
const allTestFiles = findTestFiles({ dir: testsDir });
// Find matches with scores
const matches = allTestFiles
.map((file) => ({
path: file,
relative: relative(serverDir, file),
score: fuzzyMatchScore({ search: scriptName, filePath: file }),
}))
.filter((match) => match.score > 0)
.sort((a, b) => b.score - a.score);
if (matches.length === 0) {
console.log(chalk.red(`❌ No test file found matching: ${scriptName}`));
console.log(chalk.gray(` Searched in: ${testsDir}\n`));
process.exit(1);
}
const bestMatch = matches[0];
const otherMatches = matches.slice(1, 5);
if (otherMatches.length > 0) {
console.log(
chalk.yellow(`⚠️ Multiple matches found, picking best match:\n`),
);
console.log(chalk.green(`${bestMatch.relative} (selected)`));
for (const match of otherMatches) {
console.log(chalk.gray(` ${match.relative}`));
}
console.log();
}
const testFile = bestMatch;
console.log(chalk.green(`✓ Found: ${testFile.relative}\n`));
console.log(chalk.cyan(`🧪 Running test file...\n`));
// Run the test file with mocha
const child = spawn("bunx", ["mocha", "--timeout", "0", testFile.path], {
cwd: serverDir,
stdio: "inherit",
env: { ...process.env, NODE_ENV: "production" },
detached: true,
});
// Store the process group ID
const pgid = child.pid;
// Forward termination signals to entire process group
const killProcessGroup = () => {
if (pgid) {
try {
// Kill the entire process group with SIGKILL (force kill)
process.kill(-pgid, "SIGKILL");
} catch (_err) {
// Process group might already be dead
}
}
};
process.on("SIGINT", () => {
console.log(chalk.yellow("\n⚠ Received SIGINT, killing test process...\n"));
killProcessGroup();
process.exit(130);
});
process.on("SIGTERM", () => {
console.log(chalk.yellow("\n⚠ Received SIGTERM, killing test process...\n"));
killProcessGroup();
process.exit(143);
});
process.on("exit", () => {
killProcessGroup();
});
child.on("exit", (code) => {
if (code === 0) {
console.log(
chalk.green(`\n✅ Test ${scriptName} completed successfully`),
);
} else {
console.log(
chalk.red(`\n❌ Test ${scriptName} failed with code ${code}`),
);
process.exit(code || 1);
}
});
child.on("error", (error) => {
console.log(chalk.red(`\n❌ Error running test: ${error.message}`));
process.exit(1);
});
}
runTest();

25
scripts/tsconfig.json Normal file
View File

@@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"lib": ["ES2022"],
"moduleResolution": "bundler",
"resolveJsonModule": true,
"allowJs": true,
"checkJs": false,
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"allowSyntheticDefaultImports": true,
"types": ["node"],
"baseUrl": ".",
"paths": {
"@server/*": ["../server/src/*"],
"@shared/*": ["../shared/*"]
}
},
"include": ["**/*.ts", "**/*.js"],
"exclude": ["node_modules", "dist"]
}

View File

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

View File

@@ -5,9 +5,9 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/config.sh" source "$SCRIPT_DIR/config.sh"
# If contains setup then run $MOCHA_SETUP # If contains setup then run $MOCHA_SETUP
# if [[ "$1" == *"setup"* ]]; then if [[ "$1" == *"setup"* ]]; then
# MOCHA_PARALLEL=true $MOCHA_SETUP MOCHA_PARALLEL=true $MOCHA_SETUP
# fi fi
$MOCHA_CMD \ $MOCHA_CMD \
'tests/attach/basic/*.ts' \ 'tests/attach/basic/*.ts' \

View File

@@ -29,7 +29,13 @@ export const handleConnectWebhook = async (c: Context<HonoEnv>) => {
body, body,
}); });
const masterStripe = initMasterStripe(); let masterStripe: Stripe;
try {
masterStripe = initMasterStripe();
} catch (error) {
logger.error(`Failed to initialize master stripe client ${error}`);
return c.json(200);
}
let event: Stripe.Event; let event: Stripe.Event;
// Step 1: Get webhook secret // Step 1: Get webhook secret

View File

@@ -1,75 +1,6 @@
#!/bin/bash #!/bin/bash
MOCHA_SETUP="npx mocha tests/00_setup.ts"
MOCHA_CMD="npx mocha --parallel --timeout 10000000 --ignore tests/00_setup.ts"
# Group 1 if [ "$1" == "custom" ]; then
if [ "$1" == "group1" ]; then
$MOCHA_SETUP \
&& $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 5 - Paid referrals
if [ "$1" == "paid-referrals" ]; then
$MOCHA_SETUP && $MOCHA_CMD \
'tests/advanced/referrals/paid/*.ts'
fi
# Group 4
if [ "$1" == "g4" ]; then
$MOCHA_SETUP && $MOCHA_CMD \
'tests/advanced/usage/*.ts'
fi
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' \
--ignore 'tests/alex/00_setup.ts'
elif [ "$1" == "alex" ]; then
npx mocha 'tests/alex/00_setup.ts' && \
npx mocha --timeout 10000000 \
'tests/alex/01_free.ts' \
--ignore 'tests/alex/00_setup.ts'
elif [ "$1" == "alex-custom" ]; then
FILE_TO_TEST="$2"
npx mocha --timeout 10000000 "tests/alex/$FILE_TO_TEST.ts"
elif [ "$1" == "custom" ]; then
FILE_TO_TEST="$2" FILE_TO_TEST="$2"
ARG3="$3" ARG3="$3"
if [ "$ARG3" == "setup" ]; then if [ "$ARG3" == "setup" ]; then
@@ -79,41 +10,123 @@ elif [ "$1" == "custom" ]; then
else else
npx mocha --bail --timeout 10000000 "tests/$FILE_TO_TEST.ts" npx mocha --bail --timeout 10000000 "tests/$FILE_TO_TEST.ts"
fi fi
else
npx mocha --timeout 10000000 'tests/00_setup.ts' && npx mocha --timeout 10000000 \
'tests/**/*.ts' \
--ignore 'tests/00_setup.ts' \
--ignore 'tests/alex/**/*.ts'
fi
# # TEST PARALLEL
# if [ "$1" == "basic-parallel" ]; then # MOCHA_SETUP="npx mocha tests/00_setup.ts"
# MOCHA_PARALLEL=true $MOCHA_SETUP \ # MOCHA_CMD="npx mocha --parallel --timeout 10000000 --ignore tests/00_setup.ts"
# && $MOCHA_CMD \
# # Group 1
# if [ "$1" == "group1" ]; then
# $MOCHA_SETUP \
# && $MOCHA_CMD \
# 'tests/attach/basic/*.ts' \ # 'tests/attach/basic/*.ts' \
# 'tests/attach/upgrade/*.ts' \ # 'tests/attach/upgrade/*.ts' \
# 'tests/attach/downgrade/*.ts' \ # 'tests/attach/downgrade/*.ts'
# && $MOCHA_CMD \ # fi
# # Group 2
# if [ "$1" == "g2" ]; then
# $MOCHA_SETUP && $MOCHA_CMD \
# 'tests/attach/upgradeOld/*.ts' \ # 'tests/attach/upgradeOld/*.ts' \
# 'tests/attach/entities/*.ts' \ # 'tests/attach/entities/*.ts' \
# 'tests/attach/migrations/*.ts' \ # 'tests/attach/migrations/*.ts' \
# 'tests/attach/multiProduct/*.ts' \
# 'tests/attach/newVersion/*.ts' \ # 'tests/attach/newVersion/*.ts' \
# 'tests/attach/others/*.ts' \ # 'tests/attach/others/*.ts' \
# 'tests/attach/updateEnts/*.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/updateQuantity/*.ts' \
# 'tests/contUse/entities/*.ts' \ # 'tests/attach/multiProduct/*.ts' \
# 'tests/contUse/track/*.ts' \
# 'tests/contUse/update/*.ts' \
# && $MOCHA_CMD \
# 'tests/advanced/multiFeature/*.ts' \ # 'tests/advanced/multiFeature/*.ts' \
# 'tests/advanced/referrals/*.ts' \ # 'tests/advanced/referrals/*.ts' \
# 'tests/advanced/coupons/*.ts' \ # 'tests/advanced/coupons/*.ts'
# fi
# # Group 5 - Paid referrals
# if [ "$1" == "paid-referrals" ]; then
# $MOCHA_SETUP && $MOCHA_CMD \
# 'tests/advanced/referrals/paid/*.ts'
# fi
# # Group 4
# if [ "$1" == "g4" ]; then
# $MOCHA_SETUP && $MOCHA_CMD \
# 'tests/advanced/usage/*.ts'
# fi
# elif [ "$1" == "advanced-parallel" ]; then
# MOCHA_PARALLEL=true \ # if [ "$1" == "alex-parallel" ]; then
# $MOCHA_SETUP \ # MOCHA_PARALLEL=true npx mocha 'tests/alex/00_setup.ts' && npx mocha --parallel --timeout 10000000 \
# && $MOCHA_CMD 'tests/advanced/arrear_prorated/*.ts' 'tests/advanced/coupons/*.ts'\ # 'tests/alex/01_free.ts' 'tests/alex/02_pro.ts' 'tests/alex/03_premium.ts' \
# # && $MOCHA_CMD 'tests/advanced/usage/*.ts' \ # 'tests/alex/04_topups.ts' 'tests/alex/05_cancel.ts' 'tests/alex/06_switch.ts' \
# # && $MOCHA_CMD 'tests/advanced/coupons/*.ts'\ # --ignore 'tests/alex/00_setup.ts'
# elif [ "$1" == "alex" ]; then
# npx mocha 'tests/alex/00_setup.ts' && \
# npx mocha --timeout 10000000 \
# 'tests/alex/01_free.ts' \
# --ignore 'tests/alex/00_setup.ts'
# elif [ "$1" == "alex-custom" ]; then
# FILE_TO_TEST="$2"
# npx mocha --timeout 10000000 "tests/alex/$FILE_TO_TEST.ts"
# elif [ "$1" == "custom" ]; then
# FILE_TO_TEST="$2"
# ARG3="$3"
# if [ "$ARG3" == "setup" ]; then
# npx mocha --bail --timeout 10000000 'tests/00_setup.ts'
# elif [ "$ARG3" == "parallel" ]; then
# npx mocha --parallel --bail --timeout 10000000 "tests/$FILE_TO_TEST.ts"
# else
# npx mocha --bail --timeout 10000000 "tests/$FILE_TO_TEST.ts"
# fi
# else
# npx mocha --timeout 10000000 'tests/00_setup.ts' && npx mocha --timeout 10000000 \
# 'tests/**/*.ts' \
# --ignore 'tests/00_setup.ts' \
# --ignore 'tests/alex/**/*.ts'
# 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,35 +1,31 @@
import dotenv from "dotenv"; import dotenv from "dotenv";
dotenv.config(); dotenv.config();
import { AppEnv } from "@autumn/shared"; import { AppEnv } from "@autumn/shared";
import { clearOrg, setupOrg } from "tests/utils/setup.js"; import { clearOrg, setupOrg } from "tests/utils/setup.js";
import { initDrizzle } from "@/db/initDrizzle.js";
import { import {
features,
products,
creditSystems,
advanceProducts, advanceProducts,
attachProducts, attachProducts,
rewards, creditSystems,
oneTimeProducts,
entityProducts, entityProducts,
features,
oneTimeProducts,
products,
referralPrograms, referralPrograms,
rewards,
} from "./global.js"; } from "./global.js";
import { initDrizzle } from "@/db/initDrizzle.js";
const ORG_SLUG = process.env.TESTS_ORG!; const ORG_SLUG = process.env.TESTS_ORG!;
const DEFAULT_ENV = AppEnv.Sandbox; const DEFAULT_ENV = AppEnv.Sandbox;
import { Hyperbrowser } from "@hyperbrowser/sdk";
const hyperbrowser = new Hyperbrowser({
apiKey: process.env.HYPERBROWSER_API_KEY,
});
describe("Initialize org for tests", () => { describe("Initialize org for tests", () => {
it("should initialize org", async function () { it("should initialize org", async function () {
this.timeout(1000000000); this.timeout(1000000000);
this.org = await clearOrg({ orgSlug: ORG_SLUG, env: DEFAULT_ENV }); this.org = await clearOrg({ orgSlug: ORG_SLUG, env: DEFAULT_ENV });
this.env = DEFAULT_ENV; this.env = DEFAULT_ENV;
let { db, client } = initDrizzle(); const { db, client } = initDrizzle();
this.db = db; this.db = db;
this.client = client; this.client = client;

View File

@@ -13,12 +13,6 @@ import { OrgService } from "@/internal/orgs/OrgService.js";
const ORG_SLUG = process.env.TESTS_ORG!; const ORG_SLUG = process.env.TESTS_ORG!;
const DEFAULT_ENV = AppEnv.Sandbox; const DEFAULT_ENV = AppEnv.Sandbox;
import { Hyperbrowser } from "@hyperbrowser/sdk";
const hyperbrowser = new Hyperbrowser({
apiKey: process.env.HYPERBROWSER_API_KEY,
});
export const setupBefore = async (instance: any) => { export const setupBefore = async (instance: any) => {
try { try {
const { db, client } = initDrizzle(); const { db, client } = initDrizzle();

View File

@@ -7,7 +7,8 @@
"exports": { "exports": {
".": { ".": {
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
"import": "./dist/index.js" "import": "./dist/index.js",
"default": "./dist/index.js"
} }
}, },
"author": "Recase Inc.", "author": "Recase Inc.",

View File

@@ -1,3 +1,5 @@
import { useState } from "react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { import {
@@ -9,8 +11,6 @@ import { useOrg } from "@/hooks/common/useOrg";
import { authClient, useListOrganizations } from "@/lib/auth-client"; import { authClient, useListOrganizations } from "@/lib/auth-client";
import { useAxiosInstance } from "@/services/useAxiosInstance"; import { useAxiosInstance } from "@/services/useAxiosInstance";
import { getBackendErr } from "@/utils/genUtils"; import { getBackendErr } from "@/utils/genUtils";
import { useState } from "react";
import { toast } from "sonner";
import { useMemberships } from "../hooks/useMemberships"; import { useMemberships } from "../hooks/useMemberships";
export const DeleteOrgPopover = () => { export const DeleteOrgPopover = () => {
@@ -64,6 +64,7 @@ export const DeleteOrgPopover = () => {
try { try {
await deleteOrg(); await deleteOrg();
} catch (error) { } catch (error) {
console.log("Error:", error);
toast.error(getBackendErr(error, "Failed to delete org")); toast.error(getBackendErr(error, "Failed to delete org"));
} }