221 lines
8.1 KiB
Dart
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);
|
|
});
|
|
});
|
|
}
|