diff --git a/.cursor/environment.json b/.cursor/environment.json deleted file mode 100644 index f32303afa..000000000 --- a/.cursor/environment.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "terminals": [] -} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 7d2f6b9d0..000000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -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"] -} diff --git a/archives/benchmark.yml b/archives/benchmark.yml deleted file mode 100644 index 4dd9e35e9..000000000 --- a/archives/benchmark.yml +++ /dev/null @@ -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<> $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} - -#
-# Click to view benchmark results - -# \`\`\`javascript -# ${output} -# \`\`\` - -#
- -# *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 -# }); \ No newline at end of file diff --git a/bun.lock b/bun.lock index 17a5e8939..6cdcfafb4 100644 --- a/bun.lock +++ b/bun.lock @@ -18,6 +18,21 @@ "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": { "name": "@autumn/server", "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=="], + "@autumn/scripts": ["@autumn/scripts@workspace:scripts"], + "@autumn/server": ["@autumn/server@workspace:server"], "@autumn/shared": ["@autumn/shared@workspace:shared"], @@ -1264,6 +1281,8 @@ "@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/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/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/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], diff --git a/package.json b/package.json index ba6a5e5fb..788570b42 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "workspaces": [ "server", "shared", - "vite" + "vite", + "scripts" ], "type": "module", "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\"", "vite:build": "bun -F @autumn/shared build && bun -F @autumn/vite build:bun", "vite:start": "bun -F @autumn/vite start:bun", + "shared": "bun -F @autumn/shared build", "server": "bun -F @autumn/shared build && bun -F @autumn/server start", "workers": "bun -F @autumn/shared build && bun -F @autumn/server workers", @@ -20,14 +22,19 @@ "server:cron": "pnpm -F server cron:start", "server:check": "NODE_ENV=production pnpm -F server check", "setup": "node scripts/setup.js", + + "setup:test": "bun scripts/setup-test.ts", + "tests": "bun scripts/test.ts", + "setupci": "node scripts/setupci.js", "db:push": " bun -F @autumn/shared db:push", "db:generate": "bun -F @autumn/shared db:generate", "db:migrate": " bun -F @autumn/shared db:migrate", + "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: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:start:bun": "bun -F @autumn/shared build && bun -F @autumn/vite start:bun" }, diff --git a/scripts/package.json b/scripts/package.json new file mode 100644 index 000000000..2da9851be --- /dev/null +++ b/scripts/package.json @@ -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" + } +} diff --git a/scripts/setup-test.ts b/scripts/setup-test.ts new file mode 100644 index 000000000..d4ddec866 --- /dev/null +++ b/scripts/setup-test.ts @@ -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(); diff --git a/scripts/setupTestUtils/createTestOrg.ts b/scripts/setupTestUtils/createTestOrg.ts new file mode 100644 index 000000000..f2a62e852 --- /dev/null +++ b/scripts/setupTestUtils/createTestOrg.ts @@ -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 { + 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 }; diff --git a/scripts/setupTestUtils/incrementalEnvUpdate.ts b/scripts/setupTestUtils/incrementalEnvUpdate.ts new file mode 100644 index 000000000..a14d78dfd --- /dev/null +++ b/scripts/setupTestUtils/incrementalEnvUpdate.ts @@ -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) { + try { + const envContent = readFileSync(envPath, "utf-8"); + const lines = envContent.split("\n"); + const keysToUpdate = Object.keys(vars); + const foundKeys = new Set(); + + // 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.", + ), + ); + } +} diff --git a/scripts/setupTestUtils/setupPrompts.ts b/scripts/setupTestUtils/setupPrompts.ts new file mode 100644 index 000000000..eb73214db --- /dev/null +++ b/scripts/setupTestUtils/setupPrompts.ts @@ -0,0 +1,136 @@ +import inquirer from "inquirer"; +import chalk from "chalk"; + +/** + * Prompts user for Stripe test API key + */ +export async function setupStripeTestKey(): Promise { + 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 { + 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; +} diff --git a/scripts/setupTestUtils/updateEnvFile.ts b/scripts/setupTestUtils/updateEnvFile.ts new file mode 100644 index 000000000..e34af0215 --- /dev/null +++ b/scripts/setupTestUtils/updateEnvFile.ts @@ -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(); + 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}`), + ); +} diff --git a/scripts/test.ts b/scripts/test.ts new file mode 100644 index 000000000..be06646c3 --- /dev/null +++ b/scripts/test.ts @@ -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 [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(); diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json new file mode 100644 index 000000000..024ea09d3 --- /dev/null +++ b/scripts/tsconfig.json @@ -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"] +} diff --git a/server/shell/config.sh b/server/shell/config.sh index 384ab7bfd..a6f94394e 100755 --- a/server/shell/config.sh +++ b/server/shell/config.sh @@ -1,4 +1,4 @@ #!/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" \ No newline at end of file diff --git a/server/shell/g1.sh b/server/shell/g1.sh index 98896f245..6d0569571 100755 --- a/server/shell/g1.sh +++ b/server/shell/g1.sh @@ -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' \ diff --git a/server/src/external/webhooks/connectWebhookRouter.ts b/server/src/external/webhooks/connectWebhookRouter.ts index afea07ec8..755d1315b 100644 --- a/server/src/external/webhooks/connectWebhookRouter.ts +++ b/server/src/external/webhooks/connectWebhookRouter.ts @@ -29,7 +29,13 @@ export const handleConnectWebhook = async (c: Context) => { 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; // Step 1: Get webhook secret diff --git a/server/test.sh b/server/test.sh index 24893f49c..b782ac7a4 100755 --- a/server/test.sh +++ b/server/test.sh @@ -1,75 +1,6 @@ #!/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" == "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 +if [ "$1" == "custom" ]; then FILE_TO_TEST="$2" ARG3="$3" if [ "$ARG3" == "setup" ]; then @@ -79,41 +10,123 @@ elif [ "$1" == "custom" ]; then 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 \ + +# MOCHA_SETUP="npx mocha tests/00_setup.ts" +# MOCHA_CMD="npx mocha --parallel --timeout 10000000 --ignore tests/00_setup.ts" + +# # Group 1 +# if [ "$1" == "group1" ]; then +# $MOCHA_SETUP \ +# && $MOCHA_CMD \ # 'tests/attach/basic/*.ts' \ # 'tests/attach/upgrade/*.ts' \ -# 'tests/attach/downgrade/*.ts' \ -# && $MOCHA_CMD \ +# '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/multiProduct/*.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/contUse/entities/*.ts' \ -# 'tests/contUse/track/*.ts' \ -# 'tests/contUse/update/*.ts' \ -# && $MOCHA_CMD \ +# 'tests/attach/multiProduct/*.ts' \ # 'tests/advanced/multiFeature/*.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 \ -# $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'\ + +# 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" +# 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'\ diff --git a/server/tests/00_setup.ts b/server/tests/00_setup.ts index b015071fc..2b1549360 100644 --- a/server/tests/00_setup.ts +++ b/server/tests/00_setup.ts @@ -1,35 +1,31 @@ import dotenv from "dotenv"; + dotenv.config(); import { AppEnv } from "@autumn/shared"; import { clearOrg, setupOrg } from "tests/utils/setup.js"; +import { initDrizzle } from "@/db/initDrizzle.js"; import { - features, - products, - creditSystems, advanceProducts, attachProducts, - rewards, - oneTimeProducts, + creditSystems, entityProducts, + features, + oneTimeProducts, + products, referralPrograms, + rewards, } from "./global.js"; -import { initDrizzle } from "@/db/initDrizzle.js"; const ORG_SLUG = process.env.TESTS_ORG!; 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", () => { it("should initialize org", async function () { this.timeout(1000000000); this.org = await clearOrg({ orgSlug: ORG_SLUG, env: DEFAULT_ENV }); this.env = DEFAULT_ENV; - let { db, client } = initDrizzle(); + const { db, client } = initDrizzle(); this.db = db; this.client = client; diff --git a/server/tests/before.ts b/server/tests/before.ts index 8ae1f1fcb..9b69ce053 100644 --- a/server/tests/before.ts +++ b/server/tests/before.ts @@ -13,12 +13,6 @@ import { OrgService } from "@/internal/orgs/OrgService.js"; const ORG_SLUG = process.env.TESTS_ORG!; 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) => { try { const { db, client } = initDrizzle(); diff --git a/shared/package.json b/shared/package.json index 840a86285..7f13a0be7 100644 --- a/shared/package.json +++ b/shared/package.json @@ -7,7 +7,8 @@ "exports": { ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.js" + "import": "./dist/index.js", + "default": "./dist/index.js" } }, "author": "Recase Inc.", diff --git a/vite/src/views/main-sidebar/org-dropdown/manage-org/DeleteOrgPopover.tsx b/vite/src/views/main-sidebar/org-dropdown/manage-org/DeleteOrgPopover.tsx index 161d54f81..99779ee14 100644 --- a/vite/src/views/main-sidebar/org-dropdown/manage-org/DeleteOrgPopover.tsx +++ b/vite/src/views/main-sidebar/org-dropdown/manage-org/DeleteOrgPopover.tsx @@ -1,3 +1,5 @@ +import { useState } from "react"; +import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { @@ -9,8 +11,6 @@ import { useOrg } from "@/hooks/common/useOrg"; import { authClient, useListOrganizations } from "@/lib/auth-client"; import { useAxiosInstance } from "@/services/useAxiosInstance"; import { getBackendErr } from "@/utils/genUtils"; -import { useState } from "react"; -import { toast } from "sonner"; import { useMemberships } from "../hooks/useMemberships"; export const DeleteOrgPopover = () => { @@ -64,6 +64,7 @@ export const DeleteOrgPopover = () => { try { await deleteOrg(); } catch (error) { + console.log("Error:", error); toast.error(getBackendErr(error, "Failed to delete org")); }