mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-12 06:12:18 +00:00
fix(rail): trip-update delays are as old as the server's read
The browser stamped every trip-updates answer as just read. While an operator's feed fails, the rail proxy replays its last good copy for up to ten minutes, and a cached copy can be a TTL old, so those delays never reached RAIL_UPDATES_MAX_AGE_MS and a selected train's card showed them as current. Delays now take their age from the snapshot's fetchedAt (capped at now), as positions do in ingestion.js, so they stop applying 75 s after the server last read the operator.
This commit is contained in:
1 parent
ace2160002
commit
283890cee0
2 files changed
+58
-1
No files matched your search
@@ -287,8 +287,16 @@ export function createRailSchedules({
|
||||
if (!response.ok) throw new Error(`rail updates HTTP ${response.status}`);
|
||||
const body = await response.json();
|
||||
if (slot.controller !== controller) return;
|
||||
// Delays are as old as the server's read of the operator (`fetchedAt`),
|
||||
// not this answer: a cached copy, or the last good one replayed while
|
||||
// the operator fails, stops applying RAIL_UPDATES_MAX_AGE_MS after it,
|
||||
// as positions do (ingestion.js applySnapshot).
|
||||
const at = now();
|
||||
slot.delays = delaysFromUpdates(body);
|
||||
slot.at = now();
|
||||
slot.at = Math.min(
|
||||
Number.isFinite(body?.fetchedAt) ? body.fetchedAt : at,
|
||||
at,
|
||||
);
|
||||
onChange(feedId);
|
||||
} catch (error) {
|
||||
if (error?.name !== 'AbortError')
|
||||
|
||||
@@ -372,6 +372,55 @@ test('delays are polled every 30 s only while a train on the feed is selected',
|
||||
assert.equal(h.timers.size, 0);
|
||||
});
|
||||
|
||||
test('delays are as old as the server’s read, even when it replays them', async () => {
|
||||
let readAt = null;
|
||||
const h = harness({
|
||||
updates: () =>
|
||||
reply({
|
||||
fetchedAt: readAt ?? h.now,
|
||||
trips: [
|
||||
{
|
||||
tripId: 'T1',
|
||||
delaySec: 240,
|
||||
stops: [{ stopId: 'B', arrival: { delay: 240 }, departure: null }],
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
const train = liveTrain({
|
||||
id: 'v1',
|
||||
tripId: 'T1',
|
||||
routeId: 'R1',
|
||||
lat: LAT,
|
||||
lon: 0.05,
|
||||
});
|
||||
h.provider.onSelect(train);
|
||||
await flush();
|
||||
assert.equal(h.provider.stopsFor(train, h.now).stops[1].arrivalDelaySec, 240);
|
||||
|
||||
// The operator fails: each poll is answered with the server's last copy,
|
||||
// still stamped with the time it was read.
|
||||
readAt = h.now;
|
||||
const poll = [...h.timers.values()][0].fn;
|
||||
for (let i = 0; i < 3; i++) {
|
||||
h.now += RAIL_UPDATES_POLL_MS;
|
||||
poll();
|
||||
await flush();
|
||||
}
|
||||
assert.equal(h.calls.updates, 4);
|
||||
assert.ok(3 * RAIL_UPDATES_POLL_MS > RAIL_UPDATES_MAX_AGE_MS);
|
||||
assert.equal(h.provider.delaysFor(FEED), null);
|
||||
assert.equal(h.provider.stopsFor(train, h.now).stops[1].arrivalDelaySec, 0);
|
||||
|
||||
// A fresh read applies again; a server clock ahead counts as now.
|
||||
readAt = h.now + 5_000;
|
||||
poll();
|
||||
await flush();
|
||||
assert.equal(h.provider.stopsFor(train, h.now).stops[1].arrivalDelaySec, 240);
|
||||
h.now += RAIL_UPDATES_MAX_AGE_MS;
|
||||
assert.notEqual(h.provider.delaysFor(FEED), null);
|
||||
});
|
||||
|
||||
test('a selected train’s delays are not polled when its feed has no timetable', async () => {
|
||||
const h = harness({
|
||||
schedule: () => reply({ error: 'No rail schedule' }, 404),
|
||||
|
||||
Reference in new issue
Block a user