true]); } public function test_public_catalogue_and_detail_exclude_reserved_fields_and_locations(): void { $business = $this->publishedBusiness(); $this->getJson('/api/v1/business-opportunities')->assertOk() ->assertJsonPath('data.0.title', 'Public synthetic opportunity') ->assertJsonMissingPath('data.0.owner_user_id') ->assertJsonMissingPath('data.0.payload') ->assertJsonMissingPath('data.0.geom'); $this->getJson('/api/v1/business-opportunities/'.$business['public_slug'])->assertOk() ->assertJsonPath('data.title', 'Public synthetic opportunity') ->assertJsonMissingPath('data.payload') ->assertJsonMissingPath('data.owner_user_id'); } public function test_interest_is_limited_to_published_business_and_deduplicated_idempotently(): void { $this->withoutVite(); config(['portal.notifications_enabled' => false]); $business = $this->publishedBusiness(); $payload = [ 'existingBusinessId' => $business['id'], 'modality' => 'company_sale', 'fullName' => 'Synthetic investor', 'email' => 'portal-integration-test@gmail.com', 'phone' => '+258 840000000', 'investorType' => 'empresa', 'country' => 'PT', 'declared_origin_country_code' => 'ZA', 'sectorInterest' => 'Energia', 'message' => 'Synthetic interest for the published business listing.', 'privacyConsent' => true, ]; $key = (string) Str::uuid(); $this->get('/negocios/'.$business['public_slug'].'/manifestar-interesse')->assertInertia(fn (Assert $p) => $p ->component('investment/business-interest')->has('countries', 249)->where('record.id', $business['id'])); $this->postJson('/api/v1/interest-requests', $payload, ['Idempotency-Key' => $key])->assertUnauthorized(); $this->assertDatabaseCount('interest_requests', 0); $this->actingAs(User::factory()->create()); $this->postJson('/api/v1/interest-requests', [...$payload, 'country' => 'ZZ'], ['Idempotency-Key' => (string) Str::uuid()])->assertUnprocessable(); $this->assertDatabaseCount('interest_requests', 0); $first = $this->postJson('/api/v1/interest-requests', $payload, ['Idempotency-Key' => $key])->assertCreated(); $second = $this->postJson('/api/v1/interest-requests', $payload, ['Idempotency-Key' => $key])->assertCreated(); $this->assertSame($first->json(), $second->json()); $this->assertDatabaseHas('interest_requests', [ 'existing_business_id' => $business['id'], 'modality' => 'company_sale', 'status' => 'received', ]); $this->assertDatabaseCount('interest_requests', 1); $this->assertDatabaseCount('outbox_events', 1); $this->postJson('/api/v1/interest-requests', [...$payload, 'message' => 'Another valid message for this active interest.'], ['Idempotency-Key' => (string) Str::uuid()]) ->assertStatus(409); $this->assertDatabaseCount('interest_requests', 1); $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 $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', 1)->where('originStatistics.interests.0.code', 'ZA')->where('originStatistics.interests.0.count', 1) ->has('originStatistics.businesses', 0)); $this->get('/management/interests?origin=PT')->assertInertia(fn (Assert $p) => $p->where('requests.total', 0)->has('originStatistics.interests', 0)); $this->actingAs(User::factory()->create())->get('/management/interests/'.$interest->id)->assertForbidden(); } public function test_public_catalogue_filters_by_existing_opportunity_modality_and_opens_response_flow(): void { $business = $this->publishedBusiness('real_estate'); $companySale = $this->publishedBusiness('company_sale'); $landRight = $this->publishedBusiness('land_right'); $this->getJson('/api/v1/business-opportunities?modality=real_estate')->assertOk() ->assertJsonCount(1, 'data')->assertJsonPath('data.0.modality', 'real_estate'); $this->getJson('/api/v1/business-opportunities?modality=company_sale')->assertOk() ->assertJsonCount(1, 'data')->assertJsonPath('data.0.modality', 'company_sale'); $this->getJson('/api/v1/business-opportunities?modality=land_right')->assertOk() ->assertJsonCount(1, 'data')->assertJsonPath('data.0.modality', 'land_right'); $this->get('/negocios?modality=real_estate')->assertOk() ->assertInertia(fn ($page) => $page->component('investment/business-marketplace') ->where('filters.modality', 'real_estate')->has('results.data', 1)); $this->get('/negocios/'.$business['public_slug'])->assertOk() ->assertInertia(fn ($page) => $page->component('investment/business-opportunity') ->where('record.modality', 'real_estate') ->where('similarCatalogueUrl', '/negocios?modality=real_estate')); $this->get('/negocios/'.$business['public_slug'].'/manifestar-interesse')->assertOk() ->assertInertia(fn ($page) => $page->component('investment/business-interest') ->where('record.modality', 'real_estate')); $this->get('/negocios?modality=company_sale')->assertOk() ->assertInertia(fn ($page) => $page->component('investment/business-marketplace') ->where('filters.modality', 'company_sale')->has('results.data', 1)); $this->get('/negocios/'.$companySale['public_slug'])->assertOk() ->assertInertia(fn ($page) => $page->component('investment/business-opportunity') ->where('record.modality', 'company_sale')); $this->get('/negocios?modality=land_right')->assertOk() ->assertInertia(fn ($page) => $page->component('investment/business-marketplace') ->where('filters.modality', 'land_right')->has('results.data', 1)); $this->get('/negocios/'.$landRight['public_slug'])->assertOk() ->assertInertia(fn ($page) => $page->component('investment/business-opportunity') ->where('record.modality', 'land_right')); $this->actingAs(User::factory()->create())->postJson('/api/v1/interest-requests', [ 'existingBusinessId' => $business['id'], 'modality' => 'real_estate', 'fullName' => 'Synthetic investor', 'email' => 'portal-integration-test@gmail.com', 'phone' => '+258 840000000', 'investorType' => 'empresa', 'country' => 'MZ', 'sectorInterest' => 'Imobiliário', 'message' => 'Synthetic interest in the published real estate opportunity.', 'privacyConsent' => true, ], ['Idempotency-Key' => (string) Str::uuid()])->assertCreated(); $this->assertDatabaseHas('interest_requests', ['existing_business_id' => $business['id'], 'modality' => 'real_estate', 'status' => 'received']); } public function test_similar_catalogue_uses_only_disclosed_primary_province_and_preserves_publication_limits(): void { $this->withoutVite(); $country = (string) Str::uuid(); $province = (string) Str::uuid(); DB::table('geo_locations')->insert([ ['id' => $country, 'name' => 'Synthetic country', 'code' => $country, 'location_type' => 'country', 'parent_id' => null], ['id' => $province, 'name' => 'Synthetic province', 'code' => $province, 'location_type' => 'province', 'parent_id' => $country], ]); foreach ([$country, $province] as $location) { DB::table('geo_locations')->where('id', $location)->update([ 'data_classification' => 'public', 'source_system' => 'cartografia-workbook', 'external_id' => '964a187ad32ea553b9e112cd159caa90b900050c42b7329594a75bcd798a96ff:'.$location, ]); } $origin = $this->publishedBusiness(); $match = $this->publishedBusiness(); $reserved = $this->publishedBusiness(); $expired = $this->publishedBusiness(); foreach ([$origin, $match, $reserved, $expired] as $business) { DB::table('existing_business_locations')->insert([ 'id' => (string) Str::uuid(), 'existing_business_id' => $business['id'], 'province_id' => $province, 'precision_level' => 'province', 'is_primary' => true, 'is_public' => $business['id'] !== $reserved['id'], ]); } DB::table('existing_businesses')->where('id', $expired['id'])->update(['expires_at' => now()->subDay()]); $url = '/negocios?modality=company_sale&province_id='.$province; $this->get('/negocios/'.$origin['public_slug'])->assertInertia(fn (Assert $page) => $page ->where('similarCatalogueUrl', $url)->where('record.province', 'Synthetic province')); $this->get('/negocios/'.$reserved['public_slug'])->assertInertia(fn (Assert $page) => $page ->where('similarCatalogueUrl', '/negocios?modality=company_sale')->where('record.province', null)); $this->getJson('/api/v1/business-opportunities?modality=company_sale&province_id='.$province)->assertOk() ->assertJsonCount(2, 'data')->assertJsonMissing(['id' => $reserved['id']])->assertJsonMissing(['id' => $expired['id']]); DB::table('geo_locations')->where('id', $province)->update(['source_system' => 'unpublished-synthetic']); $this->get('/negocios/'.$origin['public_slug'])->assertInertia(fn (Assert $page) => $page ->where('similarCatalogueUrl', '/negocios?modality=company_sale')); } /** @return array{id:string,public_slug:string} */ private function publishedBusiness(string $modality = 'company_sale'): array { $id = (string) Str::uuid(); $slug = 'public-synthetic-'.$id; $now = now(); DB::table('existing_businesses')->insert([ 'id' => $id, 'code' => 'SYN-'.strtoupper(substr(str_replace('-', '', $id), 0, 12)), 'owner_user_id' => User::factory()->create()->id, 'modality' => $modality, 'status' => 'published', 'classification' => 'confidential', 'version_no' => 1, 'payload' => Crypt::encryptString(json_encode(['title' => 'Private synthetic title'], JSON_THROW_ON_ERROR)), 'public_title' => 'Public synthetic opportunity', 'public_slug' => $slug, 'published_at' => $now, 'created_at' => $now, 'updated_at' => $now, ]); return ['id' => $id, 'public_slug' => $slug]; } }