136 lines
11 KiB
PHP
136 lines
11 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\Opportunity;
|
|
use App\Models\User;
|
|
use App\Services\CorridorAgribusinessOpportunities;
|
|
use App\Services\InvestmentCorridorContext;
|
|
use App\Services\InvestmentOpportunityChoices;
|
|
use App\Services\VirtualDesk;
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
use Tests\TestCase;
|
|
|
|
class InvestmentCorridorContextTest extends TestCase
|
|
{
|
|
use DatabaseTransactions;
|
|
private User $investor;
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp(); $this->withoutVite();
|
|
$t=DB::selectOne('select current_database() as name,inet_server_port() as port');
|
|
$this->assertSame('simulator_fiscal_delta_20261021_test',$t->name);$this->assertSame(55441,(int)$t->port);
|
|
config(['investor-path.enabled'=>true,'investor-path.private_preparation'=>true,'suitecrm.enabled'=>false,'investmoz_ai.enabled'=>false]);
|
|
$this->investor=User::factory()->create();$this->actingAs($this->investor);
|
|
}
|
|
private function record(string $corridor): Opportunity
|
|
{
|
|
$entry=collect(app(CorridorAgribusinessOpportunities::class)->entries())->firstWhere('corridor',config('investment_corridors.'.$corridor.'.name'));
|
|
return Opportunity::where('management_creation_key',$entry['key'])->firstOrFail();
|
|
}
|
|
public function test_nonapplicable_land_hides_the_action_and_reopening_recovers_saved_evidence(): void
|
|
{
|
|
$id = $this->postJson('/investimentos', ['title' => 'QA adaptação sem perda', 'client_key' => (string) Str::uuid(), 'reuse_consent' => true])->assertCreated()->json('id');
|
|
$view = $this->getJson('/investimentos/'.$id)->assertOk()->json();
|
|
$this->assertNotNull($view['tools']['land']);
|
|
$this->assertSame('undetermined', collect($view['steps'])->firstWhere('code', 'S10')['applicability']);
|
|
$this->postJson('/investimentos/'.$id.'/etapas/S10', ['version' => $view['case']['version'], 'state' => 'preparing', 'action' => 'prepare', 'reason' => 'Informação territorial já guardada'])->assertOk();
|
|
$view = $this->getJson('/investimentos/'.$id)->json();
|
|
$facts = ['land_needed' => 'no', 'external_funding' => 'no', 'viability' => 'unknown', 'reason' => 'Investimento sem necessidade de terra nem financiamento externo'];
|
|
$this->postJson('/investimentos/'.$id.'/aplicabilidade', ['version' => $view['case']['version'], ...$facts])->assertOk();
|
|
$view = $this->getJson('/investimentos/'.$id)->assertJsonPath('tools.land', null)->json();
|
|
$step = collect($view['steps'])->firstWhere('code', 'S10');
|
|
$this->assertFalse($step['preparation_allowed']);
|
|
$this->assertSame('preparing', $step['saved_state']);
|
|
$this->assertSame('Informação territorial já guardada', $step['detail']['reason']);
|
|
$this->postJson('/investimentos/'.$id.'/etapas/S10', ['version' => $view['case']['version'], 'state' => 'preparing', 'action' => 'prepare', 'reason' => 'Não deve executar etapa sem aplicação'])->assertUnprocessable();
|
|
$this->postJson('/investimentos/'.$id.'/aplicabilidade', ['version' => $view['case']['version'], ...$facts, 'land_needed' => 'yes'])->assertOk();
|
|
$view = $this->getJson('/investimentos/'.$id)->assertOk()->json();
|
|
$this->assertNotNull($view['tools']['land']);
|
|
$this->assertSame('preparing', collect($view['steps'])->firstWhere('code', 'S10')['state']);
|
|
$this->assertCount(19, $view['steps']);
|
|
}
|
|
public function test_tempo_configuration_uses_existing_ids_and_maputo_includes_both_units(): void
|
|
{
|
|
foreach(config('investment_corridors') as $key=>$entry){
|
|
$provinces=app(InvestmentCorridorContext::class)->provinces($key);
|
|
$this->assertEqualsCanonicalizing($entry['provinces'],array_column($provinces,'source_code'));
|
|
$response=$this->getJson('/api/v1/investment/territory-options?type=province&corridor='.$key)->assertOk();
|
|
$this->assertEqualsCanonicalizing(array_column($provinces,'id'),array_column($response->json('data'),'id'));
|
|
}
|
|
$this->assertEqualsCanonicalizing(['Maputo Província','Maputo Cidade'],array_column(app(InvestmentCorridorContext::class)->provinces('maputo'),'name'));
|
|
foreach(['nacala','limpopo'] as $key){
|
|
$choice=app(InvestmentOpportunityChoices::class)->resolve([['source'=>'opportunity:'.$this->record($key)->id]])[0];
|
|
$this->assertSame(app(InvestmentCorridorContext::class)->provinces($key)[0]->id,$choice['territory']['id']);
|
|
}
|
|
}
|
|
public function test_server_refuses_incompatible_location_in_draft_and_confirmation_preview(): void
|
|
{
|
|
$id=$this->postJson('/investimentos',['title'=>'QA corridor guard','client_key'=>(string)Str::uuid(),'reuse_consent'=>true])->assertCreated()->json('id');
|
|
$version=$this->getJson('/investimentos/'.$id.'/contexto')->json('case.version');
|
|
$gaza=app(InvestmentCorridorContext::class)->provinces('limpopo')[0]->id;
|
|
$body=['version'=>$version,'selection'=>[],'opportunity_choices'=>[['source'=>'opportunity:'.$this->record('beira')->id,'corridor_key'=>'beira','territory'=>'territory:'.$gaza]]];
|
|
$this->postJson('/investimentos/'.$id.'/contexto/previa',$body)->assertUnprocessable();
|
|
$this->postJson('/investimentos/'.$id.'/contexto/rascunho',[...$body,'draft_sequence'=>1])->assertUnprocessable();
|
|
$this->getJson('/api/v1/investment/territory-options?type=district&corridor=beira&parent_id='.$gaza)->assertUnprocessable();
|
|
$this->getJson('/investimentos/'.$id.'/contexto')->assertJsonPath('context.revision',0);
|
|
}
|
|
public function test_encrypted_draft_survives_refresh_and_reauthentication_without_committing_context(): void
|
|
{
|
|
$id=$this->postJson('/investimentos',['title'=>'QA recovery','client_key'=>(string)Str::uuid(),'reuse_consent'=>true])->assertCreated()->json('id');
|
|
$before=$this->getJson('/investimentos/'.$id.'/contexto')->json();
|
|
$record=$this->record('beira');$province=app(InvestmentCorridorContext::class)->provinces('beira')[0]->id;
|
|
$body=['version'=>$before['case']['version'],'selection'=>[],'declarations'=>['currency'=>'MZN'],'opportunity_choices'=>[['source'=>'opportunity:'.$record->id,'corridor_key'=>'beira','territory'=>'territory:'.$province]]];
|
|
$this->postJson('/investimentos/'.$id.'/contexto/rascunho',[...$body,'draft_sequence'=>10])->assertOk();
|
|
$this->postJson('/investimentos/'.$id.'/contexto/rascunho',[...$body,'draft_sequence'=>9])->assertConflict();
|
|
$encrypted=DB::table('desk_cases')->where('id',$id)->value('payload');$this->assertStringNotContainsString($province,$encrypted);
|
|
$this->flushSession();$this->actingAs($this->investor);
|
|
$this->get('/investimentos/'.$id.'/contexto/editar')->assertOk()->assertInertia(fn(\Inertia\Testing\AssertableInertia $page)=>$page
|
|
->where('draft.declarations.currency','MZN')->where('requestedChoices.0.territory.id',$province)->where('requestedChoices.0.corridor_key','beira'));
|
|
$after=$this->getJson('/investimentos/'.$id.'/contexto')->json();
|
|
$this->assertSame($before['context'],$after['context']);$this->assertSame($before['case']['version'],$after['case']['version']);
|
|
$empty=[...$body,'opportunity_choices'=>[],'entry_sources'=>['opportunity:'.$record->id],'draft_sequence'=>11];
|
|
$this->postJson('/investimentos/'.$id.'/contexto/rascunho',$empty)->assertOk();
|
|
$this->get('/investimentos/'.$id.'/contexto/editar?source=opportunity:'.$record->id)->assertOk()->assertInertia(fn(\Inertia\Testing\AssertableInertia $page)=>$page->has('requestedChoices',0));
|
|
$new=$this->record('nacala');
|
|
$this->get('/investimentos/'.$id.'/contexto/editar?source=opportunity:'.$new->id)->assertOk()->assertInertia(fn(\Inertia\Testing\AssertableInertia $page)=>$page->where('requestedChoices.0.opportunity.key','opportunity:'.$new->id));
|
|
$this->actingAs(User::factory()->create());$this->postJson('/investimentos/'.$id.'/contexto/rascunho',[...$body,'draft_sequence'=>12])->assertForbidden();
|
|
}
|
|
public function test_zones_need_compatible_documented_territory_and_catalogue_presence_alone_is_insufficient(): void
|
|
{
|
|
$service=app(InvestmentCorridorContext::class);
|
|
$sofala=collect($service->provinces('beira'))->firstWhere('source_code','07')->id;
|
|
$gaza=$service->provinces('limpopo')[0]->id;
|
|
$base=app(\App\Services\InvestmentZones::class)->visible()->whereIn('category',['industrial_park','zee','zfi'])->firstOrFail();
|
|
$clones=[];
|
|
foreach([$sofala,$gaza,null] as $territory){$zone=$base->replicate();$zone->code='QA-'.Str::uuid();$zone->name='Synthetic corridor zone';$zone->primary_territory_id=$territory;$zone->save();$clones[]=$zone;}
|
|
$available=array_column($service->parks('beira',$sofala),'id');
|
|
$this->assertContains($clones[0]->id,$available);$this->assertNotContains($clones[1]->id,$available);$this->assertNotContains($clones[2]->id,$available);
|
|
$id=$this->postJson('/investimentos',['title'=>'QA zone guard','client_key'=>(string)Str::uuid(),'reuse_consent'=>true])->assertCreated()->json('id');
|
|
$version=$this->getJson('/investimentos/'.$id.'/contexto')->json('case.version');
|
|
$body=['version'=>$version,'selection'=>['park'=>'park:'.$clones[1]->id],'opportunity_choices'=>[['source'=>'opportunity:'.$this->record('beira')->id,'corridor_key'=>'beira','territory'=>'territory:'.$sofala]]];
|
|
$this->postJson('/investimentos/'.$id.'/contexto/previa',$body)->assertUnprocessable();
|
|
$this->postJson('/investimentos/'.$id.'/contexto/rascunho',[...$body,'draft_sequence'=>1])->assertUnprocessable();
|
|
$this->getJson('/investimentos/opcoes-corredor?corridor=beira&territory='.$sofala)->assertOk()->assertJsonFragment(['id'=>$clones[0]->id]);
|
|
}
|
|
public function test_corridor_change_keeps_compatible_paths_and_removes_only_incompatible_geography(): void
|
|
{
|
|
$service=app(InvestmentCorridorContext::class);$source='opportunity:'.$this->record('beira')->id;
|
|
$sofala=collect($service->provinces('beira'))->firstWhere('source_code','07')->id;
|
|
$district=app(\App\Services\InvestmentGeography::class)->publicReferences()->where('parent_id',$sofala)->where('location_type','district')->firstOrFail();
|
|
$resolver=app(InvestmentOpportunityChoices::class);
|
|
$old=$resolver->resolve([['source'=>$source,'corridor_key'=>'beira','territory'=>'territory:'.$district->id]]);
|
|
$same=$resolver->resolve([['source'=>$source,'corridor_key'=>'beira','territory'=>'territory:'.$district->id]],$old);
|
|
$this->assertSame($old,$same);
|
|
$changed=$resolver->resolve([['source'=>$source,'corridor_key'=>'limpopo']],$old);
|
|
$this->assertSame($service->provinces('limpopo')[0]->id,$changed[0]['territory']['id']);
|
|
$this->assertCount(1,$changed[0]['territory_path']);
|
|
$this->assertNull($this->record('beira')->primary_territory_id);
|
|
$this->getJson('/api/v1/investment/territory-options?type=locality')->assertOk()->assertJsonPath('data',[]);
|
|
config(['investment_corridors.nacala.provinces'=>['03','99']]);
|
|
$this->getJson('/api/v1/investment/territory-options?type=province&corridor=nacala')->assertStatus(503);
|
|
}
|
|
}
|