mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-23 07:26:30 +00:00
fix(mcp): update bookings through updateWithCost like REST and the plugin RPC
update_reservation and update_transport accept a type but still called the bare ReservationsService.update. Only updateWithCost re-files a linked expense that still carries the category derived from the old type, so changing a booking from flight to train over MCP left its expense filed under flights while the same change over REST or RPC moved it. Both tools now call updateWithCost without a price entry, and send the cost events and the accommodation change after the commit, as REST does.
This commit is contained in:
@@ -212,6 +212,26 @@ describe('Tool: update_reservation', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('re-files a linked expense on a type change in the same write, as REST and the plugin RPC do', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const reservation = createReservation(testDb, trip.id, { title: 'Dinner', type: 'restaurant' });
|
||||
const auto = createBudgetItem(testDb, trip.id, { name: 'Dinner', category: 'food' });
|
||||
const picked = createBudgetItem(testDb, trip.id, { name: 'Taxi', category: 'transport' });
|
||||
testDb.prepare('UPDATE budget_items SET reservation_id = ? WHERE id IN (?, ?)').run(reservation.id, auto.id, picked.id);
|
||||
await withHarness(user.id, async (h) => {
|
||||
const result = await h.client.callTool({ name: 'update_reservation', arguments: { tripId: trip.id, reservationId: reservation.id, type: 'activity' } });
|
||||
expect((parseToolResult(result) as { reservation: { type: string } }).reservation.type).toBe('activity');
|
||||
});
|
||||
// restaurant -> activity moves the auto-derived category; the hand-picked one stays.
|
||||
expect(testDb.prepare('SELECT id, category FROM budget_items WHERE trip_id = ? ORDER BY id').all(trip.id))
|
||||
.toEqual([{ id: auto.id, category: 'activities' }, { id: picked.id, category: 'transport' }]);
|
||||
const updated = broadcastMock.mock.calls.filter(c => c[1] === 'budget:updated').map(c => (c[2] as { item: { id: number } }).item.id);
|
||||
expect(updated).toEqual([auto.id]);
|
||||
const events = broadcastMock.mock.calls.map(c => c[1]);
|
||||
expect(events.indexOf('budget:updated')).toBeLessThan(events.indexOf('reservation:updated'));
|
||||
});
|
||||
|
||||
it('returns error for reservation not found', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
|
||||
@@ -189,6 +189,38 @@ describe('Tool: update_transport', () => {
|
||||
expect(data.reservation.endpoints).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('re-files a linked expense on a type change in the same write, as REST and the plugin RPC do', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
await withHarness(user.id, async (h) => {
|
||||
const created = parseToolResult(await h.client.callTool({
|
||||
name: 'create_transport',
|
||||
arguments: { tripId: trip.id, type: 'flight', title: 'F', endpoints: flightEndpoints },
|
||||
})) as { reservation: { id: number } };
|
||||
const auto = Number(testDb.prepare("INSERT INTO budget_items (trip_id, name, category, total_price, reservation_id) VALUES (?, 'Fare', 'flights', 100, ?)")
|
||||
.run(trip.id, created.reservation.id).lastInsertRowid);
|
||||
const picked = Number(testDb.prepare("INSERT INTO budget_items (trip_id, name, category, total_price, reservation_id) VALUES (?, 'Lounge', 'food', 20, ?)")
|
||||
.run(trip.id, created.reservation.id).lastInsertRowid);
|
||||
broadcastMock.mockClear();
|
||||
|
||||
const result = await h.client.callTool({
|
||||
name: 'update_transport',
|
||||
arguments: { tripId: trip.id, reservationId: created.reservation.id, type: 'train' },
|
||||
});
|
||||
expect((parseToolResult(result) as { reservation: { type: string } }).reservation.type).toBe('train');
|
||||
|
||||
// flight -> train moves the auto-derived category; the hand-picked one stays.
|
||||
const category = (id: number) => (testDb.prepare('SELECT category FROM budget_items WHERE id = ?').get(id) as { category: string }).category;
|
||||
expect(category(auto)).toBe('transport');
|
||||
expect(category(picked)).toBe('food');
|
||||
const updated = broadcastMock.mock.calls.filter(c => c[1] === 'budget:updated').map(c => (c[2] as { item: { id: number } }).item.id);
|
||||
expect(updated).toEqual([auto]);
|
||||
// The expense goes out after the commit, before the booking.
|
||||
const events = broadcastMock.mock.calls.map(c => c[1]);
|
||||
expect(events.indexOf('budget:updated')).toBeLessThan(events.indexOf('reservation:updated'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user