| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // @dart=2.9 |
| |
| import 'dart:collection'; |
| import 'dart:convert'; |
| import 'dart:io' as io; |
| |
| import 'package:args/command_runner.dart'; |
| import 'package:file/file.dart'; |
| import 'package:flutter_plugin_tools/src/common.dart'; |
| import 'package:flutter_plugin_tools/src/publish_check_command.dart'; |
| import 'package:http/http.dart' as http; |
| import 'package:http/testing.dart'; |
| import 'package:test/test.dart'; |
| |
| import 'mocks.dart'; |
| import 'util.dart'; |
| |
| void main() { |
| group('$PublishCheckProcessRunner tests', () { |
| PublishCheckProcessRunner processRunner; |
| CommandRunner<void> runner; |
| |
| setUp(() { |
| initializeFakePackages(); |
| processRunner = PublishCheckProcessRunner(); |
| final PublishCheckCommand publishCheckCommand = PublishCheckCommand( |
| mockPackagesDir, mockFileSystem, |
| processRunner: processRunner); |
| |
| runner = CommandRunner<void>( |
| 'publish_check_command', |
| 'Test for publish-check command.', |
| ); |
| runner.addCommand(publishCheckCommand); |
| }); |
| |
| tearDown(() { |
| mockPackagesDir.deleteSync(recursive: true); |
| }); |
| |
| test('publish check all packages', () async { |
| final Directory plugin1Dir = createFakePlugin('a'); |
| final Directory plugin2Dir = createFakePlugin('b'); |
| |
| processRunner.processesToReturn.add( |
| MockProcess()..exitCodeCompleter.complete(0), |
| ); |
| processRunner.processesToReturn.add( |
| MockProcess()..exitCodeCompleter.complete(0), |
| ); |
| await runner.run(<String>['publish-check']); |
| |
| expect( |
| processRunner.recordedCalls, |
| orderedEquals(<ProcessCall>[ |
| ProcessCall( |
| 'flutter', |
| const <String>['pub', 'publish', '--', '--dry-run'], |
| plugin1Dir.path), |
| ProcessCall( |
| 'flutter', |
| const <String>['pub', 'publish', '--', '--dry-run'], |
| plugin2Dir.path), |
| ])); |
| }); |
| |
| test('fail on negative test', () async { |
| createFakePlugin('a'); |
| |
| final MockProcess process = MockProcess(); |
| process.stdoutController.close(); // ignore: unawaited_futures |
| process.stderrController.close(); // ignore: unawaited_futures |
| process.exitCodeCompleter.complete(1); |
| |
| processRunner.processesToReturn.add(process); |
| |
| expect( |
| () => runner.run(<String>['publish-check']), |
| throwsA(isA<ToolExit>()), |
| ); |
| }); |
| |
| test('fail on bad pubspec', () async { |
| final Directory dir = createFakePlugin('c'); |
| await dir.childFile('pubspec.yaml').writeAsString('bad-yaml'); |
| |
| final MockProcess process = MockProcess(); |
| processRunner.processesToReturn.add(process); |
| |
| expect(() => runner.run(<String>['publish-check']), |
| throwsA(isA<ToolExit>())); |
| }); |
| |
| test('pass on prerelease if --allow-pre-release flag is on', () async { |
| createFakePlugin('d'); |
| |
| const String preReleaseOutput = 'Package has 1 warning.' |
| 'Packages with an SDK constraint on a pre-release of the Dart SDK should themselves be published as a pre-release version.'; |
| |
| final MockProcess process = MockProcess(); |
| process.stdoutController.add(preReleaseOutput.codeUnits); |
| process.stdoutController.close(); // ignore: unawaited_futures |
| process.stderrController.close(); // ignore: unawaited_futures |
| |
| process.exitCodeCompleter.complete(1); |
| |
| processRunner.processesToReturn.add(process); |
| |
| expect(runner.run(<String>['publish-check', '--allow-pre-release']), |
| completes); |
| }); |
| |
| test('fail on prerelease if --allow-pre-release flag is off', () async { |
| createFakePlugin('d'); |
| |
| const String preReleaseOutput = 'Package has 1 warning.' |
| 'Packages with an SDK constraint on a pre-release of the Dart SDK should themselves be published as a pre-release version.'; |
| |
| final MockProcess process = MockProcess(); |
| process.stdoutController.add(preReleaseOutput.codeUnits); |
| process.stdoutController.close(); // ignore: unawaited_futures |
| process.stderrController.close(); // ignore: unawaited_futures |
| |
| process.exitCodeCompleter.complete(1); |
| |
| processRunner.processesToReturn.add(process); |
| |
| expect(runner.run(<String>['publish-check']), throwsA(isA<ToolExit>())); |
| }); |
| |
| test( |
| '--machine: Log JSON with status:no-publish and correct human message, if there are no packages need to be published. ', |
| () async { |
| const Map<String, dynamic> httpResponseA = <String, dynamic>{ |
| 'name': 'a', |
| 'versions': <String>[ |
| '0.0.1', |
| '0.1.0', |
| ], |
| }; |
| |
| const Map<String, dynamic> httpResponseB = <String, dynamic>{ |
| 'name': 'b', |
| 'versions': <String>[ |
| '0.0.1', |
| '0.1.0', |
| '0.2.0', |
| ], |
| }; |
| |
| final MockClient mockClient = MockClient((http.Request request) async { |
| if (request.url.pathSegments.last == 'no_publish_a.json') { |
| return http.Response(json.encode(httpResponseA), 200); |
| } else if (request.url.pathSegments.last == 'no_publish_b.json') { |
| return http.Response(json.encode(httpResponseB), 200); |
| } |
| return null; |
| }); |
| final PublishCheckCommand command = PublishCheckCommand( |
| mockPackagesDir, mockFileSystem, |
| processRunner: processRunner, httpClient: mockClient); |
| |
| runner = CommandRunner<void>( |
| 'publish_check_command', |
| 'Test for publish-check command.', |
| ); |
| runner.addCommand(command); |
| |
| final Directory plugin1Dir = |
| createFakePlugin('no_publish_a', includeVersion: true); |
| final Directory plugin2Dir = |
| createFakePlugin('no_publish_b', includeVersion: true); |
| |
| createFakePubspec(plugin1Dir, |
| name: 'no_publish_a', includeVersion: true, version: '0.1.0'); |
| createFakePubspec(plugin2Dir, |
| name: 'no_publish_b', includeVersion: true, version: '0.2.0'); |
| |
| processRunner.processesToReturn.add( |
| MockProcess()..exitCodeCompleter.complete(0), |
| ); |
| final List<String> output = await runCapturingPrint( |
| runner, <String>['publish-check', '--machine']); |
| |
| // ignore: use_raw_strings |
| expect(output.first, ''' |
| { |
| "status": "no-publish", |
| "humanMessage": [ |
| "Checking that no_publish_a can be published.", |
| "Package no_publish_a version: 0.1.0 has already be published on pub.", |
| "Checking that no_publish_b can be published.", |
| "Package no_publish_b version: 0.2.0 has already be published on pub.", |
| "SUCCESS: All packages passed publish check!" |
| ] |
| }'''); |
| }); |
| |
| test( |
| '--machine: Log JSON with status:needs-publish and correct human message, if there is at least 1 plugin needs to be published.', |
| () async { |
| const Map<String, dynamic> httpResponseA = <String, dynamic>{ |
| 'name': 'a', |
| 'versions': <String>[ |
| '0.0.1', |
| '0.1.0', |
| ], |
| }; |
| |
| const Map<String, dynamic> httpResponseB = <String, dynamic>{ |
| 'name': 'b', |
| 'versions': <String>[ |
| '0.0.1', |
| '0.1.0', |
| ], |
| }; |
| |
| final MockClient mockClient = MockClient((http.Request request) async { |
| if (request.url.pathSegments.last == 'no_publish_a.json') { |
| return http.Response(json.encode(httpResponseA), 200); |
| } else if (request.url.pathSegments.last == 'no_publish_b.json') { |
| return http.Response(json.encode(httpResponseB), 200); |
| } |
| return null; |
| }); |
| final PublishCheckCommand command = PublishCheckCommand( |
| mockPackagesDir, mockFileSystem, |
| processRunner: processRunner, httpClient: mockClient); |
| |
| runner = CommandRunner<void>( |
| 'publish_check_command', |
| 'Test for publish-check command.', |
| ); |
| runner.addCommand(command); |
| |
| final Directory plugin1Dir = |
| createFakePlugin('no_publish_a', includeVersion: true); |
| final Directory plugin2Dir = |
| createFakePlugin('no_publish_b', includeVersion: true); |
| |
| createFakePubspec(plugin1Dir, |
| name: 'no_publish_a', includeVersion: true, version: '0.1.0'); |
| createFakePubspec(plugin2Dir, |
| name: 'no_publish_b', includeVersion: true, version: '0.2.0'); |
| |
| processRunner.processesToReturn.add( |
| MockProcess()..exitCodeCompleter.complete(0), |
| ); |
| |
| final List<String> output = await runCapturingPrint( |
| runner, <String>['publish-check', '--machine']); |
| |
| // ignore: use_raw_strings |
| expect(output.first, ''' |
| { |
| "status": "needs-publish", |
| "humanMessage": [ |
| "Checking that no_publish_a can be published.", |
| "Package no_publish_a version: 0.1.0 has already be published on pub.", |
| "Checking that no_publish_b can be published.", |
| "Package no_publish_b is able to be published.", |
| "SUCCESS: All packages passed publish check!" |
| ] |
| }'''); |
| }); |
| |
| test( |
| '--machine: Log correct JSON, if there is at least 1 plugin contains error.', |
| () async { |
| const Map<String, dynamic> httpResponseA = <String, dynamic>{ |
| 'name': 'a', |
| 'versions': <String>[ |
| '0.0.1', |
| '0.1.0', |
| ], |
| }; |
| |
| const Map<String, dynamic> httpResponseB = <String, dynamic>{ |
| 'name': 'b', |
| 'versions': <String>[ |
| '0.0.1', |
| '0.1.0', |
| ], |
| }; |
| |
| final MockClient mockClient = MockClient((http.Request request) async { |
| print('url ${request.url}'); |
| print(request.url.pathSegments.last); |
| if (request.url.pathSegments.last == 'no_publish_a.json') { |
| return http.Response(json.encode(httpResponseA), 200); |
| } else if (request.url.pathSegments.last == 'no_publish_b.json') { |
| return http.Response(json.encode(httpResponseB), 200); |
| } |
| return null; |
| }); |
| final PublishCheckCommand command = PublishCheckCommand( |
| mockPackagesDir, mockFileSystem, |
| processRunner: processRunner, httpClient: mockClient); |
| |
| runner = CommandRunner<void>( |
| 'publish_check_command', |
| 'Test for publish-check command.', |
| ); |
| runner.addCommand(command); |
| |
| final Directory plugin1Dir = |
| createFakePlugin('no_publish_a', includeVersion: true); |
| final Directory plugin2Dir = |
| createFakePlugin('no_publish_b', includeVersion: true); |
| |
| createFakePubspec(plugin1Dir, |
| name: 'no_publish_a', includeVersion: true, version: '0.1.0'); |
| createFakePubspec(plugin2Dir, |
| name: 'no_publish_b', includeVersion: true, version: '0.2.0'); |
| await plugin1Dir.childFile('pubspec.yaml').writeAsString('bad-yaml'); |
| |
| processRunner.processesToReturn.add( |
| MockProcess()..exitCodeCompleter.complete(0), |
| ); |
| |
| bool hasError = false; |
| final List<String> output = await runCapturingPrint( |
| runner, <String>['publish-check', '--machine'], |
| errorHandler: (Error error) { |
| expect(error, isA<ToolExit>()); |
| hasError = true; |
| }); |
| expect(hasError, isTrue); |
| |
| // ignore: use_raw_strings |
| expect(output.first, ''' |
| { |
| "status": "error", |
| "humanMessage": [ |
| "Checking that no_publish_a can be published.", |
| "Failed to parse `pubspec.yaml` at /packages/no_publish_a/pubspec.yaml: ParsedYamlException: line 1, column 1: Not a map\\n ╷\\n1 │ bad-yaml\\n │ ^^^^^^^^\\n ╵}", |
| "no pubspec", |
| "Checking that no_publish_b can be published.", |
| "url https://pub.dev/packages/no_publish_b.json", |
| "no_publish_b.json", |
| "Package no_publish_b is able to be published.", |
| "ERROR: The following 1 package(s) failed the publishing check:\\nMemoryDirectory: '/packages/no_publish_a'" |
| ] |
| }'''); |
| }); |
| }); |
| } |
| |
| class PublishCheckProcessRunner extends RecordingProcessRunner { |
| final Queue<MockProcess> processesToReturn = Queue<MockProcess>(); |
| |
| @override |
| io.Process get processToReturn => processesToReturn.removeFirst(); |
| } |