Files
Investidor-V2/tests/Feature/InvestmentTreatmentScopeTest.php
T

170 lines
13 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\Opportunity;
use App\Models\User;
use App\Services\{CorridorAgribusinessOpportunities, InvestmentCorridorContext, InvestmentConsumerScope, InvestorContext, InvestmentZones, VirtualDesk};
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Tests\TestCase;
final class InvestmentTreatmentScopeTest extends TestCase
{
use DatabaseTransactions;
private User $actor;
private string $id;
private array $choices;
private array $parks;
protected function setUp(): void
{
parent::setUp();$this->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']);
}
}