mirror of
https://github.com/public-transport/db-vendo-client.git
synced 2025-02-22 22:59:35 +02:00
arrivals/departures: return obj with realtimeDataUpdatedAt & results 💥✅📝
This commit is contained in:
parent
1000e48dfd
commit
c4470ca962
33 changed files with 198 additions and 148 deletions
|
@ -55,7 +55,7 @@ await client.departures('900000024101', {products: {tram: false, ferry: false}})
|
|||
|
||||
*Note:* As stated in the [*Friendly Public Transport Format* v2 draft spec](https://github.com/public-transport/friendly-public-transport-format/blob/3bd36faa721e85d9f5ca58fb0f38cdbedb87bbca/spec/readme.md), the `when` field includes the current delay. The `delay` field, if present, expresses how much the former differs from the schedule.
|
||||
|
||||
You may pass the `tripId` field into [`trip(id, lineName, [opt])`](trip.md) to get details on the vehicle's trip.
|
||||
You may pass a departure's `tripId` into [`trip(id, lineName, [opt])`](trip.md) to get details on the whole trip.
|
||||
|
||||
As an example, we're going to use the [VBB profile](../p/vbb):
|
||||
|
||||
|
@ -66,10 +66,15 @@ const vbbProfile = require('hafas-client/p/vbb')
|
|||
const client = createClient(vbbProfile, 'my-awesome-program')
|
||||
|
||||
// S Charlottenburg
|
||||
await client.departures('900000024101', {duration: 3})
|
||||
const {
|
||||
departures,
|
||||
realtimeDataUpdatedAt,
|
||||
} = await client.departures('900000024101', {duration: 3})
|
||||
```
|
||||
|
||||
The result may look like this:
|
||||
`realtimeDataUpdatedAt` is a UNIX timestamp reflecting the latest moment when (at least some) of the response's realtime data have been updated.
|
||||
|
||||
`departures` may look like this:
|
||||
|
||||
```js
|
||||
[ {
|
||||
|
|
18
index.js
18
index.js
|
@ -38,7 +38,7 @@ const createClient = (profile, userAgent, opt = {}) => {
|
|||
throw new TypeError('userAgent must be a string');
|
||||
}
|
||||
|
||||
const _stationBoard = async (station, type, parse, opt = {}) => {
|
||||
const _stationBoard = async (station, type, resultsField, parse, opt = {}) => {
|
||||
if (isObj(station)) station = profile.formatStation(station.id)
|
||||
else if ('string' === typeof station) station = profile.formatStation(station)
|
||||
else throw new TypeError('station must be an object or a string.')
|
||||
|
@ -75,19 +75,25 @@ const createClient = (profile, userAgent, opt = {}) => {
|
|||
const req = profile.formatStationBoardReq({profile, opt}, station, type)
|
||||
|
||||
const {res, common} = await profile.request({profile, opt}, userAgent, req)
|
||||
if (!Array.isArray(res.jnyL)) return []
|
||||
|
||||
const ctx = {profile, opt, common, res}
|
||||
// todo [breaking]: return object with realtimeDataUpdatedAt
|
||||
return res.jnyL.map(res => parse(ctx, res))
|
||||
const jnyL = Array.isArray(res.jnyL) ? res.jnyL : []
|
||||
const results = jnyL.map(res => parse(ctx, res))
|
||||
.sort((a, b) => new Date(a.when) - new Date(b.when)) // todo
|
||||
|
||||
return {
|
||||
[resultsField]: results,
|
||||
realtimeDataUpdatedAt: res.planrtTS && res.planrtTS !== '0'
|
||||
? parseInt(res.planrtTS)
|
||||
: null,
|
||||
}
|
||||
}
|
||||
|
||||
const departures = async (station, opt = {}) => {
|
||||
return await _stationBoard(station, 'DEP', profile.parseDeparture, opt)
|
||||
return await _stationBoard(station, 'DEP', 'departures', profile.parseDeparture, opt)
|
||||
}
|
||||
const arrivals = async (station, opt = {}) => {
|
||||
return await _stationBoard(station, 'ARR', profile.parseArrival, opt)
|
||||
return await _stationBoard(station, 'ARR', 'arrivals', profile.parseArrival, opt)
|
||||
}
|
||||
|
||||
const journeys = async (from, to, opt = {}) => {
|
||||
|
|
|
@ -299,13 +299,13 @@ tap.test('journeys: via works – with detour', async (t) => {
|
|||
// todo: without detour test
|
||||
|
||||
tap.test('departures', async (t) => {
|
||||
const departures = await client.departures(spichernstr, {
|
||||
const res = await client.departures(spichernstr, {
|
||||
duration: 5, when
|
||||
})
|
||||
|
||||
await testDepartures({
|
||||
test: t,
|
||||
departures,
|
||||
res,
|
||||
validate,
|
||||
id: spichernstr
|
||||
})
|
||||
|
@ -313,7 +313,7 @@ tap.test('departures', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('departures with station object', async (t) => {
|
||||
const deps = await client.departures({
|
||||
const res = await client.departures({
|
||||
type: 'station',
|
||||
id: spichernstr,
|
||||
name: 'U Spichernstr',
|
||||
|
@ -324,7 +324,7 @@ tap.test('departures with station object', async (t) => {
|
|||
}
|
||||
}, {when})
|
||||
|
||||
validate(t, deps, 'departures', 'departures')
|
||||
validate(t, res, 'departuresResponse', 'res')
|
||||
t.end()
|
||||
})
|
||||
|
||||
|
@ -349,13 +349,13 @@ tap.test('departures at 7-digit station', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('arrivals', async (t) => {
|
||||
const arrivals = await client.arrivals(spichernstr, {
|
||||
const res = await client.arrivals(spichernstr, {
|
||||
duration: 5, when
|
||||
})
|
||||
|
||||
await testArrivals({
|
||||
test: t,
|
||||
arrivals,
|
||||
res,
|
||||
validate,
|
||||
id: spichernstr
|
||||
})
|
||||
|
|
|
@ -183,13 +183,13 @@ tap.test('trip', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('departures at Ettelbruck.', async (t) => {
|
||||
const departures = await client.departures(ettelbruck, {
|
||||
const res = await client.departures(ettelbruck, {
|
||||
duration: 20, when
|
||||
})
|
||||
|
||||
await testDepartures({
|
||||
test: t,
|
||||
departures,
|
||||
res,
|
||||
validate,
|
||||
id: ettelbruck
|
||||
})
|
||||
|
@ -197,13 +197,13 @@ tap.test('departures at Ettelbruck.', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('arrivals at Ettelbruck.', async (t) => {
|
||||
const arrivals = await client.arrivals(ettelbruck, {
|
||||
const res = await client.arrivals(ettelbruck, {
|
||||
duration: 20, when
|
||||
})
|
||||
|
||||
await testArrivals({
|
||||
test: t,
|
||||
arrivals,
|
||||
res,
|
||||
validate,
|
||||
id: ettelbruck
|
||||
})
|
||||
|
@ -211,7 +211,7 @@ tap.test('arrivals at Ettelbruck.', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('departures with station object', async (t) => {
|
||||
const deps = await client.departures({
|
||||
const res = await client.departures({
|
||||
type: 'station',
|
||||
id: ettelbruck,
|
||||
name: 'Ettelbruck',
|
||||
|
@ -222,7 +222,7 @@ tap.test('departures with station object', async (t) => {
|
|||
}
|
||||
}, {when})
|
||||
|
||||
validate(t, deps, 'departures', 'departures')
|
||||
validate(t, res, 'departuresResponse', 'res')
|
||||
t.end()
|
||||
})
|
||||
|
||||
|
|
|
@ -162,13 +162,13 @@ tap.test('trip details', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('departures at Broadie Oaks', async (t) => {
|
||||
const departures = await client.departures(broadieOaks, {
|
||||
const res = await client.departures(broadieOaks, {
|
||||
duration: 10, when,
|
||||
})
|
||||
|
||||
await testDepartures({
|
||||
test: t,
|
||||
departures,
|
||||
res,
|
||||
validate,
|
||||
id: broadieOaks
|
||||
})
|
||||
|
@ -176,7 +176,7 @@ tap.test('departures at Broadie Oaks', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('departures with station object', async (t) => {
|
||||
const deps = await client.departures({
|
||||
const res = await client.departures({
|
||||
type: 'station',
|
||||
id: broadieOaks,
|
||||
name: 'Magdeburg Hbf',
|
||||
|
@ -187,18 +187,18 @@ tap.test('departures with station object', async (t) => {
|
|||
}
|
||||
}, {when})
|
||||
|
||||
validate(t, deps, 'departures', 'departures')
|
||||
validate(t, res, 'departuresResponse', 'res')
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('arrivals at Broadie Oaks', async (t) => {
|
||||
const arrivals = await client.arrivals(broadieOaks, {
|
||||
const res = await client.arrivals(broadieOaks, {
|
||||
duration: 10, when
|
||||
})
|
||||
|
||||
await testArrivals({
|
||||
test: t,
|
||||
arrivals,
|
||||
res,
|
||||
validate,
|
||||
id: broadieOaks
|
||||
})
|
||||
|
|
|
@ -34,13 +34,13 @@ const bielefeldHbf = '8000036'
|
|||
const hagenVorhalle = '8000977'
|
||||
|
||||
tap.test('departures at Hagen Bauhaus', async (t) => {
|
||||
const departures = await client.departures(hagenBauhaus, {
|
||||
const res = await client.departures(hagenBauhaus, {
|
||||
duration: 120, when,
|
||||
})
|
||||
|
||||
await testDepartures({
|
||||
test: t,
|
||||
departures,
|
||||
res,
|
||||
validate,
|
||||
id: hagenBauhaus
|
||||
})
|
||||
|
@ -48,11 +48,11 @@ tap.test('departures at Hagen Bauhaus', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('trip details', async (t) => {
|
||||
const deps = await client.departures(hagenBauhaus, {
|
||||
const res = await client.departures(hagenBauhaus, {
|
||||
results: 1, duration: 120, when
|
||||
})
|
||||
|
||||
const p = deps[0] || {}
|
||||
const p = res.departures[0] || {}
|
||||
t.ok(p.tripId, 'precondition failed')
|
||||
t.ok(p.line.name, 'precondition failed')
|
||||
const trip = await client.trip(p.tripId, p.line.name, {when})
|
||||
|
@ -62,7 +62,7 @@ tap.test('trip details', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('departures with station object', async (t) => {
|
||||
const deps = await client.departures({
|
||||
const res = await client.departures({
|
||||
type: 'station',
|
||||
id: hagenBauhaus,
|
||||
name: 'Hagen(Westf) Bauhaus',
|
||||
|
@ -73,20 +73,20 @@ tap.test('departures with station object', async (t) => {
|
|||
}
|
||||
}, {when, duration: 120})
|
||||
|
||||
validate(t, deps, 'departures', 'departures')
|
||||
validate(t, res, 'departuresResponse', 'res')
|
||||
t.end()
|
||||
})
|
||||
|
||||
// todo: departures at hagenBauhaus in direction of …
|
||||
|
||||
tap.test('arrivals at Hagen Bauhaus', async (t) => {
|
||||
const arrivals = await client.arrivals(hagenBauhaus, {
|
||||
const res = await client.arrivals(hagenBauhaus, {
|
||||
duration: 120, when
|
||||
})
|
||||
|
||||
await testArrivals({
|
||||
test: t,
|
||||
arrivals,
|
||||
res,
|
||||
validate,
|
||||
id: hagenBauhaus
|
||||
})
|
||||
|
|
|
@ -330,13 +330,13 @@ tap.test('trip details', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('departures at Berlin Schwedter Str.', async (t) => {
|
||||
const departures = await client.departures(blnSchwedterStr, {
|
||||
const res = await client.departures(blnSchwedterStr, {
|
||||
duration: 5, when,
|
||||
})
|
||||
|
||||
await testDepartures({
|
||||
test: t,
|
||||
departures,
|
||||
res,
|
||||
validate,
|
||||
id: blnSchwedterStr
|
||||
})
|
||||
|
@ -344,7 +344,7 @@ tap.test('departures at Berlin Schwedter Str.', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('departures with station object', async (t) => {
|
||||
const deps = await client.departures({
|
||||
const res = await client.departures({
|
||||
type: 'station',
|
||||
id: jungfernheide,
|
||||
name: 'Berlin Jungfernheide',
|
||||
|
@ -355,7 +355,7 @@ tap.test('departures with station object', async (t) => {
|
|||
}
|
||||
}, {when})
|
||||
|
||||
validate(t, deps, 'departures', 'departures')
|
||||
validate(t, res, 'departuresResponse', 'res')
|
||||
t.end()
|
||||
})
|
||||
|
||||
|
@ -373,13 +373,13 @@ tap.test('departures at Berlin Hbf in direction of Berlin Ostbahnhof', async (t)
|
|||
})
|
||||
|
||||
tap.test('arrivals at Berlin Schwedter Str.', async (t) => {
|
||||
const arrivals = await client.arrivals(blnSchwedterStr, {
|
||||
const res = await client.arrivals(blnSchwedterStr, {
|
||||
duration: 5, when,
|
||||
})
|
||||
|
||||
await testArrivals({
|
||||
test: t,
|
||||
arrivals,
|
||||
res,
|
||||
validate,
|
||||
id: blnSchwedterStr
|
||||
})
|
||||
|
@ -434,7 +434,7 @@ tap.test('stop', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('line with additionalName', async (t) => {
|
||||
const departures = await client.departures(potsdamHbf, {
|
||||
const {departures} = await client.departures(potsdamHbf, {
|
||||
when,
|
||||
duration: 12 * 60, // 12 minutes
|
||||
products: {bus: false, suburban: false, tram: false}
|
||||
|
|
|
@ -151,13 +151,13 @@ tap.skip('trip details', async (t) => {
|
|||
})
|
||||
|
||||
tap.skip('departures at Hamburg Barmbek', async (t) => {
|
||||
const departures = await client.departures(barmbek, {
|
||||
const res = await client.departures(barmbek, {
|
||||
duration: 5, when,
|
||||
})
|
||||
|
||||
await testDepartures({
|
||||
test: t,
|
||||
departures,
|
||||
res,
|
||||
validate,
|
||||
id: barmbek
|
||||
})
|
||||
|
@ -165,7 +165,7 @@ tap.skip('departures at Hamburg Barmbek', async (t) => {
|
|||
})
|
||||
|
||||
tap.skip('departures with station object', async (t) => {
|
||||
const deps = await client.departures({
|
||||
const res = await client.departures({
|
||||
type: 'station',
|
||||
id: tiefstack,
|
||||
name: 'Hamburg Tiefstack',
|
||||
|
@ -176,7 +176,7 @@ tap.skip('departures with station object', async (t) => {
|
|||
}
|
||||
}, {when})
|
||||
|
||||
validate(t, deps, 'departures', 'departures')
|
||||
validate(t, res, 'departuresResponse', 'res')
|
||||
t.end()
|
||||
})
|
||||
|
||||
|
@ -194,13 +194,13 @@ tap.skip('departures at Barmbek in direction of Altona', async (t) => {
|
|||
})
|
||||
|
||||
tap.skip('arrivals at Hamburg Barmbek', async (t) => {
|
||||
const arrivals = await client.arrivals(barmbek, {
|
||||
const res = await client.arrivals(barmbek, {
|
||||
duration: 5, when
|
||||
})
|
||||
|
||||
await testArrivals({
|
||||
test: t,
|
||||
arrivals,
|
||||
res,
|
||||
validate,
|
||||
id: barmbek
|
||||
})
|
||||
|
|
|
@ -189,13 +189,13 @@ tap.test('trip details', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('departures at Magdeburg Universität', async (t) => {
|
||||
const departures = await client.departures(universitaet, {
|
||||
const res = await client.departures(universitaet, {
|
||||
duration: 30, when,
|
||||
})
|
||||
|
||||
await testDepartures({
|
||||
test: t,
|
||||
departures,
|
||||
res,
|
||||
validate,
|
||||
id: universitaet
|
||||
})
|
||||
|
@ -203,7 +203,7 @@ tap.test('departures at Magdeburg Universität', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('departures with station object', async (t) => {
|
||||
const deps = await client.departures({
|
||||
const res = await client.departures({
|
||||
type: 'stop',
|
||||
id: universitaet,
|
||||
name: 'Universität',
|
||||
|
@ -216,7 +216,7 @@ tap.test('departures with station object', async (t) => {
|
|||
duration: 30, when,
|
||||
})
|
||||
|
||||
validate(t, deps, 'departures', 'deps')
|
||||
validate(t, res, 'departuresResponse', 'res')
|
||||
t.end()
|
||||
})
|
||||
|
||||
|
@ -235,13 +235,13 @@ tap.test('departures at Universität in direction of Spielhagenstr.', async (t)
|
|||
})
|
||||
|
||||
tap.test('arrivals at Magdeburg Universität', async (t) => {
|
||||
const arrivals = await client.arrivals(universitaet, {
|
||||
const res = await client.arrivals(universitaet, {
|
||||
duration: 30, when
|
||||
})
|
||||
|
||||
await testArrivals({
|
||||
test: t,
|
||||
arrivals,
|
||||
res,
|
||||
validate,
|
||||
id: universitaet
|
||||
})
|
||||
|
|
|
@ -181,13 +181,13 @@ tap.test('departures at Ingolstadt Hbf', async (t) => {
|
|||
'80303', // stop "Ingolstadt, Hauptbahnhof Stadtauswärts"
|
||||
]
|
||||
|
||||
const deps = await client.departures(ingolstadtHbf, {
|
||||
const res = await client.departures(ingolstadtHbf, {
|
||||
duration: 10, when
|
||||
})
|
||||
|
||||
await testDepartures({
|
||||
test: t,
|
||||
departures: deps,
|
||||
res,
|
||||
validate,
|
||||
ids,
|
||||
})
|
||||
|
@ -195,7 +195,7 @@ tap.test('departures at Ingolstadt Hbf', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('departures with station object', async (t) => {
|
||||
const deps = await client.departures({
|
||||
const res = await client.departures({
|
||||
type: 'station',
|
||||
id: ingolstadtHbf,
|
||||
name: 'Ingolstadt Hbf',
|
||||
|
@ -206,7 +206,7 @@ tap.test('departures with station object', async (t) => {
|
|||
}
|
||||
}, {when})
|
||||
|
||||
validate(t, deps, 'departures', 'departures')
|
||||
validate(t, res, 'departuresResponse', 'res')
|
||||
t.end()
|
||||
})
|
||||
|
||||
|
@ -217,13 +217,13 @@ tap.test('arrivals at Ingolstadt Hbf', async (t) => {
|
|||
'80302' // stop "Ingolstadt, Hauptbahnhof Stadteinwärts"
|
||||
]
|
||||
|
||||
const arrs = await client.arrivals(ingolstadtHbf, {
|
||||
const res = await client.arrivals(ingolstadtHbf, {
|
||||
duration: 10, when
|
||||
})
|
||||
|
||||
await testArrivals({
|
||||
test: t,
|
||||
arrivals: arrs,
|
||||
res,
|
||||
validate,
|
||||
ids,
|
||||
})
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
'use strict'
|
||||
|
||||
const testArrivals = async (cfg) => {
|
||||
const {test: t, arrivals: arrs, validate} = cfg
|
||||
const {test: t, res, validate} = cfg
|
||||
const ids = cfg.ids || (cfg.id ? [cfg.id] : [])
|
||||
const {arrivals: arrs} = res
|
||||
|
||||
validate(t, res, 'arrivalsResponse', 'res')
|
||||
|
||||
validate(t, arrs, 'arrivals', 'arrivals')
|
||||
t.ok(arrs.length > 0, 'must be >0 arrivals')
|
||||
for (let i = 0; i < arrs.length; i++) {
|
||||
let stop = arrs[i].stop
|
||||
let name = `arrs[${i}].stop`
|
||||
let name = `res.arrivals[${i}].stop`
|
||||
if (stop.station) {
|
||||
stop = stop.station
|
||||
name += '.station'
|
||||
|
@ -22,7 +23,7 @@ const testArrivals = async (cfg) => {
|
|||
}
|
||||
|
||||
// todo: move into arrivals validator
|
||||
t.same(arrs, arrs.sort((a, b) => t.when > b.when), 'arrivals must be sorted by .when')
|
||||
t.same(arrs, arrs.sort((a, b) => t.when > b.when), 'res.arrivals must be sorted by .when')
|
||||
}
|
||||
|
||||
module.exports = testArrivals
|
||||
|
|
|
@ -11,12 +11,13 @@ const testDeparturesInDirection = async (cfg) => {
|
|||
validate
|
||||
} = cfg
|
||||
|
||||
const deps = await fetchDepartures(id, {
|
||||
const res = await fetchDepartures(id, {
|
||||
direction: directionIds[0],
|
||||
when
|
||||
})
|
||||
validate(t, deps, 'departures', 'departures')
|
||||
t.ok(deps.length > 0, 'must be >0 departures')
|
||||
const {departures: deps} = res
|
||||
|
||||
validate(t, res, 'departuresResponse', 'res')
|
||||
|
||||
for (let i = 0; i < deps.length; i++) {
|
||||
const dep = deps[i]
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
'use strict'
|
||||
|
||||
const testDepartures = async (cfg) => {
|
||||
const {test: t, departures: deps, validate} = cfg
|
||||
const {test: t, res, validate} = cfg
|
||||
const ids = cfg.ids || (cfg.id ? [cfg.id] : [])
|
||||
const {departures: deps} = res
|
||||
|
||||
validate(t, res, 'departuresResponse', 'res')
|
||||
|
||||
validate(t, deps, 'departures', 'departures')
|
||||
t.ok(deps.length > 0, 'must be >0 departures')
|
||||
for (let i = 0; i < deps.length; i++) {
|
||||
let stop = deps[i].stop
|
||||
let name = `deps[${i}].stop`
|
||||
let name = `res.departures[${i}].stop`
|
||||
if (stop.station) {
|
||||
stop = stop.station
|
||||
name += '.station'
|
||||
|
@ -22,7 +23,7 @@ const testDepartures = async (cfg) => {
|
|||
}
|
||||
|
||||
// todo: move into deps validator
|
||||
t.same(deps, deps.sort((a, b) => t.when > b.when), 'departures must be sorted by .when')
|
||||
t.same(deps, deps.sort((a, b) => t.when > b.when), 'res.departures must be sorted by .when')
|
||||
}
|
||||
|
||||
module.exports = testDepartures
|
||||
|
|
|
@ -23,11 +23,10 @@ const createWhen = (timezone, locale, tMock) => {
|
|||
}).startOf('week').plus({weeks: 1, hours: 10}).toJSDate()
|
||||
}
|
||||
|
||||
const assertValidWhen = (actual, expected, name) => {
|
||||
const assertValidWhen = (actual, expected, name, delta = day + 6 * hour) => {
|
||||
const ts = +new Date(actual)
|
||||
a.ok(!Number.isNaN(ts), name + ' is not parsable by Date')
|
||||
// the timestamps might be from long-distance trains
|
||||
const delta = day + 6 * hour
|
||||
if (!isRoughlyEqual(delta, +expected, ts)) {
|
||||
throw new AssertionError({
|
||||
message: name + ' is out of range',
|
||||
|
|
|
@ -6,9 +6,18 @@ const anyOf = require('validate-fptf/lib/any-of')
|
|||
|
||||
const {assertValidWhen} = require('./util')
|
||||
|
||||
const DAY = 24 * 60 * 60 * 1000
|
||||
|
||||
const isObj = o => o !== null && 'object' === typeof o && !Array.isArray(o)
|
||||
const is = val => val !== null && val !== undefined
|
||||
|
||||
const createValidateRealtimeDataUpdatedAt = (cfg) => {
|
||||
const validateRealtimeDataUpdatedAt = (val, rtDataUpdatedAt, name = 'realtimeDataUpdatedAt') => {
|
||||
assertValidWhen(rtDataUpdatedAt * 1000, cfg.when, name, 100 * DAY)
|
||||
}
|
||||
return validateRealtimeDataUpdatedAt
|
||||
}
|
||||
|
||||
const createValidateProducts = (cfg) => {
|
||||
const validateProducts = (val, p, name = 'products') => {
|
||||
a.ok(isObj(p), name + ' must be an object')
|
||||
|
@ -518,6 +527,24 @@ const createValidateDepartures = (cfg) => {
|
|||
return _createValidateStationBoardResults(cfg, 'departure')
|
||||
}
|
||||
|
||||
const _createValidateStationBoardResponse = (cfg, validatorsKey, arrsOrDepsKey) => {
|
||||
const _validateStationBoardResponse = (val, res, name) => {
|
||||
a.ok(isObj(res), name + ' must be an object')
|
||||
|
||||
val.realtimeDataUpdatedAt(val, res.realtimeDataUpdatedAt, name + '.realtimeDataUpdatedAt')
|
||||
|
||||
const arrsOrDeps = res[arrsOrDepsKey]
|
||||
val[validatorsKey](val, arrsOrDeps, `${name}.${arrsOrDepsKey}`)
|
||||
}
|
||||
return _validateStationBoardResponse
|
||||
}
|
||||
const createValidateArrivalsResponse = (cfg) => {
|
||||
return _createValidateStationBoardResponse(cfg, 'arrivals', 'arrivals')
|
||||
}
|
||||
const createValidateDeparturesResponse = (cfg) => {
|
||||
return _createValidateStationBoardResponse(cfg, 'departures', 'departures')
|
||||
}
|
||||
|
||||
const createValidateMovement = (cfg) => {
|
||||
const { maxLatitude, minLatitude, maxLongitude, minLongitude } = cfg
|
||||
const validateMovement = (val, m, name = 'movement') => {
|
||||
|
@ -576,6 +603,7 @@ const validateMovements = (val, ms, name = 'movements') => {
|
|||
}
|
||||
|
||||
module.exports = {
|
||||
realtimeDataUpdatedAt: createValidateRealtimeDataUpdatedAt,
|
||||
products: createValidateProducts,
|
||||
station: createValidateStation,
|
||||
stop: () => validateStop,
|
||||
|
@ -596,6 +624,8 @@ module.exports = {
|
|||
departure: createValidateDeparture,
|
||||
arrivals: createValidateArrivals,
|
||||
departures: createValidateDepartures,
|
||||
arrivalsResponse: createValidateArrivalsResponse,
|
||||
departuresResponse: createValidateDeparturesResponse,
|
||||
movement: createValidateMovement,
|
||||
movements: () => validateMovements
|
||||
}
|
||||
|
|
|
@ -156,13 +156,13 @@ tap.test('departures at Soest', async (t) => {
|
|||
'902737', // Bahnhof E, Soest
|
||||
]
|
||||
|
||||
const departures = await client.departures(soest, {
|
||||
const res = await client.departures(soest, {
|
||||
duration: 10, when,
|
||||
})
|
||||
|
||||
await testDepartures({
|
||||
test: t,
|
||||
departures,
|
||||
res,
|
||||
validate,
|
||||
ids,
|
||||
})
|
||||
|
@ -170,7 +170,7 @@ tap.test('departures at Soest', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('departures with station object', async (t) => {
|
||||
const deps = await client.departures({
|
||||
const res = await client.departures({
|
||||
type: 'station',
|
||||
id: soest,
|
||||
name: 'Magdeburg Hbf',
|
||||
|
@ -181,7 +181,7 @@ tap.test('departures with station object', async (t) => {
|
|||
}
|
||||
}, {when})
|
||||
|
||||
validate(t, deps, 'departures', 'departures')
|
||||
validate(t, res, 'departuresResponse', 'res')
|
||||
t.end()
|
||||
})
|
||||
|
||||
|
@ -193,13 +193,13 @@ tap.test('arrivals at Soest', async (t) => {
|
|||
'902737', // Bahnhof E, Soest
|
||||
]
|
||||
|
||||
const arrivals = await client.arrivals(soest, {
|
||||
const res = await client.arrivals(soest, {
|
||||
duration: 10, when,
|
||||
})
|
||||
|
||||
await testArrivals({
|
||||
test: t,
|
||||
arrivals,
|
||||
res,
|
||||
validate,
|
||||
ids,
|
||||
})
|
||||
|
|
|
@ -187,13 +187,13 @@ tap.test('trip', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('departures at Ettelbruck.', async (t) => {
|
||||
const departures = await client.departures(ettelbruck, {
|
||||
const res = await client.departures(ettelbruck, {
|
||||
duration: 20, when
|
||||
})
|
||||
|
||||
await testDepartures({
|
||||
test: t,
|
||||
departures,
|
||||
res,
|
||||
validate,
|
||||
id: ettelbruck
|
||||
})
|
||||
|
@ -201,13 +201,13 @@ tap.test('departures at Ettelbruck.', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('arrivals at Ettelbruck.', async (t) => {
|
||||
const arrivals = await client.arrivals(ettelbruck, {
|
||||
const res = await client.arrivals(ettelbruck, {
|
||||
duration: 20, when
|
||||
})
|
||||
|
||||
await testArrivals({
|
||||
test: t,
|
||||
arrivals,
|
||||
res,
|
||||
validate,
|
||||
id: ettelbruck
|
||||
})
|
||||
|
@ -215,7 +215,7 @@ tap.test('arrivals at Ettelbruck.', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('departures with station object', async (t) => {
|
||||
const deps = await client.departures({
|
||||
const res = await client.departures({
|
||||
type: 'station',
|
||||
id: ettelbruck,
|
||||
name: 'Ettelbruck',
|
||||
|
@ -226,7 +226,7 @@ tap.test('departures with station object', async (t) => {
|
|||
}
|
||||
}, {when})
|
||||
|
||||
validate(t, deps, 'departures', 'departures')
|
||||
validate(t, res, 'departuresResponse', 'res')
|
||||
t.end()
|
||||
})
|
||||
|
||||
|
|
|
@ -224,13 +224,13 @@ tap.test('trip details', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('departures at Kiel Räucherei', async (t) => {
|
||||
const departures = await client.departures(kielRaeucherei, {
|
||||
const res = await client.departures(kielRaeucherei, {
|
||||
duration: 30, when,
|
||||
})
|
||||
|
||||
await testDepartures({
|
||||
test: t,
|
||||
departures,
|
||||
res,
|
||||
validate,
|
||||
id: kielRaeucherei
|
||||
})
|
||||
|
@ -238,7 +238,7 @@ tap.test('departures at Kiel Räucherei', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('departures with station object', async (t) => {
|
||||
const deps = await client.departures({
|
||||
const res = await client.departures({
|
||||
type: 'station',
|
||||
id: kielHbf,
|
||||
name: 'Kiel Hbf',
|
||||
|
@ -249,7 +249,7 @@ tap.test('departures with station object', async (t) => {
|
|||
}
|
||||
}, {when})
|
||||
|
||||
validate(t, deps, 'departures', 'departures')
|
||||
validate(t, res, 'departuresResponse', 'res')
|
||||
t.end()
|
||||
})
|
||||
|
||||
|
@ -267,13 +267,13 @@ tap.test('departures at Berlin Hbf in direction of Berlin Ostbahnhof', async (t)
|
|||
})
|
||||
|
||||
tap.test('arrivals at Kiel Räucherei', async (t) => {
|
||||
const arrivals = await client.arrivals(kielRaeucherei, {
|
||||
const res = await client.arrivals(kielRaeucherei, {
|
||||
duration: 30, when
|
||||
})
|
||||
|
||||
await testArrivals({
|
||||
test: t,
|
||||
arrivals,
|
||||
res,
|
||||
validate,
|
||||
id: kielRaeucherei
|
||||
})
|
||||
|
|
|
@ -171,13 +171,13 @@ tap.test('trip details', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('departures at Kassel Auestadion.', async (t) => {
|
||||
const departures = await client.departures(auestadion, {
|
||||
const res = await client.departures(auestadion, {
|
||||
duration: 11, when,
|
||||
})
|
||||
|
||||
await testDepartures({
|
||||
test: t,
|
||||
departures,
|
||||
res,
|
||||
validate,
|
||||
id: auestadion
|
||||
})
|
||||
|
@ -185,7 +185,7 @@ tap.test('departures at Kassel Auestadion.', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('departures with station object', async (t) => {
|
||||
const deps = await client.departures({
|
||||
const res = await client.departures({
|
||||
type: 'station',
|
||||
id: auestadion,
|
||||
name: 'Kassel Auestadion',
|
||||
|
@ -196,7 +196,7 @@ tap.test('departures with station object', async (t) => {
|
|||
}
|
||||
}, {when})
|
||||
|
||||
validate(t, deps, 'departures', 'departures')
|
||||
validate(t, res, 'departuresResponse', 'res')
|
||||
t.end()
|
||||
})
|
||||
|
||||
|
@ -214,13 +214,13 @@ tap.test('departures at Auestadion in direction of Friedrichsplatz', async (t) =
|
|||
})
|
||||
|
||||
tap.test('arrivals at Kassel Weigelstr.', async (t) => {
|
||||
const arrivals = await client.arrivals(weigelstr, {
|
||||
const res = await client.arrivals(weigelstr, {
|
||||
duration: 5, when
|
||||
})
|
||||
|
||||
await testArrivals({
|
||||
test: t,
|
||||
arrivals,
|
||||
res,
|
||||
validate,
|
||||
id: weigelstr,
|
||||
})
|
||||
|
|
|
@ -251,13 +251,13 @@ tap.test('departures at Wien Leibenfrostgasse', async (t) => {
|
|||
'904030' // stop "Wien Leibenfrostgasse (Ziegelofengasse)"
|
||||
]
|
||||
|
||||
const deps = await client.departures(wienLeibenfrostgasse, {
|
||||
const res = await client.departures(wienLeibenfrostgasse, {
|
||||
duration: 15, when,
|
||||
})
|
||||
|
||||
await testDepartures({
|
||||
test: t,
|
||||
departures: deps,
|
||||
res,
|
||||
validate,
|
||||
ids,
|
||||
})
|
||||
|
@ -265,7 +265,7 @@ tap.test('departures at Wien Leibenfrostgasse', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('departures with station object', async (t) => {
|
||||
const deps = await client.departures({
|
||||
const res = await client.departures({
|
||||
type: 'station',
|
||||
id: salzburgHbf,
|
||||
name: 'Salzburg Hbf',
|
||||
|
@ -276,7 +276,7 @@ tap.test('departures with station object', async (t) => {
|
|||
}
|
||||
}, {when})
|
||||
|
||||
validate(t, deps, 'departures', 'departures')
|
||||
validate(t, res, 'departuresResponse', 'res')
|
||||
t.end()
|
||||
})
|
||||
|
||||
|
|
|
@ -118,13 +118,13 @@ tap.test('trip', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('departures at Næstved.', async (t) => {
|
||||
const departures = await client.departures(næstved, {
|
||||
const res = await client.departures(næstved, {
|
||||
duration: 20, when,
|
||||
})
|
||||
|
||||
await testDepartures({
|
||||
test: t,
|
||||
departures,
|
||||
res,
|
||||
validate,
|
||||
ids: [
|
||||
næstved,
|
||||
|
@ -136,13 +136,13 @@ tap.test('departures at Næstved.', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('arrivals at Næstved.', async (t) => {
|
||||
const arrivals = await client.arrivals(næstved, {
|
||||
const res = await client.arrivals(næstved, {
|
||||
duration: 20, when
|
||||
})
|
||||
|
||||
await testArrivals({
|
||||
test: t,
|
||||
arrivals,
|
||||
res,
|
||||
validate,
|
||||
ids: [
|
||||
næstved,
|
||||
|
|
|
@ -66,13 +66,13 @@ tap.test('trip details', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('arrivals at Wiesbaden Hbf', async (t) => {
|
||||
const arrivals = await client.arrivals(wiesbadenHbf, {
|
||||
const res = await client.arrivals(wiesbadenHbf, {
|
||||
duration: 10, when
|
||||
})
|
||||
|
||||
await testArrivals({
|
||||
test: t,
|
||||
arrivals,
|
||||
res,
|
||||
validate,
|
||||
id: wiesbadenHbf,
|
||||
})
|
||||
|
|
|
@ -81,13 +81,13 @@ tap.test('refreshJourney', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('arrivals at Platz der Jugend', async (t) => {
|
||||
const arrivals = await client.arrivals(sternwarte, {
|
||||
const res = await client.arrivals(sternwarte, {
|
||||
duration: 30, when
|
||||
})
|
||||
|
||||
await testArrivals({
|
||||
test: t,
|
||||
arrivals,
|
||||
res,
|
||||
validate,
|
||||
ids: [
|
||||
sternwarte,
|
||||
|
|
|
@ -172,13 +172,13 @@ tap.test('trip details', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('departures', async (t) => {
|
||||
const departures = await client.departures(saarbrueckenUhlandstr, {
|
||||
const res = await client.departures(saarbrueckenUhlandstr, {
|
||||
duration: 5, when
|
||||
})
|
||||
|
||||
await testDepartures({
|
||||
test: t,
|
||||
departures,
|
||||
res,
|
||||
validate,
|
||||
id: saarbrueckenUhlandstr,
|
||||
})
|
||||
|
@ -186,9 +186,9 @@ tap.test('departures', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('departures with stop object', async (t) => {
|
||||
const deps = await client.departures({
|
||||
const res = await client.departures({
|
||||
type: 'stop',
|
||||
id: '8000323',
|
||||
id: saarbrueckenHbf,
|
||||
name: 'Saarbrücken Hbf',
|
||||
location: {
|
||||
type: 'location',
|
||||
|
@ -197,7 +197,7 @@ tap.test('departures with stop object', async (t) => {
|
|||
}
|
||||
}, {when})
|
||||
|
||||
validate(t, deps, 'departures', 'departures')
|
||||
validate(t, res, 'departuresResponse', 'res')
|
||||
t.end()
|
||||
})
|
||||
|
||||
|
|
|
@ -178,13 +178,13 @@ tap.test('trip details', async (t) => {
|
|||
|
||||
tap.test('departures at Dietlindenstraße', async (t) => {
|
||||
const dietlindenstr = '624391'
|
||||
const departures = await client.departures(dietlindenstr, {
|
||||
const res = await client.departures(dietlindenstr, {
|
||||
duration: 10, when,
|
||||
})
|
||||
|
||||
await testDepartures({
|
||||
test: t,
|
||||
departures,
|
||||
res,
|
||||
validate,
|
||||
id: dietlindenstr
|
||||
})
|
||||
|
@ -192,7 +192,7 @@ tap.test('departures at Dietlindenstraße', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('departures with station object', async (t) => {
|
||||
const deps = await client.departures({
|
||||
const res = await client.departures({
|
||||
type: 'station',
|
||||
id: mittersendling,
|
||||
name: 'Mittersendling',
|
||||
|
@ -203,18 +203,18 @@ tap.test('departures with station object', async (t) => {
|
|||
}
|
||||
}, {when})
|
||||
|
||||
validate(t, deps, 'departures', 'departures')
|
||||
validate(t, res, 'departuresResponse', 'res')
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('arrivals at Karl-Theodor-Straße', async (t) => {
|
||||
const arrivals = await client.arrivals(karlTheodorStr, {
|
||||
const res = await client.arrivals(karlTheodorStr, {
|
||||
duration: 10, when,
|
||||
})
|
||||
|
||||
await testArrivals({
|
||||
test: t,
|
||||
arrivals,
|
||||
res,
|
||||
validate,
|
||||
id: karlTheodorStr
|
||||
})
|
||||
|
|
|
@ -71,13 +71,13 @@ tap.test('trip details', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('arrivals at Bruxelles Midi', async (t) => {
|
||||
const arrivals = await client.arrivals(bruxellesMidi, {
|
||||
const res = await client.arrivals(bruxellesMidi, {
|
||||
duration: 10, when
|
||||
})
|
||||
|
||||
await testArrivals({
|
||||
test: t,
|
||||
arrivals,
|
||||
res,
|
||||
validate,
|
||||
id: bruxellesMidi,
|
||||
})
|
||||
|
|
|
@ -73,13 +73,13 @@ tap.test('trip details', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('arrivals at Volksgarten', async (t) => {
|
||||
const arrivals = await client.arrivals(volksgarten, {
|
||||
const res = await client.arrivals(volksgarten, {
|
||||
duration: 10, when
|
||||
})
|
||||
|
||||
await testArrivals({
|
||||
test: t,
|
||||
arrivals,
|
||||
res,
|
||||
validate,
|
||||
id: volksgarten,
|
||||
})
|
||||
|
|
|
@ -285,13 +285,13 @@ tap.test('journeys: via works – with detour', async (t) => {
|
|||
// todo: without detour test
|
||||
|
||||
tap.test('departures', async (t) => {
|
||||
const departures = await client.departures(spichernstr, {
|
||||
const res = await client.departures(spichernstr, {
|
||||
duration: 5, when,
|
||||
})
|
||||
|
||||
await testDepartures({
|
||||
test: t,
|
||||
departures,
|
||||
res,
|
||||
validate,
|
||||
id: spichernstr
|
||||
})
|
||||
|
@ -299,7 +299,7 @@ tap.test('departures', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('departures with station object', async (t) => {
|
||||
const deps = await client.departures({
|
||||
const res = await client.departures({
|
||||
type: 'station',
|
||||
id: spichernstr,
|
||||
name: 'U Spichernstr',
|
||||
|
@ -310,7 +310,7 @@ tap.test('departures with station object', async (t) => {
|
|||
}
|
||||
}, {when})
|
||||
|
||||
validate(t, deps, 'departures', 'departures')
|
||||
validate(t, res, 'departuresResponse', 'res')
|
||||
t.end()
|
||||
})
|
||||
|
||||
|
@ -335,13 +335,13 @@ tap.test('departures at 7-digit station', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('arrivals', async (t) => {
|
||||
const arrivals = await client.arrivals(spichernstr, {
|
||||
const res = await client.arrivals(spichernstr, {
|
||||
duration: 5, when,
|
||||
})
|
||||
|
||||
await testArrivals({
|
||||
test: t,
|
||||
arrivals,
|
||||
res,
|
||||
validate,
|
||||
id: spichernstr
|
||||
})
|
||||
|
|
|
@ -66,13 +66,13 @@ tap.test('trip details', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('arrivals at Bremen Humboldtstr.', async (t) => {
|
||||
const arrivals = await client.arrivals(bremenHumboldtstr, {
|
||||
const res = await client.arrivals(bremenHumboldtstr, {
|
||||
duration: 10, when
|
||||
})
|
||||
|
||||
await testArrivals({
|
||||
test: t,
|
||||
arrivals,
|
||||
res,
|
||||
validate,
|
||||
id: bremenHumboldtstr,
|
||||
})
|
||||
|
|
|
@ -145,13 +145,13 @@ tap.test('trip details', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('departures at Meckesheim', async (t) => {
|
||||
const departures = await client.departures(meckesheim, {
|
||||
const res = await client.departures(meckesheim, {
|
||||
duration: 3 * 60, when,
|
||||
})
|
||||
|
||||
await testDepartures({
|
||||
test: t,
|
||||
departures,
|
||||
res,
|
||||
validate,
|
||||
id: meckesheim
|
||||
})
|
||||
|
@ -173,13 +173,13 @@ tap.test('departures at Meckesheim in direction of Reilsheim', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('arrivals at Meckesheim', async (t) => {
|
||||
const arrivals = await client.arrivals(meckesheim, {
|
||||
const res = await client.arrivals(meckesheim, {
|
||||
duration: 3 * 60, when
|
||||
})
|
||||
|
||||
await testArrivals({
|
||||
test: t,
|
||||
arrivals,
|
||||
res,
|
||||
validate,
|
||||
id: meckesheim
|
||||
})
|
||||
|
|
|
@ -101,13 +101,13 @@ tap.test('trip', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('departures at Kornmarkt.', async (t) => {
|
||||
const departures = await client.departures(kornmarkt, {
|
||||
const res = await client.departures(kornmarkt, {
|
||||
duration: 20, when
|
||||
})
|
||||
|
||||
await testDepartures({
|
||||
test: t,
|
||||
departures,
|
||||
res,
|
||||
validate,
|
||||
id: kornmarkt
|
||||
})
|
||||
|
@ -115,13 +115,13 @@ tap.test('departures at Kornmarkt.', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('arrivals at Kornmarkt.', async (t) => {
|
||||
const arrivals = await client.arrivals(kornmarkt, {
|
||||
const res = await client.arrivals(kornmarkt, {
|
||||
duration: 20, when
|
||||
})
|
||||
|
||||
await testArrivals({
|
||||
test: t,
|
||||
arrivals,
|
||||
res,
|
||||
validate,
|
||||
id: kornmarkt
|
||||
})
|
||||
|
@ -129,7 +129,7 @@ tap.test('arrivals at Kornmarkt.', async (t) => {
|
|||
})
|
||||
|
||||
tap.test('departures with station object', async (t) => {
|
||||
const deps = await client.departures({
|
||||
const res = await client.departures({
|
||||
type: 'station',
|
||||
id: kornmarkt,
|
||||
name: 'Kornmarkt',
|
||||
|
@ -140,7 +140,7 @@ tap.test('departures with station object', async (t) => {
|
|||
}
|
||||
}, {when})
|
||||
|
||||
validate(t, deps, 'departures', 'departures')
|
||||
validate(t, res, 'departuresResponse', 'res')
|
||||
t.end()
|
||||
})
|
||||
|
||||
|
|
|
@ -79,13 +79,13 @@ tap.test('trip details', async (t) => {
|
|||
|
||||
tap.test('departures at ETH/Universitätsspital', async (t) => { // todo
|
||||
const polyterrasseETH = '8503500'
|
||||
const departures = await client.departures(ethUniversitätsspital, {
|
||||
const res = await client.departures(ethUniversitätsspital, {
|
||||
duration: 5, when,
|
||||
})
|
||||
|
||||
await testDepartures({
|
||||
test: t,
|
||||
departures,
|
||||
res,
|
||||
validate,
|
||||
ids: [ethUniversitätsspital, polyterrasseETH],
|
||||
})
|
||||
|
|
|
@ -42,9 +42,16 @@ tap.test('withRetrying works', (t) => {
|
|||
})
|
||||
const client = createClient(profile, userAgent)
|
||||
|
||||
t.plan(1 + 4)
|
||||
t.plan(2 + 4)
|
||||
client.departures(spichernstr, {duration: 1})
|
||||
.then(deps => t.same(deps, [], 'resolved with invalid value'))
|
||||
.then((res) => {
|
||||
const {
|
||||
departures: deps,
|
||||
realtimeDataUpdatedAt,
|
||||
} = res
|
||||
t.same(deps, [], 'resolved with invalid value')
|
||||
t.equal(realtimeDataUpdatedAt, null, 'resolved with invalid value')
|
||||
})
|
||||
.catch(t.ifError)
|
||||
|
||||
setTimeout(() => t.equal(calls, 1), 50) // buffer
|
||||
|
|
Loading…
Add table
Reference in a new issue