import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; import { Test, TestingModule } from "@nestjs/testing"; import { WorkspaceSettingsService } from "./workspace-settings.service"; import { PrismaService } from "../prisma/prisma.service"; import type { WorkspaceLlmSettings, LlmProviderInstance, Personality } from "@prisma/client"; describe("WorkspaceSettingsService", () => { let service: WorkspaceSettingsService; let prisma: PrismaService; const mockWorkspaceId = "workspace-123"; const mockUserId = "user-123"; const mockSettings: WorkspaceLlmSettings = { id: "settings-123", workspaceId: mockWorkspaceId, defaultLlmProviderId: "provider-123", defaultPersonalityId: "personality-123", settings: {}, createdAt: new Date(), updatedAt: new Date(), }; const mockProvider: LlmProviderInstance = { id: "provider-123", providerType: "ollama", displayName: "Test Provider", userId: null, config: { endpoint: "http://localhost:11434" }, isDefault: true, isEnabled: true, createdAt: new Date(), updatedAt: new Date(), }; const mockUserProvider: LlmProviderInstance = { id: "user-provider-123", providerType: "ollama", displayName: "User Provider", userId: mockUserId, config: { endpoint: "http://user-ollama:11434" }, isDefault: false, isEnabled: true, createdAt: new Date(), updatedAt: new Date(), }; const mockPersonality: Personality = { id: "personality-123", workspaceId: mockWorkspaceId, name: "default", displayName: "Default", description: "Default personality", systemPrompt: "You are a helpful assistant", temperature: null, maxTokens: null, llmProviderInstanceId: null, isDefault: true, isEnabled: true, createdAt: new Date(), updatedAt: new Date(), }; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ WorkspaceSettingsService, { provide: PrismaService, useValue: { workspaceLlmSettings: { findUnique: vi.fn(), create: vi.fn(), update: vi.fn(), }, llmProviderInstance: { findFirst: vi.fn(), findUnique: vi.fn(), }, personality: { findFirst: vi.fn(), findUnique: vi.fn(), }, }, }, ], }).compile(); service = module.get(WorkspaceSettingsService); prisma = module.get(PrismaService); }); afterEach(() => { vi.clearAllMocks(); }); describe("getSettings", () => { it("should return existing settings for workspace", async () => { vi.spyOn(prisma.workspaceLlmSettings, "findUnique").mockResolvedValue(mockSettings); const result = await service.getSettings(mockWorkspaceId); expect(result).toEqual(mockSettings); expect(prisma.workspaceLlmSettings.findUnique).toHaveBeenCalledWith({ where: { workspaceId: mockWorkspaceId }, }); }); it("should create default settings if not exists", async () => { vi.spyOn(prisma.workspaceLlmSettings, "findUnique").mockResolvedValue(null); vi.spyOn(prisma.workspaceLlmSettings, "create").mockResolvedValue(mockSettings); const result = await service.getSettings(mockWorkspaceId); expect(result).toEqual(mockSettings); expect(prisma.workspaceLlmSettings.create).toHaveBeenCalledWith({ data: { workspaceId: mockWorkspaceId, settings: {}, }, }); }); it("should handle workspace with no settings gracefully", async () => { const newSettings = { ...mockSettings, defaultLlmProviderId: null, defaultPersonalityId: null, }; vi.spyOn(prisma.workspaceLlmSettings, "findUnique").mockResolvedValue(null); vi.spyOn(prisma.workspaceLlmSettings, "create").mockResolvedValue(newSettings); const result = await service.getSettings(mockWorkspaceId); expect(result).toBeDefined(); expect(result.workspaceId).toBe(mockWorkspaceId); }); }); describe("updateSettings", () => { it("should update existing settings", async () => { const updateDto = { defaultLlmProviderId: "new-provider-123", defaultPersonalityId: "new-personality-123", }; const updatedSettings = { ...mockSettings, ...updateDto }; vi.spyOn(prisma.workspaceLlmSettings, "update").mockResolvedValue(updatedSettings); const result = await service.updateSettings(mockWorkspaceId, updateDto); expect(result).toEqual(updatedSettings); expect(prisma.workspaceLlmSettings.update).toHaveBeenCalledWith({ where: { workspaceId: mockWorkspaceId }, data: updateDto, }); }); it("should allow setting provider to null", async () => { const updateDto = { defaultLlmProviderId: null, }; const updatedSettings = { ...mockSettings, defaultLlmProviderId: null }; vi.spyOn(prisma.workspaceLlmSettings, "update").mockResolvedValue(updatedSettings); const result = await service.updateSettings(mockWorkspaceId, updateDto); expect(result.defaultLlmProviderId).toBeNull(); }); it("should allow setting personality to null", async () => { const updateDto = { defaultPersonalityId: null, }; const updatedSettings = { ...mockSettings, defaultPersonalityId: null }; vi.spyOn(prisma.workspaceLlmSettings, "update").mockResolvedValue(updatedSettings); const result = await service.updateSettings(mockWorkspaceId, updateDto); expect(result.defaultPersonalityId).toBeNull(); }); it("should update custom settings object", async () => { const updateDto = { settings: { customKey: "customValue" }, }; const updatedSettings = { ...mockSettings, settings: updateDto.settings }; vi.spyOn(prisma.workspaceLlmSettings, "update").mockResolvedValue(updatedSettings); const result = await service.updateSettings(mockWorkspaceId, updateDto); expect(result.settings).toEqual(updateDto.settings); }); }); describe("getEffectiveLlmProvider", () => { it("should return workspace provider when set", async () => { vi.spyOn(prisma.workspaceLlmSettings, "findUnique").mockResolvedValue(mockSettings); vi.spyOn(prisma.llmProviderInstance, "findUnique").mockResolvedValue(mockProvider); const result = await service.getEffectiveLlmProvider(mockWorkspaceId); expect(result).toEqual(mockProvider); expect(prisma.llmProviderInstance.findUnique).toHaveBeenCalledWith({ where: { id: mockSettings.defaultLlmProviderId! }, }); }); it("should return user provider when workspace provider not set and userId provided", async () => { const settingsWithoutProvider = { ...mockSettings, defaultLlmProviderId: null }; vi.spyOn(prisma.workspaceLlmSettings, "findUnique").mockResolvedValue( settingsWithoutProvider ); vi.spyOn(prisma.llmProviderInstance, "findFirst") .mockResolvedValueOnce(mockUserProvider) .mockResolvedValueOnce(null); const result = await service.getEffectiveLlmProvider(mockWorkspaceId, mockUserId); expect(result).toEqual(mockUserProvider); expect(prisma.llmProviderInstance.findFirst).toHaveBeenCalledWith({ where: { userId: mockUserId, isEnabled: true, }, }); }); it("should fall back to system default when workspace and user providers not set", async () => { const settingsWithoutProvider = { ...mockSettings, defaultLlmProviderId: null }; vi.spyOn(prisma.workspaceLlmSettings, "findUnique").mockResolvedValue( settingsWithoutProvider ); vi.spyOn(prisma.llmProviderInstance, "findFirst") .mockResolvedValueOnce(null) // No user provider .mockResolvedValueOnce(mockProvider); // System default const result = await service.getEffectiveLlmProvider(mockWorkspaceId, mockUserId); expect(result).toEqual(mockProvider); expect(prisma.llmProviderInstance.findFirst).toHaveBeenNthCalledWith(2, { where: { userId: null, isDefault: true, isEnabled: true, }, }); }); it("should throw error when no provider available", async () => { const settingsWithoutProvider = { ...mockSettings, defaultLlmProviderId: null }; vi.spyOn(prisma.workspaceLlmSettings, "findUnique").mockResolvedValue( settingsWithoutProvider ); vi.spyOn(prisma.llmProviderInstance, "findFirst").mockResolvedValue(null); await expect(service.getEffectiveLlmProvider(mockWorkspaceId)).rejects.toThrow( `No LLM provider available for workspace ${mockWorkspaceId}` ); }); it("should throw error when workspace provider is set but not found", async () => { vi.spyOn(prisma.workspaceLlmSettings, "findUnique").mockResolvedValue(mockSettings); vi.spyOn(prisma.llmProviderInstance, "findUnique").mockResolvedValue(null); await expect(service.getEffectiveLlmProvider(mockWorkspaceId)).rejects.toThrow( `LLM provider ${mockSettings.defaultLlmProviderId} not found` ); }); }); describe("getEffectivePersonality", () => { it("should return workspace personality when set", async () => { vi.spyOn(prisma.workspaceLlmSettings, "findUnique").mockResolvedValue(mockSettings); vi.spyOn(prisma.personality, "findUnique").mockResolvedValue(mockPersonality); const result = await service.getEffectivePersonality(mockWorkspaceId); expect(result).toEqual(mockPersonality); expect(prisma.personality.findUnique).toHaveBeenCalledWith({ where: { id: mockSettings.defaultPersonalityId!, }, }); }); it("should fall back to default personality when workspace personality not set", async () => { const settingsWithoutPersonality = { ...mockSettings, defaultPersonalityId: null }; vi.spyOn(prisma.workspaceLlmSettings, "findUnique").mockResolvedValue( settingsWithoutPersonality ); vi.spyOn(prisma.personality, "findFirst").mockResolvedValue(mockPersonality); const result = await service.getEffectivePersonality(mockWorkspaceId); expect(result).toEqual(mockPersonality); expect(prisma.personality.findFirst).toHaveBeenCalledWith({ where: { workspaceId: mockWorkspaceId, isDefault: true, isEnabled: true, }, }); }); it("should fall back to any enabled personality when no default exists", async () => { const settingsWithoutPersonality = { ...mockSettings, defaultPersonalityId: null }; const nonDefaultPersonality = { ...mockPersonality, isDefault: false }; vi.spyOn(prisma.workspaceLlmSettings, "findUnique").mockResolvedValue( settingsWithoutPersonality ); vi.spyOn(prisma.personality, "findFirst") .mockResolvedValueOnce(null) // No default personality .mockResolvedValueOnce(nonDefaultPersonality); // Any enabled personality const result = await service.getEffectivePersonality(mockWorkspaceId); expect(result).toEqual(nonDefaultPersonality); expect(prisma.personality.findFirst).toHaveBeenNthCalledWith(2, { where: { workspaceId: mockWorkspaceId, isEnabled: true, }, }); }); it("should throw error when no personality available", async () => { const settingsWithoutPersonality = { ...mockSettings, defaultPersonalityId: null }; vi.spyOn(prisma.workspaceLlmSettings, "findUnique").mockResolvedValue( settingsWithoutPersonality ); vi.spyOn(prisma.personality, "findFirst").mockResolvedValue(null); await expect(service.getEffectivePersonality(mockWorkspaceId)).rejects.toThrow( `No personality available for workspace ${mockWorkspaceId}` ); }); it("should throw error when workspace personality is set but not found", async () => { vi.spyOn(prisma.workspaceLlmSettings, "findUnique").mockResolvedValue(mockSettings); vi.spyOn(prisma.personality, "findUnique").mockResolvedValue(null); await expect(service.getEffectivePersonality(mockWorkspaceId)).rejects.toThrow( `Personality ${mockSettings.defaultPersonalityId} not found` ); }); }); describe("workspace isolation", () => { it("should only access settings for specified workspace", async () => { vi.spyOn(prisma.workspaceLlmSettings, "findUnique").mockResolvedValue(mockSettings); await service.getSettings(mockWorkspaceId); expect(prisma.workspaceLlmSettings.findUnique).toHaveBeenCalledWith({ where: { workspaceId: mockWorkspaceId }, }); }); it("should not allow cross-workspace settings access", async () => { const otherWorkspaceId = "other-workspace-123"; vi.spyOn(prisma.workspaceLlmSettings, "findUnique").mockResolvedValue(null); const result1 = await service.getSettings(mockWorkspaceId); const result2 = await service.getSettings(otherWorkspaceId); // Each workspace should have separate calls expect(prisma.workspaceLlmSettings.findUnique).toHaveBeenCalledTimes(2); expect(prisma.workspaceLlmSettings.findUnique).toHaveBeenCalledWith({ where: { workspaceId: mockWorkspaceId }, }); expect(prisma.workspaceLlmSettings.findUnique).toHaveBeenCalledWith({ where: { workspaceId: otherWorkspaceId }, }); }); }); });