app->bind(MalwareScanner::class, fn () => new CleanScannerFixture); } public function test_upload_is_private_until_administrative_review_and_explicit_public_selection(): void { Storage::fake('local'); $manager = $this->manager(true); $zoneId = $this->draft($manager); $response = $this->actingAs($manager)->post('/api/v2/investment/admin/zones/'.$zoneId.'/attachments', [ 'file' => UploadedFile::fake()->createWithContent('decreto.pdf', "%PDF-1.7\nconteudo sintetico de ensaio\n"), 'kind' => 'legal_instrument', 'caption' => 'Diploma de ensaio', 'source_reference' => 'Arquivo oficial, referência sintética', 'source_date' => '2026-01-01', 'rights_status' => 'pending', 'rights_basis' => '', ], ['Accept' => 'application/json'])->assertCreated(); $attachment = $response->json(); $this->assertSame('pending', $attachment['review_status']); $this->assertFalse($attachment['publicly_selected']); $this->assertSame($manager->id, $attachment['uploaded_by']); $document = DB::table('documents')->where('id', $attachment['document_id'])->first(); $this->assertSame('active', $document->status); $this->assertSame('clean', $document->malware_scan_status); $this->assertSame($document->sha256, $document->malware_scanned_sha256); $this->assertSame('confidential', $document->data_classification); $this->assertDatabaseHas('investment_zone_attachment_events', ['attachment_id' => $attachment['id'], 'action' => 'uploaded']); $this->get('/zonas/'.$zoneId.'/anexos/'.$attachment['id'])->assertNotFound(); $this->actingAs($manager)->get('/management/investment/zones/'.$zoneId.'/attachments/'.$attachment['id'])->assertOk(); $this->actingAs($manager)->postJson('/api/v2/investment/admin/zone-attachments/'.$attachment['id'].'/review', [ 'version_no' => 1, 'decision' => 'reviewed', 'rights_status' => 'confirmed', 'rights_basis' => 'Permissão documentada para o ensaio', 'personal_data_status' => 'none', ])->assertOk()->assertJsonPath('independent_review', false)->assertJsonPath('review_status', 'reviewed'); $this->assertDatabaseHas('investment_zone_attachment_events', ['attachment_id' => $attachment['id'], 'action' => 'review']); $this->makeVisibleWithoutOperator($zoneId, $manager); $this->actingAs($manager)->postJson('/api/v2/investment/admin/zone-attachments/'.$attachment['id'].'/select-public', ['version_no' => 2])->assertOk()->assertJsonPath('publicly_selected', true); $this->getJson('/api/v2/investment/zones/'.$zoneId)->assertOk() ->assertJsonPath('attachments.0.caption', 'Diploma de ensaio') ->assertJsonMissingPath('attachments.0.uploaded_by') ->assertJsonMissingPath('attachments.0.rights_basis'); $this->get('/zonas/'.$zoneId.'/anexos/'.$attachment['id'])->assertOk(); $this->actingAs($manager)->postJson('/api/v2/investment/admin/zone-attachments/'.$attachment['id'].'/withdraw-public', ['version_no' => 3])->assertOk()->assertJsonPath('publicly_selected', false); $this->get('/zonas/'.$zoneId.'/anexos/'.$attachment['id'])->assertNotFound(); } public function test_public_selection_rejects_unknown_rights_or_personal_data_and_invalid_file_content(): void { Storage::fake('local'); $manager = $this->manager(true); $zoneId = $this->draft($manager); $this->actingAs($manager)->post('/api/v2/investment/admin/zones/'.$zoneId.'/attachments', [ 'file' => UploadedFile::fake()->createWithContent('bad.pdf', 'not a PDF document'), 'kind' => 'study', 'caption' => 'Invalid test file', 'source_reference' => 'Synthetic test source', 'rights_status' => 'pending', ], ['Accept' => 'application/json'])->assertUnprocessable(); $this->actingAs($manager)->post('/api/v2/investment/admin/zones/'.$zoneId.'/attachments', [ 'file' => UploadedFile::fake()->create('large.pdf', 20481, 'application/pdf'), 'kind' => 'study', 'caption' => 'Oversized test file', 'source_reference' => 'Synthetic test source', 'rights_status' => 'pending', ], ['Accept' => 'application/json'])->assertUnprocessable(); $attachment = $this->actingAs($manager)->post('/api/v2/investment/admin/zones/'.$zoneId.'/attachments', [ 'file' => UploadedFile::fake()->createWithContent('study.pdf', "%PDF-1.7\nsynthetic test\n"), 'kind' => 'study', 'caption' => 'Study under review', 'source_reference' => 'Synthetic test source', 'rights_status' => 'pending', ], ['Accept' => 'application/json'])->assertCreated()->json(); $this->actingAs($manager)->postJson('/api/v2/investment/admin/zone-attachments/'.$attachment['id'].'/review', [ 'version_no' => 1, 'decision' => 'reviewed', 'rights_status' => 'pending', 'personal_data_status' => 'unknown', ])->assertOk()->assertJsonPath('review_status', 'reviewed')->assertJsonPath('independent_review', false); $this->makeVisibleWithoutOperator($zoneId, $manager); $this->actingAs($manager)->postJson('/api/v2/investment/admin/zone-attachments/'.$attachment['id'].'/select-public', ['version_no' => 2])->assertUnprocessable(); $this->get('/zonas/'.$zoneId.'/anexos/'.$attachment['id'])->assertNotFound(); } private function manager(bool $publisher): User { $user = User::factory()->create(['email_verified_at' => now()]); $user->forceFill(['two_factor_secret' => Crypt::encryptString('synthetic-test-secret'), 'two_factor_confirmed_at' => now()])->save(); config(['portal.managers' => [...config('portal.managers', []), $user->email]]); if ($publisher) { config(['investment-zones.publishers' => [...config('investment-zones.publishers', []), $user->email]]); } return $user; } private function draft(User $manager): string { return $this->actingAs($manager)->postJson('/api/v2/investment/admin/zones', [ 'code' => 'ATTACHMENT-TEST-'.Str::uuid(), 'category' => 'zee', 'physical_category' => 'economic_zone', 'legal_regime' => 'zee', 'name' => 'Zona de ensaio', 'description' => 'Descrição de ensaio para anexo.', ])->assertCreated()->json('id'); } private function makeVisibleWithoutOperator(string $zoneId, User $editor): void { $reviewer = $this->manager(false); $sourceId = (string) Str::uuid(); DB::table('investment_legal_sources')->insert([ 'id' => $sourceId, 'title' => 'Fonte de ensaio', 'url' => 'https://example.test/zone-source', 'diploma' => 'Documento sintético', 'article' => 'Artigo de ensaio', 'competent_entity' => 'Entidade sintética', 'valid_from' => today()->toDateString(), 'validation_status' => 'validated', 'owner_id' => $editor->id, 'validator_id' => $reviewer->id, 'validated_at' => now(), 'version_no' => 1, 'created_at' => now(), 'updated_at' => now(), ]); DB::table('investment_zones')->where('id', $zoneId)->update([ 'legal_source_id' => $sourceId, 'publication_status' => 'published', 'validation_status' => 'validated', 'reviewer_id' => $reviewer->id, 'published_at' => now(), 'version_no' => 2, ]); } }