userRepository = $this->createMock(UserRepositoryInterface::class); $this->sessionManager = $this->createMock(SessionManagerInterface::class); $this->loginAttemptRepository = $this->createMock(LoginAttemptRepositoryInterface::class); $this->service = new AuthService( $this->userRepository, $this->sessionManager, $this->loginAttemptRepository, ); } // ── checkRateLimit ───────────────────────────────────────────── /** * checkRateLimit() doit retourner 0 si l'IP n'a aucune entrée en base. */ public function testCheckRateLimitUnknownIpReturnsZero(): void { $this->loginAttemptRepository->method('deleteExpired'); $this->loginAttemptRepository->method('findByIp')->willReturn(null); $result = $this->service->checkRateLimit('192.168.1.1'); $this->assertSame(0, $result); } /** * checkRateLimit() doit retourner 0 si l'IP a des tentatives * mais n'est pas encore verrouillée (locked_until = null). */ public function testCheckRateLimitNoLockReturnsZero(): void { $this->loginAttemptRepository->method('deleteExpired'); $this->loginAttemptRepository->method('findByIp')->willReturn([ 'ip' => '192.168.1.1', 'attempts' => 3, 'locked_until' => null, 'updated_at' => date('Y-m-d H:i:s'), ]); $result = $this->service->checkRateLimit('192.168.1.1'); $this->assertSame(0, $result); } /** * checkRateLimit() doit retourner le nombre de minutes restantes * si l'IP est actuellement verrouillée. */ public function testCheckRateLimitLockedIpReturnsRemainingMinutes(): void { $lockedUntil = date('Y-m-d H:i:s', time() + 10 * 60); $this->loginAttemptRepository->method('deleteExpired'); $this->loginAttemptRepository->method('findByIp')->willReturn([ 'ip' => '192.168.1.1', 'attempts' => 5, 'locked_until' => $lockedUntil, 'updated_at' => date('Y-m-d H:i:s'), ]); $result = $this->service->checkRateLimit('192.168.1.1'); $this->assertGreaterThan(0, $result); $this->assertLessThanOrEqual(15, $result); } /** * checkRateLimit() doit retourner au minimum 1 minute * même si le verrouillage expire dans quelques secondes. */ public function testCheckRateLimitReturnsAtLeastOneMinute(): void { $lockedUntil = date('Y-m-d H:i:s', time() + 30); $this->loginAttemptRepository->method('deleteExpired'); $this->loginAttemptRepository->method('findByIp')->willReturn([ 'ip' => '192.168.1.1', 'attempts' => 5, 'locked_until' => $lockedUntil, 'updated_at' => date('Y-m-d H:i:s'), ]); $result = $this->service->checkRateLimit('192.168.1.1'); $this->assertSame(1, $result); } /** * checkRateLimit() doit retourner 0 si le verrouillage est expiré. */ public function testCheckRateLimitExpiredLockReturnsZero(): void { $lockedUntil = date('Y-m-d H:i:s', time() - 60); $this->loginAttemptRepository->method('deleteExpired'); $this->loginAttemptRepository->method('findByIp')->willReturn([ 'ip' => '192.168.1.1', 'attempts' => 5, 'locked_until' => $lockedUntil, 'updated_at' => date('Y-m-d H:i:s'), ]); $result = $this->service->checkRateLimit('192.168.1.1'); $this->assertSame(0, $result); } /** * checkRateLimit() doit toujours appeler deleteExpired() avant la vérification. */ public function testCheckRateLimitCallsDeleteExpired(): void { $this->loginAttemptRepository ->expects($this->once()) ->method('deleteExpired'); $this->loginAttemptRepository->method('findByIp')->willReturn(null); $this->service->checkRateLimit('192.168.1.1'); } // ── recordFailure ────────────────────────────────────────────── /** * recordFailure() doit déléguer avec MAX_ATTEMPTS = 5 et LOCK_MINUTES = 15. */ public function testRecordFailureDelegatesWithConstants(): void { $this->loginAttemptRepository ->expects($this->once()) ->method('recordFailure') ->with('192.168.1.1', 5, 15); $this->service->recordFailure('192.168.1.1'); } /** * recordFailure() doit transmettre l'adresse IP exacte au dépôt. */ public function testRecordFailurePassesIpAddress(): void { $ip = '10.0.0.42'; $this->loginAttemptRepository ->expects($this->once()) ->method('recordFailure') ->with($ip, $this->anything(), $this->anything()); $this->service->recordFailure($ip); } // ── resetRateLimit ───────────────────────────────────────────── /** * resetRateLimit() doit appeler resetForIp() avec l'adresse IP fournie. */ public function testResetRateLimitCallsResetForIp(): void { $ip = '192.168.1.1'; $this->loginAttemptRepository ->expects($this->once()) ->method('resetForIp') ->with($ip); $this->service->resetRateLimit($ip); } /** * resetRateLimit() ne doit pas appeler recordFailure() ni deleteExpired(). */ public function testResetRateLimitCallsNothingElse(): void { $this->loginAttemptRepository->expects($this->never())->method('recordFailure'); $this->loginAttemptRepository->expects($this->never())->method('deleteExpired'); $this->loginAttemptRepository->method('resetForIp'); $this->service->resetRateLimit('192.168.1.1'); } }