Files
doormile_milderapp/test/device_telemetry_test.dart
2026-08-28 15:07:30 +05:30

101 lines
4.4 KiB
Dart

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<String>());
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<bool>());
});
});
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);
});
});
}