refactor(shorebird_cli): publish command updates and tests (#1)

This commit is contained in:
Felix Angelov
2023-03-02 14:46:51 -06:00
committed by GitHub
parent 5d3c2a2786
commit ec25e8090a
7 changed files with 81 additions and 52 deletions
@@ -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<int> {
/// {@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<int> {
);
// 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<int> {
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) {
@@ -39,20 +39,20 @@ class PublishCommand extends Command<int> {
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;
}
+1
View File
@@ -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:
@@ -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'),
);
}
View File
@@ -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);
});
});
});
@@ -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);
});
});
}