mirror of
https://github.com/public-transport/db-vendo-client.git
synced 2025-04-20 23:23:56 +03:00
split dbweb and dbregioguide profiles; add db profile
This commit is contained in:
parent
239c279573
commit
d3707db7a2
36 changed files with 1272 additions and 52 deletions
80
p/db/dynamicProfileData.json
Normal file
80
p/db/dynamicProfileData.json
Normal file
|
@ -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"]
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
|
|
5
p/dbregioguide/base.json
Normal file
5
p/dbregioguide/base.json
Normal file
|
@ -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"
|
||||
}
|
18
p/dbregioguide/index.js
Normal file
18
p/dbregioguide/index.js
Normal file
|
@ -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,
|
||||
};
|
11
p/dbregioguide/trip-req.js
Normal file
11
p/dbregioguide/trip-req.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
const formatTripReq = ({profile, opt}, id) => {
|
||||
return {
|
||||
endpoint: profile.tripEndpoint,
|
||||
path: id,
|
||||
method: 'get',
|
||||
};
|
||||
};
|
||||
|
||||
export {
|
||||
formatTripReq,
|
||||
};
|
|
@ -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"
|
||||
}
|
26
p/dbweb/index.js
Normal file
26
p/dbweb/index.js
Normal file
|
@ -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,
|
||||
};
|
60
test/db-dynamic-handling.js
Normal file
60
test/db-dynamic-handling.js
Normal file
|
@ -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();
|
||||
});
|
|
@ -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;
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
@ -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]);
|
||||
|
|
@ -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]);
|
||||
|
|
@ -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;
|
|
@ -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,
|
||||
|
|
499
test/e2e/dbregioguide.js
Normal file
499
test/e2e/dbregioguide.js
Normal file
|
@ -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();
|
||||
});
|
||||
*/
|
498
test/e2e/dbweb.js
Normal file
498
test/e2e/dbweb.js
Normal file
|
@ -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();
|
||||
});
|
||||
*/
|
|
@ -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,
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
const dbJourney = {
|
||||
const dbwebJourney = {
|
||||
type: 'journey',
|
||||
legs: [
|
||||
{
|
||||
|
@ -304,5 +304,5 @@ const dbJourney = {
|
|||
};
|
||||
|
||||
export {
|
||||
dbJourney,
|
||||
dbwebJourney,
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
const dbTrip = {
|
||||
const dbwebTrip = {
|
||||
trip: {
|
||||
id: 'foo',
|
||||
origin: {
|
||||
|
@ -470,5 +470,5 @@ const dbTrip = {
|
|||
};
|
||||
|
||||
export {
|
||||
dbTrip,
|
||||
dbwebTrip,
|
||||
};
|
|
@ -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;
|
|
@ -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};
|
||||
|
Loading…
Add table
Reference in a new issue