From cbca3b28510b75fff6c35307d6de8b6dc45e1ca0 Mon Sep 17 00:00:00 2001 From: Maurice Date: Wed, 21 Oct 2026 13:53:24 +0200 Subject: [PATCH] test(maps): the overpass place names cases get a suite of their own, the service suite back under its size --- server/scripts/test-size-baseline.json | 2 +- .../unit/nest/maps.overpass-names.test.ts | 111 ++++++++++++++++++ server/tests/unit/nest/maps.service.test.ts | 79 ------------- 3 files changed, 112 insertions(+), 80 deletions(-) create mode 100644 server/tests/unit/nest/maps.overpass-names.test.ts diff --git a/server/scripts/test-size-baseline.json b/server/scripts/test-size-baseline.json index 16d8aa61c..b30382988 100644 --- a/server/scripts/test-size-baseline.json +++ b/server/scripts/test-size-baseline.json @@ -3,7 +3,7 @@ "tests/unit/nest/budget.service.db.test.ts": 2220, "tests/unit/nest/doc-sync/doc-sync.service.test.ts": 2511, "tests/unit/nest/journey-domain.service.test.ts": 3876, - "tests/unit/nest/maps.service.test.ts": 3751, + "tests/unit/nest/maps.service.test.ts": 3681, "tests/unit/nest/places.service.test.ts": 2561, "tests/unit/nest/trips.service.test.ts": 2404 } diff --git a/server/tests/unit/nest/maps.overpass-names.test.ts b/server/tests/unit/nest/maps.overpass-names.test.ts new file mode 100644 index 000000000..059816f09 --- /dev/null +++ b/server/tests/unit/nest/maps.overpass-names.test.ts @@ -0,0 +1,111 @@ +/** + * The names a search of OpenStreetMap places shows (#1655): the language of the + * traveller before the native name, places closed for good left out, and an + * unnamed e-bike charging point kept where nothing else unnamed is (BIK-07). + * Moved out of maps.service.test.ts as they were, under the describe name they + * had there. + */ +import { OsmClient } from '../../../src/nest/maps/providers/osm.client'; + +import { describe, it, expect, vi, afterEach } from 'vitest'; + +const osm = new OsmClient(); + +afterEach(() => { + vi.unstubAllGlobals(); +}); + +describe('searchOverpassPois localized names (#1655)', () => { + // Elephant and Obelisk in Rome: OSM ships the native name plus localized tags. + const tags = { + name: 'Obelisco della Minerva', + 'name:en': 'Elephant and Obelisk', + 'name:de': 'Minerva-Obelisk', + int_name: 'Elephant Obelisk', + tourism: 'attraction', + }; + const stubOverpass = (elementTags: Record) => + vi.stubGlobal( + 'fetch', + vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({ elements: [{ type: 'node', id: 1, lat: 41.9, lon: 12.48, tags: elementTags }] }), + }), + ); + // Distinct bboxes per case so the module-level POI_CACHE (keyed by lang+bbox) + // never serves one case's names to another. + const bbox = (n: number) => ({ south: 41 + n / 100, west: 12, north: 42 + n / 100, east: 13 }); + + it('prefers name: for the user language over the native name', async () => { + stubOverpass(tags); + const { pois } = await osm.searchOverpassPois('sights', bbox(1), 'en-US'); + expect(pois[0].name).toBe('Elephant and Obelisk'); + }); + + it('localizes to a non-English language too', async () => { + stubOverpass(tags); + const { pois } = await osm.searchOverpassPois('sights', bbox(2), 'de-DE'); + expect(pois[0].name).toBe('Minerva-Obelisk'); + }); + + it('falls back to int_name when the language tag is absent', async () => { + stubOverpass({ name: tags.name, int_name: tags.int_name, tourism: 'attraction' }); + const { pois } = await osm.searchOverpassPois('sights', bbox(3), 'fr-FR'); + expect(pois[0].name).toBe('Elephant Obelisk'); + }); + + it('skips POIs tagged as closed for good (#1341)', async () => { + vi.stubGlobal( + 'fetch', + vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({ + elements: [ + { + type: 'node', + id: 1, + lat: 41.9, + lon: 12.48, + tags: { name: 'Gone Forever', tourism: 'attraction', disused: 'yes' }, + }, + { + type: 'node', + id: 2, + lat: 41.9, + lon: 12.49, + tags: { name: 'Ruin', tourism: 'attraction', abandoned: 'yes' }, + }, + { + type: 'node', + id: 3, + lat: 41.9, + lon: 12.5, + tags: { name: 'Shut', tourism: 'attraction', opening_hours: 'closed' }, + }, + { type: 'node', id: 4, lat: 41.9, lon: 12.51, tags: { name: 'Open For Business', tourism: 'attraction' } }, + ], + }), + }), + ); + const { pois } = await osm.searchOverpassPois('sights', bbox(5), 'en-US'); + expect(pois.map((p) => p.name)).toEqual(['Open For Business']); + }); + + it('falls back to the native name when no localized tag exists', async () => { + stubOverpass({ name: tags.name, tourism: 'attraction' }); + const { pois } = await osm.searchOverpassPois('sights', bbox(4), 'fr-FR'); + expect(pois[0].name).toBe('Obelisco della Minerva'); + }); + + it('keeps an unnamed e-bike charging point, as the search along a tour does (BIK-07)', async () => { + stubOverpass({ amenity: 'charging_station', bicycle: 'yes', 'socket:schuko': '2' }); + const { pois } = await osm.searchOverpassPois('ebike_charging', bbox(6), 'en-US'); + expect(pois.map((p) => [p.name, p.category])).toEqual([['', 'ebike_charging']]); + }); + + it('still drops an unnamed place of any other kind', async () => { + stubOverpass({ tourism: 'attraction' }); + const { pois } = await osm.searchOverpassPois('sights', bbox(7), 'en-US'); + expect(pois).toEqual([]); + }); +}); diff --git a/server/tests/unit/nest/maps.service.test.ts b/server/tests/unit/nest/maps.service.test.ts index cc9f473f3..8ebc768b2 100644 --- a/server/tests/unit/nest/maps.service.test.ts +++ b/server/tests/unit/nest/maps.service.test.ts @@ -1009,85 +1009,6 @@ describe('fetchOverpassDetails (fetch stubbed)', () => { }); }); -// ── searchOverpassPois localized names (#1655) ─────────────────────────────── - -describe('searchOverpassPois localized names (#1655)', () => { - // Elephant and Obelisk in Rome: OSM ships the native name plus localized tags. - const tags = { - name: 'Obelisco della Minerva', - 'name:en': 'Elephant and Obelisk', - 'name:de': 'Minerva-Obelisk', - int_name: 'Elephant Obelisk', - tourism: 'attraction', - }; - const stubOverpass = (elementTags: Record) => - vi.stubGlobal( - 'fetch', - vi.fn().mockResolvedValue({ - ok: true, - json: async () => ({ elements: [{ type: 'node', id: 1, lat: 41.9, lon: 12.48, tags: elementTags }] }), - }), - ); - // Distinct bboxes per case so the module-level POI_CACHE (keyed by lang+bbox) - // never serves one case's names to another. - const bbox = (n: number) => ({ south: 41 + n / 100, west: 12, north: 42 + n / 100, east: 13 }); - - it('prefers name: for the user language over the native name', async () => { - stubOverpass(tags); - const { pois } = await osm.searchOverpassPois('sights', bbox(1), 'en-US'); - expect(pois[0].name).toBe('Elephant and Obelisk'); - }); - - it('localizes to a non-English language too', async () => { - stubOverpass(tags); - const { pois } = await osm.searchOverpassPois('sights', bbox(2), 'de-DE'); - expect(pois[0].name).toBe('Minerva-Obelisk'); - }); - - it('falls back to int_name when the language tag is absent', async () => { - stubOverpass({ name: tags.name, int_name: tags.int_name, tourism: 'attraction' }); - const { pois } = await osm.searchOverpassPois('sights', bbox(3), 'fr-FR'); - expect(pois[0].name).toBe('Elephant Obelisk'); - }); - - it('skips POIs tagged as closed for good (#1341)', async () => { - vi.stubGlobal( - 'fetch', - vi.fn().mockResolvedValue({ - ok: true, - json: async () => ({ - elements: [ - { type: 'node', id: 1, lat: 41.9, lon: 12.48, tags: { name: 'Gone Forever', tourism: 'attraction', disused: 'yes' } }, - { type: 'node', id: 2, lat: 41.9, lon: 12.49, tags: { name: 'Ruin', tourism: 'attraction', abandoned: 'yes' } }, - { type: 'node', id: 3, lat: 41.9, lon: 12.5, tags: { name: 'Shut', tourism: 'attraction', opening_hours: 'closed' } }, - { type: 'node', id: 4, lat: 41.9, lon: 12.51, tags: { name: 'Open For Business', tourism: 'attraction' } }, - ], - }), - }), - ); - const { pois } = await osm.searchOverpassPois('sights', bbox(5), 'en-US'); - expect(pois.map((p: any) => p.name)).toEqual(['Open For Business']); - }); - - it('falls back to the native name when no localized tag exists', async () => { - stubOverpass({ name: tags.name, tourism: 'attraction' }); - const { pois } = await osm.searchOverpassPois('sights', bbox(4), 'fr-FR'); - expect(pois[0].name).toBe('Obelisco della Minerva'); - }); - - it('keeps an unnamed e-bike charging point, as the search along a tour does (BIK-07)', async () => { - stubOverpass({ amenity: 'charging_station', bicycle: 'yes', 'socket:schuko': '2' }); - const { pois } = await osm.searchOverpassPois('ebike_charging', bbox(6), 'en-US'); - expect(pois.map((p: any) => [p.name, p.category])).toEqual([['', 'ebike_charging']]); - }); - - it('still drops an unnamed place of any other kind', async () => { - stubOverpass({ tourism: 'attraction' }); - const { pois } = await osm.searchOverpassPois('sights', bbox(7), 'en-US'); - expect(pois).toEqual([]); - }); -}); - // ── fetchWikimediaPhoto (fetch stubbed) ─────────────────────────────────────── describe('fetchWikimediaPhoto (fetch stubbed)', () => {