Files
TREK/server/tests/unit/nest/maps.helpers.test.ts
T
Maurice 061259b3d9 feat(maps): pass through what OSM knows about a charging station
The corridor query has always ended in `out center tags`, and the projection
has always kept six keys out of the whole tag set. Everything an electric car
needs to know was in the answer and went in the bin: which plug families the
station has, how many of each, at what power, how many vehicles fit, and whether
it charges. Reading them costs no extra request, no key and no second query.

The filters treat "not stated" as its own answer, which is the only honest way
to build them: OSM states a socket type on roughly a third of charging stations
and a power on fewer still, so a power filter keeps every station that says
nothing rather than reading silence as too slow. The plug dropdown offers only
the families this search actually turned up, because CHAdeMO in a list for a
corridor with three Type 2 posts on it is a filter that can only return nothing.

Socket names stay as they are printed on the plug. "Type 2" is not a string
anyone translates, and the lowercase tag key is not what is written on the
charger.

The tool description follows, as parity requires, including the coverage caveat
— a model that reads a missing power as "no fast charging here" would be wrong
about most of the map.
2026-09-07 18:36:54 +02:00

65 lines
3.0 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { readChargingInfo } from '../../../src/nest/maps/maps.helpers';
/**
* The tag reading that turns an Overpass answer into something a traveller can filter on.
*
* Pure, so it is tested on its own: no request, no cache, no service. The query already
* asks for the whole tag set, and every one of these fields was being dropped on the way
* through.
*/
describe('readChargingInfo', () => {
// The query has always asked for `out center tags` and the projection has always
// thrown these away. Nothing here costs an extra request.
it('MAPS-CHARGE-001: reads the socket families, their count and their power', () => {
expect(readChargingInfo({
'socket:type2': '4',
'socket:type2:output': '22 kW',
'socket:ccs': '2',
'socket:ccs:output': '150',
})).toEqual({
sockets: [
{ type: 'type2', count: 4, kw: 22 },
{ type: 'ccs', count: 2, kw: 150 },
],
capacity: null,
fee: null,
});
});
it('MAPS-CHARGE-002: a socket that states nothing beyond its presence still counts', () => {
// `socket:type2=yes` is common. The family is the useful part; the rest is unknown
// rather than zero, and a filter has to be able to tell those apart.
expect(readChargingInfo({ 'socket:type2': 'yes' })?.sockets).toEqual([
{ type: 'type2', count: null, kw: null },
]);
});
it('MAPS-CHARGE-003: free text power is read off the front, comma or not', () => {
expect(readChargingInfo({ 'socket:type2': '1', 'socket:type2:output': '11kW' })?.sockets[0].kw).toBe(11);
expect(readChargingInfo({ 'socket:type2': '1', 'socket:type2:output': '2,3 kW' })?.sockets[0].kw).toBe(2.3);
expect(readChargingInfo({ 'socket:type2': '1', 'socket:type2:output': 'unknown' })?.sockets[0].kw).toBeNull();
});
it('MAPS-CHARGE-004: fee is three answers, not two', () => {
expect(readChargingInfo({ fee: 'yes' })?.fee).toBe(true);
expect(readChargingInfo({ fee: 'no' })?.fee).toBe(false);
// Half the charging stations in OSM say nothing about it, and "not stated" is not
// "free". A value nobody can act on is read as unknown; with a socket alongside it
// the station still reports, it just does not claim a price.
expect(readChargingInfo({ 'socket:type2': '2', fee: 'interval' })?.fee).toBeNull();
// On its own it says nothing at all, so there is nothing to report.
expect(readChargingInfo({ fee: 'interval' })).toBeNull();
});
it('MAPS-CHARGE-005: a station that says nothing at all is null, not an empty shell', () => {
// So the client can tell "no data" from "no sockets" without inspecting three fields.
expect(readChargingInfo({ amenity: 'charging_station', name: 'Ionity' })).toBeNull();
});
it('MAPS-CHARGE-006: a nonsense count is dropped rather than written through', () => {
expect(readChargingInfo({ 'socket:type2': '0' })?.sockets[0].count).toBeNull();
expect(readChargingInfo({ capacity: '-2' })).toBeNull();
});
});