merge derhuerst/master into jfilter/master

This commit is contained in:
Jannis R 2018-03-12 22:34:40 +01:00
commit 47e26163e5
No known key found for this signature in database
GPG key ID: 0FE83946296A88A5
18 changed files with 509 additions and 193 deletions

View file

@ -41,6 +41,8 @@ With `opt`, you can override the default options, which look like this:
```js ```js
{ {
when: new Date(), when: new Date(),
earlierThan: null, // ref to get journeys earlier than the last query
laterThan: null, // ref to get journeys later than the last query
results: 5, // how many journeys? results: 5, // how many journeys?
via: null, // let journeys pass this station via: null, // let journeys pass this station
passedStations: false, // return stations on the way? passedStations: false, // return stations on the way?
@ -85,7 +87,8 @@ client.journeys('900000003201', '900000100008', {
The response may look like this: The response may look like this:
```js ```js
[ { [
{
legs: [ { legs: [ {
id: '1|31041|35|86|17122017', id: '1|31041|35|86|17122017',
origin: { origin: {
@ -201,7 +204,10 @@ The response may look like this:
}, },
arrival: '2017-12-17T19:47:00.000+01:00', arrival: '2017-12-17T19:47:00.000+01:00',
arrivalDelay: 30 arrivalDelay: 30
} ] },
earlierRef: '…', // use with the `earlierThan` option
laterRef: '…' // use with the `laterThan` option
]
``` ```
Some [profiles](../p) are able to parse the ticket information, if returned by the API. For example, if you pass `tickets: true` with the [VBB profile](../p/vbb), each `journey` will have a tickets array that looks like this: Some [profiles](../p) are able to parse the ticket information, if returned by the API. For example, if you pass `tickets: true` with the [VBB profile](../p/vbb), each `journey` will have a tickets array that looks like this:
@ -242,3 +248,31 @@ Some [profiles](../p) are able to parse the ticket information, if returned by t
``` ```
If a journey leg has been cancelled, a `cancelled: true` will be added. Also, `departure`/`departureDelay`/`departurePlatform` and `arrival`/`arrivalDelay`/`arrivalPlatform` will be `null`. If a journey leg has been cancelled, a `cancelled: true` will be added. Also, `departure`/`departureDelay`/`departurePlatform` and `arrival`/`arrivalDelay`/`arrivalPlatform` will be `null`.
To get more journeys earlier/later than the current set of results, pass `journeys.earlierRef`/`journeys.laterRef` into `opt.earlierThan`/`opt.laterThan`. For example, query *later* journeys as follows:
```js
const hbf = '900000003201'
const heinrichHeineStr = '900000100008'
client.journeys(hbf, heinrichHeineStr)
.then((journeys) => {
const lastJourney = journeys[journeys.length - 1]
console.log('departure of last journey', lastJourney.departure)
// get later journeys
return client.journeys(hbf, heinrichHeineStr, {
laterThan: journeys.laterRef
})
})
.then((laterourneys) => {
const firstJourney = laterourneys[laterourneys.length - 1]
console.log('departure of first (later) journey', firstJourney.departure)
})
.catch(console.error)
```
```
departure of last journey 2017-12-17T19:07:00.000+01:00
departure of first (later) journey 2017-12-17T19:19:00.000+01:00
```

View file

@ -7,7 +7,7 @@ const formatLocationIdentifier = (data) => {
for (let key in data) { for (let key in data) {
if (!Object.prototype.hasOwnProperty.call(data, key)) continue if (!Object.prototype.hasOwnProperty.call(data, key)) continue
str += key + '=' + data[key] + sep // todo: escape str += key + '=' + data[key] + sep // todo: escape, but how?
} }
return str return str

View file

@ -13,9 +13,9 @@ const formatPoi = (p) => {
lid: formatLocationIdentifier({ lid: formatLocationIdentifier({
A: '4', // POI A: '4', // POI
O: p.name, O: p.name,
L: p.id,
X: formatCoord(p.longitude), X: formatCoord(p.longitude),
Y: formatCoord(p.latitude), Y: formatCoord(p.latitude)
L: p.id
}) })
} }
} }

View file

@ -1,11 +1,12 @@
'use strict' 'use strict'
const createFormatBitmask = (modes) => { const createFormatBitmask = (allProducts) => {
const formatBitmask = (products) => { const formatBitmask = (products) => {
if(Object.keys(products).length === 0) throw new Error('products filter must not be empty') if(Object.keys(products).length === 0) throw new Error('products filter must not be empty')
let bitmask = 0 let bitmask = 0
for (let product in products) { for (let product in products) {
if (products[product] === true) bitmask += modes[product].bitmask if (!allProducts[product]) throw new Error('unknown product ' + product)
if (products[product] === true) bitmask += allProducts[product].bitmask
} }
return bitmask return bitmask
} }

View file

@ -1,5 +1,15 @@
'use strict' 'use strict'
const formatStation = id => ({type: 'S', lid: 'L=' + id}) const formatLocationIdentifier = require('./location-identifier')
const formatStation = (id) => {
return {
// todo: name necessary?
lid: formatLocationIdentifier({
A: '1', // station
L: id
})
}
}
module.exports = formatStation module.exports = formatStation

View file

@ -7,12 +7,15 @@ const validateProfile = require('./lib/validate-profile')
const defaultProfile = require('./lib/default-profile') const defaultProfile = require('./lib/default-profile')
const _request = require('./lib/request') const _request = require('./lib/request')
const isObj = o => o !== null && 'object' === typeof o && !Array.isArray(o)
const isNonEmptyString = str => 'string' === typeof str && str.length > 0
const createClient = (profile, request = _request) => { const createClient = (profile, request = _request) => {
profile = Object.assign({}, defaultProfile, profile) profile = Object.assign({}, defaultProfile, profile)
validateProfile(profile) validateProfile(profile)
const departures = (station, opt = {}) => { const departures = (station, opt = {}) => {
if ('object' === typeof station) station = profile.formatStation(station.id) if (isObj(station)) station = profile.formatStation(station.id)
else if ('string' === typeof station) station = profile.formatStation(station) else if ('string' === typeof station) station = profile.formatStation(station)
else throw new Error('station must be an object or a string.') else throw new Error('station must be an object or a string.')
@ -49,6 +52,29 @@ const createClient = (profile, request = _request) => {
from = profile.formatLocation(profile, from) from = profile.formatLocation(profile, from)
to = profile.formatLocation(profile, to) to = profile.formatLocation(profile, to)
if (('earlierThan' in opt) && ('laterThan' in opt)) {
throw new Error('opt.laterThan and opt.laterThan are mutually exclusive.')
}
let journeysRef = null
if ('earlierThan' in opt) {
if (!isNonEmptyString(opt.earlierThan)) {
throw new Error('opt.earlierThan must be a non-empty string.')
}
if ('when' in opt) {
throw new Error('opt.earlierThan and opt.when are mutually exclusive.')
}
journeysRef = opt.earlierThan
}
if ('laterThan' in opt) {
if (!isNonEmptyString(opt.laterThan)) {
throw new Error('opt.laterThan must be a non-empty string.')
}
if ('when' in opt) {
throw new Error('opt.laterThan and opt.when are mutually exclusive.')
}
journeysRef = opt.laterThan
}
opt = Object.assign({ opt = Object.assign({
results: 5, // how many journeys? results: 5, // how many journeys?
via: null, // let journeys pass this station? via: null, // let journeys pass this station?
@ -75,10 +101,18 @@ const createClient = (profile, request = _request) => {
filters.push(profile.filters.accessibility[opt.accessibility]) filters.push(profile.filters.accessibility[opt.accessibility])
} }
const query = profile.transformJourneysQuery({ // With protocol version `1.16`, the VBB endpoint fails with
outDate: profile.formatDate(profile, opt.when), // `CGI_READ_FAILED` if you pass `numF`, the parameter for the number
outTime: profile.formatTime(profile, opt.when), // of results. To circumvent this, we loop here, collecting journeys
numF: opt.results, // until we have enough.
// see https://github.com/derhuerst/hafas-client/pull/23#issuecomment-370246163
// todo: check if `numF` is supported again, revert this change
const journeys = []
const more = (when, journeysRef) => {
const query = {
outDate: profile.formatDate(profile, when),
outTime: profile.formatTime(profile, when),
ctxScr: journeysRef,
getPasslist: !!opt.passedStations, getPasslist: !!opt.passedStations,
maxChg: opt.transfers, maxChg: opt.transfers,
minChgTime: opt.transferTime, minChgTime: opt.transferTime,
@ -93,22 +127,44 @@ const createClient = (profile, request = _request) => {
outFrwd: true, // todo: what is this? outFrwd: true, // todo: what is this?
getIV: false, // todo: walk & bike as alternatives? getIV: false, // todo: walk & bike as alternatives?
getPolyline: false // todo: shape for displaying on a map? getPolyline: false // todo: shape for displaying on a map?
}, opt) }
if (profile.journeysNumF) query.numF = opt.results
return request(profile, { return request(profile, {
cfg: {polyEnc: 'GPA'}, cfg: {polyEnc: 'GPA'},
meth: 'TripSearch', meth: 'TripSearch',
req: query req: profile.transformJourneysQuery(query, opt)
}) })
.then((d) => { .then((d) => {
if (!Array.isArray(d.outConL)) return [] if (!Array.isArray(d.outConL)) return []
const parse = profile.parseJourney(profile, d.locations, d.lines, d.remarks) const parse = profile.parseJourney(profile, d.locations, d.lines, d.remarks)
return d.outConL.map(parse) if (!journeys.earlierRef) journeys.earlierRef = d.outCtxScrB
let latestDep = -Infinity
for (let j of d.outConL) {
j = parse(j)
journeys.push(j)
if (journeys.length === opt.results) { // collected enough
journeys.laterRef = d.outCtxScrF
return journeys
}
const dep = +new Date(j.departure)
if (dep > latestDep) latestDep = dep
}
const when = new Date(latestDep)
return more(when, d.outCtxScrF) // otherwise continue
}) })
} }
return more(opt.when, journeysRef)
}
const locations = (query, opt = {}) => { const locations = (query, opt = {}) => {
if ('string' !== typeof query) throw new Error('query must be a string.') if (!isNonEmptyString(query)) {
throw new Error('query must be a non-empty string.')
}
opt = Object.assign({ opt = Object.assign({
fuzzy: true, // find only exact matches? fuzzy: true, // find only exact matches?
results: 10, // how many search results? results: 10, // how many search results?
@ -158,7 +214,7 @@ const createClient = (profile, request = _request) => {
} }
const nearby = (location, opt = {}) => { const nearby = (location, opt = {}) => {
if ('object' !== typeof location || Array.isArray(location)) { if (!isObj(location)) {
throw new Error('location must be an object.') throw new Error('location must be an object.')
} else if (location.type !== 'location') { } else if (location.type !== 'location') {
throw new Error('invalid location object.') throw new Error('invalid location object.')
@ -200,6 +256,12 @@ const createClient = (profile, request = _request) => {
} }
const journeyLeg = (ref, lineName, opt = {}) => { const journeyLeg = (ref, lineName, opt = {}) => {
if (!isNonEmptyString(ref)) {
throw new Error('ref must be a non-empty string.')
}
if (!isNonEmptyString(lineName)) {
throw new Error('lineName must be a non-empty string.')
}
opt = Object.assign({ opt = Object.assign({
passedStations: true // return stations on the way? passedStations: true // return stations on the way?
}, opt) }, opt)

View file

@ -26,6 +26,10 @@ const filters = require('../format/filters')
const id = x => x const id = x => x
const defaultProfile = { const defaultProfile = {
salt: null,
addChecksum: false,
addMicMac: false,
transformReqBody: id, transformReqBody: id,
transformReq: id, transformReq: id,
@ -55,6 +59,7 @@ const defaultProfile = {
formatRectangle, formatRectangle,
filters, filters,
journeysNumF: true, // `journeys()` method: support for `numF` field
journeyLeg: false, journeyLeg: false,
radar: false radar: false
} }

View file

@ -1,5 +1,6 @@
'use strict' 'use strict'
const crypto = require('crypto')
let captureStackTrace = () => {} let captureStackTrace = () => {}
if (process.env.NODE_ENV === 'dev') { if (process.env.NODE_ENV === 'dev') {
captureStackTrace = require('capture-stack-trace') captureStackTrace = require('capture-stack-trace')
@ -8,6 +9,8 @@ const {stringify} = require('query-string')
const Promise = require('pinkie-promise') const Promise = require('pinkie-promise')
const {fetch} = require('fetch-ponyfill')({Promise}) const {fetch} = require('fetch-ponyfill')({Promise})
const md5 = input => crypto.createHash('md5').update(input).digest()
const request = (profile, data) => { const request = (profile, data) => {
const body = profile.transformReqBody({lang: 'en', svcReqL: [data]}) const body = profile.transformReqBody({lang: 'en', svcReqL: [data]})
const req = profile.transformReq({ const req = profile.transformReq({
@ -19,14 +22,37 @@ const request = (profile, data) => {
'Accept-Encoding': 'gzip, deflate', 'Accept-Encoding': 'gzip, deflate',
'user-agent': 'https://github.com/derhuerst/hafas-client' 'user-agent': 'https://github.com/derhuerst/hafas-client'
}, },
query: null query: {}
}) })
const url = profile.endpoint + (req.query ? '?' + stringify(req.query) : '')
if (profile.addChecksum || profile.addMicMac) {
if (!Buffer.isBuffer(profile.salt)) {
throw new Error('profile.salt must be a Buffer.')
}
if (profile.addChecksum) {
const checksum = md5(Buffer.concat([
Buffer.from(req.body, 'utf8'),
profile.salt
]))
req.query.checksum = checksum.toString('hex')
}
if (profile.addMicMac) {
const mic = md5(Buffer.from(req.body, 'utf8'))
req.query.mic = mic.toString('hex')
const micAsHex = Buffer.from(mic.toString('hex'), 'utf8')
const mac = md5(Buffer.concat([micAsHex, profile.salt]))
req.query.mac = mac.toString('hex')
}
}
const url = profile.endpoint + '?' + stringify(req.query)
// Async stack traces are not supported everywhere yet, so we create our own. // Async stack traces are not supported everywhere yet, so we create our own.
const err = new Error() const err = new Error()
err.isHafasError = true err.isHafasError = true
err.request = body err.request = body
err.url = url
captureStackTrace(err) captureStackTrace(err)
return fetch(url, req) return fetch(url, req)

View file

@ -1,7 +1,5 @@
'use strict' 'use strict'
const crypto = require('crypto')
const _createParseLine = require('../../parse/line') const _createParseLine = require('../../parse/line')
const _createParseJourney = require('../../parse/journey') const _createParseJourney = require('../../parse/journey')
const _formatStation = require('../../format/station') const _formatStation = require('../../format/station')
@ -23,17 +21,6 @@ const transformReqBody = (body) => {
return body return body
} }
const salt = 'bdI8UVj40K5fvxwf'
const transformReq = (req) => {
const hash = crypto.createHash('md5')
hash.update(req.body + salt)
if (!req.query) req.query = {}
req.query.checksum = hash.digest('hex')
return req
}
const transformJourneysQuery = (query, opt) => { const transformJourneysQuery = (query, opt) => {
const filters = query.jnyFltrL const filters = query.jnyFltrL
if (opt.bike) filters.push(bike) if (opt.bike) filters.push(bike)
@ -142,8 +129,11 @@ const dbProfile = {
locale: 'de-DE', locale: 'de-DE',
timezone: 'Europe/Berlin', timezone: 'Europe/Berlin',
endpoint: 'https://reiseauskunft.bahn.de/bin/mgate.exe', endpoint: 'https://reiseauskunft.bahn.de/bin/mgate.exe',
salt: Buffer.from('bdI8UVj40K5fvxwf', 'utf8'),
addChecksum: true,
transformReqBody, transformReqBody,
transformReq,
transformJourneysQuery, transformJourneysQuery,
products: modes.allProducts, products: modes.allProducts,

View file

@ -20,10 +20,10 @@ const modes = require('./modes')
const formatBitmask = createFormatBitmask(modes) const formatBitmask = createFormatBitmask(modes)
const transformReqBody = (body) => { const transformReqBody = (body) => {
body.client = {type: 'IPA', id: 'BVG', name: 'FahrInfo', v: '4070700'} body.client = {type: 'IPA', id: 'VBB', name: 'vbbPROD', v: '4010300'}
body.ext = 'BVG.1' body.ext = 'VBB.1'
body.ver = '1.15' // todo: 1.16 with `mic` and `mac` query params body.ver = '1.16'
body.auth = {type: 'AID', aid: '1Rxs112shyHLatUX4fofnmdxK'} body.auth = {type: 'AID', aid: 'hafas-vbb-apps'}
return body return body
} }
@ -167,7 +167,13 @@ const formatProducts = (products) => {
const vbbProfile = { const vbbProfile = {
locale: 'de-DE', locale: 'de-DE',
timezone: 'Europe/Berlin', timezone: 'Europe/Berlin',
endpoint: 'https://bvg-apps.hafas.de/bin/mgate.exe', endpoint: 'https://fahrinfo.vbb.de/bin/mgate.exe',
// https://gist.github.com/derhuerst/a8d94a433358abc015ff77df4481070c#file-haf_config_base-properties-L39
// https://runkit.com/derhuerst/hafas-decrypt-encrypted-mac-salt
salt: Buffer.from('5243544a4d3266467846667878516649', 'hex'),
addMicMac: true,
transformReqBody, transformReqBody,
products: modes.allProducts, products: modes.allProducts,
@ -183,6 +189,7 @@ const vbbProfile = {
formatStation, formatStation,
formatProducts, formatProducts,
journeysNumF: false,
journeyLeg: true, journeyLeg: true,
radar: true radar: true
} }

View file

@ -1,7 +1,7 @@
{ {
"name": "hafas-client", "name": "hafas-client",
"description": "JavaScript client for HAFAS public transport APIs.", "description": "JavaScript client for HAFAS public transport APIs.",
"version": "2.3.2", "version": "2.3.3",
"main": "index.js", "main": "index.js",
"files": [ "files": [
"index.js", "index.js",

View file

@ -32,7 +32,11 @@ const createParseDeparture = (profile, stations, lines, remarks) => {
// see also derhuerst/vbb-rest#19 // see also derhuerst/vbb-rest#19
if (d.stbStop.aCncl || d.stbStop.dCncl) { if (d.stbStop.aCncl || d.stbStop.dCncl) {
res.cancelled = true res.cancelled = true
Object.defineProperty(res, 'canceled', {value: true})
res.when = res.delay = null res.when = res.delay = null
const when = profile.parseDateTime(profile, d.date, d.stbStop.dTimeS)
res.formerScheduledWhen = when.toISO()
} }
return res return res

View file

@ -75,11 +75,17 @@ const createParseJourneyLeg = (profile, stations, lines, remarks) => {
// see also derhuerst/vbb-rest#19 // see also derhuerst/vbb-rest#19
if (pt.arr.aCncl) { if (pt.arr.aCncl) {
res.cancelled = true res.cancelled = true
Object.defineProperty(res, 'canceled', {value: true})
res.arrival = res.arrivalPlatform = res.arrivalDelay = null res.arrival = res.arrivalPlatform = res.arrivalDelay = null
const arr = profile.parseDateTime(profile, j.date, pt.arr.aTimeS)
res.formerScheduledArrival = arr.toISO()
} }
if (pt.dep.dCncl) { if (pt.dep.dCncl) {
res.cancelled = true res.cancelled = true
Object.defineProperty(res, 'canceled', {value: true})
res.departure = res.departurePlatform = res.departureDelay = null res.departure = res.departurePlatform = res.departureDelay = null
const dep = profile.parseDateTime(profile, j.date, pt.dep.dTimeS)
res.formerScheduledDeparture = dep.toISO()
} }
return res return res

View file

@ -14,6 +14,7 @@ const createParseJourney = (profile, stations, lines, remarks) => {
const parseJourney = (j) => { const parseJourney = (j) => {
const legs = j.secL.map(leg => parseLeg(j, leg)) const legs = j.secL.map(leg => parseLeg(j, leg))
const res = { const res = {
type: 'journey',
legs, legs,
origin: legs[0].origin, origin: legs[0].origin,
destination: legs[legs.length - 1].destination, destination: legs[legs.length - 1].destination,
@ -22,7 +23,16 @@ const createParseJourney = (profile, stations, lines, remarks) => {
} }
if (legs.some(p => p.cancelled)) { if (legs.some(p => p.cancelled)) {
res.cancelled = true res.cancelled = true
Object.defineProperty(res, 'canceled', {value: true})
res.departure = res.arrival = null res.departure = res.arrival = null
const firstLeg = j.secL[0]
const dep = profile.parseDateTime(profile, j.date, firstLeg.dep.dTimeS)
res.formerScheduledDeparture = dep.toISO()
const lastLeg = j.secL[j.secL.length - 1]
const arr = profile.parseDateTime(profile, j.date, lastLeg.arr.aTimeS)
res.formerScheduledArrival = arr.toISO()
} }
return res return res

View file

@ -175,6 +175,7 @@ The returned [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript
- [`vbb-hafas`](https://github.com/derhuerst/vbb-hafas#vbb-hafas) JavaScript client for Berlin & Brandenburg public transport HAFAS API. - [`vbb-hafas`](https://github.com/derhuerst/vbb-hafas#vbb-hafas) JavaScript client for Berlin & Brandenburg public transport HAFAS API.
- [`hafas-departures-in-direction`](https://github.com/derhuerst/hafas-departures-in-direction#hafas-departures-in-direction)  Pass in a HAFAS client, get departures in a certain direction. - [`hafas-departures-in-direction`](https://github.com/derhuerst/hafas-departures-in-direction#hafas-departures-in-direction)  Pass in a HAFAS client, get departures in a certain direction.
- [`hafas-collect-departures-at`](https://github.com/derhuerst/hafas-collect-departures-at#hafas-collect-departures-at)  Utility to collect departures, using any HAFAS client. - [`hafas-collect-departures-at`](https://github.com/derhuerst/hafas-collect-departures-at#hafas-collect-departures-at)  Utility to collect departures, using any HAFAS client.
- [`hafas-discover-stations`](https://github.com/derhuerst/hafas-discover-stations#hafas-discover-stations) Pass in a HAFAS client, discover stations by querying departures.
- [`hafas-rest-api`](https://github.com/derhuerst/hafas-rest-api#hafas-rest-api) Expose a HAFAS client via an HTTP REST API. - [`hafas-rest-api`](https://github.com/derhuerst/hafas-rest-api#hafas-rest-api) Expose a HAFAS client via an HTTP REST API.
- [List of european long-distance transport operators, available API endpoints, GTFS feeds and client modules.](https://github.com/public-transport/european-transport-operators) - [List of european long-distance transport operators, available API endpoints, GTFS feeds and client modules.](https://github.com/public-transport/european-transport-operators)
- [Collection of european transport JavaScript modules.](https://github.com/public-transport/european-transport-modules) - [Collection of european transport JavaScript modules.](https://github.com/public-transport/european-transport-modules)

View file

@ -53,7 +53,7 @@ const findStation = (id) => new Promise((yay, nay) => {
const isJungfernheide = (s) => { const isJungfernheide = (s) => {
return s.type === 'station' && return s.type === 'station' &&
(s.id === '008011167' || s.id === '8011167') && (s.id === '008011167' || s.id === jungfernh) &&
s.name === 'Berlin Jungfernheide' && s.name === 'Berlin Jungfernheide' &&
s.location && s.location &&
isRoughlyEqual(s.location.latitude, 52.530408, .0005) && isRoughlyEqual(s.location.latitude, 52.530408, .0005) &&
@ -62,7 +62,7 @@ const isJungfernheide = (s) => {
const assertIsJungfernheide = (t, s) => { const assertIsJungfernheide = (t, s) => {
t.equal(s.type, 'station') t.equal(s.type, 'station')
t.ok(s.id === '008011167' || s.id === '8011167', 'id should be 8011167') t.ok(s.id === '008011167' || s.id === jungfernh, 'id should be 8011167')
t.equal(s.name, 'Berlin Jungfernheide') t.equal(s.name, 'Berlin Jungfernheide')
t.ok(s.location) t.ok(s.location)
t.ok(isRoughlyEqual(s.location.latitude, 52.530408, .0005)) t.ok(isRoughlyEqual(s.location.latitude, 52.530408, .0005))
@ -92,14 +92,22 @@ const assertValidPrice = (t, p) => {
const test = tapePromise(tape) const test = tapePromise(tape)
const client = createClient(dbProfile) const client = createClient(dbProfile)
const jungfernh = '8011167'
const berlinHbf = '8011160'
const münchenHbf = '8000261'
const hannoverHbf = '8000152'
const regensburgHbf = '8000309'
test('Berlin Jungfernheide to München Hbf', co(function* (t) { test('Berlin Jungfernheide to München Hbf', co(function* (t) {
const journeys = yield client.journeys('8011167', '8000261', { const journeys = yield client.journeys(jungfernh, münchenHbf, {
when, passedStations: true when, passedStations: true
}) })
t.ok(Array.isArray(journeys)) t.ok(Array.isArray(journeys))
t.ok(journeys.length > 0, 'no journeys') t.ok(journeys.length > 0, 'no journeys')
for (let journey of journeys) { for (let journey of journeys) {
t.equal(journey.type, 'journey')
assertValidStation(t, journey.origin) assertValidStation(t, journey.origin)
assertValidStationProducts(t, journey.origin.products) assertValidStationProducts(t, journey.origin.products)
if (!(yield findStation(journey.origin.id))) { if (!(yield findStation(journey.origin.id))) {
@ -152,7 +160,7 @@ test('Berlin Jungfernheide to München Hbf', co(function* (t) {
})) }))
test('Berlin Jungfernheide to Torfstraße 17', co(function* (t) { test('Berlin Jungfernheide to Torfstraße 17', co(function* (t) {
const journeys = yield client.journeys('8011167', { const journeys = yield client.journeys(jungfernh, {
type: 'location', address: 'Torfstraße 17', type: 'location', address: 'Torfstraße 17',
latitude: 52.5416823, longitude: 13.3491223 latitude: 52.5416823, longitude: 13.3491223
}, {when}) }, {when})
@ -181,7 +189,7 @@ test('Berlin Jungfernheide to Torfstraße 17', co(function* (t) {
})) }))
test('Berlin Jungfernheide to ATZE Musiktheater', co(function* (t) { test('Berlin Jungfernheide to ATZE Musiktheater', co(function* (t) {
const journeys = yield client.journeys('8011167', { const journeys = yield client.journeys(jungfernh, {
type: 'location', id: '991598902', name: 'ATZE Musiktheater', type: 'location', id: '991598902', name: 'ATZE Musiktheater',
latitude: 52.542417, longitude: 13.350437 latitude: 52.542417, longitude: 13.350437
}, {when}) }, {when})
@ -210,9 +218,6 @@ test('Berlin Jungfernheide to ATZE Musiktheater', co(function* (t) {
})) }))
test('Berlin Hbf to München Hbf with stopover at Hannover Hbf', co(function* (t) { test('Berlin Hbf to München Hbf with stopover at Hannover Hbf', co(function* (t) {
const berlinHbf = '8011160'
const münchenHbf = '8000261'
const hannoverHbf = '8000152'
const [journey] = yield client.journeys(berlinHbf, münchenHbf, { const [journey] = yield client.journeys(berlinHbf, münchenHbf, {
via: hannoverHbf, via: hannoverHbf,
results: 1 results: 1
@ -228,8 +233,58 @@ test('Berlin Hbf to München Hbf with stopover at Hannover Hbf', co(function* (t
t.end() t.end()
})) }))
test('earlier/later journeys, Jungfernheide -> München Hbf', co(function* (t) {
const model = yield client.journeys(jungfernh, münchenHbf, {
results: 3, when
})
t.equal(typeof model.earlierRef, 'string')
t.ok(model.earlierRef)
t.equal(typeof model.laterRef, 'string')
t.ok(model.laterRef)
// when and earlierThan/laterThan should be mutually exclusive
t.throws(() => {
client.journeys(jungfernh, münchenHbf, {
when, earlierThan: model.earlierRef
})
})
t.throws(() => {
client.journeys(jungfernh, münchenHbf, {
when, laterThan: model.laterRef
})
})
let earliestDep = Infinity, latestDep = -Infinity
for (let j of model) {
const dep = +new Date(j.departure)
if (dep < earliestDep) earliestDep = dep
else if (dep > latestDep) latestDep = dep
}
const earlier = yield client.journeys(jungfernh, münchenHbf, {
results: 3,
// todo: single journey ref?
earlierThan: model.earlierRef
})
for (let j of earlier) {
t.ok(new Date(j.departure) < earliestDep)
}
const later = yield client.journeys(jungfernh, münchenHbf, {
results: 3,
// todo: single journey ref?
laterThan: model.laterRef
})
for (let j of later) {
t.ok(new Date(j.departure) > latestDep)
}
t.end()
}))
test('departures at Berlin Jungfernheide', co(function* (t) { test('departures at Berlin Jungfernheide', co(function* (t) {
const deps = yield client.departures('8011167', { const deps = yield client.departures(jungfernh, {
duration: 5, when duration: 5, when
}) })
@ -250,7 +305,7 @@ test('departures at Berlin Jungfernheide', co(function* (t) {
test('departures with station object', co(function* (t) { test('departures with station object', co(function* (t) {
yield client.departures({ yield client.departures({
type: 'station', type: 'station',
id: '8011167', id: jungfernh,
name: 'Berlin Jungfernheide', name: 'Berlin Jungfernheide',
location: { location: {
type: 'location', type: 'location',
@ -306,7 +361,6 @@ test('locations named Jungfernheide', co(function* (t) {
})) }))
test('location', co(function* (t) { test('location', co(function* (t) {
const regensburgHbf = '8000309'
const loc = yield client.location(regensburgHbf) const loc = yield client.location(regensburgHbf)
assertValidStation(t, loc) assertValidStation(t, loc)

View file

@ -110,9 +110,14 @@ const assertValidLine = (t, l) => { // with optional mode
const test = tapePromise(tape) const test = tapePromise(tape)
const client = createClient(oebbProfile) const client = createClient(oebbProfile)
test('Salzburg Hbf to Wien Westbahnhof', co(function* (t) {
const salzburgHbf = '8100002' const salzburgHbf = '8100002'
const wienWestbahnhof = '1291501' const wienWestbahnhof = '1291501'
const wien = '1190100'
const klagenfurtHbf = '8100085'
const muenchenHbf = '8000261'
const grazHbf = '8100173'
test('Salzburg Hbf to Wien Westbahnhof', co(function* (t) {
const journeys = yield client.journeys(salzburgHbf, wienWestbahnhof, { const journeys = yield client.journeys(salzburgHbf, wienWestbahnhof, {
when, passedStations: true when, passedStations: true
}) })
@ -120,6 +125,8 @@ test('Salzburg Hbf to Wien Westbahnhof', co(function* (t) {
t.ok(Array.isArray(journeys)) t.ok(Array.isArray(journeys))
t.ok(journeys.length > 0, 'no journeys') t.ok(journeys.length > 0, 'no journeys')
for (let journey of journeys) { for (let journey of journeys) {
t.equal(journey.type, 'journey')
assertValidStation(t, journey.origin) assertValidStation(t, journey.origin)
assertValidStationProducts(t, journey.origin.products) assertValidStationProducts(t, journey.origin.products)
// todo // todo
@ -176,7 +183,6 @@ test('Salzburg Hbf to Wien Westbahnhof', co(function* (t) {
})) }))
test('Salzburg Hbf to 1220 Wien, Wagramer Straße 5', co(function* (t) { test('Salzburg Hbf to 1220 Wien, Wagramer Straße 5', co(function* (t) {
const salzburgHbf = '8100002'
const wagramerStr = { const wagramerStr = {
type: 'location', type: 'location',
latitude: 48.236216, latitude: 48.236216,
@ -221,7 +227,6 @@ test('Albertina to Salzburg Hbf', co(function* (t) {
name: 'Albertina', name: 'Albertina',
id: '975900003' id: '975900003'
} }
const salzburgHbf = '8100002'
const journeys = yield client.journeys(albertina, salzburgHbf, {when}) const journeys = yield client.journeys(albertina, salzburgHbf, {when})
t.ok(Array.isArray(journeys)) t.ok(Array.isArray(journeys))
@ -253,9 +258,6 @@ test('Albertina to Salzburg Hbf', co(function* (t) {
})) }))
test('Wien to Klagenfurt Hbf with stopover at Salzburg Hbf', co(function* (t) { test('Wien to Klagenfurt Hbf with stopover at Salzburg Hbf', co(function* (t) {
const wien = '1190100'
const klagenfurtHbf = '8100085'
const salzburgHbf = '8100002'
const [journey] = yield client.journeys(wien, klagenfurtHbf, { const [journey] = yield client.journeys(wien, klagenfurtHbf, {
via: salzburgHbf, via: salzburgHbf,
results: 1, results: 1,
@ -272,9 +274,57 @@ test('Wien to Klagenfurt Hbf with stopover at Salzburg Hbf', co(function* (t) {
t.end() t.end()
})) }))
test('earlier/later journeys, Salzburg Hbf -> Wien Westbahnhof', co(function* (t) {
const model = yield client.journeys(salzburgHbf, wienWestbahnhof, {
results: 3, when
})
t.equal(typeof model.earlierRef, 'string')
t.ok(model.earlierRef)
t.equal(typeof model.laterRef, 'string')
t.ok(model.laterRef)
// when and earlierThan/laterThan should be mutually exclusive
t.throws(() => {
client.journeys(salzburgHbf, wienWestbahnhof, {
when, earlierThan: model.earlierRef
})
})
t.throws(() => {
client.journeys(salzburgHbf, wienWestbahnhof, {
when, laterThan: model.laterRef
})
})
let earliestDep = Infinity, latestDep = -Infinity
for (let j of model) {
const dep = +new Date(j.departure)
if (dep < earliestDep) earliestDep = dep
else if (dep > latestDep) latestDep = dep
}
const earlier = yield client.journeys(salzburgHbf, wienWestbahnhof, {
results: 3,
// todo: single journey ref?
earlierThan: model.earlierRef
})
for (let j of earlier) {
t.ok(new Date(j.departure) < earliestDep)
}
const later = yield client.journeys(salzburgHbf, wienWestbahnhof, {
results: 3,
// todo: single journey ref?
laterThan: model.laterRef
})
for (let j of later) {
t.ok(new Date(j.departure) > latestDep)
}
t.end()
}))
test('leg details for Wien Westbahnhof to München Hbf', co(function* (t) { test('leg details for Wien Westbahnhof to München Hbf', co(function* (t) {
const wienWestbahnhof = '1291501'
const muenchenHbf = '8000261'
const journeys = yield client.journeys(wienWestbahnhof, muenchenHbf, { const journeys = yield client.journeys(wienWestbahnhof, muenchenHbf, {
results: 1, when results: 1, when
}) })
@ -299,7 +349,6 @@ test('leg details for Wien Westbahnhof to München Hbf', co(function* (t) {
})) }))
test('departures at Salzburg Hbf', co(function* (t) { test('departures at Salzburg Hbf', co(function* (t) {
const salzburgHbf = '8100002'
const deps = yield client.departures(salzburgHbf, { const deps = yield client.departures(salzburgHbf, {
duration: 5, when duration: 5, when
}) })
@ -363,7 +412,6 @@ test('locations named Salzburg', co(function* (t) {
})) }))
test('location', co(function* (t) { test('location', co(function* (t) {
const grazHbf = '8100173'
const loc = yield client.location(grazHbf) const loc = yield client.location(grazHbf)
assertValidStation(t, loc) assertValidStation(t, loc)

View file

@ -67,6 +67,8 @@ test('journeys  station to station', co(function* (t) {
t.strictEqual(journeys.length, 3) t.strictEqual(journeys.length, 3)
for (let journey of journeys) { for (let journey of journeys) {
t.equal(journey.type, 'journey')
assertValidStation(t, journey.origin) assertValidStation(t, journey.origin)
assertValidStationProducts(t, journey.origin.products) assertValidStationProducts(t, journey.origin.products)
t.ok(journey.origin.name.indexOf('(Berlin)') === -1) t.ok(journey.origin.name.indexOf('(Berlin)') === -1)
@ -146,7 +148,7 @@ test('journeys  only subway', co(function* (t) {
test('journeys  fails with no product', co(function* (t) { test('journeys  fails with no product', co(function* (t) {
try { try {
yield client.journeys(spichernstr, bismarckstr, { client.journeys(spichernstr, bismarckstr, {
when, when,
products: { products: {
suburban: false, suburban: false,
@ -158,12 +160,64 @@ test('journeys  fails with no product', co(function* (t) {
regional: false regional: false
} }
}) })
// silence rejections, we're only interested in exceptions
.catch(() => {})
} catch (err) { } catch (err) {
t.ok(err, 'error thrown') t.ok(err, 'error thrown')
t.end() t.end()
} }
})) }))
test('earlier/later journeys', co(function* (t) {
const model = yield client.journeys(spichernstr, bismarckstr, {
results: 3, when
})
t.equal(typeof model.earlierRef, 'string')
t.ok(model.earlierRef)
t.equal(typeof model.laterRef, 'string')
t.ok(model.laterRef)
// when and earlierThan/laterThan should be mutually exclusive
t.throws(() => {
client.journeys(spichernstr, bismarckstr, {
when, earlierThan: model.earlierRef
})
})
t.throws(() => {
client.journeys(spichernstr, bismarckstr, {
when, laterThan: model.laterRef
})
})
let earliestDep = Infinity, latestDep = -Infinity
for (let j of model) {
const dep = +new Date(j.departure)
if (dep < earliestDep) earliestDep = dep
else if (dep > latestDep) latestDep = dep
}
const earlier = yield client.journeys(spichernstr, bismarckstr, {
results: 3,
// todo: single journey ref?
earlierThan: model.earlierRef
})
for (let j of earlier) {
t.ok(new Date(j.departure) < earliestDep)
}
const later = yield client.journeys(spichernstr, bismarckstr, {
results: 3,
// todo: single journey ref?
laterThan: model.laterRef
})
for (let j of later) {
t.ok(new Date(j.departure) > latestDep)
}
t.end()
}))
test('journey leg details', co(function* (t) { test('journey leg details', co(function* (t) {
const journeys = yield client.journeys(spichernstr, amrumerStr, { const journeys = yield client.journeys(spichernstr, amrumerStr, {
results: 1, when results: 1, when
@ -192,8 +246,9 @@ test('journey leg details', co(function* (t) {
test('journeys  station to address', co(function* (t) { test('journeys  station to address', co(function* (t) {
const journeys = yield client.journeys(spichernstr, { const journeys = yield client.journeys(spichernstr, {
type: 'location', address: 'Torfstraße 17', type: 'location',
latitude: 52.5416823, longitude: 13.3491223 address: 'Torfstr. 17, Berlin',
latitude: 52.541797, longitude: 13.350042
}, {results: 1, when}) }, {results: 1, when})
t.ok(Array.isArray(journeys)) t.ok(Array.isArray(journeys))
@ -207,9 +262,9 @@ test('journeys  station to address', co(function* (t) {
const dest = leg.destination const dest = leg.destination
assertValidAddress(t, dest) assertValidAddress(t, dest)
t.strictEqual(dest.address, 'Torfstraße 17') t.strictEqual(dest.address, '13353 Berlin-Wedding, Torfstr. 17')
t.ok(isRoughlyEqual(.0001, dest.latitude, 52.5416823)) t.ok(isRoughlyEqual(.0001, dest.latitude, 52.541797))
t.ok(isRoughlyEqual(.0001, dest.longitude, 13.3491223)) t.ok(isRoughlyEqual(.0001, dest.longitude, 13.350042))
assertValidWhen(t, leg.arrival, when) assertValidWhen(t, leg.arrival, when)
t.end() t.end()
@ -219,7 +274,9 @@ test('journeys  station to address', co(function* (t) {
test('journeys  station to POI', co(function* (t) { test('journeys  station to POI', co(function* (t) {
const journeys = yield client.journeys(spichernstr, { const journeys = yield client.journeys(spichernstr, {
type: 'location', id: '9980720', name: 'ATZE Musiktheater', type: 'location',
id: '900980720',
name: 'Berlin, Atze Musiktheater für Kinder',
latitude: 52.543333, longitude: 13.351686 latitude: 52.543333, longitude: 13.351686
}, {results: 1, when}) }, {results: 1, when})
@ -234,7 +291,8 @@ test('journeys  station to POI', co(function* (t) {
const dest = leg.destination const dest = leg.destination
assertValidPoi(t, dest) assertValidPoi(t, dest)
t.strictEqual(dest.name, 'ATZE Musiktheater') t.strictEqual(dest.id, '900980720')
t.strictEqual(dest.name, 'Berlin, Atze Musiktheater für Kinder')
t.ok(isRoughlyEqual(.0001, dest.latitude, 52.543333)) t.ok(isRoughlyEqual(.0001, dest.latitude, 52.543333))
t.ok(isRoughlyEqual(.0001, dest.longitude, 13.351686)) t.ok(isRoughlyEqual(.0001, dest.longitude, 13.351686))
assertValidWhen(t, leg.arrival, when) assertValidWhen(t, leg.arrival, when)