diff --git a/backend/src/modules/persons/controllers/persons.controller.spec.ts b/backend/src/modules/persons/controllers/persons.controller.spec.ts new file mode 100644 index 0000000..fb2f746 --- /dev/null +++ b/backend/src/modules/persons/controllers/persons.controller.spec.ts @@ -0,0 +1,154 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { PersonsController } from './persons.controller'; +import { PersonsService } from '../services/persons.service'; +import { CreatePersonDto, Gender, OralEaseLevel } from '../dto/create-person.dto'; +import { UpdatePersonDto } from '../dto/update-person.dto'; +import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard'; + +describe('PersonsController', () => { + let controller: PersonsController; + let service: PersonsService; + + // Mock data + const mockPerson = { + id: 'person1', + firstName: 'John', + lastName: 'Doe', + gender: Gender.MALE, + technicalLevel: 3, + hasTechnicalTraining: true, + frenchSpeakingLevel: 4, + oralEaseLevel: OralEaseLevel.COMFORTABLE, + age: 30, + projectId: 'project1', + attributes: {}, + createdAt: new Date(), + updatedAt: new Date(), + }; + + const mockPersonToGroup = { + personId: 'person1', + groupId: 'group1', + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [PersonsController], + providers: [ + { + provide: PersonsService, + useValue: { + create: jest.fn().mockResolvedValue(mockPerson), + findAll: jest.fn().mockResolvedValue([mockPerson]), + findByProjectId: jest.fn().mockResolvedValue([mockPerson]), + findById: jest.fn().mockResolvedValue(mockPerson), + update: jest.fn().mockResolvedValue(mockPerson), + remove: jest.fn().mockResolvedValue(mockPerson), + findByProjectIdAndGroupId: jest.fn().mockResolvedValue([{ person: mockPerson }]), + addToGroup: jest.fn().mockResolvedValue(mockPersonToGroup), + removeFromGroup: jest.fn().mockResolvedValue(mockPersonToGroup), + }, + }, + ], + }) + .overrideGuard(JwtAuthGuard) + .useValue({ canActivate: () => true }) + .compile(); + + controller = module.get(PersonsController); + service = module.get(PersonsService); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); + + describe('create', () => { + it('should create a new person', async () => { + const createPersonDto: CreatePersonDto = { + firstName: 'John', + lastName: 'Doe', + gender: Gender.MALE, + technicalLevel: 3, + hasTechnicalTraining: true, + frenchSpeakingLevel: 4, + oralEaseLevel: OralEaseLevel.COMFORTABLE, + projectId: 'project1', + }; + + expect(await controller.create(createPersonDto)).toBe(mockPerson); + expect(service.create).toHaveBeenCalledWith(createPersonDto); + }); + }); + + describe('findAll', () => { + it('should return all persons when no projectId is provided', async () => { + expect(await controller.findAll()).toEqual([mockPerson]); + expect(service.findAll).toHaveBeenCalled(); + }); + + it('should return persons filtered by projectId when projectId is provided', async () => { + const projectId = 'project1'; + expect(await controller.findAll(projectId)).toEqual([mockPerson]); + expect(service.findByProjectId).toHaveBeenCalledWith(projectId); + }); + }); + + describe('findOne', () => { + it('should return a person by ID', async () => { + const id = 'person1'; + expect(await controller.findOne(id)).toBe(mockPerson); + expect(service.findById).toHaveBeenCalledWith(id); + }); + }); + + describe('update', () => { + it('should update a person', async () => { + const id = 'person1'; + const updatePersonDto: UpdatePersonDto = { + firstName: 'Jane', + }; + + expect(await controller.update(id, updatePersonDto)).toBe(mockPerson); + expect(service.update).toHaveBeenCalledWith(id, updatePersonDto); + }); + }); + + describe('remove', () => { + it('should delete a person', async () => { + const id = 'person1'; + expect(await controller.remove(id)).toBe(mockPerson); + expect(service.remove).toHaveBeenCalledWith(id); + }); + }); + + describe('findByProjectIdAndGroupId', () => { + it('should return persons by project ID and group ID', async () => { + const projectId = 'project1'; + const groupId = 'group1'; + + expect(await controller.findByProjectIdAndGroupId(projectId, groupId)).toEqual([{ person: mockPerson }]); + expect(service.findByProjectIdAndGroupId).toHaveBeenCalledWith(projectId, groupId); + }); + }); + + describe('addToGroup', () => { + it('should add a person to a group', async () => { + const id = 'person1'; + const groupId = 'group1'; + + expect(await controller.addToGroup(id, groupId)).toBe(mockPersonToGroup); + expect(service.addToGroup).toHaveBeenCalledWith(id, groupId); + }); + }); + + describe('removeFromGroup', () => { + it('should remove a person from a group', async () => { + const id = 'person1'; + const groupId = 'group1'; + + expect(await controller.removeFromGroup(id, groupId)).toBe(mockPersonToGroup); + expect(service.removeFromGroup).toHaveBeenCalledWith(id, groupId); + }); + }); +}); \ No newline at end of file diff --git a/backend/src/modules/persons/services/persons.service.spec.ts b/backend/src/modules/persons/services/persons.service.spec.ts new file mode 100644 index 0000000..5c609eb --- /dev/null +++ b/backend/src/modules/persons/services/persons.service.spec.ts @@ -0,0 +1,277 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { PersonsService } from './persons.service'; +import { NotFoundException } from '@nestjs/common'; +import { DRIZZLE } from '../../../database/database.module'; +import { Gender, OralEaseLevel } from '../dto/create-person.dto'; + +describe('PersonsService', () => { + let service: PersonsService; + let mockDb: any; + + // Mock data + const mockPerson = { + id: 'person1', + firstName: 'John', + lastName: 'Doe', + gender: Gender.MALE, + technicalLevel: 3, + hasTechnicalTraining: true, + frenchSpeakingLevel: 4, + oralEaseLevel: OralEaseLevel.COMFORTABLE, + age: 30, + projectId: 'project1', + attributes: {}, + createdAt: new Date(), + updatedAt: new Date(), + }; + + const mockGroup = { + id: 'group1', + name: 'Test Group', + projectId: 'project1', + createdAt: new Date(), + updatedAt: new Date(), + }; + + const mockPersonToGroup = { + personId: 'person1', + groupId: 'group1', + }; + + // Mock database operations + const mockDbOperations = { + select: jest.fn().mockReturnThis(), + from: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + insert: jest.fn().mockReturnThis(), + values: jest.fn().mockReturnThis(), + update: jest.fn().mockReturnThis(), + set: jest.fn().mockReturnThis(), + delete: jest.fn().mockReturnThis(), + innerJoin: jest.fn().mockReturnThis(), + returning: jest.fn().mockImplementation(() => { + return [mockPerson]; + }), + }; + + beforeEach(async () => { + mockDb = { + ...mockDbOperations, + }; + + const module: TestingModule = await Test.createTestingModule({ + providers: [ + PersonsService, + { + provide: DRIZZLE, + useValue: mockDb, + }, + ], + }).compile(); + + service = module.get(PersonsService); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + describe('create', () => { + it('should create a new person', async () => { + const createPersonDto = { + firstName: 'John', + lastName: 'Doe', + gender: Gender.MALE, + technicalLevel: 3, + hasTechnicalTraining: true, + frenchSpeakingLevel: 4, + oralEaseLevel: OralEaseLevel.COMFORTABLE, + projectId: 'project1', + }; + + const result = await service.create(createPersonDto); + + expect(mockDb.insert).toHaveBeenCalled(); + expect(mockDb.values).toHaveBeenCalledWith(createPersonDto); + expect(result).toEqual(mockPerson); + }); + }); + + describe('findAll', () => { + it('should return all persons', async () => { + mockDb.select.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.from.mockImplementationOnce(() => [mockPerson]); + + const result = await service.findAll(); + + expect(mockDb.select).toHaveBeenCalled(); + expect(mockDb.from).toHaveBeenCalled(); + expect(result).toEqual([mockPerson]); + }); + }); + + describe('findByProjectId', () => { + it('should return persons for a specific project', async () => { + const projectId = 'project1'; + mockDb.select.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.from.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.where.mockImplementationOnce(() => [mockPerson]); + + const result = await service.findByProjectId(projectId); + + expect(mockDb.select).toHaveBeenCalled(); + expect(mockDb.from).toHaveBeenCalled(); + expect(mockDb.where).toHaveBeenCalled(); + expect(result).toEqual([mockPerson]); + }); + }); + + describe('findById', () => { + it('should return a person by ID', async () => { + const id = 'person1'; + mockDb.select.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.from.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.where.mockImplementationOnce(() => [mockPerson]); + + const result = await service.findById(id); + + expect(mockDb.select).toHaveBeenCalled(); + expect(mockDb.from).toHaveBeenCalled(); + expect(mockDb.where).toHaveBeenCalled(); + expect(result).toEqual(mockPerson); + }); + + it('should throw NotFoundException if person not found', async () => { + const id = 'nonexistent'; + mockDb.select.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.from.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.where.mockImplementationOnce(() => []); + + await expect(service.findById(id)).rejects.toThrow(NotFoundException); + }); + }); + + describe('update', () => { + it('should update a person', async () => { + const id = 'person1'; + const updatePersonDto = { + firstName: 'Jane', + }; + + const result = await service.update(id, updatePersonDto); + + expect(mockDb.update).toHaveBeenCalled(); + expect(mockDb.set).toHaveBeenCalled(); + expect(mockDb.where).toHaveBeenCalled(); + expect(result).toEqual(mockPerson); + }); + + it('should throw NotFoundException if person not found', async () => { + const id = 'nonexistent'; + const updatePersonDto = { + firstName: 'Jane', + }; + + mockDb.update.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.set.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.where.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.returning.mockImplementationOnce(() => []); + + await expect(service.update(id, updatePersonDto)).rejects.toThrow(NotFoundException); + }); + }); + + describe('remove', () => { + it('should delete a person', async () => { + const id = 'person1'; + + const result = await service.remove(id); + + expect(mockDb.delete).toHaveBeenCalled(); + expect(mockDb.where).toHaveBeenCalled(); + expect(result).toEqual(mockPerson); + }); + + it('should throw NotFoundException if person not found', async () => { + const id = 'nonexistent'; + + mockDb.delete.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.where.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.returning.mockImplementationOnce(() => []); + + await expect(service.remove(id)).rejects.toThrow(NotFoundException); + }); + }); + + describe('findByProjectIdAndGroupId', () => { + it('should return persons by project ID and group ID', async () => { + const projectId = 'project1'; + const groupId = 'group1'; + + mockDb.select.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.from.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.innerJoin.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.where.mockImplementationOnce(() => [{ person: mockPerson }]); + + const result = await service.findByProjectIdAndGroupId(projectId, groupId); + + expect(mockDb.select).toHaveBeenCalled(); + expect(mockDb.from).toHaveBeenCalled(); + expect(mockDb.innerJoin).toHaveBeenCalled(); + expect(mockDb.where).toHaveBeenCalled(); + expect(result).toEqual([{ person: mockPerson }]); + }); + }); + + describe('addToGroup', () => { + it('should add a person to a group', async () => { + const personId = 'person1'; + const groupId = 'group1'; + + mockDb.insert.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.values.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.returning.mockImplementationOnce(() => [mockPersonToGroup]); + + const result = await service.addToGroup(personId, groupId); + + expect(mockDb.insert).toHaveBeenCalled(); + expect(mockDb.values).toHaveBeenCalledWith({ + personId, + groupId, + }); + expect(result).toEqual(mockPersonToGroup); + }); + }); + + describe('removeFromGroup', () => { + it('should remove a person from a group', async () => { + const personId = 'person1'; + const groupId = 'group1'; + + mockDb.delete.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.where.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.returning.mockImplementationOnce(() => [mockPersonToGroup]); + + const result = await service.removeFromGroup(personId, groupId); + + expect(mockDb.delete).toHaveBeenCalled(); + expect(mockDb.where).toHaveBeenCalled(); + expect(result).toEqual(mockPersonToGroup); + }); + + it('should throw NotFoundException if relation not found', async () => { + const personId = 'nonexistent'; + const groupId = 'group1'; + + mockDb.delete.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.where.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.returning.mockImplementationOnce(() => []); + + await expect(service.removeFromGroup(personId, groupId)).rejects.toThrow(NotFoundException); + }); + }); +}); \ No newline at end of file diff --git a/backend/src/modules/projects/controllers/projects.controller.spec.ts b/backend/src/modules/projects/controllers/projects.controller.spec.ts new file mode 100644 index 0000000..4e3d053 --- /dev/null +++ b/backend/src/modules/projects/controllers/projects.controller.spec.ts @@ -0,0 +1,116 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ProjectsController } from './projects.controller'; +import { ProjectsService } from '../services/projects.service'; +import { CreateProjectDto } from '../dto/create-project.dto'; +import { UpdateProjectDto } from '../dto/update-project.dto'; +import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard'; + +describe('ProjectsController', () => { + let controller: ProjectsController; + let service: ProjectsService; + + // Mock data + const mockProject = { + id: 'project1', + name: 'Test Project', + description: 'Test Description', + ownerId: 'user1', + settings: {}, + createdAt: new Date(), + updatedAt: new Date(), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [ProjectsController], + providers: [ + { + provide: ProjectsService, + useValue: { + create: jest.fn().mockResolvedValue(mockProject), + findAll: jest.fn().mockResolvedValue([mockProject]), + findByOwnerId: jest.fn().mockResolvedValue([mockProject]), + findById: jest.fn().mockResolvedValue(mockProject), + update: jest.fn().mockResolvedValue(mockProject), + remove: jest.fn().mockResolvedValue(mockProject), + checkUserAccess: jest.fn().mockResolvedValue(true), + }, + }, + ], + }) + .overrideGuard(JwtAuthGuard) + .useValue({ canActivate: () => true }) + .compile(); + + controller = module.get(ProjectsController); + service = module.get(ProjectsService); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); + + describe('create', () => { + it('should create a new project', async () => { + const createProjectDto: CreateProjectDto = { + name: 'Test Project', + description: 'Test Description', + ownerId: 'user1', + }; + + expect(await controller.create(createProjectDto)).toBe(mockProject); + expect(service.create).toHaveBeenCalledWith(createProjectDto); + }); + }); + + describe('findAll', () => { + it('should return all projects when no ownerId is provided', async () => { + expect(await controller.findAll()).toEqual([mockProject]); + expect(service.findAll).toHaveBeenCalled(); + }); + + it('should return projects filtered by ownerId when ownerId is provided', async () => { + const ownerId = 'user1'; + expect(await controller.findAll(ownerId)).toEqual([mockProject]); + expect(service.findByOwnerId).toHaveBeenCalledWith(ownerId); + }); + }); + + describe('findOne', () => { + it('should return a project by ID', async () => { + const id = 'project1'; + expect(await controller.findOne(id)).toBe(mockProject); + expect(service.findById).toHaveBeenCalledWith(id); + }); + }); + + describe('update', () => { + it('should update a project', async () => { + const id = 'project1'; + const updateProjectDto: UpdateProjectDto = { + name: 'Updated Project', + }; + + expect(await controller.update(id, updateProjectDto)).toBe(mockProject); + expect(service.update).toHaveBeenCalledWith(id, updateProjectDto); + }); + }); + + describe('remove', () => { + it('should delete a project', async () => { + const id = 'project1'; + expect(await controller.remove(id)).toBe(mockProject); + expect(service.remove).toHaveBeenCalledWith(id); + }); + }); + + describe('checkUserAccess', () => { + it('should check if a user has access to a project', async () => { + const projectId = 'project1'; + const userId = 'user1'; + + expect(await controller.checkUserAccess(projectId, userId)).toBe(true); + expect(service.checkUserAccess).toHaveBeenCalledWith(projectId, userId); + }); + }); +}); \ No newline at end of file diff --git a/backend/src/modules/projects/services/projects.service.spec.ts b/backend/src/modules/projects/services/projects.service.spec.ts new file mode 100644 index 0000000..a729865 --- /dev/null +++ b/backend/src/modules/projects/services/projects.service.spec.ts @@ -0,0 +1,217 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ProjectsService } from './projects.service'; +import { NotFoundException } from '@nestjs/common'; +import { DRIZZLE } from '../../../database/database.module'; + +describe('ProjectsService', () => { + let service: ProjectsService; + let mockDb: any; + + // Mock data + const mockProject = { + id: 'project1', + name: 'Test Project', + description: 'Test Description', + ownerId: 'user1', + settings: {}, + createdAt: new Date(), + updatedAt: new Date(), + }; + + // Mock database operations + const mockDbOperations = { + select: jest.fn().mockReturnThis(), + from: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + insert: jest.fn().mockReturnThis(), + values: jest.fn().mockReturnThis(), + update: jest.fn().mockReturnThis(), + set: jest.fn().mockReturnThis(), + delete: jest.fn().mockReturnThis(), + returning: jest.fn().mockImplementation(() => { + return [mockProject]; + }), + }; + + beforeEach(async () => { + mockDb = { + ...mockDbOperations, + }; + + const module: TestingModule = await Test.createTestingModule({ + providers: [ + ProjectsService, + { + provide: DRIZZLE, + useValue: mockDb, + }, + ], + }).compile(); + + service = module.get(ProjectsService); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + describe('create', () => { + it('should create a new project', async () => { + const createProjectDto = { + name: 'Test Project', + description: 'Test Description', + ownerId: 'user1', + }; + + const result = await service.create(createProjectDto); + + expect(mockDb.insert).toHaveBeenCalled(); + expect(mockDb.values).toHaveBeenCalledWith(createProjectDto); + expect(result).toEqual(mockProject); + }); + }); + + describe('findAll', () => { + it('should return all projects', async () => { + mockDb.select.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.from.mockImplementationOnce(() => [mockProject]); + + const result = await service.findAll(); + + expect(mockDb.select).toHaveBeenCalled(); + expect(mockDb.from).toHaveBeenCalled(); + expect(result).toEqual([mockProject]); + }); + }); + + describe('findByOwnerId', () => { + it('should return projects for a specific owner', async () => { + const ownerId = 'user1'; + mockDb.select.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.from.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.where.mockImplementationOnce(() => [mockProject]); + + const result = await service.findByOwnerId(ownerId); + + expect(mockDb.select).toHaveBeenCalled(); + expect(mockDb.from).toHaveBeenCalled(); + expect(mockDb.where).toHaveBeenCalled(); + expect(result).toEqual([mockProject]); + }); + }); + + describe('findById', () => { + it('should return a project by ID', async () => { + const id = 'project1'; + mockDb.select.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.from.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.where.mockImplementationOnce(() => [mockProject]); + + const result = await service.findById(id); + + expect(mockDb.select).toHaveBeenCalled(); + expect(mockDb.from).toHaveBeenCalled(); + expect(mockDb.where).toHaveBeenCalled(); + expect(result).toEqual(mockProject); + }); + + it('should throw NotFoundException if project not found', async () => { + const id = 'nonexistent'; + mockDb.select.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.from.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.where.mockImplementationOnce(() => []); + + await expect(service.findById(id)).rejects.toThrow(NotFoundException); + }); + }); + + describe('update', () => { + it('should update a project', async () => { + const id = 'project1'; + const updateProjectDto = { + name: 'Updated Project', + }; + + const result = await service.update(id, updateProjectDto); + + expect(mockDb.update).toHaveBeenCalled(); + expect(mockDb.set).toHaveBeenCalled(); + expect(mockDb.where).toHaveBeenCalled(); + expect(result).toEqual(mockProject); + }); + + it('should throw NotFoundException if project not found', async () => { + const id = 'nonexistent'; + const updateProjectDto = { + name: 'Updated Project', + }; + + mockDb.update.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.set.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.where.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.returning.mockImplementationOnce(() => []); + + await expect(service.update(id, updateProjectDto)).rejects.toThrow(NotFoundException); + }); + }); + + describe('remove', () => { + it('should delete a project', async () => { + const id = 'project1'; + + const result = await service.remove(id); + + expect(mockDb.delete).toHaveBeenCalled(); + expect(mockDb.where).toHaveBeenCalled(); + expect(result).toEqual(mockProject); + }); + + it('should throw NotFoundException if project not found', async () => { + const id = 'nonexistent'; + + mockDb.delete.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.where.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.returning.mockImplementationOnce(() => []); + + await expect(service.remove(id)).rejects.toThrow(NotFoundException); + }); + }); + + describe('checkUserAccess', () => { + it('should return true if user has access to project', async () => { + const projectId = 'project1'; + const userId = 'user1'; + + mockDb.select.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.from.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.where.mockImplementationOnce(() => [mockProject]); + + const result = await service.checkUserAccess(projectId, userId); + + expect(mockDb.select).toHaveBeenCalled(); + expect(mockDb.from).toHaveBeenCalled(); + expect(mockDb.where).toHaveBeenCalled(); + expect(result).toBe(true); + }); + + it('should return false if user does not have access to project', async () => { + const projectId = 'project1'; + const userId = 'user2'; + + mockDb.select.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.from.mockImplementationOnce(() => mockDbOperations); + mockDbOperations.where.mockImplementationOnce(() => []); + + const result = await service.checkUserAccess(projectId, userId); + + expect(mockDb.select).toHaveBeenCalled(); + expect(mockDb.from).toHaveBeenCalled(); + expect(mockDb.where).toHaveBeenCalled(); + expect(result).toBe(false); + }); + }); +}); \ No newline at end of file