test(maps): the overpass place names cases get a suite of their own, the service suite back under its size

This commit is contained in:
Maurice
2026-10-21 13:53:24 +02:00
parent 8a75712041
commit cbca3b2851
3 changed files with 112 additions and 80 deletions
+1 -1
View File
@@ -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
}
@@ -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<string, string>) =>
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:<lang> 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([]);
});
});
@@ -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<string, string>) =>
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:<lang> 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)', () => {