first commit

This commit is contained in:
julien
2026-03-20 22:13:41 +01:00
commit 41f8b3afb4
323 changed files with 27222 additions and 0 deletions

View File

@@ -0,0 +1,336 @@
<?php
declare(strict_types=1);
namespace Tests\Taxonomy;
use Netig\Netslim\Taxonomy\Domain\Entity\Taxon;
use Netig\Netslim\Taxonomy\Infrastructure\PdoTaxonRepository;
use PDO;
use PDOStatement;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
/**
* Tests unitaires pour PdoTaxonRepository.
*
* 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 TaxonRepositoryTest extends TestCase
{
/** @var PDO&MockObject */
private PDO $db;
private PdoTaxonRepository $repository;
/**
* Données représentant une ligne taxon en base de données.
*
* @var array<string, mixed>
*/
private array $rowPhp;
protected function setUp(): void
{
$this->db = $this->createMock(PDO::class);
$this->repository = new PdoTaxonRepository($this->db);
$this->rowPhp = [
'id' => 1,
'name' => 'PHP',
'slug' => 'php',
];
}
// ── Helpers ────────────────────────────────────────────────────
private function stmtForRead(array $rows = [], array|false $row = false): MockObject&PDOStatement
{
$stmt = $this->createMock(PDOStatement::class);
$stmt->method('execute')->willReturn(true);
$stmt->method('fetchAll')->willReturn($rows);
$stmt->method('fetch')->willReturn($row);
return $stmt;
}
private function stmtForScalar(mixed $value): MockObject&PDOStatement
{
$stmt = $this->createMock(PDOStatement::class);
$stmt->method('execute')->willReturn(true);
$stmt->method('fetchColumn')->willReturn($value);
return $stmt;
}
private function stmtForWrite(int $rowCount = 1): MockObject&PDOStatement
{
$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 taxon n'existe.
*/
public function testFindAllReturnsEmptyArrayWhenNone(): void
{
$stmt = $this->stmtForRead([]);
$this->db->method('query')->willReturn($stmt);
$this->assertSame([], $this->repository->findAll());
}
/**
* findAll() retourne des instances Taxon hydratées.
*/
public function testFindAllReturnsTaxonInstances(): void
{
$stmt = $this->stmtForRead([$this->rowPhp]);
$this->db->method('query')->willReturn($stmt);
$result = $this->repository->findAll();
$this->assertCount(1, $result);
$this->assertInstanceOf(Taxon::class, $result[0]);
$this->assertSame('PHP', $result[0]->getName());
$this->assertSame('php', $result[0]->getSlug());
}
/**
* findAll() interroge bien la table historique `categories`.
*/
public function testFindAllRequestsCategoriesQuery(): void
{
$stmt = $this->stmtForRead([]);
$this->db->expects($this->once())
->method('query')
->with($this->stringContains('FROM categories'))
->willReturn($stmt);
$this->repository->findAll();
}
// ── findById ───────────────────────────────────────────────────
/**
* findById() retourne null si le taxon est absent.
*/
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() retourne une instance Taxon si le taxon existe.
*/
public function testFindByIdReturnsTaxonWhenFound(): void
{
$stmt = $this->stmtForRead(row: $this->rowPhp);
$this->db->method('prepare')->willReturn($stmt);
$result = $this->repository->findById(1);
$this->assertInstanceOf(Taxon::class, $result);
$this->assertSame(1, $result->getId());
$this->assertSame('PHP', $result->getName());
}
/**
* 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($this->callback(fn (array $params): bool => in_array(42, $params, true)));
$this->repository->findById(42);
}
// ── findBySlug ─────────────────────────────────────────────────
/**
* findBySlug() retourne null si le slug est absent.
*/
public function testFindBySlugReturnsNullWhenMissing(): void
{
$stmt = $this->stmtForRead(row: false);
$this->db->method('prepare')->willReturn($stmt);
$this->assertNull($this->repository->findBySlug('inconnu'));
}
/**
* findBySlug() retourne une instance Taxon si le slug existe.
*/
public function testFindBySlugReturnsTaxonWhenFound(): void
{
$stmt = $this->stmtForRead(row: $this->rowPhp);
$this->db->method('prepare')->willReturn($stmt);
$result = $this->repository->findBySlug('php');
$this->assertInstanceOf(Taxon::class, $result);
$this->assertSame('php', $result->getSlug());
}
/**
* findBySlug() exécute avec le bon slug.
*/
public function testFindBySlugQueriesWithCorrectSlug(): 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('php', $params, true)));
$this->repository->findBySlug('php');
}
// ── create ─────────────────────────────────────────────────────
/**
* create() prépare un INSERT avec le nom et le slug du taxon.
*/
public function testCreateCallsInsertWithNameAndSlug(): void
{
$taxon = Taxon::fromArray($this->rowPhp);
$stmt = $this->stmtForWrite();
$this->db->expects($this->once())->method('prepare')
->with($this->stringContains('INSERT INTO categories'))
->willReturn($stmt);
$stmt->expects($this->once())
->method('execute')
->with($this->callback(
fn (array $data): bool =>
$data[':name'] === $taxon->getName()
&& $data[':slug'] === $taxon->getSlug(),
));
$this->db->method('lastInsertId')->willReturn('1');
$this->repository->create($taxon);
}
/**
* create() retourne l'identifiant généré par la base de données.
*/
public function testCreateReturnsGeneratedId(): void
{
$taxon = Taxon::fromArray($this->rowPhp);
$stmt = $this->stmtForWrite();
$this->db->method('prepare')->willReturn($stmt);
$this->db->method('lastInsertId')->willReturn('7');
$this->assertSame(7, $this->repository->create($taxon));
}
// ── 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 categories'))
->willReturn($stmt);
$stmt->expects($this->once())
->method('execute')
->with($this->callback(fn (array $params): bool => in_array(3, $params, true)));
$this->repository->delete(3);
}
/**
* 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(3));
}
/**
* delete() retourne 0 si le taxon n'existait plus.
*/
public function testDeleteReturnsZeroWhenNotFound(): void
{
$stmt = $this->stmtForWrite(0);
$this->db->method('prepare')->willReturn($stmt);
$this->assertSame(0, $this->repository->delete(99));
}
// ── nameExists ─────────────────────────────────────────────────
/**
* nameExists() retourne true si le nom existe déjà.
*/
public function testNameExistsReturnsTrueWhenTaken(): void
{
$stmt = $this->stmtForScalar(1);
$this->db->method('prepare')->willReturn($stmt);
$this->assertTrue($this->repository->nameExists('PHP'));
}
/**
* nameExists() retourne false si le nom est disponible.
*/
public function testNameExistsReturnsFalseWhenFree(): void
{
$stmt = $this->stmtForScalar(false);
$this->db->method('prepare')->willReturn($stmt);
$this->assertFalse($this->repository->nameExists('Nouveau'));
}
/**
* nameExists() exécute avec le bon nom.
*/
public function testNameExistsQueriesWithCorrectName(): void
{
$stmt = $this->stmtForScalar(false);
$this->db->method('prepare')->willReturn($stmt);
$stmt->expects($this->once())
->method('execute')
->with($this->callback(fn (array $params): bool => in_array('PHP', $params, true)));
$this->repository->nameExists('PHP');
}
}