Refatoring : Working state

This commit is contained in:
julien
2026-03-16 16:58:54 +01:00
parent 0453697cd3
commit e0f7c77d6e
54 changed files with 287 additions and 279 deletions

View File

@@ -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())