fix: update tests for removed Google/Naver import and URL resolution
Some checks are pending
Security Scan / scout (push) Waiting to run
Some checks are pending
Security Scan / scout (push) Waiting to run
- Remove importGoogleList tests from placeService.test.ts - Remove resolveGoogleMapsUrl tests from mapsService.test.ts - Remove google-list/naver-list import tests from places.controller.test.ts - Update maps integration test to mock resolveAmapUri instead of resolveGoogleMapsUrl
This commit is contained in:
parent
a02b06042a
commit
536a43b75e
@ -46,18 +46,24 @@ vi.mock('../../src/config', () => ({
|
||||
}));
|
||||
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
|
||||
|
||||
// Default mock: resolveGoogleMapsUrl rejects with 400 (SSRF-like behaviour for
|
||||
// URLs that look internal); individual tests override with mockResolvedValueOnce.
|
||||
vi.mock('../../src/services/amap/amap.places', () => ({
|
||||
searchAmapPlaces: vi.fn(),
|
||||
autocompleteAmapPlaces: vi.fn(),
|
||||
getAmapPlaceDetails: vi.fn(),
|
||||
getAmapPlaceDetailsExpanded: vi.fn(),
|
||||
reverseAmapGeocode: vi.fn(),
|
||||
resolveAmapUri: vi.fn().mockImplementation(() => {
|
||||
throw Object.assign(new Error('SSRF or invalid URL'), { status: 400 })
|
||||
}),
|
||||
searchAmapPois: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../src/services/mapsService', () => ({
|
||||
searchPlaces: vi.fn(),
|
||||
autocompletePlaces: vi.fn(),
|
||||
getPlaceDetails: vi.fn(),
|
||||
getPlacePhoto: vi.fn(),
|
||||
reverseGeocode: vi.fn(),
|
||||
resolveGoogleMapsUrl: vi.fn().mockRejectedValue(
|
||||
Object.assign(new Error('SSRF or invalid URL'), { status: 400 })
|
||||
),
|
||||
// Imported at module load by the maps stack (pulled in via app.module).
|
||||
buildUserAgent: () => 'TREK-Test-UA',
|
||||
}));
|
||||
|
||||
@ -68,6 +74,7 @@ import { resetTestDb, resetRateLimits } from '../helpers/test-db';
|
||||
import { createUser } from '../helpers/factories';
|
||||
import { authCookie } from '../helpers/auth';
|
||||
import * as mapsService from '../../src/services/mapsService';
|
||||
import * as amapPlaces from '../../src/services/amap/amap.places';
|
||||
|
||||
let nestApp: INestApplication;
|
||||
let app: Application;
|
||||
@ -222,15 +229,17 @@ describe('Maps happy paths (mocked service)', () => {
|
||||
|
||||
it('MAPS-008 — POST /maps/resolve-url returns extracted coordinates', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
vi.mocked(mapsService.resolveGoogleMapsUrl).mockResolvedValueOnce({
|
||||
vi.mocked(amapPlaces.resolveAmapUri).mockReturnValueOnce({
|
||||
lat: 48.8584,
|
||||
lng: 2.2945,
|
||||
} as any);
|
||||
name: null,
|
||||
address: null,
|
||||
});
|
||||
|
||||
const res = await request(app)
|
||||
.post('/api/maps/resolve-url')
|
||||
.set('Cookie', authCookie(user.id))
|
||||
.send({ url: 'https://maps.google.com/place/eiffel-tower' });
|
||||
.send({ url: 'https://uri.amap.com/marker?position=2.2945,48.8584' });
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.lat).toBe(48.8584);
|
||||
|
||||
@ -135,43 +135,6 @@ describe('PlacesController (parity with the legacy /api/trips/:tripId/places rou
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /import/google-list + naver-list', () => {
|
||||
it('400 without a url', async () => {
|
||||
expect(await thrownAsync(() => new PlacesController(svc()).importGoogle(user, '5', undefined))).toEqual({ status: 400, body: { error: 'URL is required' } });
|
||||
});
|
||||
it('400 when url is the wrong type (not a string)', async () => {
|
||||
expect(await thrownAsync(() => new PlacesController(svc()).importNaver(user, '5', 123))).toEqual({ status: 400, body: { error: 'URL is required' } });
|
||||
});
|
||||
it('maps a service { error, status } to the same response', async () => {
|
||||
const s = svc({ importGoogleList: vi.fn().mockResolvedValue({ error: 'List is empty', status: 400 }) } as Partial<PlacesService>);
|
||||
expect(await thrownAsync(() => new PlacesController(s).importGoogle(user, '5', 'http://x'))).toEqual({ status: 400, body: { error: 'List is empty' } });
|
||||
});
|
||||
it('imports a naver list and returns the count + listName', async () => {
|
||||
const s = svc({ importNaverList: vi.fn().mockResolvedValue({ places: [{ id: 1 }], listName: 'Trip', skipped: 2 }), broadcast: vi.fn() } as Partial<PlacesService>);
|
||||
expect(await new PlacesController(s).importNaver(user, '5', 'http://x')).toEqual({ places: [{ id: 1 }], count: 1, listName: 'Trip', skipped: 2 });
|
||||
});
|
||||
it('forwards the enrich flag + userId and broadcasts each imported place', async () => {
|
||||
const importGoogleList = vi.fn().mockResolvedValue({ places: [{ id: 1 }, { id: 2 }], listName: 'L', skipped: 0 });
|
||||
const broadcast = vi.fn();
|
||||
const s = svc({ importGoogleList, broadcast } as Partial<PlacesService>);
|
||||
expect(await new PlacesController(s).importGoogle(user, '5', 'http://x', 'true', 'sock')).toEqual({ places: [{ id: 1 }, { id: 2 }], count: 2, listName: 'L', skipped: 0 });
|
||||
expect(importGoogleList).toHaveBeenCalledWith('5', 'http://x', { enrich: true, userId: 1 });
|
||||
expect(broadcast).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
it('wraps a thrown Error in the provider-specific 400 (Google)', async () => {
|
||||
const s = svc({ importGoogleList: vi.fn().mockRejectedValue(new Error('network down')) } as Partial<PlacesService>);
|
||||
expect(await thrownAsync(() => new PlacesController(s).importGoogle(user, '5', 'http://x'))).toEqual({
|
||||
status: 400, body: { error: 'Failed to import Google Maps list. Make sure the list is shared publicly.' },
|
||||
});
|
||||
});
|
||||
it('wraps a non-Error rejection in the provider-specific 400 (Naver)', async () => {
|
||||
const s = svc({ importNaverList: vi.fn().mockRejectedValue('weird') } as Partial<PlacesService>);
|
||||
expect(await thrownAsync(() => new PlacesController(s).importNaver(user, '5', 'http://x'))).toEqual({
|
||||
status: 400, body: { error: 'Failed to import Naver Maps list. Make sure the list is shared publicly.' },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /bulk-delete', () => {
|
||||
it('400 when ids is not an array of numbers', () => {
|
||||
expect(thrown(() => new PlacesController(svc()).bulkDelete(user, '5', ['a']))).toEqual({ status: 400, body: { error: 'ids must be an array of numbers' } });
|
||||
|
||||
@ -296,159 +296,6 @@ describe('reverseGeocode (fetch stubbed)', () => {
|
||||
expect(result.address).toBe('Somewhere');
|
||||
});
|
||||
});
|
||||
|
||||
// Nominatim stub used by resolveGoogleMapsUrl after coordinate extraction
|
||||
const nominatimStub = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({ display_name: 'Paris, France', name: null, address: {} }),
|
||||
});
|
||||
|
||||
// ── resolveGoogleMapsUrl coordinate extraction ────────────────────────────────
|
||||
|
||||
describe('resolveGoogleMapsUrl coordinate extraction (ReDoS guards)', () => {
|
||||
it('MAPS-020: extracts lat/lng from @lat,lng pattern', async () => {
|
||||
vi.stubGlobal('fetch', nominatimStub);
|
||||
const { resolveGoogleMapsUrl } = await import('../../../src/services/mapsService');
|
||||
const result = await resolveGoogleMapsUrl('https://www.google.com/maps/@48.8566,2.3522,15z');
|
||||
expect(result.lat).toBeCloseTo(48.8566, 3);
|
||||
expect(result.lng).toBeCloseTo(2.3522, 3);
|
||||
});
|
||||
|
||||
it('MAPS-021: extracts lat/lng from !3d!4d data pattern', async () => {
|
||||
vi.stubGlobal('fetch', nominatimStub);
|
||||
const { resolveGoogleMapsUrl } = await import('../../../src/services/mapsService');
|
||||
const result = await resolveGoogleMapsUrl('https://www.google.com/maps/place/Eiffel+Tower/data=!3d48.8584!4d2.2945');
|
||||
expect(result.lat).toBeCloseTo(48.8584, 3);
|
||||
expect(result.lng).toBeCloseTo(2.2945, 3);
|
||||
});
|
||||
|
||||
it('MAPS-022: extracts lat/lng from ?q=lat,lng pattern', async () => {
|
||||
vi.stubGlobal('fetch', nominatimStub);
|
||||
const { resolveGoogleMapsUrl } = await import('../../../src/services/mapsService');
|
||||
const result = await resolveGoogleMapsUrl('https://www.google.com/maps?q=48.8566,2.3522');
|
||||
expect(result.lat).toBeCloseTo(48.8566, 3);
|
||||
expect(result.lng).toBeCloseTo(2.3522, 3);
|
||||
});
|
||||
|
||||
it('MAPS-023: extracts place name from /place/ path', async () => {
|
||||
vi.stubGlobal('fetch', nominatimStub);
|
||||
const { resolveGoogleMapsUrl } = await import('../../../src/services/mapsService');
|
||||
const result = await resolveGoogleMapsUrl('https://www.google.com/maps/place/Eiffel+Tower/@48.8584,2.2945,15z');
|
||||
expect(result.name).toBe('Eiffel Tower');
|
||||
});
|
||||
|
||||
it('MAPS-CID-001: resolves a cid= URL by following the redirect to a coordinate URL', async () => {
|
||||
// cid URLs (what get_place_details returns, and Google "Share" links) carry no
|
||||
// inline coords; the redirect target carries the !3d!4d data param.
|
||||
const fetchMock = vi.fn(async (u: string) => {
|
||||
if (u.includes('nominatim')) {
|
||||
return { ok: true, json: async () => ({ display_name: 'Paris, France', name: 'Eiffel Tower', address: {} }) };
|
||||
}
|
||||
return { url: 'https://www.google.com/maps/place/Eiffel+Tower/data=!3d48.8584!4d2.2945', text: async () => '' };
|
||||
});
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
const { resolveGoogleMapsUrl } = await import('../../../src/services/mapsService');
|
||||
const result = await resolveGoogleMapsUrl('https://maps.google.com/?cid=1234567890');
|
||||
expect(result.lat).toBeCloseTo(48.8584, 3);
|
||||
expect(result.lng).toBeCloseTo(2.2945, 3);
|
||||
});
|
||||
|
||||
it('MAPS-CID-002: falls back to parsing coordinates from the page body', async () => {
|
||||
const fetchMock = vi.fn(async (u: string) => {
|
||||
if (u.includes('nominatim')) {
|
||||
return { ok: true, json: async () => ({ display_name: 'NYC, USA', name: null, address: {} }) };
|
||||
}
|
||||
if (u.includes('cid=')) {
|
||||
// Redirect target has no inline coords.
|
||||
return { url: 'https://www.google.com/maps/place/Somewhere', text: async () => '' };
|
||||
}
|
||||
// Body fetch of the resolved URL embeds coords in the map data.
|
||||
return { url: 'https://www.google.com/maps/place/Somewhere', text: async () => 'x!3d40.6892!4d-74.0445y' };
|
||||
});
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
const { resolveGoogleMapsUrl } = await import('../../../src/services/mapsService');
|
||||
const result = await resolveGoogleMapsUrl('https://www.google.com/maps?cid=999');
|
||||
expect(result.lat).toBeCloseTo(40.6892, 3);
|
||||
expect(result.lng).toBeCloseTo(-74.0445, 3);
|
||||
});
|
||||
|
||||
it('MAPS-024 (ReDoS): /@(-?\\d+\\.?\\d*),(-?\\d+\\.?\\d*)/ on adversarial input < 500ms', () => {
|
||||
const adversarial = '/@' + '1'.repeat(10000) + '.';
|
||||
const start = Date.now();
|
||||
adversarial.match(/@(-?\d+\.?\d*),(-?\d+\.?\d*)/);
|
||||
expect(Date.now() - start).toBeLessThan(500);
|
||||
});
|
||||
|
||||
it('MAPS-025 (ReDoS): /!3d(-?\\d+\\.?\\d*)!4d/ on adversarial input < 500ms', () => {
|
||||
const adversarial = '!3d' + '1'.repeat(10000) + '.';
|
||||
const start = Date.now();
|
||||
adversarial.match(/!3d(-?\d+\.?\d*)!4d(-?\d+\.?\d*)/);
|
||||
expect(Date.now() - start).toBeLessThan(500);
|
||||
});
|
||||
|
||||
it('MAPS-026 (ReDoS): /[?&]q=(-?\\d+\\.?\\d*)/ on adversarial input < 500ms', () => {
|
||||
const adversarial = '?q=' + '1'.repeat(10000) + '.';
|
||||
const start = Date.now();
|
||||
adversarial.match(/[?&]q=(-?\d+\.?\d*),(-?\d+\.?\d*)/);
|
||||
expect(Date.now() - start).toBeLessThan(500);
|
||||
});
|
||||
|
||||
it('MAPS-027 (ReDoS): /<[^>]+>/ HTML strip on adversarial input < 100ms', () => {
|
||||
const adversarial = '<' + 'a'.repeat(10000);
|
||||
const start = Date.now();
|
||||
adversarial.replace(/<[^>]+>/g, '');
|
||||
expect(Date.now() - start).toBeLessThan(100);
|
||||
});
|
||||
|
||||
it('MAPS-028: throws when no coordinates found in URL', async () => {
|
||||
vi.stubGlobal('fetch', nominatimStub);
|
||||
const { resolveGoogleMapsUrl } = await import('../../../src/services/mapsService');
|
||||
await expect(resolveGoogleMapsUrl('https://www.google.com/maps')).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('MAPS-028b: throws 403 when short URL is blocked by SSRF check', async () => {
|
||||
mockCheckSsrf.mockResolvedValueOnce({ allowed: false });
|
||||
const { resolveGoogleMapsUrl } = await import('../../../src/services/mapsService');
|
||||
await expect(
|
||||
resolveGoogleMapsUrl('https://goo.gl/maps/abc123')
|
||||
).rejects.toMatchObject({ status: 403 });
|
||||
});
|
||||
|
||||
it('MAPS-028c: follows redirect for short goo.gl URL and extracts coordinates', async () => {
|
||||
const redirectFetch = vi.fn()
|
||||
// First call: the redirect (goo.gl), returns resolved URL in .url
|
||||
.mockResolvedValueOnce({
|
||||
url: 'https://www.google.com/maps/@48.8566,2.3522,15z',
|
||||
})
|
||||
// Second call: the Nominatim reverse geocode
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({ display_name: 'Paris, France', name: 'Paris', address: {} }),
|
||||
});
|
||||
vi.stubGlobal('fetch', redirectFetch);
|
||||
const { resolveGoogleMapsUrl } = await import('../../../src/services/mapsService');
|
||||
const result = await resolveGoogleMapsUrl('https://goo.gl/maps/abc123');
|
||||
expect(result.lat).toBeCloseTo(48.8566, 3);
|
||||
expect(result.lng).toBeCloseTo(2.3522, 3);
|
||||
});
|
||||
|
||||
it('MAPS-028d: falls back to nominatim address fields when no placeName in URL', async () => {
|
||||
const fetchMock = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
display_name: 'Louvre Museum, Paris',
|
||||
name: null,
|
||||
address: { tourism: 'Louvre Museum' },
|
||||
}),
|
||||
});
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
const { resolveGoogleMapsUrl } = await import('../../../src/services/mapsService');
|
||||
// URL with coordinates but no /place/ path segment
|
||||
const result = await resolveGoogleMapsUrl('https://www.google.com/maps/@48.8606,2.3376,15z');
|
||||
expect(result.name).toBe('Louvre Museum');
|
||||
});
|
||||
});
|
||||
|
||||
// ── searchNominatim (fetch-dependent) ────────────────────────────────────────
|
||||
|
||||
describe('searchNominatim (fetch stubbed)', () => {
|
||||
|
||||
@ -55,7 +55,7 @@ import { resetTestDb } from '../../helpers/test-db';
|
||||
import { createUser, createTrip, createPlace, createCategory, createTag } from '../../helpers/factories';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { listPlaces, createPlace as svcCreatePlace, getPlace, updatePlace, updatePlacesMany, deletePlace, importGpx, importKmlPlaces, importGoogleList, searchPlaceImage } from '../../../src/services/placeService';
|
||||
import { listPlaces, createPlace as svcCreatePlace, getPlace, updatePlace, updatePlacesMany, deletePlace, importGpx, importKmlPlaces, searchPlaceImage } from '../../../src/services/placeService';
|
||||
|
||||
const GPX_FIXTURE = path.join(__dirname, '../../fixtures/test.gpx');
|
||||
const KML_FIXTURE = path.join(__dirname, '../../fixtures/test.kml');
|
||||
@ -443,134 +443,6 @@ describe('importGpx', () => {
|
||||
expect(result.places[0].name).toBe('morning-hike');
|
||||
});
|
||||
});
|
||||
|
||||
// ── importGoogleList ──────────────────────────────────────────────────────────
|
||||
|
||||
describe('importGoogleList', () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it('PLACE-SVC-026 — returns error when list ID cannot be extracted from URL', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const result = await importGoogleList(String(trip.id), 'https://example.com/no-id-here') as any;
|
||||
expect(result.error).toMatch(/Could not extract list ID/);
|
||||
expect(result.status).toBe(400);
|
||||
});
|
||||
|
||||
it('PLACE-SVC-026b — a single-place link gives a guiding error instead of the generic one (#1304)', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const url = 'https://www.google.com/maps/place/Eiffel+Tower/@48.8584,2.2945,17z/data=!3m1';
|
||||
const result = await importGoogleList(String(trip.id), url) as any;
|
||||
expect(result.status).toBe(400);
|
||||
expect(result.error).toMatch(/single place/i);
|
||||
});
|
||||
|
||||
it('PLACE-SVC-027 — returns error when Google Maps API responds with non-ok status', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: false, text: async () => '', status: 502 }));
|
||||
const url = 'https://www.google.com/maps/placelists/list/ABC123DEF456';
|
||||
const result = await importGoogleList(String(trip.id), url) as any;
|
||||
expect(result.error).toMatch(/Failed to fetch list/);
|
||||
expect(result.status).toBe(502);
|
||||
});
|
||||
|
||||
it('PLACE-SVC-028 — imports places from a valid Google Maps list response', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
|
||||
const listPayload = [
|
||||
[null, null, null, null, 'My Test List', null, null, null, [
|
||||
[null, [null, null, null, null, null, [null, null, 48.8566, 2.3522]], 'Paris', null],
|
||||
[null, [null, null, null, null, null, [null, null, 51.5074, -0.1278]], 'London', 'Great city'],
|
||||
]],
|
||||
];
|
||||
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
text: async () => 'prefix\n' + JSON.stringify(listPayload),
|
||||
}));
|
||||
|
||||
const url = 'https://www.google.com/maps/placelists/list/ABC123DEF456';
|
||||
const result = await importGoogleList(String(trip.id), url) as any;
|
||||
expect(result.listName).toBe('My Test List');
|
||||
expect(result.places).toHaveLength(2);
|
||||
expect(result.places[0].name).toBe('Paris');
|
||||
expect(result.places[1].name).toBe('London');
|
||||
});
|
||||
|
||||
it('PLACE-SVC-028b — stores a Google Maps ftid separately from google_place_id', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
|
||||
const listPayload = [
|
||||
[null, null, null, null, 'My Test List', null, null, null, [
|
||||
[null, [null, null, null, null, '878 Weber St N', [null, null, 43.5118527, -80.5542617], ['-8634542354666695567', '-8822026229683971437']], "St. Jacobs Farmers' Market"],
|
||||
]],
|
||||
];
|
||||
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
text: async () => 'prefix\n' + JSON.stringify(listPayload),
|
||||
}));
|
||||
|
||||
const url = 'https://www.google.com/maps/placelists/list/ABC123DEF456';
|
||||
const result = await importGoogleList(String(trip.id), url) as any;
|
||||
|
||||
expect(result.places).toHaveLength(1);
|
||||
expect(result.places[0].google_place_id).toBeNull();
|
||||
expect(result.places[0].google_ftid).toBe('0x882bf179e806d471:0x8591dde29c821a93');
|
||||
});
|
||||
|
||||
it('PLACE-SVC-028c — backfills google_ftid when re-import skips a duplicate', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const existing = createPlace(testDb, trip.id, {
|
||||
name: "St. Jacobs Farmers' Market",
|
||||
lat: 43.5118527,
|
||||
lng: -80.5542617,
|
||||
}) as any;
|
||||
|
||||
const listPayload = [
|
||||
[null, null, null, null, 'My Test List', null, null, null, [
|
||||
[null, [null, null, null, null, '878 Weber St N', [null, null, 43.5118527, -80.5542617], ['-8634542354666695567', '-8822026229683971437']], "St. Jacobs Farmers' Market"],
|
||||
]],
|
||||
];
|
||||
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
text: async () => 'prefix\n' + JSON.stringify(listPayload),
|
||||
}));
|
||||
|
||||
const url = 'https://www.google.com/maps/placelists/list/ABC123DEF456';
|
||||
const result = await importGoogleList(String(trip.id), url) as any;
|
||||
const row = testDb.prepare('SELECT google_place_id, google_ftid FROM places WHERE id = ?').get(existing.id) as any;
|
||||
|
||||
expect(result.places).toHaveLength(0);
|
||||
expect(result.skipped).toBe(1);
|
||||
expect(row.google_place_id).toBeNull();
|
||||
expect(row.google_ftid).toBe('0x882bf179e806d471:0x8591dde29c821a93');
|
||||
});
|
||||
|
||||
it('PLACE-SVC-029 — returns error when list items array is empty', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
|
||||
const listPayload = [[null, null, null, null, 'Empty List', null, null, null, []]];
|
||||
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
text: async () => 'prefix\n' + JSON.stringify(listPayload),
|
||||
}));
|
||||
|
||||
const url = 'https://www.google.com/maps/placelists/list/ABC123DEF456';
|
||||
const result = await importGoogleList(String(trip.id), url) as any;
|
||||
expect(result.error).toBeDefined();
|
||||
expect(result.status).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
// ── searchPlaceImage ──────────────────────────────────────────────────────────
|
||||
|
||||
describe('searchPlaceImage', () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user