161 lines
12 KiB
PHP
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);
|
|
}
|
|
}
|