import 'package:flutter_test/flutter_test.dart'; import 'package:halkyon_app/halkyon_app.dart'; import 'package:halkyon_app/testing.dart'; void main() { late FakeAuthGateway gateway; late SessionBloc bloc; /// An anonymous account's session, left from before they went (ADR 0020, /// D25). const anonymous = SignedIn(authUserId: 'anonymous-1', isIdentified: false); void start({AuthStatus status = const SignedOut()}) { gateway = FakeAuthGateway(status: status); bloc = SessionBloc(AuthRepository(gateway, MemorySignInAddresses())); } tearDown(() => bloc.close()); test('nobody signed in, it asks how to start, making no account (ADR ' '0020, D4)', () async { start(); bloc.add(const SessionEventStarted()); await expectLater(bloc.stream, emits(const SessionStateSignedOut())); expect(gateway.accountDeletions, 0); expect(gateway.signOuts, 0); }); test('keeps a stored session', () async { const stored = SignedIn(authUserId: 'stored', isIdentified: true); start(status: stored); bloc.add(const SessionEventStarted()); await expectLater(bloc.stream, emits(const SessionStateActive(stored))); }); test( "an anonymous account's session left from before ends as the app " 'starts: the account deleted, the device asked how to start (D25)', () async { start(status: anonymous); bloc.add(const SessionEventStarted()); await expectLater(bloc.stream, emits(const SessionStateSignedOut())); expect(gateway.accountDeletions, 1); expect(gateway.status, const SignedOut()); }, ); test('the instance out of reach, the anonymous session still ends on the ' "device: the account is left to the instance's pruning", () async { start(status: anonymous); gateway.deletionError = Exception('No connection'); bloc.add(const SessionEventStarted()); await expectLater(bloc.stream, emits(const SessionStateSignedOut())); expect(gateway.signOuts, 1); expect(gateway.status, const SignedOut()); }); test("an anonymous session the device can't even end never opens its " 'app: the start screen all the same', () async { start(status: anonymous); gateway ..deletionError = Exception('No connection') ..signOutError = Exception('The keyring is locked'); bloc.add(const SessionEventStarted()); await expectLater(bloc.stream, emits(const SessionStateSignedOut())); expect(gateway.status, anonymous); }); test('after signing out, asks how to start, deleting nothing', () async { const stored = SignedIn(authUserId: 'stored', isIdentified: true); start(status: stored); bloc.add(const SessionEventStarted()); await expectLater(bloc.stream, emits(const SessionStateActive(stored))); bloc.add(const SessionEventSignOutRequested()); // The person's app stays while signing out, never the launch screen in // between. await expectLater( bloc.stream, emitsInOrder([ const SessionStateActive(stored, busy: SessionAction.signOut), const SessionStateSignedOut(), ]), ); expect(gateway.accountDeletions, 0); }); test('a session the server ended asks how to start too', () async { const stored = SignedIn(authUserId: 'person-1', isIdentified: true); start(status: stored); bloc.add(const SessionEventStarted()); await expectLater(bloc.stream, emits(const SessionStateActive(stored))); gateway.revokeSession(); await expectLater(bloc.stream, emits(const SessionStateSignedOut())); }); test('deleting the account asks how to start', () async { const stored = SignedIn(authUserId: 'person-1', isIdentified: true); start(status: stored); bloc.add(const SessionEventStarted()); await expectLater(bloc.stream, emits(const SessionStateActive(stored))); bloc.add(const SessionEventDeleteRequested()); await expectLater( bloc.stream, emitsInOrder([ const SessionStateActive(stored, busy: SessionAction.delete), const SessionStateSignedOut(), ]), ); expect(gateway.accountDeletions, 1); }); test('a deletion that fails keeps the person signed in, their app in ' 'place, and says so; asked again, it tries again', () async { const stored = SignedIn(authUserId: 'person-1', isIdentified: true); start(status: stored); bloc.add(const SessionEventStarted()); await expectLater(bloc.stream, emits(const SessionStateActive(stored))); gateway.deletionError = Exception('No connection'); bloc.add(const SessionEventDeleteRequested()); await expectLater( bloc.stream, emitsInOrder([ const SessionStateActive(stored, busy: SessionAction.delete), const SessionStateActive(stored, problem: SessionProblem.deleteFailed), ]), ); expect(gateway.status, stored); gateway.deletionError = null; bloc.add(const SessionEventDeleteRequested()); await expectLater( bloc.stream, emitsInOrder([ const SessionStateActive(stored, busy: SessionAction.delete), const SessionStateSignedOut(), ]), ); expect(gateway.accountDeletions, 2); }); test('a sign-out that fails keeps the person signed in, their app in ' 'place, and says so', () async { const stored = SignedIn(authUserId: 'person-1', isIdentified: true); start(status: stored); bloc.add(const SessionEventStarted()); await expectLater(bloc.stream, emits(const SessionStateActive(stored))); gateway.signOutError = Exception('The keyring is locked'); bloc.add(const SessionEventSignOutRequested()); await expectLater( bloc.stream, emitsInOrder([ const SessionStateActive(stored, busy: SessionAction.signOut), const SessionStateActive(stored, problem: SessionProblem.signOutFailed), ]), ); expect(gateway.status, stored); }); group("an account waiting for the instance's operator (Halkyon Learn's " 'ADR 0021, D31)', () { final wait = fakeWaitOf('anna', handle: 'anna'); test('a wait kept from before shows as the app starts', () async { start(status: Waiting(wait)); bloc.add(const SessionEventStarted()); await expectLater(bloc.stream, emits(SessionStateWaiting(wait))); }); test('follows the wait as it changes, and as it ends', () async { start(); bloc.add(const SessionEventStarted()); await expectLater(bloc.stream, emits(const SessionStateSignedOut())); final states = []; final listening = bloc.stream.listen(states.add); await gateway.wait(wait); await pumpEventQueue(); gateway.waitAnswer = AccountWaitState.approved; await gateway.lookWhileWaiting(); await pumpEventQueue(); await gateway.stopWaiting(); await pumpEventQueue(); await listening.cancel(); expect(states, [ SessionStateWaiting(wait), SessionStateWaiting(wait.standing(AccountWaitState.approved)), const SessionStateSignedOut(), ]); }); }); }