mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 05:26:47 +00:00
fix(reservations): a booking read on its own carries its day slots, so an edit no longer drops them
The create and update answers, the reservation:updated broadcast and the MCP reservation tools all hand back the single booking read, and that read never loaded reservation_day_positions. The day list puts a booking moved on a day by that slot before its time, and the client replaces the booking with the answer, so a booking moved on a day jumped back to its time after every edit until the next reload. The read now attaches day_positions in the shape the trip list already sends. Refs #2631
This commit is contained in:
@@ -439,6 +439,23 @@ describe('Reservations + accommodations e2e (real auth guard + temp SQLite, real
|
||||
expect(await positionRow(ownReservationId, ownDayId)).toEqual({ position: 3 });
|
||||
});
|
||||
|
||||
// The list puts a booking where it was moved on a day before its time, and the
|
||||
// client replaces the booking with what an edit answers (#2631).
|
||||
it('keeps the day slot in what an edit of the booking answers', async () => {
|
||||
const ownReservationId = await seedReservation(tripId, 'Mine', 'other');
|
||||
await request(server)
|
||||
.put(`/api/trips/${tripId}/reservations/positions`)
|
||||
.set('Cookie', sessionCookie(1))
|
||||
.send({ positions: [{ id: ownReservationId, day_plan_position: 2.5 }], day_id: ownDayId })
|
||||
.expect(200);
|
||||
const res = await request(server)
|
||||
.put(`/api/trips/${tripId}/reservations/${ownReservationId}`)
|
||||
.set('Cookie', sessionCookie(1))
|
||||
.send({ notes: 'aisle' });
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.reservation.day_positions).toEqual({ [ownDayId]: 2.5 });
|
||||
});
|
||||
|
||||
// An id that exists nowhere used to raise a foreign-key error, which the
|
||||
// exception filter turned into a 500 — telling the caller apart from the
|
||||
// 200 a real id returns, for any id on the instance.
|
||||
|
||||
@@ -479,6 +479,7 @@ export async function createPluginRpcHostParts(
|
||||
await createTestReservationsRepo(db),
|
||||
await createTestReservationEndpointsRepo(db),
|
||||
await createTestReservationTravelersRepo(db),
|
||||
await createTestReservationDayPositionsRepo(db),
|
||||
),
|
||||
accommodations,
|
||||
await createTestUnitOfWork(db),
|
||||
|
||||
@@ -75,4 +75,14 @@ describe('ReservationDayPositionsRepository — fully seeded world', () => {
|
||||
const trip = createTrip(testDb, user.id);
|
||||
expect(await repo.listForTrip(trip.id)).toEqual([]);
|
||||
});
|
||||
|
||||
it('listForReservation — the slots of one booking per day, none of another booking', async () => {
|
||||
const { multiDay, day1, day2 } = await seed();
|
||||
const rows = await repo.listForReservation(multiDay.id);
|
||||
expect(rows.map((r) => [r.day_id, r.position]).sort()).toEqual([
|
||||
[day1.id, 0],
|
||||
[day2.id, 1],
|
||||
]);
|
||||
expect(await repo.listForReservation(999999)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import { db as testDb } from '../../../src/db/database';
|
||||
import { BudgetItems } from '../../../src/db/entities/BudgetItems.entity';
|
||||
import { DayAccommodations } from '../../../src/db/entities/DayAccommodations.entity';
|
||||
import { ReservationDayPositions } from '../../../src/db/entities/ReservationDayPositions.entity';
|
||||
import { ReservationTravelers } from '../../../src/db/entities/ReservationTravelers.entity';
|
||||
import { Reservations } from '../../../src/db/entities/Reservations.entity';
|
||||
import { Trips } from '../../../src/db/entities/Trips.entity';
|
||||
@@ -293,6 +294,26 @@ describe('Tool: update_reservation', () => {
|
||||
expect(events.indexOf('budget:updated')).toBeLessThan(events.indexOf('reservation:updated'));
|
||||
});
|
||||
|
||||
// #2631: the day planner puts a booking moved on a day by its day slot, and the
|
||||
// edit's answer and broadcast replace the booking in every open list.
|
||||
it('keeps the day slots in the answer and the broadcast', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const day = createDay(testDb, trip.id);
|
||||
const reservation = createReservation(testDb, trip.id, { title: 'Bus', type: 'bus' });
|
||||
await insertRow(orm, ReservationDayPositions, { reservation: reservation.id, day: day.id, position: 2.5 });
|
||||
await withHarness(user.id, async (h) => {
|
||||
const result = await h.client.callTool({
|
||||
name: 'update_reservation',
|
||||
arguments: { tripId: trip.id, reservationId: reservation.id, notes: 'aisle' },
|
||||
});
|
||||
type Answer = { reservation: { day_positions: Record<string, number> | null } };
|
||||
expect((parseToolResult(result) as Answer).reservation.day_positions).toEqual({ [day.id]: 2.5 });
|
||||
const sent = broadcastMock.mock.calls.find((c) => c[1] === 'reservation:updated')?.[2] as Answer;
|
||||
expect(sent.reservation.day_positions).toEqual({ [day.id]: 2.5 });
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error for reservation not found', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
|
||||
@@ -58,7 +58,7 @@ let orm: TestOrm;
|
||||
beforeAll(async () => {
|
||||
budgetSvc = new BudgetService(new PermissionsService(await createTestAppSettingsRepo(testDb), await createTestUnitOfWork(testDb)), new ExchangeRatesService(), realtime, await createTestUnitOfWork(testDb), ...(await budgetRepoArgs(testDb)));
|
||||
svc = new CalendarService(
|
||||
new ReservationsService(new PermissionsService(await createTestAppSettingsRepo(testDb), await createTestUnitOfWork(testDb)), budgetSvc, realtime, notificationsStub(), new ReservationsReadService(await createTestReservationsRepo(testDb), await createTestReservationEndpointsRepo(testDb), await createTestReservationTravelersRepo(testDb)), await accommodationsOver(testDb, realtime), await createTestUnitOfWork(testDb), await createTestReservationsRepo(testDb), await createTestReservationEndpointsRepo(testDb), await createTestReservationTravelersRepo(testDb), await createTestReservationDayPositionsRepo(testDb), await createTestDayAccommodationsRepo(testDb), await createTestDaysRepo(testDb), await createTestPlacesRepo(testDb), await createTestDayAssignmentsRepo(testDb), await createTestTripMembersRepo(testDb), await createTestUsersRepo(testDb), await createTestTripsRepo(testDb), await createTestBudgetItemsRepo(testDb)),
|
||||
new ReservationsService(new PermissionsService(await createTestAppSettingsRepo(testDb), await createTestUnitOfWork(testDb)), budgetSvc, realtime, notificationsStub(), new ReservationsReadService(await createTestReservationsRepo(testDb), await createTestReservationEndpointsRepo(testDb), await createTestReservationTravelersRepo(testDb), await createTestReservationDayPositionsRepo(testDb)), await accommodationsOver(testDb, realtime), await createTestUnitOfWork(testDb), await createTestReservationsRepo(testDb), await createTestReservationEndpointsRepo(testDb), await createTestReservationTravelersRepo(testDb), await createTestReservationDayPositionsRepo(testDb), await createTestDayAccommodationsRepo(testDb), await createTestDaysRepo(testDb), await createTestPlacesRepo(testDb), await createTestDayAssignmentsRepo(testDb), await createTestTripMembersRepo(testDb), await createTestUsersRepo(testDb), await createTestTripsRepo(testDb), await createTestBudgetItemsRepo(testDb)),
|
||||
await createTestTripsRepo(testDb), await createTestDaysRepo(testDb), await createTestDayNotesRepo(testDb), await createTestReservationsRepo(testDb),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -73,6 +73,7 @@ async function makeImportService(): Promise<AirtrailImportService> {
|
||||
await createTestReservationsRepo(db),
|
||||
await createTestReservationEndpointsRepo(db),
|
||||
await createTestReservationTravelersRepo(db),
|
||||
await createTestReservationDayPositionsRepo(db),
|
||||
),
|
||||
await accommodationsOver(db),
|
||||
await createTestUnitOfWork(db),
|
||||
|
||||
@@ -80,7 +80,7 @@ const broadcast = realtime.broadcastMock;
|
||||
let svc: ReservationsService;
|
||||
let orm: TestOrm;
|
||||
beforeAll(async () => {
|
||||
svc = new ReservationsService(permissionsStub, budget as unknown as BudgetService, realtime, notificationsStub(notif.send), new ReservationsReadService(await createTestReservationsRepo(testDb), await createTestReservationEndpointsRepo(testDb), await createTestReservationTravelersRepo(testDb)), await makeAccommodationsService(testDb, realtime), await createTestUnitOfWork(testDb), await createTestReservationsRepo(testDb), await createTestReservationEndpointsRepo(testDb), await createTestReservationTravelersRepo(testDb), await createTestReservationDayPositionsRepo(testDb), await createTestDayAccommodationsRepo(testDb), await createTestDaysRepo(testDb), await createTestPlacesRepo(testDb), await createTestDayAssignmentsRepo(testDb), await createTestTripMembersRepo(testDb), await createTestUsersRepo(testDb), await createTestTripsRepo(testDb), await createTestBudgetItemsRepo(testDb));
|
||||
svc = new ReservationsService(permissionsStub, budget as unknown as BudgetService, realtime, notificationsStub(notif.send), new ReservationsReadService(await createTestReservationsRepo(testDb), await createTestReservationEndpointsRepo(testDb), await createTestReservationTravelersRepo(testDb), await createTestReservationDayPositionsRepo(testDb)), await makeAccommodationsService(testDb, realtime), await createTestUnitOfWork(testDb), await createTestReservationsRepo(testDb), await createTestReservationEndpointsRepo(testDb), await createTestReservationTravelersRepo(testDb), await createTestReservationDayPositionsRepo(testDb), await createTestDayAccommodationsRepo(testDb), await createTestDaysRepo(testDb), await createTestPlacesRepo(testDb), await createTestDayAssignmentsRepo(testDb), await createTestTripMembersRepo(testDb), await createTestUsersRepo(testDb), await createTestTripsRepo(testDb), await createTestBudgetItemsRepo(testDb));
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -387,6 +387,23 @@ describe('ReservationsService (DI-native, real SQL)', () => {
|
||||
const single = (await svc.getReservationWithJoins(res.id))!;
|
||||
expect(single.accommodation_id).toBe(acc.id);
|
||||
expect(single.travelers).toHaveLength(1);
|
||||
expect(single.day_positions).toEqual({ [days[0].id]: 2 });
|
||||
});
|
||||
|
||||
// The create and update responses and their broadcasts come from the single read.
|
||||
// Without the day slots in it, a booking moved on a day went back to its time in
|
||||
// the list after every edit until the next reload (#2631).
|
||||
it('RESV-SVC-015b: the update result keeps the day slot, null for a booking without one', async () => {
|
||||
const { trip } = ownerTrip({ start_date: '2030-05-01', end_date: '2030-05-02' });
|
||||
const days = await findRows(orm, Days, { trip: trip.id }, { day_number: 'asc' }) as { id: number }[];
|
||||
const moved = createReservation(testDb, trip.id, { title: 'Train', type: 'train' });
|
||||
const untouched = createReservation(testDb, trip.id, { title: 'Bus', type: 'bus' });
|
||||
await svc.updatePositions(String(trip.id), [{ id: moved.id, day_plan_position: 3.5 }], days[1].id);
|
||||
|
||||
const current = await svc.getReservation(String(moved.id), String(trip.id));
|
||||
const { reservation } = await svc.update(String(moved.id), String(trip.id), { notes: 'window seat' }, current as Parameters<ReservationsService['update']>[3]);
|
||||
expect(reservation.day_positions).toEqual({ [days[1].id]: 3.5 });
|
||||
expect((await svc.getReservationWithJoins(untouched.id))?.day_positions).toBeNull();
|
||||
});
|
||||
|
||||
it('RESV-SVC-016: getReservationWithJoins returns undefined for a missing id', async () => {
|
||||
|
||||
@@ -296,6 +296,7 @@ const buildReadModel = async (tripsRepo: TripsRepository, roster: TripMembersSer
|
||||
await createTestReservationsRepo(testDb),
|
||||
await createTestReservationEndpointsRepo(testDb),
|
||||
await createTestReservationTravelersRepo(testDb),
|
||||
await createTestReservationDayPositionsRepo(testDb),
|
||||
),
|
||||
await accommodationsOver(testDb, realtime),
|
||||
await createTestUnitOfWork(testDb),
|
||||
|
||||
@@ -278,7 +278,7 @@ beforeAll(async () => {
|
||||
buildPlaceImportService(),
|
||||
);
|
||||
svc = new TripsService(
|
||||
new ReservationsService(new PermissionsService(await createTestAppSettingsRepo(testDb), await createTestUnitOfWork(testDb)), budgetSvc, realtime, notificationsStub(), new ReservationsReadService(await createTestReservationsRepo(testDb), await createTestReservationEndpointsRepo(testDb), await createTestReservationTravelersRepo(testDb)), accommodationsSvc, await createTestUnitOfWork(testDb), await createTestReservationsRepo(testDb), await createTestReservationEndpointsRepo(testDb), await createTestReservationTravelersRepo(testDb), await createTestReservationDayPositionsRepo(testDb), await createTestDayAccommodationsRepo(testDb), await createTestDaysRepo(testDb), await createTestPlacesRepo(testDb), await createTestDayAssignmentsRepo(testDb), await createTestTripMembersRepo(testDb), await createTestUsersRepo(testDb), await createTestTripsRepo(testDb), await createTestBudgetItemsRepo(testDb)),
|
||||
new ReservationsService(new PermissionsService(await createTestAppSettingsRepo(testDb), await createTestUnitOfWork(testDb)), budgetSvc, realtime, notificationsStub(), new ReservationsReadService(await createTestReservationsRepo(testDb), await createTestReservationEndpointsRepo(testDb), await createTestReservationTravelersRepo(testDb), await createTestReservationDayPositionsRepo(testDb)), accommodationsSvc, await createTestUnitOfWork(testDb), await createTestReservationsRepo(testDb), await createTestReservationEndpointsRepo(testDb), await createTestReservationTravelersRepo(testDb), await createTestReservationDayPositionsRepo(testDb), await createTestDayAccommodationsRepo(testDb), await createTestDaysRepo(testDb), await createTestPlacesRepo(testDb), await createTestDayAssignmentsRepo(testDb), await createTestTripMembersRepo(testDb), await createTestUsersRepo(testDb), await createTestTripsRepo(testDb), await createTestBudgetItemsRepo(testDb)),
|
||||
daysSvc,
|
||||
new PermissionsService(await createTestAppSettingsRepo(testDb), await createTestUnitOfWork(testDb)),
|
||||
budgetSvc,
|
||||
@@ -307,7 +307,7 @@ beforeAll(async () => {
|
||||
await createTestPackingCategoryAssigneesRepo(testDb), await createTestPackingTemplatesRepo(testDb), await createTestPackingTemplateCategoriesRepo(testDb),
|
||||
await createTestPackingTemplateItemsRepo(testDb), await createTestTripsRepo(testDb), await createTestTripMembersRepo(testDb),
|
||||
),
|
||||
new ReservationsService(new PermissionsService(await createTestAppSettingsRepo(testDb), await createTestUnitOfWork(testDb)), budgetSvc, realtime, notificationsStub(), new ReservationsReadService(await createTestReservationsRepo(testDb), await createTestReservationEndpointsRepo(testDb), await createTestReservationTravelersRepo(testDb)), accommodationsSvc, await createTestUnitOfWork(testDb), await createTestReservationsRepo(testDb), await createTestReservationEndpointsRepo(testDb), await createTestReservationTravelersRepo(testDb), await createTestReservationDayPositionsRepo(testDb), await createTestDayAccommodationsRepo(testDb), await createTestDaysRepo(testDb), await createTestPlacesRepo(testDb), await createTestDayAssignmentsRepo(testDb), await createTestTripMembersRepo(testDb), await createTestUsersRepo(testDb), await createTestTripsRepo(testDb), await createTestBudgetItemsRepo(testDb)),
|
||||
new ReservationsService(new PermissionsService(await createTestAppSettingsRepo(testDb), await createTestUnitOfWork(testDb)), budgetSvc, realtime, notificationsStub(), new ReservationsReadService(await createTestReservationsRepo(testDb), await createTestReservationEndpointsRepo(testDb), await createTestReservationTravelersRepo(testDb), await createTestReservationDayPositionsRepo(testDb)), accommodationsSvc, await createTestUnitOfWork(testDb), await createTestReservationsRepo(testDb), await createTestReservationEndpointsRepo(testDb), await createTestReservationTravelersRepo(testDb), await createTestReservationDayPositionsRepo(testDb), await createTestDayAccommodationsRepo(testDb), await createTestDaysRepo(testDb), await createTestPlacesRepo(testDb), await createTestDayAssignmentsRepo(testDb), await createTestTripMembersRepo(testDb), await createTestUsersRepo(testDb), await createTestTripsRepo(testDb), await createTestBudgetItemsRepo(testDb)),
|
||||
new CollabService(
|
||||
// Plan 4 Task 2 — CollabService's own DatabaseService param is gone:
|
||||
// canAccessTrip now reads through the TripsRepository at the end.
|
||||
|
||||
Reference in New Issue
Block a user