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 MemorySignedOutMark mark; late SessionBloc bloc; SessionStateActive anonymous(int number) => SessionStateActive( SignedIn(authUserId: 'anonymous-$number', isIdentified: false), ); void start({AuthStatus status = const SignedOut(), bool signedOut = false}) { gateway = FakeAuthGateway(status: status); mark = MemorySignedOutMark(signedOut); bloc = SessionBloc(AuthRepository(gateway, MemorySignInAddresses()), mark); } tearDown(() => bloc.close()); test('signs in anonymously at a first start, nobody having signed out ' 'here', () async { start(); bloc.add(const SessionEventStarted()); await expectLater( bloc.stream, emitsInOrder([const SessionStateLoading(), anonymous(1)]), ); expect(gateway.signIns, 1); }); test('keeps a stored session instead of creating a second account', () async { const stored = SignedIn(authUserId: 'stored', isIdentified: false); start(status: stored); bloc.add(const SessionEventStarted()); await expectLater(bloc.stream, emits(const SessionStateActive(stored))); expect(gateway.signIns, 0); }); test('reports a failed sign-in and signs in when asked again', () async { start(); gateway.signInError = Exception('No connection'); bloc.add(const SessionEventStarted()); await expectLater( bloc.stream, emitsInOrder([const SessionStateLoading(), const SessionStateFailed()]), ); gateway.signInError = null; bloc.add(const SessionEventSignInRequested()); await expectLater( bloc.stream, emitsInOrder([const SessionStateLoading(), anonymous(2)]), ); }); test('after signing out, asks how to start, making no account; the next ' 'start of the app asks again', () async { const stored = SignedIn(authUserId: 'stored', isIdentified: false); start(status: stored); bloc.add(const SessionEventStarted()); await expectLater(bloc.stream, emits(const SessionStateActive(stored))); bloc.add(const SessionEventSignOutRequested()); await expectLater( bloc.stream, emitsInOrder([ const SessionStateLoading(), const SessionStateSignedOut(), ]), ); // Signing out of an anonymous account deletes it. expect(gateway.accountDeletions, 1); expect(gateway.signIns, 0); expect(mark.signedOut, isTrue); await bloc.close(); bloc = SessionBloc(AuthRepository(gateway, MemorySignInAddresses()), mark) ..add(const SessionEventStarted()); await expectLater(bloc.stream, emits(const SessionStateSignedOut())); expect(gateway.signIns, 0); }); test('starting without an account from there signs in anonymously, and ' 'the device is no longer signed out', () async { start(signedOut: true); bloc.add(const SessionEventStarted()); await expectLater(bloc.stream, emits(const SessionStateSignedOut())); bloc.add(const SessionEventStartedAnonymously()); await expectLater( bloc.stream, emitsInOrder([const SessionStateLoading(), anonymous(1)]), ); expect(mark.signedOut, isFalse); }); test('signing in with GitHub from there switches to its person; ' 'cancelled, it asks again', () async { start(signedOut: true); bloc.add(const SessionEventStarted()); await expectLater(bloc.stream, emits(const SessionStateSignedOut())); bloc.add(const SessionEventGitHubRequested()); // A password is offered first (ADR 0017, D2), then the person's app. const person = SignedIn(authUserId: 'github-person', isIdentified: true); await expectLater( bloc.stream, emitsThrough(const SessionStateGitHubSignedIn(person)), ); bloc.add(const SessionEventPasswordOfferDone()); await expectLater(bloc.stream, emits(const SessionStateActive(person))); expect(gateway.signIns, 0); expect(mark.signedOut, isFalse); }); test('a GitHub account nobody here signs in with yet is said so; any ' 'other refusal fails, and nothing is made either way', () async { start(signedOut: true); bloc.add(const SessionEventStarted()); await expectLater(bloc.stream, emits(const SessionStateSignedOut())); gateway.gitHubError = const GitHubSignInFailed( GitHubSignInProblem.unknownAccount, ); bloc.add(const SessionEventGitHubRequested()); await expectLater( bloc.stream, emitsInOrder([ const SessionStateSignedOut(busy: true), const SessionStateSignedOut(problem: SignedOutProblem.noGitHubAccount), ]), ); gateway.gitHubError = const GitHubSignInFailed(GitHubSignInProblem.refused); bloc.add(const SessionEventGitHubRequested()); await expectLater( bloc.stream, emitsInOrder([ const SessionStateSignedOut(busy: true), const SessionStateSignedOut(problem: SignedOutProblem.failed), ]), ); expect(gateway.signIns, 0); expect(gateway.gitHubSwitches, 0); }); test('a session the server ended asks how to start too', () async { const stored = SignedIn(authUserId: 'github-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())); expect(gateway.signIns, 0); }); test('deleting the account asks how to start', () async { const stored = SignedIn(authUserId: 'github-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 SessionStateLoading(), const SessionStateSignedOut(), ]), ); expect(gateway.accountDeletions, 1); expect(gateway.signIns, 0); }); }