test(tours): read the MCP fields and stage edits the way the other tests do

The confirmed field is checked through z.object(...).pick() as the dawarich
and doc-sync tests read their tool shapes, and the stage edits pass typed
waypoints through the request contract as the walk tests do. The previous
forms sent the test typecheck out of memory.
This commit is contained in:
Maurice
2026-10-20 18:19:00 +02:00
parent 5ae8ac8e70
commit fb208a88e2
3 changed files with 11 additions and 15 deletions
@@ -413,12 +413,10 @@ describe('TourChangeMcp.saveTourChange (MCP-T03)', () => {
];
for (const term of terms) expect(options.description).toContain(term);
// The fields are zod schemas from @trek/shared, read here by what they answer.
const shape = options.inputSchema as ZodRawShape;
const fields = ['tripId', 'placeId', 'changes', 'expected_revision', 'proposal_id', 'confirmed'];
expect(Object.keys(shape)).toEqual(fields);
const confirmed = shape.confirmed;
if (!confirmed) throw new Error('the tool takes no confirmed field');
const yes = [true, false, undefined].map((value) => z.safeParse(confirmed, value).success);
expect(Object.keys(options.inputSchema as ZodRawShape)).toEqual(fields);
const confirmed = z.object(options.inputSchema as ZodRawShape).pick({ confirmed: true });
const yes = [true, false, undefined].map((value) => confirmed.safeParse({ confirmed: value }).success);
expect(yes).toEqual([true, false, false]);
const isAddonEnabled = vi.fn(async (id: string) => id === 'tours');
expect(await options.when?.(ctx(1), { addons: { isAddonEnabled } })).toBe(true);
@@ -508,10 +508,8 @@ describe('TourChangeUndoMcp (MCP-T04)', () => {
const entry = getEntry(TourChangeUndoMcp as unknown as ClassRef, 'undoTourChange');
if (!entry || entry.kind !== 'tool') throw new Error('undoTourChange is no MCP tool');
// The fields are zod schemas from @trek/shared, read here by what they answer.
const shape = entry.options.inputSchema as ZodRawShape;
const confirmed = shape.confirmed;
if (!confirmed) throw new Error('the tool takes no confirmed field');
const yes = [true, false, undefined].map((value) => z.safeParse(confirmed, value).success);
const confirmed = z.object(entry.options.inputSchema as ZodRawShape).pick({ confirmed: true });
const yes = [true, false, undefined].map((value) => confirmed.safeParse({ confirmed: value }).success);
expect(yes).toEqual([true, false, false]);
for (const term of ['prepare_tour_undo', 'expected_revision', 'conflict', 'get_tour_plan']) {
expect(entry.options.description).toContain(term);
+6 -6
View File
@@ -33,7 +33,7 @@ import {
} from '../../helpers/tours-repos';
import type { EntityClass } from '@mikro-orm/core';
import { Logger, NotFoundException } from '@nestjs/common';
import { tourCreateRequestSchema, type TourCreateRequest } from '@trek/shared';
import { tourCreateRequestSchema, type TourCreateInput, type TourCreateRequest, type TourWaypoint } from '@trek/shared';
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
@@ -786,8 +786,7 @@ describe('ToursService stages on days (MDT-02)', () => {
}
/** An edit of the hut-to-hut tour as the API takes it: the points a read gave back go through the request contract. */
const editOf = (changes: Record<string, unknown>): TourCreateRequest =>
tourCreateRequestSchema.parse({ ...hutToHut, ...changes });
const editOf = (overrides: Partial<TourCreateInput>) => tourCreateRequestSchema.parse({ ...hutToHut, ...overrides });
const heldStages = async (placeId: number) =>
(await findRows(await sharedTestOrm(testDb), DayAssignments, { place: placeId }, { id: 'asc' })).map(
@@ -811,7 +810,7 @@ describe('ToursService stages on days (MDT-02)', () => {
it('MDT-SVC-005: a saved change takes only the stages it took away off their days, and tells every client', async () => {
const { created, placeId, keys, days, held } = await onThreeDays();
// The third point no longer ends a stage: the stage that started there is gone, the others keep their keys.
const joined = created.waypoints.map(({ stage_end: _end, ...point }, index) =>
const joined: TourWaypoint[] = created.waypoints.map(({ stage_end: _end, ...point }, index) =>
index === 1 ? { ...point, stage_end: true } : point,
);
await service.updateTour(tripId, String(placeId), editOf({ waypoints: joined }), 'socket');
@@ -829,7 +828,8 @@ describe('ToursService stages on days (MDT-02)', () => {
it('MDT-SVC-006: a change that keeps every stage leaves every day as it was and says nothing', async () => {
const { created, placeId, keys } = await onThreeDays();
await service.updateTour(tripId, String(placeId), editOf({ name: 'Renamed', waypoints: created.waypoints }));
const kept: TourWaypoint[] = created.waypoints;
await service.updateTour(tripId, String(placeId), editOf({ name: 'Renamed', waypoints: kept }));
expect(await heldStages(placeId)).toEqual(keys.slice(0, 3));
expect(broadcast.mock.calls.filter(([, event]) => event === 'assignment:deleted')).toEqual([]);
});
@@ -854,7 +854,7 @@ describe('ToursService stages on days (MDT-02)', () => {
if (event === 'assignment:deleted') throw new Error('socket closed');
});
const warn = vi.spyOn(Logger.prototype, 'warn').mockImplementation(() => undefined);
const dayTour = created.waypoints.map(({ stage_end: _end, ...point }) => point);
const dayTour: TourWaypoint[] = created.waypoints.map(({ stage_end: _end, ...point }) => point);
await expect(
service.updateTour(tripId, String(placeId), editOf({ duration_seconds: 3600, waypoints: dayTour })),
).resolves.toMatchObject({ tour: { stage_count: null } });