Certificate manager built on Laravel 13, Livewire 4, and Flux. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
105 lines
3.1 KiB
PHP
105 lines
3.1 KiB
PHP
<?php
|
|
|
|
use App\Mail\SharedCertificateLinksMail;
|
|
use App\Mail\SharedCertificatesMail;
|
|
use App\Models\Certificate;
|
|
use App\Models\User;
|
|
use Illuminate\Support\Facades\Mail;
|
|
use Illuminate\Support\Facades\RateLimiter;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Facades\URL;
|
|
use Livewire\Livewire;
|
|
|
|
beforeEach(function () {
|
|
Storage::fake('local');
|
|
Mail::fake();
|
|
});
|
|
|
|
function sharableCertificate(User $user): Certificate
|
|
{
|
|
$path = 'certificates/'.$user->id.'/'.uniqid().'.pdf';
|
|
Storage::disk('local')->put($path, 'file-contents');
|
|
|
|
return Certificate::factory()->for($user)->create(['file_path' => $path]);
|
|
}
|
|
|
|
test('sharing by secure link emails the recipient', function () {
|
|
$user = User::factory()->create();
|
|
$certificate = sharableCertificate($user);
|
|
|
|
$this->actingAs($user);
|
|
|
|
Livewire::test('pages::certificates.index')
|
|
->set('selected', [$certificate->id])
|
|
->set('shareEmail', 'friend@example.com')
|
|
->set('shareMethod', 'link')
|
|
->call('share')
|
|
->assertHasNoErrors();
|
|
|
|
Mail::assertSent(SharedCertificateLinksMail::class, fn ($mail) => $mail->hasTo('friend@example.com'));
|
|
});
|
|
|
|
test('sharing by zip emails an attachment and reveals a password', function () {
|
|
$user = User::factory()->create();
|
|
$certificate = sharableCertificate($user);
|
|
|
|
$this->actingAs($user);
|
|
|
|
$component = Livewire::test('pages::certificates.index')
|
|
->set('selected', [$certificate->id])
|
|
->set('shareEmail', 'friend@example.com')
|
|
->set('shareMethod', 'zip')
|
|
->call('share')
|
|
->assertHasNoErrors();
|
|
|
|
expect($component->get('sharePassword'))->toHaveLength(12);
|
|
|
|
Mail::assertSent(SharedCertificatesMail::class, fn ($mail) => $mail->hasTo('friend@example.com'));
|
|
});
|
|
|
|
test('sharing requires a recipient and a selection', function () {
|
|
$this->actingAs(User::factory()->create());
|
|
|
|
Livewire::test('pages::certificates.index')
|
|
->set('shareEmail', '')
|
|
->set('selected', [])
|
|
->call('share')
|
|
->assertHasErrors(['shareEmail', 'selected']);
|
|
|
|
Mail::assertNothingSent();
|
|
});
|
|
|
|
test('sharing is rate limited', function () {
|
|
$user = User::factory()->create();
|
|
$certificate = sharableCertificate($user);
|
|
|
|
$this->actingAs($user);
|
|
|
|
for ($i = 0; $i < 10; $i++) {
|
|
RateLimiter::hit('share-certificates:'.$user->id, 3600);
|
|
}
|
|
|
|
Livewire::test('pages::certificates.index')
|
|
->set('selected', [$certificate->id])
|
|
->set('shareEmail', 'friend@example.com')
|
|
->call('share')
|
|
->assertHasErrors(['shareEmail']);
|
|
|
|
Mail::assertNothingSent();
|
|
});
|
|
|
|
test('a valid signed link streams the file to an unauthenticated recipient', function () {
|
|
$user = User::factory()->create();
|
|
$certificate = sharableCertificate($user);
|
|
|
|
$url = URL::temporarySignedRoute('shared.certificate', now()->addHours(48), ['certificate' => $certificate->id]);
|
|
|
|
$this->get($url)->assertOk();
|
|
});
|
|
|
|
test('a tampered signed link is rejected', function () {
|
|
$user = User::factory()->create();
|
|
$certificate = sharableCertificate($user);
|
|
|
|
$this->get(route('shared.certificate', $certificate))->assertForbidden();
|
|
});
|