mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 13:36:48 +00:00
724c88c201
A mechanical prettier --write over server/src, server/tests, server/scripts and shared/src with the workspaces' .prettierrc; nothing but layout and import order changes, and every file keeps the same set of imports. shared is formatted throughout and its format baseline is empty. The server baseline keeps the 35 files that Prettier would grow past their lint:size limit (they need a split first, which is not a formatting change), and the generated entity files stay as their generator writes them.
114 lines
4.5 KiB
TypeScript
114 lines
4.5 KiB
TypeScript
/**
|
|
* MAPS-AREA-001..006 — the trip's area, handed to the client for offline use.
|
|
*
|
|
* The one maps call that is not driven by something a user just typed: it runs
|
|
* inside an offline sync, which is why every failure path here ends in null
|
|
* rather than an exception.
|
|
*/
|
|
import type { AppSettingsRepository } from '../../../src/db/repositories/AppSettings.repository';
|
|
import type { UsersRepository } from '../../../src/db/repositories/Users.repository';
|
|
import type { PlacePhotoCacheService } from '../../../src/nest/place-photos/place-photo-cache.service';
|
|
import { noGoogleQuota } from '../../helpers/google-quota';
|
|
import { buildMapsService } from '../../helpers/maps-service';
|
|
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
|
|
const { mockArea } = vi.hoisted(() => ({
|
|
mockArea: vi.fn(async (): Promise<unknown> => ({ results: [], truncated: false, count: 0 })),
|
|
}));
|
|
vi.mock('../../../src/nest/maps/trek-places.client', async (importOriginal) => ({
|
|
...(await importOriginal<typeof import('../../../src/nest/maps/trek-places.client')>()),
|
|
trekPlacesArea: mockArea,
|
|
}));
|
|
|
|
// keyedProvider/resolveMapsKey (maps.service.ts) go through instance-api-keys.ts
|
|
// on every call now — none of these cases configure a key, so the stubs just
|
|
// answer "unset" the way the fake database.get(() => undefined) already did.
|
|
const noAppSettings = { getValue: async () => null } as unknown as AppSettingsRepository;
|
|
const noUsers = { getApiKeyColumn: async () => null } as unknown as UsersRepository;
|
|
|
|
const BOX = { minLat: 54, minLng: 12, maxLat: 54.2, maxLng: 12.3 };
|
|
|
|
const PLACE = {
|
|
gers: 'abc-123',
|
|
name: "L'Osteria",
|
|
lat: 54.0879,
|
|
lng: 12.1408,
|
|
category: 'restaurant',
|
|
categoryPath: 'eat_and_drink>restaurant',
|
|
confidence: 1,
|
|
address: { freeform: 'Steinstrasse 9', locality: 'Rostock', postcode: '18055', region: null, country: 'DE' },
|
|
contact: { website: 'https://losteria.net/', phone: null, email: null, socials: null },
|
|
brand: null,
|
|
source: 'overture',
|
|
hours: null,
|
|
};
|
|
|
|
const ORIGINAL_TREK_PLACES = process.env.TREK_PLACES_ENABLED;
|
|
|
|
afterEach(() => {
|
|
if (ORIGINAL_TREK_PLACES === undefined) delete process.env.TREK_PLACES_ENABLED;
|
|
else process.env.TREK_PLACES_ENABLED = ORIGINAL_TREK_PLACES;
|
|
});
|
|
|
|
// The index is an operator switch, not an admin row, so switching it off means
|
|
// moving the env rather than stubbing a settings lookup. The service reads the
|
|
// flag per call, which is why the restore above belongs in afterEach.
|
|
function make(enabled = true) {
|
|
if (enabled) delete process.env.TREK_PLACES_ENABLED;
|
|
else process.env.TREK_PLACES_ENABLED = 'false';
|
|
return buildMapsService(
|
|
{} as PlacePhotoCacheService,
|
|
noAppSettings,
|
|
noUsers,
|
|
{} as never,
|
|
{} as never,
|
|
noGoogleQuota,
|
|
);
|
|
}
|
|
|
|
beforeEach(() => {
|
|
mockArea.mockReset();
|
|
mockArea.mockResolvedValue({ results: [PLACE], truncated: false, count: 1 });
|
|
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
});
|
|
|
|
describe('MapsService.placesInArea', () => {
|
|
it('MAPS-AREA-001: hands back records in the shape the client already reads', async () => {
|
|
const out = await make().placesInArea(BOX, 500);
|
|
expect(mockArea).toHaveBeenCalledWith(BOX, 500);
|
|
expect(out?.results[0]).toMatchObject({
|
|
osm_id: 'gers:abc-123',
|
|
name: "L'Osteria",
|
|
source: 'trek-places',
|
|
});
|
|
});
|
|
|
|
it('MAPS-AREA-002: passes truncation through instead of smoothing it over', async () => {
|
|
// A client that believed it had the whole area would search offline and
|
|
// quietly miss places.
|
|
mockArea.mockResolvedValue({ results: [PLACE], truncated: true, count: 1 });
|
|
expect((await make().placesInArea(BOX))?.truncated).toBe(true);
|
|
});
|
|
|
|
it('MAPS-AREA-003: returns null when the operator switched the index off', async () => {
|
|
expect(await make(false).placesInArea(BOX)).toBeNull();
|
|
expect(mockArea).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('MAPS-AREA-004: a service failure is a null, not a thrown sync', async () => {
|
|
mockArea.mockRejectedValue(new Error('places api down'));
|
|
await expect(make().placesInArea(BOX)).resolves.toBeNull();
|
|
});
|
|
|
|
it('MAPS-AREA-005: an empty area is an answer, not a failure', async () => {
|
|
mockArea.mockResolvedValue({ results: [], truncated: false, count: 0 });
|
|
expect(await make().placesInArea(BOX)).toEqual({ results: [], truncated: false });
|
|
});
|
|
|
|
it('MAPS-AREA-006: leaves the limit to the caller when none is given', async () => {
|
|
await make().placesInArea(BOX);
|
|
expect(mockArea).toHaveBeenCalledWith(BOX, undefined);
|
|
});
|
|
});
|