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

182 lines
13 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\InterestRequest;
use App\Models\SimulationFormDraft;
use App\Models\User;
use App\Services\CountryCatalogue;
use App\Services\VirtualDesk;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use Inertia\Testing\AssertableInertia as Assert;
use Tests\Support\AuthenticatesQaActors;
use Tests\TestCase;
class PortalAuditContinuationTest extends TestCase
{
use AuthenticatesQaActors;
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
$this->withoutVite();
$identity = DB::selectOne('SELECT current_database() AS database,inet_server_port() AS port');
$this->assertStringEndsWith('_test', $identity->database);
$this->assertSame(55441, (int) $identity->port);
config(['portal.notifications_enabled' => false, 'investmoz_ai.enabled' => false, 'suitecrm.enabled' => false]);
Http::fake();
}
private function publicPayload(): array
{
return ['fullName' => 'Pessoa sintética QA', 'email' => 'portal-integration-test@gmail.com', 'investorType' => 'individual',
'country' => 'PT', 'declared_origin_country_code' => 'ZA', 'sectorInterest' => 'Energia',
'message' => 'Pedido exclusivamente sintético na base QA.', 'privacyConsent' => true];
}
public function test_public_country_and_origin_survive_api_storage_management_recovery_and_statistics(): void
{
$this->actingAs(User::factory()->create());
foreach (['/', '/manifestar-interesse', '/apoio', '/formularios/investidor', '/formularios/empresa'] as $url) {
$this->get($url)->assertInertia(fn (Assert $page) => $page->has('countries', 249));
}
$data = $this->publicPayload();
$this->postJson('/api/v1/interest-requests', [...$data, 'country' => 'ZZ'], ['Idempotency-Key' => (string) Str::uuid()])->assertUnprocessable()->assertJsonValidationErrors('country');
$this->assertDatabaseCount('interest_requests', 0);
$key = ['Idempotency-Key' => (string) Str::uuid()];
$receipt = $this->postJson('/api/v1/interest-requests', $data, $key)->assertCreated()->json();
$this->postJson('/api/v1/interest-requests', $data, $key)->assertExactJson($receipt);
$interest = InterestRequest::firstOrFail();
$this->assertSame('PT', $interest->country);
$this->assertSame('ZA', $interest->metadata['declared_origin_country_code']);
$manager = User::factory()->create();
config(['portal.managers' => [$manager->email]]);
$this->actingAsWithMfa($manager)->get('/management/interests/'.$interest->id)->assertInertia(fn (Assert $page) => $page
->component('management/interest')->where('interest.country', 'PT')->where('declaredOrigin.code', 'ZA')->where('declaredOrigin.name', app(CountryCatalogue::class)->describe('ZA')['name']));
$this->get('/management/crm/leads?origin=ZA')->assertInertia(fn (Assert $page) => $page
->where('originStatistics.interests.0.code', 'ZA')->where('originStatistics.interests.0.count', 1));
$this->get('/management/crm/leads?origin=PT')->assertInertia(fn (Assert $page) => $page->has('originStatistics.interests', 0));
$this->actingAs(User::factory()->create())->get('/management/interests/'.$interest->id)->assertForbidden();
$this->assertDatabaseCount('interest_requests', 1);
Http::assertNothingSent();
}
public function test_support_kinds_keep_iso_country_separate_and_legacy_text_unchanged(): void
{
$data = $this->publicPayload();
$manager = User::factory()->create();
config(['portal.managers' => [$manager->email]]);
foreach (['support', 'investor', 'company', 'partnership'] as $index => $kind) {
$payload = [...$data, 'kind' => $kind, 'company' => 'Empresa sintética QA', 'submission_key' => (string) Str::uuid()];
$this->post('/apoio', $payload)->assertSessionHasNoErrors()->assertRedirect();
$this->assertSame('PT', InterestRequest::latest('id')->firstOrFail()->country);
$this->assertSame('ZA', InterestRequest::latest('id')->firstOrFail()->metadata['declared_origin_country_code']);
$interest = InterestRequest::latest('id')->firstOrFail();
$this->actingAsWithMfa($manager)->get('/management/interests/'.$interest->id)->assertInertia(fn (Assert $p) => $p
->where('interest.country', 'PT')->where('declaredOrigin.code', 'ZA'));
$this->get('/management/interests?country=PT&origin=ZA')->assertInertia(fn (Assert $p) => $p
->where('requests.total', $index + 1)->where('originStatistics.interests.0.code', 'ZA')
->where('originStatistics.interests.0.count', $index + 1));
$this->get('/management/interests?origin=PT')->assertInertia(fn (Assert $p) => $p->where('requests.total', 0)->has('originStatistics.interests', 0));
}
$this->postJson('/apoio', [...$data, 'kind' => 'support', 'country' => 'ZZ', 'submission_key' => (string) Str::uuid()])->assertUnprocessable()->assertJsonValidationErrors('country');
$this->post('/apoio', [...$data, 'kind' => 'support', 'country' => 'Moçambique', 'submission_key' => (string) Str::uuid()])->assertSessionHasNoErrors();
$this->assertSame('Moçambique', InterestRequest::latest('id')->firstOrFail()->country);
$this->assertDatabaseCount('interest_requests', 5);
}
private function territory(string $type, ?string $parent = null, bool $public = true): string
{
$id = (string) Str::uuid();
DB::table('geo_locations')->insert(['id' => $id, 'code' => $id, 'name' => 'Território sintético QA '.$type, 'location_type' => $type,
'parent_id' => $parent, 'source_system' => 'cartografia-workbook', 'external_id' => '964a187ad32ea553b9e112cd159caa90b900050c42b7329594a75bcd798a96ff:'.$id,
'data_classification' => $public ? 'public' : 'internal']);
return $id;
}
public function test_iso_catalogue_does_not_depend_on_geo_publication_and_is_searchable_and_pageable(): void
{
$this->assertDatabaseCount('geo_locations', 0);
$this->assertDatabaseCount('reference_catalog_versions', 0);
$codes = [];
foreach ([1, 2, 3] as $page) {
$rows = $this->getJson('/api/v1/catalogs/countries?per_page=100&page='.$page)->assertOk()->assertJsonPath('total', 249)->json('data');
$codes = [...$codes, ...array_column($rows, 'code')];
}
$this->assertCount(249, array_unique($codes));
$this->assertContains('PT', $codes);
$this->assertContains('ZA', $codes);
$this->getJson('/api/v1/catalogs/countries?q=Mocambique')->assertOk()->assertJsonPath('data.0.value', 'MZ');
$this->getJson('/api/v1/catalogs/countries?q=Portugal')->assertOk()->assertJsonPath('data.0.value', 'PT');
$this->getJson('/api/v1/catalogs/countries?parent_id='.Str::uuid())->assertUnprocessable();
$this->getJson('/api/v1/catalogs/countries?per_page=101')->assertUnprocessable();
$this->assertDatabaseCount('geo_locations', 0);
$this->assertDatabaseCount('reference_catalog_versions', 0);
}
public function test_simulator_recovers_iso_countries_in_private_draft_without_reinterpreting_legacy_values(): void
{
$owner = User::factory()->create();
$this->actingAs($owner);
$data = ['personal_snapshot' => ['fiscal_residency_country' => 'PT'],
'economic_snapshot' => ['purpose' => 'simulate_only', 'journey_type' => 'investor', 'tax_calculation' => 'no', 'funds_origin_countries' => ['PT', 'ZA']]];
$headers = ['Idempotency-Key' => (string) Str::uuid()];
$created = $this->postJson('/api/v1/simulation-drafts', $data, $headers)->assertCreated();
$id = $created->json('data.id');
$row = DB::table('simulation_form_drafts')->where('id', $id)->first();
$this->assertSame('PT', SimulationFormDraft::decryptSnapshot($row->personal_snapshot)['fiscal_residency_country']);
$this->assertSame(['PT', 'ZA'], SimulationFormDraft::decryptSnapshot($row->economic_snapshot)['funds_origin_countries']);
$this->getJson('/api/v1/simulation-drafts/'.$id)->assertOk()->assertJsonPath('data.personal_snapshot.fiscal_residency_country', 'PT')->assertJsonPath('data.economic_snapshot.funds_origin_countries', ['PT', 'ZA']);
$this->postJson('/api/v1/simulation-drafts', [...$data, 'personal_snapshot' => ['fiscal_residency_country' => 'ZZ']], ['Idempotency-Key' => (string) Str::uuid()])
->assertUnprocessable()->assertJsonValidationErrors('personal_snapshot.fiscal_residency_country');
$legacy = [...$data, 'personal_snapshot' => ['fiscal_residency_country' => 'Portugal']];
$this->putJson('/api/v1/simulation-drafts/'.$id, $legacy, ['If-Match' => $created->headers->get('ETag')])->assertOk()->assertJsonPath('data.version_no', 2);
$this->actingAs(User::factory()->create())->getJson('/api/v1/simulation-drafts/'.$id)->assertNotFound();
$this->actingAs($owner)->getJson('/api/v1/simulation-drafts/'.$id)->assertJsonPath('data.personal_snapshot.fiscal_residency_country', 'Portugal');
$this->assertDatabaseCount('simulations', 0);
}
public function test_desk_territory_cascade_is_saved_recovered_and_presented_with_origin_without_cross_account_access(): void
{
$owner = User::factory()->create();
$manager = User::factory()->create();
config(['portal.managers' => [$manager->email]]);
$country = $this->territory('country');
$province = $this->territory('province', $country);
$district = $this->territory('district', $province);
$post = $this->territory('admin_post', $district);
$locality = $this->territory('locality', $post);
$private = $this->territory('district', $province, false);
foreach ([['province', null, $province], ['district', $province, $district], ['admin_post', $district, $post], ['locality', $post, $locality]] as [$type, $parent, $selected]) {
$this->getJson('/api/v1/investment/territory-options?'.http_build_query(['type' => $type, 'parent_id' => $parent, 'q' => 'sintetico']))
->assertOk()->assertJsonPath('data.0.id', $selected);
}
$service = $this->actingAsWithMfa($manager)->postJson('/management/balcao/servicos', ['code' => 'project-preparation', 'confirm' => true])->assertCreated()->json('id');
$case = $this->actingAs($owner)->postJson('/balcao-virtual/dossiers', ['title' => 'Dossier sintético QA', 'client_key' => (string) Str::uuid(), 'reuse_consent' => true])->assertCreated()->json('id');
$id = $this->postJson('/balcao-virtual/dossiers/'.$case.'/pedidos', ['service_version_id' => $service, 'client_key' => (string) Str::uuid()])->assertCreated()->json('id');
$url = '/balcao-virtual/pedidos/'.$id;
$payload = ['title' => 'Projeto sintético QA', 'territory_id' => $locality, 'location_source' => 'Fonte sintética exclusivamente QA', 'declared_origin_country_code' => 'PT'];
$this->putJson($url, ['version' => 1, 'payload' => $payload])->assertOk()->assertJsonPath('version', 2);
$stored = DB::table('desk_requests')->where('id', $id)->value('payload');
$this->assertSame($locality, app(VirtualDesk::class)->decode($stored)['territory_id']);
$this->assertStringNotContainsString($locality, $stored);
$this->get($url)->assertInertia(fn (Assert $page) => $page->component('desk/request')->has('countries', 249)
->where('request.payload.territory_id', $locality)->where('request.payload.declared_origin_country_code', 'PT')
->has('territoryPaths.territory_id', 4)->where('territoryPaths.territory_id.0.id', $province)->where('territoryPaths.territory_id.3.id', $locality));
$this->putJson($url, ['version' => 2, 'payload' => [...$payload, 'territory_id' => $private]])->assertUnprocessable()->assertJsonValidationErrors('payload.territory_id');
$this->getJson($url)->assertJsonPath('request.version', 2)->assertJsonPath('request.payload.territory_id', $locality);
$this->actingAs(User::factory()->create())->getJson($url)->assertForbidden();
$this->actingAs($owner)->getJson($url)->assertJsonPath('territoryPaths.territory_id.3.id', $locality);
$submitted = [...$payload, 'description' => 'Descrição exclusivamente sintética na QA.', 'investment_amount' => '10.00', 'currency' => 'MZN'];
$this->putJson($url, ['version' => 2, 'payload' => $submitted, 'consent' => true, 'submit' => true])->assertOk()->assertJsonPath('status', 'submitted');
$this->actingAsWithMfa($manager)->get('/management/crm/leads?origin=PT')->assertInertia(fn (Assert $page) => $page
->has('originStatistics.interests', 0)->where('originStatistics.desk.0.code', 'PT')->where('originStatistics.desk.0.count', 1));
Http::assertNothingSent();
}
}