diff --git a/src/services/core/__tests__/database.test.ts b/src/services/core/__tests__/database.test.ts new file mode 100644 index 0000000..af31958 --- /dev/null +++ b/src/services/core/__tests__/database.test.ts @@ -0,0 +1,93 @@ +/** + * Tests unitaires pour database.ts + */ + +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { + testDatabaseConnection, + closeDatabaseConnection, + clearDatabase, + prisma, +} from '../database'; + +describe('database utilities', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe('testDatabaseConnection', () => { + it('devrait retourner true si la connexion réussit', async () => { + vi.spyOn(prisma, '$connect').mockResolvedValue(undefined); + + const result = await testDatabaseConnection(); + + expect(result).toBe(true); + expect(prisma.$connect).toHaveBeenCalled(); + }); + + it('devrait retourner false si la connexion échoue', async () => { + vi.spyOn(prisma, '$connect').mockRejectedValue( + new Error('Connection failed') + ); + + const result = await testDatabaseConnection(); + + expect(result).toBe(false); + }); + }); + + describe('closeDatabaseConnection', () => { + it('devrait fermer la connexion avec succès', async () => { + vi.spyOn(prisma, '$disconnect').mockResolvedValue(undefined); + + await closeDatabaseConnection(); + + expect(prisma.$disconnect).toHaveBeenCalled(); + }); + + it('devrait gérer les erreurs lors de la fermeture', async () => { + vi.spyOn(prisma, '$disconnect').mockRejectedValue( + new Error('Disconnect failed') + ); + + // Ne devrait pas throw + await expect(closeDatabaseConnection()).resolves.not.toThrow(); + }); + }); + + describe('clearDatabase', () => { + beforeEach(() => { + // S'assurer qu'on est pas en production + vi.stubEnv('NODE_ENV', 'test'); + }); + + afterEach(() => { + vi.unstubAllEnvs(); + }); + + it('devrait nettoyer toutes les tables', async () => { + const deleteManySpy = vi.fn().mockResolvedValue({ count: 0 }); + + // Mock des méthodes deleteMany directement + (prisma.taskTag as any) = { deleteMany: deleteManySpy }; + (prisma.task as any) = { deleteMany: deleteManySpy }; + (prisma.tag as any) = { deleteMany: deleteManySpy }; + (prisma.syncLog as any) = { deleteMany: deleteManySpy }; + + await clearDatabase(); + + expect(deleteManySpy).toHaveBeenCalledTimes(4); + }); + + it('ne devrait pas permettre le nettoyage en production', async () => { + vi.stubEnv('NODE_ENV', 'production'); + + await expect(clearDatabase()).rejects.toThrow( + 'Cannot clear database in production' + ); + + vi.unstubAllEnvs(); + }); + }); +}); diff --git a/src/services/core/__tests__/system-info.test.ts b/src/services/core/__tests__/system-info.test.ts new file mode 100644 index 0000000..f1c872a --- /dev/null +++ b/src/services/core/__tests__/system-info.test.ts @@ -0,0 +1,256 @@ +/** + * Tests unitaires pour SystemInfoService + */ + +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { SystemInfoService } from '../system-info'; +import { prisma } from '../database'; +import { readFile } from 'fs/promises'; +import { stat } from 'fs/promises'; + +// Mock de prisma +vi.mock('../database', () => ({ + prisma: { + task: { + count: vi.fn(), + }, + userPreferences: { + count: vi.fn(), + }, + tag: { + count: vi.fn(), + }, + dailyCheckbox: { + count: vi.fn(), + }, + }, +})); + +// Mock de fs/promises +vi.mock('fs/promises', () => ({ + readFile: vi.fn(), + stat: vi.fn(), +})); + +// Mock de path +vi.mock('path', () => ({ + join: (...args: string[]) => args.join('/'), + resolve: (...args: string[]) => args.join('/'), +})); + +// Mock du service backup +vi.mock('@/services/data-management/backup', () => ({ + backupService: { + listBackups: vi.fn(), + }, +})); + +describe('SystemInfoService', () => { + beforeEach(() => { + vi.clearAllMocks(); + vi.stubEnv('NODE_ENV', 'test'); + }); + + afterEach(() => { + vi.unstubAllEnvs(); + }); + + describe('getSystemInfo', () => { + it('devrait retourner les informations système complètes', async () => { + const mockPackageJson = { + name: 'towercontrol', + version: '1.0.0', + }; + + vi.mocked(readFile).mockResolvedValue(JSON.stringify(mockPackageJson)); + vi.mocked(prisma.task.count).mockResolvedValue(10); + vi.mocked(prisma.userPreferences.count).mockResolvedValue(1); + vi.mocked(prisma.tag.count).mockResolvedValue(5); + vi.mocked(prisma.dailyCheckbox.count).mockResolvedValue(20); + + const { backupService } = await import( + '@/services/data-management/backup' + ); + vi.mocked(backupService.listBackups).mockResolvedValue([ + { id: '1', filename: 'backup1.db.gz' }, + { id: '2', filename: 'backup2.db.gz' }, + ] as any); + + vi.mocked(stat).mockResolvedValue({ + size: 1024 * 1024, // 1 MB + } as any); + + const result = await SystemInfoService.getSystemInfo(); + + expect(result).toHaveProperty('version'); + expect(result).toHaveProperty('environment'); + expect(result).toHaveProperty('database'); + expect(result).toHaveProperty('backups'); + expect(result).toHaveProperty('app'); + expect(result).toHaveProperty('uptime'); + expect(result).toHaveProperty('lastUpdate'); + + expect(result.version).toBe('1.0.0'); + expect(result.environment).toBe('test'); + expect(result.database.totalTasks).toBe(10); + expect(result.database.totalUsers).toBe(1); + expect(result.database.totalBackups).toBe(2); + expect(result.database.totalTags).toBe(5); + expect(result.database.totalDailies).toBe(20); + }); + + it('devrait gérer les erreurs lors de la lecture du package.json', async () => { + vi.mocked(readFile).mockRejectedValue(new Error('File not found')); + + vi.mocked(prisma.task.count).mockResolvedValue(0); + vi.mocked(prisma.userPreferences.count).mockResolvedValue(0); + vi.mocked(prisma.tag.count).mockResolvedValue(0); + vi.mocked(prisma.dailyCheckbox.count).mockResolvedValue(0); + + const { backupService } = await import( + '@/services/data-management/backup' + ); + vi.mocked(backupService.listBackups).mockResolvedValue([]); + + vi.mocked(stat).mockRejectedValue(new Error('File not found')); + + const result = await SystemInfoService.getSystemInfo(); + + expect(result.version).toBe('1.0.0'); // Valeur par défaut + expect(result.database.databaseSize).toBe('N/A'); + }); + + it('devrait calculer correctement la taille de la base de données', async () => { + const mockPackageJson = { + name: 'towercontrol', + version: '1.0.0', + }; + + vi.mocked(readFile).mockResolvedValue(JSON.stringify(mockPackageJson)); + vi.mocked(prisma.task.count).mockResolvedValue(0); + vi.mocked(prisma.userPreferences.count).mockResolvedValue(0); + vi.mocked(prisma.tag.count).mockResolvedValue(0); + vi.mocked(prisma.dailyCheckbox.count).mockResolvedValue(0); + + const { backupService } = await import( + '@/services/data-management/backup' + ); + vi.mocked(backupService.listBackups).mockResolvedValue([]); + + // Test différentes tailles + vi.mocked(stat).mockResolvedValue({ + size: 1024, // 1 KB + } as any); + + const result = await SystemInfoService.getSystemInfo(); + + expect(result.database.databaseSize).toContain('KB'); + }); + + it('devrait utiliser DATABASE_URL pour trouver la base de données', async () => { + vi.stubEnv('DATABASE_URL', 'file:/path/to/db.db'); + + const mockPackageJson = { + name: 'towercontrol', + version: '1.0.0', + }; + + vi.mocked(readFile).mockResolvedValue(JSON.stringify(mockPackageJson)); + vi.mocked(prisma.task.count).mockResolvedValue(0); + vi.mocked(prisma.userPreferences.count).mockResolvedValue(0); + vi.mocked(prisma.tag.count).mockResolvedValue(0); + vi.mocked(prisma.dailyCheckbox.count).mockResolvedValue(0); + + const { backupService } = await import( + '@/services/data-management/backup' + ); + vi.mocked(backupService.listBackups).mockResolvedValue([]); + + vi.mocked(stat).mockResolvedValue({ + size: 1024, + } as any); + + await SystemInfoService.getSystemInfo(); + + expect(stat).toHaveBeenCalled(); + }); + + it('devrait utiliser BACKUP_DATABASE_PATH si défini', async () => { + vi.stubEnv('BACKUP_DATABASE_PATH', '/custom/path/db.db'); + + const mockPackageJson = { + name: 'towercontrol', + version: '1.0.0', + }; + + vi.mocked(readFile).mockResolvedValue(JSON.stringify(mockPackageJson)); + vi.mocked(prisma.task.count).mockResolvedValue(0); + vi.mocked(prisma.userPreferences.count).mockResolvedValue(0); + vi.mocked(prisma.tag.count).mockResolvedValue(0); + vi.mocked(prisma.dailyCheckbox.count).mockResolvedValue(0); + + const { backupService } = await import( + '@/services/data-management/backup' + ); + vi.mocked(backupService.listBackups).mockResolvedValue([]); + + vi.mocked(stat).mockResolvedValue({ + size: 1024, + } as any); + + await SystemInfoService.getSystemInfo(); + + expect(stat).toHaveBeenCalled(); + }); + + it('devrait gérer les erreurs de base de données', async () => { + const mockPackageJson = { + name: 'towercontrol', + version: '1.0.0', + }; + + vi.mocked(readFile).mockResolvedValue(JSON.stringify(mockPackageJson)); + vi.mocked(prisma.task.count).mockRejectedValue( + new Error('Database error') + ); + + const { backupService } = await import( + '@/services/data-management/backup' + ); + vi.mocked(backupService.listBackups).mockResolvedValue([]); + + const result = await SystemInfoService.getSystemInfo(); + + expect(result.database.totalTasks).toBe(0); + expect(result.database.totalUsers).toBe(0); + }); + + it("devrait calculer correctement l'uptime", async () => { + const mockPackageJson = { + name: 'towercontrol', + version: '1.0.0', + }; + + vi.mocked(readFile).mockResolvedValue(JSON.stringify(mockPackageJson)); + vi.mocked(prisma.task.count).mockResolvedValue(0); + vi.mocked(prisma.userPreferences.count).mockResolvedValue(0); + vi.mocked(prisma.tag.count).mockResolvedValue(0); + vi.mocked(prisma.dailyCheckbox.count).mockResolvedValue(0); + + const { backupService } = await import( + '@/services/data-management/backup' + ); + vi.mocked(backupService.listBackups).mockResolvedValue([]); + + vi.mocked(stat).mockResolvedValue({ + size: 1024, + } as any); + + const result = await SystemInfoService.getSystemInfo(); + + expect(result.uptime).toBeDefined(); + expect(result.uptime.length).toBeGreaterThan(0); + }); + }); +}); diff --git a/src/services/core/__tests__/user-preferences.test.ts b/src/services/core/__tests__/user-preferences.test.ts new file mode 100644 index 0000000..245f4a0 --- /dev/null +++ b/src/services/core/__tests__/user-preferences.test.ts @@ -0,0 +1,836 @@ +/** + * Tests unitaires pour UserPreferencesService + */ + +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { userPreferencesService } from '../user-preferences'; +import { prisma } from '../database'; +import { getConfig } from '@/lib/config'; +import { + KanbanFilters, + ViewPreferences, + ColumnVisibility, + JiraConfig, +} from '@/lib/types'; +import { TfsConfig } from '@/services/integrations/tfs/tfs'; + +// Mock de prisma +vi.mock('../database', () => ({ + prisma: { + user: { + upsert: vi.fn(), + }, + userPreferences: { + upsert: vi.fn(), + update: vi.fn(), + findUnique: vi.fn(), + }, + $executeRaw: vi.fn(), + $queryRaw: vi.fn(), + }, +})); + +// Mock de getConfig +vi.mock('@/lib/config', () => ({ + getConfig: vi.fn(), +})); + +describe('UserPreferencesService', () => { + const userId = 'test-user-id'; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe('Kanban Filters', () => { + it('devrait sauvegarder les filtres Kanban', async () => { + const filters: KanbanFilters = { + search: 'test', + tags: ['important'], + priorities: ['high'], + showCompleted: false, + sortBy: 'priority', + showWithDueDate: true, + }; + + vi.mocked(prisma.user.upsert).mockResolvedValue({ + id: userId, + email: `${userId}@towercontrol.local`, + name: 'Default User', + role: 'user', + password: 'default', + createdAt: new Date(), + updatedAt: new Date(), + } as any); + + vi.mocked(prisma.userPreferences.upsert).mockResolvedValue({ + userId, + kanbanFilters: filters, + viewPreferences: {}, + columnVisibility: {}, + jiraConfig: {}, + } as any); + + vi.mocked(prisma.userPreferences.update).mockResolvedValue({ + userId, + kanbanFilters: filters, + } as any); + + vi.mocked(prisma.$executeRaw).mockResolvedValue(0); + + await userPreferencesService.saveKanbanFilters(userId, filters); + + expect(prisma.userPreferences.update).toHaveBeenCalledWith({ + where: { userId }, + data: { kanbanFilters: filters }, + }); + }); + + it('devrait récupérer les filtres Kanban avec valeurs par défaut', async () => { + const filters: KanbanFilters = { + search: 'test', + tags: ['important'], + priorities: [], + showCompleted: true, + sortBy: '', + showWithDueDate: false, + }; + + vi.mocked(prisma.user.upsert).mockResolvedValue({ + id: userId, + email: `${userId}@towercontrol.local`, + name: 'Default User', + role: 'user', + password: 'default', + createdAt: new Date(), + updatedAt: new Date(), + } as any); + + vi.mocked(prisma.userPreferences.upsert).mockResolvedValue({ + userId, + kanbanFilters: filters, + viewPreferences: {}, + columnVisibility: {}, + jiraConfig: {}, + } as any); + + vi.mocked(prisma.$executeRaw).mockResolvedValue(0); + + const result = await userPreferencesService.getKanbanFilters(userId); + + expect(result).toHaveProperty('search'); + expect(result).toHaveProperty('tags'); + expect(result).toHaveProperty('priorities'); + expect(result).toHaveProperty('showCompleted'); + }); + + it("devrait retourner les valeurs par défaut en cas d'erreur", async () => { + vi.mocked(prisma.user.upsert).mockRejectedValue( + new Error('Database error') + ); + + const result = await userPreferencesService.getKanbanFilters(userId); + + expect(result).toHaveProperty('search'); + expect(result.search).toBe(''); + }); + }); + + describe('View Preferences', () => { + it('devrait sauvegarder les préférences de vue', async () => { + const preferences: ViewPreferences = { + compactView: true, + swimlanesByTags: true, + swimlanesMode: 'tags', + showObjectives: false, + showFilters: true, + objectivesCollapsed: false, + theme: 'light', + fontSize: 'large', + backgroundImage: undefined, + backgroundBlur: 0, + backgroundOpacity: 100, + }; + + vi.mocked(prisma.user.upsert).mockResolvedValue({ + id: userId, + email: `${userId}@towercontrol.local`, + name: 'Default User', + role: 'user', + password: 'default', + createdAt: new Date(), + updatedAt: new Date(), + } as any); + + vi.mocked(prisma.userPreferences.upsert).mockResolvedValue({ + userId, + kanbanFilters: {}, + viewPreferences: preferences, + columnVisibility: {}, + jiraConfig: {}, + } as any); + + vi.mocked(prisma.userPreferences.update).mockResolvedValue({ + userId, + viewPreferences: preferences, + } as any); + + vi.mocked(prisma.$executeRaw).mockResolvedValue(0); + + await userPreferencesService.saveViewPreferences(userId, preferences); + + expect(prisma.userPreferences.update).toHaveBeenCalledWith({ + where: { userId }, + data: { viewPreferences: preferences }, + }); + }); + + it('devrait récupérer le thème', async () => { + vi.mocked(prisma.user.upsert).mockResolvedValue({ + id: userId, + email: `${userId}@towercontrol.local`, + name: 'Default User', + role: 'user', + password: 'default', + createdAt: new Date(), + updatedAt: new Date(), + } as any); + + vi.mocked(prisma.userPreferences.upsert).mockResolvedValue({ + userId, + kanbanFilters: {}, + viewPreferences: { theme: 'light' }, + columnVisibility: {}, + jiraConfig: {}, + } as any); + + vi.mocked(prisma.$executeRaw).mockResolvedValue(0); + + const theme = await userPreferencesService.getTheme(userId); + + expect(theme).toBe('light'); + }); + }); + + describe('Column Visibility', () => { + it('devrait sauvegarder la visibilité des colonnes', async () => { + const visibility: ColumnVisibility = { + hiddenStatuses: ['archived', 'cancelled'], + }; + + vi.mocked(prisma.user.upsert).mockResolvedValue({ + id: userId, + email: `${userId}@towercontrol.local`, + name: 'Default User', + role: 'user', + password: 'default', + createdAt: new Date(), + updatedAt: new Date(), + } as any); + + vi.mocked(prisma.userPreferences.upsert).mockResolvedValue({ + userId, + kanbanFilters: {}, + viewPreferences: {}, + columnVisibility: visibility, + jiraConfig: {}, + } as any); + + vi.mocked(prisma.userPreferences.update).mockResolvedValue({ + userId, + columnVisibility: visibility, + } as any); + + vi.mocked(prisma.$executeRaw).mockResolvedValue(0); + + await userPreferencesService.saveColumnVisibility(userId, visibility); + + expect(prisma.userPreferences.update).toHaveBeenCalledWith({ + where: { userId }, + data: { columnVisibility: visibility }, + }); + }); + + it("devrait toggle la visibilité d'une colonne", async () => { + vi.mocked(prisma.user.upsert).mockResolvedValue({ + id: userId, + email: `${userId}@towercontrol.local`, + name: 'Default User', + role: 'user', + password: 'default', + createdAt: new Date(), + updatedAt: new Date(), + } as any); + + vi.mocked(prisma.userPreferences.upsert).mockResolvedValue({ + userId, + kanbanFilters: {}, + viewPreferences: {}, + columnVisibility: { hiddenStatuses: [] }, + jiraConfig: {}, + } as any); + + vi.mocked(prisma.userPreferences.update).mockResolvedValue({ + userId, + columnVisibility: { hiddenStatuses: ['archived'] }, + } as any); + + vi.mocked(prisma.$executeRaw).mockResolvedValue(0); + + await userPreferencesService.toggleColumnVisibility(userId, 'archived'); + + expect(prisma.userPreferences.update).toHaveBeenCalled(); + }); + }); + + describe('Jira Config', () => { + it('devrait sauvegarder la configuration Jira', async () => { + const config: JiraConfig = { + enabled: true, + baseUrl: 'https://jira.example.com', + email: 'user@example.com', + apiToken: 'token123', + ignoredProjects: [], + }; + + vi.mocked(prisma.user.upsert).mockResolvedValue({ + id: userId, + email: `${userId}@towercontrol.local`, + name: 'Default User', + role: 'user', + password: 'default', + createdAt: new Date(), + updatedAt: new Date(), + } as any); + + vi.mocked(prisma.userPreferences.upsert).mockResolvedValue({ + userId, + kanbanFilters: {}, + viewPreferences: {}, + columnVisibility: {}, + jiraConfig: config, + } as any); + + vi.mocked(prisma.userPreferences.update).mockResolvedValue({ + userId, + jiraConfig: config, + } as any); + + vi.mocked(prisma.$executeRaw).mockResolvedValue(0); + + await userPreferencesService.saveJiraConfig(userId, config); + + expect(prisma.userPreferences.update).toHaveBeenCalledWith({ + where: { userId }, + data: { jiraConfig: config }, + }); + }); + + it('devrait récupérer la configuration Jira depuis la DB', async () => { + const config: JiraConfig = { + enabled: true, + baseUrl: 'https://jira.example.com', + email: 'user@example.com', + apiToken: 'token123', + ignoredProjects: [], + }; + + vi.mocked(prisma.user.upsert).mockResolvedValue({ + id: userId, + email: `${userId}@towercontrol.local`, + name: 'Default User', + role: 'user', + password: 'default', + createdAt: new Date(), + updatedAt: new Date(), + } as any); + + vi.mocked(prisma.userPreferences.upsert).mockResolvedValue({ + userId, + kanbanFilters: {}, + viewPreferences: {}, + columnVisibility: {}, + jiraConfig: config, + } as any); + + vi.mocked(prisma.$executeRaw).mockResolvedValue(0); + + const result = await userPreferencesService.getJiraConfig(userId); + + expect(result.baseUrl).toBe('https://jira.example.com'); + expect(result.email).toBe('user@example.com'); + }); + + it("devrait utiliser les variables d'environnement si pas de config DB", async () => { + vi.mocked(prisma.user.upsert).mockResolvedValue({ + id: userId, + email: `${userId}@towercontrol.local`, + name: 'Default User', + role: 'user', + password: 'default', + createdAt: new Date(), + updatedAt: new Date(), + } as any); + + vi.mocked(prisma.userPreferences.upsert).mockResolvedValue({ + userId, + kanbanFilters: {}, + viewPreferences: {}, + columnVisibility: {}, + jiraConfig: null, + } as any); + + vi.mocked(prisma.$executeRaw).mockResolvedValue(0); + + vi.mocked(getConfig).mockReturnValue({ + integrations: { + jira: { + baseUrl: 'https://env-jira.example.com', + email: 'env@example.com', + enabled: true, + }, + }, + } as any); + + const result = await userPreferencesService.getJiraConfig(userId); + + expect(result.baseUrl).toBe('https://env-jira.example.com'); + expect(result.email).toBe('env@example.com'); + }); + }); + + describe('TFS Config', () => { + it('devrait sauvegarder la configuration TFS', async () => { + const config: TfsConfig = { + enabled: true, + organizationUrl: 'https://dev.azure.com/org', + projectName: 'MyProject', + personalAccessToken: 'token123', + repositories: ['repo1', 'repo2'], + ignoredRepositories: [], + maxSyncPeriod: '90d', + }; + + vi.mocked(prisma.user.upsert).mockResolvedValue({ + id: userId, + email: `${userId}@towercontrol.local`, + name: 'Default User', + role: 'user', + password: 'default', + createdAt: new Date(), + updatedAt: new Date(), + } as any); + + vi.mocked(prisma.userPreferences.upsert).mockResolvedValue({ + userId, + kanbanFilters: {}, + viewPreferences: {}, + columnVisibility: {}, + jiraConfig: {}, + tfsConfig: config, + } as any); + + vi.mocked(prisma.userPreferences.update).mockResolvedValue({ + userId, + tfsConfig: config, + } as any); + + vi.mocked(prisma.$executeRaw).mockResolvedValue(0); + + await userPreferencesService.saveTfsConfig(userId, config); + + expect(prisma.userPreferences.update).toHaveBeenCalledWith({ + where: { userId }, + data: { tfsConfig: config }, + }); + }); + + it('devrait récupérer la configuration TFS', async () => { + const config: TfsConfig = { + enabled: true, + organizationUrl: 'https://dev.azure.com/org', + projectName: 'MyProject', + personalAccessToken: 'token123', + repositories: [], + ignoredRepositories: [], + maxSyncPeriod: '90d', + }; + + vi.mocked(prisma.user.upsert).mockResolvedValue({ + id: userId, + email: `${userId}@towercontrol.local`, + name: 'Default User', + role: 'user', + password: 'default', + createdAt: new Date(), + updatedAt: new Date(), + } as any); + + vi.mocked(prisma.userPreferences.upsert).mockResolvedValue({ + userId, + kanbanFilters: {}, + viewPreferences: {}, + columnVisibility: {}, + jiraConfig: {}, + tfsConfig: config, + } as any); + + vi.mocked(prisma.$executeRaw).mockResolvedValue(0); + + const result = await userPreferencesService.getTfsConfig(userId); + + expect(result.organizationUrl).toBe('https://dev.azure.com/org'); + expect(result.projectName).toBe('MyProject'); + }); + }); + + describe('Scheduler Config', () => { + it('devrait sauvegarder la configuration scheduler Jira', async () => { + vi.mocked(prisma.user.upsert).mockResolvedValue({ + id: userId, + email: `${userId}@towercontrol.local`, + name: 'Default User', + role: 'user', + password: 'default', + createdAt: new Date(), + updatedAt: new Date(), + } as any); + + vi.mocked(prisma.userPreferences.upsert).mockResolvedValue({ + userId, + kanbanFilters: {}, + viewPreferences: {}, + columnVisibility: {}, + jiraConfig: {}, + } as any); + + vi.mocked(prisma.$executeRaw).mockResolvedValue(0); + + await userPreferencesService.saveJiraSchedulerConfig( + userId, + true, + 'hourly' + ); + + expect(prisma.$executeRaw).toHaveBeenCalled(); + }); + + it('devrait récupérer la configuration scheduler Jira', async () => { + vi.mocked(prisma.user.upsert).mockResolvedValue({ + id: userId, + email: `${userId}@towercontrol.local`, + name: 'Default User', + role: 'user', + password: 'default', + createdAt: new Date(), + updatedAt: new Date(), + } as any); + + vi.mocked(prisma.userPreferences.upsert).mockResolvedValue({ + userId, + kanbanFilters: {}, + viewPreferences: {}, + columnVisibility: {}, + jiraConfig: {}, + } as any); + + vi.mocked(prisma.$executeRaw).mockResolvedValue(0); + vi.mocked(prisma.$queryRaw).mockResolvedValue([ + { jiraAutoSync: 1, jiraSyncInterval: 'hourly' }, + ] as any); + + const result = + await userPreferencesService.getJiraSchedulerConfig(userId); + + expect(result.jiraAutoSync).toBe(true); + expect(result.jiraSyncInterval).toBe('hourly'); + }); + + it('devrait sauvegarder la configuration scheduler TFS', async () => { + vi.mocked(prisma.user.upsert).mockResolvedValue({ + id: userId, + email: `${userId}@towercontrol.local`, + name: 'Default User', + role: 'user', + password: 'default', + createdAt: new Date(), + updatedAt: new Date(), + } as any); + + vi.mocked(prisma.userPreferences.upsert).mockResolvedValue({ + userId, + kanbanFilters: {}, + viewPreferences: {}, + columnVisibility: {}, + jiraConfig: {}, + } as any); + + vi.mocked(prisma.$executeRaw).mockResolvedValue(0); + + await userPreferencesService.saveTfsSchedulerConfig( + userId, + true, + 'daily' + ); + + expect(prisma.$executeRaw).toHaveBeenCalled(); + }); + + it('devrait récupérer la configuration scheduler TFS', async () => { + vi.mocked(prisma.user.upsert).mockResolvedValue({ + id: userId, + email: `${userId}@towercontrol.local`, + name: 'Default User', + role: 'user', + password: 'default', + createdAt: new Date(), + updatedAt: new Date(), + } as any); + + vi.mocked(prisma.userPreferences.upsert).mockResolvedValue({ + userId, + kanbanFilters: {}, + viewPreferences: {}, + columnVisibility: {}, + jiraConfig: {}, + } as any); + + vi.mocked(prisma.$executeRaw).mockResolvedValue(0); + vi.mocked(prisma.$queryRaw).mockResolvedValue([ + { tfsAutoSync: 1, tfsSyncInterval: 'daily' }, + ] as any); + + const result = await userPreferencesService.getTfsSchedulerConfig(userId); + + expect(result.tfsAutoSync).toBe(true); + expect(result.tfsSyncInterval).toBe('daily'); + }); + }); + + describe('All Preferences', () => { + it('devrait récupérer toutes les préférences', async () => { + vi.mocked(prisma.user.upsert).mockResolvedValue({ + id: userId, + email: `${userId}@towercontrol.local`, + name: 'Default User', + role: 'user', + password: 'default', + createdAt: new Date(), + updatedAt: new Date(), + } as any); + + vi.mocked(prisma.userPreferences.upsert).mockResolvedValue({ + userId, + kanbanFilters: {}, + viewPreferences: {}, + columnVisibility: {}, + jiraConfig: {}, + } as any); + + vi.mocked(prisma.$executeRaw).mockResolvedValue(0); + vi.mocked(prisma.$queryRaw).mockResolvedValue([ + { jiraAutoSync: 0, jiraSyncInterval: 'daily' }, + { tfsAutoSync: 0, tfsSyncInterval: 'daily' }, + ] as any); + + vi.mocked(getConfig).mockReturnValue({ + integrations: { + jira: { + baseUrl: '', + email: '', + enabled: false, + }, + }, + } as any); + + const result = await userPreferencesService.getAllPreferences(userId); + + expect(result).toHaveProperty('kanbanFilters'); + expect(result).toHaveProperty('viewPreferences'); + expect(result).toHaveProperty('columnVisibility'); + expect(result).toHaveProperty('jiraConfig'); + expect(result).toHaveProperty('jiraAutoSync'); + expect(result).toHaveProperty('jiraSyncInterval'); + expect(result).toHaveProperty('tfsConfig'); + expect(result).toHaveProperty('tfsAutoSync'); + expect(result).toHaveProperty('tfsSyncInterval'); + }); + + it('devrait sauvegarder toutes les préférences', async () => { + const preferences = { + kanbanFilters: { + search: '', + tags: [], + priorities: [], + showCompleted: true, + sortBy: '', + showWithDueDate: false, + }, + viewPreferences: { + compactView: false, + swimlanesByTags: false, + swimlanesMode: 'tags' as const, + showObjectives: true, + showFilters: true, + objectivesCollapsed: false, + theme: 'dark' as const, + fontSize: 'medium' as const, + backgroundImage: undefined, + backgroundBlur: 0, + backgroundOpacity: 100, + }, + columnVisibility: { + hiddenStatuses: [], + }, + jiraConfig: { + enabled: false, + baseUrl: '', + email: '', + apiToken: '', + ignoredProjects: [], + }, + jiraAutoSync: false, + jiraSyncInterval: 'daily' as const, + tfsConfig: { + enabled: false, + organizationUrl: '', + projectName: '', + personalAccessToken: '', + repositories: [], + ignoredRepositories: [], + maxSyncPeriod: '90d' as const, + }, + tfsAutoSync: false, + tfsSyncInterval: 'daily' as const, + }; + + vi.mocked(prisma.user.upsert).mockResolvedValue({ + id: userId, + email: `${userId}@towercontrol.local`, + name: 'Default User', + role: 'user', + password: 'default', + createdAt: new Date(), + updatedAt: new Date(), + } as any); + + vi.mocked(prisma.userPreferences.upsert).mockResolvedValue({ + userId, + kanbanFilters: {}, + viewPreferences: {}, + columnVisibility: {}, + jiraConfig: {}, + } as any); + + vi.mocked(prisma.userPreferences.update).mockResolvedValue({ + userId, + } as any); + + vi.mocked(prisma.$executeRaw).mockResolvedValue(0); + + await userPreferencesService.saveAllPreferences(userId, preferences); + + expect(prisma.userPreferences.update).toHaveBeenCalled(); + expect(prisma.$executeRaw).toHaveBeenCalled(); + }); + + it('devrait remettre à zéro toutes les préférences', async () => { + vi.mocked(prisma.user.upsert).mockResolvedValue({ + id: userId, + email: `${userId}@towercontrol.local`, + name: 'Default User', + role: 'user', + password: 'default', + createdAt: new Date(), + updatedAt: new Date(), + } as any); + + vi.mocked(prisma.userPreferences.upsert).mockResolvedValue({ + userId, + kanbanFilters: {}, + viewPreferences: {}, + columnVisibility: {}, + jiraConfig: {}, + } as any); + + vi.mocked(prisma.userPreferences.update).mockResolvedValue({ + userId, + } as any); + + vi.mocked(prisma.$executeRaw).mockResolvedValue(0); + + await userPreferencesService.resetAllPreferences(userId); + + expect(prisma.userPreferences.update).toHaveBeenCalled(); + }); + }); + + describe('Update methods', () => { + it('devrait mettre à jour partiellement les filtres Kanban', async () => { + vi.mocked(prisma.user.upsert).mockResolvedValue({ + id: userId, + email: `${userId}@towercontrol.local`, + name: 'Default User', + role: 'user', + password: 'default', + createdAt: new Date(), + updatedAt: new Date(), + } as any); + + vi.mocked(prisma.userPreferences.upsert).mockResolvedValue({ + userId, + kanbanFilters: { search: '', tags: [] }, + viewPreferences: {}, + columnVisibility: {}, + jiraConfig: {}, + } as any); + + vi.mocked(prisma.userPreferences.update).mockResolvedValue({ + userId, + kanbanFilters: { search: 'new', tags: [] }, + } as any); + + vi.mocked(prisma.$executeRaw).mockResolvedValue(0); + + await userPreferencesService.updateKanbanFilters(userId, { + search: 'new', + }); + + expect(prisma.userPreferences.update).toHaveBeenCalled(); + }); + + it('devrait mettre à jour partiellement les préférences de vue', async () => { + vi.mocked(prisma.user.upsert).mockResolvedValue({ + id: userId, + email: `${userId}@towercontrol.local`, + name: 'Default User', + role: 'user', + password: 'default', + createdAt: new Date(), + updatedAt: new Date(), + } as any); + + vi.mocked(prisma.userPreferences.upsert).mockResolvedValue({ + userId, + kanbanFilters: {}, + viewPreferences: { theme: 'dark' }, + columnVisibility: {}, + jiraConfig: {}, + } as any); + + vi.mocked(prisma.userPreferences.update).mockResolvedValue({ + userId, + viewPreferences: { theme: 'light' }, + } as any); + + vi.mocked(prisma.$executeRaw).mockResolvedValue(0); + + await userPreferencesService.updateViewPreferences(userId, { + theme: 'light', + }); + + expect(prisma.userPreferences.update).toHaveBeenCalled(); + }); + }); +});