make(Kernel::class); $kernel->bootstrap(); $identity = portalQaAssertTarget($target); DB::beginTransaction(); DB::statement('SET TRANSACTION READ ONLY'); // Enumerating the whole catalogue is a QA coverage run, independent of rate-limit acceptance. RateLimiter::for('api', fn () => Limit::none()); $expected = app(InvestmentGeography::class)->publicReferences()->whereIn('location_type', ['province', 'district', 'admin_post', 'locality']) ->get(['id', 'parent_id', 'location_type']); $counts = []; $results = []; $seen = []; foreach (['province' => null, 'district' => 'province', 'admin_post' => 'district', 'locality' => 'admin_post'] as $type => $parentType) { $parents = $parentType === null ? [null] : $expected->where('location_type', $parentType)->pluck('id')->all(); foreach ($parents as $parent) { $page = 1; $actual = []; do { $uri = '/api/v1/investment/territory-options?'.http_build_query(['type' => $type, 'parent_id' => $parent, 'page' => $page]); $request = Request::create($uri, 'GET', [], [], [], ['HTTP_ACCEPT' => 'application/json']); $response = $kernel->handle($request); if ($response->getStatusCode() !== 200) { throw new RuntimeException('Catalogue API failed: '.$uri.' HTTP '.$response->getStatusCode()); } $body = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR); foreach ($body['data'] as $item) { if ($item['location_type'] !== $type || ($parent !== null && $item['parent_id'] !== $parent)) { throw new RuntimeException('Hierarchy mismatch'); } $actual[] = $item['id']; $seen[$item['id']] = true; } $kernel->terminate($request, $response); $results[] = ['type' => $type, 'parent_id' => $parent, 'page' => $page, 'http' => 200, 'rows' => count($body['data'])]; $page++; } while ($body['next_page_url'] !== null); $wanted = $expected->where('location_type', $type); if ($parent !== null) { $wanted = $wanted->where('parent_id', $parent); } $wanted = $wanted->pluck('id')->sort()->values()->all(); sort($actual); if ($wanted !== $actual) { throw new RuntimeException('API omissions/duplicates at '.$type.' '.$parent); } } $counts[$type] = $expected->where('location_type', $type)->count(); } if (count($seen) !== $expected->count()) { throw new RuntimeException('Unreachable catalogue references'); } DB::rollBack(); $result = ['captured_at' => gmdate('c'), 'target' => $identity, 'source' => 'Verified active backup restored in QA', 'counts' => $counts, 'references_expected' => $expected->count(), 'references_recovered' => count($seen), 'api_requests' => count($results), 'checks' => $results, 'visual_verified' => false, 'all_consumers_accepted' => false, 'geometry_rows' => DB::table('geo_geometries')->count(), 'rate_limit_scope' => 'Excluded only in QA bulk catalogue enumeration; access/rate behavior covered separately', 'active_writes' => 0]; file_put_contents($root.'/storage/app/private/investmoz-v2/portal-audit/territory-catalogue-coverage.json', json_encode($result, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); unset($result['checks']); echo json_encode($result, JSON_THROW_ON_ERROR).PHP_EOL;