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

251 lines
9.7 KiB
Dart

import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:miler/data/miler_api.dart';
import 'package:miler/data/mock_backend.dart';
import 'package:miler/providers/pickup/pickup_provider.dart';
import 'package:miler/data/meal_run_mock.dart';
import 'package:miler/data/service_profile.dart';
import 'package:miler/views/Dashboard/home/trip.dart';
import 'package:miler/views/Dashboard/pickups/stop_type.dart';
/// ─────────────────────────────────────────────────────────────────────────
/// THE MEAL DAY THAT HAS NO BACKEND
///
/// The meal line runs on a day held in memory, because the backend has no
/// kitchens, crates or subscribers to serve. That is a deliberate stand-in, and
/// it carries the risk the app has already been burned by once: a mock layer was
/// deleted from this repo after demo stops kept surfacing on live riders' tabs
/// weeks later.
///
/// So these tests are mostly about containment — the day is complete enough to
/// walk, and there is no path from a parcel login to any of it.
/// ─────────────────────────────────────────────────────────────────────────
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
setUp(() {
MealRunMock.reset();
// The canned day is opt-in now, so the tests that exercise it have to ask
// for it — exactly as a bench session does. The containment group turns it
// back off for the assertions that are about the default.
MockBackend.enabled = true;
});
tearDown(() {
MealRunMock.reset();
MockBackend.enabled = kMockBackend;
ServiceProfile.setActive(ServiceProfile.parcel);
});
group('containment', () {
test('a parcel rider cannot reach it, whatever else is true', () {
ServiceProfile.setActive(ServiceProfile.parcel);
expect(MealRunMock.active, isFalse);
});
test('it is off unless somebody asks for it', () {
MockBackend.enabled = kMockBackend;
// ── What this assertion is for ──
//
// This switch was off, and it was off for a real reason: while it was on,
// a meal rider tapped Arrived and Picked up, watched the card advance,
// and the hub console never changed — every status write was recorded on
// the phone and reported as sent.
//
// It survives as a bench tool, but it is **opt in** now. It used to
// default to on in debug and profile, which meant the default development
// experience diverged from production silently: a rider signing in with
// his real number became the fixture's rider, and a whole class of
// contract bugs could not be seen because nothing left the device.
//
// Now it is off unless somebody types the flag, in every build type.
expect(
kMockBackend,
isFalse,
reason: 'the canned day must never be what you get by not deciding',
);
expect(MockBackend.enabled, isFalse);
// And the two are one switch, so there is no state where the day is
// fictional but the statuses are real, or the other way round.
expect(kMealMockEnabled, MockBackend.enabled);
});
test('the meal line is still the only thing that could turn it on', () {
// The containment rule survives the switch: `active` is `enabled && meal
// line`, so re-enabling it for a demo still cannot reach a parcel rider.
// Asserted on the guard itself, since `active` is false either way now.
ServiceProfile.setActive(ServiceProfile.parcel);
expect(ServiceProfile.active.sourceIsKitchen, isFalse);
ServiceProfile.setActive(ServiceProfile.meals);
expect(ServiceProfile.active.sourceIsKitchen, isTrue);
});
test('every id announces itself as mock', () {
// `purgeDemoRecords()` matches on exactly this prefix, so anything that
// leaks into a rider's stores is removed at the next launch by code that
// already ships. See the note in `accepted_store.dart`.
ServiceProfile.setActive(ServiceProfile.meals);
for (final s in MealRunMock.stops()) {
expect(
(s['orderid'] as String).toLowerCase(),
startsWith('mock-'),
reason: 'a row that does not say it is mock cannot be purged',
);
}
});
});
group('the day itself', () {
setUp(() => ServiceProfile.setActive(ServiceProfile.meals));
test('is two kitchens, so the grouping has something to group', () {
final kitchens = MealRunMock.stops()
.map((s) => stopSourceName(s))
.toSet();
// One kitchen hides every question the sections exist to answer.
expect(kitchens.length, 2);
});
test('every stop is a drop, with a bag the rider can read off a box', () {
for (final s in MealRunMock.stops()) {
expect(stopKindOf(s).isDelivery, isTrue);
expect(stopPrintedLabel(s), isNotEmpty);
expect((s['pickupcustomer'] as String), isNotEmpty);
expect((s['pickupaddress'] as String), contains('Coimbatore'));
}
});
test('nothing is owed at any door', () {
// A subscriber paid the client by the month. A rupee figure here would be
// a question the rider cannot answer.
for (final s in MealRunMock.stops()) {
expect(stopCollectionAmount(s), 0);
}
});
test('it starts undecided — the rider has done nothing yet', () {
for (final s in MealRunMock.stops()) {
expect(s['orderstatus'], 'assigned');
}
});
test('it groups into one trip in route order', () {
final trips = Trip.groupIntoTrips(MealRunMock.stops());
expect(trips, hasLength(1));
expect(trips.single.stops.map((s) => s['step']), [
1,
2,
3,
4,
5,
6,
7,
8,
9,
]);
});
});
group('walking it', () {
setUp(() => ServiceProfile.setActive(ServiceProfile.meals));
test('a status change sticks, and only touches its own stop', () {
MealRunMock.setStatus('MOCK-M-1001', 'accepted');
final byId = {
for (final s in MealRunMock.stops()) s['orderid']: s['orderstatus'],
};
expect(byId['MOCK-M-1001'], 'accepted');
expect(byId['MOCK-M-1002'], 'assigned');
});
test('the controllers key on the booking id they actually carry', () {
// Every status method has an int `pickupId` and often no order id at all,
// so the mock has to resolve one from the other or half the ladder writes
// nothing.
expect(MealRunMock.setStatusByPickupId(1002, 'arrived'), isTrue);
final row = MealRunMock.stops().firstWhere(
(s) => s['orderid'] == 'MOCK-M-1002',
);
expect(row['orderstatus'], 'arrived');
});
test('the whole ladder runs: assigned → accepted → arrived → picked', () {
const id = 'MOCK-M-1003';
String statusOf() =>
MealRunMock.stops().firstWhere(
(s) => s['orderid'] == id,
)['orderstatus']
as String;
expect(statusOf(), 'assigned');
for (final rung in ['accepted', 'arrived', 'picked']) {
MealRunMock.setStatus(id, rung);
expect(statusOf(), rung);
}
});
test('reset puts the day back, so a demo can be run twice', () {
MealRunMock.setStatus('MOCK-M-1001', 'picked');
MealRunMock.reset();
expect(
MealRunMock.stops().every((s) => s['orderstatus'] == 'assigned'),
isTrue,
);
});
test('handing out rows never hands out the master copy', () {
// The screens stamp compliance and proof blocks onto stop maps in place.
// Sharing the originals would carry one run's leftovers into the next.
MealRunMock.stops().first['pickupcustomer'] = 'SCRIBBLED';
expect(MealRunMock.stops().first['pickupcustomer'], isNot('SCRIBBLED'));
});
});
// ── The wiring, not just the fixture ──
//
// The day above is only useful if the app actually receives it. These walk the
// real path — provider, transport, auth — with no network available, which is
// exactly the situation a developer running `flutter run` on a bench is in.
group('the app runs on it', () {
test(
'the queue provider serves the day without touching the network',
() async {
ServiceProfile.setActive(ServiceProfile.meals);
final stops = await PickupProvider().getPickupQueues();
expect(stops, hasLength(9));
expect(
stops.every((s) => (s['orderid'] as String).startsWith('MOCK-')),
isTrue,
reason:
'every row is self-identifying, so a leak is purged at launch',
);
},
);
test('a parcel rider still gets the real call, and no fixture', () async {
ServiceProfile.setActive(ServiceProfile.parcel);
// The transport answers rather than the fixture: an empty booking list,
// not somebody else's kitchens. Inventing parcel work is the one thing
// this mock must not do.
final stops = await PickupProvider().getPickupQueues();
expect(stops, isEmpty);
});
test('every write is answered without a server', () async {
final res = await MilerApi.pickupComplete(1001);
expect(res.ok, isTrue);
expect(res.status, 200);
});
test('signing in works with no directory to ask', () async {
expect(MockBackend.enabled, isTrue);
// The profile the mock hands back is what puts the build on the meal
// line, so the login and the day agree about which app the rider is in.
expect(MockBackend.rider['tenantname'], 'DailyGrubs');
});
});
}