170 lines
13 KiB
PHP
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']);
|
|
}
|
|
}
|