first commit
This commit is contained in:
286
tests/Identity/AuthServiceTest.php
Normal file
286
tests/Identity/AuthServiceTest.php
Normal file
@@ -0,0 +1,286 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Identity;
|
||||
|
||||
use Netig\Netslim\Identity\Application\AuthApplicationService;
|
||||
use Netig\Netslim\Identity\Application\AuthSessionInterface;
|
||||
use Netig\Netslim\Identity\Application\UseCase\AuthenticateUser;
|
||||
use Netig\Netslim\Identity\Application\UseCase\ChangePassword;
|
||||
use Netig\Netslim\Identity\Domain\Entity\User;
|
||||
use Netig\Netslim\Identity\Domain\Exception\WeakPasswordException;
|
||||
use Netig\Netslim\Identity\Domain\Policy\LoginRateLimitPolicy;
|
||||
use Netig\Netslim\Identity\Domain\Policy\PasswordPolicy;
|
||||
use Netig\Netslim\Identity\Domain\Repository\LoginAttemptRepositoryInterface;
|
||||
use Netig\Netslim\Identity\Domain\Repository\UserRepositoryInterface;
|
||||
use Netig\Netslim\Kernel\Support\Exception\NotFoundException;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests unitaires pour AuthApplicationService.
|
||||
*
|
||||
* 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 AuthSessionInterface&MockObject */
|
||||
private AuthSessionInterface $authSession;
|
||||
|
||||
/** @var LoginAttemptRepositoryInterface&MockObject */
|
||||
private LoginAttemptRepositoryInterface $loginAttemptRepository;
|
||||
|
||||
private AuthApplicationService $service;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->userRepository = $this->createMock(UserRepositoryInterface::class);
|
||||
$this->authSession = $this->createMock(AuthSessionInterface::class);
|
||||
$this->loginAttemptRepository = $this->createMock(LoginAttemptRepositoryInterface::class);
|
||||
|
||||
$this->service = new AuthApplicationService(
|
||||
$this->authSession,
|
||||
$this->loginAttemptRepository,
|
||||
new LoginRateLimitPolicy(),
|
||||
new AuthenticateUser($this->userRepository, new PasswordPolicy()),
|
||||
new ChangePassword($this->userRepository, new PasswordPolicy()),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// ── authenticate ───────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* authenticate() doit retourner l'utilisateur si les identifiants sont corrects.
|
||||
*/
|
||||
public function testAuthenticateValidCredentials(): void
|
||||
{
|
||||
$password = 'motdepasse12';
|
||||
$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 = 'motdepasse12';
|
||||
$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() ne doit pas supprimer les espaces du mot de passe saisi.
|
||||
*/
|
||||
public function testAuthenticatePreservesPasswordWhitespace(): void
|
||||
{
|
||||
$password = ' motdepasse12 ';
|
||||
$user = $this->makeUser('alice', 'alice@example.com', $password);
|
||||
|
||||
$this->userRepository->expects($this->exactly(2))->method('findByUsername')->with('alice')->willReturn($user);
|
||||
|
||||
$this->assertSame($user, $this->service->authenticate('alice', $password));
|
||||
$this->assertNull($this->service->authenticate('alice', 'motdepasse12'));
|
||||
}
|
||||
|
||||
/**
|
||||
* authenticate() doit retourner null si l'utilisateur est introuvable.
|
||||
*/
|
||||
public function testAuthenticateUnknownUser(): void
|
||||
{
|
||||
$this->userRepository->method('findByUsername')->willReturn(null);
|
||||
|
||||
$result = $this->service->authenticate('inconnu', 'motdepasse12');
|
||||
|
||||
$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 = 'ancienmdp12';
|
||||
$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, 'nouveaumdp12');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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', 'nouveaumdp12');
|
||||
}
|
||||
|
||||
/**
|
||||
* changePassword() doit conserver exactement les espaces du nouveau mot de passe.
|
||||
*/
|
||||
public function testChangePasswordPreservesNewPasswordWhitespace(): void
|
||||
{
|
||||
$currentPassword = 'ancienmdp12';
|
||||
$newPassword = ' nouveaumdp12 ';
|
||||
$user = $this->makeUser('alice', 'alice@example.com', $currentPassword, 1);
|
||||
|
||||
$this->userRepository->expects($this->once())->method('findById')->with(1)->willReturn($user);
|
||||
$this->userRepository->expects($this->once())
|
||||
->method('updatePassword')
|
||||
->with(1, $this->callback(static function (string $hash) use ($newPassword): bool {
|
||||
return password_verify($newPassword, $hash)
|
||||
&& !password_verify(trim($newPassword), $hash);
|
||||
}));
|
||||
|
||||
$this->service->changePassword(1, $currentPassword, $newPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* changePassword() avec exactement 12 caractères doit réussir (frontière basse).
|
||||
*/
|
||||
public function testChangePasswordMinimumLengthNewPassword(): void
|
||||
{
|
||||
$password = 'ancienmdp12';
|
||||
$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, '123456789012');
|
||||
$this->addToAssertionCount(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* changePassword() doit lever WeakPasswordException si le nouveau mot de passe est trop court.
|
||||
*/
|
||||
public function testChangePasswordTooShortNewPasswordThrowsWeakPasswordException(): void
|
||||
{
|
||||
$password = 'ancienmdp12';
|
||||
$user = $this->makeUser('alice', 'alice@example.com', $password, 1);
|
||||
$this->userRepository->method('findById')->willReturn($user);
|
||||
|
||||
$this->expectException(WeakPasswordException::class);
|
||||
|
||||
$this->service->changePassword(1, $password, '12345678901');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, 'ancienmdp12', 'nouveaumdp12');
|
||||
}
|
||||
|
||||
|
||||
// ── 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->authSession->expects($this->once())
|
||||
->method('startForUser')
|
||||
->with($user);
|
||||
|
||||
$this->service->login($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* logout() doit appeler SessionManager::destroy().
|
||||
*/
|
||||
public function testLogoutCallsDestroy(): void
|
||||
{
|
||||
$this->authSession->expects($this->once())->method('clear');
|
||||
|
||||
$this->service->logout();
|
||||
}
|
||||
|
||||
/**
|
||||
* isLoggedIn() doit déléguer à SessionManager::isAuthenticated().
|
||||
*/
|
||||
public function testIsLoggedInDelegatesToSessionManager(): void
|
||||
{
|
||||
$this->authSession->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 = 'motdepasse12',
|
||||
int $id = 1,
|
||||
string $role = User::ROLE_USER,
|
||||
): User {
|
||||
return new User(
|
||||
$id,
|
||||
$username,
|
||||
$email,
|
||||
password_hash($password, PASSWORD_BCRYPT),
|
||||
$role,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user