userRepository = $this->createMock(UserRepositoryInterface::class); $this->service = new UserService($this->userRepository); } // ── createUser ───────────────────────────────────────────────── /** * createUser() doit créer et retourner un utilisateur avec les bonnes données. */ public function testCreateUserWithValidData(): void { $this->userRepository->method('findByUsername')->willReturn(null); $this->userRepository->method('findByEmail')->willReturn(null); $this->userRepository->expects($this->once())->method('create'); $user = $this->service->createUser('Alice', 'alice@example.com', 'motdepasse1'); $this->assertSame('alice', $user->getUsername()); $this->assertSame('alice@example.com', $user->getEmail()); $this->assertSame(User::ROLE_USER, $user->getRole()); } /** * createUser() doit normaliser le nom d'utilisateur et l'email en minuscules. */ public function testCreateUserNormalizesToLowercase(): void { $this->userRepository->method('findByUsername')->willReturn(null); $this->userRepository->method('findByEmail')->willReturn(null); $this->userRepository->method('create'); $user = $this->service->createUser(' ALICE ', ' ALICE@EXAMPLE.COM ', 'motdepasse1'); $this->assertSame('alice', $user->getUsername()); $this->assertSame('alice@example.com', $user->getEmail()); } /** * createUser() doit lever DuplicateUsernameException si le nom est déjà pris. */ public function testCreateUserDuplicateUsernameThrowsDuplicateUsernameException(): void { $existingUser = $this->makeUser('alice', 'alice@example.com'); $this->userRepository->method('findByUsername')->willReturn($existingUser); $this->expectException(DuplicateUsernameException::class); $this->service->createUser('alice', 'autre@example.com', 'motdepasse1'); } /** * createUser() doit lever DuplicateEmailException si l'email est déjà utilisé. */ public function testCreateUserDuplicateEmailThrowsDuplicateEmailException(): void { $existingUser = $this->makeUser('bob', 'alice@example.com'); $this->userRepository->method('findByUsername')->willReturn(null); $this->userRepository->method('findByEmail')->willReturn($existingUser); $this->expectException(DuplicateEmailException::class); $this->service->createUser('newuser', 'alice@example.com', 'motdepasse1'); } /** * createUser() doit lever WeakPasswordException si le mot de passe est trop court. */ public function testCreateUserTooShortPasswordThrowsWeakPasswordException(): void { $this->userRepository->method('findByUsername')->willReturn(null); $this->userRepository->method('findByEmail')->willReturn(null); $this->expectException(WeakPasswordException::class); $this->service->createUser('alice', 'alice@example.com', '1234567'); } /** * createUser() avec exactement 8 caractères de mot de passe doit réussir. */ public function testCreateUserMinimumPasswordLength(): void { $this->userRepository->method('findByUsername')->willReturn(null); $this->userRepository->method('findByEmail')->willReturn(null); $this->userRepository->method('create'); $user = $this->service->createUser('alice', 'alice@example.com', '12345678'); $this->assertInstanceOf(User::class, $user); } /** * createUser() doit stocker un hash bcrypt, jamais le mot de passe en clair. */ public function testCreateUserPasswordIsHashed(): void { $plainPassword = 'motdepasse1'; $this->userRepository->method('findByUsername')->willReturn(null); $this->userRepository->method('findByEmail')->willReturn(null); $this->userRepository->method('create'); $user = $this->service->createUser('alice', 'alice@example.com', $plainPassword); $this->assertNotSame($plainPassword, $user->getPasswordHash()); $this->assertTrue(password_verify($plainPassword, $user->getPasswordHash())); } /** * createUser() doit attribuer le rôle passé en paramètre. */ public function testCreateUserWithEditorRole(): void { $this->userRepository->method('findByUsername')->willReturn(null); $this->userRepository->method('findByEmail')->willReturn(null); $this->userRepository->method('create'); $user = $this->service->createUser('alice', 'alice@example.com', 'motdepasse1', User::ROLE_EDITOR); $this->assertSame(User::ROLE_EDITOR, $user->getRole()); } // ── findAll ──────────────────────────────────────────────────── /** * findAll() délègue au repository et retourne la liste. */ public function testFindAllDelegatesToRepository(): void { $users = [$this->makeUser('alice', 'alice@example.com')]; $this->userRepository->method('findAll')->willReturn($users); $this->assertSame($users, $this->service->findAll()); } // ── findById ─────────────────────────────────────────────────── /** * findById() retourne null si l'utilisateur est introuvable. */ public function testFindByIdReturnsNullWhenMissing(): void { $this->userRepository->method('findById')->willReturn(null); $this->assertNull($this->service->findById(99)); } /** * findById() retourne l'utilisateur trouvé. */ public function testFindByIdReturnsUser(): void { $user = $this->makeUser('alice', 'alice@example.com'); $this->userRepository->expects($this->once())->method('findById')->with(1)->willReturn($user); $this->assertSame($user, $this->service->findById(1)); } // ── delete ───────────────────────────────────────────────────── /** * delete() délègue la suppression au repository. */ public function testDeleteDelegatesToRepository(): void { $this->userRepository->expects($this->once())->method('findById')->with(5)->willReturn($this->makeUser('alice', 'alice@example.com')); $this->userRepository->expects($this->once())->method('delete')->with(5); $this->service->delete(5); } // ── updateRole ───────────────────────────────────────────────── /** * updateRole() doit déléguer au repository avec le rôle validé. */ public function testUpdateRoleDelegatesToRepository(): void { $this->userRepository->expects($this->once())->method('findById')->with(3)->willReturn($this->makeUser('alice', 'alice@example.com')); $this->userRepository->expects($this->once()) ->method('updateRole') ->with(3, User::ROLE_EDITOR); $this->service->updateRole(3, User::ROLE_EDITOR); } /** * updateRole() doit lever InvalidArgumentException pour un rôle inconnu. */ public function testUpdateRoleThrowsOnInvalidRole(): void { $this->userRepository->expects($this->never())->method('updateRole'); $this->expectException(InvalidRoleException::class); $this->service->updateRole(1, 'superadmin'); } /** * updateRole() accepte uniquement les rôles attribuables depuis l'interface. */ #[\PHPUnit\Framework\Attributes\DataProvider('validRolesProvider')] public function testUpdateRoleAcceptsAllValidRoles(string $role): void { $this->userRepository->expects($this->once())->method('findById')->with(1)->willReturn($this->makeUser('alice', 'alice@example.com')); $this->userRepository->expects($this->once())->method('updateRole')->with(1, $role); $this->service->updateRole(1, $role); } /** * @return array */ public static function validRolesProvider(): array { return [ 'user' => [User::ROLE_USER], 'editor' => [User::ROLE_EDITOR], ]; } // ── Helpers ──────────────────────────────────────────────────── /** * Crée un utilisateur de test avec un hash bcrypt du mot de passe fourni. */ private function makeUser(string $username, string $email): User { return new User(1, $username, $email, password_hash('motdepasse1', PASSWORD_BCRYPT)); } }