Attainly/backend/tests/Unit/User/UseCases/ConfirmUserEmailTest.php

149 lines
4.5 KiB
PHP

<?php
namespace Tests\Unit\User\UseCases;
use App\Email\EmailConfirmationToken\CreateEmailConfirmationTokenDto;
use App\Exceptions\BadRequestException;
use App\Shared\ValueObject\EmailAddress;
use App\User\CreateUserDto;
use App\User\UseCases\ConfirmUserEmail\ConfirmUserEmail;
use App\User\UseCases\ConfirmUserEmail\ConfirmUserEmailRequest;
use DateTimeImmutable;
use DateTimeZone;
use DomainException;
use PHPUnit\Framework\TestCase;
use Tests\Fakes\FakeClock;
use Tests\Fakes\FakeEmailConfirmationTokenRepository;
use Tests\Fakes\FakePasswordHasher;
use Tests\Fakes\FakeUserRepository;
class ConfirmUserEmailTest extends TestCase
{
private DateTimeImmutable $now;
private FakeUserRepository $userRepository;
private FakeEmailConfirmationTokenRepository $tokenRepository;
private ConfirmUserEmail $confirmUserEmail;
protected function setUp(): void
{
$this->now = new DateTimeImmutable(
'2026-08-03T12:00:00',
new DateTimeZone('UTC'),
);
$this->userRepository = new FakeUserRepository;
$this->tokenRepository = new FakeEmailConfirmationTokenRepository;
$this->confirmUserEmail = new ConfirmUserEmail(
$this->tokenRepository,
$this->userRepository,
new FakePasswordHasher,
new FakeClock($this->now),
);
}
public function test_it_sets_the_password_and_consumes_the_token(): void
{
$this->createPendingUserToken(
'confirmation-token',
$this->now->modify('+10 minutes'),
);
$confirmedUser = $this->confirmUserEmail->execute(
new ConfirmUserEmailRequest(
token: 'confirmation-token',
password: 'password123',
),
);
$this->assertSame('hashed:password123', $confirmedUser->getPasswordHash());
$this->assertSame(
'hashed:password123',
$this->userRepository->find($confirmedUser->getId())
?->getPasswordHash(),
);
$this->assertNull(
$this->tokenRepository->findByToken('confirmation-token'),
);
}
public function test_it_rejects_an_expired_token(): void
{
$this->createPendingUserToken(
'expired-token',
$this->now->modify('-1 minute'),
);
$this->expectException(DomainException::class);
$this->expectExceptionMessage('token expired');
$this->confirmUserEmail->execute(new ConfirmUserEmailRequest(
token: 'expired-token',
password: 'password123',
));
}
public function test_it_rejects_an_unknown_token(): void
{
$this->expectException(DomainException::class);
$this->expectExceptionMessage('token not found');
$this->confirmUserEmail->execute(new ConfirmUserEmailRequest(
token: 'unknown-token',
password: 'password123',
));
}
public function test_it_requires_a_token(): void
{
$this->expectException(BadRequestException::class);
$this->expectExceptionMessage('token is required');
$this->confirmUserEmail->execute(new ConfirmUserEmailRequest(
token: null,
password: 'password123',
));
}
public function test_it_requires_a_password(): void
{
$this->expectException(BadRequestException::class);
$this->expectExceptionMessage('password is required');
$this->confirmUserEmail->execute(new ConfirmUserEmailRequest(
token: 'confirmation-token',
password: null,
));
}
public function test_it_rejects_a_short_password(): void
{
$this->expectException(BadRequestException::class);
$this->expectExceptionMessage(
'password must be at least 8 characters',
);
$this->confirmUserEmail->execute(new ConfirmUserEmailRequest(
token: 'confirmation-token',
password: 'short',
));
}
private function createPendingUserToken(
string $token,
DateTimeImmutable $availableTo,
): void {
$user = $this->userRepository->create(new CreateUserDto(
email: new EmailAddress('user@example.com'),
passwordHash: null,
));
$this->tokenRepository->create(
new CreateEmailConfirmationTokenDto(
user: $user,
availableTo: $availableTo,
token: $token,
),
);
}
}