merge #57: rewrite the tests

This commit is contained in:
Jannis Redmann 2018-06-10 15:06:08 +02:00 committed by GitHub
commit 47155cdb83
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 1468 additions and 1752 deletions

View file

@ -62,7 +62,11 @@ const createParseJourney = (profile, stations, lines, remarks, polylines) => {
) {
const tariff = j.trfRes.fareSetL[0].fareL[0]
if (tariff.prc >= 0) { // wat
res.price = {amount: tariff.prc / 100, hint: null}
res.price = {
amount: tariff.prc / 100,
currency: 'EUR',
hint: null
}
}
}

View file

@ -56,7 +56,7 @@
"tap-spec": "^4.1.1",
"tape": "^4.8.0",
"tape-promise": "^3.0.0",
"validate-fptf": "^1.2.1",
"validate-fptf": "^2.0.1",
"vbb-stations-autocomplete": "^3.1.0"
},
"scripts": {

View file

@ -1,83 +1,55 @@
'use strict'
const getStations = require('db-stations').full
const stations = require('db-stations/full.json')
const a = require('assert')
const tapePromise = require('tape-promise').default
const tape = require('tape')
const isRoughlyEqual = require('is-roughly-equal')
const co = require('./co')
const {createWhen} = require('./lib/util')
const co = require('./lib/co')
const createClient = require('..')
const dbProfile = require('../p/db')
const allProducts = require('../p/db/products')
const products = require('../p/db/products')
const {
assertValidStation,
assertValidPoi,
assertValidAddress,
assertValidLocation,
assertValidLine,
assertValidStopover,
createWhen, assertValidWhen
} = require('./util.js')
station: createValidateStation,
journeyLeg: createValidateJourneyLeg
} = require('./lib/validators')
const createValidate = require('./lib/validate-fptf-with')
const testJourneysStationToStation = require('./lib/journeys-station-to-station')
const testJourneysStationToAddress = require('./lib/journeys-station-to-address')
const testJourneysStationToPoi = require('./lib/journeys-station-to-poi')
const testEarlierLaterJourneys = require('./lib/earlier-later-journeys')
const journeysFailsWithNoProduct = require('./lib/journeys-fails-with-no-product')
const testDepartures = require('./lib/departures')
const testJourneysWithDetour = require('./lib/journeys-with-detour')
const isObj = o => o !== null && 'object' === typeof o && !Array.isArray(o)
const when = createWhen('Europe/Berlin', 'de-DE')
const assertValidStationProducts = (t, p) => {
t.ok(p)
t.equal(typeof p.nationalExp, 'boolean')
t.equal(typeof p.national, 'boolean')
t.equal(typeof p.regionalExp, '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.taxi, 'boolean')
const cfg = {
when,
stationCoordsOptional: false,
products
}
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 isJungfernheide = (s) => {
return s.type === 'station' &&
(s.id === '008011167' || s.id === jungfernh) &&
s.name === 'Berlin Jungfernheide' &&
s.location &&
isRoughlyEqual(.0005, s.location.latitude, 52.530408) &&
isRoughlyEqual(.0005, s.location.longitude, 13.299424)
}
const assertIsJungfernheide = (t, s) => {
t.equal(s.type, 'station')
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(.0005, s.location.latitude, 52.530408))
t.ok(isRoughlyEqual(.0005, s.location.longitude, 13.299424))
}
// todo: DRY with assertValidStationProducts
// todo: DRY with other tests
const assertValidProducts = (t, p) => {
for (let product of allProducts) {
product = product.id
t.equal(typeof p[product], 'boolean', 'product ' + p + ' must be a boolean')
const _validateStation = createValidateStation(cfg)
const validateStation = (validate, s, name) => {
_validateStation(validate, s, name)
const match = stations.some(station => (
station.id === s.id ||
(station.additionalIds && station.additionalIds.includes(s.id))
))
if (!match) {
console.error(name + `.id: unknown ID "${s.id}"`)
}
}
const validate = createValidate(cfg, {
station: validateStation
})
const assertValidPrice = (t, p) => {
t.ok(p)
if (p.amount !== null) {
@ -93,226 +65,167 @@ 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 jungfernheide = '8011167'
const blnSchwedterStr = '732652'
const westhafen = '008089116'
const wedding = '008089131'
const württembergallee = '731084'
const regensburgHbf = '8000309'
test('Berlin Jungfernheide to München Hbf', co(function* (t) {
const journeys = yield client.journeys(jungfernh, münchenHbf, {
departure: when, passedStations: true
test('journeys  Berlin Schwedter Str. to München Hbf', co(function* (t) {
const journeys = yield client.journeys(blnSchwedterStr, münchenHbf, {
results: 3, departure: when, passedStations: true
})
t.ok(Array.isArray(journeys))
t.ok(journeys.length > 0, 'no journeys')
yield testJourneysStationToStation({
test: t,
journeys,
validate,
fromId: blnSchwedterStr,
toId: münchenHbf
})
// todo: find a journey where there pricing info is always available
for (let journey of journeys) {
t.equal(journey.type, 'journey')
t.ok(Array.isArray(journey.legs))
t.ok(journey.legs.length > 0, 'no legs')
const leg = journey.legs[0] // todo: all legs
assertValidStation(t, leg.origin)
assertValidStationProducts(t, leg.origin.products)
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)
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('Berlin Jungfernheide to Torfstraße 17', co(function* (t) {
const journeys = yield client.journeys(jungfernh, {
type: 'location', address: 'Torfstraße 17',
latitude: 52.5416823, longitude: 13.3491223
}, {departure: when})
// todo: journeys, only one product
t.ok(Array.isArray(journeys))
t.ok(journeys.length >= 1, 'no journeys')
const journey = journeys[0]
const leg = journey.legs[journey.legs.length - 1]
test('journeys  fails with no product', (t) => {
journeysFailsWithNoProduct({
test: t,
fetchJourneys: client.journeys,
fromId: blnSchwedterStr,
toId: münchenHbf,
when,
products
})
t.end()
})
assertValidStation(t, leg.origin)
assertValidStationProducts(t, leg.origin.products)
if (!(yield findStation(leg.origin.id))) {
console.error('unknown station', leg.origin.id, leg.origin.name)
test('Berlin Schwedter Str. to Torfstraße 17', co(function* (t) {
const torfstr = {
type: 'location',
address: 'Torfstraße 17',
latitude: 52.5416823,
longitude: 13.3491223
}
if (leg.origin.products) assertValidProducts(t, leg.origin.products)
assertValidWhen(t, leg.departure, when)
assertValidWhen(t, leg.arrival, when)
const d = leg.destination
assertValidAddress(t, d)
t.equal(d.address, 'Torfstraße 17')
t.ok(isRoughlyEqual(.0001, d.latitude, 52.5416823))
t.ok(isRoughlyEqual(.0001, d.longitude, 13.3491223))
const journeys = yield client.journeys(blnSchwedterStr, torfstr, {
results: 3,
departure: when
})
yield testJourneysStationToAddress({
test: t,
journeys,
validate,
fromId: blnSchwedterStr,
to: torfstr
})
t.end()
}))
test('Berlin Jungfernheide to ATZE Musiktheater', co(function* (t) {
const journeys = yield client.journeys(jungfernh, {
type: 'location', id: '991598902', name: 'ATZE Musiktheater',
latitude: 52.542417, longitude: 13.350437
}, {departure: when})
t.ok(Array.isArray(journeys))
t.ok(journeys.length >= 1, 'no journeys')
const journey = journeys[0]
const leg = journey.legs[journey.legs.length - 1]
assertValidStation(t, leg.origin)
assertValidStationProducts(t, leg.origin.products)
if (!(yield findStation(leg.origin.id))) {
console.error('unknown station', leg.origin.id, leg.origin.name)
test('Berlin Schwedter Str. to ATZE Musiktheater', co(function* (t) {
const atze = {
type: 'location',
id: '991598902',
name: 'ATZE Musiktheater',
latitude: 52.542417,
longitude: 13.350437
}
if (leg.origin.products) assertValidProducts(t, leg.origin.products)
assertValidWhen(t, leg.departure, when)
assertValidWhen(t, leg.arrival, when)
const d = leg.destination
assertValidPoi(t, d)
t.equal(d.name, 'ATZE Musiktheater')
t.ok(isRoughlyEqual(.0001, d.latitude, 52.542399))
t.ok(isRoughlyEqual(.0001, d.longitude, 13.350402))
const journeys = yield client.journeys(blnSchwedterStr, atze, {
results: 3,
departure: when
})
yield testJourneysStationToPoi({
test: t,
journeys,
validate,
fromId: blnSchwedterStr,
to: atze
})
t.end()
}))
test('journeys: via works with detour', co(function* (t) {
// Going from Westhafen to Wedding via Württembergalle without detour
// is currently impossible. We check if the routing engine computes a detour.
const westhafen = '008089116'
const wedding = '008089131'
const württembergallee = '731084'
const [journey] = yield client.journeys(westhafen, wedding, {
const journeys = yield client.journeys(westhafen, wedding, {
via: württembergallee,
results: 1,
departure: when,
passedStations: true
})
t.ok(journey)
const l = journey.legs.some(l => l.passed && l.passed.some(p => p.station.id === württembergallee))
t.ok(l, 'Württembergalle is not being passed')
t.end()
}))
test('journeys: via works without detour', co(function* (t) {
// When going from Ruhleben to Zoo via Kastanienallee, there is *no need*
// to change trains / no need for a "detour".
const ruhleben = '000731058'
const zoo = '008010406'
const kastanienallee = '730983'
const [journey] = yield client.journeys(ruhleben, zoo, {
via: kastanienallee,
results: 1,
departure: when,
passedStations: true
yield testJourneysWithDetour({
test: t,
journeys,
validate,
detourIds: [württembergallee]
})
t.ok(journey)
const l = journey.legs.some(l => l.passed && l.passed.some(p => p.station.id === kastanienallee))
t.ok(l, 'Kastanienallee is not being passed')
t.end()
}))
// todo: without detour
test('earlier/later journeys, Jungfernheide -> München Hbf', co(function* (t) {
const model = yield client.journeys(jungfernh, münchenHbf, {
results: 3, departure: when
yield testEarlierLaterJourneys({
test: t,
fetchJourneys: client.journeys,
validate,
fromId: jungfernheide,
toId: münchenHbf
})
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, {
departure: when, earlierThan: model.earlierRef
})
})
t.throws(() => {
client.journeys(jungfernh, münchenHbf, {
departure: when, laterThan: model.laterRef
})
})
let earliestDep = Infinity, latestDep = -Infinity
for (let j of model) {
const dep = +new Date(j.legs[0].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.legs[0].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.legs[0].departure) > latestDep)
}
t.end()
}))
test('departures at Berlin Jungfernheide', co(function* (t) {
const deps = yield client.departures(jungfernh, {
test('journey leg details', co(function* (t) {
const journeys = yield client.journeys(berlinHbf, münchenHbf, {
results: 1, departure: 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})
const validateJourneyLeg = createValidateJourneyLeg(cfg)
const validate = createValidate(cfg, {
journeyLeg: (validate, leg, name) => {
if (!leg.direction) leg.direction = 'foo' // todo, see #49
validateJourneyLeg(validate, leg, name)
}
})
validate(t, leg, 'journeyLeg', 'leg')
t.end()
}))
test('departures at Berlin Schwedter Str.', co(function* (t) {
const departures = yield client.departures(blnSchwedterStr, {
duration: 5, when
})
t.ok(Array.isArray(deps))
for (let dep of deps) {
assertValidStation(t, dep.station)
assertValidStationProducts(t, dep.station.products)
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)
}
yield testDepartures({
test: t,
departures,
validate,
id: blnSchwedterStr
})
t.end()
}))
test('departures with station object', co(function* (t) {
yield client.departures({
const deps = yield client.departures({
type: 'station',
id: jungfernh,
id: jungfernheide,
name: 'Berlin Jungfernheide',
location: {
type: 'location',
@ -321,7 +234,7 @@ test('departures with station object', co(function* (t) {
}
}, {when})
t.ok('did not fail')
validate(t, deps, 'departures', 'departures')
t.end()
}))
@ -334,17 +247,18 @@ test('nearby Berlin Jungfernheide', co(function* (t) {
results: 2, distance: 400
})
t.ok(Array.isArray(nearby))
validate(t, nearby, 'locations', 'nearby')
t.equal(nearby.length, 2)
assertIsJungfernheide(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)
}
const s0 = nearby[0]
// todo: trim IDs
t.ok(s0.id === '008011167' || s0.id === jungfernheide)
t.equal(s0.name, 'Berlin Jungfernheide')
t.ok(isRoughlyEqual(.0005, s0.location.latitude, 52.530408))
t.ok(isRoughlyEqual(.0005, s0.location.longitude, 13.299424))
t.ok(s0.distance >= 0)
t.ok(s0.distance <= 100)
t.end()
}))
@ -354,29 +268,21 @@ test('locations named Jungfernheide', co(function* (t) {
results: 10
})
t.ok(Array.isArray(locations))
t.ok(locations.length > 0)
validate(t, locations, 'locations', 'locations')
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(isJungfernheide))
t.ok(locations.some((l) => {
// todo: trim IDs
return l.id === '008011167' || l.id === jungfernheide
}), 'Jungfernheide not found')
t.end()
}))
test('station', co(function* (t) {
const loc = yield client.station(regensburgHbf)
const s = yield client.station(regensburgHbf)
assertValidStation(t, loc)
t.equal(loc.id, regensburgHbf)
t.ok(Array.isArray(loc.lines))
if (Array.isArray(loc.lines)) {
for (let line of loc.lines) assertValidLine(t, line)
}
validate(t, s, 'station', 'station')
t.equal(s.id, regensburgHbf)
t.end()
}))

View file

@ -3,374 +3,244 @@
const tapePromise = require('tape-promise').default
const tape = require('tape')
const isRoughlyEqual = require('is-roughly-equal')
const validateFptf = require('validate-fptf')
const co = require('./co')
const {createWhen} = require('./lib/util')
const co = require('./lib/co')
const createClient = require('..')
const insaProfile = require('../p/insa')
const allProducts = require('../p/insa/products')
const {
assertValidStation,
assertValidPoi,
assertValidAddress,
assertValidLocation,
assertValidLine,
assertValidStopover,
hour,
createWhen,
assertValidWhen
} = require('./util.js')
const products = require('../p/insa/products')
const createValidate = require('./lib/validate-fptf-with')
const testJourneysStationToStation = require('./lib/journeys-station-to-station')
const testJourneysStationToAddress = require('./lib/journeys-station-to-address')
const testJourneysStationToPoi = require('./lib/journeys-station-to-poi')
const testEarlierLaterJourneys = require('./lib/earlier-later-journeys')
const journeysFailsWithNoProduct = require('./lib/journeys-fails-with-no-product')
const testDepartures = require('./lib/departures')
const testJourneysWithDetour = require('./lib/journeys-with-detour')
const isObj = o => o !== null && 'object' === typeof o && !Array.isArray(o)
const when = createWhen('Europe/Berlin', 'de-DE')
const assertValidStationProducts = (t, p) => {
t.ok(p)
t.equal(typeof p.nationalExp, 'boolean')
t.equal(typeof p.national, 'boolean')
t.equal(typeof p.regional, 'boolean')
t.equal(typeof p.suburban, 'boolean')
t.equal(typeof p.tram, 'boolean')
t.equal(typeof p.bus, 'boolean')
t.equal(typeof p.tourismTrain, 'boolean')
const cfg = {
when,
stationCoordsOptional: false,
products
}
const isMagdeburgHbf = s => {
return (
s.type === 'station' &&
(s.id === '8010224' || s.id === '008010224') &&
s.name === 'Magdeburg Hbf' &&
s.location &&
isRoughlyEqual(.001, s.location.latitude, 52.130352) &&
isRoughlyEqual(.001, s.location.longitude, 11.626891)
)
}
const assertIsMagdeburgHbf = (t, s) => {
t.equal(s.type, 'station')
t.ok(s.id === '8010224' || s.id === '008010224', 'id should be 8010224')
t.equal(s.name, 'Magdeburg Hbf')
t.ok(s.location)
t.ok(isRoughlyEqual(.001, s.location.latitude, 52.130352))
t.ok(isRoughlyEqual(.001, s.location.longitude, 11.626891))
}
// todo: DRY with assertValidStationProducts
// todo: DRY with other tests
const assertValidProducts = (t, p) => {
for (let product of allProducts) {
product = product.id
t.equal(typeof p[product], 'boolean', 'product ' + p + ' must be a boolean')
}
}
const validate = createValidate(cfg, {})
const test = tapePromise(tape)
const client = createClient(insaProfile)
test('Magdeburg Hbf to Magdeburg-Buckau', co(function*(t) {
const magdeburgHbf = '8010224'
const magdeburgBuckau = '8013456'
const magdeburgHbf = '8010224'
const magdeburgBuckau = '8013456'
const leiterstr = '7464'
const hasselbachplatzSternstrasse = '000006545'
const stendal = '008010334'
const dessau = '008010077'
test('journeys  Magdeburg Hbf to Magdeburg-Buckau', co(function* (t) {
const journeys = yield client.journeys(magdeburgHbf, magdeburgBuckau, {
results: 3,
departure: when,
passedStations: true
})
t.ok(Array.isArray(journeys))
t.ok(journeys.length > 0, 'no journeys')
for (let journey of journeys) {
t.ok(Array.isArray(journey.legs))
t.ok(journey.legs.length > 0, 'no legs')
const leg = journey.legs[0] // todo: all legs
assertValidStation(t, leg.origin)
assertValidStationProducts(t, leg.origin.products)
assertValidWhen(t, leg.departure, when)
t.equal(typeof leg.departurePlatform, 'string')
assertValidStation(t, leg.destination)
assertValidStationProducts(t, leg.origin.products)
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)
}
yield testJourneysStationToStation({
test: t,
journeys,
validate,
fromId: magdeburgHbf,
toId: magdeburgBuckau
})
t.end()
}))
// todo: journeys, only one product
test('journeys  fails with no product', (t) => {
journeysFailsWithNoProduct({
test: t,
fetchJourneys: client.journeys,
fromId: magdeburgHbf,
toId: magdeburgBuckau,
when,
products
})
t.end()
})
test('Magdeburg Hbf to 39104 Magdeburg, Sternstr. 10', co(function*(t) {
const magdeburgHbf = '8010224'
const sternStr = {
type: 'location',
address: 'Magdeburg - Altenstadt, Sternstraße 10',
latitude: 52.118414,
longitude: 11.422332,
address: 'Magdeburg - Altenstadt, Sternstraße 10'
longitude: 11.422332
}
const journeys = yield client.journeys(magdeburgHbf, sternStr, {
results: 3,
departure: 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)
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, 'Magdeburg - Altenstadt, Sternstraße 10')
t.ok(isRoughlyEqual(0.0001, d.latitude, 52.118414))
t.ok(isRoughlyEqual(0.0001, d.longitude, 11.422332))
yield testJourneysStationToAddress({
test: t,
journeys,
validate,
fromId: magdeburgHbf,
to: sternStr
})
t.end()
}))
test('Kloster Unser Lieben Frauen to Magdeburg Hbf', co(function*(t) {
test('Magdeburg Hbf to Kloster Unser Lieben Frauen', co(function*(t) {
const kloster = {
type: 'location',
latitude: 52.127601,
longitude: 11.636437,
id: '970012223',
name: 'Magdeburg, Kloster Unser Lieben Frauen (Denkmal)',
id: '970012223'
latitude: 52.127601,
longitude: 11.636437
}
const magdeburgHbf = '8010224'
const journeys = yield client.journeys(kloster, magdeburgHbf, {
const journeys = yield client.journeys(magdeburgHbf, kloster, {
results: 3,
departure: 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, 'Magdeburg, Kloster Unser Lieben Frauen (Denkmal)')
t.ok(isRoughlyEqual(0.0001, o.latitude, 52.127601))
t.ok(isRoughlyEqual(0.0001, o.longitude, 11.636437))
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)
yield testJourneysStationToPoi({
test: t,
journeys,
validate,
fromId: magdeburgHbf,
to: kloster
})
t.end()
}))
test('journeys: via works with detour', co(function* (t) {
// Going from Magdeburg, Hasselbachplatz (Sternstr.) (Tram/Bus) to Stendal via Dessau without detour
// is currently impossible. We check if the routing engine computes a detour.
const hasselbachplatzSternstrasse = '000006545'
const stendal = '008010334'
const dessau = '008010077'
const dessauPassed = '8010077'
const [journey] = yield client.journeys(hasselbachplatzSternstrasse, stendal, {
// Going from Magdeburg, Hasselbachplatz (Sternstr.) (Tram/Bus) to Stendal
// via Dessau without detour is currently impossible. We check if the routing
// engine computes a detour.
const journeys = yield client.journeys(hasselbachplatzSternstrasse, stendal, {
via: dessau,
results: 1,
departure: when,
passedStations: true
})
t.ok(journey)
const l = journey.legs.some(l => l.passed && l.passed.some(p => p.station.id === dessauPassed))
t.ok(l, 'Dessau is not being passed')
yield testJourneysWithDetour({
test: t,
journeys,
validate,
detourIds: ['8010077', dessau] // todo: trim IDs
})
t.end()
}))
test('journeys: via works without detour', co(function* (t) {
// When going from Magdeburg, Hasselbachplatz (Sternstr.) (Tram/Bus) to Magdeburg, Universität via Magdeburg, Breiter Weg, there is *no need*
// to change trains / no need for a "detour".
const hasselbachplatzSternstrasse = '000006545'
const universitaet = '000019686'
const breiterWeg = '000013519'
const breiterWegPassed = '13519'
// todo: without detour
const [journey] = yield client.journeys(hasselbachplatzSternstrasse, universitaet, {
via: breiterWeg,
results: 1,
departure: when,
passedStations: true
test('earlier/later journeys', co(function* (t) {
yield testEarlierLaterJourneys({
test: t,
fetchJourneys: client.journeys,
validate,
fromId: magdeburgHbf,
toId: magdeburgBuckau
})
t.ok(journey)
const l = journey.legs.some(l => l.passed && l.passed.some(p => p.station.id === breiterWegPassed))
t.ok(l, 'Magdeburg, Breiter Weg is not being passed')
t.end()
}))
test('departures at Magdeburg Hbf', co(function*(t) {
const magdeburgHbf = '8010224'
const deps = yield client.departures(magdeburgHbf, {
duration: 5,
when
})
t.ok(Array.isArray(deps))
for (let dep of deps) {
assertValidStation(t, dep.station)
assertValidStationProducts(t, dep.station.products)
if (dep.station.products) {
assertValidProducts(t, dep.station.products)
}
assertValidWhen(t, dep.when, when)
}
t.end()
}))
test('nearby Magdeburg Hbf', co(function*(t) {
const magdeburgHbfPosition = {
type: 'location',
latitude: 52.130352,
longitude: 11.626891
}
const nearby = yield client.nearby(magdeburgHbfPosition, {
results: 2,
distance: 400
})
t.ok(Array.isArray(nearby))
t.equal(nearby.length, 2)
assertIsMagdeburgHbf(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('journey leg details', co(function* (t) {
const magdeburgHbf = '8010224'
const magdeburgBuckau = '8013456'
const [journey] = yield client.journeys(magdeburgHbf, magdeburgBuckau, {
const journeys = yield client.journeys(magdeburgHbf, magdeburgBuckau, {
results: 1, departure: when
})
const p = journey.legs[0]
t.ok(p, 'missing legs[0]')
t.ok(p.id, 'missing legs[0].id')
t.ok(p.line, 'missing legs[0].line')
t.ok(p.line.name, 'missing legs[0].line.name')
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)
validate(t, leg, 'journeyLeg', 'leg')
t.end()
}))
test('departures at Magdeburg Leiterstr.', co(function*(t) {
const departures = yield client.departures(leiterstr, {
duration: 5, when
})
yield testDepartures({
test: t,
departures,
validate,
id: leiterstr
})
t.end()
}))
test('departures with station object', co(function* (t) {
const deps = yield client.departures({
type: 'station',
id: magdeburgHbf,
name: 'Magdeburg Hbf',
location: {
type: 'location',
latitude: 1.23,
longitude: 2.34
}
}, {when})
validate(t, deps, 'departures', 'departures')
t.end()
}))
// todo: nearby
test('locations named Magdeburg', co(function*(t) {
const locations = yield client.locations('Magdeburg', {
results: 10
results: 20
})
t.ok(Array.isArray(locations))
t.ok(locations.length > 0)
t.ok(locations.length <= 10)
validate(t, locations, 'locations', 'locations')
t.ok(locations.length <= 20)
for (let l of locations) {
if (l.type === 'station') assertValidStation(t, l)
else assertValidLocation(t, l)
}
t.ok(locations.some(isMagdeburgHbf))
t.ok(locations.find(s => s.type === 'station'))
t.ok(locations.find(s => s.id && s.name)) // POIs
t.ok(locations.some((loc) => {
return (
loc.id === '008010224' || // todo: trim IDs
loc.id === magdeburgHbf
)
}))
t.end()
}))
test('station', co(function*(t) {
const magdeburgBuckau = '8013456'
const loc = yield client.station(magdeburgBuckau)
test('station Magdeburg-Buckau', co(function* (t) {
const s = yield client.station(magdeburgBuckau)
assertValidStation(t, loc)
t.equal(loc.id, magdeburgBuckau)
validate(t, s, 'station', 'station')
t.equal(s.id, magdeburgBuckau)
t.end()
}))
test('radar', co(function* (t) {
const north = 52.148364
const west = 11.600826
const south = 52.108486
const east = 11.651451
const vehicles = yield client.radar({north, west, south, east}, {
const vehicles = yield client.radar({
north: 52.148364,
west: 11.600826,
south: 52.108486,
east: 11.651451
}, {
duration: 5 * 60, when, results: 10
})
t.ok(Array.isArray(vehicles))
t.ok(vehicles.length > 0)
for (let v of vehicles) {
assertValidLine(t, v.line)
const customCfg = Object.assign({}, cfg, {
stationCoordsOptional: true, // see #28
})
const validate = createValidate(customCfg, {})
validate(t, vehicles, 'movements', 'vehicles')
t.equal(typeof v.location.latitude, 'number')
t.ok(v.location.latitude <= 57, 'vehicle is too far away')
t.ok(v.location.latitude >= 47, 'vehicle is too far away')
t.equal(typeof v.location.longitude, 'number')
t.ok(v.location.longitude >= 8, 'vehicle is too far away')
t.ok(v.location.longitude <= 14, '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)
// note that this can be an ICE train
t.ok(isRoughlyEqual(14 * hour, +when, dep))
}
}
t.ok(Array.isArray(v.frames))
for (let f of v.frames) {
// see #28
// todo: check if this works by now
assertValidStation(t, f.origin, true)
assertValidStationProducts(t, f.origin.products)
assertValidStation(t, f.destination, true)
assertValidStationProducts(t, f.destination.products)
t.equal(typeof f.t, 'number')
}
}
t.end()
}))

View file

@ -1,3 +1,5 @@
'use strict'
// https://github.com/babel/babel/blob/3c8d831fe41f502cbe2459a271d19c7329ffe369/packages/babel-helpers/src/helpers.js#L242-L270
const co = (fn) => {
return function run () {

21
test/lib/departures.js Normal file
View file

@ -0,0 +1,21 @@
'use strict'
const co = require('./co')
const testDepartures = co(function* (cfg) {
const {test: t, departures: deps, validate, id} = cfg
validate(t, deps, 'departures', 'departures')
t.ok(deps.length > 0, 'must be >0 departures')
for (let i = 0; i < deps.length; i++) {
const dep = deps[i]
const name = `deps[${i}]`
t.equal(dep.station.id, id, name + '.station.id is invalid')
}
// todo: move into deps validator
t.deepEqual(deps, deps.sort((a, b) => t.when > b.when))
})
module.exports = testDepartures

View file

@ -0,0 +1,82 @@
'use strict'
const co = require('./co')
const testEarlierLaterJourneys = co(function* (cfg) {
const {
test: t,
fetchJourneys,
fromId,
toId,
when,
// todo: validate
} = cfg
const model = yield fetchJourneys(fromId, toId, {
results: 3, departure: when
})
// todo: move to journeys validator?
t.equal(typeof model.earlierRef, 'string')
t.ok(model.earlierRef)
t.equal(typeof model.laterRef, 'string')
t.ok(model.laterRef)
// departure/arrival and earlierThan/laterThan should be mutually exclusive
t.throws(() => {
fetchJourneys(fromId, toId, {
departure: when, earlierThan: model.earlierRef
})
// silence rejections, we're only interested in exceptions
.catch(() => {})
})
t.throws(() => {
fetchJourneys(fromId, toId, {
departure: when, laterThan: model.laterRef
})
// silence rejections, we're only interested in exceptions
.catch(() => {})
})
t.throws(() => {
fetchJourneys(fromId, toId, {
arrival: when, earlierThan: model.earlierRef
})
// silence rejections, we're only interested in exceptions
.catch(() => {})
})
t.throws(() => {
fetchJourneys(fromId, toId, {
arrival: when, laterThan: model.laterRef
})
// silence rejections, we're only interested in exceptions
.catch(() => {})
})
let earliestDep = Infinity, latestDep = -Infinity
for (let j of model) {
if (j.legs[0].departure === null) continue
const dep = +new Date(j.legs[0].departure)
if (dep < earliestDep) earliestDep = dep
else if (dep > latestDep) latestDep = dep
}
const earlier = yield fetchJourneys(fromId, toId, {
results: 3,
// todo: single journey ref?
earlierThan: model.earlierRef
})
for (let j of earlier) {
t.ok(new Date(j.legs[0].departure) < earliestDep)
}
const later = yield fetchJourneys(fromId, toId, {
results: 3,
// todo: single journey ref?
laterThan: model.laterRef
})
for (let j of later) {
t.ok(new Date(j.legs[0].departure) > latestDep)
}
})
module.exports = testEarlierLaterJourneys

View file

@ -0,0 +1,23 @@
'use strict'
const journeysFailsWithNoProduct = (cfg) => {
const {
test: t,
fetchJourneys,
fromId,
toId,
when,
products
} = cfg
const productsObj = Object.create(null)
for (let p of products) productsObj[p.id] = false
t.throws(() => {
client.journeys(fromId, toId, {departure: when, products})
// silence rejections, we're only interested in exceptions
.catch(() => {})
})
}
module.exports = journeysFailsWithNoProduct

View file

@ -0,0 +1,29 @@
'use strict'
const isRoughlyEqual = require('is-roughly-equal')
const co = require('./co')
const testJourneysStationToAddress = co(function* (cfg) {
const {test: t, journeys, validate, fromId} = cfg
const {address, latitude, longitude} = cfg.to
validate(t, journeys, 'journeys', 'journeys')
t.strictEqual(journeys.length, 3)
for (let i = 0; i < journeys.length; i++) {
const j = journeys[i]
const firstLeg = j.legs[0]
t.strictEqual(firstLeg.origin.id, fromId)
const d = j.legs[j.legs.length - 1].destination
const n = `journeys[0].legs[${i}].destination`
t.strictEqual(d.type, 'location', n + '.type is invalid')
t.strictEqual(d.address, address, n + '.address is invalid')
t.ok(isRoughlyEqual(.0001, d.latitude, latitude), n + '.latitude is invalid')
t.ok(isRoughlyEqual(.0001, d.longitude, longitude), n + '.longitude is invalid')
}
})
module.exports = testJourneysStationToAddress

View file

@ -0,0 +1,30 @@
'use strict'
const isRoughlyEqual = require('is-roughly-equal')
const co = require('./co')
const testJourneysStationToPoi = co(function* (cfg) {
const {test: t, journeys, validate, fromId} = cfg
const {id, name, latitude, longitude} = cfg.to
validate(t, journeys, 'journeys', 'journeys')
t.strictEqual(journeys.length, 3)
for (let i = 0; i < journeys.length; i++) {
const j = journeys[i]
const firstLeg = j.legs[0]
t.strictEqual(firstLeg.origin.id, fromId)
const d = j.legs[j.legs.length - 1].destination
const n = `journeys[0].legs[${i}].destination`
t.strictEqual(d.type, 'location', n + '.type is invalid')
t.strictEqual(d.id, id, n + '.id is invalid')
t.strictEqual(d.name, name, n + '.name is invalid')
t.ok(isRoughlyEqual(.0001, d.latitude, latitude), n + '.latitude is invalid')
t.ok(isRoughlyEqual(.0001, d.longitude, longitude), n + '.longitude is invalid')
}
})
module.exports = testJourneysStationToPoi

View file

@ -0,0 +1,20 @@
'use strict'
const co = require('./co')
const testJourneysStationToStation = co(function* (cfg) {
const {test: t, journeys, validate, fromId, toId} = cfg
validate(t, journeys, 'journeys', 'journeys')
t.strictEqual(journeys.length, 3)
for (let i = 0; i < journeys.length; i++) {
const j = journeys[i]
const firstLeg = j.legs[0]
const lastLeg = j.legs[j.legs.length - 1]
t.strictEqual(firstLeg.origin.id, fromId)
t.strictEqual(lastLeg.destination.id, toId)
}
})
module.exports = testJourneysStationToStation

View file

@ -0,0 +1,21 @@
'use strict'
const co = require('./co')
const testJourneysWithDetour = co(function* (cfg) {
const {test: t, journeys, validate, detourIds} = cfg
// We assume that going from A to B via C *without* detour is currently
// impossible. We check if the routing engine computes a detour.
validate(t, journeys, 'journeys', 'journeys')
const leg = journeys[0].legs.some((leg) => {
return leg.passed && leg.passed.some((passed) => {
return detourIds.includes(passed.station.id)
})
})
t.ok(leg, detourIds.join('/') + ' is not being passed')
})
module.exports = testJourneysWithDetour

28
test/lib/util.js Normal file
View file

@ -0,0 +1,28 @@
'use strict'
const isRoughlyEqual = require('is-roughly-equal')
const {DateTime} = require('luxon')
const a = require('assert')
const hour = 60 * 60 * 1000
const day = 24 * hour
const week = 7 * day
// next Monday 10 am
const createWhen = (timezone, locale) => {
return DateTime.fromMillis(Date.now(), {
zone: timezone,
locale,
}).startOf('week').plus({weeks: 1, hours: 10}).toJSDate()
}
const assertValidWhen = (actual, expected, name) => {
const ts = +new Date(actual)
a.ok(!Number.isNaN(ts), name + ' is not parsable by Date')
// the timestamps might be from long-distance trains
a.ok(isRoughlyEqual(day, +expected, ts), name + ' is out of range')
}
module.exports = {
hour, createWhen, assertValidWhen
}

View file

@ -0,0 +1,30 @@
'use strict'
const {defaultValidators} = require('validate-fptf')
const anyOf = require('validate-fptf/lib/any-of')
const validators = require('./validators')
const create = (cfg, customValidators = {}) => {
const val = Object.assign({}, defaultValidators)
for (let key of Object.keys(validators)) {
val[key] = validators[key](cfg)
}
Object.assign(val, customValidators)
const validateFptfWith = (t, item, allowedTypes, name) => {
try {
if ('string' === typeof allowedTypes) {
val[allowedTypes](val, item, name)
} else {
anyOf(allowedTypes, val, item, name)
}
t.pass(name + ' is valid')
} catch (err) {
t.ifError(err) // todo: improve error logging
}
}
return validateFptfWith
}
module.exports = create

306
test/lib/validators.js Normal file
View file

@ -0,0 +1,306 @@
'use strict'
const a = require('assert')
const {defaultValidators} = require('validate-fptf')
const anyOf = require('validate-fptf/lib/any-of')
const {assertValidWhen} = require('./util')
const isObj = o => o !== null && 'object' === typeof o && !Array.isArray(o)
const is = val => val !== null && val !== undefined
const createValidateStation = (cfg) => {
const validateStation = (val, s, name = 'station') => {
defaultValidators.station(val, s, name)
if (!cfg.stationCoordsOptional) {
a.ok(is(s.location), `missing ${name}.location`)
}
a.ok(isObj(s.products), name + '.products must be an object')
for (let product of cfg.products) {
const msg = name + `.products[${product.id}] must be a boolean`
a.strictEqual(typeof s.products[product.id], 'boolean', msg)
}
if ('lines' in s) {
a.ok(Array.isArray(s.lines), name + `.lines must be an array`)
for (let i = 0; i < s.lines.length; i++) {
val.line(val, s.lines[i], name + `.lines[${i}]`)
}
}
}
return validateStation
}
const validatePoi = (val, poi, name = 'location') => {
defaultValidators.location(val, poi, name)
val.ref(val, poi.id, name + '.id')
a.ok(poi.name, name + '.name must not be empty')
}
const validateAddress = (val, addr, name = 'location') => {
defaultValidators.location(val, addr, name)
a.strictEqual(typeof addr.address, 'string', name + '.address must be a string')
a.ok(addr.address, name + '.address must not be empty')
}
const validateLocation = (val, loc, name = 'location') => {
a.ok(isObj(loc), name + ' must be an object')
if (loc.type === 'station') val.station(val, loc, name)
else if ('id' in loc) validatePoi(val, loc, name)
else if (!('name' in loc) && ('address' in loc)) {
validateAddress(val, loc, name)
} else defaultValidators.location(val, loc, name)
}
const validateLocations = (val, locs, name = 'locations') => {
a.ok(Array.isArray(locs), name + ' must be an array')
a.ok(locs.length > 0, name + ' must not be empty')
for (let i = 0; i < locs.length; i++) {
val.location(val, locs[i], name + `[${i}]`)
}
}
const createValidateLine = (cfg) => {
const validLineModes = []
for (let product of cfg.products) {
if (!validLineModes.includes(product.mode)) {
validLineModes.push(product.mode)
}
}
const validateLine = (val, line, name = 'line') => {
defaultValidators.line(val, line, name)
a.ok(validLineModes.includes(line.mode), name + '.mode is invalid')
}
return validateLine
}
const createValidateStopover = (cfg) => {
const validateStopover = (val, s, name = 'stopover') => {
if (is(s.arrival)) {
val.date(val, s.arrival, name + '.arrival')
assertValidWhen(s.arrival, cfg.when, name)
}
if (is(s.departure)) {
val.date(val, s.departure, name + '.departure')
assertValidWhen(s.departure, cfg.when, name)
}
if (!is(s.arrival) && !is(s.departure)) {
a.fail(name + ' contains neither arrival nor departure')
}
if (is(s.arrivalDelay)) {
const msg = name + '.arrivalDelay must be a number'
a.strictEqual(typeof s.arrivalDelay, 'number', msg)
}
if (is(s.departureDelay)) {
const msg = name + '.departureDelay must be a number'
a.strictEqual(typeof s.departureDelay, 'number', msg)
}
val.station(val, s.station, name + '.station')
}
return validateStopover
}
const validateTicket = (val, ti, name = 'ticket') => {
a.strictEqual(typeof ti.name, 'string', name + '.name must be a string')
a.ok(ti.name, name + '.name must not be empty')
if (is(ti.price)) {
a.strictEqual(typeof ti.price, 'number', name + '.price must be a number')
a.ok(ti.price > 0, name + '.price must be >0')
}
if (is(ti.amount)) {
a.strictEqual(typeof ti.amount, 'number', name + '.amount must be a number')
a.ok(ti.amount > 0, name + '.amount must be >0')
}
// todo: move to VBB tests
if ('bike' in ti) {
a.strictEqual(typeof ti.bike, 'boolean', name + '.bike must be a boolean')
}
if ('shortTrip' in ti) {
a.strictEqual(typeof ti.shortTrip, 'boolean', name + '.shortTrip must be a boolean')
}
if ('group' in ti) {
a.strictEqual(typeof ti.group, 'boolean', name + '.group must be a boolean')
}
if ('fullDay' in ti) {
a.strictEqual(typeof ti.fullDay, 'boolean', name + '.fullDay must be a boolean')
}
if ('tariff' in ti) {
a.strictEqual(typeof ti.tariff, 'string', name + '.tariff must be a string')
a.ok(ti.tariff, name + '.tariff must not be empty')
}
if ('coverage' in ti) {
a.strictEqual(typeof ti.coverage, 'string', name + '.coverage must be a string')
a.ok(ti.coverage, name + '.coverage must not be empty')
}
if ('variant' in ti) {
a.strictEqual(typeof ti.variant, 'string', name + '.variant must be a string')
a.ok(ti.variant, name + '.variant must not be empty')
}
}
const createValidateJourneyLeg = (cfg) => {
const validateJourneyLeg = (val, leg, name = 'journeyLeg') => {
const withFakeScheduleAndOperator = Object.assign({
schedule: 'foo', // todo: let hafas-client parse a schedule ID
operator: 'bar' // todo: let hafas-client parse the operator
}, leg)
defaultValidators.journeyLeg(val, withFakeScheduleAndOperator, name)
if (leg.arrival !== null) {
assertValidWhen(leg.arrival, cfg.when, name + '.arrival')
}
if (leg.departure !== null) {
assertValidWhen(leg.departure, cfg.when, name + '.departure')
}
// todo: leg.arrivalPlatform !== null
if (is(leg.arrivalPlatform)) {
const msg = name + '.arrivalPlatform must be a string'
a.strictEqual(typeof leg.arrivalPlatform, 'string', msg)
a.ok(leg.arrivalPlatform, name + '.arrivalPlatform must not be empty')
}
// todo: leg.departurePlatform !== null
if (is(leg.departurePlatform)) {
const msg = name + '.departurePlatform must be a string'
a.strictEqual(typeof leg.departurePlatform, 'string', msg)
a.ok(leg.departurePlatform, name + '.departurePlatform must not be empty')
}
if ('passed' in leg) {
a.ok(Array.isArray(leg.passed), name + '.passed must be an array')
a.ok(leg.passed.length > 0, name + '.passed must not be empty')
for (let i = 0; i < leg.passed.length; i++) {
val.stopover(val, leg.passed[i], name + `.passed[${i}]`)
}
}
if (leg.mode !== 'walking') {
const msg = name + '.direction must be a string'
a.strictEqual(typeof leg.direction, 'string', msg)
a.ok(leg.direction, name + '.direction must not be empty')
}
}
return validateJourneyLeg
}
const validateJourney = (val, j, name = 'journey') => {
const withFakeId = Object.assign({
id: 'foo' // todo: let hafas-client parse a journey ID
}, j)
defaultValidators.journey(val, withFakeId, name)
if ('tickets' in j) {
a.ok(Array.isArray(j.tickets), name + '.tickets must be an array')
a.ok(j.tickets.length > 0, name + '.tickets must not be empty')
for (let i = 0; i < j.tickets.length; i++) {
val.ticket(val, j.tickets[i], name + `.tickets[${i}]`)
}
}
}
const validateJourneys = (val, js, name = 'journeys') => {
a.ok(Array.isArray(js), name + ' must be an array')
a.ok(js.length > 0, name + ' must not be empty')
for (let i = 0; i < js.length; i++) {
val.journey(val, js[i], name + `[${i}]`)
}
}
const createValidateDeparture = (cfg) => {
const validateDeparture = (val, dep, name = 'departure') => {
a.ok(isObj(dep), name + ' must be an object')
// todo: let hafas-client add a .type field
a.strictEqual(typeof dep.journeyId, 'string', name + '.journeyId must be a string')
a.ok(dep.journeyId, name + '.journeyId must not be empty')
a.strictEqual(typeof dep.trip, 'number', name + '.trip must be a number')
val.station(val, dep.station, name + '.station')
assertValidWhen(dep.when, cfg.when, name)
if (dep.delay !== null) {
const msg = name + '.delay must be a number'
a.strictEqual(typeof dep.delay, 'number', msg)
}
val.line(val, dep.line, name + '.line')
a.strictEqual(typeof dep.direction, 'string', name + '.direction must be a string')
a.ok(dep.direction, name + '.direction must not be empty')
}
return validateDeparture
}
const validateDepartures = (val, deps, name = 'departures') => {
a.ok(Array.isArray(deps), name + ' must be an array')
a.ok(deps.length > 0, name + ' must not be empty')
for (let i = 0; i < deps.length; i++) {
val.departure(val, deps[i], name + `[${i}]`)
}
}
const validateMovement = (val, m, name = 'movement') => {
a.ok(isObj(m), name + ' must be an object')
// todo: let hafas-client add a .type field
val.line(val, m.line, name + '.line')
a.strictEqual(typeof m.direction, 'string', name + '.direction must be a string')
a.ok(m.direction, name + '.direction must not be empty')
const lName = name + '.location'
val.location(val, m.location, lName)
a.ok(m.location.latitude <= 55, lName + '.latitude is too small')
a.ok(m.location.latitude >= 45, lName + '.latitude is too large')
a.ok(m.location.longitude >= 9, lName + '.longitude is too small')
a.ok(m.location.longitude <= 15, lName + '.longitude is too small')
a.ok(Array.isArray(m.nextStops), name + '.nextStops must be an array')
for (let i = 0; i < m.nextStops.length; i++) {
const st = m.nextStops[i]
val.stopover(val, m.nextStops[i], name + `.nextStops[${i}]`)
}
a.ok(Array.isArray(m.frames), name + '.frames must be an array')
a.ok(m.frames.length > 0, name + '.frames must not be empty')
for (let i = 0; i < m.frames.length; i++) {
const f = m.frames[i]
const fName = name + `.frames[${i}]`
a.ok(isObj(f), fName + ' must be an object')
anyOf(['location', 'station'], val, f.origin, fName + '.origin')
anyOf(['location', 'station'], val, f.destination, fName + '.destination')
a.strictEqual(typeof f.t, 'number', fName + '.frames must be a number')
}
}
const validateMovements = (val, ms, name = 'movements') => {
a.ok(Array.isArray(ms), name + ' must be an array')
a.ok(ms.length > 0, name + ' must not be empty')
for (let i = 0; i < ms.length; i++) {
val.movement(val, ms[i], name + `[${i}]`)
}
}
module.exports = {
station: createValidateStation,
location: () => validateLocation,
locations: () => validateLocations,
poi: () => validatePoi,
address: () => validateAddress,
line: createValidateLine,
stopover: createValidateStopover,
ticket: () => validateTicket,
journeyLeg: createValidateJourneyLeg,
journey: () => validateJourney,
journeys: () => validateJourneys,
departure: createValidateDeparture,
departures: () => validateDepartures,
movement: () => validateMovement,
movements: () => validateMovements
}

View file

@ -1,69 +1,49 @@
'use strict'
// todo
// const getStations = require('db-stations').full
const tapePromise = require('tape-promise').default
const tape = require('tape')
const isRoughlyEqual = require('is-roughly-equal')
const validateFptf = require('validate-fptf')
const validateLineWithoutMode = require('./validate-line-without-mode')
const co = require('./co')
const {createWhen} = require('./lib/util')
const co = require('./lib/co')
const createClient = require('..')
const nahshProfile = require('../p/nahsh')
const allProducts = require('../p/nahsh/products')
const products = require('../p/nahsh/products')
const {
assertValidStation,
assertValidPoi,
assertValidAddress,
assertValidLocation,
assertValidStopover,
hour, createWhen, assertValidWhen
} = require('./util.js')
line: createValidateLine,
station: createValidateStation
} = require('./lib/validators')
const createValidate = require('./lib/validate-fptf-with')
const testJourneysStationToStation = require('./lib/journeys-station-to-station')
const testJourneysStationToAddress = require('./lib/journeys-station-to-address')
const testJourneysStationToPoi = require('./lib/journeys-station-to-poi')
const testEarlierLaterJourneys = require('./lib/earlier-later-journeys')
const journeysFailsWithNoProduct = require('./lib/journeys-fails-with-no-product')
const testDepartures = require('./lib/departures')
const when = createWhen('Europe/Berlin', 'de-DE')
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')
const cfg = {
when,
stationCoordsOptional: false,
products
}
const isKielHbf = (s) => {
return s.type === 'station' &&
(s.id === '8000199') &&
s.name === 'Kiel Hbf' &&
s.location &&
isRoughlyEqual(.0005, s.location.latitude, 54.314982) &&
isRoughlyEqual(.0005, s.location.longitude, 10.131976)
}
const assertIsKielHbf = (t, s) => {
t.equal(s.type, 'station')
t.ok(s.id === '8000199', 'id should be 8000199')
t.equal(s.name, 'Kiel Hbf')
t.ok(s.location)
t.ok(isRoughlyEqual(.0005, s.location.latitude, 54.314982))
t.ok(isRoughlyEqual(.0005, s.location.longitude, 10.131976))
}
// todo: DRY with assertValidStationProducts
// todo: DRY with other tests
const assertValidProducts = (t, products) => {
for (let p of allProducts) {
t.equal(typeof products[p.id], 'boolean', `product ${p.id} must be a boolean`)
const _validateLine = createValidateLine(cfg)
const validateLine = (validate, l, name) => {
if (l && l.product === 'onCall') {
// skip line validation
// https://github.com/derhuerst/hafas-client/issues/8#issuecomment-355839965
l = Object.assign({}, l)
l.mode = 'taxi'
}
_validateLine(validate, l, name)
}
const validate = createValidate(cfg, {
line: validateLine
})
const assertValidPrice = (t, p) => {
t.ok(p)
if (p.amount !== null) {
@ -76,18 +56,6 @@ const assertValidPrice = (t, p) => {
}
}
const assertValidLine = (t, l) => { // with optional mode
const validators = Object.assign({}, validateFptf.defaultValidators, {
line: validateLineWithoutMode
})
const recurse = validateFptf.createRecurse(validators)
try {
recurse(['line'], l, 'line')
} catch (err) {
t.ifError(err)
}
}
const test = tapePromise(tape)
const client = createClient(nahshProfile)
@ -97,192 +65,125 @@ const luebeckHbf = '8000237'
const husum = '8000181'
const schleswig = '8005362'
test('Kiel Hbf to Flensburg', co(function* (t) {
test('journeys  Kiel Hbf to Flensburg', co(function* (t) {
const journeys = yield client.journeys(kielHbf, flensburg, {
departure: when, passedStations: true
results: 3,
departure: 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')
yield testJourneysStationToStation({
test: t,
journeys,
validate,
fromId: kielHbf,
toId: flensburg
})
t.ok(Array.isArray(journey.legs))
t.ok(journey.legs.length > 0, 'no legs')
const leg = journey.legs[0] // todo: all legs
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)
for (let i = 0; i < journeys.length; i++) {
const j = journeys[i]
// todo: find a journey where there pricing info is always available
if (j.price) assertValidPrice(t, j.price, `journeys[${i}].price`)
}
t.end()
}))
// todo: journeys, only one product
test('journeys  fails with no product', (t) => {
journeysFailsWithNoProduct({
test: t,
fetchJourneys: client.journeys,
fromId: kielHbf,
toId: flensburg,
when,
products
})
t.end()
})
test('Kiel Hbf to Husum, Zingel 10', co(function* (t) {
const zingel = {
type: 'location',
address: 'Husum, Zingel 10',
latitude: 54.475359,
longitude: 9.050798,
address: 'Husum, Zingel 10'
longitude: 9.050798
}
const journeys = yield client.journeys(kielHbf, zingel, {departure: 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, 'Husum, Zingel 10')
t.ok(isRoughlyEqual(.0001, d.latitude, 54.475359))
t.ok(isRoughlyEqual(.0001, d.longitude, 9.050798))
t.end()
}))
test('Holstentor to Kiel Hbf', co(function* (t) {
const holstentor = {
type: 'location',
latitude: 53.866321,
longitude: 10.679976,
name: 'Hansestadt Lübeck, Holstentor (Denkmal)',
id: '970003547'
}
const journeys = yield client.journeys(holstentor, kielHbf, {departure: 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, 'Hansestadt Lübeck, Holstentor (Denkmal)')
t.ok(isRoughlyEqual(.0001, o.latitude, 53.866321))
t.ok(isRoughlyEqual(.0001, o.longitude, 10.679976))
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('Husum to Lübeck Hbf with stopover at Husum', co(function* (t) {
const [journey] = yield client.journeys(husum, luebeckHbf, {
via: kielHbf,
results: 1,
const journeys = yield client.journeys(kielHbf, zingel, {
results: 3,
departure: when
})
const i1 = journey.legs.findIndex(leg => leg.destination.id === kielHbf)
t.ok(i1 >= 0, 'no leg with Kiel Hbf as destination')
yield testJourneysStationToAddress({
test: t,
journeys,
validate,
fromId: kielHbf,
to: zingel
})
t.end()
}))
const i2 = journey.legs.findIndex(leg => leg.origin.id === kielHbf)
t.ok(i2 >= 0, 'no leg with Kiel Hbf as origin')
t.ok(i2 > i1, 'leg with Kiel Hbf as origin must be after leg to it')
test('Kiel Hbf to Holstentor', co(function* (t) {
const holstentor = {
type: 'location',
id: '970003547',
name: 'Hansestadt Lübeck, Holstentor (Denkmal)',
latitude: 53.866321,
longitude: 10.679976
}
const journeys = yield client.journeys(kielHbf, holstentor, {
results: 3,
departure: when
})
yield testJourneysStationToPoi({
test: t,
journeys,
validate,
fromId: kielHbf,
to: holstentor
})
t.end()
}))
test('Husum to Lübeck Hbf with stopover at Kiel Hbf', co(function* (t) {
const journeys = yield client.journeys(husum, luebeckHbf, {
via: kielHbf,
results: 1,
departure: when,
passedStations: true
})
validate(t, journeys, 'journeys', 'journeys')
const leg = journeys[0].legs.some((leg) => {
return leg.passed && leg.passed.some((passed) => {
return passed.station.id === kielHbf
})
})
t.ok(leg, 'Kiel Hbf is not being passed')
t.end()
}))
test('earlier/later journeys, Kiel Hbf -> Flensburg', co(function* (t) {
const model = yield client.journeys(kielHbf, flensburg, {
results: 3, departure: when
yield testEarlierLaterJourneys({
test: t,
fetchJourneys: client.journeys,
validate,
fromId: kielHbf,
toId: flensburg
})
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(kielHbf, flensburg, {
departure: when, earlierThan: model.earlierRef
})
})
t.throws(() => {
client.journeys(kielHbf, flensburg, {
departure: when, laterThan: model.laterRef
})
})
let earliestDep = Infinity, latestDep = -Infinity
for (let j of model) {
const dep = +new Date(j.legs[0].departure)
if (dep < earliestDep) earliestDep = dep
else if (dep > latestDep) latestDep = dep
}
const earlier = yield client.journeys(kielHbf, flensburg, {
results: 3,
// todo: single journey ref?
earlierThan: model.earlierRef
})
for (let j of earlier) {
t.ok(new Date(j.legs[0].departure) < earliestDep)
}
const later = yield client.journeys(kielHbf, flensburg, {
results: 3,
// todo: single journey ref?
laterThan: model.laterRef
})
for (let j of later) {
t.ok(new Date(j.legs[0].departure) > latestDep)
}
t.end()
}))
test('leg details for Flensburg to Husum', co(function* (t) {
// todo: with detour test
// todo: without detour test
test('journey leg details for Flensburg to Husum', co(function* (t) {
const journeys = yield client.journeys(flensburg, husum, {
results: 1, departure: when
})
@ -292,37 +193,39 @@ test('leg details for Flensburg to Husum', co(function* (t) {
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)
validate(t, leg, 'journeyLeg', 'leg')
t.end()
}))
test('departures at Kiel Hbf', co(function* (t) {
const deps = yield client.departures(kielHbf, {
test('departures at Kiel Räucherei', co(function* (t) {
const kielRaeucherei = '3440091'
const departures = yield client.departures(kielRaeucherei, {
duration: 30, 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)
}
yield testDepartures({
test: t,
departures,
validate,
id: kielRaeucherei
})
t.end()
}))
test('departures with station object', co(function* (t) {
const deps = yield client.departures({
type: 'station',
id: kielHbf,
name: 'Kiel Hbf',
location: {
type: 'location',
latitude: 1.23,
longitude: 2.34
}
}, {when})
validate(t, deps, 'departures', 'departures')
t.end()
}))
@ -336,77 +239,44 @@ test('nearby Kiel Hbf', co(function* (t) {
results: 2, distance: 400
})
validate(t, nearby, 'locations', 'nearby')
t.ok(Array.isArray(nearby))
t.equal(nearby.length, 2)
assertIsKielHbf(t, nearby[0])
t.equal(nearby[0].id, kielHbf)
t.equal(nearby[0].name, 'Kiel Hbf')
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 Kiel', co(function* (t) {
const locations = yield client.locations('Kiel', {
results: 10
results: 20
})
t.ok(Array.isArray(locations))
t.ok(locations.length > 0)
t.ok(locations.length <= 10)
validate(t, locations, 'locations', 'locations')
t.ok(locations.length <= 20)
for (let l of locations) {
if (l.type === 'station') assertValidStation(t, l)
else assertValidLocation(t, l)
}
t.ok(locations.some(isKielHbf))
t.ok(locations.find(s => s.type === 'station'))
t.ok(locations.find(s => s.id && s.name)) // POIs
t.ok(locations.some(l => l.id === kielHbf))
t.end()
}))
test('station', co(function* (t) {
const loc = yield client.station(schleswig)
const s = yield client.station(kielHbf)
assertValidStation(t, loc)
t.equal(loc.id, schleswig)
validate(t, s, 'station', 'station')
t.equal(s.id, kielHbf)
t.end()
}))
test('radar Kiel', co(function* (t) {
const fakeStation = (s) => {
const fake = Object.assign({
products: {
nationalExp: true,
national: false,
interregional: true,
regional: false,
suburban: true,
bus: false,
ferry: true,
subway: false,
tram: true,
onCall: false
}
}, s)
if (s.name === null) fake.name = 'foo'
return fake
}
const _assertValidStation = (t, s, coordsOptional = false) => {
assertValidStation(t, fakeStation(s), coordsOptional)
}
const _assertValidStopover = (t, s, coordsOptional = false) => {
const fake = Object.assign({}, s, {
station: fakeStation(s.station)
})
assertValidStopover(t, fake, coordsOptional)
}
test('radar', co(function* (t) {
const vehicles = yield client.radar({
north: 54.4,
west: 10.0,
@ -416,51 +286,19 @@ test('radar Kiel', co(function* (t) {
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 <= 57, 'vehicle is too far away')
t.ok(v.location.latitude >= 51, 'vehicle is too far away')
t.equal(typeof v.location.longitude, 'number')
t.ok(v.location.longitude >= 7, 'vehicle is too far away')
t.ok(v.location.longitude <= 13, '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))
}
// todo: cfg.stationProductsOptional option
const allProducts = products.reduce((acc, p) => (acc[p.id] = true, acc), {})
const validateStation = createValidateStation(cfg)
const validate = createValidate(cfg, {
station: (validate, s, name) => {
s = Object.assign({
products: allProducts // todo: fix station.products
}, s)
if (!s.name) s.name = 'foo' // todo, see #34
validateStation(validate, s, name)
}
})
validate(t, vehicles, 'movements', 'vehicles')
t.ok(Array.isArray(v.frames))
for (let f of v.frames) {
// todo: see #34
_assertValidStation(t, f.origin, true)
if (f.origin.products) {
assertValidStationProducts(t, f.origin.products)
}
_assertValidStation(t, f.destination, true)
if (f.destination.products) {
assertValidStationProducts(t, f.destination.products)
}
t.equal(typeof f.t, 'number')
}
}
t.end()
}))

View file

@ -1,86 +1,39 @@
'use strict'
// todo
// const getStations = require('db-stations').full
const tapePromise = require('tape-promise').default
const tape = require('tape')
const isRoughlyEqual = require('is-roughly-equal')
const validateFptf = require('validate-fptf')
const validateLine = require('validate-fptf/line')
const validateLineWithoutMode = require('./validate-line-without-mode')
const co = require('./co')
const {createWhen} = require('./lib/util')
const co = require('./lib/co')
const createClient = require('..')
const oebbProfile = require('../p/oebb')
const allProducts = require('../p/oebb/products')
const products = require('../p/oebb/products')
const {
assertValidStation,
assertValidPoi,
assertValidAddress,
assertValidLocation,
assertValidStopover,
hour, createWhen, assertValidWhen
} = require('./util.js')
station: createValidateStation
} = require('./lib/validators')
const createValidate = require('./lib/validate-fptf-with')
const testJourneysStationToStation = require('./lib/journeys-station-to-station')
const testJourneysStationToAddress = require('./lib/journeys-station-to-address')
const testJourneysStationToPoi = require('./lib/journeys-station-to-poi')
const testEarlierLaterJourneys = require('./lib/earlier-later-journeys')
const journeysFailsWithNoProduct = require('./lib/journeys-fails-with-no-product')
const testJourneysWithDetour = require('./lib/journeys-with-detour')
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')
const cfg = {
when,
stationCoordsOptional: false,
products
}
// 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)
// })
// todo validateDirection: search list of stations for direction
const isSalzburgHbf = (s) => {
return s.type === 'station' &&
(s.id === '008100002' || s.id === '8100002') &&
s.name === 'Salzburg Hbf' &&
s.location &&
isRoughlyEqual(.0005, s.location.latitude, 47.812851) &&
isRoughlyEqual(.0005, s.location.longitude, 13.045604)
}
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(.0005, s.location.latitude, 47.812851))
t.ok(isRoughlyEqual(.0005, s.location.longitude, 13.045604))
}
// todo: DRY with assertValidStationProducts
// todo: DRY with other tests
const assertValidProducts = (t, p) => {
for (let product of allProducts) {
product = product.id
t.equal(typeof p[product], 'boolean', 'product ' + p + ' must be a boolean')
}
}
const validate = createValidate(cfg, {
line: validateLine // bypass line validator in lib/validators
})
const assertValidPrice = (t, p) => {
t.ok(p)
@ -94,145 +47,95 @@ const assertValidPrice = (t, p) => {
}
}
// todo: fix this upstream
// see https://github.com/public-transport/hafas-client/blob/c6e558be217667f1bcdac4a605898eb75ea80374/p/oebb/products.js#L71
const assertValidLine = (t, l) => { // with optional mode
const validators = Object.assign({}, validateFptf.defaultValidators, {
line: validateLineWithoutMode
})
const recurse = validateFptf.createRecurse(validators)
try {
recurse(['line'], l, 'line')
} catch (err) {
t.ifError(err)
}
}
const test = tapePromise(tape)
const client = createClient(oebbProfile)
const salzburgHbf = '8100002'
const wienWestbahnhof = '1291501'
const wienFickeystr = '911014'
const wien = '1190100'
const wienWestbahnhof = '1291501'
const klagenfurtHbf = '8100085'
const muenchenHbf = '8000261'
const grazHbf = '8100173'
const wienRenngasse = '1390186'
test('Salzburg Hbf to Wien Westbahnhof', co(function* (t) {
const journeys = yield client.journeys(salzburgHbf, wienWestbahnhof, {
departure: when, passedStations: true
test.skip('journeys  Salzburg Hbf to Wien Westbahnhof', co(function* (t) {
const journeys = yield client.journeys(salzburgHbf, wienFickeystr, {
results: 3,
departure: 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')
yield testJourneysStationToStation({
test: t,
journeys,
validate,
fromId: salzburgHbf,
toId: wienFickeystr
})
t.ok(Array.isArray(journey.legs))
t.ok(journey.legs.length > 0, 'no legs')
const leg = journey.legs[0] // todo: all legs
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)
for (let i = 0; i < journeys.length; i++) {
const j = journeys[i]
if (j.price) assertValidPrice(t, j.price, `journeys[${i}].price`)
}
t.end()
}))
// todo: journeys, only one product
test('journeys  fails with no product', (t) => {
journeysFailsWithNoProduct({
test: t,
fetchJourneys: client.journeys,
fromId: salzburgHbf,
toId: wienFickeystr,
when,
products
})
t.end()
})
test('Salzburg Hbf to 1220 Wien, Wagramer Straße 5', co(function* (t) {
const wagramerStr = {
type: 'location',
address: '1220 Wien, Wagramer Straße 5',
latitude: 48.236216,
longitude: 16.425863,
address: '1220 Wien, Wagramer Straße 5'
longitude: 16.425863
}
const journeys = yield client.journeys(salzburgHbf, wagramerStr, {
results: 3,
departure: when
})
const journeys = yield client.journeys(salzburgHbf, wagramerStr, {departure: 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))
yield testJourneysStationToAddress({
test: t,
journeys,
validate,
fromId: salzburgHbf,
to: wagramerStr
})
t.end()
}))
test('Albertina to Salzburg Hbf', co(function* (t) {
test('Salzburg Hbf to Albertina', co(function* (t) {
const albertina = {
type: 'location',
latitude: 48.204699,
longitude: 16.368404,
id: '975900003',
name: 'Albertina',
id: '975900003'
latitude: 48.204699,
longitude: 16.368404
}
const journeys = yield client.journeys(albertina, salzburgHbf, {departure: 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)
// }
const journeys = yield client.journeys(salzburgHbf, albertina, {
results: 3, departure: when
})
yield testJourneysStationToPoi({
test: t,
journeys,
validate,
fromId: salzburgHbf,
to: albertina
})
t.end()
}))
@ -243,91 +146,66 @@ test('journeys: via works with detour', co(function* (t) {
const schottenring = '001390163'
const donauinsel = '001392277'
const donauinselPassed = '922001'
const [journey] = yield client.journeys(stephansplatz, schottenring, {
const journeys = yield client.journeys(stephansplatz, schottenring, {
via: donauinsel,
results: 1,
departure: when,
passedStations: true
})
t.ok(journey)
const l = journey.legs.some(l => l.passed && l.passed.some(p => p.station.id === donauinselPassed))
t.ok(l, 'Donauinsel is not being passed')
yield testJourneysWithDetour({
test: t,
journeys,
validate,
detourIds: [donauinsel, donauinselPassed]
})
t.end()
}))
test('journeys: via works without detour', co(function* (t) {
// When going from Karlsplatz to Praterstern via Museumsquartier, there is *no need*
// to change trains / no need for a "detour".
// When going from Karlsplatz to Praterstern via Museumsquartier, there is
// *no need* to change trains / no need for a "detour".
const karlsplatz = '001390461'
const praterstern = '001290201'
const museumsquartier = '001390171'
const museumsquartierPassed = '901014'
const [journey] = yield client.journeys(karlsplatz, praterstern, {
const journeys = yield client.journeys(karlsplatz, praterstern, {
via: museumsquartier,
results: 1,
departure: when,
passedStations: true
})
t.ok(journey)
validate(t, journeys, 'journeys', 'journeys')
const l = journey.legs.some(l => l.passed && l.passed.some(p => p.station.id === museumsquartierPassed))
t.ok(l, 'Weihburggasse is not being passed')
const l1 = journeys[0].legs.some((leg) => {
return (
leg.destination.id === museumsquartier ||
leg.destination.id === museumsquartierPassed
)
})
t.notOk(l1, 'transfer at Museumsquartier')
const l2 = journeys[0].legs.some((leg) => {
return leg.passed && leg.passed.some((passed) => {
return passed.station.id === museumsquartierPassed
})
})
t.ok(l2, 'Museumsquartier is not being passed')
t.end()
}))
test('earlier/later journeys, Salzburg Hbf -> Wien Westbahnhof', co(function* (t) {
const model = yield client.journeys(salzburgHbf, wienWestbahnhof, {
results: 3, departure: when
yield testEarlierLaterJourneys({
test: t,
fetchJourneys: client.journeys,
validate,
fromId: salzburgHbf,
toId: wienWestbahnhof
})
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, {
departure: when, earlierThan: model.earlierRef
})
})
t.throws(() => {
client.journeys(salzburgHbf, wienWestbahnhof, {
departure: when, laterThan: model.laterRef
})
})
let earliestDep = Infinity, latestDep = -Infinity
for (let j of model) {
const dep = +new Date(j.legs[0].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.legs[0].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.legs[0].departure) > latestDep)
}
t.end()
}))
@ -341,144 +219,136 @@ test('leg details for Wien Westbahnhof to München Hbf', co(function* (t) {
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)
validate(t, leg, 'journeyLeg', 'leg')
t.end()
}))
assertValidLine(t, leg.line)
test('departures at Wien Leibenfrostgasse', co(function* (t) {
const wienLeibenfrostgasse = '1390469'
const ids = [
wienLeibenfrostgasse, // station
'904029', // stop "Wien Leibenfrostgasse (Phorusgasse)s"
'904030' // stop "Wien Leibenfrostgasse (Ziegelofengasse)"
]
t.equal(typeof leg.direction, 'string')
t.ok(leg.direction)
const deps = yield client.departures(wienLeibenfrostgasse, {
duration: 15, when
})
t.ok(Array.isArray(leg.passed))
for (let passed of leg.passed) assertValidStopover(t, passed)
validate(t, deps, 'departures', 'departures')
t.ok(deps.length > 0, 'must be >0 departures')
// todo: move into deps validator
t.deepEqual(deps, deps.sort((a, b) => t.when > b.when))
for (let i = 0; i < deps.length; i++) {
const dep = deps[i]
const msg = `deps[${i}].station.id is invalid`
t.ok(ids.includes(dep.station.id, msg))
}
t.end()
}))
test('departures at Salzburg Hbf', co(function* (t) {
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)
}
test('departures with station object', co(function* (t) {
const deps = yield client.departures({
type: 'station',
id: salzburgHbf,
name: 'Salzburg Hbf',
location: {
type: 'location',
latitude: 1.23,
longitude: 2.34
}
}, {when})
validate(t, deps, 'departures', 'departures')
t.end()
}))
test('nearby Salzburg Hbf', co(function* (t) {
const salzburgHbfPosition = {
const nearby = yield client.nearby({
type: 'location',
longitude: 13.045604,
latitude: 47.812851
}
const nearby = yield client.nearby(salzburgHbfPosition, {
results: 2, distance: 400
}, {
results: 5, distance: 400
})
t.ok(Array.isArray(nearby))
t.equal(nearby.length, 2)
validate(t, nearby, 'locations', 'nearby')
t.equal(nearby.length, 5)
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)
}
const s = nearby[0]
t.ok(s.id === '008100002' || s.id === '8100002', 'id should be 8100002')
t.equal(s.name, 'Salzburg Hbf')
t.ok(isRoughlyEqual(.0005, s.location.latitude, 47.812851))
t.ok(isRoughlyEqual(.0005, s.location.longitude, 13.045604))
t.ok(s.distance >= 0)
t.ok(s.distance <= 100)
t.end()
}))
test('locations named Salzburg', co(function* (t) {
const locations = yield client.locations('Salzburg', {
results: 10
results: 20
})
t.ok(Array.isArray(locations))
t.ok(locations.length > 0)
t.ok(locations.length <= 10)
validate(t, locations, 'locations', 'locations')
t.ok(locations.length <= 20)
for (let l of locations) {
if (l.type === 'station') assertValidStation(t, l)
else assertValidLocation(t, l)
}
t.ok(locations.some(isSalzburgHbf))
t.ok(locations.find(s => s.type === 'station'))
t.ok(locations.find(s => s.id && s.name)) // POIs
t.ok(locations.some(s => s.id === '008100002' || s.id === '8100002'))
t.end()
}))
test('station', co(function* (t) {
const loc = yield client.station(grazHbf)
const loc = yield client.station(wienRenngasse)
assertValidStation(t, loc)
t.equal(loc.id, grazHbf)
// todo: find a way to always get products from the API
// todo: cfg.stationProductsOptional option
const allProducts = products.reduce((acc, p) => (acc[p.id] = true, acc), {})
const validateStation = createValidateStation(cfg)
const validate = createValidate(cfg, {
station: (validate, s, name) => {
const withFakeProducts = Object.assign({products: allProducts}, s)
validateStation(validate, withFakeProducts, name)
}
})
validate(t, loc, 'station', 'station')
t.equal(loc.id, wienRenngasse)
t.end()
}))
test('radar Salzburg', co(function* (t) {
const vehicles = yield client.radar({
let vehicles = yield client.radar({
north: 47.827203,
west: 13.001261,
south: 47.773278,
east: 13.07562
}, {
duration: 5 * 60, when
duration: 5 * 60,
// when
})
t.ok(Array.isArray(vehicles))
t.ok(vehicles.length > 0)
for (let v of vehicles) {
// todo: find a way to always get frames from the API
vehicles = vehicles.filter(m => m.frames && m.frames.length > 0)
// todo
// t.ok(findStation(v.direction))
assertValidLine(t, v.line)
// todo: find a way to always get products from the API
// todo: cfg.stationProductsOptional option
const allProducts = products.reduce((acc, p) => (acc[p.id] = true, acc), {})
const validateStation = createValidateStation(cfg)
const validate = createValidate(cfg, {
station: (validate, s, name) => {
const withFakeProducts = Object.assign({products: allProducts}, s)
validateStation(validate, withFakeProducts, name)
},
line: validateLine
})
validate(t, vehicles, 'movements', 'vehicles')
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()
}))

View file

@ -1,158 +0,0 @@
'use strict'
const validateFptf = require('validate-fptf')
const isRoughlyEqual = require('is-roughly-equal')
const {DateTime} = require('luxon')
const isValidWGS84 = require('is-coordinates')
const validateFptfWith = (t, item, allowedTypes, name) => {
try {
validateFptf.recurse(allowedTypes, item, name)
} catch (err) {
t.ifError(err)
}
}
const assertValidStation = (t, s, coordsOptional = false) => {
validateFptfWith(t, s, ['station'], 'station')
if (!coordsOptional || (s.location !== null && s.location !== undefined)) {
t.ok(s.location)
assertValidLocation(t, s.location, coordsOptional)
}
}
const assertValidPoi = (t, p) => {
assertValidLocation(t, p, true)
t.equal(typeof p.id, 'string')
t.equal(typeof p.name, 'string')
if (p.address !== null && p.address !== undefined) {
t.equal(typeof p.address, 'string')
t.ok(p.address)
}
}
const assertValidAddress = (t, a) => {
assertValidLocation(t, a, true)
t.equal(typeof a.address, 'string')
}
const assertValidLocation = (t, l, coordsOptional = false) => {
t.equal(l.type, 'location')
if (l.name !== null && l.name !== undefined) {
t.equal(typeof l.name, 'string')
t.ok(l.name)
}
if (l.address !== null && l.address !== undefined) {
t.equal(typeof l.address, 'string')
t.ok(l.address)
}
const hasLatitude = l.latitude !== null && l.latitude !== undefined
const hasLongitude = l.longitude !== null && l.longitude !== undefined
if (!coordsOptional && hasLatitude) t.equal(typeof l.latitude, 'number')
if (!coordsOptional && hasLongitude) t.equal(typeof l.longitude, 'number')
if ((hasLongitude && !hasLatitude) || (hasLatitude && !hasLongitude)) {
t.fail('should have both .latitude and .longitude')
}
if (hasLatitude && hasLongitude) isValidWGS84([l.longitude, l.latitude])
if (!coordsOptional && l.altitude !== null && l.altitude !== undefined) {
t.equal(typeof l.altitude, 'number')
}
}
const validLineModes = [
'train', 'bus', 'watercraft', 'taxi', 'gondola', 'aircraft',
'car', 'bicycle', 'walking'
]
const assertValidLine = (t, l) => {
validateFptfWith(t, l, ['line'], 'line')
}
const isValidDateTime = (w) => {
return !Number.isNaN(+new Date(w))
}
const assertValidStopover = (t, s, coordsOptional = false) => {
if ('arrival' in s) t.ok(isValidDateTime(s.arrival))
if ('departure' in s) t.ok(isValidDateTime(s.departure))
if (s.arrivalDelay !== null && s.arrivalDelay !== undefined) {
t.equal(typeof s.arrivalDelay, 'number')
}
if (s.departureDelay !== null && s.departureDelay !== undefined) {
t.equal(typeof s.departureDelay, 'number')
}
if (!('arrival' in s) && !('departure' in s)) {
t.fail('stopover doesn\'t contain arrival or departure')
}
t.ok(s.station)
assertValidStation(t, s.station, coordsOptional)
}
const hour = 60 * 60 * 1000
const week = 7 * 24 * hour
// next Monday 10 am
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, +expected, ts)
}
const assertValidWhen = (t, actual, expected) => {
t.ok(isValidWhen(actual, expected), 'invalid when')
}
const assertValidTicket = (t, ti) => {
t.strictEqual(typeof ti.name, 'string')
t.ok(ti.name.length > 0)
if (ti.price !== null) {
t.strictEqual(typeof ti.price, 'number')
t.ok(ti.price > 0)
}
if (ti.amount !== null) {
t.strictEqual(typeof ti.amount, 'number')
t.ok(ti.amount > 0)
}
if ('bike' in ti) t.strictEqual(typeof ti.bike, 'boolean')
if ('shortTrip' in ti) t.strictEqual(typeof ti.shortTrip, 'boolean')
if ('group' in ti) t.strictEqual(typeof ti.group, 'boolean')
if ('fullDay' in ti) t.strictEqual(typeof ti.fullDay, 'boolean')
if (ti.tariff !== null) {
t.strictEqual(typeof ti.tariff, 'string')
t.ok(ti.tariff.length > 0)
}
if (ti.coverage !== null) {
t.strictEqual(typeof ti.coverage, 'string')
t.ok(ti.coverage.length > 0)
}
if (ti.variant !== null) {
t.strictEqual(typeof ti.variant, 'string')
t.ok(ti.variant.length > 0)
}
}
module.exports = {
assertValidStation,
assertValidPoi,
assertValidAddress,
assertValidLocation,
assertValidLine,
isValidDateTime,
assertValidStopover,
hour, createWhen, isValidWhen, assertValidWhen,
assertValidTicket
}

View file

@ -1,38 +0,0 @@
'use strict'
const a = require('assert')
const is = require('@sindresorhus/is')
const validateItem = require('validate-fptf/lib/item')
const validateReference = require('validate-fptf/lib/reference')
// todo: this is copied code, DRY this up!
// see https://github.com/public-transport/validate-fptf/blob/373b4847ec9668c4a9ec9b0dbd50f8a70ffbe127/line.js
const validateLineWithoutMode = (validate, line, name) => {
validateItem(line, name)
a.strictEqual(line.type, 'line', name + '.type must be `line`')
validateReference(line.id, name + '.id')
a.strictEqual(typeof line.name, 'string', name + '.name must be a string')
a.ok(line.name.length > 0, name + '.name can\'t be empty')
// skipping line validation here
// see https://github.com/public-transport/hafas-client/issues/8#issuecomment-355839965
if (is.undefined(line.mode) || is.null(line.mode)) {
console.error(`ÖBB: Missing \`mode\` for line ${line.name} (at ${name}).`)
}
if (!is.undefined(line.subMode)) {
a.fail(name + '.subMode is reserved an should not be used for now')
}
// todo: routes
if (!is.null(line.operator) && !is.undefined(line.operator)) {
validate(['operator'], line.operator, name + '.operator')
}
}
module.exports = validateLineWithoutMode

View file

@ -1,55 +1,103 @@
'use strict'
const a = require('assert')
const isRoughlyEqual = require('is-roughly-equal')
const stations = require('vbb-stations-autocomplete')
const a = require('assert')
const shorten = require('vbb-short-station-name')
const tapePromise = require('tape-promise').default
const tape = require('tape')
const shorten = require('vbb-short-station-name')
const isRoughlyEqual = require('is-roughly-equal')
const co = require('./co')
const {createWhen} = require('./lib/util')
const co = require('./lib/co')
const createClient = require('..')
const vbbProfile = require('../p/vbb')
const products = require('../p/vbb/products')
const {
assertValidStation: _assertValidStation,
assertValidPoi,
assertValidAddress,
assertValidLocation,
assertValidLine: _assertValidLine,
assertValidStopover,
hour, createWhen,
assertValidWhen,
assertValidTicket
} = require('./util')
station: createValidateStation,
line: createValidateLine,
journeyLeg: createValidateJourneyLeg,
departure: createValidateDeparture,
movement: _validateMovement
} = require('./lib/validators')
const createValidate = require('./lib/validate-fptf-with')
const testJourneysStationToStation = require('./lib/journeys-station-to-station')
const testJourneysStationToAddress = require('./lib/journeys-station-to-address')
const testJourneysStationToPoi = require('./lib/journeys-station-to-poi')
const testEarlierLaterJourneys = require('./lib/earlier-later-journeys')
const journeysFailsWithNoProduct = require('./lib/journeys-fails-with-no-product')
const testDepartures = require('./lib/departures')
const testJourneysWithDetour = require('./lib/journeys-with-detour')
const when = createWhen('Europe/Berlin', 'de-DE')
const assertValidStation = (t, s, coordsOptional = false) => {
_assertValidStation(t, s, coordsOptional)
t.equal(s.name, shorten(s.name))
const cfg = {
when,
stationCoordsOptional: false,
products
}
const assertValidStationProducts = (t, p) => {
t.ok(p)
t.equal(typeof p.suburban, 'boolean')
t.equal(typeof p.subway, 'boolean')
t.equal(typeof p.tram, 'boolean')
t.equal(typeof p.bus, 'boolean')
t.equal(typeof p.ferry, 'boolean')
t.equal(typeof p.express, 'boolean')
t.equal(typeof p.regional, 'boolean')
const validateDirection = (dir, name) => {
if (!stations(dir, true, false)[0]) {
console.error(name + `: station "${dir}" is unknown`)
}
}
const assertValidLine = (t, l) => {
_assertValidLine(t, l)
if (l.symbol !== null) t.equal(typeof l.symbol, 'string')
if (l.nr !== null) t.equal(typeof l.nr, 'number')
if (l.metro !== null) t.equal(typeof l.metro, 'boolean')
if (l.express !== null) t.equal(typeof l.express, 'boolean')
if (l.night !== null) t.equal(typeof l.night, 'boolean')
// todo: coordsOptional = false
const _validateStation = createValidateStation(cfg)
const validateStation = (validate, s, name) => {
_validateStation(validate, s, name)
// todo: find station by ID
a.equal(s.name, shorten(s.name), name + '.name must be shortened')
}
const findStation = (query) => stations(query, true, false)[0]
const _validateLine = createValidateLine(cfg)
const validateLine = (validate, l, name) => {
_validateLine(validate, l, name)
if (l.symbol !== null) {
a.strictEqual(typeof l.symbol, 'string', name + '.symbol must be a string')
a.ok(l.symbol, name + '.symbol must not be empty')
}
if (l.nr !== null) {
a.strictEqual(typeof l.nr, 'number', name + '.nr must be a string')
a.ok(l.nr, name + '.nr must not be empty')
}
if (l.metro !== null) {
a.strictEqual(typeof l.metro, 'boolean', name + '.metro must be a boolean')
}
if (l.express !== null) {
a.strictEqual(typeof l.express, 'boolean', name + '.express must be a boolean')
}
if (l.night !== null) {
a.strictEqual(typeof l.night, 'boolean', name + '.night must be a boolean')
}
}
const _validateJourneyLeg = createValidateJourneyLeg(cfg)
const validateJourneyLeg = (validate, l, name) => {
_validateJourneyLeg(validate, l, name)
if (l.mode !== 'walking') {
validateDirection(l.direction, name + '.direction')
}
}
const _validateDeparture = createValidateDeparture(cfg)
const validateDeparture = (validate, dep, name) => {
_validateDeparture(validate, dep, name)
validateDirection(dep.direction, name + '.direction')
}
const validateMovement = (validate, m, name) => {
_validateMovement(validate, m, name)
validateDirection(m.direction, name + '.direction')
}
const validate = createValidate(cfg, {
station: validateStation,
line: validateLine,
journeyLeg: validateJourneyLeg,
departure: validateDeparture,
movement: validateMovement
})
const test = tapePromise(tape)
const client = createClient(vbbProfile)
@ -57,58 +105,33 @@ const client = createClient(vbbProfile)
const amrumerStr = '900000009101'
const spichernstr = '900000042101'
const bismarckstr = '900000024201'
const westhafen = '900000001201'
const wedding = '900000009104'
const württembergallee = '900000026153'
test('journeys  station to station', co(function* (t) {
const journeys = yield client.journeys(spichernstr, amrumerStr, {
results: 3, departure: when, passedStations: true
test('journeys  Spichernstr. to Bismarckstr.', co(function* (t) {
const journeys = yield client.journeys(spichernstr, bismarckstr, {
results: 3,
departure: when,
passedStations: true
})
t.ok(Array.isArray(journeys))
t.strictEqual(journeys.length, 3)
yield testJourneysStationToStation({
test: t,
journeys,
validate,
fromId: spichernstr,
toId: bismarckstr
})
// todo: find a journey where there ticket info is always available
for (let journey of journeys) {
t.equal(journey.type, 'journey')
t.ok(Array.isArray(journey.legs))
t.strictEqual(journey.legs.length, 1)
const leg = journey.legs[0] // todo: all legs
t.equal(typeof leg.id, 'string')
t.ok(leg.id)
assertValidStation(t, leg.origin)
assertValidStationProducts(t, leg.origin.products)
t.ok(leg.origin.name.indexOf('(Berlin)') === -1)
t.strictEqual(leg.origin.id, spichernstr)
assertValidWhen(t, leg.departure, when)
assertValidStation(t, leg.destination)
assertValidStationProducts(t, leg.destination.products)
t.strictEqual(leg.destination.id, amrumerStr)
assertValidWhen(t, leg.arrival, when)
assertValidLine(t, leg.line)
if (!findStation(leg.direction)) {
const err = new Error('unknown direction: ' + leg.direction)
err.stack = err.stack.split('\n').slice(0, 2).join('\n')
console.error(err)
}
t.ok(leg.direction.indexOf('(Berlin)') === -1)
t.ok(Array.isArray(leg.passed))
for (let passed of leg.passed) assertValidStopover(t, passed)
// todo: find a journey where there ticket info is always available
if (journey.tickets) {
t.ok(Array.isArray(journey.tickets))
for (let ticket of journey.tickets) assertValidTicket(t, ticket)
}
}
t.end()
}))
test('journeys  only subway', co(function* (t) {
const journeys = yield client.journeys(spichernstr, bismarckstr, {
results: 20, departure: when,
results: 20,
departure: when,
products: {
suburban: false,
subway: true,
@ -120,118 +143,46 @@ test('journeys  only subway', co(function* (t) {
}
})
t.ok(Array.isArray(journeys))
validate(t, journeys, 'journeys', 'journeys')
t.ok(journeys.length > 1)
for (let i = 0; i < journeys.length; i++) {
const journey = journeys[i]
for (let j = 0; j < journey.legs.length; j++) {
const leg = journey.legs[j]
for (let journey of journeys) {
for (let leg of journey.legs) {
const name = `journeys[${i}].legs[${i}].line`
if (leg.line) {
assertValidLine(t, leg.line)
t.equal(leg.line.mode, 'train')
t.equal(leg.line.product, 'subway')
t.equal(leg.line.mode, 'train', name + '.mode is invalid')
t.equal(leg.line.product, 'subway', name + '.product is invalid')
}
t.ok(journey.legs.some(l => l.line), name + '.legs has no subway leg')
}
}
t.end()
}))
test('journeys  fails with no product', co(function* (t) {
t.plan(1)
try {
client.journeys(spichernstr, bismarckstr, {
departure: when,
products: {
suburban: false,
subway: false,
tram: false,
bus: false,
ferry: false,
express: false,
regional: false
}
})
// silence rejections, we're only interested in exceptions
.catch(() => {})
} catch (err) {
t.ok(err, 'error thrown')
t.end()
}
}))
test('journeys before date/time', co(function* (t) {
const journeys = yield client.journeys(spichernstr, bismarckstr, {
results: 3, arrival: when
test('journeys  fails with no product', (t) => {
journeysFailsWithNoProduct({
test: t,
fetchJourneys: client.journeys,
fromId: spichernstr,
toId: bismarckstr,
when,
products
})
for (let i = 0; i < journeys.length; i++) {
const j = journeys[i]
const name = `journeys[${i}]`
const lastLeg = j.legs[j.legs.length - 1]
const arr = +new Date(lastLeg.arrival)
t.ok(arr <= when, name + '.arrival is after `when`')
}
t.end()
}))
})
test('earlier/later journeys', co(function* (t) {
const model = yield client.journeys(spichernstr, bismarckstr, {
results: 3, departure: when
yield testEarlierLaterJourneys({
test: t,
fetchJourneys: client.journeys,
validate,
fromId: spichernstr,
toId: bismarckstr
})
t.equal(typeof model.earlierRef, 'string')
t.ok(model.earlierRef)
t.equal(typeof model.laterRef, 'string')
t.ok(model.laterRef)
// departure/arrival and earlierThan/laterThan should be mutually exclusive
t.throws(() => {
client.journeys(spichernstr, bismarckstr, {
departure: when, earlierThan: model.earlierRef
})
})
t.throws(() => {
client.journeys(spichernstr, bismarckstr, {
departure: when, laterThan: model.laterRef
})
})
t.throws(() => {
client.journeys(spichernstr, bismarckstr, {
arrival: when, earlierThan: model.earlierRef
})
})
t.throws(() => {
client.journeys(spichernstr, bismarckstr, {
arrival: when, laterThan: model.laterRef
})
})
let earliestDep = Infinity, latestDep = -Infinity
for (let j of model) {
const dep = +new Date(j.legs[0].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.legs[0].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.legs[0].departure) > latestDep)
}
t.end()
}))
@ -245,147 +196,92 @@ test('journey leg details', co(function* (t) {
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)
validate(t, leg, 'journeyLeg', 'leg')
t.end()
}))
test('journeys  station to address', co(function* (t) {
const journeys = yield client.journeys(spichernstr, {
const torfstr = {
type: 'location',
address: 'Torfstr. 17, Berlin',
latitude: 52.541797, longitude: 13.350042
}, {results: 1, departure: when})
t.ok(Array.isArray(journeys))
t.strictEqual(journeys.length, 1)
const journey = journeys[0]
const leg = journey.legs[journey.legs.length - 1]
assertValidStation(t, leg.origin)
assertValidStationProducts(t, leg.origin.products)
assertValidWhen(t, leg.departure, when)
const dest = leg.destination
assertValidAddress(t, dest)
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)
address: '13353 Berlin-Wedding, Torfstr. 17',
latitude: 52.541797,
longitude: 13.350042
}
const journeys = yield client.journeys(spichernstr, torfstr, {
results: 3,
departure: when
})
yield testJourneysStationToAddress({
test: t,
journeys,
validate,
fromId: spichernstr,
to: torfstr
})
t.end()
}))
test('journeys  station to POI', co(function* (t) {
const journeys = yield client.journeys(spichernstr, {
const atze = {
type: 'location',
id: '900980720',
name: 'Berlin, Atze Musiktheater für Kinder',
latitude: 52.543333, longitude: 13.351686
}, {results: 1, departure: when})
t.ok(Array.isArray(journeys))
t.strictEqual(journeys.length, 1)
const journey = journeys[0]
const leg = journey.legs[journey.legs.length - 1]
assertValidStation(t, leg.origin)
assertValidStationProducts(t, leg.origin.products)
assertValidWhen(t, leg.departure, when)
const dest = leg.destination
assertValidPoi(t, dest)
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)
latitude: 52.543333,
longitude: 13.351686
}
const journeys = yield client.journeys(spichernstr, atze, {
results: 3,
departure: when
})
yield testJourneysStationToPoi({
test: t,
journeys,
validate,
fromId: spichernstr,
to: atze
})
t.end()
}))
test('journeys: via works with detour', co(function* (t) {
// Going from Westhafen to Wedding via Württembergalle without detour
// is currently impossible. We check if the routing engine computes a detour.
const westhafen = '900000001201'
const wedding = '900000009104'
const württembergallee = '900000026153'
const [journey] = yield client.journeys(westhafen, wedding, {
const journeys = yield client.journeys(westhafen, wedding, {
via: württembergallee,
results: 1,
departure: when,
passedStations: true
})
t.ok(journey)
const l = journey.legs.some(l => l.passed && l.passed.some(p => p.station.id === württembergallee))
t.ok(l, 'Württembergalle is not being passed')
t.end()
}))
test('journeys: via works without detour', co(function* (t) {
// When going from Ruhleben to Zoo via Kastanienallee, there is *no need*
// to change trains / no need for a "detour".
const ruhleben = '900000025202'
const zoo = '900000023201'
const kastanienallee = '900000020152'
const [journey] = yield client.journeys(ruhleben, zoo, {
via: kastanienallee,
results: 1,
departure: when,
passedStations: true
yield testJourneysWithDetour({
test: t,
journeys,
validate,
detourIds: [württembergallee]
})
t.ok(journey)
const l = journey.legs.some(l => l.passed && l.passed.some(p => p.station.id === kastanienallee))
t.ok(l, 'Kastanienallee is not being passed')
t.end()
}))
// todo: without detour test
test('departures', co(function* (t) {
const deps = yield client.departures(spichernstr, {duration: 5, when})
const departures = yield client.departures(spichernstr, {
duration: 5, when
})
t.ok(Array.isArray(deps))
t.deepEqual(deps, deps.sort((a, b) => t.when > b.when))
for (let dep of deps) {
t.equal(typeof dep.journeyId, 'string')
t.ok(dep.journeyId)
t.equal(dep.station.name, 'U Spichernstr.')
assertValidStation(t, dep.station)
assertValidStationProducts(t, dep.station.products)
t.strictEqual(dep.station.id, spichernstr)
assertValidWhen(t, dep.when, when)
if (!findStation(dep.direction)) {
const err = new Error('unknown direction: ' + dep.direction)
err.stack = err.stack.split('\n').slice(0, 2).join('\n')
console.error(err)
}
assertValidLine(t, dep.line)
}
yield testDepartures({
test: t,
departures,
validate,
id: spichernstr
})
t.end()
}))
test('departures with station object', co(function* (t) {
yield client.departures({
const deps = yield client.departures({
type: 'station',
id: spichernstr,
name: 'U Spichernstr',
@ -396,7 +292,7 @@ test('departures with station object', co(function* (t) {
}
}, {when})
t.ok('did not fail')
validate(t, deps, 'departures', 'departures')
t.end()
}))
@ -404,13 +300,13 @@ test('departures at 7-digit station', co(function* (t) {
const eisenach = '8010097' // see derhuerst/vbb-hafas#22
yield client.departures(eisenach, {when})
t.pass('did not fail')
t.end()
}))
test('nearby', co(function* (t) {
const berlinerStr = '900000044201'
const landhausstr = '900000043252'
// Berliner Str./Bundesallee
const nearby = yield client.nearby({
type: 'location',
@ -418,18 +314,14 @@ test('nearby', co(function* (t) {
longitude: 13.3310411
}, {distance: 200})
t.ok(Array.isArray(nearby))
for (let n of nearby) {
if (n.type === 'station') assertValidStation(t, n)
else assertValidLocation(t, n, false)
}
validate(t, nearby, 'locations', 'nearby')
t.equal(nearby[0].id, '900000044201')
t.equal(nearby[0].id, berlinerStr)
t.equal(nearby[0].name, 'U Berliner Str.')
t.ok(nearby[0].distance > 0)
t.ok(nearby[0].distance < 100)
t.equal(nearby[1].id, '900000043252')
t.equal(nearby[1].id, landhausstr)
t.equal(nearby[1].name, 'Landhausstr.')
t.ok(nearby[1].distance > 100)
t.ok(nearby[1].distance < 200)
@ -437,18 +329,12 @@ test('nearby', co(function* (t) {
t.end()
}))
test('locations', co(function* (t) {
const locations = yield client.locations('Alexanderplatz', {results: 20})
t.ok(Array.isArray(locations))
t.ok(locations.length > 0)
validate(t, locations, 'locations', 'locations')
t.ok(locations.length <= 20)
for (let l of locations) {
if (l.type === 'station') assertValidStation(t, l)
else assertValidLocation(t, l)
}
t.ok(locations.find(s => s.type === 'station'))
t.ok(locations.find(s => s.id && s.name)) // POIs
t.ok(locations.find(s => !s.name && s.address)) // addresses
@ -457,21 +343,14 @@ test('locations', co(function* (t) {
}))
test('station', co(function* (t) {
const loc = yield client.station(spichernstr)
const s = yield client.station(spichernstr)
assertValidStation(t, loc)
t.equal(loc.id, spichernstr)
t.ok(Array.isArray(loc.lines))
if (Array.isArray(loc.lines)) {
for (let line of loc.lines) assertValidLine(t, line)
}
validate(t, s, 'station', 'station')
t.equal(s.id, spichernstr)
t.end()
}))
test('radar', co(function* (t) {
const vehicles = yield client.radar({
north: 52.52411,
@ -482,53 +361,6 @@ test('radar', co(function* (t) {
duration: 5 * 60, when
})
t.ok(Array.isArray(vehicles))
t.ok(vehicles.length > 0)
for (let v of vehicles) {
if (!findStation(v.direction)) {
const err = new Error('unknown direction: ' + v.direction)
err.stack = err.stack.split('\n').slice(0, 2).join('\n')
console.error(err)
}
assertValidLine(t, v.line)
t.equal(typeof v.location.latitude, 'number')
t.ok(v.location.latitude <= 55, 'vehicle is too far away')
t.ok(v.location.latitude >= 45, 'vehicle is too far away')
t.equal(typeof v.location.longitude, 'number')
t.ok(v.location.longitude >= 9, 'vehicle is too far away')
t.ok(v.location.longitude <= 15, 'vehicle is too far away')
t.ok(Array.isArray(v.nextStops))
for (let st of v.nextStops) {
assertValidStopover(t, st, true)
t.strictEqual(st.station.name.indexOf('(Berlin)'), -1)
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)
// note that this can be an ICE train
t.ok(isRoughlyEqual(14 * hour, +when, dep))
}
}
t.ok(Array.isArray(v.frames))
for (let f of v.frames) {
assertValidStation(t, f.origin, true)
assertValidStationProducts(t, f.origin.products)
t.strictEqual(f.origin.name.indexOf('(Berlin)'), -1)
assertValidStation(t, f.destination, true)
assertValidStationProducts(t, f.destination.products)
t.strictEqual(f.destination.name.indexOf('(Berlin)'), -1)
t.equal(typeof f.t, 'number')
}
}
validate(t, vehicles, 'movements', 'vehicles')
t.end()
}))