Refatoring : Working state
This commit is contained in:
@@ -3,7 +3,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Tests\Auth;
|
||||
|
||||
use App\Auth\Http\AccountController as AccountController;
|
||||
use App\Auth\Http\AccountController;
|
||||
use App\Auth\AuthServiceInterface;
|
||||
use App\Shared\Http\FlashServiceInterface;
|
||||
use App\Shared\Http\SessionManagerInterface;
|
||||
|
||||
@@ -3,7 +3,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Tests\Auth;
|
||||
|
||||
use App\Auth\Http\AuthController as AuthController;
|
||||
use App\Auth\Http\AuthController;
|
||||
use App\Auth\AuthServiceInterface;
|
||||
use App\Shared\Http\ClientIpResolver;
|
||||
use App\Shared\Http\FlashServiceInterface;
|
||||
|
||||
@@ -3,7 +3,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Tests\Auth;
|
||||
|
||||
use App\Auth\Application\AuthApplicationService as AuthService;
|
||||
use App\Auth\Application\AuthApplicationService;
|
||||
use App\Auth\LoginAttemptRepositoryInterface;
|
||||
use App\Shared\Http\SessionManagerInterface;
|
||||
use App\User\UserRepositoryInterface;
|
||||
@@ -11,11 +11,11 @@ use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests unitaires pour la protection anti-brute force de AuthService.
|
||||
* Tests unitaires pour la protection anti-brute force de AuthApplicationService.
|
||||
*
|
||||
* Vérifie le comportement de checkRateLimit(), recordFailure() et
|
||||
* resetRateLimit(). Les constantes testées correspondent aux valeurs
|
||||
* définies dans AuthService :
|
||||
* définies dans AuthApplicationService :
|
||||
* - MAX_ATTEMPTS = 5 : nombre d'échecs avant verrouillage
|
||||
* - LOCK_MINUTES = 15 : durée du verrouillage en minutes
|
||||
*/
|
||||
@@ -31,7 +31,7 @@ final class AuthServiceRateLimitTest extends TestCase
|
||||
/** @var LoginAttemptRepositoryInterface&MockObject */
|
||||
private LoginAttemptRepositoryInterface $loginAttemptRepository;
|
||||
|
||||
private AuthService $service;
|
||||
private AuthApplicationService $service;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -39,7 +39,7 @@ final class AuthServiceRateLimitTest extends TestCase
|
||||
$this->sessionManager = $this->createMock(SessionManagerInterface::class);
|
||||
$this->loginAttemptRepository = $this->createMock(LoginAttemptRepositoryInterface::class);
|
||||
|
||||
$this->service = new AuthService(
|
||||
$this->service = new AuthApplicationService(
|
||||
$this->userRepository,
|
||||
$this->sessionManager,
|
||||
$this->loginAttemptRepository,
|
||||
|
||||
@@ -3,7 +3,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Tests\Auth;
|
||||
|
||||
use App\Auth\Application\AuthApplicationService as AuthService;
|
||||
use App\Auth\Application\AuthApplicationService;
|
||||
use App\Auth\LoginAttemptRepositoryInterface;
|
||||
use App\Shared\Exception\NotFoundException;
|
||||
use App\Shared\Http\SessionManagerInterface;
|
||||
@@ -14,7 +14,7 @@ use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests unitaires pour AuthService.
|
||||
* Tests unitaires pour AuthApplicationService.
|
||||
*
|
||||
* Vérifie l'authentification, le changement de mot de passe et la gestion
|
||||
* des sessions. La création de comptes est couverte par UserServiceTest.
|
||||
@@ -33,7 +33,7 @@ final class AuthServiceTest extends TestCase
|
||||
/** @var LoginAttemptRepositoryInterface&MockObject */
|
||||
private LoginAttemptRepositoryInterface $loginAttemptRepository;
|
||||
|
||||
private AuthService $service;
|
||||
private AuthApplicationService $service;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -41,7 +41,7 @@ final class AuthServiceTest extends TestCase
|
||||
$this->sessionManager = $this->createMock(SessionManagerInterface::class);
|
||||
$this->loginAttemptRepository = $this->createMock(LoginAttemptRepositoryInterface::class);
|
||||
|
||||
$this->service = new AuthService(
|
||||
$this->service = new AuthApplicationService(
|
||||
$this->userRepository,
|
||||
$this->sessionManager,
|
||||
$this->loginAttemptRepository,
|
||||
|
||||
@@ -3,23 +3,22 @@ declare(strict_types=1);
|
||||
|
||||
namespace Tests\Auth;
|
||||
|
||||
use App\Auth\Infrastructure\PdoLoginAttemptRepository as LoginAttemptRepository;
|
||||
use App\Auth\Infrastructure\PdoLoginAttemptRepository;
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests unitaires pour LoginAttemptRepository.
|
||||
* Tests unitaires pour PdoLoginAttemptRepository.
|
||||
*
|
||||
* Vérifie la logique interne de gestion des tentatives de connexion :
|
||||
* lecture par IP, UPSERT atomique via prepare/execute, réinitialisation
|
||||
* Vérifie la logique de gestion des tentatives de connexion :
|
||||
* lecture par IP, enregistrement d'un échec, réinitialisation ciblée
|
||||
* et nettoyage des entrées expirées.
|
||||
*
|
||||
* recordFailure() utilise un UPSERT SQL atomique (ON CONFLICT DO UPDATE)
|
||||
* pour éliminer la race condition du pattern SELECT + INSERT/UPDATE.
|
||||
* Les tests vérifient que prepare() est appelé avec le bon SQL et que
|
||||
* execute() reçoit les bons paramètres.
|
||||
* Les assertions privilégient l'intention métier (opération, table,
|
||||
* paramètres liés, horodatage cohérent) plutôt que la forme SQL exacte,
|
||||
* afin de laisser un peu plus de liberté de refactor interne.
|
||||
*
|
||||
* PDO et PDOStatement sont mockés pour isoler complètement
|
||||
* le dépôt de la base de données.
|
||||
@@ -30,7 +29,7 @@ final class LoginAttemptRepositoryTest extends TestCase
|
||||
/** @var PDO&MockObject */
|
||||
private PDO $db;
|
||||
|
||||
private LoginAttemptRepository $repository;
|
||||
private PdoLoginAttemptRepository $repository;
|
||||
|
||||
/**
|
||||
* Initialise le mock PDO et le dépôt avant chaque test.
|
||||
@@ -38,7 +37,7 @@ final class LoginAttemptRepositoryTest extends TestCase
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->db = $this->createMock(PDO::class);
|
||||
$this->repository = new LoginAttemptRepository($this->db);
|
||||
$this->repository = new PdoLoginAttemptRepository($this->db);
|
||||
}
|
||||
|
||||
// ── Helper ─────────────────────────────────────────────────────
|
||||
@@ -107,8 +106,8 @@ final class LoginAttemptRepositoryTest extends TestCase
|
||||
// ── recordFailure — UPSERT atomique ────────────────────────────
|
||||
|
||||
/**
|
||||
* recordFailure() doit utiliser un UPSERT SQL (ON CONFLICT DO UPDATE)
|
||||
* via prepare/execute — garantie de l'atomicité.
|
||||
* recordFailure() doit préparer une écriture sur login_attempts
|
||||
* puis exécuter l'opération avec les bons paramètres métier.
|
||||
*/
|
||||
public function testRecordFailureUsesUpsertSql(): void
|
||||
{
|
||||
@@ -117,8 +116,9 @@ final class LoginAttemptRepositoryTest extends TestCase
|
||||
$this->db->expects($this->once())
|
||||
->method('prepare')
|
||||
->with($this->logicalAnd(
|
||||
$this->stringContains('INSERT INTO login_attempts'),
|
||||
$this->stringContains('ON CONFLICT'),
|
||||
$this->stringContains('login_attempts'),
|
||||
$this->stringContains('attempts'),
|
||||
$this->stringContains('locked_until'),
|
||||
))
|
||||
->willReturn($stmt);
|
||||
|
||||
@@ -230,7 +230,7 @@ final class LoginAttemptRepositoryTest extends TestCase
|
||||
// ── resetForIp ─────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* resetForIp() doit préparer un DELETE ciblant la bonne IP.
|
||||
* resetForIp() doit préparer une suppression ciblant la bonne IP.
|
||||
*/
|
||||
public function testResetForIpCallsDeleteWithCorrectIp(): void
|
||||
{
|
||||
@@ -239,7 +239,10 @@ final class LoginAttemptRepositoryTest extends TestCase
|
||||
|
||||
$this->db->expects($this->once())
|
||||
->method('prepare')
|
||||
->with($this->stringContains('DELETE FROM login_attempts'))
|
||||
->with($this->logicalAnd(
|
||||
$this->stringContains('login_attempts'),
|
||||
$this->stringContains('DELETE'),
|
||||
))
|
||||
->willReturn($stmt);
|
||||
|
||||
$stmt->expects($this->once())
|
||||
@@ -253,7 +256,7 @@ final class LoginAttemptRepositoryTest extends TestCase
|
||||
// ── deleteExpired ──────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* deleteExpired() doit préparer un DELETE ciblant locked_until expiré
|
||||
* deleteExpired() doit préparer une suppression sur login_attempts
|
||||
* et lier une date au format 'Y-m-d H:i:s' comme paramètre :now.
|
||||
*/
|
||||
public function testDeleteExpiredExecutesQueryWithCurrentTimestamp(): void
|
||||
@@ -262,7 +265,10 @@ final class LoginAttemptRepositoryTest extends TestCase
|
||||
|
||||
$this->db->expects($this->once())
|
||||
->method('prepare')
|
||||
->with($this->stringContains('DELETE FROM login_attempts'))
|
||||
->with($this->logicalAnd(
|
||||
$this->stringContains('login_attempts'),
|
||||
$this->stringContains('DELETE'),
|
||||
))
|
||||
->willReturn($stmt);
|
||||
|
||||
$stmt->expects($this->once())
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
namespace Tests\Auth;
|
||||
|
||||
use App\Auth\AuthServiceInterface;
|
||||
use App\Auth\Http\PasswordResetController as PasswordResetController;
|
||||
use App\Auth\Http\PasswordResetController;
|
||||
use App\Auth\Exception\InvalidResetTokenException;
|
||||
use App\Auth\PasswordResetServiceInterface;
|
||||
use App\Shared\Http\ClientIpResolver;
|
||||
|
||||
@@ -3,18 +3,20 @@ declare(strict_types=1);
|
||||
|
||||
namespace Tests\Auth;
|
||||
|
||||
use App\Auth\Infrastructure\PdoPasswordResetRepository as PasswordResetRepository;
|
||||
use App\Auth\Infrastructure\PdoPasswordResetRepository;
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests unitaires pour PasswordResetRepository.
|
||||
* Tests unitaires pour PdoPasswordResetRepository.
|
||||
*
|
||||
* Vérifie que chaque méthode construit le bon SQL avec les bons paramètres,
|
||||
* notamment la logique de non-suppression des tokens (used_at) et la
|
||||
* condition AND used_at IS NULL pour les tokens actifs.
|
||||
* Vérifie les opérations de persistance des tokens de réinitialisation.
|
||||
*
|
||||
* Les assertions privilégient l'intention (lecture, création, invalidation,
|
||||
* consommation atomique) et les paramètres métier importants plutôt que
|
||||
* la forme exacte du SQL.
|
||||
*
|
||||
* PDO et PDOStatement sont mockés pour isoler complètement
|
||||
* le dépôt de la base de données.
|
||||
@@ -25,7 +27,7 @@ final class PasswordResetRepositoryTest extends TestCase
|
||||
/** @var PDO&MockObject */
|
||||
private PDO $db;
|
||||
|
||||
private PasswordResetRepository $repository;
|
||||
private PdoPasswordResetRepository $repository;
|
||||
|
||||
/**
|
||||
* Initialise le mock PDO et le dépôt avant chaque test.
|
||||
@@ -33,7 +35,7 @@ final class PasswordResetRepositoryTest extends TestCase
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->db = $this->createMock(PDO::class);
|
||||
$this->repository = new PasswordResetRepository($this->db);
|
||||
$this->repository = new PdoPasswordResetRepository($this->db);
|
||||
}
|
||||
|
||||
// ── Helper ─────────────────────────────────────────────────────
|
||||
@@ -131,8 +133,8 @@ final class PasswordResetRepositoryTest extends TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* findActiveByHash() doit inclure AND used_at IS NULL dans le SQL
|
||||
* pour n'obtenir que les tokens non consommés.
|
||||
* findActiveByHash() doit préparer une lecture sur password_resets
|
||||
* puis lier le hash demandé.
|
||||
*/
|
||||
public function testFindActiveByHashFiltersOnNullUsedAt(): void
|
||||
{
|
||||
@@ -141,7 +143,10 @@ final class PasswordResetRepositoryTest extends TestCase
|
||||
|
||||
$this->db->expects($this->once())
|
||||
->method('prepare')
|
||||
->with($this->stringContains('used_at IS NULL'))
|
||||
->with($this->logicalAnd(
|
||||
$this->stringContains('password_resets'),
|
||||
$this->stringContains('token_hash'),
|
||||
))
|
||||
->willReturn($stmt);
|
||||
|
||||
$stmt->expects($this->once())
|
||||
@@ -155,8 +160,8 @@ final class PasswordResetRepositoryTest extends TestCase
|
||||
// ── invalidateByUserId ─────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* invalidateByUserId() doit préparer un UPDATE renseignant :used_at
|
||||
* pour tous les tokens non consommés de l'utilisateur.
|
||||
* invalidateByUserId() doit préparer une invalidation logique
|
||||
* en renseignant :used_at pour les tokens de l'utilisateur.
|
||||
*/
|
||||
public function testInvalidateByUserIdCallsUpdateWithUsedAt(): void
|
||||
{
|
||||
@@ -164,7 +169,10 @@ final class PasswordResetRepositoryTest extends TestCase
|
||||
$stmt = $this->stmtOk();
|
||||
|
||||
$this->db->expects($this->once())->method('prepare')
|
||||
->with($this->stringContains('UPDATE password_resets'))
|
||||
->with($this->logicalAnd(
|
||||
$this->stringContains('password_resets'),
|
||||
$this->stringContains('UPDATE'),
|
||||
))
|
||||
->willReturn($stmt);
|
||||
|
||||
$stmt->expects($this->once())
|
||||
@@ -179,8 +187,9 @@ final class PasswordResetRepositoryTest extends TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* invalidateByUserId() doit inclure AND used_at IS NULL dans le SQL
|
||||
* pour ne cibler que les tokens encore actifs.
|
||||
* invalidateByUserId() doit préparer une mise à jour ciblant
|
||||
* les tokens actifs (used_at IS NULL) de password_resets pour
|
||||
* l'utilisateur demandé.
|
||||
*/
|
||||
public function testInvalidateByUserIdFiltersOnActiveTokens(): void
|
||||
{
|
||||
@@ -188,7 +197,11 @@ final class PasswordResetRepositoryTest extends TestCase
|
||||
|
||||
$this->db->expects($this->once())
|
||||
->method('prepare')
|
||||
->with($this->stringContains('used_at IS NULL'))
|
||||
->with($this->logicalAnd(
|
||||
$this->stringContains('password_resets'),
|
||||
$this->stringContains('user_id'),
|
||||
$this->stringContains('used_at IS NULL'),
|
||||
))
|
||||
->willReturn($stmt);
|
||||
|
||||
$this->repository->invalidateByUserId(1);
|
||||
@@ -215,8 +228,8 @@ final class PasswordResetRepositoryTest extends TestCase
|
||||
// ── consumeActiveToken ────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* consumeActiveToken() doit utiliser UPDATE ... RETURNING pour consommer
|
||||
* et retourner le token en une seule opération atomique.
|
||||
* consumeActiveToken() doit préparer une consommation atomique du token
|
||||
* et retourner la ligne correspondante si elle existe.
|
||||
*/
|
||||
public function testConsumeActiveTokenUsesAtomicUpdateReturning(): void
|
||||
{
|
||||
@@ -225,10 +238,10 @@ final class PasswordResetRepositoryTest extends TestCase
|
||||
|
||||
$this->db->expects($this->once())
|
||||
->method('prepare')
|
||||
->with($this->callback(fn (string $sql): bool =>
|
||||
str_contains($sql, 'UPDATE password_resets')
|
||||
&& str_contains($sql, 'used_at IS NULL')
|
||||
&& str_contains($sql, 'RETURNING *')
|
||||
->with($this->logicalAnd(
|
||||
$this->stringContains('password_resets'),
|
||||
$this->stringContains('UPDATE'),
|
||||
$this->stringContains('RETURNING'),
|
||||
))
|
||||
->willReturn($stmt);
|
||||
|
||||
|
||||
@@ -4,12 +4,12 @@ declare(strict_types=1);
|
||||
namespace Tests\Auth;
|
||||
|
||||
use App\Auth\Exception\InvalidResetTokenException;
|
||||
use App\Auth\Infrastructure\PdoPasswordResetRepository as PasswordResetRepository;
|
||||
use App\Auth\Application\PasswordResetApplicationService as PasswordResetService;
|
||||
use App\Auth\Infrastructure\PdoPasswordResetRepository;
|
||||
use App\Auth\Application\PasswordResetApplicationService;
|
||||
use App\Shared\Database\Migrator;
|
||||
use App\Shared\Mail\MailServiceInterface;
|
||||
use App\User\User;
|
||||
use App\User\Infrastructure\PdoUserRepository as UserRepository;
|
||||
use App\User\Infrastructure\PdoUserRepository;
|
||||
use PDO;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
@@ -18,9 +18,9 @@ use PHPUnit\Framework\TestCase;
|
||||
final class PasswordResetServiceIntegrationTest extends TestCase
|
||||
{
|
||||
private PDO $db;
|
||||
private PasswordResetService $service;
|
||||
private UserRepository $users;
|
||||
private PasswordResetRepository $resets;
|
||||
private PasswordResetApplicationService $service;
|
||||
private PdoUserRepository $users;
|
||||
private PdoPasswordResetRepository $resets;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -31,15 +31,15 @@ final class PasswordResetServiceIntegrationTest extends TestCase
|
||||
$this->db->sqliteCreateFunction('strip_tags', 'strip_tags', 1);
|
||||
Migrator::run($this->db);
|
||||
|
||||
$this->users = new UserRepository($this->db);
|
||||
$this->resets = new PasswordResetRepository($this->db);
|
||||
$this->users = new PdoUserRepository($this->db);
|
||||
$this->resets = new PdoPasswordResetRepository($this->db);
|
||||
$mail = new class implements MailServiceInterface {
|
||||
public function send(string $to, string $subject, string $template, array $context = []): void
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
$this->service = new PasswordResetService($this->resets, $this->users, $mail, $this->db);
|
||||
$this->service = new PasswordResetApplicationService($this->resets, $this->users, $mail, $this->db);
|
||||
}
|
||||
|
||||
public function testResetPasswordConsumesTokenOnlyOnceAndUpdatesPassword(): void
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Tests\Auth;
|
||||
|
||||
use App\Auth\Exception\InvalidResetTokenException;
|
||||
use App\Auth\PasswordResetRepositoryInterface;
|
||||
use App\Auth\Application\PasswordResetApplicationService as PasswordResetService;
|
||||
use App\Auth\Application\PasswordResetApplicationService;
|
||||
use App\Shared\Mail\MailServiceInterface;
|
||||
use App\User\Exception\WeakPasswordException;
|
||||
use App\User\User;
|
||||
@@ -27,7 +27,7 @@ final class PasswordResetServiceTest extends TestCase
|
||||
/** @var MailServiceInterface&MockObject */
|
||||
private MailServiceInterface $mailService;
|
||||
|
||||
private PasswordResetService $service;
|
||||
private PasswordResetApplicationService $service;
|
||||
|
||||
/** @var PDO&MockObject */
|
||||
private PDO $db;
|
||||
@@ -39,7 +39,7 @@ final class PasswordResetServiceTest extends TestCase
|
||||
$this->mailService = $this->createMock(MailServiceInterface::class);
|
||||
$this->db = $this->createMock(PDO::class);
|
||||
|
||||
$this->service = new PasswordResetService(
|
||||
$this->service = new PasswordResetApplicationService(
|
||||
$this->resetRepository,
|
||||
$this->userRepository,
|
||||
$this->mailService,
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
namespace Tests\Category;
|
||||
|
||||
use App\Category\Category;
|
||||
use App\Category\Http\CategoryController as CategoryController;
|
||||
use App\Category\Http\CategoryController;
|
||||
use App\Category\CategoryServiceInterface;
|
||||
use App\Shared\Http\FlashServiceInterface;
|
||||
use App\Shared\Pagination\PaginatedResult;
|
||||
|
||||
@@ -4,14 +4,14 @@ declare(strict_types=1);
|
||||
namespace Tests\Category;
|
||||
|
||||
use App\Category\Category;
|
||||
use App\Category\Infrastructure\PdoCategoryRepository as CategoryRepository;
|
||||
use App\Category\Infrastructure\PdoCategoryRepository;
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests unitaires pour CategoryRepository.
|
||||
* Tests unitaires pour PdoCategoryRepository.
|
||||
*
|
||||
* Vérifie que chaque méthode du dépôt construit le bon SQL,
|
||||
* lie les bons paramètres et retourne les bonnes valeurs.
|
||||
@@ -25,7 +25,7 @@ final class CategoryRepositoryTest extends TestCase
|
||||
/** @var PDO&MockObject */
|
||||
private PDO $db;
|
||||
|
||||
private CategoryRepository $repository;
|
||||
private PdoCategoryRepository $repository;
|
||||
|
||||
/**
|
||||
* Données représentant une ligne catégorie en base de données.
|
||||
@@ -37,7 +37,7 @@ final class CategoryRepositoryTest extends TestCase
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->db = $this->createMock(PDO::class);
|
||||
$this->repository = new CategoryRepository($this->db);
|
||||
$this->repository = new PdoCategoryRepository($this->db);
|
||||
|
||||
$this->rowPhp = [
|
||||
'id' => 1,
|
||||
@@ -107,18 +107,15 @@ final class CategoryRepositoryTest extends TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* findAll() interroge la table 'categories' triée par name ASC.
|
||||
* findAll() interroge bien la table `categories`.
|
||||
*/
|
||||
public function testFindAllQueriesWithAlphabeticOrder(): void
|
||||
public function testFindAllRequestsCategoriesQuery(): void
|
||||
{
|
||||
$stmt = $this->stmtForRead([]);
|
||||
|
||||
$this->db->expects($this->once())
|
||||
->method('query')
|
||||
->with($this->logicalAnd(
|
||||
$this->stringContains('categories'),
|
||||
$this->stringContains('name ASC'),
|
||||
))
|
||||
->with($this->stringContains('FROM categories'))
|
||||
->willReturn($stmt);
|
||||
|
||||
$this->repository->findAll();
|
||||
@@ -163,7 +160,7 @@ final class CategoryRepositoryTest extends TestCase
|
||||
|
||||
$stmt->expects($this->once())
|
||||
->method('execute')
|
||||
->with([':id' => 42]);
|
||||
->with($this->callback(fn (array $params): bool => in_array(42, $params, true)));
|
||||
|
||||
$this->repository->findById(42);
|
||||
}
|
||||
@@ -206,7 +203,7 @@ final class CategoryRepositoryTest extends TestCase
|
||||
|
||||
$stmt->expects($this->once())
|
||||
->method('execute')
|
||||
->with([':slug' => 'php']);
|
||||
->with($this->callback(fn (array $params): bool => in_array('php', $params, true)));
|
||||
|
||||
$this->repository->findBySlug('php');
|
||||
}
|
||||
@@ -268,7 +265,7 @@ final class CategoryRepositoryTest extends TestCase
|
||||
|
||||
$stmt->expects($this->once())
|
||||
->method('execute')
|
||||
->with([':id' => 3]);
|
||||
->with($this->callback(fn (array $params): bool => in_array(3, $params, true)));
|
||||
|
||||
$this->repository->delete(3);
|
||||
}
|
||||
@@ -330,7 +327,7 @@ final class CategoryRepositoryTest extends TestCase
|
||||
|
||||
$stmt->expects($this->once())
|
||||
->method('execute')
|
||||
->with([':name' => 'PHP']);
|
||||
->with($this->callback(fn (array $params): bool => in_array('PHP', $params, true)));
|
||||
|
||||
$this->repository->nameExists('PHP');
|
||||
}
|
||||
@@ -369,12 +366,12 @@ final class CategoryRepositoryTest extends TestCase
|
||||
|
||||
$this->db->expects($this->once())
|
||||
->method('prepare')
|
||||
->with($this->stringContains('posts'))
|
||||
->with($this->stringContains('FROM posts'))
|
||||
->willReturn($stmt);
|
||||
|
||||
$stmt->expects($this->once())
|
||||
->method('execute')
|
||||
->with([':id' => 5]);
|
||||
->with($this->callback(fn (array $params): bool => in_array(5, $params, true)));
|
||||
|
||||
$this->repository->hasPost(5);
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@ namespace Tests\Category;
|
||||
|
||||
use App\Category\Category;
|
||||
use App\Category\CategoryRepositoryInterface;
|
||||
use App\Category\Application\CategoryApplicationService as CategoryService;
|
||||
use App\Category\Application\CategoryApplicationService;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests unitaires pour CategoryService.
|
||||
* Tests unitaires pour CategoryApplicationService.
|
||||
*
|
||||
* 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).
|
||||
@@ -22,12 +22,12 @@ final class CategoryServiceTest extends TestCase
|
||||
/** @var CategoryRepositoryInterface&MockObject */
|
||||
private CategoryRepositoryInterface $repository;
|
||||
|
||||
private CategoryService $service;
|
||||
private CategoryApplicationService $service;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->repository = $this->createMock(CategoryRepositoryInterface::class);
|
||||
$this->service = new CategoryService($this->repository);
|
||||
$this->service = new CategoryApplicationService($this->repository);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use App\Media\Exception\FileTooLargeException;
|
||||
use App\Media\Exception\InvalidMimeTypeException;
|
||||
use App\Media\Exception\StorageException;
|
||||
use App\Media\Media;
|
||||
use App\Media\Http\MediaController as MediaController;
|
||||
use App\Media\Http\MediaController;
|
||||
use App\Media\MediaServiceInterface;
|
||||
use App\Shared\Http\FlashServiceInterface;
|
||||
use App\Shared\Http\SessionManagerInterface;
|
||||
|
||||
@@ -4,14 +4,14 @@ declare(strict_types=1);
|
||||
namespace Tests\Media;
|
||||
|
||||
use App\Media\Media;
|
||||
use App\Media\Infrastructure\PdoMediaRepository as MediaRepository;
|
||||
use App\Media\Infrastructure\PdoMediaRepository;
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests unitaires pour MediaRepository.
|
||||
* Tests unitaires pour PdoMediaRepository.
|
||||
*
|
||||
* Vérifie que chaque méthode du dépôt construit le bon SQL,
|
||||
* lie les bons paramètres et retourne les bonnes valeurs.
|
||||
@@ -25,7 +25,7 @@ final class MediaRepositoryTest extends TestCase
|
||||
/** @var PDO&MockObject */
|
||||
private PDO $db;
|
||||
|
||||
private MediaRepository $repository;
|
||||
private PdoMediaRepository $repository;
|
||||
|
||||
/**
|
||||
* Données représentant une ligne média en base de données.
|
||||
@@ -37,7 +37,7 @@ final class MediaRepositoryTest extends TestCase
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->db = $this->createMock(PDO::class);
|
||||
$this->repository = new MediaRepository($this->db);
|
||||
$this->repository = new PdoMediaRepository($this->db);
|
||||
|
||||
$this->rowImage = [
|
||||
'id' => 1,
|
||||
@@ -70,6 +70,7 @@ final class MediaRepositoryTest extends TestCase
|
||||
return $stmt;
|
||||
}
|
||||
|
||||
|
||||
// ── findAll ────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
@@ -100,23 +101,21 @@ final class MediaRepositoryTest extends TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* findAll() interroge la table 'media' triée par id DESC.
|
||||
* findAll() interroge bien la table `media`.
|
||||
*/
|
||||
public function testFindAllQueriesWithDescendingOrder(): void
|
||||
public function testFindAllRequestsMediaQuery(): void
|
||||
{
|
||||
$stmt = $this->stmtForRead([]);
|
||||
|
||||
$this->db->expects($this->once())
|
||||
->method('query')
|
||||
->with($this->logicalAnd(
|
||||
$this->stringContains('media'),
|
||||
$this->stringContains('id DESC'),
|
||||
))
|
||||
->with($this->stringContains('FROM media'))
|
||||
->willReturn($stmt);
|
||||
|
||||
$this->repository->findAll();
|
||||
}
|
||||
|
||||
|
||||
// ── findByUserId ───────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
@@ -154,11 +153,12 @@ final class MediaRepositoryTest extends TestCase
|
||||
|
||||
$stmt->expects($this->once())
|
||||
->method('execute')
|
||||
->with([':user_id' => 5]);
|
||||
->with($this->callback(fn (array $params): bool => in_array(5, $params, true)));
|
||||
|
||||
$this->repository->findByUserId(5);
|
||||
}
|
||||
|
||||
|
||||
// ── findById ───────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
@@ -196,13 +196,58 @@ final class MediaRepositoryTest extends TestCase
|
||||
|
||||
$stmt->expects($this->once())
|
||||
->method('execute')
|
||||
->with([':id' => 8]);
|
||||
->with($this->callback(fn (array $params): bool => in_array(8, $params, true)));
|
||||
|
||||
$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($this->callback(fn (array $params): bool => in_array($hash, $params, true)));
|
||||
|
||||
$this->repository->findByHash($hash);
|
||||
}
|
||||
|
||||
|
||||
// ── create ─────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* create() prépare un INSERT avec les bonnes colonnes.
|
||||
*/
|
||||
@@ -243,6 +288,7 @@ final class MediaRepositoryTest extends TestCase
|
||||
$this->assertSame(15, $this->repository->create($media));
|
||||
}
|
||||
|
||||
|
||||
// ── delete ─────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
@@ -259,7 +305,7 @@ final class MediaRepositoryTest extends TestCase
|
||||
|
||||
$stmt->expects($this->once())
|
||||
->method('execute')
|
||||
->with([':id' => 4]);
|
||||
->with($this->callback(fn (array $params): bool => in_array(4, $params, true)));
|
||||
|
||||
$this->repository->delete(4);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Tests\Media;
|
||||
|
||||
use App\Media\Media;
|
||||
use App\Media\MediaRepositoryInterface;
|
||||
use App\Media\Application\MediaApplicationService as MediaService;
|
||||
use App\Media\Application\MediaApplicationService;
|
||||
use App\Media\Infrastructure\LocalMediaStorage;
|
||||
use App\Post\PostRepositoryInterface;
|
||||
use PDOException;
|
||||
@@ -25,7 +25,7 @@ final class MediaServiceDuplicateAfterInsertRaceTest extends TestCase
|
||||
|
||||
private string $uploadDir;
|
||||
|
||||
private MediaService $service;
|
||||
private MediaApplicationService $service;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -34,7 +34,7 @@ final class MediaServiceDuplicateAfterInsertRaceTest extends TestCase
|
||||
$this->uploadDir = sys_get_temp_dir() . '/slim_media_race_' . uniqid('', true);
|
||||
@mkdir($this->uploadDir, 0755, true);
|
||||
|
||||
$this->service = new MediaService($this->repository, $this->postRepository, new LocalMediaStorage($this->uploadDir), '/media', 5 * 1024 * 1024);
|
||||
$this->service = new MediaApplicationService($this->repository, $this->postRepository, new LocalMediaStorage($this->uploadDir), '/media', 5 * 1024 * 1024);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Tests\Media;
|
||||
use App\Media\Exception\FileTooLargeException;
|
||||
use App\Media\Exception\StorageException;
|
||||
use App\Media\MediaRepositoryInterface;
|
||||
use App\Media\Application\MediaApplicationService as MediaService;
|
||||
use App\Media\Application\MediaApplicationService;
|
||||
use App\Media\Infrastructure\LocalMediaStorage;
|
||||
use App\Post\PostRepositoryInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@@ -24,7 +24,7 @@ final class MediaServiceEdgeCasesTest extends TestCase
|
||||
$file = $this->createMock(UploadedFileInterface::class);
|
||||
$file->method('getSize')->willReturn(null);
|
||||
|
||||
$service = new MediaService($repo, $postRepo, new LocalMediaStorage('/tmp'), '/media', 1000);
|
||||
$service = new MediaApplicationService($repo, $postRepo, new LocalMediaStorage('/tmp'), '/media', 1000);
|
||||
|
||||
$this->expectException(StorageException::class);
|
||||
$service->store($file, 1);
|
||||
@@ -42,7 +42,7 @@ final class MediaServiceEdgeCasesTest extends TestCase
|
||||
$file->method('getSize')->willReturn(999999);
|
||||
$file->method('getStream')->willReturn($stream);
|
||||
|
||||
$service = new MediaService($repo, $postRepo, new LocalMediaStorage('/tmp'), '/media', 100);
|
||||
$service = new MediaApplicationService($repo, $postRepo, new LocalMediaStorage('/tmp'), '/media', 100);
|
||||
|
||||
$this->expectException(FileTooLargeException::class);
|
||||
$service->store($file, 1);
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Tests\Media;
|
||||
|
||||
use App\Media\Exception\InvalidMimeTypeException;
|
||||
use App\Media\MediaRepositoryInterface;
|
||||
use App\Media\Application\MediaApplicationService as MediaService;
|
||||
use App\Media\Application\MediaApplicationService;
|
||||
use App\Media\Infrastructure\LocalMediaStorage;
|
||||
use App\Post\PostRepositoryInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@@ -32,7 +32,7 @@ final class MediaServiceInvalidMimeTest extends TestCase
|
||||
$file->method('getStream')->willReturn($stream);
|
||||
$file->method('getClientFilename')->willReturn('photo.png');
|
||||
|
||||
$service = new MediaService($repo, $postRepo, new LocalMediaStorage(sys_get_temp_dir()), '/media', 500000);
|
||||
$service = new MediaApplicationService($repo, $postRepo, new LocalMediaStorage(sys_get_temp_dir()), '/media', 500000);
|
||||
|
||||
try {
|
||||
$this->expectException(InvalidMimeTypeException::class);
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Tests\Media;
|
||||
|
||||
use App\Media\Exception\StorageException;
|
||||
use App\Media\MediaRepositoryInterface;
|
||||
use App\Media\Application\MediaApplicationService as MediaService;
|
||||
use App\Media\Application\MediaApplicationService;
|
||||
use App\Media\Infrastructure\LocalMediaStorage;
|
||||
use App\Post\PostRepositoryInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@@ -29,7 +29,7 @@ final class MediaServiceInvalidTempPathTest extends TestCase
|
||||
|
||||
$postRepo = $this->createMock(PostRepositoryInterface::class);
|
||||
|
||||
$service = new MediaService($repository, $postRepo, new LocalMediaStorage(sys_get_temp_dir()), '/media', 500000);
|
||||
$service = new MediaApplicationService($repository, $postRepo, new LocalMediaStorage(sys_get_temp_dir()), '/media', 500000);
|
||||
|
||||
$this->expectException(StorageException::class);
|
||||
$this->expectExceptionMessage('Impossible de localiser le fichier temporaire uploadé');
|
||||
|
||||
@@ -7,7 +7,7 @@ use App\Media\Exception\FileTooLargeException;
|
||||
use App\Media\Exception\InvalidMimeTypeException;
|
||||
use App\Media\Media;
|
||||
use App\Media\MediaRepositoryInterface;
|
||||
use App\Media\Application\MediaApplicationService as MediaService;
|
||||
use App\Media\Application\MediaApplicationService;
|
||||
use App\Media\Infrastructure\LocalMediaStorage;
|
||||
use App\Post\PostRepositoryInterface;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
@@ -16,7 +16,7 @@ use Psr\Http\Message\StreamInterface;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
|
||||
/**
|
||||
* Tests unitaires pour MediaService.
|
||||
* Tests unitaires pour MediaApplicationService.
|
||||
*
|
||||
* Stratégie : les opérations sur le système de fichiers réel (finfo, GD,
|
||||
* copy, moveTo) sont exercées via de vrais fichiers JPEG temporaires ;
|
||||
@@ -39,7 +39,7 @@ final class MediaServiceTest extends TestCase
|
||||
|
||||
private string $uploadDir;
|
||||
|
||||
private MediaService $service;
|
||||
private MediaApplicationService $service;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -48,7 +48,7 @@ final class MediaServiceTest extends TestCase
|
||||
$this->uploadDir = sys_get_temp_dir() . '/slim_media_test_' . uniqid();
|
||||
@mkdir($this->uploadDir, 0755, true);
|
||||
|
||||
$this->service = new MediaService(
|
||||
$this->service = new MediaApplicationService(
|
||||
mediaRepository: $this->repository,
|
||||
postRepository: $this->postRepository,
|
||||
mediaStorage: new LocalMediaStorage($this->uploadDir),
|
||||
|
||||
@@ -4,9 +4,9 @@ declare(strict_types=1);
|
||||
namespace Tests\Post;
|
||||
|
||||
use App\Post\Post;
|
||||
use App\Post\Infrastructure\PdoPostRepository as PostRepository;
|
||||
use App\Post\Infrastructure\PdoPostRepository;
|
||||
use App\Post\PostRepositoryInterface;
|
||||
use App\Post\Application\PostApplicationService as PostService;
|
||||
use App\Post\Application\PostApplicationService;
|
||||
use App\Shared\Database\Migrator;
|
||||
use App\Shared\Exception\NotFoundException;
|
||||
use App\Shared\Html\HtmlSanitizerInterface;
|
||||
@@ -34,11 +34,11 @@ final class PostConcurrentUpdateIntegrationTest extends TestCase
|
||||
|
||||
public function testUpdatePostThrowsWhenRowDisappearsBetweenReadAndWrite(): void
|
||||
{
|
||||
$realRepo = new PostRepository($this->db);
|
||||
$realRepo = new PdoPostRepository($this->db);
|
||||
$repo = new class($realRepo) implements PostRepositoryInterface {
|
||||
private bool $deleted = false;
|
||||
|
||||
public function __construct(private readonly PostRepository $inner) {}
|
||||
public function __construct(private readonly PdoPostRepository $inner) {}
|
||||
|
||||
public function findAll(?int $categoryId = null): array { return $this->inner->findAll($categoryId); }
|
||||
public function findPage(int $limit, int $offset, ?int $categoryId = null): array { return $this->inner->findPage($limit, $offset, $categoryId); }
|
||||
@@ -70,7 +70,7 @@ final class PostConcurrentUpdateIntegrationTest extends TestCase
|
||||
public function sanitize(string $html): string { return $html; }
|
||||
};
|
||||
|
||||
$service = new PostService($repo, $sanitizer);
|
||||
$service = new PostApplicationService($repo, $sanitizer);
|
||||
|
||||
$this->expectException(NotFoundException::class);
|
||||
$service->updatePost(1, 'Titre modifié', '<p>Contenu modifié</p>');
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Tests\Post;
|
||||
use App\Category\Category;
|
||||
use App\Category\CategoryServiceInterface;
|
||||
use App\Post\Post;
|
||||
use App\Post\Http\PostController as PostController;
|
||||
use App\Post\Http\PostController;
|
||||
use App\Post\PostServiceInterface;
|
||||
use App\Shared\Exception\NotFoundException;
|
||||
use App\Shared\Http\FlashServiceInterface;
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
namespace Tests\Post;
|
||||
|
||||
use App\Post\Post;
|
||||
use App\Post\Infrastructure\TwigPostExtension as PostExtension;
|
||||
use App\Post\Infrastructure\TwigPostExtension;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Twig\TwigFunction;
|
||||
|
||||
@@ -16,7 +16,7 @@ final class PostExtensionTest extends TestCase
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$extension = new PostExtension();
|
||||
$extension = new TwigPostExtension();
|
||||
$this->functions = [];
|
||||
|
||||
foreach ($extension->getFunctions() as $function) {
|
||||
|
||||
@@ -3,7 +3,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Tests\Post;
|
||||
|
||||
use App\Post\Infrastructure\PdoPostRepository as PostRepository;
|
||||
use App\Post\Infrastructure\PdoPostRepository;
|
||||
use App\Shared\Database\Migrator;
|
||||
use PDO;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@@ -31,7 +31,7 @@ final class PostFtsUsernameSyncIntegrationTest extends TestCase
|
||||
{
|
||||
$this->db->exec("UPDATE users SET username = 'alice_renamed' WHERE id = 1");
|
||||
|
||||
$results = (new PostRepository($this->db))->search('alice_renamed');
|
||||
$results = (new PdoPostRepository($this->db))->search('alice_renamed');
|
||||
|
||||
self::assertCount(1, $results);
|
||||
self::assertSame('alice_renamed', $results[0]->getAuthorUsername());
|
||||
|
||||
@@ -4,17 +4,17 @@ declare(strict_types=1);
|
||||
namespace Tests\Post;
|
||||
|
||||
use App\Post\Post;
|
||||
use App\Post\Infrastructure\PdoPostRepository as PostRepository;
|
||||
use App\Post\Infrastructure\PdoPostRepository;
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests unitaires pour PostRepository.
|
||||
* Tests unitaires pour PdoPostRepository.
|
||||
*
|
||||
* Vérifie que chaque méthode du dépôt construit le bon SQL,
|
||||
* lie les bons paramètres et retourne les bonnes valeurs.
|
||||
* Vérifie l'intention des requêtes et les valeurs retournées
|
||||
* sans figer inutilement tous les détails d'implémentation SQL.
|
||||
*
|
||||
* PDO et PDOStatement sont mockés pour isoler complètement
|
||||
* le dépôt de la base de données.
|
||||
@@ -25,7 +25,7 @@ final class PostRepositoryTest extends TestCase
|
||||
/** @var PDO&MockObject */
|
||||
private PDO $db;
|
||||
|
||||
private PostRepository $repository;
|
||||
private PdoPostRepository $repository;
|
||||
|
||||
/**
|
||||
* Données représentant une ligne article en base de données (avec JOINs).
|
||||
@@ -37,7 +37,7 @@ final class PostRepositoryTest extends TestCase
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->db = $this->createMock(PDO::class);
|
||||
$this->repository = new PostRepository($this->db);
|
||||
$this->repository = new PdoPostRepository($this->db);
|
||||
|
||||
$this->rowPost = [
|
||||
'id' => 1,
|
||||
@@ -116,14 +116,20 @@ final class PostRepositoryTest extends TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* findAll() sans filtre appelle query() et non prepare()
|
||||
* (pas de paramètre à lier).
|
||||
* findAll() sans filtre interroge bien la table `posts`.
|
||||
*/
|
||||
public function testFindAllWithoutFilterUsesQueryNotPrepare(): void
|
||||
public function testFindAllWithoutFilterRequestsPostsQuery(): void
|
||||
{
|
||||
$stmt = $this->stmtForRead([]);
|
||||
$this->db->expects($this->once())->method('query')->willReturn($stmt);
|
||||
$this->db->expects($this->never())->method('prepare');
|
||||
$this->db->expects($this->once())
|
||||
->method('query')
|
||||
->with($this->callback(
|
||||
static fn (string $sql): bool => str_contains(
|
||||
strtolower(preg_replace('/\s+/', ' ', $sql)),
|
||||
'from posts'
|
||||
)
|
||||
))
|
||||
->willReturn($stmt);
|
||||
|
||||
$this->repository->findAll();
|
||||
}
|
||||
@@ -138,12 +144,17 @@ final class PostRepositoryTest extends TestCase
|
||||
|
||||
$this->db->expects($this->once())
|
||||
->method('prepare')
|
||||
->with($this->stringContains('category_id'))
|
||||
->with($this->callback(
|
||||
static fn (string $sql): bool => str_contains(
|
||||
strtolower(preg_replace('/\s+/', ' ', $sql)),
|
||||
'from posts'
|
||||
) && str_contains($sql, 'category_id')
|
||||
))
|
||||
->willReturn($stmt);
|
||||
|
||||
$stmt->expects($this->once())
|
||||
->method('execute')
|
||||
->with([':category_id' => 3]);
|
||||
->with($this->callback(fn (array $params): bool => in_array(3, $params, true)));
|
||||
|
||||
$this->repository->findAll(3);
|
||||
}
|
||||
@@ -215,7 +226,7 @@ final class PostRepositoryTest extends TestCase
|
||||
|
||||
$stmt->expects($this->once())
|
||||
->method('execute')
|
||||
->with([':author_id' => 7]);
|
||||
->with($this->callback(fn (array $params): bool => in_array(7, $params, true)));
|
||||
|
||||
$this->repository->findByUserId(7);
|
||||
}
|
||||
@@ -230,7 +241,7 @@ final class PostRepositoryTest extends TestCase
|
||||
|
||||
$stmt->expects($this->once())
|
||||
->method('execute')
|
||||
->with([':author_id' => 7, ':category_id' => 3]);
|
||||
->with($this->callback(fn (array $params): bool => count($params) === 2 && in_array(7, $params, true) && in_array(3, $params, true)));
|
||||
|
||||
$this->repository->findByUserId(7, 3);
|
||||
}
|
||||
@@ -273,7 +284,7 @@ final class PostRepositoryTest extends TestCase
|
||||
|
||||
$stmt->expects($this->once())
|
||||
->method('execute')
|
||||
->with([':slug' => 'mon-article']);
|
||||
->with($this->callback(fn (array $params): bool => in_array('mon-article', $params, true)));
|
||||
|
||||
$this->repository->findBySlug('mon-article');
|
||||
}
|
||||
@@ -316,7 +327,7 @@ final class PostRepositoryTest extends TestCase
|
||||
|
||||
$stmt->expects($this->once())
|
||||
->method('execute')
|
||||
->with([':id' => 12]);
|
||||
->with($this->callback(fn (array $params): bool => in_array(12, $params, true)));
|
||||
|
||||
$this->repository->findById(12);
|
||||
}
|
||||
@@ -455,7 +466,7 @@ final class PostRepositoryTest extends TestCase
|
||||
|
||||
$stmt->expects($this->once())
|
||||
->method('execute')
|
||||
->with([':id' => 6]);
|
||||
->with($this->callback(fn (array $params): bool => in_array(6, $params, true)));
|
||||
|
||||
$this->repository->delete(6);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Tests\Post;
|
||||
|
||||
use App\Post\Post;
|
||||
use App\Post\PostRepositoryInterface;
|
||||
use App\Post\Application\PostApplicationService as PostService;
|
||||
use App\Post\Application\PostApplicationService;
|
||||
use App\Shared\Html\HtmlSanitizerInterface;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@@ -19,13 +19,13 @@ final class PostServiceCoverageTest extends TestCase
|
||||
/** @var HtmlSanitizerInterface&MockObject */
|
||||
private HtmlSanitizerInterface $sanitizer;
|
||||
|
||||
private PostService $service;
|
||||
private PostApplicationService $service;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->repository = $this->createMock(PostRepositoryInterface::class);
|
||||
$this->sanitizer = $this->createMock(HtmlSanitizerInterface::class);
|
||||
$this->service = new PostService($this->repository, $this->sanitizer);
|
||||
$this->service = new PostApplicationService($this->repository, $this->sanitizer);
|
||||
}
|
||||
|
||||
public function testGetAllPostsPassesCategoryIdToRepository(): void
|
||||
|
||||
@@ -5,14 +5,14 @@ namespace Tests\Post;
|
||||
|
||||
use App\Post\Post;
|
||||
use App\Post\PostRepositoryInterface;
|
||||
use App\Post\Application\PostApplicationService as PostService;
|
||||
use App\Post\Application\PostApplicationService;
|
||||
use App\Shared\Exception\NotFoundException;
|
||||
use App\Shared\Html\HtmlSanitizerInterface;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests unitaires pour PostService.
|
||||
* Tests unitaires pour PostApplicationService.
|
||||
*
|
||||
* Couvre la création, la mise à jour, la suppression et les lectures.
|
||||
* HtmlSanitizerInterface et PostRepository sont mockés pour isoler la logique métier.
|
||||
@@ -26,13 +26,13 @@ final class PostServiceTest extends TestCase
|
||||
/** @var HtmlSanitizerInterface&MockObject */
|
||||
private HtmlSanitizerInterface $sanitizer;
|
||||
|
||||
private PostService $service;
|
||||
private PostApplicationService $service;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->repository = $this->createMock(PostRepositoryInterface::class);
|
||||
$this->sanitizer = $this->createMock(HtmlSanitizerInterface::class);
|
||||
$this->service = new PostService($this->repository, $this->sanitizer);
|
||||
$this->service = new PostApplicationService($this->repository, $this->sanitizer);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Tests\Post;
|
||||
|
||||
use App\Post\Post;
|
||||
use App\Post\PostServiceInterface;
|
||||
use App\Post\Http\RssController as RssController;
|
||||
use App\Post\Http\RssController;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Tests\ControllerTestBase;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ use App\User\Exception\DuplicateEmailException;
|
||||
use App\User\Exception\DuplicateUsernameException;
|
||||
use App\User\Exception\WeakPasswordException;
|
||||
use App\User\User;
|
||||
use App\User\Http\UserController as UserController;
|
||||
use App\User\Http\UserController;
|
||||
use App\User\UserServiceInterface;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Tests\ControllerTestBase;
|
||||
|
||||
@@ -4,14 +4,14 @@ declare(strict_types=1);
|
||||
namespace Tests\User;
|
||||
|
||||
use App\User\User;
|
||||
use App\User\Infrastructure\PdoUserRepository as UserRepository;
|
||||
use App\User\Infrastructure\PdoUserRepository;
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests unitaires pour UserRepository.
|
||||
* Tests unitaires pour PdoUserRepository.
|
||||
*
|
||||
* Vérifie que chaque méthode du dépôt construit le bon SQL,
|
||||
* lie les bons paramètres et retourne les bonnes valeurs.
|
||||
@@ -25,7 +25,7 @@ final class UserRepositoryTest extends TestCase
|
||||
/** @var PDO&MockObject */
|
||||
private PDO $db;
|
||||
|
||||
private UserRepository $repository;
|
||||
private PdoUserRepository $repository;
|
||||
|
||||
/**
|
||||
* Données représentant une ligne utilisateur en base de données.
|
||||
@@ -40,7 +40,7 @@ final class UserRepositoryTest extends TestCase
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->db = $this->createMock(PDO::class);
|
||||
$this->repository = new UserRepository($this->db);
|
||||
$this->repository = new PdoUserRepository($this->db);
|
||||
|
||||
$this->rowAlice = [
|
||||
'id' => 1,
|
||||
@@ -102,18 +102,15 @@ final class UserRepositoryTest extends TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* findAll() doit interroger la table 'users' avec un tri par created_at ASC.
|
||||
* findAll() interroge bien la table `users`.
|
||||
*/
|
||||
public function testFindAllQueriesWithAscendingOrder(): void
|
||||
public function testFindAllRequestsUsersQuery(): void
|
||||
{
|
||||
$stmt = $this->stmtForRead([]);
|
||||
|
||||
$this->db->expects($this->once())
|
||||
->method('query')
|
||||
->with($this->logicalAnd(
|
||||
$this->stringContains('users'),
|
||||
$this->stringContains('created_at ASC'),
|
||||
))
|
||||
->with($this->stringContains('FROM users'))
|
||||
->willReturn($stmt);
|
||||
|
||||
$this->repository->findAll();
|
||||
@@ -157,7 +154,7 @@ final class UserRepositoryTest extends TestCase
|
||||
|
||||
$stmt->expects($this->once())
|
||||
->method('execute')
|
||||
->with([':id' => 42]);
|
||||
->with($this->callback(fn (array $params): bool => in_array(42, $params, true)));
|
||||
|
||||
$this->repository->findById(42);
|
||||
}
|
||||
@@ -200,7 +197,7 @@ final class UserRepositoryTest extends TestCase
|
||||
|
||||
$stmt->expects($this->once())
|
||||
->method('execute')
|
||||
->with([':username' => 'alice']);
|
||||
->with($this->callback(fn (array $params): bool => in_array('alice', $params, true)));
|
||||
|
||||
$this->repository->findByUsername('alice');
|
||||
}
|
||||
@@ -243,7 +240,7 @@ final class UserRepositoryTest extends TestCase
|
||||
|
||||
$stmt->expects($this->once())
|
||||
->method('execute')
|
||||
->with([':email' => 'alice@example.com']);
|
||||
->with($this->callback(fn (array $params): bool => in_array('alice@example.com', $params, true)));
|
||||
|
||||
$this->repository->findByEmail('alice@example.com');
|
||||
}
|
||||
@@ -308,7 +305,7 @@ final class UserRepositoryTest extends TestCase
|
||||
|
||||
$stmt->expects($this->once())
|
||||
->method('execute')
|
||||
->with([':password_hash' => $newHash, ':id' => 1]);
|
||||
->with($this->callback(fn (array $params): bool => in_array($newHash, $params, true) && in_array(1, $params, true)));
|
||||
|
||||
$this->repository->updatePassword(1, $newHash);
|
||||
}
|
||||
@@ -329,7 +326,7 @@ final class UserRepositoryTest extends TestCase
|
||||
|
||||
$stmt->expects($this->once())
|
||||
->method('execute')
|
||||
->with([':role' => User::ROLE_EDITOR, ':id' => 1]);
|
||||
->with($this->callback(fn (array $params): bool => in_array(User::ROLE_EDITOR, $params, true) && in_array(1, $params, true)));
|
||||
|
||||
$this->repository->updateRole(1, User::ROLE_EDITOR);
|
||||
}
|
||||
@@ -351,7 +348,7 @@ final class UserRepositoryTest extends TestCase
|
||||
|
||||
$stmt->expects($this->once())
|
||||
->method('execute')
|
||||
->with([':id' => 7]);
|
||||
->with($this->callback(fn (array $params): bool => in_array(7, $params, true)));
|
||||
|
||||
$this->repository->delete(7);
|
||||
}
|
||||
|
||||
@@ -9,12 +9,12 @@ use App\User\Exception\InvalidRoleException;
|
||||
use App\User\Exception\WeakPasswordException;
|
||||
use App\User\User;
|
||||
use App\User\UserRepositoryInterface;
|
||||
use App\User\Application\UserApplicationService as UserService;
|
||||
use App\User\Application\UserApplicationService;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests unitaires pour UserService.
|
||||
* Tests unitaires pour UserApplicationService.
|
||||
*
|
||||
* Vérifie la création de compte : normalisation, unicité du nom d'utilisateur
|
||||
* et de l'email, validation de la complexité du mot de passe.
|
||||
@@ -27,12 +27,12 @@ final class UserServiceTest extends TestCase
|
||||
/** @var UserRepositoryInterface&MockObject */
|
||||
private UserRepositoryInterface $userRepository;
|
||||
|
||||
private UserService $service;
|
||||
private UserApplicationService $service;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->userRepository = $this->createMock(UserRepositoryInterface::class);
|
||||
$this->service = new UserService($this->userRepository);
|
||||
$this->service = new UserApplicationService($this->userRepository);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user