mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
Adding stop based booking configuration
This commit is contained in:
100
e2e-ui/specs/backoffice/checkin-cutoff.spec.ts
Normal file
100
e2e-ui/specs/backoffice/checkin-cutoff.spec.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { API_URL, STATIONS, staffToken } from "../../fixtures/data";
|
||||
import { UI_IDS } from "../../../apps/edr-passenger-api/test/fixtures/seed-ui";
|
||||
|
||||
/**
|
||||
* Track B — per-station check-in cutoff, driven by the backoffice route config API. Follows the
|
||||
* same convention as config-validation.spec.ts: hits the passenger-api directly with a staff
|
||||
* bearer token rather than driving the real DOM — the route-stop form has no data-testid hooks,
|
||||
* so browser automation here would be selector-fragile for no extra coverage value.
|
||||
*
|
||||
* Creates its OWN route (not the shared ROUTE_ID from seed-ui) since updateRoute deletes and
|
||||
* recreates all stops — mutating the shared fixture route would break every other spec that
|
||||
* depends on its distances/times staying stable for the whole suite run.
|
||||
*/
|
||||
function auth() {
|
||||
return { Authorization: `Bearer ${staffToken()}` };
|
||||
}
|
||||
|
||||
test("BC-11 ✅ travelMinutesToStop drives each stop's estimated arrival independently of route-wide departure", async ({ request }) => {
|
||||
const routeRes = await request.post(`${API_URL}/routes`, {
|
||||
headers: auth(),
|
||||
data: {
|
||||
code: `E2E-CUTOFF-${Date.now()}`,
|
||||
name: "E2E Check-in Cutoff Route",
|
||||
effectiveFrom: "2020-01-01T00:00:00Z",
|
||||
stops: [
|
||||
{ stationId: STATIONS.A, sequence: 1 },
|
||||
{ stationId: STATIONS.B, sequence: 2, travelMinutesToStop: 60 },
|
||||
{ stationId: STATIONS.C, sequence: 3, travelMinutesToStop: 40 },
|
||||
],
|
||||
},
|
||||
});
|
||||
expect(routeRes.ok()).toBeTruthy();
|
||||
const route = (await routeRes.json())?.data ?? (await routeRes.json());
|
||||
const routeId = route.id;
|
||||
|
||||
// Reuse the seeded coach via a route coach template so schedule creation auto-assigns real
|
||||
// seats (createSchedule auto-applies any route coach template — see schedules.service.ts).
|
||||
const templateRes = await request.put(`${API_URL}/routes/${routeId}/coaches`, {
|
||||
headers: auth(),
|
||||
data: { coaches: [{ coachId: UI_IDS.coach, positionNumber: 1 }] },
|
||||
});
|
||||
expect(templateRes.ok()).toBeTruthy();
|
||||
|
||||
// dep only 5 min out: A's cutoff (dep - 30min default) is already ~25 min in the past by the
|
||||
// time this schedule is queried, but B's arrival (dep + 60min) keeps its own cutoff (arrival -
|
||||
// 30min default) about 35 min in the future — proving the two stations close independently.
|
||||
const dep = new Date(Date.now() + 5 * 60_000);
|
||||
const arr = new Date(dep.getTime() + 100 * 60_000); // A->B 60min + B->C 40min
|
||||
const scheduleRes = await request.post(`${API_URL}/schedules`, {
|
||||
headers: auth(),
|
||||
data: { trainId: UI_IDS.train, routeId, departureAt: dep.toISOString(), arrivalAt: arr.toISOString() },
|
||||
});
|
||||
expect(scheduleRes.ok()).toBeTruthy();
|
||||
const schedule = (await scheduleRes.json())?.data ?? (await scheduleRes.json());
|
||||
|
||||
const getRes = await request.get(`${API_URL}/schedules/${schedule.id}`, { headers: auth() });
|
||||
expect(getRes.ok()).toBeTruthy();
|
||||
const full = (await getRes.json())?.data ?? (await getRes.json());
|
||||
const stopTimes: any[] = full.stopTimes ?? [];
|
||||
const bStop = stopTimes.find((s) => s.stationId === STATIONS.B);
|
||||
const cStop = stopTimes.find((s) => s.stationId === STATIONS.C);
|
||||
|
||||
expect(new Date(bStop.plannedArrivalAt).getTime()).toBe(dep.getTime() + 60 * 60_000);
|
||||
expect(new Date(cStop.plannedArrivalAt).getTime()).toBe(arr.getTime()); // last stop locked to overall arrival
|
||||
|
||||
// A's own segment (no arrival — falls back to its departure) is already past its cutoff...
|
||||
const rejectRes = await request.post(`${API_URL}/seats/hold`, {
|
||||
headers: auth(),
|
||||
data: {
|
||||
scheduleId: schedule.id,
|
||||
originStationId: STATIONS.A,
|
||||
destinationStationId: STATIONS.B,
|
||||
passengers: [{ passengerId: "44444444-4444-4444-8444-444444444444", seatId: "00000000-0000-4000-8000-000000009999" }],
|
||||
},
|
||||
});
|
||||
expect(rejectRes.status()).toBe(400);
|
||||
expect((await rejectRes.json())?.message ?? "").toMatch(/cannot be held within/i);
|
||||
|
||||
// ...while B, whose own arrival is comfortably later, remains independently bookable.
|
||||
const seatmapRes = await request.get(`${API_URL}/seats/seatmap/${schedule.id}`, { headers: auth() });
|
||||
expect(seatmapRes.ok()).toBeTruthy();
|
||||
const seatmap = (await seatmapRes.json())?.data ?? (await seatmapRes.json());
|
||||
const seatId = seatmap.coaches?.[0]?.seats?.[0]?.id;
|
||||
expect(seatId).toBeTruthy();
|
||||
|
||||
const holdRes = await request.post(`${API_URL}/seats/hold`, {
|
||||
headers: auth(),
|
||||
data: {
|
||||
scheduleId: schedule.id,
|
||||
originStationId: STATIONS.B,
|
||||
destinationStationId: STATIONS.C,
|
||||
passengers: [{ passengerId: "55555555-5555-4555-8555-555555555555", seatId }],
|
||||
},
|
||||
});
|
||||
expect(holdRes.ok()).toBeTruthy();
|
||||
|
||||
await request.delete(`${API_URL}/schedules/${schedule.id}`, { headers: auth() }).catch(() => {});
|
||||
await request.delete(`${API_URL}/routes/${routeId}?cascade=true`, { headers: auth() }).catch(() => {});
|
||||
});
|
||||
Reference in New Issue
Block a user