import 'package:flutter_test/flutter_test.dart'; import 'package:miler/data/device_telemetry.dart'; /// ───────────────────────────────────────────────────────────────────────── /// THE CONSOLE'S DEVICE PANEL WAS READING AN EMPTY FIELD /// /// ── What the dispatcher saw ── /// /// Battery —, Connection —, GPS Accuracy —, Location Service —, Current /// Order —. Every column on the panel that answers *why has this rider gone /// quiet* was blank, on riders whose phones were reporting all of it. /// /// ── Why ── /// /// Not because nothing was measured. The heartbeat read the battery, the /// charging state, the connection type and the location-service status on every /// tick — and published them to **MQTT only**, in a block that ran *after* the /// rider log had already been posted. `POST /miler/logs` has a named field for /// each one and carried none of them. /// /// Two lanes for one fact, and the lane the console reads was the empty one. /// On top of that, `location_service` was dropped a second time further down: /// the provider forwarded every other device field to [MilerApi.postLog] and /// silently skipped that one, so it could not have arrived even if the caller /// had sent it. /// /// These pin the shape of the payload, which is the part a wrong refactor would /// break silently — a missing key renders as an em dash, which is exactly what /// it looked like before anyone noticed. /// ───────────────────────────────────────────────────────────────────────── void main() { group('the payload carries what the console reads', () { test('every field the contract names is emitted under its own name', () { const t = DeviceTelemetry( battery: 72, isCharging: false, connection: '4G', locationService: 'enabled', ); // The names are the wire's, from `POST /miler/logs`. A rename here is a // column that goes blank on the console with nothing else failing. expect(t.toPayload(), { 'battery': '72', 'is_charging': false, 'connection': '4G', 'location_service': 'enabled', }); }); test('numbers go as strings, as every other numeric field does', () { // Documented at the top of `MilerApi`: real numbers fail to parse // server-side on `/logs` and `/consignments/logs`. const t = DeviceTelemetry(battery: 5); expect(t.toPayload()['battery'], isA()); expect(t.toPayload()['battery'], '5'); }); test('is_charging stays a bool, because the contract says so', () { const t = DeviceTelemetry(isCharging: true); expect(t.toPayload()['is_charging'], isTrue); expect(t.toPayload()['is_charging'], isA()); }); }); group('unknown is absent, never a plausible number', () { test('a reading that failed emits no key at all', () { // The whole panel is read by somebody deciding whether to call a rider. // A `0` battery standing in for "could not ask" is a fact they would act // on; an absent key draws the em dash it already drew, which is true. const t = DeviceTelemetry(); expect(t.toPayload(), isEmpty); }); test('a partial reading sends the half it has', () { // The reads are guarded individually, so a platform that will not answer // about the battery must not cost the connection its column too. const t = DeviceTelemetry(connection: 'wifi'); expect(t.toPayload(), {'connection': 'wifi'}); }); test('an empty string is treated as no answer', () { const t = DeviceTelemetry(connection: '', locationService: ''); expect(t.toPayload(), isEmpty); }); test('a zero battery is a real reading and is sent', () { // The one number that must survive the "absent, never plausible" rule: // 0% is not a missing measurement, it is a rider about to disappear. const t = DeviceTelemetry(battery: 0); expect(t.toPayload()['battery'], '0'); }); test('not charging is a real reading and is sent', () { const t = DeviceTelemetry(isCharging: false); expect(t.toPayload().containsKey('is_charging'), isTrue); }); }); }