From 8a2ec108ef57ec4e6447c8e72e3ebf373d0a1f86 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Tue, 4 Apr 2023 11:02:25 -0500 Subject: [PATCH] feat(shorebird_cli): expose user (#224) --- packages/shorebird_cli/lib/src/auth/auth.dart | 37 +++++- packages/shorebird_cli/lib/src/auth/jwt.dart | 22 ++++ .../lib/src/auth/models/models.dart | 1 + .../lib/src/auth/models/user.dart | 10 ++ .../commands/apps/create_apps_command.dart | 2 +- .../commands/apps/delete_apps_command.dart | 2 +- .../src/commands/apps/list_apps_command.dart | 2 +- .../lib/src/commands/build_command.dart | 2 +- .../channels/create_channels_command.dart | 2 +- .../channels/list_channels_command.dart | 2 +- .../lib/src/commands/init_command.dart | 2 +- .../lib/src/commands/login_command.dart | 7 +- .../lib/src/commands/logout_command.dart | 2 +- .../lib/src/commands/patch_command.dart | 2 +- .../lib/src/commands/release_command.dart | 2 +- .../lib/src/commands/run_command.dart | 2 +- .../test/src/auth/auth_test.dart | 109 ++++++++++++++---- .../shorebird_cli/test/src/auth/jwt_test.dart | 25 ++++ .../apps/create_apps_command_test.dart | 7 +- .../apps/delete_apps_command_test.dart | 7 +- .../commands/apps/list_apps_command_test.dart | 8 +- .../test/src/commands/build_command_test.dart | 8 +- .../create_channels_command_test.dart | 8 +- .../channels/list_channels_command_test.dart | 7 +- .../test/src/commands/init_command_test.dart | 8 +- .../test/src/commands/login_command_test.dart | 17 ++- .../src/commands/logout_command_test.dart | 6 +- .../test/src/commands/patch_command_test.dart | 10 +- .../src/commands/release_command_test.dart | 10 +- .../test/src/commands/run_command_test.dart | 8 +- 30 files changed, 224 insertions(+), 113 deletions(-) create mode 100644 packages/shorebird_cli/lib/src/auth/jwt.dart create mode 100644 packages/shorebird_cli/lib/src/auth/models/models.dart create mode 100644 packages/shorebird_cli/lib/src/auth/models/user.dart create mode 100644 packages/shorebird_cli/test/src/auth/jwt_test.dart diff --git a/packages/shorebird_cli/lib/src/auth/auth.dart b/packages/shorebird_cli/lib/src/auth/auth.dart index 6c3cf724..75f68373 100644 --- a/packages/shorebird_cli/lib/src/auth/auth.dart +++ b/packages/shorebird_cli/lib/src/auth/auth.dart @@ -4,9 +4,11 @@ import 'dart:io'; import 'package:googleapis_auth/auth_io.dart'; import 'package:http/http.dart' as http; import 'package:path/path.dart' as p; +import 'package:shorebird_cli/src/auth/jwt.dart'; +import 'package:shorebird_cli/src/auth/models/models.dart'; import 'package:shorebird_cli/src/config/config.dart'; -export 'package:googleapis_auth/googleapis_auth.dart' show AccessCredentials; +export 'package:shorebird_cli/src/auth/models/models.dart' show User; final _clientId = ClientId( /// Shorebird CLI's OAuth 2.0 identifier. @@ -50,12 +52,12 @@ class Auth { final credentialsFilePath = p.join(shorebirdConfigDir, _credentialsFileName); http.Client get client { - if (credentials == null) return _httpClient; - return autoRefreshingClient(_clientId, credentials!, _httpClient); + if (_credentials == null) return _httpClient; + return autoRefreshingClient(_clientId, _credentials!, _httpClient); } Future login(void Function(String) prompt) async { - if (credentials != null) return; + if (_credentials != null) return; final client = http.Client(); try { @@ -65,6 +67,7 @@ class Auth { client, prompt, ); + _user = _credentials?.toUser(); _flushCredentials(_credentials!); } finally { client.close(); @@ -75,7 +78,11 @@ class Auth { AccessCredentials? _credentials; - AccessCredentials? get credentials => _credentials; + User? _user; + + User? get user => _user; + + bool get isAuthenticated => _user != null; void _loadCredentials() { final credentialsFile = File(credentialsFilePath); @@ -86,6 +93,7 @@ class Auth { _credentials = AccessCredentials.fromJson( json.decode(contents) as Map, ); + _user = _credentials?.toUser(); } catch (_) {} } } @@ -98,6 +106,7 @@ class Auth { void _clearCredentials() { _credentials = null; + _user = null; final credentialsFile = File(credentialsFilePath); if (credentialsFile.existsSync()) { @@ -109,3 +118,21 @@ class Auth { _httpClient.close(); } } + +extension on AccessCredentials { + User toUser() { + final token = idToken; + + if (token == null) throw Exception('Missing JWT'); + + final claims = Jwt.decodeClaims(token); + + if (claims == null) throw Exception('Invalid JWT'); + + try { + return User(email: claims['email'] as String); + } catch (_) { + throw Exception('Malformed claims'); + } + } +} diff --git a/packages/shorebird_cli/lib/src/auth/jwt.dart b/packages/shorebird_cli/lib/src/auth/jwt.dart new file mode 100644 index 00000000..ac63c1e4 --- /dev/null +++ b/packages/shorebird_cli/lib/src/auth/jwt.dart @@ -0,0 +1,22 @@ +import 'dart:convert'; + +/// Jwt Utilities +class Jwt { + /// Decode and extract claims from a JWT token. + static Map? decodeClaims(String value) { + final parts = value.split('.'); + if (parts.length != 3) return null; + try { + return _decodePart(parts[1]); + } catch (_) {} + return null; + } +} + +Map _decodePart(String part) { + final normalized = base64.normalize(part); + final base64Decoded = base64.decode(normalized); + final utf8Decoded = utf8.decode(base64Decoded); + final jsonDecoded = json.decode(utf8Decoded) as Map; + return jsonDecoded; +} diff --git a/packages/shorebird_cli/lib/src/auth/models/models.dart b/packages/shorebird_cli/lib/src/auth/models/models.dart new file mode 100644 index 00000000..00db2028 --- /dev/null +++ b/packages/shorebird_cli/lib/src/auth/models/models.dart @@ -0,0 +1 @@ +export 'user.dart'; diff --git a/packages/shorebird_cli/lib/src/auth/models/user.dart b/packages/shorebird_cli/lib/src/auth/models/user.dart new file mode 100644 index 00000000..f2f0cb80 --- /dev/null +++ b/packages/shorebird_cli/lib/src/auth/models/user.dart @@ -0,0 +1,10 @@ +/// {@template user} +/// A shorebird user account. +/// {@endtemplate} +class User { + /// {@macro user} + const User({required this.email}); + + /// The user's email address. + final String email; +} diff --git a/packages/shorebird_cli/lib/src/commands/apps/create_apps_command.dart b/packages/shorebird_cli/lib/src/commands/apps/create_apps_command.dart index 90524309..141518ef 100644 --- a/packages/shorebird_cli/lib/src/commands/apps/create_apps_command.dart +++ b/packages/shorebird_cli/lib/src/commands/apps/create_apps_command.dart @@ -35,7 +35,7 @@ Defaults to the name in "pubspec.yaml".''', @override Future? run() async { - if (auth.credentials == null) { + if (!auth.isAuthenticated) { logger.err('You must be logged in.'); return ExitCode.noUser.code; } diff --git a/packages/shorebird_cli/lib/src/commands/apps/delete_apps_command.dart b/packages/shorebird_cli/lib/src/commands/apps/delete_apps_command.dart index b85413cb..3e8aa6f4 100644 --- a/packages/shorebird_cli/lib/src/commands/apps/delete_apps_command.dart +++ b/packages/shorebird_cli/lib/src/commands/apps/delete_apps_command.dart @@ -32,7 +32,7 @@ Defaults to the app_id in "shorebird.yaml".''', @override Future? run() async { - if (auth.credentials == null) { + if (!auth.isAuthenticated) { logger.err('You must be logged in.'); return ExitCode.noUser.code; } diff --git a/packages/shorebird_cli/lib/src/commands/apps/list_apps_command.dart b/packages/shorebird_cli/lib/src/commands/apps/list_apps_command.dart index 31fb5403..23d97dd2 100644 --- a/packages/shorebird_cli/lib/src/commands/apps/list_apps_command.dart +++ b/packages/shorebird_cli/lib/src/commands/apps/list_apps_command.dart @@ -30,7 +30,7 @@ class ListAppsCommand extends ShorebirdCommand with ShorebirdConfigMixin { @override Future? run() async { - if (auth.credentials == null) { + if (!auth.isAuthenticated) { logger.err('You must be logged in.'); return ExitCode.noUser.code; } diff --git a/packages/shorebird_cli/lib/src/commands/build_command.dart b/packages/shorebird_cli/lib/src/commands/build_command.dart index 214a3b78..543dc9e5 100644 --- a/packages/shorebird_cli/lib/src/commands/build_command.dart +++ b/packages/shorebird_cli/lib/src/commands/build_command.dart @@ -35,7 +35,7 @@ class BuildCommand extends ShorebirdCommand @override Future run() async { - if (auth.credentials == null) { + if (!auth.isAuthenticated) { logger ..err('You must be logged in to build.') ..err("Run 'shorebird login' to log in and try again."); diff --git a/packages/shorebird_cli/lib/src/commands/channels/create_channels_command.dart b/packages/shorebird_cli/lib/src/commands/channels/create_channels_command.dart index 7a31f392..847626e3 100644 --- a/packages/shorebird_cli/lib/src/commands/channels/create_channels_command.dart +++ b/packages/shorebird_cli/lib/src/commands/channels/create_channels_command.dart @@ -37,7 +37,7 @@ class CreateChannelsCommand extends ShorebirdCommand with ShorebirdConfigMixin { @override Future? run() async { - if (auth.credentials == null) { + if (!auth.isAuthenticated) { logger.err('You must be logged in to view channels.'); return ExitCode.noUser.code; } diff --git a/packages/shorebird_cli/lib/src/commands/channels/list_channels_command.dart b/packages/shorebird_cli/lib/src/commands/channels/list_channels_command.dart index b7f679fa..a7994cad 100644 --- a/packages/shorebird_cli/lib/src/commands/channels/list_channels_command.dart +++ b/packages/shorebird_cli/lib/src/commands/channels/list_channels_command.dart @@ -36,7 +36,7 @@ class ListChannelsCommand extends ShorebirdCommand with ShorebirdConfigMixin { @override Future? run() async { - if (auth.credentials == null) { + if (!auth.isAuthenticated) { logger.err('You must be logged in to view channels.'); return ExitCode.noUser.code; } diff --git a/packages/shorebird_cli/lib/src/commands/init_command.dart b/packages/shorebird_cli/lib/src/commands/init_command.dart index 89be99b9..a3dbf351 100644 --- a/packages/shorebird_cli/lib/src/commands/init_command.dart +++ b/packages/shorebird_cli/lib/src/commands/init_command.dart @@ -24,7 +24,7 @@ class InitCommand extends ShorebirdCommand @override Future run() async { - if (auth.credentials == null) { + if (!auth.isAuthenticated) { logger.err('You must be logged in.'); return ExitCode.noUser.code; } diff --git a/packages/shorebird_cli/lib/src/commands/login_command.dart b/packages/shorebird_cli/lib/src/commands/login_command.dart index 6eb06f25..60a25250 100644 --- a/packages/shorebird_cli/lib/src/commands/login_command.dart +++ b/packages/shorebird_cli/lib/src/commands/login_command.dart @@ -18,10 +18,9 @@ class LoginCommand extends ShorebirdCommand { @override Future run() async { - final credentials = auth.credentials; - if (credentials != null) { + if (auth.isAuthenticated) { logger - ..info('You are already logged in.') + ..info('You are already logged in as <${auth.user!.email}>.') ..info("Run 'shorebird logout' to log out and try again."); return ExitCode.success.code; } @@ -30,7 +29,7 @@ class LoginCommand extends ShorebirdCommand { await auth.login(prompt); logger.info(''' -🎉 ${lightGreen.wrap('Welcome to Shorebird! You are now logged in.')} +🎉 ${lightGreen.wrap('Welcome to Shorebird! You are now logged in as <${auth.user!.email}>.')} 🔑 Credentials are stored in ${lightCyan.wrap(auth.credentialsFilePath)}. 🚪 To logout use: "${lightCyan.wrap('shorebird logout')}".'''); diff --git a/packages/shorebird_cli/lib/src/commands/logout_command.dart b/packages/shorebird_cli/lib/src/commands/logout_command.dart index bbe7703d..220916da 100644 --- a/packages/shorebird_cli/lib/src/commands/logout_command.dart +++ b/packages/shorebird_cli/lib/src/commands/logout_command.dart @@ -18,7 +18,7 @@ class LogoutCommand extends ShorebirdCommand { @override Future run() async { - if (auth.credentials == null) { + if (!auth.isAuthenticated) { logger.info('You are already logged out.'); return ExitCode.success.code; } diff --git a/packages/shorebird_cli/lib/src/commands/patch_command.dart b/packages/shorebird_cli/lib/src/commands/patch_command.dart index a183723d..dc0aedf3 100644 --- a/packages/shorebird_cli/lib/src/commands/patch_command.dart +++ b/packages/shorebird_cli/lib/src/commands/patch_command.dart @@ -93,7 +93,7 @@ class PatchCommand extends ShorebirdCommand return ExitCode.config.code; } - if (auth.credentials == null) { + if (!auth.isAuthenticated) { logger.err('You must be logged in to publish.'); return ExitCode.noUser.code; } diff --git a/packages/shorebird_cli/lib/src/commands/release_command.dart b/packages/shorebird_cli/lib/src/commands/release_command.dart index 0cec23e1..b7af976f 100644 --- a/packages/shorebird_cli/lib/src/commands/release_command.dart +++ b/packages/shorebird_cli/lib/src/commands/release_command.dart @@ -71,7 +71,7 @@ make smaller updates to your app. return ExitCode.config.code; } - if (auth.credentials == null) { + if (!auth.isAuthenticated) { logger.err('You must be logged in to release.'); return ExitCode.noUser.code; } diff --git a/packages/shorebird_cli/lib/src/commands/run_command.dart b/packages/shorebird_cli/lib/src/commands/run_command.dart index 44ca68be..c64ca94d 100644 --- a/packages/shorebird_cli/lib/src/commands/run_command.dart +++ b/packages/shorebird_cli/lib/src/commands/run_command.dart @@ -34,7 +34,7 @@ class RunCommand extends ShorebirdCommand @override Future run() async { - if (auth.credentials == null) { + if (!auth.isAuthenticated) { logger ..err('You must be logged in to run.') ..err("Run 'shorebird login' to log in and try again."); diff --git a/packages/shorebird_cli/test/src/auth/auth_test.dart b/packages/shorebird_cli/test/src/auth/auth_test.dart index e850abd1..9c78f172 100644 --- a/packages/shorebird_cli/test/src/auth/auth_test.dart +++ b/packages/shorebird_cli/test/src/auth/auth_test.dart @@ -6,19 +6,27 @@ import 'package:test/test.dart'; class _MockHttpClient extends Mock implements http.Client {} +class _MockAccessCredentials extends Mock implements AccessCredentials {} + void main() { group('Auth', () { + const idToken = + '''eyJhbGciOiJSUzI1NiIsImN0eSI6IkpXVCJ9.eyJlbWFpbCI6InRlc3RAZW1haWwuY29tIn0.pD47BhF3MBLyIpfsgWCzP9twzC1HJxGukpcR36DqT6yfiOMHTLcjDbCjRLAnklWEHiT0BQTKTfhs8IousU90Fm5bVKObudfKu8pP5iZZ6Ls4ohDjTrXky9j3eZpZjwv8CnttBVgRfMJG-7YASTFRYFcOLUpnb4Zm5R6QdoCDUYg'''; + const email = 'test@email.com'; final credentials = AccessCredentials( - AccessToken('Bearer', 'token', DateTime.now().toUtc()), - 'refreshToken', + AccessToken('Bearer', 'accessToken', DateTime.now().toUtc()), + '', [], + idToken: idToken, ); late http.Client httpClient; + late AccessCredentials accessCredentials; late Auth auth; setUp(() { httpClient = _MockHttpClient(); + accessCredentials = _MockAccessCredentials(); auth = Auth( httpClient: httpClient, obtainAccessCredentials: (clientId, scopes, client, userPrompt) async { @@ -47,42 +55,95 @@ void main() { }); group('login', () { - test('should set the credentials', () async { + test('should set the user when claims are valid', () async { + when(() => accessCredentials.idToken).thenReturn(idToken); await auth.login((_) {}); + expect(auth.user, isA().having((u) => u.email, 'email', email)); + expect(auth.isAuthenticated, isTrue); expect( - auth.credentials, - isA().having( - (c) => c.accessToken.data, - 'accessToken', - credentials.accessToken.data, + Auth().user, + isA().having((u) => u.email, 'email', email), + ); + expect(Auth().isAuthenticated, isTrue); + }); + + test('should not set the user when token is null', () async { + when(() => accessCredentials.idToken).thenReturn(null); + auth = Auth( + httpClient: httpClient, + obtainAccessCredentials: + (clientId, scopes, client, userPrompt) async => accessCredentials, + ); + await expectLater( + auth.login((_) {}), + throwsA( + isA().having( + (e) => '$e', + 'description', + 'Exception: Missing JWT', + ), ), ); - expect( - Auth().credentials, - isA().having( - (c) => c.accessToken.data, - 'accessToken', - credentials.accessToken.data, + expect(auth.user, isNull); + expect(auth.isAuthenticated, isFalse); + }); + + test('should not set the user when token is empty', () async { + when(() => accessCredentials.idToken).thenReturn(''); + auth = Auth( + httpClient: httpClient, + obtainAccessCredentials: + (clientId, scopes, client, userPrompt) async => accessCredentials, + ); + await expectLater( + auth.login((_) {}), + throwsA( + isA().having( + (e) => '$e', + 'description', + 'Exception: Invalid JWT', + ), ), ); + expect(auth.user, isNull); + expect(auth.isAuthenticated, isFalse); + }); + + test('should not set the user when token claims are malformed', () async { + when(() => accessCredentials.idToken).thenReturn( + '''eyJhbGciOiJSUzI1NiIsImN0eSI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.LaR0JfOiDrS1AuABC38kzxpSjRLJ_OtfOkZ8hL6I1GPya-cJYwsmqhi5eMBwEbpYHcJhguG5l56XM6dW8xjdK7JbUN6_53gHBosSnL-Ccf29oW71Ado9sxO17YFQyihyMofJ_v78BPVy2H5O10hNjRn_M0JnnAe0Fvd2VrInlIE''', + ); + auth = Auth( + httpClient: httpClient, + obtainAccessCredentials: + (clientId, scopes, client, userPrompt) async => accessCredentials, + ); + await expectLater( + auth.login((_) {}), + throwsA( + isA().having( + (e) => '$e', + 'description', + 'Exception: Malformed claims', + ), + ), + ); + expect(auth.user, isNull); + expect(auth.isAuthenticated, isFalse); }); }); group('logout', () { test('clears session and wipes state', () async { await auth.login((_) {}); - expect( - auth.credentials, - isA().having( - (c) => c.accessToken.data, - 'accessToken', - credentials.accessToken.data, - ), - ); + expect(auth.user, isA().having((u) => u.email, 'email', email)); + expect(auth.isAuthenticated, isTrue); auth.logout(); - expect(auth.credentials, isNull); - expect(Auth().credentials, isNull); + expect(auth.user, isNull); + expect(auth.isAuthenticated, isFalse); + expect(Auth().user, isNull); + expect(Auth().isAuthenticated, isFalse); }); }); diff --git a/packages/shorebird_cli/test/src/auth/jwt_test.dart b/packages/shorebird_cli/test/src/auth/jwt_test.dart new file mode 100644 index 00000000..ffff4bbf --- /dev/null +++ b/packages/shorebird_cli/test/src/auth/jwt_test.dart @@ -0,0 +1,25 @@ +import 'package:shorebird_cli/src/auth/jwt.dart'; +import 'package:test/test.dart'; + +void main() { + group('Jwt', () { + group('decodeClaims', () { + test('returns null jwt does not contain 3 segments', () { + expect(Jwt.decodeClaims('invalid'), isNull); + }); + + test('returns null when jwt payload segment is malformed', () { + expect(Jwt.decodeClaims('this.is.invalid'), isNull); + }); + + test('returns correct claims when jwt payload segment is valid', () { + expect( + Jwt.decodeClaims( + '''eyJhbGciOiJSUzI1NiIsImN0eSI6IkpXVCJ9.eyJlbWFpbCI6InRlc3RAZW1haWwuY29tIn0.pD47BhF3MBLyIpfsgWCzP9twzC1HJxGukpcR36DqT6yfiOMHTLcjDbCjRLAnklWEHiT0BQTKTfhs8IousU90Fm5bVKObudfKu8pP5iZZ6Ls4ohDjTrXky9j3eZpZjwv8CnttBVgRfMJG-7YASTFRYFcOLUpnb4Zm5R6QdoCDUYg''', + ), + equals({'email': 'test@email.com'}), + ); + }); + }); + }); +} diff --git a/packages/shorebird_cli/test/src/commands/apps/create_apps_command_test.dart b/packages/shorebird_cli/test/src/commands/apps/create_apps_command_test.dart index c5a4b92c..52228f5b 100644 --- a/packages/shorebird_cli/test/src/commands/apps/create_apps_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/apps/create_apps_command_test.dart @@ -17,13 +17,10 @@ class _MockCodePushClient extends Mock implements CodePushClient {} class _MockLogger extends Mock implements Logger {} -class _MockAccessCredentials extends Mock implements AccessCredentials {} - void main() { group('create', () { const appId = 'app-id'; const displayName = 'Example App'; - final credentials = _MockAccessCredentials(); late ArgResults argResults; late http.Client httpClient; @@ -49,7 +46,7 @@ void main() { logger: logger, )..testArgResults = argResults; - when(() => auth.credentials).thenReturn(credentials); + when(() => auth.isAuthenticated).thenReturn(true); when(() => auth.client).thenReturn(httpClient); }); @@ -58,7 +55,7 @@ void main() { }); test('returns no user error when not logged in', () async { - when(() => auth.credentials).thenReturn(null); + when(() => auth.isAuthenticated).thenReturn(false); final result = await command.run(); expect(result, ExitCode.noUser.code); }); diff --git a/packages/shorebird_cli/test/src/commands/apps/delete_apps_command_test.dart b/packages/shorebird_cli/test/src/commands/apps/delete_apps_command_test.dart index daf56fa1..3a439211 100644 --- a/packages/shorebird_cli/test/src/commands/apps/delete_apps_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/apps/delete_apps_command_test.dart @@ -7,8 +7,6 @@ import 'package:shorebird_cli/src/commands/commands.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; -class _MockAccessCredentials extends Mock implements AccessCredentials {} - class _MockArgResults extends Mock implements ArgResults {} class _MockHttpClient extends Mock implements http.Client {} @@ -22,7 +20,6 @@ class _MockLogger extends Mock implements Logger {} void main() { group('delete', () { const appId = 'example'; - final credentials = _MockAccessCredentials(); late ArgResults argResults; late http.Client httpClient; @@ -48,7 +45,7 @@ void main() { logger: logger, )..testArgResults = argResults; - when(() => auth.credentials).thenReturn(credentials); + when(() => auth.isAuthenticated).thenReturn(true); when(() => auth.client).thenReturn(httpClient); }); @@ -60,7 +57,7 @@ void main() { }); test('returns no user error when not logged in', () async { - when(() => auth.credentials).thenReturn(null); + when(() => auth.isAuthenticated).thenReturn(false); final result = await command.run(); expect(result, ExitCode.noUser.code); }); diff --git a/packages/shorebird_cli/test/src/commands/apps/list_apps_command_test.dart b/packages/shorebird_cli/test/src/commands/apps/list_apps_command_test.dart index 7bb52446..41ba23c7 100644 --- a/packages/shorebird_cli/test/src/commands/apps/list_apps_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/apps/list_apps_command_test.dart @@ -6,8 +6,6 @@ import 'package:shorebird_cli/src/commands/commands.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; -class _MockAccessCredentials extends Mock implements AccessCredentials {} - class _MockHttpClient extends Mock implements http.Client {} class _MockAuth extends Mock implements Auth {} @@ -18,8 +16,6 @@ class _MockLogger extends Mock implements Logger {} void main() { group('list', () { - final credentials = _MockAccessCredentials(); - late http.Client httpClient; late Auth auth; late CodePushClient codePushClient; @@ -43,7 +39,7 @@ void main() { logger: logger, ); - when(() => auth.credentials).thenReturn(credentials); + when(() => auth.isAuthenticated).thenReturn(true); when(() => auth.client).thenReturn(httpClient); }); @@ -52,7 +48,7 @@ void main() { }); test('returns ExitCode.noUser when not logged in', () async { - when(() => auth.credentials).thenReturn(null); + when(() => auth.isAuthenticated).thenReturn(false); expect(await command.run(), ExitCode.noUser.code); }); diff --git a/packages/shorebird_cli/test/src/commands/build_command_test.dart b/packages/shorebird_cli/test/src/commands/build_command_test.dart index 8e3af8df..bf0a3f8d 100644 --- a/packages/shorebird_cli/test/src/commands/build_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/build_command_test.dart @@ -14,8 +14,6 @@ import 'package:test/test.dart'; class _MockArgResults extends Mock implements ArgResults {} -class _MockAccessCredentials extends Mock implements AccessCredentials {} - class _MockHttpClient extends Mock implements http.Client {} class _MockAuth extends Mock implements Auth {} @@ -30,8 +28,6 @@ class _MockCodePushClient extends Mock implements CodePushClient {} void main() { group('build', () { - final credentials = _MockAccessCredentials(); - late ArgResults argResults; late Directory applicationConfigHome; late http.Client httpClient; @@ -70,7 +66,7 @@ void main() { testApplicationConfigHome = (_) => applicationConfigHome.path; when(() => argResults.rest).thenReturn([]); - when(() => auth.credentials).thenReturn(credentials); + when(() => auth.isAuthenticated).thenReturn(true); when(() => auth.client).thenReturn(httpClient); when( () => codePushClient.downloadEngine(revision: any(named: 'revision')), @@ -79,7 +75,7 @@ void main() { }); test('exits with no user when not logged in', () async { - when(() => auth.credentials).thenReturn(null); + when(() => auth.isAuthenticated).thenReturn(false); final result = await buildCommand.run(); expect(result, equals(ExitCode.noUser.code)); diff --git a/packages/shorebird_cli/test/src/commands/channels/create_channels_command_test.dart b/packages/shorebird_cli/test/src/commands/channels/create_channels_command_test.dart index 09006ecf..d898e34e 100644 --- a/packages/shorebird_cli/test/src/commands/channels/create_channels_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/channels/create_channels_command_test.dart @@ -7,8 +7,6 @@ import 'package:shorebird_cli/src/commands/commands.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; -class _MockAccessCredentials extends Mock implements AccessCredentials {} - class _MockArgResults extends Mock implements ArgResults {} class _MockHttpClient extends Mock implements http.Client {} @@ -27,8 +25,6 @@ void main() { const channelName = 'my-channel'; const channel = Channel(id: 0, appId: appId, name: channelName); - final credentials = _MockAccessCredentials(); - late ArgResults argResults; late http.Client httpClient; late Auth auth; @@ -57,7 +53,7 @@ void main() { when(() => argResults['app-id']).thenReturn(appId); when(() => argResults['name']).thenReturn(channelName); - when(() => auth.credentials).thenReturn(credentials); + when(() => auth.isAuthenticated).thenReturn(true); when(() => auth.client).thenReturn(httpClient); when(() => logger.confirm(any())).thenReturn(true); when(() => logger.progress(any())).thenReturn(progress); @@ -71,7 +67,7 @@ void main() { }); test('returns ExitCode.noUser when not logged in', () async { - when(() => auth.credentials).thenReturn(null); + when(() => auth.isAuthenticated).thenReturn(false); expect(await command.run(), ExitCode.noUser.code); }); diff --git a/packages/shorebird_cli/test/src/commands/channels/list_channels_command_test.dart b/packages/shorebird_cli/test/src/commands/channels/list_channels_command_test.dart index b4f10683..87bb9718 100644 --- a/packages/shorebird_cli/test/src/commands/channels/list_channels_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/channels/list_channels_command_test.dart @@ -7,8 +7,6 @@ import 'package:shorebird_cli/src/commands/commands.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; -class _MockAccessCredentials extends Mock implements AccessCredentials {} - class _MockArgResults extends Mock implements ArgResults {} class _MockHttpClient extends Mock implements http.Client {} @@ -22,7 +20,6 @@ class _MockLogger extends Mock implements Logger {} void main() { group('list', () { const appId = 'test-app-id'; - final credentials = _MockAccessCredentials(); late ArgResults argResults; late http.Client httpClient; @@ -49,7 +46,7 @@ void main() { )..testArgResults = argResults; when(() => argResults['app-id']).thenReturn(appId); - when(() => auth.credentials).thenReturn(credentials); + when(() => auth.isAuthenticated).thenReturn(true); when(() => auth.client).thenReturn(httpClient); }); @@ -61,7 +58,7 @@ void main() { }); test('returns ExitCode.noUser when not logged in', () async { - when(() => auth.credentials).thenReturn(null); + when(() => auth.isAuthenticated).thenReturn(false); expect(await command.run(), ExitCode.noUser.code); }); diff --git a/packages/shorebird_cli/test/src/commands/init_command_test.dart b/packages/shorebird_cli/test/src/commands/init_command_test.dart index 388db386..2a408339 100644 --- a/packages/shorebird_cli/test/src/commands/init_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/init_command_test.dart @@ -9,8 +9,6 @@ import 'package:shorebird_cli/src/commands/init_command.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; -class _MockAccessCredentials extends Mock implements AccessCredentials {} - class _MockHttpClient extends Mock implements http.Client {} class _MockAuth extends Mock implements Auth {} @@ -34,8 +32,6 @@ version: $version environment: sdk: ">=2.19.0 <3.0.0"'''; - final credentials = _MockAccessCredentials(); - late http.Client httpClient; late Auth auth; late CodePushClient codePushClient; @@ -60,7 +56,7 @@ environment: logger: logger, ); - when(() => auth.credentials).thenReturn(credentials); + when(() => auth.isAuthenticated).thenReturn(true); when(() => auth.client).thenReturn(httpClient); when( () => codePushClient.createApp(displayName: any(named: 'displayName')), @@ -75,7 +71,7 @@ environment: }); test('returns no user error when not logged in', () async { - when(() => auth.credentials).thenReturn(null); + when(() => auth.isAuthenticated).thenReturn(false); final result = await command.run(); expect(result, ExitCode.noUser.code); }); diff --git a/packages/shorebird_cli/test/src/commands/login_command_test.dart b/packages/shorebird_cli/test/src/commands/login_command_test.dart index b2f31967..86f3a908 100644 --- a/packages/shorebird_cli/test/src/commands/login_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/login_command_test.dart @@ -8,15 +8,13 @@ import 'package:shorebird_cli/src/commands/login_command.dart'; import 'package:shorebird_cli/src/config/config.dart'; import 'package:test/test.dart'; -class _MockAccessCredentials extends Mock implements AccessCredentials {} - class _MockAuth extends Mock implements Auth {} class _MockLogger extends Mock implements Logger {} void main() { group('login', () { - final credentials = _MockAccessCredentials(); + const user = User(email: 'test@email.com'); late Directory applicationConfigHome; late Logger logger; @@ -34,15 +32,19 @@ void main() { when(() => auth.credentialsFilePath).thenReturn( p.join(applicationConfigHome.path, 'credentials.json'), ); + when(() => auth.isAuthenticated).thenReturn(false); }); test('exits with code 0 when already logged in', () async { - when(() => auth.credentials).thenReturn(credentials); + when(() => auth.isAuthenticated).thenReturn(true); + when(() => auth.user).thenReturn(user); final result = await loginCommand.run(); expect(result, equals(ExitCode.success.code)); - verify(() => logger.info('You are already logged in.')).called(1); + verify( + () => logger.info('You are already logged in as <${user.email}>.'), + ).called(1); verify( () => logger.info("Run 'shorebird logout' to log out and try again."), ).called(1); @@ -61,13 +63,16 @@ void main() { test('exits with code 0 when logged in successfully', () async { when(() => auth.login(any())).thenAnswer((_) async {}); + when(() => auth.user).thenReturn(user); final result = await loginCommand.run(); expect(result, equals(ExitCode.success.code)); verify(() => auth.login(any())).called(1); verify( - () => logger.info(any(that: contains('You are now logged in.'))), + () => logger.info( + any(that: contains('You are now logged in as <${user.email}>.')), + ), ).called(1); }); diff --git a/packages/shorebird_cli/test/src/commands/logout_command_test.dart b/packages/shorebird_cli/test/src/commands/logout_command_test.dart index 4184797d..1ea17199 100644 --- a/packages/shorebird_cli/test/src/commands/logout_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/logout_command_test.dart @@ -4,8 +4,6 @@ import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/commands/logout_command.dart'; import 'package:test/test.dart'; -class _MockAccessCredentials extends Mock implements AccessCredentials {} - class _MockLogger extends Mock implements Logger {} class _MockAuth extends Mock implements Auth {} @@ -27,6 +25,7 @@ void main() { }); test('exits with code 0 when already logged out', () async { + when(() => auth.isAuthenticated).thenReturn(false); final result = await logoutCommand.run(); expect(result, equals(ExitCode.success.code)); @@ -36,8 +35,7 @@ void main() { }); test('exits with code 0 when logged out successfully', () async { - final credentials = _MockAccessCredentials(); - when(() => auth.credentials).thenReturn(credentials); + when(() => auth.isAuthenticated).thenReturn(true); final progress = _MockProgress(); when(() => progress.complete(any())).thenAnswer((invocation) {}); diff --git a/packages/shorebird_cli/test/src/commands/patch_command_test.dart b/packages/shorebird_cli/test/src/commands/patch_command_test.dart index d085f0c3..d99c0a62 100644 --- a/packages/shorebird_cli/test/src/commands/patch_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch_command_test.dart @@ -13,8 +13,6 @@ import 'package:shorebird_cli/src/config/config.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; -class _MockAccessCredentials extends Mock implements AccessCredentials {} - class _FakeBaseRequest extends Fake implements http.BaseRequest {} class _MockArgResults extends Mock implements ArgResults {} @@ -76,8 +74,6 @@ flutter: assets: - shorebird.yaml'''; - final credentials = _MockAccessCredentials(); - late ArgResults argResults; late Directory applicationConfigHome; late Auth auth; @@ -145,7 +141,7 @@ flutter: when(() => argResults['channel']).thenReturn(channelName); when(() => argResults['dry-run']).thenReturn(false); when(() => argResults['force']).thenReturn(false); - when(() => auth.credentials).thenReturn(credentials); + when(() => auth.isAuthenticated).thenReturn(true); when(() => auth.client).thenReturn(httpClient); when(() => logger.progress(any())).thenReturn(progress); when( @@ -218,8 +214,8 @@ flutter: expect(exitCode, ExitCode.config.code); }); - test('throws no user error when session does not exist', () async { - when(() => auth.credentials).thenReturn(null); + test('throws no user error when user is not logged in', () async { + when(() => auth.isAuthenticated).thenReturn(false); final tempDir = setUpTempDir(); final exitCode = await IOOverrides.runZoned( () => command.run(), diff --git a/packages/shorebird_cli/test/src/commands/release_command_test.dart b/packages/shorebird_cli/test/src/commands/release_command_test.dart index d0e9c85d..062c2b75 100644 --- a/packages/shorebird_cli/test/src/commands/release_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/release_command_test.dart @@ -13,8 +13,6 @@ import 'package:shorebird_cli/src/config/config.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; -class _MockAccessCredentials extends Mock implements AccessCredentials {} - class _MockArgResults extends Mock implements ArgResults {} class _MockHttpClient extends Mock implements http.Client {} @@ -63,8 +61,6 @@ flutter: assets: - shorebird.yaml'''; - final credentials = _MockAccessCredentials(); - late ArgResults argResults; late Directory applicationConfigHome; late http.Client httpClient; @@ -120,7 +116,7 @@ flutter: when(() => argResults.rest).thenReturn([]); when(() => argResults['arch']).thenReturn(arch); when(() => argResults['platform']).thenReturn(platform); - when(() => auth.credentials).thenReturn(credentials); + when(() => auth.isAuthenticated).thenReturn(true); when(() => auth.client).thenReturn(httpClient); when(() => logger.progress(any())).thenReturn(progress); when(() => logger.confirm(any())).thenReturn(true); @@ -168,8 +164,8 @@ flutter: expect(exitCode, ExitCode.config.code); }); - test('throws no user error when session does not exist', () async { - when(() => auth.credentials).thenReturn(null); + test('throws no user error when user is not logged in', () async { + when(() => auth.isAuthenticated).thenReturn(false); final tempDir = setUpTempDir(); final exitCode = await IOOverrides.runZoned( () => command.run(), diff --git a/packages/shorebird_cli/test/src/commands/run_command_test.dart b/packages/shorebird_cli/test/src/commands/run_command_test.dart index a8494c44..099436ea 100644 --- a/packages/shorebird_cli/test/src/commands/run_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/run_command_test.dart @@ -15,8 +15,6 @@ import 'package:shorebird_cli/src/config/config.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; -class _MockAccessCredentials extends Mock implements AccessCredentials {} - class _MockArgResults extends Mock implements ArgResults {} class _MockHttpClient extends Mock implements http.Client {} @@ -33,8 +31,6 @@ class _MockCodePushClient extends Mock implements CodePushClient {} void main() { group('run', () { - final credentials = _MockAccessCredentials(); - late ArgResults argResults; late Directory applicationConfigHome; late http.Client httpClient; @@ -69,13 +65,13 @@ void main() { testApplicationConfigHome = (_) => applicationConfigHome.path; when(() => argResults.rest).thenReturn([]); - when(() => auth.credentials).thenReturn(credentials); + when(() => auth.isAuthenticated).thenReturn(true); when(() => auth.client).thenReturn(httpClient); when(() => logger.progress(any())).thenReturn(_MockProgress()); }); test('exits with no user when not logged in', () async { - when(() => auth.credentials).thenReturn(null); + when(() => auth.isAuthenticated).thenReturn(false); final result = await runCommand.run(); expect(result, equals(ExitCode.noUser.code));