From 51d536b7ba0d525323200418155459620ec07e41 Mon Sep 17 00:00:00 2001 From: nala Date: Tue, 27 Oct 2026 12:01:24 +0200 Subject: [PATCH] fix: keep private idea preparation valid in complement lookup --- app/Services/InvestmentComplements.php | 4 ++++ tests/Feature/ProactiveInvestmentTest.php | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/app/Services/InvestmentComplements.php b/app/Services/InvestmentComplements.php index 1467518c..e1463b03 100644 --- a/app/Services/InvestmentComplements.php +++ b/app/Services/InvestmentComplements.php @@ -46,6 +46,10 @@ final class InvestmentComplements } else { $reference = app(InvestorContext::class)->reference($origin); abort_unless($reference['kind'] === 'opportunity', 422); + // The context resolver has already checked the actor's private scope. + // A private preparation is valid dossier input, not a public offer + // from which this public-complement catalogue can infer services. + if (($reference['visibility'] ?? 'public') === 'private') return []; $principal = Opportunity::published()->findOrFail($reference['id']); $text = $principal->summary; $basis = 'Resumo cadastrado da oportunidade '.$principal->reference; diff --git a/tests/Feature/ProactiveInvestmentTest.php b/tests/Feature/ProactiveInvestmentTest.php index 4de523bd..7e36e483 100644 --- a/tests/Feature/ProactiveInvestmentTest.php +++ b/tests/Feature/ProactiveInvestmentTest.php @@ -140,6 +140,28 @@ final class ProactiveInvestmentTest extends TestCase $this->assertTrue(app(\App\Services\InvestmentCatalogue::class)->query(['perspective'=>$kind])->whereKey($id)->exists()); } } + public function test_private_analysis_complements_keep_preparation_valid_without_inventing_public_offers(): void + { + $private=$this->create('Gurué');$public=$this->create(); + $input=$this->publication($public,['summary'=>'SYNTHETIC QA: silos e armazenagem como componente documentada.']); + $plan=$this->postJson('/minhas-oportunidades/'.$public->id.'/divulgacao/previa',$input)->assertOk()->json(); + $this->postJson('/minhas-oportunidades/'.$public->id.'/divulgacao/confirmar',['token'=>$plan['token'],'operation_key'=>(string)Str::uuid(),'confirm'=>true,'reason'=>'SYNTHETIC QA public component in transaction only'])->assertOk(); + $result=$this->postJson('/complementos-investimento',['contexts'=>[['origin'=>'opportunity:'.$private->id],['origin'=>'opportunity:'.$public->id]]])->assertOk()->json(); + $this->assertSame([],$result['issues']);$this->assertNotEmpty($result['items']); + foreach($result['items']as$item)$this->assertSame('opportunity:'.$public->id,$item['origin']['key']); + $this->assertStringNotContainsString('PRIVATE-QA-',json_encode($result)); + $this->assertFalse(Opportunity::published()->whereKey($private->id)->exists()); + $this->assertSame('draft',$private->fresh()->status); + } + public function test_private_analysis_complements_still_hide_unauthorised_sources(): void + { + $private=$this->create('Gurué');$this->login(User::factory()->create()); + $this->postJson('/complementos-investimento',['origin'=>'opportunity:'.$private->id])->assertNotFound(); + $result=$this->postJson('/complementos-investimento',['contexts'=>[['origin'=>'opportunity:'.$private->id]]])->assertOk()->json(); + $this->assertSame([],$result['items']);$this->assertCount(1,$result['issues']); + $this->assertStringNotContainsString($private->title,json_encode($result)); + $this->assertStringNotContainsString('PRIVATE-QA-',json_encode($result)); + } public function test_authenticated_multi_idea_preparation_keeps_each_origin_and_deliberate_edits_in_existing_dossier(): void { $first=$this->create();$second=$this->create('Gurué');$this->publish($first);$this->publish($second);$investor=User::factory()->create();$this->login($investor);