mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-02 02:23:25 +00:00
fix issues
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
|
||||
import { TrainSchedulingService } from './services/train-scheduling.service';
|
||||
|
||||
/**
|
||||
* Per-wagon loading dispatch gate. A booking half-loaded at the DEPARTURE yard
|
||||
* blocks the train; a booking that boards further down the corridor
|
||||
* (A→B→C→D carrying a B→C load) never does — its wagons are not due until its
|
||||
* own yard, so the SQL is scoped by `b.origin_yard_id = <schedule origin>`.
|
||||
* The scoping lives in the query, so this checks the parameters that carry it
|
||||
* plus the throw/pass decision on the rows it returns.
|
||||
*/
|
||||
describe('TrainSchedulingService.assertNoPartiallyLoadedBookings', () => {
|
||||
const ORIGIN = 'yard-a';
|
||||
const SET = 'set-1';
|
||||
|
||||
const makeService = (rows: Array<{ reference: string; loaded: string; total: string }>) => {
|
||||
const calls: Array<{ sql: string; params: unknown[] }> = [];
|
||||
const svc = Object.create(TrainSchedulingService.prototype) as {
|
||||
dataSource: { query: (sql: string, params: unknown[]) => Promise<unknown> };
|
||||
assertNoPartiallyLoadedBookings(
|
||||
schedule: unknown,
|
||||
boardingYardId: string,
|
||||
context: { action: string; yardLabel?: string },
|
||||
): Promise<void>;
|
||||
assertPassedYardsFullyLoaded(
|
||||
schedule: unknown,
|
||||
stations: Array<{ sequenceNo: number; yardId: string; label: string }>,
|
||||
sequenceNo: number,
|
||||
): Promise<void>;
|
||||
};
|
||||
svc.dataSource = {
|
||||
query: async (sql: string, params: unknown[]) => {
|
||||
calls.push({ sql, params });
|
||||
return rows;
|
||||
},
|
||||
};
|
||||
return { svc, calls };
|
||||
};
|
||||
const schedule = { trainSetId: SET, originStationId: ORIGIN };
|
||||
|
||||
it('scopes the scan to bookings boarding at this departure yard', async () => {
|
||||
const { svc, calls } = makeService([]);
|
||||
await svc.assertNoPartiallyLoadedBookings(schedule, ORIGIN, { action: 'dispatch' });
|
||||
expect(calls).toHaveLength(1);
|
||||
// The origin filter is what keeps a mid-corridor booking from holding the
|
||||
// train — without it, one early-loaded B→C wagon blocks dispatch at A.
|
||||
expect(calls[0].sql).toContain('b.origin_yard_id = $2');
|
||||
expect(calls[0].params).toEqual([SET, ORIGIN]);
|
||||
});
|
||||
|
||||
it('lets the train go when nothing at this yard is half-loaded', async () => {
|
||||
const { svc } = makeService([]);
|
||||
await expect(
|
||||
svc.assertNoPartiallyLoadedBookings(schedule, ORIGIN, { action: 'dispatch' }),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('blocks a booking half-loaded at this yard, naming its progress', async () => {
|
||||
const { svc } = makeService([{ reference: 'BK-2026-000220', loaded: '4', total: '5' }]);
|
||||
await expect(
|
||||
svc.assertNoPartiallyLoadedBookings(schedule, ORIGIN, { action: 'dispatch' }),
|
||||
).rejects.toThrow(/BK-2026-000220 \(4\/5 wagons loaded\)/);
|
||||
await expect(
|
||||
svc.assertNoPartiallyLoadedBookings(schedule, ORIGIN, { action: 'dispatch' }),
|
||||
).rejects.toBeInstanceOf(BadRequestException);
|
||||
});
|
||||
|
||||
it('skips the scan entirely for a schedule with no train set', async () => {
|
||||
const { svc, calls } = makeService([{ reference: 'X', loaded: '1', total: '2' }]);
|
||||
await expect(
|
||||
svc.assertNoPartiallyLoadedBookings({ trainSetId: null }, ORIGIN, {
|
||||
action: 'dispatch',
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
expect(calls).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Mid-corridor twin: logging a checkpoint at station N means the train left
|
||||
* every earlier stop, so each of those yards is checked for its OWN
|
||||
* half-loaded bookings. The origin is excluded (dispatch gated it) and the
|
||||
* yard being arrived at is excluded (its loading has not happened yet).
|
||||
*/
|
||||
describe('TrainSchedulingService.assertPassedYardsFullyLoaded', () => {
|
||||
const STATIONS = [
|
||||
{ sequenceNo: 0, yardId: 'mojo', label: 'Mojo' },
|
||||
{ sequenceNo: 1, yardId: 'adama', label: 'Adama' },
|
||||
{ sequenceNo: 2, yardId: 'dire', label: 'Dire Dawa' },
|
||||
{ sequenceNo: 3, yardId: 'djibouti', label: 'Djibouti' },
|
||||
];
|
||||
|
||||
const makeService = (rowsByYard: Record<string, Array<Record<string, string>>>) => {
|
||||
const scanned: string[] = [];
|
||||
const svc = Object.create(TrainSchedulingService.prototype) as {
|
||||
dataSource: { query: (sql: string, params: unknown[]) => Promise<unknown> };
|
||||
assertPassedYardsFullyLoaded(
|
||||
schedule: unknown,
|
||||
stations: typeof STATIONS,
|
||||
sequenceNo: number,
|
||||
): Promise<void>;
|
||||
};
|
||||
svc.dataSource = {
|
||||
query: async (_sql: string, params: unknown[]) => {
|
||||
const yardId = params[1] as string;
|
||||
scanned.push(yardId);
|
||||
return rowsByYard[yardId] ?? [];
|
||||
},
|
||||
};
|
||||
return { svc, scanned };
|
||||
};
|
||||
const schedule = { trainSetId: 'set-1', originStationId: 'mojo' };
|
||||
|
||||
it('checks the stops already departed, never the origin or the yard being reached', async () => {
|
||||
const { svc, scanned } = makeService({});
|
||||
await svc.assertPassedYardsFullyLoaded(schedule, STATIONS, 3);
|
||||
// Mojo is dispatch's job; Djibouti has not been loaded at yet.
|
||||
expect(scanned).toEqual(['adama', 'dire']);
|
||||
});
|
||||
|
||||
it('blocks the checkpoint when a passed yard left a booking half-loaded', async () => {
|
||||
const { svc } = makeService({
|
||||
adama: [{ reference: 'BK-200', loaded: '5', total: '8' }],
|
||||
});
|
||||
await expect(svc.assertPassedYardsFullyLoaded(schedule, STATIONS, 2)).rejects.toThrow(
|
||||
/Adama.*BK-200 \(5\/8 wagons loaded\)/s,
|
||||
);
|
||||
});
|
||||
|
||||
it('names the resolution the operator has: load the rest, or cancel it', async () => {
|
||||
const { svc } = makeService({
|
||||
adama: [{ reference: 'BK-200', loaded: '5', total: '8' }],
|
||||
});
|
||||
await expect(svc.assertPassedYardsFullyLoaded(schedule, STATIONS, 2)).rejects.toThrow(
|
||||
/customer fault: cancellation fee; EDR fault: no fee, rebookable/,
|
||||
);
|
||||
});
|
||||
|
||||
it('scans nothing at the first checkpoint after the origin', async () => {
|
||||
const { svc, scanned } = makeService({});
|
||||
await svc.assertPassedYardsFullyLoaded(schedule, STATIONS, 1);
|
||||
expect(scanned).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user