mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 15:06:32 +00:00
fix(journey,tours): read a day's stays as a read, give the services the request context in the e2e runs and hold the tests to the foot-only walks and the translated notices
This commit is contained in:
@@ -501,9 +501,10 @@ function JourneyMapGL(
|
||||
// fit bounds to all points
|
||||
const known = marksRef.current ?? []
|
||||
marksFramedRef.current = known.length > 0
|
||||
known.forEach(mark => bounds.extend([mark.lng, mark.lat]))
|
||||
if (hasPoints || known.length > 0) {
|
||||
try {
|
||||
map.fitBounds(markBounds(gl, known, entryPointsRef.current), fitOptions(paddingBottom, enableMapbox3d && fullScreen))
|
||||
map.fitBounds(bounds, fitOptions(paddingBottom, enableMapbox3d && fullScreen))
|
||||
} catch { /* empty bounds */ }
|
||||
}
|
||||
})
|
||||
|
||||
@@ -152,7 +152,7 @@ describe('useJourneyDayImport', () => {
|
||||
await act(async () => result.current.submit());
|
||||
|
||||
expect(toast.success).not.toHaveBeenCalled();
|
||||
expect(toast.info).toHaveBeenCalledWith('2 stays were in the journal already. Nothing was written twice.');
|
||||
expect(toast.info).toHaveBeenCalledWith('2 stays were taken over already. Nothing was written twice.');
|
||||
expect(repo.save).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ describe('useJourneyRecordingPrep', () => {
|
||||
await act(async () => {
|
||||
await result.current.setEnabled(true);
|
||||
});
|
||||
expect(toast.error).toHaveBeenCalledWith('Failed to save');
|
||||
expect(toast.error).toHaveBeenCalledWith('journey.settings.saveFailed');
|
||||
expect(opts.onSwitched).not.toHaveBeenCalled();
|
||||
expect(dawarich.syncNow).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -124,7 +124,7 @@ describe('useJourneyRecordingPrep', () => {
|
||||
await act(async () => {
|
||||
await result.current.fetchNow();
|
||||
});
|
||||
expect(toast.error).toHaveBeenCalledWith('Dawarich could not be asked right now.');
|
||||
expect(toast.error).toHaveBeenCalledWith('journey.prep.fetchFailed');
|
||||
await waitFor(() => expect(repo.prep).toHaveBeenCalledTimes(3));
|
||||
});
|
||||
|
||||
@@ -136,9 +136,7 @@ describe('useJourneyRecordingPrep', () => {
|
||||
await act(async () => {
|
||||
await result.current.fetchNow();
|
||||
});
|
||||
expect(toast.info).toHaveBeenCalledWith(
|
||||
'A fetch is already running in the background. Its stays show up once it is through.'
|
||||
);
|
||||
expect(toast.info).toHaveBeenCalledWith('journey.prep.alreadyRunning');
|
||||
});
|
||||
|
||||
it('FE-JRN-PREP-HOOK-007: a journey whose trips all ended out of reach marks no day and offers no fetch', async () => {
|
||||
@@ -149,7 +147,7 @@ describe('useJourneyRecordingPrep', () => {
|
||||
expect(result.current.dayPrep('2026-05-14')).toBeNull();
|
||||
expect(result.current.canFetch).toBe(false);
|
||||
expect(result.current.statusLine).toEqual({
|
||||
text: 'Trips that ended more than 400 days ago are not prepared in the background. Open one of their days to fetch it by hand.',
|
||||
text: 'journey.prep.outOfReach',
|
||||
tone: 'warning',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -63,6 +63,7 @@ const searchPlanner = (over: Partial<TourPlannerController> = {}) =>
|
||||
tourType: 'hike',
|
||||
route: ROUTE,
|
||||
mode: { type: 'edit-saved', placeId: 9 },
|
||||
waypoints: [],
|
||||
...over,
|
||||
}) as TourPlannerController;
|
||||
|
||||
@@ -720,7 +721,9 @@ describe('e-bike charging along a bike tour (BIK-07)', () => {
|
||||
points: TourPlannerWaypoint[]
|
||||
) => TourPlannerWaypoint[];
|
||||
const added = normalizePlannerWaypoints(updater(POINTS)).find((point) => point.lat === CHARGER.lat);
|
||||
expect(added).toMatchObject({ lng: CHARGER.lng, name: 'E-bike charging' });
|
||||
// Unnamed, it keeps no name of its own, as every unnamed place does; its row shows the kind.
|
||||
expect(added).toMatchObject({ lng: CHARGER.lng, poiCategory: 'ebike_charging' });
|
||||
expect(added).not.toHaveProperty('name');
|
||||
});
|
||||
|
||||
it('FE-TOUR-POI-029: a point that only lets bikes charge says so, and an unstated plug is dimmed, never named', async () => {
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import { ADDON_IDS } from '../../addons';
|
||||
import { McpController, TOOL_ANNOTATIONS_WRITE, Tool, ok, type McpContext } from '../../nest-mcp';
|
||||
import {
|
||||
McpController,
|
||||
TOOL_ANNOTATIONS_READONLY,
|
||||
TOOL_ANNOTATIONS_WRITE,
|
||||
Tool,
|
||||
ok,
|
||||
type McpContext,
|
||||
} from '../../nest-mcp';
|
||||
import { addonGate, type HasAddons } from '../addons/addon-gate';
|
||||
import { AddonsService } from '../addons/addons.service';
|
||||
import { JourneyDayImportService } from './journey-day-import.service';
|
||||
@@ -33,9 +40,9 @@ export class JourneyDayImportMcp {
|
||||
description:
|
||||
"The stays Dawarich found on one local day, from the caller's own connection, as a journey would take them over: each with its suggestionId, name, place, arrival and departure, whether Dawarich only guessed it, and its status for this journey (open, skipped before, here with the entry holding it, or taken over elsewhere), plus the entries the journey has on the day. When Dawarich does not answer, the stays of the last sync are shown with fresh: false and the reason. Reading the day again refreshes the caller's own stored stays of it, nothing in the journey. Needs edit access to the journey.",
|
||||
inputSchema: { journeyId: idSchema, date: journeyRecordingDateSchema },
|
||||
// Reaches the caller's own Dawarich instance and refreshes their stored stays of the day; the journey
|
||||
// scope's read access covers that, since only the caller's own stay list changes.
|
||||
annotations: { ...TOOL_ANNOTATIONS_WRITE, openWorldHint: true },
|
||||
// Reaches the caller's own Dawarich instance and refreshes their stored stays of the day, like the
|
||||
// recording preview; nothing of the journey changes, so it reads as a read.
|
||||
annotations: { ...TOOL_ANNOTATIONS_READONLY, openWorldHint: true },
|
||||
when: bothAddonsOn,
|
||||
access: { group: 'journey', mode: 'read' },
|
||||
})
|
||||
|
||||
@@ -20,6 +20,7 @@ import { JourneyEntryStays } from '../../src/db/entities/JourneyEntryStays.entit
|
||||
import { encrypt_api_key } from '../../src/nest/common/crypto/apiKeyCrypto';
|
||||
import { TrekExceptionFilter } from '../../src/nest/common/trek-exception.filter';
|
||||
import { ZodValidationPipe } from '../../src/nest/common/zod-validation.pipe';
|
||||
import { withRequestContext } from '../../src/nest/database/request-context';
|
||||
import { DawarichClient } from '../../src/nest/integrations/dawarich.client';
|
||||
import { JourneyRecordingsModule } from '../../src/nest/journey-recordings/journey-recordings.module';
|
||||
import { JourneyShareService } from '../../src/nest/journey/journey-share.service';
|
||||
@@ -34,6 +35,7 @@ import { FakeRealtimeService } from '../helpers/fake-realtime';
|
||||
import { createTestMikroOrmModule, createTestOrm, type TestOrm } from '../helpers/test-orm';
|
||||
import { TestUnitOfWorkModule } from '../helpers/test-uow';
|
||||
import { sessionCookie } from './harness';
|
||||
import { MikroORM } from '@mikro-orm/core';
|
||||
import { Test } from '@nestjs/testing';
|
||||
|
||||
import cookieParser from 'cookie-parser';
|
||||
@@ -263,13 +265,15 @@ describe('Journey day take-over e2e (real guards + temp SQLite)', () => {
|
||||
await makeJourneyEntry(orm, journeyId, OWNER, { title: 'Arrival', entry_date: DAY });
|
||||
expect((await takeOver({ groups: [{ kind: 'single', stays: [{ suggestionId: first }] }] })).status).toBe(200);
|
||||
const share = app.get(JourneyShareService, { strict: false });
|
||||
const link = await share.createOrUpdateJourneyShareLink(journeyId, OWNER, {
|
||||
share_timeline: true,
|
||||
share_map: true,
|
||||
// Called beside a request, so the services get the request context a route would give them.
|
||||
const published = await withRequestContext(app.get(MikroORM), async () => {
|
||||
const link = await share.createOrUpdateJourneyShareLink(journeyId, OWNER, {
|
||||
share_timeline: true,
|
||||
share_map: true,
|
||||
});
|
||||
if (!link) throw new Error('no share link');
|
||||
return share.getPublicJourney(link.token);
|
||||
});
|
||||
if (!link) throw new Error('no share link');
|
||||
|
||||
const published = await share.getPublicJourney(link.token);
|
||||
|
||||
expect(published?.entries.map((entry) => entry.title)).toEqual(['Arrival']);
|
||||
expect(published?.stats.entries).toBe(1);
|
||||
|
||||
@@ -19,6 +19,7 @@ import { Journeys } from '../../src/db/entities/Journeys.entity';
|
||||
import { encrypt_api_key } from '../../src/nest/common/crypto/apiKeyCrypto';
|
||||
import { TrekExceptionFilter } from '../../src/nest/common/trek-exception.filter';
|
||||
import { ZodValidationPipe } from '../../src/nest/common/zod-validation.pipe';
|
||||
import { withRequestContext } from '../../src/nest/database/request-context';
|
||||
import { DawarichSyncService } from '../../src/nest/integrations/dawarich-sync.service';
|
||||
import { DawarichClient, DawarichError } from '../../src/nest/integrations/dawarich.client';
|
||||
import { JourneyRecordingsModule } from '../../src/nest/journey-recordings/journey-recordings.module';
|
||||
@@ -34,6 +35,7 @@ import { FakeRealtimeService } from '../helpers/fake-realtime';
|
||||
import { createTestMikroOrmModule, createTestOrm, type TestOrm } from '../helpers/test-orm';
|
||||
import { TestUnitOfWorkModule } from '../helpers/test-uow';
|
||||
import { sessionCookie } from './harness';
|
||||
import { MikroORM } from '@mikro-orm/core';
|
||||
import { Test } from '@nestjs/testing';
|
||||
|
||||
import cookieParser from 'cookie-parser';
|
||||
@@ -313,7 +315,8 @@ describe('Journey recordings e2e (real guards + temp SQLite)', () => {
|
||||
await linkTripToJourney(orm, journeyId, trip.id);
|
||||
await updateRows(orm, Journeys, { id: journeyId }, { auto_prepare_recordings: 1 });
|
||||
client.listVisits.mockResolvedValue({ visits: [], truncated: false, version: null });
|
||||
await app.get(DawarichSyncService).syncUser(OWNER);
|
||||
// The cron runs the sync in a request context of its own; called here, the test gives it one.
|
||||
await withRequestContext(app.get(MikroORM), () => app.get(DawarichSyncService).syncUser(OWNER));
|
||||
|
||||
const on = await request(server).get(`${base()}/prep`).set(as(OWNER));
|
||||
expect(on.body).toMatchObject({ enabled: true, connection: 'ready', lastError: null });
|
||||
|
||||
@@ -440,7 +440,7 @@ describe('CityWalkMcp (CW-11)', () => {
|
||||
{ lat: 48.009, lng: 11 },
|
||||
{ lat: 48.018, lng: 11 },
|
||||
];
|
||||
expect(route).toHaveBeenCalledWith(f.user.id, f.trip.id, 0, points, 'walking', []);
|
||||
expect(route).toHaveBeenCalledWith(f.user.id, f.trip.id, 0, points, 'walking', [], { footOnly: true });
|
||||
expect(proposal.estimate).toEqual({ kind: 'routed', profile: 'walking' });
|
||||
expect(
|
||||
proposal.stations.map((s: { name: string; arrival: string; walk_from_previous: unknown }) => [
|
||||
@@ -461,7 +461,7 @@ describe('CityWalkMcp (CW-11)', () => {
|
||||
ctx(f.user.id),
|
||||
),
|
||||
);
|
||||
expect(route).toHaveBeenLastCalledWith(f.user.id, f.trip.id, 0, points, 'walking', []);
|
||||
expect(route).toHaveBeenLastCalledWith(f.user.id, f.trip.id, 0, points, 'walking', [], { footOnly: true });
|
||||
expect(created.foot_way).toEqual({ planned: true });
|
||||
expect(created.tour).toMatchObject({ route_pending: false, duration: 27, elevation_gain: null, station_count: 3 });
|
||||
expect(created.tour.distance).toBeCloseTo(2.0, 1);
|
||||
|
||||
@@ -884,12 +884,12 @@ describe('DawarichTracksService.fetchDay', () => {
|
||||
expect(day.segments).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('DAWARICH-TRACKS-063: the source zone is the one of the first track on the date, not of the evening before in another zone', async () => {
|
||||
it('DAWARICH-TRACKS-063: the source zone is the one of the first track on the date, not of the afternoon before in another zone', async () => {
|
||||
const h = harness({ capabilities: CAPABILITIES });
|
||||
h.listTracks.mockResolvedValue({
|
||||
features: [
|
||||
// The evening before in Berlin, then the morning after landing in Tokyo.
|
||||
feature({ start_at: '2026-04-30T20:00:00+02:00', end_at: '2026-04-30T21:00:00+02:00' }),
|
||||
// The afternoon before in Berlin, then the morning after landing in Tokyo.
|
||||
feature({ start_at: '2026-04-30T15:00:00+02:00', end_at: '2026-04-30T16:00:00+02:00' }),
|
||||
feature({ start_at: '2026-05-01T09:00:00+09:00', end_at: '2026-05-01T10:00:00+09:00' }),
|
||||
],
|
||||
truncated: false,
|
||||
|
||||
@@ -204,6 +204,7 @@ describe('TourChangeMcp.saveTourChange (MCP-T03)', () => {
|
||||
after.map(([lat, lng]) => ({ lat, lng })),
|
||||
'walking',
|
||||
[],
|
||||
{ footOnly: true },
|
||||
);
|
||||
|
||||
// Saved: the line, the points with the kept keys and the link, the measures, the visits as they were.
|
||||
|
||||
@@ -163,7 +163,7 @@ describe('TourChangeMcp (MCP-T02)', () => {
|
||||
[48.108, 11.5],
|
||||
[48.112, 11.5],
|
||||
];
|
||||
expect(route).toHaveBeenCalledWith(f.user.id, f.trip.id, 0, asked(saved), 'walking', []);
|
||||
expect(route).toHaveBeenCalledWith(f.user.id, f.trip.id, 0, asked(saved), 'walking', [], { footOnly: true });
|
||||
expect(route).toHaveBeenCalledWith(
|
||||
f.user.id,
|
||||
f.trip.id,
|
||||
@@ -171,6 +171,7 @@ describe('TourChangeMcp (MCP-T02)', () => {
|
||||
asked([...saved.slice(0, 3), [48.109, 11.501], [48.113, 11.502]]),
|
||||
'walking',
|
||||
[],
|
||||
{ footOnly: true },
|
||||
);
|
||||
expect(proposal.effects.walking).toEqual({
|
||||
planned_by: 'router_on_foot',
|
||||
|
||||
Reference in New Issue
Block a user