198 lines
6 KiB
PHP
198 lines
6 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\User\UseCases;
|
|
|
|
use App\Exceptions\BadRequestException;
|
|
use App\Shared\ValueObject\EmailAddress;
|
|
use App\User\CreateUserDto;
|
|
use App\User\UseCases\SignupUser\SignupUser;
|
|
use App\User\UseCases\SignupUser\SignupUserRequest;
|
|
use App\User\User;
|
|
use DateTimeImmutable;
|
|
use DateTimeZone;
|
|
use DomainException;
|
|
use Tests\Fakes\FakeClock;
|
|
use Tests\Fakes\FakeEmailConfirmationTokenRepository;
|
|
use Tests\Fakes\FakeEmailer;
|
|
use Tests\Fakes\FakeEmailFactory;
|
|
use Tests\Fakes\FakeUserRepository;
|
|
use Tests\TestCase;
|
|
|
|
class SignupUserTest extends TestCase
|
|
{
|
|
private FakeUserRepository $userRepo;
|
|
|
|
private FakeEmailConfirmationTokenRepository $tokenRepo;
|
|
|
|
private FakeEmailer $emailer;
|
|
|
|
private FakeEmailFactory $emailFactory;
|
|
|
|
private FakeClock $clock;
|
|
|
|
private DateTimeImmutable $now;
|
|
|
|
private SignupUser $useCase;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->now = new DateTimeImmutable(
|
|
'2026-05-06T12:00:00',
|
|
new DateTimeZone('UTC'),
|
|
);
|
|
$this->userRepo = new FakeUserRepository;
|
|
$this->tokenRepo = new FakeEmailConfirmationTokenRepository(
|
|
$this->userRepo,
|
|
);
|
|
$this->emailer = new FakeEmailer;
|
|
$this->emailFactory = new FakeEmailFactory;
|
|
$this->clock = new FakeClock($this->now);
|
|
$this->useCase = new SignupUser(
|
|
$this->userRepo,
|
|
$this->tokenRepo,
|
|
$this->emailer,
|
|
$this->emailFactory,
|
|
$this->clock,
|
|
'noreply@tide.test',
|
|
);
|
|
}
|
|
|
|
public function test_null_email_throws_bad_request(): void
|
|
{
|
|
$this->expectException(BadRequestException::class);
|
|
$this->useCase->execute(new SignupUserRequest(
|
|
email: null,
|
|
displayName: 'alice',
|
|
));
|
|
}
|
|
|
|
public function test_empty_email_throws_bad_request(): void
|
|
{
|
|
$this->expectException(BadRequestException::class);
|
|
$this->useCase->execute(new SignupUserRequest(
|
|
email: '',
|
|
displayName: 'alice',
|
|
));
|
|
}
|
|
|
|
public function test_invalid_email_format_throws_bad_request(): void
|
|
{
|
|
$this->expectException(BadRequestException::class);
|
|
$this->useCase->execute(new SignupUserRequest(
|
|
email: 'not-an-email',
|
|
displayName: 'alice',
|
|
));
|
|
}
|
|
|
|
public function test_null_display_name_throws_bad_request(): void
|
|
{
|
|
$this->expectException(BadRequestException::class);
|
|
$this->useCase->execute(new SignupUserRequest(
|
|
email: 'user@example.com',
|
|
displayName: null,
|
|
));
|
|
}
|
|
|
|
public function test_short_display_name_throws_bad_request(): void
|
|
{
|
|
$this->expectException(BadRequestException::class);
|
|
$this->useCase->execute(new SignupUserRequest(
|
|
email: 'user@example.com',
|
|
displayName: 'ab',
|
|
));
|
|
}
|
|
|
|
public function test_display_name_with_invalid_chars_throws(): void
|
|
{
|
|
$this->expectException(BadRequestException::class);
|
|
$this->useCase->execute(new SignupUserRequest(
|
|
email: 'user@example.com',
|
|
displayName: 'Has Spaces',
|
|
));
|
|
}
|
|
|
|
public function test_duplicate_email_throws_domain_exception(): void
|
|
{
|
|
$this->userRepo->create(new CreateUserDto(
|
|
email: new EmailAddress('user@example.com'),
|
|
displayName: 'first',
|
|
passwordHash: '',
|
|
isAdmin: false,
|
|
emailConfirmedAt: null,
|
|
));
|
|
|
|
$this->expectException(DomainException::class);
|
|
$this->useCase->execute(new SignupUserRequest(
|
|
email: 'user@example.com',
|
|
displayName: 'second',
|
|
));
|
|
}
|
|
|
|
public function test_duplicate_display_name_throws_domain_exception(): void
|
|
{
|
|
$this->userRepo->create(new CreateUserDto(
|
|
email: new EmailAddress('first@example.com'),
|
|
displayName: 'taken',
|
|
passwordHash: '',
|
|
isAdmin: false,
|
|
emailConfirmedAt: null,
|
|
));
|
|
|
|
$this->expectException(DomainException::class);
|
|
$this->useCase->execute(new SignupUserRequest(
|
|
email: 'second@example.com',
|
|
displayName: 'taken',
|
|
));
|
|
}
|
|
|
|
public function test_valid_signup_creates_unconfirmed_user(): void
|
|
{
|
|
$created = $this->useCase->execute(new SignupUserRequest(
|
|
email: 'new@example.com',
|
|
displayName: 'newuser',
|
|
));
|
|
|
|
$this->assertInstanceOf(User::class, $created);
|
|
$this->assertSame('new@example.com', $created->getEmail()->value());
|
|
$this->assertSame('newuser', $created->getDisplayName());
|
|
$this->assertSame('', $created->getPasswordHash());
|
|
$this->assertFalse($created->isAdmin());
|
|
$this->assertFalse($created->isEmailConfirmed());
|
|
}
|
|
|
|
public function test_valid_signup_creates_confirmation_token(): void
|
|
{
|
|
$created = $this->useCase->execute(new SignupUserRequest(
|
|
email: 'new@example.com',
|
|
displayName: 'newuser',
|
|
));
|
|
|
|
$token = $this->tokenRepo->findByUser($created);
|
|
$this->assertNotNull($token);
|
|
$this->assertGreaterThan($this->now, $token->getAvailableTo());
|
|
}
|
|
|
|
public function test_valid_signup_sends_confirmation_email(): void
|
|
{
|
|
$this->useCase->execute(new SignupUserRequest(
|
|
email: 'new@example.com',
|
|
displayName: 'newuser',
|
|
));
|
|
|
|
$this->assertSame(1, $this->emailer->getNumberOfEmailsSent());
|
|
$sent = $this->emailer->getSentEmails()[0];
|
|
$this->assertSame('noreply@tide.test', $sent['from']);
|
|
$this->assertSame('new@example.com', $sent['to']);
|
|
$this->assertStringContainsString('confirm:', $sent['body']);
|
|
}
|
|
|
|
public function test_signup_normalizes_email_domain(): void
|
|
{
|
|
$created = $this->useCase->execute(new SignupUserRequest(
|
|
email: 'Mixed@CASE.com',
|
|
displayName: 'mixed',
|
|
));
|
|
|
|
$this->assertSame('Mixed@case.com', $created->getEmail()->value());
|
|
}
|
|
}
|