import { describe, it, expect, beforeEach, vi } from "vitest"; import { Test, TestingModule } from "@nestjs/testing"; import { DomainsService } from "./domains.service"; import { PrismaService } from "../prisma/prisma.service"; import { ActivityService } from "../activity/activity.service"; import { NotFoundException, ConflictException } from "@nestjs/common"; describe("DomainsService", () => { let service: DomainsService; let prisma: PrismaService; let activityService: ActivityService; const mockPrismaService = { domain: { create: vi.fn(), findMany: vi.fn(), count: vi.fn(), findUnique: vi.fn(), findFirst: vi.fn(), update: vi.fn(), delete: vi.fn(), }, }; const mockActivityService = { logDomainCreated: vi.fn(), logDomainUpdated: vi.fn(), logDomainDeleted: vi.fn(), }; const mockWorkspaceId = "550e8400-e29b-41d4-a716-446655440001"; const mockUserId = "550e8400-e29b-41d4-a716-446655440002"; const mockDomainId = "550e8400-e29b-41d4-a716-446655440003"; const mockDomain = { id: mockDomainId, workspaceId: mockWorkspaceId, name: "Work", slug: "work", description: "Work-related tasks and projects", color: "#3B82F6", icon: "briefcase", sortOrder: 0, metadata: {}, createdAt: new Date(), updatedAt: new Date(), }; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ DomainsService, { provide: PrismaService, useValue: mockPrismaService, }, { provide: ActivityService, useValue: mockActivityService, }, ], }).compile(); service = module.get(DomainsService); prisma = module.get(PrismaService); activityService = module.get(ActivityService); // Clear all mocks before each test vi.clearAllMocks(); }); it("should be defined", () => { expect(service).toBeDefined(); }); describe("create", () => { it("should create a domain and log activity", async () => { const createDto = { name: "Work", slug: "work", description: "Work-related tasks", color: "#3B82F6", icon: "briefcase", }; mockPrismaService.domain.create.mockResolvedValue(mockDomain); mockActivityService.logDomainCreated.mockResolvedValue({}); const result = await service.create(mockWorkspaceId, mockUserId, createDto); expect(result).toEqual(mockDomain); expect(prisma.domain.create).toHaveBeenCalledWith({ data: { name: createDto.name, slug: createDto.slug, description: createDto.description, color: createDto.color, icon: createDto.icon, workspace: { connect: { id: mockWorkspaceId }, }, sortOrder: 0, metadata: {}, }, include: { _count: { select: { tasks: true, events: true, projects: true, ideas: true }, }, }, }); expect(activityService.logDomainCreated).toHaveBeenCalledWith( mockWorkspaceId, mockUserId, mockDomainId, { name: mockDomain.name } ); }); it("should throw ConflictException if slug already exists", async () => { const createDto = { name: "Work", slug: "work", }; // Mock Prisma throwing unique constraint error const prismaError = new Error("Unique constraint failed") as any; prismaError.code = "P2002"; mockPrismaService.domain.create.mockRejectedValue(prismaError); await expect(service.create(mockWorkspaceId, mockUserId, createDto)).rejects.toThrow(); }); it("should use default values for optional fields", async () => { const createDto = { name: "Work", slug: "work", }; mockPrismaService.domain.create.mockResolvedValue(mockDomain); mockActivityService.logDomainCreated.mockResolvedValue({}); await service.create(mockWorkspaceId, mockUserId, createDto); expect(prisma.domain.create).toHaveBeenCalledWith({ data: { name: "Work", slug: "work", description: null, color: null, icon: null, workspace: { connect: { id: mockWorkspaceId }, }, sortOrder: 0, metadata: {}, }, include: { _count: { select: { tasks: true, events: true, projects: true, ideas: true }, }, }, }); }); }); describe("findAll", () => { it("should return paginated domains", async () => { const query = { workspaceId: mockWorkspaceId, page: 1, limit: 10 }; const mockDomains = [mockDomain]; mockPrismaService.domain.findMany.mockResolvedValue(mockDomains); mockPrismaService.domain.count.mockResolvedValue(1); const result = await service.findAll(query); expect(result).toEqual({ data: mockDomains, meta: { total: 1, page: 1, limit: 10, totalPages: 1, }, }); expect(prisma.domain.findMany).toHaveBeenCalled(); expect(prisma.domain.count).toHaveBeenCalled(); }); it("should filter by search term", async () => { const query = { workspaceId: mockWorkspaceId, page: 1, limit: 10, search: "work", }; mockPrismaService.domain.findMany.mockResolvedValue([mockDomain]); mockPrismaService.domain.count.mockResolvedValue(1); await service.findAll(query); expect(prisma.domain.findMany).toHaveBeenCalled(); }); it("should use default pagination values", async () => { const query = { workspaceId: mockWorkspaceId }; mockPrismaService.domain.findMany.mockResolvedValue([]); mockPrismaService.domain.count.mockResolvedValue(0); await service.findAll(query); expect(prisma.domain.findMany).toHaveBeenCalled(); }); it("should calculate pagination correctly", async () => { const query = { workspaceId: mockWorkspaceId, page: 3, limit: 20 }; mockPrismaService.domain.findMany.mockResolvedValue([]); mockPrismaService.domain.count.mockResolvedValue(55); const result = await service.findAll(query); expect(result.meta).toEqual({ total: 55, page: 3, limit: 20, totalPages: 3, }); expect(prisma.domain.findMany).toHaveBeenCalled(); }); }); describe("findOne", () => { it("should return a domain by ID", async () => { mockPrismaService.domain.findUnique.mockResolvedValue(mockDomain); const result = await service.findOne(mockDomainId, mockWorkspaceId); expect(result).toEqual(mockDomain); expect(prisma.domain.findUnique).toHaveBeenCalledWith({ where: { id: mockDomainId, workspaceId: mockWorkspaceId, }, include: { _count: { select: { tasks: true, projects: true, events: true, ideas: true, }, }, }, }); }); it("should throw NotFoundException if domain not found", async () => { mockPrismaService.domain.findUnique.mockResolvedValue(null); await expect(service.findOne(mockDomainId, mockWorkspaceId)).rejects.toThrow( NotFoundException ); }); }); describe("update", () => { it("should update a domain and log activity", async () => { const updateDto = { name: "Updated Work", color: "#10B981", }; const updatedDomain = { ...mockDomain, ...updateDto }; mockPrismaService.domain.findUnique.mockResolvedValue(mockDomain); mockPrismaService.domain.update.mockResolvedValue(updatedDomain); mockActivityService.logDomainUpdated.mockResolvedValue({}); const result = await service.update(mockDomainId, mockWorkspaceId, mockUserId, updateDto); expect(result).toEqual(updatedDomain); expect(prisma.domain.update).toHaveBeenCalledWith({ where: { id: mockDomainId, workspaceId: mockWorkspaceId, }, data: updateDto, include: { _count: { select: { tasks: true, events: true, projects: true, ideas: true }, }, }, }); expect(activityService.logDomainUpdated).toHaveBeenCalledWith( mockWorkspaceId, mockUserId, mockDomainId, { changes: updateDto } ); }); it("should throw NotFoundException if domain not found", async () => { const updateDto = { name: "Updated Work" }; mockPrismaService.domain.findUnique.mockResolvedValue(null); await expect( service.update(mockDomainId, mockWorkspaceId, mockUserId, updateDto) ).rejects.toThrow(NotFoundException); expect(prisma.domain.update).not.toHaveBeenCalled(); }); it("should throw ConflictException if slug already exists for another domain", async () => { const updateDto = { slug: "existing-slug" }; mockPrismaService.domain.findUnique.mockResolvedValue(mockDomain); // Mock Prisma throwing unique constraint error const prismaError = new Error("Unique constraint failed") as any; prismaError.code = "P2002"; mockPrismaService.domain.update.mockRejectedValue(prismaError); await expect( service.update(mockDomainId, mockWorkspaceId, mockUserId, updateDto) ).rejects.toThrow(); }); it("should allow updating to the same slug", async () => { const updateDto = { slug: "work", name: "Updated Work" }; mockPrismaService.domain.findUnique.mockResolvedValue(mockDomain); mockPrismaService.domain.update.mockResolvedValue({ ...mockDomain, ...updateDto }); mockActivityService.logDomainUpdated.mockResolvedValue({}); await service.update(mockDomainId, mockWorkspaceId, mockUserId, updateDto); expect(prisma.domain.update).toHaveBeenCalled(); }); }); describe("remove", () => { it("should delete a domain and log activity", async () => { mockPrismaService.domain.findUnique.mockResolvedValue(mockDomain); mockPrismaService.domain.delete.mockResolvedValue(mockDomain); mockActivityService.logDomainDeleted.mockResolvedValue({}); await service.remove(mockDomainId, mockWorkspaceId, mockUserId); expect(prisma.domain.delete).toHaveBeenCalledWith({ where: { id: mockDomainId, workspaceId: mockWorkspaceId, }, }); expect(activityService.logDomainDeleted).toHaveBeenCalledWith( mockWorkspaceId, mockUserId, mockDomainId, { name: mockDomain.name } ); }); it("should throw NotFoundException if domain not found", async () => { mockPrismaService.domain.findUnique.mockResolvedValue(null); await expect(service.remove(mockDomainId, mockWorkspaceId, mockUserId)).rejects.toThrow( NotFoundException ); expect(prisma.domain.delete).not.toHaveBeenCalled(); }); }); });