fix(tests) : fiabilise la suite PHPUnit contre la derive d'horloge (ERP-98)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Failing after 3m39s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m15s

La suite echouait de facon intermittente (~1 run sur 2), faisant planter le
hook pre-commit. Cause racine unique : l'horloge CLOCK_REALTIME du conteneur
n'est pas monotone sous WSL2/Docker (saut arriere sous charge), alors que le
code et les tests supposaient une horloge stable.

- JWT « Invalid JWT Token » (401) : lexik validait iat/nbf/exp avec
  clock_skew=0 (LooseValidAt PT0S). Un recul d'horloge apres /login_check
  rendait le token « dans le futur ». Ajout de clock_skew: 15 (benefice prod
  aussi si les noeuds derivent).

- Horodatages « meme seconde » : colonnes TIMESTAMP(0) + sleep(1) reel.
  L'ecart floor-seconde n'etait nul que si l'horloge reculait. Le subscriber
  injecte desormais ClockInterface (comportement prod inchange via NativeClock)
  et les tests pilotent un MockClock fige/avance (ClockSensitiveTrait), sans
  sleep reel -> deterministe et plus rapide. Le mock est seede dans le fuseau
  PHP par defaut pour eviter le decalage UTC <-> Europe/Paris au round-trip
  des colonnes TIMESTAMP WITHOUT TIME ZONE.

make test : 464 tests verts ; test timestamp 5/5 deterministe.
This commit is contained in:
Matthieu
2026-06-02 17:35:12 +02:00
parent 583d634a83
commit dfd6e5251d
4 changed files with 69 additions and 16 deletions
@@ -14,6 +14,7 @@ use Doctrine\ORM\Event\PrePersistEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use PHPUnit\Framework\TestCase;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Clock\MockClock;
use Symfony\Component\Security\Core\User\UserInterface;
/**
@@ -30,7 +31,7 @@ final class TimestampableBlamableSubscriberTest extends TestCase
public function testPrePersistWithUser(): void
{
$user = $this->createStub(UserInterface::class);
$subscriber = new TimestampableBlamableSubscriber($this->securityReturning($user));
$subscriber = new TimestampableBlamableSubscriber($this->securityReturning($user), new MockClock());
$entity = new FullAuditableFixture();
$subscriber->prePersist($this->prePersistArgs($entity));
@@ -45,7 +46,7 @@ final class TimestampableBlamableSubscriberTest extends TestCase
public function testPrePersistWithoutUser(): void
{
$subscriber = new TimestampableBlamableSubscriber($this->securityReturning(null));
$subscriber = new TimestampableBlamableSubscriber($this->securityReturning(null), new MockClock());
$entity = new FullAuditableFixture();
$subscriber->prePersist($this->prePersistArgs($entity));
@@ -59,8 +60,13 @@ final class TimestampableBlamableSubscriberTest extends TestCase
public function testPreUpdate(): void
{
$user = $this->createStub(UserInterface::class);
$subscriber = new TimestampableBlamableSubscriber($this->securityReturning($user));
$user = $this->createStub(UserInterface::class);
// Horloge figee 1s apres le createdAt simule : updatedAt doit avancer
// de facon deterministe, sans dependre de l'heure reelle.
$subscriber = new TimestampableBlamableSubscriber(
$this->securityReturning($user),
new MockClock(new DateTimeImmutable('2020-01-01 10:00:01')),
);
// On simule une entite deja persistee : createdAt fige dans le passe,
// createdBy positionne par une creation anterieure.
@@ -80,7 +86,7 @@ final class TimestampableBlamableSubscriberTest extends TestCase
public function testPartialEntityTimestampableOnly(): void
{
$user = $this->createStub(UserInterface::class);
$subscriber = new TimestampableBlamableSubscriber($this->securityReturning($user));
$subscriber = new TimestampableBlamableSubscriber($this->securityReturning($user), new MockClock());
$entity = new TimestampableOnlyFixture();
// Entite Timestampable mais NON Blamable : seules les dates sont posees,