101 lines
4.4 KiB
Dart
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);
|
|
});
|
|
});
|
|
}
|