'clamd', 'services.malware.host' => '127.0.0.1', 'services.malware.port' => 3310]); if (! app(MalwareScanner::class)->health()['available']) { $this->markTestSkipped('Real ClamD required; no fabricated scan proof.'); } } private function files(): array { $image = imagecreatetruecolor(16, 16); ob_start(); imagepng($image); $png = ob_get_clean(); ob_start(); imagejpeg($image); $jpeg = ob_get_clean(); imagedestroy($image); return ['qa.pdf' => SyntheticDocument::pdf(), 'qa.png' => $png, 'qa.jpg' => $jpeg]; } private function scan(string $id): object { (new ScanDocumentForMalware($id))->handle(app(MalwareScanner::class)); $document = DB::table('documents')->where('id', $id)->first(); $this->assertSame('active', $document->status); $this->assertSame('clean', $document->malware_scan_status); $this->assertSame('ClamAV', $document->malware_engine); $this->assertSame(trim($document->sha256), $document->malware_scanned_sha256); $this->assertNotNull($document->malware_signatures_at); return $document; } public function test_land_pdf_png_jpeg_scan_recovery_print_and_profile_permissions_preserve_originals(): void { $owner = User::factory()->create(); $manager = User::factory()->create(); config(['portal.managers' => [$manager->email]]); $this->actingAs($owner)->postJson('/investidor/terra-duat', ['payload' => ['full_name' => 'Synthetic QA investor']])->assertCreated(); $case = LandAssistanceCase::firstOrFail(); $base = '/investidor/terra-duat/'.$case->id; foreach ($this->files() as $name => $bytes) { $this->post($base.'/documentos', ['version' => $case->fresh()->version, 'category' => str_ends_with($name, '.pdf') ? 'prior_right' : 'photo', 'file' => UploadedFile::fake()->createWithContent($name, $bytes)], ['Accept' => 'application/json'])->assertCreated(); $documentId = DB::table('documents')->where('file_name', $name)->value('id'); $link = DB::table('land_assistance_documents')->where('case_id', $case->id)->where('document_id', $documentId)->first(); $url = $base.'/documentos/'.$link->id; $this->get($url)->assertNotFound(); $document = $this->scan($link->document_id); $response = $this->get($url)->assertOk()->assertDownload($name); $this->assertSame($bytes, file_get_contents($response->baseResponse->getFile()->getPathname())); // Existing canonical encrypted originals must remain readable without making plaintext copies. Storage::disk('local')->put($document->storage_key, Crypt::encryptString($bytes)); DB::table('documents')->where('id', $document->id)->update(['storage_encryption' => 'laravel']); $this->get($url)->assertOk()->assertContent($bytes); $this->assertSame($owner->id, $document->uploaded_by); $this->assertSame(strlen($bytes), $document->size_bytes); $this->actingAs($manager)->get($url)->assertNotFound(); $this->actingAs(User::factory()->create())->get($url)->assertNotFound(); $this->actingAs($owner)->getJson($base)->assertOk(); $this->assertSame($bytes, Crypt::decryptString(Storage::disk('local')->get($document->storage_key))); Storage::disk('local')->put($document->storage_key, Crypt::encryptString('Tampered QA only')); $this->get($url)->assertNotFound(); } $this->get($base.'/imprimir')->assertOk(); // Printable response; browser PDF rendering remains pending. $this->assertSame(3, DB::table('documents')->count()); $this->assertSame(3, DB::table('document_malware_scans')->where('result', 'clean')->count()); } public function test_partnership_real_scans_sharing_revocation_new_session_and_encrypted_originals(): void { $owner = User::factory()->create(); $candidate = User::factory()->create(); $manager = User::factory()->create(); $outsider = User::factory()->create(); config(['portal.managers' => [$manager->email]]); $exchange = app(PartnershipExchange::class); $record = $exchange->save($owner, ['client_key' => (string) Str::uuid(), 'title' => 'Synthetic document QA partnership', 'kind' => 'seeking', 'partnership_type' => 'joint_venture', 'offer' => 'Synthetic QA capacity', 'seeking' => 'Synthetic QA contribution', 'payload' => ['summary' => 'Synthetic QA only', 'consent' => true], 'origins' => [], 'locations' => []]); $record = $exchange->transition($owner, $record->id, $record->version, 'submit', 'QA private document test'); $record = $exchange->transition($manager, $record->id, $record->version, 'publish', 'QA fixture only; no active disclosure'); $interest = $exchange->interest($candidate, $record->id, ['proposal' => 'Synthetic QA proposal', 'payload' => ['consent' => true]]); $interest = $exchange->decideInterest($owner, $interest->id, $interest->version, 'shortlist', 'QA room preparation'); $exchange->decideInterest($owner, $interest->id, $interest->version, 'connect', 'QA private room access'); $room = DB::table('partnership_rooms')->where('interest_id', $interest->id)->first(); $base = '/parcerias/salas/'.$room->id; foreach ($this->files() as $name => $bytes) { $this->actingAs($owner)->post($base.'/documentos', ['shared' => false, 'file' => UploadedFile::fake()->createWithContent($name, $bytes)], ['Accept' => 'application/json'])->assertOk(); $link = DB::table('partnership_documents')->where('room_id', $room->id)->orderByDesc('id')->first(); $url = $base.'/documentos/'.$link->id; $this->get($url)->assertNotFound(); $document = $this->scan($link->document_id); $response = $this->get($url)->assertOk()->assertDownload($name); $this->assertSame($bytes, file_get_contents($response->baseResponse->getFile()->getPathname())); $this->actingAs($candidate)->get($url)->assertNotFound(); $this->postJson($url.'/partilha', ['shared' => true])->assertForbidden(); $this->actingAs($owner)->postJson($url.'/partilha', ['shared' => true])->assertOk(); Storage::disk('local')->put($document->storage_key, Crypt::encryptString($bytes)); DB::table('documents')->where('id', $document->id)->update(['storage_encryption' => 'laravel']); $this->actingAs($candidate)->get($url)->assertOk()->assertContent($bytes); $this->assertSame($owner->id, $document->uploaded_by); $this->assertSame(strlen($bytes), $document->size_bytes); // A new authentication context recovers shared documents from the repository. auth()->logout(); $this->actingAs($candidate->fresh())->getJson($base)->assertOk()->assertJsonCount(1, 'documents'); $this->get($url)->assertOk()->assertContent($bytes); $this->actingAs($manager)->get($url)->assertForbidden(); $this->actingAs($outsider)->get($url)->assertForbidden(); $this->actingAs($owner)->postJson($url.'/partilha', ['shared' => false])->assertOk(); $this->actingAs($candidate)->get($url)->assertNotFound(); $this->getJson($base)->assertOk()->assertJsonCount(0, 'documents'); $this->actingAs($owner)->get($url)->assertOk()->assertContent($bytes); $this->assertSame($bytes, Crypt::decryptString(Storage::disk('local')->get($document->storage_key))); Storage::disk('local')->put($document->storage_key, Crypt::encryptString('Tampered QA only')); $this->get($url)->assertNotFound(); } $this->assertSame(3, DB::table('documents')->count()); $this->assertSame(6, DB::table('partnership_events')->where('event_type', 'partnership.document_sharing_changed.v1')->count()); $this->assertSame(3, DB::table('document_malware_scans')->where('result', 'clean')->count()); } }