From d3707db7a215d7948b54206c6a6bb8b7cfe1b159 Mon Sep 17 00:00:00 2001 From: dabund24 Date: Fri, 7 Feb 2025 12:55:08 +0100 Subject: [PATCH] split dbweb and dbregioguide profiles; add db profile --- p/db/dynamicProfileData.json | 80 +++ p/db/index.js | 51 +- p/db/trip-req.js | 15 +- p/dbregioguide/base.json | 5 + p/dbregioguide/index.js | 18 + p/dbregioguide/trip-req.js | 11 + p/{db => dbweb}/base.json | 1 - p/{db => dbweb}/example.js | 0 p/dbweb/index.js | 26 + p/{db => dbweb}/journeys-req.js | 0 p/{db => dbweb}/locations-req.js | 0 p/{db => dbweb}/station-board-req.js | 0 test/db-dynamic-handling.js | 60 +++ ...ip-regio-guide.js => dbregioguide-trip.js} | 6 +- test/dbris-arrivals.js | 2 +- .../{db-departures.js => dbweb-departures.js} | 8 +- test/{db-journey.js => dbweb-journey.js} | 8 +- ...sh-journey.js => dbweb-refresh-journey.js} | 8 +- test/{db-trip.js => dbweb-trip.js} | 6 +- test/e2e/db.js | 2 +- test/e2e/dbregioguide.js | 499 ++++++++++++++++++ test/e2e/dbweb.js | 498 +++++++++++++++++ ...io-guide.js => dbregioguide-departures.js} | 0 ...uide.json => dbregioguide-departures.json} | 0 ...ip-regio-guide.js => dbregioguide-trip.js} | 0 ...egio-guide.json => dbregioguide-trip.json} | 0 .../{db-departures.js => dbweb-departures.js} | 4 +- ...-departures.json => dbweb-departures.json} | 0 .../{db-journey.js => dbweb-journey.js} | 4 +- .../{db-journey.json => dbweb-journey.json} | 0 ...sh-journey.js => dbweb-refresh-journey.js} | 0 ...ourney.json => dbweb-refresh-journey.json} | 0 test/fixtures/{db-trip.js => dbweb-trip.js} | 4 +- .../{db-trip.json => dbweb-trip.json} | 0 ...ivals-query.js => dbweb-arrivals-query.js} | 2 +- ...rneys-query.js => dbweb-journeys-query.js} | 6 +- 36 files changed, 1272 insertions(+), 52 deletions(-) create mode 100644 p/db/dynamicProfileData.json create mode 100644 p/dbregioguide/base.json create mode 100644 p/dbregioguide/index.js create mode 100644 p/dbregioguide/trip-req.js rename p/{db => dbweb}/base.json (83%) rename p/{db => dbweb}/example.js (100%) create mode 100644 p/dbweb/index.js rename p/{db => dbweb}/journeys-req.js (100%) rename p/{db => dbweb}/locations-req.js (100%) rename p/{db => dbweb}/station-board-req.js (100%) create mode 100644 test/db-dynamic-handling.js rename test/{db-trip-regio-guide.js => dbregioguide-trip.js} (78%) rename test/{db-departures.js => dbweb-departures.js} (75%) rename test/{db-journey.js => dbweb-journey.js} (77%) rename test/{db-refresh-journey.js => dbweb-refresh-journey.js} (77%) rename test/{db-trip.js => dbweb-trip.js} (79%) create mode 100644 test/e2e/dbregioguide.js create mode 100644 test/e2e/dbweb.js rename test/fixtures/{db-departures-regio-guide.js => dbregioguide-departures.js} (100%) rename test/fixtures/{db-departures-regio-guide.json => dbregioguide-departures.json} (100%) rename test/fixtures/{db-trip-regio-guide.js => dbregioguide-trip.js} (100%) rename test/fixtures/{db-trip-regio-guide.json => dbregioguide-trip.json} (100%) rename test/fixtures/{db-departures.js => dbweb-departures.js} (99%) rename test/fixtures/{db-departures.json => dbweb-departures.json} (100%) rename test/fixtures/{db-journey.js => dbweb-journey.js} (99%) rename test/fixtures/{db-journey.json => dbweb-journey.json} (100%) rename test/fixtures/{db-refresh-journey.js => dbweb-refresh-journey.js} (100%) rename test/fixtures/{db-refresh-journey.json => dbweb-refresh-journey.json} (100%) rename test/fixtures/{db-trip.js => dbweb-trip.js} (99%) rename test/fixtures/{db-trip.json => dbweb-trip.json} (100%) rename test/format/{db-arrivals-query.js => dbweb-arrivals-query.js} (96%) rename test/format/{db-journeys-query.js => dbweb-journeys-query.js} (95%) diff --git a/p/db/dynamicProfileData.json b/p/db/dynamicProfileData.json new file mode 100644 index 00000000..36743eb9 --- /dev/null +++ b/p/db/dynamicProfileData.json @@ -0,0 +1,80 @@ +{ + "journeys": { + "profileName": "dbnav", + "profileMethods": [ + { + "methodName": "formatJourneysReq", + "moduleName": "journeys-req.js" + } + ], + "baseKeys": ["journeysEndpoint"] + }, + "refreshJourneys": { + "profileName": "dbnav", + "profileMethods": [ + { + "methodName": "formatRefreshJourneyReq", + "moduleName": "journeys-req.js" + } + ], + "baseKeys": ["refreshJourneysEndpointTickets", "refreshJourneysEndpointPolyline"] + }, + "locations": { + "profileName": "dbnav", + "profileMethods": [ + { + "methodName": "formatLocationFilter", + "moduleName": "location-filter.js" + }, + { + "methodName": "formatLocationsReq", + "moduleName": "locations-req.js" + } + ], + "baseKeys": ["locationsEndpoint"] + }, + "stop": { + "profileName": "dbnav", + "profileMethods": [ + { + "methodName": "formatStopReq", + "moduleName": "stop-req.js" + }, + { + "methodName": "parseStop", + "moduleName": "parse-stop.js" + } + ], + "baseKeys": ["stopEndpoint"] + }, + "nearby": { + "profileName": "dbnav", + "profileMethods": [ + { + "methodName": "formatNearbyReq", + "moduleName": "nearby-req.js" + } + ], + "baseKeys": ["nearbyEndpoint"] + }, + "trip": { + "profileName": "db", + "profileMethods": [ + { + "methodName": "formatTripReq", + "moduleName": "trip-req.js" + } + ], + "baseKeys": ["tripEndpoint"] + }, + "board": { + "profileName": "dbregioguide", + "profileMethods": [ + { + "methodName": "formatStationBoardReq", + "moduleName": "station-board-req.js" + } + ], + "baseKeys": ["boardEndpoint"] + } +} diff --git a/p/db/index.js b/p/db/index.js index 3f6154b7..dd6210a4 100644 --- a/p/db/index.js +++ b/p/db/index.js @@ -1,28 +1,51 @@ import {createRequire} from 'module'; const require = createRequire(import.meta.url); - -const baseProfile = require('./base.json'); +const dynamicProfileData = require('./dynamicProfileData.json'); +const dbnavBase = require('../dbnav/base.json'); +const dbregioguideBase = require('../dbregioguide/base.json'); +const dbwebBase = require('../dbweb/base.json'); import {products} from '../../lib/products.js'; -import {formatJourneysReq, formatRefreshJourneyReq} from './journeys-req.js'; -import {formatTripReq} from './trip-req.js'; -import {formatLocationFilter} from './location-filter.js'; -import {formatLocationsReq} from './locations-req.js'; -import {formatStationBoardReq} from './station-board-req.js'; const profile = { - ...baseProfile, locale: 'de-DE', timezone: 'Europe/Berlin', products, - formatJourneysReq, - formatRefreshJourneyReq, - formatTripReq, - formatLocationsReq, - formatLocationFilter, - formatStationBoardReq, }; +// add profile methods +for (const {profileName, profileMethods} of Object.values(dynamicProfileData)) { + for (const {methodName, moduleName} of profileMethods) { + try { + // TODO use `import()` with top-level await once updated to es2022 + profile[methodName] = require(`../${profileName}/${moduleName}`)[methodName]; + } catch { /* use implementation from default profile if module doesn't exist */ } + } +} + +const bases = { + dbnav: dbnavBase, + dbregioguide: dbregioguideBase, + dbweb: dbwebBase, +}; + +// add endpoint bases +for (const {profileName, baseKeys} of Object.values(dynamicProfileData)) { + if (profileName !== 'db') { // only add endpoint(s) from specified profile + for (const baseKey of baseKeys) { + profile[baseKey] = bases[profileName][baseKey]; + } + continue; + } + + // add endpoints from all profiles with the profile names as key suffixes and dynamically decide which to use later + for (const [profileName, profileBases] of Object.entries(bases)) { + for (const baseKey of baseKeys) { + profile[`${baseKey}_${profileName}`] = profileBases[baseKey]; + } + } +} + export { profile, }; diff --git a/p/db/trip-req.js b/p/db/trip-req.js index 7b25aa40..3cc2f5a4 100644 --- a/p/db/trip-req.js +++ b/p/db/trip-req.js @@ -1,15 +1,16 @@ -import {formatTripReq as hafasFormatTripReq} from '../../format/trip-req.js'; +import {formatTripReq as hafasFormatTripReq} from '../dbnav/trip-req.js'; +import {formatTripReq as risTripReq} from '../dbregioguide/trip-req.js'; const formatTripReq = ({profile, opt}, id) => { + const _profile = {...profile}; if (id.includes('#')) { - return hafasFormatTripReq({profile, opt}, id); + _profile['tripEndpoint'] = profile.tripEndpoint_dbnav; + return hafasFormatTripReq({profile: _profile, opt}, id); } - return { - endpoint: profile.regioGuideTripEndpoint, - path: id, - method: 'get', - }; + + _profile['tripEndpoint'] = profile.tripEndpoint_dbregioguide; + return risTripReq({profile: _profile, opt}, id); }; export { diff --git a/p/dbregioguide/base.json b/p/dbregioguide/base.json new file mode 100644 index 00000000..28f09409 --- /dev/null +++ b/p/dbregioguide/base.json @@ -0,0 +1,5 @@ +{ + "tripEndpoint": "https://regio-guide.de/@prd/zupo-travel-information/api/public/ri/journey/", + "boardEndpoint": "https://regio-guide.de/@prd/zupo-travel-information/api/public/ri/board/", + "defaultLanguage": "en" +} diff --git a/p/dbregioguide/index.js b/p/dbregioguide/index.js new file mode 100644 index 00000000..4a67c738 --- /dev/null +++ b/p/dbregioguide/index.js @@ -0,0 +1,18 @@ +import {createRequire} from 'module'; +const require = createRequire(import.meta.url); +const baseProfile = require('./base.json'); +import {products} from '../../lib/products.js'; +import {formatTripReq} from './trip-req.js'; + +const profile = { + ...baseProfile, + locale: 'de-DE', + timezone: 'Europe/Berlin', + + products, + formatTripReq, +}; + +export { + profile, +}; diff --git a/p/dbregioguide/trip-req.js b/p/dbregioguide/trip-req.js new file mode 100644 index 00000000..9c28ca5a --- /dev/null +++ b/p/dbregioguide/trip-req.js @@ -0,0 +1,11 @@ +const formatTripReq = ({profile, opt}, id) => { + return { + endpoint: profile.tripEndpoint, + path: id, + method: 'get', + }; +}; + +export { + formatTripReq, +}; diff --git a/p/db/base.json b/p/dbweb/base.json similarity index 83% rename from p/db/base.json rename to p/dbweb/base.json index 01c44e0f..ae4637fd 100644 --- a/p/db/base.json +++ b/p/dbweb/base.json @@ -5,7 +5,6 @@ "locationsEndpoint": "https://int.bahn.de/web/api/reiseloesung/orte", "nearbyEndpoint": "https://int.bahn.de/web/api/reiseloesung/orte/nearby", "tripEndpoint": "https://int.bahn.de/web/api/reiseloesung/fahrt", - "regioGuideTripEndpoint": "https://regio-guide.de/@prd/zupo-travel-information/api/public/ri/journey/", "boardEndpoint": "https://int.bahn.de/web/api/reiseloesung/", "defaultLanguage": "en" } diff --git a/p/db/example.js b/p/dbweb/example.js similarity index 100% rename from p/db/example.js rename to p/dbweb/example.js diff --git a/p/dbweb/index.js b/p/dbweb/index.js new file mode 100644 index 00000000..1f14bf35 --- /dev/null +++ b/p/dbweb/index.js @@ -0,0 +1,26 @@ +import {createRequire} from 'module'; +const require = createRequire(import.meta.url); + +const baseProfile = require('./base.json'); +import {products} from '../../lib/products.js'; +import {formatJourneysReq, formatRefreshJourneyReq} from './journeys-req.js'; +import {formatLocationFilter} from './location-filter.js'; +import {formatLocationsReq} from './locations-req.js'; +import {formatStationBoardReq} from './station-board-req.js'; + +const profile = { + ...baseProfile, + locale: 'de-DE', + timezone: 'Europe/Berlin', + + products, + formatJourneysReq, + formatRefreshJourneyReq, + formatLocationsReq, + formatLocationFilter, + formatStationBoardReq, +}; + +export { + profile, +}; diff --git a/p/db/journeys-req.js b/p/dbweb/journeys-req.js similarity index 100% rename from p/db/journeys-req.js rename to p/dbweb/journeys-req.js diff --git a/p/db/locations-req.js b/p/dbweb/locations-req.js similarity index 100% rename from p/db/locations-req.js rename to p/dbweb/locations-req.js diff --git a/p/db/station-board-req.js b/p/dbweb/station-board-req.js similarity index 100% rename from p/db/station-board-req.js rename to p/dbweb/station-board-req.js diff --git a/test/db-dynamic-handling.js b/test/db-dynamic-handling.js new file mode 100644 index 00000000..5b05c3b7 --- /dev/null +++ b/test/db-dynamic-handling.js @@ -0,0 +1,60 @@ +import {createRequire} from 'module'; +const require = createRequire(import.meta.url); + +import {createClient} from '../index.js'; +import {profile as rawProfile} from '../p/db/index.js'; +const dynamicProfileData = require('../p/db/dynamicProfileData.json'); +const dbnavBase = require('../p/dbnav/base.json'); +const dbwebBase = require('../p/dbweb/base.json'); +const dbregioguideBase = require('../p/dbregioguide/base.json'); + +import tap from 'tap'; + +const client = createClient(rawProfile, 'public-transport/hafas-client:test'); + +tap.test('db: determine base urls', (t) => { + const fqdns = { + dbnav: 'app.vendo.noncd.db.de', + dbregioguide: 'regio-guide.de', + dbweb: 'int.bahn.de', + }; + + for (const {profileName, baseKeys} of Object.values(dynamicProfileData)) { + if (profileName !== 'db') { // endpoint(s) is(/are) static. Check if correct fqdn is contained in base(s) + for (const baseKey of baseKeys) { + t.ok(client.profile[baseKey].includes(fqdns[profileName]), [`base url for ${profileName} profile should include ${fqdns[profileName]}`]); + } + continue; + } + + // endpoint(s) is(/are) dynamic. Check if actual base key does not exist yet. Also check, if bases for all profiles are properly stored in other aux entries for later use + for (const baseKey of baseKeys) { + for (const [profileName, fqdn] of Object.entries(fqdns)) { + t.notHas(client.profile, baseKey, [`db profile should not contain the key ${baseKey}, since it is dynamically dispatched at runtime`]); + t.ok(client.profile[`${baseKey}_${profileName}`].includes(fqdn), [`key ${baseKey}_${profileName} of db profile should include ${fqdns[profileName]}`]); + } + } + } + t.end(); +}); + +tap.test('db: dynamic client method', async (t) => { + t.equal(dynamicProfileData.trip.profileName, 'db', ['if this fails, check a different client method in this test']); + + t.equal(client.profile.formatTripReq, (await import('../p/db/trip-req.js')).formatTripReq); + t.notHas(client.profile, 'tripEndpoint'); + t.equal(client.profile.tripEndpoint_dbnav, dbnavBase.tripEndpoint); + t.equal(client.profile.tripEndpoint_dbweb, dbwebBase.tripEndpoint); + t.equal(client.profile.tripEndpoint_dbregioguide, dbregioguideBase.tripEndpoint); + + t.end(); +}); + +tap.test('db: static client method', async (t) => { + t.equal(dynamicProfileData.nearby.profileName, 'dbnav', ['if this fails, check a different client method in this test']); + + t.equal(client.profile.formatNearbyReq, (await import('../p/dbnav/nearby-req.js')).formatNearbyReq); + t.equal(client.profile.nearbyEndpoint, dbnavBase.nearbyEndpoint); + + t.end(); +}); diff --git a/test/db-trip-regio-guide.js b/test/dbregioguide-trip.js similarity index 78% rename from test/db-trip-regio-guide.js rename to test/dbregioguide-trip.js index 93b90862..8bb38b6a 100644 --- a/test/db-trip-regio-guide.js +++ b/test/dbregioguide-trip.js @@ -6,9 +6,9 @@ const require = createRequire(import.meta.url); import tap from 'tap'; import {createClient} from '../index.js'; -import {profile as rawProfile} from '../p/db/index.js'; -const res = require('./fixtures/db-trip-regio-guide.json'); -import {dbTrip as expected} from './fixtures/db-trip-regio-guide.js'; +import {profile as rawProfile} from '../p/dbregioguide/index.js'; +const res = require('./fixtures/dbregioguide-trip.json'); +import {dbTrip as expected} from './fixtures/dbregioguide-trip.js'; const client = createClient(rawProfile, 'public-transport/hafas-client:test', {enrichStations: false}); const {profile} = client; diff --git a/test/dbris-arrivals.js b/test/dbris-arrivals.js index 225e1223..be30f2d9 100644 --- a/test/dbris-arrivals.js +++ b/test/dbris-arrivals.js @@ -6,7 +6,7 @@ const require = createRequire(import.meta.url); import tap from 'tap'; import {createClient} from '../index.js'; -import {profile as rawProfile} from '../p/db/index.js'; +import {profile as rawProfile} from '../p/dbweb/index.js'; const res = require('./fixtures/dbris-arrivals.json'); import {dbArrivals as expected} from './fixtures/dbris-arrivals.js'; diff --git a/test/db-departures.js b/test/dbweb-departures.js similarity index 75% rename from test/db-departures.js rename to test/dbweb-departures.js index ff6e198f..b8e13c8b 100644 --- a/test/db-departures.js +++ b/test/dbweb-departures.js @@ -6,9 +6,9 @@ const require = createRequire(import.meta.url); import tap from 'tap'; import {createClient} from '../index.js'; -import {profile as rawProfile} from '../p/db/index.js'; -const res = require('./fixtures/db-departures.json'); -import {dbDepartures as expected} from './fixtures/db-departures.js'; +import {profile as rawProfile} from '../p/dbweb/index.js'; +const res = require('./fixtures/dbweb-departures.json'); +import {dbwebDepartures as expected} from './fixtures/dbweb-departures.js'; const client = createClient(rawProfile, 'public-transport/hafas-client:test', {enrichStations: false}); const {profile} = client; @@ -25,7 +25,7 @@ const opt = { vias: 5, }; -tap.test('parses a db departure correctly', (t) => { +tap.test('parses a dbweb departure correctly', (t) => { const ctx = {profile, opt, common: null, res}; const departures = res.entries.map(d => profile.parseDeparture(ctx, d)); diff --git a/test/db-journey.js b/test/dbweb-journey.js similarity index 77% rename from test/db-journey.js rename to test/dbweb-journey.js index 64e3585b..f777c829 100644 --- a/test/db-journey.js +++ b/test/dbweb-journey.js @@ -6,9 +6,9 @@ const require = createRequire(import.meta.url); import tap from 'tap'; import {createClient} from '../index.js'; -import {profile as rawProfile} from '../p/db/index.js'; -const res = require('./fixtures/db-journey.json'); -import {dbJourney as expected} from './fixtures/db-journey.js'; +import {profile as rawProfile} from '../p/dbweb/index.js'; +const res = require('./fixtures/dbweb-journey.json'); +import {dbwebJourney as expected} from './fixtures/dbweb-journey.js'; const client = createClient(rawProfile, 'public-transport/hafas-client:test', {enrichStations: false}); const {profile} = client; @@ -31,7 +31,7 @@ const opt = { products: {}, }; -tap.test('parses a journey correctly (DB)', (t) => { // TODO DEVI leg +tap.test('parses a dbweb journey correctly', (t) => { // TODO DEVI leg const ctx = {profile, opt, common: null, res}; const journey = profile.parseJourney(ctx, res.verbindungen[0]); diff --git a/test/db-refresh-journey.js b/test/dbweb-refresh-journey.js similarity index 77% rename from test/db-refresh-journey.js rename to test/dbweb-refresh-journey.js index c3520990..fe97f2b7 100644 --- a/test/db-refresh-journey.js +++ b/test/dbweb-refresh-journey.js @@ -6,9 +6,9 @@ const require = createRequire(import.meta.url); import tap from 'tap'; import {createClient} from '../index.js'; -import {profile as rawProfile} from '../p/db/index.js'; -const res = require('./fixtures/db-refresh-journey.json'); -import {dbJourney as expected} from './fixtures/db-refresh-journey.js'; +import {profile as rawProfile} from '../p/dbweb/index.js'; +const res = require('./fixtures/dbweb-refresh-journey.json'); +import {dbJourney as expected} from './fixtures/dbweb-refresh-journey.js'; const client = createClient(rawProfile, 'public-transport/hafas-client:test', {enrichStations: false}); const {profile} = client; @@ -31,7 +31,7 @@ const opt = { products: {}, }; -tap.test('parses a refresh journey correctly (DB)', (t) => { +tap.test('parses a refresh journey correctly (dbweb)', (t) => { const ctx = {profile, opt, common: null, res}; const journey = profile.parseJourney(ctx, res.verbindungen[0]); diff --git a/test/db-trip.js b/test/dbweb-trip.js similarity index 79% rename from test/db-trip.js rename to test/dbweb-trip.js index 2365d582..64e8cab3 100644 --- a/test/db-trip.js +++ b/test/dbweb-trip.js @@ -6,9 +6,9 @@ const require = createRequire(import.meta.url); import tap from 'tap'; import {createClient} from '../index.js'; -import {profile as rawProfile} from '../p/db/index.js'; -const res = require('./fixtures/db-trip.json'); -import {dbTrip as expected} from './fixtures/db-trip.js'; +import {profile as rawProfile} from '../p/dbweb/index.js'; +const res = require('./fixtures/dbweb-trip.json'); +import {dbwebTrip as expected} from './fixtures/dbweb-trip.js'; const client = createClient(rawProfile, 'public-transport/hafas-client:test', {enrichStations: false}); const {profile} = client; diff --git a/test/e2e/db.js b/test/e2e/db.js index d7d6da3a..f6604c24 100644 --- a/test/e2e/db.js +++ b/test/e2e/db.js @@ -476,7 +476,6 @@ tap.test('locations named Jungfernheide', async (t) => { t.end(); }); -/* tap.test('stop', async (t) => { const s = await client.stop(regensburgHbf); @@ -486,6 +485,7 @@ tap.test('stop', async (t) => { t.end(); }); +/* tap.test('line with additionalName', async (t) => { const {departures} = await client.departures(potsdamHbf, { when, diff --git a/test/e2e/dbregioguide.js b/test/e2e/dbregioguide.js new file mode 100644 index 00000000..9350f936 --- /dev/null +++ b/test/e2e/dbregioguide.js @@ -0,0 +1,499 @@ +import tap from 'tap'; +import isRoughlyEqual from 'is-roughly-equal'; + +import {createWhen} from './lib/util.js'; +import {createClient} from '../../index.js'; +import {profile as dbProfile} from '../../p/dbregioguide/index.js'; +import { + createValidateStation, + createValidateTrip, +} from './lib/validators.js'; +import {createValidateFptfWith as createValidate} from './lib/validate-fptf-with.js'; +import {testJourneysStationToStation} from './lib/journeys-station-to-station.js'; +import {testJourneysStationToAddress} from './lib/journeys-station-to-address.js'; +import {testJourneysStationToPoi} from './lib/journeys-station-to-poi.js'; +import {testEarlierLaterJourneys} from './lib/earlier-later-journeys.js'; +import {testLegCycleAlternatives} from './lib/leg-cycle-alternatives.js'; +import {testRefreshJourney} from './lib/refresh-journey.js'; +import {journeysFailsWithNoProduct} from './lib/journeys-fails-with-no-product.js'; +import {testDepartures} from './lib/departures.js'; +import {testArrivals} from './lib/arrivals.js'; +import {testJourneysWithDetour} from './lib/journeys-with-detour.js'; + +const isObj = o => o !== null && 'object' === typeof o && !Array.isArray(o); +const minute = 60 * 1000; + +const T_MOCK = 1747040400 * 1000; // 2025-05-12T08:00:00+01:00 +const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK); + +const cfg = { + when, + stationCoordsOptional: true, // TODO + products: dbProfile.products, + minLatitude: 46.673100, + maxLatitude: 55.030671, + minLongitude: 6.896517, + maxLongitude: 16.180237, +}; + +const validate = createValidate(cfg); + +const assertValidPrice = (t, p) => { + t.ok(p); + if (p.amount !== null) { + t.equal(typeof p.amount, 'number'); + t.ok(p.amount > 0); + } + if (p.hint !== null) { + t.equal(typeof p.hint, 'string'); + t.ok(p.hint); + } +}; + +const assertValidTickets = (test, tickets) => { + test.ok(Array.isArray(tickets)); + for (let fare of tickets) { + test.equal(typeof fare.name, 'string', 'Mandatory field "name" is missing or not a string'); + test.ok(fare.name); + + test.ok(isObj(fare.priceObj), 'Mandatory field "priceObj" is missing or not an object'); + test.equal(typeof fare.priceObj.amount, 'number', 'Mandatory field "amount" in "priceObj" is missing or not a number'); + test.ok(fare.priceObj.amount > 0); + if ('currency' in fare.priceObj) { + test.equal(typeof fare.priceObj.currency, 'string'); + } + + // Check optional fields + if ('addData' in fare) { + test.equal(typeof fare.addData, 'string'); + } + if ('addDataTicketInfo' in fare) { + test.equal(typeof fare.addDataTicketInfo, 'string'); + } + if ('addDataTicketDetails' in fare) { + test.equal(typeof fare.addDataTicketDetails, 'string'); + } + if ('addDataTravelInfo' in fare) { + test.equal(typeof fare.addDataTravelInfo, 'string'); + } + if ('addDataTravelDetails' in fare) { + test.equal(typeof fare.firstClass, 'boolean'); + } + } +}; + +const client = createClient(dbProfile, 'public-transport/hafas-client:test', {enrichStations: false}); + +const berlinHbf = '8011160'; +const münchenHbf = '8000261'; +const jungfernheide = '8011167'; +const blnSchwedterStr = '732652'; +const westhafen = '8089116'; +const wedding = '8089131'; +const württembergallee = '731084'; +const regensburgHbf = '8000309'; +const blnOstbahnhof = '8010255'; +const blnTiergarten = '8089091'; +const blnJannowitzbrücke = '8089019'; +const potsdamHbf = '8012666'; +const berlinSüdkreuz = '8011113'; +const kölnHbf = '8000207'; + +/* +tap.test('journeys – Berlin Schwedter Str. to München Hbf', async (t) => { + const res = await client.journeys(blnSchwedterStr, münchenHbf, { + results: 4, + departure: when, + stopovers: true, + }); + + await testJourneysStationToStation({ + test: t, + res, + validate, + fromId: blnSchwedterStr, + toId: münchenHbf, + }); + // todo: find a journey where there pricing info is always available + for (let journey of res.journeys) { + if (journey.price) { + assertValidPrice(t, journey.price); + } + if (journey.tickets) { + assertValidTickets(t, journey.tickets); + } + } + t.end(); +}); + +tap.test('refreshJourney – valid tickets', async (t) => { + const T_MOCK = 1710831600 * 1000; // 2024-03-19T08:00:00+01:00 + const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK); + + const journeysRes = await client.journeys(berlinHbf, münchenHbf, { + results: 4, + departure: when, + stopovers: true, + }); + const refreshWithoutTicketsRes = await client.refreshJourney(journeysRes.journeys[0].refreshToken, { + tickets: false, + }); + const refreshWithTicketsRes = await client.refreshJourney(journeysRes.journeys[0].refreshToken, { + tickets: true, + }); + for (let res of [refreshWithoutTicketsRes, refreshWithTicketsRes]) { + if (res.journey.tickets !== undefined) { + assertValidTickets(t, res.journey.tickets); + } + } + + t.end(); +}); + +// todo: journeys, only one product + +tap.test('journeys – fails with no product', async (t) => { + await journeysFailsWithNoProduct({ + test: t, + fetchJourneys: client.journeys, + fromId: blnSchwedterStr, + toId: münchenHbf, + when, + products: dbProfile.products, + }); + t.end(); +}); + +tap.test('Berlin Schwedter Str. to Torfstraße 17', async (t) => { + const torfstr = { + type: 'location', + address: 'Torfstraße 17', + latitude: 52.5416823, + longitude: 13.3491223, + }; + const res = await client.journeys(blnSchwedterStr, torfstr, { + results: 3, + departure: when, + }); + + await testJourneysStationToAddress({ + test: t, + res, + validate, + fromId: blnSchwedterStr, + to: torfstr, + }); + t.end(); +}); + +tap.test('Berlin Schwedter Str. to ATZE Musiktheater', async (t) => { + const atze = { + type: 'location', + id: '991598902', + poi: true, + name: 'Berlin, Atze Musiktheater für Kinder (Kultur und U', + latitude: 52.542417, + longitude: 13.350437, + }; + const res = await client.journeys(blnSchwedterStr, atze, { + results: 3, + departure: when, + }); + + await testJourneysStationToPoi({ + test: t, + res, + validate, + fromId: blnSchwedterStr, + to: atze, + }); + t.end(); +}); + +tap.test('journeys: via works – with detour', async (t) => { + // Going from Westhafen to Wedding via Württembergalle without detour + // is currently impossible. We check if the routing engine computes a detour. + const res = await client.journeys(westhafen, wedding, { + via: württembergallee, + results: 1, + departure: when, + stopovers: true, + }); + + await testJourneysWithDetour({ + test: t, + res, + validate, + detourIds: [württembergallee], + }); + t.end(); +}); + +// todo: walkingSpeed "Berlin - Charlottenburg, Hallerstraße" -> jungfernheide +// todo: without detour + + +// todo: with the DB endpoint, earlierRef/laterRef is missing queries many days in the future +tap.skip('earlier/later journeys, Jungfernheide -> München Hbf', async (t) => { + await testEarlierLaterJourneys({ + test: t, + fetchJourneys: client.journeys, + validate, + fromId: jungfernheide, + toId: münchenHbf, + when, + }); + + t.end(); +}); + +if (!process.env.VCR_MODE) { + tap.test('journeys – leg cycle & alternatives', async (t) => { + await testLegCycleAlternatives({ + test: t, + fetchJourneys: client.journeys, + fromId: blnTiergarten, + toId: blnJannowitzbrücke, + when, + }); + t.end(); + }); +} + +tap.test('refreshJourney', async (t) => { + const T_MOCK = 1710831600 * 1000; // 2024-03-19T08:00:00+01:00 + const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK); + const validate = createValidate({...cfg, when}); + + await testRefreshJourney({ + test: t, + fetchJourneys: client.journeys, + refreshJourney: client.refreshJourney, + validate, + fromId: jungfernheide, + toId: münchenHbf, + when, + }); + t.end(); +}); + +tap.skip('journeysFromTrip – U Mehringdamm to U Naturkundemuseum, reroute to Spittelmarkt.', async (t) => { + const blnMehringdamm = '730939'; + const blnStadtmitte = '732541'; + const blnNaturkundemuseum = '732539'; + const blnSpittelmarkt = '732543'; + + const isU6Leg = leg => leg.line && leg.line.name + && leg.line.name.toUpperCase() + .replace(/\s+/g, '') === 'U6'; + + const sameStopOrStation = (stopA) => (stopB) => { + if (stopA.id && stopB.id && stopA.id === stopB.id) { + return true; + } + const statA = stopA.stat && stopA.stat.id || NaN; + const statB = stopB.stat && stopB.stat.id || NaN; + return statA === statB || stopA.id === statB || stopB.id === statA; + }; + const departureOf = st => Number(new Date(st.departure || st.scheduledDeparture)); + const arrivalOf = st => Number(new Date(st.arrival || st.scheduledArrival)); + + // `journeysFromTrip` only supports queries *right now*, so we can't use `when` as in all + // other tests. To make the test less brittle, we pick a connection that is served all night. 🙄 + const when = new Date(); + const validate = createValidate({...cfg, when}); + + const findTripBetween = async (stopAId, stopBId, products = {}) => { + const {journeys} = await client.journeys(stopAId, stopBId, { + departure: new Date(when - 10 * minute), + transfers: 0, products, + results: 8, stopovers: false, remarks: false, + }); + for (const j of journeys) { + const l = j.legs.find(isU6Leg); + if (!l) { + continue; + } + const t = await client.trip(l.tripId, { + stopovers: true, remarks: false, + }); + + const pastStopovers = t.stopovers + .filter(st => departureOf(st) < Date.now()); // todo: <= ? + const hasStoppedAtA = pastStopovers + .find(sameStopOrStation({id: stopAId})); + const willStopAtB = t.stopovers + .filter(st => arrivalOf(st) > Date.now()) // todo: >= ? + .find(sameStopOrStation({id: stopBId})); + + if (hasStoppedAtA && willStopAtB) { + const prevStopover = maxBy(pastStopovers, departureOf); + return {trip: t, prevStopover}; + } + } + return {trip: null, prevStopover: null}; + }; + + // Find a vehicle from U Mehringdamm to U Stadtmitte (to the north) that is currently + // between these two stations. + const {trip, prevStopover} = await findTripBetween(blnMehringdamm, blnStadtmitte, { + regionalExpress: false, regional: false, suburban: false, + }); + t.ok(trip, 'precondition failed: trip not found'); + t.ok(prevStopover, 'precondition failed: previous stopover missing'); + + // todo: "Error: Suche aus dem Zug: Vor Abfahrt des Zuges" + const newJourneys = await client.journeysFromTrip(trip.id, prevStopover, blnSpittelmarkt, { + results: 3, stopovers: true, remarks: false, + }); + + // Validate with fake prices. + const withFakePrice = (j) => { + const clone = Object.assign({}, j); + clone.price = {amount: 123, currency: 'EUR'}; + return clone; + }; + // todo: there is no such validator! + validate(t, newJourneys.map(withFakePrice), 'journeysFromTrip', 'newJourneys'); + + for (let i = 0; i < newJourneys.length; i++) { + const j = newJourneys[i]; + const n = `newJourneys[${i}]`; + + const legOnTrip = j.legs.find(l => l.tripId === trip.id); + t.ok(legOnTrip, n + ': leg with trip ID not found'); + t.equal(last(legOnTrip.stopovers).stop.id, blnStadtmitte); + } +}); + +tap.test('trip details', async (t) => { + const res = await client.journeys(berlinHbf, münchenHbf, { + results: 1, departure: when, + }); + + const p = res.journeys[0].legs.find(l => !l.walking); + t.ok(p.tripId, 'precondition failed'); + t.ok(p.line.name, 'precondition failed'); + + const tripRes = await client.trip(p.tripId, {when}); + + const validate = createValidate(cfg, { + trip: (cfg) => { + const validateTrip = createValidateTrip(cfg); + const validateTripWithFakeDirection = (val, trip, name) => { + validateTrip(val, { + ...trip, + direction: trip.direction || 'foo', // todo, see #49 + }, name); + }; + return validateTripWithFakeDirection; + }, + }); + validate(t, tripRes, 'tripResult', 'tripRes'); + + t.end(); +}); +*/ + +tap.test('departures at Berlin Schwedter Str.', async (t) => { + const res = await client.departures(blnSchwedterStr, { + duration: 5, when, + }); + + await testDepartures({ + test: t, + res, + validate, + id: blnSchwedterStr, + }); + t.end(); +}); + +tap.test('departures with station object', async (t) => { + const res = await client.departures({ + type: 'station', + id: jungfernheide, + name: 'Berlin Jungfernheide', + location: { + type: 'location', + latitude: 1.23, + longitude: 2.34, + }, + }, {when}); + + validate(t, res, 'departuresResponse', 'res'); + t.end(); +}); + +tap.test('arrivals at Berlin Schwedter Str.', async (t) => { + const res = await client.arrivals(blnSchwedterStr, { + duration: 5, when, + }); + + await testArrivals({ + test: t, + res, + validate, + id: blnSchwedterStr, + }); + t.end(); +}); + +/* +tap.test('nearby Berlin Jungfernheide', async (t) => { + const nearby = await client.nearby({ + type: 'location', + latitude: 52.530273, + longitude: 13.299433, + }, { + results: 2, distance: 400, + }); + + validate(t, nearby, 'locations', 'nearby'); + + t.equal(nearby.length, 2); + + const s0 = nearby[0]; + t.equal(s0.id, jungfernheide); + t.equal(s0.name, 'Berlin Jungfernheide'); + t.ok(isRoughlyEqual(0.0005, s0.location.latitude, 52.530408)); + t.ok(isRoughlyEqual(0.0005, s0.location.longitude, 13.299424)); + t.ok(s0.distance >= 0); + t.ok(s0.distance <= 100); + + t.end(); +}); + +tap.test('locations named Jungfernheide', async (t) => { + const locations = await client.locations('Jungfernheide', { + results: 10, + }); + + validate(t, locations, 'locations', 'locations'); + t.ok(locations.length <= 10); + t.ok(locations.some((l) => { + return l.station && l.station.id === jungfernheide || l.id === jungfernheide; + }), 'Jungfernheide not found'); + + t.end(); +}); + +tap.test('stop', async (t) => { + const s = await client.stop(regensburgHbf); + + validate(t, s, ['stop', 'station'], 'stop'); + t.equal(s.id, regensburgHbf); + + t.end(); +}); + +tap.test('line with additionalName', async (t) => { + const {departures} = await client.departures(potsdamHbf, { + when, + duration: 12 * 60, // 12 minutes + products: {bus: false, suburban: false, tram: false}, + }); + t.ok(departures.some(d => d.line && d.line.additionalName)); + t.end(); +}); +*/ diff --git a/test/e2e/dbweb.js b/test/e2e/dbweb.js new file mode 100644 index 00000000..f90747f9 --- /dev/null +++ b/test/e2e/dbweb.js @@ -0,0 +1,498 @@ +import tap from 'tap'; +import isRoughlyEqual from 'is-roughly-equal'; + +import {createWhen} from './lib/util.js'; +import {createClient} from '../../index.js'; +import {profile as dbProfile} from '../../p/dbweb/index.js'; +import { + createValidateStation, + createValidateTrip, +} from './lib/validators.js'; +import {createValidateFptfWith as createValidate} from './lib/validate-fptf-with.js'; +import {testJourneysStationToStation} from './lib/journeys-station-to-station.js'; +import {testJourneysStationToAddress} from './lib/journeys-station-to-address.js'; +import {testJourneysStationToPoi} from './lib/journeys-station-to-poi.js'; +import {testEarlierLaterJourneys} from './lib/earlier-later-journeys.js'; +import {testLegCycleAlternatives} from './lib/leg-cycle-alternatives.js'; +import {testRefreshJourney} from './lib/refresh-journey.js'; +import {journeysFailsWithNoProduct} from './lib/journeys-fails-with-no-product.js'; +import {testDepartures} from './lib/departures.js'; +import {testArrivals} from './lib/arrivals.js'; +import {testJourneysWithDetour} from './lib/journeys-with-detour.js'; + +const isObj = o => o !== null && 'object' === typeof o && !Array.isArray(o); +const minute = 60 * 1000; + +const T_MOCK = 1747040400 * 1000; // 2025-05-12T08:00:00+01:00 +const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK); + +const cfg = { + when, + stationCoordsOptional: true, // TODO + products: dbProfile.products, + minLatitude: 46.673100, + maxLatitude: 55.030671, + minLongitude: 6.896517, + maxLongitude: 16.180237, +}; + +const validate = createValidate(cfg); + +const assertValidPrice = (t, p) => { + t.ok(p); + if (p.amount !== null) { + t.equal(typeof p.amount, 'number'); + t.ok(p.amount > 0); + } + if (p.hint !== null) { + t.equal(typeof p.hint, 'string'); + t.ok(p.hint); + } +}; + +const assertValidTickets = (test, tickets) => { + test.ok(Array.isArray(tickets)); + for (let fare of tickets) { + test.equal(typeof fare.name, 'string', 'Mandatory field "name" is missing or not a string'); + test.ok(fare.name); + + test.ok(isObj(fare.priceObj), 'Mandatory field "priceObj" is missing or not an object'); + test.equal(typeof fare.priceObj.amount, 'number', 'Mandatory field "amount" in "priceObj" is missing or not a number'); + test.ok(fare.priceObj.amount > 0); + if ('currency' in fare.priceObj) { + test.equal(typeof fare.priceObj.currency, 'string'); + } + + // Check optional fields + if ('addData' in fare) { + test.equal(typeof fare.addData, 'string'); + } + if ('addDataTicketInfo' in fare) { + test.equal(typeof fare.addDataTicketInfo, 'string'); + } + if ('addDataTicketDetails' in fare) { + test.equal(typeof fare.addDataTicketDetails, 'string'); + } + if ('addDataTravelInfo' in fare) { + test.equal(typeof fare.addDataTravelInfo, 'string'); + } + if ('addDataTravelDetails' in fare) { + test.equal(typeof fare.firstClass, 'boolean'); + } + } +}; + +const client = createClient(dbProfile, 'public-transport/hafas-client:test', {enrichStations: false}); + +const berlinHbf = '8011160'; +const münchenHbf = '8000261'; +const jungfernheide = '8011167'; +const blnSchwedterStr = '732652'; +const westhafen = '8089116'; +const wedding = '8089131'; +const württembergallee = '731084'; +const regensburgHbf = '8000309'; +const blnOstbahnhof = '8010255'; +const blnTiergarten = '8089091'; +const blnJannowitzbrücke = '8089019'; +const potsdamHbf = '8012666'; +const berlinSüdkreuz = '8011113'; +const kölnHbf = '8000207'; + +tap.test('journeys – Berlin Schwedter Str. to München Hbf', async (t) => { + const res = await client.journeys(blnSchwedterStr, münchenHbf, { + results: 4, + departure: when, + stopovers: true, + }); + + await testJourneysStationToStation({ + test: t, + res, + validate, + fromId: blnSchwedterStr, + toId: münchenHbf, + }); + // todo: find a journey where there pricing info is always available + for (let journey of res.journeys) { + if (journey.price) { + assertValidPrice(t, journey.price); + } + if (journey.tickets) { + assertValidTickets(t, journey.tickets); + } + } + t.end(); +}); + +tap.test('refreshJourney – valid tickets', async (t) => { + const T_MOCK = 1710831600 * 1000; // 2024-03-19T08:00:00+01:00 + const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK); + + const journeysRes = await client.journeys(berlinHbf, münchenHbf, { + results: 4, + departure: when, + stopovers: true, + }); + const refreshWithoutTicketsRes = await client.refreshJourney(journeysRes.journeys[0].refreshToken, { + tickets: false, + }); + const refreshWithTicketsRes = await client.refreshJourney(journeysRes.journeys[0].refreshToken, { + tickets: true, + }); + for (let res of [refreshWithoutTicketsRes, refreshWithTicketsRes]) { + if (res.journey.tickets !== undefined) { + assertValidTickets(t, res.journey.tickets); + } + } + + t.end(); +}); + +// todo: journeys, only one product + +tap.test('journeys – fails with no product', async (t) => { + await journeysFailsWithNoProduct({ + test: t, + fetchJourneys: client.journeys, + fromId: blnSchwedterStr, + toId: münchenHbf, + when, + products: dbProfile.products, + }); + t.end(); +}); + +tap.test('Berlin Schwedter Str. to Torfstraße 17', async (t) => { + const torfstr = { + type: 'location', + address: 'Torfstraße 17', + latitude: 52.5416823, + longitude: 13.3491223, + }; + const res = await client.journeys(blnSchwedterStr, torfstr, { + results: 3, + departure: when, + }); + + await testJourneysStationToAddress({ + test: t, + res, + validate, + fromId: blnSchwedterStr, + to: torfstr, + }); + t.end(); +}); + +tap.test('Berlin Schwedter Str. to ATZE Musiktheater', async (t) => { + const atze = { + type: 'location', + id: '991598902', + poi: true, + name: 'Berlin, Atze Musiktheater für Kinder (Kultur und U', + latitude: 52.542417, + longitude: 13.350437, + }; + const res = await client.journeys(blnSchwedterStr, atze, { + results: 3, + departure: when, + }); + + await testJourneysStationToPoi({ + test: t, + res, + validate, + fromId: blnSchwedterStr, + to: atze, + }); + t.end(); +}); + +tap.test('journeys: via works – with detour', async (t) => { + // Going from Westhafen to Wedding via Württembergalle without detour + // is currently impossible. We check if the routing engine computes a detour. + const res = await client.journeys(westhafen, wedding, { + via: württembergallee, + results: 1, + departure: when, + stopovers: true, + }); + + await testJourneysWithDetour({ + test: t, + res, + validate, + detourIds: [württembergallee], + }); + t.end(); +}); + +// todo: walkingSpeed "Berlin - Charlottenburg, Hallerstraße" -> jungfernheide +// todo: without detour + + +// todo: with the DB endpoint, earlierRef/laterRef is missing queries many days in the future +tap.skip('earlier/later journeys, Jungfernheide -> München Hbf', async (t) => { + await testEarlierLaterJourneys({ + test: t, + fetchJourneys: client.journeys, + validate, + fromId: jungfernheide, + toId: münchenHbf, + when, + }); + + t.end(); +}); + +if (!process.env.VCR_MODE) { + tap.test('journeys – leg cycle & alternatives', async (t) => { + await testLegCycleAlternatives({ + test: t, + fetchJourneys: client.journeys, + fromId: blnTiergarten, + toId: blnJannowitzbrücke, + when, + }); + t.end(); + }); +} + +tap.test('refreshJourney', async (t) => { + const T_MOCK = 1710831600 * 1000; // 2024-03-19T08:00:00+01:00 + const when = createWhen(dbProfile.timezone, dbProfile.locale, T_MOCK); + const validate = createValidate({...cfg, when}); + + await testRefreshJourney({ + test: t, + fetchJourneys: client.journeys, + refreshJourney: client.refreshJourney, + validate, + fromId: jungfernheide, + toId: münchenHbf, + when, + }); + t.end(); +}); + +/* +tap.skip('journeysFromTrip – U Mehringdamm to U Naturkundemuseum, reroute to Spittelmarkt.', async (t) => { + const blnMehringdamm = '730939'; + const blnStadtmitte = '732541'; + const blnNaturkundemuseum = '732539'; + const blnSpittelmarkt = '732543'; + + const isU6Leg = leg => leg.line && leg.line.name + && leg.line.name.toUpperCase() + .replace(/\s+/g, '') === 'U6'; + + const sameStopOrStation = (stopA) => (stopB) => { + if (stopA.id && stopB.id && stopA.id === stopB.id) { + return true; + } + const statA = stopA.stat && stopA.stat.id || NaN; + const statB = stopB.stat && stopB.stat.id || NaN; + return statA === statB || stopA.id === statB || stopB.id === statA; + }; + const departureOf = st => Number(new Date(st.departure || st.scheduledDeparture)); + const arrivalOf = st => Number(new Date(st.arrival || st.scheduledArrival)); + + // `journeysFromTrip` only supports queries *right now*, so we can't use `when` as in all + // other tests. To make the test less brittle, we pick a connection that is served all night. 🙄 + const when = new Date(); + const validate = createValidate({...cfg, when}); + + const findTripBetween = async (stopAId, stopBId, products = {}) => { + const {journeys} = await client.journeys(stopAId, stopBId, { + departure: new Date(when - 10 * minute), + transfers: 0, products, + results: 8, stopovers: false, remarks: false, + }); + for (const j of journeys) { + const l = j.legs.find(isU6Leg); + if (!l) { + continue; + } + const t = await client.trip(l.tripId, { + stopovers: true, remarks: false, + }); + + const pastStopovers = t.stopovers + .filter(st => departureOf(st) < Date.now()); // todo: <= ? + const hasStoppedAtA = pastStopovers + .find(sameStopOrStation({id: stopAId})); + const willStopAtB = t.stopovers + .filter(st => arrivalOf(st) > Date.now()) // todo: >= ? + .find(sameStopOrStation({id: stopBId})); + + if (hasStoppedAtA && willStopAtB) { + const prevStopover = maxBy(pastStopovers, departureOf); + return {trip: t, prevStopover}; + } + } + return {trip: null, prevStopover: null}; + }; + + // Find a vehicle from U Mehringdamm to U Stadtmitte (to the north) that is currently + // between these two stations. + const {trip, prevStopover} = await findTripBetween(blnMehringdamm, blnStadtmitte, { + regionalExpress: false, regional: false, suburban: false, + }); + t.ok(trip, 'precondition failed: trip not found'); + t.ok(prevStopover, 'precondition failed: previous stopover missing'); + + // todo: "Error: Suche aus dem Zug: Vor Abfahrt des Zuges" + const newJourneys = await client.journeysFromTrip(trip.id, prevStopover, blnSpittelmarkt, { + results: 3, stopovers: true, remarks: false, + }); + + // Validate with fake prices. + const withFakePrice = (j) => { + const clone = Object.assign({}, j); + clone.price = {amount: 123, currency: 'EUR'}; + return clone; + }; + // todo: there is no such validator! + validate(t, newJourneys.map(withFakePrice), 'journeysFromTrip', 'newJourneys'); + + for (let i = 0; i < newJourneys.length; i++) { + const j = newJourneys[i]; + const n = `newJourneys[${i}]`; + + const legOnTrip = j.legs.find(l => l.tripId === trip.id); + t.ok(legOnTrip, n + ': leg with trip ID not found'); + t.equal(last(legOnTrip.stopovers).stop.id, blnStadtmitte); + } +});*/ + +tap.test('trip details', async (t) => { + const res = await client.journeys(berlinHbf, münchenHbf, { + results: 1, departure: when, + }); + + const p = res.journeys[0].legs.find(l => !l.walking); + t.ok(p.tripId, 'precondition failed'); + t.ok(p.line.name, 'precondition failed'); + + const tripRes = await client.trip(p.tripId, {when}); + + const validate = createValidate(cfg, { + trip: (cfg) => { + const validateTrip = createValidateTrip(cfg); + const validateTripWithFakeDirection = (val, trip, name) => { + validateTrip(val, { + ...trip, + direction: trip.direction || 'foo', // todo, see #49 + }, name); + }; + return validateTripWithFakeDirection; + }, + }); + validate(t, tripRes, 'tripResult', 'tripRes'); + + t.end(); +}); + +tap.test('departures at Berlin Schwedter Str.', async (t) => { + const res = await client.departures(blnSchwedterStr, { + duration: 5, when, + }); + + await testDepartures({ + test: t, + res, + validate, + id: blnSchwedterStr, + }); + t.end(); +}); + +tap.test('departures with station object', async (t) => { + const res = await client.departures({ + type: 'station', + id: jungfernheide, + name: 'Berlin Jungfernheide', + location: { + type: 'location', + latitude: 1.23, + longitude: 2.34, + }, + }, {when}); + + validate(t, res, 'departuresResponse', 'res'); + t.end(); +}); + +tap.test('arrivals at Berlin Schwedter Str.', async (t) => { + const res = await client.arrivals(blnSchwedterStr, { + duration: 5, when, + }); + + await testArrivals({ + test: t, + res, + validate, + id: blnSchwedterStr, + }); + t.end(); +}); + +tap.test('nearby Berlin Jungfernheide', async (t) => { + const nearby = await client.nearby({ + type: 'location', + latitude: 52.530273, + longitude: 13.299433, + }, { + results: 2, distance: 400, + }); + + validate(t, nearby, 'locations', 'nearby'); + + t.equal(nearby.length, 2); + + const s0 = nearby[0]; + t.equal(s0.id, jungfernheide); + t.equal(s0.name, 'Berlin Jungfernheide'); + t.ok(isRoughlyEqual(0.0005, s0.location.latitude, 52.530408)); + t.ok(isRoughlyEqual(0.0005, s0.location.longitude, 13.299424)); + t.ok(s0.distance >= 0); + t.ok(s0.distance <= 100); + + t.end(); +}); + +tap.test('locations named Jungfernheide', async (t) => { + const locations = await client.locations('Jungfernheide', { + results: 10, + }); + + validate(t, locations, 'locations', 'locations'); + t.ok(locations.length <= 10); + t.ok(locations.some((l) => { + return l.station && l.station.id === jungfernheide || l.id === jungfernheide; + }), 'Jungfernheide not found'); + + t.end(); +}); + +/* +tap.test('stop', async (t) => { + const s = await client.stop(regensburgHbf); + + validate(t, s, ['stop', 'station'], 'stop'); + t.equal(s.id, regensburgHbf); + + t.end(); +}); + +tap.test('line with additionalName', async (t) => { + const {departures} = await client.departures(potsdamHbf, { + when, + duration: 12 * 60, // 12 minutes + products: {bus: false, suburban: false, tram: false}, + }); + t.ok(departures.some(d => d.line && d.line.additionalName)); + t.end(); +}); +*/ diff --git a/test/fixtures/db-departures-regio-guide.js b/test/fixtures/dbregioguide-departures.js similarity index 100% rename from test/fixtures/db-departures-regio-guide.js rename to test/fixtures/dbregioguide-departures.js diff --git a/test/fixtures/db-departures-regio-guide.json b/test/fixtures/dbregioguide-departures.json similarity index 100% rename from test/fixtures/db-departures-regio-guide.json rename to test/fixtures/dbregioguide-departures.json diff --git a/test/fixtures/db-trip-regio-guide.js b/test/fixtures/dbregioguide-trip.js similarity index 100% rename from test/fixtures/db-trip-regio-guide.js rename to test/fixtures/dbregioguide-trip.js diff --git a/test/fixtures/db-trip-regio-guide.json b/test/fixtures/dbregioguide-trip.json similarity index 100% rename from test/fixtures/db-trip-regio-guide.json rename to test/fixtures/dbregioguide-trip.json diff --git a/test/fixtures/db-departures.js b/test/fixtures/dbweb-departures.js similarity index 99% rename from test/fixtures/db-departures.js rename to test/fixtures/dbweb-departures.js index 30f845d9..08676db7 100644 --- a/test/fixtures/db-departures.js +++ b/test/fixtures/dbweb-departures.js @@ -1,4 +1,4 @@ -const dbDepartures = [ +const dbwebDepartures = [ { tripId: '2|#VN#1#ST#1738610742#PI#0#ZI#1401845#TA#0#DA#50225#1S#801324#1T#1348#LS#801293#LT#1418#PU#80#RT#2#CA#Bus#ZE#-#ZB#Bus -#PC#5#FR#801324#FT#1348#TO#801293#TT#1418#', stop: { @@ -4263,5 +4263,5 @@ const dbDepartures = [ ]; export { - dbDepartures, + dbwebDepartures, }; diff --git a/test/fixtures/db-departures.json b/test/fixtures/dbweb-departures.json similarity index 100% rename from test/fixtures/db-departures.json rename to test/fixtures/dbweb-departures.json diff --git a/test/fixtures/db-journey.js b/test/fixtures/dbweb-journey.js similarity index 99% rename from test/fixtures/db-journey.js rename to test/fixtures/dbweb-journey.js index a252b7b6..95cc75e8 100644 --- a/test/fixtures/db-journey.js +++ b/test/fixtures/dbweb-journey.js @@ -1,4 +1,4 @@ -const dbJourney = { +const dbwebJourney = { type: 'journey', legs: [ { @@ -304,5 +304,5 @@ const dbJourney = { }; export { - dbJourney, + dbwebJourney, }; diff --git a/test/fixtures/db-journey.json b/test/fixtures/dbweb-journey.json similarity index 100% rename from test/fixtures/db-journey.json rename to test/fixtures/dbweb-journey.json diff --git a/test/fixtures/db-refresh-journey.js b/test/fixtures/dbweb-refresh-journey.js similarity index 100% rename from test/fixtures/db-refresh-journey.js rename to test/fixtures/dbweb-refresh-journey.js diff --git a/test/fixtures/db-refresh-journey.json b/test/fixtures/dbweb-refresh-journey.json similarity index 100% rename from test/fixtures/db-refresh-journey.json rename to test/fixtures/dbweb-refresh-journey.json diff --git a/test/fixtures/db-trip.js b/test/fixtures/dbweb-trip.js similarity index 99% rename from test/fixtures/db-trip.js rename to test/fixtures/dbweb-trip.js index 8a933533..a084deba 100644 --- a/test/fixtures/db-trip.js +++ b/test/fixtures/dbweb-trip.js @@ -1,4 +1,4 @@ -const dbTrip = { +const dbwebTrip = { trip: { id: 'foo', origin: { @@ -470,5 +470,5 @@ const dbTrip = { }; export { - dbTrip, + dbwebTrip, }; diff --git a/test/fixtures/db-trip.json b/test/fixtures/dbweb-trip.json similarity index 100% rename from test/fixtures/db-trip.json rename to test/fixtures/dbweb-trip.json diff --git a/test/format/db-arrivals-query.js b/test/format/dbweb-arrivals-query.js similarity index 96% rename from test/format/db-arrivals-query.js rename to test/format/dbweb-arrivals-query.js index 9b26d3a9..ebec948a 100644 --- a/test/format/db-arrivals-query.js +++ b/test/format/dbweb-arrivals-query.js @@ -1,7 +1,7 @@ import tap from 'tap'; import {createClient} from '../../index.js'; -import {profile as rawProfile} from '../../p/db/index.js'; +import {profile as rawProfile} from '../../p/dbweb/index.js'; const client = createClient(rawProfile, 'public-transport/hafas-client:test'); const {profile} = client; diff --git a/test/format/db-journeys-query.js b/test/format/dbweb-journeys-query.js similarity index 95% rename from test/format/db-journeys-query.js rename to test/format/dbweb-journeys-query.js index 4ea19cba..cf23dbf2 100644 --- a/test/format/db-journeys-query.js +++ b/test/format/dbweb-journeys-query.js @@ -1,7 +1,7 @@ import tap from 'tap'; import {createClient} from '../../index.js'; -import {profile as rawProfile} from '../../p/db/index.js'; +import {profile as rawProfile} from '../../p/dbweb/index.js'; import {data as loyaltyCards} from '../../format/loyalty-cards.js'; const client = createClient(rawProfile, 'public-transport/hafas-client:test', {enrichStations: false}); @@ -91,7 +91,7 @@ tap.test('formats a journeys() request correctly (DB)', (t) => { }); -tap.test('formats a journeys() request with BC correctly (DB)', (t) => { +tap.test('formats a journeys() request with BC correctly (dbweb)', (t) => { const ctx = {profile, opt}; const req = profile.formatJourneysReq(ctx, '8098160', '8000284', new Date('2024-12-07T23:50:12+01:00'), true, null); @@ -115,7 +115,7 @@ tap.test('formats a journeys() request with BC correctly (DB)', (t) => { t.end(); }); -tap.test('formats a journeys() request with unlimited transfers (DB)', (t) => { +tap.test('formats a journeys() request with unlimited transfers (dbweb)', (t) => { const _opt = {...opt}; const ctx = {profile, opt: _opt};