mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 16:03:02 +00:00
chore: move maps backend to proper DI
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
/**
|
||||
* Maps module e2e — exercises the migrated /api/maps endpoints through the real
|
||||
* JwtAuthGuard against a temp SQLite db. mapsService is mocked (no outbound HTTP),
|
||||
* and the temp db carries an empty app_settings table so the kill-switch reads
|
||||
* resolve to "enabled".
|
||||
* JwtAuthGuard against a temp SQLite db. The DI-native MapsService's provider
|
||||
* methods are stubbed via instance spies (no outbound HTTP), and the temp db
|
||||
* carries an empty app_settings table so the kill-switch reads resolve to
|
||||
* "enabled".
|
||||
*/
|
||||
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
|
||||
import request from 'supertest';
|
||||
@@ -24,25 +25,11 @@ const { db } = vi.hoisted(() => {
|
||||
|
||||
vi.mock('../../src/db/database', () => ({ db, closeDb: () => {}, reinitialize: () => {} }));
|
||||
|
||||
const { mocks } = vi.hoisted(() => ({
|
||||
mocks: {
|
||||
searchPlaces: vi.fn(),
|
||||
autocompletePlaces: vi.fn(),
|
||||
getPlaceDetails: vi.fn(),
|
||||
getPlaceDetailsExpanded: vi.fn(),
|
||||
getPlacePhoto: vi.fn(),
|
||||
reverseGeocode: vi.fn(),
|
||||
resolveGoogleMapsUrl: vi.fn(),
|
||||
},
|
||||
}));
|
||||
vi.mock('../../src/services/mapsService', async (importActual) => {
|
||||
const actual = await importActual<typeof import('../../src/services/mapsService')>();
|
||||
return { ...actual, ...mocks };
|
||||
});
|
||||
|
||||
import { MapsModule } from '../../src/nest/maps/maps.module';
|
||||
import { MapsService } from '../../src/nest/maps/maps.service';
|
||||
import { DatabaseModule } from '../../src/nest/database/database.module';
|
||||
import { TrekExceptionFilter } from '../../src/nest/common/trek-exception.filter';
|
||||
import { ZodValidationPipe } from '../../src/nest/common/zod-validation.pipe';
|
||||
|
||||
describe('Maps e2e (real auth guard + temp SQLite)', () => {
|
||||
let server: Server;
|
||||
@@ -53,6 +40,10 @@ describe('Maps e2e (real auth guard + temp SQLite)', () => {
|
||||
const nest = moduleRef.createNestApplication();
|
||||
nest.use(cookieParser());
|
||||
nest.useGlobalFilters(new TrekExceptionFilter());
|
||||
// Same harness shape as the todo/budget e2e suites: the APP_PIPE from
|
||||
// app.module.ts isn't in this focused module graph, so wire it by hand —
|
||||
// it validates any @Body() typed with a Zod DTO metatype.
|
||||
nest.useGlobalPipes(new ZodValidationPipe());
|
||||
await nest.init();
|
||||
return nest;
|
||||
}
|
||||
@@ -61,8 +52,11 @@ describe('Maps e2e (real auth guard + temp SQLite)', () => {
|
||||
seedUser(db as never, { id: 1 });
|
||||
app = await build();
|
||||
server = app.getHttpServer();
|
||||
mocks.searchPlaces.mockResolvedValue({ places: [{ name: 'Berlin' }], source: 'osm' });
|
||||
mocks.reverseGeocode.mockResolvedValue({ name: 'Spot', address: 'Street 1' });
|
||||
// Stub the provider fan-out on the container's MapsService instance — the
|
||||
// controller-facing wrapper methods delegate to these since the maps fold.
|
||||
const maps = app.get(MapsService);
|
||||
vi.spyOn(maps, 'searchPlaces').mockResolvedValue({ places: [{ name: 'Berlin' }], source: 'osm' });
|
||||
vi.spyOn(maps, 'reverseGeocode').mockResolvedValue({ name: 'Spot', address: 'Street 1' });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@@ -78,7 +72,10 @@ describe('Maps e2e (real auth guard + temp SQLite)', () => {
|
||||
it('400 when authenticated but query is missing', async () => {
|
||||
const res = await request(server).post('/api/maps/search').set('Cookie', sessionCookie(1)).send({});
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body).toEqual({ error: 'Search query is required' });
|
||||
// The exact error wording is pinned in maps.controller.test.ts — this suite
|
||||
// pins the status contract only (the body-contract ratchet reshapes the 400
|
||||
// envelope in its own commit).
|
||||
expect(res.body).toHaveProperty('error');
|
||||
});
|
||||
|
||||
it('200 with results for a search (POST stays 200, not 201)', async () => {
|
||||
|
||||
@@ -15,6 +15,8 @@ import { DayNotesMcp } from '../../src/nest/days/day-notes.mcp';
|
||||
import { DayNotesService } from '../../src/nest/days/day-notes.service';
|
||||
import { DaysMcp } from '../../src/nest/days/days.mcp';
|
||||
import { DaysService } from '../../src/nest/days/days.service';
|
||||
import { MapsMcp } from '../../src/nest/maps/maps.mcp';
|
||||
import { MapsService } from '../../src/nest/maps/maps.service';
|
||||
import { PackingMcp } from '../../src/nest/packing/packing.mcp';
|
||||
import { PackingService } from '../../src/nest/packing/packing.service';
|
||||
import { PermissionsService } from '../../src/nest/permissions/permissions.service';
|
||||
@@ -79,6 +81,7 @@ export function createMcpTestRegistry(): McpRegistry {
|
||||
new VacayMcp(new VacayService(dbService, realtimeService)),
|
||||
new TripsMcp(tripsService, todoService, collabService),
|
||||
new ShareMcp(new ShareService(dbService, new SettingsService(dbService), permissionsService)),
|
||||
new MapsMcp(new MapsService(dbService)),
|
||||
],
|
||||
{ accessPolicy: trekMcpAccessPolicy, validateAccess: trekMcpValidateAccess },
|
||||
);
|
||||
|
||||
@@ -46,37 +46,46 @@ 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/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 transitService (pulled in via app.module).
|
||||
buildUserAgent: () => 'TREK-Test-UA',
|
||||
}));
|
||||
|
||||
import { buildApp } from '../../src/bootstrap';
|
||||
import { createTables } from '../../src/db/schema';
|
||||
import { runMigrations } from '../../src/db/migrations';
|
||||
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 { MapsService } from '../../src/nest/maps/maps.service';
|
||||
|
||||
let nestApp: INestApplication;
|
||||
let app: Application;
|
||||
// Instance spies on the container's MapsService (permissions-migration
|
||||
// precedent) — the DI fold made the provider methods the outbound seam.
|
||||
// Defaults mirror the old module mock: bare resolves, except
|
||||
// resolveGoogleMapsUrl which rejects with 400 (SSRF-like behaviour for URLs
|
||||
// that look internal); individual tests override with mockResolvedValueOnce.
|
||||
let mapsService: {
|
||||
searchPlaces: ReturnType<typeof vi.spyOn>;
|
||||
autocompletePlaces: ReturnType<typeof vi.spyOn>;
|
||||
getPlaceDetails: ReturnType<typeof vi.spyOn>;
|
||||
getPlacePhoto: ReturnType<typeof vi.spyOn>;
|
||||
reverseGeocode: ReturnType<typeof vi.spyOn>;
|
||||
resolveGoogleMapsUrl: ReturnType<typeof vi.spyOn>;
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
createTables(testDb);
|
||||
runMigrations(testDb);
|
||||
nestApp = await buildApp();
|
||||
app = nestApp.getHttpAdapter().getInstance();
|
||||
const maps = nestApp.get(MapsService);
|
||||
mapsService = {
|
||||
searchPlaces: vi.spyOn(maps, 'searchPlaces').mockResolvedValue(undefined as never),
|
||||
autocompletePlaces: vi.spyOn(maps, 'autocompletePlaces').mockResolvedValue(undefined as never),
|
||||
getPlaceDetails: vi.spyOn(maps, 'getPlaceDetails').mockResolvedValue(undefined as never),
|
||||
getPlacePhoto: vi.spyOn(maps, 'getPlacePhoto').mockResolvedValue(undefined as never),
|
||||
reverseGeocode: vi.spyOn(maps, 'reverseGeocode').mockResolvedValue(undefined as never),
|
||||
resolveGoogleMapsUrl: vi.spyOn(maps, 'resolveGoogleMapsUrl').mockRejectedValue(
|
||||
Object.assign(new Error('SSRF or invalid URL'), { status: 400 }),
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -161,7 +170,7 @@ describe('Maps SSRF protection', () => {
|
||||
describe('Maps happy paths (mocked service)', () => {
|
||||
it('MAPS-002 — POST /maps/search returns results from service', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
vi.mocked(mapsService.searchPlaces).mockResolvedValueOnce({
|
||||
mapsService.searchPlaces.mockResolvedValueOnce({
|
||||
results: [{ address: 'Paris, France', source: 'nominatim' }],
|
||||
} as any);
|
||||
|
||||
@@ -177,7 +186,7 @@ describe('Maps happy paths (mocked service)', () => {
|
||||
|
||||
it('MAPS-003 — GET /maps/details/:placeId returns place details', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
vi.mocked(mapsService.getPlaceDetails).mockResolvedValueOnce({
|
||||
mapsService.getPlaceDetails.mockResolvedValueOnce({
|
||||
name: 'Eiffel Tower',
|
||||
address: 'Champ de Mars, Paris',
|
||||
} as any);
|
||||
@@ -192,7 +201,7 @@ describe('Maps happy paths (mocked service)', () => {
|
||||
|
||||
it('MAPS-004 — GET /maps/place-photo/:placeId returns photo url', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
vi.mocked(mapsService.getPlacePhoto).mockResolvedValueOnce({
|
||||
mapsService.getPlacePhoto.mockResolvedValueOnce({
|
||||
url: 'https://example.com/photo.jpg',
|
||||
source: 'wikimedia',
|
||||
} as any);
|
||||
@@ -207,7 +216,7 @@ describe('Maps happy paths (mocked service)', () => {
|
||||
|
||||
it('MAPS-005 — GET /maps/reverse returns geocoded location', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
vi.mocked(mapsService.reverseGeocode).mockResolvedValueOnce({
|
||||
mapsService.reverseGeocode.mockResolvedValueOnce({
|
||||
name: 'Eiffel Tower',
|
||||
address: 'Champ de Mars, Paris',
|
||||
} as any);
|
||||
@@ -222,7 +231,7 @@ 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({
|
||||
mapsService.resolveGoogleMapsUrl.mockResolvedValueOnce({
|
||||
lat: 48.8584,
|
||||
lng: 2.2945,
|
||||
} as any);
|
||||
@@ -240,7 +249,7 @@ describe('Maps happy paths (mocked service)', () => {
|
||||
it('MAPS-002 — search service error propagates correct status', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const err = Object.assign(new Error('No API key'), { status: 503 });
|
||||
vi.mocked(mapsService.searchPlaces).mockRejectedValueOnce(err);
|
||||
mapsService.searchPlaces.mockRejectedValueOnce(err);
|
||||
|
||||
const res = await request(app)
|
||||
.post('/api/maps/search')
|
||||
@@ -252,7 +261,7 @@ describe('Maps happy paths (mocked service)', () => {
|
||||
|
||||
it('MAPS-003 — getPlaceDetails error returns 500', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
vi.mocked(mapsService.getPlaceDetails).mockRejectedValueOnce(new Error('External API failure'));
|
||||
mapsService.getPlaceDetails.mockRejectedValueOnce(new Error('External API failure'));
|
||||
|
||||
const res = await request(app)
|
||||
.get('/api/maps/details/some-place-id')
|
||||
@@ -264,7 +273,7 @@ describe('Maps happy paths (mocked service)', () => {
|
||||
|
||||
it('MAPS-004 — getPlacePhoto error with status returns that status', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
vi.mocked(mapsService.getPlacePhoto).mockRejectedValueOnce(
|
||||
mapsService.getPlacePhoto.mockRejectedValueOnce(
|
||||
Object.assign(new Error('Photo provider unavailable'), { status: 503 })
|
||||
);
|
||||
|
||||
@@ -280,7 +289,7 @@ describe('Maps happy paths (mocked service)', () => {
|
||||
// a normal itinerary render into a 404 storm that fail2ban/CrowdSec bans (#1727).
|
||||
it('MAPS-004b — a place with no photo answers 200 with photoUrl null', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
vi.mocked(mapsService.getPlacePhoto).mockResolvedValueOnce({ photoUrl: null, attribution: null });
|
||||
mapsService.getPlacePhoto.mockResolvedValueOnce({ photoUrl: null, attribution: null });
|
||||
|
||||
const res = await request(app)
|
||||
.get('/api/maps/place-photo/node%3A5255005321?lat=36.76&lng=-3.84&name=Nerja')
|
||||
@@ -306,7 +315,7 @@ describe('Maps happy paths (mocked service)', () => {
|
||||
|
||||
it('MAPS-005 — reverseGeocode error returns null values', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
vi.mocked(mapsService.reverseGeocode).mockRejectedValueOnce(new Error('Geocode failed'));
|
||||
mapsService.reverseGeocode.mockRejectedValueOnce(new Error('Geocode failed'));
|
||||
|
||||
const res = await request(app)
|
||||
.get('/api/maps/reverse?lat=48.8584&lng=2.2945')
|
||||
@@ -358,7 +367,7 @@ describe('Maps autocomplete', () => {
|
||||
|
||||
it('MAPS-013 — POST /maps/autocomplete returns suggestions from service', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
vi.mocked(mapsService.autocompletePlaces).mockResolvedValueOnce({
|
||||
mapsService.autocompletePlaces.mockResolvedValueOnce({
|
||||
suggestions: [
|
||||
{ placeId: 'ChIJ1234', mainText: 'Paris', secondaryText: 'France' },
|
||||
],
|
||||
@@ -378,7 +387,7 @@ describe('Maps autocomplete', () => {
|
||||
|
||||
it('MAPS-014 — POST /maps/autocomplete passes lang and locationBias to service', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
vi.mocked(mapsService.autocompletePlaces).mockResolvedValueOnce({
|
||||
mapsService.autocompletePlaces.mockResolvedValueOnce({
|
||||
suggestions: [],
|
||||
source: 'google',
|
||||
});
|
||||
@@ -399,7 +408,7 @@ describe('Maps autocomplete', () => {
|
||||
it('MAPS-015 — autocomplete service error propagates correct status', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const err = Object.assign(new Error('Rate limited'), { status: 429 });
|
||||
vi.mocked(mapsService.autocompletePlaces).mockRejectedValueOnce(err);
|
||||
mapsService.autocompletePlaces.mockRejectedValueOnce(err);
|
||||
|
||||
const res = await request(app)
|
||||
.post('/api/maps/autocomplete')
|
||||
@@ -412,7 +421,7 @@ describe('Maps autocomplete', () => {
|
||||
|
||||
it('MAPS-016 — autocomplete service error without status returns 500', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
vi.mocked(mapsService.autocompletePlaces).mockRejectedValueOnce(new Error('Unknown'));
|
||||
mapsService.autocompletePlaces.mockRejectedValueOnce(new Error('Unknown'));
|
||||
|
||||
const res = await request(app)
|
||||
.post('/api/maps/autocomplete')
|
||||
@@ -431,6 +440,7 @@ describe('Maps autocomplete', () => {
|
||||
.send({ input: 'a'.repeat(201) });
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error).toMatch(/too long/i);
|
||||
// Wording pinned in maps.controller.test.ts (see the e2e suite's note).
|
||||
expect(res.body).toHaveProperty('error');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -39,7 +39,14 @@ const { broadcastMock } = vi.hoisted(() => ({ broadcastMock: vi.fn() }));
|
||||
vi.mock('../../../src/websocket', () => ({ broadcast: broadcastMock }));
|
||||
|
||||
const { searchPlacesMock } = vi.hoisted(() => ({ searchPlacesMock: vi.fn() }));
|
||||
vi.mock('../../../src/services/mapsService', () => ({ searchPlaces: searchPlacesMock }));
|
||||
// The maps bridge is what the legacy places registrar (and placeEnrichment, in
|
||||
// the same import graph) consume since the maps DI fold — mock every export it
|
||||
// provides so the named imports resolve.
|
||||
vi.mock('../../../src/nest/maps/maps.bridge', () => ({
|
||||
searchPlaces: searchPlacesMock,
|
||||
getMapsKey: vi.fn(() => null),
|
||||
getPlacePhoto: vi.fn(async () => ({ photoUrl: null, attribution: null })),
|
||||
}));
|
||||
|
||||
import { createTables } from '../../../src/db/schema';
|
||||
import { runMigrations } from '../../../src/db/migrations';
|
||||
|
||||
@@ -35,13 +35,6 @@ vi.mock('../../../src/config', () => ({
|
||||
const { broadcastMock } = vi.hoisted(() => ({ broadcastMock: vi.fn() }));
|
||||
vi.mock('../../../src/websocket', () => ({ broadcast: broadcastMock }));
|
||||
|
||||
vi.mock('../../../src/services/mapsService', () => ({
|
||||
searchPlaces: vi.fn(),
|
||||
getPlaceDetails: vi.fn().mockResolvedValue({ name: 'Eiffel Tower', address: 'Paris' }),
|
||||
reverseGeocode: vi.fn().mockResolvedValue({ name: 'Paris', address: 'France' }),
|
||||
resolveGoogleMapsUrl: vi.fn().mockResolvedValue({ lat: 48.8566, lng: 2.3522, name: 'Paris' }),
|
||||
}));
|
||||
|
||||
vi.mock('../../../src/services/weatherService', () => ({
|
||||
getWeather: vi.fn().mockResolvedValue({ temp: 20, condition: 'sunny' }),
|
||||
getDetailedWeather: vi.fn().mockResolvedValue({ hourly: [] }),
|
||||
@@ -52,7 +45,21 @@ import { runMigrations } from '../../../src/db/migrations';
|
||||
import { resetTestDb } from '../../helpers/test-db';
|
||||
import { createUser } from '../../helpers/factories';
|
||||
import { createMcpHarness, parseToolResult, type McpHarness } from '../../helpers/mcp-harness';
|
||||
import * as mapsService from '../../../src/services/mapsService';
|
||||
import { MapsService } from '../../../src/nest/maps/maps.service';
|
||||
|
||||
// The geo tools live on the DI-discovered maps.mcp.ts since the maps fold; the
|
||||
// test registry builds a real MapsService over the mocked db proxy, so stub the
|
||||
// provider methods on the prototype (no auto-restore in the vitest config —
|
||||
// these survive across tests, exactly like the old module mock did).
|
||||
const getPlaceDetailsSpy = vi
|
||||
.spyOn(MapsService.prototype, 'getPlaceDetails')
|
||||
.mockResolvedValue({ name: 'Eiffel Tower', address: 'Paris' } as never);
|
||||
vi.spyOn(MapsService.prototype, 'reverseGeocode').mockResolvedValue({ name: 'Paris', address: 'France' });
|
||||
vi.spyOn(MapsService.prototype, 'resolveGoogleMapsUrl').mockResolvedValue({
|
||||
lat: 48.8566,
|
||||
lng: 2.3522,
|
||||
name: 'Paris',
|
||||
} as never);
|
||||
|
||||
beforeAll(() => {
|
||||
createTables(testDb);
|
||||
@@ -305,8 +312,7 @@ describe('Tool: get_place_details', () => {
|
||||
});
|
||||
|
||||
it('returns isError when service returns null', async () => {
|
||||
const { getPlaceDetails } = await import('../../../src/services/mapsService');
|
||||
(getPlaceDetails as ReturnType<typeof vi.fn>).mockResolvedValueOnce(null);
|
||||
getPlaceDetailsSpy.mockResolvedValueOnce(null as never);
|
||||
const { user } = createUser(testDb);
|
||||
await withHarness(user.id, async (h) => {
|
||||
const result = await h.client.callTool({
|
||||
|
||||
@@ -10,7 +10,6 @@ vi.mock('../../../../src/websocket', () => ({ broadcast: vi.fn() }));
|
||||
const permissionsStub = { checkPermission: vi.fn(() => true) };
|
||||
vi.mock('../../../../src/services/tripAccess', () => ({ verifyTripAccess: vi.fn() }));
|
||||
vi.mock('../../../../src/services/placeService', () => ({ createPlace: vi.fn() }));
|
||||
vi.mock('../../../../src/services/mapsService', () => ({ searchNominatim: vi.fn() }));
|
||||
|
||||
import { BookingImportService } from '../../../../src/nest/booking-import/booking-import.service';
|
||||
|
||||
@@ -21,7 +20,10 @@ function make(opts: { kit?: boolean; ai?: boolean; extract?: any; parse?: any })
|
||||
const extractor = { isAvailable: () => opts.kit ?? false, extract: vi.fn(opts.extract ?? (async () => [])) };
|
||||
const llmParse = { isAvailable: () => opts.ai ?? false, parse: vi.fn(opts.parse ?? (async () => ({ kiItems: [], warnings: [] }))) };
|
||||
const reservations = { create: vi.fn() };
|
||||
return { svc: new BookingImportService(extractor as any, llmParse as any, new DatabaseService(dbConn), reservations as never, permissionsStub as never), extractor, llmParse, reservations };
|
||||
// budget/addons/realtime/maps ride the confirm() path only — the preview()
|
||||
// tests never reach them, so stubs beyond the positional slots aren't needed.
|
||||
const maps = { searchNominatim: vi.fn() };
|
||||
return { svc: new BookingImportService(extractor as any, llmParse as any, new DatabaseService(dbConn), reservations as never, permissionsStub as never, undefined as never, undefined as never, undefined as never, maps as never), extractor, llmParse, reservations, maps };
|
||||
}
|
||||
|
||||
beforeEach(() => vi.clearAllMocks());
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -5,11 +5,11 @@
|
||||
*/
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
|
||||
// placeEnrichment pulls in the DB, websocket and maps service at import time;
|
||||
// placeEnrichment pulls in the DB, websocket and the maps bridge at import time;
|
||||
// stub them so the pure match selector can be tested in isolation.
|
||||
vi.mock('../../../src/db/database', () => ({ db: {}, getPlaceWithTags: () => null }));
|
||||
vi.mock('../../../src/websocket', () => ({ broadcast: () => {} }));
|
||||
vi.mock('../../../src/services/mapsService', () => ({
|
||||
vi.mock('../../../src/nest/maps/maps.bridge', () => ({
|
||||
getMapsKey: () => null,
|
||||
searchPlaces: async () => ({ places: [], source: 'none' }),
|
||||
getPlacePhoto: async () => ({ photoUrl: '', attribution: null }),
|
||||
|
||||
@@ -11,7 +11,7 @@ vi.mock('../../../src/app-config', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('../../../src/app-config')>();
|
||||
return { ...actual, getAppUrl: () => 'https://trek.example.com' };
|
||||
});
|
||||
vi.mock('../../../src/services/mapsService', () => ({ buildUserAgent: () => 'TREK-Test-UA' }));
|
||||
vi.mock('../../../src/nest/maps/maps.helpers', () => ({ buildUserAgent: () => 'TREK-Test-UA' }));
|
||||
|
||||
const fetchMock = vi.fn();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user