import { describe, test, expect, vi, beforeEach } from "vitest"; import { WatchlistType, WatchlistAction, WatchlistSource } from "@calcom/prisma/enums"; import type { IGlobalWatchlistRepository } from "../interface/IWatchlistRepositories"; import { GlobalBlockingService } from "./GlobalBlockingService"; const mockGlobalRepo: IGlobalWatchlistRepository = { findBlockedEmail: vi.fn(), findBlockedDomain: vi.fn(), findFreeEmailDomain: vi.fn(), createEntry: vi.fn(), updateEntry: vi.fn(), deleteEntry: vi.fn(), findById: vi.fn(), listBlockedEntries: vi.fn(), findBlockingEntriesForEmailsAndDomains: vi.fn(), }; describe("GlobalBlockingService", () => { let service: GlobalBlockingService; beforeEach(() => { vi.clearAllMocks(); service = new GlobalBlockingService({ globalRepo: mockGlobalRepo }); }); describe("isBlocked", () => { test("should return blocked when email matches", async () => { const mockEntry = { id: "123", type: WatchlistType.EMAIL, value: "blocked@example.com", description: null, action: WatchlistAction.BLOCK, isGlobal: true, organizationId: null, source: WatchlistSource.MANUAL, lastUpdatedAt: new Date(), }; vi.mocked(mockGlobalRepo.findBlockingEntriesForEmailsAndDomains).mockResolvedValue([mockEntry]); const result = await service.isBlocked("blocked@example.com"); expect(result.isBlocked).toBe(true); expect(result.reason).toBe(WatchlistType.EMAIL); expect(result.watchlistEntry).toEqual(mockEntry); expect(mockGlobalRepo.findBlockingEntriesForEmailsAndDomains).toHaveBeenCalledWith({ emails: ["blocked@example.com"], domains: ["example.com"], }); }); test("should return blocked when domain matches", async () => { const mockEntry = { id: "456", type: WatchlistType.DOMAIN, value: "spam.com", description: null, action: WatchlistAction.BLOCK, isGlobal: true, organizationId: null, source: WatchlistSource.MANUAL, lastUpdatedAt: new Date(), }; vi.mocked(mockGlobalRepo.findBlockingEntriesForEmailsAndDomains).mockResolvedValue([mockEntry]); const result = await service.isBlocked("user@spam.com"); expect(result.isBlocked).toBe(true); expect(result.reason).toBe(WatchlistType.DOMAIN); expect(result.watchlistEntry).toEqual(mockEntry); expect(mockGlobalRepo.findBlockingEntriesForEmailsAndDomains).toHaveBeenCalledWith({ emails: ["user@spam.com"], domains: ["spam.com"], }); }); test("should return not blocked when no matches", async () => { vi.mocked(mockGlobalRepo.findBlockingEntriesForEmailsAndDomains).mockResolvedValue([]); const result = await service.isBlocked("clean@example.com"); expect(result.isBlocked).toBe(false); expect(result.reason).toBeUndefined(); expect(result.watchlistEntry).toBeUndefined(); }); test("should normalize email before checking", async () => { vi.mocked(mockGlobalRepo.findBlockingEntriesForEmailsAndDomains).mockResolvedValue([]); await service.isBlocked("USER@EXAMPLE.COM"); expect(mockGlobalRepo.findBlockingEntriesForEmailsAndDomains).toHaveBeenCalledWith({ emails: ["user@example.com"], domains: ["example.com"], }); }); test("should return email match over domain match", async () => { const emailEntry = { id: "123", type: WatchlistType.EMAIL, value: "specific@example.com", description: null, action: WatchlistAction.BLOCK, isGlobal: true, organizationId: null, source: WatchlistSource.MANUAL, lastUpdatedAt: new Date(), }; const domainEntry = { id: "456", type: WatchlistType.DOMAIN, value: "example.com", description: null, action: WatchlistAction.BLOCK, isGlobal: true, organizationId: null, source: WatchlistSource.MANUAL, lastUpdatedAt: new Date(), }; vi.mocked(mockGlobalRepo.findBlockingEntriesForEmailsAndDomains).mockResolvedValue([ emailEntry, domainEntry, ]); const result = await service.isBlocked("specific@example.com"); expect(result.isBlocked).toBe(true); expect(result.reason).toBe(WatchlistType.EMAIL); expect(result.watchlistEntry).toEqual(emailEntry); }); test("should NOT block subdomain when exact domain is blocked (no wildcard)", async () => { const exactDomainEntry = { id: "789", type: WatchlistType.DOMAIN, value: "cal.com", description: null, action: WatchlistAction.BLOCK, isGlobal: true, organizationId: null, source: WatchlistSource.MANUAL, lastUpdatedAt: new Date(), }; vi.mocked(mockGlobalRepo.findBlockingEntriesForEmailsAndDomains).mockResolvedValue([exactDomainEntry]); const result = await service.isBlocked("user@app.cal.com"); expect(result.isBlocked).toBe(false); expect(mockGlobalRepo.findBlockingEntriesForEmailsAndDomains).toHaveBeenCalledWith({ emails: ["user@app.cal.com"], domains: ["app.cal.com", "*.cal.com"], }); }); test("should block subdomain when wildcard pattern is used", async () => { const wildcardEntry = { id: "789", type: WatchlistType.DOMAIN, value: "*.cal.com", description: null, action: WatchlistAction.BLOCK, isGlobal: true, organizationId: null, source: WatchlistSource.MANUAL, lastUpdatedAt: new Date(), }; vi.mocked(mockGlobalRepo.findBlockingEntriesForEmailsAndDomains).mockResolvedValue([wildcardEntry]); const result = await service.isBlocked("user@app.cal.com"); expect(result.isBlocked).toBe(true); expect(result.reason).toBe(WatchlistType.DOMAIN); expect(result.watchlistEntry).toEqual(wildcardEntry); }); test("should block deeply nested subdomain with wildcard pattern", async () => { const wildcardEntry = { id: "789", type: WatchlistType.DOMAIN, value: "*.app.cal.com", description: null, action: WatchlistAction.BLOCK, isGlobal: true, organizationId: null, source: WatchlistSource.MANUAL, lastUpdatedAt: new Date(), }; vi.mocked(mockGlobalRepo.findBlockingEntriesForEmailsAndDomains).mockResolvedValue([wildcardEntry]); const result = await service.isBlocked("user@sub.app.cal.com"); expect(result.isBlocked).toBe(true); expect(result.reason).toBe(WatchlistType.DOMAIN); expect(result.watchlistEntry).toEqual(wildcardEntry); expect(mockGlobalRepo.findBlockingEntriesForEmailsAndDomains).toHaveBeenCalledWith({ emails: ["user@sub.app.cal.com"], domains: ["sub.app.cal.com", "*.app.cal.com"], }); }); test("should NOT block exact domain with wildcard pattern", async () => { const wildcardEntry = { id: "789", type: WatchlistType.DOMAIN, value: "*.cal.com", description: null, action: WatchlistAction.BLOCK, isGlobal: true, organizationId: null, source: WatchlistSource.MANUAL, lastUpdatedAt: new Date(), }; vi.mocked(mockGlobalRepo.findBlockingEntriesForEmailsAndDomains).mockResolvedValue([wildcardEntry]); const result = await service.isBlocked("user@cal.com"); expect(result.isBlocked).toBe(false); }); test("should prefer exact domain match over wildcard", async () => { const exactEntry = { id: "111", type: WatchlistType.DOMAIN, value: "app.cal.com", description: null, action: WatchlistAction.BLOCK, isGlobal: true, organizationId: null, source: WatchlistSource.MANUAL, lastUpdatedAt: new Date(), }; const wildcardEntry = { id: "222", type: WatchlistType.DOMAIN, value: "*.cal.com", description: null, action: WatchlistAction.BLOCK, isGlobal: true, organizationId: null, source: WatchlistSource.MANUAL, lastUpdatedAt: new Date(), }; vi.mocked(mockGlobalRepo.findBlockingEntriesForEmailsAndDomains).mockResolvedValue([ exactEntry, wildcardEntry, ]); const result = await service.isBlocked("user@app.cal.com"); expect(result.isBlocked).toBe(true); expect(result.reason).toBe(WatchlistType.DOMAIN); expect(result.watchlistEntry).toEqual(exactEntry); }); }); describe("isFreeEmailDomain", () => { test("should return true when domain is in free email list", async () => { const mockEntry = { id: "789", type: WatchlistType.DOMAIN, value: "yahoo.com", description: null, action: WatchlistAction.REPORT, isGlobal: true, organizationId: null, source: WatchlistSource.FREE_DOMAIN_POLICY, lastUpdatedAt: new Date(), }; vi.mocked(mockGlobalRepo.findFreeEmailDomain).mockResolvedValue(mockEntry); const result = await service.isFreeEmailDomain("yahoo.com"); expect(result).toBe(true); expect(mockGlobalRepo.findFreeEmailDomain).toHaveBeenCalledWith("yahoo.com"); }); test("should return false when domain is not in free email list", async () => { vi.mocked(mockGlobalRepo.findFreeEmailDomain).mockResolvedValue(null); const result = await service.isFreeEmailDomain("corporatedomain.com"); expect(result).toBe(false); expect(mockGlobalRepo.findFreeEmailDomain).toHaveBeenCalledWith("corporatedomain.com"); }); test("should normalize domain before checking", async () => { vi.mocked(mockGlobalRepo.findFreeEmailDomain).mockResolvedValue(null); await service.isFreeEmailDomain("GMAIL.COM"); expect(mockGlobalRepo.findFreeEmailDomain).toHaveBeenCalledWith("gmail.com"); }); test("should handle domain with @ prefix", async () => { vi.mocked(mockGlobalRepo.findFreeEmailDomain).mockResolvedValue(null); await service.isFreeEmailDomain("@hotmail.com"); expect(mockGlobalRepo.findFreeEmailDomain).toHaveBeenCalledWith("hotmail.com"); }); }); });