204 lines
14 KiB
PHP
204 lines
14 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\InvestmentZones;
|
|
use App\Services\VirtualDesk;
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
use Illuminate\Routing\Middleware\ThrottleRequests;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
use Tests\TestCase;
|
|
|
|
final class InvestmentTreatmentScopeTest extends TestCase
|
|
{
|
|
use DatabaseTransactions;
|
|
|
|
private User $actor;
|
|
|
|
private string $id;
|
|
|
|
private array $choices;
|
|
|
|
private array $parks;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->withoutVite();
|
|
$this->withoutMiddleware(ThrottleRequests::class);
|
|
$db = DB::selectOne('select current_database() as name,inet_server_port() as port');
|
|
$this->assertSame('simulator_fiscal_delta_20261021_test', $db->name);
|
|
$this->assertSame(55441, (int) $db->port);
|
|
config(['investor-path.enabled' => true, 'investor-path.private_preparation' => true, 'suitecrm.enabled' => false, 'investmoz_ai.enabled' => false]);
|
|
$this->actor = User::factory()->create();
|
|
$this->actingAs($this->actor);
|
|
$this->id = $this->postJson('/investimentos', ['title' => 'SYNTHETIC QA — âmbito específico', 'client_key' => (string) Str::uuid(), 'reuse_consent' => true])->assertCreated()->json('id');
|
|
$corridors = app(InvestmentCorridorContext::class);
|
|
$records = [];
|
|
foreach (['beira', 'limpopo'] as $corridor) {
|
|
$entry = collect(app(CorridorAgribusinessOpportunities::class)->entries())->firstWhere('corridor', config('investment_corridors.'.$corridor.'.name'));
|
|
$records[] = Opportunity::where('management_creation_key', $entry['key'])->firstOrFail();
|
|
}
|
|
$sofala = collect($corridors->provinces('beira'))->firstWhere('source_code', '07')->id;
|
|
$gaza = $corridors->provinces('limpopo')[0]->id;
|
|
$manica = collect($corridors->provinces('beira'))->firstWhere('name', 'Manica')->id;
|
|
$template = app(InvestmentZones::class)->visible()->whereIn('category', ['industrial_park', 'zee', 'zfi'])->firstOrFail();
|
|
foreach ([$sofala, $gaza, $manica] as $territory) {
|
|
$p = $template->replicate();
|
|
$p->code = 'SYNTHETIC-QA-'.Str::uuid();
|
|
$p->name = 'SYNTHETIC QA — parque '.$territory;
|
|
$p->primary_territory_id = $territory;
|
|
$p->save();
|
|
$this->parks[] = $p;
|
|
}
|
|
$this->choices = [['source' => 'opportunity:'.$records[0]->id, 'corridor_key' => 'beira', 'territory' => 'territory:'.$sofala, 'park' => 'park:'.$this->parks[0]->id, 'activities' => ['production']], ['source' => 'opportunity:'.$records[1]->id, 'corridor_key' => 'limpopo', 'territory' => 'territory:'.$gaza, 'park' => 'park:'.$this->parks[1]->id, 'activities' => ['trade']]];
|
|
$this->confirm();
|
|
}
|
|
|
|
private function confirm(): array
|
|
{
|
|
$v = $this->getJson('/investimentos/'.$this->id.'/contexto')->assertOk()->json();
|
|
$body = ['version' => $v['case']['version'], 'selection' => [], 'opportunity_choices' => $this->choices];
|
|
$plan = $this->postJson('/investimentos/'.$this->id.'/contexto/previa', $body)->assertOk()->json();
|
|
$this->postJson('/investimentos/'.$this->id.'/contexto/confirmar', [...$body, 'preview_hash' => $plan['preview_hash'], 'operation_key' => (string) Str::uuid(), 'confirm' => true])->assertOk();
|
|
|
|
return $this->getJson('/investimentos/'.$this->id.'/contexto')->assertOk()->json();
|
|
}
|
|
|
|
private function treatment(string $key): array
|
|
{
|
|
return $this->getJson('/investimentos/'.$this->id.'?opportunity='.urlencode($key))->assertOk()->json();
|
|
}
|
|
|
|
public function test_opportunity_decisions_steps_and_simulator_are_isolated_from_common_dossier(): void
|
|
{
|
|
$keys = array_column($this->choices, 'source');
|
|
foreach ($keys as $index => $key) {
|
|
$v = $this->treatment($key);
|
|
$this->postJson('/investimentos/'.$this->id.'/aplicabilidade', ['version' => $v['case']['version'], 'context_opportunity' => $key, 'land_needed' => $index ? 'yes' : 'no', 'external_funding' => 'unknown', 'viability' => 'proceed', 'reason' => 'QA decisão específica por oportunidade'])->assertOk();
|
|
}
|
|
$a = $this->treatment($keys[0]);
|
|
$b = $this->treatment($keys[1]);
|
|
$this->assertSame('no', $a['facts']['land_needed']);
|
|
$this->assertSame('yes', $b['facts']['land_needed']);
|
|
$this->assertNull($a['tools']['land']);
|
|
$this->assertNotNull($b['tools']['land']);
|
|
$this->assertSame('not_needed_for_preparation', collect($a['steps'])->firstWhere('code', 'S10')['applicability']);
|
|
$this->postJson('/investimentos/'.$this->id.'/etapas/S10', ['version' => $b['case']['version'], 'context_opportunity' => $keys[1], 'state' => 'preparing', 'action' => 'prepare', 'reason' => 'Preparação apenas da segunda oportunidade'])->assertOk();
|
|
$this->assertSame('preparing', collect($this->treatment($keys[1])['steps'])->firstWhere('code', 'S10')['saved_state']);
|
|
$this->assertSame('not_started', collect($this->treatment($keys[0])['steps'])->firstWhere('code', 'S10')['saved_state']);
|
|
$v = $this->getJson('/investimentos/'.$this->id.'/contexto?opportunity='.urlencode($keys[1]))->assertOk()->json();
|
|
$sim = $v['prefill']['simulator'];
|
|
$snap = json_decode($sim['context_applicability_snapshot'], true);
|
|
$this->assertSame($keys[1], $snap['consumer_opportunity']);
|
|
$this->assertSame('yes', $snap['land_preparation']['need']);
|
|
$this->assertSame($this->parks[1]->primary_territory_id, $sim['province_id']);
|
|
$this->assertStringContainsString('opportunity=', $b['tools']['simulator']);
|
|
$this->assertArrayNotHasKey('province_id', $this->getJson('/investimentos/'.$this->id.'/contexto')->json('prefill.simulator'));
|
|
$payload = app(VirtualDesk::class)->decode(DB::table('desk_cases')->where('id', $this->id)->value('payload'));
|
|
$this->assertArrayNotHasKey('facts', $payload['investor_path']);
|
|
}
|
|
|
|
public function test_land_shortcut_recovers_associated_request_even_when_other_request_is_newer(): void
|
|
{
|
|
$ids = [];
|
|
foreach (array_reverse($this->choices) as $choice) {
|
|
$ids[$choice['source']] = $this->postJson('/investidor/terra-duat', ['payload' => ['investor_path_case_id' => $this->id, 'context_opportunity' => $choice['source'], 'project_reference' => 'QA scope']])->assertCreated()->json('id');
|
|
}
|
|
foreach ($ids as $key => $id) {
|
|
$this->assertStringEndsWith($id, $this->treatment($key)['tools']['land']);
|
|
}
|
|
$unscoped = $this->getJson('/investimentos/'.$this->id)->assertOk()->json();
|
|
foreach ($ids as $id) {
|
|
$this->assertStringNotContainsString($id, $unscoped['tools']['land']);
|
|
}
|
|
$this->getJson('/investimentos/'.$this->id.'?opportunity=opportunity:99999999')->assertUnprocessable();
|
|
}
|
|
|
|
public function test_simulator_saves_recovers_selected_scope_and_rejects_foreign_choice(): void
|
|
{
|
|
$key = $this->choices[1]['source'];
|
|
$sim = $this->getJson('/investimentos/'.$this->id.'/contexto?opportunity='.urlencode($key))->json('prefill.simulator');
|
|
$data = ['context' => $this->id, 'personal_snapshot' => [], 'economic_snapshot' => ['purpose' => 'simulate_only', 'project_title' => 'QA âmbito simulado', 'context_opportunity' => $key, 'context_applicability_snapshot' => $sim['context_applicability_snapshot'], 'financial_tools_snapshot' => json_encode(['version' => 1, 'data' => []])]];
|
|
$this->postJson('/api/v1/simulation-drafts', $data, ['Idempotency-Key' => (string) Str::uuid()])->assertCreated();
|
|
$this->getJson('/api/v1/simulation-drafts/current?context='.$this->id)->assertOk()->assertJsonPath('data.economic_snapshot.context_opportunity', $key);
|
|
$this->actingAs($this->actor->fresh());
|
|
$saved = $this->getJson('/api/v1/simulation-drafts/current?context='.$this->id)->assertOk()->json('data.economic_snapshot');
|
|
$this->assertSame($key, json_decode($saved['context_applicability_snapshot'], true)['consumer_opportunity']);
|
|
$current = $this->getJson('/api/v1/simulation-drafts/current?context='.$this->id)->json('data');
|
|
$case = $this->treatment($key);
|
|
$this->postJson('/investimentos/'.$this->id.'/resultados', ['version' => $case['case']['version'], 'draft_id' => $current['id'], 'draft_version' => $current['version_no'], 'context_revision' => $case['investment_context']['revision'], 'context_reviewed' => true])->assertOk()->assertJsonPath('result.context_opportunity', $key);
|
|
$other = $this->postJson('/investimentos', ['title' => 'QA identidade alheia ao dossier', 'client_key' => (string) Str::uuid(), 'reuse_consent' => true])->assertCreated()->json('id');
|
|
$this->postJson('/api/v1/simulation-drafts', [...$data, 'context' => $other], ['Idempotency-Key' => (string) Str::uuid()])->assertUnprocessable();
|
|
}
|
|
|
|
public function test_authorised_common_contacts_are_reused_without_inventing_company_or_tax_identity(): void
|
|
{
|
|
$this->putJson('/investidor/profile', ['version' => 0, 'payload' => ['phone' => '+258840001234', 'country' => 'Portugal', 'sector' => 'Sector de preferência']])->assertOk();
|
|
$id = $this->postJson('/investimentos', ['title' => 'QA contactos autorizados', 'client_key' => (string) Str::uuid(), 'reuse_consent' => true])->assertCreated()->json('id');
|
|
$v = $this->getJson('/investimentos/'.$id.'/contexto')->assertOk()->json('prefill');
|
|
foreach (['land', 'simulator'] as $tool) {
|
|
$this->assertSame('+258840001234', $v[$tool]['phone']);
|
|
$this->assertSame($this->actor->email, $v[$tool]['email']);
|
|
$this->assertArrayNotHasKey('tax_residency_country', $v[$tool]);
|
|
$this->assertArrayNotHasKey('sector', $v[$tool]);
|
|
}
|
|
$this->assertSame($this->actor->name, $v['land']['full_name']);
|
|
}
|
|
|
|
public function test_idea_sector_and_territory_entries_keep_explicit_source_through_recovery(): void
|
|
{
|
|
$id = $this->postJson('/investimentos', ['title' => 'QA ideia livre', 'client_key' => (string) Str::uuid(), 'reuse_consent' => true])->assertCreated()->json('id');
|
|
$this->get('/investimentos/iniciar')->assertOk()->assertInertia(fn ($p) => $p->where('source', null));
|
|
$sector = DB::table('sectors')->where('is_active', true)->first();
|
|
foreach (['sector:'.$sector->id, 'territory:'.$this->parks[1]->primary_territory_id] as $source) {
|
|
$this->get('/investimentos/'.$id.'/contexto/editar?source='.urlencode($source))->assertOk()->assertInertia(fn ($p) => $p->where('source.key', $source));
|
|
$slot = str_starts_with($source, 'sector:') ? 'sector' : 'territory';
|
|
$v = $this->getJson('/investimentos/'.$id.'/contexto')->json();
|
|
$body = ['version' => $v['case']['version'], 'selection' => [$slot => $source]];
|
|
$preview = $this->postJson('/investimentos/'.$id.'/contexto/previa', $body)->assertOk()->json();
|
|
$this->postJson('/investimentos/'.$id.'/contexto/confirmar', [...$body, 'preview_hash' => $preview['preview_hash'], 'operation_key' => (string) Str::uuid(), 'confirm' => true])->assertOk();
|
|
$this->actingAs($this->actor->fresh());
|
|
$this->getJson('/investimentos/'.$id.'/contexto')->assertOk()->assertJsonPath('context.selection.'.$slot.'.key', $source);
|
|
}
|
|
$view = $this->getJson('/investimentos/'.$id.'/contexto')->json('prefill');
|
|
$this->assertSame($this->parks[1]->primary_territory_id, $view['simulator']['province_id']);
|
|
$this->assertSame($this->parks[1]->primary_territory_id, $view['land']['parcels'][0]['province_id']);
|
|
$this->assertSame($id, $this->getJson('/investimentos/'.$id)->json('case.id'));
|
|
}
|
|
|
|
public function test_sector_entry_survives_authentication_redirect_with_same_source(): void
|
|
{
|
|
$sector = DB::table('sectors')->where('is_active', true)->first();
|
|
$url = '/investimentos/iniciar?source='.urlencode('sector:'.$sector->id);
|
|
Auth::logout();
|
|
$this->get($url)->assertRedirect('/login')->assertSessionHas('url.intended', url($url));
|
|
$this->post('/login', ['email' => $this->actor->email, 'password' => 'password'])->assertRedirect(url($url));
|
|
$this->get($url)->assertOk()->assertInertia(fn ($p) => $p->where('source.key', 'sector:'.$sector->id));
|
|
}
|
|
|
|
public function test_removing_first_keeps_second_identity_common_edits_and_historical_requests(): void
|
|
{
|
|
$before = $this->confirm();
|
|
$second = $before['context']['opportunity_choices'][1];
|
|
$key = $this->choices[1]['source'];
|
|
$land = $this->postJson('/investidor/terra-duat', ['payload' => ['investor_path_case_id' => $this->id, 'context_opportunity' => $this->choices[0]['source'], 'project_reference' => 'QA histórico']])->assertCreated()->json('id');
|
|
$this->choices = [$this->choices[1]];
|
|
$v = $this->getJson('/investimentos/'.$this->id.'/contexto')->json();
|
|
$body = ['version' => $v['case']['version'], 'selection' => [], 'opportunity_choices' => $this->choices, 'declarations' => ['project_title' => 'Título deliberado comum']];
|
|
$preview = $this->postJson('/investimentos/'.$this->id.'/contexto/previa', $body)->assertOk()->json();
|
|
$this->postJson('/investimentos/'.$this->id.'/contexto/confirmar', [...$body, 'preview_hash' => $preview['preview_hash'], 'operation_key' => (string) Str::uuid(), 'confirm' => true])->assertOk();
|
|
$after = $this->getJson('/investimentos/'.$this->id.'/contexto')->assertOk()->json();
|
|
$this->assertSame($second, $after['context']['opportunity_choices'][0]);
|
|
$this->assertSame('Título deliberado comum', $after['context']['fields']['project_title']);
|
|
$this->getJson('/investidor/terra-duat/'.$land)->assertOk();
|
|
$this->assertStringNotContainsString($land, $this->treatment($key)['tools']['land']);
|
|
}
|
|
}
|