diff --git a/apps/api/v1/lib/utils/isAdmin.ts b/apps/api/v1/lib/utils/isAdmin.ts index 38f3cc3099..3198712041 100644 --- a/apps/api/v1/lib/utils/isAdmin.ts +++ b/apps/api/v1/lib/utils/isAdmin.ts @@ -19,7 +19,9 @@ export const isAdminGuard = async (req: NextApiRequest) => { team: { isOrganization: true, organizationSettings: { - isAdminAPIEnabled: true, + is: { + isAdminAPIEnabled: true, + }, }, }, OR: [{ role: MembershipRole.OWNER }, { role: MembershipRole.ADMIN }], diff --git a/apps/api/v1/test/lib/bookings/[id]/_patch.integration-test.ts b/apps/api/v1/test/lib/bookings/[id]/_patch.integration-test.ts index 251583cb28..5554d6411d 100644 --- a/apps/api/v1/test/lib/bookings/[id]/_patch.integration-test.ts +++ b/apps/api/v1/test/lib/bookings/[id]/_patch.integration-test.ts @@ -1,7 +1,7 @@ import type { Request, Response } from "express"; import type { NextApiRequest, NextApiResponse } from "next"; import { createMocks } from "node-mocks-http"; -import { describe, it, expect, beforeAll } from "vitest"; +import { describe, it, expect } from "vitest"; import prisma from "@calcom/prisma"; @@ -11,30 +11,6 @@ type CustomNextApiRequest = NextApiRequest & Request; type CustomNextApiResponse = NextApiResponse & Response; describe("PATCH /api/bookings", () => { - beforeAll(async () => { - const acmeOrg = await prisma.team.findFirst({ - where: { - slug: "acme", - isOrganization: true, - }, - }); - - if (acmeOrg) { - await prisma.organizationSettings.upsert({ - where: { - organizationId: acmeOrg.id, - }, - update: { - isAdminAPIEnabled: true, - }, - create: { - organizationId: acmeOrg.id, - orgAutoAcceptEmail: "acme.com", - isAdminAPIEnabled: true, - }, - }); - } - }); it("Returns 403 when user has no permission to the booking", async () => { const memberUser = await prisma.user.findFirstOrThrow({ where: { email: "member2-acme@example.com" } }); const proUser = await prisma.user.findFirstOrThrow({ where: { email: "pro@example.com" } }); diff --git a/apps/api/v1/test/lib/bookings/_get.integration-test.ts b/apps/api/v1/test/lib/bookings/_get.integration-test.ts index 2f2ba4f98e..18099ef3fe 100644 --- a/apps/api/v1/test/lib/bookings/_get.integration-test.ts +++ b/apps/api/v1/test/lib/bookings/_get.integration-test.ts @@ -16,33 +16,14 @@ const DefaultPagination = { skip: 0, }; -describe("GET /api/bookings", async () => { - beforeAll(async () => { - const acmeOrg = await prisma.team.findFirst({ - where: { - slug: "acme", - isOrganization: true, - }, - }); +describe("GET /api/bookings", () => { + let proUser: Awaited>; + let proUserBooking: Awaited>; - if (acmeOrg) { - await prisma.organizationSettings.upsert({ - where: { - organizationId: acmeOrg.id, - }, - update: { - isAdminAPIEnabled: true, - }, - create: { - organizationId: acmeOrg.id, - orgAutoAcceptEmail: "acme.com", - isAdminAPIEnabled: true, - }, - }); - } + beforeAll(async () => { + proUser = await prisma.user.findFirstOrThrow({ where: { email: "pro@example.com" } }); + proUserBooking = await prisma.booking.findFirstOrThrow({ where: { userId: proUser.id } }); }); - const proUser = await prisma.user.findFirstOrThrow({ where: { email: "pro@example.com" } }); - const proUserBooking = await prisma.booking.findFirstOrThrow({ where: { userId: proUser.id } }); it("Does not return bookings of other users when user has no permission", async () => { const memberUser = await prisma.user.findFirstOrThrow({ where: { email: "member2-acme@example.com" } }); diff --git a/apps/api/v1/test/lib/utils/isAdmin.integration-test.ts b/apps/api/v1/test/lib/utils/isAdmin.integration-test.ts index f8485605cb..2136617d23 100644 --- a/apps/api/v1/test/lib/utils/isAdmin.integration-test.ts +++ b/apps/api/v1/test/lib/utils/isAdmin.integration-test.ts @@ -1,7 +1,7 @@ import type { Request, Response } from "express"; import type { NextApiRequest, NextApiResponse } from "next"; import { createMocks } from "node-mocks-http"; -import { describe, it, expect, beforeAll } from "vitest"; +import { describe, it, expect } from "vitest"; import prisma from "@calcom/prisma"; @@ -12,53 +12,6 @@ type CustomNextApiRequest = NextApiRequest & Request; type CustomNextApiResponse = NextApiResponse & Response; describe("isAdmin guard", () => { - beforeAll(async () => { - const acmeOrg = await prisma.team.findFirst({ - where: { - slug: "acme", - isOrganization: true, - }, - }); - - if (acmeOrg) { - await prisma.organizationSettings.upsert({ - where: { - organizationId: acmeOrg.id, - }, - update: { - isAdminAPIEnabled: true, - }, - create: { - organizationId: acmeOrg.id, - orgAutoAcceptEmail: "acme.com", - isAdminAPIEnabled: true, - }, - }); - } - - const dunderOrg = await prisma.team.findFirst({ - where: { - slug: "dunder-mifflin", - isOrganization: true, - }, - }); - - if (dunderOrg) { - await prisma.organizationSettings.upsert({ - where: { - organizationId: dunderOrg.id, - }, - update: { - isAdminAPIEnabled: false, - }, - create: { - organizationId: dunderOrg.id, - orgAutoAcceptEmail: "dunder-mifflin.com", - isAdminAPIEnabled: false, - }, - }); - } - }); it("Returns false when user does not exist in the system", async () => { const { req } = createMocks({ method: "POST", diff --git a/apps/api/v1/test/lib/utils/retrieveScopedAccessibleUsers.integration-test.ts b/apps/api/v1/test/lib/utils/retrieveScopedAccessibleUsers.integration-test.ts index 66f180c4e0..d670defc72 100644 --- a/apps/api/v1/test/lib/utils/retrieveScopedAccessibleUsers.integration-test.ts +++ b/apps/api/v1/test/lib/utils/retrieveScopedAccessibleUsers.integration-test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, beforeAll } from "vitest"; +import { describe, it, expect } from "vitest"; import prisma from "@calcom/prisma"; @@ -8,30 +8,6 @@ import { } from "../../../lib/utils/retrieveScopedAccessibleUsers"; describe("retrieveScopedAccessibleUsers tests", () => { - beforeAll(async () => { - const acmeOrg = await prisma.team.findFirst({ - where: { - slug: "acme", - isOrganization: true, - }, - }); - - if (acmeOrg) { - await prisma.organizationSettings.upsert({ - where: { - organizationId: acmeOrg.id, - }, - update: { - isAdminAPIEnabled: true, - }, - create: { - organizationId: acmeOrg.id, - orgAutoAcceptEmail: "acme.com", - isAdminAPIEnabled: true, - }, - }); - } - }); describe("getAccessibleUsers", () => { it("Does not return members when only admin user ID is supplied", async () => { const adminUser = await prisma.user.findFirstOrThrow({ where: { email: "owner1-acme@example.com" } }); diff --git a/tests/integration/global-setup.ts b/tests/integration/global-setup.ts new file mode 100644 index 0000000000..5d9afe25bd --- /dev/null +++ b/tests/integration/global-setup.ts @@ -0,0 +1,58 @@ +/** + * Global setup for integration tests + * Runs once before all integration tests to seed org-admin state + */ +export default async function globalSetup() { + const module = await import("../lib/test-team-repository"); + const TestTeamRepository = module.TestTeamRepository; + const teamRepo = await TestTeamRepository.withGlobalPrisma(); + + const acmeOrg = await teamRepo.findOrganizationBySlug("acme"); + + if (acmeOrg) { + await teamRepo.upsertOrganizationSettings({ + organizationId: acmeOrg.id, + isAdminAPIEnabled: true, + orgAutoAcceptEmail: "acme.com", + }); + + await teamRepo.ensureMembership({ + userEmail: "owner1-acme@example.com", + organizationId: acmeOrg.id, + role: "OWNER", + accepted: true, + }); + + for (let i = 0; i < 10; i++) { + const memberEmail = `member${i}-acme@example.com`; + const memberUsername = `member${i}-acme`; + const memberName = `Member ${i}`; + + await teamRepo.ensureUser({ + email: memberEmail, + username: memberUsername, + name: memberName, + }); + + await teamRepo.ensureMembership({ + userEmail: memberEmail, + organizationId: acmeOrg.id, + role: "MEMBER", + accepted: true, + }); + } + } + + const dunderOrg = await teamRepo.findOrganizationBySlug("dunder-mifflin"); + + if (dunderOrg) { + await teamRepo.upsertOrganizationSettings({ + organizationId: dunderOrg.id, + isAdminAPIEnabled: false, + orgAutoAcceptEmail: "dunder-mifflin.com", + }); + } + + return async () => { + }; +} diff --git a/tests/lib/test-team-repository.ts b/tests/lib/test-team-repository.ts new file mode 100644 index 0000000000..0fbc5f0ab5 --- /dev/null +++ b/tests/lib/test-team-repository.ts @@ -0,0 +1,144 @@ +import type { PrismaClient } from "@calcom/prisma"; + +export class TestTeamRepository { + constructor(private prismaClient: PrismaClient) {} + + static async withGlobalPrisma() { + return new TestTeamRepository((await import("@calcom/prisma")).prisma); + } + + async findOrganizationBySlug(slug: string) { + return this.prismaClient.team.findFirst({ + where: { + slug, + isOrganization: true, + }, + select: { + id: true, + slug: true, + name: true, + }, + }); + } + + async upsertOrganizationSettings({ + organizationId, + isAdminAPIEnabled, + orgAutoAcceptEmail, + }: { + organizationId: number; + isAdminAPIEnabled: boolean; + orgAutoAcceptEmail?: string; + }) { + return this.prismaClient.organizationSettings.upsert({ + where: { + organizationId, + }, + update: { + isAdminAPIEnabled, + }, + create: { + organizationId, + orgAutoAcceptEmail: orgAutoAcceptEmail || "", + isAdminAPIEnabled, + }, + }); + } + + async findUserMembershipsInOrg({ + userEmail, + organizationId, + }: { + userEmail: string; + organizationId: number; + }) { + return this.prismaClient.membership.findMany({ + where: { + user: { + email: userEmail, + }, + teamId: organizationId, + }, + select: { + id: true, + role: true, + accepted: true, + user: { + select: { + id: true, + email: true, + }, + }, + }, + }); + } + + async ensureUser({ + email, + username, + name, + }: { + email: string; + username: string; + name: string; + }) { + return this.prismaClient.user.upsert({ + where: { email }, + update: { + name, + }, + create: { + email, + username, + name, + emailVerified: new Date(), + }, + }); + } + + async ensureMembership({ + userEmail, + organizationId, + role, + accepted, + }: { + userEmail: string; + organizationId: number; + role: "OWNER" | "ADMIN" | "MEMBER"; + accepted: boolean; + }) { + const user = await this.prismaClient.user.findFirst({ + where: { email: userEmail }, + }); + + if (!user) { + throw new Error(`User with email ${userEmail} not found`); + } + + const existingMembership = await this.prismaClient.membership.findFirst({ + where: { + userId: user.id, + teamId: organizationId, + }, + }); + + if (existingMembership) { + return this.prismaClient.membership.update({ + where: { id: existingMembership.id }, + data: { + role, + accepted, + }, + }); + } + + return this.prismaClient.membership.create({ + data: { + userId: user.id, + teamId: organizationId, + role, + accepted, + }, + }); + } +} diff --git a/vitest.workspace.ts b/vitest.workspace.ts index 498e907eef..62260ef55f 100644 --- a/vitest.workspace.ts +++ b/vitest.workspace.ts @@ -27,6 +27,7 @@ const workspaces = packagedEmbedTestsOnly name: `IntegrationTests`, include: ["packages/**/*.integration-test.ts", "apps/**/*.integration-test.ts"], exclude: ["**/node_modules/**/*", "packages/embeds/**/*"], + globalSetup: new URL("./tests/integration/global-setup.ts", import.meta.url).pathname, setupFiles: ["setupVitest.ts"], }, resolve: {