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); } }