auditLogger = $this->createMock(AuditLogger::class); $this->handler = new AuditAuthenticationHandler( $this->auditLogger, 'test-secret', ); } public function testHandleConnexionReussieLogsSuccessfulLogin(): void { $userId = Uuid::uuid4()->toString(); $event = new ConnexionReussie( userId: $userId, email: 'user@example.com', tenantId: TenantId::generate(), ipAddress: '192.168.1.1', userAgent: 'Mozilla/5.0', occurredOn: new DateTimeImmutable(), ); $this->auditLogger->expects($this->once()) ->method('logAuthentication') ->with( $this->equalTo('ConnexionReussie'), $this->callback(static fn ($uuid) => $uuid->toString() === $userId), $this->callback(static fn ($payload) => $payload['result'] === 'success' && $payload['method'] === 'password' && isset($payload['email_hash']) ), ); $this->handler->handleConnexionReussie($event); } public function testHandleConnexionEchoueeLogsFailedLogin(): void { $tenantId = TenantId::generate(); $event = new ConnexionEchouee( email: 'user@example.com', tenantId: $tenantId, ipAddress: '192.168.1.1', userAgent: 'Mozilla/5.0', reason: 'invalid_credentials', occurredOn: new DateTimeImmutable(), ); $this->auditLogger->expects($this->once()) ->method('logAuthentication') ->with( $this->equalTo('ConnexionEchouee'), $this->isNull(), $this->callback(static fn ($payload) => $payload['result'] === 'failure' && $payload['reason'] === 'invalid_credentials' && isset($payload['email_hash']) ), $this->equalTo((string) $tenantId), ); $this->handler->handleConnexionEchouee($event); } public function testHandleCompteBloqueTemporairementLogsLockout(): void { $tenantId = TenantId::generate(); $event = new CompteBloqueTemporairement( email: 'user@example.com', tenantId: $tenantId, ipAddress: '192.168.1.1', userAgent: 'Mozilla/5.0', blockedForSeconds: 300, failedAttempts: 5, occurredOn: new DateTimeImmutable(), ); $this->auditLogger->expects($this->once()) ->method('logAuthentication') ->with( $this->equalTo('CompteBloqueTemporairement'), $this->isNull(), $this->callback(static fn ($payload) => $payload['blocked_for_seconds'] === 300 && $payload['failed_attempts'] === 5 && isset($payload['email_hash']) ), $this->equalTo((string) $tenantId), ); $this->handler->handleCompteBloqueTemporairement($event); } public function testHandleMotDePasseChangeLogsPasswordChange(): void { $userId = Uuid::uuid4()->toString(); $event = new MotDePasseChange( userId: $userId, email: 'user@example.com', tenantId: TenantId::generate(), occurredOn: new DateTimeImmutable(), ); $this->auditLogger->expects($this->once()) ->method('logAuthentication') ->with( $this->equalTo('MotDePasseChange'), $this->callback(static fn ($uuid) => $uuid->toString() === $userId), $this->callback(static fn ($payload) => isset($payload['email_hash'])), ); $this->handler->handleMotDePasseChange($event); } public function testEmailIsHashedConsistently(): void { $email = 'user@example.com'; $expectedHash = hash('sha256', strtolower($email) . 'test-secret'); $capturedPayload = null; $this->auditLogger->expects($this->once()) ->method('logAuthentication') ->willReturnCallback(static function ($eventType, $userId, $payload) use (&$capturedPayload) { $capturedPayload = $payload; }); $event = new ConnexionReussie( userId: Uuid::uuid4()->toString(), email: $email, tenantId: TenantId::generate(), ipAddress: '192.168.1.1', userAgent: 'Mozilla/5.0', occurredOn: new DateTimeImmutable(), ); $this->handler->handleConnexionReussie($event); $this->assertSame($expectedHash, $capturedPayload['email_hash']); } public function testEmailHashIsCaseInsensitive(): void { $lowerEmail = 'user@example.com'; $upperEmail = 'USER@EXAMPLE.COM'; $expectedHash = hash('sha256', strtolower($lowerEmail) . 'test-secret'); $payloads = []; $this->auditLogger->expects($this->exactly(2)) ->method('logAuthentication') ->willReturnCallback(static function ($eventType, $userId, $payload) use (&$payloads) { $payloads[] = $payload; }); $event1 = new ConnexionReussie( userId: Uuid::uuid4()->toString(), email: $lowerEmail, tenantId: TenantId::generate(), ipAddress: '192.168.1.1', userAgent: 'Mozilla/5.0', occurredOn: new DateTimeImmutable(), ); $event2 = new ConnexionReussie( userId: Uuid::uuid4()->toString(), email: $upperEmail, tenantId: TenantId::generate(), ipAddress: '192.168.1.1', userAgent: 'Mozilla/5.0', occurredOn: new DateTimeImmutable(), ); $this->handler->handleConnexionReussie($event1); $this->handler->handleConnexionReussie($event2); $this->assertSame($payloads[0]['email_hash'], $payloads[1]['email_hash']); } }