Files
Investidor-V2/tests/Feature/IndependentDocumentOperationsTest.php
nala 01405cbf8a
tests / ci (push) Canceled after 0s
oportunidades 5
2026-10-10 14:15:28 +02:00

150 lines
8.8 KiB
PHP

<?php
namespace Tests\Feature;
use App\Jobs\ScanDocumentForMalware;
use App\Models\LandAssistanceCase;
use App\Models\User;
use App\Services\Malware\MalwareScanner;
use App\Services\PartnershipExchange;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Tests\Support\SyntheticDocument;
use Tests\TestCase;
class IndependentDocumentOperationsTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
Storage::fake('local');
Queue::fake();
config(['services.malware.scanner' => '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());
}
}