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 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 = {}; final released = {}; 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); }); }); }