Working state but no uploads

This commit is contained in:
julien
2026-03-16 02:33:18 +01:00
parent cc3fbdc830
commit 55d2da9f2f
52 changed files with 121 additions and 49 deletions

1
.gitignore vendored
View File

@@ -24,6 +24,7 @@ public/assets/
database/*.sqlite database/*.sqlite
database/*.sqlite-shm database/*.sqlite-shm
database/*.sqlite-wal database/*.sqlite-wal
database/.provision.lock
# ============================================ # ============================================
# Cache & Logs # Cache & Logs

View File

@@ -8,14 +8,14 @@ interface PasswordResetRepositoryInterface
public function create(int $userId, string $tokenHash, string $expiresAt): void; public function create(int $userId, string $tokenHash, string $expiresAt): void;
/** /**
* Consomme atomiquement un token non utilisé et non expiré. * @return array<string, mixed>|null
*
* L'implémentation doit effectuer l'opération en une seule étape SQL
* afin d'éviter les courses entre lecture et écriture.
*
* @param string $tokenHash Hash SHA-256 du token de reset
* @param string $usedAt Horodatage de consommation au format SQL
* @return array<string, mixed>|null Les données du token consommé, ou null si le token est invalide, expiré ou déjà utilisé
*/ */
public function consumeActiveToken(string $tokenHash, string $usedAt): ?array; public function findActiveByHash(string $tokenHash): ?array;
public function invalidateByUserId(int $userId): void;
/**
* @return array<string, mixed>|null
*/
public function consumeActiveToken(string $tokenHash, string $usedAt): ?array;
} }

View File

