Files
calendar/packages/features/auth/signup/utils/createOrUpdateMemberships.integration-test.ts
T
98b6d63164 refactor: apply biome formatting to packages/features (#27844)
* refactor: apply biome formatting to packages/features (batch 1 - small subdirs)

Format small subdirectories in packages/features: di, flags, holidays, oauth,
settings, users, assignment-reason, selectedCalendar, hashedLink, host, form,
form-builder, availability, data-table, pbac, schedules, troubleshooter,
eventtypes, calendar-subscription, and root-level files.

Also includes straggler apps/web BookEventForm.tsx.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 2 - medium subdirs)

Format medium subdirectories in packages/features: auth, credentials,
calendars, routing-forms, routing-trace, attributes, watchlist, calAIPhone,
tasker, and webhooks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 3 - bookings + insights)

Format bookings and insights subdirectories in packages/features.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 4 - ee)

Format packages/features/ee subdirectory covering billing, workflows,
organizations, teams, managed-event-types, round-robin, dsync,
integration-attribute-sync, and payments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 5 - booking-audit part 1)

Format booking-audit di, actions, common, dto, repository, and types
subdirectories in packages/features/booking-audit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 6 - booking-audit part 2)

Format booking-audit service subdirectory in packages/features/booking-audit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 15:47:14 +01:00

