338 lines
10 KiB
PHP
338 lines
10 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Media;
|
|
|
|
use App\Media\Media;
|
|
use App\Media\MediaRepository;
|
|
use PDO;
|
|
use PDOStatement;
|
|
use PHPUnit\Framework\MockObject\MockObject;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* Tests unitaires pour MediaRepository.
|
|
*
|
|
* Vérifie que chaque méthode du dépôt construit le bon SQL,
|
|
* lie les bons paramètres et retourne les bonnes valeurs.
|
|
*
|
|
* PDO et PDOStatement sont mockés pour isoler complètement
|
|
* le dépôt de la base de données.
|
|
*/
|
|
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
|
final class MediaRepositoryTest extends TestCase
|
|
{
|
|
/** @var PDO&MockObject */
|
|
private PDO $db;
|
|
|
|
private MediaRepository $repository;
|
|
|
|
/**
|
|
* Données représentant une ligne média en base de données.
|
|
*
|
|
* @var array<string, mixed>
|
|
*/
|
|
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));
|
|
}
|
|
}
|