import 'package:flutter_test/flutter_test.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:miler/data/miler_api.dart'; import 'package:miler/data/service_profile.dart'; import 'package:miler/views/Dashboard/pickups/stop_type.dart'; /// One app, two lines of work. The tests that matter here are about the /// direction the resolver fails in: an unconfigured or unrecognised tenant must /// land on the parcel line, because that is the larger business and the failure /// it cannot afford is a rider losing the screen that collects a customer's /// cash. Showing a meal rider a payment prompt he can dismiss is the cheap /// mistake. void main() { TestWidgetsFlutterBinding.ensureInitialized(); /// Resolves through the free function, not the GetX controller — see the /// note on [ServiceProfile.active] for why data code must not need a /// container. [ServiceProfile.setActive] is what `TenantController.load` /// would have done. Future resolveWith(Map prefs) async { SharedPreferences.setMockInitialValues(prefs); final profile = await resolveServiceProfile(); ServiceProfile.setActive(profile); return profile; } tearDown(() => ServiceProfile.setActive(ServiceProfile.parcel)); group('tenant resolution', () { test( 'an unknown tenant falls back to parcel, not to the meal flow', () async { final p = await resolveWith({'tenantid': 4242}); expect(p.line, ServiceLine.parcel); expect(p.collectsCash, isTrue); expect(p.needsVerification, isTrue); }, ); test('a login with no tenant at all still gets a working app', () async { final p = await resolveWith({}); expect(p.line, ServiceLine.parcel); }); test('the tenant name selects meals, case and spacing aside', () async { final p = await resolveWith({ 'tenantid': 0, 'tenantname': ' DailyGrubs ', }); expect(p.line, ServiceLine.milkMan); }); test( 'a meal rider is not asked for money, proof or a verification form', () async { final p = await resolveWith({'tenantname': 'meals'}); expect(p.collectsCash, isFalse); expect(p.needsVerification, isFalse); expect(p.needsProofPhoto, isFalse); // He cannot decline one subscriber's lunch. expect(p.acceptsPerStop, isFalse); expect(p.sourceIsKitchen, isTrue); }, ); test( 'an unrecognised name falls through to the id rather than deciding', () async { // A client the app has never been told about must not guess its way onto // the meal flow. final p = await resolveWith({ 'tenantname': 'someclient', 'tenantid': 1, }); expect(p.line, ServiceLine.parcel); }, ); }); group('the money gate', () { final stop = {'collectionamt': 450}; test( 'a parcel route still collects what the stop says it collects', () async { await resolveWith({'tenantid': 1}); expect(stopCollectionAmount(stop), 450); }, ); test('a meal tenant collects nothing, whatever the payload says', () async { await resolveWith({'tenantname': 'meals'}); // The one gate every "is anything owed?" branch in the app reads. expect(stopCollectionAmount(stop), 0); expect(stopCollectionAmount({'codamount': '₹1,200'}), 0); }); test('the alternate spellings still work on a parcel route', () async { await resolveWith({'tenantid': 1}); expect(stopCollectionAmount({'pickupamt': '₹1,200'}), 1200); expect(stopCollectionAmount({'codamount': 75.5}), 75.5); }); }); group('the tenant id sent at login', () { test('an ordinary build declares none, and the field is omitted', () { // `--dart-define=TENANT_ID` is unset here, as it is in every default // build. `hasTenantId` is what gates the field onto the request body, so // a plain APK asks the server to decide rather than asserting a tenant. expect(MilerApi.tenantId, 0); expect(MilerApi.hasTenantId, isFalse); }); test('zero is never a tenant', () { // Sending `tenantid: 0` would look like a real lookup key to the server. // The only way to say "you decide" is to leave the field out, so the // guard is > 0 rather than a null check. expect(MilerApi.hasTenantId, MilerApi.tenantId > 0); }); }); }