fix: Not able to create consecutive ooo (#17388)

* fix: able to create consecutive ooo

* added e2e test

* chore

* update to allow overlapping ooo and test for same

* refactor to reduce repeating code in tests

* nit

* added testcases for consecutive and reverse redirects

* fix:duplicate OOO creation

* prevent duplicate entries, also added test

* chore

* update after merges

* correct datetype in prisma query

* update to changes in rdp after merge

---------

Co-authored-by: Prashant Varma <prashantvarma5083@gmail.com>
This commit is contained in:
Vijay
2024-12-10 19:46:28 +05:30
committed by GitHub
co-authored by Prashant Varma
parent a1fc6668d5
commit 370345da22
4 changed files with 145 additions and 12 deletions
+18 -1
View File
@@ -1,4 +1,4 @@
import type { Frame, Page, Request as PlaywrightRequest } from "@playwright/test";
import type { Frame, Locator, Page, Request as PlaywrightRequest } from "@playwright/test";
import { expect } from "@playwright/test";
import { createHash } from "crypto";
import EventEmitter from "events";
@@ -538,3 +538,20 @@ export async function expectPageToBeNotFound({ page, url }: { page: Page; url: s
await page.goto(`${url}`);
await expect(page.getByTestId(`404-page`)).toBeVisible();
}
export async function clickUntilDialogVisible(
dialogOpenButton: Locator,
visibleLocatorOnDialog: Locator,
retries = 3,
delay = 500
) {
for (let i = 0; i < retries; i++) {
await dialogOpenButton.click();
try {
await visibleLocatorOnDialog.waitFor({ state: "visible", timeout: delay });
return;
} catch {
if (i === retries - 1) throw new Error("Dialog did not appear after multiple attempts.");
}
}
}
+125 -1
View File
@@ -7,7 +7,7 @@ import { randomString } from "@calcom/lib/random";
import prisma from "@calcom/prisma";
import { test } from "./lib/fixtures";
import { submitAndWaitForResponse } from "./lib/testUtils";
import { submitAndWaitForResponse, localize, clickUntilDialogVisible } from "./lib/testUtils";
test.describe.configure({ mode: "parallel" });
test.afterEach(async ({ users }) => {
@@ -295,6 +295,107 @@ test.describe("Out of office", () => {
// send request
await saveAndWaitForResponse(page, 409);
});
test("User can create separate out of office entries for consecutive dates", async ({ page, users }) => {
const user = await users.create({ name: "userOne" });
await user.apiLogin();
await page.goto("/settings/my-account/out-of-office");
await page.waitForLoadState();
const addOOOButton = await page.getByTestId("add_entry_ooo");
const dateButton = await page.locator('[data-testid="date-range"]');
//Creates 2 OOO entries:
//First OOO is created on Next month 1st - 3rd
await clickUntilDialogVisible(addOOOButton, dateButton);
await dateButton.click();
await selectDateAndCreateOOO(page, "1", "3");
await expect(page.locator(`data-testid=table-redirect-n-a`).nth(0)).toBeVisible();
//Second OOO is created on Next month 4th - 6th
await clickUntilDialogVisible(addOOOButton, dateButton);
await dateButton.click();
await selectDateAndCreateOOO(page, "4", "6");
await expect(page.locator(`data-testid=table-redirect-n-a`).nth(1)).toBeVisible();
});
test("User can create consecutive reverse redirect OOOs", async ({ page, users }) => {
const teamMatesObj = [{ name: "member-1" }, { name: "member-2" }];
const owner = await users.create(
{ name: "owner" },
{
hasTeam: true,
isOrg: true,
teammates: teamMatesObj,
}
);
const member1User = users.get().find((user) => user.name === "member-1");
await owner.apiLogin();
await page.goto("/settings/my-account/out-of-office");
await page.waitForLoadState();
const addOOOButton = await page.getByTestId("add_entry_ooo");
const dateButton = await page.locator('[data-testid="date-range"]');
//As owner,OOO is created on Next month 1st - 3rd, forwarding to 'member-1'
await clickUntilDialogVisible(addOOOButton, dateButton);
await dateButton.click();
await selectDateAndCreateOOO(page, "1", "3", "member-1");
await expect(
page.locator(`data-testid=table-redirect-${member1User?.username ?? "n-a"}`).nth(0)
).toBeVisible();
//As member1, OOO is created on Next month 4th - 5th, forwarding to 'owner'
await member1User?.apiLogin();
await page.goto("/settings/my-account/out-of-office");
await page.waitForLoadState();
await clickUntilDialogVisible(addOOOButton, dateButton);
await dateButton.click();
await selectDateAndCreateOOO(page, "4", "5", "owner");
await expect(page.locator(`data-testid=table-redirect-${owner.username ?? "n-a"}`).nth(0)).toBeVisible();
});
test("User cannot create infinite or overlapping reverse redirect OOOs", async ({ page, users }) => {
const t = await localize("en");
const teamMatesObj = [{ name: "member-1" }, { name: "member-2" }];
const owner = await users.create(
{ name: "owner" },
{
hasTeam: true,
isOrg: true,
teammates: teamMatesObj,
}
);
const member1User = users.get().find((user) => user.name === "member-1");
await owner.apiLogin();
await page.goto("/settings/my-account/out-of-office");
await page.waitForLoadState();
const addOOOButton = await page.getByTestId("add_entry_ooo");
const dateButton = await page.locator('[data-testid="date-range"]');
//As owner,OOO is created on Next month 1st - 3rd, forwarding to 'member-1'
await clickUntilDialogVisible(addOOOButton, dateButton);
await dateButton.click();
await selectDateAndCreateOOO(page, "1", "3", "member-1");
await expect(
page.locator(`data-testid=table-redirect-${member1User?.username ?? "n-a"}`).nth(0)
).toBeVisible();
//As member1, expect error while OOO is created on Next month 2nd - 5th, forwarding to 'owner'
await member1User?.apiLogin();
await page.goto("/settings/my-account/out-of-office");
await page.waitForLoadState();
await clickUntilDialogVisible(addOOOButton, dateButton);
await dateButton.click();
await selectDateAndCreateOOO(page, "2", "5", "owner", 400);
await expect(page.locator(`text=${t("booking_redirect_infinite_not_allowed")}`)).toBeTruthy();
});
});
async function saveAndWaitForResponse(page: Page, expectedStatusCode = 200) {
@@ -312,3 +413,26 @@ async function selectToAndFromDates(page: Page, fromDate: string, toDate: string
await page.locator(`button[name="day"]:has-text("${fromDate}")`).nth(0).click();
await page.locator(`button[name="day"]:has-text("${toDate}")`).nth(0).click();
}
async function selectDateAndCreateOOO(
page: Page,
fromDate: string,
toDate: string,
redirectToUser?: string,
expectedStatusCode = 200
) {
const t = await localize("en");
await page.locator(`button[name="next-month"]`).click();
await page.locator(`button[name="day"]:has-text("${fromDate}")`).nth(0).click();
await page.locator(`button[name="day"]:has-text("${toDate}")`).nth(0).click();
await page.locator(`text=${t("create_an_out_of_office")}`).click();
await page.getByTestId("reason_select").click();
await page.getByTestId("select-option-4").click();
await page.getByTestId("notes_input").click();
await page.getByTestId("notes_input").fill("Demo notes");
if (redirectToUser) {
await page.getByTestId("profile-redirect-switch").click();
await page.locator(`text=${redirectToUser}`).click();
}
await saveAndWaitForResponse(page, expectedStatusCode);
}
@@ -114,15 +114,7 @@ export default function CreateEventTypeDialog({
return (
<Dialog
name="new"
clearQueryParamsOnClose={[
"eventPage",
"type",
"description",
"title",
"length",
"slug",
"locations",
]}>
clearQueryParamsOnClose={["eventPage", "type", "description", "title", "length", "slug", "locations"]}>
<DialogContent
type="creation"
enableOverflow
+1 -1
View File
@@ -1,3 +1,4 @@
import type { calendar_v3 } from "@googleapis/calendar";
import type {
BookingSeat,
DestinationCalendar,
@@ -5,7 +6,6 @@ import type {
SelectedCalendar as _SelectedCalendar,
} from "@prisma/client";
import type { Dayjs } from "dayjs";
import type { calendar_v3 } from "@googleapis/calendar";
import type { Time } from "ical.js";
import type { TFunction } from "next-i18next";
import type z from "zod";