fix: fix date comparison for OOO (#17756)

* fix: fix date comparison for OOO

* fix date comparison with today, and add tests

* remove wrong code

* remove logic that is no longer in use

* fix type error

* mock more methods

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>
This commit is contained in:
Eunjae Lee
2024-12-02 19:39:44 +00:00
committed by GitHub
co-authored by Alex van Andel
parent caeaf9053b
commit 58b77b2a16
2 changed files with 141 additions and 21 deletions
@@ -0,0 +1,129 @@
import { describe, expect, it, beforeEach, afterEach } from "vitest";
import { vi } from "vitest";
import { prisma } from "@calcom/prisma";
import { outOfOfficeCreateOrUpdate } from "../outOfOffice.handler";
const prismaMock = {
outOfOfficeEntry: {
findFirst: vi.fn().mockResolvedValue(undefined),
findUnique: vi.fn().mockResolvedValue(undefined),
upsert: vi.fn().mockResolvedValue(undefined),
},
};
vi.spyOn(prisma.outOfOfficeEntry, "findFirst").mockImplementation(prismaMock.outOfOfficeEntry.findFirst);
vi.spyOn(prisma.outOfOfficeEntry, "findUnique").mockImplementation(prismaMock.outOfOfficeEntry.findUnique);
vi.spyOn(prisma.outOfOfficeEntry, "upsert").mockImplementation(prismaMock.outOfOfficeEntry.upsert);
afterEach(() => {
prismaMock.outOfOfficeEntry.findFirst.mockClear();
prismaMock.outOfOfficeEntry.findUnique.mockClear();
prismaMock.outOfOfficeEntry.upsert.mockClear();
});
const mockUser = {
id: 4,
username: "pro",
email: "pro@example.com",
password: {
hash: "",
userId: 0,
},
completedOnboarding: true,
identityProvider: "CAL",
profiles: [],
avatar: "",
organization: null,
organizationId: null,
locale: "en",
timeZone: "UTC",
defaultScheduleId: null,
email_verified: true,
name: null,
theme: null,
} as const;
describe("outOfOfficeCreateOrUpdate", () => {
beforeEach(() => {
vi.resetAllMocks();
vi.setSystemTime(new Date("2024-11-22T03:23:45Z"));
});
it("should throw error if start date is after end date", async () => {
const input = {
dateRange: {
startDate: new Date("2024-11-23T23:00:00.000Z"),
endDate: new Date("2024-11-22T23:00:00.000Z"),
},
offset: 60,
reasonId: 1,
notes: "",
toTeamUserId: null,
};
await expect(outOfOfficeCreateOrUpdate({ ctx: { user: mockUser }, input })).rejects.toThrow(
"start_date_must_be_before_end_date"
);
});
it("should handle timezone offset correctly", async () => {
const input = {
dateRange: {
startDate: new Date("2024-11-23T23:00:00.000Z"),
endDate: new Date("2024-11-23T23:00:00.000Z"),
},
offset: 60, // Paris timezone offset in minutes
reasonId: 1,
notes: "",
toTeamUserId: null,
};
const startTimeUtc = "2024-11-24T00:00:00.000Z";
const endTimeUtc = "2024-11-24T23:59:59.999Z";
await outOfOfficeCreateOrUpdate({
ctx: { user: mockUser },
input,
});
expect(prisma.outOfOfficeEntry.findFirst).toHaveBeenNthCalledWith(1, {
select: {
toUserId: true,
userId: true,
},
where: {
OR: [
{
AND: [
{
start: {
lte: endTimeUtc,
},
},
{
end: {
gte: startTimeUtc,
},
},
],
},
{
AND: [
{
start: {
gte: startTimeUtc,
},
},
{
end: {
lte: endTimeUtc,
},
},
],
},
],
toUserId: 4,
},
});
});
});
@@ -10,12 +10,12 @@ import type { OOOEntryPayloadType } from "@calcom/features/webhooks/lib/sendPayl
import sendPayload from "@calcom/features/webhooks/lib/sendPayload";
import { getTranslation } from "@calcom/lib/server";
import prisma from "@calcom/prisma";
import { WebhookTriggerEvents } from "@calcom/prisma/enums";
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
import { TRPCError } from "@trpc/server";
import type { TOutOfOfficeDelete, TOutOfOfficeInputSchema } from "./outOfOffice.schema";
import { WebhookTriggerEvents } from ".prisma/client";
type TBookingRedirect = {
ctx: {
@@ -30,11 +30,11 @@ export const outOfOfficeCreateOrUpdate = async ({ ctx, input }: TBookingRedirect
throw new TRPCError({ code: "BAD_REQUEST", message: "start_date_and_end_date_required" });
}
const inputStartTime = dayjs(startDate).startOf("day");
const inputEndTime = dayjs(endDate).endOf("day");
const startTimeUtc = dayjs.utc(startDate).add(input.offset, "minute").startOf("day");
const endTimeUtc = dayjs.utc(endDate).add(input.offset, "minute").endOf("day");
// If start date is after end date throw error
if (inputStartTime.isAfter(inputEndTime)) {
if (startTimeUtc.isAfter(endTimeUtc)) {
throw new TRPCError({ code: "BAD_REQUEST", message: "start_date_must_be_before_end_date" });
}
@@ -85,17 +85,11 @@ export const outOfOfficeCreateOrUpdate = async ({ ctx, input }: TBookingRedirect
OR: [
// Outside of range
{
AND: [
{ start: { lte: inputEndTime.toISOString() } },
{ end: { gte: inputStartTime.toISOString() } },
],
AND: [{ start: { lte: endTimeUtc.toISOString() } }, { end: { gte: startTimeUtc.toISOString() } }],
},
// Inside of range
{
AND: [
{ start: { gte: inputStartTime.toISOString() } },
{ end: { lte: inputEndTime.toISOString() } },
],
AND: [{ start: { gte: startTimeUtc.toISOString() } }, { end: { lte: endTimeUtc.toISOString() } }],
},
],
},
@@ -106,14 +100,11 @@ export const outOfOfficeCreateOrUpdate = async ({ ctx, input }: TBookingRedirect
throw new TRPCError({ code: "BAD_REQUEST", message: "booking_redirect_infinite_not_allowed" });
}
const startTimeUtc = dayjs.utc(startDate).add(input.offset, "minute").startOf("day").toISOString();
const endTimeUtc = dayjs.utc(endDate).add(input.offset, "minute").endOf("day").toISOString();
const isDuplicateOutOfOfficeEntry = await prisma.outOfOfficeEntry.findFirst({
where: {
userId: ctx.user.id,
start: startTimeUtc,
end: endTimeUtc,
start: startTimeUtc.toISOString(),
end: endTimeUtc.toISOString(),
},
});
@@ -144,8 +135,8 @@ export const outOfOfficeCreateOrUpdate = async ({ ctx, input }: TBookingRedirect
},
create: {
uuid: uuidv4(),
start: startTimeUtc,
end: endTimeUtc,
start: startTimeUtc.toISOString(),
end: endTimeUtc.toISOString(),
notes: input.notes,
userId: ctx.user.id,
reasonId: input.reasonId,
@@ -154,8 +145,8 @@ export const outOfOfficeCreateOrUpdate = async ({ ctx, input }: TBookingRedirect
updatedAt: new Date(),
},
update: {
start: startTimeUtc,
end: endTimeUtc,
start: startTimeUtc.toISOString(),
end: endTimeUtc.toISOString(),
notes: input.notes,
userId: ctx.user.id,
reasonId: input.reasonId,