*/ private array $rowAlice; /** * Initialise le mock PDO, le dépôt et les données de test avant chaque test. */ protected function setUp(): void { $this->db = $this->createMock(PDO::class); $this->repository = new PdoUserRepository($this->db); $this->rowAlice = [ 'id' => 1, 'username' => 'alice', 'email' => 'alice@example.com', 'password_hash' => password_hash('secret', PASSWORD_BCRYPT), 'role' => User::ROLE_USER, 'created_at' => '2024-01-01 00:00:00', ]; } // ── Helpers ──────────────────────────────────────────────────── private function stmtForRead(array $rows = [], array|false $row = false): PDOStatement&MockObject { $stmt = $this->createMock(PDOStatement::class); $stmt->method('execute')->willReturn(true); $stmt->method('fetchAll')->willReturn($rows); $stmt->method('fetch')->willReturn($row); return $stmt; } private function stmtForWrite(): PDOStatement&MockObject { $stmt = $this->createMock(PDOStatement::class); $stmt->method('execute')->willReturn(true); return $stmt; } // ── findAll ──────────────────────────────────────────────────── /** * findAll() doit retourner un tableau vide si aucun utilisateur n'existe. */ public function testFindAllReturnsEmptyArrayWhenNone(): void { $stmt = $this->stmtForRead([]); $this->db->method('query')->willReturn($stmt); $this->assertSame([], $this->repository->findAll()); } /** * findAll() doit retourner un tableau d'instances User hydratées. */ public function testFindAllReturnsUserInstances(): void { $stmt = $this->stmtForRead([$this->rowAlice]); $this->db->method('query')->willReturn($stmt); $result = $this->repository->findAll(); $this->assertCount(1, $result); $this->assertInstanceOf(User::class, $result[0]); $this->assertSame('alice', $result[0]->getUsername()); } /** * findAll() interroge bien la table `users`. */ public function testFindAllRequestsUsersQuery(): void { $stmt = $this->stmtForRead([]); $this->db->expects($this->once()) ->method('query') ->with($this->stringContains('FROM users')) ->willReturn($stmt); $this->repository->findAll(); } // ── findById ─────────────────────────────────────────────────── /** * findById() doit retourner null si aucun utilisateur ne correspond à cet identifiant. */ public function testFindByIdReturnsNullWhenMissing(): void { $stmt = $this->stmtForRead(row: false); $this->db->expects($this->once())->method('prepare')->willReturn($stmt); $this->assertNull($this->repository->findById(99)); } /** * findById() doit retourner une instance User hydratée si l'utilisateur existe. */ public function testFindByIdReturnsUserWhenFound(): void { $stmt = $this->stmtForRead(row: $this->rowAlice); $this->db->expects($this->once())->method('prepare')->willReturn($stmt); $result = $this->repository->findById(1); $this->assertInstanceOf(User::class, $result); $this->assertSame(1, $result->getId()); } /** * findById() doit exécuter avec le bon identifiant. */ public function testFindByIdQueriesWithCorrectId(): void { $stmt = $this->stmtForRead(row: false); $this->db->expects($this->once())->method('prepare')->willReturn($stmt); $stmt->expects($this->once()) ->method('execute') ->with($this->callback(fn (array $params): bool => in_array(42, $params, true))); $this->repository->findById(42); } // ── findByUsername ───────────────────────────────────────────── /** * findByUsername() doit retourner null si le nom d'utilisateur est introuvable. */ public function testFindByUsernameReturnsNullWhenMissing(): void { $stmt = $this->stmtForRead(row: false); $this->db->method('prepare')->willReturn($stmt); $this->assertNull($this->repository->findByUsername('inconnu')); } /** * findByUsername() doit retourner une instance User si le nom est trouvé. */ public function testFindByUsernameReturnsUserWhenFound(): void { $stmt = $this->stmtForRead(row: $this->rowAlice); $this->db->method('prepare')->willReturn($stmt); $result = $this->repository->findByUsername('alice'); $this->assertInstanceOf(User::class, $result); $this->assertSame('alice', $result->getUsername()); } /** * findByUsername() doit exécuter avec le bon nom d'utilisateur. */ public function testFindByUsernameQueriesWithCorrectName(): void { $stmt = $this->stmtForRead(row: false); $this->db->method('prepare')->willReturn($stmt); $stmt->expects($this->once()) ->method('execute') ->with($this->callback(fn (array $params): bool => in_array('alice', $params, true))); $this->repository->findByUsername('alice'); } // ── findByEmail ──────────────────────────────────────────────── /** * findByEmail() doit retourner null si l'adresse e-mail est introuvable. */ public function testFindByEmailReturnsNullWhenMissing(): void { $stmt = $this->stmtForRead(row: false); $this->db->method('prepare')->willReturn($stmt); $this->assertNull($this->repository->findByEmail('inconnu@example.com')); } /** * findByEmail() doit retourner une instance User si l'e-mail est trouvé. */ public function testFindByEmailReturnsUserWhenFound(): void { $stmt = $this->stmtForRead(row: $this->rowAlice); $this->db->method('prepare')->willReturn($stmt); $result = $this->repository->findByEmail('alice@example.com'); $this->assertInstanceOf(User::class, $result); $this->assertSame('alice@example.com', $result->getEmail()); } /** * findByEmail() doit exécuter avec la bonne adresse e-mail. */ public function testFindByEmailQueriesWithCorrectEmail(): void { $stmt = $this->stmtForRead(row: false); $this->db->method('prepare')->willReturn($stmt); $stmt->expects($this->once()) ->method('execute') ->with($this->callback(fn (array $params): bool => in_array('alice@example.com', $params, true))); $this->repository->findByEmail('alice@example.com'); } // ── create ───────────────────────────────────────────────────── /** * create() doit préparer un INSERT sur la table 'users' avec les bonnes données. */ public function testCreateCallsInsertWithCorrectData(): void { $user = User::fromArray($this->rowAlice); $stmt = $this->stmtForWrite(); $this->db->expects($this->once())->method('prepare') ->with($this->stringContains('INSERT INTO users')) ->willReturn($stmt); $stmt->expects($this->once()) ->method('execute') ->with($this->callback(function (array $data) use ($user): bool { return $data[':username'] === $user->getUsername() && $data[':email'] === $user->getEmail() && $data[':password_hash'] === $user->getPasswordHash() && $data[':role'] === $user->getRole() && isset($data[':created_at']); })); $this->db->method('lastInsertId')->willReturn('1'); $this->repository->create($user); } /** * create() doit retourner l'identifiant généré par la base de données. */ public function testCreateReturnsGeneratedId(): void { $user = User::fromArray($this->rowAlice); $stmt = $this->stmtForWrite(); $this->db->method('prepare')->willReturn($stmt); $this->db->method('lastInsertId')->willReturn('42'); $this->assertSame(42, $this->repository->create($user)); } // ── updatePassword ───────────────────────────────────────────── /** * updatePassword() doit préparer un UPDATE avec le nouveau hash et le bon identifiant. */ public function testUpdatePasswordCallsUpdateWithCorrectHash(): void { $newHash = password_hash('nouveaumdp', PASSWORD_BCRYPT); $stmt = $this->stmtForWrite(); $this->db->expects($this->once())->method('prepare') ->with($this->stringContains('UPDATE users')) ->willReturn($stmt); $stmt->expects($this->once()) ->method('execute') ->with($this->callback(fn (array $params): bool => in_array($newHash, $params, true) && in_array(1, $params, true))); $this->repository->updatePassword(1, $newHash); } // ── updateRole ───────────────────────────────────────────────── /** * updateRole() doit préparer un UPDATE avec le bon rôle et le bon identifiant. */ public function testUpdateRoleCallsUpdateWithCorrectRole(): void { $stmt = $this->stmtForWrite(); $this->db->expects($this->once())->method('prepare') ->with($this->stringContains('UPDATE users')) ->willReturn($stmt); $stmt->expects($this->once()) ->method('execute') ->with($this->callback(fn (array $params): bool => in_array(User::ROLE_EDITOR, $params, true) && in_array(1, $params, true))); $this->repository->updateRole(1, User::ROLE_EDITOR); } // ── delete ───────────────────────────────────────────────────── /** * delete() doit préparer un DELETE avec le bon identifiant. */ public function testDeleteCallsDeleteWithCorrectId(): void { $stmt = $this->stmtForWrite(); $this->db->expects($this->once()) ->method('prepare') ->with($this->stringContains('DELETE FROM users')) ->willReturn($stmt); $stmt->expects($this->once()) ->method('execute') ->with($this->callback(fn (array $params): bool => in_array(7, $params, true))); $this->repository->delete(7); } }