Compare commits
12 Commits
main
...
test/msw-g
Author | SHA1 | Date |
---|---|---|
Joe Au-Yeung | 70531b202d | |
Joe Au-Yeung | 6fa2726537 | |
Joe Au-Yeung | 937fe300ba | |
Joe Au-Yeung | f3379cd3f1 | |
Joe Au-Yeung | ff80cd110c | |
Joe Au-Yeung | 342cafdd4b | |
Joe Au-Yeung | d53f9b07eb | |
Joe Au-Yeung | 9aa600035f | |
Joe Au-Yeung | 61201375c1 | |
Joe Au-Yeung | ba04740e21 | |
Joe Au-Yeung | 9bbec71e17 | |
Joe Au-Yeung | 170d6232fc |
|
@ -151,7 +151,7 @@ describe("POST /api/bookings", () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe("Success", () => {
|
||||
describe("Regular event-type", () => {
|
||||
test("Creates one single booking", async () => {
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
const path = require("path");
|
||||
const i18nConfig = require("@calcom/config/next-i18next.config");
|
||||
|
||||
/** @type {import("next-i18next").UserConfig} */
|
||||
const config = {
|
||||
...i18nConfig,
|
||||
localePath: path.resolve("../web/public/static/locales"),
|
||||
};
|
||||
|
||||
module.exports = config;
|
|
@ -0,0 +1,10 @@
|
|||
const path = require("path");
|
||||
const i18nConfig = require("@calcom/config/next-i18next.config");
|
||||
|
||||
/** @type {import("next-i18next").UserConfig} */
|
||||
const config = {
|
||||
...i18nConfig,
|
||||
localePath: path.resolve("apps/web/public/static/locales"),
|
||||
};
|
||||
|
||||
module.exports = config;
|
|
@ -15,6 +15,7 @@ let client_secret = "";
|
|||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { code } = req.query;
|
||||
const state = decodeOAuthState(req);
|
||||
console.log("🚀 ~ file: callback.ts:18 ~ handler ~ state:", state);
|
||||
|
||||
if (code && typeof code !== "string") {
|
||||
res.status(400).json({ message: "`code` must be a string" });
|
||||
|
|
|
@ -199,6 +199,7 @@ export default class GoogleCalendarService implements Calendar {
|
|||
useDefault: true,
|
||||
},
|
||||
guestsCanSeeOtherGuests: !!calEventRaw.seatsPerTimeSlot ? calEventRaw.seatsShowAttendees : true,
|
||||
iCalUID: calEventRaw.iCalUID || calEventRaw.uid,
|
||||
};
|
||||
|
||||
if (calEventRaw.location) {
|
||||
|
|
|
@ -9,7 +9,14 @@
|
|||
"@calcom/prisma": "*",
|
||||
"googleapis": "^84.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "vitest",
|
||||
"test:coverage": "vitest run --coverage"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@calcom/types": "*"
|
||||
"@calcom/types": "*",
|
||||
"msw": "1.2.3",
|
||||
"node-mocks-http": "^1.11.0",
|
||||
"vitest": "^0.34.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
import { rest } from "msw";
|
||||
|
||||
import { testExpiryDate } from "../../gcal.test";
|
||||
|
||||
export const handlers = [
|
||||
// Handles a POST request
|
||||
rest.get("https://accounts.google.com/o/oauth2/v2/auth", (req, res, ctx) => {
|
||||
return res(
|
||||
// Respond with a 200 status code
|
||||
ctx.status(200)
|
||||
);
|
||||
}),
|
||||
rest.get("https://accounts.google.com/v3/signin/identifier", (req, res, ctx) => {
|
||||
return res(
|
||||
// Respond with a 200 status code
|
||||
ctx.status(200)
|
||||
);
|
||||
}),
|
||||
|
||||
rest.post(`https://oauth2.googleapis.com/token`, (req, res, ctx) => {
|
||||
return res(
|
||||
// Respond with a 200 status code
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
access_token: "access_token",
|
||||
refresh_token: "refresh_token",
|
||||
expiry_date: testExpiryDate,
|
||||
scope:
|
||||
"https://www.googleapis.com/auth/calendar.readonly https://www.googleapis.com/auth/calendar.events",
|
||||
token_type: "Bearer",
|
||||
})
|
||||
);
|
||||
}),
|
||||
rest.get(`/api/integrations/googlecalendar/add`, (req, res, ctx) => {
|
||||
return res(
|
||||
// Respond with a 200 status code
|
||||
ctx.status(200)
|
||||
);
|
||||
}),
|
||||
rest.post("https://www.googleapis.com/calendar/v3/calendars/primary/events", async (req, res, ctx) => {
|
||||
const eventData = await req.json();
|
||||
const organizer = eventData.attendees.find((attendee: any) => attendee.organizer);
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
// ...eventData,
|
||||
id: 12345,
|
||||
iCalUID: 67890,
|
||||
kind: "calendar#event",
|
||||
etag: "12345",
|
||||
status: "confirmed",
|
||||
reminders: { useDefault: true },
|
||||
summary: eventData.title,
|
||||
location: eventData.location,
|
||||
organizer: {
|
||||
email: organizer.email,
|
||||
displayName: organizer.name,
|
||||
self: true,
|
||||
},
|
||||
start: {
|
||||
dateTime: eventData.startTime,
|
||||
timeZone: organizer.timeZone,
|
||||
},
|
||||
end: {
|
||||
dateTime: eventData.endTime,
|
||||
timeZone: organizer.timeZone,
|
||||
},
|
||||
})
|
||||
);
|
||||
}),
|
||||
];
|
||||
// https://accounts.google.com/v3/signin/identifier
|
|
@ -0,0 +1,6 @@
|
|||
import { setupServer } from "msw/node";
|
||||
|
||||
import { handlers } from "./handlers";
|
||||
|
||||
// This configures a request mocking server with the given request handlers.
|
||||
export const server = setupServer(...handlers);
|
|
@ -0,0 +1,257 @@
|
|||
import { server } from "./__mocks__/server/server";
|
||||
import { mockCredential } from "@calcom/prisma/__mocks__/mockCredential";
|
||||
|
||||
import type { TFunction } from "next-i18next";
|
||||
import { createMocks } from "node-mocks-http";
|
||||
import { test, expect, describe, beforeAll, afterAll, afterEach, vi, beforeEach } from "vitest";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { default as handleNewBooking } from "@calcom/features/bookings/lib/handleNewBooking";
|
||||
import { buildCalendarEvent } from "@calcom/lib/test/builder";
|
||||
import { expectFunctionToBeCalledNthTimesWithArgs } from "@calcom/lib/test/expectFunctionToBeCalledNthTimesWithArgs";
|
||||
import type { CustomNextApiRequest, CustomNextApiResponse } from "@calcom/lib/test/types";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { default as prismaMock } from "@calcom/prisma/__mocks__";
|
||||
|
||||
import { default as addHandler } from "../api/add";
|
||||
import { default as callbackHandler } from "../api/callback";
|
||||
import { default as CalendarService } from "../lib/CalendarService";
|
||||
|
||||
vi.mock("@calcom/lib/getIP", () => ({
|
||||
_esModule: true,
|
||||
default: vi.fn().mockReturnValue("127.0.0.1"),
|
||||
}));
|
||||
const mockT: TFunction = vi.fn();
|
||||
vi.mock("");
|
||||
|
||||
export const testExpiryDate = Date.now();
|
||||
|
||||
describe("Google oauth endpoints", () => {
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
beforeAll(() => server.listen());
|
||||
afterAll(() => server.close());
|
||||
afterEach(() => server.resetHandlers());
|
||||
|
||||
describe("Google calendar oauth flows", () => {
|
||||
test("OAuth URL should contain the correct scopes", async () => {
|
||||
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
prismaMock.app.findUnique.mockResolvedValueOnce({
|
||||
slug: "google-calendar",
|
||||
keys: { client_id: "client_id", client_secret: "client_secret" },
|
||||
dirName: "googlecalendar",
|
||||
categories: ["calendar"],
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
enabled: true,
|
||||
});
|
||||
|
||||
await addHandler(req, res);
|
||||
const responseJSON = JSON.parse(res._getData());
|
||||
const authURL = responseJSON.url;
|
||||
expect(authURL).toEqual(expect.stringContaining("access_type=offline"));
|
||||
expect(authURL).toContain("https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcalendar.readonly");
|
||||
expect(authURL).toContain("https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcalendar.events");
|
||||
});
|
||||
test("OAuth callback should create the correct credentials", async () => {
|
||||
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
query: {
|
||||
code: "testcode",
|
||||
},
|
||||
session: {
|
||||
user: {
|
||||
id: 123,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
prismaMock.app.findUnique.mockResolvedValueOnce({
|
||||
slug: "google-calendar",
|
||||
keys: { client_id: "client_id", client_secret: "client_secret" },
|
||||
dirName: "googlecalendar",
|
||||
categories: ["calendar"],
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
enabled: true,
|
||||
});
|
||||
|
||||
await callbackHandler(req, res);
|
||||
|
||||
expectFunctionToBeCalledNthTimesWithArgs(
|
||||
prismaMock.credential.create,
|
||||
1,
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
type: "google_calendar",
|
||||
key: {
|
||||
scope:
|
||||
"https://www.googleapis.com/auth/calendar.readonly https://www.googleapis.com/auth/calendar.events",
|
||||
token_type: "Bearer",
|
||||
access_token: "access_token",
|
||||
refresh_token: "refresh_token",
|
||||
expiry_date: testExpiryDate,
|
||||
},
|
||||
userId: req.session?.user.id,
|
||||
appId: "google-calendar",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(res._getStatusCode()).toBe(302);
|
||||
});
|
||||
});
|
||||
test("Oauth callback should create Google Meet credentials if installGoogleVideo is true", async () => {
|
||||
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
query: {
|
||||
code: "testcode",
|
||||
state: JSON.stringify({ installGoogleVideo: true }),
|
||||
},
|
||||
session: {
|
||||
user: {
|
||||
id: 123,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
prismaMock.app.findUnique.mockResolvedValueOnce({
|
||||
slug: "google-calendar",
|
||||
keys: { client_id: "client_id", client_secret: "client_secret" },
|
||||
dirName: "googlecalendar",
|
||||
categories: ["calendar"],
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
enabled: true,
|
||||
});
|
||||
|
||||
await callbackHandler(req, res);
|
||||
|
||||
expect(prismaMock.credential.create).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
type: "google_calendar",
|
||||
key: {
|
||||
scope:
|
||||
"https://www.googleapis.com/auth/calendar.readonly https://www.googleapis.com/auth/calendar.events",
|
||||
token_type: "Bearer",
|
||||
access_token: "access_token",
|
||||
refresh_token: "refresh_token",
|
||||
expiry_date: testExpiryDate,
|
||||
},
|
||||
userId: req.session?.user.id,
|
||||
appId: "google-calendar",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(prismaMock.credential.create).toHaveBeenNthCalledWith(2, {
|
||||
data: {
|
||||
type: "google_video",
|
||||
key: {},
|
||||
userId: req.session?.user.id,
|
||||
appId: "google-meet",
|
||||
},
|
||||
});
|
||||
});
|
||||
describe("handle sending data to Google", () => {
|
||||
test("Create event", async () => {
|
||||
const credential = mockCredential({
|
||||
type: "google_calendar",
|
||||
key: {
|
||||
scope:
|
||||
"https://www.googleapis.com/auth/calendar.readonly https://www.googleapis.com/auth/calendar.events",
|
||||
token_type: "Bearer",
|
||||
access_token: "access_token",
|
||||
refresh_token: "refresh_token",
|
||||
expiry_date: testExpiryDate + 60 * 60 * 1000,
|
||||
},
|
||||
userId: 123,
|
||||
appId: "google-calendar",
|
||||
});
|
||||
|
||||
const calendarEventRaw = buildCalendarEvent({
|
||||
attendees: [
|
||||
{
|
||||
name: "test",
|
||||
email: "test@test.com",
|
||||
timeZone: "America/Montevideo",
|
||||
language: { translate: mockT, locale: "en" },
|
||||
},
|
||||
],
|
||||
});
|
||||
console.log("🚀 ~ file: gcal.test.ts:188 ~ test ~ calendarEventRaw:", calendarEventRaw);
|
||||
|
||||
const googleCalendarService = new CalendarService(credential);
|
||||
const response = await googleCalendarService.createEvent(calendarEventRaw, credential.id);
|
||||
console.log("🚀 ~ file: gcal.test.ts:187 ~ test ~ response:", response);
|
||||
|
||||
expect(response).toEqual(
|
||||
expect.objectContaining({
|
||||
uid: "",
|
||||
id: "12345",
|
||||
kind: "calendar#event",
|
||||
etag: "12345",
|
||||
iCalUID: calendarEventRaw.iCalUID,
|
||||
type: "google_calendar",
|
||||
password: "",
|
||||
url: "",
|
||||
summary: calendarEventRaw.title,
|
||||
status: "confirmed",
|
||||
reminders: { useDefault: true },
|
||||
location: calendarEventRaw.location,
|
||||
organizer: {
|
||||
email: calendarEventRaw.organizer.email,
|
||||
displayName: calendarEventRaw.organizer.name,
|
||||
self: true,
|
||||
},
|
||||
additionalInfo: { hangoutLink: "" },
|
||||
start: {
|
||||
dateTime: calendarEventRaw.startTime,
|
||||
timeZone: calendarEventRaw.organizer.timeZone,
|
||||
},
|
||||
end: {
|
||||
dateTime: calendarEventRaw.endTime,
|
||||
timeZone: calendarEventRaw.organizer.timeZone,
|
||||
},
|
||||
attendees: calendarEventRaw.attendees.map((attendee) => ({
|
||||
email: attendee.email,
|
||||
})),
|
||||
})
|
||||
);
|
||||
});
|
||||
test("API call to handleNewBooking", async () => {
|
||||
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "POST",
|
||||
body: {
|
||||
name: "test",
|
||||
start: dayjs().add(1, "hour").format(),
|
||||
end: dayjs().add(1, "day").format(),
|
||||
eventTypeId: 3,
|
||||
email: "test@example.com",
|
||||
location: "Cal.com Video",
|
||||
timeZone: "America/Montevideo",
|
||||
language: "en",
|
||||
customInputs: [],
|
||||
metadata: {},
|
||||
userId: 4,
|
||||
},
|
||||
userId: 4,
|
||||
prisma,
|
||||
});
|
||||
|
||||
// prismaMock.eventType.findUniqueOrThrow.mockResolvedValue(buildEventType());
|
||||
// prismaMock.booking.findMany.mockResolvedValue([]);
|
||||
|
||||
await handleNewBooking(req, res);
|
||||
console.log({ statusCode: res._getStatusCode(), data: JSON.parse(res._getData()) });
|
||||
|
||||
expect(prismaMock.booking.create).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
const path = require("path");
|
||||
const i18nConfig = require("@calcom/config/next-i18next.config");
|
||||
|
||||
/** @type {import("next-i18next").UserConfig} */
|
||||
const config = {
|
||||
...i18nConfig,
|
||||
localePath: path.resolve("/apps/web/public/static/locales"),
|
||||
};
|
||||
|
||||
module.exports = config;
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { getCalendarCredentials } from "./CalendarManager";
|
||||
import { getCalendarCredentials } from "../CalendarManager";
|
||||
|
||||
describe("CalendarManager tests", () => {
|
||||
describe("fn: getCalendarCredentials", () => {
|
|
@ -379,7 +379,7 @@ async function ensureAvailableUsers(
|
|||
}
|
||||
) {
|
||||
const availableUsers: IsFixedAwareUser[] = [];
|
||||
const duration = dayjs(input.dateTo).diff(input.dateFrom, 'minute');
|
||||
const duration = dayjs(input.dateTo).diff(input.dateFrom, "minute");
|
||||
|
||||
const originalBookingDuration = input.originalRescheduledBooking
|
||||
? dayjs(input.originalRescheduledBooking.endTime).diff(
|
||||
|
@ -668,6 +668,7 @@ async function handler(
|
|||
...eventType,
|
||||
bookingFields: getBookingFieldsWithSystemFields(eventType),
|
||||
};
|
||||
|
||||
const {
|
||||
recurringCount,
|
||||
allRecurringDates,
|
||||
|
|
|
@ -227,3 +227,15 @@ export const buildUser = <T extends Partial<UserPayload>>(user?: T): UserPayload
|
|||
...user,
|
||||
};
|
||||
};
|
||||
|
||||
export const buildNewBooking = (user: ReturnType<typeof buildUser>, eventTypeId: number) => {
|
||||
return {
|
||||
responses: {
|
||||
email: "test@example.com",
|
||||
name: "Test",
|
||||
guests: [],
|
||||
location: { optionValue: "", value: "integrations:daily" },
|
||||
},
|
||||
user: user.username,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
import { expect } from "vitest";
|
||||
|
||||
// eslint-disable-next-line
|
||||
export const expectFunctionToBeCalledNthTimesWithArgs = (fn: Function, n: number, args: any) => {
|
||||
expect(fn).toHaveBeenCalledTimes(n);
|
||||
expect(fn).toHaveBeenCalledWith(args);
|
||||
};
|
|
@ -0,0 +1,2 @@
|
|||
export type CustomNextApiRequest = NextApiRequest & Request;
|
||||
export type CustomNextApiResponse = NextApiResponse & Response;
|
|
@ -0,0 +1,17 @@
|
|||
import type { PrismaClient } from "@prisma/client";
|
||||
import { beforeEach, vi } from "vitest";
|
||||
import { mockDeep, mockReset } from "vitest-mock-extended";
|
||||
|
||||
vi.mock("@calcom/prisma", () => ({
|
||||
default: prisma,
|
||||
availabilityUserSelect: vi.fn(),
|
||||
userSelect: vi.fn(),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
mockReset(prisma);
|
||||
});
|
||||
|
||||
const prisma = mockDeep<PrismaClient>();
|
||||
|
||||
export default prisma;
|
|
@ -0,0 +1,28 @@
|
|||
import type { Credential } from "@prisma/client";
|
||||
|
||||
export const mockCredential = ({
|
||||
type,
|
||||
key,
|
||||
userId,
|
||||
appId,
|
||||
teamId = 1,
|
||||
}: {
|
||||
type: string;
|
||||
key: object;
|
||||
userId: number;
|
||||
appId: string;
|
||||
teamId?: number;
|
||||
}) => {
|
||||
return {
|
||||
id: 1,
|
||||
type,
|
||||
userId,
|
||||
teamId,
|
||||
key,
|
||||
appId,
|
||||
invalid: false,
|
||||
user: {
|
||||
email: "user@example.com",
|
||||
},
|
||||
} as Credential & { user: { email: string } };
|
||||
};
|
|
@ -4,6 +4,8 @@ process.env.INTEGRATION_TEST_MODE = "true";
|
|||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
// clearMocks: true,
|
||||
// cache: false,
|
||||
coverage: {
|
||||
provider: "v8",
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue