diff --git a/src/app/Packages/Domains/WorldHeritage/Adapter/AlgoliaWorldHeritageSearchAdapter.php b/src/app/Packages/Domains/WorldHeritage/Adapter/AlgoliaWorldHeritageSearchAdapter.php index 6053bba..388d44e 100644 --- a/src/app/Packages/Domains/WorldHeritage/Adapter/AlgoliaWorldHeritageSearchAdapter.php +++ b/src/app/Packages/Domains/WorldHeritage/Adapter/AlgoliaWorldHeritageSearchAdapter.php @@ -6,6 +6,7 @@ use App\Packages\Domains\WorldHeritage\Ports\Dto\HeritageSearchResult; use App\Packages\Domains\WorldHeritage\Ports\WorldHeritageSearchPort; use App\Packages\Features\QueryUseCases\ListQuery\AlgoliaSearchListQuery; +use Illuminate\Support\Facades\Log; class AlgoliaWorldHeritageSearchAdapter implements WorldHeritageSearchPort { @@ -152,11 +153,26 @@ public function search( $hits = $response['hits'] ?? []; - $ids = array_values(array_filter(array_map(static function (array $h) { - return isset($h['id']) - ? (int) $h['id'] - : (isset($h['objectID']) ? (int) $h['objectID'] : null); - }, $hits))); + $ids = []; + foreach ($hits as $hit) { + $rawId = $hit['id'] ?? $hit['objectID'] ?? null; + + if ($rawId === null) { + continue; + } + + if (!is_numeric($rawId)) { + // Don't silently coerce a non-numeric id to 0 and drop it via + // array_filter: that would hide an Algolia/DB data inconsistency. + Log::warning('Algolia search hit has a non-numeric id', [ + 'id' => $hit['id'] ?? null, + 'objectID' => $hit['objectID'] ?? null, + ]); + continue; + } + + $ids[] = (int) $rawId; + } $nbHits = (int) ($response['nbHits'] ?? 0); $nbPages = (int) ($response['nbPages'] ?? 0); diff --git a/src/app/Packages/Domains/WorldHeritage/Tests/AlgoliaWorldHeritageSearchAdapterTest.php b/src/app/Packages/Domains/WorldHeritage/Tests/AlgoliaWorldHeritageSearchAdapterTest.php index 5d500db..888c1c4 100644 --- a/src/app/Packages/Domains/WorldHeritage/Tests/AlgoliaWorldHeritageSearchAdapterTest.php +++ b/src/app/Packages/Domains/WorldHeritage/Tests/AlgoliaWorldHeritageSearchAdapterTest.php @@ -6,8 +6,9 @@ use App\Enums\StudyRegion; use App\Packages\Domains\WorldHeritage\Adapter\AlgoliaWorldHeritageSearchAdapter; use App\Packages\Features\QueryUseCases\ListQuery\AlgoliaSearchListQuery; +use Illuminate\Support\Facades\Log; use Mockery; -use PHPUnit\Framework\TestCase; +use Tests\TestCase; class AlgoliaWorldHeritageSearchAdapterTest extends TestCase { @@ -216,6 +217,53 @@ public function test_search_by_country_name_that_does_not_resolve_still_runs_ful $this->assertSame([7, 8], $result->ids); } + public function test_search_logs_and_skips_a_hit_with_a_non_numeric_id_instead_of_silently_dropping_it(): void + { + $indexName = 'world_heritage'; + $adapter = new AlgoliaWorldHeritageSearchAdapter($this->client, $indexName); + + $q = new AlgoliaSearchListQuery( + keyword: 'temple', + countryName: null, + countryIso3: null, + region: null, + category: null, + yearFrom: null, + yearTo: null, + criteria: [], + isEndangered: null, + currentPage: 1, + perPage: 30, + ); + + $this->client + ->shouldReceive('searchSingleIndex') + ->once() + ->andReturn([ + 'hits' => [ + ['id' => 10], + ['id' => 'not-a-number', 'objectID' => 'broken-1'], + ], + 'nbHits' => 2, + 'nbPages' => 1, + 'page' => 0, + 'hitsPerPage' => 30, + ]); + + Log::shouldReceive('warning') + ->once() + ->with('Algolia search hit has a non-numeric id', [ + 'id' => 'not-a-number', + 'objectID' => 'broken-1', + ]); + + $result = $adapter->search($q, currentPage: 1, perPage: 30); + + // The valid hit survives; the broken one is logged and skipped rather + // than silently coerced to 0 and dropped via array_filter. + $this->assertSame([10], $result->ids); + } + public function test_search_combines_keyword_with_country_iso3_filter_instead_of_dropping_keyword(): void { $indexName = 'world_heritage';