This reverts commit f86c448359.
This commit is contained in:
@@ -1,15 +1,9 @@
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { randomString } from "@calcom/lib/random";
|
||||
import slugify from "@calcom/lib/slugify";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { BookingStatus } from "@calcom/prisma/client";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
|
||||
import { createTeamEventType } from "./fixtures/users";
|
||||
import type { Fixtures } from "./lib/fixtures";
|
||||
import { test } from "./lib/fixtures";
|
||||
import { assertBookingVisibleFor, bookTeamEvent, bookUserEvent } from "./lib/testUtils";
|
||||
|
||||
test.afterEach(({ users }) => users.deleteAll());
|
||||
|
||||
@@ -67,137 +61,6 @@ test.describe("Bookings", () => {
|
||||
secondUpcomingBooking.locator(`text=${bookingWhereFirstUserIsOrganizer!.title}`)
|
||||
).toBeVisible();
|
||||
});
|
||||
test("collective eventType booking should be visible to team admin", async ({ page, users }) => {
|
||||
const { owner1, owner2, commonUser, team1_teammate1 } = await createTeams(users);
|
||||
|
||||
const { team: team1 } = await owner1.getFirstTeamMembership();
|
||||
const scenario = {
|
||||
schedulingType: SchedulingType.COLLECTIVE,
|
||||
teamEventTitle: `collective-team-event`,
|
||||
teamEventSlug: slugify(`collective-team-event-${randomString(5)}`),
|
||||
};
|
||||
|
||||
const eventType = await createTeamEventType(owner1, team1, scenario);
|
||||
const { id: eventId, slug: evnetSlug } = eventType;
|
||||
|
||||
await prisma.host.createMany({
|
||||
data: [
|
||||
{
|
||||
userId: commonUser.id,
|
||||
eventTypeId: eventId,
|
||||
isFixed: true,
|
||||
},
|
||||
{
|
||||
userId: team1_teammate1.id,
|
||||
eventTypeId: eventId,
|
||||
isFixed: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// to test if booking is visible to team admin/owner even if he is not part of the booking
|
||||
await prisma.host.deleteMany({
|
||||
where: {
|
||||
userId: owner1.id,
|
||||
eventTypeId: eventId,
|
||||
},
|
||||
});
|
||||
|
||||
await bookTeamEvent(page, team1, eventType);
|
||||
|
||||
// booking should be visible for the team host even though he is not part of the booking
|
||||
await assertBookingVisibleFor(owner1, page, eventType, true);
|
||||
// booking should not be visible for other team host
|
||||
await assertBookingVisibleFor(owner2, page, eventType, false);
|
||||
// booking should be visible for commonUser as he is part of the booking
|
||||
await assertBookingVisibleFor(commonUser, page, eventType, true);
|
||||
// booking should be visible for team1_teammate1 as he is part of the booking
|
||||
await assertBookingVisibleFor(team1_teammate1, page, eventType, true);
|
||||
});
|
||||
test("round-robin eventType booking should be visible to team admin", async ({ page, users }) => {
|
||||
const { owner1, owner2, commonUser, team1_teammate1 } = await createTeams(users);
|
||||
|
||||
const { team: team1 } = await owner1.getFirstTeamMembership();
|
||||
const scenario = {
|
||||
schedulingType: SchedulingType.ROUND_ROBIN,
|
||||
teamEventTitle: `round-robin-team-event`,
|
||||
teamEventSlug: slugify(`round-robin-team-event-${randomString(5)}`),
|
||||
};
|
||||
|
||||
const eventType = await createTeamEventType(owner1, team1, scenario);
|
||||
const { id: eventId } = eventType;
|
||||
|
||||
await prisma.host.createMany({
|
||||
data: [
|
||||
{
|
||||
userId: commonUser.id,
|
||||
eventTypeId: eventId,
|
||||
},
|
||||
{
|
||||
userId: team1_teammate1.id,
|
||||
eventTypeId: eventId,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await prisma.host.deleteMany({
|
||||
where: {
|
||||
userId: owner1.id,
|
||||
eventTypeId: eventId,
|
||||
},
|
||||
});
|
||||
|
||||
await bookTeamEvent(page, team1, eventType);
|
||||
|
||||
// booking should be visible for the team host even though he is not part of the booking
|
||||
await assertBookingVisibleFor(owner1, page, eventType, true);
|
||||
// bookings should not be visible for other team host
|
||||
await assertBookingVisibleFor(owner2, page, eventType, false);
|
||||
});
|
||||
test("managed eventType booking should be visible to team admin", async ({ page, users }) => {
|
||||
const { owner1, owner2, commonUser, team1_teammate1 } = await createTeams(users);
|
||||
|
||||
const { team: team1 } = await owner1.getFirstTeamMembership();
|
||||
|
||||
await owner1.apiLogin();
|
||||
await page.goto(`/event-types?dialog=new&eventPage=team%2F${team1.slug}&teamId=${team1.id}`);
|
||||
await page.getByTestId("managed-event-type").click();
|
||||
await page.getByTestId("event-type-quick-chat").click();
|
||||
await page.getByTestId("event-type-quick-chat").fill("managed-event");
|
||||
await page.getByRole("button", { name: "Continue" }).click();
|
||||
await page.getByTestId("vertical-tab-assignment").click();
|
||||
|
||||
await page.getByRole("combobox", { name: "assignment-dropdown" }).fill("commonUser");
|
||||
await page.getByRole("combobox", { name: "assignment-dropdown" }).press("Enter");
|
||||
await page.getByRole("combobox", { name: "assignment-dropdown" }).fill("team1_teammate1");
|
||||
await page.getByRole("combobox", { name: "assignment-dropdown" }).press("Enter");
|
||||
await page.getByTestId("update-eventtype").click();
|
||||
|
||||
const eventType = await owner1.getFirstTeamEvent(team1.id);
|
||||
|
||||
await bookUserEvent(page, commonUser, eventType);
|
||||
|
||||
// booking should be visible for the team host even though he is not part of the booking
|
||||
await assertBookingVisibleFor(owner1, page, eventType, true);
|
||||
// booking should not be visible for other team host
|
||||
await assertBookingVisibleFor(owner2, page, eventType, false);
|
||||
// booking should be visible for commonUser as he is part of the booking
|
||||
await assertBookingVisibleFor(commonUser, page, eventType, true);
|
||||
// booking should not be visible for team1_teammate1 as we booked for commonUser
|
||||
await assertBookingVisibleFor(team1_teammate1, page, eventType, false);
|
||||
});
|
||||
test("individual team members booking should not be visible to team admin", async ({ page, users }) => {
|
||||
const { owner1, owner2, commonUser, team1_teammate1 } = await createTeams(users);
|
||||
|
||||
const eventType = await commonUser.getFirstEventAsOwner();
|
||||
await bookUserEvent(page, commonUser, eventType);
|
||||
// non-team bookings should not be visible for the team host
|
||||
await assertBookingVisibleFor(owner1, page, eventType, false);
|
||||
// non-team bookings should not be visible for other team host
|
||||
await assertBookingVisibleFor(owner2, page, eventType, false);
|
||||
// booking should be visible for commonUser as he is part of the booking
|
||||
await assertBookingVisibleFor(commonUser, page, eventType, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -248,75 +111,3 @@ async function createBooking({
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const createTeams = async (userFixture: Fixtures["users"]) => {
|
||||
const owner1 = await userFixture.create({ name: "team-owner-1" });
|
||||
const owner2 = await userFixture.create({ name: "team-owner-2" });
|
||||
const commonUser = await userFixture.create({ name: "commonUser" });
|
||||
const team1_teammate1 = await userFixture.create({ name: "team1_teammate1" });
|
||||
const team2_teammate1 = await userFixture.create({ name: "team2_teammate1" });
|
||||
const teamOne = await prisma.team.create({
|
||||
data: {
|
||||
name: "bookings-test-team-1",
|
||||
slug: slugify(`bookings-test-team-2-${randomString(5)}`),
|
||||
},
|
||||
});
|
||||
|
||||
const teamTwo = await prisma.team.create({
|
||||
data: {
|
||||
name: "bookings-test-team-2",
|
||||
slug: slugify(`bookings-test-team-2-${randomString(5)}`),
|
||||
},
|
||||
});
|
||||
|
||||
// create memberships
|
||||
await prisma.membership.createMany({
|
||||
data: [
|
||||
{
|
||||
userId: owner1.id,
|
||||
teamId: teamOne.id,
|
||||
accepted: true,
|
||||
role: "OWNER",
|
||||
},
|
||||
{
|
||||
userId: commonUser.id,
|
||||
teamId: teamOne.id,
|
||||
accepted: true,
|
||||
role: "MEMBER",
|
||||
},
|
||||
{
|
||||
userId: team1_teammate1.id,
|
||||
teamId: teamOne.id,
|
||||
accepted: true,
|
||||
role: "MEMBER",
|
||||
},
|
||||
{
|
||||
userId: owner2.id,
|
||||
teamId: teamTwo.id,
|
||||
accepted: true,
|
||||
role: "OWNER",
|
||||
},
|
||||
{
|
||||
userId: commonUser.id,
|
||||
teamId: teamTwo.id,
|
||||
accepted: true,
|
||||
role: "MEMBER",
|
||||
},
|
||||
{
|
||||
userId: team2_teammate1.id,
|
||||
teamId: teamTwo.id,
|
||||
accepted: true,
|
||||
role: "MEMBER",
|
||||
},
|
||||
],
|
||||
});
|
||||
return {
|
||||
owner1,
|
||||
owner2,
|
||||
commonUser,
|
||||
team1_teammate1,
|
||||
team2_teammate1,
|
||||
teamOne,
|
||||
teamTwo,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -58,7 +58,7 @@ const createTeamWorkflow = async (user: { id: number }, team: { id: number }) =>
|
||||
});
|
||||
};
|
||||
|
||||
export const createTeamEventType = async (
|
||||
const createTeamEventType = async (
|
||||
user: { id: number },
|
||||
team: { id: number },
|
||||
scenario?: {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { Frame, Page } from "@playwright/test";
|
||||
import type { APIResponse } from "@playwright/test";
|
||||
import { expect } from "@playwright/test";
|
||||
import { createHash } from "crypto";
|
||||
import EventEmitter from "events";
|
||||
@@ -372,53 +371,3 @@ export async function doOnOrgDomain(
|
||||
// When App directory is there, this is the 404 page text. We should work on fixing the 404 page as it changed due to app directory.
|
||||
export const NotFoundPageTextAppDir = "This page does not exist.";
|
||||
// export const NotFoundPageText = "ERROR 404";
|
||||
|
||||
export type Team = {
|
||||
id: number;
|
||||
slug: string | null;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type EventType = {
|
||||
id: number;
|
||||
title: string;
|
||||
slug: string;
|
||||
};
|
||||
export type User = {
|
||||
id: number;
|
||||
name: string | null;
|
||||
username: string | null;
|
||||
};
|
||||
|
||||
export async function bookTeamEvent(page: Page, team: Team, eventType: EventType) {
|
||||
await page.goto(`/team/${team.slug}/${eventType.slug}/`);
|
||||
await bookEventOnThisPage(page);
|
||||
const bookingTitle = `${eventType.title} between ${team.name} and ${testName}`;
|
||||
await assertBookingIsCorrect(page, bookingTitle);
|
||||
}
|
||||
export async function bookUserEvent(page: Page, user: User, eventType: EventType) {
|
||||
await page.goto(`/${user.username}/${eventType.slug}/`);
|
||||
await bookEventOnThisPage(page);
|
||||
const bookingTitle = `${eventType.title} between ${user.name} and ${testName}`;
|
||||
await assertBookingIsCorrect(page, bookingTitle);
|
||||
}
|
||||
|
||||
export async function assertBookingIsCorrect(page: Page, bookingTitle: string) {
|
||||
await expect(page.locator("[data-testid=booking-title]")).toHaveText(bookingTitle);
|
||||
// The booker should be in the attendee list
|
||||
await expect(page.locator(`[data-testid="attendee-name-${testName}"]`)).toHaveText(testName);
|
||||
}
|
||||
|
||||
export const assertBookingVisibleFor = async (
|
||||
user: { apiLogin: () => Promise<APIResponse> },
|
||||
pageFixture: Fixtures["page"],
|
||||
eventType: EventType,
|
||||
shouldBeVisible: boolean
|
||||
) => {
|
||||
await user.apiLogin();
|
||||
await pageFixture.goto(`/bookings/upcoming`);
|
||||
const upcomingBookingList = pageFixture.locator('[data-testid="booking-item"]');
|
||||
shouldBeVisible
|
||||
? await expect(upcomingBookingList.locator(`text=${eventType.title}`)).toBeVisible()
|
||||
: await expect(upcomingBookingList.locator(`text=${eventType.title}`)).toBeHidden();
|
||||
};
|
||||
|
||||
@@ -1,391 +0,0 @@
|
||||
import type { Page } from "@playwright/test";
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { randomString } from "@calcom/lib/random";
|
||||
import slugify from "@calcom/lib/slugify";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { MembershipRole, SchedulingType } from "@calcom/prisma/enums";
|
||||
|
||||
import { createTeamEventType } from "../fixtures/users";
|
||||
import { test } from "../lib/fixtures";
|
||||
import type { Fixtures } from "../lib/fixtures";
|
||||
import {
|
||||
assertBookingVisibleFor,
|
||||
bookTimeSlot,
|
||||
doOnOrgDomain,
|
||||
NotFoundPageTextAppDir,
|
||||
selectFirstAvailableTimeSlotNextMonth,
|
||||
testName,
|
||||
} from "../lib/testUtils";
|
||||
|
||||
test.describe("Bookings list for organizations", () => {
|
||||
test.afterEach(({ orgs, users }) => {
|
||||
orgs.deleteAll();
|
||||
users.deleteAll();
|
||||
});
|
||||
|
||||
test.describe("Team Event", () => {
|
||||
test("Collective EventType booking list", async ({ page, users, orgs }) => {
|
||||
const { org, orgOwner, orgAdmin, team1owner, team2owner, commonUser, team1_teammate1 } =
|
||||
await createOrg(orgs, users);
|
||||
|
||||
const { team: team1 } = await team1owner.getFirstTeamMembership();
|
||||
const scenario = {
|
||||
schedulingType: SchedulingType.COLLECTIVE,
|
||||
teamEventTitle: `collective-team-event`,
|
||||
teamEventSlug: slugify(`collective-team-event-${randomString(5)}`),
|
||||
};
|
||||
const eventType = await createTeamEventType(team1owner, team1, scenario);
|
||||
const { id: eventId, slug: evnetSlug } = eventType;
|
||||
|
||||
await prisma.host.createMany({
|
||||
data: [
|
||||
{
|
||||
userId: commonUser.id,
|
||||
eventTypeId: eventId,
|
||||
isFixed: true,
|
||||
},
|
||||
{
|
||||
userId: team1_teammate1.id,
|
||||
eventTypeId: eventId,
|
||||
isFixed: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// to test if booking is visible to team admin/owner even if he is not part of the booking
|
||||
await prisma.host.deleteMany({
|
||||
where: {
|
||||
userId: team1owner.id,
|
||||
eventTypeId: eventId,
|
||||
},
|
||||
});
|
||||
|
||||
await expectPageToBeNotFound({ page, url: `/team/${team1.slug}/${eventType.slug}` });
|
||||
await doOnOrgDomain(
|
||||
{
|
||||
orgSlug: org.slug,
|
||||
page,
|
||||
},
|
||||
async () => {
|
||||
await bookTeamEvent({ page, team: team1, event: eventType });
|
||||
}
|
||||
);
|
||||
// booking should be visible for the Org OWNER even though he is not part of the booking
|
||||
await assertBookingVisibleFor(orgOwner, page, eventType, true);
|
||||
// booking should be visible for the Org ADMIN even though he is not part of the booking
|
||||
await assertBookingVisibleFor(orgAdmin, page, eventType, true);
|
||||
// booking should be visible for the team host even though he is not part of the booking
|
||||
await assertBookingVisibleFor(team1owner, page, eventType, true);
|
||||
// booking should not be visible for other team host, because its a team1 team-event
|
||||
await assertBookingVisibleFor(team2owner, page, eventType, false);
|
||||
// booking should be visible for commonUser as he is part of the booking
|
||||
await assertBookingVisibleFor(commonUser, page, eventType, true);
|
||||
// booking should be visible for team1_teammate1 as he is part of the booking
|
||||
await assertBookingVisibleFor(team1_teammate1, page, eventType, true);
|
||||
});
|
||||
|
||||
test("Round Robin EventType booking list", async ({ page, users, orgs }) => {
|
||||
const { org, orgOwner, orgAdmin, team1owner, team2owner, commonUser, team1_teammate1 } =
|
||||
await createOrg(orgs, users);
|
||||
|
||||
const { team: team1 } = await team1owner.getFirstTeamMembership();
|
||||
const scenario = {
|
||||
schedulingType: SchedulingType.ROUND_ROBIN,
|
||||
teamEventTitle: `round-robin-team-event`,
|
||||
teamEventSlug: slugify(`round-robin-team-event-${randomString(5)}`),
|
||||
};
|
||||
|
||||
const eventType = await createTeamEventType(team1owner, team1, scenario);
|
||||
const { id: eventId } = eventType;
|
||||
await prisma.host.createMany({
|
||||
data: [
|
||||
{
|
||||
userId: commonUser.id,
|
||||
eventTypeId: eventId,
|
||||
},
|
||||
{
|
||||
userId: team1_teammate1.id,
|
||||
eventTypeId: eventId,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await prisma.host.deleteMany({
|
||||
where: {
|
||||
userId: team1owner.id,
|
||||
eventTypeId: eventId,
|
||||
},
|
||||
});
|
||||
|
||||
await expectPageToBeNotFound({ page, url: `/team/${team1.slug}/${eventType.slug}` });
|
||||
|
||||
await doOnOrgDomain(
|
||||
{
|
||||
orgSlug: org.slug,
|
||||
page,
|
||||
},
|
||||
async () => {
|
||||
await bookTeamEvent({ page, team: team1, event: eventType });
|
||||
}
|
||||
);
|
||||
// booking should be visible for the Org OWNER even though he is not part of the booking
|
||||
await assertBookingVisibleFor(orgOwner, page, eventType, true);
|
||||
// booking should be visible for the Org ADMIN even though he is not part of the booking
|
||||
await assertBookingVisibleFor(orgAdmin, page, eventType, true);
|
||||
// booking should be visible for the team host even though he is not part of the booking
|
||||
await assertBookingVisibleFor(team1owner, page, eventType, true);
|
||||
// booking should not be visible for other team host
|
||||
await assertBookingVisibleFor(team2owner, page, eventType, false);
|
||||
});
|
||||
|
||||
test("Managed EventType booking list", async ({ page, users, orgs }) => {
|
||||
const { org, orgOwner, orgAdmin, team1owner, team2owner, commonUser, team1_teammate1 } =
|
||||
await createOrg(orgs, users);
|
||||
|
||||
const { team: team1 } = await team1owner.getFirstTeamMembership();
|
||||
|
||||
await team1owner.apiLogin();
|
||||
await page.goto(`/event-types?dialog=new&eventPage=team%2F${team1.slug}&teamId=${team1.id}`);
|
||||
await page.getByTestId("managed-event-type").click();
|
||||
await page.getByTestId("event-type-quick-chat").click();
|
||||
await page.getByTestId("event-type-quick-chat").fill("managed-event");
|
||||
await page.getByRole("button", { name: "Continue" }).click();
|
||||
await page.getByTestId("vertical-tab-assignment").click();
|
||||
|
||||
await page.getByRole("combobox", { name: "assignment-dropdown" }).fill("commonUser");
|
||||
await page.getByRole("combobox", { name: "assignment-dropdown" }).press("Enter");
|
||||
await page.getByRole("combobox", { name: "assignment-dropdown" }).fill("team1_teammate1");
|
||||
await page.getByRole("combobox", { name: "assignment-dropdown" }).press("Enter");
|
||||
await page.getByTestId("update-eventtype").click();
|
||||
|
||||
const eventType = await team1owner.getFirstTeamEvent(team1.id);
|
||||
|
||||
await expectPageToBeNotFound({ page, url: `/${commonUser.username}/${eventType.slug}` });
|
||||
|
||||
await doOnOrgDomain(
|
||||
{
|
||||
orgSlug: org.slug,
|
||||
page,
|
||||
},
|
||||
async () => {
|
||||
await bookUserEvent({ page, user: commonUser, event: eventType });
|
||||
}
|
||||
);
|
||||
|
||||
// booking should be visible for the Org OWNER even though he is not part of the booking
|
||||
await assertBookingVisibleFor(orgOwner, page, eventType, true);
|
||||
// booking should be visible for the Org ADMIN even though he is not part of the booking
|
||||
await assertBookingVisibleFor(orgAdmin, page, eventType, true);
|
||||
// booking should be visible for the team host even though he is not part of the booking
|
||||
await assertBookingVisibleFor(team1owner, page, eventType, true);
|
||||
// booking should not be visible for other team host
|
||||
await assertBookingVisibleFor(team2owner, page, eventType, false);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("User Event", () => {
|
||||
test("User personal booking list", async ({ page, users, orgs }) => {
|
||||
const { org, orgOwner, orgAdmin, team1owner, team2owner, commonUser, team1_teammate1 } =
|
||||
await createOrg(orgs, users);
|
||||
|
||||
const event = await commonUser.getFirstEventAsOwner();
|
||||
|
||||
await page.goto(`/${commonUser.username}/${event.slug}`);
|
||||
// Shouldn't be servable on the non-org domain
|
||||
await expect(page.locator(`text=${NotFoundPageTextAppDir}`)).toBeVisible();
|
||||
|
||||
await doOnOrgDomain(
|
||||
{
|
||||
orgSlug: org.slug,
|
||||
page,
|
||||
},
|
||||
async () => {
|
||||
await bookUserEvent({ page, user: commonUser, event });
|
||||
}
|
||||
);
|
||||
// booking should be visible for the Org OWNER
|
||||
await assertBookingVisibleFor(orgOwner, page, event, true);
|
||||
// booking should be visible for the Org ADMIN
|
||||
await assertBookingVisibleFor(orgAdmin, page, event, true);
|
||||
// booking should be visible for the team host
|
||||
await assertBookingVisibleFor(team1owner, page, event, true);
|
||||
// booking should be visible for other team host because commonUser is part of both teams
|
||||
await assertBookingVisibleFor(team2owner, page, event, true);
|
||||
// booking should be visible for commonUser as he is part of the booking
|
||||
await assertBookingVisibleFor(commonUser, page, event, true);
|
||||
// booking should not be visible for team1_teammate1
|
||||
await assertBookingVisibleFor(team1_teammate1, page, event, false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function bookUserEvent({
|
||||
page,
|
||||
user,
|
||||
event,
|
||||
}: {
|
||||
page: Page;
|
||||
user: {
|
||||
username: string | null;
|
||||
name: string | null;
|
||||
};
|
||||
event: { slug: string; title: string };
|
||||
}) {
|
||||
await page.goto(`/${user.username}/${event.slug}`);
|
||||
|
||||
await selectFirstAvailableTimeSlotNextMonth(page);
|
||||
await bookTimeSlot(page);
|
||||
await expect(page.getByTestId("success-page")).toBeVisible();
|
||||
|
||||
// The title of the booking
|
||||
const BookingTitle = `${event.title} between ${user.name} and ${testName}`;
|
||||
await expect(page.getByTestId("booking-title")).toHaveText(BookingTitle);
|
||||
// The booker should be in the attendee list
|
||||
await expect(page.getByTestId(`attendee-name-${testName}`)).toHaveText(testName);
|
||||
}
|
||||
|
||||
async function bookTeamEvent({
|
||||
page,
|
||||
team,
|
||||
event,
|
||||
}: {
|
||||
page: Page;
|
||||
team: {
|
||||
slug: string | null;
|
||||
name: string | null;
|
||||
};
|
||||
event: { slug: string; title: string };
|
||||
}) {
|
||||
// Note that even though the default way to access a team booking in an organization is to not use /team in the URL, but it isn't testable with playwright as the rewrite is taken care of by Next.js config which can't handle on the fly org slug's handling
|
||||
// So, we are using /team in the URL to access the team booking
|
||||
// There are separate tests to verify that the next.config.js rewrites are working
|
||||
// Also there are additional checkly tests that verify absolute e2e flow. They are in __checks__/organization.spec.ts
|
||||
await page.goto(`/team/${team.slug}/${event.slug}`);
|
||||
|
||||
await selectFirstAvailableTimeSlotNextMonth(page);
|
||||
await bookTimeSlot(page);
|
||||
await expect(page.getByTestId("success-page")).toBeVisible();
|
||||
|
||||
// The title of the booking
|
||||
const BookingTitle = `${event.title} between ${team.name} and ${testName}`;
|
||||
await expect(page.getByTestId("booking-title")).toHaveText(BookingTitle);
|
||||
// The booker should be in the attendee list
|
||||
await expect(page.getByTestId(`attendee-name-${testName}`)).toHaveText(testName);
|
||||
}
|
||||
|
||||
async function expectPageToBeNotFound({ page, url }: { page: Page; url: string }) {
|
||||
await page.goto(`${url}`);
|
||||
await expect(page.locator(`text=${NotFoundPageTextAppDir}`)).toBeVisible();
|
||||
}
|
||||
|
||||
const createOrg = async (orgs: Fixtures["orgs"], userFixture: Fixtures["users"]) => {
|
||||
const org = await orgs.create({
|
||||
name: "TestOrg",
|
||||
});
|
||||
const orgOwner = await userFixture.create({
|
||||
name: "org-owner",
|
||||
organizationId: org.id,
|
||||
roleInOrganization: MembershipRole.OWNER,
|
||||
});
|
||||
|
||||
const orgAdmin = await userFixture.create({
|
||||
name: "org-admin",
|
||||
organizationId: org.id,
|
||||
roleInOrganization: MembershipRole.ADMIN,
|
||||
});
|
||||
const team1owner = await userFixture.create({
|
||||
name: "team-owner-1",
|
||||
organizationId: org.id,
|
||||
roleInOrganization: MembershipRole.MEMBER,
|
||||
});
|
||||
const team2owner = await userFixture.create({
|
||||
name: "team-owner-2",
|
||||
organizationId: org.id,
|
||||
roleInOrganization: MembershipRole.MEMBER,
|
||||
});
|
||||
const commonUser = await userFixture.create({
|
||||
name: "commonUser",
|
||||
organizationId: org.id,
|
||||
roleInOrganization: MembershipRole.MEMBER,
|
||||
});
|
||||
const team1_teammate1 = await userFixture.create({
|
||||
name: "team1_teammate1",
|
||||
organizationId: org.id,
|
||||
roleInOrganization: MembershipRole.MEMBER,
|
||||
});
|
||||
const team2_teammate1 = await userFixture.create({
|
||||
name: "team2_teammate1",
|
||||
organizationId: org.id,
|
||||
roleInOrganization: MembershipRole.MEMBER,
|
||||
});
|
||||
const teamOne = await prisma.team.create({
|
||||
data: {
|
||||
name: "bookings-test-team-1",
|
||||
slug: slugify(`bookings-test-team-2-${randomString(5)}`),
|
||||
parentId: org.id,
|
||||
},
|
||||
});
|
||||
|
||||
const teamTwo = await prisma.team.create({
|
||||
data: {
|
||||
name: "bookings-test-team-2",
|
||||
slug: slugify(`bookings-test-team-2-${randomString(5)}`),
|
||||
parentId: org.id,
|
||||
},
|
||||
});
|
||||
|
||||
// create memberships
|
||||
await prisma.membership.createMany({
|
||||
data: [
|
||||
{
|
||||
userId: team1owner.id,
|
||||
teamId: teamOne.id,
|
||||
accepted: true,
|
||||
role: "OWNER",
|
||||
},
|
||||
{
|
||||
userId: commonUser.id,
|
||||
teamId: teamOne.id,
|
||||
accepted: true,
|
||||
role: "MEMBER",
|
||||
},
|
||||
{
|
||||
userId: team1_teammate1.id,
|
||||
teamId: teamOne.id,
|
||||
accepted: true,
|
||||
role: "MEMBER",
|
||||
},
|
||||
{
|
||||
userId: team2owner.id,
|
||||
teamId: teamTwo.id,
|
||||
accepted: true,
|
||||
role: "OWNER",
|
||||
},
|
||||
{
|
||||
userId: commonUser.id,
|
||||
teamId: teamTwo.id,
|
||||
accepted: true,
|
||||
role: "MEMBER",
|
||||
},
|
||||
{
|
||||
userId: team2_teammate1.id,
|
||||
teamId: teamTwo.id,
|
||||
accepted: true,
|
||||
role: "MEMBER",
|
||||
},
|
||||
],
|
||||
});
|
||||
return {
|
||||
org,
|
||||
orgOwner,
|
||||
orgAdmin,
|
||||
team1owner,
|
||||
team2owner,
|
||||
commonUser,
|
||||
team1_teammate1,
|
||||
team2_teammate1,
|
||||
teamOne,
|
||||
teamTwo,
|
||||
};
|
||||
};
|
||||
@@ -40,12 +40,15 @@ export const getHandler = async ({ ctx, input }: GetOptions) => {
|
||||
};
|
||||
};
|
||||
|
||||
const getUniqueBookings = <T extends { id: number }>(arr: T[]) => {
|
||||
const unique = new Set<number>();
|
||||
arr.forEach((booking) => {
|
||||
unique.add(booking.id);
|
||||
const set = new Set();
|
||||
const getUniqueBookings = <T extends { uid: string }>(arr: T[]) => {
|
||||
const unique = arr.filter((booking) => {
|
||||
const duplicate = set.has(booking.uid);
|
||||
set.add(booking.uid);
|
||||
return !duplicate;
|
||||
});
|
||||
return Array.from(unique.values());
|
||||
set.clear();
|
||||
return unique;
|
||||
};
|
||||
|
||||
export async function getBookings({
|
||||
@@ -125,6 +128,7 @@ export async function getBookings({
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const filtersCombined: Prisma.BookingWhereInput[] = !filters
|
||||
? []
|
||||
: Object.keys(filters)
|
||||
@@ -192,171 +196,15 @@ export async function getBookings({
|
||||
},
|
||||
};
|
||||
|
||||
const userBookingsFilter: Prisma.BookingWhereInput = {
|
||||
userId: user.id,
|
||||
};
|
||||
const userAttendingBookingsFilter: Prisma.BookingWhereInput = {
|
||||
attendees: {
|
||||
some: {
|
||||
email: user.email,
|
||||
},
|
||||
},
|
||||
};
|
||||
// all (collective + round-robin) team bookings should be visible to team-owner and team-admins
|
||||
const teamBookingsFilter: Prisma.BookingWhereInput = {
|
||||
eventType: {
|
||||
team: {
|
||||
members: {
|
||||
some: {
|
||||
userId: user.id,
|
||||
role: {
|
||||
in: ["ADMIN", "OWNER"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
// all (collective + round-robin) team bookings should be visible to org-owner and org-admins
|
||||
const organizationBookingsFilter: Prisma.BookingWhereInput = {
|
||||
eventType: {
|
||||
team: {
|
||||
parent: {
|
||||
members: {
|
||||
some: {
|
||||
userId: user.id,
|
||||
role: {
|
||||
in: ["ADMIN", "OWNER"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// all managed team bookings should be visible to team-owner and team-admins
|
||||
const teamManagedBookingsFilter: Prisma.BookingWhereInput = {
|
||||
eventType: {
|
||||
parent: {
|
||||
team: {
|
||||
members: {
|
||||
some: {
|
||||
userId: user.id,
|
||||
role: {
|
||||
in: ["ADMIN", "OWNER"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// all managed team bookings should be visible to org-owner and org-admins
|
||||
const organizationManagedBookingsFilter: Prisma.BookingWhereInput = {
|
||||
eventType: {
|
||||
parent: {
|
||||
team: {
|
||||
parent: {
|
||||
members: {
|
||||
some: {
|
||||
userId: user.id,
|
||||
role: {
|
||||
in: ["ADMIN", "OWNER"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// show personal booking to org-owner and org-admins
|
||||
const organizationPersonalBookingsFilter: Prisma.BookingWhereInput = {
|
||||
AND: [
|
||||
{
|
||||
user: {
|
||||
teams: {
|
||||
some: {
|
||||
team: {
|
||||
parentId: { gt: 0 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
user: {
|
||||
teams: {
|
||||
some: {
|
||||
team: {
|
||||
parent: {
|
||||
members: {
|
||||
some: {
|
||||
userId: user.id,
|
||||
role: {
|
||||
in: ["ADMIN", "OWNER"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
user: {
|
||||
teams: {
|
||||
some: {
|
||||
team: {
|
||||
members: {
|
||||
some: {
|
||||
userId: user.id,
|
||||
role: {
|
||||
in: ["ADMIN", "OWNER"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// filter out all (collective + round-robin) event bookings
|
||||
{
|
||||
eventType: {
|
||||
teamId: null,
|
||||
},
|
||||
},
|
||||
// filter out all (managed) event bookings
|
||||
{
|
||||
eventType: {
|
||||
parentId: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const seatsReferenceFilter: Prisma.BookingWhereInput = {
|
||||
seatsReferences: {
|
||||
some: {
|
||||
attendee: {
|
||||
email: user.email,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const [
|
||||
// Quering these in parallel to save time.
|
||||
// Note that because we are applying `take` to individual queries, we will usually get more bookings then we need. It is okay to have more bookings faster than having what we need slower
|
||||
bookingsQueryUserId,
|
||||
bookingsQueryAttendees,
|
||||
bookingsQueryTeamMember,
|
||||
bookingsQuerySeatReference,
|
||||
//////////////////////////
|
||||
|
||||
recurringInfoBasic,
|
||||
recurringInfoExtended,
|
||||
// We need all promises to be successful, so we are not using Promise.allSettled
|
||||
@@ -364,19 +212,73 @@ export async function getBookings({
|
||||
prisma.booking.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
userBookingsFilter,
|
||||
userAttendingBookingsFilter,
|
||||
teamBookingsFilter,
|
||||
organizationBookingsFilter,
|
||||
teamManagedBookingsFilter,
|
||||
organizationManagedBookingsFilter,
|
||||
organizationPersonalBookingsFilter,
|
||||
seatsReferenceFilter,
|
||||
{
|
||||
userId: user.id,
|
||||
},
|
||||
],
|
||||
AND: [passedBookingsStatusFilter, ...filtersCombined],
|
||||
},
|
||||
orderBy,
|
||||
take: take + 1,
|
||||
skip,
|
||||
}),
|
||||
prisma.booking.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{
|
||||
attendees: {
|
||||
some: {
|
||||
email: user.email,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
AND: [passedBookingsStatusFilter, ...filtersCombined],
|
||||
},
|
||||
orderBy,
|
||||
take: take + 1,
|
||||
skip,
|
||||
}),
|
||||
prisma.booking.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{
|
||||
eventType: {
|
||||
team: {
|
||||
members: {
|
||||
some: {
|
||||
userId: user.id,
|
||||
role: {
|
||||
in: ["ADMIN", "OWNER"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
AND: [passedBookingsStatusFilter, ...filtersCombined],
|
||||
},
|
||||
orderBy,
|
||||
take: take + 1,
|
||||
skip,
|
||||
}),
|
||||
prisma.booking.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{
|
||||
seatsReferences: {
|
||||
some: {
|
||||
attendee: {
|
||||
email: user.email,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
AND: [passedBookingsStatusFilter, ...filtersCombined],
|
||||
},
|
||||
orderBy,
|
||||
select: { id: true },
|
||||
take: take + 1,
|
||||
skip,
|
||||
}),
|
||||
@@ -443,6 +345,9 @@ export async function getBookings({
|
||||
const plainBookings = getUniqueBookings(
|
||||
// It's going to mess up the orderBy as we are concatenating independent queries results
|
||||
bookingsQueryUserId
|
||||
.concat(bookingsQueryAttendees)
|
||||
.concat(bookingsQueryTeamMember)
|
||||
.concat(bookingsQuerySeatReference)
|
||||
);
|
||||
|
||||
// Now enrich bookings with relation data. We could have queried the relation data along with the bookings, but that would cause unnecessary queries to the database.
|
||||
@@ -451,7 +356,7 @@ export async function getBookings({
|
||||
await prisma.booking.findMany({
|
||||
where: {
|
||||
id: {
|
||||
in: plainBookings,
|
||||
in: plainBookings.map((booking) => booking.id),
|
||||
},
|
||||
},
|
||||
select: bookingSelect,
|
||||
|
||||
Reference in New Issue
Block a user