*/ private array $rowImage; protected function setUp(): void { $this->db = $this->createMock(PDO::class); $this->repository = new MediaRepository($this->db); $this->rowImage = [ 'id' => 1, 'filename' => 'photo.webp', 'url' => '/media/photo.webp', 'hash' => str_repeat('a', 64), 'user_id' => 2, 'created_at' => '2024-06-01 10: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(int $rowCount = 1): PDOStatement&MockObject { $stmt = $this->createMock(PDOStatement::class); $stmt->method('execute')->willReturn(true); $stmt->method('rowCount')->willReturn($rowCount); return $stmt; } // ── findAll ──────────────────────────────────────────────────── /** * findAll() retourne un tableau vide si aucun média n'existe. */ public function testFindAllReturnsEmptyArrayWhenNone(): void { $stmt = $this->stmtForRead([]); $this->db->method('query')->willReturn($stmt); $this->assertSame([], $this->repository->findAll()); } /** * findAll() retourne des instances Media hydratées. */ public function testFindAllReturnsMediaInstances(): void { $stmt = $this->stmtForRead([$this->rowImage]); $this->db->method('query')->willReturn($stmt); $result = $this->repository->findAll(); $this->assertCount(1, $result); $this->assertInstanceOf(Media::class, $result[0]); $this->assertSame('photo.webp', $result[0]->getFilename()); $this->assertSame('/media/photo.webp', $result[0]->getUrl()); } /** * findAll() interroge la table 'media' triée par id DESC. */ public function testFindAllQueriesWithDescendingOrder(): void { $stmt = $this->stmtForRead([]); $this->db->expects($this->once()) ->method('query') ->with($this->logicalAnd( $this->stringContains('media'), $this->stringContains('id DESC'), )) ->willReturn($stmt); $this->repository->findAll(); } // ── findByUserId ─────────────────────────────────────────────── /** * findByUserId() retourne un tableau vide si l'utilisateur n'a aucun média. */ public function testFindByUserIdReturnsEmptyArrayWhenNone(): void { $stmt = $this->stmtForRead([]); $this->db->method('prepare')->willReturn($stmt); $this->assertSame([], $this->repository->findByUserId(99)); } /** * findByUserId() retourne uniquement les médias de l'utilisateur donné. */ public function testFindByUserIdReturnsUserMedia(): void { $stmt = $this->stmtForRead([$this->rowImage]); $this->db->method('prepare')->willReturn($stmt); $result = $this->repository->findByUserId(2); $this->assertCount(1, $result); $this->assertSame(2, $result[0]->getUserId()); } /** * findByUserId() exécute avec le bon user_id. */ public function testFindByUserIdQueriesWithCorrectUserId(): void { $stmt = $this->stmtForRead([]); $this->db->method('prepare')->willReturn($stmt); $stmt->expects($this->once()) ->method('execute') ->with([':user_id' => 5]); $this->repository->findByUserId(5); } // ── findById ─────────────────────────────────────────────────── /** * findById() retourne null si le média est absent. */ public function testFindByIdReturnsNullWhenMissing(): void { $stmt = $this->stmtForRead(row: false); $this->db->method('prepare')->willReturn($stmt); $this->assertNull($this->repository->findById(99)); } /** * findById() retourne une instance Media si le média existe. */ public function testFindByIdReturnsMediaWhenFound(): void { $stmt = $this->stmtForRead(row: $this->rowImage); $this->db->method('prepare')->willReturn($stmt); $result = $this->repository->findById(1); $this->assertInstanceOf(Media::class, $result); $this->assertSame(1, $result->getId()); } /** * findById() exécute avec le bon identifiant. */ public function testFindByIdQueriesWithCorrectId(): void { $stmt = $this->stmtForRead(row: false); $this->db->method('prepare')->willReturn($stmt); $stmt->expects($this->once()) ->method('execute') ->with([':id' => 8]); $this->repository->findById(8); } // ── findByHash ───────────────────────────────────────────────── /** * findByHash() retourne null si aucun média ne correspond au hash. */ public function testFindByHashReturnsNullWhenMissing(): void { $stmt = $this->stmtForRead(row: false); $this->db->method('prepare')->willReturn($stmt); $this->assertNull($this->repository->findByHash(str_repeat('b', 64))); } /** * findByHash() retourne une instance Media si le hash existe (doublon détecté). */ public function testFindByHashReturnsDuplicateMedia(): void { $stmt = $this->stmtForRead(row: $this->rowImage); $this->db->method('prepare')->willReturn($stmt); $result = $this->repository->findByHash(str_repeat('a', 64)); $this->assertInstanceOf(Media::class, $result); $this->assertSame(str_repeat('a', 64), $result->getHash()); } /** * findByHash() exécute avec le bon hash. */ public function testFindByHashQueriesWithCorrectHash(): void { $hash = str_repeat('c', 64); $stmt = $this->stmtForRead(row: false); $this->db->method('prepare')->willReturn($stmt); $stmt->expects($this->once()) ->method('execute') ->with([':hash' => $hash]); $this->repository->findByHash($hash); } // ── create ───────────────────────────────────────────────────── /** * create() prépare un INSERT avec les bonnes colonnes. */ public function testCreateCallsInsertWithCorrectData(): void { $media = Media::fromArray($this->rowImage); $stmt = $this->stmtForWrite(); $this->db->expects($this->once())->method('prepare') ->with($this->stringContains('INSERT INTO media')) ->willReturn($stmt); $stmt->expects($this->once()) ->method('execute') ->with($this->callback(function (array $data) use ($media): bool { return $data[':filename'] === $media->getFilename() && $data[':url'] === $media->getUrl() && $data[':hash'] === $media->getHash() && $data[':user_id'] === $media->getUserId() && isset($data[':created_at']); })); $this->db->method('lastInsertId')->willReturn('1'); $this->repository->create($media); } /** * create() retourne l'identifiant généré par la base de données. */ public function testCreateReturnsGeneratedId(): void { $media = Media::fromArray($this->rowImage); $stmt = $this->stmtForWrite(); $this->db->method('prepare')->willReturn($stmt); $this->db->method('lastInsertId')->willReturn('15'); $this->assertSame(15, $this->repository->create($media)); } // ── delete ───────────────────────────────────────────────────── /** * delete() prépare un DELETE avec le bon identifiant. */ public function testDeleteCallsDeleteWithCorrectId(): void { $stmt = $this->stmtForWrite(1); $this->db->expects($this->once()) ->method('prepare') ->with($this->stringContains('DELETE FROM media')) ->willReturn($stmt); $stmt->expects($this->once()) ->method('execute') ->with([':id' => 4]); $this->repository->delete(4); } /** * delete() retourne le nombre de lignes supprimées. */ public function testDeleteReturnsDeletedRowCount(): void { $stmt = $this->stmtForWrite(1); $this->db->method('prepare')->willReturn($stmt); $this->assertSame(1, $this->repository->delete(4)); } /** * delete() retourne 0 si le média n'existait plus. */ public function testDeleteReturnsZeroWhenNotFound(): void { $stmt = $this->stmtForWrite(0); $this->db->method('prepare')->willReturn($stmt); $this->assertSame(0, $this->repository->delete(99)); } }