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

161 lines
12 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\VirtualDesk;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Inertia\Testing\AssertableInertia;
use Tests\TestCase;
final class InvestmentOpportunityChoicesTest extends TestCase
{
use DatabaseTransactions;
private User $investor;
protected function setUp(): void
{
parent::setUp();
$target = DB::selectOne('select current_database() as name, inet_server_port() as port');
$this->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 (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(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']);
// Multiple unselected opportunities must not supply a regime to the consumer.
$this->assertArrayNotHasKey('investment_regime', $snapshot['fields']);
foreach ($after['context']['opportunity_choices'] as $choice) {
$this->getJson('/investimentos/'.$id.'/contexto?opportunity='.$choice['opportunity']['key'])->assertOk()->assertJsonPath('prefill.simulator.investment_regime', 'general');
}
$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(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));
// Mirror the existing browser partition: labels are persisted under selection_labels.
$economic = [...$view['prefill']['simulator'], 'purpose' => 'simulate_only', 'currency' => 'MZN'];
foreach (array_keys($economic) as $field) {
if (str_ends_with($field, '_label')) {
$economic['selection_labels'][substr($field, 0, -6)] = $economic[$field];
unset($economic[$field]);
}
}
$personalKeys = ['email', 'phone', 'person_type', 'investor_origin', 'tax_residency_country', 'fiscal_residency_country', 'entity_stage'];
$draft = $this->postJson('/api/v1/simulation-drafts', ['context' => $id, 'personal_snapshot' => Arr::only($economic, $personalKeys),
'economic_snapshot' => Arr::except($economic, $personalKeys)], ['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);
}
}