fix: Prevent invalid host assignment (#9441)

* Avoids invalid hosts

* Fixed test selector, instead using react-select-2-listbox

* Different way of selecting the managed user

* Convert select-option- to value instead of label

* Replaced all instances of networkidle

* done rewriting test

* Fix prisma is not defined error

* Depend on inner text rather than testid

* Fill of team create is unstable

* Removed console.log

---------

Co-authored-by: Efraín Rochín <roae.85@gmail.com>
This commit is contained in:
Alex van Andel
2023-06-10 02:39:49 +00:00
committed by GitHub
co-authored by Efraín Rochín
parent d9a7952467
commit bb31c4ba14
4 changed files with 85 additions and 71 deletions
+30 -36
View File
@@ -12,33 +12,28 @@ test.describe("Managed Event Types tests", () => {
const memberUser = await users.create();
// First we work with owner user, logging in
await adminUser.apiLogin();
await page.goto("/event-types");
// Making sure page loads completely
await page.waitForLoadState("networkidle");
// Let's create a team
await page.goto("/teams");
await page.goto("/settings/teams/new");
await test.step("Managed event option exists for team admin", async () => {
// Proceed to create a team
await page.locator("text=Create Team").click();
await page.waitForURL("/settings/teams/new");
// Filling team creation form wizard
await page.locator('input[name="name"]').waitFor();
await page.locator('input[name="name"]').fill(`${adminUser.username}'s Team`);
await page.locator("text=Continue").click();
await page.waitForURL(/\/settings\/teams\/(\d+)\/onboard-members$/i);
await page.locator('[data-testid="new-member-button"]').click();
await page.getByTestId("new-member-button").click();
await page.locator('[placeholder="email\\@example\\.com"]').fill(`${memberUser.username}@example.com`);
await page.locator('[data-testid="invite-new-member-button"]').click();
await page.waitForLoadState("networkidle");
await page.getByTestId("invite-new-member-button").click();
// wait for the second member to be added to the pending-member-list.
await page.getByTestId("pending-member-list").locator("li:nth-child(2)").waitFor();
// and publish
await page.locator("text=Publish team").click();
await page.waitForLoadState("networkidle");
await page.waitForURL("/settings/teams/**");
// Going to create an event type
await page.goto("/event-types");
await page.waitForLoadState("networkidle");
await page.click("[data-testid=new-event-type-dropdown]");
await page.click("[data-testid=option-team-1]");
await page.getByTestId("new-event-type-dropdown").click();
await page.getByTestId("option-team-1").click();
// Expecting we can add a managed event type as team owner
await expect(page.locator('button[value="MANAGED"]')).toBeVisible();
@@ -46,51 +41,50 @@ test.describe("Managed Event Types tests", () => {
await page.click('button[value="MANAGED"]');
await page.fill("[name=title]", "managed");
await page.click("[type=submit]");
await page.waitForURL("event-types/**");
});
await test.step("Managed event type has unlocked fields for admin", async () => {
await page.waitForSelector('[data-testid="update-eventtype"]');
await page.getByTestId("update-eventtype").waitFor();
await expect(page.locator('input[name="title"]')).toBeEditable();
await expect(page.locator('input[name="slug"]')).toBeEditable();
await expect(page.locator('input[name="length"]')).toBeEditable();
await adminUser.logout();
});
await test.step("Managed event type exists for added member", async () => {
// Now we need to accept the invitation as member and come back in as admin to
// assign the member in the managed event type
await adminUser.logout();
await memberUser.apiLogin();
await page.goto("/event-types");
// We wait until loading is finished
await page.waitForSelector('[data-testid="event-types"]');
await page.goto("/teams");
await page.waitForLoadState("networkidle");
await page.locator('button[data-testid^="accept-invitation"]').click();
await page.waitForLoadState("networkidle");
await page.getByText("Member").waitFor();
await memberUser.logout();
// Coming back as team owner to assign member user to managed event
await adminUser.apiLogin();
await page.goto("/event-types");
await page.waitForLoadState("networkidle");
await page.locator('[data-testid="event-types"] a[title="managed"]').click();
await page.locator('[data-testid="vertical-tab-assignment"]').click();
await page.getByTestId("event-types").locator('a[title="managed"]').click();
await page.getByTestId("vertical-tab-assignment").click();
await page.locator('[class$="control"]').filter({ hasText: "Select..." }).click();
await page.locator("#react-select-5-option-1").click();
await page.getByTestId(`select-option-${memberUser.id}`).click();
await page.locator('[type="submit"]').click();
await page.waitForLoadState("networkidle");
await adminUser.logout();
await page.getByTestId("toast-success").waitFor();
// Coming back as member user to see if there is a managed event present after assignment
await memberUser.apiLogin();
await page.goto("/event-types");
await page.waitForLoadState("networkidle");
await expect(page.locator('[data-testid="event-types"] a[title="managed"]')).toBeVisible();
await adminUser.logout();
});
await test.step("Managed event type has locked fields for added member", async () => {
page.locator('[data-testid="event-types"] a[title="managed"]').click();
await page.waitForLoadState("networkidle");
// Coming back as member user to see if there is a managed event present after assignment
await memberUser.apiLogin();
await page.goto("/event-types");
await page.getByTestId("event-types").locator('a[title="managed"]').click();
await page.waitForURL("event-types/**");
await expect(page.locator('input[name="title"]')).not.toBeEditable();
await expect(page.locator('input[name="slug"]')).not.toBeEditable();
await expect(page.locator('input[name="length"]')).not.toBeEditable();
@@ -463,7 +463,7 @@ async function addAllTypesOfFieldsAndSaveForm(
// Click on the field type dropdown.
await page.locator(".data-testid-field-type").nth(nth).click();
// Click on the dropdown option.
await page.locator(`[data-testid="select-option-${fieldTypeLabel}"]`).click();
await page.locator(`[data-testid^="select-option-"]`).filter({ hasText: fieldTypeLabel }).click();
} else {
// Set the identifier manually for the first field to test out a case when identifier isn't computed from label automatically
// First field type is by default selected. So, no need to choose from dropdown
@@ -45,13 +45,39 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
// Extract this from the input so it doesn't get saved in the db
// eslint-disable-next-line
userId,
// eslint-disable-next-line
teamId,
bookingFields,
offsetStart,
...rest
} = input;
const eventType = await ctx.prisma.eventType.findUniqueOrThrow({
where: { id },
select: {
children: {
select: {
userId: true,
},
},
workflows: {
select: {
workflowId: true,
},
},
team: {
select: {
name: true,
id: true,
},
},
},
});
if (input.teamId && eventType.team?.id && input.teamId !== eventType.team.id) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
const teamId = input.teamId || eventType.team?.id;
ensureUniqueBookingFields(bookingFields);
const data: Prisma.EventTypeUpdateInput = {
@@ -149,7 +175,23 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
};
}
if (hosts) {
if (teamId && hosts) {
// check if all hosts can be assigned (memberships that have accepted invite)
const memberships =
(await ctx.prisma.membership.findMany({
where: {
teamId,
accepted: true,
},
})) || [];
const teamMemberIds = memberships.map((membership) => membership.userId);
// guard against missing IDs, this may mean a member has just been removed
// or this request was forged.
if (!hosts.every((host) => teamMemberIds.includes(host.userId))) {
throw new TRPCError({
code: "FORBIDDEN",
});
}
data.hosts = {
deleteMany: {},
create: hosts.map((host) => ({
@@ -256,48 +298,26 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
});
}
}
const [oldEventType, eventType] = await ctx.prisma.$transaction([
ctx.prisma.eventType.findFirst({
where: { id },
select: {
children: {
select: {
userId: true,
},
},
workflows: {
select: {
workflowId: true,
},
},
team: {
select: {
name: true,
},
},
},
}),
ctx.prisma.eventType.update({
where: { id },
data,
}),
]);
const updatedEventType = await ctx.prisma.eventType.update({
where: { id },
data,
});
// Handling updates to children event types (managed events types)
await updateChildrenEventTypes({
eventTypeId: id,
currentUserId: ctx.user.id,
oldEventType,
oldEventType: eventType,
hashedLink,
connectedLink,
updatedEventType: eventType,
updatedEventType,
children,
prisma: ctx.prisma,
});
const res = ctx.res as NextApiResponse;
if (typeof res?.revalidate !== "undefined") {
try {
await res?.revalidate(`/${ctx.user.username}/${eventType.slug}`);
await res?.revalidate(`/${ctx.user.username}/${updatedEventType.slug}`);
} catch (e) {
// if reach this it is because the event type page has not been created, so it is not possible to revalidate it
logger.debug((e as Error)?.message);
@@ -43,8 +43,8 @@ export const OptionComponent = <
// This gets styled in the select classNames prop now - handles overrides with styles vs className here doesnt
<reactSelectComponents.Option {...props}>
<div className="flex">
<span className="mr-auto" data-testid={`select-option-${props.label}`}>
{props.label}
<span className="mr-auto" data-testid={`select-option-${(props as unknown as ExtendedOption).value}`}>
{props.label || <>&nbsp;</>}
</span>
{(props.data as unknown as ExtendedOption).needsUpgrade && <UpgradeTeamsBadge />}
{props.isSelected && <Check className="ml-2 h-4 w-4" />}