From ec25e8090a923f76b65b4917910456d496db385d Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Thu, 2 Mar 2023 14:46:51 -0600 Subject: [PATCH] refactor(shorebird_cli): publish command updates and tests (#1) --- .../shorebird_cli/lib/src/command_runner.dart | 25 +------ .../lib/src/commands/publish_command.dart | 8 +- packages/shorebird_cli/pubspec.yaml | 1 + .../shorebird_cli/test/ensure_build_test.dart | 5 +- .../shorebird_cli/test/fixtures/release.txt | 0 .../test/src/command_runner_test.dart | 21 ------ .../src/commands/publish_command_test.dart | 73 ++++++++++++++++++- 7 files changed, 81 insertions(+), 52 deletions(-) create mode 100644 packages/shorebird_cli/test/fixtures/release.txt diff --git a/packages/shorebird_cli/lib/src/command_runner.dart b/packages/shorebird_cli/lib/src/command_runner.dart index ccdc648f..d9118ea8 100644 --- a/packages/shorebird_cli/lib/src/command_runner.dart +++ b/packages/shorebird_cli/lib/src/command_runner.dart @@ -1,6 +1,7 @@ import 'package:args/args.dart'; import 'package:args/command_runner.dart'; import 'package:cli_completion/cli_completion.dart'; +import 'package:http/http.dart' as http; import 'package:mason_logger/mason_logger.dart'; import 'package:pub_updater/pub_updater.dart'; import 'package:shorebird_cli/src/commands/commands.dart'; @@ -21,6 +22,7 @@ class ShorebirdCliCommandRunner extends CompletionCommandRunner { /// {@macro shorebird_cli_command_runner} ShorebirdCliCommandRunner({ Logger? logger, + http.Client? httpClient, PubUpdater? pubUpdater, }) : _logger = logger ?? Logger(), _pubUpdater = pubUpdater ?? PubUpdater(), @@ -39,7 +41,7 @@ class ShorebirdCliCommandRunner extends CompletionCommandRunner { ); // Add sub commands - addCommand(PublishCommand(logger: _logger)); + addCommand(PublishCommand(logger: _logger, httpClient: httpClient)); addCommand(UpdateCommand(logger: _logger, pubUpdater: _pubUpdater)); } @@ -85,27 +87,6 @@ class ShorebirdCliCommandRunner extends CompletionCommandRunner { return ExitCode.success.code; } - // Verbose logs - _logger - ..detail('Argument information:') - ..detail(' Top level options:'); - for (final option in topLevelResults.options) { - if (topLevelResults.wasParsed(option)) { - _logger.detail(' - $option: ${topLevelResults[option]}'); - } - } - if (topLevelResults.command != null) { - final commandResult = topLevelResults.command!; - _logger - ..detail(' Command: ${commandResult.name}') - ..detail(' Command options:'); - for (final option in commandResult.options) { - if (commandResult.wasParsed(option)) { - _logger.detail(' - $option: ${commandResult[option]}'); - } - } - } - // Run the command or show version final int? exitCode; if (topLevelResults['version'] == true) { diff --git a/packages/shorebird_cli/lib/src/commands/publish_command.dart b/packages/shorebird_cli/lib/src/commands/publish_command.dart index 08b8ae4b..726a324e 100644 --- a/packages/shorebird_cli/lib/src/commands/publish_command.dart +++ b/packages/shorebird_cli/lib/src/commands/publish_command.dart @@ -39,20 +39,20 @@ class PublishCommand extends Command { final request = http.MultipartRequest( 'POST', - Uri.parse('http://localhost:8080/deploy'), + Uri.parse('http://localhost:8080/api/v1/releases'), ); final file = await http.MultipartFile.fromPath('file', artifact.path); request.files.add(file); final response = await _httpClient.send(request); - if (response.statusCode != HttpStatus.ok) { + if (response.statusCode != HttpStatus.created) { _logger.err( - 'Deploy failed: ${response.statusCode} ${response.reasonPhrase}', + 'Failed to deploy: ${response.statusCode} ${response.reasonPhrase}', ); return ExitCode.software.code; } - _logger.success('Deployed ${artifact.path} successfully!'); + _logger.success('Deployed ${artifact.path}!'); return ExitCode.success.code; } diff --git a/packages/shorebird_cli/pubspec.yaml b/packages/shorebird_cli/pubspec.yaml index 4a623779..62341185 100644 --- a/packages/shorebird_cli/pubspec.yaml +++ b/packages/shorebird_cli/pubspec.yaml @@ -10,6 +10,7 @@ dependencies: cli_completion: ^0.2.0 http: ^0.13.5 mason_logger: ^0.2.4 + path: ^1.8.3 pub_updater: ^0.2.4 dev_dependencies: diff --git a/packages/shorebird_cli/test/ensure_build_test.dart b/packages/shorebird_cli/test/ensure_build_test.dart index 3d1173b0..71a69814 100644 --- a/packages/shorebird_cli/test/ensure_build_test.dart +++ b/packages/shorebird_cli/test/ensure_build_test.dart @@ -5,5 +5,8 @@ import 'package:build_verify/build_verify.dart'; import 'package:test/test.dart'; void main() { - test('ensure_build', expectBuildClean); + test( + 'ensure_build', + () => expectBuildClean(packageRelativeDirectory: 'packages/shorebird_cli'), + ); } diff --git a/packages/shorebird_cli/test/fixtures/release.txt b/packages/shorebird_cli/test/fixtures/release.txt new file mode 100644 index 00000000..e69de29b diff --git a/packages/shorebird_cli/test/src/command_runner_test.dart b/packages/shorebird_cli/test/src/command_runner_test.dart index 6dcefa28..a190b6cc 100644 --- a/packages/shorebird_cli/test/src/command_runner_test.dart +++ b/packages/shorebird_cli/test/src/command_runner_test.dart @@ -151,27 +151,6 @@ void main() { test('enables verbose logging', () async { final result = await commandRunner.run(['--verbose']); expect(result, equals(ExitCode.success.code)); - - verify(() => logger.detail('Argument information:')).called(1); - verify(() => logger.detail(' Top level options:')).called(1); - verify(() => logger.detail(' - verbose: true')).called(1); - verifyNever(() => logger.detail(' Command options:')); - }); - - test('enables verbose logging for sub commands', () async { - final result = await commandRunner.run([ - '--verbose', - 'sample', - '--cyan', - ]); - expect(result, equals(ExitCode.success.code)); - - verify(() => logger.detail('Argument information:')).called(1); - verify(() => logger.detail(' Top level options:')).called(1); - verify(() => logger.detail(' - verbose: true')).called(1); - verify(() => logger.detail(' Command: sample')).called(1); - verify(() => logger.detail(' Command options:')).called(1); - verify(() => logger.detail(' - cyan: true')).called(1); }); }); }); diff --git a/packages/shorebird_cli/test/src/commands/publish_command_test.dart b/packages/shorebird_cli/test/src/commands/publish_command_test.dart index 0df9bf3e..01854242 100644 --- a/packages/shorebird_cli/test/src/commands/publish_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/publish_command_test.dart @@ -1,26 +1,91 @@ +import 'dart:io'; + +import 'package:http/http.dart' as http; import 'package:mason_logger/mason_logger.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:path/path.dart' as p; import 'package:shorebird_cli/src/command_runner.dart'; import 'package:test/test.dart'; class _MockLogger extends Mock implements Logger {} +class _MockHttpClient extends Mock implements http.Client {} + +class _FakeBaseRequest extends Fake implements http.BaseRequest {} + void main() { group('publish', () { late Logger logger; + late http.Client httpClient; late ShorebirdCliCommandRunner commandRunner; + setUpAll(() { + registerFallbackValue(_FakeBaseRequest()); + }); + setUp(() { logger = _MockLogger(); - commandRunner = ShorebirdCliCommandRunner(logger: logger); + httpClient = _MockHttpClient(); + commandRunner = ShorebirdCliCommandRunner( + logger: logger, + httpClient: httpClient, + ); }); - test('outputs coming soon...', () async { + test('throws usage error when no file path is specified.', () async { final exitCode = await commandRunner.run(['publish']); + verify( + () => logger.err('A single file path must be specified.'), + ).called(1); + expect(exitCode, ExitCode.usage.code); + }); + test('throws usage error when multiple args are passed.', () async { + final exitCode = await commandRunner.run(['publish', 'arg1', 'arg2']); + verify( + () => logger.err('A single file path must be specified.'), + ).called(1); + expect(exitCode, ExitCode.usage.code); + }); + + test('throws no input error when file is not found.', () async { + final exitCode = await commandRunner.run([ + 'publish', + 'missing.txt', + ]); + verify(() => logger.err('File not found: missing.txt')).called(1); + expect(exitCode, ExitCode.noInput.code); + }); + + test('throws error when release fails.', () async { + const statusCode = HttpStatus.internalServerError; + const reasonPhrase = 'something went wrong'; + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + const Stream.empty(), + statusCode, + reasonPhrase: reasonPhrase, + ), + ); + final release = p.join('test', 'fixtures', 'release.txt'); + final exitCode = await commandRunner.run(['publish', release]); + verify( + () => logger.err('Failed to deploy: $statusCode $reasonPhrase'), + ).called(1); + expect(exitCode, ExitCode.software.code); + }); + + test('succeeds when release is successful.', () async { + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + const Stream.empty(), + HttpStatus.created, + ), + ); + final release = p.join('test', 'fixtures', 'release.txt'); + final exitCode = await commandRunner.run(['publish', release]); + verify(() => logger.success('Deployed $release!')).called(1); expect(exitCode, ExitCode.success.code); - - verify(() => logger.info('Coming soon...')).called(1); }); }); }