fix: `videoCallUrl` not updating when rescheduling with a broken Calendar integration (#11923)
This commit is contained in:
parent
7a014761dc
commit
d12a5c5883
|
@ -166,6 +166,7 @@
|
||||||
"env-cmd": "^10.1.0",
|
"env-cmd": "^10.1.0",
|
||||||
"module-alias": "^2.2.2",
|
"module-alias": "^2.2.2",
|
||||||
"msw": "^0.42.3",
|
"msw": "^0.42.3",
|
||||||
|
"node-html-parser": "^6.1.10",
|
||||||
"postcss": "^8.4.18",
|
"postcss": "^8.4.18",
|
||||||
"tailwindcss": "^3.3.1",
|
"tailwindcss": "^3.3.1",
|
||||||
"tailwindcss-animate": "^1.0.6",
|
"tailwindcss-animate": "^1.0.6",
|
||||||
|
|
|
@ -2,7 +2,7 @@ import appStoreMock from "../../../../../tests/libs/__mocks__/app-store";
|
||||||
import i18nMock from "../../../../../tests/libs/__mocks__/libServerI18n";
|
import i18nMock from "../../../../../tests/libs/__mocks__/libServerI18n";
|
||||||
import prismock from "../../../../../tests/libs/__mocks__/prisma";
|
import prismock from "../../../../../tests/libs/__mocks__/prisma";
|
||||||
|
|
||||||
import type { BookingReference, Attendee } from "@prisma/client";
|
import type { BookingReference, Attendee, Booking } from "@prisma/client";
|
||||||
import type { Prisma } from "@prisma/client";
|
import type { Prisma } from "@prisma/client";
|
||||||
import type { WebhookTriggerEvents } from "@prisma/client";
|
import type { WebhookTriggerEvents } from "@prisma/client";
|
||||||
import type Stripe from "stripe";
|
import type Stripe from "stripe";
|
||||||
|
@ -102,7 +102,7 @@ export type InputEventType = {
|
||||||
schedule?: InputUser["schedules"][number];
|
schedule?: InputUser["schedules"][number];
|
||||||
} & Partial<Omit<Prisma.EventTypeCreateInput, "users" | "schedule">>;
|
} & Partial<Omit<Prisma.EventTypeCreateInput, "users" | "schedule">>;
|
||||||
|
|
||||||
type InputBooking = {
|
type WhiteListedBookingProps = {
|
||||||
id?: number;
|
id?: number;
|
||||||
uid?: string;
|
uid?: string;
|
||||||
userId?: number;
|
userId?: number;
|
||||||
|
@ -118,6 +118,8 @@ type InputBooking = {
|
||||||
})[];
|
})[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type InputBooking = Partial<Omit<Booking, keyof WhiteListedBookingProps>> & WhiteListedBookingProps;
|
||||||
|
|
||||||
export const Timezones = {
|
export const Timezones = {
|
||||||
"+5:30": "Asia/Kolkata",
|
"+5:30": "Asia/Kolkata",
|
||||||
"+6:00": "Asia/Dhaka",
|
"+6:00": "Asia/Dhaka",
|
||||||
|
@ -1203,3 +1205,35 @@ export const enum BookingLocations {
|
||||||
CalVideo = "integrations:daily",
|
CalVideo = "integrations:daily",
|
||||||
ZoomVideo = "integrations:zoom",
|
ZoomVideo = "integrations:zoom",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getMockAppStatus = ({
|
||||||
|
slug,
|
||||||
|
failures,
|
||||||
|
success,
|
||||||
|
}: {
|
||||||
|
slug: string;
|
||||||
|
failures: number;
|
||||||
|
success: number;
|
||||||
|
}) => {
|
||||||
|
const foundEntry = Object.entries(appStoreMetadata).find(([, app]) => {
|
||||||
|
return app.slug === slug;
|
||||||
|
});
|
||||||
|
if (!foundEntry) {
|
||||||
|
throw new Error("App not found for the slug");
|
||||||
|
}
|
||||||
|
const foundApp = foundEntry[1];
|
||||||
|
return {
|
||||||
|
appName: foundApp.slug,
|
||||||
|
type: foundApp.type,
|
||||||
|
failures,
|
||||||
|
success,
|
||||||
|
errors: [],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export const getMockFailingAppStatus = ({ slug }: { slug: string }) => {
|
||||||
|
return getMockAppStatus({ slug, failures: 1, success: 0 });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getMockPassingAppStatus = ({ slug }: { slug: string }) => {
|
||||||
|
return getMockAppStatus({ slug, failures: 0, success: 1 });
|
||||||
|
};
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import prismaMock from "../../../../../tests/libs/__mocks__/prisma";
|
import prismaMock from "../../../../../tests/libs/__mocks__/prisma";
|
||||||
|
|
||||||
import type { WebhookTriggerEvents, Booking, BookingReference, DestinationCalendar } from "@prisma/client";
|
import type { WebhookTriggerEvents, Booking, BookingReference, DestinationCalendar } from "@prisma/client";
|
||||||
|
import { parse } from "node-html-parser";
|
||||||
import ical from "node-ical";
|
import ical from "node-ical";
|
||||||
import { expect } from "vitest";
|
import { expect } from "vitest";
|
||||||
import "vitest-fetch-mock";
|
import "vitest-fetch-mock";
|
||||||
|
@ -8,6 +9,7 @@ import "vitest-fetch-mock";
|
||||||
import logger from "@calcom/lib/logger";
|
import logger from "@calcom/lib/logger";
|
||||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||||
import { BookingStatus } from "@calcom/prisma/enums";
|
import { BookingStatus } from "@calcom/prisma/enums";
|
||||||
|
import type { AppsStatus } from "@calcom/types/Calendar";
|
||||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||||
import type { Fixtures } from "@calcom/web/test/fixtures/fixtures";
|
import type { Fixtures } from "@calcom/web/test/fixtures/fixtures";
|
||||||
|
|
||||||
|
@ -19,14 +21,14 @@ declare global {
|
||||||
interface Matchers<R> {
|
interface Matchers<R> {
|
||||||
toHaveEmail(
|
toHaveEmail(
|
||||||
expectedEmail: {
|
expectedEmail: {
|
||||||
//TODO: Support email HTML parsing to target specific elements
|
title?: string;
|
||||||
htmlToContain?: string;
|
|
||||||
to: string;
|
to: string;
|
||||||
noIcs?: true;
|
noIcs?: true;
|
||||||
ics?: {
|
ics?: {
|
||||||
filename: string;
|
filename: string;
|
||||||
iCalUID: string;
|
iCalUID: string;
|
||||||
};
|
};
|
||||||
|
appsStatus?: AppsStatus[];
|
||||||
},
|
},
|
||||||
to: string
|
to: string
|
||||||
): R;
|
): R;
|
||||||
|
@ -38,21 +40,23 @@ expect.extend({
|
||||||
toHaveEmail(
|
toHaveEmail(
|
||||||
emails: Fixtures["emails"],
|
emails: Fixtures["emails"],
|
||||||
expectedEmail: {
|
expectedEmail: {
|
||||||
//TODO: Support email HTML parsing to target specific elements
|
title?: string;
|
||||||
htmlToContain?: string;
|
|
||||||
to: string;
|
to: string;
|
||||||
ics: {
|
ics: {
|
||||||
filename: string;
|
filename: string;
|
||||||
iCalUID: string;
|
iCalUID: string;
|
||||||
};
|
};
|
||||||
noIcs: true;
|
noIcs: true;
|
||||||
|
appsStatus: AppsStatus[];
|
||||||
},
|
},
|
||||||
to: string
|
to: string
|
||||||
) {
|
) {
|
||||||
|
const { isNot } = this;
|
||||||
const testEmail = emails.get().find((email) => email.to.includes(to));
|
const testEmail = emails.get().find((email) => email.to.includes(to));
|
||||||
const emailsToLog = emails
|
const emailsToLog = emails
|
||||||
.get()
|
.get()
|
||||||
.map((email) => ({ to: email.to, html: email.html, ics: email.icalEvent }));
|
.map((email) => ({ to: email.to, html: email.html, ics: email.icalEvent }));
|
||||||
|
|
||||||
if (!testEmail) {
|
if (!testEmail) {
|
||||||
logger.silly("All Emails", JSON.stringify({ numEmails: emailsToLog.length, emailsToLog }));
|
logger.silly("All Emails", JSON.stringify({ numEmails: emailsToLog.length, emailsToLog }));
|
||||||
return {
|
return {
|
||||||
|
@ -63,48 +67,93 @@ expect.extend({
|
||||||
const ics = testEmail.icalEvent;
|
const ics = testEmail.icalEvent;
|
||||||
const icsObject = ics?.content ? ical.sync.parseICS(ics?.content) : null;
|
const icsObject = ics?.content ? ical.sync.parseICS(ics?.content) : null;
|
||||||
|
|
||||||
let isHtmlContained = true;
|
|
||||||
let isToAddressExpected = true;
|
let isToAddressExpected = true;
|
||||||
const isIcsFilenameExpected = expectedEmail.ics ? ics?.filename === expectedEmail.ics.filename : true;
|
const isIcsFilenameExpected = expectedEmail.ics ? ics?.filename === expectedEmail.ics.filename : true;
|
||||||
const isIcsUIDExpected = expectedEmail.ics
|
const isIcsUIDExpected = expectedEmail.ics
|
||||||
? !!(icsObject ? icsObject[expectedEmail.ics.iCalUID] : null)
|
? !!(icsObject ? icsObject[expectedEmail.ics.iCalUID] : null)
|
||||||
: true;
|
: true;
|
||||||
|
const emailDom = parse(testEmail.html);
|
||||||
|
|
||||||
if (expectedEmail.htmlToContain) {
|
const actualEmailContent = {
|
||||||
isHtmlContained = testEmail.html.includes(expectedEmail.htmlToContain);
|
title: emailDom.querySelector("title")?.innerText,
|
||||||
|
subject: emailDom.querySelector("subject")?.innerText,
|
||||||
|
};
|
||||||
|
|
||||||
|
const expectedEmailContent = {
|
||||||
|
title: expectedEmail.title,
|
||||||
|
};
|
||||||
|
|
||||||
|
const isEmailContentMatched = this.equals(
|
||||||
|
actualEmailContent,
|
||||||
|
expect.objectContaining(expectedEmailContent)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isEmailContentMatched) {
|
||||||
|
logger.silly("All Emails", JSON.stringify({ numEmails: emailsToLog.length, emailsToLog }));
|
||||||
|
|
||||||
|
return {
|
||||||
|
pass: false,
|
||||||
|
message: () => `Email content ${isNot ? "is" : "is not"} matching`,
|
||||||
|
actual: actualEmailContent,
|
||||||
|
expected: expectedEmailContent,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
isToAddressExpected = expectedEmail.to === testEmail.to;
|
isToAddressExpected = expectedEmail.to === testEmail.to;
|
||||||
|
if (!isToAddressExpected) {
|
||||||
if (!isHtmlContained || !isToAddressExpected) {
|
|
||||||
logger.silly("All Emails", JSON.stringify({ numEmails: emailsToLog.length, emailsToLog }));
|
logger.silly("All Emails", JSON.stringify({ numEmails: emailsToLog.length, emailsToLog }));
|
||||||
|
return {
|
||||||
|
pass: false,
|
||||||
|
message: () => `To address ${isNot ? "is" : "is not"} matching`,
|
||||||
|
actual: testEmail.to,
|
||||||
|
expected: expectedEmail.to,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!expectedEmail.noIcs && !isIcsFilenameExpected) {
|
||||||
|
return {
|
||||||
|
pass: false,
|
||||||
|
actual: ics?.filename,
|
||||||
|
expected: expectedEmail.ics.filename,
|
||||||
|
message: () => `ICS Filename ${isNot ? "is" : "is not"} matching`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!expectedEmail.noIcs && !isIcsUIDExpected) {
|
||||||
|
return {
|
||||||
|
pass: false,
|
||||||
|
actual: JSON.stringify(icsObject),
|
||||||
|
expected: expectedEmail.ics.iCalUID,
|
||||||
|
message: () => `Expected ICS UID ${isNot ? "is" : "isn't"} present in actual`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expectedEmail.appsStatus) {
|
||||||
|
const actualAppsStatus = emailDom.querySelectorAll('[data-testid="appsStatus"] li').map((li) => {
|
||||||
|
return li.innerText.trim();
|
||||||
|
});
|
||||||
|
const expectedAppStatus = expectedEmail.appsStatus.map((appStatus) => {
|
||||||
|
if (appStatus.success && !appStatus.failures) {
|
||||||
|
return `${appStatus.appName} ✅`;
|
||||||
|
}
|
||||||
|
return `${appStatus.appName} ❌`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const isAppsStatusCorrect = this.equals(actualAppsStatus, expectedAppStatus);
|
||||||
|
|
||||||
|
if (!isAppsStatusCorrect) {
|
||||||
|
return {
|
||||||
|
pass: false,
|
||||||
|
actual: actualAppsStatus,
|
||||||
|
expected: expectedAppStatus,
|
||||||
|
message: () => `AppsStatus ${isNot ? "is" : "isn't"} matching`,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pass:
|
pass: true,
|
||||||
isHtmlContained &&
|
message: () => `Email ${isNot ? "is" : "isn't"} correct`,
|
||||||
isToAddressExpected &&
|
|
||||||
(expectedEmail.noIcs ? true : isIcsFilenameExpected && isIcsUIDExpected),
|
|
||||||
message: () => {
|
|
||||||
if (!isHtmlContained) {
|
|
||||||
return `Email HTML is not as expected. Expected:"${expectedEmail.htmlToContain}" isn't contained in "${testEmail.html}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isToAddressExpected) {
|
|
||||||
return `Email To address is not as expected. Expected:${expectedEmail.to} isn't equal to ${testEmail.to}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isIcsFilenameExpected) {
|
|
||||||
return `ICS Filename is not as expected. Expected:${expectedEmail.ics.filename} isn't equal to ${ics?.filename}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isIcsUIDExpected) {
|
|
||||||
return `ICS UID is not as expected. Expected:${
|
|
||||||
expectedEmail.ics.iCalUID
|
|
||||||
} isn't present in ${JSON.stringify(icsObject)}`;
|
|
||||||
}
|
|
||||||
throw new Error("Unknown error");
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -139,9 +188,10 @@ export function expectWebhookToHaveBeenCalledWith(
|
||||||
const parsedBody = JSON.parse((body as string) || "{}");
|
const parsedBody = JSON.parse((body as string) || "{}");
|
||||||
|
|
||||||
expect(parsedBody.triggerEvent).toBe(data.triggerEvent);
|
expect(parsedBody.triggerEvent).toBe(data.triggerEvent);
|
||||||
|
|
||||||
if (parsedBody.payload.metadata?.videoCallUrl) {
|
if (parsedBody.payload.metadata?.videoCallUrl) {
|
||||||
parsedBody.payload.metadata.videoCallUrl = parsedBody.payload.metadata.videoCallUrl
|
parsedBody.payload.metadata.videoCallUrl = parsedBody.payload.metadata.videoCallUrl
|
||||||
? parsedBody.payload.metadata.videoCallUrl.replace(/\/video\/[a-zA-Z0-9]{22}/, "/video/DYNAMIC_UID")
|
? parsedBody.payload.metadata.videoCallUrl
|
||||||
: parsedBody.payload.metadata.videoCallUrl;
|
: parsedBody.payload.metadata.videoCallUrl;
|
||||||
}
|
}
|
||||||
if (data.payload) {
|
if (data.payload) {
|
||||||
|
@ -195,7 +245,7 @@ export function expectSuccessfulBookingCreationEmails({
|
||||||
}) {
|
}) {
|
||||||
expect(emails).toHaveEmail(
|
expect(emails).toHaveEmail(
|
||||||
{
|
{
|
||||||
htmlToContain: "<title>confirmed_event_type_subject</title>",
|
title: "confirmed_event_type_subject",
|
||||||
to: `${organizer.email}`,
|
to: `${organizer.email}`,
|
||||||
ics: {
|
ics: {
|
||||||
filename: "event.ics",
|
filename: "event.ics",
|
||||||
|
@ -207,7 +257,7 @@ export function expectSuccessfulBookingCreationEmails({
|
||||||
|
|
||||||
expect(emails).toHaveEmail(
|
expect(emails).toHaveEmail(
|
||||||
{
|
{
|
||||||
htmlToContain: "<title>confirmed_event_type_subject</title>",
|
title: "confirmed_event_type_subject",
|
||||||
to: `${booker.name} <${booker.email}>`,
|
to: `${booker.name} <${booker.email}>`,
|
||||||
ics: {
|
ics: {
|
||||||
filename: "event.ics",
|
filename: "event.ics",
|
||||||
|
@ -221,7 +271,7 @@ export function expectSuccessfulBookingCreationEmails({
|
||||||
otherTeamMembers.forEach((otherTeamMember) => {
|
otherTeamMembers.forEach((otherTeamMember) => {
|
||||||
expect(emails).toHaveEmail(
|
expect(emails).toHaveEmail(
|
||||||
{
|
{
|
||||||
htmlToContain: "<title>confirmed_event_type_subject</title>",
|
title: "confirmed_event_type_subject",
|
||||||
// Don't know why but organizer and team members of the eventType don'thave their name here like Booker
|
// Don't know why but organizer and team members of the eventType don'thave their name here like Booker
|
||||||
to: `${otherTeamMember.email}`,
|
to: `${otherTeamMember.email}`,
|
||||||
ics: {
|
ics: {
|
||||||
|
@ -238,7 +288,7 @@ export function expectSuccessfulBookingCreationEmails({
|
||||||
guests.forEach((guest) => {
|
guests.forEach((guest) => {
|
||||||
expect(emails).toHaveEmail(
|
expect(emails).toHaveEmail(
|
||||||
{
|
{
|
||||||
htmlToContain: "<title>confirmed_event_type_subject</title>",
|
title: "confirmed_event_type_subject",
|
||||||
to: `${guest.email}`,
|
to: `${guest.email}`,
|
||||||
ics: {
|
ics: {
|
||||||
filename: "event.ics",
|
filename: "event.ics",
|
||||||
|
@ -261,7 +311,7 @@ export function expectBrokenIntegrationEmails({
|
||||||
// Broken Integration email is only sent to the Organizer
|
// Broken Integration email is only sent to the Organizer
|
||||||
expect(emails).toHaveEmail(
|
expect(emails).toHaveEmail(
|
||||||
{
|
{
|
||||||
htmlToContain: "<title>broken_integration</title>",
|
title: "broken_integration",
|
||||||
to: `${organizer.email}`,
|
to: `${organizer.email}`,
|
||||||
// No ics goes in case of broken integration email it seems
|
// No ics goes in case of broken integration email it seems
|
||||||
// ics: {
|
// ics: {
|
||||||
|
@ -274,7 +324,7 @@ export function expectBrokenIntegrationEmails({
|
||||||
|
|
||||||
// expect(emails).toHaveEmail(
|
// expect(emails).toHaveEmail(
|
||||||
// {
|
// {
|
||||||
// htmlToContain: "<title>confirmed_event_type_subject</title>",
|
// title: "confirmed_event_type_subject",
|
||||||
// to: `${booker.name} <${booker.email}>`,
|
// to: `${booker.name} <${booker.email}>`,
|
||||||
// },
|
// },
|
||||||
// `${booker.name} <${booker.email}>`
|
// `${booker.name} <${booker.email}>`
|
||||||
|
@ -294,7 +344,7 @@ export function expectCalendarEventCreationFailureEmails({
|
||||||
}) {
|
}) {
|
||||||
expect(emails).toHaveEmail(
|
expect(emails).toHaveEmail(
|
||||||
{
|
{
|
||||||
htmlToContain: "<title>broken_integration</title>",
|
title: "broken_integration",
|
||||||
to: `${organizer.email}`,
|
to: `${organizer.email}`,
|
||||||
ics: {
|
ics: {
|
||||||
filename: "event.ics",
|
filename: "event.ics",
|
||||||
|
@ -306,7 +356,7 @@ export function expectCalendarEventCreationFailureEmails({
|
||||||
|
|
||||||
expect(emails).toHaveEmail(
|
expect(emails).toHaveEmail(
|
||||||
{
|
{
|
||||||
htmlToContain: "<title>calendar_event_creation_failure_subject</title>",
|
title: "calendar_event_creation_failure_subject",
|
||||||
to: `${booker.name} <${booker.email}>`,
|
to: `${booker.name} <${booker.email}>`,
|
||||||
ics: {
|
ics: {
|
||||||
filename: "event.ics",
|
filename: "event.ics",
|
||||||
|
@ -322,27 +372,30 @@ export function expectSuccessfulBookingRescheduledEmails({
|
||||||
organizer,
|
organizer,
|
||||||
booker,
|
booker,
|
||||||
iCalUID,
|
iCalUID,
|
||||||
|
appsStatus,
|
||||||
}: {
|
}: {
|
||||||
emails: Fixtures["emails"];
|
emails: Fixtures["emails"];
|
||||||
organizer: { email: string; name: string };
|
organizer: { email: string; name: string };
|
||||||
booker: { email: string; name: string };
|
booker: { email: string; name: string };
|
||||||
iCalUID: string;
|
iCalUID: string;
|
||||||
|
appsStatus: AppsStatus[];
|
||||||
}) {
|
}) {
|
||||||
expect(emails).toHaveEmail(
|
expect(emails).toHaveEmail(
|
||||||
{
|
{
|
||||||
htmlToContain: "<title>event_type_has_been_rescheduled_on_time_date</title>",
|
title: "event_type_has_been_rescheduled_on_time_date",
|
||||||
to: `${organizer.email}`,
|
to: `${organizer.email}`,
|
||||||
ics: {
|
ics: {
|
||||||
filename: "event.ics",
|
filename: "event.ics",
|
||||||
iCalUID,
|
iCalUID,
|
||||||
},
|
},
|
||||||
|
appsStatus,
|
||||||
},
|
},
|
||||||
`${organizer.email}`
|
`${organizer.email}`
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(emails).toHaveEmail(
|
expect(emails).toHaveEmail(
|
||||||
{
|
{
|
||||||
htmlToContain: "<title>event_type_has_been_rescheduled_on_time_date</title>",
|
title: "event_type_has_been_rescheduled_on_time_date",
|
||||||
to: `${booker.name} <${booker.email}>`,
|
to: `${booker.name} <${booker.email}>`,
|
||||||
ics: {
|
ics: {
|
||||||
filename: "event.ics",
|
filename: "event.ics",
|
||||||
|
@ -362,7 +415,7 @@ export function expectAwaitingPaymentEmails({
|
||||||
}) {
|
}) {
|
||||||
expect(emails).toHaveEmail(
|
expect(emails).toHaveEmail(
|
||||||
{
|
{
|
||||||
htmlToContain: "<title>awaiting_payment_subject</title>",
|
title: "awaiting_payment_subject",
|
||||||
to: `${booker.name} <${booker.email}>`,
|
to: `${booker.name} <${booker.email}>`,
|
||||||
noIcs: true,
|
noIcs: true,
|
||||||
},
|
},
|
||||||
|
@ -381,7 +434,7 @@ export function expectBookingRequestedEmails({
|
||||||
}) {
|
}) {
|
||||||
expect(emails).toHaveEmail(
|
expect(emails).toHaveEmail(
|
||||||
{
|
{
|
||||||
htmlToContain: "<title>event_awaiting_approval_subject</title>",
|
title: "event_awaiting_approval_subject",
|
||||||
to: `${organizer.email}`,
|
to: `${organizer.email}`,
|
||||||
noIcs: true,
|
noIcs: true,
|
||||||
},
|
},
|
||||||
|
@ -390,7 +443,7 @@ export function expectBookingRequestedEmails({
|
||||||
|
|
||||||
expect(emails).toHaveEmail(
|
expect(emails).toHaveEmail(
|
||||||
{
|
{
|
||||||
htmlToContain: "<title>booking_submitted_subject</title>",
|
title: "booking_submitted_subject",
|
||||||
to: `${booker.email}`,
|
to: `${booker.email}`,
|
||||||
noIcs: true,
|
noIcs: true,
|
||||||
},
|
},
|
||||||
|
@ -509,6 +562,7 @@ export function expectBookingRescheduledWebhookToHaveBeenFired({
|
||||||
location,
|
location,
|
||||||
subscriberUrl,
|
subscriberUrl,
|
||||||
videoCallUrl,
|
videoCallUrl,
|
||||||
|
payload,
|
||||||
}: {
|
}: {
|
||||||
organizer: { email: string; name: string };
|
organizer: { email: string; name: string };
|
||||||
booker: { email: string; name: string };
|
booker: { email: string; name: string };
|
||||||
|
@ -516,10 +570,12 @@ export function expectBookingRescheduledWebhookToHaveBeenFired({
|
||||||
location: string;
|
location: string;
|
||||||
paidEvent?: boolean;
|
paidEvent?: boolean;
|
||||||
videoCallUrl?: string;
|
videoCallUrl?: string;
|
||||||
|
payload?: Record<string, unknown>;
|
||||||
}) {
|
}) {
|
||||||
expectWebhookToHaveBeenCalledWith(subscriberUrl, {
|
expectWebhookToHaveBeenCalledWith(subscriberUrl, {
|
||||||
triggerEvent: "BOOKING_RESCHEDULED",
|
triggerEvent: "BOOKING_RESCHEDULED",
|
||||||
payload: {
|
payload: {
|
||||||
|
...payload,
|
||||||
metadata: {
|
metadata: {
|
||||||
...(videoCallUrl ? { videoCallUrl } : null),
|
...(videoCallUrl ? { videoCallUrl } : null),
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { TFunction } from "next-i18next";
|
import type { TFunction } from "next-i18next";
|
||||||
|
|
||||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ export const AppsStatus = (props: { calEvent: CalendarEvent; t: TFunction }) =>
|
||||||
<Info
|
<Info
|
||||||
label={t("apps_status")}
|
label={t("apps_status")}
|
||||||
description={
|
description={
|
||||||
<ul style={{ lineHeight: "24px" }}>
|
<ul style={{ lineHeight: "24px" }} data-testid="appsStatus">
|
||||||
{props.calEvent.appsStatus.map((status) => (
|
{props.calEvent.appsStatus.map((status) => (
|
||||||
<li key={status.type} style={{ fontWeight: 400 }}>
|
<li key={status.type} style={{ fontWeight: 400 }}>
|
||||||
{status.appName}{" "}
|
{status.appName}{" "}
|
||||||
|
|
|
@ -1431,7 +1431,7 @@ async function handler(
|
||||||
metadata.hangoutLink = updatedEvent.hangoutLink;
|
metadata.hangoutLink = updatedEvent.hangoutLink;
|
||||||
metadata.conferenceData = updatedEvent.conferenceData;
|
metadata.conferenceData = updatedEvent.conferenceData;
|
||||||
metadata.entryPoints = updatedEvent.entryPoints;
|
metadata.entryPoints = updatedEvent.entryPoints;
|
||||||
handleAppsStatus(results, newBooking);
|
evt.appsStatus = handleAppsStatus(results, newBooking);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2105,7 +2105,7 @@ async function handler(
|
||||||
booking: (Booking & { appsStatus?: AppsStatus[] }) | null
|
booking: (Booking & { appsStatus?: AppsStatus[] }) | null
|
||||||
) {
|
) {
|
||||||
// Taking care of apps status
|
// Taking care of apps status
|
||||||
const resultStatus: AppsStatus[] = results.map((app) => ({
|
let resultStatus: AppsStatus[] = results.map((app) => ({
|
||||||
appName: app.appName,
|
appName: app.appName,
|
||||||
type: app.type,
|
type: app.type,
|
||||||
success: app.success ? 1 : 0,
|
success: app.success ? 1 : 0,
|
||||||
|
@ -2118,8 +2118,7 @@ async function handler(
|
||||||
if (booking !== null) {
|
if (booking !== null) {
|
||||||
booking.appsStatus = resultStatus;
|
booking.appsStatus = resultStatus;
|
||||||
}
|
}
|
||||||
evt.appsStatus = resultStatus;
|
return resultStatus;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
// From down here we can assume reqAppsStatus is not undefined anymore
|
// From down here we can assume reqAppsStatus is not undefined anymore
|
||||||
// Other status exist, so this is the last booking of a series,
|
// Other status exist, so this is the last booking of a series,
|
||||||
|
@ -2134,7 +2133,8 @@ async function handler(
|
||||||
}
|
}
|
||||||
return prev;
|
return prev;
|
||||||
}, {} as { [key: string]: AppsStatus });
|
}, {} as { [key: string]: AppsStatus });
|
||||||
evt.appsStatus = Object.values(calcAppsStatus);
|
resultStatus = Object.values(calcAppsStatus);
|
||||||
|
return resultStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
let videoCallUrl;
|
let videoCallUrl;
|
||||||
|
@ -2174,35 +2174,34 @@ async function handler(
|
||||||
|
|
||||||
results = updateManager.results;
|
results = updateManager.results;
|
||||||
referencesToCreate = updateManager.referencesToCreate;
|
referencesToCreate = updateManager.referencesToCreate;
|
||||||
if (results.length > 0 && results.some((res) => !res.success)) {
|
const isThereAnIntegrationError = results && results.some((res) => !res.success);
|
||||||
|
if (isThereAnIntegrationError) {
|
||||||
const error = {
|
const error = {
|
||||||
errorCode: "BookingReschedulingMeetingFailed",
|
errorCode: "BookingReschedulingMeetingFailed",
|
||||||
message: "Booking Rescheduling failed",
|
message: "Booking Rescheduling failed",
|
||||||
};
|
};
|
||||||
|
|
||||||
loggerWithEventDetails.error(`Booking ${organizerUser.name} failed`, safeStringify({ error, results }));
|
loggerWithEventDetails.error(
|
||||||
|
`EventManager.create failure in some of the integrations ${organizerUser.username}`,
|
||||||
|
safeStringify({ error, results })
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
const metadata: AdditionalInformation = {};
|
|
||||||
const calendarResult = results.find((result) => result.type.includes("_calendar"));
|
const calendarResult = results.find((result) => result.type.includes("_calendar"));
|
||||||
|
|
||||||
evt.iCalUID = Array.isArray(calendarResult?.updatedEvent)
|
evt.iCalUID = Array.isArray(calendarResult?.updatedEvent)
|
||||||
? calendarResult?.updatedEvent[0]?.iCalUID
|
? calendarResult?.updatedEvent[0]?.iCalUID
|
||||||
: calendarResult?.updatedEvent?.iCalUID || undefined;
|
: calendarResult?.updatedEvent?.iCalUID || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
if (results.length) {
|
const { metadata, videoCallUrl: _videoCallUrl } = getVideoCallDetails({
|
||||||
// TODO: Handle created event metadata more elegantly
|
results,
|
||||||
const [updatedEvent] = Array.isArray(results[0].updatedEvent)
|
});
|
||||||
? results[0].updatedEvent
|
|
||||||
: [results[0].updatedEvent];
|
videoCallUrl = _videoCallUrl;
|
||||||
if (updatedEvent) {
|
evt.appsStatus = handleAppsStatus(results, booking);
|
||||||
metadata.hangoutLink = updatedEvent.hangoutLink;
|
|
||||||
metadata.conferenceData = updatedEvent.conferenceData;
|
// If there is an integration error, we don't send successful rescheduling email, instead broken integration email should be sent that are handled by either CalendarManager or videoClient
|
||||||
metadata.entryPoints = updatedEvent.entryPoints;
|
if (noEmail !== true && isConfirmedByDefault && !isThereAnIntegrationError) {
|
||||||
handleAppsStatus(results, booking);
|
|
||||||
videoCallUrl = metadata.hangoutLink || videoCallUrl || updatedEvent?.url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (noEmail !== true && isConfirmedByDefault) {
|
|
||||||
const copyEvent = cloneDeep(evt);
|
const copyEvent = cloneDeep(evt);
|
||||||
loggerWithEventDetails.debug("Emails: Sending rescheduled emails for booking confirmation");
|
loggerWithEventDetails.debug("Emails: Sending rescheduled emails for booking confirmation");
|
||||||
await sendRescheduledEmails({
|
await sendRescheduledEmails({
|
||||||
|
@ -2212,7 +2211,6 @@ async function handler(
|
||||||
cancellationReason: `$RCH$${rescheduleReason ? rescheduleReason : ""}`, // Removable code prefix to differentiate cancellation from rescheduling for email
|
cancellationReason: `$RCH$${rescheduleReason ? rescheduleReason : ""}`, // Removable code prefix to differentiate cancellation from rescheduling for email
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// If it's not a reschedule, doesn't require confirmation and there's no price,
|
// If it's not a reschedule, doesn't require confirmation and there's no price,
|
||||||
// Create a booking
|
// Create a booking
|
||||||
} else if (isConfirmedByDefault) {
|
} else if (isConfirmedByDefault) {
|
||||||
|
@ -2234,7 +2232,7 @@ async function handler(
|
||||||
};
|
};
|
||||||
|
|
||||||
loggerWithEventDetails.error(
|
loggerWithEventDetails.error(
|
||||||
`Failure in creating events in some of the integrations ${organizerUser.username} failed`,
|
`EventManager.create failure in some of the integrations ${organizerUser.username}`,
|
||||||
safeStringify({ error, results })
|
safeStringify({ error, results })
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -2296,7 +2294,7 @@ async function handler(
|
||||||
metadata.hangoutLink = results[0].createdEvent?.hangoutLink;
|
metadata.hangoutLink = results[0].createdEvent?.hangoutLink;
|
||||||
metadata.conferenceData = results[0].createdEvent?.conferenceData;
|
metadata.conferenceData = results[0].createdEvent?.conferenceData;
|
||||||
metadata.entryPoints = results[0].createdEvent?.entryPoints;
|
metadata.entryPoints = results[0].createdEvent?.entryPoints;
|
||||||
handleAppsStatus(results, booking);
|
evt.appsStatus = handleAppsStatus(results, booking);
|
||||||
videoCallUrl =
|
videoCallUrl =
|
||||||
metadata.hangoutLink || organizerOrFirstDynamicGroupMemberDefaultLocationUrl || videoCallUrl;
|
metadata.hangoutLink || organizerOrFirstDynamicGroupMemberDefaultLocationUrl || videoCallUrl;
|
||||||
}
|
}
|
||||||
|
@ -2374,6 +2372,7 @@ async function handler(
|
||||||
videoCallUrl: getVideoCallUrlFromCalEvent(evt),
|
videoCallUrl: getVideoCallUrlFromCalEvent(evt),
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const webhookData = {
|
const webhookData = {
|
||||||
...evt,
|
...evt,
|
||||||
...eventTypeInfo,
|
...eventTypeInfo,
|
||||||
|
@ -2558,6 +2557,31 @@ async function handler(
|
||||||
|
|
||||||
export default handler;
|
export default handler;
|
||||||
|
|
||||||
|
function getVideoCallDetails({
|
||||||
|
results,
|
||||||
|
}: {
|
||||||
|
results: EventResult<AdditionalInformation & { url?: string | undefined; iCalUID?: string | undefined }>[];
|
||||||
|
}) {
|
||||||
|
const firstVideoResult = results.find((result) => result.type.includes("_video"));
|
||||||
|
const metadata: AdditionalInformation = {};
|
||||||
|
let updatedVideoEvent = null;
|
||||||
|
|
||||||
|
if (firstVideoResult && firstVideoResult.success) {
|
||||||
|
updatedVideoEvent = Array.isArray(firstVideoResult.updatedEvent)
|
||||||
|
? firstVideoResult.updatedEvent[0]
|
||||||
|
: firstVideoResult.updatedEvent;
|
||||||
|
|
||||||
|
if (updatedVideoEvent) {
|
||||||
|
metadata.hangoutLink = updatedVideoEvent.hangoutLink;
|
||||||
|
metadata.conferenceData = updatedVideoEvent.conferenceData;
|
||||||
|
metadata.entryPoints = updatedVideoEvent.entryPoints;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const videoCallUrl = metadata.hangoutLink || updatedVideoEvent?.url;
|
||||||
|
|
||||||
|
return { videoCallUrl, metadata, updatedVideoEvent };
|
||||||
|
}
|
||||||
|
|
||||||
function getRequiresConfirmationFlags({
|
function getRequiresConfirmationFlags({
|
||||||
eventType,
|
eventType,
|
||||||
bookingStartTime,
|
bookingStartTime,
|
||||||
|
|
|
@ -206,7 +206,7 @@ describe("handleNewBooking", () => {
|
||||||
organizer,
|
organizer,
|
||||||
location: BookingLocations.CalVideo,
|
location: BookingLocations.CalVideo,
|
||||||
subscriberUrl: "http://my-webhook.example.com",
|
subscriberUrl: "http://my-webhook.example.com",
|
||||||
videoCallUrl: `${WEBAPP_URL}/video/DYNAMIC_UID`,
|
videoCallUrl: `${WEBAPP_URL}/video/${createdBooking.uid}`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
timeout
|
timeout
|
||||||
|
@ -353,7 +353,7 @@ describe("handleNewBooking", () => {
|
||||||
organizer,
|
organizer,
|
||||||
location: BookingLocations.CalVideo,
|
location: BookingLocations.CalVideo,
|
||||||
subscriberUrl: "http://my-webhook.example.com",
|
subscriberUrl: "http://my-webhook.example.com",
|
||||||
videoCallUrl: `${WEBAPP_URL}/video/DYNAMIC_UID`,
|
videoCallUrl: `${WEBAPP_URL}/video/${createdBooking.uid}`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
timeout
|
timeout
|
||||||
|
@ -499,7 +499,7 @@ describe("handleNewBooking", () => {
|
||||||
organizer,
|
organizer,
|
||||||
location: BookingLocations.CalVideo,
|
location: BookingLocations.CalVideo,
|
||||||
subscriberUrl: "http://my-webhook.example.com",
|
subscriberUrl: "http://my-webhook.example.com",
|
||||||
videoCallUrl: `${WEBAPP_URL}/video/DYNAMIC_UID`,
|
videoCallUrl: `${WEBAPP_URL}/video/${createdBooking.uid}`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
timeout
|
timeout
|
||||||
|
@ -760,7 +760,7 @@ describe("handleNewBooking", () => {
|
||||||
organizer,
|
organizer,
|
||||||
location: BookingLocations.CalVideo,
|
location: BookingLocations.CalVideo,
|
||||||
subscriberUrl: "http://my-webhook.example.com",
|
subscriberUrl: "http://my-webhook.example.com",
|
||||||
videoCallUrl: `${WEBAPP_URL}/video/DYNAMIC_UID`,
|
videoCallUrl: `${WEBAPP_URL}/video/${createdBooking.uid}`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
timeout
|
timeout
|
||||||
|
@ -1447,7 +1447,7 @@ describe("handleNewBooking", () => {
|
||||||
organizer,
|
organizer,
|
||||||
location: BookingLocations.CalVideo,
|
location: BookingLocations.CalVideo,
|
||||||
subscriberUrl,
|
subscriberUrl,
|
||||||
videoCallUrl: `${WEBAPP_URL}/video/DYNAMIC_UID`,
|
videoCallUrl: `${WEBAPP_URL}/video/${createdBooking.uid}`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
timeout
|
timeout
|
||||||
|
@ -1883,7 +1883,7 @@ describe("handleNewBooking", () => {
|
||||||
organizer,
|
organizer,
|
||||||
location: BookingLocations.CalVideo,
|
location: BookingLocations.CalVideo,
|
||||||
subscriberUrl: "http://my-webhook.example.com",
|
subscriberUrl: "http://my-webhook.example.com",
|
||||||
videoCallUrl: `${WEBAPP_URL}/video/DYNAMIC_UID`,
|
videoCallUrl: `${WEBAPP_URL}/video/${createdBooking.uid}`,
|
||||||
paidEvent: true,
|
paidEvent: true,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -21,6 +21,8 @@ import {
|
||||||
BookingLocations,
|
BookingLocations,
|
||||||
getMockBookingReference,
|
getMockBookingReference,
|
||||||
getMockBookingAttendee,
|
getMockBookingAttendee,
|
||||||
|
getMockFailingAppStatus,
|
||||||
|
getMockPassingAppStatus,
|
||||||
} from "@calcom/web/test/utils/bookingScenario/bookingScenario";
|
} from "@calcom/web/test/utils/bookingScenario/bookingScenario";
|
||||||
import {
|
import {
|
||||||
expectWorkflowToBeTriggered,
|
expectWorkflowToBeTriggered,
|
||||||
|
@ -103,6 +105,9 @@ describe("handleNewBooking", () => {
|
||||||
status: BookingStatus.ACCEPTED,
|
status: BookingStatus.ACCEPTED,
|
||||||
startTime: `${plus1DateString}T05:00:00.000Z`,
|
startTime: `${plus1DateString}T05:00:00.000Z`,
|
||||||
endTime: `${plus1DateString}T05:15:00.000Z`,
|
endTime: `${plus1DateString}T05:15:00.000Z`,
|
||||||
|
metadata: {
|
||||||
|
videoCallUrl: "https://existing-daily-video-call-url.example.com",
|
||||||
|
},
|
||||||
references: [
|
references: [
|
||||||
{
|
{
|
||||||
type: appStoreMetadata.dailyvideo.type,
|
type: appStoreMetadata.dailyvideo.type,
|
||||||
|
@ -254,6 +259,10 @@ describe("handleNewBooking", () => {
|
||||||
organizer,
|
organizer,
|
||||||
emails,
|
emails,
|
||||||
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
|
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
|
||||||
|
appsStatus: [
|
||||||
|
getMockPassingAppStatus({ slug: appStoreMetadata.dailyvideo.slug }),
|
||||||
|
getMockPassingAppStatus({ slug: appStoreMetadata.googlecalendar.slug }),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
expectBookingRescheduledWebhookToHaveBeenFired({
|
expectBookingRescheduledWebhookToHaveBeenFired({
|
||||||
|
@ -261,7 +270,7 @@ describe("handleNewBooking", () => {
|
||||||
organizer,
|
organizer,
|
||||||
location: BookingLocations.CalVideo,
|
location: BookingLocations.CalVideo,
|
||||||
subscriberUrl: "http://my-webhook.example.com",
|
subscriberUrl: "http://my-webhook.example.com",
|
||||||
videoCallUrl: `${WEBAPP_URL}/video/DYNAMIC_UID`,
|
videoCallUrl: `${WEBAPP_URL}/video/${createdBooking.uid}`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
timeout
|
timeout
|
||||||
|
@ -464,7 +473,7 @@ describe("handleNewBooking", () => {
|
||||||
organizer,
|
organizer,
|
||||||
location: BookingLocations.CalVideo,
|
location: BookingLocations.CalVideo,
|
||||||
subscriberUrl: "http://my-webhook.example.com",
|
subscriberUrl: "http://my-webhook.example.com",
|
||||||
videoCallUrl: `${WEBAPP_URL}/video/DYNAMIC_UID`,
|
videoCallUrl: `${WEBAPP_URL}/video/${createdBooking.uid}`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
timeout
|
timeout
|
||||||
|
@ -525,6 +534,9 @@ describe("handleNewBooking", () => {
|
||||||
status: BookingStatus.ACCEPTED,
|
status: BookingStatus.ACCEPTED,
|
||||||
startTime: `${plus1DateString}T05:00:00.000Z`,
|
startTime: `${plus1DateString}T05:00:00.000Z`,
|
||||||
endTime: `${plus1DateString}T05:15:00.000Z`,
|
endTime: `${plus1DateString}T05:15:00.000Z`,
|
||||||
|
metadata: {
|
||||||
|
videoCallUrl: "https://existing-daily-video-call-url.example.com",
|
||||||
|
},
|
||||||
references: [
|
references: [
|
||||||
{
|
{
|
||||||
type: appStoreMetadata.dailyvideo.type,
|
type: appStoreMetadata.dailyvideo.type,
|
||||||
|
@ -551,6 +563,9 @@ describe("handleNewBooking", () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const _calendarMock = mockCalendarToCrashOnUpdateEvent("googlecalendar");
|
const _calendarMock = mockCalendarToCrashOnUpdateEvent("googlecalendar");
|
||||||
|
const _videoMock = mockSuccessfulVideoMeetingCreation({
|
||||||
|
metadataLookupKey: "dailyvideo",
|
||||||
|
});
|
||||||
|
|
||||||
const mockBookingData = getMockRequestDataForBooking({
|
const mockBookingData = getMockRequestDataForBooking({
|
||||||
data: {
|
data: {
|
||||||
|
@ -559,7 +574,7 @@ describe("handleNewBooking", () => {
|
||||||
responses: {
|
responses: {
|
||||||
email: booker.email,
|
email: booker.email,
|
||||||
name: booker.name,
|
name: booker.name,
|
||||||
location: { optionValue: "", value: "New York" },
|
location: { optionValue: "", value: BookingLocations.CalVideo },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -577,16 +592,27 @@ describe("handleNewBooking", () => {
|
||||||
},
|
},
|
||||||
to: {
|
to: {
|
||||||
description: "",
|
description: "",
|
||||||
location: "New York",
|
location: "integrations:daily",
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
uid: createdBooking.uid!,
|
uid: createdBooking.uid!,
|
||||||
eventTypeId: mockBookingData.eventTypeId,
|
eventTypeId: mockBookingData.eventTypeId,
|
||||||
status: BookingStatus.ACCEPTED,
|
status: BookingStatus.ACCEPTED,
|
||||||
|
metadata: {
|
||||||
|
videoCallUrl: `${WEBAPP_URL}/video/${createdBooking?.uid}`,
|
||||||
|
},
|
||||||
responses: expect.objectContaining({
|
responses: expect.objectContaining({
|
||||||
email: booker.email,
|
email: booker.email,
|
||||||
name: booker.name,
|
name: booker.name,
|
||||||
}),
|
}),
|
||||||
|
// Booking References still use the original booking's references - Not sure how intentional it is.
|
||||||
references: [
|
references: [
|
||||||
|
{
|
||||||
|
type: appStoreMetadata.dailyvideo.type,
|
||||||
|
uid: "MOCK_ID",
|
||||||
|
meetingId: "MOCK_ID",
|
||||||
|
meetingPassword: "MOCK_PASS",
|
||||||
|
meetingUrl: "http://mock-dailyvideo.example.com",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: appStoreMetadata.googlecalendar.type,
|
type: appStoreMetadata.googlecalendar.type,
|
||||||
// A reference is still created in case of event creation failure, with nullish values. Not sure what's the purpose for this.
|
// A reference is still created in case of event creation failure, with nullish values. Not sure what's the purpose for this.
|
||||||
|
@ -607,8 +633,18 @@ describe("handleNewBooking", () => {
|
||||||
expectBookingRescheduledWebhookToHaveBeenFired({
|
expectBookingRescheduledWebhookToHaveBeenFired({
|
||||||
booker,
|
booker,
|
||||||
organizer,
|
organizer,
|
||||||
location: "New York",
|
location: "integrations:daily",
|
||||||
subscriberUrl: "http://my-webhook.example.com",
|
subscriberUrl: "http://my-webhook.example.com",
|
||||||
|
payload: {
|
||||||
|
uid: createdBooking.uid,
|
||||||
|
appsStatus: [
|
||||||
|
expect.objectContaining(getMockPassingAppStatus({ slug: appStoreMetadata.dailyvideo.slug })),
|
||||||
|
expect.objectContaining(
|
||||||
|
getMockFailingAppStatus({ slug: appStoreMetadata.googlecalendar.slug })
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
videoCallUrl: `${WEBAPP_URL}/video/${createdBooking?.uid}`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
timeout
|
timeout
|
||||||
|
@ -1048,7 +1084,7 @@ describe("handleNewBooking", () => {
|
||||||
organizer,
|
organizer,
|
||||||
location: BookingLocations.CalVideo,
|
location: BookingLocations.CalVideo,
|
||||||
subscriberUrl: "http://my-webhook.example.com",
|
subscriberUrl: "http://my-webhook.example.com",
|
||||||
videoCallUrl: `${WEBAPP_URL}/video/DYNAMIC_UID`,
|
videoCallUrl: `${WEBAPP_URL}/video/${createdBooking.uid}`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
timeout
|
timeout
|
||||||
|
@ -1497,12 +1533,13 @@ describe("handleNewBooking", () => {
|
||||||
emails,
|
emails,
|
||||||
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
|
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
|
||||||
});
|
});
|
||||||
|
|
||||||
expectBookingRescheduledWebhookToHaveBeenFired({
|
expectBookingRescheduledWebhookToHaveBeenFired({
|
||||||
booker,
|
booker,
|
||||||
organizer,
|
organizer,
|
||||||
location: BookingLocations.CalVideo,
|
location: BookingLocations.CalVideo,
|
||||||
subscriberUrl: "http://my-webhook.example.com",
|
subscriberUrl: "http://my-webhook.example.com",
|
||||||
videoCallUrl: `${WEBAPP_URL}/video/DYNAMIC_UID`,
|
videoCallUrl: `${WEBAPP_URL}/video/${createdBooking.uid}`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
timeout
|
timeout
|
||||||
|
|
|
@ -225,7 +225,7 @@ describe("handleNewBooking", () => {
|
||||||
organizer,
|
organizer,
|
||||||
location: BookingLocations.CalVideo,
|
location: BookingLocations.CalVideo,
|
||||||
subscriberUrl: "http://my-webhook.example.com",
|
subscriberUrl: "http://my-webhook.example.com",
|
||||||
videoCallUrl: `${WEBAPP_URL}/video/DYNAMIC_UID`,
|
videoCallUrl: `${WEBAPP_URL}/video/${createdBooking.uid}`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
timeout
|
timeout
|
||||||
|
@ -537,7 +537,7 @@ describe("handleNewBooking", () => {
|
||||||
organizer,
|
organizer,
|
||||||
location: BookingLocations.CalVideo,
|
location: BookingLocations.CalVideo,
|
||||||
subscriberUrl: "http://my-webhook.example.com",
|
subscriberUrl: "http://my-webhook.example.com",
|
||||||
videoCallUrl: `${WEBAPP_URL}/video/DYNAMIC_UID`,
|
videoCallUrl: `${WEBAPP_URL}/video/${createdBooking.uid}`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
timeout
|
timeout
|
||||||
|
@ -854,7 +854,7 @@ describe("handleNewBooking", () => {
|
||||||
organizer,
|
organizer,
|
||||||
location: BookingLocations.CalVideo,
|
location: BookingLocations.CalVideo,
|
||||||
subscriberUrl: "http://my-webhook.example.com",
|
subscriberUrl: "http://my-webhook.example.com",
|
||||||
videoCallUrl: `${WEBAPP_URL}/video/DYNAMIC_UID`,
|
videoCallUrl: `${WEBAPP_URL}/video/${createdBooking.uid}`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
timeout
|
timeout
|
||||||
|
|
26
yarn.lock
26
yarn.lock
|
@ -4576,6 +4576,7 @@ __metadata:
|
||||||
next-i18next: ^13.2.2
|
next-i18next: ^13.2.2
|
||||||
next-seo: ^6.0.0
|
next-seo: ^6.0.0
|
||||||
next-themes: ^0.2.0
|
next-themes: ^0.2.0
|
||||||
|
node-html-parser: ^6.1.10
|
||||||
nodemailer: ^6.7.8
|
nodemailer: ^6.7.8
|
||||||
otplib: ^12.0.1
|
otplib: ^12.0.1
|
||||||
postcss: ^8.4.18
|
postcss: ^8.4.18
|
||||||
|
@ -18514,6 +18515,19 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"css-select@npm:^5.1.0":
|
||||||
|
version: 5.1.0
|
||||||
|
resolution: "css-select@npm:5.1.0"
|
||||||
|
dependencies:
|
||||||
|
boolbase: ^1.0.0
|
||||||
|
css-what: ^6.1.0
|
||||||
|
domhandler: ^5.0.2
|
||||||
|
domutils: ^3.0.1
|
||||||
|
nth-check: ^2.0.1
|
||||||
|
checksum: 2772c049b188d3b8a8159907192e926e11824aea525b8282981f72ba3f349cf9ecd523fdf7734875ee2cb772246c22117fc062da105b6d59afe8dcd5c99c9bda
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"css-to-react-native@npm:^3.0.0":
|
"css-to-react-native@npm:^3.0.0":
|
||||||
version: 3.0.0
|
version: 3.0.0
|
||||||
resolution: "css-to-react-native@npm:3.0.0"
|
resolution: "css-to-react-native@npm:3.0.0"
|
||||||
|
@ -18532,7 +18546,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"css-what@npm:^6.0.1":
|
"css-what@npm:^6.0.1, css-what@npm:^6.1.0":
|
||||||
version: 6.1.0
|
version: 6.1.0
|
||||||
resolution: "css-what@npm:6.1.0"
|
resolution: "css-what@npm:6.1.0"
|
||||||
checksum: b975e547e1e90b79625918f84e67db5d33d896e6de846c9b584094e529f0c63e2ab85ee33b9daffd05bff3a146a1916bec664e18bb76dd5f66cbff9fc13b2bbe
|
checksum: b975e547e1e90b79625918f84e67db5d33d896e6de846c9b584094e529f0c63e2ab85ee33b9daffd05bff3a146a1916bec664e18bb76dd5f66cbff9fc13b2bbe
|
||||||
|
@ -29541,6 +29555,16 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"node-html-parser@npm:^6.1.10":
|
||||||
|
version: 6.1.10
|
||||||
|
resolution: "node-html-parser@npm:6.1.10"
|
||||||
|
dependencies:
|
||||||
|
css-select: ^5.1.0
|
||||||
|
he: 1.2.0
|
||||||
|
checksum: 927f6a38b3b1cbc042bce609e24fb594d3b1e0f1067ffb416a925fa5a699e907be31980f349e094d55bab706dc16a71958b08f8dcdab62faf7b12013f29442bc
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"node-ical@npm:^0.16.1":
|
"node-ical@npm:^0.16.1":
|
||||||
version: 0.16.1
|
version: 0.16.1
|
||||||
resolution: "node-ical@npm:0.16.1"
|
resolution: "node-ical@npm:0.16.1"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user