srs/test/core_test.dart
Aswin Biju 45fcdcebfb Initial commit
Add SRS app: results, shops, orders and users, wired to srs-api
Flutter web/PWA for the lottery agency, built on Riverpod and go_router,
talking to srs-api over HTTP (HttpApi, token kept in SharedPreferences).

- Results, Shops, Orders and Users tabs (Users for admins only), with
  phone, tablet and desktop layouts and shared widgets and theme tokens
- Users: list with search and Active/Inactive filters; user detail with
  shops, sign-in details, and an Access card
- WhatsApp results switch on each user (sendResults on
  PATCH /api/users/:id). Shows the WhatsApp logo (Simple Icons, CC0,
  drawn with flutter_svg), green only when results will really go out.
  Can't be turned on without a phone number ("Add a phone number first"
  opens the phone sheet), and is paused while the user is inactive. The
  users list marks people who get results with the logo.
- Each Access switch has a one-line description; the explanation
  paragraphs are gone
- Remove demo mode: the app always uses the real AP
  test/support and is used only by tests.
2026-11-04 23:21:50 +05:30

211 lines
6.9 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:srs_app/core/api.dart';
import 'support/fake_api.dart';
import 'package:srs_app/core/format.dart';
void main() {
group('format', () {
test('Indian digit grouping', () {
expect(inr(0), '0');
expect(inr(999), '999');
expect(inr(1000), '1,000');
expect(inr(7700), '7,700');
expect(inr(519900), '5,19,900');
expect(inr(12345678), '1,23,45,678');
expect(rupees(3700), '₹3,700');
});
test('dates and times', () {
expect(dateLong(DateTime(2026, 10, 16)), 'Fri, 16 Oct 2026');
expect(dateShort(DateTime(2026, 10, 16)), 'Fri, 16 Oct');
expect(time12(DateTime(2026, 10, 16, 12, 41)), '12:41 PM');
expect(time12(DateTime(2026, 10, 16, 0, 5)), '12:05 AM');
expect(fmtPhone('919000012345'), '+91 90000 12345');
});
test('server instants are shown on the Indian clock', () {
expect(
istWallClock(DateTime.utc(2026, 10, 16, 5, 30)),
DateTime(2026, 10, 16, 11, 0),
);
expect(
istWallClock(DateTime.utc(2026, 10, 16, 20, 0)),
DateTime(2026, 10, 17, 1, 30),
);
expect(parseDate('2026-10-16'), DateTime(2026, 10, 16));
});
test('prize tiers read naturally', () {
expect(tiersLabel([5]), '5th prize');
expect(tiersLabel([8, 5]), '5th + 8th prize');
expect(tiersLabel([]), '');
});
});
group('FakeApi fixture (SK-74, Fri 16 Oct 2026)', () {
final d = DateTime(2026, 10, 16);
test('admin sees the design numbers', () async {
final api = FakeApi();
await api.login('owner', 'admin123');
final r = await api.results(d);
expect(r.draw.published, isTrue);
expect(r.draw.label, 'Suvarna Keralam · SK-74');
expect(r.totalPrize, 7700);
expect(r.winningTickets, 16);
expect(r.totalTickets, 500);
expect(r.orders, 6);
expect(r.winningShops, 4);
expect(r.topShop!.shop.code, 'KZK');
expect(r.topShop!.prize, 3700);
expect(r.topShop!.winTickets, 10);
});
test('KZK detail: 180 tickets, 52 endings, 2 orders', () async {
final api = FakeApi();
await api.login('owner', 'admin123');
final shops = await api.shops(d);
final kzk = shops.firstWhere((s) => s.shop.code == 'KZK');
expect(kzk.tickets, 180);
expect(kzk.endings, 52);
expect(kzk.orders, 2);
final det = await api.shopDetail(kzk.shop.id, d);
expect(det.prize, 3700);
expect(det.wins.map((w) => w.ending).toSet(), {
'0376',
'0987',
'1360',
'2564',
});
});
test('results for tomorrow are not out; counts only', () async {
final api = FakeApi();
await api.login('owner', 'admin123');
final r = await api.results(DateTime(2026, 10, 17));
expect(r.draw.published, isFalse);
expect(r.totalPrize, 0);
expect(r.totalTickets, 30);
});
});
group('roles', () {
final d = DateTime(2026, 10, 16);
test('user only sees assigned shops', () async {
final api = FakeApi();
await api.login('kzk.staff', 'staff123');
final r = await api.results(d);
expect(r.shops.map((s) => s.shop.code).toSet(), {'KZK', 'SKM'});
expect(r.totalPrize, 3700 + 2600);
final shops = await api.shops(d);
expect(shops.length, 2);
expect(() => api.shopDetail(3, d), throwsA(isA<ApiException>()));
});
test('user cannot manage shops or users', () async {
final api = FakeApi();
await api.login('kzk.staff', 'staff123');
expect(() => api.createShop('X', 'XXX'), throwsA(isA<ApiException>()));
expect(() => api.users(), throwsA(isA<ApiException>()));
});
test('wrong password and inactive users cannot sign in', () async {
final api = FakeApi();
expect(() => api.login('owner', 'nope'), throwsA(isA<ApiException>()));
expect(
() => api.login('ptm.staff', 'staff123'),
throwsA(isA<ApiException>()),
);
});
});
group('editing', () {
final d = DateTime(2026, 10, 16);
test(
'changing a quantity updates totals and marks the order edited',
() async {
final api = FakeApi();
await api.login('owner', 'admin123');
final orders = await api.orders(d);
final ptm = orders.firstWhere((o) => o.shop.code == 'PTM');
await api.setLineQty(ptm.id, '3821', 3);
final r = await api.results(d);
expect(r.totalPrize, 7700 + 400);
expect((await api.order(ptm.id)).edited, isTrue);
},
);
test(
'removing the last ending deletes the order; cancel removes tickets',
() async {
final api = FakeApi();
await api.login('owner', 'admin123');
final orders = await api.orders(DateTime(2026, 10, 17));
final one = orders.firstWhere((o) => o.shop.code == 'KZK');
for (final l in one.lines) {
await api.removeLine(one.id, l.ending);
}
expect(() => api.order(one.id), throwsA(isA<ApiException>()));
final ulr = orders.firstWhere((o) => o.shop.code == 'ULR');
await api.cancelOrder(ulr.id);
expect((await api.orders(DateTime(2026, 10, 17))), isEmpty);
},
);
test('shop and user rules', () async {
final d = DateTime(2026, 10, 16);
final api = FakeApi();
await api.login('owner', 'admin123');
expect(
() => api.createShop('Another', 'KZK'),
throwsA(isA<ApiException>()),
);
final s = await api.createShop('Medical College', 'MCH');
await api.renameShop(s.id, 'Medical College Jn');
await api.setShopActive(s.id, false);
expect((await api.activeShops()).any((x) => x.id == s.id), isFalse);
expect(
(await api.shops(d)).any((x) => x.shop.id == s.id && !x.shop.active),
isTrue,
);
await api.setShopActive(s.id, true);
expect((await api.activeShops()).any((x) => x.id == s.id), isTrue);
expect(
() =>
api.createUser(username: 'ab', password: 'longenough', shopIds: []),
throwsA(isA<ApiException>()),
);
expect(
() =>
api.createUser(username: 'newuser', password: 'short', shopIds: []),
throwsA(isA<ApiException>()),
);
final u = await api.createUser(
username: 'newuser',
phone: '98765 43210',
password: 'longenough',
shopIds: [1],
);
expect(u.phone, '919876543210');
await api.updateUser(u.id, active: false);
await api.deleteUser(u.id);
expect((await api.users()).any((x) => x.id == u.id), isFalse);
expect(() => api.deleteUser(1), throwsA(isA<ApiException>()));
final u2 = await api.createUser(
username: 'second',
password: 'longenough',
shopIds: [],
);
await api.updateUser(u2.id, active: false);
api.logout();
expect(
() => api.login('second', 'longenough'),
throwsA(isA<ApiException>()),
);
});
});
}