* refactor: apply biome formatting to packages/features (batch 1 - small subdirs) Format small subdirectories in packages/features: di, flags, holidays, oauth, settings, users, assignment-reason, selectedCalendar, hashedLink, host, form, form-builder, availability, data-table, pbac, schedules, troubleshooter, eventtypes, calendar-subscription, and root-level files. Also includes straggler apps/web BookEventForm.tsx. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: apply biome formatting to packages/features (batch 2 - medium subdirs) Format medium subdirectories in packages/features: auth, credentials, calendars, routing-forms, routing-trace, attributes, watchlist, calAIPhone, tasker, and webhooks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: apply biome formatting to packages/features (batch 3 - bookings + insights) Format bookings and insights subdirectories in packages/features. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: apply biome formatting to packages/features (batch 4 - ee) Format packages/features/ee subdirectory covering billing, workflows, organizations, teams, managed-event-types, round-robin, dsync, integration-attribute-sync, and payments. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: apply biome formatting to packages/features (batch 5 - booking-audit part 1) Format booking-audit di, actions, common, dto, repository, and types subdirectories in packages/features/booking-audit. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: apply biome formatting to packages/features (batch 6 - booking-audit part 2) Format booking-audit service subdirectory in packages/features/booking-audit. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
331 lines
8.5 KiB
TypeScript
331 lines
8.5 KiB
TypeScript
import { describe, expect, test, vi, beforeEach } from "vitest";
|
|
|
|
import { CredentialRepository } from "@calcom/features/credentials/repositories/CredentialRepository";
|
|
import { UserRepository } from "@calcom/features/users/repositories/UserRepository";
|
|
import { HttpError } from "@calcom/lib/http-error";
|
|
import { prisma } from "@calcom/prisma";
|
|
|
|
import { CredentialAccessService } from "./CredentialAccessService";
|
|
|
|
vi.mock("@calcom/prisma", () => {
|
|
return {
|
|
prisma: {
|
|
user: {
|
|
findUnique: vi.fn(),
|
|
},
|
|
},
|
|
};
|
|
});
|
|
|
|
vi.mock("@calcom/features/credentials/repositories/CredentialRepository", () => {
|
|
return {
|
|
CredentialRepository: {
|
|
findFirstByIdWithKeyAndUser: vi.fn(),
|
|
},
|
|
};
|
|
});
|
|
|
|
vi.mock("@calcom/features/users/repositories/UserRepository", () => {
|
|
return {
|
|
UserRepository: vi.fn().mockImplementation(function () {
|
|
return {
|
|
getUserOrganizationAndTeams: vi.fn(),
|
|
};
|
|
}),
|
|
};
|
|
});
|
|
|
|
describe("CredentialAccessService", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
test("should allow access when credential belongs to logged-in user", async () => {
|
|
const credentialId = 1;
|
|
const loggedInUserId = 100;
|
|
const bookingOwnerId = 200;
|
|
|
|
vi.mocked(CredentialRepository.findFirstByIdWithKeyAndUser).mockResolvedValue({
|
|
id: credentialId,
|
|
userId: loggedInUserId,
|
|
teamId: null,
|
|
type: "zoom_video",
|
|
appId: "zoom",
|
|
key: {},
|
|
invalid: false,
|
|
user: { email: "user@example.com" },
|
|
delegatedTo: null,
|
|
delegatedToId: null,
|
|
delegationCredentialId: null,
|
|
} as any);
|
|
|
|
const service = new CredentialAccessService();
|
|
await expect(
|
|
service.ensureAccessible({
|
|
credentialId,
|
|
loggedInUserId,
|
|
bookingOwnerId,
|
|
})
|
|
).resolves.not.toThrow();
|
|
});
|
|
|
|
test("should allow access when credential belongs to booking owner", async () => {
|
|
const credentialId = 1;
|
|
const loggedInUserId = 100;
|
|
const bookingOwnerId = 200;
|
|
|
|
vi.mocked(CredentialRepository.findFirstByIdWithKeyAndUser).mockResolvedValue({
|
|
id: credentialId,
|
|
userId: bookingOwnerId,
|
|
teamId: null,
|
|
type: "zoom_video",
|
|
appId: "zoom",
|
|
key: {},
|
|
invalid: false,
|
|
user: { email: "owner@example.com" },
|
|
delegatedTo: null,
|
|
delegatedToId: null,
|
|
delegationCredentialId: null,
|
|
} as any);
|
|
|
|
const service = new CredentialAccessService();
|
|
await expect(
|
|
service.ensureAccessible({
|
|
credentialId,
|
|
loggedInUserId,
|
|
bookingOwnerId,
|
|
})
|
|
).resolves.not.toThrow();
|
|
});
|
|
|
|
test("should allow access when credential belongs to team the logged-in user belongs to", async () => {
|
|
const credentialId = 1;
|
|
const loggedInUserId = 100;
|
|
const bookingOwnerId = 200;
|
|
const teamId = 50;
|
|
|
|
vi.mocked(CredentialRepository.findFirstByIdWithKeyAndUser).mockResolvedValue({
|
|
id: credentialId,
|
|
userId: null,
|
|
teamId: teamId,
|
|
type: "zoom_video",
|
|
appId: "zoom",
|
|
key: {},
|
|
invalid: false,
|
|
user: null,
|
|
delegatedTo: null,
|
|
delegatedToId: null,
|
|
delegationCredentialId: null,
|
|
} as any);
|
|
|
|
const mockUserRepo = {
|
|
getUserOrganizationAndTeams: vi.fn().mockResolvedValue({
|
|
organizationId: null,
|
|
teams: [{ teamId: teamId }],
|
|
}),
|
|
};
|
|
|
|
vi.mocked(UserRepository).mockImplementation(function () {
|
|
return mockUserRepo as any;
|
|
});
|
|
|
|
const service = new CredentialAccessService();
|
|
await expect(
|
|
service.ensureAccessible({
|
|
credentialId,
|
|
loggedInUserId,
|
|
bookingOwnerId,
|
|
})
|
|
).resolves.not.toThrow();
|
|
});
|
|
|
|
test("should allow access when credential belongs to team the booking owner belongs to", async () => {
|
|
const credentialId = 1;
|
|
const loggedInUserId = 100;
|
|
const bookingOwnerId = 200;
|
|
const teamId = 50;
|
|
|
|
vi.mocked(CredentialRepository.findFirstByIdWithKeyAndUser).mockResolvedValue({
|
|
id: credentialId,
|
|
userId: null,
|
|
teamId: teamId,
|
|
type: "zoom_video",
|
|
appId: "zoom",
|
|
key: {},
|
|
invalid: false,
|
|
user: null,
|
|
delegatedTo: null,
|
|
delegatedToId: null,
|
|
delegationCredentialId: null,
|
|
} as any);
|
|
|
|
const mockUserRepo = {
|
|
getUserOrganizationAndTeams: vi
|
|
.fn()
|
|
.mockResolvedValueOnce({
|
|
organizationId: null,
|
|
teams: [],
|
|
})
|
|
.mockResolvedValueOnce({
|
|
organizationId: null,
|
|
teams: [{ teamId: teamId }],
|
|
}),
|
|
};
|
|
|
|
vi.mocked(UserRepository).mockImplementation(function () {
|
|
return mockUserRepo as any;
|
|
});
|
|
|
|
const service = new CredentialAccessService();
|
|
await expect(
|
|
service.ensureAccessible({
|
|
credentialId,
|
|
loggedInUserId,
|
|
bookingOwnerId,
|
|
})
|
|
).resolves.not.toThrow();
|
|
});
|
|
|
|
test("should allow access when credential belongs to organization the logged-in user belongs to", async () => {
|
|
const credentialId = 1;
|
|
const loggedInUserId = 100;
|
|
const bookingOwnerId = 200;
|
|
const orgId = 50;
|
|
|
|
vi.mocked(CredentialRepository.findFirstByIdWithKeyAndUser).mockResolvedValue({
|
|
id: credentialId,
|
|
userId: null,
|
|
teamId: orgId,
|
|
type: "zoom_video",
|
|
appId: "zoom",
|
|
key: {},
|
|
invalid: false,
|
|
user: null,
|
|
delegatedTo: null,
|
|
delegatedToId: null,
|
|
delegationCredentialId: null,
|
|
} as any);
|
|
|
|
const mockUserRepo = {
|
|
getUserOrganizationAndTeams: vi.fn().mockResolvedValue({
|
|
organizationId: orgId,
|
|
teams: [],
|
|
}),
|
|
};
|
|
|
|
vi.mocked(UserRepository).mockImplementation(function () {
|
|
return mockUserRepo as any;
|
|
});
|
|
|
|
const service = new CredentialAccessService();
|
|
await expect(
|
|
service.ensureAccessible({
|
|
credentialId,
|
|
loggedInUserId,
|
|
bookingOwnerId,
|
|
})
|
|
).resolves.not.toThrow();
|
|
});
|
|
|
|
test("should throw NOT_FOUND when credential does not exist", async () => {
|
|
const credentialId = 1;
|
|
const loggedInUserId = 100;
|
|
const bookingOwnerId = 200;
|
|
|
|
vi.mocked(CredentialRepository.findFirstByIdWithKeyAndUser).mockResolvedValue(null);
|
|
|
|
const service = new CredentialAccessService();
|
|
const error = await service
|
|
.ensureAccessible({
|
|
credentialId,
|
|
loggedInUserId,
|
|
bookingOwnerId,
|
|
})
|
|
.catch((e) => e);
|
|
|
|
expect(error).toBeInstanceOf(HttpError);
|
|
expect(error.statusCode).toBe(404);
|
|
expect(error.message).toBe("Credential not found");
|
|
});
|
|
|
|
test("should throw FORBIDDEN when credential is not accessible", async () => {
|
|
const credentialId = 1;
|
|
const loggedInUserId = 100;
|
|
const bookingOwnerId = 200;
|
|
const otherUserId = 300;
|
|
|
|
vi.mocked(CredentialRepository.findFirstByIdWithKeyAndUser).mockResolvedValue({
|
|
id: credentialId,
|
|
userId: otherUserId,
|
|
teamId: null,
|
|
type: "zoom_video",
|
|
appId: "zoom",
|
|
key: {},
|
|
invalid: false,
|
|
user: { email: "other@example.com" },
|
|
delegatedTo: null,
|
|
delegatedToId: null,
|
|
delegationCredentialId: null,
|
|
} as any);
|
|
|
|
const mockUserRepo = {
|
|
getUserOrganizationAndTeams: vi
|
|
.fn()
|
|
.mockResolvedValueOnce({
|
|
organizationId: null,
|
|
teams: [],
|
|
})
|
|
.mockResolvedValueOnce({
|
|
organizationId: null,
|
|
teams: [],
|
|
}),
|
|
};
|
|
|
|
vi.mocked(UserRepository).mockImplementation(function () {
|
|
return mockUserRepo as any;
|
|
});
|
|
|
|
const service = new CredentialAccessService();
|
|
const error = await service
|
|
.ensureAccessible({
|
|
credentialId,
|
|
loggedInUserId,
|
|
bookingOwnerId,
|
|
})
|
|
.catch((e) => e);
|
|
|
|
expect(error).toBeInstanceOf(HttpError);
|
|
expect(error.statusCode).toBe(403);
|
|
expect(error.message).toBe("You do not have access to this credential");
|
|
});
|
|
|
|
test("should handle null bookingOwnerId", async () => {
|
|
const credentialId = 1;
|
|
const loggedInUserId = 100;
|
|
const bookingOwnerId = null;
|
|
|
|
vi.mocked(CredentialRepository.findFirstByIdWithKeyAndUser).mockResolvedValue({
|
|
id: credentialId,
|
|
userId: loggedInUserId,
|
|
teamId: null,
|
|
type: "zoom_video",
|
|
appId: "zoom",
|
|
key: {},
|
|
invalid: false,
|
|
user: { email: "user@example.com" },
|
|
delegatedTo: null,
|
|
delegatedToId: null,
|
|
delegationCredentialId: null,
|
|
} as any);
|
|
|
|
const service = new CredentialAccessService();
|
|
await expect(
|
|
service.ensureAccessible({
|
|
credentialId,
|
|
loggedInUserId,
|
|
bookingOwnerId,
|
|
})
|
|
).resolves.not.toThrow();
|
|
});
|
|
});
|