createTestOrg now calls the production afterOrgCreated hook directly so the test org goes through the same bootstrap as real orgs (Stripe Connect, Svix, OrgService.update). Both org branches gate createStripeAccount on the existing test_stripe_connect to stay idempotent across re-runs. Pins TEST_ORG_PUBLISHABLE_KEY (a real KSUID-generated am_pk_test_* value) and persists UNIT_TEST_AUTUMN_PUBLIC_KEY plus the auto-generated UNIT_TEST_AUTUMN_SECRET_KEY to server/.env.local + server/.env so test clients pick them up. Deletes ensureDefaultStripeAccount.ts (replaced by the gated afterOrgCreated call).
292 lines
7.6 KiB
TypeScript
292 lines
7.6 KiB
TypeScript
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<string> {
|
|
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,
|
|
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=<inv_id> (or
|
|
// equivalent UI) after signing in to finish joining the org.
|
|
async function seedTeamInvites({ db }: { db: DrizzleCli }): Promise<void> {
|
|
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)`,
|
|
),
|
|
);
|
|
}
|