actingAs(User::factory()->create()); $this->get(route('certificates.create'))->assertOk(); }); test('a certificate is stored on the private disk', function () { $user = User::factory()->create(); $this->actingAs($user); Livewire::test('pages::certificates.create') ->set('file', UploadedFile::fake()->create('cert.pdf', 200, 'application/pdf')) ->set('title', 'My Diploma') ->set('expires_at', now()->addYear()->toDateString()) ->call('save') ->assertHasNoErrors() ->assertRedirect(route('certificates.index')); $certificate = $user->certificates()->firstOrFail(); expect($certificate->title)->toBe('My Diploma') ->and($certificate->file_path)->toStartWith('certificates/'.$user->id.'/'); Storage::disk('local')->assertExists($certificate->file_path); }); test('certificate details are stored', function () { $user = User::factory()->create(); $this->actingAs($user); Livewire::test('pages::certificates.create') ->set('file', UploadedFile::fake()->create('cert.pdf', 200, 'application/pdf')) ->set('title', 'First Aid') ->set('issuer', 'Red Cross') ->set('certificate_number', 'RC-2026-001') ->set('issued_at', '2026-01-10') ->set('expires_at', '2028-01-10') ->set('notes', 'Renewed on-site.') ->call('save') ->assertHasNoErrors(); $certificate = $user->certificates()->firstOrFail(); expect($certificate->issuer)->toBe('Red Cross') ->and($certificate->certificate_number)->toBe('RC-2026-001') ->and($certificate->issued_at->toDateString())->toBe('2026-01-10') ->and($certificate->notes)->toBe('Renewed on-site.'); }); test('the issue date cannot be after the expiry date', function () { $this->actingAs(User::factory()->create()); Livewire::test('pages::certificates.create') ->set('file', UploadedFile::fake()->create('cert.pdf', 200, 'application/pdf')) ->set('title', 'Bad Dates') ->set('issued_at', '2028-01-10') ->set('expires_at', '2026-01-10') ->call('save') ->assertHasErrors(['issued_at']); }); test('an invalid file type is rejected', function () { $this->actingAs(User::factory()->create()); Livewire::test('pages::certificates.create') ->set('file', UploadedFile::fake()->create('malware.exe', 100)) ->assertHasErrors(['file']); }); test('ocr suggestions pre-fill empty fields on upload', function () { Http::fake([ 'custom-ocr.klippa.com/*' => Http::response([ 'data' => ['parsed' => [ 'document_subject' => 'Scanned Title', 'expiry_date' => '2028-01-15', ]], ]), ]); $user = User::factory()->create(); Subscription::factory()->for($user)->starter()->create(); $user->ocr_provider = 'klippa'; $user->ocr_api_key = 'key'; $user->save(); $this->actingAs($user); Livewire::test('pages::certificates.create') ->set('file', UploadedFile::fake()->create('cert.pdf', 200, 'application/pdf')) ->assertSet('title', 'Scanned Title') ->assertSet('expires_at', '2028-01-15'); }); test('ocr is skipped for a free plan user even if an ocr provider is set', function () { Http::fake([ 'custom-ocr.klippa.com/*' => Http::response([ 'data' => ['parsed' => ['document_subject' => 'Scanned Title']], ]), ]); // A free-plan user can end up with a stale ocr_provider (e.g. after a // downgrade); scanning must stay off regardless of that setting. $user = User::factory()->create(); $user->ocr_provider = 'klippa'; $user->ocr_api_key = 'key'; $user->save(); $this->actingAs($user); Livewire::test('pages::certificates.create') ->set('file', UploadedFile::fake()->create('cert.pdf', 200, 'application/pdf')) ->assertSet('title', ''); Http::assertNothingSent(); }); test('ocr does not overwrite fields the user already filled', function () { Http::fake([ 'custom-ocr.klippa.com/*' => Http::response([ 'data' => ['parsed' => ['document_subject' => 'Scanned Title']], ]), ]); $user = User::factory()->create(); Subscription::factory()->for($user)->starter()->create(); $user->ocr_provider = 'klippa'; $user->ocr_api_key = 'key'; $user->save(); $this->actingAs($user); Livewire::test('pages::certificates.create') ->set('title', 'My Own Title') ->set('file', UploadedFile::fake()->create('cert.pdf', 200, 'application/pdf')) ->assertSet('title', 'My Own Title'); }); test('a free plan user is blocked from adding a certificate past their limit', function () { $user = User::factory()->create(); Certificate::factory()->for($user)->count(5)->create(); $this->actingAs($user); $this->get(route('certificates.create')) ->assertOk() ->assertSee('Certificate limit reached'); Livewire::test('pages::certificates.create') ->set('file', UploadedFile::fake()->create('cert.pdf', 200, 'application/pdf')) ->set('title', 'One Too Many') ->set('expires_at', now()->addYear()->toDateString()) ->call('save'); expect($user->certificates()->count())->toBe(5); }); test('a starter plan user is blocked once they reach their higher limit', function () { $user = User::factory()->create(); Subscription::factory()->for($user)->starter()->create(); Certificate::factory()->for($user)->count(15)->create(); $this->actingAs($user); Livewire::test('pages::certificates.create') ->set('file', UploadedFile::fake()->create('cert.pdf', 200, 'application/pdf')) ->set('title', 'One Too Many') ->set('expires_at', now()->addYear()->toDateString()) ->call('save'); expect($user->certificates()->count())->toBe(15); }); test('an unlimited plan user is never blocked by the certificate limit', function () { $user = User::factory()->create(); Subscription::factory()->for($user)->unlimited()->create(); Certificate::factory()->for($user)->count(20)->create(); $this->actingAs($user); Livewire::test('pages::certificates.create') ->set('file', UploadedFile::fake()->create('cert.pdf', 200, 'application/pdf')) ->set('title', 'Number 21') ->set('expires_at', now()->addYear()->toDateString()) ->call('save') ->assertHasNoErrors() ->assertRedirect(route('certificates.index')); expect($user->certificates()->count())->toBe(21); });