Adding stop based booking configuration

This commit is contained in:
Muluhabt
2026-07-23 20:03:35 +03:00
parent e31a2d64e5
commit 8bba882710
4 changed files with 333 additions and 0 deletions

View 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(() => {});
});