test SignupUser two-step confirm flow
This commit is contained in:
parent
edb0bc0478
commit
11f2823a30
1 changed files with 49 additions and 58 deletions
|
|
@ -8,8 +8,13 @@ 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\FakePasswordHasher;
|
||||
use Tests\Fakes\FakeClock;
|
||||
use Tests\Fakes\FakeEmailConfirmationTokenRepository;
|
||||
use Tests\Fakes\FakeEmailer;
|
||||
use Tests\Fakes\FakeEmailFactory;
|
||||
use Tests\Fakes\FakeUserRepository;
|
||||
use Tests\TestCase;
|
||||
|
||||
|
|
@ -17,17 +22,38 @@ class SignupUserTest extends TestCase
|
|||
{
|
||||
private FakeUserRepository $userRepo;
|
||||
|
||||
private FakePasswordHasher $hasher;
|
||||
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->hasher = new FakePasswordHasher;
|
||||
$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->hasher,
|
||||
$this->tokenRepo,
|
||||
$this->emailer,
|
||||
$this->emailFactory,
|
||||
$this->clock,
|
||||
'noreply@tide.test',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -37,7 +63,6 @@ class SignupUserTest extends TestCase
|
|||
$this->useCase->execute(new SignupUserRequest(
|
||||
email: null,
|
||||
displayName: 'alice',
|
||||
password: 'longenoughpassword',
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -47,7 +72,6 @@ class SignupUserTest extends TestCase
|
|||
$this->useCase->execute(new SignupUserRequest(
|
||||
email: '',
|
||||
displayName: 'alice',
|
||||
password: 'longenoughpassword',
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -57,7 +81,6 @@ class SignupUserTest extends TestCase
|
|||
$this->useCase->execute(new SignupUserRequest(
|
||||
email: 'not-an-email',
|
||||
displayName: 'alice',
|
||||
password: 'longenoughpassword',
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -67,7 +90,6 @@ class SignupUserTest extends TestCase
|
|||
$this->useCase->execute(new SignupUserRequest(
|
||||
email: 'user@example.com',
|
||||
displayName: null,
|
||||
password: 'longenoughpassword',
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -77,7 +99,6 @@ class SignupUserTest extends TestCase
|
|||
$this->useCase->execute(new SignupUserRequest(
|
||||
email: 'user@example.com',
|
||||
displayName: 'ab',
|
||||
password: 'longenoughpassword',
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -87,27 +108,6 @@ class SignupUserTest extends TestCase
|
|||
$this->useCase->execute(new SignupUserRequest(
|
||||
email: 'user@example.com',
|
||||
displayName: 'Has Spaces',
|
||||
password: 'longenoughpassword',
|
||||
));
|
||||
}
|
||||
|
||||
public function test_null_password_throws_bad_request(): void
|
||||
{
|
||||
$this->expectException(BadRequestException::class);
|
||||
$this->useCase->execute(new SignupUserRequest(
|
||||
email: 'user@example.com',
|
||||
displayName: 'alice',
|
||||
password: null,
|
||||
));
|
||||
}
|
||||
|
||||
public function test_short_password_throws_bad_request(): void
|
||||
{
|
||||
$this->expectException(BadRequestException::class);
|
||||
$this->useCase->execute(new SignupUserRequest(
|
||||
email: 'user@example.com',
|
||||
displayName: 'alice',
|
||||
password: 'short',
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -116,7 +116,7 @@ class SignupUserTest extends TestCase
|
|||
$this->userRepo->create(new CreateUserDto(
|
||||
email: new EmailAddress('user@example.com'),
|
||||
displayName: 'first',
|
||||
passwordHash: $this->hasher->hash('original-password'),
|
||||
passwordHash: '',
|
||||
isAdmin: false,
|
||||
emailConfirmedAt: null,
|
||||
));
|
||||
|
|
@ -125,7 +125,6 @@ class SignupUserTest extends TestCase
|
|||
$this->useCase->execute(new SignupUserRequest(
|
||||
email: 'user@example.com',
|
||||
displayName: 'second',
|
||||
password: 'second-attempt-password',
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -134,7 +133,7 @@ class SignupUserTest extends TestCase
|
|||
$this->userRepo->create(new CreateUserDto(
|
||||
email: new EmailAddress('first@example.com'),
|
||||
displayName: 'taken',
|
||||
passwordHash: $this->hasher->hash('original-password'),
|
||||
passwordHash: '',
|
||||
isAdmin: false,
|
||||
emailConfirmedAt: null,
|
||||
));
|
||||
|
|
@ -143,55 +142,48 @@ class SignupUserTest extends TestCase
|
|||
$this->useCase->execute(new SignupUserRequest(
|
||||
email: 'second@example.com',
|
||||
displayName: 'taken',
|
||||
password: 'second-attempt-password',
|
||||
));
|
||||
}
|
||||
|
||||
public function test_valid_signup_returns_user_with_hashed_password(): void
|
||||
public function test_valid_signup_creates_unconfirmed_user(): void
|
||||
{
|
||||
$created = $this->useCase->execute(new SignupUserRequest(
|
||||
email: 'new@example.com',
|
||||
displayName: 'newuser',
|
||||
password: 'longenoughpassword',
|
||||
));
|
||||
|
||||
$this->assertInstanceOf(User::class, $created);
|
||||
$this->assertSame('new@example.com', $created->getEmail()->value());
|
||||
$this->assertSame('newuser', $created->getDisplayName());
|
||||
$this->assertSame(
|
||||
$this->hasher->hash('longenoughpassword'),
|
||||
$created->getPasswordHash(),
|
||||
);
|
||||
$this->assertSame('', $created->getPasswordHash());
|
||||
$this->assertFalse($created->isAdmin());
|
||||
$this->assertFalse($created->isEmailConfirmed());
|
||||
}
|
||||
|
||||
public function test_created_user_is_findable_by_email(): void
|
||||
public function test_valid_signup_creates_confirmation_token(): void
|
||||
{
|
||||
$created = $this->useCase->execute(new SignupUserRequest(
|
||||
email: 'lookup@example.com',
|
||||
displayName: 'lookup',
|
||||
password: 'longenoughpassword',
|
||||
email: 'new@example.com',
|
||||
displayName: 'newuser',
|
||||
));
|
||||
|
||||
$found = $this->userRepo->findByEmail(
|
||||
new EmailAddress('lookup@example.com')
|
||||
);
|
||||
$this->assertNotNull($found);
|
||||
$this->assertSame($created->getId(), $found->getId());
|
||||
$token = $this->tokenRepo->findByUser($created);
|
||||
$this->assertNotNull($token);
|
||||
$this->assertGreaterThan($this->now, $token->getAvailableTo());
|
||||
}
|
||||
|
||||
public function test_created_user_is_findable_by_display_name(): void
|
||||
public function test_valid_signup_sends_confirmation_email(): void
|
||||
{
|
||||
$created = $this->useCase->execute(new SignupUserRequest(
|
||||
email: 'lookup@example.com',
|
||||
displayName: 'lookupbyname',
|
||||
password: 'longenoughpassword',
|
||||
$this->useCase->execute(new SignupUserRequest(
|
||||
email: 'new@example.com',
|
||||
displayName: 'newuser',
|
||||
));
|
||||
|
||||
$found = $this->userRepo->findByDisplayName('lookupbyname');
|
||||
$this->assertNotNull($found);
|
||||
$this->assertSame($created->getId(), $found->getId());
|
||||
$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
|
||||
|
|
@ -199,7 +191,6 @@ class SignupUserTest extends TestCase
|
|||
$created = $this->useCase->execute(new SignupUserRequest(
|
||||
email: 'Mixed@CASE.com',
|
||||
displayName: 'mixed',
|
||||
password: 'longenoughpassword',
|
||||
));
|
||||
|
||||
$this->assertSame('Mixed@case.com', $created->getEmail()->value());
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue