import { readFileSync } from 'node:fs'; import test from 'node:test'; import assert from 'node:assert/strict'; import { TRANSIT_ENABLED_FEEDS, TRANSIT_FEED_REGISTRY, TRANSIT_FEED_ID_PATTERN, TRANSIT_MODES, getRegisteredTransitFeed, getTransitFeed, haversineKm, publicTransitCatalog, transitFeedsInRange, transitModeFor, transitModeResolved, transitRouteLabel, } from './transitFeeds.js'; test('every registered feed is keyless, https, licensed, and uniquely identified', () => { const ids = new Set(); for (const feed of TRANSIT_FEED_REGISTRY) { assert.match( feed.id, TRANSIT_FEED_ID_PATTERN, `${feed.id} is a valid path segment`, ); assert.equal(ids.has(feed.id), false, `${feed.id} is unique`); ids.add(feed.id); const url = new URL(feed.url); assert.equal(url.protocol, 'https:', `${feed.id} fetches over https`); assert.equal( url.search.includes('key='), false, `${feed.id} carries no key in its URL`, ); assert.ok( feed.license && feed.licenseUrl && feed.attribution, `${feed.id} names its license`, ); assert.ok( feed.loadRadiusKm > 0 && feed.loadRadiusKm <= 1000, `${feed.id} radius is sane`, ); assert.ok( Math.abs(feed.center.lat) <= 90 && Math.abs(feed.center.lon) <= 180, ); assert.ok( TRANSIT_MODES.includes(feed.defaultMode), `${feed.id} default mode is known`, ); assert.ok(Object.isFrozen(feed), `${feed.id} is immutable`); } }); test('getTransitFeed is the only door to an upstream URL and refuses anything unregistered', () => { assert.equal( getTransitFeed('mbta')?.url, 'https://cdn.mbta.com/realtime/VehiclePositions.pb', ); assert.equal(getTransitFeed('MBTA'), null); assert.equal(getTransitFeed('../etc/passwd'), null); assert.equal(getTransitFeed('https://evil.example'), null); assert.equal(getTransitFeed(''), null); assert.equal(getTransitFeed(null), null); assert.equal(getTransitFeed(42), null); }); test('haversine matches known city distances', () => { const bostonToNyc = haversineKm(42.3601, -71.0589, 40.7128, -74.006); assert.ok( Math.abs(bostonToNyc - 306) < 5, `Boston–NYC ≈ 306 km, got ${bostonToNyc}`, ); assert.equal(haversineKm(0, 0, 0, 0), 0); }); test('feeds in range are nearest-first and honor slack as hysteresis', () => { // Camera over Cambridge, MA → MBTA only. const boston = transitFeedsInRange(42.37, -71.11); assert.deepEqual( boston.map((f) => f.id), ['mbta'], ); // Mid-Atlantic: nothing. assert.deepEqual(transitFeedsInRange(40, -40), []); // Just outside MBTA's 70 km circle (≈ 80 km south) — out without slack, in with 20 km slack. const farLat = 42.3601 - 80 / 111; assert.deepEqual(transitFeedsInRange(farLat, -71.0589), []); assert.deepEqual( transitFeedsInRange(farLat, -71.0589, 20).map((f) => f.id), ['mbta'], ); // Bad input never throws. assert.deepEqual(transitFeedsInRange(NaN, 1), []); // A national feed covers its own cities and not the neighbour's capital. for (const [city, lat, lon] of [ ['Oslo', 59.91, 10.75], ['Bergen', 60.39, 5.32], ['Tromsø', 69.65, 18.96], ]) { assert.ok( transitFeedsInRange(lat, lon).some((f) => f.id === 'entur-norway'), `${city} is covered by Entur`, ); } assert.deepEqual( transitFeedsInRange(60.17, 24.94).map((f) => f.id), ['hsl-helsinki'], 'Helsinki polls HSL only', ); }); test('route hints refine a feed default and never escape the known modes', () => { const mbta = getTransitFeed('mbta'); assert.equal(transitModeFor(mbta, 'Red'), 'subway'); assert.equal(transitModeFor(mbta, 'Green-B'), 'tram'); assert.equal(transitModeFor(mbta, 'CR-Fitchburg'), 'rail'); assert.equal(transitModeFor(mbta, 'Boat-F1'), 'ferry'); assert.equal(transitModeFor(mbta, '66'), 'bus'); assert.equal(transitModeFor(mbta, null), 'bus'); const hsl = getTransitFeed('hsl-helsinki'); assert.equal(transitModeFor(hsl, '31M1'), 'subway'); assert.equal(transitModeFor(hsl, '1006'), 'tram'); assert.equal(transitModeFor(hsl, '9982'), 'bus'); const msp = getTransitFeed('metrotransit-msp'); assert.equal(transitModeFor(msp, '901'), 'tram'); assert.equal(transitModeFor(msp, '17'), 'bus'); const entur = getTransitFeed('entur-norway'); assert.equal(transitModeFor(entur, 'VYG:Line:R10'), 'rail'); assert.equal(transitModeFor(entur, 'TRO:Line:1_310'), 'bus'); assert.equal(transitModeFor({ defaultMode: 'spaceship' }, 'x'), 'unknown'); assert.equal(transitModeFor(null, 'x'), 'unknown'); }); test('the public catalog exposes coverage and credit, never the upstream URL or headers', () => { const catalog = publicTransitCatalog(); assert.equal(catalog.length, TRANSIT_ENABLED_FEEDS.length); for (const entry of catalog) { assert.equal('url' in entry, false); assert.equal('headers' in entry, false); assert.ok(entry.id && entry.name && entry.region && entry.attribution); assert.ok( Number.isFinite(entry.center.lat) && Number.isFinite(entry.loadRadiusKm), ); } }); test('the enabled set is a gate on the registry, not a copy of it', () => { // Every registered feed is switched on today. The gate still has to be the // thing that decides that, because the moment an operator's terms change the // owner flips one flag and expects the feed to become unreachable — not to // stay routable because the filter had quietly become a no-op. assert.deepEqual( TRANSIT_ENABLED_FEEDS.map((feed) => feed.id), TRANSIT_FEED_REGISTRY.filter((feed) => feed.defaultEnabled === true).map( (feed) => feed.id, ), ); for (const feed of TRANSIT_FEED_REGISTRY) { assert.equal( typeof feed.defaultEnabled, 'boolean', `${feed.id} states whether it ships on`, ); assert.equal( Boolean(getTransitFeed(feed.id)), feed.defaultEnabled, `${feed.id} is routable exactly when it is enabled`, ); assert.ok(getRegisteredTransitFeed(feed.id), `${feed.id} stays documented`); } }); test('a mode the route id established is resolved; a feed default is only a guess', () => { // TransLink publishes rail in a feed whose default is bus. A consumer that // judges physical plausibility must know the difference, or a 140 km/h // train is refused as an impossible bus. const translink = getTransitFeed('translink-seq'); assert.equal(transitModeFor(translink, '600'), 'bus'); assert.equal(transitModeResolved(translink, '600'), false, 'defaulted'); const mbta = getTransitFeed('mbta'); assert.equal(transitModeResolved(mbta, 'Red'), true); assert.equal( transitModeResolved(mbta, '66'), true, 'a numeric MBTA route is a bus by rule, not by default', ); assert.equal(transitModeResolved(mbta, null), false); assert.equal(transitModeResolved({ defaultMode: 'bus' }, 'x'), false); assert.equal( transitModeResolved({ routeMode: () => 'spaceship' }, 'x'), false, 'an unknown hint resolves nothing', ); }); test('only MBTA retains proxy history while every other registered feed stays live-enabled', () => { assert.deepEqual( publicTransitCatalog() .filter((feed) => feed.historyRetention) .map((feed) => feed.id), ['mbta'], ); assert.equal(getTransitFeed('capmetro-austin').defaultEnabled, true); assert.equal(getTransitFeed('mbta').attribution, 'MBTA / MassDOT'); }); test('published transit history scope agrees with the catalog capability', () => { const retained = publicTransitCatalog().filter( (feed) => feed.historyRetention, ); assert.deepEqual( retained.map((feed) => feed.id), ['mbta'], ); const sources = readFileSync( new URL('../../DATA_SOURCES.md', import.meta.url), 'utf8', ); const row = sources .split('\n') .find((line) => line.startsWith('| **MBTA / MassDOT**')); assert.match( row, /live vehicles and up to 15 minutes of recently observed positions/, ); assert.match(row, /MBTA \/ MassDOT.*courtesy/); assert.match(sources, /other feeds have no proxy retention/); const state = readFileSync( new URL('../../docs/CURRENT-STATE.md', import.meta.url), 'utf8', ); const transit = state.slice( state.indexOf('Transit is off by default'), state.indexOf('`src/data/militaryAwareness.js` remains'), ); assert.match(transit, /Restart clears it/); assert.doesNotMatch( transit, /four fixes|0\.95x|guaranteed lower|one poll interval behind/, ); }); test('the TTC feed is registered, routable, and covers Toronto', () => { const ttc = getTransitFeed('ttc-toronto'); assert.equal(ttc?.url, 'https://bustime.ttc.ca/gtfsrt/vehicles'); assert.equal(ttc.defaultEnabled, true); assert.match(ttc.attribution, /Toronto Transit Commission/); assert.equal(ttc.historyRetention, undefined, 'no proxy retention'); assert.ok( transitFeedsInRange(43.6532, -79.3832).some( (feed) => feed.id === 'ttc-toronto', ), 'downtown Toronto is inside the load radius', ); }); test('TTC route numbers tell a streetcar from a bus', () => { // BusTime is the surface feed: 500-series are streetcar routes and the // streetcar-operated Blue Night routes keep their own numbers. Everything // else on this feed is a bus, so the mode is established, not defaulted. const ttc = getTransitFeed('ttc-toronto'); assert.equal(transitModeFor(ttc, '501'), 'tram'); assert.equal(transitModeFor(ttc, '512'), 'tram'); assert.equal(transitModeFor(ttc, '304'), 'tram'); assert.equal(transitModeFor(ttc, '36'), 'bus'); // 52 Lawrence West is a bus. Only the three-digit 500-series is streetcar, // so the rule must not match on a leading 5 alone. assert.equal(transitModeFor(ttc, '52'), 'bus'); assert.equal(transitModeFor(ttc, '59'), 'bus'); assert.equal(transitModeFor(ttc, '900'), 'bus'); assert.equal(transitModeFor(ttc, '352'), 'bus'); assert.equal(transitModeResolved(ttc, '501'), true); assert.equal(transitModeResolved(ttc, null), false); assert.equal( transitModeFor(ttc, null), 'bus', 'an unrouted vehicle falls back to the feed default, unresolved', ); }); test('a TTC fleet number outranks the route', () => { // Buses replace streetcars on 5xx routes during track work, and streetcars // out of service report no route; the Flexity fleet is 4400–4699. const ttc = getTransitFeed('ttc-toronto'); assert.equal(transitModeFor(ttc, '506', '8515'), 'bus'); assert.equal(transitModeFor(ttc, '512', '3101'), 'bus'); assert.equal(transitModeFor(ttc, null, '4507'), 'tram'); assert.equal(transitModeResolved(ttc, null, '4507'), true); assert.equal(transitModeFor(ttc, '504', '4400'), 'tram'); assert.equal(transitModeFor(ttc, '29', '4699'), 'tram'); assert.equal(transitModeFor(ttc, '501', '4700'), 'bus'); // A vehicle id that is not a fleet number leaves the route to decide. assert.equal(transitModeFor(ttc, '501', 'abc'), 'tram'); assert.equal(transitModeFor(ttc, '501'), 'tram'); }); test('the TTC row records that BusTime carries no subway', () => { const sources = readFileSync( new URL('../../DATA_SOURCES.md', import.meta.url), 'utf8', ); const row = sources.split('\n').find((line) => line.startsWith('| **TTC**')); assert.ok(row, 'TTC has a DATA_SOURCES row'); assert.match(row, /Toronto/); assert.match(sources, /surface vehicles \(buses and streetcars\) only/); assert.match(sources, /Open Government Licence – Toronto/); }); test('the Golden Horseshoe neighbours are registered, keyless and routable', () => { const expected = { 'miway-mississauga': 'https://www.miapp.ca/GTFS_RT/Vehicle/VehiclePositions.pb', 'hsr-hamilton': 'https://opendata.hamilton.ca/GTFS-RT/GTFS_VehiclePositions.pb', 'durham-region': 'https://drtonline.durhamregiontransit.com/gtfsrealtime/VehiclePositions', }; for (const [id, url] of Object.entries(expected)) { const feed = getTransitFeed(id); assert.ok(feed, `${id} is routable`); assert.equal(feed.url, url); assert.equal(feed.defaultEnabled, true); assert.equal(feed.defaultMode, 'bus'); assert.equal(feed.historyRetention, undefined, `${id} retains nothing`); assert.ok(feed.terms?.quote, `${id} records the terms it ships on`); } }); test('each Golden Horseshoe feed covers its own city and not its neighbours', () => { const near = (lat, lon) => transitFeedsInRange(lat, lon).map((f) => f.id); // Mississauga City Centre, downtown Hamilton, Oshawa. assert.ok(near(43.589, -79.6441).includes('miway-mississauga')); assert.ok(near(43.2557, -79.8711).includes('hsr-hamilton')); assert.ok(near(43.8971, -78.8658).includes('durham-region')); // Hamilton is 60 km from Toronto: far enough that the TTC feed is not polled // for it, which is the whole point of a per-feed radius. assert.equal(near(43.2557, -79.8711).includes('ttc-toronto'), false); }); test('a bus-only feed never claims a mode its route ids cannot establish', () => { for (const id of ['miway-mississauga', 'hsr-hamilton', 'durham-region']) { const feed = getTransitFeed(id); assert.equal(transitModeFor(feed, '1'), 'bus'); assert.equal( transitModeResolved(feed, '1'), false, `${id} defaults rather than resolves`, ); } }); test('every Golden Horseshoe feed is documented with its licence', () => { const sources = readFileSync( new URL('../../DATA_SOURCES.md', import.meta.url), 'utf8', ); for (const name of ['MiWay', 'Hamilton HSR', 'Durham Region Transit']) { assert.ok( sources.split('\n').some((line) => line.startsWith(`| **${name}`)), `${name} has a DATA_SOURCES row`, ); } assert.match(sources, /Durham Region Transit publishes no bearing/); }); test('a feed whose route ids are internal database keys can name its routes', () => { // Guelph's realtime feed emits `2991` where the rider sees route `1`. A feed // that knows the difference translates it; every other feed is unaffected. const guelph = getTransitFeed('guelph-transit'); assert.equal(transitRouteLabel(guelph, '2991'), '1'); assert.equal(transitRouteLabel(guelph, '3016'), '99'); assert.equal(transitRouteLabel(guelph, '3007'), '50 U'); assert.equal(transitRouteLabel(guelph, '3017'), '99Lite'); }); test('an unknown or absent route id survives translation unchanged', () => { const guelph = getTransitFeed('guelph-transit'); // A route added after this table was built must still render its raw id // rather than vanish or read as "undefined". assert.equal(transitRouteLabel(guelph, '4242'), '4242'); assert.equal(transitRouteLabel(guelph, null), null); assert.equal(transitRouteLabel(guelph, ''), ''); }); test('a feed with rider-facing route ids is left alone', () => { // TTC and Kingston publish the route a rider would say, so translation is // identity for them and no table is carried. assert.equal(transitRouteLabel(getTransitFeed('ttc-toronto'), '501'), '501'); assert.equal(transitRouteLabel(getTransitFeed('mbta'), 'Red'), 'Red'); assert.equal(transitRouteLabel(null, '7'), '7'); }); test('the feeds whose licences are accepted by use are switched on', () => { // Barrie and YRT each ask for acceptance through a form, and each licence // also says that using the data is itself acceptance. That reading was // taken deliberately, so both are routable and both record the reasoning // in their terms note rather than leaving it to be re-derived later. for (const id of ['barrie-transit', 'yrt-york']) { assert.ok(getTransitFeed(id), `${id} is routable`); assert.ok( publicTransitCatalog().some((feed) => feed.id === id), `${id} is offered to the browser`, ); assert.match( getRegisteredTransitFeed(id).terms.note, /acceptance is by use|use itself acceptance|took that clause as sufficient/, `${id} records why it ships on`, ); } }); test('every registered feed now ships on, and the gate still decides that', () => { // The enabled set must stay a filter over the registry, not a copy of it: // flipping one feed off has to make it unreachable. assert.equal(getRegisteredTransitFeed('yrt-york')?.name, 'YRT/Viva'); assert.deepEqual( TRANSIT_FEED_REGISTRY.filter((feed) => feed.defaultEnabled !== true), [], ); }); test('Viva bus rapid transit is named by its colour, not its route number', () => { // YRT publishes `601` where every rider, map and station sign says // "Viva Blue". The numeric local routes already read correctly and are // left alone. const yrt = getTransitFeed('yrt-york'); assert.equal(transitRouteLabel(yrt, '601'), 'Viva Blue'); assert.equal(transitRouteLabel(yrt, '60301'), 'Viva Purple A'); assert.equal(transitRouteLabel(yrt, '607'), 'Viva Yellow'); assert.equal(transitRouteLabel(yrt, '105'), '105', 'a local bus is a number'); assert.equal(transitRouteLabel(yrt, '9899'), '9899'); }); test('YRT local branches read as the branch letter, not the padded id', () => { // YRT suffixes a two-digit branch onto the base route, the same scheme the // Viva table already relies on for `60102`. Unsuffixed, that leaks to a rider // as "8301". The letters are York Region's own published GTFS // `routes.txt` short names, unpadded to match the local routes beside them. const yrt = getTransitFeed('yrt-york'); assert.equal(transitRouteLabel(yrt, '10702'), '107B'); assert.equal(transitRouteLabel(yrt, '8301'), '83A'); assert.equal(transitRouteLabel(yrt, '9002'), '90B'); // `9101` runs in the realtime feed but is absent from routes.txt, so there is // no published name to give it. Inventing "91A" from the pattern is exactly // the guess the registry refuses to make; it stays opaque until it is // published. assert.equal(transitRouteLabel(yrt, '9101'), '9101'); }); test('Hamilton route ids are internal keys, read as the number on the bus', () => { // Not one of HSR's 80 route ids equals its published short name, so every // vehicle reads as "Route 5783" until the table translates it. Hamilton also // carries two generations of id for the same route — `5688` and `5780` are // both the 2 BARTON — so both must land on the same answer. const hsr = getTransitFeed('hsr-hamilton'); assert.equal(transitRouteLabel(hsr, '5780'), '2'); assert.equal(transitRouteLabel(hsr, '5688'), '2', 'the older id agrees'); assert.equal(transitRouteLabel(hsr, '5687'), '1'); assert.equal(transitRouteLabel(hsr, '5829'), '10', 'the B-Line'); assert.equal(transitRouteLabel(hsr, '5793'), '20', 'the A-Line'); // Published names that are not numbers are already what a rider would say, // so they survive as published rather than being forced into a number. assert.equal(transitRouteLabel(hsr, '5820'), 'TC230'); assert.equal(transitRouteLabel(hsr, '9999'), '9999', 'unknown survives'); }); test('Burlington route ids are joined, never prefix-stripped', () => { // `351` is route 1 and `3510` is route 10, so no amount of trimming a `35` // prefix works — the table is the only correct answer. const bur = getTransitFeed('burlington-transit'); assert.equal(transitRouteLabel(bur, '351'), '1'); assert.equal(transitRouteLabel(bur, '3510'), '10'); assert.equal(transitRouteLabel(bur, '3512'), '12'); assert.equal(transitRouteLabel(bur, '3587'), '87'); assert.equal(transitRouteLabel(bur, '9999'), '9999', 'unknown survives'); }); test('Burlington is live and carries its terms link, which its licence requires', () => { const bur = getTransitFeed('burlington-transit'); assert.equal(bur.defaultEnabled, true); assert.match(bur.licenseUrl, /Open%20Data%20Terms%20of%20Use\.pdf$/); assert.match(bur.url, /^https:\/\//, 'port 80 has no listener at all'); });