Files
Benny JooGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
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: [email protected] <[email protected]>

* 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

240 lines
7.3 KiB
TypeScript

import type { CredentialForCalendarService } from "@calcom/types/Credential";
import { TRPCError } from "@trpc/server";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { appByIdHandler } from "./appById.handler";
import type { TAppByIdInputSchema } from "./appById.schema";
// Mock the dependencies
vi.mock("@calcom/app-store/delegationCredential", () => ({
getUsersCredentialsIncludeServiceAccountKey: vi.fn(),
}));
vi.mock("@calcom/app-store/utils", () => ({
default: vi.fn(),
sanitizeAppForViewer: vi.fn((app) => {
const { key: _, credential: _1, credentials: _2, ...sanitized } = app;
return sanitized;
}),
}));
vi.mock("@calcom/app-store/_utils/getCalendar", () => ({
getCalendar: vi.fn().mockReturnValue(null),
}));
vi.mock("@calcom/prisma", () => ({
default: {},
prisma: {},
}));
import { getUsersCredentialsIncludeServiceAccountKey } from "@calcom/app-store/delegationCredential";
import type { CredentialDataWithTeamName, LocationOption } from "@calcom/app-store/utils";
import getApps, { sanitizeAppForViewer } from "@calcom/app-store/utils";
import type { App } from "@calcom/types/App";
describe("appByIdHandler", () => {
const mockUser = {
id: 1,
email: "[email protected]",
username: "testuser",
name: "Test User",
};
beforeEach(() => {
vi.clearAllMocks();
});
it("should not expose key field for globally installed apps", async () => {
const secretApiKey = "secret-daily-api-key-12345";
const mockCredential: CredentialDataWithTeamName = {
id: 0,
type: "daily_video",
key: { apikey: secretApiKey },
userId: 0,
user: { email: "" },
teamId: null,
appId: "daily-video",
invalid: false,
delegatedTo: null,
delegatedToId: null,
delegationCredentialId: null,
team: {
name: "Default",
},
};
const mockApp: App & {
credential: CredentialDataWithTeamName | null;
credentials: CredentialDataWithTeamName[];
locationOption: LocationOption | null;
} = {
type: "daily_video",
name: "Cal Video",
description: "Video conferencing",
variant: "conferencing",
slug: "daily-video",
categories: ["conferencing"],
logo: "icon.svg",
publisher: "Cal.diy",
url: "https://daily.co",
email: "[email protected]",
isGlobal: true,
// This is the sensitive key that should NOT be exposed
key: { apikey: secretApiKey },
credential: mockCredential,
credentials: [mockCredential],
locationOption: {
value: "integrations:daily",
label: "Cal Video",
},
appData: {
location: {
linkType: "dynamic",
type: "integrations:daily",
label: "Cal Video",
},
},
};
vi.mocked(getUsersCredentialsIncludeServiceAccountKey).mockResolvedValue([]);
vi.mocked(getApps).mockReturnValue([mockApp]);
const result = await appByIdHandler({
ctx: { user: mockUser },
input: { appId: "daily-video" } as TAppByIdInputSchema,
});
// Verify that key is NOT in the response
expect(result).not.toHaveProperty("key");
expect(result).not.toHaveProperty("credential");
expect(result).not.toHaveProperty("credentials");
// Verify that other properties are preserved
expect(result).toHaveProperty("slug", "daily-video");
expect(result).toHaveProperty("name", "Cal Video");
expect(result).toHaveProperty("isGlobal", true);
expect(result).toHaveProperty("locationOption");
expect(result).toHaveProperty("isInstalled", 1);
// Verify sanitizeAppForViewer was called
expect(sanitizeAppForViewer).toHaveBeenCalledWith(mockApp);
});
it("should throw BAD_REQUEST when app is not found", async () => {
vi.mocked(getUsersCredentialsIncludeServiceAccountKey).mockResolvedValue([]);
vi.mocked(getApps).mockReturnValue([]);
await expect(
appByIdHandler({
ctx: { user: mockUser },
input: { appId: "non-existent-app" } as TAppByIdInputSchema,
})
).rejects.toThrow(TRPCError);
await expect(
appByIdHandler({
ctx: { user: mockUser },
input: { appId: "non-existent-app" } as TAppByIdInputSchema,
})
).rejects.toThrow("Could not find app non-existent-app");
});
it("should preserve all non-sensitive properties", async () => {
const mockApp: App & {
credential: CredentialDataWithTeamName | null;
credentials: CredentialDataWithTeamName[];
locationOption: LocationOption | null;
} = {
type: "zoom_video",
name: "Zoom",
description: "Video conferencing",
variant: "conferencing",
slug: "zoom",
categories: ["conferencing"],
logo: "icon.svg",
publisher: "Zoom",
url: "https://zoom.us",
email: "[email protected]",
isGlobal: false,
verified: true,
trending: true,
rating: 4.5,
reviews: 1000,
key: { api_key: "secret-zoom-key" },
credential: null,
credentials: [],
locationOption: null,
};
vi.mocked(getUsersCredentialsIncludeServiceAccountKey).mockResolvedValue([]);
vi.mocked(getApps).mockReturnValue([mockApp]);
const result = await appByIdHandler({
ctx: { user: mockUser },
input: { appId: "zoom" } as TAppByIdInputSchema,
});
// Verify sensitive fields are removed
expect(result).not.toHaveProperty("key");
expect(result).not.toHaveProperty("credential");
expect(result).not.toHaveProperty("credentials");
// Verify non-sensitive fields are preserved
expect(result).toHaveProperty("slug", "zoom");
expect(result).toHaveProperty("name", "Zoom");
expect(result).toHaveProperty("verified", true);
expect(result).toHaveProperty("trending", true);
expect(result).toHaveProperty("rating", 4.5);
expect(result).toHaveProperty("reviews", 1000);
expect(result).toHaveProperty("isInstalled", 0);
});
it("should correctly set isInstalled based on credentials length", async () => {
const mockCredential: CredentialDataWithTeamName = {
id: 1,
type: "google_calendar",
key: { access_token: "token" },
userId: 1,
user: { email: "[email protected]" },
teamId: null,
appId: "google-calendar",
invalid: false,
delegatedTo: null,
delegatedToId: null,
delegationCredentialId: null,
team: null,
};
const mockApp: App & {
credential: CredentialDataWithTeamName | null;
credentials: CredentialDataWithTeamName[];
locationOption: LocationOption | null;
} = {
type: "google_calendar",
name: "Google Calendar",
description: "Calendar integration",
variant: "calendar",
slug: "google-calendar",
categories: ["calendar"],
logo: "icon.svg",
publisher: "Google",
url: "https://google.com",
email: "[email protected]",
key: { api_key: "secret-key" },
credential: mockCredential,
credentials: [mockCredential],
locationOption: null,
};
vi.mocked(getUsersCredentialsIncludeServiceAccountKey).mockResolvedValue([]);
vi.mocked(getApps).mockReturnValue([mockApp]);
const result = await appByIdHandler({
ctx: { user: mockUser },
input: { appId: "google-calendar" } as TAppByIdInputSchema,
});
expect(result.isInstalled).toBe(1);
expect(result).not.toHaveProperty("key");
});
});