import 'dart:convert'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; import 'package:http/testing.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:srs_app/core/api.dart'; import 'package:srs_app/core/http_api.dart'; const _user = { 'id': 7, 'username': 'kzk.staff', 'phone': '919847012345', 'isAdmin': false, 'active': true, 'shopIds': [1, 2], }; const _shop = {'id': 1, 'code': 'KZK', 'name': 'Kozhikode', 'active': true}; const _order = { 'id': 11, 'shop': _shop, 'drawDate': '2026-10-16', 'createdAt': '2026-10-16T05:30:00Z', 'edited': true, 'isPhoto': false, 'rawText': '1234-2', 'lines': [ { 'ending': '1234', 'qty': 2, 'hits': [ {'tier': 5, 'amount': 1000}, {'tier': 8, 'amount': 100}, ], }, {'ending': '5678', 'qty': 1, 'hits': []}, ], }; http.Response _json(Object body, [int status = 200]) => http.Response( jsonEncode(body), status, headers: {'content-type': 'application/json'}, ); Future<(HttpApi, List, SharedPreferences)> _api( http.Response Function(http.Request) reply, { Map prefs = const {}, }) async { SharedPreferences.setMockInitialValues(prefs); final store = await SharedPreferences.getInstance(); final seen = []; final api = HttpApi( baseUrl: 'https://srs.test/', prefs: store, client: MockClient((r) async { seen.add(r); return reply(r); }), ); return (api, seen, store); } void main() { group('signing in', () { test('keeps the token and sends it on later calls', () async { final (api, seen, store) = await _api( (r) => r.url.path == '/api/auth/login' ? _json({'token': 'abc', 'user': _user}) : _json({'date': '2026-10-16'}), ); final u = await api.login(' kzk.staff ', 'secret-pass'); expect(u.username, 'kzk.staff'); expect(u.shopIds, [1, 2]); expect(jsonDecode(seen.single.body), { 'username': 'kzk.staff', 'password': 'secret-pass', }); expect(seen.single.headers['authorization'], isNull); expect(store.getString('token'), 'abc'); expect(await api.latestDrawDate(), DateTime(2026, 10, 16)); expect(seen.last.url.toString(), 'https://srs.test/api/draws/latest'); expect(seen.last.headers['authorization'], 'Bearer abc'); }); test('shows the server message for a wrong password', () async { final (api, _, store) = await _api( (_) => _json({ 'error': 'invalid_credentials', 'message': 'Incorrect username or password.', }, 401), ); await expectLater( api.login('a', 'b'), throwsA( isA().having( (e) => e.message, 'message', 'Incorrect username or password.', ), ), ); expect(store.getString('token'), isNull); }); test('restores the user from a saved token, or none without one', () async { final (none, calls, _) = await _api((_) => _json(_user)); expect(await none.restore(), isNull); expect(calls, isEmpty); final (api, _, _) = await _api( (_) => _json(_user), prefs: {'token': 'abc'}, ); expect((await api.restore())!.id, 7); }); test('forgets an old token the server no longer accepts', () async { final (api, _, store) = await _api( (_) => _json({'error': 'unauthorized', 'message': 'Expired.'}, 401), prefs: {'token': 'old'}, ); expect(await api.restore(), isNull); expect(store.getString('token'), isNull); }); test('logout drops the token', () async { final (api, _, store) = await _api( (_) => _json(_user), prefs: {'token': 'abc'}, ); api.logout(); await Future.delayed(Duration.zero); expect(store.getString('token'), isNull); }); }); group('a session that ends', () { test('clears the token and tells the app', () async { final (api, _, store) = await _api( (_) => _json({'error': 'unauthorized', 'message': 'Ended.'}, 401), prefs: {'token': 'abc'}, ); final ended = expectLater(api.sessionEnded, emits(anything)); await expectLater( api.orders(DateTime(2026, 10, 16)), throwsA(isA()), ); await ended; expect(store.getString('token'), isNull); }); }); group('failures', () { test('an unreachable server gives a plain message', () async { SharedPreferences.setMockInitialValues({}); final api = HttpApi( baseUrl: 'https://srs.test', prefs: await SharedPreferences.getInstance(), client: MockClient((_) async => throw http.ClientException('down')), ); await expectLater( api.activeShops(), throwsA( isA().having( (e) => e.message, 'message', startsWith('Unable to reach the server'), ), ), ); }); test('a server error without a message is not shown raw', () async { final (api, _, _) = await _api((_) => http.Response('', 502)); await expectLater( api.activeShops(), throwsA( isA().having( (e) => e.message, 'message', 'Something went wrong on the server. Try again shortly.', ), ), ); }); }); group('reading', () { test( 'turns an order into models in Indian time with every prize', () async { final (api, seen, _) = await _api((_) => _json(_order)); final o = await api.order(11); expect(seen.single.url.path, '/api/orders/11'); expect(o.time, DateTime(2026, 10, 16, 11, 0)); expect(o.drawDate, DateTime(2026, 10, 16)); expect(o.edited, isTrue); expect(o.lines.first.tierText, '5th + 8th prize'); expect(o.lines.first.perTicket, 1100); expect(o.prize, 2200); expect(o.lines.last.wins, isFalse); }, ); test('reads a day of results, an awaited draw included', () async { final (api, seen, _) = await _api( (_) => _json({ 'draw': { 'date': '2026-10-17', 'scheme': null, 'code': null, 'published': false, 'hasWin': false, }, 'shops': [ { 'shop': _shop, 'orders': 2, 'tickets': 9, 'winTickets': 0, 'prize': 0, }, ], }), ); final r = await api.results(DateTime(2026, 10, 17)); expect(seen.single.url.path, '/api/draws/2026-10-17/results'); expect(r.draw.published, isFalse); expect(r.draw.label, 'No result yet'); expect(r.shops.single.tickets, 9); }); test('asks for a month and a shop day with the right addresses', () async { final (api, seen, _) = await _api( (r) => r.url.path == '/api/draws' ? _json({'days': []}) : _json({ 'shop': _shop, 'draw': { 'date': '2026-10-16', 'scheme': 'Suvarna Keralam', 'code': 'SK-74', 'published': true, 'hasWin': true, }, 'endings': [], 'orders': [_order], }), ); await api.month(2026, 3); expect( seen.last.url.toString(), 'https://srs.test/api/draws?month=2026-03', ); final d = await api.shopDetail(1, DateTime(2026, 10, 16)); expect(seen.last.url.path, '/api/shops/1/draws/2026-10-16'); expect(d.draw.label, 'Suvarna Keralam ยท SK-74'); expect(d.orders.single.id, 11); }); }); group('changing things', () { test('sends each edit to the right place', () async { final (api, seen, _) = await _api( (r) => r.method == 'POST' ? _json(_shop, 201) : http.Response('', 204), ); await api.setLineQty(11, '1234', 5); await api.removeLine(11, '1234'); await api.cancelOrder(11); await api.setShopActive(1, false); await api.renameShop(1, 'New name'); await api.setUserShops(7, [1, 2]); await api.deleteUser(7); final shop = await api.createShop('Kozhikode', 'KZK'); final calls = [for (final r in seen) '${r.method} ${r.url.path}']; expect(calls, [ 'PUT /api/orders/11/lines/1234', 'DELETE /api/orders/11/lines/1234', 'DELETE /api/orders/11', 'PATCH /api/shops/1', 'PATCH /api/shops/1', 'PUT /api/users/7/shops', 'DELETE /api/users/7', 'POST /api/shops', ]); expect(jsonDecode(seen[0].body), {'qty': 5}); expect(jsonDecode(seen[3].body), {'active': false}); expect(jsonDecode(seen[5].body), { 'shopIds': [1, 2], }); expect(shop.code, 'KZK'); }); test('sends only the user fields that changed', () async { final (api, seen, _) = await _api((_) => http.Response('', 204)); await api.updateUser(7, username: 'new.name'); await api.updateUser(7, clearPhone: true); await api.updateUser(7, active: false, password: 'longenough'); await api.updateUser(7, sendResults: true); expect(jsonDecode(seen[0].body), {'username': 'new.name'}); expect(jsonDecode(seen[1].body), {'phone': null}); expect(jsonDecode(seen[2].body), { 'password': 'longenough', 'active': false, }); expect(jsonDecode(seen[3].body), {'sendResults': true}); }); test( 'reads the results setting, off when the server leaves it out', () async { final (api, _, _) = await _api( (r) => r.url.path == '/api/users/7' ? _json({..._user, 'sendResults': true}) : _json(_user), ); expect((await api.user(7)).sendResults, isTrue); expect((await api.user(8)).sendResults, isFalse); }, ); test('creates a user with a password and shops', () async { final (api, seen, _) = await _api((_) => _json(_user, 201)); final u = await api.createUser( username: 'kzk.staff', password: 'longenough', shopIds: [1, 2], ); expect(jsonDecode(seen.single.body), { 'username': 'kzk.staff', 'phone': null, 'password': 'longenough', 'shopIds': [1, 2], }); expect(u.id, 7); }); }); }