Files
calendar/packages/trpc/server/routers/viewer/apps/appById.handler.test.ts
T

236 lines
7.1 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from "vitest";
import { TRPCError } from "@trpc/server";
import type { CredentialForCalendarService } from "@calcom/types/Credential";
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;
}),
}));
import { getUsersCredentialsIncludeServiceAccountKey } from "@calcom/app-store/delegationCredential";
import getApps, { sanitizeAppForViewer } from "@calcom/app-store/utils";
import type { CredentialDataWithTeamName, LocationOption } 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");
});
});