assertSame('simulator_fiscal_delta_20261021_test', $target->name); $this->assertSame(55441, (int) $target->port); config(['investor-path.enabled' => true, 'investor-path.private_preparation' => true, 'suitecrm.enabled' => false, 'investmoz_ai.enabled' => false]); $this->withoutVite(); $this->investor = User::factory()->create(); $this->actingAs($this->investor); } private function dossier(): string { return $this->postJson('/investimentos', ['title' => 'QA: investimento conjunto', 'client_key' => (string) Str::uuid(), 'reuse_consent' => true])->assertCreated()->json('id'); } public function test_edit_recovers_requested_opportunity_without_presenting_it_as_already_saved(): void { $record = Opportunity::where('management_creation_key', app(CorridorAgribusinessOpportunities::class)->entries()[0]['key'])->firstOrFail(); $id = $this->dossier(); $before = $this->getJson('/investimentos/'.$id.'/contexto')->assertOk()->json('context'); $this->get('/investimentos/'.$id.'/contexto/editar?source=opportunity:'.$record->id)->assertOk() ->assertInertia(fn (\Inertia\Testing\AssertableInertia $page) => $page->component('investor-path/context') ->where('source.key', 'opportunity:'.$record->id) ->where('requestedChoices.0.opportunity.key', 'opportunity:'.$record->id) ->where('requestedChoices.0.opportunity.title', $record->title)); $after = $this->getJson('/investimentos/'.$id.'/contexto')->assertOk()->json('context'); $this->assertSame($before, $after); } public function test_multiple_opportunities_keep_sites_sources_requirements_and_results_in_one_dossier(): void { $entries = array_slice(app(CorridorAgribusinessOpportunities::class)->entries(), 0, 2); $records = Opportunity::whereIn('management_creation_key', array_column($entries, 'key'))->orderBy('id')->get(); $sites = collect(app(\App\Services\InvestmentCorridorContext::class)->provinces('pemba-lichinga'))->pluck('id'); $this->get('/investimentos/iniciar?opportunities[]='.$records[0]->id.'&opportunities[]='.$records[1]->id)->assertOk(); $id = $this->dossier(); $before = $this->getJson('/investimentos/'.$id.'/contexto')->assertOk()->json(); $input = ['version' => $before['case']['version'], 'selection' => [], 'opportunity_choices' => [ ['source' => 'opportunity:'.$records[0]->id, 'territory' => 'territory:'.$sites[0], 'activities' => ['production', 'processing']], ['source' => 'opportunity:'.$records[1]->id, 'territory' => 'territory:'.$sites[1], 'activities' => ['trade']], ]]; $plan = $this->postJson('/investimentos/'.$id.'/contexto/previa', $input)->assertOk()->json(); $this->assertCount(2, $plan['opportunity_choices']); $this->assertSame($sites[0], $plan['opportunity_choices'][0]['territory']['id']); $this->assertSame($sites[1], $plan['opportunity_choices'][1]['territory']['id']); $this->assertSame(CorridorAgribusinessOpportunities::SOURCE_HASH, $plan['opportunity_choices'][1]['source_sha256']); $this->assertSame('QA: investimento conjunto', $plan['fields']['project_title']); $this->assertSame($records[0]->summary, $plan['fields']['project_summary']); $this->assertContains('processing', array_column($plan['requirements_assessment']['requirements'], 'code')); $this->assertContains('trade', array_column($plan['requirements_assessment']['requirements'], 'code')); $this->assertSame($before['case']['version'], $this->getJson('/investimentos/'.$id.'/contexto')->json('case.version')); $control = [...$input, 'preview_hash' => $plan['preview_hash'], 'operation_key' => (string) Str::uuid(), 'confirm' => true]; $this->postJson('/investimentos/'.$id.'/contexto/confirmar', $control)->assertOk(); $this->postJson('/investimentos/'.$id.'/contexto/confirmar', $control)->assertOk()->assertJsonPath('replayed', true); $after = $this->getJson('/investimentos/'.$id.'/contexto')->assertOk()->json(); $this->assertCount(2, $after['context']['opportunity_choices']); $snapshot = json_decode($after['prefill']['simulator']['context_applicability_snapshot'], true); $this->assertSame($after['context']['opportunity_choices'], $snapshot['opportunity_choices']); $this->assertSame('general', $snapshot['fields']['investment_regime']); $this->assertArrayNotHasKey('tax_benefit', $snapshot['fields']); $this->get('/investimentos/'.$id)->assertOk(); $desk = app(VirtualDesk::class); $case = $desk->case($this->investor, $id); $payload = $desk->decode($case->payload); $resultId = (string) Str::uuid(); $payload['investor_path']['results'] = [['id' => $resultId, 'engine' => 'synthetic-qa-history', 'context_state' => 'current', 'value' => 'preserve']]; DB::table('desk_cases')->where('id', $id)->update(['payload' => $desk->encode($payload)]); $next = ['version' => $after['case']['version'], 'selection' => [], 'opportunity_choices' => [ ['source' => 'opportunity:'.$records[0]->id, 'territory' => 'territory:'.$sites[0], 'activities' => ['production']], ['source' => 'opportunity:'.$records[1]->id, 'territory' => 'territory:'.$sites[0], 'confirm_location_change' => true, 'location_reason' => 'Alternativa territorial escolhida expressamente para comparar o investimento.', 'activities' => ['trade']], ]]; $impact = $this->postJson('/investimentos/'.$id.'/contexto/previa', $next)->assertOk()->json(); $this->assertSame($resultId, $impact['dependent_results'][0]['id']); $this->postJson('/investimentos/'.$id.'/contexto/confirmar', [...$next, 'preview_hash' => $impact['preview_hash'], 'operation_key' => (string) Str::uuid(), 'confirm' => true])->assertOk(); $this->getJson('/investimentos/'.$id.'/contexto')->assertOk()->assertJsonPath('results.0.value', 'preserve')->assertJsonPath('results.0.context_state', 'review_required'); $this->actingAs(User::factory()->create())->getJson('/investimentos/'.$id.'/contexto')->assertForbidden(); } public function test_documented_location_cannot_change_silently_and_stale_source_cannot_be_confirmed(): void { $record = Opportunity::where('management_creation_key', app(CorridorAgribusinessOpportunities::class)->entries()[0]['key'])->firstOrFail(); $sites = collect(app(\App\Services\InvestmentCorridorContext::class)->provinces('pemba-lichinga'))->pluck('id'); $record->primary_territory_id = $sites[0]; $record->save(); $id = $this->dossier(); $version = $this->getJson('/investimentos/'.$id.'/contexto')->json('case.version'); $input = ['version' => $version, 'selection' => [], 'opportunity_choices' => [['source' => 'opportunity:'.$record->id, 'territory' => 'territory:'.$sites[1]]]]; $this->postJson('/investimentos/'.$id.'/contexto/previa', $input)->assertUnprocessable(); $this->postJson('/investimentos/'.$id.'/contexto/previa', ['version' => $version, 'selection' => ['opportunity' => 'opportunity:'.$record->id, 'territory' => 'territory:'.$sites[1]]])->assertUnprocessable(); $input['opportunity_choices'][0]['confirm_location_change'] = true; $input['opportunity_choices'][0]['location_reason'] = 'Escolha explícita de implantação alternativa, sem alterar a origem.'; $plan = $this->postJson('/investimentos/'.$id.'/contexto/previa', $input)->assertOk()->json(); $this->assertSame($sites[0], $record->fresh()->primary_territory_id); $this->postJson('/investimentos/'.$id.'/contexto/confirmar', [...$input, 'preview_hash' => $plan['preview_hash'], 'operation_key' => (string) Str::uuid(), 'confirm' => true])->assertOk(); $input['version'] = $this->getJson('/investimentos/'.$id.'/contexto')->json('case.version'); unset($input['opportunity_choices'][0]['confirm_location_change']); $plan = $this->postJson('/investimentos/'.$id.'/contexto/previa', $input)->assertOk()->assertJsonPath('dependency_changed', false)->json(); $record->title .= ' QA source changed'; $record->save(); $this->postJson('/investimentos/'.$id.'/contexto/confirmar', [...$input, 'preview_hash' => $plan['preview_hash'], 'operation_key' => (string) Str::uuid(), 'confirm' => true])->assertConflict(); $this->getJson('/investimentos/'.$id.'/contexto')->assertOk()->assertJsonPath('context.revision', 1); } public function test_the_34_choices_and_sources_fit_the_existing_simulation_draft_without_reentry(): void { $records = Opportunity::whereIn('management_creation_key', array_column(app(CorridorAgribusinessOpportunities::class)->entries(), 'key'))->orderBy('id')->get(); $this->assertCount(34, $records); $id = $this->dossier(); $view = $this->getJson('/investimentos/'.$id.'/contexto')->assertOk()->json(); $input = ['version' => $view['case']['version'], 'selection' => [], 'opportunity_choices' => $records->map(fn ($r) => ['source' => 'opportunity:'.$r->id])->all()]; $plan = $this->postJson('/investimentos/'.$id.'/contexto/previa', $input)->assertOk()->json(); $this->postJson('/investimentos/'.$id.'/contexto/confirmar', [...$input, 'preview_hash' => $plan['preview_hash'], 'operation_key' => (string) Str::uuid(), 'confirm' => true])->assertOk(); $view = $this->getJson('/investimentos/'.$id.'/contexto')->assertOk()->json(); $snapshot = $view['prefill']['simulator']['context_applicability_snapshot']; $this->assertCount(34, json_decode($snapshot, true)['opportunity_choices']); $this->assertLessThan(1000000, strlen($snapshot)); $draft = $this->postJson('/api/v1/simulation-drafts', ['context' => $id, 'personal_snapshot' => [], 'economic_snapshot' => [...$view['prefill']['simulator'], 'purpose' => 'simulate_only', 'currency' => 'MZN']], ['Idempotency-Key' => (string) Str::uuid()])->assertCreated()->json(); $this->assertNotEmpty($draft); $this->getJson('/investimentos/'.$id.'/contexto')->assertOk()->assertJsonPath('context.opportunity_choices.33.opportunity.key', 'opportunity:'.$records[33]->id); } }