fix: prevent attendee phone number from overriding organizer default app location (#27620)
* se organizer default app * nit * Add tests: packages/features/bookings/lib/handleNewBooking/getBookingData.test.ts Generated by Paragon from proposal for PR #27620
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { z } from "zod";
|
||||
|
||||
import { OrganizerDefaultConferencingAppType } from "@calcom/app-store/locations";
|
||||
|
||||
import { getBookingData } from "./getBookingData";
|
||||
import type { getEventTypeResponse } from "./getEventTypesFromDB";
|
||||
|
||||
// Mock the dependencies
|
||||
vi.mock("@calcom/features/bookings/lib/getCalEventResponses", () => ({
|
||||
getCalEventResponses: vi.fn().mockReturnValue({
|
||||
userFieldsResponses: {},
|
||||
responses: {},
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/lib/sentryWrapper", () => ({
|
||||
withReporting: (fn: Function) => fn,
|
||||
}));
|
||||
|
||||
const createMockEventType = (overrides: Partial<getEventTypeResponse> = {}): getEventTypeResponse => {
|
||||
return {
|
||||
id: 1,
|
||||
length: 30,
|
||||
bookingFields: [
|
||||
{ name: "name", type: "name", required: true },
|
||||
{ name: "email", type: "email", required: true },
|
||||
{ name: "location", type: "radioInput", required: false },
|
||||
],
|
||||
customInputs: [],
|
||||
seatsPerTimeSlot: null,
|
||||
...overrides,
|
||||
} as getEventTypeResponse;
|
||||
};
|
||||
|
||||
const createMockSchema = () => {
|
||||
return z.object({
|
||||
eventTypeId: z.number(),
|
||||
start: z.string(),
|
||||
end: z.string().optional(),
|
||||
timeZone: z.string(),
|
||||
language: z.string(),
|
||||
metadata: z.record(z.string()),
|
||||
responses: z.object({
|
||||
name: z.string(),
|
||||
email: z.string(),
|
||||
location: z
|
||||
.object({
|
||||
value: z.string(),
|
||||
optionValue: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
attendeePhoneNumber: z.string().optional(),
|
||||
guests: z.array(z.string()).optional(),
|
||||
smsReminderNumber: z.string().optional(),
|
||||
notes: z.string().optional(),
|
||||
rescheduleReason: z.string().optional(),
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
describe("getBookingData", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("location extraction", () => {
|
||||
it("should ignore optionValue when location type is OrganizerDefaultConferencingAppType", async () => {
|
||||
const mockEventType = createMockEventType();
|
||||
const schema = createMockSchema();
|
||||
|
||||
const reqBody = {
|
||||
eventTypeId: 1,
|
||||
start: "2024-01-15T10:00:00.000Z",
|
||||
end: "2024-01-15T10:30:00.000Z",
|
||||
timeZone: "America/New_York",
|
||||
language: "en",
|
||||
metadata: {},
|
||||
responses: {
|
||||
name: "Test User",
|
||||
email: "test@example.com",
|
||||
location: {
|
||||
value: OrganizerDefaultConferencingAppType, // "conferencing"
|
||||
optionValue: "+15551234567", // Phone number that should be ignored
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await getBookingData({
|
||||
reqBody,
|
||||
eventType: mockEventType,
|
||||
schema,
|
||||
});
|
||||
|
||||
// The location should be the conferencing type, NOT the phone number
|
||||
expect(result.location).toBe(OrganizerDefaultConferencingAppType);
|
||||
expect(result.location).not.toBe("+15551234567");
|
||||
});
|
||||
|
||||
it("should use optionValue for non-OrganizerDefaultConferencingAppType locations", async () => {
|
||||
const mockEventType = createMockEventType();
|
||||
const schema = createMockSchema();
|
||||
|
||||
const reqBody = {
|
||||
eventTypeId: 1,
|
||||
start: "2024-01-15T10:00:00.000Z",
|
||||
end: "2024-01-15T10:30:00.000Z",
|
||||
timeZone: "America/New_York",
|
||||
language: "en",
|
||||
metadata: {},
|
||||
responses: {
|
||||
name: "Test User",
|
||||
email: "test@example.com",
|
||||
location: {
|
||||
value: "phone",
|
||||
optionValue: "+15551234567",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await getBookingData({
|
||||
reqBody,
|
||||
eventType: mockEventType,
|
||||
schema,
|
||||
});
|
||||
|
||||
// For phone location type, optionValue should be used
|
||||
expect(result.location).toBe("+15551234567");
|
||||
});
|
||||
|
||||
it("should fall back to value when optionValue is empty for non-conferencing locations", async () => {
|
||||
const mockEventType = createMockEventType();
|
||||
const schema = createMockSchema();
|
||||
|
||||
const reqBody = {
|
||||
eventTypeId: 1,
|
||||
start: "2024-01-15T10:00:00.000Z",
|
||||
end: "2024-01-15T10:30:00.000Z",
|
||||
timeZone: "America/New_York",
|
||||
language: "en",
|
||||
metadata: {},
|
||||
responses: {
|
||||
name: "Test User",
|
||||
email: "test@example.com",
|
||||
location: {
|
||||
value: "integrations:daily",
|
||||
optionValue: "",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await getBookingData({
|
||||
reqBody,
|
||||
eventType: mockEventType,
|
||||
schema,
|
||||
});
|
||||
|
||||
// Should fall back to value when optionValue is empty
|
||||
expect(result.location).toBe("integrations:daily");
|
||||
});
|
||||
|
||||
it("should return empty string when location is not provided", async () => {
|
||||
const mockEventType = createMockEventType();
|
||||
const schema = createMockSchema();
|
||||
|
||||
const reqBody = {
|
||||
eventTypeId: 1,
|
||||
start: "2024-01-15T10:00:00.000Z",
|
||||
end: "2024-01-15T10:30:00.000Z",
|
||||
timeZone: "America/New_York",
|
||||
language: "en",
|
||||
metadata: {},
|
||||
responses: {
|
||||
name: "Test User",
|
||||
email: "test@example.com",
|
||||
// No location provided
|
||||
},
|
||||
};
|
||||
|
||||
const result = await getBookingData({
|
||||
reqBody,
|
||||
eventType: mockEventType,
|
||||
schema,
|
||||
});
|
||||
|
||||
expect(result.location).toBe("");
|
||||
});
|
||||
|
||||
it("should use OrganizerDefaultConferencingAppType value even when optionValue contains meeting URL", async () => {
|
||||
const mockEventType = createMockEventType();
|
||||
const schema = createMockSchema();
|
||||
|
||||
const reqBody = {
|
||||
eventTypeId: 1,
|
||||
start: "2024-01-15T10:00:00.000Z",
|
||||
end: "2024-01-15T10:30:00.000Z",
|
||||
timeZone: "America/New_York",
|
||||
language: "en",
|
||||
metadata: {},
|
||||
responses: {
|
||||
name: "Test User",
|
||||
email: "test@example.com",
|
||||
location: {
|
||||
value: OrganizerDefaultConferencingAppType,
|
||||
optionValue: "https://meet.google.com/abc-defg-hij", // Should be ignored
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await getBookingData({
|
||||
reqBody,
|
||||
eventType: mockEventType,
|
||||
schema,
|
||||
});
|
||||
|
||||
// The location should be the conferencing type, NOT the URL
|
||||
expect(result.location).toBe(OrganizerDefaultConferencingAppType);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,11 +1,10 @@
|
||||
import type z from "zod";
|
||||
|
||||
import { OrganizerDefaultConferencingAppType } from "@calcom/app-store/locations";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
|
||||
import { ErrorCode } from "@calcom/lib/errorCodes";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
import type { EventTypeCustomInput } from "@calcom/prisma/client";
|
||||
|
||||
import type z from "zod";
|
||||
import { bookingCreateSchemaLegacyPropsForApi } from "../bookingCreateBodySchema";
|
||||
import type { TgetBookingDataSchema } from "../getBookingDataSchema";
|
||||
import type { getEventTypeResponse } from "./getEventTypesFromDB";
|
||||
@@ -66,13 +65,24 @@ const _getBookingData = async <T extends z.ZodType>({
|
||||
responses,
|
||||
seatsEnabled: !!eventType.seatsPerTimeSlot,
|
||||
});
|
||||
// Extract location value, but ignore optionValue when location is organizer's default app
|
||||
let locationValue = "";
|
||||
if (responses.location) {
|
||||
const locationType = responses.location.value;
|
||||
if (locationType === OrganizerDefaultConferencingAppType) {
|
||||
locationValue = locationType;
|
||||
} else {
|
||||
locationValue = responses.location.optionValue || locationType || "";
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...parsedBody,
|
||||
name: responses.name,
|
||||
email: responses.email,
|
||||
attendeePhoneNumber: responses.attendeePhoneNumber,
|
||||
guests: responses.guests ? responses.guests : [],
|
||||
location: responses.location?.optionValue || responses.location?.value || "",
|
||||
location: locationValue,
|
||||
smsReminderNumber: responses.smsReminderNumber,
|
||||
notes: responses.notes || "",
|
||||
calEventUserFieldsResponses,
|
||||
|
||||
Reference in New Issue
Block a user