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

170 lines
9.3 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\Opportunity;
use App\Services\CorridorAgribusinessOpportunities;
use App\Services\InvestmentGeography;
use App\Support\IsolatedQaDatabase;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Str;
use Inertia\Testing\AssertableInertia;
use Tests\TestCase;
class ProvinceSearchCompatibilityTest extends TestCase
{
use DatabaseTransactions;
public function createApplication(): Application
{
$app = parent::createApplication();
if (! IsolatedQaDatabase::allowsSchemaChanges()) {
throw new \LogicException('Isolated QA required');
}
return $app;
}
protected function setUp(): void
{
parent::setUp();
$this->withoutVite();
}
private function createOpportunity(?string $territory, string $prefix, string $status = 'published', ?string $province = null): Opportunity
{
return Opportunity::create(['sector_id' => 1, 'reference' => 'SYNTHETIC-QA-'.Str::uuid(), 'slug' => 'synthetic-'.Str::uuid(),
'title' => $prefix.' synthetic provincial search', 'status' => $status, 'published_at' => now()->subMinute(),
'primary_territory_id' => $territory, 'province' => $province, 'district' => null, 'geography_scope' => 'specific']);
}
private function requests(array $filters): array
{
$query = http_build_query([...$filters, 'per_page' => 50]);
return ['/api/v1/investment/search?'.$query, '/api/v1/opportunities/search?'.$query];
}
public function test_a_single_province_includes_all_lower_levels_and_linked_locations_in_both_apis(): void
{
$geo = app(InvestmentGeography::class);
$locality = $geo->publicReferences()->where('location_type', 'locality')->orderBy('id')->first();
$this->assertNotNull($locality, 'Public locality fixture required');
$path = $geo->path($locality->id);
$this->assertCount(4, $path);
$prefix = 'SYNTHETIC-'.Str::uuid();
$records = array_map(fn ($node) => $this->createOpportunity($node->id, $prefix, 'published', 'Legacy label differs from canonical hierarchy'), $path);
$linked = $this->createOpportunity(null, $prefix);
$linked->additionalTerritories()->attach($locality->id);
$records[] = $linked;
$private = $this->createOpportunity($locality->id, $prefix, 'draft');
$deleted = $this->createOpportunity($locality->id, $prefix);
$deleted->delete();
$otherProvince = $geo->publicReferences()->where('location_type', 'province')->where('id', '<>', $path[0]->id)->first();
$outside = $this->createOpportunity($otherProvince->id, $prefix);
foreach ([['province_id' => $path[0]->id], ['province' => $path[0]->name], ['geo_location_id' => $path[0]->id]] as $entry) {
foreach ($this->requests([...$entry, 'q' => $prefix]) as $url) {
$response = $this->getJson($url)->assertOk()->assertJsonPath('total', 5);
$this->assertEqualsCanonicalizing(array_map(fn ($row) => $row->id, $records), array_column($response->json('data'), 'id'), $url);
foreach ($response->json('data') as $row) {
$this->assertNull($row['district']);
}
foreach ([$private, $deleted, $outside] as $row) {
$this->assertNotContains($row->id, array_column($response->json('data'), 'id'));
}
}
}
foreach ($records as $record) {
$this->assertNull($record->fresh()->district);
$this->assertSame($record->primary_territory_id, $record->fresh()->primary_territory_id);
}
$this->assertSame([$locality->id], $linked->additionalTerritories()->pluck('geo_locations.id')->all());
foreach ($this->requests(['province_id' => $path[0]->id, 'district_id' => $path[1]->id, 'q' => $prefix]) as $url) {
// Province-only records are not silently assigned to a district.
$this->getJson($url)->assertOk()->assertJsonPath('total', 4);
}
foreach ($this->requests(['district_id' => $path[1]->id]) as $url) {
$this->getJson($url)->assertUnprocessable();
}
}
public function test_published_province_declaration_is_found_without_inventing_a_district_or_overriding_an_explicit_other_territory(): void
{
$geo = app(InvestmentGeography::class);
$province = $geo->publicReferences()->where('location_type', 'province')->first();
$other = $geo->publicReferences()->where('location_type', 'province')->where('id', '<>', $province->id)->first();
$district = $geo->publicReferences()->where('parent_id', $province->id)->where('location_type', 'district')->first();
$prefix = 'SYNTHETIC-'.Str::uuid();
$legacy = $this->createOpportunity(null, $prefix, 'published', $province->name);
$contradictory = $this->createOpportunity($other->id, $prefix, 'published', $province->name);
$linkedOutside = $this->createOpportunity(null, $prefix, 'published', $province->name);
$linkedOutside->additionalTerritories()->attach($other->id);
foreach ([['province' => $province->name], ['province_id' => $province->id], ['geo_location_id' => $province->id]] as $entry) {
foreach ($this->requests([...$entry, 'q' => $prefix]) as $url) {
$this->getJson($url)->assertOk()->assertJsonPath('total', 1)->assertJsonPath('data.0.id', $legacy->id)->assertJsonPath('data.0.district', null);
}
}
foreach ($this->requests(['province_id' => $province->id, 'district_id' => $district->id, 'q' => $prefix]) as $url) {
$this->getJson($url)->assertOk()->assertJsonPath('total', 0);
}
$this->assertNull($legacy->fresh()->primary_territory_id);
$this->assertNull($legacy->fresh()->district);
$this->assertSame($other->id, $contradictory->fresh()->primary_territory_id);
}
public function test_provincial_only_search_covers_the_eleven_provinces_without_including_neighbouring_provinces(): void
{
$geo = app(InvestmentGeography::class);
$provinces = $geo->publicReferences()->where('location_type', 'province')->get();
$this->assertCount(11, $provinces);
$prefix = 'SYNTHETIC-'.Str::uuid();
$records = [];
foreach ($provinces as $province) {
$district = $geo->publicReferences()->where('parent_id', $province->id)->where('location_type', 'district')->first();
$this->assertNotNull($district, $province->name);
$records[$province->id] = $this->createOpportunity($district->id, $prefix);
}
foreach ($provinces as $province) {
foreach ([['province_id' => $province->id], ['province' => $province->name]] as $entry) {
foreach ($this->requests([...$entry, 'q' => $prefix]) as $url) {
$this->getJson($url)->assertOk()->assertJsonPath('total', 1)->assertJsonPath('data.0.id', $records[$province->id]->id);
}
}
}
}
public function test_maputo_names_ids_page_map_and_export_share_the_existing_approved_corridor_coverage(): void
{
$provinceId = 'bfde2651-2b46-5d9c-a500-733fa55a8c5c';
$expected = Opportunity::published()->where('workflow_context->corridor_agribusiness->source_sha256', CorridorAgribusinessOpportunities::SOURCE_HASH)
->where('workflow_context->corridor_agribusiness->corridor', 'Corredor de Maputo')->pluck('id')->all();
$this->assertCount(5, $expected);
foreach ([['province' => 'Maputo Província'], ['province' => ' mApUtO Província '], ['province_id' => $provinceId]] as $entry) {
foreach ($this->requests([...$entry, 'sector' => 'agronegocio']) as $url) {
$response = $this->getJson($url)->assertOk()->assertJsonPath('total', 5);
$this->assertEqualsCanonicalizing($expected, array_column($response->json('data'), 'id'));
foreach ($response->json('data') as $row) {
$this->assertNull($row['district']);
$this->assertStringContainsString('contexto provincial do corredor', $row['territorial_summary']);
}
}
}
$query = http_build_query(['sector' => 'agronegocio', 'province' => 'Maputo Província']);
$this->get('/oportunidades?'.$query)->assertInertia(fn (AssertableInertia $page) => $page
->component('investment/catalogue')->where('filters.province_id', $provinceId)->missing('filters.province')
->has('territorySelection', 1)->where('results.total', 5));
$this->getJson('/api/v1/investment/map?'.$query)->assertOk()->assertJsonCount(0, 'features')->assertJsonCount(5, 'unlocated');
$csv = $this->get('/api/v1/investment/export?'.$query)->assertOk()->streamedContent();
foreach (Opportunity::whereIn('id', $expected)->get() as $row) {
$this->assertStringContainsString($row->title, $csv);
}
foreach ($this->requests(['sector' => 'turismo', 'province_id' => $provinceId]) as $url) {
$response = $this->getJson($url)->assertOk();
foreach ($response->json('data') as $row) {
$this->assertNotContains($row['id'], $expected);
}
}
}
}