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
{
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?
via: null, // let journeys pass this station
passedStations: false, // return stations on the way?
@ -85,123 +87,127 @@ client.journeys('900000003201', '900000100008', {
The response may look like this:
```js
[ {
legs: [ {
id: '1|31041|35|86|17122017',
[
{
legs: [ {
id: '1|31041|35|86|17122017',
origin: {
type: 'station',
id: '900000003201',
name: 'S+U Berlin Hauptbahnhof',
location: {
type: 'location',
latitude: 52.52585,
longitude: 13.368928
},
products: {
suburban: true,
subway: true,
tram: true,
bus: true,
ferry: false,
express: true,
regional: true
}
},
departure: '2017-12-17T19:07:00.000+01:00',
departurePlatform: '16',
destination: {
type: 'station',
id: '900000024101',
name: 'S Charlottenburg',
location: {
type: 'location',
latitude: 52.504806,
longitude: 13.303846
},
products: {
suburban: true,
subway: false,
tram: false,
bus: true,
ferry: false,
express: false,
regional: true
}
},
arrival: '2017-12-17T19:47:00.000+01:00',
arrivalPlatform: '8',
arrivalDelay: 30,
line: {
type: 'line',
id: '16845',
name: 'S7',
public: true,
mode: 'train',
product: 'suburban',
symbol: 'S',
nr: 7,
metro: false,
express: false,
night: false,
productCode: 0,
operator: {
type: 'operator',
id: 's-bahn-berlin-gmbh',
name: 'S-Bahn Berlin GmbH'
}
},
direction: 'S Potsdam Hauptbahnhof',
passed: [ {
station: {
type: 'station',
id: '900000003201',
name: 'S+U Berlin Hauptbahnhof',
location: { /* … */ },
products: { /* … */ }
},
arrival: null,
departure: null,
cancelled: true
}, {
station: {
type: 'station',
id: '900000003102',
name: 'S Bellevue',
location: { /* … */ },
products: { /* … */ }
},
arrival: '2017-12-17T19:09:00.000+01:00',
departure: '2017-12-17T19:09:00.000+01:00'
}, /* … */ {
station: {
type: 'station',
id: '900000024101',
name: 'S Charlottenburg',
location: { /* … */ },
products: { /* … */ }
},
arrival: '2017-12-17T19:17:00.000+01:00',
departure: '2017-12-17T19:17:00.000+01:00'
} ]
} ],
origin: {
type: 'station',
id: '900000003201',
name: 'S+U Berlin Hauptbahnhof',
location: {
type: 'location',
latitude: 52.52585,
longitude: 13.368928
},
products: {
suburban: true,
subway: true,
tram: true,
bus: true,
ferry: false,
express: true,
regional: true
}
location: { /* … */ },
products: { /* … */ }
},
departure: '2017-12-17T19:07:00.000+01:00',
departurePlatform: '16',
destination: {
type: 'station',
id: '900000024101',
name: 'S Charlottenburg',
location: {
type: 'location',
latitude: 52.504806,
longitude: 13.303846
},
products: {
suburban: true,
subway: false,
tram: false,
bus: true,
ferry: false,
express: false,
regional: true
}
location: { /* … */ },
products: { /* … */ }
},
arrival: '2017-12-17T19:47:00.000+01:00',
arrivalPlatform: '8',
arrivalDelay: 30,
line: {
type: 'line',
id: '16845',
name: 'S7',
public: true,
mode: 'train',
product: 'suburban',
symbol: 'S',
nr: 7,
metro: false,
express: false,
night: false,
productCode: 0,
operator: {
type: 'operator',
id: 's-bahn-berlin-gmbh',
name: 'S-Bahn Berlin GmbH'
}
},
direction: 'S Potsdam Hauptbahnhof',
passed: [ {
station: {
type: 'station',
id: '900000003201',
name: 'S+U Berlin Hauptbahnhof',
location: { /* … */ },
products: { /* … */ }
},
arrival: null,
departure: null,
cancelled: true
}, {
station: {
type: 'station',
id: '900000003102',
name: 'S Bellevue',
location: { /* … */ },
products: { /* … */ }
},
arrival: '2017-12-17T19:09:00.000+01:00',
departure: '2017-12-17T19:09:00.000+01:00'
}, /* … */ {
station: {
type: 'station',
id: '900000024101',
name: 'S Charlottenburg',
location: { /* … */ },
products: { /* … */ }
},
arrival: '2017-12-17T19:17:00.000+01:00',
departure: '2017-12-17T19:17:00.000+01:00'
} ]
} ],
origin: {
type: 'station',
id: '900000003201',
name: 'S+U Berlin Hauptbahnhof',
location: { /* … */ },
products: { /* … */ }
arrivalDelay: 30
},
departure: '2017-12-17T19:07:00.000+01:00',
destination: {
type: 'station',
id: '900000024101',
name: 'S Charlottenburg',
location: { /* … */ },
products: { /* … */ }
},
arrival: '2017-12-17T19:47:00.000+01:00',
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:
@ -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`.
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) {
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

View file

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

View file

@ -1,11 +1,12 @@
'use strict'
const createFormatBitmask = (modes) => {
const createFormatBitmask = (allProducts) => {
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
if (!allProducts[product]) throw new Error('unknown product ' + product)
if (products[product] === true) bitmask += allProducts[product].bitmask
}
return bitmask
}

View file

@ -1,5 +1,15 @@
'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

124
index.js
View file

@ -7,12 +7,15 @@ const validateProfile = require('./lib/validate-profile')
const defaultProfile = require('./lib/default-profile')
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) => {
profile = Object.assign({}, defaultProfile, profile)
validateProfile(profile)
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 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)
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({
results: 5, // how many journeys?
via: null, // let journeys pass this station?
@ -75,40 +101,70 @@ const createClient = (profile, request = _request) => {
filters.push(profile.filters.accessibility[opt.accessibility])
}
const query = profile.transformJourneysQuery({
outDate: profile.formatDate(profile, opt.when),
outTime: profile.formatTime(profile, opt.when),
numF: opt.results,
getPasslist: !!opt.passedStations,
maxChg: opt.transfers,
minChgTime: opt.transferTime,
depLocL: [from],
viaLocL: opt.via ? [{loc: opt.via}] : null,
arrLocL: [to],
jnyFltrL: filters,
getTariff: !!opt.tickets,
// With protocol version `1.16`, the VBB endpoint fails with
// `CGI_READ_FAILED` if you pass `numF`, the parameter for the number
// of results. To circumvent this, we loop here, collecting journeys
// 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,
maxChg: opt.transfers,
minChgTime: opt.transferTime,
depLocL: [from],
viaLocL: opt.via ? [{loc: opt.via}] : null,
arrLocL: [to],
jnyFltrL: filters,
getTariff: !!opt.tickets,
// todo: what is req.gisFltrL?
getPT: true, // todo: what is this?
outFrwd: true, // todo: what is this?
getIV: false, // todo: walk & bike as alternatives?
getPolyline: false // todo: shape for displaying on a map?
}, opt)
// todo: what is req.gisFltrL?
getPT: true, // todo: what is this?
outFrwd: true, // todo: what is this?
getIV: false, // todo: walk & bike as alternatives?
getPolyline: false // todo: shape for displaying on a map?
}
if (profile.journeysNumF) query.numF = opt.results
return request(profile, {
cfg: {polyEnc: 'GPA'},
meth: 'TripSearch',
req: query
})
.then((d) => {
if (!Array.isArray(d.outConL)) return []
const parse = profile.parseJourney(profile, d.locations, d.lines, d.remarks)
return d.outConL.map(parse)
})
return request(profile, {
cfg: {polyEnc: 'GPA'},
meth: 'TripSearch',
req: profile.transformJourneysQuery(query, opt)
})
.then((d) => {
if (!Array.isArray(d.outConL)) return []
const parse = profile.parseJourney(profile, d.locations, d.lines, d.remarks)
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 = {}) => {
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({
fuzzy: true, // find only exact matches?
results: 10, // how many search results?
@ -158,7 +214,7 @@ const createClient = (profile, request = _request) => {
}
const nearby = (location, opt = {}) => {
if ('object' !== typeof location || Array.isArray(location)) {
if (!isObj(location)) {
throw new Error('location must be an object.')
} else if (location.type !== 'location') {
throw new Error('invalid location object.')
@ -200,6 +256,12 @@ const createClient = (profile, request = _request) => {
}
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({
passedStations: true // return stations on the way?
}, opt)

View file

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

View file

@ -1,5 +1,6 @@
'use strict'
const crypto = require('crypto')
let captureStackTrace = () => {}
if (process.env.NODE_ENV === 'dev') {
captureStackTrace = require('capture-stack-trace')
@ -8,6 +9,8 @@ const {stringify} = require('query-string')
const Promise = require('pinkie-promise')
const {fetch} = require('fetch-ponyfill')({Promise})
const md5 = input => crypto.createHash('md5').update(input).digest()
const request = (profile, data) => {
const body = profile.transformReqBody({lang: 'en', svcReqL: [data]})
const req = profile.transformReq({
@ -19,14 +22,37 @@ const request = (profile, data) => {
'Accept-Encoding': 'gzip, deflate',
'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.
const err = new Error()
err.isHafasError = true
err.request = body
err.url = url
captureStackTrace(err)
return fetch(url, req)

View file

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

View file

@ -20,10 +20,10 @@ const modes = require('./modes')
const formatBitmask = createFormatBitmask(modes)
const transformReqBody = (body) => {
body.client = {type: 'IPA', id: 'BVG', name: 'FahrInfo', v: '4070700'}
body.ext = 'BVG.1'
body.ver = '1.15' // todo: 1.16 with `mic` and `mac` query params
body.auth = {type: 'AID', aid: '1Rxs112shyHLatUX4fofnmdxK'}
body.client = {type: 'IPA', id: 'VBB', name: 'vbbPROD', v: '4010300'}
body.ext = 'VBB.1'
body.ver = '1.16'
body.auth = {type: 'AID', aid: 'hafas-vbb-apps'}
return body
}
@ -167,7 +167,13 @@ const formatProducts = (products) => {
const vbbProfile = {
locale: 'de-DE',
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,
products: modes.allProducts,
@ -183,6 +189,7 @@ const vbbProfile = {
formatStation,
formatProducts,
journeysNumF: false,
journeyLeg: true,
radar: true
}

View file

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

View file

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

View file

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

View file

@ -14,6 +14,7 @@ const createParseJourney = (profile, stations, lines, remarks) => {
const parseJourney = (j) => {
const legs = j.secL.map(leg => parseLeg(j, leg))
const res = {
type: 'journey',
legs,
origin: legs[0].origin,
destination: legs[legs.length - 1].destination,
@ -22,7 +23,16 @@ const createParseJourney = (profile, stations, lines, remarks) => {
}
if (legs.some(p => p.cancelled)) {
res.cancelled = true
Object.defineProperty(res, 'canceled', {value: true})
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

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.
- [`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-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.
- [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)

View file

@ -53,7 +53,7 @@ const findStation = (id) => new Promise((yay, nay) => {
const isJungfernheide = (s) => {
return s.type === 'station' &&
(s.id === '008011167' || s.id === '8011167') &&
(s.id === '008011167' || s.id === jungfernh) &&
s.name === 'Berlin Jungfernheide' &&
s.location &&
isRoughlyEqual(s.location.latitude, 52.530408, .0005) &&
@ -62,7 +62,7 @@ const isJungfernheide = (s) => {
const assertIsJungfernheide = (t, s) => {
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.ok(s.location)
t.ok(isRoughlyEqual(s.location.latitude, 52.530408, .0005))
@ -92,14 +92,22 @@ const assertValidPrice = (t, p) => {
const test = tapePromise(tape)
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) {
const journeys = yield client.journeys('8011167', '8000261', {
const journeys = yield client.journeys(jungfernh, münchenHbf, {
when, passedStations: true
})
t.ok(Array.isArray(journeys))
t.ok(journeys.length > 0, 'no journeys')
for (let journey of journeys) {
t.equal(journey.type, 'journey')
assertValidStation(t, journey.origin)
assertValidStationProducts(t, journey.origin.products)
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) {
const journeys = yield client.journeys('8011167', {
const journeys = yield client.journeys(jungfernh, {
type: 'location', address: 'Torfstraße 17',
latitude: 52.5416823, longitude: 13.3491223
}, {when})
@ -181,7 +189,7 @@ test('Berlin Jungfernheide to Torfstraße 17', 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',
latitude: 52.542417, longitude: 13.350437
}, {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) {
const berlinHbf = '8011160'
const münchenHbf = '8000261'
const hannoverHbf = '8000152'
const [journey] = yield client.journeys(berlinHbf, münchenHbf, {
via: hannoverHbf,
results: 1
@ -228,8 +233,58 @@ test('Berlin Hbf to München Hbf with stopover at Hannover Hbf', co(function* (t
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) {
const deps = yield client.departures('8011167', {
const deps = yield client.departures(jungfernh, {
duration: 5, when
})
@ -250,7 +305,7 @@ test('departures at Berlin Jungfernheide', co(function* (t) {
test('departures with station object', co(function* (t) {
yield client.departures({
type: 'station',
id: '8011167',
id: jungfernh,
name: 'Berlin Jungfernheide',
location: {
type: 'location',
@ -306,7 +361,6 @@ test('locations named Jungfernheide', co(function* (t) {
}))
test('location', co(function* (t) {
const regensburgHbf = '8000309'
const loc = yield client.location(regensburgHbf)
assertValidStation(t, loc)

View file

@ -110,9 +110,14 @@ const assertValidLine = (t, l) => { // with optional mode
const test = tapePromise(tape)
const client = createClient(oebbProfile)
const salzburgHbf = '8100002'
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 salzburgHbf = '8100002'
const wienWestbahnhof = '1291501'
const journeys = yield client.journeys(salzburgHbf, wienWestbahnhof, {
when, passedStations: true
})
@ -120,6 +125,8 @@ test('Salzburg Hbf to Wien Westbahnhof', co(function* (t) {
t.ok(Array.isArray(journeys))
t.ok(journeys.length > 0, 'no journeys')
for (let journey of journeys) {
t.equal(journey.type, 'journey')
assertValidStation(t, journey.origin)
assertValidStationProducts(t, journey.origin.products)
// 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) {
const salzburgHbf = '8100002'
const wagramerStr = {
type: 'location',
latitude: 48.236216,
@ -221,7 +227,6 @@ test('Albertina to Salzburg Hbf', co(function* (t) {
name: 'Albertina',
id: '975900003'
}
const salzburgHbf = '8100002'
const journeys = yield client.journeys(albertina, salzburgHbf, {when})
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) {
const wien = '1190100'
const klagenfurtHbf = '8100085'
const salzburgHbf = '8100002'
const [journey] = yield client.journeys(wien, klagenfurtHbf, {
via: salzburgHbf,
results: 1,
@ -272,9 +274,57 @@ test('Wien to Klagenfurt Hbf with stopover at Salzburg Hbf', co(function* (t) {
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) {
const wienWestbahnhof = '1291501'
const muenchenHbf = '8000261'
const journeys = yield client.journeys(wienWestbahnhof, muenchenHbf, {
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) {
const salzburgHbf = '8100002'
const deps = yield client.departures(salzburgHbf, {
duration: 5, when
})
@ -363,7 +412,6 @@ test('locations named Salzburg', co(function* (t) {
}))
test('location', co(function* (t) {
const grazHbf = '8100173'
const loc = yield client.location(grazHbf)
assertValidStation(t, loc)

View file

@ -67,6 +67,8 @@ test('journeys  station to station', co(function* (t) {
t.strictEqual(journeys.length, 3)
for (let journey of journeys) {
t.equal(journey.type, 'journey')
assertValidStation(t, journey.origin)
assertValidStationProducts(t, journey.origin.products)
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) {
try {
yield client.journeys(spichernstr, bismarckstr, {
client.journeys(spichernstr, bismarckstr, {
when,
products: {
suburban: false,
@ -158,12 +160,64 @@ test('journeys  fails with no product', co(function* (t) {
regional: false
}
})
// silence rejections, we're only interested in exceptions
.catch(() => {})
} catch (err) {
t.ok(err, 'error thrown')
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) {
const journeys = yield client.journeys(spichernstr, amrumerStr, {
results: 1, when
@ -192,8 +246,9 @@ test('journey leg details', co(function* (t) {
test('journeys  station to address', co(function* (t) {
const journeys = yield client.journeys(spichernstr, {
type: 'location', address: 'Torfstraße 17',
latitude: 52.5416823, longitude: 13.3491223
type: 'location',
address: 'Torfstr. 17, Berlin',
latitude: 52.541797, longitude: 13.350042
}, {results: 1, when})
t.ok(Array.isArray(journeys))
@ -207,9 +262,9 @@ test('journeys  station to address', co(function* (t) {
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))
t.strictEqual(dest.address, '13353 Berlin-Wedding, Torfstr. 17')
t.ok(isRoughlyEqual(.0001, dest.latitude, 52.541797))
t.ok(isRoughlyEqual(.0001, dest.longitude, 13.350042))
assertValidWhen(t, leg.arrival, when)
t.end()
@ -219,7 +274,9 @@ test('journeys  station to address', co(function* (t) {
test('journeys  station to POI', co(function* (t) {
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
}, {results: 1, when})
@ -234,7 +291,8 @@ test('journeys  station to POI', co(function* (t) {
const dest = leg.destination
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.longitude, 13.351686))
assertValidWhen(t, leg.arrival, when)