diff --git a/packages/shorebird_cli/lib/src/command_runner.dart b/packages/shorebird_cli/lib/src/command_runner.dart index 41627b63..5bd17642 100644 --- a/packages/shorebird_cli/lib/src/command_runner.dart +++ b/packages/shorebird_cli/lib/src/command_runner.dart @@ -50,6 +50,7 @@ class ShorebirdCliCommandRunner extends CompletionCommandRunner { final buildCodePushApiClient = codePushApiClientBuilder ?? ShorebirdCodePushApiClient.new; + addCommand(LoginCommand(auth: authentication, logger: _logger)); addCommand( PublishCommand( auth: authentication, diff --git a/packages/shorebird_cli/lib/src/commands/commands.dart b/packages/shorebird_cli/lib/src/commands/commands.dart index 25f6ce6c..61b0b6ee 100644 --- a/packages/shorebird_cli/lib/src/commands/commands.dart +++ b/packages/shorebird_cli/lib/src/commands/commands.dart @@ -1,2 +1,3 @@ +export 'login_command.dart'; export 'publish_command.dart'; export 'update_command.dart'; diff --git a/packages/shorebird_cli/lib/src/commands/login_command.dart b/packages/shorebird_cli/lib/src/commands/login_command.dart new file mode 100644 index 00000000..37a93851 --- /dev/null +++ b/packages/shorebird_cli/lib/src/commands/login_command.dart @@ -0,0 +1,50 @@ +import 'package:args/command_runner.dart'; +import 'package:mason_logger/mason_logger.dart'; +import 'package:shorebird_cli/src/auth/auth.dart'; + +/// {@template login_command} +/// +/// `shorebird login` +/// Login as a new Shorebird user. +/// {@endtemplate} +class LoginCommand extends Command { + /// {@macro login_command} + LoginCommand({required Auth auth, required Logger logger}) + : _auth = auth, + _logger = logger; + + @override + String get description => 'Login as a new Shorebird user.'; + + @override + String get name => 'login'; + + final Auth _auth; + final Logger _logger; + + @override + Future run() async { + final session = _auth.currentSession; + if (session != null) { + _logger + ..info('You are already logged in.') + ..info("Run 'shorebird logout' to log out and try again."); + return ExitCode.success.code; + } + + final apiKey = _logger.prompt( + '${lightGreen.wrap('?')} Please enter your API Key:', + ); + final loginProgress = _logger.progress('Logging into shorebird.dev'); + try { + _auth.login(projectId: 'example', apiKey: apiKey); + loginProgress.complete(); + _logger.success('You are now logged in.'); + return ExitCode.success.code; + } catch (error) { + loginProgress.fail(); + _logger.err(error.toString()); + return ExitCode.software.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 new file mode 100644 index 00000000..3cf4bfa7 --- /dev/null +++ b/packages/shorebird_cli/test/src/commands/login_command_test.dart @@ -0,0 +1,87 @@ +import 'package:mason_logger/mason_logger.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:shorebird_cli/src/auth/auth.dart'; +import 'package:shorebird_cli/src/auth/session.dart'; +import 'package:shorebird_cli/src/commands/login_command.dart'; +import 'package:test/test.dart'; + +class _MockAuth extends Mock implements Auth {} + +class _MockLogger extends Mock implements Logger {} + +class _MockProgress extends Mock implements Progress {} + +void main() { + group('LoginCommand', () { + const apiKey = 'test-api-key'; + const projectId = 'example'; + const session = Session(apiKey: apiKey, projectId: projectId); + + late Logger logger; + late Auth auth; + late LoginCommand loginCommand; + + setUp(() { + logger = _MockLogger(); + auth = _MockAuth(); + loginCommand = LoginCommand(auth: auth, logger: logger); + + when(() => logger.progress(any())).thenReturn(_MockProgress()); + }); + + test('exits with code 0 when already logged in', () async { + when(() => auth.currentSession).thenReturn(session); + + final result = await loginCommand.run(); + expect(result, equals(ExitCode.success.code)); + + verify(() => logger.info('You are already logged in.')).called(1); + verify( + () => logger.info("Run 'shorebird logout' to log out and try again."), + ).called(1); + }); + + test('exits with code 70 when error occurs', () async { + final error = Exception('oops something went wrong!'); + when(() => logger.prompt(any())).thenReturn(apiKey); + when(() => auth.currentSession).thenReturn(null); + when( + () => auth.login( + apiKey: any(named: 'apiKey'), + projectId: any(named: 'projectId'), + ), + ).thenThrow(error); + + final result = await loginCommand.run(); + expect(result, equals(ExitCode.software.code)); + + verify(() => logger.progress('Logging into shorebird.dev')).called(1); + verify( + () => auth.login(apiKey: apiKey, projectId: projectId), + ).called(1); + verify(() => logger.err(error.toString())).called(1); + }); + + test('exits with code 0 when logged in successfully', () async { + when(() => logger.prompt(any())).thenReturn(apiKey); + when(() => auth.currentSession).thenReturn(null); + when( + () => auth.login( + apiKey: any(named: 'apiKey'), + projectId: any(named: 'projectId'), + ), + ).thenAnswer((_) async {}); + + final result = await loginCommand.run(); + expect(result, equals(ExitCode.success.code)); + + verify(() => logger.progress('Logging into shorebird.dev')).called(1); + verify( + () => auth.login(apiKey: apiKey, projectId: projectId), + ).called(1); + verify( + () => logger.success('You are now logged in.'), + ).called(1); + }); + }); +}