Files
ridebus-board/test/timetable.test.mjs
2026-10-08 22:56:41 +03:00

65 lines
5.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import test from 'node:test';
import assert from 'node:assert/strict';
import { loadTimetable } from '../server/timetable.mjs';
import { isTimetable, localDate, timetableAvailable, timetableDepartures } from '../src/lib/timetable.mjs';
import { passengerLink } from '../src/lib/passenger.mjs';
const now = Date.parse('2026-10-08T12:00:00+03:00');
const route = { id: 37, route_number: '5', name: 'А — В', type: 'bus' };
const stops = [{ stop_id: 1, sequence: 1, time_offset_minutes: 0, name: 'А' }, { stop_id: 2, sequence: 2, time_offset_minutes: 15, name: 'Б' }, { stop_id: 3, sequence: 3, time_offset_minutes: 30, name: 'В' }];
const schedule = (day_type, trips, extra = {}) => ({ route_id: 37, day_type, trips, ...extra });
async function bundle(schedules, options = {}) {
return loadTimetable({ cityId: 1, cityName: 'Полоцк', stop: { id: 2, name: 'Б', routes: [route] }, now,
upstream: async path => path.includes('holidays') ? options.holidays || [] : path.endsWith('/stops') ? stops : schedules });
}
test('full cache contains all trips and regenerates Friday and Saturday without network', async () => {
const data = await bundle([schedule('weekday', ['08:00', '12:00', '15:00', '18:00', '22:00'].map(departureTime => ({ departureTime }))), schedule('saturday', [{ departureTime: '09:00' }])]);
assert.equal(isTimetable(data, 1, 2), true);
assert.equal(isTimetable(data, 1, 1), false);
assert.equal(isTimetable({ ...data, routes: [{ ...data.routes[0], schedules: [{ dayType: 'weekday', trips: [{ arrivalSeconds: NaN }] }] }] }, 1, 2), false);
const departures = timetableDepartures(data, '2026-10-09');
assert.equal(departures.filter(item => localDate(Date.parse(item.scheduledAt)) === '2026-10-09').length, 5);
assert.equal(departures.find(item => localDate(Date.parse(item.scheduledAt)) === '2026-10-10').scheduledTime, '09:15');
assert.equal(timetableAvailable(data, now + 86400000), true);
assert.equal(timetableAvailable(data, now + 7 * 86400000), false);
});
test('holiday timetable wins; missing holiday variant falls back to Sunday', async () => {
const holidays = [{ date: '2026-10-09T00:00:00.000Z' }];
const schedules = [schedule('weekday', [{ departureTime: '08:00' }]), schedule('sunday', [{ departureTime: '09:00' }])];
const fallback = timetableDepartures(await bundle(schedules, { holidays }), '2026-10-09');
assert.equal(fallback[0].scheduledTime, '09:15');
const dedicated = timetableDepartures(await bundle([...schedules, schedule('holiday', [{ departureTime: '10:00' }])], { holidays }), '2026-10-09');
assert.equal(dedicated[0].scheduledTime, '10:15');
});
test('trip restrictions and schedule validity apply to the actual service date', async () => {
const data = await bundle([schedule('weekday', [{ departureTime: '08:00', daysOfWeek: [4] }, { departureTime: '09:00', exceptDaysOfWeek: [4] }], { valid_until: '2026-10-09' })]);
assert.equal(timetableDepartures(data, '2026-10-08')[0].scheduledTime, '08:15');
assert.equal(timetableDepartures(data, '2026-10-09')[0].scheduledTime, '09:15');
assert.equal(timetableDepartures(data, '2026-10-12').length, 0);
});
test('previous service day retains after-midnight arrivals, with no time wrapping', async () => {
const data = await bundle([schedule('weekday', [{ departureTime: '23:55', daysOfWeek: [4] }])]);
const departures = timetableDepartures(data, '2026-10-09');
assert.equal(departures.length, 1);
assert.equal(departures[0].scheduledTime, '00:10');
assert.equal(departures[0].scheduledAt, '2026-10-08T21:10:00.000Z');
});
test('partial and reverse trips use the start offset and correct terminal', async () => {
const data = await bundle([schedule('weekday', [{ departureTime: '12:00', startStopId: 3, endStopId: 1, note: 'Обратно' }, { departureTime: '13:00', startStopId: 3, endStopId: 3 }])]);
const departures = timetableDepartures(data, '2026-10-08').filter(item => localDate(Date.parse(item.scheduledAt)) === '2026-10-08');
assert.equal(departures.length, 1); assert.equal(departures[0].scheduledTime, '12:15'); assert.equal(departures[0].destination, 'А');
});
test('failed calendar, route data, or malformed trips cannot publish a misleading complete cache', async () => {
await assert.rejects(bundle([schedule('weekday', [{ departureTime: '12:00', startStopId: 999 }])]));
await assert.rejects(bundle([schedule('weekday', [{ departureTime: '25:00' }])]));
await assert.rejects(bundle([], { holidays: {} }));
await assert.rejects(loadTimetable({ cityId: 1, cityName: 'Полоцк', stop: { id: 2, name: 'Б', routes: [route] }, now, upstream: async path => {
if (path.endsWith('/schedules')) throw new Error('Upstream unavailable'); return path.includes('holidays') ? [] : stops;
} }));
});
test('QR link identifies the exact city and platform, strips credentials and old query state', () => {
assert.equal(passengerLink('https://pwa.ridebus.by/?demo=1', 1, 107), 'https://pwa.ridebus.by/?city=1&stop=107');
assert.equal(passengerLink('javascript:alert(1)', 1, 1), 'https://pwa.ridebus.by/?city=1&stop=1');
assert.equal(passengerLink('https://user:password@pwa.ridebus.by/#old', 2, 88), 'https://pwa.ridebus.by/?city=2&stop=88');
});