@@ -18,6 +18,7 @@ use Tests\ControllerTestCase;
* mots de passe non identiques, mot de passe faible, mot de passe actuel * mots de passe non identiques, mot de passe faible, mot de passe actuel
* incorrect, erreur inattendue et succès. * incorrect, erreur inattendue et succès.
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class AccountControllerTest extends ControllerTestCase final class AccountControllerTest extends ControllerTestCase
{ {
/** @var \Slim\Views\Twig&MockObject */ /** @var \Slim\Views\Twig&MockObject */

View File

@@ -18,6 +18,7 @@ use Tests\ControllerTestCase;
* AuthService, FlashService et Twig sont mockés — aucune session PHP, * AuthService, FlashService et Twig sont mockés — aucune session PHP,
* aucune base de données, aucun serveur HTTP n'est requis. * aucune base de données, aucun serveur HTTP n'est requis.
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class AuthControllerTest extends ControllerTestCase final class AuthControllerTest extends ControllerTestCase
{ {
/** @var \Slim\Views\Twig&MockObject */ /** @var \Slim\Views\Twig&MockObject */

View File

@@ -19,6 +19,7 @@ use PHPUnit\Framework\TestCase;
* - MAX_ATTEMPTS = 5 : nombre d'échecs avant verrouillage * - MAX_ATTEMPTS = 5 : nombre d'échecs avant verrouillage
* - LOCK_MINUTES = 15 : durée du verrouillage en minutes * - LOCK_MINUTES = 15 : durée du verrouillage en minutes
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class AuthServiceRateLimitTest extends TestCase final class AuthServiceRateLimitTest extends TestCase
{ {
/** @var UserRepositoryInterface&MockObject */ /** @var UserRepositoryInterface&MockObject */

View File

@@ -21,6 +21,7 @@ use PHPUnit\Framework\TestCase;
* Les dépendances sont remplacées par des mocks via leurs interfaces pour * Les dépendances sont remplacées par des mocks via leurs interfaces pour
* isoler le service. * isoler le service.
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class AuthServiceTest extends TestCase final class AuthServiceTest extends TestCase
{ {
/** @var UserRepositoryInterface&MockObject */ /** @var UserRepositoryInterface&MockObject */
@@ -58,7 +59,7 @@ final class AuthServiceTest extends TestCase
$password = 'motdepasse1'; $password = 'motdepasse1';
$user = $this->makeUser('alice', 'alice@example.com', $password); $user = $this->makeUser('alice', 'alice@example.com', $password);
$this->userRepository->method('findByUsername')->with('alice')->willReturn($user); $this->userRepository->expects($this->once())->method('findByUsername')->with('alice')->willReturn($user);
$result = $this->service->authenticate('alice', $password); $result = $this->service->authenticate('alice', $password);
@@ -73,7 +74,7 @@ final class AuthServiceTest extends TestCase
$password = 'motdepasse1'; $password = 'motdepasse1';
$user = $this->makeUser('alice', 'alice@example.com', $password); $user = $this->makeUser('alice', 'alice@example.com', $password);
$this->userRepository->method('findByUsername')->with('alice')->willReturn($user); $this->userRepository->expects($this->once())->method('findByUsername')->with('alice')->willReturn($user);
$result = $this->service->authenticate('ALICE', $password); $result = $this->service->authenticate('ALICE', $password);
@@ -116,7 +117,7 @@ final class AuthServiceTest extends TestCase
$password = 'ancienmdp1'; $password = 'ancienmdp1';
$user = $this->makeUser('alice', 'alice@example.com', $password, 1); $user = $this->makeUser('alice', 'alice@example.com', $password, 1);
$this->userRepository->method('findById')->with(1)->willReturn($user); $this->userRepository->expects($this->once())->method('findById')->with(1)->willReturn($user);
$this->userRepository->expects($this->once())->method('updatePassword')->with(1); $this->userRepository->expects($this->once())->method('updatePassword')->with(1);
$this->service->changePassword(1, $password, 'nouveaumdp1'); $this->service->changePassword(1, $password, 'nouveaumdp1');

View File

@@ -24,6 +24,7 @@ use PHPUnit\Framework\TestCase;
* PDO et PDOStatement sont mockés pour isoler complètement * PDO et PDOStatement sont mockés pour isoler complètement
* le dépôt de la base de données. * le dépôt de la base de données.
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class LoginAttemptRepositoryTest extends TestCase final class LoginAttemptRepositoryTest extends TestCase
{ {
/** @var PDO&MockObject */ /** @var PDO&MockObject */

View File

@@ -15,6 +15,8 @@ use Psr\Http\Server\RequestHandlerInterface;
use Slim\Psr7\Factory\ServerRequestFactory; use Slim\Psr7\Factory\ServerRequestFactory;
use Slim\Psr7\Response; use Slim\Psr7\Response;
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class MiddlewareTest extends TestCase final class MiddlewareTest extends TestCase
{ {
/** @var SessionManagerInterface&MockObject */ /** @var SessionManagerInterface&MockObject */

View File

@@ -27,6 +27,7 @@ use Tests\ControllerTestCase;
* - showReset() valide le token avant d'afficher le formulaire * - showReset() valide le token avant d'afficher le formulaire
* - reset() couvre 5 chemins de sortie (token vide, mismatch, trop court, invalide, succès) * - reset() couvre 5 chemins de sortie (token vide, mismatch, trop court, invalide, succès)
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class PasswordResetControllerTest extends ControllerTestCase final class PasswordResetControllerTest extends ControllerTestCase
{ {
/** @var \Slim\Views\Twig&MockObject */ /** @var \Slim\Views\Twig&MockObject */

View File

@@ -19,6 +19,7 @@ use PHPUnit\Framework\TestCase;
* PDO et PDOStatement sont mockés pour isoler complètement * PDO et PDOStatement sont mockés pour isoler complètement
* le dépôt de la base de données. * le dépôt de la base de données.
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class PasswordResetRepositoryTest extends TestCase final class PasswordResetRepositoryTest extends TestCase
{ {
/** @var PDO&MockObject */ /** @var PDO&MockObject */
@@ -60,7 +61,7 @@ final class PasswordResetRepositoryTest extends TestCase
$expiresAt = date('Y-m-d H:i:s', time() + 3600); $expiresAt = date('Y-m-d H:i:s', time() + 3600);
$stmt = $this->stmtOk(); $stmt = $this->stmtOk();
$this->db->method('prepare') $this->db->expects($this->once())->method('prepare')
->with($this->stringContains('INSERT INTO password_resets')) ->with($this->stringContains('INSERT INTO password_resets'))
->willReturn($stmt); ->willReturn($stmt);
@@ -162,7 +163,7 @@ final class PasswordResetRepositoryTest extends TestCase
$userId = 42; $userId = 42;
$stmt = $this->stmtOk(); $stmt = $this->stmtOk();
$this->db->method('prepare') $this->db->expects($this->once())->method('prepare')
->with($this->stringContains('UPDATE password_resets')) ->with($this->stringContains('UPDATE password_resets'))
->willReturn($stmt); ->willReturn($stmt);
@@ -200,7 +201,7 @@ final class PasswordResetRepositoryTest extends TestCase
public function testInvalidateByUserIdNeverCallsDelete(): void public function testInvalidateByUserIdNeverCallsDelete(): void
{ {
$stmt = $this->stmtOk(); $stmt = $this->stmtOk();
$this->db->method('prepare') $this->db->expects($this->once())->method('prepare')
->with($this->stringContains('UPDATE')) ->with($this->stringContains('UPDATE'))
->willReturn($stmt); ->willReturn($stmt);

View File

@@ -13,6 +13,8 @@ use App\User\UserRepository;
use PDO; use PDO;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class PasswordResetServiceIntegrationTest extends TestCase final class PasswordResetServiceIntegrationTest extends TestCase
{ {
private PDO $db; private PDO $db;

View File

@@ -20,6 +20,7 @@ use PHPUnit\Framework\TestCase;
* Vérifie la génération de token, la validation et la réinitialisation * Vérifie la génération de token, la validation et la réinitialisation
* du mot de passe. Les dépendances sont mockées via leurs interfaces. * du mot de passe. Les dépendances sont mockées via leurs interfaces.
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class PasswordResetServiceTest extends TestCase final class PasswordResetServiceTest extends TestCase
{ {
/** @var PasswordResetRepositoryInterface&MockObject */ /** @var PasswordResetRepositoryInterface&MockObject */
@@ -95,7 +96,7 @@ final class PasswordResetServiceTest extends TestCase
$this->resetRepository->method('invalidateByUserId'); $this->resetRepository->method('invalidateByUserId');
$this->resetRepository->expects($this->once()) $this->resetRepository->expects($this->once())
->method('create') ->method('create')
->with($user->getId(), $this->isType('string'), $this->isType('string')); ->with($user->getId(), $this->callback('is_string'), $this->callback('is_string'));
$this->mailService->method('send'); $this->mailService->method('send');
$this->service->requestReset('alice@example.com', 'https://blog.exemple.com'); $this->service->requestReset('alice@example.com', 'https://blog.exemple.com');
@@ -116,9 +117,9 @@ final class PasswordResetServiceTest extends TestCase
->method('send') ->method('send')
->with( ->with(
'alice@example.com', 'alice@example.com',
$this->isType('string'), $this->callback('is_string'),
'emails/password-reset.twig', 'emails/password-reset.twig',
$this->isType('array'), $this->callback('is_array'),
); );
$this->service->requestReset('alice@example.com', 'https://blog.exemple.com'); $this->service->requestReset('alice@example.com', 'https://blog.exemple.com');
@@ -173,7 +174,7 @@ final class PasswordResetServiceTest extends TestCase
$tokenRaw = 'montokenbrut'; $tokenRaw = 'montokenbrut';
$tokenHash = hash('sha256', $tokenRaw); $tokenHash = hash('sha256', $tokenRaw);
$this->resetRepository->method('findActiveByHash')->with($tokenHash)->willReturn([ $this->resetRepository->expects($this->once())->method('findActiveByHash')->with($tokenHash)->willReturn([
'user_id' => 1, 'user_id' => 1,
'token_hash' => $tokenHash, 'token_hash' => $tokenHash,
'expires_at' => date('Y-m-d H:i:s', time() - 3600), 'expires_at' => date('Y-m-d H:i:s', time() - 3600),
@@ -194,13 +195,13 @@ final class PasswordResetServiceTest extends TestCase
$tokenRaw = 'montokenbrut'; $tokenRaw = 'montokenbrut';
$tokenHash = hash('sha256', $tokenRaw); $tokenHash = hash('sha256', $tokenRaw);
$this->resetRepository->method('findActiveByHash')->with($tokenHash)->willReturn([ $this->resetRepository->expects($this->once())->method('findActiveByHash')->with($tokenHash)->willReturn([
'user_id' => $user->getId(), 'user_id' => $user->getId(),
'token_hash' => $tokenHash, 'token_hash' => $tokenHash,
'expires_at' => date('Y-m-d H:i:s', time() + 3600), 'expires_at' => date('Y-m-d H:i:s', time() + 3600),
'used_at' => null, 'used_at' => null,
]); ]);
$this->userRepository->method('findById')->with($user->getId())->willReturn($user); $this->userRepository->expects($this->once())->method('findById')->with($user->getId())->willReturn($user);
$result = $this->service->validateToken($tokenRaw); $result = $this->service->validateToken($tokenRaw);
@@ -224,8 +225,8 @@ final class PasswordResetServiceTest extends TestCase
'used_at' => null, 'used_at' => null,
]; ];
$this->resetRepository->method('findActiveByHash')->willReturn($row); $this->resetRepository->expects($this->once())->method('findActiveByHash')->willReturn($row);
$this->userRepository->method('findById')->with(999)->willReturn(null); $this->userRepository->expects($this->once())->method('findById')->with(999)->willReturn(null);
$result = $this->service->validateToken('token-valide-mais-user-supprime'); $result = $this->service->validateToken('token-valide-mais-user-supprime');
@@ -282,7 +283,7 @@ final class PasswordResetServiceTest extends TestCase
$this->resetRepository->expects($this->once()) $this->resetRepository->expects($this->once())
->method('consumeActiveToken') ->method('consumeActiveToken')
->with($tokenHash, $this->isType('string')) ->with($tokenHash, $this->callback('is_string'))
->willReturn([ ->willReturn([
'user_id' => $user->getId(), 'user_id' => $user->getId(),
'token_hash' => $tokenHash, 'token_hash' => $tokenHash,

View File

@@ -17,6 +17,7 @@ use Tests\ControllerTestCase;
* rendu de la liste, création réussie, erreur de création, * rendu de la liste, création réussie, erreur de création,
* suppression avec catégorie introuvable, succès et erreur métier. * suppression avec catégorie introuvable, succès et erreur métier.
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class CategoryControllerTest extends ControllerTestCase final class CategoryControllerTest extends ControllerTestCase
{ {
/** @var \Slim\Views\Twig&MockObject */ /** @var \Slim\Views\Twig&MockObject */

View File

@@ -6,6 +6,8 @@ namespace Tests\Category;
use App\Category\Category; use App\Category\Category;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class CategoryModelTest extends TestCase final class CategoryModelTest extends TestCase
{ {
public function testConstructAndGettersExposeCategoryData(): void public function testConstructAndGettersExposeCategoryData(): void

View File

@@ -19,6 +19,7 @@ use PHPUnit\Framework\TestCase;
* PDO et PDOStatement sont mockés pour isoler complètement * PDO et PDOStatement sont mockés pour isoler complètement
* le dépôt de la base de données. * le dépôt de la base de données.
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class CategoryRepositoryTest extends TestCase final class CategoryRepositoryTest extends TestCase
{ {
/** @var PDO&MockObject */ /** @var PDO&MockObject */
@@ -221,7 +222,7 @@ final class CategoryRepositoryTest extends TestCase
$category = Category::fromArray($this->rowPhp); $category = Category::fromArray($this->rowPhp);
$stmt = $this->stmtForWrite(); $stmt = $this->stmtForWrite();
$this->db->method('prepare') $this->db->expects($this->once())->method('prepare')
->with($this->stringContains('INSERT INTO categories')) ->with($this->stringContains('INSERT INTO categories'))
->willReturn($stmt); ->willReturn($stmt);

View File

@@ -16,6 +16,7 @@ use PHPUnit\Framework\TestCase;
* et la suppression (blocage si articles rattachés). * et la suppression (blocage si articles rattachés).
* Le repository est remplacé par un mock pour isoler le service. * Le repository est remplacé par un mock pour isoler le service.
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class CategoryServiceTest extends TestCase final class CategoryServiceTest extends TestCase
{ {
/** @var CategoryRepositoryInterface&MockObject */ /** @var CategoryRepositoryInterface&MockObject */
@@ -129,7 +130,7 @@ final class CategoryServiceTest extends TestCase
{ {
$category = new Category(5, 'PHP', 'php'); $category = new Category(5, 'PHP', 'php');
$this->repository->method('hasPost')->with(5)->willReturn(false); $this->repository->expects($this->once())->method('hasPost')->with(5)->willReturn(false);
$this->repository->expects($this->once())->method('delete')->with(5); $this->repository->expects($this->once())->method('delete')->with(5);
$this->service->delete($category); $this->service->delete($category);
@@ -142,7 +143,7 @@ final class CategoryServiceTest extends TestCase
{ {
$category = new Category(5, 'PHP', 'php'); $category = new Category(5, 'PHP', 'php');
$this->repository->method('hasPost')->with(5)->willReturn(true); $this->repository->expects($this->once())->method('hasPost')->with(5)->willReturn(true);
$this->repository->expects($this->never())->method('delete'); $this->repository->expects($this->never())->method('delete');
$this->expectException(\InvalidArgumentException::class); $this->expectException(\InvalidArgumentException::class);
@@ -181,7 +182,7 @@ final class CategoryServiceTest extends TestCase
public function testFindBySlugReturnsCategoryWhenFound(): void public function testFindBySlugReturnsCategoryWhenFound(): void
{ {
$cat = new Category(3, 'PHP', 'php'); $cat = new Category(3, 'PHP', 'php');
$this->repository->method('findBySlug')->with('php')->willReturn($cat); $this->repository->expects($this->once())->method('findBySlug')->with('php')->willReturn($cat);
$this->assertSame($cat, $this->service->findBySlug('php')); $this->assertSame($cat, $this->service->findBySlug('php'));
} }

View File

@@ -18,6 +18,7 @@ use Slim\Psr7\Response as SlimResponse;
* Chaque test de contrôleur invoque directement l'action (méthode publique) * Chaque test de contrôleur invoque directement l'action (méthode publique)
* sans passer par le routeur Slim — les middlewares sont testés séparément. * sans passer par le routeur Slim — les middlewares sont testés séparément.
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
abstract class ControllerTestCase extends TestCase abstract class ControllerTestCase extends TestCase
{ {
// ── Factories ──────────────────────────────────────────────────── // ── Factories ────────────────────────────────────────────────────

View File

@@ -23,6 +23,7 @@ use Tests\ControllerTestCase;
* - upload : absence de fichier, erreur PSR-7, exceptions métier (taille, MIME, stockage), succès * - upload : absence de fichier, erreur PSR-7, exceptions métier (taille, MIME, stockage), succès
* - delete : introuvable, non-propriétaire, succès propriétaire, succès admin * - delete : introuvable, non-propriétaire, succès propriétaire, succès admin
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class MediaControllerTest extends ControllerTestCase final class MediaControllerTest extends ControllerTestCase
{ {
/** @var \Slim\Views\Twig&MockObject */ /** @var \Slim\Views\Twig&MockObject */

View File

@@ -7,6 +7,8 @@ use App\Media\Media;
use DateTime; use DateTime;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class MediaModelTest extends TestCase final class MediaModelTest extends TestCase
{ {
public function testConstructAndGettersExposeMediaData(): void public function testConstructAndGettersExposeMediaData(): void

View File

@@ -19,6 +19,7 @@ use PHPUnit\Framework\TestCase;
* PDO et PDOStatement sont mockés pour isoler complètement * PDO et PDOStatement sont mockés pour isoler complètement
* le dépôt de la base de données. * le dépôt de la base de données.
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class MediaRepositoryTest extends TestCase final class MediaRepositoryTest extends TestCase
{ {
/** @var PDO&MockObject */ /** @var PDO&MockObject */
@@ -258,7 +259,7 @@ final class MediaRepositoryTest extends TestCase
$media = Media::fromArray($this->rowImage); $media = Media::fromArray($this->rowImage);
$stmt = $this->stmtForWrite(); $stmt = $this->stmtForWrite();
$this->db->method('prepare') $this->db->expects($this->once())->method('prepare')
->with($this->stringContains('INSERT INTO media')) ->with($this->stringContains('INSERT INTO media'))
->willReturn($stmt); ->willReturn($stmt);

View File

@@ -12,6 +12,8 @@ use PHPUnit\Framework\TestCase;
use Psr\Http\Message\StreamInterface; use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileInterface; use Psr\Http\Message\UploadedFileInterface;
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class MediaServiceDuplicateAfterInsertRaceTest extends TestCase final class MediaServiceDuplicateAfterInsertRaceTest extends TestCase
{ {
/** @var MediaRepositoryInterface&MockObject */ /** @var MediaRepositoryInterface&MockObject */
@@ -67,7 +69,7 @@ final class MediaServiceDuplicateAfterInsertRaceTest extends TestCase
private function makeUploadedFileFromPath(string $path, int $size): UploadedFileInterface private function makeUploadedFileFromPath(string $path, int $size): UploadedFileInterface
{ {
$stream = $this->createMock(StreamInterface::class); $stream = $this->createMock(StreamInterface::class);
$stream->method('getMetadata')->with('uri')->willReturn($path); $stream->expects($this->once())->method('getMetadata')->with('uri')->willReturn($path);
$file = $this->createMock(UploadedFileInterface::class); $file = $this->createMock(UploadedFileInterface::class);
$file->method('getSize')->willReturn($size); $file->method('getSize')->willReturn($size);

View File

@@ -11,6 +11,8 @@ use PHPUnit\Framework\TestCase;
use Psr\Http\Message\UploadedFileInterface; use Psr\Http\Message\UploadedFileInterface;
use Psr\Http\Message\StreamInterface; use Psr\Http\Message\StreamInterface;
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class MediaServiceEdgeCasesTest extends TestCase final class MediaServiceEdgeCasesTest extends TestCase
{ {
public function testRejectsWhenSizeUnknown(): void public function testRejectsWhenSizeUnknown(): void

View File

@@ -10,6 +10,8 @@ use PHPUnit\Framework\TestCase;
use Psr\Http\Message\UploadedFileInterface; use Psr\Http\Message\UploadedFileInterface;
use Psr\Http\Message\StreamInterface; use Psr\Http\Message\StreamInterface;
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class MediaServiceInvalidMimeTest extends TestCase final class MediaServiceInvalidMimeTest extends TestCase
{ {
public function testRejectsNonImageContentEvenWithImageLikeFilename(): void public function testRejectsNonImageContentEvenWithImageLikeFilename(): void
@@ -21,7 +23,7 @@ final class MediaServiceInvalidMimeTest extends TestCase
file_put_contents($tmpFile, 'not an image'); file_put_contents($tmpFile, 'not an image');
$stream = $this->createMock(StreamInterface::class); $stream = $this->createMock(StreamInterface::class);
$stream->method('getMetadata')->with('uri')->willReturn($tmpFile); $stream->expects($this->once())->method('getMetadata')->with('uri')->willReturn($tmpFile);
$file = $this->createMock(UploadedFileInterface::class); $file = $this->createMock(UploadedFileInterface::class);
$file->method('getSize')->willReturn(filesize($tmpFile)); $file->method('getSize')->willReturn(filesize($tmpFile));

View File

@@ -10,6 +10,8 @@ use PHPUnit\Framework\TestCase;
use Psr\Http\Message\StreamInterface; use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileInterface; use Psr\Http\Message\UploadedFileInterface;
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class MediaServiceInvalidTempPathTest extends TestCase final class MediaServiceInvalidTempPathTest extends TestCase
{ {
public function testRejectsWhenTemporaryPathIsMissing(): void public function testRejectsWhenTemporaryPathIsMissing(): void
@@ -17,7 +19,7 @@ final class MediaServiceInvalidTempPathTest extends TestCase
$repository = $this->createMock(MediaRepositoryInterface::class); $repository = $this->createMock(MediaRepositoryInterface::class);
$stream = $this->createMock(StreamInterface::class); $stream = $this->createMock(StreamInterface::class);
$stream->method('getMetadata')->with('uri')->willReturn(null); $stream->expects($this->once())->method('getMetadata')->with('uri')->willReturn(null);
$file = $this->createMock(UploadedFileInterface::class); $file = $this->createMock(UploadedFileInterface::class);
$file->method('getSize')->willReturn(128); $file->method('getSize')->willReturn(128);

View File

@@ -27,6 +27,7 @@ use Psr\Http\Message\UploadedFileInterface;
* - stockage : fichier écrit sur disque, media créé en base * - stockage : fichier écrit sur disque, media créé en base
* - suppression : fichier supprimé du disque et entrée retirée de la base * - suppression : fichier supprimé du disque et entrée retirée de la base
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class MediaServiceTest extends TestCase final class MediaServiceTest extends TestCase
{ {
/** @var MediaRepositoryInterface&MockObject */ /** @var MediaRepositoryInterface&MockObject */
@@ -196,7 +197,7 @@ final class MediaServiceTest extends TestCase
public function testFindByIdReturnsMedia(): void public function testFindByIdReturnsMedia(): void
{ {
$media = new Media(3, 'photo.jpg', '/media/photo.jpg', 'abc123', 1); $media = new Media(3, 'photo.jpg', '/media/photo.jpg', 'abc123', 1);
$this->repository->method('findById')->with(3)->willReturn($media); $this->repository->expects($this->once())->method('findById')->with(3)->willReturn($media);
$this->assertSame($media, $this->service->findById(3)); $this->assertSame($media, $this->service->findById(3));
} }
@@ -210,7 +211,7 @@ final class MediaServiceTest extends TestCase
private function makeUploadedFile(int $size): UploadedFileInterface private function makeUploadedFile(int $size): UploadedFileInterface
{ {
$stream = $this->createMock(StreamInterface::class); $stream = $this->createMock(StreamInterface::class);
$stream->method('getMetadata')->with('uri')->willReturn('/nonexistent/path'); $stream->method('getMetadata')->willReturnMap([['uri', '/nonexistent/path']]);
$file = $this->createMock(UploadedFileInterface::class); $file = $this->createMock(UploadedFileInterface::class);
$file->method('getSize')->willReturn($size); $file->method('getSize')->willReturn($size);
@@ -226,7 +227,7 @@ final class MediaServiceTest extends TestCase
private function makeUploadedFileFromPath(string $path, int $size): UploadedFileInterface private function makeUploadedFileFromPath(string $path, int $size): UploadedFileInterface
{ {
$stream = $this->createMock(StreamInterface::class); $stream = $this->createMock(StreamInterface::class);
$stream->method('getMetadata')->with('uri')->willReturn($path); $stream->method('getMetadata')->willReturnMap([['uri', $path]]);
$file = $this->createMock(UploadedFileInterface::class); $file = $this->createMock(UploadedFileInterface::class);
$file->method('getSize')->willReturn($size); $file->method('getSize')->willReturn($size);

View File

@@ -13,6 +13,8 @@ use App\Shared\Html\HtmlSanitizerInterface;
use PDO; use PDO;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class PostConcurrentUpdateIntegrationTest extends TestCase final class PostConcurrentUpdateIntegrationTest extends TestCase
{ {
private PDO $db; private PDO $db;

View File

@@ -27,6 +27,7 @@ use Tests\ControllerTestCase;
* - update() : 404, droits insuffisants, succès, erreur de validation * - update() : 404, droits insuffisants, succès, erreur de validation
* - delete() : 404, droits insuffisants, succès * - delete() : 404, droits insuffisants, succès
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class PostControllerTest extends ControllerTestCase final class PostControllerTest extends ControllerTestCase
{ {
/** @var \Slim\Views\Twig&MockObject */ /** @var \Slim\Views\Twig&MockObject */
@@ -100,7 +101,7 @@ final class PostControllerTest extends ControllerTestCase
public function testIndexFiltersByCategoryWhenSlugProvided(): void public function testIndexFiltersByCategoryWhenSlugProvided(): void
{ {
$category = new Category(3, 'PHP', 'php'); $category = new Category(3, 'PHP', 'php');
$this->categoryService->method('findBySlug')->with('php')->willReturn($category); $this->categoryService->expects($this->once())->method('findBySlug')->with('php')->willReturn($category);
$this->postService->expects($this->once()) $this->postService->expects($this->once())
->method('getAllPosts') ->method('getAllPosts')
@@ -233,7 +234,7 @@ final class PostControllerTest extends ControllerTestCase
public function testFormRendersFilledFormWhenUserIsAuthor(): void public function testFormRendersFilledFormWhenUserIsAuthor(): void
{ {
$post = $this->buildPostEntity(7, 'Titre', 'Contenu', 'titre', 5); $post = $this->buildPostEntity(7, 'Titre', 'Contenu', 'titre', 5);
$this->postService->method('getPostById')->with(7)->willReturn($post); $this->postService->expects($this->once())->method('getPostById')->with(7)->willReturn($post);
$this->sessionManager->method('isAdmin')->willReturn(false); $this->sessionManager->method('isAdmin')->willReturn(false);
$this->sessionManager->method('isEditor')->willReturn(false); $this->sessionManager->method('isEditor')->willReturn(false);
$this->sessionManager->method('getUserId')->willReturn(5); $this->sessionManager->method('getUserId')->willReturn(5);

View File

@@ -8,6 +8,8 @@ use App\Post\PostExtension;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Twig\TwigFunction; use Twig\TwigFunction;
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class PostExtensionTest extends TestCase final class PostExtensionTest extends TestCase
{ {
/** @var array<string, TwigFunction> */ /** @var array<string, TwigFunction> */

View File

@@ -8,6 +8,8 @@ use App\Shared\Database\Migrator;
use PDO; use PDO;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class PostFtsUsernameSyncIntegrationTest extends TestCase final class PostFtsUsernameSyncIntegrationTest extends TestCase
{ {
private PDO $db; private PDO $db;

View File

@@ -6,6 +6,8 @@ namespace Tests\Post;
use App\Post\Post; use App\Post\Post;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class PostModelEdgeCasesTest extends TestCase final class PostModelEdgeCasesTest extends TestCase
{ {
public function testFromArrayKeepsMissingOptionalFieldsNull(): void public function testFromArrayKeepsMissingOptionalFieldsNull(): void

View File

@@ -7,6 +7,8 @@ use App\Post\Post;
use DateTime; use DateTime;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class PostModelTest extends TestCase final class PostModelTest extends TestCase
{ {
public function testConstructAndGettersExposePostData(): void public function testConstructAndGettersExposePostData(): void

View File

@@ -19,6 +19,7 @@ use PHPUnit\Framework\TestCase;
* PDO et PDOStatement sont mockés pour isoler complètement * PDO et PDOStatement sont mockés pour isoler complètement
* le dépôt de la base de données. * le dépôt de la base de données.
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class PostRepositoryTest extends TestCase final class PostRepositoryTest extends TestCase
{ {
/** @var PDO&MockObject */ /** @var PDO&MockObject */
@@ -339,7 +340,7 @@ final class PostRepositoryTest extends TestCase
$post = Post::fromArray($this->rowPost); $post = Post::fromArray($this->rowPost);
$stmt = $this->stmtForWrite(); $stmt = $this->stmtForWrite();
$this->db->method('prepare') $this->db->expects($this->once())->method('prepare')
->with($this->stringContains('INSERT INTO posts')) ->with($this->stringContains('INSERT INTO posts'))
->willReturn($stmt); ->willReturn($stmt);
@@ -403,7 +404,7 @@ final class PostRepositoryTest extends TestCase
$post = Post::fromArray($this->rowPost); $post = Post::fromArray($this->rowPost);
$stmt = $this->stmtForWrite(1); $stmt = $this->stmtForWrite(1);
$this->db->method('prepare') $this->db->expects($this->once())->method('prepare')
->with($this->stringContains('UPDATE posts')) ->with($this->stringContains('UPDATE posts'))
->willReturn($stmt); ->willReturn($stmt);

View File

@@ -17,6 +17,7 @@ use PHPUnit\Framework\TestCase;
* Couvre la création, la mise à jour, la suppression et les lectures. * Couvre la création, la mise à jour, la suppression et les lectures.
* HtmlSanitizerInterface et PostRepository sont mockés pour isoler la logique métier. * HtmlSanitizerInterface et PostRepository sont mockés pour isoler la logique métier.
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class PostServiceTest extends TestCase final class PostServiceTest extends TestCase
{ {
/** @var PostRepositoryInterface&MockObject */ /** @var PostRepositoryInterface&MockObject */
@@ -65,7 +66,7 @@ final class PostServiceTest extends TestCase
public function testGetPostsByUserIdDelegatesToRepository(): void public function testGetPostsByUserIdDelegatesToRepository(): void
{ {
$posts = [$this->makePost(1, 'Titre', 'slug-titre')]; $posts = [$this->makePost(1, 'Titre', 'slug-titre')];
$this->repository->method('findByUserId')->with(3, null)->willReturn($posts); $this->repository->expects($this->once())->method('findByUserId')->with(3, null)->willReturn($posts);
$this->assertSame($posts, $this->service->getPostsByUserId(3)); $this->assertSame($posts, $this->service->getPostsByUserId(3));
} }
@@ -181,7 +182,7 @@ final class PostServiceTest extends TestCase
public function testSearchPostsDelegatesToRepository(): void public function testSearchPostsDelegatesToRepository(): void
{ {
$posts = [$this->makePost(1, 'Résultat', 'resultat')]; $posts = [$this->makePost(1, 'Résultat', 'resultat')];
$this->repository->method('search')->with('mot', null, null)->willReturn($posts); $this->repository->expects($this->once())->method('search')->with('mot', null, null)->willReturn($posts);
$this->assertSame($posts, $this->service->searchPosts('mot')); $this->assertSame($posts, $this->service->searchPosts('mot'));
} }

View File

@@ -19,6 +19,7 @@ use Tests\ControllerTestCase;
* - Flux vide : XML minimal valide * - Flux vide : XML minimal valide
* - Appel à getRecentPosts() avec la constante FEED_LIMIT (20) * - Appel à getRecentPosts() avec la constante FEED_LIMIT (20)
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class RssControllerTest extends ControllerTestCase final class RssControllerTest extends ControllerTestCase
{ {
/** @var PostServiceInterface&MockObject */ /** @var PostServiceInterface&MockObject */

View File

@@ -7,6 +7,8 @@ use App\Shared\Http\ClientIpResolver;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Slim\Psr7\Factory\ServerRequestFactory; use Slim\Psr7\Factory\ServerRequestFactory;
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class ClientIpResolverTest extends TestCase final class ClientIpResolverTest extends TestCase
{ {
public function testResolveReturnsDefaultWhenRemoteAddrMissing(): void public function testResolveReturnsDefaultWhenRemoteAddrMissing(): void

View File

@@ -6,6 +6,8 @@ namespace Tests\Shared;
use App\Shared\Config; use App\Shared\Config;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class ConfigTest extends TestCase final class ConfigTest extends TestCase
{ {
public function testGetTwigCacheReturnsFalseInDev(): void public function testGetTwigCacheReturnsFalseInDev(): void

View File

@@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase;
* Couvre la conversion de valeurs brutes de base de données en DateTime, * Couvre la conversion de valeurs brutes de base de données en DateTime,
* ainsi que les cas silencieux (null, chaîne vide, valeur invalide). * ainsi que les cas silencieux (null, chaîne vide, valeur invalide).
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class DateParserTest extends TestCase final class DateParserTest extends TestCase
{ {

View File

@@ -10,6 +10,8 @@ use PHPUnit\Framework\TestCase;
use Slim\Csrf\Guard; use Slim\Csrf\Guard;
use Slim\Psr7\Factory\ResponseFactory; use Slim\Psr7\Factory\ResponseFactory;
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class ExtensionTest extends TestCase final class ExtensionTest extends TestCase
{ {
protected function setUp(): void protected function setUp(): void

View File

@@ -6,6 +6,8 @@ namespace Tests\Shared;
use App\Shared\Http\FlashService; use App\Shared\Http\FlashService;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class FlashServiceConsumeTest extends TestCase final class FlashServiceConsumeTest extends TestCase
{ {
protected function setUp(): void protected function setUp(): void

View File

@@ -6,6 +6,8 @@ namespace Tests\Shared;
use App\Shared\Http\FlashService; use App\Shared\Http\FlashService;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class FlashServiceTest extends TestCase final class FlashServiceTest extends TestCase
{ {
protected function setUp(): void protected function setUp(): void

View File

@@ -7,6 +7,8 @@ use App\Shared\Http\ClientIpResolver;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Slim\Psr7\Factory\ServerRequestFactory; use Slim\Psr7\Factory\ServerRequestFactory;
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class HelperEdgeCasesTest extends TestCase final class HelperEdgeCasesTest extends TestCase
{ {
public function testClientIpResolverFallsBackToRemoteAddr(): void public function testClientIpResolverFallsBackToRemoteAddr(): void

View File

@@ -6,6 +6,8 @@ namespace Tests\Shared;
use App\Shared\Html\HtmlPurifierFactory; use App\Shared\Html\HtmlPurifierFactory;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class HtmlPurifierFactoryTest extends TestCase final class HtmlPurifierFactoryTest extends TestCase
{ {
public function testCreateBuildsPurifierAndSanitizesDangerousHtml(): void public function testCreateBuildsPurifierAndSanitizesDangerousHtml(): void

View File

@@ -17,6 +17,7 @@ use PHPUnit\Framework\TestCase;
* Ces tests utilisent une vraie instance HTMLPurifier (pas de mock) * Ces tests utilisent une vraie instance HTMLPurifier (pas de mock)
* car c'est le comportement de purification lui-même qui est testé. * car c'est le comportement de purification lui-même qui est testé.
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class HtmlSanitizerTest extends TestCase final class HtmlSanitizerTest extends TestCase
{ {
private HtmlSanitizer $sanitizer; private HtmlSanitizer $sanitizer;

View File

@@ -22,6 +22,7 @@ use PHPUnit\Framework\TestCase;
* syncFtsIndex() requiert les tables posts, users et posts_fts — elles sont * syncFtsIndex() requiert les tables posts, users et posts_fts — elles sont
* créées minimalement avant chaque test qui en a besoin. * créées minimalement avant chaque test qui en a besoin.
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class MigratorTest extends TestCase final class MigratorTest extends TestCase
{ {
private PDO $db; private PDO $db;

View File

@@ -18,6 +18,7 @@ use PHPUnit\Framework\TestCase;
* PDO et PDOStatement sont mockés pour isoler le Seeder de la base de données. * PDO et PDOStatement sont mockés pour isoler le Seeder de la base de données.
* Les variables d'environnement sont définies dans setUp() et restaurées dans tearDown(). * Les variables d'environnement sont définies dans setUp() et restaurées dans tearDown().
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class SeederTest extends TestCase final class SeederTest extends TestCase
{ {
/** @var PDO&MockObject */ /** @var PDO&MockObject */

View File

@@ -6,6 +6,8 @@ namespace Tests\Shared;
use App\Shared\Http\SessionManager; use App\Shared\Http\SessionManager;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class SessionManagerEdgeCasesTest extends TestCase final class SessionManagerEdgeCasesTest extends TestCase
{ {
private SessionManager $manager; private SessionManager $manager;

View File

@@ -18,6 +18,7 @@ use PHPUnit\Framework\TestCase;
* session_status() === PHP_SESSION_ACTIVE dans SessionManager, ce qui les rend * session_status() === PHP_SESSION_ACTIVE dans SessionManager, ce qui les rend
* sans effet en contexte CLI et évite toute notice PHP. * sans effet en contexte CLI et évite toute notice PHP.
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class SessionManagerTest extends TestCase final class SessionManagerTest extends TestCase
{ {
private SessionManager $manager; private SessionManager $manager;

View File

@@ -12,6 +12,7 @@ use PHPUnit\Framework\TestCase;
* Couvre la translittération ASCII, la normalisation en minuscules, * Couvre la translittération ASCII, la normalisation en minuscules,
* le remplacement des caractères non alphanumériques, et les cas limites. * le remplacement des caractères non alphanumériques, et les cas limites.
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class SlugHelperTest extends TestCase final class SlugHelperTest extends TestCase
{ {

View File

@@ -24,6 +24,7 @@ use Tests\ControllerTestCase;
* - updateRole() : introuvable, propre rôle, cible admin, rôle invalide, succès * - updateRole() : introuvable, propre rôle, cible admin, rôle invalide, succès
* - delete() : introuvable, cible admin, soi-même, succès * - delete() : introuvable, cible admin, soi-même, succès
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class UserControllerTest extends ControllerTestCase final class UserControllerTest extends ControllerTestCase
{ {
/** @var \Slim\Views\Twig&MockObject */ /** @var \Slim\Views\Twig&MockObject */

View File

@@ -19,6 +19,7 @@ use PHPUnit\Framework\TestCase;
* PDO et PDOStatement sont mockés pour isoler complètement * PDO et PDOStatement sont mockés pour isoler complètement
* le dépôt de la base de données. * le dépôt de la base de données.
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class UserRepositoryTest extends TestCase final class UserRepositoryTest extends TestCase
{ {
/** @var PDO&MockObject */ /** @var PDO&MockObject */
@@ -258,7 +259,7 @@ final class UserRepositoryTest extends TestCase
$user = User::fromArray($this->rowAlice); $user = User::fromArray($this->rowAlice);
$stmt = $this->stmtForWrite(); $stmt = $this->stmtForWrite();
$this->db->method('prepare') $this->db->expects($this->once())->method('prepare')
->with($this->stringContains('INSERT INTO users')) ->with($this->stringContains('INSERT INTO users'))
->willReturn($stmt); ->willReturn($stmt);
@@ -301,7 +302,7 @@ final class UserRepositoryTest extends TestCase
$newHash = password_hash('nouveaumdp', PASSWORD_BCRYPT); $newHash = password_hash('nouveaumdp', PASSWORD_BCRYPT);
$stmt = $this->stmtForWrite(); $stmt = $this->stmtForWrite();
$this->db->method('prepare') $this->db->expects($this->once())->method('prepare')
->with($this->stringContains('UPDATE users')) ->with($this->stringContains('UPDATE users'))
->willReturn($stmt); ->willReturn($stmt);
@@ -322,7 +323,7 @@ final class UserRepositoryTest extends TestCase
{ {
$stmt = $this->stmtForWrite(); $stmt = $this->stmtForWrite();
$this->db->method('prepare') $this->db->expects($this->once())->method('prepare')
->with($this->stringContains('UPDATE users')) ->with($this->stringContains('UPDATE users'))
->willReturn($stmt); ->willReturn($stmt);

View File

@@ -21,6 +21,7 @@ use PHPUnit\Framework\TestCase;
* Les dépendances sont remplacées par des mocks via leurs interfaces pour * Les dépendances sont remplacées par des mocks via leurs interfaces pour
* isoler le service. * isoler le service.
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class UserServiceTest extends TestCase final class UserServiceTest extends TestCase
{ {
/** @var UserRepositoryInterface&MockObject */ /** @var UserRepositoryInterface&MockObject */
@@ -186,7 +187,7 @@ final class UserServiceTest extends TestCase
public function testFindByIdReturnsUser(): void public function testFindByIdReturnsUser(): void
{ {
$user = $this->makeUser('alice', 'alice@example.com'); $user = $this->makeUser('alice', 'alice@example.com');
$this->userRepository->method('findById')->with(1)->willReturn($user); $this->userRepository->expects($this->once())->method('findById')->with(1)->willReturn($user);
$this->assertSame($user, $this->service->findById(1)); $this->assertSame($user, $this->service->findById(1));
} }
@@ -199,7 +200,7 @@ final class UserServiceTest extends TestCase
*/ */
public function testDeleteDelegatesToRepository(): void public function testDeleteDelegatesToRepository(): void
{ {
$this->userRepository->method('findById')->with(5)->willReturn($this->makeUser('alice', 'alice@example.com')); $this->userRepository->expects($this->once())->method('findById')->with(5)->willReturn($this->makeUser('alice', 'alice@example.com'));
$this->userRepository->expects($this->once())->method('delete')->with(5); $this->userRepository->expects($this->once())->method('delete')->with(5);
$this->service->delete(5); $this->service->delete(5);
@@ -213,7 +214,7 @@ final class UserServiceTest extends TestCase
*/ */
public function testUpdateRoleDelegatesToRepository(): void public function testUpdateRoleDelegatesToRepository(): void
{ {
$this->userRepository->method('findById')->with(3)->willReturn($this->makeUser('alice', 'alice@example.com')); $this->userRepository->expects($this->once())->method('findById')->with(3)->willReturn($this->makeUser('alice', 'alice@example.com'));
$this->userRepository->expects($this->once()) $this->userRepository->expects($this->once())
->method('updateRole') ->method('updateRole')
->with(3, User::ROLE_EDITOR); ->with(3, User::ROLE_EDITOR);
@@ -239,7 +240,7 @@ final class UserServiceTest extends TestCase
#[\PHPUnit\Framework\Attributes\DataProvider('validRolesProvider')] #[\PHPUnit\Framework\Attributes\DataProvider('validRolesProvider')]
public function testUpdateRoleAcceptsAllValidRoles(string $role): void public function testUpdateRoleAcceptsAllValidRoles(string $role): void
{ {
$this->userRepository->method('findById')->with(1)->willReturn($this->makeUser('alice', 'alice@example.com')); $this->userRepository->expects($this->once())->method('findById')->with(1)->willReturn($this->makeUser('alice', 'alice@example.com'));
$this->userRepository->expects($this->once())->method('updateRole')->with(1, $role); $this->userRepository->expects($this->once())->method('updateRole')->with(1, $role);
$this->service->updateRole(1, $role); $this->service->updateRole(1, $role);

View File

@@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase;
* Vérifie la construction, la validation, les accesseurs * Vérifie la construction, la validation, les accesseurs
* et l'hydratation depuis un tableau de base de données. * et l'hydratation depuis un tableau de base de données.
*/ */
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class UserTest extends TestCase final class UserTest extends TestCase
{ {