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 { 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->actingAs($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->actingAs($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->actingAs($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->actingAs($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(); } }