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: "test@example.com", 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.com", url: "https://daily.co", email: "help@cal.com", 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: "support@zoom.us", 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: "test@example.com" }, 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: "support@google.com", 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"); }); });