userRepository = new FakeUserRepository; $this->tokenRepository = new FakeEmailConfirmationTokenRepository; $this->emailer = new FakeEmailer; $this->emailFactory = new FakeEmailFactory; $createToken = new CreateEmailConfirmationToken( $this->tokenRepository, new FakeClock(new DateTimeImmutable( '2026-08-03T12:00:00', new DateTimeZone('UTC'), )), new FakeTokenGenerator(['first-token', 'second-token']), ); $this->signupUser = new SignupUser( $this->userRepository, $createToken, $this->emailer, $this->emailFactory, ); } public function test_it_creates_a_pending_user_token_and_email(): void { $this->signupUser->execute(new SignupUserRequest( email: ' Founder@EXAMPLE.COM ', )); $user = $this->userRepository->findByEmail( new EmailAddress('Founder@example.com'), ); $this->assertNotNull($user); $this->assertNull($user->getPasswordHash()); $token = $this->tokenRepository->findByUser($user); $this->assertNotNull($token); $this->assertSame('first-token', $token->getToken()); $this->assertSame( '2026-08-03T12:10:00+00:00', $token->getAvailableTo()->format('c'), ); $this->assertSame(1, $this->emailer->getSendCount()); $this->assertSame( 'Founder@example.com', $this->emailer->getLastRecipient()?->value(), ); $this->assertSame( 'Confirm your Attainly email', $this->emailer->getLastSubject(), ); $this->assertSame('first-token', $this->emailFactory->getLastToken()); } public function test_it_replaces_the_token_for_a_pending_user(): void { $request = new SignupUserRequest(email: 'user@example.com'); $this->signupUser->execute($request); $this->signupUser->execute($request); $user = $this->userRepository->findByEmail( new EmailAddress('user@example.com'), ); $this->assertNotNull($user); $this->assertSame( 'second-token', $this->tokenRepository->findByUser($user)?->getToken(), ); $this->assertSame(2, $this->emailer->getSendCount()); } public function test_it_rejects_an_existing_confirmed_account(): void { $this->userRepository->create(new CreateUserDto( email: new EmailAddress('user@example.com'), passwordHash: 'hashed-password', )); $this->expectException(DomainException::class); $this->expectExceptionMessage('user@example.com already has an account'); $this->signupUser->execute(new SignupUserRequest( email: 'user@example.com', )); } public function test_it_rejects_a_missing_email(): void { $this->expectException(BadRequestException::class); $this->expectExceptionMessage('email is required'); $this->signupUser->execute(new SignupUserRequest(email: null)); } public function test_it_rejects_an_invalid_email(): void { $this->expectException(BadRequestException::class); $this->expectExceptionMessage('email must be valid'); $this->signupUser->execute(new SignupUserRequest( email: 'not-an-email', )); } }