Files
slim-blog/tests/Auth/AuthServiceTest.php
2026-03-16 02:33:18 +01:00

246 lines
8.3 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Auth;
use App\Auth\AuthService;
use App\Auth\LoginAttemptRepositoryInterface;
use App\Shared\Exception\NotFoundException;
use App\Shared\Http\SessionManagerInterface;
use App\User\Exception\WeakPasswordException;
use App\User\User;
use App\User\UserRepositoryInterface;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
/**
* Tests unitaires pour AuthService.
*
* Vérifie l'authentification, le changement de mot de passe et la gestion
* des sessions. La création de comptes est couverte par UserServiceTest.
* Les dépendances sont remplacées par des mocks via leurs interfaces pour
* isoler le service.
*/
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class AuthServiceTest extends TestCase
{
/** @var UserRepositoryInterface&MockObject */
private UserRepositoryInterface $userRepository;
/** @var SessionManagerInterface&MockObject */
private SessionManagerInterface $sessionManager;
/** @var LoginAttemptRepositoryInterface&MockObject */
private LoginAttemptRepositoryInterface $loginAttemptRepository;
private AuthService $service;
protected function setUp(): void
{
$this->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,
);
}
// ── authenticate ───────────────────────────────────────────────
/**
* authenticate() doit retourner l'utilisateur si les identifiants sont corrects.
*/
public function testAuthenticateValidCredentials(): void
{
$password = 'motdepasse1';
$user = $this->makeUser('alice', 'alice@example.com', $password);
$this->userRepository->expects($this->once())->method('findByUsername')->with('alice')->willReturn($user);
$result = $this->service->authenticate('alice', $password);
$this->assertSame($user, $result);
}
/**
* authenticate() doit normaliser le nom d'utilisateur en minuscules.
*/
public function testAuthenticateNormalizesUsername(): void
{
$password = 'motdepasse1';
$user = $this->makeUser('alice', 'alice@example.com', $password);
$this->userRepository->expects($this->once())->method('findByUsername')->with('alice')->willReturn($user);
$result = $this->service->authenticate('ALICE', $password);
$this->assertNotNull($result);
}
/**
* authenticate() doit retourner null si l'utilisateur est introuvable.
*/
public function testAuthenticateUnknownUser(): void
{
$this->userRepository->method('findByUsername')->willReturn(null);
$result = $this->service->authenticate('inconnu', 'motdepasse1');
$this->assertNull($result);
}
/**
* authenticate() doit retourner null si le mot de passe est incorrect.
*/
public function testAuthenticateWrongPassword(): void
{
$user = $this->makeUser('alice', 'alice@example.com', 'bonmotdepasse');
$this->userRepository->method('findByUsername')->willReturn($user);
$result = $this->service->authenticate('alice', 'mauvaismdp');
$this->assertNull($result);
}
// ── changePassword ─────────────────────────────────────────────
/**
* changePassword() doit mettre à jour le hash si les données sont valides.
*/
public function testChangePasswordWithValidData(): void
{
$password = 'ancienmdp1';
$user = $this->makeUser('alice', 'alice@example.com', $password, 1);
$this->userRepository->expects($this->once())->method('findById')->with(1)->willReturn($user);
$this->userRepository->expects($this->once())->method('updatePassword')->with(1);
$this->service->changePassword(1, $password, 'nouveaumdp1');
}
/**
* changePassword() doit lever une exception si le mot de passe actuel est incorrect.
*/
public function testChangePasswordWrongCurrentPassword(): void
{
$user = $this->makeUser('alice', 'alice@example.com', 'bonmotdepasse', 1);
$this->userRepository->method('findById')->willReturn($user);
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessageMatches('/actuel incorrect/');
$this->service->changePassword(1, 'mauvaismdp', 'nouveaumdp1');
}
/**
* changePassword() avec exactement 8 caractères doit réussir (frontière basse).
*/
public function testChangePasswordMinimumLengthNewPassword(): void
{
$password = 'ancienmdp1';
$user = $this->makeUser('alice', 'alice@example.com', $password, 1);
$this->userRepository->method('findById')->willReturn($user);
$this->userRepository->expects($this->once())->method('updatePassword');
// Ne doit pas lever d'exception
$this->service->changePassword(1, $password, '12345678');
$this->addToAssertionCount(1);
}
/**
* changePassword() doit lever WeakPasswordException si le nouveau mot de passe est trop court.
*/
public function testChangePasswordTooShortNewPasswordThrowsWeakPasswordException(): void
{
$password = 'ancienmdp1';
$user = $this->makeUser('alice', 'alice@example.com', $password, 1);
$this->userRepository->method('findById')->willReturn($user);
$this->expectException(WeakPasswordException::class);
$this->service->changePassword(1, $password, '1234567');
}
/**
* changePassword() doit lever NotFoundException si l'utilisateur est introuvable.
*/
public function testChangePasswordUnknownUserThrowsNotFoundException(): void
{
$this->userRepository->method('findById')->willReturn(null);
$this->expectException(NotFoundException::class);
$this->service->changePassword(99, 'ancienmdp1', 'nouveaumdp1');
}
// ── login / logout / isLoggedIn ────────────────────────────────
/**
* login() doit appeler SessionManager::setUser() avec les bonnes données.
*/
public function testLoginCallsSetUser(): void
{
$user = $this->makeUser('alice', 'alice@example.com', 'secret', 7, User::ROLE_ADMIN);
$this->sessionManager->expects($this->once())
->method('setUser')
->with(7, 'alice', User::ROLE_ADMIN);
$this->service->login($user);
}
/**
* logout() doit appeler SessionManager::destroy().
*/
public function testLogoutCallsDestroy(): void
{
$this->sessionManager->expects($this->once())->method('destroy');
$this->service->logout();
}
/**
* isLoggedIn() doit déléguer à SessionManager::isAuthenticated().
*/
public function testIsLoggedInDelegatesToSessionManager(): void
{
$this->sessionManager->method('isAuthenticated')->willReturn(true);
$this->assertTrue($this->service->isLoggedIn());
}
// ── Helpers ────────────────────────────────────────────────────
/**
* Crée un utilisateur de test avec un hash bcrypt du mot de passe fourni.
*
* @param string $username Nom d'utilisateur
* @param string $email Adresse e-mail
* @param string $password Mot de passe en clair (haché en bcrypt)
* @param int $id Identifiant (défaut : 1)
* @param string $role Rôle (défaut : 'user')
*/
private function makeUser(
string $username,
string $email,
string $password = 'motdepasse1',
int $id = 1,
string $role = User::ROLE_USER,
): User {
return new User(
$id,
$username,
$email,
password_hash($password, PASSWORD_BCRYPT),
$role,
);
}
}