import { NotFoundException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { JwtService } from '@nestjs/jwt'; import { getRepositoryToken } from '@nestjs/typeorm'; import { ConfigService } from '@nestjs/config'; import { AuthService } from './auth.service'; import { UserService } from '../user/user.service'; import { ParentsService } from '../parents/parents.service'; import { DossiersService } from '../dossiers/dossiers.service'; import { AppConfigService } from 'src/modules/config/config.service'; import { MailService } from 'src/modules/mail/mail.service'; import { NumeroDossierService } from 'src/modules/numero-dossier/numero-dossier.service'; import { Parents } from 'src/entities/parents.entity'; import { Users, RoleType, StatutUtilisateurType } from 'src/entities/users.entity'; import { Children } from 'src/entities/children.entity'; import { AssistanteMaternelle } from 'src/entities/assistantes_maternelles.entity'; describe('AuthService (#118 create-password API)', () => { let service: AuthService; const usersRepoMock = { findOne: jest.fn(), createQueryBuilder: jest.fn(), update: jest.fn(), manager: { transaction: jest.fn() }, }; const userServiceMock = { findByTokenReprise: jest.fn(), }; const parentsServiceMock = { getDossierFamilleByNumero: jest.fn(), getFamilyUserIds: jest.fn(), }; const dossiersServiceMock = { getDossierByNumero: jest.fn(), }; const mailServiceMock = { sendPasswordResetEmail: jest.fn().mockResolvedValue(undefined), }; const appConfigMock = { get: jest.fn().mockReturnValue(7), }; beforeEach(async () => { jest.clearAllMocks(); const qbChain: any = {}; qbChain.where = jest.fn().mockReturnValue(qbChain); qbChain.select = jest.fn().mockReturnValue(qbChain); qbChain.getOne = jest.fn().mockResolvedValue(null); qbChain.update = jest.fn().mockReturnValue(qbChain); qbChain.set = jest.fn().mockReturnValue(qbChain); qbChain.andWhere = jest.fn().mockReturnValue(qbChain); qbChain.execute = jest.fn().mockResolvedValue({ affected: 0 }); usersRepoMock.createQueryBuilder.mockReturnValue(qbChain); usersRepoMock.update.mockResolvedValue({ affected: 1, raw: [] }); const module: TestingModule = await Test.createTestingModule({ providers: [ AuthService, { provide: UserService, useValue: userServiceMock }, { provide: ParentsService, useValue: parentsServiceMock }, { provide: DossiersService, useValue: dossiersServiceMock }, { provide: JwtService, useValue: {} }, { provide: ConfigService, useValue: { get: jest.fn() } }, { provide: AppConfigService, useValue: appConfigMock }, { provide: MailService, useValue: mailServiceMock }, { provide: NumeroDossierService, useValue: {} }, { provide: getRepositoryToken(Parents), useValue: {} }, { provide: getRepositoryToken(Users), useValue: usersRepoMock }, { provide: getRepositoryToken(Children), useValue: {} }, { provide: getRepositoryToken(AssistanteMaternelle), useValue: {} }, ], }).compile(); service = module.get(AuthService); }); it('should be defined', () => { expect(service).toBeDefined(); }); it('returns 404-like error when verify token is missing', async () => { await expect(service.verifyCreatePasswordToken('')).rejects.toThrow(NotFoundException); }); it('returns valid=true when token exists and is not expired', async () => { usersRepoMock.findOne.mockResolvedValue({ id: 'u1', password: null, token_creation_mdp_expire_le: new Date(Date.now() + 60_000), }); await expect(service.verifyCreatePasswordToken('tok-123')).resolves.toEqual({ valid: true, message: 'Token valide', }); }); describe('#127 forgot / reset password', () => { it('requestPasswordReset returns generic message when email unknown', async () => { const r = await service.requestPasswordReset('unknown@test.fr'); expect(r.message).toContain('Si cette adresse'); expect(mailServiceMock.sendPasswordResetEmail).not.toHaveBeenCalled(); }); it('requestPasswordReset sends mail when user has password', async () => { usersRepoMock.createQueryBuilder.mockReturnValueOnce({ where: jest.fn().mockReturnThis(), select: jest.fn().mockReturnThis(), getOne: jest.fn().mockResolvedValue({ id: 'u1', email: 'a@test.fr', prenom: 'A', nom: 'B', password: 'hashed', }), }); const r = await service.requestPasswordReset('a@test.fr'); expect(r.message).toContain('Si cette adresse'); expect(usersRepoMock.update).toHaveBeenCalled(); expect(mailServiceMock.sendPasswordResetEmail).toHaveBeenCalledWith( expect.objectContaining({ email: 'a@test.fr', token: expect.any(String) }), ); }); it('resetPasswordWithResetToken throws when token unknown', async () => { usersRepoMock.findOne.mockResolvedValue(null); await expect(service.resetPasswordWithResetToken('bad', 'Password1')).rejects.toThrow( NotFoundException, ); }); it('resetPasswordWithResetToken succeeds when update affects row', async () => { usersRepoMock.findOne.mockResolvedValue({ id: 'u1', password: 'oldhash', password_reset_expires: new Date(Date.now() + 60_000), }); const exec = jest.fn().mockResolvedValue({ affected: 1 }); const chain: any = {}; chain.update = jest.fn().mockReturnValue(chain); chain.set = jest.fn().mockReturnValue(chain); chain.where = jest.fn().mockReturnValue(chain); chain.andWhere = jest.fn().mockReturnValue(chain); chain.execute = exec; usersRepoMock.createQueryBuilder.mockReturnValueOnce(chain); const r = await service.resetPasswordWithResetToken('good-token', 'Password1'); expect(r.message.toLowerCase()).toContain('mis à jour'); expect(exec).toHaveBeenCalled(); }); }); describe('Reprise après refus (#112)', () => { const token = '11111111-1111-1111-1111-111111111111'; it('getRepriseDossier throws when token invalid', async () => { userServiceMock.findByTokenReprise.mockResolvedValue(null); await expect(service.getRepriseDossier(token)).rejects.toThrow(NotFoundException); }); it('getRepriseDossier returns famille complète pour parent', async () => { userServiceMock.findByTokenReprise.mockResolvedValue({ id: 'p1', email: 'claire@test.fr', prenom: 'Claire', nom: 'MARTIN', role: RoleType.PARENT, numero_dossier: '2026-000021', statut: StatutUtilisateurType.REFUSE, }); parentsServiceMock.getDossierFamilleByNumero.mockResolvedValue({ numero_dossier: '2026-000021', parents: [{ user_id: 'p1', email: 'claire@test.fr', statut: StatutUtilisateurType.REFUSE }], enfants: [{ id: 'e1', first_name: 'Emma', status: 'sans_garde' }], texte_motivation: 'Motivation test', }); const result = await service.getRepriseDossier(token); expect(result.parents).toHaveLength(1); expect(result.enfants).toHaveLength(1); expect(result.texte_motivation).toBe('Motivation test'); expect(result.numero_dossier).toBe('2026-000021'); }); it('getRepriseDossier returns fiche AM complète', async () => { userServiceMock.findByTokenReprise.mockResolvedValue({ id: 'am1', email: 'am@test.fr', role: RoleType.ASSISTANTE_MATERNELLE, numero_dossier: '2026-000003', }); dossiersServiceMock.getDossierByNumero.mockResolvedValue({ type: 'am', dossier: { numero_dossier: '2026-000003', user: { id: 'am1', email: 'am@test.fr', prenom: 'Marie', nom: 'DUPONT', statut: StatutUtilisateurType.REFUSE, }, numero_agrement: 'AGR-1', nir: '123456789012345', biographie: 'Bio', nb_max_enfants: 4, place_disponible: 2, }, }); const result = await service.getRepriseDossier(token); expect(result.numero_agrement).toBe('AGR-1'); expect(result.biographie).toBe('Bio'); expect(result.nb_max_enfants).toBe(4); }); }); });