assertTrue(IsolatedQaDatabase::allowsSchemaChanges()); $this->withoutVite(); $this->withoutMiddleware(ThrottleRequests::class); } private function proof(string $category = 'resource', string $purpose = 'local'): array { return ['id' => (string) Str::uuid(), 'category' => $category, 'purpose' => $purpose, 'statement' => 'SYNTHETIC QA documented observation', 'source' => 'Synthetic public source', 'locator' => 'Table QA', 'period' => '2026', 'scope' => 'Synthetic district', 'coverage' => 'Partial observation; no availability confirmed', 'verified_on' => '2026-10-01']; } private function record(string $prefix, array $proofs, ?string $territory = null): Opportunity { $record = Opportunity::create(['sector_id' => 1, 'reference' => 'QA-'.Str::uuid(), 'slug' => 'qa-'.Str::uuid(), 'title' => $prefix.' synthetic thematic map', 'status' => 'published', 'published_at' => now()->subMinute(), 'primary_territory_id' => $territory]); $record->forceFill(['workflow_context' => ['promotional_summary' => ['projection' => ['proactive' => [ 'evidence' => $proofs, 'state' => 'hypothesis', 'limitations' => 'SYNTHETIC QA scope only']]], 'private_evidence' => 'PRIVATE-DRAFT-MUST-NOT-LEAK']])->save(); return $record; } private function url(string $prefix, array $filters = []): string { return '/api/v1/investment/map?'.http_build_query(['q' => $prefix, ...$filters]); } public function test_only_selected_public_local_thematic_evidence_is_exposed_with_its_sources_and_limits(): void { $prefix = 'THEME-'.Str::uuid(); $record = $this->record($prefix, [$this->proof(), $this->proof('infrastructure'), $this->proof('resource', 'methodology'), $this->proof('demand')]); $before = $record->fresh()->getRawOriginal('workflow_context'); $response = $this->getJson($this->url($prefix))->assertOk()->assertJsonPath('matched_count', 1) ->assertJsonPath('thematic_records.0.id', $record->id)->assertJsonCount(3, 'thematic_records.0.evidence') ->assertJsonPath('thematic_records.0.evidence.0.source', 'Synthetic public source') ->assertJsonPath('thematic_records.0.evidence.0.period', '2026') ->assertJsonPath('thematic_records.0.evidence.0.scope', 'Synthetic district') ->assertJsonPath('thematic_records.0.evidence.2.category', 'economic_potential') ->assertJsonPath('thematic_records.0.evidence.2.source_category', 'demand') ->assertJsonPath('thematic_records.0.state', 'hypothesis')->assertJsonCount(0, 'features')->assertJsonCount(1, 'unlocated'); $this->assertStringNotContainsString('PRIVATE-DRAFT-MUST-NOT-LEAK', $response->getContent()); $this->assertSame($before, $record->fresh()->getRawOriginal('workflow_context')); } public function test_workforce_is_explicit_and_economic_context_preserves_each_source_without_inferred_totals(): void { $prefix = 'THEME-'.Str::uuid(); $this->record($prefix, [$this->proof('workforce'), $this->proof('activity'), $this->proof('supply'), $this->proof('demand'), $this->proof('workforce', 'methodology')]); $r = $this->getJson($this->url($prefix))->assertOk()->assertJsonCount(4, 'thematic_records.0.evidence') ->assertJsonPath('thematic_records.0.evidence.0.category', 'workforce') ->assertJsonPath('thematic_records.0.evidence.0.source_category', 'workforce') ->assertJsonPath('thematic_records.0.state', 'hypothesis')->assertJsonCount(0, 'features')->json('thematic_records.0'); foreach (['activity', 'supply', 'demand'] as $i => $source) { $this->assertSame('economic_potential', $r['evidence'][$i + 1]['category']); $this->assertSame($source, $r['evidence'][$i + 1]['source_category']); $this->assertSame('2026', $r['evidence'][$i + 1]['period']); } $this->assertArrayNotHasKey('available_workers', $r); $this->assertArrayNotHasKey('potential_score', $r); } public function test_unpublished_future_restricted_and_unselected_private_evidence_are_not_disclosed(): void { $prefix = 'THEME-'.Str::uuid(); $draft = $this->record($prefix, [$this->proof()]); $draft->update(['status' => 'draft']); $future = $this->record($prefix, [$this->proof()]); $future->update(['published_at' => now()->addDay()]); $restricted = $this->record($prefix, [$this->proof()]); $restricted->forceFill(['owner_user_id' => User::factory()->create()->id])->save(); $private = $this->record($prefix, []); $private->forceFill(['workflow_context' => ['private_evidence' => [$this->proof()]]])->save(); $this->getJson($this->url($prefix))->assertOk()->assertJsonPath('matched_count', 1)->assertJsonCount(0, 'thematic_records'); } public function test_thematic_records_follow_the_same_province_and_district_filters_without_inventing_coordinates(): void { $prefix = 'THEME-'.Str::uuid(); $geo = app(InvestmentGeography::class); $districts = $geo->publicReferences()->where('location_type', 'district')->get(['id', 'parent_id']); $district = $districts->first(); $other = $districts->first(fn ($row) => $row->parent_id !== $district->parent_id); $this->assertNotNull($other); $record = $this->record($prefix, [$this->proof()], $district->id); $this->record($prefix, [$this->proof('infrastructure')], $other->id); foreach ([['province_id' => $district->parent_id], ['province_id' => $district->parent_id, 'district_id' => $district->id]] as $filters) { $this->getJson($this->url($prefix, $filters))->assertOk()->assertJsonPath('matched_count', 1) ->assertJsonCount(1, 'thematic_records')->assertJsonPath('thematic_records.0.id', $record->id) ->assertJsonCount(0, 'features')->assertJsonCount(1, 'unlocated'); } $this->assertNull($record->fresh()->district); } public function test_thematic_evidence_reuses_existing_site_coordinates_without_relocating_the_resource(): void { $prefix = 'THEME-'.Str::uuid(); $record = $this->record($prefix, [$this->proof()]); $site = (string) Str::uuid(); DB::table('geo_locations')->insert(['id' => $site, 'name' => 'Synthetic opportunity site', 'code' => $site, 'location_type' => 'site', 'data_classification' => 'public', 'source_system' => 'investment-site-editor']); DB::insert("INSERT INTO geo_geometries(id,geo_location_id,geometry_type,geom,properties,data_classification) VALUES (?,?,'point',ST_SetSRID(ST_MakePoint(32.5,-25.9),4326),'{}'::jsonb,'public')", [(string) Str::uuid(), $site]); $record->update(['primary_territory_id' => $site]); $this->getJson($this->url($prefix))->assertOk()->assertJsonCount(1, 'features') ->assertJsonPath('features.0.geometry.coordinates', [32.5, -25.9]) ->assertJsonPath('features.0.properties.id', $record->id)->assertJsonPath('thematic_records.0.id', $record->id); $this->assertSame($site, $record->fresh()->primary_territory_id); } }