mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-30 18:46:00 +00:00
test: make the Google Maps ftid path honest + cover the URL helper
The Places API googleMapsUri is a cid-style URL with no ftid, so the search/getPlaceDetails fixtures had stored a fabricated ftid. Switch them to real cid URLs and assert google_ftid is null — the precise query_place_id link still fixes the wrong-spot bug — and document the behaviour on googleFtidFromMapsUrl. - add a direct googleFtidFromMapsUrl test: extracts a real /place ftid, returns null for a cid URL, rejects malformed/hostile values - add placeGoogleMaps.test.ts covering the whole fallback chain (ftid -> place_id -> details URL -> coords) and the hostile-ftid rejection - PlaceInspector: use a freshly-fetched ftid when the place hasn't stored one
This commit is contained in:
@@ -165,7 +165,11 @@ export default function PlaceInspector({
|
||||
|
||||
const openingHours = googleDetails?.opening_hours || null
|
||||
const openNow = googleDetails?.open_now ?? null
|
||||
const googleMapsUrl = getGoogleMapsUrlForPlace(place, googleDetails?.google_maps_url)
|
||||
// Prefer the place's stored ftid; if it has none yet, use the one just fetched from Google.
|
||||
const googleMapsUrl = getGoogleMapsUrlForPlace(
|
||||
place ? { ...place, google_ftid: place.google_ftid || googleDetails?.google_ftid || null } : null,
|
||||
googleDetails?.google_maps_url,
|
||||
)
|
||||
const selectedDay = days?.find(d => d.id === selectedDayId)
|
||||
const weekdayIndex = getWeekdayIndex(selectedDay?.date)
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { getGoogleMapsUrlForPlace } from './placeGoogleMaps'
|
||||
|
||||
const base = { name: 'Eiffel Tower', lat: 48.8584, lng: 2.2945, google_place_id: null, google_ftid: null } as any
|
||||
|
||||
describe('getGoogleMapsUrlForPlace', () => {
|
||||
it('FE-PLACE-GMAPS-001: uses a valid ftid for a precise /place link', () => {
|
||||
const url = getGoogleMapsUrlForPlace({ ...base, google_ftid: '0x47e66e2964e34e2d:0x8ddca9ee380ef7e0' })
|
||||
expect(url).toBe('https://www.google.com/maps/place/?q=Eiffel%20Tower&ftid=0x47e66e2964e34e2d:0x8ddca9ee380ef7e0')
|
||||
})
|
||||
|
||||
it('FE-PLACE-GMAPS-002: falls back to query_place_id when there is no ftid', () => {
|
||||
const url = getGoogleMapsUrlForPlace({ ...base, google_place_id: 'ChIJ123' })
|
||||
expect(url).toBe('https://www.google.com/maps/search/?api=1&query=Eiffel%20Tower&query_place_id=ChIJ123')
|
||||
})
|
||||
|
||||
it('FE-PLACE-GMAPS-003: ignores a malformed/hostile ftid and falls through to the place id', () => {
|
||||
const url = getGoogleMapsUrlForPlace({ ...base, google_ftid: '0xAB&q=evil', google_place_id: 'ChIJ123' })
|
||||
expect(url).toBe('https://www.google.com/maps/search/?api=1&query=Eiffel%20Tower&query_place_id=ChIJ123')
|
||||
})
|
||||
|
||||
it('FE-PLACE-GMAPS-004: uses the details URL when there is no ftid or place id', () => {
|
||||
const url = getGoogleMapsUrlForPlace(base, 'https://maps.google.com/?cid=123')
|
||||
expect(url).toBe('https://maps.google.com/?cid=123')
|
||||
})
|
||||
|
||||
it('FE-PLACE-GMAPS-005: falls back to coordinates as a last resort', () => {
|
||||
const url = getGoogleMapsUrlForPlace(base)
|
||||
expect(url).toBe('https://www.google.com/maps/search/?api=1&query=48.8584,2.2945')
|
||||
})
|
||||
|
||||
it('FE-PLACE-GMAPS-006: returns null for no place or no location', () => {
|
||||
expect(getGoogleMapsUrlForPlace(null)).toBeNull()
|
||||
expect(getGoogleMapsUrlForPlace({ ...base, lat: null, lng: null })).toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -90,6 +90,11 @@ function toApiLang(lang: string | undefined, fallback = 'en'): string {
|
||||
|
||||
const GOOGLE_FTID_RE = /^0x[0-9a-f]+:0x[0-9a-f]+$/i;
|
||||
|
||||
// Extracts a Google Maps feature id (ftid, 0x..:0x..) from a URL's ?ftid= param.
|
||||
// The Places API (New) googleMapsUri is usually a cid-style URL (https://maps.google.com/?cid=NNN)
|
||||
// with no ftid, so this returns null for most API responses — the precise query_place_id link is
|
||||
// used instead. It does recover an ftid from a /place/?...&ftid= URL, e.g. a pasted share link
|
||||
// resolved by resolveGoogleMapsUrl or a Google MyMaps list import.
|
||||
export function googleFtidFromMapsUrl(url?: string | null): string | null {
|
||||
if (!url) return null;
|
||||
try {
|
||||
|
||||
@@ -73,6 +73,7 @@ import {
|
||||
parseOpeningHours,
|
||||
buildOsmDetails,
|
||||
getMapsKey,
|
||||
googleFtidFromMapsUrl,
|
||||
} from '../../../src/services/mapsService';
|
||||
|
||||
afterEach(() => {
|
||||
@@ -756,7 +757,8 @@ describe('searchPlaces (fetch stubbed)', () => {
|
||||
displayName: { text: 'Eiffel Tower' },
|
||||
formattedAddress: 'Paris',
|
||||
location: { latitude: 48.8, longitude: 2.3 },
|
||||
googleMapsUri: 'https://www.google.com/maps/place/?q=Eiffel%20Tower&ftid=0x882bf179e806d471:0x8591dde29c821a93',
|
||||
// Real search API returns a cid-style URL with no ftid → google_ftid stays null.
|
||||
googleMapsUri: 'https://maps.google.com/?cid=10403719659250533155',
|
||||
}],
|
||||
}),
|
||||
}));
|
||||
@@ -764,7 +766,7 @@ describe('searchPlaces (fetch stubbed)', () => {
|
||||
const result = await searchPlaces(1, 'Eiffel Tower');
|
||||
expect(result.source).toBe('google');
|
||||
expect((result.places[0] as any).google_place_id).toBe('gid1');
|
||||
expect((result.places[0] as any).google_ftid).toBe('0x882bf179e806d471:0x8591dde29c821a93');
|
||||
expect((result.places[0] as any).google_ftid).toBeNull();
|
||||
});
|
||||
|
||||
it('MAPS-039b: throws with Google error status when Google API returns non-ok', async () => {
|
||||
@@ -1090,7 +1092,9 @@ describe('getPlaceDetails (fetch stubbed)', () => {
|
||||
weekdayDescriptions: ['Monday: 9:00 AM – 12:00 AM'],
|
||||
openNow: true,
|
||||
},
|
||||
googleMapsUri: 'https://maps.google.com/?cid=123&ftid=0x882bf179e806d471:0x8591dde29c821a93',
|
||||
// The Places API returns a cid-style URL with no ftid, so google_ftid stays null
|
||||
// and the precise query_place_id link is used on the client instead.
|
||||
googleMapsUri: 'https://maps.google.com/?cid=10403719659250533155',
|
||||
editorialSummary: { text: 'Iconic iron tower.' },
|
||||
reviews: [
|
||||
{
|
||||
@@ -1107,7 +1111,7 @@ describe('getPlaceDetails (fetch stubbed)', () => {
|
||||
const result = await getPlaceDetails(1, 'ChIJ123');
|
||||
const place = result.place as any;
|
||||
expect(place.google_place_id).toBe('ChIJ123');
|
||||
expect(place.google_ftid).toBe('0x882bf179e806d471:0x8591dde29c821a93');
|
||||
expect(place.google_ftid).toBeNull();
|
||||
expect(place.name).toBe('Eiffel Tower');
|
||||
expect(place.rating).toBe(4.7);
|
||||
expect(place.rating_count).toBe(200000);
|
||||
@@ -1476,3 +1480,19 @@ describe('getPlacePhoto (fetch stubbed)', () => {
|
||||
expect(mockCachePut).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
describe('googleFtidFromMapsUrl', () => {
|
||||
it('MAPS-FTID-001: extracts a valid ftid from a /place/?ftid= URL (resolved share link)', () => {
|
||||
expect(googleFtidFromMapsUrl('https://www.google.com/maps/place/?q=X&ftid=0x882bf179e806d471:0x8591dde29c821a93'))
|
||||
.toBe('0x882bf179e806d471:0x8591dde29c821a93');
|
||||
});
|
||||
it('MAPS-FTID-002: returns null for a cid-style URL (the usual Places API shape)', () => {
|
||||
expect(googleFtidFromMapsUrl('https://maps.google.com/?cid=10403719659250533155')).toBeNull();
|
||||
});
|
||||
it('MAPS-FTID-003: rejects malformed / hostile ftid values', () => {
|
||||
expect(googleFtidFromMapsUrl('https://maps.google.com/?ftid=not-an-ftid')).toBeNull();
|
||||
expect(googleFtidFromMapsUrl('https://maps.google.com/?ftid=0xAB%26q%3Devil%3Cscript%3E')).toBeNull();
|
||||
expect(googleFtidFromMapsUrl('not a url')).toBeNull();
|
||||
expect(googleFtidFromMapsUrl(null)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user