From c6125f359dc92523c382d92f66805a7cc2bbdc61 Mon Sep 17 00:00:00 2001 From: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com> Date: Fri, 6 Feb 2026 17:58:22 +0530 Subject: [PATCH] fix: event is not created on the Zoho Calendar when hideOrganizerEmail is enabled (#27675) * fix: event not created * Update CalendarManager.ts * Add tests: packages/features/calendars/lib/CalendarManager.ts Generated by Paragon from proposal for PR #27675 * Add tests: packages/features/calendars/lib/CalendarManager.test.ts Generated by Paragon from proposal for PR #27675 * Export processEvent for testing and refactor logic * Fix missing newline at end of CalendarManager.test.ts Add missing newline at the end of the file. * fix test --- .../zohocalendar/lib/CalendarService.ts | 8 +- .../calendars/lib/CalendarManager.test.ts | 192 +++++++++++++++++- .../features/calendars/lib/CalendarManager.ts | 7 +- 3 files changed, 199 insertions(+), 8 deletions(-) diff --git a/packages/app-store/zohocalendar/lib/CalendarService.ts b/packages/app-store/zohocalendar/lib/CalendarService.ts index 1e11d66ad4..e590b9c566 100644 --- a/packages/app-store/zohocalendar/lib/CalendarService.ts +++ b/packages/app-store/zohocalendar/lib/CalendarService.ts @@ -466,13 +466,13 @@ class ZohoCalendarService implements Calendar { timezone: event.organizer.timeZone, }, attendees: event.attendees.map((attendee) => ({ email: attendee.email })), - isprivate: event.seatsShowAttendees, + isprivate: event.hideCalendarEventDetails ?? false, reminders: [ { minutes: "-15", - action: "popup", - }, - ], + action: "popup", + }, + ], location: event.location ? getLocation({ videoCallData: event.videoCallData, diff --git a/packages/features/calendars/lib/CalendarManager.test.ts b/packages/features/calendars/lib/CalendarManager.test.ts index 56a49ea5b4..0c2effdb5b 100644 --- a/packages/features/calendars/lib/CalendarManager.test.ts +++ b/packages/features/calendars/lib/CalendarManager.test.ts @@ -1,13 +1,30 @@ import { prisma } from "@calcom/prisma/__mocks__/prisma"; -import { describe, expect, it, vi } from "vitest"; +import { describe, expect, it, vi, beforeEach } from "vitest"; -import { getCalendarCredentials, deduplicateCredentialsBasedOnSelectedCalendars } from "./CalendarManager"; +import { + getCalendarCredentials, + deduplicateCredentialsBasedOnSelectedCalendars, + processEvent, +} from "./CalendarManager"; vi.mock("@calcom/prisma", () => ({ prisma, })); +vi.mock("@calcom/lib/constants", () => ({ + ORGANIZER_EMAIL_EXEMPT_DOMAINS: "", + IS_PRODUCTION: false, +})); + +vi.mock("@calcom/app-store/locations", () => ({ + MeetLocationType: "integrations:google:meet", +})); + +vi.mock("@calcom/lib/CalEventParser", () => ({ + getRichDescription: vi.fn(() => "Test description"), +})); + function buildCredential(data: { type: string; appId: string; @@ -32,7 +49,178 @@ function buildCredential(data: { }; } +function buildCalendarEvent(overrides = {}) { + return { + type: "test-event", + title: "Test Event", + startTime: "2024-01-01T10:00:00Z", + endTime: "2024-01-01T11:00:00Z", + organizer: { + name: "Organizer", + email: "organizer@example.com", + timeZone: "UTC", + language: { translate: (x: string) => x, locale: "en" }, + }, + attendees: [ + { + name: "Attendee 1", + email: "attendee1@example.com", + timeZone: "UTC", + language: { translate: (x: string) => x, locale: "en" }, + }, + { + name: "Attendee 2", + email: "attendee2@example.com", + timeZone: "UTC", + language: { translate: (x: string) => x, locale: "en" }, + }, + ], + destinationCalendar: null, + hideOrganizerEmail: false, + location: null, + ...overrides, + }; +} + describe("CalendarManager tests", () => { + describe("fn: processEvent", () => { + it("should clear attendees when hideOrganizerEmail is true and no Zoho Calendar destination", () => { + const calEvent = buildCalendarEvent({ + hideOrganizerEmail: true, + destinationCalendar: [ + { + integration: "google_calendar", + externalId: "calendar-1", + }, + ], + }); + + const result = processEvent(calEvent as any); + + expect(result.attendees).toEqual([]); + }); + + it("should NOT clear attendees when hideOrganizerEmail is true and destination is Zoho Calendar", () => { + const calEvent = buildCalendarEvent({ + hideOrganizerEmail: true, + destinationCalendar: [ + { + integration: "zoho_calendar", + externalId: "calendar-1", + }, + ], + }); + + const result = processEvent(calEvent as any); + + // Zoho Calendar requires at least one attendee, so attendees should NOT be cleared + expect(result.attendees).toHaveLength(2); + expect(result.attendees[0].email).toBe("attendee1@example.com"); + expect(result.attendees[1].email).toBe("attendee2@example.com"); + }); + + it("should NOT clear attendees when hideOrganizerEmail is true and one of multiple destinations is Zoho Calendar", () => { + const calEvent = buildCalendarEvent({ + hideOrganizerEmail: true, + destinationCalendar: [ + { + integration: "google_calendar", + externalId: "google-calendar-1", + }, + { + integration: "zoho_calendar", + externalId: "zoho-calendar-1", + }, + ], + }); + + const result = processEvent(calEvent as any); + + // Zoho Calendar is in the list, so attendees should NOT be cleared + expect(result.attendees).toHaveLength(2); + }); + + it("should NOT clear attendees when hideOrganizerEmail is false", () => { + const calEvent = buildCalendarEvent({ + hideOrganizerEmail: false, + destinationCalendar: [ + { + integration: "google_calendar", + externalId: "calendar-1", + }, + ], + }); + + const result = processEvent(calEvent as any); + + expect(result.attendees).toHaveLength(2); + }); + + it("should NOT clear attendees when location is MeetLocationType even with hideOrganizerEmail true", () => { + const calEvent = buildCalendarEvent({ + hideOrganizerEmail: true, + location: "integrations:google:meet", + destinationCalendar: [ + { + integration: "google_calendar", + externalId: "calendar-1", + }, + ], + }); + + const result = processEvent(calEvent as any); + + expect(result.attendees).toHaveLength(2); + }); + + it("should handle null destinationCalendar with hideOrganizerEmail true", () => { + const calEvent = buildCalendarEvent({ + hideOrganizerEmail: true, + destinationCalendar: null, + }); + + const result = processEvent(calEvent as any); + + expect(result.attendees).toEqual([]); + }); + + it("should handle empty destinationCalendar array with hideOrganizerEmail true", () => { + const calEvent = buildCalendarEvent({ + hideOrganizerEmail: true, + destinationCalendar: [], + }); + + const result = processEvent(calEvent as any); + + expect(result.attendees).toEqual([]); + }); + + it("should include calendarDescription from getRichDescription", () => { + const calEvent = buildCalendarEvent(); + + const result = processEvent(calEvent as any); + + expect(result.calendarDescription).toBe("Test description"); + }); + + it("should clear responses for seatsPerTimeSlot events", () => { + const calEvent = buildCalendarEvent({ + seatsPerTimeSlot: 5, + responses: { field1: { label: "Field 1", value: "test" } }, + userFieldsResponses: { field1: { label: "Field 1", value: "test" } }, + additionalNotes: "Test notes", + customInputs: { input1: "value1" }, + }); + + const result = processEvent(calEvent as any); + + expect(result.responses).toBeNull(); + expect(result.userFieldsResponses).toBeNull(); + expect(result.additionalNotes).toBeNull(); + expect(result.customInputs).toBeNull(); + }); + }); + describe("fn: getCalendarCredentials", () => { it("should only return credentials for calendar apps", async () => { const googleCalendarCredentials = { diff --git a/packages/features/calendars/lib/CalendarManager.ts b/packages/features/calendars/lib/CalendarManager.ts index 64d6141167..c0422945df 100644 --- a/packages/features/calendars/lib/CalendarManager.ts +++ b/packages/features/calendars/lib/CalendarManager.ts @@ -31,7 +31,7 @@ const log = logger.getSubLogger({ prefix: ["CalendarManager"] }); /** * Process the calendar event by generating description and removing attendees if needed */ -const processEvent = (calEvent: CalendarEvent): CalendarServiceEvent => { +export const processEvent = (calEvent: CalendarEvent): CalendarServiceEvent => { if (calEvent.seatsPerTimeSlot) { calEvent.responses = null; calEvent.userFieldsResponses = null; @@ -51,7 +51,10 @@ const processEvent = (calEvent: CalendarEvent): CalendarServiceEvent => { .filter((domain) => domain.trim() !== "") .some((domain) => calEvent.organizer.email.toLowerCase().endsWith(domain.toLowerCase())); - if (calEvent.hideOrganizerEmail && !isOrganizerExempt && !isMeetLocationType) { + // Zoho Calendar requires at least one attendee, so don't empty attendees array for Zoho Calendar + const hasZohoCalendar = calEvent.destinationCalendar?.some((cal) => cal.integration === "zoho_calendar") ?? false; + + if (calEvent.hideOrganizerEmail && !isOrganizerExempt && !isMeetLocationType && !hasZohoCalendar) { calendarEvent.attendees = []; }