test: add unit tests to various important fns in routing flow (#21902)

* Add unit tests to routing flow

* fix: resolve TypeScript errors in routing forms test files

- Fix mock form structure to match TargetRoutingFormForResponse type
- Add missing properties like selectText, deleted, updatedById to form fields
- Correct route action types to use valid RouteActionType enum values
- Fix response objects to include required label property
- Update mock return values for findTeamMembersMatchingAttributeLogic
- Use proper type assertions for complex route objects

Co-Authored-By: hariom@cal.com <hariom@cal.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: hariom@cal.com <hariom@cal.com>
This commit is contained in:
Hariom Balhara
2025-06-18 14:14:14 +00:00
committed by GitHub
co-authored by Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> hariom@cal.com <hariom@cal.com>
parent 916ed13bea
commit bbcd0b7e78
5 changed files with 767 additions and 1 deletions
@@ -0,0 +1,309 @@
import "@calcom/lib/__mocks__/logger";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { z } from "zod";
import { findTeamMembersMatchingAttributeLogic } from "@calcom/lib/raqb/findTeamMembersMatchingAttributeLogic";
import { RoutingFormResponseRepository } from "@calcom/lib/server/repository/formResponse";
import type { ZResponseInputSchema } from "@calcom/trpc/server/routers/viewer/routing-forms/response.schema";
import isRouter from "../lib/isRouter";
import routerGetCrmContactOwnerEmail from "./crmRouting/routerGetCrmContactOwnerEmail";
import type { TargetRoutingFormForResponse } from "./formSubmissionUtils";
import { onSubmissionOfFormResponse } from "./formSubmissionUtils";
import { handleResponse } from "./handleResponse";
vi.mock("@calcom/lib/raqb/findTeamMembersMatchingAttributeLogic", () => ({
findTeamMembersMatchingAttributeLogic: vi.fn(),
}));
vi.mock("@calcom/lib/server/repository/formResponse", () => ({
RoutingFormResponseRepository: {
recordQueuedFormResponse: vi.fn(),
recordFormResponse: vi.fn(),
},
}));
vi.mock("./crmRouting/routerGetCrmContactOwnerEmail", () => ({
default: vi.fn(),
}));
vi.mock("./formSubmissionUtils", () => ({
onSubmissionOfFormResponse: vi.fn(),
}));
vi.mock("../lib/isRouter", () => ({
default: vi.fn(),
}));
vi.mock("@calcom/lib/sentryWrapper", () => ({
withReporting: (fn: unknown) => fn,
}));
const mockForm: TargetRoutingFormForResponse = {
id: "form-id",
name: "Test Form",
fields: [
{
id: "name",
label: "Name",
type: "text" as const,
required: true,
identifier: "name",
placeholder: undefined,
selectText: undefined,
deleted: false,
},
{
id: "email",
label: "Email",
type: "email" as const,
required: true,
identifier: "email",
placeholder: undefined,
selectText: undefined,
deleted: false,
},
{
id: "guests",
label: "Guests",
type: "text" as const,
required: false,
identifier: "guests",
placeholder: undefined,
selectText: undefined,
deleted: false,
},
],
routes: [],
userId: 1,
teamId: 1,
user: {
id: 1,
email: "test@example.com",
},
team: {
parentId: 2,
},
createdAt: "2023-01-01T00:00:00.000Z",
updatedAt: "2023-01-01T00:00:00.000Z",
description: null,
disabled: false,
settings: {
emailOwnerOnSubmission: false,
},
connectedForms: [],
routers: [],
teamMembers: [],
position: 0,
updatedById: null,
};
const mockResponse: z.infer<typeof ZResponseInputSchema>["response"] = {
name: { value: "John Doe", label: "Name" },
email: { value: "john.doe@example.com", label: "Email" },
};
describe("handleResponse", () => {
beforeEach(() => {
vi.resetAllMocks();
});
it("should throw a TRPCError for missing required fields", async () => {
await expect(
handleResponse({
response: { email: { value: "test@test.com", label: "Email" } }, // Name is missing
form: mockForm,
formFillerId: "user1",
chosenRouteId: null,
isPreview: false,
})
).rejects.toThrow(/Missing required fields Name/);
});
it("should throw a TRPCError for invalid email", async () => {
await expect(
handleResponse({
response: {
name: { value: "John Doe", label: "Name" },
email: { value: "invalid-email", label: "Email" },
},
form: mockForm,
formFillerId: "user1",
chosenRouteId: null,
isPreview: false,
})
).rejects.toThrow(/Invalid value for fields 'Email' with value 'invalid-email' should be valid email/);
});
it("should record form response when not in preview and not queued", async () => {
const dbFormResponse = {
id: 1,
formId: mockForm.id,
response: mockResponse,
chosenRouteId: null,
formFillerId: "user1",
routedToBookingUid: null,
createdAt: new Date(),
updatedAt: new Date(),
};
vi.mocked(RoutingFormResponseRepository.recordFormResponse).mockResolvedValue(dbFormResponse);
const result = await handleResponse({
response: mockResponse,
form: mockForm,
formFillerId: "user1",
chosenRouteId: null,
isPreview: false,
});
expect(RoutingFormResponseRepository.recordFormResponse).toHaveBeenCalledWith({
formId: mockForm.id,
response: mockResponse,
chosenRouteId: null,
});
expect(onSubmissionOfFormResponse).toHaveBeenCalledWith({
form: mockForm,
formResponseInDb: dbFormResponse,
chosenRouteAction: null,
});
expect(result.formResponse).toEqual(dbFormResponse);
expect(result.queuedFormResponse).toBeNull();
});
it("should queue form response when queueFormResponse is true", async () => {
const queuedResponse = {
id: "queued-id",
formId: mockForm.id,
response: mockResponse,
chosenRouteId: null,
actualResponseId: null,
createdAt: new Date(),
updatedAt: new Date(),
};
vi.mocked(RoutingFormResponseRepository.recordQueuedFormResponse).mockResolvedValue(queuedResponse);
const result = await handleResponse({
response: mockResponse,
form: mockForm,
formFillerId: "user1",
chosenRouteId: null,
isPreview: false,
queueFormResponse: true,
});
expect(RoutingFormResponseRepository.recordQueuedFormResponse).toHaveBeenCalledWith({
formId: mockForm.id,
response: mockResponse,
chosenRouteId: null,
});
expect(RoutingFormResponseRepository.recordFormResponse).not.toHaveBeenCalled();
expect(onSubmissionOfFormResponse).not.toHaveBeenCalled();
expect(result.queuedFormResponse).toEqual(queuedResponse);
expect(result.formResponse).toBeNull();
});
describe("Preview mode", () => {
it("should send formResponse with id=0 and quueueFormResponse isn't true", async () => {
const result = await handleResponse({
response: mockResponse,
form: mockForm,
formFillerId: "user1",
chosenRouteId: null,
isPreview: true,
});
expect(RoutingFormResponseRepository.recordFormResponse).not.toHaveBeenCalled();
expect(onSubmissionOfFormResponse).not.toHaveBeenCalled();
expect(result.isPreview).toBe(true);
expect(result.formResponse).toBeDefined();
expect(result.formResponse?.id).toBe(0);
});
it("should send queuedFormResponse with id=00000000-0000-0000-0000-000000000000 when queueFormResponse is true", async () => {
const result = await handleResponse({
response: mockResponse,
form: mockForm,
formFillerId: "user1",
chosenRouteId: null,
isPreview: true,
queueFormResponse: true,
});
expect(RoutingFormResponseRepository.recordQueuedFormResponse).not.toHaveBeenCalled();
expect(result.isPreview).toBe(true);
expect(result.queuedFormResponse).toBeDefined();
expect(result.queuedFormResponse?.id).toBe("00000000-0000-0000-0000-000000000000");
expect(result.formResponse).toBeUndefined();
});
});
it("should handle chosen route and call routing logic", async () => {
const chosenRoute = {
id: "route1",
queryValue: { type: "group", children1: {} } as any,
action: {
type: "customPageMessage" as const,
value: "Thank you for your submission!",
},
attributeRoutingConfig: null,
attributesQueryValue: { type: "group", children1: {} } as any,
fallbackAttributesQueryValue: { type: "group", children1: {} } as any,
isFallback: false,
};
const formWithRoute: TargetRoutingFormForResponse = { ...mockForm, routes: [chosenRoute as any] };
vi.mocked(routerGetCrmContactOwnerEmail).mockResolvedValue({
email: "owner@example.com",
recordType: "contact",
crmAppSlug: "hubspot",
});
vi.mocked(findTeamMembersMatchingAttributeLogic).mockResolvedValue({
teamMembersMatchingAttributeLogic: [{ userId: 123, result: "MATCH" as any }],
checkedFallback: false,
fallbackAttributeLogicBuildingWarnings: [],
mainAttributeLogicBuildingWarnings: [],
timeTaken: {
ttGetAttributesForLogic: 50,
},
});
const result = await handleResponse({
response: mockResponse,
form: formWithRoute,
formFillerId: "user1",
chosenRouteId: "route1",
isPreview: false,
});
expect(routerGetCrmContactOwnerEmail).toHaveBeenCalled();
expect(findTeamMembersMatchingAttributeLogic).toHaveBeenCalled();
expect(result.crmContactOwnerEmail).toBe("owner@example.com");
expect(result.teamMembersMatchingAttributeLogic).toEqual([123]);
});
it("should throw error if chosen route is a router", async () => {
const chosenRoute = {
id: "route1",
name: "A router",
description: "Router description",
isRouter: true as const,
routes: [],
};
vi.mocked(isRouter).mockReturnValue(true);
const formWithRoute: TargetRoutingFormForResponse = {
...mockForm,
routes: [chosenRoute as any],
};
await expect(
handleResponse({
response: mockResponse,
form: formWithRoute,
formFillerId: "user1",
chosenRouteId: "route1",
isPreview: false,
})
).rejects.toThrow(/Chosen route is a router/);
});
});
@@ -3,6 +3,15 @@ import slugify from "@calcom/lib/slugify";
import type { FormResponse, NonRouterRoute, Field } from "../types/types";
import getFieldIdentifier from "./getFieldIdentifier";
/**
* Substitues variables in the target URL identified by routeValue with values from response
* e.g. {firstName} is replaced with value of the field with identifier firstName
*
* @param routeValue - The target URL with variables to be substituted
* @param response - The form response containing the values to be substituted
* @param fields - The fields of the form
* @returns The URL with variables substituted
*/
export const substituteVariables = (
routeValue: NonRouterRoute["action"]["value"],
response: FormResponse,
@@ -0,0 +1,168 @@
import "@calcom/lib/__mocks__/logger";
import { describe, it, vi, expect, beforeEach, afterEach } from "vitest";
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
import { sendGenericWebhookPayload } from "@calcom/features/webhooks/lib/sendPayload";
import { WebhookTriggerEvents } from "@calcom/prisma/client";
import { _onFormSubmission } from "./utils";
// Mock dependencies
vi.mock("@calcom/lib/getOrgIdFromMemberOrTeamId", () => ({
default: vi.fn(() => Promise.resolve(1)),
}));
vi.mock("@calcom/features/webhooks/lib/getWebhooks", () => ({
default: vi.fn(() => Promise.resolve([])),
}));
vi.mock("@calcom/features/webhooks/lib/sendPayload", () => ({
sendGenericWebhookPayload: vi.fn(() => Promise.resolve()),
}));
vi.mock("@calcom/features/tasker", () => {
const tasker = {
create: vi.fn(() => Promise.resolve()),
};
return { default: Promise.resolve(tasker) };
});
const mockSendEmail = vi.fn(() => Promise.resolve());
const mockResponseEmailConstructor = vi.fn();
vi.mock("../emails/templates/response-email", () => ({
default: class MockResponseEmail {
sendEmail = mockSendEmail;
constructor(...args: unknown[]) {
mockResponseEmailConstructor(...args);
}
},
}));
describe("_onFormSubmission", () => {
const mockForm = {
id: "form-1",
name: "Test Form",
fields: [
{ id: "field-1", identifier: "email", label: "Email", type: "email" },
{ id: "field-2", identifier: "name", label: "Name", type: "text" },
],
user: { id: 1, email: "test@example.com" },
teamId: null,
settings: { emailOwnerOnSubmission: true },
};
const mockResponse = {
"field-1": { label: "Email", value: "test@response.com" },
"field-2": { label: "Name", value: "Test Name" },
};
const responseId = 123;
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.restoreAllMocks();
});
describe("Webhooks", () => {
it("should call FORM_SUBMITTED webhooks", async () => {
vi.mocked(getWebhooks).mockResolvedValueOnce([{ id: "wh-1", secret: "secret" } as any]);
await _onFormSubmission(mockForm as any, mockResponse, responseId);
expect(getWebhooks).toHaveBeenCalledWith({
userId: 1,
teamId: null,
orgId: 1,
triggerEvent: WebhookTriggerEvents.FORM_SUBMITTED,
});
expect(sendGenericWebhookPayload).toHaveBeenCalledTimes(1);
});
it("should schedule FORM_SUBMITTED_NO_EVENT webhooks via tasker", async () => {
const tasker = await (await import("@calcom/features/tasker")).default;
const mockWebhook = { id: "wh-no-event-1", secret: "secret" };
const chosenAction = { type: "customPageMessage" as const, value: "test" };
vi.mocked(getWebhooks).mockImplementation(async (options) => {
if (options.triggerEvent === WebhookTriggerEvents.FORM_SUBMITTED_NO_EVENT) {
return [mockWebhook as any];
}
return [];
});
await _onFormSubmission(mockForm as any, mockResponse, responseId, chosenAction);
expect(getWebhooks).toHaveBeenCalledWith(
expect.objectContaining({
triggerEvent: WebhookTriggerEvents.FORM_SUBMITTED_NO_EVENT,
})
);
expect(tasker.create).toHaveBeenCalledWith(
"triggerFormSubmittedNoEventWebhook",
{
responseId,
form: mockForm,
responses: {
email: {
value: "test@response.com",
response: "test@response.com",
},
name: { value: "Test Name", response: "Test Name" },
},
redirect: chosenAction,
webhook: mockWebhook,
},
{ scheduledAt: expect.any(Date) }
);
});
});
describe("Response Email", () => {
it("should send response email to team members for a team form", async () => {
const teamForm = {
...mockForm,
teamId: 1,
userWithEmails: ["team-member1@example.com", "team-member2@example.com"],
user: { id: 1, email: "test@example.com" },
};
await _onFormSubmission(teamForm as any, mockResponse, responseId);
expect(mockResponseEmailConstructor).toHaveBeenCalledWith({
form: teamForm,
toAddresses: ["team-member1@example.com", "team-member2@example.com"],
orderedResponses: [mockResponse["field-1"], mockResponse["field-2"]],
});
expect(mockSendEmail).toHaveBeenCalled();
});
it("should send response email to owner when enabled", async () => {
const ownerForm = {
...mockForm,
settings: { emailOwnerOnSubmission: true },
};
await _onFormSubmission(ownerForm as any, mockResponse, responseId);
expect(mockResponseEmailConstructor).toHaveBeenCalledWith({
form: ownerForm,
toAddresses: [ownerForm.user.email],
orderedResponses: [mockResponse["field-1"], mockResponse["field-2"]],
});
expect(mockSendEmail).toHaveBeenCalled();
});
it("should not send response email to owner when disabled", async () => {
const ownerForm = {
...mockForm,
settings: { emailOwnerOnSubmission: false },
};
await _onFormSubmission(ownerForm as any, mockResponse, responseId);
expect(mockResponseEmailConstructor).not.toHaveBeenCalled();
expect(mockSendEmail).not.toHaveBeenCalled();
});
});
});
@@ -85,7 +85,7 @@ export function getFieldResponse({
* Not called in preview mode or dry run mode
* It takes care of sending webhooks and emails for form submissions
*/
async function _onFormSubmission(
export async function _onFormSubmission(
form: Ensure<
SerializableForm<App_RoutingForms_Form> & { user: Pick<User, "id" | "email">; userWithEmails?: string[] },
"fields"
+280
View File
@@ -0,0 +1,280 @@
import "@calcom/lib/__mocks__/logger";
import type { GetServerSidePropsContext } from "next";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { getAbsoluteEventTypeRedirectUrlWithEmbedSupport } from "@calcom/app-store/routing-forms/getEventTypeRedirectUrl";
import { getResponseToStore } from "@calcom/app-store/routing-forms/lib/getResponseToStore";
import { getSerializableForm } from "@calcom/app-store/routing-forms/lib/getSerializableForm";
import { handleResponse } from "@calcom/app-store/routing-forms/lib/handleResponse";
import { findMatchingRoute } from "@calcom/app-store/routing-forms/lib/processRoute";
import { substituteVariables } from "@calcom/app-store/routing-forms/lib/substituteVariables";
import { getUrlSearchParamsToForward } from "@calcom/app-store/routing-forms/pages/routing-link/getUrlSearchParamsToForward";
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
import { isAuthorizedToViewFormOnOrgDomain } from "@calcom/features/routing-forms/lib/isAuthorizedToViewForm";
import { RoutingFormRepository } from "@calcom/lib/server/repository/routingForm";
import { UserRepository } from "@calcom/lib/server/repository/user";
import { getRoutedUrl } from "./getRoutedUrl";
// Mock dependencies
vi.mock("@calcom/app-store/routing-forms/lib/handleResponse");
vi.mock("@calcom/lib/server/repository/routingForm");
vi.mock("@calcom/lib/server/repository/user");
vi.mock("@calcom/features/ee/organizations/lib/orgDomains");
vi.mock("@calcom/features/routing-forms/lib/isAuthorizedToViewForm");
vi.mock("@calcom/app-store/routing-forms/lib/getSerializableForm");
vi.mock("@calcom/app-store/routing-forms/lib/getResponseToStore");
vi.mock("@calcom/app-store/routing-forms/lib/processRoute");
vi.mock("@calcom/app-store/routing-forms/lib/substituteVariables");
vi.mock("@calcom/app-store/routing-forms/pages/routing-link/getUrlSearchParamsToForward");
vi.mock("@calcom/app-store/routing-forms/getEventTypeRedirectUrl");
vi.mock("@calcom/app-store/routing-forms/enrichFormWithMigrationData", () => ({
enrichFormWithMigrationData: vi.fn((form) => form),
}));
vi.mock("@calcom/lib/sentryWrapper", () => ({
withReporting: (fn: unknown) => fn,
}));
const mockForm = {
id: "form-id",
user: { id: 1, name: "Test User" },
team: null,
name: "Test Form",
fields: [],
routes: [],
userId: 1,
teamId: null,
createdAt: new Date(),
updatedAt: new Date(),
description: null,
enabled: true,
isDefault: false,
fieldsOrder: [],
position: 0,
slug: "test-form",
};
const mockSerializableForm = {
id: "form-id",
fields: [{ id: "email", type: "email", label: "Email", identifier: "email" }],
routes: [],
user: { id: 1 },
};
const mockContext = (
query: Record<string, unknown>,
url = "/link/form-id"
): Pick<GetServerSidePropsContext, "query" | "req"> => ({
query: { form: "form-id", ...query },
req: { url },
});
describe("getRoutedUrl", () => {
beforeEach(() => {
vi.resetAllMocks();
// Provide default mock implementations
vi.mocked(orgDomainConfig).mockReturnValue({ currentOrgDomain: null });
vi.mocked(RoutingFormRepository.findFormByIdIncludeUserTeamAndOrg).mockResolvedValue(null);
vi.mocked(UserRepository.enrichUserWithItsProfile).mockImplementation(async ({ user }) => user);
vi.mocked(isAuthorizedToViewFormOnOrgDomain).mockReturnValue(true);
vi.mocked(getSerializableForm).mockResolvedValue(mockSerializableForm as never);
vi.mocked(findMatchingRoute).mockReturnValue(null);
vi.mocked(handleResponse).mockResolvedValue({
teamMembersMatchingAttributeLogic: null,
formResponse: { id: 1 },
queuedFormResponse: null,
attributeRoutingConfig: null,
timeTaken: {},
crmContactOwnerEmail: null,
crmContactOwnerRecordType: null,
crmAppSlug: null,
isPreview: false,
});
vi.mocked(substituteVariables).mockImplementation((value) => value);
vi.mocked(getUrlSearchParamsToForward).mockReturnValue(new URLSearchParams());
vi.mocked(getAbsoluteEventTypeRedirectUrlWithEmbedSupport).mockImplementation(
({ eventTypeRedirectUrl }) => eventTypeRedirectUrl
);
vi.mocked(getResponseToStore).mockReturnValue({ email: "test@cal.com" });
});
it("should return notFound if query params are invalid", async () => {
const context = {
query: { wrong_param: "value" }, // 'form' is missing
req: { url: "/link/form-id" },
};
// @ts-expect-error we are testing invalid input
const result = await getRoutedUrl(context);
expect(result).toEqual({ notFound: true });
});
it("should return notFound if form is not found", async () => {
vi.mocked(RoutingFormRepository.findFormByIdIncludeUserTeamAndOrg).mockResolvedValue(null);
const context = mockContext({});
const result = await getRoutedUrl(context);
expect(result).toEqual({ notFound: true });
expect(RoutingFormRepository.findFormByIdIncludeUserTeamAndOrg).toHaveBeenCalledWith("form-id");
});
it("should return notFound if user is not authorized", async () => {
vi.mocked(RoutingFormRepository.findFormByIdIncludeUserTeamAndOrg).mockResolvedValue(mockForm as never);
vi.mocked(isAuthorizedToViewFormOnOrgDomain).mockReturnValue(false);
const context = mockContext({});
const result = await getRoutedUrl(context);
expect(result).toEqual({ notFound: true });
expect(isAuthorizedToViewFormOnOrgDomain).toHaveBeenCalledWith({
user: mockForm.user,
team: mockForm.team,
currentOrgDomain: null,
});
});
it("should throw an error if no matching route is found", async () => {
vi.mocked(RoutingFormRepository.findFormByIdIncludeUserTeamAndOrg).mockResolvedValue(mockForm as never);
vi.mocked(getSerializableForm).mockResolvedValue(mockSerializableForm as never);
vi.mocked(findMatchingRoute).mockReturnValue(null);
const context = mockContext({});
await expect(getRoutedUrl(context)).rejects.toThrow("No matching route could be found");
expect(findMatchingRoute).toHaveBeenCalledWith(
expect.objectContaining({
form: mockSerializableForm,
})
);
});
it("should return props with a message for customPageMessage action", async () => {
vi.mocked(RoutingFormRepository.findFormByIdIncludeUserTeamAndOrg).mockResolvedValue(mockForm as never);
const message = "This is a custom message";
const mockRoute = { id: "route1", action: { type: "customPageMessage", value: message } };
vi.mocked(findMatchingRoute).mockReturnValue(mockRoute as never);
const context = mockContext({});
const result = await getRoutedUrl(context);
expect(result).toEqual({
props: {
isEmbed: false,
form: mockSerializableForm,
message: message,
errorMessage: null,
},
});
expect(handleResponse).toHaveBeenCalledWith(
expect.objectContaining({
form: mockSerializableForm,
})
);
expect(findMatchingRoute).toHaveBeenCalledWith(
expect.objectContaining({
form: mockSerializableForm,
})
);
});
it("should return a redirect for eventTypeRedirectUrl action and substitute variables", async () => {
vi.mocked(RoutingFormRepository.findFormByIdIncludeUserTeamAndOrg).mockResolvedValue(mockForm as never);
const redirectUrl = "test-user/30min/{email}";
const mockRoute = { id: "route1", action: { type: "eventTypeRedirectUrl", value: redirectUrl } };
vi.mocked(findMatchingRoute).mockReturnValue(mockRoute as never);
const substitutedUrl = "test-user/30min/test@cal.com";
vi.mocked(substituteVariables).mockReturnValue(substitutedUrl);
vi.mocked(getAbsoluteEventTypeRedirectUrlWithEmbedSupport).mockReturnValue(`/${substitutedUrl}`);
const context = mockContext({ email: "test@cal.com" });
const result = await getRoutedUrl(context);
expect(substituteVariables).toHaveBeenCalledWith(
redirectUrl,
{ email: "test@cal.com" },
mockSerializableForm.fields
);
expect(getUrlSearchParamsToForward).toHaveBeenCalledWith(
expect.objectContaining({
searchParams: expect.any(URLSearchParams),
formResponse: { email: "test@cal.com" },
fields: mockSerializableForm.fields,
})
);
expect(getAbsoluteEventTypeRedirectUrlWithEmbedSupport).toHaveBeenCalledWith(
expect.objectContaining({
eventTypeRedirectUrl: substitutedUrl,
})
);
expect(result).toEqual({
redirect: {
destination: `/${substitutedUrl}`,
permanent: false,
},
});
});
it("should return a redirect for externalRedirectUrl action", async () => {
vi.mocked(RoutingFormRepository.findFormByIdIncludeUserTeamAndOrg).mockResolvedValue(mockForm as never);
const redirectUrl = "https://example.com";
const mockRoute = { id: "route1", action: { type: "externalRedirectUrl", value: redirectUrl } };
vi.mocked(findMatchingRoute).mockReturnValue(mockRoute as never);
const context = mockContext({ email: "test@cal.com" });
const result = await getRoutedUrl(context);
const searchParams = new URLSearchParams({
...(context.query as Record<string, string>),
"cal.action": "externalRedirectUrl",
});
expect(result).toEqual({
redirect: {
destination: `${redirectUrl}?${searchParams.toString()}`,
permanent: false,
},
});
expect(findMatchingRoute).toHaveBeenCalledWith(
expect.objectContaining({
form: mockSerializableForm,
})
);
});
it("should call handleResponse with correct response prop obtained from getResponseToStore", async () => {
vi.mocked(RoutingFormRepository.findFormByIdIncludeUserTeamAndOrg).mockResolvedValue(mockForm as never);
const redirectUrl = "test-user/30min";
const mockRoute = { id: "route1", action: { type: "eventTypeRedirectUrl", value: redirectUrl } };
vi.mocked(findMatchingRoute).mockReturnValue(mockRoute as never);
vi.mocked(getAbsoluteEventTypeRedirectUrlWithEmbedSupport).mockReturnValue(`/${redirectUrl}`);
vi.mocked(getResponseToStore).mockReturnValue({ "xxx-xxx": { value: "test@cal.com" } } as never);
const context = mockContext({ email: "test@cal.com" });
await getRoutedUrl(context);
expect(getResponseToStore).toHaveBeenCalledWith({
formFields: mockSerializableForm.fields,
fieldsResponses: { email: "test@cal.com" },
});
expect(handleResponse).toHaveBeenCalledWith(
expect.objectContaining({ response: { "xxx-xxx": { value: "test@cal.com" } } })
);
});
describe("Dry Run", () => {
it("should call handleResponse with isPreview:true", async () => {
vi.mocked(RoutingFormRepository.findFormByIdIncludeUserTeamAndOrg).mockResolvedValue(mockForm as never);
const redirectUrl = "test-user/30min";
const mockRoute = { id: "route1", action: { type: "eventTypeRedirectUrl", value: redirectUrl } };
vi.mocked(findMatchingRoute).mockReturnValue(mockRoute as never);
vi.mocked(getAbsoluteEventTypeRedirectUrlWithEmbedSupport).mockReturnValue(`/${redirectUrl}`);
const context = mockContext({ "cal.isBookingDryRun": "true" });
await getRoutedUrl(context);
expect(handleResponse).toHaveBeenCalledWith(
expect.objectContaining({
isPreview: true,
})
);
});
});
});