473 lines
14 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { prisma } from "@calcom/prisma";
import type { Team, User, OrganizationSettings } from "@calcom/prisma/client";
import { MembershipRole } from "@calcom/prisma/enums";
import { createOrUpdateMemberships } from "./createOrUpdateMemberships";
function generateUniqueId() {
return `${Date.now()}-${Math.random().toString(36).substring(7)}`;
}
async function createTestUser(data: {
email: string;
username: string;
organizationId?: number | null;
}): Promise<User> {
const uniqueId = generateUniqueId();
const uniqueEmail = data.email.includes("@")
? data.email.replace("@", `-${uniqueId}@`)
: `${data.email}-${uniqueId}@example.com`;
const uniqueUsername = `${data.username}-${uniqueId}`;
return await prisma.user.create({
data: {
email: uniqueEmail,
username: uniqueUsername,
organizationId: data.organizationId ?? undefined,
},
});
}
async function createTestOrganization(data: {
name: string;
slug: string;
orgAutoAcceptEmail?: string;
}): Promise<Team & { organizationSettings: OrganizationSettings | null }> {
const uniqueId = generateUniqueId();
const uniqueSlug = `${data.slug}-${uniqueId}`;
const team = await prisma.team.create({
data: {
name: `${data.name} ${uniqueId}`,
slug: uniqueSlug,
isOrganization: true,
},
});
let organizationSettings: OrganizationSettings | null = null;
if (data.orgAutoAcceptEmail) {
organizationSettings = await prisma.organizationSettings.create({
data: {
organizationId: team.id,
orgAutoAcceptEmail: data.orgAutoAcceptEmail,
isOrganizationVerified: true,
},
});
}
return { ...team, organizationSettings };
}
async function createTestSubteam(data: { name: string; slug: string; parentId: number }): Promise<Team> {
const uniqueId = generateUniqueId();
const uniqueSlug = `${data.slug}-${uniqueId}`;
return await prisma.team.create({
data: {
name: `${data.name} ${uniqueId}`,
slug: uniqueSlug,
isOrganization: false,
parentId: data.parentId,
},
});
}
describe("createOrUpdateMemberships Integration Tests", () => {
let testUsers: User[] = [];
let testTeams: Team[] = [];
beforeEach(() => {
testUsers = [];
testTeams = [];
});
afterEach(async () => {
const userIds = testUsers.map((u) => u.id);
const teamIds = testTeams.map((t) => t.id);
if (userIds.length > 0 || teamIds.length > 0) {
// Clean up in reverse dependency order
await prisma.profile.deleteMany({
where: {
OR: [{ userId: { in: userIds } }, { organizationId: { in: teamIds } }],
},
});
await prisma.membership.deleteMany({
where: {
OR: [{ userId: { in: userIds } }, { teamId: { in: teamIds } }],
},
});
await prisma.organizationSettings.deleteMany({
where: { organizationId: { in: teamIds } },
});
await prisma.team.deleteMany({
where: { id: { in: teamIds } },
});
await prisma.user.deleteMany({
where: { id: { in: userIds } },
});
}
});
function trackUser(user: User): User {
testUsers.push(user);
return user;
}
function trackTeam(team: Team): Team {
testTeams.push(team);
return team;
}
describe("Organization Direct Membership", () => {
it("should create membership and profile when joining an organization directly", async () => {
// Setup
const org = trackTeam(
await createTestOrganization({
name: "Test Org",
slug: "test-org",
orgAutoAcceptEmail: "example.com",
})
);
const user = trackUser(
await createTestUser({
email: "user@example.com",
username: "testuser",
organizationId: org.id,
})
);
// Act
await createOrUpdateMemberships({
user: { id: user.id },
team: {
id: org.id,
parentId: org.parentId,
isOrganization: org.isOrganization ?? false,
organizationSettings: { orgAutoAcceptEmail: "example.com" },
},
});
// Assert - membership created with accepted: true
const membership = await prisma.membership.findUnique({
where: { userId_teamId: { userId: user.id, teamId: org.id } },
});
expect(membership).toBeTruthy();
expect(membership?.accepted).toBe(true);
expect(membership?.role).toBe(MembershipRole.MEMBER);
// Assert - profile created
const profile = await prisma.profile.findUnique({
where: {
userId_organizationId: { userId: user.id, organizationId: org.id },
},
});
expect(profile).toBeTruthy();
expect(profile?.organizationId).toBe(org.id);
});
});
describe("Sub-Team Membership (within organization)", () => {
it("should create memberships for both team and parent org when joining a sub-team", async () => {
// Setup
const org = trackTeam(
await createTestOrganization({
name: "Test Org",
slug: "test-org",
orgAutoAcceptEmail: "example.com",
})
);
const subteam = trackTeam(
await createTestSubteam({
name: "Sub Team",
slug: "sub-team",
parentId: org.id,
})
);
const user = trackUser(
await createTestUser({
email: "user@example.com",
username: "testuser",
organizationId: org.id, // User already has organizationId set (as done in calcomSignupHandler)
})
);
// Act
await createOrUpdateMemberships({
user: { id: user.id },
team: {
id: subteam.id,
parentId: subteam.parentId,
isOrganization: subteam.isOrganization ?? false,
organizationSettings: null,
},
});
// Assert - team membership created
const teamMembership = await prisma.membership.findUnique({
where: { userId_teamId: { userId: user.id, teamId: subteam.id } },
});
expect(teamMembership).toBeTruthy();
expect(teamMembership?.accepted).toBe(true);
// Assert - org membership created
const orgMembership = await prisma.membership.findUnique({
where: { userId_teamId: { userId: user.id, teamId: org.id } },
});
expect(orgMembership).toBeTruthy();
expect(orgMembership?.accepted).toBe(true);
});
it("should create profile for parent org when joining a sub-team", async () => {
// Setup
const org = trackTeam(
await createTestOrganization({
name: "Test Org",
slug: "test-org",
orgAutoAcceptEmail: "example.com",
})
);
const subteam = trackTeam(
await createTestSubteam({
name: "Sub Team",
slug: "sub-team",
parentId: org.id,
})
);
const user = trackUser(
await createTestUser({
email: "user@example.com",
username: "testuser",
organizationId: org.id, // User already has organizationId set
})
);
// Act - NOTE: We need to pass parent data for profile creation
await createOrUpdateMemberships({
user: { id: user.id },
team: {
id: subteam.id,
parentId: subteam.parentId,
isOrganization: subteam.isOrganization ?? false,
organizationSettings: null,
parent: {
id: org.id,
organizationSettings: { orgAutoAcceptEmail: "example.com" },
},
},
});
// Assert - profile should be created for the parent organization
const profile = await prisma.profile.findUnique({
where: {
userId_organizationId: { userId: user.id, organizationId: org.id },
},
});
// BUG: This assertion will FAIL because profile is not created for sub-team signup
expect(profile).toBeTruthy();
expect(profile?.organizationId).toBe(org.id);
});
it("should handle user invited to sub-team via invite link (new user flow)", async () => {
// This simulates the API v2 invite link flow:
// 1. Token created with identifier: "invite-link-for-teamId-${teamId}"
// 2. User fills in their email and signs up
// 3. User should be added to both team and org with proper profile
// Setup
const org = trackTeam(
await createTestOrganization({
name: "Acme Corp",
slug: "acme",
orgAutoAcceptEmail: "acme.com",
})
);
const salesTeam = trackTeam(
await createTestSubteam({
name: "Sales Team",
slug: "sales",
parentId: org.id,
})
);
// User is created during signup with organizationId already set
// (this happens in calcomSignupHandler.ts:200-232)
const newUser = trackUser(
await createTestUser({
email: "newuser@acme.com",
username: "newuser",
organizationId: org.id,
})
);
// Act - simulate createOrUpdateMemberships call from calcomSignupHandler
await createOrUpdateMemberships({
user: { id: newUser.id },
team: {
id: salesTeam.id,
parentId: salesTeam.parentId,
isOrganization: salesTeam.isOrganization ?? false,
organizationSettings: null,
parent: {
id: org.id,
organizationSettings: { orgAutoAcceptEmail: "acme.com" },
},
},
});
// Assert - team membership exists
const teamMembership = await prisma.membership.findUnique({
where: { userId_teamId: { userId: newUser.id, teamId: salesTeam.id } },
});
expect(teamMembership).toBeTruthy();
expect(teamMembership?.accepted).toBe(true);
// Assert - org membership exists
const orgMembership = await prisma.membership.findUnique({
where: { userId_teamId: { userId: newUser.id, teamId: org.id } },
});
expect(orgMembership).toBeTruthy();
expect(orgMembership?.accepted).toBe(true);
// Assert - profile exists for the organization
// BUG: This will FAIL - the profile is not created for sub-team invites
const profile = await prisma.profile.findUnique({
where: {
userId_organizationId: { userId: newUser.id, organizationId: org.id },
},
});
expect(profile).toBeTruthy();
expect(profile?.organizationId).toBe(org.id);
expect(profile?.username).toBe(newUser.username);
});
it("should upsert existing memberships when user was previously invited", async () => {
// Setup - user was invited via email (membership created with accepted: false)
const org = trackTeam(
await createTestOrganization({
name: "Test Org",
slug: "test-org",
orgAutoAcceptEmail: "example.com",
})
);
const subteam = trackTeam(
await createTestSubteam({
name: "Sub Team",
slug: "sub-team",
parentId: org.id,
})
);
const user = trackUser(
await createTestUser({
email: "invited@other.com",
username: "inviteduser",
organizationId: org.id,
})
);
// Pre-create memberships as they would be created during invite
await prisma.membership.create({
data: {
userId: user.id,
teamId: subteam.id,
role: MembershipRole.MEMBER,
accepted: false,
},
});
await prisma.membership.create({
data: {
userId: user.id,
teamId: org.id,
role: MembershipRole.MEMBER,
accepted: false,
},
});
// Act - user completes signup
await createOrUpdateMemberships({
user: { id: user.id },
team: {
id: subteam.id,
parentId: subteam.parentId,
isOrganization: subteam.isOrganization ?? false,
organizationSettings: null,
parent: {
id: org.id,
organizationSettings: null,
},
},
});
// Assert - memberships are now accepted
const teamMembership = await prisma.membership.findUnique({
where: { userId_teamId: { userId: user.id, teamId: subteam.id } },
});
expect(teamMembership?.accepted).toBe(true);
const orgMembership = await prisma.membership.findUnique({
where: { userId_teamId: { userId: user.id, teamId: org.id } },
});
expect(orgMembership?.accepted).toBe(true);
});
});
describe("Regular Team Membership (not in organization)", () => {
it("should create only team membership for regular teams", async () => {
// Setup - regular team without parent org
const uniqueId = generateUniqueId();
const regularTeam = trackTeam(
await prisma.team.create({
data: {
name: `Regular Team ${uniqueId}`,
slug: `regular-team-${uniqueId}`,
isOrganization: false,
parentId: null,
},
})
);
const user = trackUser(
await createTestUser({
email: "user@example.com",
username: "testuser",
})
);
// Act
await createOrUpdateMemberships({
user: { id: user.id },
team: {
id: regularTeam.id,
parentId: regularTeam.parentId,
isOrganization: regularTeam.isOrganization ?? false,
organizationSettings: null,
},
});
// Assert - team membership created
const teamMembership = await prisma.membership.findUnique({
where: { userId_teamId: { userId: user.id, teamId: regularTeam.id } },
});
expect(teamMembership).toBeTruthy();
expect(teamMembership?.accepted).toBe(true);
// Assert - no profile should be created for regular teams
const profiles = await prisma.profile.findMany({
where: { userId: user.id },
});
expect(profiles.length).toBe(0);
});
});
});