withoutVite(); $this->withoutMiddleware(\Illuminate\Routing\Middleware\ThrottleRequests::class); $db=DB::selectOne('select current_database() as name,inet_server_port() as port'); $this->assertSame('simulator_fiscal_delta_20261021_test',$db->name);$this->assertSame(55441,(int)$db->port); config(['investor-path.enabled'=>true,'investor-path.private_preparation'=>true,'suitecrm.enabled'=>false,'investmoz_ai.enabled'=>false]); $this->actor=User::factory()->create();$this->actingAs($this->actor); $this->id=$this->postJson('/investimentos',['title'=>'SYNTHETIC QA — âmbito específico','client_key'=>(string)Str::uuid(),'reuse_consent'=>true])->assertCreated()->json('id'); $corridors=app(InvestmentCorridorContext::class); $records=[];foreach(['beira','limpopo'] as $corridor){ $entry=collect(app(CorridorAgribusinessOpportunities::class)->entries())->firstWhere('corridor',config('investment_corridors.'.$corridor.'.name')); $records[]=Opportunity::where('management_creation_key',$entry['key'])->firstOrFail(); } $sofala=collect($corridors->provinces('beira'))->firstWhere('source_code','07')->id; $gaza=$corridors->provinces('limpopo')[0]->id; $manica=collect($corridors->provinces('beira'))->firstWhere('name','Manica')->id; $template=app(InvestmentZones::class)->visible()->whereIn('category',['industrial_park','zee','zfi'])->firstOrFail(); foreach([$sofala,$gaza,$manica] as $territory){$p=$template->replicate();$p->code='SYNTHETIC-QA-'.Str::uuid();$p->name='SYNTHETIC QA — parque '.$territory;$p->primary_territory_id=$territory;$p->save();$this->parks[]=$p;} $this->choices=[['source'=>'opportunity:'.$records[0]->id,'corridor_key'=>'beira','territory'=>'territory:'.$sofala,'park'=>'park:'.$this->parks[0]->id,'activities'=>['production']],['source'=>'opportunity:'.$records[1]->id,'corridor_key'=>'limpopo','territory'=>'territory:'.$gaza,'park'=>'park:'.$this->parks[1]->id,'activities'=>['trade']]]; $this->confirm(); } private function confirm(): array { $v=$this->getJson('/investimentos/'.$this->id.'/contexto')->assertOk()->json(); $body=['version'=>$v['case']['version'],'selection'=>[],'opportunity_choices'=>$this->choices]; $plan=$this->postJson('/investimentos/'.$this->id.'/contexto/previa',$body)->assertOk()->json(); $this->postJson('/investimentos/'.$this->id.'/contexto/confirmar',[...$body,'preview_hash'=>$plan['preview_hash'],'operation_key'=>(string)Str::uuid(),'confirm'=>true])->assertOk(); return $this->getJson('/investimentos/'.$this->id.'/contexto')->assertOk()->json(); } private function treatment(string $key): array { return $this->getJson('/investimentos/'.$this->id.'?opportunity='.urlencode($key))->assertOk()->json(); } public function test_opportunity_decisions_steps_and_simulator_are_isolated_from_common_dossier(): void { $keys=array_column($this->choices,'source'); foreach ($keys as $index=>$key) { $v=$this->treatment($key); $this->postJson('/investimentos/'.$this->id.'/aplicabilidade',['version'=>$v['case']['version'],'context_opportunity'=>$key,'land_needed'=>$index?'yes':'no','external_funding'=>'unknown','viability'=>'proceed','reason'=>'QA decisão específica por oportunidade'])->assertOk(); } $a=$this->treatment($keys[0]);$b=$this->treatment($keys[1]); $this->assertSame('no',$a['facts']['land_needed']);$this->assertSame('yes',$b['facts']['land_needed']); $this->assertNull($a['tools']['land']);$this->assertNotNull($b['tools']['land']); $this->assertSame('not_needed_for_preparation',collect($a['steps'])->firstWhere('code','S10')['applicability']); $this->postJson('/investimentos/'.$this->id.'/etapas/S10',['version'=>$b['case']['version'],'context_opportunity'=>$keys[1],'state'=>'preparing','action'=>'prepare','reason'=>'Preparação apenas da segunda oportunidade'])->assertOk(); $this->assertSame('preparing',collect($this->treatment($keys[1])['steps'])->firstWhere('code','S10')['saved_state']); $this->assertSame('not_started',collect($this->treatment($keys[0])['steps'])->firstWhere('code','S10')['saved_state']); $v=$this->getJson('/investimentos/'.$this->id.'/contexto?opportunity='.urlencode($keys[1]))->assertOk()->json(); $sim=$v['prefill']['simulator'];$snap=json_decode($sim['context_applicability_snapshot'],true); $this->assertSame($keys[1],$snap['consumer_opportunity']);$this->assertSame('yes',$snap['land_preparation']['need']); $this->assertSame($this->parks[1]->primary_territory_id,$sim['province_id']); $this->assertStringContainsString('opportunity=', $b['tools']['simulator']); $this->assertArrayNotHasKey('province_id',$this->getJson('/investimentos/'.$this->id.'/contexto')->json('prefill.simulator')); $payload=app(VirtualDesk::class)->decode(DB::table('desk_cases')->where('id',$this->id)->value('payload')); $this->assertArrayNotHasKey('facts',$payload['investor_path']); } public function test_land_shortcut_recovers_associated_request_even_when_other_request_is_newer(): void { $ids=[];foreach(array_reverse($this->choices) as $choice) { $ids[$choice['source']]=$this->postJson('/investidor/terra-duat',['payload'=>['investor_path_case_id'=>$this->id,'context_opportunity'=>$choice['source'],'project_reference'=>'QA scope']])->assertCreated()->json('id'); } foreach($ids as $key=>$id) $this->assertStringEndsWith($id,$this->treatment($key)['tools']['land']); $unscoped=$this->getJson('/investimentos/'.$this->id)->assertOk()->json(); foreach($ids as $id) $this->assertStringNotContainsString($id,$unscoped['tools']['land']); $this->getJson('/investimentos/'.$this->id.'?opportunity=opportunity:99999999')->assertUnprocessable(); } public function test_simulator_saves_recovers_selected_scope_and_rejects_foreign_choice(): void { $key=$this->choices[1]['source']; $sim=$this->getJson('/investimentos/'.$this->id.'/contexto?opportunity='.urlencode($key))->json('prefill.simulator'); $data=['context'=>$this->id,'personal_snapshot'=>[], 'economic_snapshot'=>['purpose'=>'simulate_only','project_title'=>'QA âmbito simulado','context_opportunity'=>$key,'context_applicability_snapshot'=>$sim['context_applicability_snapshot'],'financial_tools_snapshot'=>json_encode(['version'=>1,'data'=>[]])]]; $this->postJson('/api/v1/simulation-drafts',$data,['Idempotency-Key'=>(string)Str::uuid()])->assertCreated(); $this->getJson('/api/v1/simulation-drafts/current?context='.$this->id)->assertOk()->assertJsonPath('data.economic_snapshot.context_opportunity',$key); $this->actingAs($this->actor->fresh()); $saved=$this->getJson('/api/v1/simulation-drafts/current?context='.$this->id)->assertOk()->json('data.economic_snapshot'); $this->assertSame($key,json_decode($saved['context_applicability_snapshot'],true)['consumer_opportunity']); $current=$this->getJson('/api/v1/simulation-drafts/current?context='.$this->id)->json('data'); $case=$this->treatment($key); $this->postJson('/investimentos/'.$this->id.'/resultados',['version'=>$case['case']['version'],'draft_id'=>$current['id'],'draft_version'=>$current['version_no'],'context_revision'=>$case['investment_context']['revision'],'context_reviewed'=>true])->assertOk()->assertJsonPath('result.context_opportunity',$key); $other=$this->postJson('/investimentos',['title'=>'QA identidade alheia ao dossier','client_key'=>(string)Str::uuid(),'reuse_consent'=>true])->assertCreated()->json('id'); $this->postJson('/api/v1/simulation-drafts',[...$data,'context'=>$other],['Idempotency-Key'=>(string)Str::uuid()])->assertUnprocessable(); } public function test_authorised_common_contacts_are_reused_without_inventing_company_or_tax_identity(): void { $this->putJson('/investidor/profile',['version'=>0,'payload'=>['phone'=>'+258840001234','country'=>'Portugal','sector'=>'Sector de preferência']])->assertOk(); $id=$this->postJson('/investimentos',['title'=>'QA contactos autorizados','client_key'=>(string)Str::uuid(),'reuse_consent'=>true])->assertCreated()->json('id'); $v=$this->getJson('/investimentos/'.$id.'/contexto')->assertOk()->json('prefill'); foreach(['land','simulator'] as $tool) { $this->assertSame('+258840001234',$v[$tool]['phone']);$this->assertSame($this->actor->email,$v[$tool]['email']); $this->assertArrayNotHasKey('tax_residency_country',$v[$tool]);$this->assertArrayNotHasKey('sector',$v[$tool]); } $this->assertSame($this->actor->name,$v['land']['full_name']); } public function test_idea_sector_and_territory_entries_keep_explicit_source_through_recovery(): void { $id=$this->postJson('/investimentos',['title'=>'QA ideia livre','client_key'=>(string)Str::uuid(),'reuse_consent'=>true])->assertCreated()->json('id'); $this->get('/investimentos/iniciar')->assertOk()->assertInertia(fn($p)=>$p->where('source',null)); $sector=DB::table('sectors')->where('is_active',true)->first(); foreach(['sector:'.$sector->id,'territory:'.$this->parks[1]->primary_territory_id] as $source) { $this->get('/investimentos/'.$id.'/contexto/editar?source='.urlencode($source))->assertOk()->assertInertia(fn($p)=>$p->where('source.key',$source)); $slot=str_starts_with($source,'sector:')?'sector':'territory'; $v=$this->getJson('/investimentos/'.$id.'/contexto')->json(); $body=['version'=>$v['case']['version'],'selection'=>[$slot=>$source]]; $preview=$this->postJson('/investimentos/'.$id.'/contexto/previa',$body)->assertOk()->json(); $this->postJson('/investimentos/'.$id.'/contexto/confirmar',[...$body,'preview_hash'=>$preview['preview_hash'],'operation_key'=>(string)Str::uuid(),'confirm'=>true])->assertOk(); $this->actingAs($this->actor->fresh()); $this->getJson('/investimentos/'.$id.'/contexto')->assertOk()->assertJsonPath('context.selection.'.$slot.'.key',$source); } $view=$this->getJson('/investimentos/'.$id.'/contexto')->json('prefill'); $this->assertSame($this->parks[1]->primary_territory_id,$view['simulator']['province_id']); $this->assertSame($this->parks[1]->primary_territory_id,$view['land']['parcels'][0]['province_id']); $this->assertSame($id,$this->getJson('/investimentos/'.$id)->json('case.id')); } public function test_sector_entry_survives_authentication_redirect_with_same_source(): void { $sector=DB::table('sectors')->where('is_active',true)->first(); $url='/investimentos/iniciar?source='.urlencode('sector:'.$sector->id); \Illuminate\Support\Facades\Auth::logout(); $this->get($url)->assertRedirect('/login')->assertSessionHas('url.intended',url($url)); $this->post('/login',['email'=>$this->actor->email,'password'=>'password'])->assertRedirect(url($url)); $this->get($url)->assertOk()->assertInertia(fn($p)=>$p->where('source.key','sector:'.$sector->id)); } public function test_removing_first_keeps_second_identity_common_edits_and_historical_requests(): void { $before=$this->confirm();$second=$before['context']['opportunity_choices'][1]; $key=$this->choices[1]['source']; $land=$this->postJson('/investidor/terra-duat',['payload'=>['investor_path_case_id'=>$this->id,'context_opportunity'=>$this->choices[0]['source'],'project_reference'=>'QA histórico']])->assertCreated()->json('id'); $this->choices=[$this->choices[1]]; $v=$this->getJson('/investimentos/'.$this->id.'/contexto')->json(); $body=['version'=>$v['case']['version'],'selection'=>[],'opportunity_choices'=>$this->choices,'declarations'=>['project_title'=>'Título deliberado comum']]; $preview=$this->postJson('/investimentos/'.$this->id.'/contexto/previa',$body)->assertOk()->json(); $this->postJson('/investimentos/'.$this->id.'/contexto/confirmar',[...$body,'preview_hash'=>$preview['preview_hash'],'operation_key'=>(string)Str::uuid(),'confirm'=>true])->assertOk(); $after=$this->getJson('/investimentos/'.$this->id.'/contexto')->assertOk()->json(); $this->assertSame($second,$after['context']['opportunity_choices'][0]); $this->assertSame('Título deliberado comum',$after['context']['fields']['project_title']); $this->getJson('/investidor/terra-duat/'.$land)->assertOk(); $this->assertStringNotContainsString($land,$this->treatment($key)['tools']['land']); } }