withoutVite(); $this->manager = User::factory()->create(); $this->manager->forceFill(['two_factor_secret' => Crypt::encryptString('SYNTHETIC QA ONLY'), 'two_factor_confirmed_at' => now()])->save(); config(['portal.managers' => [$this->manager->email]]); $country = (string) Str::uuid(); $this->province = (string) Str::uuid(); $this->district = (string) Str::uuid(); foreach ([[$country, null, 'country'], [$this->province, $country, 'province'], [$this->district, $this->province, 'district']] as [$id, $parent, $type]) { DB::table('geo_locations')->insert(['id' => $id, 'parent_id' => $parent, 'name' => 'SYNTHETIC '.$type, 'code' => $id, 'location_type' => $type, 'source_system' => 'cartografia-workbook', 'external_id' => '964a187ad32ea553b9e112cd159caa90b900050c42b7329594a75bcd798a96ff:QA:'.$id, 'data_classification' => 'public']); } $this->batch = $this->batch('approved'); foreach ([$this->province, $this->district] as $id) { foreach ([2026 => '100.125', 2027 => '110.250'] as $year => $value) { DB::table('demographic_observations')->insert(['import_id' => $this->batch, 'geo_location_id' => $id, 'year' => $year, 'indicator' => 'population', 'sex' => 'all', 'residence' => 'all', 'age_group' => 'all', 'unit' => 'pessoas', 'data_kind' => 'projected', 'original_value' => $value, 'numeric_value' => $value, 'sheet' => (string) $year, 'cell' => 'B4', 'validation_status' => 'valid']); } } } private function batch(string $status): string { $id = (string) Str::uuid(); DB::table('demographic_imports')->insert(['id' => $id, 'sha256' => hash('sha256', $id), 'file_name' => 'SYNTHETIC-QA.xlsx', 'source' => 'SYNTHETIC TEST ONLY', 'publication_version' => 'synthetic/1', 'status' => $status, 'report' => json_encode(['record_count' => 4, 'territories' => [], 'issue_counts' => []]), 'preview_sha256' => str_repeat('0', 64), 'prepared_by' => $this->manager->id, 'created_at' => now(), 'updated_at' => now()]); return $id; } public function test_public_comparison_preserves_decimals_versions_and_missing_years(): void { $response = $this->getJson('/api/v1/investment/demography?'.http_build_query(['territories' => [$this->province, $this->district], 'start_year' => 2026, 'end_year' => 2028, 'sector' => 'industry']))->assertOk(); $response->assertJsonPath('state', 'available')->assertJsonCount(2, 'territories') ->assertJsonPath('territories.0.series.0.values.0.original_value', '100.125') ->assertJsonPath('territories.0.series.0.missing_years', [2028]); $this->assertStringNotContainsString('probability', $response->getContent()); } public function test_only_approved_series_and_public_territories_are_exposed(): void { DB::table('demographic_imports')->where('id', $this->batch)->update(['status' => 'disabled']); $this->assertSame('no_data', app(DemographicEvidence::class)->analyse([$this->province], 2026, 2027)['state']); DB::table('demographic_imports')->where('id', $this->batch)->update(['status' => 'approved']); DB::table('geo_locations')->where('id', $this->province)->update(['data_classification' => 'internal']); $this->assertSame([], app(DemographicEvidence::class)->analyse([$this->district], 2026, 2027)['territories']); } public function test_new_editions_remain_separate_and_divergence_is_reported(): void { $other = $this->batch('approved'); $row = (array) DB::table('demographic_observations')->where('import_id', $this->batch)->where('geo_location_id', $this->province)->where('year', 2026)->first(); unset($row['id']); DB::table('demographic_observations')->insert([...$row, 'import_id' => $other, 'numeric_value' => '101.125', 'original_value' => '101.125']); $analysis = app(DemographicEvidence::class)->analyse([$this->province], 2026, 2026); $this->assertCount(2, $analysis['territories'][0]['series']); $this->assertSame('divergent_values', $analysis['differences'][0]['state']); $this->assertCount(1, app(DemographicEvidence::class)->analyse([$this->province], 2026, 2026, 'general', $other)['territories'][0]['series']); } public function test_permissions_mfa_and_qa_guard_are_enforced(): void { $this->get('/management/demography')->assertRedirect(); $this->actingAs(User::factory()->create())->getJson('/management/demography')->assertForbidden(); $this->manager->forceFill(['two_factor_confirmed_at' => null])->save(); $this->actingAs($this->manager)->getJson('/management/demography')->assertForbidden(); $this->manager->forceFill(['two_factor_confirmed_at' => now()])->save(); config(['database.connections.pgsql.port' => '5432']); $this->getJson('/management/demography')->assertForbidden(); } public function test_confirmation_is_idempotent_and_status_changes_preserve_observations(): void { $service = app(DemographicImport::class); $sha = DB::table('demographic_imports')->where('id', $this->batch)->value('sha256'); $receipt = $service->confirm($this->manager, $this->batch, $sha, [], 'Synthetic replay test only.'); $this->assertTrue($receipt['repeated']); $this->assertSame(4, $receipt['records']); $service->transition($this->manager, $this->batch, 'disabled', 'Synthetic deactivate test only.'); $this->assertSame(4, DB::table('demographic_observations')->where('import_id', $this->batch)->count()); $this->assertSame('no_data', app(DemographicEvidence::class)->analyse([$this->province], 2026, 2026)['state']); app(DemographicImport::class)->transition($this->manager, $this->batch, 'approved', 'Synthetic restore test only.'); $this->assertSame('available', app(DemographicEvidence::class)->analyse([$this->province], 2026, 2026)['state']); $this->assertSame(2, DB::table('demographic_events')->where('import_id', $this->batch)->count()); } public function test_invalid_period_and_duplicate_territory_are_rejected(): void { $this->getJson('/api/v1/investment/demography?'.http_build_query(['territories' => [$this->province, $this->province]]))->assertUnprocessable(); $this->getJson('/api/v1/investment/demography?start_year=2050&end_year=2017')->assertUnprocessable(); $this->getJson('/api/v1/investment/demography?start_year=2000&end_year=2100')->assertUnprocessable(); } public function test_same_level_comparison_computes_exact_difference_and_explains_it(): void { $id = (string) Str::uuid(); $location = (array) DB::table('geo_locations')->where('id', $this->district)->first(); DB::table('geo_locations')->insert([...$location, 'id' => $id, 'name' => 'SYNTHETIC second district', 'code' => $id, 'external_id' => $location['external_id'].'-second']); $row = (array) DB::table('demographic_observations')->where('geo_location_id', $this->district)->where('year', 2026)->first(); unset($row['id']); DB::table('demographic_observations')->insert([...$row, 'geo_location_id' => $id, 'numeric_value' => '80.025', 'original_value' => '80.025']); $analysis = app(DemographicEvidence::class)->analyse([$this->district, $id], 2026, 2026, 'industry'); $this->assertCount(1, $analysis['comparisons']); $this->assertSame('20.100', ltrim($analysis['comparisons'][0]['difference'], '-')); $this->assertStringContainsString('Não mede procura efectiva', $analysis['comparisons'][0]['explanation']); } public function test_evidence_reuses_context_without_modifying_it(): void { $context = ['fields' => ['province_id' => $this->province, 'district_id' => $this->district]]; $before = $context; $analysis = app(DemographicEvidence::class)->forContext($context); $this->assertSame('available', $analysis['state']); $this->assertSame($this->district, $analysis['territories'][0]['id']); $this->assertSame($before, $context); $fallback = app(DemographicEvidence::class)->forContext(['fields' => ['province_id' => $this->province, 'district_id' => '']]); $this->assertSame($this->province, $fallback['territories'][0]['id']); $method = new \ReflectionMethod(InvestmentPreparationWorkspace::class, 'sources'); [, $sources] = $method->invoke(app(InvestmentPreparationWorkspace::class), $this->manager, (object) ['id' => (string) Str::uuid(), 'title' => 'Synthetic QA investment'], $context, ''); $source = collect($sources)->firstWhere('key', 'demography'); $this->assertSame('available', $source['current']['state']); } public function test_preview_or_original_tampering_prevents_import(): void { $prepared = $this->batch('prepared'); Storage::fake('local'); Storage::disk('local')->put('demography/'.$prepared.'/preview.json', '{}'); $sha = DB::table('demographic_imports')->where('id', $prepared)->value('sha256'); $this->actingAs($this->manager)->postJson('/management/demography/'.$prepared.'/confirm', ['sha256' => $sha, 'mapping' => [], 'reason' => 'Synthetic integrity test only.'])->assertConflict(); $this->assertSame(0, DB::table('demographic_observations')->where('import_id', $prepared)->count()); } public function test_stream_integrity_and_count_are_verified_before_import(): void { Storage::fake('local'); foreach (['tampered', 'wrong-count'] as $case) { $prepared = $this->batch('prepared'); $directory = 'demography/'.$prepared; $original = 'SYNTHETIC NDJSON integrity '.$prepared; $sha = hash('sha256', $original); $stream = $case === 'tampered' ? 'changed' : ''; $summary = ['record_count' => 1, 'records_file_sha256' => hash('sha256', ''), 'territories' => [], 'issues' => [], 'issue_counts' => []]; $preview = json_encode($summary, JSON_THROW_ON_ERROR); Storage::disk('local')->put($directory.'/original.xlsx', $original); Storage::disk('local')->put($directory.'/preview.json', $preview); Storage::disk('local')->put($directory.'/records.ndjson', $stream); DB::table('demographic_imports')->where('id', $prepared)->update(['sha256' => $sha, 'preview_sha256' => hash('sha256', $preview), 'report' => $preview]); $this->actingAs($this->manager)->postJson('/management/demography/'.$prepared.'/confirm', ['sha256' => $sha, 'mapping' => [], 'reason' => 'Synthetic stream integrity and count test.'])->assertConflict(); $this->assertSame(0, DB::table('demographic_observations')->where('import_id', $prepared)->count()); $this->assertSame('prepared', DB::table('demographic_imports')->where('id', $prepared)->value('status')); } } public function test_database_rejects_duplicate_observation_dimensions(): void { $row = (array) DB::table('demographic_observations')->where('import_id', $this->batch)->first(); unset($row['id']); $this->expectException(QueryException::class); DB::table('demographic_observations')->insert($row); } public function test_rollback_refuses_to_delete_imported_history(): void { $migration = require database_path('migrations/2026_11_13_100000_create_demographic_series.php'); $this->expectException(\RuntimeException::class); $migration->down(); } public function test_remote_consultation_requires_explicit_activation_and_preserves_versions(): void { $environment = app()->environment(); try { app()->instance('env', 'production'); config(['demography.enabled' => false]); $service = app(DemographicEvidence::class); $this->assertSame('unavailable', $service->analyse([$this->province], 2026, 2026)['state']); config(['demography.enabled' => true]); $result = $service->analyse([$this->province], 2026, 2026); $this->assertSame('available', $result['state']); $this->assertSame($this->batch, $result['territories'][0]['series'][0]['import_id']); } finally { app()->instance('env', $environment); } } }