import { describe, it, expect, beforeEach, vi } from "vitest"; import { Test, TestingModule } from "@nestjs/testing"; import { EntryStatus } from "@prisma/client"; import { SearchController } from "./search.controller"; import { SearchService } from "./services/search.service"; import { AuthGuard } from "../auth/guards/auth.guard"; import { WorkspaceGuard, PermissionGuard } from "../common/guards"; describe("SearchController", () => { let controller: SearchController; const mockWorkspaceId = "550e8400-e29b-41d4-a716-446655440000"; const mockSearchService = { search: vi.fn(), searchByTags: vi.fn(), recentEntries: vi.fn(), }; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SearchController], providers: [ { provide: SearchService, useValue: mockSearchService, }, ], }) .overrideGuard(AuthGuard) .useValue({ canActivate: () => true }) .overrideGuard(WorkspaceGuard) .useValue({ canActivate: () => true }) .overrideGuard(PermissionGuard) .useValue({ canActivate: () => true }) .compile(); controller = module.get(SearchController); vi.clearAllMocks(); }); describe("search", () => { it("should call searchService.search with correct parameters", async () => { const mockResult = { data: [], pagination: { page: 1, limit: 20, total: 0, totalPages: 0 }, query: "test", }; mockSearchService.search.mockResolvedValue(mockResult); const result = await controller.search(mockWorkspaceId, { q: "test", page: 1, limit: 20, }); expect(mockSearchService.search).toHaveBeenCalledWith("test", mockWorkspaceId, { status: undefined, page: 1, limit: 20, }); expect(result).toEqual(mockResult); }); it("should pass status filter to service", async () => { mockSearchService.search.mockResolvedValue({ data: [], pagination: { page: 1, limit: 20, total: 0, totalPages: 0 }, query: "test", }); await controller.search(mockWorkspaceId, { q: "test", status: EntryStatus.PUBLISHED, }); expect(mockSearchService.search).toHaveBeenCalledWith("test", mockWorkspaceId, { status: EntryStatus.PUBLISHED, page: undefined, limit: undefined, }); }); it("should pass tags filter to service", async () => { mockSearchService.search.mockResolvedValue({ data: [], pagination: { page: 1, limit: 20, total: 0, totalPages: 0 }, query: "test", }); await controller.search(mockWorkspaceId, { q: "test", tags: ["api", "documentation"], }); expect(mockSearchService.search).toHaveBeenCalledWith("test", mockWorkspaceId, { status: undefined, page: undefined, limit: undefined, tags: ["api", "documentation"], }); }); it("should pass both status and tags filters to service", async () => { mockSearchService.search.mockResolvedValue({ data: [], pagination: { page: 1, limit: 20, total: 0, totalPages: 0 }, query: "test", }); await controller.search(mockWorkspaceId, { q: "test", status: EntryStatus.PUBLISHED, tags: ["api"], page: 2, limit: 10, }); expect(mockSearchService.search).toHaveBeenCalledWith("test", mockWorkspaceId, { status: EntryStatus.PUBLISHED, page: 2, limit: 10, tags: ["api"], }); }); }); describe("searchByTags", () => { it("should call searchService.searchByTags with correct parameters", async () => { const mockResult = { data: [], pagination: { page: 1, limit: 20, total: 0, totalPages: 0 }, }; mockSearchService.searchByTags.mockResolvedValue(mockResult); const result = await controller.searchByTags(mockWorkspaceId, { tags: ["api", "documentation"], page: 1, limit: 20, }); expect(mockSearchService.searchByTags).toHaveBeenCalledWith( ["api", "documentation"], mockWorkspaceId, { status: undefined, page: 1, limit: 20, } ); expect(result).toEqual(mockResult); }); it("should pass status filter to service", async () => { mockSearchService.searchByTags.mockResolvedValue({ data: [], pagination: { page: 1, limit: 20, total: 0, totalPages: 0 }, }); await controller.searchByTags(mockWorkspaceId, { tags: ["api"], status: EntryStatus.DRAFT, }); expect(mockSearchService.searchByTags).toHaveBeenCalledWith(["api"], mockWorkspaceId, { status: EntryStatus.DRAFT, page: undefined, limit: undefined, }); }); }); describe("recentEntries", () => { it("should call searchService.recentEntries with correct parameters", async () => { const mockEntries = [ { id: "entry-1", title: "Recent Entry", slug: "recent-entry", tags: [], }, ]; mockSearchService.recentEntries.mockResolvedValue(mockEntries); const result = await controller.recentEntries(mockWorkspaceId, { limit: 10, }); expect(mockSearchService.recentEntries).toHaveBeenCalledWith(mockWorkspaceId, 10, undefined); expect(result).toEqual({ data: mockEntries, count: 1, }); }); it("should use default limit of 10", async () => { mockSearchService.recentEntries.mockResolvedValue([]); await controller.recentEntries(mockWorkspaceId, {}); expect(mockSearchService.recentEntries).toHaveBeenCalledWith(mockWorkspaceId, 10, undefined); }); it("should pass status filter to service", async () => { mockSearchService.recentEntries.mockResolvedValue([]); await controller.recentEntries(mockWorkspaceId, { status: EntryStatus.PUBLISHED, limit: 5, }); expect(mockSearchService.recentEntries).toHaveBeenCalledWith( mockWorkspaceId, 5, EntryStatus.PUBLISHED ); }); }); });