Files
doormile_milderapp/test/arrived_rung_test.dart
2026-08-28 11:13:15 +05:30

221 lines
8.1 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:miler/Models/stop_status.dart';
import 'package:miler/data/accepted_store.dart';
import 'package:miler/data/api_config.dart';
import 'package:miler/data/consignment_state.dart';
import 'package:miler/data/milk_run.dart';
import 'package:miler/data/service_profile.dart';
/// ─────────────────────────────────────────────────────────────────────────
/// TWO RUNGS THAT WERE NOT THERE
///
/// Reported from a milk run, and they are the same failure at two ends of the
/// ladder: a rung the rider walked was not a rung the app kept.
///
/// 1. **Arrived did nothing.** He marked arrived at the kitchen, the row went
/// back to ACCEPTED, and the sheet offered him arrival again. The write
/// was fine. `POST /miler/bookings/:id/reached` answers 200 and leaves the
/// booking on `Miler_Assigned` (request 15), so the rung lived only as a
/// field on an in-memory row — and `_advanceStopWithSheet` finishes with
/// `_fetchQueues()`, which rebuilt that row from the server about a second
/// later. Nothing kept his report, so nothing showed it.
///
/// 2. **Picked jumped to active.** He slid to collect and the card went
/// straight to ACTIVE without ever showing PICKED, with **Start delivery**
/// never offered. `pickup-complete` in compatibility mode releases the
/// consignment itself, so the stop reached the Deliveries tab already
/// `Out_for_Delivery` — and the release gate read that as "he has set
/// off".
///
/// The ladder the rider actually walks, and what answers each rung:
///
/// ```
/// ACCEPTED the queue, or the accepted store
/// ARRIVED HIS report → the arrived store (nothing else has it)
/// PICKED pickup-complete → the collected store
/// ACTIVE HIS press → the out-for-delivery store
/// DELIVERED deliver
/// ```
///
/// Two of the five are the rider's own and cannot be read back from anywhere.
/// That is why they are stored, and it is the whole content of both bugs.
/// ─────────────────────────────────────────────────────────────────────────
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
setUp(() {
SharedPreferences.setMockInitialValues({});
ServiceProfile.setActive(ServiceProfile.milkMan);
});
tearDown(() => ServiceProfile.setActive(ServiceProfile.parcel));
/// A stop as the queue hands it over.
Map<String, dynamic> stop(String backendStatus, {String id = '1001'}) => {
'orderid': id,
'pickupid': int.parse(id),
'sourcename': 'Vidhya Kitchen',
'orderstatus': ApiConfig.legacyStatusFromNew(backendStatus),
'pickuplat': 11.01,
'pickuplon': 76.95,
'droplat': 11.05,
'droplon': 76.99,
};
group('1 · arrival is kept, because nothing else keeps it', () {
test('the server does not persist it — this is the premise', () {
// Not an assertion about our code: it is the reason the store exists.
// `reached` leaves the booking where it was, so a poll cannot return the
// rung and the app cannot read it back.
expect(
ApiConfig.legacyStatusFromNew('Miler_Assigned'),
isNot('arrived'),
reason:
'if this ever becomes "arrived" the backend has shipped request '
'15 and the arrived store should be deleted, not kept',
);
});
test('an arrival survives being written and read back', () async {
await addArrivedOrderIds(['1001']);
expect(await getArrivedOrderIds(), {'1001'});
});
test(
'the rung it produces is ARRIVED, over a queue that says accepted',
() async {
await addArrivedOrderIds(['1001']);
final arrived = await getArrivedOrderIds();
// What `_fetchQueues` does with the set: the row comes back on
// Miler_Assigned and the rider's own record puts it back on the rung.
final row = stop('Miler_Assigned');
expect(
stopStatusFromRaw(row['orderstatus']),
isNot(StopStatus.arrived),
reason: 'the server really does hand it back one rung down',
);
if (arrived.contains(row['orderid'])) row['orderstatus'] = 'arrived';
expect(
MilkRun.stageOf(
row,
acceptedIds: {'1001'},
collectedIds: const {},
outForDeliveryIds: const {},
),
StopStatus.arrived,
);
},
);
test('and it is dropped the moment the stop is collected', () async {
await addArrivedOrderIds(['1001']);
await removeArrivedOrderIds(['1001']);
expect(
await getArrivedOrderIds(),
isEmpty,
reason:
'a local record that outlives its rung pins tomorrow\'s row to '
'yesterday\'s arrival',
);
});
test('one kitchen\'s arrival does not move another\'s stop', () async {
await addArrivedOrderIds(['1001']);
expect(await getArrivedOrderIds(), isNot(contains('2002')));
});
});
group('2 · picked is not active until he says so', () {
test('compatibility mode really does release at the counter', () {
// The premise of the second bug, stated as the contract does: a
// hyperlocal booking is `Out_for_Delivery` the moment it is collected.
expect(consignmentStateFromRaw('Out_for_Delivery').isDeliverable, isTrue);
expect(
consignmentStateFromRaw('Out_for_Delivery').needsRelease,
isFalse,
reason:
'which is exactly why reading needsRelease first skipped the rung '
'— the consignment does not need releasing, the RIDER has not '
'released it',
);
});
test('the card holds at PICKED while the round is unstarted', () {
expect(
MilkRun.stageOf(
stop('Out_for_Delivery'),
acceptedIds: {'1001'},
collectedIds: {'1001'},
outForDeliveryIds: const {},
),
StopStatus.picked,
);
});
test('and moves to ACTIVE only on his own press', () {
expect(
MilkRun.stageOf(
stop('Out_for_Delivery'),
acceptedIds: {'1001'},
collectedIds: {'1001'},
outForDeliveryIds: {'1001'},
),
StopStatus.outForDelivery,
);
});
test('a delivered stop is never dragged back down the ladder', () {
// The one direction the local sets must not win in: the server knows
// something the rider's phone does not.
expect(
MilkRun.stageOf(
stop('Delivered'),
acceptedIds: {'1001'},
collectedIds: {'1001'},
outForDeliveryIds: const {},
),
StopStatus.delivered,
);
});
});
group('3 · the ladder in order, walked once', () {
test('accepted → arrived → picked → active', () async {
final row = stop('Miler_Assigned');
final collected = <String>{};
final released = <String>{};
StopStatus rung() => MilkRun.stageOf(
row,
acceptedIds: {'1001'},
collectedIds: collected,
outForDeliveryIds: released,
);
expect(rung(), StopStatus.accepted);
// He arrives.
await addArrivedOrderIds(['1001']);
row['orderstatus'] = 'arrived';
expect(rung(), StopStatus.arrived);
// He collects. pickup-complete converts and — in compatibility mode —
// releases; the rider's rung is still PICKED.
await removeArrivedOrderIds(['1001']);
row['orderstatus'] = 'picked';
row['consignmentstatus'] = 'Out_for_Delivery';
collected.add('1001');
expect(rung(), StopStatus.picked);
expect(await getArrivedOrderIds(), isEmpty);
// He slides to start the ride.
released.add('1001');
expect(rung(), StopStatus.outForDelivery);
});
});
}