CluPilotCloud/tests/Feature/DoubleOptInTest.php

126 lines
4.2 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
use App\Mail\VerifyEmailMail;
use App\Models\User;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\URL;
/**
* Nobody gets into the portal on an address they have not proved is theirs.
*
* Anybody can type a stranger's address into a registration form. Until it is
* confirmed, an account is not known to belong to the person holding it — and
* everything behind the portal, the servers and the backups and the invoices,
* is worth as much to the person who typed it as to the person who owns it.
*/
it('does not let an unconfirmed account into the portal', function () {
$user = User::factory()->unverified()->create();
$this->actingAs($user)
->get(route('dashboard'))
->assertRedirect(route('verification.notice'));
});
it('lets a confirmed account through', function () {
$this->actingAs(User::factory()->create())
->get(route('dashboard'))
->assertOk();
});
it('sends the confirmation in this products design, not the frameworks', function () {
// Laravel's own notification would arrive with its logo, its button and
// its footer — as the first thing anybody ever receives from CluPilot.
Mail::fake();
User::factory()->unverified()->create()->sendEmailVerificationNotification();
Mail::assertQueued(VerifyEmailMail::class);
});
it('confirms the address when the signed link is opened', function () {
$user = User::factory()->unverified()->create();
$this->actingAs($user)->get(verificationUrlFor($user))->assertRedirect();
expect($user->refresh()->hasVerifiedEmail())->toBeTrue();
});
it('refuses a link whose signature has been tampered with', function () {
$user = User::factory()->unverified()->create();
$tampered = verificationUrlFor($user).'x';
$this->actingAs($user)->get($tampered)->assertForbidden();
expect($user->refresh()->hasVerifiedEmail())->toBeFalse();
});
it('refuses a link that has expired', function () {
$user = User::factory()->unverified()->create();
$url = verificationUrlFor($user, 60);
$this->travel(61)->minutes();
$this->actingAs($user)->get($url)->assertForbidden();
expect($user->refresh()->hasVerifiedEmail())->toBeFalse();
});
it('stops working once the address it was issued for has changed', function () {
// The address is part of what is signed. Without that, a link mailed to an
// address somebody has since corrected would still confirm the account —
// confirming an address nobody can read.
$user = User::factory()->unverified()->create(['email' => 'typo@example.com']);
$url = verificationUrlFor($user);
$user->update(['email' => 'correct@example.com']);
$this->actingAs($user)->get($url)->assertForbidden();
expect($user->refresh()->hasVerifiedEmail())->toBeFalse();
});
it('will not let one account confirm another', function () {
$mine = User::factory()->unverified()->create();
$theirs = User::factory()->unverified()->create();
$this->actingAs($mine)->get(verificationUrlFor($theirs))->assertForbidden();
expect($theirs->refresh()->hasVerifiedEmail())->toBeFalse();
});
it('throttles the resend button', function () {
// Without it this is a button that makes the server send mail to an
// arbitrary address as fast as it can be clicked.
Mail::fake();
$user = User::factory()->unverified()->create();
$component = Livewire\Livewire::actingAs($user)->test(App\Livewire\Auth\VerifyEmail::class);
foreach (range(1, 6) as $ignored) {
$component->call('resend');
}
Mail::assertQueuedCount(6);
$component->call('resend');
Mail::assertQueuedCount(6);
});
it('sends nobody back to the waiting room once they are through', function () {
$this->actingAs(User::factory()->create())
->get(route('verification.notice'))
->assertRedirect(route('dashboard'));
});
/** The signed link the mail carries, built the same way VerifyEmailMail builds it. */
function verificationUrlFor(User $user, int $minutes = 60): string
{
return URL::temporarySignedRoute('verification.verify', now()->addMinutes($minutes), [
'id' => $user->getKey(),
'hash' => sha1($user->getEmailForVerification()),
]);
}