diff --git a/format/products-bitmask.js b/format/products-bitmask.js index 9ec158e0..f28e5a9a 100644 --- a/format/products-bitmask.js +++ b/format/products-bitmask.js @@ -2,6 +2,7 @@ const createFormatBitmask = (modes) => { const formatBitmask = (products) => { + if(Object.keys(products).length === 0) throw new Error('products filter must not be empty') let bitmask = 0 for (let product in products) { if (products[product] === true) bitmask += modes[product].bitmask diff --git a/p/oebb/example.js b/p/oebb/example.js new file mode 100644 index 00000000..9c656063 --- /dev/null +++ b/p/oebb/example.js @@ -0,0 +1,18 @@ +'use strict' + +const createClient = require('../..') +const oebbProfile = require('.') + +const client = createClient(oebbProfile) + +// Wien Westbahnhof to Salzburg Hbf +client.journeys('1291501', '8100002', {results: 1}) +// client.departures('8100002', {duration: 1}) +// client.locations('Salzburg', {results: 2}) +// client.nearby(47.812851, 13.045604, {distance: 60}) +// client.radar(47.827203, 13.001261, 47.773278, 13.07562, {results: 10}) + +.then((data) => { + console.log(require('util').inspect(data, {depth: null})) +}) +.catch(console.error) diff --git a/p/oebb/index.js b/p/oebb/index.js new file mode 100644 index 00000000..9fc74fe3 --- /dev/null +++ b/p/oebb/index.js @@ -0,0 +1,122 @@ +'use strict' + +// todo: https://gist.github.com/anonymous/a5fc856bc80ae7364721943243f934f4#file-haf_config_base-properties-L5 +// todo: https://gist.github.com/anonymous/a5fc856bc80ae7364721943243f934f4#file-haf_config_base-properties-L47-L234 + +const createParseBitmask = require('../../parse/products-bitmask') +const createFormatBitmask = require('../../format/products-bitmask') +const _parseLine = require('../../parse/line') +const _parseLocation = require('../../parse/location') +const _createParseMovement = require('../../parse/movement') + +const products = require('./products') + +const transformReqBody = (body) => { + // todo: necessary headers? + body.client = { + type: 'IPA', + id: 'OEBB', + v: '6000500', + name: 'oebbIPAD_ADHOC', + os: 'iOS 10.3.3' + } + // todo: https://gist.github.com/anonymous/a5fc856bc80ae7364721943243f934f4#file-haf_config_base-properties-L33 shows 1.16 + body.ver = '1.16' + body.auth = {aid: 'OWDL4fE4ixNiPBBm'} + body.lang = 'de' + + return body +} + +const parseLine = (profile, l) => { + const res = _parseLine(profile, l) + + res.mode = res.product = null + if ('class' in res) { + const data = products.bitmasks[parseInt(res.class)] + if (data) { + res.mode = data.mode + res.product = data.product + } + } + + return res +} + +const parseLocation = (profile, l) => { + // ÖBB has some 'stations' **in austria** with no departures/products, + // like station entrances, that are actually POIs. + const res = _parseLocation(profile, l) + if ( + res.type === 'station' && + !res.products && + res.name && + res.id && res.id.length !== 7 + ) { + return Object.assign({ + type: 'location', + id: res.id, + name: res.name + }, res.location) + } + return res +} + +const createParseMovement = (profile, locations, lines, remarks) => { + const _parseMovement = _createParseMovement(profile, locations, lines, remarks) + const parseMovement = (m) => { + const res = _parseMovement(m) + // filter out POIs + // todo: make use of them, as some of them specify fare zones + res.nextStops = res.nextStops.filter(s => s.type === 'station') + res.frames = res.frames.filter((f) => { + return f.origin.type !== 'location' && f.destination.type !== 'location' + }) + return res + } + return parseMovement +} + +const defaultProducts = { + nationalExp: true, + national: true, + interregional: true, + regional: true, + suburban: true, + bus: true, + ferry: true, + subway: true, + tram: true, + onCall: true +} +const formatBitmask = createFormatBitmask(products) +const formatProducts = (products) => { + products = Object.assign(Object.create(null), defaultProducts, products) + return { + type: 'PROD', + mode: 'INC', + value: formatBitmask(products) + '' + } +} + +const oebbProfile = { + locale: 'de-AT', + timezone: 'Europe/Vienna', + // todo: there is also https://beta.verkehrsauskunft.at/bin/mgate.exe + endpoint: 'http://fahrplan.oebb.at/bin/mgate.exe', + transformReqBody, + + products: products.allProducts, + + parseProducts: createParseBitmask(products.bitmasks), + parseLine, + parseLocation, + parseMovement: createParseMovement, + + formatProducts, + + journeyLeg: true, + radar: true +} + +module.exports = oebbProfile diff --git a/p/oebb/products.js b/p/oebb/products.js new file mode 100644 index 00000000..646d4588 --- /dev/null +++ b/p/oebb/products.js @@ -0,0 +1,112 @@ +'use strict' + +const p = { + nationalExp: { + bitmask: 1, + name: 'InterCityExpress & RailJet', + short: 'ICE/RJ', + mode: 'train', + product: 'nationalExp' + }, + national: { + bitmask: 2 + 4, + name: 'InterCity & EuroCity', + short: 'IC/EC', + mode: 'train', + product: 'national' + }, + interregional: { + bitmask: 8 + 4096, + name: 'Durchgangszug & EuroNight', + short: 'D/EN', + mode: 'train', + product: 'interregional' + }, + regional: { + bitmask: 16, + name: 'Regional & RegionalExpress', + short: 'R/REX', + mode: 'train', + product: 'regional' + }, + suburban: { + bitmask: 32, + name: 'S-Bahn', + short: 'S', + mode: 'train', + product: 'suburban' + }, + bus: { + bitmask: 64, + name: 'Bus', + short: 'B', + mode: 'bus', + product: 'bus' + }, + ferry: { + bitmask: 128, + name: 'Ferry', + short: 'F', + mode: 'watercraft', + product: 'ferry' + }, + subway: { + bitmask: 256, + name: 'U-Bahn', + short: 'U', + mode: 'train', + product: 'subway' + }, + tram: { + bitmask: 512, + name: 'Tram', + short: 'T', + mode: 'train', + product: 'tram' + }, + onCall: { + bitmask: 2048, + name: 'On-call transit', + short: 'on-call', + mode: null, // todo + product: 'onCall' + }, + unknown: { + bitmask: 0, + name: 'unknown', + short: '?', + product: 'unknown' + } +} + +p.bitmasks = [] +p.bitmasks[1] = p.nationalExp +p.bitmasks[2] = p.national +p.bitmasks[4] = p.national +p.bitmasks[2+4] = p.national +p.bitmasks[8] = p.interregional +p.bitmasks[16] = p.regional +p.bitmasks[32] = p.suburban +p.bitmasks[64] = p.bus +p.bitmasks[128] = p.ferry +p.bitmasks[256] = p.subway +p.bitmasks[512] = p.tram +p.bitmasks[1024] = p.unknown +p.bitmasks[2048] = p.onCall +p.bitmasks[4096] = p.interregional +p.bitmasks[8+4096] = p.interregional + +p.allProducts = [ + p.nationalExp, + p.national, + p.interregional, + p.regional, + p.suburban, + p.bus, + p.ferry, + p.subway, + p.tram, + p.onCall +] + +module.exports = p diff --git a/p/oebb/readme.md b/p/oebb/readme.md new file mode 100644 index 00000000..f45b9c28 --- /dev/null +++ b/p/oebb/readme.md @@ -0,0 +1,19 @@ +# ÖBB profile for `hafas-client` + +[*Österreichische Bundesbahnen (ÖBB)*](https://en.wikipedia.org/wiki/Austrian_Federal_Railways) is the largest Austrian long-distance public transport company. This profile adds *ÖBB*-specific customizations to `hafas-client`. Consider using [`oebb-hafas`](https://github.com/juliuste/oebb-hafas#oebb-hafas), to always get the customized client right away. + +## Usage + +```js +const createClient = require('hafas-client') +const oebbProfile = require('hafas-client/p/oebb') + +// create a client with ÖBB profile +const client = createClient(oebbProfile) +``` + + +## Customisations + +- parses *ÖBB*-specific products (such as *RailJet*) +- parses invalid empty stations from the API as [`location`](https://github.com/public-transport/friendly-public-transport-format/blob/1.0.1/spec/readme.md#location-objects)s diff --git a/package.json b/package.json index 611d2075..fe3c998a 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "docs" ], "author": "Jannis R ", + "contributors": ["Julius Tens "], "homepage": "https://github.com/derhuerst/hafas-client", "repository": "derhuerst/hafas-client", "bugs": "https://github.com/derhuerst/hafas-client/issues", diff --git a/parse/journey-leg.js b/parse/journey-leg.js index cf8a4f7a..56630d15 100644 --- a/parse/journey-leg.js +++ b/parse/journey-leg.js @@ -43,7 +43,9 @@ const createParseJourneyLeg = (profile, stations, lines, remarks) => { if (passed && pt.jny.stopL) { const parse = profile.parseStopover(profile, stations, lines, remarks, j) - res.passed = pt.jny.stopL.map(parse) + const passedStations = pt.jny.stopL.map(parse) + // filter stations the train passes without stopping, as this doesn't comply with fptf (yet) + res.passed = passedStations.filter((x) => !x.passBy) } if (Array.isArray(pt.jny.remL)) { for (let remark of pt.jny.remL) applyRemark(j, remark) diff --git a/parse/products-bitmask.js b/parse/products-bitmask.js index 4880da7d..04f9b89a 100644 --- a/parse/products-bitmask.js +++ b/parse/products-bitmask.js @@ -5,7 +5,7 @@ const createParseBitmask = (bitmasks) => { const products = {} let i = 1 do { - products[bitmasks[i].product] = !!(bitmask & i) + products[bitmasks[i].product] = products[bitmasks[i].product] || !!(bitmask & i) i *= 2 } while (bitmasks[i] && bitmasks[i].product) return products diff --git a/parse/stopover.js b/parse/stopover.js index 216d9c1b..e17e1be3 100644 --- a/parse/stopover.js +++ b/parse/stopover.js @@ -16,6 +16,9 @@ const createParseStopover = (profile, stations, lines, remarks, connection) => { res.departure = dep.toISO() } + // mark stations the train passes without stopping + if(st.dInS === false && st.aOutS === false) res.passBy = true + // todo: follow public-transport/friendly-public-transport-format#27 here // see also derhuerst/vbb-rest#19 if (st.aCncl) { diff --git a/readme.md b/readme.md index 16fc85f5..8bcba40f 100644 --- a/readme.md +++ b/readme.md @@ -6,6 +6,7 @@ HAFAS endpoint | wrapper library? | docs | example code | source code ---------------|------------------|------|---------|------------ [Deutsche Bahn](https://en.wikipedia.org/wiki/Deutsche_Bahn) | [`vbb-hafas`](https://github.com/derhuerst/vbb-hafas), which has additional features | [docs](p/db/readme.md) | [example code](p/db/example.js) | [src](p/db/index.js) [Berlin & Brandenburg public transport](https://en.wikipedia.org/wiki/Verkehrsverbund_Berlin-Brandenburg) | [`db-hafas`](https://github.com/derhuerst/db-hafas), which has additional features | [docs](p/vbb/readme.md) | [example code](p/vbb/example.js) | [src](p/vbb/index.js) +[Österreichische Bundesbahnen (ÖBB)](https://en.wikipedia.org/wiki/Austrian_Federal_Railways) | – | [docs](p/oebb/readme.md) | [example code](p/oebb/example.js) | [src](p/oebb/index.js) [![npm version](https://img.shields.io/npm/v/hafas-client.svg)](https://www.npmjs.com/package/hafas-client) [![build status](https://img.shields.io/travis/derhuerst/hafas-client.svg)](https://travis-ci.org/derhuerst/hafas-client) @@ -30,7 +31,7 @@ npm install hafas-client ## API - [`journeys(from, to, [opt])`](docs/journeys.md) – get journeys between locations -- [`journeyLeg(ref, name, [opt])`](docs/journey-leg.md) – get details for a leg of a journey +- [`journeyLeg(ref, lineName, [opt])`](docs/journey-leg.md) – get details for a leg of a journey - [`departures(station, [opt])`](docs/departures.md) – query the next departures at a station - [`locations(query, [opt])`](docs/locations.md) – find stations, POIs and addresses - [`nearby(location, [opt])`](docs/nearby.md) – show stations & POIs around diff --git a/test/db.js b/test/db.js index 9f9801d6..e1d07c00 100644 --- a/test/db.js +++ b/test/db.js @@ -16,9 +16,11 @@ const { assertValidLocation, assertValidLine, assertValidStopover, - when, isValidWhen + createWhen, assertValidWhen } = require('./util.js') +const when = createWhen('Europe/Berlin', 'de-DE') + const assertValidStationProducts = (t, p) => { t.ok(p) t.equal(typeof p.nationalExp, 'boolean') @@ -106,7 +108,7 @@ test('Berlin Jungfernheide to München Hbf', co.wrap(function* (t) { if (journey.origin.products) { assertValidProducts(t, journey.origin.products) } - t.ok(isValidWhen(journey.departure)) + assertValidWhen(t, journey.departure, when) assertValidStation(t, journey.destination) assertValidStationProducts(t, journey.origin.products) @@ -116,7 +118,7 @@ test('Berlin Jungfernheide to München Hbf', co.wrap(function* (t) { if (journey.destination.products) { assertValidProducts(t, journey.destination.products) } - t.ok(isValidWhen(journey.arrival)) + assertValidWhen(t, journey.arrival, when) t.ok(Array.isArray(journey.legs)) t.ok(journey.legs.length > 0, 'no legs') @@ -127,7 +129,7 @@ test('Berlin Jungfernheide to München Hbf', co.wrap(function* (t) { if (!(yield findStation(leg.origin.id))) { console.error('unknown station', leg.origin.id, leg.origin.name) } - t.ok(isValidWhen(leg.departure)) + assertValidWhen(t, leg.departure, when) t.equal(typeof leg.departurePlatform, 'string') assertValidStation(t, leg.destination) @@ -135,7 +137,7 @@ test('Berlin Jungfernheide to München Hbf', co.wrap(function* (t) { if (!(yield findStation(leg.destination.id))) { console.error('unknown station', leg.destination.id, leg.destination.name) } - t.ok(isValidWhen(leg.arrival)) + assertValidWhen(t, leg.arrival, when) t.equal(typeof leg.arrivalPlatform, 'string') assertValidLine(t, leg.line) @@ -166,8 +168,8 @@ test('Berlin Jungfernheide to Torfstraße 17', co.wrap(function* (t) { console.error('unknown station', leg.origin.id, leg.origin.name) } if (leg.origin.products) assertValidProducts(t, leg.origin.products) - t.ok(isValidWhen(leg.departure)) - t.ok(isValidWhen(leg.arrival)) + assertValidWhen(t, leg.departure, when) + assertValidWhen(t, leg.arrival, when) const d = leg.destination assertValidAddress(t, d) @@ -195,8 +197,8 @@ test('Berlin Jungfernheide to ATZE Musiktheater', co.wrap(function* (t) { console.error('unknown station', leg.origin.id, leg.origin.name) } if (leg.origin.products) assertValidProducts(t, leg.origin.products) - t.ok(isValidWhen(leg.departure)) - t.ok(isValidWhen(leg.arrival)) + assertValidWhen(t, leg.departure, when) + assertValidWhen(t, leg.arrival, when) const d = leg.destination assertValidPoi(t, d) @@ -220,7 +222,7 @@ test('departures at Berlin Jungfernheide', co.wrap(function* (t) { console.error('unknown station', dep.station.id, dep.station.name) } if (dep.station.products) assertValidProducts(t, dep.station.products) - t.ok(isValidWhen(dep.when)) + assertValidWhen(t, dep.when, when) } t.end() diff --git a/test/index.js b/test/index.js index 85c39e86..5675edb4 100644 --- a/test/index.js +++ b/test/index.js @@ -2,3 +2,4 @@ require('./db') require('./vbb') +require('./oebb') diff --git a/test/oebb.js b/test/oebb.js new file mode 100644 index 00000000..a9630c53 --- /dev/null +++ b/test/oebb.js @@ -0,0 +1,377 @@ +'use strict' + +// todo +// const getStations = require('db-stations').full +const tapePromise = require('tape-promise').default +const tape = require('tape') +const co = require('co') +const isRoughlyEqual = require('is-roughly-equal') + +const createClient = require('..') +const oebbProfile = require('../p/oebb') +const products = require('../p/oebb/products') +const { + assertValidStation, + assertValidPoi, + assertValidAddress, + assertValidLocation, + assertValidLine, + assertValidStopover, + hour, createWhen, assertValidWhen +} = require('./util.js') + +const when = createWhen('Europe/Vienna', 'de-AT') + +const assertValidStationProducts = (t, p) => { + t.ok(p) + t.equal(typeof p.nationalExp, 'boolean') + t.equal(typeof p.national, 'boolean') + t.equal(typeof p.interregional, 'boolean') + t.equal(typeof p.regional, 'boolean') + t.equal(typeof p.suburban, 'boolean') + t.equal(typeof p.bus, 'boolean') + t.equal(typeof p.ferry, 'boolean') + t.equal(typeof p.subway, 'boolean') + t.equal(typeof p.tram, 'boolean') + t.equal(typeof p.onCall, 'boolean') +} + +// todo +// const findStation = (id) => new Promise((yay, nay) => { +// const stations = getStations() +// stations +// .once('error', nay) +// .on('data', (s) => { +// if ( +// s.id === id || +// (s.additionalIds && s.additionalIds.includes(id)) +// ) { +// yay(s) +// stations.destroy() +// } +// }) +// .once('end', yay) +// }) + +const isSalzburgHbf = (s) => { + return s.type === 'station' && + (s.id === '008100002' || s.id === '8100002') && + s.name === 'Salzburg Hbf' && + s.location && + isRoughlyEqual(s.location.latitude, 47.812851, .0005) && + isRoughlyEqual(s.location.longitude, 13.045604, .0005) +} + +const assertIsSalzburgHbf = (t, s) => { + t.equal(s.type, 'station') + t.ok(s.id === '008100002' || s.id === '8100002', 'id should be 8100002') + t.equal(s.name, 'Salzburg Hbf') + t.ok(s.location) + t.ok(isRoughlyEqual(s.location.latitude, 47.812851, .0005)) + t.ok(isRoughlyEqual(s.location.longitude, 13.045604, .0005)) +} + +// todo: this doesnt seem to work +// todo: DRY with assertValidStationProducts +const assertValidProducts = (t, p) => { + for (let k of Object.keys(products)) { + t.ok('boolean', typeof products[k], 'mode ' + k + ' must be a boolean') + } +} + +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 test = tapePromise(tape) +const client = createClient(oebbProfile) + +test('Salzburg Hbf to Wien Westbahnhof', co.wrap(function* (t) { + const salzburgHbf = '8100002' + const wienWestbahnhof = '1291501' + const journeys = yield client.journeys(salzburgHbf, wienWestbahnhof, { + when, passedStations: true + }) + + t.ok(Array.isArray(journeys)) + t.ok(journeys.length > 0, 'no journeys') + for (let journey of journeys) { + assertValidStation(t, journey.origin) + assertValidStationProducts(t, journey.origin.products) + // todo + // if (!(yield findStation(journey.origin.id))) { + // console.error('unknown station', journey.origin.id, journey.origin.name) + // } + if (journey.origin.products) { + assertValidProducts(t, journey.origin.products) + } + assertValidWhen(t, journey.departure, when) + + assertValidStation(t, journey.destination) + assertValidStationProducts(t, journey.origin.products) + // todo + // if (!(yield findStation(journey.origin.id))) { + // console.error('unknown station', journey.destination.id, journey.destination.name) + // } + if (journey.destination.products) { + assertValidProducts(t, journey.destination.products) + } + assertValidWhen(t, journey.arrival, when) + + t.ok(Array.isArray(journey.legs)) + t.ok(journey.legs.length > 0, 'no legs') + const leg = journey.legs[0] + + assertValidStation(t, leg.origin) + assertValidStationProducts(t, leg.origin.products) + // todo + // if (!(yield findStation(leg.origin.id))) { + // console.error('unknown station', leg.origin.id, leg.origin.name) + // } + assertValidWhen(t, leg.departure, when) + t.equal(typeof leg.departurePlatform, 'string') + + assertValidStation(t, leg.destination) + assertValidStationProducts(t, leg.origin.products) + // todo + // if (!(yield findStation(leg.destination.id))) { + // console.error('unknown station', leg.destination.id, leg.destination.name) + // } + assertValidWhen(t, leg.arrival, when) + t.equal(typeof leg.arrivalPlatform, 'string') + + assertValidLine(t, leg.line) + + t.ok(Array.isArray(leg.passed)) + for (let stopover of leg.passed) assertValidStopover(t, stopover) + + if (journey.price) assertValidPrice(t, journey.price) + } + + t.end() +})) + +test('Salzburg Hbf to 1220 Wien, Wagramer Straße 5', co.wrap(function* (t) { + const salzburgHbf = '8100002' + const wagramerStr = { + type: 'location', + latitude: 48.236216, + longitude: 16.425863, + address: '1220 Wien, Wagramer Straße 5' + } + + const journeys = yield client.journeys(salzburgHbf, wagramerStr, {when}) + + t.ok(Array.isArray(journeys)) + t.ok(journeys.length >= 1, 'no journeys') + const journey = journeys[0] + const firstLeg = journey.legs[0] + const lastLeg = journey.legs[journey.legs.length - 1] + + assertValidStation(t, firstLeg.origin) + assertValidStationProducts(t, firstLeg.origin.products) + // todo + // if (!(yield findStation(leg.origin.id))) { + // console.error('unknown station', leg.origin.id, leg.origin.name) + // } + if (firstLeg.origin.products) assertValidProducts(t, firstLeg.origin.products) + assertValidWhen(t, firstLeg.departure, when) + assertValidWhen(t, firstLeg.arrival, when) + assertValidWhen(t, lastLeg.departure, when) + assertValidWhen(t, lastLeg.arrival, when) + + const d = lastLeg.destination + assertValidAddress(t, d) + t.equal(d.address, '1220 Wien, Wagramer Straße 5') + t.ok(isRoughlyEqual(.0001, d.latitude, 48.236216)) + t.ok(isRoughlyEqual(.0001, d.longitude, 16.425863)) + + t.end() +})) + +test('Albertina to Salzburg Hbf', co.wrap(function* (t) { + const albertina = { + type: 'location', + latitude: 48.204699, + longitude: 16.368404, + name: 'Albertina', + id: '975900003' + } + const salzburgHbf = '8100002' + const journeys = yield client.journeys(albertina, salzburgHbf, {when}) + + t.ok(Array.isArray(journeys)) + t.ok(journeys.length >= 1, 'no journeys') + const journey = journeys[0] + const firstLeg = journey.legs[0] + const lastLeg = journey.legs[journey.legs.length - 1] + + const o = firstLeg.origin + assertValidPoi(t, o) + t.equal(o.name, 'Albertina') + t.ok(isRoughlyEqual(.0001, o.latitude, 48.204699)) + t.ok(isRoughlyEqual(.0001, o.longitude, 16.368404)) + + assertValidWhen(t, firstLeg.departure, when) + assertValidWhen(t, firstLeg.arrival, when) + assertValidWhen(t, lastLeg.departure, when) + assertValidWhen(t, lastLeg.arrival, when) + + assertValidStation(t, lastLeg.destination) + assertValidStationProducts(t, lastLeg.destination.products) + if (lastLeg.destination.products) assertValidProducts(t, lastLeg.destination.products) + // todo + // if (!(yield findStation(leg.destination.id))) { + // console.error('unknown station', leg.destination.id, leg.destination.name) + // } + + t.end() +})) + +test('leg details for Wien Westbahnhof to München Hbf', co.wrap(function* (t) { + const wienWestbahnhof = '1291501' + const muenchenHbf = '8000261' + const journeys = yield client.journeys(wienWestbahnhof, muenchenHbf, { + results: 1, when + }) + + const p = journeys[0].legs[0] + t.ok(p.id, 'precondition failed') + t.ok(p.line.name, 'precondition failed') + const leg = yield client.journeyLeg(p.id, p.line.name, {when}) + + t.equal(typeof leg.id, 'string') + t.ok(leg.id) + + assertValidLine(t, leg.line) + + t.equal(typeof leg.direction, 'string') + t.ok(leg.direction) + + t.ok(Array.isArray(leg.passed)) + for (let passed of leg.passed) assertValidStopover(t, passed) + + t.end() +})) + +test('departures at Salzburg Hbf', co.wrap(function* (t) { + const salzburgHbf = '8100002' + const deps = yield client.departures(salzburgHbf, { + duration: 5, when + }) + + t.ok(Array.isArray(deps)) + for (let dep of deps) { + assertValidStation(t, dep.station) + assertValidStationProducts(t, dep.station.products) + // todo + // if (!(yield findStation(dep.station.id))) { + // console.error('unknown station', dep.station.id, dep.station.name) + // } + if (dep.station.products) assertValidProducts(t, dep.station.products) + assertValidWhen(t, dep.when, when) + } + + t.end() +})) + +test('nearby Salzburg Hbf', co.wrap(function* (t) { + const salzburgHbfPosition = { + longitude: 13.045604, + latitude: 47.812851 + } + const nearby = yield client.nearby(salzburgHbfPosition.latitude, salzburgHbfPosition.longitude, { + results: 2, distance: 400 + }) + + t.ok(Array.isArray(nearby)) + t.equal(nearby.length, 2) + + assertIsSalzburgHbf(t, nearby[0]) + t.ok(nearby[0].distance >= 0) + t.ok(nearby[0].distance <= 100) + + for (let n of nearby) { + if (n.type === 'station') assertValidStation(t, n) + else assertValidLocation(t, n) + } + + t.end() +})) + +test('locations named Salzburg', co.wrap(function* (t) { + const locations = yield client.locations('Salzburg', { + results: 10 + }) + + t.ok(Array.isArray(locations)) + t.ok(locations.length > 0) + t.ok(locations.length <= 10) + + for (let l of locations) { + if (l.type === 'station') assertValidStation(t, l) + else assertValidLocation(t, l) + } + t.ok(locations.some(isSalzburgHbf)) + + t.end() +})) + +test('radar Salzburg', co.wrap(function* (t) { + const vehicles = yield client.radar(47.827203, 13.001261, 47.773278, 13.07562, { + duration: 5 * 60, when + }) + + t.ok(Array.isArray(vehicles)) + t.ok(vehicles.length > 0) + for (let v of vehicles) { + + // todo + // t.ok(findStation(v.direction)) + assertValidLine(t, v.line) + + t.equal(typeof v.location.latitude, 'number') + t.ok(v.location.latitude <= 52, 'vehicle is too far away') + t.ok(v.location.latitude >= 42, 'vehicle is too far away') + t.equal(typeof v.location.longitude, 'number') + t.ok(v.location.longitude >= 10, 'vehicle is too far away') + t.ok(v.location.longitude <= 16, 'vehicle is too far away') + + t.ok(Array.isArray(v.nextStops)) + for (let st of v.nextStops) { + assertValidStopover(t, st, true) + + if (st.arrival) { + t.equal(typeof st.arrival, 'string') + const arr = +new Date(st.arrival) + // note that this can be an ICE train + t.ok(isRoughlyEqual(14 * hour, +when, arr)) + } + if (st.departure) { + t.equal(typeof st.departure, 'string') + const dep = +new Date(st.departure) + t.ok(isRoughlyEqual(14 * hour, +when, dep)) + } + } + + t.ok(Array.isArray(v.frames)) + for (let f of v.frames) { + assertValidStation(t, f.origin, true) + // can contain stations in germany which don't have a products property, would break + // assertValidStationProducts(t, f.origin.products) + assertValidStation(t, f.destination, true) + // can contain stations in germany which don't have a products property, would break + // assertValidStationProducts(t, f.destination.products) + t.equal(typeof f.t, 'number') + } + } + t.end() +})) diff --git a/test/util.js b/test/util.js index 6a374bac..fbd0d8c5 100644 --- a/test/util.js +++ b/test/util.js @@ -98,18 +98,20 @@ const hour = 60 * 60 * 1000 const week = 7 * 24 * hour // next Monday 10 am -const when = DateTime.fromMillis(Date.now(), { - zone: 'Europe/Berlin', - locale: 'de-DE' -}).startOf('week').plus({weeks: 1, hours: 10}).toJSDate() -const isValidWhen = (w) => { - const ts = +new Date(w) +const createWhen = (timezone, locale) => { + return DateTime.fromMillis(Date.now(), { + zone: timezone, + locale, + }).startOf('week').plus({weeks: 1, hours: 10}).toJSDate() +} +const isValidWhen = (actual, expected) => { + const ts = +new Date(actual) if (Number.isNaN(ts)) return false - return isRoughlyEqual(12 * hour, +when, ts) + return isRoughlyEqual(12 * hour, +expected, ts) } -const assertValidWhen = (t, w) => { - t.ok(isValidWhen(w), 'invalid when') +const assertValidWhen = (t, actual, expected) => { + t.ok(isValidWhen(actual, expected), 'invalid when') } const assertValidTicket = (t, ti) => { @@ -151,6 +153,6 @@ module.exports = { assertValidLine, isValidDateTime, assertValidStopover, - hour, when, isValidWhen, assertValidWhen, + hour, createWhen, isValidWhen, assertValidWhen, assertValidTicket } diff --git a/test/vbb.js b/test/vbb.js index 71371a57..e613bdf7 100644 --- a/test/vbb.js +++ b/test/vbb.js @@ -17,11 +17,13 @@ const { assertValidLocation, assertValidLine: _assertValidLine, assertValidStopover, - hour, when, + hour, createWhen, assertValidWhen, assertValidTicket } = require('./util') +const when = createWhen('Europe/Berlin', 'de-DE') + const assertValidStation = (t, s, coordsOptional = false) => { _assertValidStation(t, s, coordsOptional) t.equal(s.name, shorten(s.name)) @@ -70,12 +72,12 @@ test('journeys – station to station', co.wrap(function* (t) { assertValidStationProducts(t, journey.origin.products) t.ok(journey.origin.name.indexOf('(Berlin)') === -1) t.strictEqual(journey.origin.id, spichernstr) - assertValidWhen(t, journey.departure) + assertValidWhen(t, journey.departure, when) assertValidStation(t, journey.destination) assertValidStationProducts(t, journey.destination.products) t.strictEqual(journey.destination.id, amrumerStr) - assertValidWhen(t, journey.arrival) + assertValidWhen(t, journey.arrival, when) t.ok(Array.isArray(journey.legs)) t.strictEqual(journey.legs.length, 1) @@ -87,12 +89,12 @@ test('journeys – station to station', co.wrap(function* (t) { assertValidStationProducts(t, leg.origin.products) t.ok(leg.origin.name.indexOf('(Berlin)') === -1) t.strictEqual(leg.origin.id, spichernstr) - assertValidWhen(t, leg.departure) + assertValidWhen(t, leg.departure, when) assertValidStation(t, leg.destination) assertValidStationProducts(t, leg.destination.products) t.strictEqual(leg.destination.id, amrumerStr) - assertValidWhen(t, leg.arrival) + assertValidWhen(t, leg.arrival, when) assertValidLine(t, leg.line) t.ok(findStation(leg.direction)) @@ -198,14 +200,14 @@ test('journeys – station to address', co.wrap(function* (t) { assertValidStation(t, leg.origin) assertValidStationProducts(t, leg.origin.products) - assertValidWhen(t, leg.departure) + assertValidWhen(t, leg.departure, when) const dest = leg.destination assertValidAddress(t, dest) t.strictEqual(dest.address, 'Torfstraße 17') t.ok(isRoughlyEqual(.0001, dest.latitude, 52.5416823)) t.ok(isRoughlyEqual(.0001, dest.longitude, 13.3491223)) - assertValidWhen(t, leg.arrival) + assertValidWhen(t, leg.arrival, when) t.end() })) @@ -225,14 +227,14 @@ test('journeys – station to POI', co.wrap(function* (t) { assertValidStation(t, leg.origin) assertValidStationProducts(t, leg.origin.products) - assertValidWhen(t, leg.departure) + assertValidWhen(t, leg.departure, when) const dest = leg.destination assertValidPoi(t, dest) t.strictEqual(dest.name, 'ATZE Musiktheater') t.ok(isRoughlyEqual(.0001, dest.latitude, 52.543333)) t.ok(isRoughlyEqual(.0001, dest.longitude, 13.351686)) - assertValidWhen(t, leg.arrival) + assertValidWhen(t, leg.arrival, when) t.end() })) @@ -253,7 +255,7 @@ test('departures', co.wrap(function* (t) { assertValidStationProducts(t, dep.station.products) t.strictEqual(dep.station.id, spichernstr) - assertValidWhen(t, dep.when) + assertValidWhen(t, dep.when, when) t.ok(findStation(dep.direction)) assertValidLine(t, dep.line) }