import { AppEnv, invitation, member, type OrgConfig, organizations, user, } from "@autumn/shared"; import type { DrizzleCli } from "@server/db/initDrizzle.js"; import { createHardcodedKey, createKey, } from "@server/internal/dev/api-keys/apiKeyUtils.js"; import chalk from "chalk"; import { and, eq, inArray } from "drizzle-orm"; import { clearOrgDbOnly } from "@tests/utils/setup/clearOrg.js"; import { setupOrg } from "@tests/utils/setup/setupOrg.js"; import { afterOrgCreated } from "@server/utils/authUtils/afterOrgCreated.js"; const TEST_ORG_CONFIG = { id: "org_2sWv2S8LJ9iaTjLI6UtNsfL88Kt", slug: "unit-test-org", name: "Unit Test Org", createdAt: new Date(1738583937426).toISOString(), created_at: 1738583937426, }; export const TEST_ORG_PUBLISHABLE_KEY = "am_pk_test_3DoBu1cmlgxWqEXYiKaBKOPHqsu"; // Synthetic inviter pinned to the test org; satisfies invitation.inviter_id // NOT-NULL FK without needing a real human user in a fresh worktree branch. const TEST_INVITER_USER = { id: "user_setup_test_inviter", name: "Setup Test Inviter", email: "setup-test-inviter@autumn.test", }; const TEAM_INVITE_EMAILS = [ "ayush@useautumn.com", "jy@useautumn.com", "tanvir@useautumn.com", "charlie@useautumn.com", "owen@useautumn.com", ]; /** * 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", ), ); const TEST_API_KEY = process.env.UNIT_TEST_AUTUMN_SECRET_KEY; // 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.`, ), ); await afterOrgCreated({ org: { ...existingOrg, slug: TEST_ORG_CONFIG.slug } as any, user: TEST_INVITER_USER as any, createStripeAccount: !existingOrg.test_stripe_connect?.default_account_id, pkey: TEST_ORG_PUBLISHABLE_KEY, }); await seedTeamInvites({ db }); await clearOrgDbOnly({ db, orgId: TEST_ORG_CONFIG.id, env: AppEnv.Sandbox }); await setupOrg({ orgId: TEST_ORG_CONFIG.id, env: AppEnv.Sandbox }); if (TEST_API_KEY) { const { key, alreadyExists } = await createHardcodedKey({ db, env: AppEnv.Sandbox, name: "Unit Test Key", orgId: TEST_ORG_CONFIG.id, hardcodedKey: TEST_API_KEY, meta: { createdBy: "setup-test-script", createdAt: new Date().toISOString(), }, }); console.log( chalk.greenBright( alreadyExists ? "✅ API key already exists in database" : "✅ Created API key for existing organization", ), ); return key; } // No hardcoded key in env; generate a fresh one for the existing org. const generated = await createKey({ db, env: AppEnv.Sandbox, name: "Unit Test Key", orgId: TEST_ORG_CONFIG.id, prefix: "am_sk_test_", meta: { createdBy: "setup-test-script", createdAt: new Date().toISOString(), autogenerated: true, }, }); console.log( chalk.greenBright( "✅ Generated fresh API key (UNIT_TEST_AUTUMN_SECRET_KEY unset)", ), ); return generated; } // 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})`, ), ); const insertedOrg = { id: TEST_ORG_CONFIG.id, slug: TEST_ORG_CONFIG.slug, name: TEST_ORG_CONFIG.name, createdAt: new Date(TEST_ORG_CONFIG.created_at), }; await afterOrgCreated({ org: insertedOrg as any, user: TEST_INVITER_USER as any, createStripeAccount: true, pkey: TEST_ORG_PUBLISHABLE_KEY, }); await seedTeamInvites({ db }); await clearOrgDbOnly({ db, orgId: TEST_ORG_CONFIG.id, env: AppEnv.Sandbox }); await setupOrg({ orgId: TEST_ORG_CONFIG.id, env: AppEnv.Sandbox }); // Create API key for the new org if (TEST_API_KEY) { const { key, alreadyExists } = await createHardcodedKey({ db, env: AppEnv.Sandbox, name: "Unit Test Key", orgId: TEST_ORG_CONFIG.id, hardcodedKey: TEST_API_KEY, meta: { createdBy: "setup-test-script", createdAt: new Date().toISOString(), }, }); console.log( chalk.greenBright( alreadyExists ? "✅ API key already exists in database" : "✅ Created API key for test organization", ), ); return key; } const generated = await createKey({ db, env: AppEnv.Sandbox, name: "Unit Test Key", orgId: TEST_ORG_CONFIG.id, prefix: "am_sk_test_", meta: { createdBy: "setup-test-script", createdAt: new Date().toISOString(), autogenerated: true, }, }); console.log( chalk.greenBright( "✅ Generated fresh API key (UNIT_TEST_AUTUMN_SECRET_KEY unset)", ), ); return generated; } export { TEST_ORG_CONFIG }; // Per-email: insert a member row if a user already exists, otherwise insert // an invitation row. Both paths skip if a matching member/invitation already // targets this org+email — safe to re-run. Note: better-auth does NOT auto- // accept invitations on sign-in. Invitees must visit /accept?id= (or // equivalent UI) after signing in to finish joining the org. async function seedTeamInvites({ db }: { db: DrizzleCli }): Promise { const { generateId } = await import("@server/utils/genUtils.js"); // Ensure a synthetic inviter exists so invitation.inviter_id FK resolves. await db .insert(user) .values({ id: TEST_INVITER_USER.id, name: TEST_INVITER_USER.name, email: TEST_INVITER_USER.email, emailVerified: true, createdAt: new Date(), updatedAt: new Date(), }) .onConflictDoNothing(); const existingUsers = await db .select() .from(user) .where(inArray(user.email, TEAM_INVITE_EMAILS)); const userByEmail = new Map(existingUsers.map((u) => [u.email, u])); const existingMembers = await db .select() .from(member) .where(eq(member.organizationId, TEST_ORG_CONFIG.id)); const membershipUserIds = new Set(existingMembers.map((m) => m.userId)); const existingInvitesForOrg = await db .select() .from(invitation) .where( and( eq(invitation.organizationId, TEST_ORG_CONFIG.id), inArray(invitation.email, TEAM_INVITE_EMAILS), ), ); const inviteEmails = new Set(existingInvitesForOrg.map((i) => i.email)); let membersCreated = 0; let invitesCreated = 0; const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); for (const email of TEAM_INVITE_EMAILS) { const existingUser = userByEmail.get(email); if (existingUser) { if (membershipUserIds.has(existingUser.id)) continue; await db.insert(member).values({ id: generateId("mem"), organizationId: TEST_ORG_CONFIG.id, userId: existingUser.id, role: "owner", createdAt: new Date(), }); membersCreated++; } else { if (inviteEmails.has(email)) continue; await db.insert(invitation).values({ id: generateId("inv"), organizationId: TEST_ORG_CONFIG.id, email, role: "owner", status: "pending", createdAt: new Date(), expiresAt, inviterId: TEST_INVITER_USER.id, }); invitesCreated++; } } console.log( chalk.greenBright( `✅ Team seed: ${membersCreated} member(s) + ${invitesCreated} invitation(s) for ${TEAM_INVITE_EMAILS.length} email(s)`, ), ); }