Files
ridebus-admin/backend/test/passengerTelemetryAggregator.test.js

143 lines
4.0 KiB
JavaScript

const assert = require('node:assert/strict');
const test = require('node:test');
const { PassengerTelemetryAggregator } = require('../src/services/passengerTelemetryAggregator');
const NOW = new Date('2026-05-20T12:30:00Z');
const routeRows = [{
id: 1,
route_number: '12',
name: 'Route 12',
type: 'bus',
color: '#0066CC',
stops: [
{ stop_id: 1, sequence: 1, latitude: 55.485, longitude: 28.768 },
{ stop_id: 2, sequence: 2, latitude: 55.485, longitude: 28.778 }
]
}];
function createPool(rows = routeRows) {
return {
async query(sql, params = []) {
if (params.length > 0) {
return { rows: rows.filter((row) => row.id === params[0]) };
}
return { rows };
}
};
}
function createAggregator(options = {}) {
return new PassengerTelemetryAggregator({
pool: createPool(options.rows),
nowProvider: () => NOW,
...options
});
}
async function ingestCluster(aggregator, accuracyM = 18) {
await aggregator.ingest({
session_id: 'session-a',
latitude: 55.48502,
longitude: 28.7700,
accuracy_m: accuracyM,
speed_kmh: 31,
heading: 90,
observed_at: '2026-05-20T12:29:55Z',
route_id: 1
});
await aggregator.ingest({
session_id: 'session-b',
latitude: 55.48498,
longitude: 28.77015,
accuracy_m: accuracyM,
speed_kmh: 33,
heading: 92,
observed_at: '2026-05-20T12:29:58Z',
route_id: 1
});
}
test('one passenger point does not create a virtual vehicle', async () => {
const aggregator = createAggregator();
const result = await aggregator.ingest({
session_id: 'session-a',
latitude: 55.485,
longitude: 28.770,
accuracy_m: 18,
speed_kmh: 32,
heading: 90,
observed_at: '2026-05-20T12:29:55Z',
route_id: 1
});
assert.equal(result.accepted, true);
assert.equal(result.matched, true);
assert.deepEqual(result.virtualVehicles, []);
assert.deepEqual(await aggregator.getVirtualVehiclesForRoute(1), []);
});
test('nearby points from distinct sessions create a virtual crowdsourced vehicle', async () => {
const aggregator = createAggregator();
await ingestCluster(aggregator);
const vehicles = await aggregator.getVirtualVehiclesForRoute(1);
assert.equal(vehicles.length, 1);
assert.match(vehicles[0].vehicle_id, /^crowd:1:/);
assert.equal(vehicles[0].source, 'crowdsourced');
assert.equal(vehicles[0].source_ref, null);
assert.equal(vehicles[0].is_estimated, true);
assert.equal('session_id' in vehicles[0], false);
assert.equal('session_key' in vehicles[0], false);
});
test('poor accuracy lowers crowdsourced confidence', async () => {
const goodAggregator = createAggregator();
await ingestCluster(goodAggregator, 15);
const [goodVehicle] = await goodAggregator.getVirtualVehiclesForRoute(1);
const poorAggregator = createAggregator();
await ingestCluster(poorAggregator, 150);
const [poorVehicle] = await poorAggregator.getVirtualVehiclesForRoute(1);
assert.ok(goodVehicle.confidence > poorVehicle.confidence);
assert.equal(poorVehicle.source, 'crowdsourced');
});
test('points outside route geometry are accepted but ignored', async () => {
const aggregator = createAggregator();
const result = await aggregator.ingest({
session_id: 'session-a',
latitude: 56.0,
longitude: 29.5,
accuracy_m: 20,
observed_at: '2026-05-20T12:29:55Z',
route_id: 1
});
assert.equal(result.accepted, true);
assert.equal(result.matched, false);
assert.deepEqual(await aggregator.getVirtualVehiclesForRoute(1), []);
});
test('official telemetry suppresses nearby crowdsourced virtual vehicle', async () => {
const aggregator = createAggregator();
await ingestCluster(aggregator);
const [vehicle] = await aggregator.getVirtualVehiclesForRoute(1);
assert.ok(vehicle);
const suppressed = await aggregator.getVirtualVehiclesForRoute(1, {
officialVehicles: [{
vehicle_id: 99,
latitude: vehicle.latitude,
longitude: vehicle.longitude,
is_online: true
}]
});
assert.deepEqual(suppressed, []);
});