From f57ea205c72776bf883c3e4879a2cadaa719d4f2 Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Wed, 26 Nov 2025 08:40:48 +0100 Subject: [PATCH] test(JiraSync): improve test coverage for synchronization scenarios and enhance assertions for change detection --- src/services/__tests__/notes.test.ts | 345 +++++++++++++++++++++++++++ src/services/__tests__/users.test.ts | 276 +++++++++++++++++++++ 2 files changed, 621 insertions(+) create mode 100644 src/services/__tests__/notes.test.ts create mode 100644 src/services/__tests__/users.test.ts diff --git a/src/services/__tests__/notes.test.ts b/src/services/__tests__/notes.test.ts new file mode 100644 index 0000000..a6ddedf --- /dev/null +++ b/src/services/__tests__/notes.test.ts @@ -0,0 +1,345 @@ +/** + * Tests unitaires pour NotesService + */ + +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { NotesService } from '../notes'; +import { prisma } from '@/services/core/database'; +import { tagsService } from '../task-management/tags'; + +// Mock de prisma +vi.mock('@/services/core/database', () => ({ + prisma: { + note: { + findMany: vi.fn(), + findFirst: vi.fn(), + findUnique: vi.fn(), + create: vi.fn(), + update: vi.fn(), + delete: vi.fn(), + count: vi.fn(), + }, + noteTag: { + createMany: vi.fn(), + deleteMany: vi.fn(), + findMany: vi.fn(), + }, + }, +})); + +// Mock de tagsService +vi.mock('../task-management/tags', () => ({ + tagsService: { + ensureTagsExist: vi.fn(), + }, +})); + +describe('NotesService', () => { + let service: NotesService; + const mockUserId = 'user-123'; + + beforeEach(() => { + vi.clearAllMocks(); + service = new NotesService(); + }); + + describe('getNotes', () => { + it('devrait récupérer toutes les notes pour un utilisateur', async () => { + const mockNotes = [ + { + id: 'note-1', + title: 'Note 1', + content: 'Content 1', + userId: mockUserId, + taskId: null, + createdAt: new Date(), + updatedAt: new Date(), + noteTags: [], + task: null, + }, + ]; + + vi.mocked(prisma.note.findMany).mockResolvedValue(mockNotes as any); + + const notes = await service.getNotes(mockUserId); + + expect(notes).toHaveLength(1); + expect(prisma.note.findMany).toHaveBeenCalledWith({ + where: { userId: mockUserId }, + include: { + noteTags: { + include: { + tag: true, + }, + }, + task: { + include: { + taskTags: { + include: { + tag: true, + }, + }, + primaryTag: true, + }, + }, + }, + orderBy: { updatedAt: 'desc' }, + }); + }); + }); + + describe('getNoteById', () => { + it('devrait récupérer une note par son ID', async () => { + const mockNote = { + id: 'note-1', + title: 'Note 1', + content: 'Content 1', + userId: mockUserId, + taskId: null, + createdAt: new Date(), + updatedAt: new Date(), + noteTags: [], + task: null, + }; + + vi.mocked(prisma.note.findFirst).mockResolvedValue(mockNote as any); + + const note = await service.getNoteById('note-1', mockUserId); + + expect(note).toBeDefined(); + expect(note?.id).toBe('note-1'); + expect(prisma.note.findFirst).toHaveBeenCalledWith({ + where: { + id: 'note-1', + userId: mockUserId, + }, + include: { + noteTags: { + include: { + tag: true, + }, + }, + task: { + include: { + taskTags: { + include: { + tag: true, + }, + }, + primaryTag: true, + }, + }, + }, + }); + }); + + it("devrait retourner null si la note n'existe pas", async () => { + vi.mocked(prisma.note.findFirst).mockResolvedValue(null); + + const note = await service.getNoteById('non-existent', mockUserId); + + expect(note).toBeNull(); + }); + }); + + describe('createNote', () => { + it('devrait créer une nouvelle note', async () => { + const mockNote = { + id: 'note-1', + title: 'New Note', + content: 'Content', + userId: mockUserId, + taskId: null, + createdAt: new Date(), + updatedAt: new Date(), + noteTags: [], + task: null, + }; + + vi.mocked(prisma.note.create).mockResolvedValue(mockNote as any); + vi.mocked(prisma.note.findUnique).mockResolvedValue(mockNote as any); + vi.mocked(tagsService.ensureTagsExist).mockResolvedValue([]); + + const note = await service.createNote({ + title: 'New Note', + content: 'Content', + userId: mockUserId, + }); + + expect(note).toBeDefined(); + expect(prisma.note.create).toHaveBeenCalled(); + }); + + it('devrait créer une note avec des tags', async () => { + const mockNote = { + id: 'note-1', + title: 'New Note', + content: 'Content', + userId: mockUserId, + taskId: null, + createdAt: new Date(), + updatedAt: new Date(), + noteTags: [], + task: null, + }; + + const mockTags = [ + { id: 'tag-1', name: 'Tag 1', color: '#ff0000', isPinned: false }, + ]; + + vi.mocked(prisma.note.create).mockResolvedValue(mockNote as any); + vi.mocked(prisma.note.findUnique).mockResolvedValue({ + ...mockNote, + noteTags: [{ tag: mockTags[0] }], + } as any); + vi.mocked(tagsService.ensureTagsExist).mockResolvedValue(mockTags as any); + vi.mocked(prisma.noteTag.createMany).mockResolvedValue({ + count: 1, + } as any); + + await service.createNote({ + title: 'New Note', + content: 'Content', + userId: mockUserId, + tags: ['Tag 1'], + }); + + expect(tagsService.ensureTagsExist).toHaveBeenCalledWith( + ['Tag 1'], + mockUserId + ); + }); + }); + + describe('updateNote', () => { + it('devrait mettre à jour une note', async () => { + const mockNote = { + id: 'note-1', + title: 'Updated Note', + content: 'Updated Content', + userId: mockUserId, + taskId: null, + createdAt: new Date(), + updatedAt: new Date(), + noteTags: [], + task: null, + }; + + vi.mocked(prisma.note.findFirst).mockResolvedValue({ + id: 'note-1', + userId: mockUserId, + } as any); + vi.mocked(prisma.note.update).mockResolvedValue(mockNote as any); + vi.mocked(prisma.note.findUnique).mockResolvedValue({ + ...mockNote, + noteTags: [], + } as any); + vi.mocked(prisma.noteTag.deleteMany).mockResolvedValue({ + count: 0, + } as any); + vi.mocked(tagsService.ensureTagsExist).mockResolvedValue([]); + + const note = await service.updateNote('note-1', mockUserId, { + title: 'Updated Note', + content: 'Updated Content', + }); + + expect(note).toBeDefined(); + expect(prisma.note.update).toHaveBeenCalled(); + }); + + it("devrait lancer une erreur si la note n'existe pas", async () => { + vi.mocked(prisma.note.findFirst).mockResolvedValue(null); + + await expect( + service.updateNote('non-existent', mockUserId, { + title: 'Updated', + }) + ).rejects.toThrow(); + }); + }); + + describe('deleteNote', () => { + it('devrait supprimer une note', async () => { + vi.mocked(prisma.note.findFirst).mockResolvedValue({ + id: 'note-1', + userId: mockUserId, + } as any); + vi.mocked(prisma.note.delete).mockResolvedValue({} as any); + + await service.deleteNote('note-1', mockUserId); + + expect(prisma.note.delete).toHaveBeenCalledWith({ + where: { id: 'note-1' }, + }); + }); + + it("devrait lancer une erreur si la note n'existe pas", async () => { + vi.mocked(prisma.note.findFirst).mockResolvedValue(null); + + await expect( + service.deleteNote('non-existent', mockUserId) + ).rejects.toThrow(); + }); + }); + + describe('searchNotes', () => { + it('devrait rechercher dans les notes', async () => { + const mockNotes = [ + { + id: 'note-1', + title: 'Test Note', + content: 'Content', + userId: mockUserId, + taskId: null, + createdAt: new Date(), + updatedAt: new Date(), + noteTags: [], + task: null, + }, + ]; + + vi.mocked(prisma.note.findMany).mockResolvedValue(mockNotes as any); + + const notes = await service.searchNotes(mockUserId, 'Test'); + + expect(notes).toHaveLength(1); + expect(prisma.note.findMany).toHaveBeenCalledWith({ + where: { + userId: mockUserId, + OR: [ + { title: { contains: 'Test' } }, + { content: { contains: 'Test' } }, + ], + }, + orderBy: { updatedAt: 'desc' }, + }); + }); + }); + + describe('getNotesStats', () => { + it('devrait retourner les statistiques des notes', async () => { + const mockNotes = [ + { + id: 'note-1', + content: 'This is a test note with words', + updatedAt: new Date(), + }, + { + id: 'note-2', + content: 'Another note', + updatedAt: new Date(), + }, + ]; + + vi.mocked(prisma.note.findMany).mockResolvedValue(mockNotes as any); + + const stats = await service.getNotesStats(mockUserId); + + expect(stats.totalNotes).toBeGreaterThanOrEqual(0); + expect(stats.totalWords).toBeGreaterThanOrEqual(0); + expect(stats.lastUpdated).toBeDefined(); + }); + }); +}); diff --git a/src/services/__tests__/users.test.ts b/src/services/__tests__/users.test.ts new file mode 100644 index 0000000..f64c463 --- /dev/null +++ b/src/services/__tests__/users.test.ts @@ -0,0 +1,276 @@ +/** + * Tests unitaires pour usersService + */ + +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { usersService } from '../users'; +import { prisma } from '../core/database'; +import bcrypt from 'bcryptjs'; + +// Mock de prisma +vi.mock('../core/database', () => ({ + prisma: { + user: { + create: vi.fn(), + findUnique: vi.fn(), + update: vi.fn(), + }, + }, +})); + +// Mock de bcrypt +vi.mock('bcryptjs', () => ({ + default: { + hash: vi.fn(), + compare: vi.fn(), + }, +})); + +describe('usersService', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe('createUser', () => { + it('devrait créer un nouvel utilisateur', async () => { + const mockUser = { + id: 'user-1', + email: 'test@example.com', + name: 'Test User', + firstName: 'Test', + lastName: 'User', + avatar: null, + role: 'user', + isActive: true, + lastLoginAt: null, + createdAt: new Date(), + updatedAt: new Date(), + }; + + vi.mocked(bcrypt.hash).mockResolvedValue('hashedPassword' as any); + vi.mocked(prisma.user.create).mockResolvedValue(mockUser as any); + + const user = await usersService.createUser({ + email: 'test@example.com', + name: 'Test User', + firstName: 'Test', + lastName: 'User', + password: 'password123', + }); + + expect(user).toEqual(mockUser); + expect(bcrypt.hash).toHaveBeenCalledWith('password123', 12); + expect(prisma.user.create).toHaveBeenCalledWith({ + data: { + email: 'test@example.com', + name: 'Test User', + firstName: 'Test', + lastName: 'User', + avatar: undefined, + role: 'user', + password: 'hashedPassword', + }, + select: expect.any(Object), + }); + }); + + it('devrait utiliser le rôle par défaut si non spécifié', async () => { + const mockUser = { + id: 'user-1', + email: 'test@example.com', + name: null, + firstName: null, + lastName: null, + avatar: null, + role: 'user', + isActive: true, + lastLoginAt: null, + createdAt: new Date(), + updatedAt: new Date(), + }; + + vi.mocked(bcrypt.hash).mockResolvedValue('hashedPassword' as any); + vi.mocked(prisma.user.create).mockResolvedValue(mockUser as any); + + await usersService.createUser({ + email: 'test@example.com', + password: 'password123', + }); + + expect(prisma.user.create).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ + role: 'user', + }), + }) + ); + }); + }); + + describe('getUserByEmail', () => { + it('devrait récupérer un utilisateur par email', async () => { + const mockUser = { + id: 'user-1', + email: 'test@example.com', + name: 'Test User', + firstName: 'Test', + lastName: 'User', + avatar: null, + role: 'user', + isActive: true, + lastLoginAt: null, + password: 'hashedPassword', + createdAt: new Date(), + updatedAt: new Date(), + }; + + vi.mocked(prisma.user.findUnique).mockResolvedValue(mockUser as any); + + const user = await usersService.getUserByEmail('test@example.com'); + + expect(user).toEqual(mockUser); + expect(prisma.user.findUnique).toHaveBeenCalledWith({ + where: { email: 'test@example.com' }, + select: expect.any(Object), + }); + }); + + it("devrait retourner null si l'utilisateur n'existe pas", async () => { + vi.mocked(prisma.user.findUnique).mockResolvedValue(null); + + const user = await usersService.getUserByEmail('nonexistent@example.com'); + + expect(user).toBeNull(); + }); + }); + + describe('getUserById', () => { + it('devrait récupérer un utilisateur par ID', async () => { + const mockUser = { + id: 'user-1', + email: 'test@example.com', + name: 'Test User', + firstName: 'Test', + lastName: 'User', + avatar: null, + role: 'user', + isActive: true, + lastLoginAt: null, + createdAt: new Date(), + updatedAt: new Date(), + }; + + vi.mocked(prisma.user.findUnique).mockResolvedValue(mockUser as any); + + const user = await usersService.getUserById('user-1'); + + expect(user).toEqual(mockUser); + expect(prisma.user.findUnique).toHaveBeenCalledWith({ + where: { id: 'user-1' }, + select: expect.any(Object), + }); + }); + }); + + describe('verifyPassword', () => { + it('devrait vérifier un mot de passe correct', async () => { + vi.mocked(bcrypt.compare).mockResolvedValue(true as any); + + const isValid = await usersService.verifyPassword( + 'password123', + 'hashedPassword' + ); + + expect(isValid).toBe(true); + expect(bcrypt.compare).toHaveBeenCalledWith( + 'password123', + 'hashedPassword' + ); + }); + + it('devrait retourner false pour un mot de passe incorrect', async () => { + vi.mocked(bcrypt.compare).mockResolvedValue(false as any); + + const isValid = await usersService.verifyPassword( + 'wrongPassword', + 'hashedPassword' + ); + + expect(isValid).toBe(false); + }); + }); + + describe('emailExists', () => { + it("devrait retourner true si l'email existe", async () => { + vi.mocked(prisma.user.findUnique).mockResolvedValue({ + id: 'user-1', + email: 'test@example.com', + } as any); + + const exists = await usersService.emailExists('test@example.com'); + + expect(exists).toBe(true); + }); + + it("devrait retourner false si l'email n'existe pas", async () => { + vi.mocked(prisma.user.findUnique).mockResolvedValue(null); + + const exists = await usersService.emailExists('nonexistent@example.com'); + + expect(exists).toBe(false); + }); + }); + + describe('updateLastLogin', () => { + it('devrait mettre à jour la date de dernière connexion', async () => { + vi.mocked(prisma.user.update).mockResolvedValue({} as any); + + await usersService.updateLastLogin('user-1'); + + expect(prisma.user.update).toHaveBeenCalledWith({ + where: { id: 'user-1' }, + data: { + lastLoginAt: expect.any(Date), + }, + }); + }); + }); + + describe('updateUser', () => { + it('devrait mettre à jour un utilisateur', async () => { + const mockUser = { + id: 'user-1', + email: 'test@example.com', + name: 'Updated Name', + firstName: 'Updated', + lastName: 'Name', + avatar: null, + role: 'user', + isActive: true, + lastLoginAt: null, + createdAt: new Date(), + updatedAt: new Date(), + }; + + vi.mocked(prisma.user.update).mockResolvedValue(mockUser as any); + + const user = await usersService.updateUser('user-1', { + name: 'Updated Name', + firstName: 'Updated', + lastName: 'Name', + }); + + expect(user).toEqual(mockUser); + expect(prisma.user.update).toHaveBeenCalledWith({ + where: { id: 'user-1' }, + data: { + name: 'Updated Name', + firstName: 'Updated', + lastName: 'Name', + }, + select: expect.any(Object), + }); + }); + }); +});