190 lines
6.3 KiB
PHP
190 lines
6.3 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Category;
|
|
|
|
use App\Category\Category;
|
|
use App\Category\CategoryRepositoryInterface;
|
|
use App\Category\Application\CategoryApplicationService as CategoryService;
|
|
use PHPUnit\Framework\MockObject\MockObject;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* Tests unitaires pour CategoryService.
|
|
*
|
|
* Vérifie la création (génération de slug, unicité du nom, validation du modèle)
|
|
* et la suppression (blocage si articles rattachés).
|
|
* Le repository est remplacé par un mock pour isoler le service.
|
|
*/
|
|
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
|
final class CategoryServiceTest extends TestCase
|
|
{
|
|
/** @var CategoryRepositoryInterface&MockObject */
|
|
private CategoryRepositoryInterface $repository;
|
|
|
|
private CategoryService $service;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->repository = $this->createMock(CategoryRepositoryInterface::class);
|
|
$this->service = new CategoryService($this->repository);
|
|
}
|
|
|
|
|
|
// ── create ─────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* create() doit générer le slug depuis le nom et persister la catégorie.
|
|
*/
|
|
public function testCreateGeneratesSlugAndPersists(): void
|
|
{
|
|
$this->repository->method('nameExists')->willReturn(false);
|
|
$this->repository->expects($this->once())
|
|
->method('create')
|
|
->with($this->callback(fn (Category $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 (Category $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 déjà utilisé.
|
|
*/
|
|
public function testCreateDuplicateNameThrowsException(): void
|
|
{
|
|
$this->repository->method('nameExists')->willReturn(true);
|
|
$this->repository->expects($this->never())->method('create');
|
|
|
|
$this->expectException(\InvalidArgumentException::class);
|
|
$this->expectExceptionMessage('déjà utilisé');
|
|
|
|
$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 la catégorie si elle ne contient aucun article.
|
|
*/
|
|
public function testDeleteSucceedsWhenNoPosts(): void
|
|
{
|
|
$category = new Category(5, 'PHP', 'php');
|
|
|
|
$this->repository->expects($this->once())->method('hasPost')->with(5)->willReturn(false);
|
|
$this->repository->expects($this->once())->method('delete')->with(5);
|
|
|
|
$this->service->delete($category);
|
|
}
|
|
|
|
/**
|
|
* delete() doit lever InvalidArgumentException si des articles sont rattachés.
|
|
*/
|
|
public function testDeleteBlockedWhenPostsAttached(): void
|
|
{
|
|
$category = new Category(5, 'PHP', 'php');
|
|
|
|
$this->repository->expects($this->once())->method('hasPost')->with(5)->willReturn(true);
|
|
$this->repository->expects($this->never())->method('delete');
|
|
|
|
$this->expectException(\InvalidArgumentException::class);
|
|
$this->expectExceptionMessage('contient des articles');
|
|
|
|
$this->service->delete($category);
|
|
}
|
|
|
|
|
|
// ── Lectures déléguées ─────────────────────────────────────────
|
|
|
|
/**
|
|
* findAll() doit déléguer au repository et retourner son résultat.
|
|
*/
|
|
public function testFindAllDelegatesToRepository(): void
|
|
{
|
|
$cats = [new Category(1, 'PHP', 'php'), new Category(2, 'CSS', 'css')];
|
|
$this->repository->method('findAll')->willReturn($cats);
|
|
|
|
$this->assertSame($cats, $this->service->findAll());
|
|
}
|
|
|
|
/**
|
|
* findById() doit retourner null si la catégorie n'existe pas.
|
|
*/
|
|
public function testFindByIdReturnsNullWhenMissing(): void
|
|
{
|
|
$this->repository->method('findById')->willReturn(null);
|
|
|
|
$this->assertNull($this->service->findById(99));
|
|
}
|
|
|
|
/**
|
|
* findBySlug() doit retourner la catégorie correspondante.
|
|
*/
|
|
public function testFindBySlugReturnsCategoryWhenFound(): void
|
|
{
|
|
$cat = new Category(3, 'PHP', 'php');
|
|
$this->repository->expects($this->once())->method('findBySlug')->with('php')->willReturn($cat);
|
|
|
|
$this->assertSame($cat, $this->service->findBySlug('php'));
|
|
}
|
|
}
|