fix: view team bookings (#13591)
* fix: view team bookings * fix: edge case- common user across 2 different teams * @SomayChauhan fix: edge case- common user across 2 different teams v2.0 * feat: added booking list test for collective event-types * feat: added booking list test for round-robin event-types * feat: added test for managed event types bookings * feat: added test for individual events bookings * refactor code * added comments * added isFixed to collective event hosts * better function naming * code refactor * fix: assertBookingVisibleFor ignoring today's meetings * fix: handling case for organizations * fix: non org bookings-list test * feat: added tests for organisations booking list * added comments for better clarity * booking-list in org tests * uncommented doOnOrgDomain * removed extra selectors * fix: failing tests --------- Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
This commit is contained in:
1 parent
ae2446f42f
commit
4585199b5e
5 files changed
+790
-4
No files matched your search
@@ -1,9 +1,15 @@
|
||||
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());
|
||||
|
||||
@@ -61,6 +67,137 @@ 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -111,3 +248,75 @@ 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 }) =>
|
||||
});
|
||||
};
|
||||
|
||||
const createTeamEventType = async (
|
||||
export const createTeamEventType = async (
|
||||
user: { id: number },
|
||||
team: { id: number },
|
||||
scenario?: {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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";
|
||||
@@ -369,3 +370,53 @@ 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();
|
||||
};
|
||||
@@ -0,0 +1,391 @@
|
||||
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,
|
||||
};
|
||||
};
|
||||
@@ -189,7 +189,6 @@ async function getBookings({
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const filtersCombined: Prisma.BookingWhereInput[] = !filters
|
||||
? []
|
||||
: Object.keys(filters)
|
||||
@@ -262,7 +261,8 @@ async function getBookings({
|
||||
// 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,
|
||||
bookingsQueryTeamEvents,
|
||||
bookingsQueryTeamUsersPersonalEvents,
|
||||
bookingsQuerySeatReference,
|
||||
//////////////////////////
|
||||
|
||||
@@ -300,9 +300,28 @@ async function getBookings({
|
||||
take: take + 1,
|
||||
skip,
|
||||
}),
|
||||
// get team bookings (managed + collective + round-robin)
|
||||
prisma.booking.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
// all (collective + round-robin) team bookings should be visible to org-owner and org-admins
|
||||
{
|
||||
eventType: {
|
||||
team: {
|
||||
parent: {
|
||||
members: {
|
||||
some: {
|
||||
userId: user.id,
|
||||
role: {
|
||||
in: ["ADMIN", "OWNER"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// all (collective + round-robin) team bookings should be visible to team-owner and team-admins
|
||||
{
|
||||
eventType: {
|
||||
team: {
|
||||
@@ -317,6 +336,43 @@ async function getBookings({
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// all managed team bookings should be visible to team-owner and team-admins
|
||||
{
|
||||
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
|
||||
{
|
||||
eventType: {
|
||||
parent: {
|
||||
team: {
|
||||
parent: {
|
||||
members: {
|
||||
some: {
|
||||
userId: user.id,
|
||||
role: {
|
||||
in: ["ADMIN", "OWNER"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
AND: [passedBookingsStatusFilter, ...filtersCombined],
|
||||
},
|
||||
@@ -324,6 +380,84 @@ async function getBookings({
|
||||
take: take + 1,
|
||||
skip,
|
||||
}),
|
||||
// only get personal bookings
|
||||
prisma.booking.findMany({
|
||||
where: {
|
||||
AND: [
|
||||
// only show personal booking to org-owner and org-admins
|
||||
{
|
||||
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,
|
||||
},
|
||||
},
|
||||
passedBookingsStatusFilter,
|
||||
...filtersCombined,
|
||||
],
|
||||
},
|
||||
orderBy,
|
||||
take: take + 1,
|
||||
skip,
|
||||
}),
|
||||
prisma.booking.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
@@ -407,7 +541,8 @@ async function getBookings({
|
||||
// It's going to mess up the orderBy as we are concatenating independent queries results
|
||||
bookingsQueryUserId
|
||||
.concat(bookingsQueryAttendees)
|
||||
.concat(bookingsQueryTeamMember)
|
||||
.concat(bookingsQueryTeamEvents)
|
||||
.concat(bookingsQueryTeamUsersPersonalEvents)
|
||||
.concat(bookingsQuerySeatReference)
|
||||
);
|
||||
|
||||
|
||||
Reference in new issue
Block a user