withoutVite(); Http::fake(); Mail::fake(); Queue::fake(); Storage::fake('local'); config(['portal.profile_access_enabled' => true, 'suitecrm.enabled' => false, 'investmoz_ai.enabled' => false]); $this->actor = User::factory()->create(); $this->support = User::factory()->create(); $approver = User::factory()->create(); $organization = (string) Str::uuid(); DB::table('organizations')->insert(['id' => $organization, 'name' => 'SYNTHETIC upload QA organization', 'organization_type' => 'private']); DB::table('memberships')->insert(['id' => (string) Str::uuid(), 'user_id' => $this->actor->id, 'organization_id' => $organization, 'role' => 'officer']); foreach ([[$this->actor, 'promoter'], [$this->support, 'technical_support']] as [$user,$role]) { app(PortalProfileAdministration::class)->insertGrant((string) Str::uuid(), $approver, ['user_id' => $user->id, 'role' => $role, 'organization_id' => $organization, 'opportunity_types' => ['investment_project'], 'source' => 'synthetic-resource-incident-fixture']); } $id = (string) Str::uuid(); $this->opportunity = new Opportunity(['reference' => $id, 'slug' => $id, 'title' => 'PRIVATE SYNTHETIC upload investment', 'status' => 'draft']); $this->opportunity->forceFill(['owner_user_id' => $this->actor->id, 'organization_id' => $organization, 'opportunity_category' => 'investment_project', 'management_version' => 4, 'workflow_context' => ['working_state' => 'draft']])->save(); $this->actingAs($this->actor); } private function url(string $kind = 'documents'): string { return '/minhas-oportunidades/'.$this->opportunity->id.'/resources/'.$kind; } private function input(): array { return ['etag' => app(OpportunityManagement::class)->etag($this->opportunity->fresh()), 'creation_key' => (string) Str::uuid(), 'reason' => 'PRIVATE SYNTHETIC upload reason', 'payload' => ['title' => 'PRIVATE SYNTHETIC original', 'classification' => 'confidential'], 'file' => UploadedFile::fake()->createWithContent('PRIVATE-synthetic.pdf', "%PDF-1.7\nSYNTHETIC private bytes\n%%EOF")]; } private function incidentCount(): int { return DB::table('audit_events')->where('source_system', 'portal-opportunity-incident')->count(); } public function test_database_failure_rolls_back_upload_preserves_prior_originals_and_routes_private_metadata_to_support(): void { $before = $this->opportunity->fresh()->getRawOriginal(); DB::statement("ALTER TABLE audit_events ADD CONSTRAINT qa_upload_failure CHECK (source_system <> 'opportunity-resources')"); $response = $this->post($this->url(), $this->input(), ['Accept' => 'application/json'])->assertStatus(503)->assertJsonPath('incident_recorded', true); $reference = $response->json('reference'); $this->assertTrue(Str::isUuid($reference)); $this->assertStringNotContainsString('SQLSTATE', $response->getContent()); $this->assertStringNotContainsString('PRIVATE-synthetic', $response->getContent()); $this->assertSame($before, $this->opportunity->fresh()->getRawOriginal()); $this->assertSame(0, DB::table('documents')->count()); $this->assertSame(0, DB::table('opportunity_documents')->count()); $this->assertSame([], Storage::disk('local')->allFiles()); $data = json_decode(DB::table('audit_events')->where('id', $reference)->value('after_data'), true)['incident']; $this->assertSame('upload_document', $data['operation']); $this->assertSame(4, $data['observed_version']); $this->assertSame('database_failure', $data['error_code']); $this->assertStringNotContainsString('PRIVATE', json_encode($data)); $this->get('/minhas-oportunidades/'.$this->opportunity->id.'/resources')->assertOk()->assertHeader('Cache-Control', 'no-store, private')->assertInertia(fn (Assert $page) => $page->where('technicalIncidents.0.reference', $reference)->where('technicalIncidents.0.operation', 'upload_document')); $this->actingAs($this->support)->get('/suporte-tecnico/incidentes?unread=1')->assertOk()->assertInertia(fn (Assert $page) => $page->where('incidents.total', 1)->where('incidents.data.0.operation', 'upload_document')); $this->get('/minhas-oportunidades/'.$this->opportunity->id.'/resources')->assertForbidden(); DB::statement('ALTER TABLE audit_events DROP CONSTRAINT qa_upload_failure'); $this->actingAs($this->actor)->post($this->url(), $this->input(), ['Accept' => 'application/json'])->assertCreated(); $this->assertSame(1, $this->incidentCount()); } public function test_expected_validation_scope_version_and_quarantine_errors_do_not_create_incidents(): void { $bad = $this->input(); $bad['file'] = UploadedFile::fake()->createWithContent('bad.pdf', 'not a pdf')->mimeType('text/plain'); $this->post($this->url(), $bad, ['Accept' => 'application/json'])->assertUnprocessable(); $stale = $this->input(); $stale['etag'] = str_repeat('0', 64); $this->post($this->url(), $stale, ['Accept' => 'application/json'])->assertConflict(); $this->actingAs($this->support)->post($this->url(), $this->input(), ['Accept' => 'application/json'])->assertForbidden(); $this->actingAs($this->actor)->postJson($this->url('images').'/'.Str::uuid().'/prepare', ['etag' => app(OpportunityManagement::class)->etag($this->opportunity), 'version' => 1, 'reason' => 'SYNTHETIC missing image request'])->assertStatus(function_exists('imagecreatefromstring') ? 404 : 423); $this->assertSame(0, $this->incidentCount()); $this->assertSame([], Storage::disk('local')->allFiles()); } public function test_partial_storage_failure_is_reported_and_cleanup_failure_does_not_hide_the_original_result(): void { $disk = Storage::disk('local'); $fault = Mockery::mock($disk); $fault->shouldReceive('putFileAs')->andReturnUsing(function ($path, $file, $name) use ($disk) { $disk->put($path.'/'.$name, 'SYNTHETIC partial bytes'); return false; }); $fault->shouldReceive('delete')->andThrow(new \RuntimeException('PRIVATE cleanup failure')); Storage::shouldReceive('disk')->with('local')->andReturn($fault); $response = $this->post($this->url(), $this->input(), ['Accept' => 'application/json'])->assertStatus(503)->assertJsonPath('incident_recorded', true); $this->assertStringNotContainsString('PRIVATE', $response->getContent()); $this->assertCount(1, $disk->allFiles()); $this->assertSame(0, DB::table('documents')->count()); $this->assertSame(1, $this->incidentCount()); } public function test_cleanup_preserves_referenced_originals_uncertain_commit_bytes_and_failed_reference_checks(): void { $cleanup = app(OpportunityResourceFileCleanup::class); $disk = Storage::disk('local'); $key = 'opportunity-resources/'.Str::uuid(); $disk->put($key, Crypt::encryptString('SYNTHETIC preserved original')); $hash = hash('sha256', $disk->get($key)); DB::table('documents')->insert(['id' => (string) Str::uuid(), 'source_system' => 'opportunity-resources', 'file_name' => 'synthetic.pdf', 'media_type' => 'application/pdf', 'storage_key' => $key, 'sha256' => str_repeat('a', 64), 'status' => 'quarantined', 'storage_encryption' => 'laravel', 'data_classification' => 'restricted', 'size_bytes' => 1, 'uploaded_by' => $this->actor->id]); $cleanup->afterFailure([$key], new \RuntimeException('SYNTHETIC failure after committed document')); $this->assertSame($hash, hash('sha256', $disk->get($key))); $unknown = 'opportunity-resources/'.Str::uuid(); $disk->put($unknown, 'SYNTHETIC uncertain bytes'); $cleanup->afterFailure([$unknown], new \PDOException('server closed the connection unexpectedly')); $this->assertTrue($disk->exists($unknown)); DB::statement('ALTER TABLE documents RENAME COLUMN storage_key TO qa_unavailable_storage_key'); $cleanup->afterFailure([$unknown], new \RuntimeException('SYNTHETIC failed reference lookup')); $this->assertTrue($disk->exists($unknown)); $this->assertGreaterThan(0, DB::table('users')->count()); DB::statement('ALTER TABLE documents RENAME COLUMN qa_unavailable_storage_key TO storage_key'); $cleanup->afterFailure([$unknown], new \RuntimeException('SYNTHETIC confirmed rollback')); $this->assertFalse($disk->exists($unknown)); } public function test_failed_incident_persistence_is_reported_honestly_and_does_not_leak_the_upload(): void { DB::statement("ALTER TABLE audit_events ADD CONSTRAINT qa_no_resource_audit CHECK (source_system NOT IN ('opportunity-resources','portal-opportunity-incident'))"); $response = $this->post($this->url(), $this->input(), ['Accept' => 'application/json'])->assertStatus(503)->assertJsonPath('incident_recorded', false); $this->assertTrue(Str::isUuid($response->json('reference'))); $this->assertSame(0, $this->incidentCount()); $this->assertSame([], Storage::disk('local')->allFiles()); $this->assertStringNotContainsString('PRIVATE', $response->getContent()); } public function test_filtered_rls_visibility_never_authorizes_removal_of_a_referenced_original(): void { $key = 'opportunity-resources/'.Str::uuid(); $disk = Storage::disk('local'); $disk->put($key, 'SYNTHETIC original behind RLS'); DB::table('documents')->insert(['id' => (string) Str::uuid(), 'source_system' => 'opportunity-resources', 'file_name' => 'synthetic-rls.pdf', 'media_type' => 'application/pdf', 'storage_key' => $key, 'sha256' => hash('sha256', 'SYNTHETIC original behind RLS'), 'status' => 'quarantined', 'size_bytes' => 1, 'uploaded_by' => $this->actor->id]); DB::statement('ALTER TABLE documents ENABLE ROW LEVEL SECURITY'); DB::statement('ALTER TABLE documents FORCE ROW LEVEL SECURITY'); DB::statement('SET LOCAL ROLE pg_read_all_data'); try { $this->assertTrue(DB::selectOne("SELECT row_security_active('documents'::regclass) AS active")->active); $this->assertSame(0, DB::table('documents')->where('storage_key', $key)->count()); app(OpportunityResourceFileCleanup::class)->afterFailure([$key], new \RuntimeException('SYNTHETIC failure with filtered reference visibility')); $this->assertTrue($disk->exists($key)); } finally { DB::statement('RESET ROLE'); } $this->assertSame(1, DB::table('documents')->where('storage_key', $key)->count()); $this->assertSame('investmoz_test', DB::selectOne('SELECT current_user AS role')->role); } private function png(): string { $chunk = fn ($name, $bytes) => pack('N', strlen($bytes)).$name.$bytes.pack('N', crc32($name.$bytes)); return "\x89PNG\r\n\x1a\n".$chunk('IHDR', pack('NNCCCCC', 500, 500, 8, 2, 0, 0, 0)).$chunk('IDAT', gzcompress(str_repeat("\0", 500 * 1501))).$chunk('IEND', ''); } public function test_image_upload_and_preparation_failures_have_distinct_labels_and_no_automatic_retry(): void { $input = $this->input(); $input['payload'] = ['title' => 'SYNTHETIC image', 'alt_text' => 'SYNTHETIC plain image', 'caption' => 'SYNTHETIC image caption', 'rights_status' => 'owned']; $input['file'] = UploadedFile::fake()->createWithContent('synthetic.png', $this->png()); DB::statement("ALTER TABLE audit_events ADD CONSTRAINT qa_image_upload_failure CHECK (source_system <> 'opportunity-resources')"); $this->post($this->url('images'), $input, ['Accept' => 'application/json'])->assertStatus(503)->assertJsonPath('incident_recorded', true); DB::statement('ALTER TABLE audit_events DROP CONSTRAINT qa_image_upload_failure'); $this->assertSame('upload_image', app(OpportunityTechnicalIncidents::class)->recent($this->actor, $this->opportunity)[0]['operation']); $processor = Mockery::mock(OpportunityImageProcessor::class); $processor->shouldReceive('prepare')->once()->andThrow(new \RuntimeException('PRIVATE SYNTHETIC processor failure')); $this->app->instance(OpportunityImageProcessor::class, $processor); $this->postJson($this->url('images').'/'.Str::uuid().'/prepare', ['etag' => app(OpportunityManagement::class)->etag($this->opportunity), 'version' => 1, 'reason' => 'SYNTHETIC explicit image processing'])->assertStatus(503)->assertJsonPath('incident_recorded', true); $operations = array_column(app(OpportunityTechnicalIncidents::class)->recent($this->actor, $this->opportunity), 'operation'); $this->assertContains('prepare_image', $operations); $this->assertContains('upload_image', $operations); $this->assertSame(2, $this->incidentCount()); } public function test_derivative_partial_storage_failure_preserves_original_and_cleans_new_unreferenced_bytes(): void { if (! app(OpportunityImageProcessor::class)->available()) { $this->postJson($this->url('images').'/'.Str::uuid().'/prepare', ['etag' => app(OpportunityManagement::class)->etag($this->opportunity), 'version' => 1, 'reason' => 'SYNTHETIC unavailable processing'])->assertStatus(423); $this->assertSame(0, $this->incidentCount()); return; } $input = $this->input(); $input['payload'] = ['title' => 'SYNTHETIC image', 'alt_text' => 'SYNTHETIC plain image', 'caption' => 'SYNTHETIC image caption', 'rights_status' => 'owned']; $input['file'] = UploadedFile::fake()->createWithContent('synthetic.png', $this->png()); $id = $this->post($this->url('images'), $input, ['Accept' => 'application/json'])->assertCreated()->json('id'); $image = DB::table('opportunity_images')->where('id', $id)->first(); (new ScanDocumentForMalware($image->document_id))->handle(new CleanScannerFixture); $original = DB::table('documents')->where('id', $image->document_id)->first(); $disk = Storage::disk('local'); $before = $disk->get($original->storage_key); $opportunityBefore = $this->opportunity->fresh()->getRawOriginal(); $fault = Mockery::mock($disk); $fault->shouldReceive('put')->andReturnUsing(function ($key, $bytes) use ($disk) { $disk->put($key, $bytes); return false; }); Storage::shouldReceive('disk')->with('local')->andReturn($fault); $this->postJson($this->url('images').'/'.$id.'/prepare', ['etag' => app(OpportunityManagement::class)->etag($this->opportunity->fresh()), 'version' => 1, 'reason' => 'SYNTHETIC derivative storage failure'])->assertStatus(503)->assertJsonPath('incident_recorded', true); $this->assertSame([$original->storage_key], $disk->allFiles()); $this->assertSame($before, $disk->get($original->storage_key)); $this->assertEquals($image, DB::table('opportunity_images')->where('id', $id)->first()); $this->assertSame($opportunityBefore, $this->opportunity->fresh()->getRawOriginal()); $this->assertSame(1, DB::table('documents')->count()); $this->assertSame('prepare_image', app(OpportunityTechnicalIncidents::class)->recent($this->actor, $this->opportunity)[0]['operation']); } }