Files
Investidor-V2/tests/Feature/BusinessMarketplacePublicTest.php
2026-11-02 20:31:15 +02:00

206 lines
12 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\InterestRequest;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Inertia\Testing\AssertableInertia as Assert;
use Tests\Support\AuthenticatesQaActors;
use Tests\TestCase;
class BusinessMarketplacePublicTest extends TestCase
{
use AuthenticatesQaActors;
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
config(['existing-business.enabled' => 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];
}
}