Files
Benny JooandDevin AI ab21c7f805 refactor: Cal.diy (#28903)
* feat: Cal.diy — community-driven MIT-licensed fork of Cal.com

This squashed commit contains all Cal.diy changes applied on top of calcom/cal.com main:

- Rebrand Cal.com to Cal.diy across the entire codebase
- Remove Enterprise Edition (EE) features, license checks, and AGPL restrictions
- Switch license from AGPL-3.0 to MIT
- Remove docs/ directory (migrated to Nextra at cal.diy)
- Remove dead code: org tests, EE tips, platform nav, premium username, SAML/SSO, etc.
- Clean up .env.example for self-hosted Cal.diy
- Update Docker image references to calcom/cal.diy
- Update README, CONTRIBUTING.md, and issue templates for Cal.diy community fork
- Add PR welcome bot for Cal.diy contributors
- Fix API v2 breaking changes oasdiff ignore entries
- Replace Blacksmith CI runners with default GitHub Actions

3893 files changed, 20789 insertions(+), 411020 deletions(-)

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* refactor: remove org-specific /organizations/:orgId endpoints from API v2 atoms controllers (#1701)

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix: revert Cal.diy Inc to Cal.com, Inc. in license files, copyright notices, and package metadata (#1702)

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* rip out org related comments in api v2

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-04-15 09:52:36 -03:00

576 lines
21 KiB
TypeScript

import "../__mocks__/delegationCredential";
import { beforeEach, describe, expect, test, vi } from "vitest";
vi.mock("@sentry/nextjs", () => ({
metrics: {
count: vi.fn(),
distribution: vi.fn(),
},
}));
vi.mock("@calcom/app-store/delegationCredential", async () => {
const { getCredentialForSelectedCalendar } = await import("../__mocks__/delegationCredential");
return {
enrichHostsWithDelegationCredentials: vi.fn(),
getUsersCredentialsIncludeServiceAccountKey: vi.fn(),
getCredentialForSelectedCalendar,
};
});
import type { AdapterFactory } from "@calcom/features/calendar-subscription/adapters/AdaptersFactory";
import type { CalendarCacheEventService } from "@calcom/features/calendar-subscription/lib/cache/CalendarCacheEventService";
import type { CalendarSyncService } from "@calcom/features/calendar-subscription/lib/sync/CalendarSyncService";
import type { IFeatureRepository } from "@calcom/features/flags/repositories/PrismaFeatureRepository";
import type { ITeamFeatureRepository } from "@calcom/features/flags/repositories/PrismaTeamFeatureRepository";
import type { IUserFeatureRepository } from "@calcom/features/flags/repositories/PrismaUserFeatureRepository";
import type { ISelectedCalendarRepository } from "@calcom/features/selectedCalendar/repositories/SelectedCalendarRepository.interface";
import type { SelectedCalendar } from "@calcom/prisma/client";
import { CalendarSubscriptionService } from "../CalendarSubscriptionService";
const mockSelectedCalendar: SelectedCalendar = {
id: "test-calendar-id",
userId: 1,
credentialId: 1,
integration: "google_calendar",
externalId: "test@example.com",
eventTypeId: null,
delegationCredentialId: null,
googleChannelId: null,
googleChannelKind: null,
googleChannelResourceId: null,
googleChannelResourceUri: null,
googleChannelExpiration: null,
error: null,
lastErrorAt: null,
watchAttempts: 0,
maxAttempts: 3,
unwatchAttempts: 0,
createdAt: new Date(),
updatedAt: new Date(),
channelId: "test-channel-id",
channelKind: "web_hook",
channelResourceId: "test-resource-id",
channelResourceUri: "test-resource-uri",
channelExpiration: new Date(Date.now() + 86400000),
syncSubscribedAt: new Date(),
syncSubscribedErrorAt: null,
syncSubscribedErrorCount: 0,
syncToken: "test-sync-token",
syncedAt: new Date(),
syncErrorAt: null,
syncErrorCount: 0,
};
const mockCredential = {
id: 1,
key: { access_token: "test-token" },
user: { email: "test@example.com" },
delegatedTo: null,
};
const mockSubscriptionResult = {
provider: "google_calendar" as const,
id: "test-channel-id",
resourceId: "test-resource-id",
resourceUri: "test-resource-uri",
expiration: new Date(Date.now() + 86400000),
};
const mockEvents = {
provider: "google_calendar" as const,
syncToken: "new-sync-token",
items: [
{
id: "event-1",
iCalUID: "event-1@cal.com",
start: new Date(),
end: new Date(Date.now() + 3600000),
busy: true,
summary: "Test Event",
description: "Test Description",
location: "Test Location",
status: "confirmed",
isAllDay: false,
timeZone: "UTC",
recurringEventId: null,
originalStartDate: null,
createdAt: new Date(),
updatedAt: new Date(),
etag: "test-etag",
kind: "calendar#event",
},
],
};
describe("CalendarSubscriptionService", () => {
let service: CalendarSubscriptionService;
let mockAdapterFactory: AdapterFactory;
let mockSelectedCalendarRepository: ISelectedCalendarRepository;
let mockFeatureRepository: IFeatureRepository;
let mockTeamFeatureRepository: ITeamFeatureRepository;
let mockUserFeatureRepository: IUserFeatureRepository & {
checkIfUserHasFeature: (userId: number, slug: string) => Promise<boolean>;
};
let mockCalendarCacheEventService: CalendarCacheEventService;
let mockCalendarSyncService: CalendarSyncService;
let mockAdapter: {
subscribe: ReturnType<typeof vi.fn>;
unsubscribe: ReturnType<typeof vi.fn>;
validate: ReturnType<typeof vi.fn>;
extractChannelId: ReturnType<typeof vi.fn>;
fetchEvents: ReturnType<typeof vi.fn>;
};
beforeEach(async () => {
mockAdapter = {
subscribe: vi.fn().mockResolvedValue(mockSubscriptionResult),
unsubscribe: vi.fn().mockResolvedValue(undefined),
validate: vi.fn().mockResolvedValue(true),
extractChannelId: vi.fn().mockResolvedValue("test-channel-id"),
fetchEvents: vi.fn().mockResolvedValue(mockEvents),
};
mockAdapterFactory = {
get: vi.fn().mockReturnValue(mockAdapter),
getProviders: vi.fn().mockReturnValue(["google_calendar", "office365_calendar"]),
getGenericCalendarSuffixes: vi
.fn()
.mockReturnValue([
"@group.v.calendar.google.com",
"@group.calendar.google.com",
"@import.calendar.google.com",
"@resource.calendar.google.com",
]),
};
mockSelectedCalendarRepository = {
findById: vi.fn().mockResolvedValue(mockSelectedCalendar),
findByChannelId: vi.fn().mockResolvedValue(mockSelectedCalendar),
findNextSubscriptionBatch: vi.fn().mockResolvedValue([mockSelectedCalendar]),
updateSyncStatus: vi.fn().mockResolvedValue(mockSelectedCalendar),
updateSubscription: vi.fn().mockResolvedValue(mockSelectedCalendar),
};
mockFeatureRepository = {
findAll: vi.fn(),
findBySlug: vi.fn(),
update: vi.fn(),
checkIfFeatureIsEnabledGlobally: vi.fn().mockResolvedValue(true),
};
mockTeamFeatureRepository = {
checkIfTeamHasFeature: vi.fn().mockResolvedValue(true),
getTeamsWithFeatureEnabled: vi.fn().mockResolvedValue([1, 2, 3]),
};
mockUserFeatureRepository = {
checkIfUserHasFeature: vi.fn().mockResolvedValue(true),
checkIfUserHasFeatureNonHierarchical: vi.fn().mockResolvedValue(true),
};
mockCalendarCacheEventService = {
handleEvents: vi.fn().mockResolvedValue(undefined),
cleanupCache: vi.fn().mockResolvedValue(undefined),
cleanupStaleCache: vi.fn().mockResolvedValue(undefined),
};
mockCalendarSyncService = {
handleEvents: vi.fn().mockResolvedValue(undefined),
};
service = new CalendarSubscriptionService({
adapterFactory: mockAdapterFactory,
selectedCalendarRepository: mockSelectedCalendarRepository,
featureRepository: mockFeatureRepository,
teamFeatureRepository: mockTeamFeatureRepository,
userFeatureRepository: mockUserFeatureRepository,
calendarCacheEventService: mockCalendarCacheEventService,
calendarSyncService: mockCalendarSyncService,
});
mockSelectedCalendar.syncSubscribedErrorCount = 0;
const { getCredentialForSelectedCalendar } = await import("../__mocks__/delegationCredential");
getCredentialForSelectedCalendar.mockResolvedValue(mockCredential);
});
describe("subscribe", () => {
test("should successfully subscribe to a calendar", async () => {
await service.subscribe("test-calendar-id");
expect(mockSelectedCalendarRepository.findById).toHaveBeenCalledWith("test-calendar-id");
expect(mockAdapterFactory.get).toHaveBeenCalledWith("google_calendar");
expect(mockAdapter.subscribe).toHaveBeenCalledWith(mockSelectedCalendar, mockCredential);
expect(mockSelectedCalendarRepository.updateSubscription).toHaveBeenCalledWith("test-calendar-id", {
channelId: "test-channel-id",
channelResourceId: "test-resource-id",
channelResourceUri: "test-resource-uri",
channelKind: "google_calendar",
channelExpiration: mockSubscriptionResult.expiration,
syncSubscribedAt: expect.any(Date),
syncSubscribedErrorAt: null,
syncSubscribedErrorCount: 0,
});
});
test("should record subscribe errors and retry later", async () => {
const subscribeError = new Error("subscribe failed");
mockAdapter.subscribe.mockRejectedValue(subscribeError);
await expect(service.subscribe("test-calendar-id")).rejects.toThrow(subscribeError);
expect(mockSelectedCalendarRepository.updateSubscription).toHaveBeenCalledWith("test-calendar-id", {
syncSubscribedAt: null,
syncSubscribedErrorAt: expect.any(Date),
syncSubscribedErrorCount: 1,
});
});
test("should skip subscription attempts after hitting error threshold", async () => {
mockSelectedCalendarRepository.findById.mockResolvedValue({
...mockSelectedCalendar,
syncSubscribedErrorCount: CalendarSubscriptionService.MAX_SUBSCRIBE_ERRORS,
});
await service.subscribe("test-calendar-id");
expect(mockAdapter.subscribe).not.toHaveBeenCalled();
expect(mockSelectedCalendarRepository.updateSubscription).not.toHaveBeenCalled();
});
test("should return early if selected calendar not found", async () => {
mockSelectedCalendarRepository.findById.mockResolvedValue(null);
await service.subscribe("non-existent-id");
expect(mockAdapter.subscribe).not.toHaveBeenCalled();
expect(mockSelectedCalendarRepository.updateSubscription).not.toHaveBeenCalled();
});
test("should return early if selected calendar has no credentialId or delegationCredentialId", async () => {
mockSelectedCalendarRepository.findById.mockResolvedValue({
...mockSelectedCalendar,
credentialId: null,
delegationCredentialId: null,
});
await service.subscribe("test-calendar-id");
expect(mockAdapter.subscribe).not.toHaveBeenCalled();
expect(mockSelectedCalendarRepository.updateSubscription).not.toHaveBeenCalled();
});
});
describe("unsubscribe", () => {
test("should successfully unsubscribe from a calendar", async () => {
mockFeatureRepository.checkIfFeatureIsEnabledGlobally.mockResolvedValue(true);
await service.unsubscribe("test-calendar-id");
expect(mockSelectedCalendarRepository.findById).toHaveBeenCalledWith("test-calendar-id");
expect(mockAdapter.unsubscribe).toHaveBeenCalledWith(mockSelectedCalendar, mockCredential);
expect(mockSelectedCalendarRepository.updateSubscription).toHaveBeenCalledWith("test-calendar-id", {
syncSubscribedAt: null,
});
expect(mockCalendarCacheEventService.cleanupCache).toHaveBeenCalledWith(mockSelectedCalendar);
});
test("should not cleanup cache if cache is disabled", async () => {
mockFeatureRepository.checkIfFeatureIsEnabledGlobally.mockResolvedValue(false);
await service.unsubscribe("test-calendar-id");
expect(mockCalendarCacheEventService.cleanupCache).not.toHaveBeenCalled();
});
test("should return early if selected calendar not found", async () => {
mockSelectedCalendarRepository.findById.mockResolvedValue(null);
await service.unsubscribe("non-existent-id");
expect(mockAdapter.unsubscribe).not.toHaveBeenCalled();
});
});
describe("processWebhook", () => {
test("should successfully process a valid webhook", async () => {
const mockRequest = new Request("http://example.com");
await service.processWebhook("google_calendar", mockRequest);
expect(mockAdapter.validate).toHaveBeenCalledWith(mockRequest);
expect(mockAdapter.extractChannelId).toHaveBeenCalledWith(mockRequest);
expect(mockSelectedCalendarRepository.findByChannelId).toHaveBeenCalledWith("test-channel-id");
});
test("should throw error for invalid webhook", async () => {
const mockRequest = new Request("http://example.com");
mockAdapter.validate.mockResolvedValue(false);
await expect(service.processWebhook("google_calendar", mockRequest)).rejects.toThrow(
"Invalid webhook request"
);
});
test("should throw error for missing channel ID", async () => {
const mockRequest = new Request("http://example.com");
mockAdapter.extractChannelId.mockResolvedValue(null);
await expect(service.processWebhook("google_calendar", mockRequest)).rejects.toThrow(
"Missing channel ID in webhook"
);
});
test("should return null for old subscription", async () => {
const mockRequest = new Request("http://example.com");
mockSelectedCalendarRepository.findByChannelId.mockResolvedValue(null);
const _result = await service.processWebhook("google_calendar", mockRequest);
expect(_result).toBeNull();
});
});
describe("processEvents", () => {
test("should process events when both cache and sync are enabled", async () => {
mockFeatureRepository.checkIfFeatureIsEnabledGlobally
.mockResolvedValueOnce(true)
.mockResolvedValueOnce(true);
mockUserFeatureRepository.checkIfUserHasFeature.mockResolvedValue(true);
await service.processEvents(mockSelectedCalendar);
expect(mockAdapter.fetchEvents).toHaveBeenCalledWith(mockSelectedCalendar, mockCredential);
expect(mockSelectedCalendarRepository.updateSyncStatus).toHaveBeenCalledWith(mockSelectedCalendar.id, {
syncToken: "new-sync-token",
syncedAt: expect.any(Date),
syncErrorAt: null,
syncErrorCount: 0,
});
expect(mockCalendarCacheEventService.handleEvents).toHaveBeenCalledWith(
mockSelectedCalendar,
mockEvents.items
);
expect(mockCalendarSyncService.handleEvents).toHaveBeenCalledWith(
mockSelectedCalendar,
mockEvents.items
);
});
test("should not process cache when cache is disabled globally", async () => {
mockFeatureRepository.checkIfFeatureIsEnabledGlobally
.mockResolvedValueOnce(false)
.mockResolvedValueOnce(true);
mockUserFeatureRepository.checkIfUserHasFeature.mockResolvedValue(true);
await service.processEvents(mockSelectedCalendar);
expect(mockCalendarCacheEventService.handleEvents).not.toHaveBeenCalled();
expect(mockCalendarSyncService.handleEvents).toHaveBeenCalled();
});
test("should not process cache when cache is disabled for user", async () => {
mockFeatureRepository.checkIfFeatureIsEnabledGlobally
.mockResolvedValueOnce(true)
.mockResolvedValueOnce(true);
mockUserFeatureRepository.checkIfUserHasFeature.mockResolvedValue(false);
await service.processEvents(mockSelectedCalendar);
expect(mockCalendarCacheEventService.handleEvents).not.toHaveBeenCalled();
expect(mockCalendarSyncService.handleEvents).toHaveBeenCalled();
});
test("should return early when both cache and sync are disabled", async () => {
mockFeatureRepository.checkIfFeatureIsEnabledGlobally
.mockResolvedValueOnce(false)
.mockResolvedValueOnce(false);
await service.processEvents(mockSelectedCalendar);
expect(mockAdapter.fetchEvents).not.toHaveBeenCalled();
expect(mockCalendarCacheEventService.handleEvents).not.toHaveBeenCalled();
expect(mockCalendarSyncService.handleEvents).not.toHaveBeenCalled();
});
test("should handle API errors and update sync status", async () => {
mockFeatureRepository.checkIfFeatureIsEnabledGlobally
.mockResolvedValueOnce(true)
.mockResolvedValueOnce(true);
mockUserFeatureRepository.checkIfUserHasFeature.mockResolvedValue(true);
const apiError = new Error("API Error");
mockAdapter.fetchEvents.mockRejectedValue(apiError);
await expect(service.processEvents(mockSelectedCalendar)).rejects.toThrow("API Error");
expect(mockSelectedCalendarRepository.updateSyncStatus).toHaveBeenCalledWith(mockSelectedCalendar.id, {
syncErrorAt: expect.any(Date),
syncErrorCount: {
increment: 1,
},
});
});
test("should return early when no events are fetched", async () => {
mockFeatureRepository.checkIfFeatureIsEnabledGlobally
.mockResolvedValueOnce(true)
.mockResolvedValueOnce(true);
mockUserFeatureRepository.checkIfUserHasFeature.mockResolvedValue(true);
mockAdapter.fetchEvents.mockResolvedValue({
...mockEvents,
items: [],
});
await service.processEvents(mockSelectedCalendar);
expect(mockCalendarCacheEventService.handleEvents).not.toHaveBeenCalled();
expect(mockCalendarSyncService.handleEvents).not.toHaveBeenCalled();
});
test("should return early when selected calendar has no credentialId or delegationCredentialId", async () => {
const calendarWithoutCredential = {
...mockSelectedCalendar,
credentialId: null,
delegationCredentialId: null,
};
await service.processEvents(calendarWithoutCredential);
expect(mockAdapter.fetchEvents).not.toHaveBeenCalled();
});
});
describe("checkForNewSubscriptions", () => {
test("should process new subscriptions", async () => {
const subscribeSpy = vi.spyOn(service, "subscribe").mockResolvedValue(undefined);
await service.checkForNewSubscriptions();
expect(mockTeamFeatureRepository.getTeamsWithFeatureEnabled).toHaveBeenCalledWith(
"calendar-subscription-cache"
);
expect(mockSelectedCalendarRepository.findNextSubscriptionBatch).toHaveBeenCalledWith({
take: 100,
integrations: ["google_calendar", "office365_calendar"],
teamIds: [1, 2, 3],
genericCalendarSuffixes: [
"@group.v.calendar.google.com",
"@group.calendar.google.com",
"@import.calendar.google.com",
"@resource.calendar.google.com",
],
});
expect(subscribeSpy).toHaveBeenCalledWith(mockSelectedCalendar.id);
});
test("should handle multiple calendars returned from batch", async () => {
const calendarWithCache = { ...mockSelectedCalendar, id: "calendar-with-cache", userId: 1 };
const calendarWithCache2 = { ...mockSelectedCalendar, id: "calendar-with-cache-2", userId: 2 };
mockSelectedCalendarRepository.findNextSubscriptionBatch.mockResolvedValue([
calendarWithCache,
calendarWithCache2,
]);
const subscribeSpy = vi.spyOn(service, "subscribe").mockResolvedValue(undefined);
await service.checkForNewSubscriptions();
expect(mockTeamFeatureRepository.getTeamsWithFeatureEnabled).toHaveBeenCalledWith(
"calendar-subscription-cache"
);
expect(mockSelectedCalendarRepository.findNextSubscriptionBatch).toHaveBeenCalledWith({
take: 100,
integrations: ["google_calendar", "office365_calendar"],
teamIds: [1, 2, 3],
genericCalendarSuffixes: [
"@group.v.calendar.google.com",
"@group.calendar.google.com",
"@import.calendar.google.com",
"@resource.calendar.google.com",
],
});
expect(subscribeSpy).toHaveBeenCalledTimes(2);
expect(subscribeSpy).toHaveBeenCalledWith("calendar-with-cache");
expect(subscribeSpy).toHaveBeenCalledWith("calendar-with-cache-2");
});
test("should skip when cache feature is globally disabled", async () => {
mockFeatureRepository.checkIfFeatureIsEnabledGlobally.mockResolvedValue(false);
const subscribeSpy = vi.spyOn(service, "subscribe").mockResolvedValue(undefined);
await service.checkForNewSubscriptions();
expect(mockFeatureRepository.checkIfFeatureIsEnabledGlobally).toHaveBeenCalledWith(
"calendar-subscription-cache"
);
expect(mockTeamFeatureRepository.getTeamsWithFeatureEnabled).not.toHaveBeenCalled();
expect(mockSelectedCalendarRepository.findNextSubscriptionBatch).not.toHaveBeenCalled();
expect(subscribeSpy).not.toHaveBeenCalled();
});
test("should not process any calendars when no calendars are returned", async () => {
mockSelectedCalendarRepository.findNextSubscriptionBatch.mockResolvedValue([]);
const subscribeSpy = vi.spyOn(service, "subscribe").mockResolvedValue(undefined);
await service.checkForNewSubscriptions();
expect(mockTeamFeatureRepository.getTeamsWithFeatureEnabled).toHaveBeenCalledWith(
"calendar-subscription-cache"
);
expect(mockSelectedCalendarRepository.findNextSubscriptionBatch).toHaveBeenCalledWith({
take: 100,
integrations: ["google_calendar", "office365_calendar"],
teamIds: [1, 2, 3],
genericCalendarSuffixes: [
"@group.v.calendar.google.com",
"@group.calendar.google.com",
"@import.calendar.google.com",
"@resource.calendar.google.com",
],
});
expect(subscribeSpy).not.toHaveBeenCalled();
});
});
describe("feature flag methods", () => {
test("isCacheEnabled should check global cache feature", async () => {
mockFeatureRepository.checkIfFeatureIsEnabledGlobally.mockResolvedValue(true);
const result = await service.isCacheEnabled();
expect(result).toBe(true);
expect(mockFeatureRepository.checkIfFeatureIsEnabledGlobally).toHaveBeenCalledWith(
"calendar-subscription-cache"
);
});
test("isCacheEnabledForUser should check user cache feature", async () => {
mockUserFeatureRepository.checkIfUserHasFeature.mockResolvedValue(true);
const result = await service.isCacheEnabledForUser(1);
expect(result).toBe(true);
expect(mockUserFeatureRepository.checkIfUserHasFeature).toHaveBeenCalledWith(
1,
"calendar-subscription-cache"
);
});
test("isSyncEnabled should check global sync feature", async () => {
mockFeatureRepository.checkIfFeatureIsEnabledGlobally.mockResolvedValue(true);
const result = await service.isSyncEnabled();
expect(result).toBe(true);
expect(mockFeatureRepository.checkIfFeatureIsEnabledGlobally).toHaveBeenCalledWith(
"calendar-subscription-sync"
);
});
});
});