Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 130 additions & 26 deletions packages/lib/CalendarService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,22 @@ import type {
TeamMember,
} from "@calcom/types/Calendar";
import type { CredentialPayload } from "@calcom/types/Credential";
import { ErrorCode } from "@calcom/lib/errorCodes";
import { ErrorWithCode } from "@calcom/lib/errors";
import ICAL from "ical.js";
import type { Attendee, DateArray, DurationObject } from "ics";
import { createEvent } from "ics";
import type { DAVAccount, DAVCalendar, DAVObject } from "tsdav";
import type { DAVAccount, DAVCalendar, DAVObject, DAVClient } from "tsdav";
import {
createAccount,
createCalendarObject,
createDAVClient,
deleteCalendarObject,
fetchCalendarObjects,
fetchCalendars,
getBasicAuthHeaders,
updateCalendarObject,
DAVNamespace,
} from "tsdav";
import { v4 as uuidv4 } from "uuid";
import { getLocation, getRichDescription } from "./CalEventParser";
Expand Down Expand Up @@ -392,6 +396,7 @@ export default abstract class BaseCalendarService implements Calendar {
protected integrationName = "";
private log: typeof logger;
private credential: CredentialPayload;
private client?: DAVClient;

constructor(credential: CredentialPayload, integrationName: string, url?: string) {
this.integrationName = integrationName;
Expand Down Expand Up @@ -463,23 +468,47 @@ export default abstract class BaseCalendarService implements Calendar {
: undefined;

// We create the event directly on iCal
// 1. If a specific destination calendar is requested, use it.
// 2. Else prefer the calendar identified as CALDAV:schedule-default-calendar-URL (RFC 6638 Section 9.2).
// 3. Else fall back to the first available calendar and log for debuggability.
let targetCalendars: IntegrationCalendar[];

if (mainHostDestinationCalendar?.externalId) {
targetCalendars = calendars.filter((c) => c.externalId === mainHostDestinationCalendar.externalId);
} else {
const defaultCalendar = calendars.find((c) => c.isDefault);
if (defaultCalendar) {
targetCalendars = [defaultCalendar];
this.log.debug(`CalDAV: Using schedule-default-calendar-URL calendar: ${defaultCalendar.externalId}`);
} else {
// RFC does not guarantee PROPFIND response order — log the fallback choice
targetCalendars = calendars.slice(0, 1);
this.log.warn(
`CalDAV: No default calendar found via schedule-default-calendar-URL. ` +
`Falling back to first available calendar: ${targetCalendars[0]?.externalId ?? "none"}`
);
}
}

if (targetCalendars.length === 0) {
// TODO: consider adding ErrorCode.NoTargetCalendarsFound for a more specific error
throw new ErrorWithCode(
ErrorCode.InternalServerError,
"No target calendars found to create CalDAV calendar entry"
);
}

const responses = await Promise.all(
calendars
.filter((c) =>
mainHostDestinationCalendar?.externalId
? c.externalId === mainHostDestinationCalendar.externalId
: true
)
.map((calendar) =>
createCalendarObject({
calendar: {
url: calendar.externalId,
},
filename: `${uid}.ics`,
iCalString: injectScheduleAgent(iCalStringWithTimezone),
headers: this.headers,
})
)
targetCalendars.map((calendar) =>
createCalendarObject({
calendar: {
url: calendar.externalId,
},
filename: `${uid}.ics`,
iCalString: injectScheduleAgent(iCalStringWithTimezone),
headers: this.headers,
})
)
);

if (responses.some((r) => !r.ok)) {
Expand Down Expand Up @@ -560,7 +589,7 @@ export default abstract class BaseCalendarService implements Calendar {
if (response.status >= 200 && response.status < 300) {
return {
uid,
type: this.credentials.type,
type: this.credential.type,
id: typeof calendarEvent.uid === "string" ? calendarEvent.uid : "-1",
password: "",
url: calendarEvent.url,
Expand Down Expand Up @@ -665,7 +694,7 @@ export default abstract class BaseCalendarService implements Calendar {

const userId = this.getUserId(selectedCalendars);
// we use the userId from selectedCalendars to fetch the user's timeZone from the database primarily for all-day events without any timezone information
const userTimeZone = userId ? await this.getUserTimezoneFromDB(userId) : "Europe/London";
const userTimeZone = userId ? (await this.getUserTimezoneFromDB(userId)) ?? "Europe/London" : "Europe/London";
const events: { start: string; end: string }[] = [];
objects.forEach((object) => {
if (!object || object.data == null || JSON.stringify(object.data) == "{}") return;
Expand Down Expand Up @@ -817,27 +846,38 @@ export default abstract class BaseCalendarService implements Calendar {

async listCalendars(event?: CalendarEvent): Promise<IntegrationCalendar[]> {
try {
const account = await this.getAccount();
const client = await this.getClient();

const calendars = (await fetchCalendars({
account,
headers: this.headers,
})) /** @url https://github.com/natelindev/tsdav/pull/139 */ as (Omit<DAVCalendar, "displayName"> & {
const calendars = (await client.fetchCalendars()) /** @url https://github.com/natelindev/tsdav/pull/139 */ as (Omit<
DAVCalendar,
"displayName"
> & {
displayName?: string | Record<string, unknown>;
})[];

// Attempt to resolve the scheduling default calendar URL from the principal.
// This implements RFC 6638 Section 9.2: default calendar should be identified
// via the CALDAV:schedule-default-calendar-URL property.
const defaultCalendarUrl = await this.resolveDefaultCalendarUrl(client);

return calendars.reduce<IntegrationCalendar[]>((newCalendars, calendar) => {
if (!calendar.components?.includes("VEVENT")) return newCalendars;
const [mainHostDestinationCalendar] = event?.destinationCalendar ?? [];

const isDefault = defaultCalendarUrl
? calendar.url === defaultCalendarUrl
: !!(calendar as any).props?.["schedule-default-calendar-URL"]; // fallback: check on the calendar object itself

Comment on lines +867 to +870
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm tsdav exposes a typed propfind response we can lean on
rg -nP 'propfind' node_modules/tsdav/dist 2>/dev/null | head -20 || \
  rg -nP 'propfind' --type=ts -C2 | head -40

Repository: calcom/cal.diy

Length of output: 40


🏁 Script executed:

# First, let's look at the CalendarService.ts file at the flagged lines
head -900 packages/lib/CalendarService.ts | tail -50

Repository: calcom/cal.diy

Length of output: 2569


🏁 Script executed:

# Also check the broader context around line 1099 and 1115
sed -n '1085,1130p' packages/lib/CalendarService.ts

Repository: calcom/cal.diy

Length of output: 1538


🏁 Script executed:

# Check what tsdav exports related to propfind or types
find node_modules/tsdav -name "*.d.ts" 2>/dev/null | head -5 || \
  find node_modules/tsdav -name "index.ts" -o -name "types.ts" 2>/dev/null

Repository: calcom/cal.diy

Length of output: 40


🏁 Script executed:

# Search for type definitions in tsdav
cat node_modules/tsdav/package.json | grep -E '"types"|"main"|"exports"' 2>/dev/null || echo "Check tsdav structure"

Repository: calcom/cal.diy

Length of output: 160


🏁 Script executed:

# Look for propfind related types in tsdav dist
find node_modules/tsdav/dist -type f -name "*.d.ts" 2>/dev/null | head -10

Repository: calcom/cal.diy

Length of output: 40


🏁 Script executed:

# Check imports and type definitions at top of CalendarService.ts
head -100 packages/lib/CalendarService.ts

Repository: calcom/cal.diy

Length of output: 3442


🏁 Script executed:

# Search for DAVCalendar type usage and related types in the file
rg -n "type |interface " packages/lib/CalendarService.ts | head -20

Repository: calcom/cal.diy

Length of output: 414


🏁 Script executed:

# Check if tsdav is listed in package.json dependencies
rg -A 5 -B 5 "tsdav" package.json

Repository: calcom/cal.diy

Length of output: 40


Remove as any casts — define a type-safe shape for propfind responses.

The code uses as any at lines 869, 1099, and 1115 to access props on tsdav responses, directly violating the guideline "Never use as any - use proper type-safe solutions instead."

Define a local type to capture the expected shape of the propfind response instead:

type PropfindPropsResponse = {
  props?: Record<string, { href?: string } | undefined>;
};

// At line 1099
const defaultUrl = (principalProps?.[0] as PropfindPropsResponse)?.props?.["schedule-default-calendar-URL"]?.href;

// At line 1115
const fallbackUrl = (addressSetProps?.[0] as PropfindPropsResponse)?.props?.["calendar-user-address-set"]?.href;

// At line 869
const isDefault = defaultCalendarUrl
  ? calendar.url === defaultCalendarUrl
  : !!(calendar as PropfindPropsResponse).props?.["schedule-default-calendar-URL"];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/lib/CalendarService.ts` around lines 867 - 870, Create a small,
type-safe local interface (e.g., PropfindPropsResponse with optional props:
Record<string, { href?: string } | undefined>) and replace all `as any` casts by
casting to that interface where the code reads `props` on tsdav responses:
replace `(calendar as any).props` in the isDefault computation,
`(principalProps?.[0] as any)` when reading `schedule-default-calendar-URL` and
`(addressSetProps?.[0] as any)` when reading `calendar-user-address-set` with
the new PropfindPropsResponse type; ensure nullable chaining (?.) is preserved
so the href accesses and boolean checks remain safe and type-checked.

newCalendars.push({
externalId: calendar.url,
/** @url https://github.com/calcom/cal.diy/issues/7186 */
name: typeof calendar.displayName === "string" ? calendar.displayName : "",
isDefault,
primary: mainHostDestinationCalendar?.externalId
? mainHostDestinationCalendar.externalId === calendar.url
: false,
integration: this.integrationName,
email: this.credentials.username ?? "",
email: this.credentials["username"] ?? "",
});
return newCalendars;
}, []);
Expand Down Expand Up @@ -1015,9 +1055,73 @@ export default abstract class BaseCalendarService implements Calendar {
account: {
serverUrl: this.url,
accountType: DEFAULT_CALENDAR_TYPE,
credentials: this.credentials,
credentials: {
username: this.credentials["username"],
password: this.credentials["password"],
},
},
headers: this.headers,
});
}

private async getClient(): Promise<DAVClient> {
if (this.client) return this.client;
this.client = await createDAVClient({
serverUrl: this.url,
credentials: {
username: this.credentials["username"],
password: this.credentials["password"],
},
authMethod: "Basic",
defaultAccountType: "caldav",
});
return this.client;
}

/**
* Resolves the scheduling default calendar URL from the principal properties.
* RFC 6638 Section 9.2: Checking schedule-default-calendar-URL property on the Inbox.
*/
private async resolveDefaultCalendarUrl(client: DAVClient): Promise<string | undefined> {
try {
const principalUrl = await client.fetchPrincipalUrl();

// Priority 1: CALDAV:schedule-default-calendar-URL (RFC 6638 Section 9.2)
const principalProps = await client.propfind({
url: principalUrl,
props: [{
name: "schedule-default-calendar-URL",
namespace: DAVNamespace.CALDAV,
}],
depth: "0",
});

const defaultUrl = (principalProps?.[0] as any)?.props?.["schedule-default-calendar-URL"]?.href;
if (defaultUrl) {
this.log.debug(`CalDAV: Found default calendar via schedule-default-calendar-URL: ${defaultUrl}`);
return defaultUrl;
}

// Priority 2: CALDAV:calendar-user-address-set (Fallback)
const addressSetProps = await client.propfind({
url: principalUrl,
props: [{
name: "calendar-user-address-set",
namespace: DAVNamespace.CALDAV,
}],
depth: "0",
});

const fallbackUrl = (addressSetProps?.[0] as any)?.props?.["calendar-user-address-set"]?.href;
if (fallbackUrl) {
this.log.debug(`CalDAV: Found default calendar via calendar-user-address-set: ${fallbackUrl}`);
return fallbackUrl;
}

return undefined;
} catch (e) {
this.log.warn("CalDAV: Could not resolve default calendar URL from inbox/principal", e);
return undefined;
}
Comment on lines +1105 to +1125
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Per RFC 6638, what is the semantic of the CALDAV:calendar-user-address-set property and what value types does it hold (mailto URI vs calendar collection URL)?

💡 Result:

According to RFC 6638, Section 2.4.1, the CALDAV:calendar-user-address-set property identifies the calendar addresses of the associated principal resource. It is a multivalued property defined as , containing one or more DAV:href elements. These href values are URIs that represent the calendar addresses, such as mailto: URIs (e.g., mailto:[email protected]). The RFC example uses mailto: URIs exclusively. While DAV:href allows any URI (including http/https URLs for calendar collections or principal URIs), the semantic and normative example specify mailto: URIs as the primary type for calendar user addresses, consistent with iCalendar CAL-ADDRESS (RFC 5545), which requires mailto: for email transports. The description notes that if no well-defined identifier exists, the principal resource URI can be used as a fallback, but mailto: URIs are the standard value type shown.

Citations:


🏁 Script executed:

# First, locate and examine the CalendarService.ts file
fd -t f -e ts CalendarService

# Get the file size to determine best approach
wc -l packages/lib/CalendarService.ts 2>/dev/null || true

Repository: calcom/cal.diy

Length of output: 1180


🏁 Script executed:

# Read the specific lines mentioned in the review
sed -n '1105,1125p' packages/lib/CalendarService.ts

Repository: calcom/cal.diy

Length of output: 785


🏁 Script executed:

# Check for `as any` casts in the file
rg 'as any' packages/lib/CalendarService.ts -n

# Also check the broader context of resolveDefaultCalendarUrl to understand impact
rg -A 5 -B 5 'resolveDefaultCalendarUrl' packages/lib/CalendarService.ts | head -100

Repository: calcom/cal.diy

Length of output: 1553


🏁 Script executed:

# Check the context around line 869 to see how defaultCalendarUrl is used
sed -n '860,875p' packages/lib/CalendarService.ts

Repository: calcom/cal.diy

Length of output: 896


Remove the calendar-user-address-set fallback — it returns the wrong type and will never match.

Per RFC 6638 §2.4.1, CALDAV:calendar-user-address-set is a set of URIs that identify the calendar user (typically mailto:[email protected]), not the URL of a calendar collection. The code at line 1115 casts this to defaultCalendarUrl and compares it to calendar.url at line 869 using calendar.url === defaultCalendarUrl. Since calendar.url is always an https:// URL and the fallback returns mailto: URIs, this comparison will never match — the fallback is dead code that silently fails.

Additionally: The as any cast at line 1115 violates the coding guideline "Never use as any — use proper type-safe solutions instead."

Suggested fix: Remove the entire Priority 2 fallback block (lines 1105–1119) and let line 869's secondary fallback handle the case when no schedule-default-calendar-URL is found.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/lib/CalendarService.ts` around lines 1105 - 1125, Remove the
Priority 2 fallback that queries CALDAV:calendar-user-address-set in resolve
default calendar logic: delete the propfind block that sets addressSetProps, the
cast using "as any", and the fallbackUrl handling (the code that logs and
returns fallbackUrl), since calendar-user-address-set contains mailto: URIs not
calendar collection URLs and will never match the comparison used elsewhere
(calendar.url === defaultCalendarUrl); rely on the existing
schedule-default-calendar-URL path and the secondary fallback instead.

}
}
119 changes: 119 additions & 0 deletions packages/lib/__tests__/CalendarService.caldav-defaults.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import BaseCalendarService from "../CalendarService";
import { ErrorCode } from "@calcom/lib/errorCodes";
import { ErrorWithCode } from "@calcom/lib/errors";

// Mock the dependencies
vi.mock("../crypto", () => ({
symmetricDecrypt: vi.fn().mockReturnValue(JSON.stringify({
username: "user",
password: "pass",
url: "https://caldav.example.com"
})),
}));

vi.mock("./logger", () => ({
default: {
getSubLogger: vi.fn().mockReturnThis(),
debug: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
},
}));
Comment on lines +15 to +22
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Vitest, is a relative path passed to vi.mock() resolved relative to the test file or the file under test?

💡 Result:

In Vitest, a relative path passed to vi.mock is resolved relative to the test file (the file containing the vi.mock call), not relative to the file under test.

Citations:


🏁 Script executed:

head -120 packages/lib/__tests__/CalendarService.caldav-defaults.test.ts | tail -25

Repository: calcom/cal.diy

Length of output: 988


🏁 Script executed:

# Find and examine the CalendarService.ts file to see how it handles the event object
find . -name "CalendarService.ts" -type f | head -5

Repository: calcom/cal.diy

Length of output: 358


🏁 Script executed:

# Check the test file to see which CalendarService it imports
grep -n "import.*CalendarService\|from.*CalendarService" packages/lib/__tests__/CalendarService.caldav-defaults.test.ts

Repository: calcom/cal.diy

Length of output: 113


🏁 Script executed:

# Examine the CalendarService.ts file in packages/lib/ to see how createEvent handles the event object
cat -n packages/lib/CalendarService.ts | head -500

Repository: calcom/cal.diy

Length of output: 21542


🏁 Script executed:

# Check other tests or usages of ics.createEvent to see if title is required
rg "createEvent" packages/lib/__tests__/ -A 5 -B 2

Repository: calcom/cal.diy

Length of output: 704


🏁 Script executed:

# Search for other usages of ics.createEvent to see the typical structure
rg "createEvent\(" packages/lib/CalendarService.ts -A 15 | head -40

Repository: calcom/cal.diy

Length of output: 1780


vi.mock("./logger") resolves to the wrong file — the mock is inert.

vi.mock(path) resolves path relative to the importing file (the test). From packages/lib/__tests__/CalendarService.caldav-defaults.test.ts, ./logger resolves to packages/lib/__tests__/logger, which doesn't exist. CalendarService.ts imports ./logger (resolving to packages/lib/logger.ts), so the mock never attaches to that module and the real logger is used at runtime.

🛠 Fix
-vi.mock("./logger", () => ({
+vi.mock("../logger", () => ({
   default: {
     getSubLogger: vi.fn().mockReturnThis(),
     debug: vi.fn(),
     warn: vi.fn(),
     error: vi.fn(),
   },
 }));

Event object missing title field — test will throw wrong error.

The event object (lines 108-117) lacks a title property. When CalendarService.createEvent calls the ics.createEvent function at line 438 with title: undefined, it returns an error. The code throws a generic Error at line 458 instead of reaching the "No target calendars" check at line 493. The test expects ErrorWithCode.InternalServerError with message "No target calendars found..." but will receive a different error first.

Add title: "Test Event" (or similar) to the event object:

 const event: any = {
   startTime: "2024-01-01T10:00:00Z",
   endTime: "2024-01-01T11:00:00Z",
   organizer: { timeZone: "UTC", email: "[email protected]" },
   attendees: [],
+  title: "Test Event",
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/lib/__tests__/CalendarService.caldav-defaults.test.ts` around lines
15 - 22, The test's module mock is targeting the wrong relative path so the
logger mock never replaces the real logger; change the vi.mock call to target
the same module id imported by CalendarService (i.e., mock the package's logger
module that CalendarService imports rather than "./logger" from the test), and
update the mock to export the same default API (getSubLogger, debug, warn,
error). Also fix the event fixture passed into CalendarService.createEvent (and
subsequently ics.createEvent) by adding a non-empty title property (e.g., title:
"Test Event") so the ICS creation won't error and the test can reach the
intended ErrorWithCode.InternalServerError "No target calendars found..."
assertion.


vi.mock("tsdav", async () => {
const actual = await vi.importActual("tsdav");
return {
...actual,
createDAVClient: vi.fn(),
fetchCalendarObjects: vi.fn(),
createCalendarObject: vi.fn(),
};
});

// Concrete class for testing the abstract BaseCalendarService
class TestCalendarService extends BaseCalendarService {
constructor(credential: any) {
super(credential, "test-caldav");
}
getAccountEmail() { return "[email protected]"; }
}

describe("BaseCalendarService - CalDAV Default Calendar Selection", () => {
const mockCredential = { key: "mock-key", user: { email: "[email protected]" } };
let service: TestCalendarService;

beforeEach(() => {
vi.clearAllMocks();
service = new TestCalendarService(mockCredential);
});

it("should resolve default calendar using CALDAV:schedule-default-calendar-URL", async () => {
const { createDAVClient } = await import("tsdav");
const mockClient = {
fetchCalendars: vi.fn().mockResolvedValue([
{ url: "https://caldav.example.com/cal1/", components: ["VEVENT"], displayName: "Calendar 1" },
{ url: "https://caldav.example.com/default/", components: ["VEVENT"], displayName: "Default Calendar" },
]),
fetchPrincipalUrl: vi.fn().mockResolvedValue("https://caldav.example.com/principal/"),
propfind: vi.fn().mockResolvedValue([
{
props: {
"schedule-default-calendar-URL": { href: "https://caldav.example.com/default/" }
}
}
]),
};
(createDAVClient as any).mockResolvedValue(mockClient);

const calendars = await service.listCalendars();

expect(calendars).toHaveLength(2);
expect(calendars.find(c => c.externalId.includes("default"))?.isDefault).toBe(true);
expect(calendars.find(c => c.externalId.includes("cal1"))?.isDefault).toBe(false);
});

it("should fall back to calendar-user-address-set if schedule-default-calendar-URL is missing", async () => {
const { createDAVClient } = await import("tsdav");
const mockClient = {
fetchCalendars: vi.fn().mockResolvedValue([
{ url: "https://caldav.example.com/fallback/", components: ["VEVENT"] },
]),
fetchPrincipalUrl: vi.fn().mockResolvedValue("https://caldav.example.com/principal/"),
propfind: vi.fn()
.mockResolvedValueOnce([]) // First call for schedule-default-calendar-URL returns nothing
.mockResolvedValueOnce([ // Second call for fallback
{
props: {
"calendar-user-address-set": { href: "https://caldav.example.com/fallback/" }
}
}
]),
};
(createDAVClient as any).mockResolvedValue(mockClient);

const calendars = await service.listCalendars();
expect(calendars[0].isDefault).toBe(true);
});

it("should throw ErrorWithCode.InternalServerError when no target calendars are found", async () => {
const { createDAVClient } = await import("tsdav");
const mockClient = {
fetchCalendars: vi.fn().mockResolvedValue([]), // No calendars returned
fetchPrincipalUrl: vi.fn().mockResolvedValue("url"),
propfind: vi.fn().mockResolvedValue([]),
};
(createDAVClient as any).mockResolvedValue(mockClient);

const event: any = {
startTime: "2024-01-01T10:00:00Z",
endTime: "2024-01-01T11:00:00Z",
organizer: { timeZone: "UTC", email: "[email protected]" },
attendees: [],
};

await expect(service.createEvent(event, 1)).rejects.toThrow(
new ErrorWithCode(ErrorCode.InternalServerError, "No target calendars found to create CalDAV calendar entry")
);
});
Comment on lines +108 to +118
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

This test will not exercise the "no target calendars" path — it will throw earlier.

The event object lacks a title (and calendarDescription). In createEvent() the ics.createEvent({...}) call runs before the targetCalendars.length === 0 guard, and the ics library rejects missing/empty titles with an error, causing throw new Error("Error creating iCalString:=> ...") at line 458 of CalendarService.ts. That's a plain Error, not ErrorWithCode(InternalServerError, "No target calendars found..."), so the rejects.toThrow(...) assertion will match on the message substring and falsely suggest the intended guard fired.

🛠 Suggested fix: give the event the minimum fields required to pass ics validation, so we actually reach the guard
     const event: any = {
+      type: "test-caldav",
+      title: "Test Event",
       startTime: "2024-01-01T10:00:00Z",
       endTime: "2024-01-01T11:00:00Z",
       organizer: { timeZone: "UTC", email: "[email protected]" },
       attendees: [],
     };

Also consider asserting on the error instance directly to avoid accidental message-substring matches:

await expect(service.createEvent(event, 1)).rejects.toBeInstanceOf(ErrorWithCode);
await expect(service.createEvent(event, 1)).rejects.toMatchObject({
  code: ErrorCode.InternalServerError,
});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const event: any = {
startTime: "2024-01-01T10:00:00Z",
endTime: "2024-01-01T11:00:00Z",
organizer: { timeZone: "UTC", email: "[email protected]" },
attendees: [],
};
await expect(service.createEvent(event, 1)).rejects.toThrow(
new ErrorWithCode(ErrorCode.InternalServerError, "No target calendars found to create CalDAV calendar entry")
);
});
const event: any = {
type: "test-caldav",
title: "Test Event",
startTime: "2024-01-01T10:00:00Z",
endTime: "2024-01-01T11:00:00Z",
organizer: { timeZone: "UTC", email: "[email protected]" },
attendees: [],
};
await expect(service.createEvent(event, 1)).rejects.toThrow(
new ErrorWithCode(ErrorCode.InternalServerError, "No target calendars found to create CalDAV calendar entry")
);
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/lib/__tests__/CalendarService.caldav-defaults.test.ts` around lines
108 - 118, The test is hitting ics.createEvent before the "no target calendars"
guard because the test event lacks required fields; update the test's event
object used in CalendarService.caldav-defaults.test.ts to include minimal valid
fields (e.g., title and calendarDescription) so ics.createEvent succeeds and
execution reaches CalendarService.createEvent's targetCalendars check, and
replace the string-based throws assertion with stronger checks such as
expect(...).rejects.toBeInstanceOf(ErrorWithCode) and
expect(...).rejects.toMatchObject({ code: ErrorCode.InternalServerError }) to
ensure the guard's ErrorWithCode is asserted.

});
Loading
Loading