* 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>
389 lines
14 KiB
TypeScript
389 lines
14 KiB
TypeScript
import { describe, test, expect, vi, beforeEach } from "vitest";
|
|
|
|
import {
|
|
getBlockedUsersMap,
|
|
isUserBlocked,
|
|
checkWatchlistBlocking,
|
|
type BlockableUser,
|
|
type BlockingInfo,
|
|
} from "./check-user-blocking";
|
|
|
|
vi.mock("@calcom/features/di/watchlist/containers/watchlist", () => ({
|
|
getWatchlistFeature: vi.fn(),
|
|
}));
|
|
|
|
const mockGlobalBlocking: { areBlocked: ReturnType<typeof vi.fn> } = {
|
|
areBlocked: vi.fn(),
|
|
};
|
|
|
|
const mockOrgBlocking: { areBlocked: ReturnType<typeof vi.fn> } = {
|
|
areBlocked: vi.fn(),
|
|
};
|
|
|
|
const mockWatchlistFeature: {
|
|
globalBlocking: typeof mockGlobalBlocking;
|
|
orgBlocking: typeof mockOrgBlocking;
|
|
watchlist: Record<string, unknown>;
|
|
audit: Record<string, unknown>;
|
|
} = {
|
|
globalBlocking: mockGlobalBlocking,
|
|
orgBlocking: mockOrgBlocking,
|
|
watchlist: {},
|
|
audit: {},
|
|
};
|
|
|
|
describe("check-user-blocking", () => {
|
|
beforeEach(async () => {
|
|
vi.clearAllMocks();
|
|
const { getWatchlistFeature } = await import("@calcom/features/di/watchlist/containers/watchlist");
|
|
vi.mocked(getWatchlistFeature).mockResolvedValue(mockWatchlistFeature as never);
|
|
});
|
|
|
|
describe("getBlockedUsersMap", () => {
|
|
describe("Empty input handling", () => {
|
|
test("should return empty result for empty users array", async () => {
|
|
const result = await getBlockedUsersMap([]);
|
|
|
|
expect(result).toEqual({
|
|
blockingMap: new Map(),
|
|
blockedCount: 0,
|
|
lockedCount: 0,
|
|
watchlistBlockedCount: 0,
|
|
});
|
|
expect(mockGlobalBlocking.areBlocked).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test("should skip users with empty emails", async () => {
|
|
mockGlobalBlocking.areBlocked.mockResolvedValue(
|
|
new Map([["valid@example.com", { isBlocked: false }]])
|
|
);
|
|
|
|
const users: BlockableUser[] = [
|
|
{ email: "", locked: false },
|
|
{ email: " ", locked: false },
|
|
{ email: "valid@example.com", locked: false },
|
|
];
|
|
|
|
const result = await getBlockedUsersMap(users);
|
|
|
|
expect(result.blockedCount).toBe(0);
|
|
expect(mockGlobalBlocking.areBlocked).toHaveBeenCalledWith(["valid@example.com"]);
|
|
});
|
|
|
|
test("should return empty result if all users have empty emails", async () => {
|
|
const users: BlockableUser[] = [
|
|
{ email: "", locked: false },
|
|
{ email: " ", locked: false },
|
|
];
|
|
|
|
const result = await getBlockedUsersMap(users);
|
|
|
|
expect(result).toEqual({
|
|
blockingMap: new Map(),
|
|
blockedCount: 0,
|
|
lockedCount: 0,
|
|
watchlistBlockedCount: 0,
|
|
});
|
|
expect(mockGlobalBlocking.areBlocked).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe("Locked user blocking", () => {
|
|
test("should block locked users without calling watchlist", async () => {
|
|
const users: BlockableUser[] = [
|
|
{ email: "locked@example.com", locked: true },
|
|
{ email: "unlocked@example.com", locked: false },
|
|
];
|
|
|
|
mockGlobalBlocking.areBlocked.mockResolvedValue(
|
|
new Map([["unlocked@example.com", { isBlocked: false }]])
|
|
);
|
|
|
|
const result = await getBlockedUsersMap(users);
|
|
|
|
expect(result.blockedCount).toBe(1);
|
|
expect(result.lockedCount).toBe(1);
|
|
expect(result.watchlistBlockedCount).toBe(0);
|
|
|
|
const lockedInfo = result.blockingMap.get("locked@example.com");
|
|
expect(lockedInfo).toEqual({ isBlocked: true, reason: "locked" });
|
|
|
|
// Should only check unlocked user in watchlist
|
|
expect(mockGlobalBlocking.areBlocked).toHaveBeenCalledWith(["unlocked@example.com"]);
|
|
});
|
|
|
|
test("should not call watchlist if all users are locked", async () => {
|
|
const users: BlockableUser[] = [
|
|
{ email: "locked1@example.com", locked: true },
|
|
{ email: "locked2@example.com", locked: true },
|
|
];
|
|
|
|
const result = await getBlockedUsersMap(users);
|
|
|
|
expect(result.blockedCount).toBe(2);
|
|
expect(result.lockedCount).toBe(2);
|
|
expect(result.watchlistBlockedCount).toBe(0);
|
|
expect(mockGlobalBlocking.areBlocked).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test("should correctly count multiple locked users", async () => {
|
|
const users: BlockableUser[] = [
|
|
{ email: "locked1@example.com", locked: true },
|
|
{ email: "locked2@example.com", locked: true },
|
|
{ email: "locked3@example.com", locked: true },
|
|
];
|
|
|
|
const result = await getBlockedUsersMap(users);
|
|
|
|
expect(result.lockedCount).toBe(3);
|
|
expect(result.blockedCount).toBe(3);
|
|
});
|
|
});
|
|
|
|
describe("Watchlist blocking", () => {
|
|
test("should block users on global watchlist", async () => {
|
|
const users: BlockableUser[] = [
|
|
{ email: "blocked@spam.com", locked: false },
|
|
{ email: "clean@example.com", locked: false },
|
|
];
|
|
|
|
mockGlobalBlocking.areBlocked.mockResolvedValue(
|
|
new Map([
|
|
["blocked@spam.com", { isBlocked: true }],
|
|
["clean@example.com", { isBlocked: false }],
|
|
])
|
|
);
|
|
|
|
const result = await getBlockedUsersMap(users);
|
|
|
|
expect(result.blockedCount).toBe(1);
|
|
expect(result.lockedCount).toBe(0);
|
|
expect(result.watchlistBlockedCount).toBe(1);
|
|
|
|
const blockedInfo = result.blockingMap.get("blocked@spam.com");
|
|
expect(blockedInfo).toEqual({ isBlocked: true, reason: "watchlist" });
|
|
|
|
const cleanInfo = result.blockingMap.get("clean@example.com");
|
|
expect(cleanInfo).toEqual({ isBlocked: false });
|
|
});
|
|
|
|
test("should check org-level blocking when organizationId is provided", async () => {
|
|
const users: BlockableUser[] = [{ email: "user@example.com", locked: false }];
|
|
|
|
mockGlobalBlocking.areBlocked.mockResolvedValue(
|
|
new Map([["user@example.com", { isBlocked: false }]])
|
|
);
|
|
mockOrgBlocking.areBlocked.mockResolvedValue(new Map([["user@example.com", { isBlocked: true }]]));
|
|
|
|
const result = await getBlockedUsersMap(users, 123);
|
|
|
|
expect(result.blockedCount).toBe(1);
|
|
expect(result.watchlistBlockedCount).toBe(1);
|
|
expect(mockOrgBlocking.areBlocked).toHaveBeenCalledWith(["user@example.com"], 123);
|
|
});
|
|
|
|
test("should not check org-level blocking when organizationId is not provided", async () => {
|
|
const users: BlockableUser[] = [{ email: "user@example.com", locked: false }];
|
|
|
|
mockGlobalBlocking.areBlocked.mockResolvedValue(
|
|
new Map([["user@example.com", { isBlocked: false }]])
|
|
);
|
|
|
|
await getBlockedUsersMap(users);
|
|
|
|
expect(mockOrgBlocking.areBlocked).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test("should not check org-level blocking when organizationId is null", async () => {
|
|
const users: BlockableUser[] = [{ email: "user@example.com", locked: false }];
|
|
|
|
mockGlobalBlocking.areBlocked.mockResolvedValue(
|
|
new Map([["user@example.com", { isBlocked: false }]])
|
|
);
|
|
|
|
await getBlockedUsersMap(users, null);
|
|
|
|
expect(mockOrgBlocking.areBlocked).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe("Combined blocking (locked + watchlist)", () => {
|
|
test("should correctly count both locked and watchlist-blocked users", async () => {
|
|
const users: BlockableUser[] = [
|
|
{ email: "locked@example.com", locked: true },
|
|
{ email: "watchlist-blocked@spam.com", locked: false },
|
|
{ email: "clean@example.com", locked: false },
|
|
];
|
|
|
|
mockGlobalBlocking.areBlocked.mockResolvedValue(
|
|
new Map([
|
|
["watchlist-blocked@spam.com", { isBlocked: true }],
|
|
["clean@example.com", { isBlocked: false }],
|
|
])
|
|
);
|
|
|
|
const result = await getBlockedUsersMap(users);
|
|
|
|
expect(result.blockedCount).toBe(2);
|
|
expect(result.lockedCount).toBe(1);
|
|
expect(result.watchlistBlockedCount).toBe(1);
|
|
});
|
|
|
|
test("should prioritize locked status over watchlist (locked users not checked in watchlist)", async () => {
|
|
const users: BlockableUser[] = [
|
|
{ email: "locked-and-would-be-blocked@spam.com", locked: true },
|
|
{ email: "clean@example.com", locked: false },
|
|
];
|
|
|
|
mockGlobalBlocking.areBlocked.mockResolvedValue(
|
|
new Map([["clean@example.com", { isBlocked: false }]])
|
|
);
|
|
|
|
const result = await getBlockedUsersMap(users);
|
|
|
|
// The locked user's email should NOT be sent to watchlist check
|
|
expect(mockGlobalBlocking.areBlocked).toHaveBeenCalledWith(["clean@example.com"]);
|
|
expect(mockGlobalBlocking.areBlocked).not.toHaveBeenCalledWith(
|
|
expect.arrayContaining(["locked-and-would-be-blocked@spam.com"])
|
|
);
|
|
|
|
// But the locked user should still be marked as blocked
|
|
const lockedInfo = result.blockingMap.get("locked-and-would-be-blocked@spam.com");
|
|
expect(lockedInfo).toEqual({ isBlocked: true, reason: "locked" });
|
|
});
|
|
});
|
|
|
|
describe("Email normalization", () => {
|
|
test("should normalize emails to lowercase", async () => {
|
|
const users: BlockableUser[] = [{ email: "USER@EXAMPLE.COM", locked: false }];
|
|
|
|
mockGlobalBlocking.areBlocked.mockResolvedValue(
|
|
new Map([["user@example.com", { isBlocked: false }]])
|
|
);
|
|
|
|
const result = await getBlockedUsersMap(users);
|
|
|
|
expect(result.blockingMap.has("user@example.com")).toBe(true);
|
|
expect(result.blockingMap.has("USER@EXAMPLE.COM")).toBe(false);
|
|
});
|
|
|
|
test("should trim whitespace from emails", async () => {
|
|
const users: BlockableUser[] = [{ email: " user@example.com ", locked: false }];
|
|
|
|
mockGlobalBlocking.areBlocked.mockResolvedValue(
|
|
new Map([["user@example.com", { isBlocked: false }]])
|
|
);
|
|
|
|
const result = await getBlockedUsersMap(users);
|
|
|
|
expect(result.blockingMap.has("user@example.com")).toBe(true);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("isUserBlocked", () => {
|
|
test("should return true for blocked user", () => {
|
|
const blockingMap = new Map<string, BlockingInfo>([
|
|
["blocked@example.com", { isBlocked: true, reason: "locked" }],
|
|
]);
|
|
|
|
expect(isUserBlocked("blocked@example.com", blockingMap)).toBe(true);
|
|
});
|
|
|
|
test("should return false for non-blocked user", () => {
|
|
const blockingMap = new Map<string, BlockingInfo>([["clean@example.com", { isBlocked: false }]]);
|
|
|
|
expect(isUserBlocked("clean@example.com", blockingMap)).toBe(false);
|
|
});
|
|
|
|
test("should return false for user not in map", () => {
|
|
const blockingMap = new Map<string, BlockingInfo>();
|
|
|
|
expect(isUserBlocked("unknown@example.com", blockingMap)).toBe(false);
|
|
});
|
|
|
|
test("should return false for empty email", () => {
|
|
const blockingMap = new Map<string, BlockingInfo>([
|
|
["blocked@example.com", { isBlocked: true, reason: "locked" }],
|
|
]);
|
|
|
|
expect(isUserBlocked("", blockingMap)).toBe(false);
|
|
expect(isUserBlocked(" ", blockingMap)).toBe(false);
|
|
});
|
|
|
|
test("should normalize email before lookup", () => {
|
|
const blockingMap = new Map<string, BlockingInfo>([
|
|
["blocked@example.com", { isBlocked: true, reason: "locked" }],
|
|
]);
|
|
|
|
expect(isUserBlocked("BLOCKED@EXAMPLE.COM", blockingMap)).toBe(true);
|
|
expect(isUserBlocked(" blocked@example.com ", blockingMap)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("checkWatchlistBlocking", () => {
|
|
test("should batch check all emails against global watchlist", async () => {
|
|
const emails = ["user1@example.com", "user2@example.com"];
|
|
|
|
mockGlobalBlocking.areBlocked.mockResolvedValue(
|
|
new Map([
|
|
["user1@example.com", { isBlocked: false }],
|
|
["user2@example.com", { isBlocked: true }],
|
|
])
|
|
);
|
|
|
|
const result = await checkWatchlistBlocking(emails);
|
|
|
|
expect(mockGlobalBlocking.areBlocked).toHaveBeenCalledWith(emails);
|
|
expect(result.get("user1@example.com")).toBe(false);
|
|
expect(result.get("user2@example.com")).toBe(true);
|
|
});
|
|
|
|
test("should combine global and org blocking results", async () => {
|
|
const emails = ["user@example.com"];
|
|
|
|
mockGlobalBlocking.areBlocked.mockResolvedValue(new Map([["user@example.com", { isBlocked: false }]]));
|
|
mockOrgBlocking.areBlocked.mockResolvedValue(new Map([["user@example.com", { isBlocked: true }]]));
|
|
|
|
const result = await checkWatchlistBlocking(emails, 123);
|
|
|
|
expect(result.get("user@example.com")).toBe(true);
|
|
});
|
|
|
|
test("should block if either global or org blocks", async () => {
|
|
const emails = ["global-blocked@example.com", "org-blocked@example.com", "clean@example.com"];
|
|
|
|
mockGlobalBlocking.areBlocked.mockResolvedValue(
|
|
new Map([
|
|
["global-blocked@example.com", { isBlocked: true }],
|
|
["org-blocked@example.com", { isBlocked: false }],
|
|
["clean@example.com", { isBlocked: false }],
|
|
])
|
|
);
|
|
mockOrgBlocking.areBlocked.mockResolvedValue(
|
|
new Map([
|
|
["global-blocked@example.com", { isBlocked: false }],
|
|
["org-blocked@example.com", { isBlocked: true }],
|
|
["clean@example.com", { isBlocked: false }],
|
|
])
|
|
);
|
|
|
|
const result = await checkWatchlistBlocking(emails, 123);
|
|
|
|
expect(result.get("global-blocked@example.com")).toBe(true);
|
|
expect(result.get("org-blocked@example.com")).toBe(true);
|
|
expect(result.get("clean@example.com")).toBe(false);
|
|
});
|
|
|
|
test("should normalize emails in result map", async () => {
|
|
const emails = ["USER@EXAMPLE.COM"];
|
|
|
|
mockGlobalBlocking.areBlocked.mockResolvedValue(new Map([["user@example.com", { isBlocked: true }]]));
|
|
|
|
const result = await checkWatchlistBlocking(emails);
|
|
|
|
expect(result.get("user@example.com")).toBe(true);
|
|
});
|
|
});
|
|
});
|