Working state
This commit is contained in:
@@ -14,13 +14,8 @@ use PDO;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests unitaires pour PasswordResetService.
|
||||
*
|
||||
* Vérifie la génération de token, la validation et la réinitialisation
|
||||
* du mot de passe. Les dépendances sont mockées via leurs interfaces.
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
||||
|
||||
final class PasswordResetServiceTest extends TestCase
|
||||
{
|
||||
/** @var PasswordResetRepositoryInterface&MockObject */
|
||||
@@ -40,9 +35,9 @@ final class PasswordResetServiceTest extends TestCase
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->resetRepository = $this->createMock(PasswordResetRepositoryInterface::class);
|
||||
$this->userRepository = $this->createMock(UserRepositoryInterface::class);
|
||||
$this->mailService = $this->createMock(MailServiceInterface::class);
|
||||
$this->db = $this->createMock(PDO::class);
|
||||
$this->userRepository = $this->createMock(UserRepositoryInterface::class);
|
||||
$this->mailService = $this->createMock(MailServiceInterface::class);
|
||||
$this->db = $this->createMock(PDO::class);
|
||||
|
||||
$this->service = new PasswordResetService(
|
||||
$this->resetRepository,
|
||||
@@ -52,13 +47,6 @@ final class PasswordResetServiceTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// ── requestReset ───────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* requestReset() avec un email inconnu ne doit ni envoyer d'email
|
||||
* ni lever d'exception (protection contre l'énumération d'emails).
|
||||
*/
|
||||
public function testRequestResetUnknownEmailReturnsSilently(): void
|
||||
{
|
||||
$this->userRepository->method('findByEmail')->willReturn(null);
|
||||
@@ -68,9 +56,6 @@ final class PasswordResetServiceTest extends TestCase
|
||||
$this->service->requestReset('inconnu@example.com', 'https://blog.exemple.com');
|
||||
}
|
||||
|
||||
/**
|
||||
* requestReset() doit invalider les tokens précédents avant d'en créer un nouveau.
|
||||
*/
|
||||
public function testRequestResetInvalidatesPreviousTokens(): void
|
||||
{
|
||||
$user = $this->makeUser();
|
||||
@@ -79,39 +64,39 @@ final class PasswordResetServiceTest extends TestCase
|
||||
$this->resetRepository->expects($this->once())
|
||||
->method('invalidateByUserId')
|
||||
->with($user->getId());
|
||||
$this->resetRepository->method('create');
|
||||
$this->mailService->method('send');
|
||||
$this->resetRepository->expects($this->once())
|
||||
->method('create');
|
||||
$this->mailService->expects($this->once())
|
||||
->method('send');
|
||||
|
||||
$this->service->requestReset('alice@example.com', 'https://blog.exemple.com');
|
||||
}
|
||||
|
||||
/**
|
||||
* requestReset() doit persister un nouveau token en base.
|
||||
*/
|
||||
public function testRequestResetCreatesTokenInDatabase(): void
|
||||
{
|
||||
$user = $this->makeUser();
|
||||
|
||||
$this->userRepository->method('findByEmail')->willReturn($user);
|
||||
$this->resetRepository->method('invalidateByUserId');
|
||||
$this->resetRepository->expects($this->once())
|
||||
->method('invalidateByUserId');
|
||||
$this->resetRepository->expects($this->once())
|
||||
->method('create')
|
||||
->with($user->getId(), $this->callback('is_string'), $this->callback('is_string'));
|
||||
$this->mailService->method('send');
|
||||
$this->mailService->expects($this->once())
|
||||
->method('send');
|
||||
|
||||
$this->service->requestReset('alice@example.com', 'https://blog.exemple.com');
|
||||
}
|
||||
|
||||
/**
|
||||
* requestReset() doit envoyer un email avec le bon destinataire et template.
|
||||
*/
|
||||
public function testRequestResetSendsEmailWithCorrectAddress(): void
|
||||
{
|
||||
$user = $this->makeUser();
|
||||
|
||||
$this->userRepository->method('findByEmail')->willReturn($user);
|
||||
$this->resetRepository->method('invalidateByUserId');
|
||||
$this->resetRepository->method('create');
|
||||
$this->resetRepository->expects($this->once())
|
||||
->method('invalidateByUserId');
|
||||
$this->resetRepository->expects($this->once())
|
||||
->method('create');
|
||||
|
||||
$this->mailService->expects($this->once())
|
||||
->method('send')
|
||||
@@ -125,16 +110,15 @@ final class PasswordResetServiceTest extends TestCase
|
||||
$this->service->requestReset('alice@example.com', 'https://blog.exemple.com');
|
||||
}
|
||||
|
||||
/**
|
||||
* L'URL de réinitialisation dans le contexte de l'email doit contenir le token brut.
|
||||
*/
|
||||
public function testRequestResetUrlContainsToken(): void
|
||||
{
|
||||
$user = $this->makeUser();
|
||||
|
||||
$this->userRepository->method('findByEmail')->willReturn($user);
|
||||
$this->resetRepository->method('invalidateByUserId');
|
||||
$this->resetRepository->method('create');
|
||||
$this->resetRepository->expects($this->once())
|
||||
->method('invalidateByUserId');
|
||||
$this->resetRepository->expects($this->once())
|
||||
->method('create');
|
||||
|
||||
$this->mailService->expects($this->once())
|
||||
->method('send')
|
||||
@@ -151,84 +135,81 @@ final class PasswordResetServiceTest extends TestCase
|
||||
$this->service->requestReset('alice@example.com', 'https://blog.exemple.com');
|
||||
}
|
||||
|
||||
|
||||
// ── validateToken ──────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* validateToken() avec un token inexistant doit retourner null.
|
||||
*/
|
||||
public function testValidateTokenMissingToken(): void
|
||||
{
|
||||
$this->resetRepository->method('findActiveByHash')->willReturn(null);
|
||||
$this->resetRepository->expects($this->once())
|
||||
->method('findActiveByHash')
|
||||
->willReturn(null);
|
||||
|
||||
$result = $this->service->validateToken('tokeninexistant');
|
||||
|
||||
$this->assertNull($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* validateToken() avec un token expiré doit retourner null.
|
||||
*/
|
||||
public function testValidateTokenExpiredToken(): void
|
||||
{
|
||||
$tokenRaw = 'montokenbrut';
|
||||
$tokenRaw = 'montokenbrut';
|
||||
$tokenHash = hash('sha256', $tokenRaw);
|
||||
|
||||
$this->resetRepository->expects($this->once())->method('findActiveByHash')->with($tokenHash)->willReturn([
|
||||
'user_id' => 1,
|
||||
'token_hash' => $tokenHash,
|
||||
'expires_at' => date('Y-m-d H:i:s', time() - 3600),
|
||||
'used_at' => null,
|
||||
]);
|
||||
$this->resetRepository->expects($this->once())
|
||||
->method('findActiveByHash')
|
||||
->with($tokenHash)
|
||||
->willReturn([
|
||||
'user_id' => 1,
|
||||
'token_hash' => $tokenHash,
|
||||
'expires_at' => date('Y-m-d H:i:s', time() - 3600),
|
||||
'used_at' => null,
|
||||
]);
|
||||
|
||||
$result = $this->service->validateToken($tokenRaw);
|
||||
|
||||
$this->assertNull($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* validateToken() avec un token valide doit retourner l'utilisateur associé.
|
||||
*/
|
||||
public function testValidateTokenValidToken(): void
|
||||
{
|
||||
$user = $this->makeUser();
|
||||
$tokenRaw = 'montokenbrut';
|
||||
$user = $this->makeUser();
|
||||
$tokenRaw = 'montokenbrut';
|
||||
$tokenHash = hash('sha256', $tokenRaw);
|
||||
|
||||
$this->resetRepository->expects($this->once())->method('findActiveByHash')->with($tokenHash)->willReturn([
|
||||
'user_id' => $user->getId(),
|
||||
'token_hash' => $tokenHash,
|
||||
'expires_at' => date('Y-m-d H:i:s', time() + 3600),
|
||||
'used_at' => null,
|
||||
]);
|
||||
$this->userRepository->expects($this->once())->method('findById')->with($user->getId())->willReturn($user);
|
||||
$this->resetRepository->expects($this->once())
|
||||
->method('findActiveByHash')
|
||||
->with($tokenHash)
|
||||
->willReturn([
|
||||
'user_id' => $user->getId(),
|
||||
'token_hash' => $tokenHash,
|
||||
'expires_at' => date('Y-m-d H:i:s', time() + 3600),
|
||||
'used_at' => null,
|
||||
]);
|
||||
$this->userRepository->expects($this->once())
|
||||
->method('findById')
|
||||
->with($user->getId())
|
||||
->willReturn($user);
|
||||
|
||||
$result = $this->service->validateToken($tokenRaw);
|
||||
|
||||
$this->assertSame($user, $result);
|
||||
}
|
||||
|
||||
|
||||
// ── resetPassword ──────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* resetPassword() avec un token invalide doit lever une InvalidArgumentException.
|
||||
*/
|
||||
/**
|
||||
* validateToken() doit retourner null si le token est valide mais l'utilisateur a été supprimé.
|
||||
*/
|
||||
public function testValidateTokenDeletedUserReturnsNull(): void
|
||||
{
|
||||
$row = [
|
||||
'user_id' => 999,
|
||||
'expires_at' => date('Y-m-d H:i:s', time() + 3600),
|
||||
'used_at' => null,
|
||||
];
|
||||
$tokenRaw = 'token-valide-mais-user-supprime';
|
||||
$tokenHash = hash('sha256', $tokenRaw);
|
||||
|
||||
$this->resetRepository->expects($this->once())->method('findActiveByHash')->willReturn($row);
|
||||
$this->userRepository->expects($this->once())->method('findById')->with(999)->willReturn(null);
|
||||
$this->resetRepository->expects($this->once())
|
||||
->method('findActiveByHash')
|
||||
->with($tokenHash)
|
||||
->willReturn([
|
||||
'user_id' => 999,
|
||||
'expires_at' => date('Y-m-d H:i:s', time() + 3600),
|
||||
'used_at' => null,
|
||||
]);
|
||||
$this->userRepository->expects($this->once())
|
||||
->method('findById')
|
||||
->with(999)
|
||||
->willReturn(null);
|
||||
|
||||
$result = $this->service->validateToken('token-valide-mais-user-supprime');
|
||||
$result = $this->service->validateToken($tokenRaw);
|
||||
|
||||
$this->assertNull($result);
|
||||
}
|
||||
@@ -238,7 +219,7 @@ final class PasswordResetServiceTest extends TestCase
|
||||
$this->db->method('beginTransaction')->willReturn(true);
|
||||
$this->db->method('inTransaction')->willReturn(true);
|
||||
$this->db->expects($this->once())->method('rollBack');
|
||||
$this->resetRepository->method('consumeActiveToken')->willReturn(null);
|
||||
$this->resetRepository->expects($this->once())->method('consumeActiveToken')->willReturn(null);
|
||||
|
||||
$this->expectException(InvalidResetTokenException::class);
|
||||
$this->expectExceptionMessageMatches('/invalide ou a expiré/');
|
||||
@@ -246,35 +227,17 @@ final class PasswordResetServiceTest extends TestCase
|
||||
$this->service->resetPassword('tokeninvalide', 'nouveaumdp1');
|
||||
}
|
||||
|
||||
/**
|
||||
* resetPassword() avec un mot de passe trop court doit lever WeakPasswordException.
|
||||
*/
|
||||
public function testResetPasswordTooShortPasswordThrowsWeakPasswordException(): void
|
||||
{
|
||||
$user = $this->makeUser();
|
||||
$tokenRaw = 'montokenbrut';
|
||||
$tokenHash = hash('sha256', $tokenRaw);
|
||||
|
||||
$this->resetRepository->method('findActiveByHash')->willReturn([
|
||||
'user_id' => $user->getId(),
|
||||
'token_hash' => $tokenHash,
|
||||
'expires_at' => date('Y-m-d H:i:s', time() + 3600),
|
||||
'used_at' => null,
|
||||
]);
|
||||
$this->userRepository->method('findById')->willReturn($user);
|
||||
|
||||
$this->expectException(WeakPasswordException::class);
|
||||
|
||||
$this->service->resetPassword($tokenRaw, '1234567');
|
||||
$this->service->resetPassword('montokenbrut', '1234567');
|
||||
}
|
||||
|
||||
/**
|
||||
* resetPassword() doit mettre à jour le mot de passe et marquer le token comme consommé.
|
||||
*/
|
||||
public function testResetPasswordUpdatesPasswordAndConsumesToken(): void
|
||||
{
|
||||
$user = $this->makeUser();
|
||||
$tokenRaw = 'montokenbrut';
|
||||
$user = $this->makeUser();
|
||||
$tokenRaw = 'montokenbrut';
|
||||
$tokenHash = hash('sha256', $tokenRaw);
|
||||
|
||||
$this->db->method('beginTransaction')->willReturn(true);
|
||||
@@ -285,12 +248,15 @@ final class PasswordResetServiceTest extends TestCase
|
||||
->method('consumeActiveToken')
|
||||
->with($tokenHash, $this->callback('is_string'))
|
||||
->willReturn([
|
||||
'user_id' => $user->getId(),
|
||||
'user_id' => $user->getId(),
|
||||
'token_hash' => $tokenHash,
|
||||
'expires_at' => date('Y-m-d H:i:s', time() + 3600),
|
||||
'used_at' => null,
|
||||
'used_at' => null,
|
||||
]);
|
||||
$this->userRepository->method('findById')->willReturn($user);
|
||||
$this->userRepository->expects($this->once())
|
||||
->method('findById')
|
||||
->with($user->getId())
|
||||
->willReturn($user);
|
||||
|
||||
$this->userRepository->expects($this->once())
|
||||
->method('updatePassword')
|
||||
@@ -299,12 +265,6 @@ final class PasswordResetServiceTest extends TestCase
|
||||
$this->service->resetPassword($tokenRaw, 'nouveaumdp1');
|
||||
}
|
||||
|
||||
|
||||
// ── Helpers ────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Crée un utilisateur de test standard.
|
||||
*/
|
||||
private function makeUser(): User
|
||||
{
|
||||
return new User(
|
||||
|
||||
Reference in New Issue
Block a user