'clamd', 'services.malware.host' => '127.0.0.1', 'services.malware.port' => 3310]); if (! app(MalwareScanner::class)->health()['available']) { $this->markTestSkipped('Real ClamD required for these consumer operations.'); } } private function scan(string $id): void { (new ScanDocumentForMalware($id))->handle(app(MalwareScanner::class)); $d = DB::table('documents')->where('id', $id)->first(); $this->assertSame('clean', $d->malware_scan_status); $this->assertSame('ClamAV', $d->malware_engine); $this->assertSame(trim($d->sha256), $d->malware_scanned_sha256); $this->assertNotNull($d->malware_signatures_at); } public function test_library_private_registration_real_scan_explicit_classification_publication_and_download(): void { $manager = User::factory()->create(); config(['portal.managers' => [$manager->email]]); $bytes = 'Synthetic QA guide; no official or legal value.'; $upload = ['file' => UploadedFile::fake()->createWithContent('qa-guide.txt', $bytes)]; $this->actingAs($manager)->post('/management/portal-services/upload', $upload)->assertSessionHasNoErrors(); $d = DB::table('documents')->first(); $this->assertSame('quarantined', $d->status); $payload = ['document_id' => $d->id, 'title' => 'Synthetic QA guide', 'description' => 'Synthetic quality assurance document.', 'category' => 'Guia', 'topic' => 'Investimento', 'document_type' => 'guide', 'issuer' => 'Synthetic QA only', 'source_url' => 'https://example.invalid/qa', 'language' => 'pt', 'version_label' => 'QA-1', 'territorial_scope' => 'national', 'source_confirmation' => 'identified', 'legal_status' => 'not_applicable', 'rights_status' => 'unconfirmed', 'data_classification' => 'internal', 'source_reference' => 'Synthetic QA fixture, not an official source.', 'legislation' => false, 'essential' => false, 'independent_review' => false, 'associations' => []]; $response = $this->post('/management/portal-services/resources', $payload); $this->assertSame(302, $response->status(), $response->getContent()); $response->assertSessionHasNoErrors(); $r = DB::table('portal_resources')->first(); $this->post('/management/portal-services/resources/'.$r->id, ['published' => true])->assertSessionHasErrors('published'); $this->get('/documentos/'.$r->id.'/download')->assertNotFound(); $this->scan($d->id); $this->assertDatabaseHas('portal_resources', ['id' => $r->id, 'published' => false]); $update = [...$payload, 'metadata_version' => 1, 'rights_status' => 'authorized', 'data_classification' => 'public']; $this->put('/management/portal-services/resources/'.$r->id, $update)->assertSessionHasNoErrors(); $this->get('/documentos/'.$r->id.'/download')->assertNotFound(); $this->post('/management/portal-services/resources/'.$r->id, ['published' => true])->assertSessionHasNoErrors(); $this->get('/documentos/'.$r->id.'/download')->assertOk()->assertDownload('qa-guide.txt'); $this->put('/management/portal-services/resources/'.$r->id, $update)->assertConflict(); $this->post('/management/portal-services/upload', $upload)->assertSessionHasErrors('file'); $this->assertSame(1, DB::table('documents')->count()); $this->assertSame(3, DB::table('portal_resource_revisions')->count()); Storage::disk('local')->put($d->storage_key, 'Tampered synthetic QA bytes'); $this->get('/documentos/'.$r->id.'/download')->assertNotFound(); $this->actingAs(User::factory()->create())->put('/management/portal-services/resources/'.$r->id, [...$update, 'metadata_version' => 2])->assertForbidden(); } public function test_desk_upload_real_scan_dedup_recovery_foreign_actor_and_tamper(): void { $owner = User::factory()->create(); $this->actingAs($owner); $id = $this->postJson('/balcao-virtual/dossiers', ['title' => 'Synthetic document QA case', 'client_key' => (string) Str::uuid(), 'reuse_consent' => true])->assertCreated()->json('id'); $base = '/balcao-virtual/dossiers/'.$id; $bytes = 'Synthetic confidential QA attachment only.'; $input = ['version' => 1, 'category' => 'proof', 'source_reference' => 'Synthetic QA owner attachment.', 'consent' => true, 'file' => UploadedFile::fake()->createWithContent('qa-desk.txt', $bytes)]; $this->post($base.'/documentos', $input, ['Accept' => 'application/json'])->assertOk(); $link = DB::table('desk_documents')->first(); $d = DB::table('documents')->where('id', $link->document_id)->first(); $url = $base.'/documentos/'.$link->id; $this->get($url)->assertNotFound(); $this->scan($d->id); $this->get($url)->assertOk()->assertDownload('qa-desk.txt'); $this->actingAs(User::factory()->create())->get($url)->assertForbidden(); $this->actingAs($owner)->getJson($base)->assertOk(); $version = DB::table('desk_cases')->where('id', $id)->value('version'); $this->post($base.'/documentos', [...$input, 'version' => $version], ['Accept' => 'application/json'])->assertOk(); $this->assertSame(1, DB::table('desk_documents')->count()); $this->assertSame(1, DB::table('documents')->count()); $this->assertDatabaseHas('documents', ['id' => $d->id, 'data_classification' => 'confidential']); Storage::disk('local')->put($d->storage_key, 'Tampered QA bytes'); $this->get($url)->assertNotFound(); } public function test_encrypted_canonical_original_is_reusable_in_desk_and_manager_library_download(): void { $owner = User::factory()->create(); config(['portal.managers' => [$owner->email]]); $bytes = "%PDF-1.4\n% Synthetic encrypted canonical QA only.\n%%EOF"; $doc = (string) Str::uuid(); $key = 'qa-canonical/'.$doc; Storage::disk('local')->put($key, Crypt::encryptString($bytes)); DB::table('documents')->insert(['id' => $doc, 'source_system' => 'qa', 'file_name' => 'encrypted-qa.pdf', 'media_type' => 'application/pdf', 'storage_key' => $key, 'sha256' => hash('sha256', $bytes), 'size_bytes' => strlen($bytes), 'storage_encryption' => 'laravel', 'status' => 'quarantined', 'data_classification' => 'confidential', 'uploaded_by' => $owner->id]); $this->scan($doc); $this->actingAs($owner)->get('/management/documents/'.$doc.'/download')->assertOk()->assertContent($bytes); $case = $this->postJson('/balcao-virtual/dossiers', ['title' => 'Synthetic encrypted reuse QA', 'client_key' => (string) Str::uuid(), 'reuse_consent' => true])->assertCreated()->json('id'); $this->postJson('/balcao-virtual/dossiers/'.$case.'/documentos', ['version' => 1, 'document_id' => $doc, 'category' => 'proof', 'source_reference' => 'Existing owned encrypted QA original', 'consent' => true])->assertOk(); $link = DB::table('desk_documents')->where('document_id', $doc)->first(); $url = '/balcao-virtual/dossiers/'.$case.'/documentos/'.$link->id; $this->get($url)->assertOk()->assertContent($bytes); $this->assertSame(1, DB::table('documents')->count()); $metadata = ['document_id' => $doc, 'title' => 'Synthetic encrypted QA guide', 'description' => 'Synthetic QA only.', 'category' => 'Guia', 'topic' => 'Investimento', 'document_type' => 'guide', 'source_url' => 'https://example.invalid/qa', 'language' => 'pt', 'version_label' => 'QA-1', 'territorial_scope' => 'national', 'source_confirmation' => 'identified', 'legal_status' => 'not_applicable', 'rights_status' => 'authorized', 'data_classification' => 'public', 'source_reference' => 'Synthetic QA, not an official source.', 'legislation' => false, 'essential' => false, 'independent_review' => false, 'associations' => []]; $this->post('/management/portal-services/resources', $metadata)->assertSessionHasNoErrors(); $resource = DB::table('portal_resources')->first(); $public = '/documentos/'.$resource->id; $this->get($public.'/download')->assertNotFound(); $this->get($public.'/view')->assertNotFound(); $this->post('/management/portal-services/resources/'.$resource->id, ['published' => true])->assertSessionHasNoErrors(); $this->get($public.'/download')->assertOk()->assertContent($bytes); $this->get($public.'/view')->assertOk()->assertContent($bytes)->assertHeader('Content-Type', 'application/pdf') ->assertHeader('Content-Security-Policy', "sandbox; default-src 'none'; style-src 'unsafe-inline'"); $this->assertSame(1, DB::table('documents')->count()); Storage::disk('local')->put($key, Crypt::encryptString('Changed synthetic QA bytes')); $this->get($url)->assertNotFound(); $this->get('/management/documents/'.$doc.'/download')->assertNotFound(); $this->get($public.'/download')->assertNotFound(); $this->get($public.'/view')->assertNotFound(); } }