386 lines
28 KiB
PHP
386 lines
28 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\InvestmentDossier;
|
|
use App\Models\Opportunity;
|
|
use App\Models\Project;
|
|
use App\Models\Sector;
|
|
use App\Models\User;
|
|
use App\Services\InvestmentCatalogue;
|
|
use App\Services\InvestmentGeography;
|
|
use App\Services\OpportunityClassification;
|
|
use App\Services\OpportunityManagement;
|
|
use Database\Seeders\InvestmentAreaSeeder;
|
|
use Illuminate\Database\QueryException;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Crypt;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
use Inertia\Testing\AssertableInertia;
|
|
use Tests\Support\AuthenticatesQaActors;
|
|
use Tests\TestCase;
|
|
|
|
class InvestmentTerritoryTest extends TestCase
|
|
{
|
|
use AuthenticatesQaActors;
|
|
use RefreshDatabase;
|
|
|
|
public function test_confirmed_coordinates_create_a_marker_without_hiding_missing_records(): void
|
|
{
|
|
$manager = User::factory()->create();
|
|
config(['portal.managers' => [$manager->email]]);
|
|
$sector = Sector::create(['name' => 'Synthetic map', 'slug' => 'synthetic-map', 'is_active' => true]);
|
|
$record = Opportunity::create(['sector_id' => $sector->id, 'reference' => 'MAP-COORD', 'slug' => 'map-coord', 'title' => 'Synthetic marker', 'status' => 'published', 'published_at' => now()->subMinute()]);
|
|
$this->getJson('/api/v1/investment/search')->assertOk()->assertJsonPath('total', 1)->assertJsonPath('data.0.id', $record->id);
|
|
$this->get('/oportunidades')->assertInertia(fn (AssertableInertia $page) => $page->where('results.total', 1)->where('results.data.0.id', $record->id));
|
|
$this->getJson('/api/v1/investment/map')->assertOk()->assertJsonPath('matched_count', 1)
|
|
->assertJsonPath('georeferenced_count', 0)->assertJsonPath('complete', false)->assertJsonCount(1, 'unlocated');
|
|
$this->actingAsWithMfa($manager);
|
|
$url = '/management/opportunities/'.$record->id.'/coordinates';
|
|
$payload = ['version' => 0, 'latitude' => -25.9, 'longitude' => 32.5, 'source_reference' => 'Synthetic coordinates for automated tests only', 'confirmed_site' => true];
|
|
$this->postJson($url, [...$payload, 'latitude' => 91])->assertUnprocessable();
|
|
$this->postJson($url, [...$payload, 'confirmed_site' => false])->assertUnprocessable();
|
|
$this->postJson($url, $payload)->assertOk()->assertJsonPath('version', 1);
|
|
$this->postJson($url, $payload)->assertConflict();
|
|
$this->getJson('/api/v1/investment/search')->assertOk()->assertJsonPath('total', 1)->assertJsonPath('data.0.id', $record->id);
|
|
$this->get('/oportunidades')->assertInertia(fn (AssertableInertia $page) => $page->where('results.total', 1)->where('results.data.0.id', $record->id));
|
|
$this->getJson('/api/v1/investment/map')->assertOk()->assertJsonPath('matched_count', 1)
|
|
->assertJsonPath('georeferenced_count', 1)->assertJsonPath('complete', true)
|
|
->assertJsonPath('features.0.properties.id', $record->id)->assertJsonPath('features.0.geometry.coordinates.0', 32.5);
|
|
$record->update(['status' => 'draft']);
|
|
$this->getJson('/api/v1/investment/search')->assertOk()->assertJsonPath('total', 0)->assertJsonCount(0, 'data');
|
|
$this->get('/oportunidades')->assertInertia(fn (AssertableInertia $page) => $page->where('results.total', 0)->has('results.data', 0));
|
|
$this->getJson('/api/v1/investment/map')->assertOk()->assertJsonPath('matched_count', 0)->assertJsonPath('complete', true)
|
|
->assertJsonCount(0, 'features')->assertJsonCount(0, 'unlocated');
|
|
}
|
|
|
|
public function test_public_cascade_filters_saved_locations_and_hides_unapproved_references(): void
|
|
{
|
|
$country = $this->location('country');
|
|
$province = $this->location('province', $country);
|
|
$district = $this->location('district', $province);
|
|
$other = $this->location('province', $country);
|
|
$private = $this->location('province', $country);
|
|
foreach ([$country, $province, $district, $other] as $id) {
|
|
DB::table('geo_locations')->where('id', $id)->update(['source_system' => 'cartografia-workbook', 'external_id' => '964a187ad32ea553b9e112cd159caa90b900050c42b7329594a75bcd798a96ff:'.$id, 'data_classification' => 'public']);
|
|
}
|
|
$sector = Sector::create(['name' => 'Synthetic', 'slug' => 'synthetic', 'is_active' => true]);
|
|
$opportunity = Opportunity::create(['sector_id' => $sector->id, 'reference' => 'SYNTHETIC-PUBLIC', 'slug' => 'synthetic-public', 'title' => 'Synthetic', 'status' => 'published', 'published_at' => now()->subMinute(), 'primary_territory_id' => $district]);
|
|
$this->getJson('/api/v1/investment/territory-options?type=province')->assertOk()->assertJsonCount(2, 'data');
|
|
$this->getJson('/api/v1/investment/territory-options?type=district&parent_id='.$province)->assertOk()->assertJsonPath('data.0.id', $district);
|
|
$service = app(InvestmentCatalogue::class);
|
|
$this->assertSame([$opportunity->id], $service->query(['province_id' => $province])->pluck('id')->all());
|
|
$this->assertSame([], $service->query(['province_id' => $other])->pluck('id')->all());
|
|
$this->getJson('/oportunidades?province_id='.$other.'&district_id='.$district)->assertUnprocessable();
|
|
$this->getJson('/oportunidades?province_id='.$private)->assertUnprocessable();
|
|
$this->get('/oportunidades?province_id='.$province)->assertOk();
|
|
$this->assertCount(2, $service->context($opportunity->id)['primary_territory_path']);
|
|
$this->assertEqualsCanonicalizing([$country, $province, $district, $other], app(OpportunityClassification::class)->targets('location')->pluck('id')->all());
|
|
$manager = User::factory()->create();
|
|
config(['portal.managers' => [$manager->email]]);
|
|
$created = $this->actingAsWithMfa($manager)->postJson('/management/opportunities/drafts', ['sector_id' => $sector->id, 'creation_key' => (string) Str::uuid(), 'reason' => 'Cadastro territorial de rascunho apenas em QA', 'title' => 'Synthetic create',
|
|
'estimated_investment_currency' => 'USD', 'province_id' => $province, 'district_id' => $district])->assertCreated()->json();
|
|
$this->assertDatabaseHas('opportunities', ['id' => $created['id'], 'primary_territory_id' => $district]);
|
|
$this->post('/management/clusters', ['code' => 'GEO-CREATE', 'name' => 'Synthetic cluster', 'sector' => 'Synthetic', 'status' => 'planned', 'province_id' => $province, 'district_id' => $district])->assertRedirect();
|
|
$this->assertDatabaseHas('economic_clusters', ['code' => 'GEO-CREATE', 'primary_territory_id' => $district]);
|
|
$this->actingAsWithMfa($manager);
|
|
$manager->forceFill(['two_factor_secret' => Crypt::encryptString('SYNTHETIC'), 'two_factor_confirmed_at' => now()])->save();
|
|
$zone = $this->postJson('/api/v2/investment/admin/zones', ['code' => 'GEO-ZEE', 'name' => 'Synthetic ZEE', 'category' => 'zee', 'province_id' => $province, 'district_id' => $district])->assertCreated()->json('id');
|
|
$this->assertDatabaseHas('investment_zones', ['id' => $zone, 'primary_territory_id' => $district]);
|
|
$this->getJson('/api/v2/investment/admin/zones?province_id='.$province)->assertOk()->assertJsonPath('records.total', 1);
|
|
$this->getJson('/api/v2/investment/admin/zones?province_id='.$other)->assertOk()->assertJsonPath('records.total', 0);
|
|
$this->postJson('/management/territories/zones/'.$zone, ['version' => 1, 'path' => [$province]])->assertOk();
|
|
$this->assertDatabaseHas('investment_zones', ['id' => $zone, 'primary_territory_id' => $province, 'version_no' => 2, 'publication_status' => 'draft']);
|
|
$this->assertSame(2, DB::table('investment_zone_revisions')->where('zone_id', $zone)->count());
|
|
DB::table('geo_locations')->where('id', $province)->update(['data_classification' => 'internal']);
|
|
$this->getJson('/api/v1/investment/territory-options?type=district&parent_id='.$province)->assertOk()->assertJsonCount(0, 'data');
|
|
}
|
|
|
|
public function test_opportunities_match_hierarchical_and_multiple_public_locations_without_expanding(): void
|
|
{
|
|
$country = $this->location('country');
|
|
$provinceA = $this->location('province', $country);
|
|
$districtA = $this->location('district', $provinceA);
|
|
$postA = $this->location('admin_post', $districtA);
|
|
$localityA = $this->location('locality', $postA);
|
|
$provinceB = $this->location('province', $country);
|
|
$districtB = $this->location('district', $provinceB);
|
|
$postB = $this->location('admin_post', $districtB);
|
|
$localityB = $this->location('locality', $postB);
|
|
$ids = [$country, $provinceA, $districtA, $postA, $localityA, $provinceB, $districtB, $postB, $localityB];
|
|
foreach ($ids as $id) {
|
|
DB::table('geo_locations')->where('id', $id)->update([
|
|
'source_system' => 'cartografia-workbook',
|
|
'external_id' => '964a187ad32ea553b9e112cd159caa90b900050c42b7329594a75bcd798a96ff:'.$id,
|
|
'data_classification' => 'public',
|
|
]);
|
|
}
|
|
$sector = Sector::create(['name' => 'Territorial test', 'slug' => 'territorial-test', 'is_active' => true]);
|
|
$make = function (string $code, ?string $primary, string $scope = 'specific') use ($sector): Opportunity {
|
|
return Opportunity::create(['sector_id' => $sector->id, 'reference' => $code, 'slug' => strtolower($code), 'title' => $code,
|
|
'status' => 'published', 'published_at' => now()->subMinute(), 'primary_territory_id' => $primary, 'geography_scope' => $scope]);
|
|
};
|
|
$a = $make('AREA-A', $districtA);
|
|
$a->update(['estimated_investment_amount' => '1234.50', 'estimated_investment_currency' => 'USD']);
|
|
$multi = $make('AREA-MULTI', $localityB);
|
|
DB::table('opportunity_locations')->insert(['opportunity_id' => $multi->id, 'geo_location_id' => $localityA]);
|
|
$b = $make('AREA-B', $localityB);
|
|
$national = $make('AREA-NATIONAL', null, 'national');
|
|
$unlocated = $make('AREA-UNKNOWN', null, 'unspecified');
|
|
|
|
$service = app(InvestmentCatalogue::class);
|
|
$this->assertEqualsCanonicalizing([$a->id, $multi->id], $service->query(['province_id' => $provinceA])->pluck('id')->all());
|
|
$this->assertEqualsCanonicalizing([$a->id, $multi->id], $service->query(['province_id' => $provinceA, 'district_id' => $districtA])->pluck('id')->all());
|
|
$this->assertEqualsCanonicalizing([$multi->id], $service->query(['province_id' => $provinceA, 'district_id' => $districtA,
|
|
'admin_post_id' => $postA, 'locality_id' => $localityA])->pluck('id')->all());
|
|
$this->assertEqualsCanonicalizing([$a->id, $multi->id, $national->id], $service->query(['province_id' => $provinceA, 'include_national' => true])->pluck('id')->all());
|
|
$this->assertSame([], $service->query(['province_id' => $provinceB, 'sector' => 'no-such-sector'])->pluck('id')->all());
|
|
$this->assertEqualsCanonicalizing([$a->id, $multi->id, $b->id, $unlocated->id], $service->query([])->pluck('id')->all());
|
|
$this->assertSame(2, $service->query(['province_id' => $provinceA])->reorder()->count());
|
|
$map = $this->getJson('/api/v1/investment/map?province_id='.$provinceA)->assertOk()->assertJsonPath('matched_count', 2);
|
|
$this->assertEqualsCanonicalizing([$a->id, $multi->id], array_column($map->json('unlocated'), 'id'));
|
|
$export = $this->get('/api/v1/investment/export?province_id='.$provinceA)->assertOk();
|
|
$csv = $export->streamedContent();
|
|
$this->assertStringContainsString('Referência;Título;', $csv);
|
|
$this->assertStringContainsString('AREA-A', $csv);
|
|
$this->assertStringContainsString('AREA-MULTI', $csv);
|
|
$this->assertStringNotContainsString('AREA-B', $csv);
|
|
$this->assertStringContainsString('1234.50', $csv);
|
|
$this->get('/oportunidades?province_id='.$provinceA.'&page=1')->assertInertia(fn (AssertableInertia $page) => $page
|
|
->where('results.total', 2)->has('results.data', 2)->where('filters.province_id', $provinceA));
|
|
$this->getJson('/api/v1/investment/search?province_id='.$provinceA)->assertOk()->assertJsonPath('total', 2);
|
|
$this->assertNotContains($national->id, $service->query(['province_id' => $provinceA])->pluck('id')->all());
|
|
$this->assertNotContains($unlocated->id, $service->query(['province_id' => $provinceA])->pluck('id')->all());
|
|
$this->assertNotContains($national->id, $service->query([])->pluck('id')->all());
|
|
}
|
|
|
|
public function test_opportunity_cluster_and_corridor_filters_use_their_public_opportunity_relations(): void
|
|
{
|
|
$country = $this->location('country');
|
|
$province = $this->location('province', $country);
|
|
$otherProvince = $this->location('province', $country);
|
|
foreach ([$country, $province, $otherProvince] as $territoryId) {
|
|
DB::table('geo_locations')->where('id', $territoryId)->update(['source_system' => 'cartografia-workbook',
|
|
'external_id' => '964a187ad32ea553b9e112cd159caa90b900050c42b7329594a75bcd798a96ff:'.$territoryId,
|
|
'data_classification' => 'public']);
|
|
}
|
|
$sector = Sector::create(['name' => 'Public relation test', 'slug' => 'public-relation-test', 'is_active' => true]);
|
|
$corridorId = (string) Str::uuid();
|
|
DB::table('development_corridors')->insert(['id' => $corridorId, 'code' => 'QA-CORRIDOR', 'name' => 'QA public corridor',
|
|
'status' => 'active', 'data_classification' => 'public', 'created_at' => now(), 'updated_at' => now()]);
|
|
$clusterId = (string) Str::uuid();
|
|
DB::table('economic_clusters')->insert(['id' => $clusterId, 'code' => 'QA-CLUSTER', 'name' => 'QA public cluster',
|
|
'sector' => 'QA', 'status' => 'active', 'data_classification' => 'public', 'created_at' => now(), 'updated_at' => now()]);
|
|
$opportunity = Opportunity::create(['sector_id' => $sector->id, 'reference' => 'QA-CORRIDOR-OPP', 'slug' => 'qa-corridor-opp',
|
|
'title' => 'QA opportunity linked to corridor', 'status' => 'published', 'published_at' => now()->subMinute(), 'primary_territory_id' => $province]);
|
|
DB::table('opportunity_corridors')->insert(['opportunity_id' => $opportunity->id, 'corridor_id' => $corridorId]);
|
|
DB::table('opportunity_clusters')->insert(['opportunity_id' => $opportunity->id, 'cluster_id' => $clusterId]);
|
|
|
|
$this->getJson('/api/v1/investment/references')->assertOk()->assertJsonFragment(['id' => $corridorId, 'name' => 'QA public corridor'])
|
|
->assertJsonFragment(['id' => $clusterId, 'name' => 'QA public cluster']);
|
|
$this->getJson('/api/v1/investment/search?dimension=corridor&province_id='.$province)->assertOk()
|
|
->assertJsonPath('total', 1)->assertJsonPath('data.0.id', $opportunity->id);
|
|
$this->getJson('/api/v1/investment/search?corridor_id='.$corridorId.'&province_id='.$province)->assertOk()
|
|
->assertJsonPath('total', 1)->assertJsonPath('data.0.id', $opportunity->id);
|
|
$this->getJson('/api/v1/investment/search?cluster_id='.$clusterId)->assertOk()
|
|
->assertJsonPath('total', 1)->assertJsonPath('data.0.id', $opportunity->id);
|
|
$this->getJson('/api/v1/investment/search?dimension=corridor&province_id='.$otherProvince)->assertOk()
|
|
->assertJsonPath('total', 0)->assertJsonCount(0, 'data');
|
|
}
|
|
|
|
public function test_cluster_territory_uses_only_public_validated_project_links_and_project_locations(): void
|
|
{
|
|
$country = $this->location('country');
|
|
$provinceA = $this->location('province', $country);
|
|
$provinceB = $this->location('province', $country);
|
|
foreach ([$country, $provinceA, $provinceB] as $id) {
|
|
DB::table('geo_locations')->where('id', $id)->update([
|
|
'source_system' => 'cartografia-workbook',
|
|
'external_id' => '964a187ad32ea553b9e112cd159caa90b900050c42b7329594a75bcd798a96ff:'.$id,
|
|
'data_classification' => 'public',
|
|
]);
|
|
}
|
|
|
|
$clusterIds = [];
|
|
foreach ([['MATCH', $provinceA, 'validated'], ['PENDING', $provinceB, 'pending']] as [$code, $province, $status]) {
|
|
$clusterId = (string) Str::uuid();
|
|
$projectId = Project::create(['code' => 'TERR-'.$code, 'name' => 'Project '.$code, 'status' => 'active'])->id;
|
|
DB::table('project_locations')->insert(['project_id' => $projectId, 'geo_location_id' => $province,
|
|
'role' => 'primary', 'data_classification' => 'public']);
|
|
DB::table('economic_clusters')->insert(['id' => $clusterId, 'code' => 'TERR-'.$code, 'name' => 'Cluster '.$code,
|
|
'sector' => 'Teste', 'status' => 'active']);
|
|
DB::table('cluster_projects')->insert(['cluster_id' => $clusterId, 'project_id' => $projectId, 'role' => 'anchor',
|
|
'data_classification' => 'public', 'association_type' => 'potential', 'source_reference' => 'Fonte de teste para ligação territorial',
|
|
'evidence_reference' => 'fixture:territory:'.$code, 'valid_from' => today()->toDateString(), 'validation_status' => $status]);
|
|
InvestmentDossier::create(['cluster_id' => $clusterId, 'title' => 'Dossier '.$code, 'content' => '{}', 'source' => 'fixture de teste']);
|
|
$clusterIds[] = $clusterId;
|
|
}
|
|
|
|
$ids = app(InvestmentGeography::class)->descendants($provinceA);
|
|
$matches = app(InvestmentGeography::class)->constrainDossiersToTerritories(InvestmentDossier::query(), $ids)
|
|
->pluck('cluster_id')->all();
|
|
$this->assertSame([$clusterIds[0]], $matches);
|
|
}
|
|
|
|
public function test_opportunity_pagination_retains_the_selected_territory(): void
|
|
{
|
|
$country = $this->location('country');
|
|
$province = $this->location('province', $country);
|
|
foreach ([$country, $province] as $id) {
|
|
DB::table('geo_locations')->where('id', $id)->update([
|
|
'source_system' => 'cartografia-workbook',
|
|
'external_id' => '964a187ad32ea553b9e112cd159caa90b900050c42b7329594a75bcd798a96ff:'.$id,
|
|
'data_classification' => 'public',
|
|
]);
|
|
}
|
|
$sector = Sector::create(['name' => 'Pagination area', 'slug' => 'pagination-area', 'is_active' => true]);
|
|
$ids = [];
|
|
for ($index = 1; $index <= 13; $index++) {
|
|
$record = Opportunity::create(['sector_id' => $sector->id, 'reference' => 'PAGE-'.$index,
|
|
'slug' => 'page-'.$index, 'title' => 'Page opportunity '.$index, 'status' => 'published',
|
|
'published_at' => now()->subMinute(), 'primary_territory_id' => $province, 'geography_scope' => 'specific']);
|
|
$ids[] = $record->id;
|
|
}
|
|
|
|
$this->get('/oportunidades?province_id='.$province.'&page=2')->assertInertia(fn (AssertableInertia $page) => $page
|
|
->where('results.total', 13)->has('results.data', 1)->where('results.data.0.id', $ids[0])
|
|
->where('filters.province_id', $province)
|
|
->where('results.prev_page_url', fn ($url) => str_contains($url, 'province_id='.$province)));
|
|
$this->getJson('/api/v1/investment/search?province_id='.$province.'&page=2')->assertOk()
|
|
->assertJsonPath('total', 13)->assertJsonCount(1, 'data')->assertJsonPath('data.0.id', $ids[0]);
|
|
}
|
|
|
|
public function test_cascade_assignment_persists_and_rejects_mismatched_parent_and_stale_write(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
config(['portal.managers' => [$user->email]]);
|
|
$this->actingAsWithMfa($user);
|
|
$this->actingAs($user);
|
|
$province = $this->location('province');
|
|
$other = $this->location('province');
|
|
$district = $this->location('district', $province);
|
|
$cluster = (string) Str::uuid();
|
|
DB::table('economic_clusters')->insert(['id' => $cluster, 'code' => 'SYNTHETIC', 'name' => 'Synthetic cluster', 'sector' => 'Synthetic', 'status' => 'planned']);
|
|
$this->getJson('/management/territory-subjects?kind=clusters&q=Synthetic')->assertOk()->assertJsonPath('data.0.id', $cluster);
|
|
$this->get('/management/territory-associations')->assertOk();
|
|
$this->getJson('/management/territory-options?type=district&parent_id='.$province)->assertOk()->assertJsonCount(1, 'data')->assertJsonPath('data.0.id', $district);
|
|
$this->getJson('/management/territory-options?type=district&parent_id='.$other)->assertOk()->assertJsonCount(0, 'data');
|
|
$url = '/management/territories/clusters/'.$cluster;
|
|
$this->postJson($url, ['version' => 0, 'path' => [$other, $district]])->assertUnprocessable();
|
|
$this->postJson($url, ['version' => 0, 'path' => [$province, $district]])->assertOk()->assertJsonPath('version', 1);
|
|
$this->getJson($url)->assertOk()->assertJsonPath('path.0.id', $district);
|
|
$this->assertDatabaseHas('economic_clusters', ['id' => $cluster, 'primary_territory_id' => $district]);
|
|
$this->postJson($url, ['version' => 0, 'path' => []])->assertConflict();
|
|
$this->postJson($url, ['version' => 1, 'path' => []])->assertOk();
|
|
$this->assertDatabaseHas('economic_clusters', ['id' => $cluster, 'primary_territory_id' => null]);
|
|
}
|
|
|
|
public function test_non_managers_cannot_read_internal_options(): void
|
|
{
|
|
config(['portal.managers' => []]);
|
|
$this->actingAs(User::factory()->create())->getJson('/management/territory-options?type=province')->assertForbidden();
|
|
}
|
|
|
|
public function test_opportunity_edit_saves_the_geographic_fields_with_the_record(): void
|
|
{
|
|
$manager = User::factory()->create();
|
|
config(['portal.managers' => [$manager->email]]);
|
|
$country = $this->location('country');
|
|
$province = $this->location('province', $country);
|
|
$sector = Sector::create(['name' => 'Synthetic edit', 'slug' => 'synthetic-edit', 'is_active' => true]);
|
|
$record = Opportunity::create(['sector_id' => $sector->id, 'reference' => 'EDIT-GEO', 'slug' => 'edit-geo', 'title' => 'Synthetic edit', 'status' => 'draft']);
|
|
$this->actingAsWithMfa($manager)->putJson('/management/opportunities/'.$record->id.'/draft', [
|
|
'sector_id' => $sector->id, 'reference' => 'EDIT-GEO', 'slug' => 'edit-geo', 'title' => 'Synthetic edit',
|
|
'is_featured' => false, 'estimated_investment_currency' => 'USD',
|
|
'province_id' => $province, 'territory_version' => 0,
|
|
'etag' => app(OpportunityManagement::class)->etag($record->fresh()), 'reason' => 'Atualização territorial do rascunho em QA',
|
|
])->assertOk();
|
|
$this->assertDatabaseHas('opportunities', ['id' => $record->id, 'primary_territory_id' => $province]);
|
|
$this->assertSame($province, $record->fresh()->primaryTerritory->id);
|
|
$this->get('/management/opportunities/'.$record->id.'/edit')->assertOk();
|
|
$this->getJson('/management/territory-options?type=locality&parent_id='.$province)->assertUnprocessable();
|
|
$this->getJson('/management/territory-options?type=district&parent_id=00000000-0000-4000-8000-000000000001')->assertUnprocessable();
|
|
$this->getJson('/management/territory-options?type=province&q='.$province)->assertOk()->assertJsonPath('data.0.id', $province);
|
|
$other = $this->location('province', $country);
|
|
$district = $this->location('district', $other);
|
|
$payload = ['sector_id' => $sector->id, 'reference' => 'EDIT-GEO', 'slug' => 'edit-geo', 'title' => 'Must not persist',
|
|
'is_featured' => false, 'estimated_investment_currency' => 'USD',
|
|
'province_id' => $province, 'district_id' => $district, 'territory_version' => 1];
|
|
$etag = app(OpportunityManagement::class)->etag($record->fresh());
|
|
$this->putJson('/management/opportunities/'.$record->id.'/draft', [...$payload, 'etag' => $etag, 'reason' => 'Validação de hierarquia no rascunho QA'])->assertUnprocessable()->assertJsonValidationErrors('district_id');
|
|
$this->assertDatabaseHas('opportunities', ['id' => $record->id, 'title' => 'Synthetic edit', 'primary_territory_id' => $province]);
|
|
$this->putJson('/management/opportunities/'.$record->id.'/draft', [...$payload, 'district_id' => null, 'etag' => str_repeat('0', 64), 'reason' => 'Teste de conflito de edição territorial QA'])->assertConflict();
|
|
}
|
|
|
|
public function test_database_rejects_a_cycle_wrong_parent_and_duplicate_source_code(): void
|
|
{
|
|
$country = $this->location('country');
|
|
$province = $this->location('province', $country);
|
|
$district = $this->location('district', $province);
|
|
foreach ([
|
|
fn () => DB::table('geo_locations')->where('id', $province)->update(['parent_id' => $district]),
|
|
fn () => DB::table('geo_locations')->where('id', $district)->update(['parent_id' => $country]),
|
|
fn () => DB::table('geo_locations')->where('id', $country)->update(['parent_id' => $country]),
|
|
] as $invalid) {
|
|
try {
|
|
DB::transaction($invalid);
|
|
$this->fail('Invalid hierarchy accepted');
|
|
} catch (QueryException $error) {
|
|
$this->assertStringContainsString('ERROR', $error->getMessage());
|
|
}
|
|
}
|
|
DB::table('geo_locations')->where('id', $province)->update(['source_code' => '01']);
|
|
$other = $this->location('province', $country);
|
|
try {
|
|
DB::transaction(fn () => DB::table('geo_locations')->where('id', $other)->update(['source_code' => '01']));
|
|
$this->fail('Duplicate source code accepted');
|
|
} catch (QueryException $error) {
|
|
$this->assertStringContainsString('geo_source_parent_code_uq', $error->getMessage());
|
|
}
|
|
}
|
|
|
|
public function test_integrity_migration_reverses_without_data_and_preserves_imported_codes(): void
|
|
{
|
|
$migration = require database_path('migrations/2026_09_29_100000_territory_integrity.php');
|
|
$migration->down();
|
|
$migration->up();
|
|
$country = $this->location('country');
|
|
DB::table('geo_locations')->where('id', $country)->update(['source_code' => '508']);
|
|
$this->expectException(\RuntimeException::class);
|
|
$this->expectExceptionMessage('Preserve imported source codes');
|
|
$migration->down();
|
|
}
|
|
|
|
public function test_background_options_do_not_consume_form_validation_feedback(): void
|
|
{
|
|
$manager = User::factory()->create();
|
|
config(['portal.managers' => [$manager->email]]);
|
|
$this->actingAsWithMfa($manager)->from('/management/opportunities/create')
|
|
->post('/management/opportunities', [])->assertSessionHasErrors('status');
|
|
$this->withCookie(config('session.cookie'), session()->getId())->withCredentials();
|
|
$this->getJson('/management/territory-options?type=province')->assertOk()->assertSessionHasErrors('status');
|
|
$this->get('/management/opportunities/create')->assertInertia(fn (AssertableInertia $page) => $page->has('errors.status'));
|
|
}
|
|
|
|
public function test_investment_areas_are_idempotent_and_preserve_legacy_sectors(): void
|
|
{
|
|
$old = Sector::create(['name' => 'Energia e Mineração', 'slug' => 'energia', 'is_active' => true]);
|
|
$this->seed(InvestmentAreaSeeder::class);
|
|
$count = Sector::count();
|
|
$this->seed(InvestmentAreaSeeder::class);
|
|
$this->assertSame($count, Sector::count());
|
|
$this->assertSame('Energia e Mineração', $old->fresh()->name);
|
|
$this->assertDatabaseHas('sectors', ['name' => 'Energia', 'slug' => 'energia-especifica']);
|
|
$this->assertDatabaseHas('sectors', ['name' => 'Mineração']);
|
|
}
|
|
|
|
private function location(string $type, ?string $parent = null): string
|
|
{
|
|
$id = (string) Str::uuid();
|
|
DB::table('geo_locations')->insert(['id' => $id, 'code' => $id, 'name' => 'Synthetic '.$id, 'location_type' => $type, 'parent_id' => $parent]);
|
|
|
|
return $id;
|
|
}
|
|
}
|