import { describe, it, expect, beforeEach, vi } from "vitest"; import { Test, TestingModule } from "@nestjs/testing"; import { PersonalitiesService } from "./personalities.service"; import { PrismaService } from "../prisma/prisma.service"; import { CreatePersonalityDto, UpdatePersonalityDto } from "./dto"; import { NotFoundException, ConflictException } from "@nestjs/common"; describe("PersonalitiesService", () => { let service: PersonalitiesService; let prisma: PrismaService; const mockWorkspaceId = "workspace-123"; const mockPersonalityId = "personality-123"; const mockProviderId = "provider-123"; const mockPersonality = { id: mockPersonalityId, workspaceId: mockWorkspaceId, name: "professional-assistant", displayName: "Professional Assistant", description: "A professional communication assistant", systemPrompt: "You are a professional assistant who helps with tasks.", temperature: 0.7, maxTokens: 2000, llmProviderInstanceId: mockProviderId, isDefault: true, isEnabled: true, createdAt: new Date(), updatedAt: new Date(), }; const mockPrismaService = { personality: { findMany: vi.fn(), findUnique: vi.fn(), findFirst: vi.fn(), create: vi.fn(), update: vi.fn(), delete: vi.fn(), count: vi.fn(), }, $transaction: vi.fn((callback) => callback(mockPrismaService)), }; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ PersonalitiesService, { provide: PrismaService, useValue: mockPrismaService, }, ], }).compile(); service = module.get(PersonalitiesService); prisma = module.get(PrismaService); // Reset mocks vi.clearAllMocks(); }); describe("create", () => { const createDto: CreatePersonalityDto = { name: "casual-helper", displayName: "Casual Helper", description: "A casual communication helper", systemPrompt: "You are a casual assistant.", temperature: 0.8, maxTokens: 1500, llmProviderInstanceId: mockProviderId, }; it("should create a new personality", async () => { mockPrismaService.personality.findFirst.mockResolvedValue(null); mockPrismaService.personality.create.mockResolvedValue({ ...mockPersonality, ...createDto, id: "new-personality-id", isDefault: false, isEnabled: true, }); const result = await service.create(mockWorkspaceId, createDto); expect(result).toMatchObject(createDto); expect(prisma.personality.create).toHaveBeenCalledWith({ data: { workspaceId: mockWorkspaceId, name: createDto.name, displayName: createDto.displayName, description: createDto.description ?? null, systemPrompt: createDto.systemPrompt, temperature: createDto.temperature ?? null, maxTokens: createDto.maxTokens ?? null, llmProviderInstanceId: createDto.llmProviderInstanceId ?? null, isDefault: false, isEnabled: true, }, }); }); it("should throw ConflictException when name already exists", async () => { mockPrismaService.personality.findFirst.mockResolvedValue(mockPersonality); await expect(service.create(mockWorkspaceId, createDto)).rejects.toThrow(ConflictException); }); it("should unset other defaults when creating a new default personality", async () => { const createDefaultDto = { ...createDto, isDefault: true }; // First call to findFirst checks for name conflict (should be null) // Second call to findFirst finds the existing default personality mockPrismaService.personality.findFirst .mockResolvedValueOnce(null) // No name conflict .mockResolvedValueOnce(mockPersonality); // Existing default mockPrismaService.personality.update.mockResolvedValue({ ...mockPersonality, isDefault: false, }); mockPrismaService.personality.create.mockResolvedValue({ ...mockPersonality, ...createDefaultDto, }); await service.create(mockWorkspaceId, createDefaultDto); expect(prisma.personality.update).toHaveBeenCalledWith({ where: { id: mockPersonalityId }, data: { isDefault: false }, }); }); }); describe("findAll", () => { it("should return all personalities for a workspace", async () => { const mockPersonalities = [mockPersonality]; mockPrismaService.personality.findMany.mockResolvedValue(mockPersonalities); const result = await service.findAll(mockWorkspaceId); expect(result).toEqual(mockPersonalities); expect(prisma.personality.findMany).toHaveBeenCalledWith({ where: { workspaceId: mockWorkspaceId }, orderBy: [{ isDefault: "desc" }, { name: "asc" }], }); }); }); describe("findOne", () => { it("should return a personality by id", async () => { mockPrismaService.personality.findUnique.mockResolvedValue(mockPersonality); const result = await service.findOne(mockWorkspaceId, mockPersonalityId); expect(result).toEqual(mockPersonality); expect(prisma.personality.findUnique).toHaveBeenCalledWith({ where: { id: mockPersonalityId, workspaceId: mockWorkspaceId, }, }); }); it("should throw NotFoundException when personality not found", async () => { mockPrismaService.personality.findUnique.mockResolvedValue(null); await expect(service.findOne(mockWorkspaceId, mockPersonalityId)).rejects.toThrow( NotFoundException ); }); }); describe("findByName", () => { it("should return a personality by name", async () => { mockPrismaService.personality.findFirst.mockResolvedValue(mockPersonality); const result = await service.findByName(mockWorkspaceId, "professional-assistant"); expect(result).toEqual(mockPersonality); expect(prisma.personality.findFirst).toHaveBeenCalledWith({ where: { workspaceId: mockWorkspaceId, name: "professional-assistant", }, }); }); it("should throw NotFoundException when personality not found", async () => { mockPrismaService.personality.findFirst.mockResolvedValue(null); await expect(service.findByName(mockWorkspaceId, "non-existent")).rejects.toThrow( NotFoundException ); }); }); describe("findDefault", () => { it("should return the default personality", async () => { mockPrismaService.personality.findFirst.mockResolvedValue(mockPersonality); const result = await service.findDefault(mockWorkspaceId); expect(result).toEqual(mockPersonality); expect(prisma.personality.findFirst).toHaveBeenCalledWith({ where: { workspaceId: mockWorkspaceId, isDefault: true, isEnabled: true }, }); }); it("should throw NotFoundException when no default personality exists", async () => { mockPrismaService.personality.findFirst.mockResolvedValue(null); await expect(service.findDefault(mockWorkspaceId)).rejects.toThrow(NotFoundException); }); }); describe("update", () => { const updateDto: UpdatePersonalityDto = { description: "Updated description", temperature: 0.9, }; it("should update a personality", async () => { mockPrismaService.personality.findUnique.mockResolvedValue(mockPersonality); mockPrismaService.personality.findFirst.mockResolvedValue(null); mockPrismaService.personality.update.mockResolvedValue({ ...mockPersonality, ...updateDto, }); const result = await service.update(mockWorkspaceId, mockPersonalityId, updateDto); expect(result).toMatchObject(updateDto); expect(prisma.personality.update).toHaveBeenCalledWith({ where: { id: mockPersonalityId }, data: updateDto, }); }); it("should throw NotFoundException when personality not found", async () => { mockPrismaService.personality.findUnique.mockResolvedValue(null); await expect(service.update(mockWorkspaceId, mockPersonalityId, updateDto)).rejects.toThrow( NotFoundException ); }); it("should throw ConflictException when updating to existing name", async () => { const updateNameDto = { name: "existing-name" }; mockPrismaService.personality.findUnique.mockResolvedValue(mockPersonality); mockPrismaService.personality.findFirst.mockResolvedValue({ ...mockPersonality, id: "different-id", }); await expect( service.update(mockWorkspaceId, mockPersonalityId, updateNameDto) ).rejects.toThrow(ConflictException); }); it("should unset other defaults when setting as default", async () => { const updateDefaultDto = { isDefault: true }; const otherPersonality = { ...mockPersonality, id: "other-id", isDefault: true }; mockPrismaService.personality.findUnique.mockResolvedValue(mockPersonality); mockPrismaService.personality.findFirst.mockResolvedValue(otherPersonality); // Existing default from unsetOtherDefaults mockPrismaService.personality.update .mockResolvedValueOnce({ ...otherPersonality, isDefault: false }) // Unset old default .mockResolvedValueOnce({ ...mockPersonality, isDefault: true }); // Set new default await service.update(mockWorkspaceId, mockPersonalityId, updateDefaultDto); expect(prisma.personality.update).toHaveBeenNthCalledWith(1, { where: { id: "other-id" }, data: { isDefault: false }, }); expect(prisma.personality.update).toHaveBeenNthCalledWith(2, { where: { id: mockPersonalityId }, data: updateDefaultDto, }); }); }); describe("delete", () => { it("should delete a personality", async () => { mockPrismaService.personality.findUnique.mockResolvedValue(mockPersonality); mockPrismaService.personality.delete.mockResolvedValue(undefined); await service.delete(mockWorkspaceId, mockPersonalityId); expect(prisma.personality.delete).toHaveBeenCalledWith({ where: { id: mockPersonalityId }, }); }); it("should throw NotFoundException when personality not found", async () => { mockPrismaService.personality.findUnique.mockResolvedValue(null); await expect(service.delete(mockWorkspaceId, mockPersonalityId)).rejects.toThrow( NotFoundException ); }); }); describe("setDefault", () => { it("should set a personality as default", async () => { const otherPersonality = { ...mockPersonality, id: "other-id", isDefault: true }; const updatedPersonality = { ...mockPersonality, isDefault: true }; mockPrismaService.personality.findUnique.mockResolvedValue(mockPersonality); mockPrismaService.personality.findFirst.mockResolvedValue(otherPersonality); mockPrismaService.personality.update .mockResolvedValueOnce({ ...otherPersonality, isDefault: false }) // Unset old default .mockResolvedValueOnce(updatedPersonality); // Set new default const result = await service.setDefault(mockWorkspaceId, mockPersonalityId); expect(result).toMatchObject({ isDefault: true }); expect(prisma.personality.update).toHaveBeenNthCalledWith(1, { where: { id: "other-id" }, data: { isDefault: false }, }); expect(prisma.personality.update).toHaveBeenNthCalledWith(2, { where: { id: mockPersonalityId }, data: { isDefault: true }, }); }); it("should throw NotFoundException when personality not found", async () => { mockPrismaService.personality.findUnique.mockResolvedValue(null); await expect(service.setDefault(mockWorkspaceId, mockPersonalityId)).rejects.toThrow( NotFoundException ); }); }); });