assertTrue(IsolatedQaDatabase::allowsSchemaChanges()); Http::preventStrayRequests(); } private function observation(?string $kind): array { $item = ['id' => (string) Str::uuid(), 'label' => 'SYNTHETIC nature QA', 'description' => 'Synthetic source only.', 'indicator' => 'employment', 'value' => '0', 'unit' => 'pessoas', 'period_start' => '2025-01-01', 'period_end' => '2025-12-31', 'scope' => 'Synthetic zone', 'population' => 'Synthetic establishments', 'methodology' => 'Synthetic register', 'source_reference' => 'SYNTHETIC source; not real statistics', 'source_date' => '2026-01-15', 'knowledge' => 'documented', 'disclosure' => true]; return $kind === null ? $item : [...$item, 'measure_kind' => $kind]; } private function zone(): InvestmentZone { $actor = User::factory()->create(['email' => 'measure-'.Str::uuid().'@example.test']); return InvestmentZone::create(['code' => 'MEASURE-QA-'.Str::uuid(), 'category' => 'industrial_park', 'name' => 'SYNTHETIC nature QA', 'editor_id' => $actor->id, 'publication_status' => 'draft', 'validation_status' => 'pending']); } public function test_explicit_natures_persist_recover_and_project_without_inference_or_private_disclosure(): void { $zone = $this->zone(); $service = app(InvestmentZoneIntegralDetails::class); $ops = app(InvestmentZoneOperationalDetails::class); $rows = array_map(fn ($kind) => $this->observation($kind), ['observed', 'planned', 'projected', null]); $hidden = [...$this->observation('observed'), 'disclosure' => false, 'source_reference' => 'PRIVATE-NATURE-SOURCE']; $merged = $service->merge($zone, [], ['observations' => [...$rows, $hidden]]); $zone->forceFill(['operational_details' => $ops->pack(['integral' => $merged])])->save(); $saved = $zone->fresh(); $before = $saved->getRawOriginal('operational_details'); $recovered = $ops->recover($saved)['integral']['observations']; $this->assertSame(['observed', 'planned', 'projected'], array_column(array_slice($recovered, 0, 3), 'measure_kind')); $this->assertArrayNotHasKey('measure_kind', $recovered[3]); $this->assertSame('0', $recovered[0]['value']); $view = app(InvestmentMapIndicators::class)->forZones(collect([$saved])); $this->assertSame(['observed', 'planned', 'projected', 'unknown'], array_column($view['records'], 'measure_kind')); $this->assertSame(['0', '0', '0', '0'], array_column($view['records'], 'value')); $this->assertStringNotContainsString('PRIVATE-NATURE-SOURCE', json_encode($view, JSON_THROW_ON_ERROR)); $this->assertSame($before, $zone->fresh()->getRawOriginal('operational_details')); $this->assertSame(1, $zone->fresh()->version_no); } public function test_invalid_nature_is_rejected_and_an_omitted_nature_does_not_erase_an_explicit_choice(): void { $zone = $this->zone(); $service = app(InvestmentZoneIntegralDetails::class); $row = $this->observation('observed'); $before = $service->merge($zone, [], ['observations' => [$row]]); $withoutKind = $row; unset($withoutKind['measure_kind']); $recovered = $service->merge($zone, $before, ['observations' => [$withoutKind]]); $this->assertSame('observed', $recovered['observations'][0]['measure_kind']); $changed = $service->merge($zone, $before, ['observations' => [[...$row, 'measure_kind' => 'planned']]]); $this->assertSame('planned', $changed['observations'][0]['measure_kind']); $this->assertSame('observed', $before['observations'][0]['measure_kind']); try { $service->merge($zone, $before, ['observations' => [[...$row, 'measure_kind' => 'inferred_from_description']]]); $this->fail('Invalid nature accepted'); } catch (ValidationException $error) { $this->assertArrayHasKey('observations.0.measure_kind', $error->errors()); } } public function test_nature_change_stays_in_private_preparation_with_history_until_existing_editorial_review(): void { $zone = $this->zone(); $ops = app(InvestmentZoneOperationalDetails::class); $row = $this->observation('observed'); $fields = app(InvestmentZoneIntegralDetails::class)->merge($zone, [], ['observations' => [$row]]); // Synthetic public snapshot only, inside the rolled-back QA transaction. $zone->forceFill(['publication_status' => 'published', 'validation_status' => 'validated', 'reviewer_id' => User::factory()->create(['email' => 'nature-review-'.Str::uuid().'@example.test'])->id, 'published_at' => now(), 'physical_category' => 'industrial_park', 'source_evidence' => [['source_type' => 'promoter', 'title' => 'SYNTHETIC QA only', 'url' => 'https://example.invalid/qa', 'supports' => 'Synthetic test fixture']], 'operational_details' => $ops->pack(['integral' => $fields])])->save(); $original = $zone->fresh()->getRawOriginal('operational_details'); $profile = app(InvestmentZoneOperationalProfile::class); $prepared = $profile->prepare($zone->fresh(), ['version_no' => 1, 'operational_details' => ['integral' => ['observations' => [[...$row, 'measure_kind' => 'planned']]]]], $zone->editor_id); $this->assertSame($original, $prepared->getRawOriginal('operational_details')); $this->assertSame('observed', $ops->publicData($prepared)['integral']['observations'][0]['measure_kind']); $this->assertSame('planned', $profile->draft($prepared)['fields']['operational_details']['integral']['observations'][0]['measure_kind']); $this->assertSame(1, $prepared->version_no); $this->assertSame(1, DB::table('investment_zone_revisions')->where('zone_id', $zone->id)->where('action', 'profile_prepared')->count()); $this->assertSame(1, DB::table('audit_events')->where('entity_id', $zone->id)->where('action', 'profile_prepared')->count()); } }