Files
netslim-core/tests/Taxonomy/TaxonomyServiceTest.php
2026-03-20 22:13:41 +01:00

204 lines
7.0 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Taxonomy;
use Netig\Netslim\Taxonomy\Application\TaxonomyApplicationService;
use Netig\Netslim\Taxonomy\Application\UseCase\CreateTaxon;
use Netig\Netslim\Taxonomy\Application\UseCase\DeleteTaxon;
use Netig\Netslim\Taxonomy\Contracts\TaxonUsageCheckerInterface;
use Netig\Netslim\Taxonomy\Domain\Entity\Taxon;
use Netig\Netslim\Taxonomy\Domain\Repository\TaxonRepositoryInterface;
use Netig\Netslim\Taxonomy\Domain\Service\TaxonSlugGenerator;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
/**
* Tests unitaires pour TaxonomyApplicationService.
*
* Vérifie la création (génération de slug, unicité du nom, validation du modèle)
* et la suppression (blocage si le terme est encore utilisé).
* Le repository est remplacé par un mock pour isoler le service.
*/
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class TaxonomyServiceTest extends TestCase
{
/** @var TaxonRepositoryInterface&MockObject */
private TaxonRepositoryInterface $repository;
/** @var TaxonUsageCheckerInterface&MockObject */
private TaxonUsageCheckerInterface $taxonUsageChecker;
private TaxonomyApplicationService $service;
protected function setUp(): void
{
$this->repository = $this->createMock(TaxonRepositoryInterface::class);
$this->taxonUsageChecker = $this->createMock(TaxonUsageCheckerInterface::class);
$this->service = new TaxonomyApplicationService(
$this->repository,
new CreateTaxon($this->repository, new TaxonSlugGenerator()),
new DeleteTaxon($this->repository, $this->taxonUsageChecker),
);
}
// ── create ─────────────────────────────────────────────────────
/**
* create() doit générer le slug depuis le nom et persister le terme.
*/
public function testCreateGeneratesSlugAndPersists(): void
{
$this->repository->method('nameExists')->willReturn(false);
$this->repository->expects($this->once())
->method('create')
->with($this->callback(
fn (Taxon $c) =>
$c->getName() === 'Développement web'
&& $c->getSlug() === 'developpement-web',
))
->willReturn(1);
$id = $this->service->create('Développement web');
$this->assertSame(1, $id);
}
/**
* create() doit trimmer le nom avant de générer le slug.
*/
public function testCreateTrimsName(): void
{
$this->repository->method('nameExists')->willReturn(false);
$this->repository->expects($this->once())
->method('create')
->with($this->callback(fn (Taxon $c) => $c->getName() === 'PHP'))
->willReturn(2);
$this->service->create(' PHP ');
}
/**
* create() doit lever InvalidArgumentException si le slug généré est vide.
*/
public function testCreateNonAsciiNameThrowsException(): void
{
$this->repository->expects($this->never())->method('create');
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('slug URL valide');
$this->service->create('日本語');
}
/**
* create() doit lever InvalidArgumentException si le nom est existe déjà.
*/
public function testCreateDuplicateNameThrowsException(): void
{
$this->repository->method('nameExists')->willReturn(true);
$this->repository->expects($this->never())->method('create');
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('existe déjà');
$this->service->create('PHP');
}
/**
* create() doit lever InvalidArgumentException si le nom est vide.
*/
public function testCreateEmptyNameThrowsException(): void
{
$this->repository->method('nameExists')->willReturn(false);
$this->repository->expects($this->never())->method('create');
$this->expectException(\InvalidArgumentException::class);
$this->service->create('');
}
/**
* create() doit lever InvalidArgumentException si le nom dépasse 100 caractères.
*/
public function testCreateNameTooLongThrowsException(): void
{
$longName = str_repeat('a', 101);
$this->repository->method('nameExists')->willReturn(false);
$this->repository->expects($this->never())->method('create');
$this->expectException(\InvalidArgumentException::class);
$this->service->create($longName);
}
// ── delete ─────────────────────────────────────────────────────
/**
* delete() doit supprimer le terme s'il n'est pas utilisé.
*/
public function testDeleteSucceedsWhenTaxonIsUnused(): void
{
$taxon = new Taxon(5, 'PHP', 'php');
$this->taxonUsageChecker->expects($this->once())->method('isTaxonInUse')->with(5)->willReturn(false);
$this->repository->expects($this->once())->method('delete')->with(5);
$this->service->delete($taxon);
}
/**
* delete() doit lever InvalidArgumentException si le terme est encore utilisé.
*/
public function testDeleteBlockedWhenTaxonIsStillUsed(): void
{
$taxon = new Taxon(5, 'PHP', 'php');
$this->taxonUsageChecker->expects($this->once())->method('isTaxonInUse')->with(5)->willReturn(true);
$this->repository->expects($this->never())->method('delete');
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('est encore utilisé');
$this->service->delete($taxon);
}
// ── Lectures déléguées ─────────────────────────────────────────
/**
* findAll() doit déléguer au repository et retourner son résultat.
*/
public function testFindAllDelegatesToRepository(): void
{
$cats = [new Taxon(1, 'PHP', 'php'), new Taxon(2, 'CSS', 'css')];
$this->repository->method('findAll')->willReturn($cats);
$this->assertSame($cats, $this->service->findAll());
}
/**
* findById() doit retourner null si le terme n'existe pas.
*/
public function testFindByIdReturnsNullWhenMissing(): void
{
$this->repository->method('findById')->willReturn(null);
$this->assertNull($this->service->findById(99));
}
/**
* findBySlug() doit retourner le terme correspondant.
*/
public function testFindBySlugReturnsTaxonWhenFound(): void
{
$taxon = new Taxon(3, 'PHP', 'php');
$this->repository->expects($this->once())->method('findBySlug')->with('php')->willReturn($taxon);
$this->assertSame($taxon, $this->service->findBySlug('php'));
}
}