From 7a4e4ee834d123577a8c2e8f7175295ce283da99 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Wed, 2 Aug 2023 13:28:25 -0500 Subject: [PATCH] refactor(shorebird_cli): use scoped `ShorebirdValidator` (#996) --- packages/shorebird_cli/bin/shorebird.dart | 2 + .../account/downgrade_account_command.dart | 10 +- .../account/upgrade_account_command.dart | 8 +- .../commands/apps/create_apps_command.dart | 9 +- .../commands/apps/delete_apps_command.dart | 8 +- .../src/commands/apps/list_apps_command.dart | 8 +- .../src/commands/build/build_aar_command.dart | 6 +- .../src/commands/build/build_apk_command.dart | 7 +- .../build/build_app_bundle_command.dart | 7 +- .../src/commands/build/build_ipa_command.dart | 7 +- .../add_collaborators_command.dart | 8 +- .../delete_collaborators_command.dart | 8 +- .../list_collaborators_command.dart | 8 +- .../lib/src/commands/init_command.dart | 19 ++- .../src/commands/patch/patch_aar_command.dart | 10 +- .../commands/patch/patch_android_command.dart | 5 +- .../src/commands/patch/patch_ios_command.dart | 10 +- .../patch/patch_ios_framework_command.dart | 10 +- .../lib/src/commands/preview_command.dart | 10 +- .../commands/release/release_aar_command.dart | 5 +- .../release/release_android_command.dart | 5 +- .../commands/release/release_ios_command.dart | 10 +- .../release_ios_framework_command.dart | 6 +- .../releases/delete_releases_command.dart | 8 +- .../releases/list_releases_command.dart | 8 +- .../lib/src/commands/run_command.dart | 8 +- packages/shorebird_cli/lib/src/logger.dart | 4 +- .../lib/src/shorebird_config_mixin.dart | 23 +-- .../lib/src/shorebird_environment.dart | 23 +++ ...on_mixin.dart => shorebird_validator.dart} | 20 ++- .../downgrade_account_command_test.dart | 37 +++-- .../account/upgrade_account_command_test.dart | 47 ++++-- .../apps/create_apps_command_test.dart | 35 +++- .../apps/delete_apps_command_test.dart | 35 +++- .../commands/apps/list_apps_command_test.dart | 34 +++- .../build/build_aar_command_test.dart | 38 ++++- .../build/build_apk_command_test.dart | 99 ++++-------- .../build/build_app_bundle_command_test.dart | 96 +++++------ .../build/build_ipa_command_test.dart | 102 +++++------- .../add_collaborators_command_test.dart | 32 +++- .../delete_collaborators_command_test.dart | 32 +++- .../list_collaborators_command_test.dart | 32 +++- .../test/src/commands/init_command_test.dart | 31 +++- .../patch/patch_aar_command_test.dart | 52 +++--- .../patch/patch_android_command_test.dart | 104 ++++-------- .../patch/patch_ios_command_test.dart | 103 ++++-------- .../patch_ios_framework_command_test.dart | 113 ++++--------- .../src/commands/preview_command_test.dart | 37 ++++- .../release/release_aar_command_test.dart | 66 ++++---- .../release/release_android_command_test.dart | 118 +++++--------- .../release/release_ios_command_test.dart | 120 +++++--------- .../release_ios_framework_command_test.dart | 120 +++++--------- .../delete_releases_command_test.dart | 45 ++++-- .../releases/list_releases_command_test.dart | 46 ++++-- .../test/src/commands/run_command_test.dart | 56 +++---- .../test/src/shorebird_validator_test.dart | 150 ++++++++++++++++++ 56 files changed, 1082 insertions(+), 978 deletions(-) rename packages/shorebird_cli/lib/src/{shorebird_validation_mixin.dart => shorebird_validator.dart} (84%) create mode 100644 packages/shorebird_cli/test/src/shorebird_validator_test.dart diff --git a/packages/shorebird_cli/bin/shorebird.dart b/packages/shorebird_cli/bin/shorebird.dart index 1e83ec4d..8fcd0a5c 100644 --- a/packages/shorebird_cli/bin/shorebird.dart +++ b/packages/shorebird_cli/bin/shorebird.dart @@ -16,6 +16,7 @@ import 'package:shorebird_cli/src/java.dart'; import 'package:shorebird_cli/src/logger.dart'; import 'package:shorebird_cli/src/platform.dart'; import 'package:shorebird_cli/src/process.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_cli/src/shorebird_version_manager.dart'; import 'package:shorebird_cli/src/xcodebuild.dart'; @@ -39,6 +40,7 @@ Future main(List args) async { loggerRef, platformRef, processRef, + shorebirdValidatorRef, shorebirdVersionManagerRef, xcodeBuildRef, }, diff --git a/packages/shorebird_cli/lib/src/commands/account/downgrade_account_command.dart b/packages/shorebird_cli/lib/src/commands/account/downgrade_account_command.dart index 56f790b7..537baba8 100644 --- a/packages/shorebird_cli/lib/src/commands/account/downgrade_account_command.dart +++ b/packages/shorebird_cli/lib/src/commands/account/downgrade_account_command.dart @@ -5,13 +5,11 @@ import 'package:mason_logger/mason_logger.dart'; import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/logger.dart'; -import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; import 'package:shorebird_cli/src/shorebird_environment.dart'; -import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; -class DowngradeAccountCommand extends ShorebirdCommand - with ShorebirdConfigMixin, ShorebirdValidationMixin { +class DowngradeAccountCommand extends ShorebirdCommand { DowngradeAccountCommand({super.buildCodePushClient}); @override @@ -23,7 +21,9 @@ class DowngradeAccountCommand extends ShorebirdCommand @override Future run() async { try { - await validatePreconditions(checkUserIsAuthenticated: true); + await shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + ); } on PreconditionFailedException catch (e) { return e.exitCode.code; } diff --git a/packages/shorebird_cli/lib/src/commands/account/upgrade_account_command.dart b/packages/shorebird_cli/lib/src/commands/account/upgrade_account_command.dart index 71103f99..63e9fd22 100644 --- a/packages/shorebird_cli/lib/src/commands/account/upgrade_account_command.dart +++ b/packages/shorebird_cli/lib/src/commands/account/upgrade_account_command.dart @@ -4,16 +4,14 @@ import 'package:mason_logger/mason_logger.dart'; import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/logger.dart'; -import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; import 'package:shorebird_cli/src/shorebird_environment.dart'; -import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// {@template upgrade_account_command} /// `shorebird account upgrade` /// {@endtemplate} -class UpgradeAccountCommand extends ShorebirdCommand - with ShorebirdConfigMixin, ShorebirdValidationMixin { +class UpgradeAccountCommand extends ShorebirdCommand { /// {@macro upgrade_account_command} UpgradeAccountCommand({super.buildCodePushClient}); @@ -33,7 +31,7 @@ Please use $consoleLink instead.''', ); try { - await validatePreconditions( + await shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: true, ); } on PreconditionFailedException catch (e) { 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 8bdfa29a..f6539494 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 @@ -5,7 +5,7 @@ import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/logger.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; import 'package:shorebird_cli/src/shorebird_create_app_mixin.dart'; -import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// {@template create_app_command} @@ -14,10 +14,7 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// Create a new app on Shorebird. /// {@endtemplate} class CreateAppCommand extends ShorebirdCommand - with - ShorebirdConfigMixin, - ShorebirdValidationMixin, - ShorebirdCreateAppMixin { + with ShorebirdConfigMixin, ShorebirdCreateAppMixin { /// {@macro create_app_command} CreateAppCommand({super.buildCodePushClient}) { argParser.addOption( @@ -37,7 +34,7 @@ Defaults to the name in "pubspec.yaml".''', @override Future? run() async { try { - await validatePreconditions( + await shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: true, ); } on PreconditionFailedException catch (e) { 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 043f8a82..2dad04dc 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 @@ -4,17 +4,15 @@ import 'package:mason_logger/mason_logger.dart'; import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/logger.dart'; -import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; import 'package:shorebird_cli/src/shorebird_environment.dart'; -import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; /// {@template delete_app_command} /// /// `shorebird apps delete` /// Delete an existing app on Shorebird. /// {@endtemplate} -class DeleteAppCommand extends ShorebirdCommand - with ShorebirdConfigMixin, ShorebirdValidationMixin { +class DeleteAppCommand extends ShorebirdCommand { /// {@macro delete_app_command} DeleteAppCommand({super.buildCodePushClient}) { argParser @@ -48,7 +46,7 @@ Please use $consoleLink instead.''', ); try { - await validatePreconditions( + await shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: true, ); } on PreconditionFailedException catch (e) { 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 f5ad4f23..60d787af 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 @@ -5,8 +5,7 @@ import 'package:mason_logger/mason_logger.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/logger.dart'; -import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; -import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// {@template list_apps_command} @@ -14,8 +13,7 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// `shorebird apps list` /// List all apps using Shorebird. /// {@endtemplate} -class ListAppsCommand extends ShorebirdCommand - with ShorebirdConfigMixin, ShorebirdValidationMixin { +class ListAppsCommand extends ShorebirdCommand { @override String get description => 'List all apps using Shorebird.'; @@ -35,7 +33,7 @@ Please use $consoleLink instead.''', ); try { - await validatePreconditions( + await shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: true, ); } on PreconditionFailedException catch (e) { diff --git a/packages/shorebird_cli/lib/src/commands/build/build_aar_command.dart b/packages/shorebird_cli/lib/src/commands/build/build_aar_command.dart index 61cfb6cc..3b5bc0e0 100644 --- a/packages/shorebird_cli/lib/src/commands/build/build_aar_command.dart +++ b/packages/shorebird_cli/lib/src/commands/build/build_aar_command.dart @@ -7,7 +7,7 @@ import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/logger.dart'; import 'package:shorebird_cli/src/shorebird_build_mixin.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; -import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; /// {@template build_aar_command} /// @@ -15,7 +15,7 @@ import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; /// Build an Android aar file from your app. /// {@endtemplate} class BuildAarCommand extends ShorebirdCommand - with ShorebirdConfigMixin, ShorebirdValidationMixin, ShorebirdBuildMixin { + with ShorebirdConfigMixin, ShorebirdBuildMixin { BuildAarCommand() { // We would have a "target" option here, similar to what [BuildApkCommand] // and [BuildAabCommand] have, but target cannot currently be configured in @@ -39,7 +39,7 @@ class BuildAarCommand extends ShorebirdCommand @override Future run() async { try { - await validatePreconditions( + await shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: true, checkShorebirdInitialized: true, ); diff --git a/packages/shorebird_cli/lib/src/commands/build/build_apk_command.dart b/packages/shorebird_cli/lib/src/commands/build/build_apk_command.dart index 50fa81fd..9b8aa0cf 100644 --- a/packages/shorebird_cli/lib/src/commands/build/build_apk_command.dart +++ b/packages/shorebird_cli/lib/src/commands/build/build_apk_command.dart @@ -7,7 +7,7 @@ import 'package:shorebird_cli/src/doctor.dart'; import 'package:shorebird_cli/src/logger.dart'; import 'package:shorebird_cli/src/shorebird_build_mixin.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; -import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; /// {@template build_apk_command} /// @@ -15,7 +15,7 @@ import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; /// Build an Android APK file from your app. /// {@endtemplate} class BuildApkCommand extends ShorebirdCommand - with ShorebirdConfigMixin, ShorebirdValidationMixin, ShorebirdBuildMixin { + with ShorebirdConfigMixin, ShorebirdBuildMixin { /// {@macro build_apk_command} BuildApkCommand() { argParser @@ -39,8 +39,9 @@ class BuildApkCommand extends ShorebirdCommand @override Future run() async { try { - await validatePreconditions( + await shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, validators: doctor.androidCommandValidators, ); } on PreconditionFailedException catch (e) { diff --git a/packages/shorebird_cli/lib/src/commands/build/build_app_bundle_command.dart b/packages/shorebird_cli/lib/src/commands/build/build_app_bundle_command.dart index b7b30d4e..b67a9125 100644 --- a/packages/shorebird_cli/lib/src/commands/build/build_app_bundle_command.dart +++ b/packages/shorebird_cli/lib/src/commands/build/build_app_bundle_command.dart @@ -7,7 +7,7 @@ import 'package:shorebird_cli/src/doctor.dart'; import 'package:shorebird_cli/src/logger.dart'; import 'package:shorebird_cli/src/shorebird_build_mixin.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; -import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; /// {@template build_app_bundle_command} /// @@ -15,7 +15,7 @@ import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; /// Build an Android App Bundle file from your app. /// {@endtemplate} class BuildAppBundleCommand extends ShorebirdCommand - with ShorebirdConfigMixin, ShorebirdValidationMixin, ShorebirdBuildMixin { + with ShorebirdConfigMixin, ShorebirdBuildMixin { /// {@macro build_app_bundle_command} BuildAppBundleCommand() { argParser @@ -39,8 +39,9 @@ class BuildAppBundleCommand extends ShorebirdCommand @override Future run() async { try { - await validatePreconditions( + await shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, validators: doctor.androidCommandValidators, ); } on PreconditionFailedException catch (e) { diff --git a/packages/shorebird_cli/lib/src/commands/build/build_ipa_command.dart b/packages/shorebird_cli/lib/src/commands/build/build_ipa_command.dart index debbe382..d3282c10 100644 --- a/packages/shorebird_cli/lib/src/commands/build/build_ipa_command.dart +++ b/packages/shorebird_cli/lib/src/commands/build/build_ipa_command.dart @@ -7,7 +7,7 @@ import 'package:shorebird_cli/src/doctor.dart'; import 'package:shorebird_cli/src/logger.dart'; import 'package:shorebird_cli/src/shorebird_build_mixin.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; -import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; /// {@template build_ipa_command} /// `shorebird build ipa` @@ -15,7 +15,7 @@ import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; /// App Store submission. /// {@endtemplate} class BuildIpaCommand extends ShorebirdCommand - with ShorebirdConfigMixin, ShorebirdValidationMixin, ShorebirdBuildMixin { + with ShorebirdConfigMixin, ShorebirdBuildMixin { /// {@macro build_ipa_command} BuildIpaCommand() { argParser @@ -45,8 +45,9 @@ class BuildIpaCommand extends ShorebirdCommand @override Future run() async { try { - await validatePreconditions( + await shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, validators: doctor.iosCommandValidators, ); } on PreconditionFailedException catch (e) { diff --git a/packages/shorebird_cli/lib/src/commands/collaborators/add_collaborators_command.dart b/packages/shorebird_cli/lib/src/commands/collaborators/add_collaborators_command.dart index ce646d82..c6ba436c 100644 --- a/packages/shorebird_cli/lib/src/commands/collaborators/add_collaborators_command.dart +++ b/packages/shorebird_cli/lib/src/commands/collaborators/add_collaborators_command.dart @@ -4,16 +4,14 @@ import 'package:mason_logger/mason_logger.dart'; import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/logger.dart'; -import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; import 'package:shorebird_cli/src/shorebird_environment.dart'; -import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; /// {@template add_collaborators_command} /// `shorebird collaborators add` /// Add a new collaborator to a Shorebird app. /// {@endtemplate} -class AddCollaboratorsCommand extends ShorebirdCommand - with ShorebirdConfigMixin, ShorebirdValidationMixin { +class AddCollaboratorsCommand extends ShorebirdCommand { /// {@macro add_collaborators_command} AddCollaboratorsCommand({super.buildCodePushClient}) { argParser @@ -39,7 +37,7 @@ class AddCollaboratorsCommand extends ShorebirdCommand @override Future? run() async { try { - await validatePreconditions( + await shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: true, ); } on PreconditionFailedException catch (e) { diff --git a/packages/shorebird_cli/lib/src/commands/collaborators/delete_collaborators_command.dart b/packages/shorebird_cli/lib/src/commands/collaborators/delete_collaborators_command.dart index db6d10d1..5b46aa08 100644 --- a/packages/shorebird_cli/lib/src/commands/collaborators/delete_collaborators_command.dart +++ b/packages/shorebird_cli/lib/src/commands/collaborators/delete_collaborators_command.dart @@ -5,17 +5,15 @@ import 'package:mason_logger/mason_logger.dart'; import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/logger.dart'; -import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; import 'package:shorebird_cli/src/shorebird_environment.dart'; -import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// {@template delete_collaborators_command} /// `shorebird collaborators delete` /// Delete an existing collaborator from a Shorebird app. /// {@endtemplate} -class DeleteCollaboratorsCommand extends ShorebirdCommand - with ShorebirdConfigMixin, ShorebirdValidationMixin { +class DeleteCollaboratorsCommand extends ShorebirdCommand { /// {@macro delete_collaborators_command} DeleteCollaboratorsCommand({super.buildCodePushClient}) { argParser @@ -42,7 +40,7 @@ class DeleteCollaboratorsCommand extends ShorebirdCommand @override Future? run() async { try { - await validatePreconditions( + await shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: true, ); } on PreconditionFailedException catch (e) { diff --git a/packages/shorebird_cli/lib/src/commands/collaborators/list_collaborators_command.dart b/packages/shorebird_cli/lib/src/commands/collaborators/list_collaborators_command.dart index ba516ad4..db391a90 100644 --- a/packages/shorebird_cli/lib/src/commands/collaborators/list_collaborators_command.dart +++ b/packages/shorebird_cli/lib/src/commands/collaborators/list_collaborators_command.dart @@ -5,17 +5,15 @@ import 'package:mason_logger/mason_logger.dart'; import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/logger.dart'; -import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; import 'package:shorebird_cli/src/shorebird_environment.dart'; -import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// {@template list_collaborators_command} /// `shorebird collaborators list` /// List all collaborators for a Shorebird app. /// {@endtemplate} -class ListCollaboratorsCommand extends ShorebirdCommand - with ShorebirdConfigMixin, ShorebirdValidationMixin { +class ListCollaboratorsCommand extends ShorebirdCommand { /// {@macro list_collaborators_command} ListCollaboratorsCommand({super.buildCodePushClient}) { argParser.addOption( @@ -38,7 +36,7 @@ class ListCollaboratorsCommand extends ShorebirdCommand @override Future? run() async { try { - await validatePreconditions( + await shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: true, ); } on PreconditionFailedException catch (e) { diff --git a/packages/shorebird_cli/lib/src/commands/init_command.dart b/packages/shorebird_cli/lib/src/commands/init_command.dart index 511dca3e..09bebe5e 100644 --- a/packages/shorebird_cli/lib/src/commands/init_command.dart +++ b/packages/shorebird_cli/lib/src/commands/init_command.dart @@ -11,7 +11,7 @@ import 'package:shorebird_cli/src/platform.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; import 'package:shorebird_cli/src/shorebird_create_app_mixin.dart'; import 'package:shorebird_cli/src/shorebird_environment.dart'; -import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_cli/src/xcodebuild.dart'; /// {@template init_command} @@ -20,10 +20,7 @@ import 'package:shorebird_cli/src/xcodebuild.dart'; /// Initialize Shorebird. /// {@endtemplate} class InitCommand extends ShorebirdCommand - with - ShorebirdConfigMixin, - ShorebirdValidationMixin, - ShorebirdCreateAppMixin { + with ShorebirdConfigMixin, ShorebirdCreateAppMixin { /// {@macro init_command} InitCommand({super.buildCodePushClient}) { argParser.addFlag( @@ -43,7 +40,7 @@ class InitCommand extends ShorebirdCommand @override Future run() async { try { - await validatePreconditions( + await shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: true, ); } on PreconditionFailedException catch (e) { @@ -51,7 +48,7 @@ class InitCommand extends ShorebirdCommand } try { - if (!hasPubspecYaml) { + if (!ShorebirdEnvironment.hasPubspecYaml) { logger.err(''' Could not find a "pubspec.yaml". Please make sure you are running "shorebird init" from the root of your Flutter project. @@ -64,11 +61,11 @@ Please make sure you are running "shorebird init" from the root of your Flutter } final force = results['force'] == true; - if (force && hasShorebirdYaml) { + if (force && ShorebirdEnvironment.hasShorebirdYaml) { ShorebirdEnvironment.getShorebirdYamlFile().deleteSync(); } - if (hasShorebirdYaml) { + if (ShorebirdEnvironment.hasShorebirdYaml) { logger.err(''' A "shorebird.yaml" already exists. If you want to reinitialize Shorebird, please run "shorebird init --force".'''); @@ -141,7 +138,9 @@ If you want to reinitialize Shorebird, please run "shorebird init --force".'''); addShorebirdYamlToProject(appId, flavors: flavors); - if (!pubspecContainsShorebirdYaml) addShorebirdYamlToPubspecAssets(); + if (!ShorebirdEnvironment.pubspecContainsShorebirdYaml) { + addShorebirdYamlToPubspecAssets(); + } await doctor.runValidators(doctor.allValidators, applyFixes: true); diff --git a/packages/shorebird_cli/lib/src/commands/patch/patch_aar_command.dart b/packages/shorebird_cli/lib/src/commands/patch/patch_aar_command.dart index f1387914..95832c82 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/patch_aar_command.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/patch_aar_command.dart @@ -17,7 +17,7 @@ import 'package:shorebird_cli/src/shorebird_artifact_mixin.dart'; import 'package:shorebird_cli/src/shorebird_build_mixin.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; import 'package:shorebird_cli/src/shorebird_environment.dart'; -import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// {@template patch_aar_command} @@ -25,11 +25,7 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// Create a patch for an Android archive release. /// {@endtemplate} class PatchAarCommand extends ShorebirdCommand - with - ShorebirdConfigMixin, - ShorebirdValidationMixin, - ShorebirdBuildMixin, - ShorebirdArtifactMixin { + with ShorebirdConfigMixin, ShorebirdBuildMixin, ShorebirdArtifactMixin { /// {@macro patch_aar_command} PatchAarCommand({ HashFunction? hashFn, @@ -91,7 +87,7 @@ of the Android app that is using this module.''', @override Future run() async { try { - await validatePreconditions( + await shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: true, checkShorebirdInitialized: true, ); diff --git a/packages/shorebird_cli/lib/src/commands/patch/patch_android_command.dart b/packages/shorebird_cli/lib/src/commands/patch/patch_android_command.dart index 03124048..35fda9f3 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/patch_android_command.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/patch_android_command.dart @@ -17,7 +17,7 @@ import 'package:shorebird_cli/src/shorebird_build_mixin.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; import 'package:shorebird_cli/src/shorebird_environment.dart'; import 'package:shorebird_cli/src/shorebird_release_version_mixin.dart'; -import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// {@template patch_android_command} @@ -28,7 +28,6 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; class PatchAndroidCommand extends ShorebirdCommand with ShorebirdConfigMixin, - ShorebirdValidationMixin, ShorebirdBuildMixin, ShorebirdReleaseVersionMixin { /// {@macro patch_android_command} @@ -81,7 +80,7 @@ class PatchAndroidCommand extends ShorebirdCommand @override Future run() async { try { - await validatePreconditions( + await shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: true, checkShorebirdInitialized: true, validators: doctor.androidCommandValidators, diff --git a/packages/shorebird_cli/lib/src/commands/patch/patch_ios_command.dart b/packages/shorebird_cli/lib/src/commands/patch/patch_ios_command.dart index 2a0e85a5..b3698d2b 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/patch_ios_command.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/patch_ios_command.dart @@ -17,18 +17,14 @@ import 'package:shorebird_cli/src/shorebird_artifact_mixin.dart'; import 'package:shorebird_cli/src/shorebird_build_mixin.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; import 'package:shorebird_cli/src/shorebird_environment.dart'; -import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// {@template patch_ios_command} /// `shorebird patch ios-alpha` command. /// {@endtemplate} class PatchIosCommand extends ShorebirdCommand - with - ShorebirdConfigMixin, - ShorebirdBuildMixin, - ShorebirdValidationMixin, - ShorebirdArtifactMixin { + with ShorebirdConfigMixin, ShorebirdBuildMixin, ShorebirdArtifactMixin { /// {@macro patch_ios_command} PatchIosCommand({ HashFunction? hashFn, @@ -72,7 +68,7 @@ class PatchIosCommand extends ShorebirdCommand @override Future run() async { try { - await validatePreconditions( + await shorebirdValidator.validatePreconditions( checkShorebirdInitialized: true, checkUserIsAuthenticated: true, validators: doctor.iosCommandValidators, diff --git a/packages/shorebird_cli/lib/src/commands/patch/patch_ios_framework_command.dart b/packages/shorebird_cli/lib/src/commands/patch/patch_ios_framework_command.dart index 98d4bbfd..4d5507cf 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/patch_ios_framework_command.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/patch_ios_framework_command.dart @@ -14,15 +14,11 @@ import 'package:shorebird_cli/src/shorebird_artifact_mixin.dart'; import 'package:shorebird_cli/src/shorebird_build_mixin.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; import 'package:shorebird_cli/src/shorebird_environment.dart'; -import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; class PatchIosFrameworkCommand extends ShorebirdCommand - with - ShorebirdConfigMixin, - ShorebirdValidationMixin, - ShorebirdBuildMixin, - ShorebirdArtifactMixin { + with ShorebirdConfigMixin, ShorebirdBuildMixin, ShorebirdArtifactMixin { PatchIosFrameworkCommand({ HashFunction? hashFn, }) : _hashFn = hashFn ?? ((m) => sha256.convert(m).toString()) { @@ -60,7 +56,7 @@ of the iOS app that is using this module.''', @override Future run() async { try { - await validatePreconditions( + await shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: true, checkShorebirdInitialized: true, validators: doctor.iosCommandValidators, diff --git a/packages/shorebird_cli/lib/src/commands/preview_command.dart b/packages/shorebird_cli/lib/src/commands/preview_command.dart index 7a7ef692..ac94f2af 100644 --- a/packages/shorebird_cli/lib/src/commands/preview_command.dart +++ b/packages/shorebird_cli/lib/src/commands/preview_command.dart @@ -13,16 +13,14 @@ import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/ios_deploy.dart'; import 'package:shorebird_cli/src/logger.dart'; -import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; -import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_cli/src/third_party/flutter_tools/lib/flutter_tools.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// {@template preview_command} /// `shorebird preview` command. /// {@endtemplate} -class PreviewCommand extends ShorebirdCommand - with ShorebirdConfigMixin, ShorebirdValidationMixin { +class PreviewCommand extends ShorebirdCommand { /// {@macro preview_command} PreviewCommand() { argParser @@ -60,7 +58,9 @@ class PreviewCommand extends ShorebirdCommand // TODO(bryanoltman): check preview target and run either // doctor.iosValidators or doctor.androidValidators as appropriate. try { - await validatePreconditions(checkUserIsAuthenticated: true); + await shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + ); } on PreconditionFailedException catch (error) { return error.exitCode.code; } diff --git a/packages/shorebird_cli/lib/src/commands/release/release_aar_command.dart b/packages/shorebird_cli/lib/src/commands/release/release_aar_command.dart index 2eba6faa..0e76c02c 100644 --- a/packages/shorebird_cli/lib/src/commands/release/release_aar_command.dart +++ b/packages/shorebird_cli/lib/src/commands/release/release_aar_command.dart @@ -12,7 +12,7 @@ import 'package:shorebird_cli/src/shorebird_build_mixin.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; import 'package:shorebird_cli/src/shorebird_environment.dart'; import 'package:shorebird_cli/src/shorebird_release_version_mixin.dart'; -import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// {@template release_aar_command} @@ -22,7 +22,6 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; class ReleaseAarCommand extends ShorebirdCommand with ShorebirdConfigMixin, - ShorebirdValidationMixin, ShorebirdBuildMixin, ShorebirdReleaseVersionMixin, ShorebirdArtifactMixin { @@ -68,7 +67,7 @@ make smaller updates to your app. @override Future run() async { try { - await validatePreconditions( + await shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: true, checkShorebirdInitialized: true, ); diff --git a/packages/shorebird_cli/lib/src/commands/release/release_android_command.dart b/packages/shorebird_cli/lib/src/commands/release/release_android_command.dart index 67eea595..f7ce211f 100644 --- a/packages/shorebird_cli/lib/src/commands/release/release_android_command.dart +++ b/packages/shorebird_cli/lib/src/commands/release/release_android_command.dart @@ -11,7 +11,7 @@ import 'package:shorebird_cli/src/shorebird_build_mixin.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; import 'package:shorebird_cli/src/shorebird_environment.dart'; import 'package:shorebird_cli/src/shorebird_release_version_mixin.dart'; -import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// {@template release_android_command} @@ -21,7 +21,6 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; class ReleaseAndroidCommand extends ShorebirdCommand with ShorebirdConfigMixin, - ShorebirdValidationMixin, ShorebirdBuildMixin, ShorebirdReleaseVersionMixin { /// {@macro release_android_command} @@ -67,7 +66,7 @@ make smaller updates to your app. @override Future run() async { try { - await validatePreconditions( + await shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: true, checkShorebirdInitialized: true, validators: doctor.androidCommandValidators, diff --git a/packages/shorebird_cli/lib/src/commands/release/release_ios_command.dart b/packages/shorebird_cli/lib/src/commands/release/release_ios_command.dart index 16eef172..6c09315c 100644 --- a/packages/shorebird_cli/lib/src/commands/release/release_ios_command.dart +++ b/packages/shorebird_cli/lib/src/commands/release/release_ios_command.dart @@ -14,7 +14,7 @@ import 'package:shorebird_cli/src/shorebird_artifact_mixin.dart'; import 'package:shorebird_cli/src/shorebird_build_mixin.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; import 'package:shorebird_cli/src/shorebird_environment.dart'; -import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// {@template release_ios_command} @@ -22,11 +22,7 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// Create new app releases for iOS. /// {@endtemplate} class ReleaseIosCommand extends ShorebirdCommand - with - ShorebirdBuildMixin, - ShorebirdConfigMixin, - ShorebirdArtifactMixin, - ShorebirdValidationMixin { + with ShorebirdBuildMixin, ShorebirdConfigMixin, ShorebirdArtifactMixin { /// {@macro release_ios_command} ReleaseIosCommand({ IpaReader? ipaReader, @@ -64,7 +60,7 @@ make smaller updates to your app. @override Future run() async { try { - await validatePreconditions( + await shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: true, checkShorebirdInitialized: true, validators: doctor.iosCommandValidators, diff --git a/packages/shorebird_cli/lib/src/commands/release/release_ios_framework_command.dart b/packages/shorebird_cli/lib/src/commands/release/release_ios_framework_command.dart index a4a01820..c6a414f0 100644 --- a/packages/shorebird_cli/lib/src/commands/release/release_ios_framework_command.dart +++ b/packages/shorebird_cli/lib/src/commands/release/release_ios_framework_command.dart @@ -12,11 +12,11 @@ import 'package:shorebird_cli/src/logger.dart'; import 'package:shorebird_cli/src/shorebird_build_mixin.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; import 'package:shorebird_cli/src/shorebird_environment.dart'; -import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; class ReleaseIosFrameworkCommand extends ShorebirdCommand - with ShorebirdConfigMixin, ShorebirdValidationMixin, ShorebirdBuildMixin { + with ShorebirdConfigMixin, ShorebirdBuildMixin { ReleaseIosFrameworkCommand() { argParser ..addOption( @@ -44,7 +44,7 @@ of the iOS app that is using this module.''', @override Future run() async { try { - await validatePreconditions( + await shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: true, checkShorebirdInitialized: true, validators: doctor.iosCommandValidators, diff --git a/packages/shorebird_cli/lib/src/commands/releases/delete_releases_command.dart b/packages/shorebird_cli/lib/src/commands/releases/delete_releases_command.dart index 7f5631bc..f66f91b0 100644 --- a/packages/shorebird_cli/lib/src/commands/releases/delete_releases_command.dart +++ b/packages/shorebird_cli/lib/src/commands/releases/delete_releases_command.dart @@ -6,9 +6,8 @@ import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/config/shorebird_yaml.dart'; import 'package:shorebird_cli/src/logger.dart'; -import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; import 'package:shorebird_cli/src/shorebird_environment.dart'; -import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// {@template delete_releases_command} @@ -16,8 +15,7 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// `shorebird releases delete` /// Delete the specified release. /// {@endtemplate} -class DeleteReleasesCommand extends ShorebirdCommand - with ShorebirdConfigMixin, ShorebirdValidationMixin { +class DeleteReleasesCommand extends ShorebirdCommand { /// {@macro delete_releases_command} DeleteReleasesCommand({super.buildCodePushClient}) { argParser @@ -40,7 +38,7 @@ class DeleteReleasesCommand extends ShorebirdCommand @override Future run() async { try { - await validatePreconditions( + await shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: true, checkShorebirdInitialized: true, ); diff --git a/packages/shorebird_cli/lib/src/commands/releases/list_releases_command.dart b/packages/shorebird_cli/lib/src/commands/releases/list_releases_command.dart index d87deac8..a0084fc8 100644 --- a/packages/shorebird_cli/lib/src/commands/releases/list_releases_command.dart +++ b/packages/shorebird_cli/lib/src/commands/releases/list_releases_command.dart @@ -4,9 +4,8 @@ import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/config/shorebird_yaml.dart'; import 'package:shorebird_cli/src/logger.dart'; -import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; import 'package:shorebird_cli/src/shorebird_environment.dart'; -import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// {@template list_releases_command} @@ -14,8 +13,7 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// `shorebird releases list` /// List all releases for this app. /// {@endtemplate} -class ListReleasesCommand extends ShorebirdCommand - with ShorebirdConfigMixin, ShorebirdValidationMixin { +class ListReleasesCommand extends ShorebirdCommand { /// {@macro list_releases_command} ListReleasesCommand({super.buildCodePushClient}) { argParser.addOption( @@ -40,7 +38,7 @@ Please use $consoleLink instead.''', ); try { - await validatePreconditions( + await shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: true, checkShorebirdInitialized: true, ); diff --git a/packages/shorebird_cli/lib/src/commands/run_command.dart b/packages/shorebird_cli/lib/src/commands/run_command.dart index 1fa44bc3..fb6df6d2 100644 --- a/packages/shorebird_cli/lib/src/commands/run_command.dart +++ b/packages/shorebird_cli/lib/src/commands/run_command.dart @@ -6,15 +6,13 @@ import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/doctor.dart'; import 'package:shorebird_cli/src/logger.dart'; import 'package:shorebird_cli/src/process.dart'; -import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; -import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; /// {@template run_command} /// `shorebird run` /// Run the Flutter application. /// {@endtemplate} -class RunCommand extends ShorebirdCommand - with ShorebirdConfigMixin, ShorebirdValidationMixin { +class RunCommand extends ShorebirdCommand { /// {@macro run_command} RunCommand({super.buildCodePushClient}) { argParser @@ -63,7 +61,7 @@ Please use "shorebird preview" instead.''', // TODO(bryanoltman): check run target and run either // doctor.iosValidators or doctor.androidValidators as appropriate. try { - await validatePreconditions( + await shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: true, validators: doctor.allValidators, ); diff --git a/packages/shorebird_cli/lib/src/logger.dart b/packages/shorebird_cli/lib/src/logger.dart index f4032517..f09f7e54 100644 --- a/packages/shorebird_cli/lib/src/logger.dart +++ b/packages/shorebird_cli/lib/src/logger.dart @@ -1,8 +1,8 @@ import 'package:mason_logger/mason_logger.dart'; import 'package:scoped/scoped.dart'; -// A reference to a [Logger] instance. +/// A reference to a [Logger] instance. final loggerRef = create(Logger.new); -// The [Logger] instance available in the current zone. +/// The [Logger] instance available in the current zone. Logger get logger => read(loggerRef); diff --git a/packages/shorebird_cli/lib/src/shorebird_config_mixin.dart b/packages/shorebird_cli/lib/src/shorebird_config_mixin.dart index a83fc2e4..53842941 100644 --- a/packages/shorebird_cli/lib/src/shorebird_config_mixin.dart +++ b/packages/shorebird_cli/lib/src/shorebird_config_mixin.dart @@ -9,30 +9,11 @@ import 'package:yaml/yaml.dart'; import 'package:yaml_edit/yaml_edit.dart'; mixin ShorebirdConfigMixin on ShorebirdCommand { - bool get hasShorebirdYaml => - ShorebirdEnvironment.getShorebirdYamlFile().existsSync(); - - bool get hasPubspecYaml => ShorebirdEnvironment.getPubspecYaml() != null; - - bool get isShorebirdInitialized { - return hasShorebirdYaml && pubspecContainsShorebirdYaml; - } - - bool get pubspecContainsShorebirdYaml { - final file = File(p.join(Directory.current.path, 'pubspec.yaml')); - final pubspecContents = file.readAsStringSync(); - final yaml = loadYaml(pubspecContents, sourceUrl: file.uri) as Map; - if (!yaml.containsKey('flutter')) return false; - if (!(yaml['flutter'] as Map).containsKey('assets')) return false; - final assets = (yaml['flutter'] as Map)['assets'] as List; - return assets.contains('shorebird.yaml'); - } - /// Returns the Android package name from the pubspec.yaml file of a Flutter /// module. String? get androidPackageName { - final pubspec = ShorebirdEnvironment.getPubspecYaml()!; - final module = pubspec.flutter?['module'] as Map?; + final pubspec = ShorebirdEnvironment.getPubspecYaml(); + final module = pubspec?.flutter?['module'] as Map?; return module?['androidPackage'] as String?; } diff --git a/packages/shorebird_cli/lib/src/shorebird_environment.dart b/packages/shorebird_cli/lib/src/shorebird_environment.dart index 9cd7b0d3..e2f23a1a 100644 --- a/packages/shorebird_cli/lib/src/shorebird_environment.dart +++ b/packages/shorebird_cli/lib/src/shorebird_environment.dart @@ -8,6 +8,7 @@ import 'package:pubspec_parse/pubspec_parse.dart'; import 'package:shorebird_cli/src/config/shorebird_yaml.dart'; import 'package:shorebird_cli/src/platform.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; +import 'package:yaml/yaml.dart'; abstract class ShorebirdEnvironment { /// Environment variables from [Platform.environment]. @@ -95,6 +96,28 @@ abstract class ShorebirdEnvironment { return checkedYamlDecode(yaml, (m) => ShorebirdYaml.fromJson(m!)); } + static bool get isShorebirdInitialized { + return hasShorebirdYaml && pubspecContainsShorebirdYaml; + } + + static bool get hasShorebirdYaml { + return ShorebirdEnvironment.getShorebirdYamlFile().existsSync(); + } + + static bool get hasPubspecYaml { + return ShorebirdEnvironment.getPubspecYaml() != null; + } + + static bool get pubspecContainsShorebirdYaml { + final file = File(p.join(Directory.current.path, 'pubspec.yaml')); + final pubspecContents = file.readAsStringSync(); + final yaml = loadYaml(pubspecContents, sourceUrl: file.uri) as Map; + if (!yaml.containsKey('flutter')) return false; + if (!(yaml['flutter'] as Map).containsKey('assets')) return false; + final assets = (yaml['flutter'] as Map)['assets'] as List; + return assets.contains('shorebird.yaml'); + } + /// The `pubspec.yaml` file for this project, parsed into a [Pubspec] object. /// /// Returns `null` if the file does not exist. diff --git a/packages/shorebird_cli/lib/src/shorebird_validation_mixin.dart b/packages/shorebird_cli/lib/src/shorebird_validator.dart similarity index 84% rename from packages/shorebird_cli/lib/src/shorebird_validation_mixin.dart rename to packages/shorebird_cli/lib/src/shorebird_validator.dart index 1b181dfa..3e677bab 100644 --- a/packages/shorebird_cli/lib/src/shorebird_validation_mixin.dart +++ b/packages/shorebird_cli/lib/src/shorebird_validator.dart @@ -1,9 +1,10 @@ import 'package:collection/collection.dart'; import 'package:mason_logger/mason_logger.dart'; +import 'package:scoped/scoped.dart'; import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/logger.dart'; import 'package:shorebird_cli/src/platform.dart'; -import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_environment.dart'; import 'package:shorebird_cli/src/validators/validators.dart'; abstract interface class PreconditionFailedException implements Exception { @@ -31,7 +32,19 @@ class UnsupportedOperatingSystemException ExitCode get exitCode => ExitCode.unavailable; } -mixin ShorebirdValidationMixin on ShorebirdConfigMixin { +/// A reference to a [ShorebirdValidator] instance. +final shorebirdValidatorRef = create(ShorebirdValidator.new); + +/// The [ShorebirdValidator] instance available in the current zone. +ShorebirdValidator get shorebirdValidator => read(shorebirdValidatorRef); + +/// {@template shorebird_validator} +/// A class that provides common validation functionality for commands. +/// {@endtemplate} +class ShorebirdValidator { + /// {@macro shorebird_validator} + const ShorebirdValidator(); + /// Checks common preconditions for running a command and throws an /// appropriate [PreconditionFailedException] if any of them fail. Future validatePreconditions({ @@ -60,7 +73,8 @@ mixin ShorebirdValidationMixin on ShorebirdConfigMixin { throw UserNotAuthorizedException(); } - if (checkShorebirdInitialized && !isShorebirdInitialized) { + if (checkShorebirdInitialized && + !ShorebirdEnvironment.isShorebirdInitialized) { logger.err( 'Shorebird is not initialized. Did you run "shorebird init"?', ); diff --git a/packages/shorebird_cli/test/src/commands/account/downgrade_account_command_test.dart b/packages/shorebird_cli/test/src/commands/account/downgrade_account_command_test.dart index d72bf5fb..0948c427 100644 --- a/packages/shorebird_cli/test/src/commands/account/downgrade_account_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/account/downgrade_account_command_test.dart @@ -5,6 +5,7 @@ import 'package:scoped/scoped.dart'; import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/commands/account/account.dart'; import 'package:shorebird_cli/src/logger.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; @@ -18,6 +19,8 @@ class _MockLogger extends Mock implements Logger {} class _MockProgress extends Mock implements Progress {} +class _MockShorebirdValidator extends Mock implements ShorebirdValidator {} + void main() { group(DowngradeAccountCommand, () { const noSubscriptionUser = User(id: 1, email: 'tester1@shorebird.dev'); @@ -32,6 +35,7 @@ void main() { late http.Client httpClient; late Logger logger; late Progress progress; + late ShorebirdValidator shorebirdValidator; late DowngradeAccountCommand command; R runWithOverrides(R Function() body) { @@ -39,7 +43,8 @@ void main() { body, values: { authRef.overrideWith(() => auth), - loggerRef.overrideWith(() => logger) + loggerRef.overrideWith(() => logger), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), }, ); } @@ -50,10 +55,15 @@ void main() { httpClient = _MockHttpClient(); logger = _MockLogger(); progress = _MockProgress(); + shorebirdValidator = _MockShorebirdValidator(); when(() => auth.client).thenReturn(httpClient); - when(() => logger.progress(any())).thenReturn(progress); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + ), + ).thenAnswer((_) async {}); command = runWithOverrides( () => DowngradeAccountCommand( @@ -67,18 +77,25 @@ void main() { ); }); - test('returns a non-empty description', () { + test('has a description', () { expect(command.description, isNotEmpty); }); - test('prints an error if the user is not logged in', () async { - when(() => auth.isAuthenticated).thenReturn(false); - - final result = await runWithOverrides(command.run); - - expect(result, ExitCode.noUser.code); + test('exits when validation fails', () async { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + ), + ).thenThrow(exception); + await expectLater( + runWithOverrides(command.run), + completion(equals(exception.exitCode.code)), + ); verify( - () => logger.err(any(that: contains('You must be logged in'))), + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + ), ).called(1); }); diff --git a/packages/shorebird_cli/test/src/commands/account/upgrade_account_command_test.dart b/packages/shorebird_cli/test/src/commands/account/upgrade_account_command_test.dart index c0ba4ad8..0214a6a4 100644 --- a/packages/shorebird_cli/test/src/commands/account/upgrade_account_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/account/upgrade_account_command_test.dart @@ -5,6 +5,7 @@ import 'package:scoped/scoped.dart'; import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/commands/account/account.dart'; import 'package:shorebird_cli/src/logger.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; @@ -18,6 +19,8 @@ class _MockLogger extends Mock implements Logger {} class _MockProgress extends Mock implements Progress {} +class _MockShorebirdValidator extends Mock implements ShorebirdValidator {} + class _MockUser extends Mock implements User {} void main() { @@ -28,8 +31,8 @@ void main() { late http.Client httpClient; late Logger logger; late Progress progress; + late ShorebirdValidator shorebirdValidator; late User user; - late UpgradeAccountCommand command; group(UpgradeAccountCommand, () { @@ -38,7 +41,8 @@ void main() { body, values: { authRef.overrideWith(() => auth), - loggerRef.overrideWith(() => logger) + loggerRef.overrideWith(() => logger), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), }, ); } @@ -49,19 +53,27 @@ void main() { httpClient = _MockHttpClient(); logger = _MockLogger(); progress = _MockProgress(); + shorebirdValidator = _MockShorebirdValidator(); user = _MockUser(); when(() => auth.client).thenReturn(httpClient); when(() => auth.isAuthenticated).thenReturn(true); - when(() => codePushClient.createPaymentLink()) - .thenAnswer((_) async => paymentLink); + when( + () => codePushClient.createPaymentLink(), + ).thenAnswer((_) async => paymentLink); when(() => codePushClient.getCurrentUser()).thenAnswer((_) async => user); when(() => logger.err(any())).thenReturn(null); when(() => logger.info(any())).thenReturn(null); when(() => logger.progress(any())).thenReturn(progress); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + ), + ).thenAnswer((_) async {}); + when(() => user.hasActiveSubscription).thenReturn(false); command = runWithOverrides( @@ -77,23 +89,30 @@ void main() { expect(command.description, isNotEmpty); }); - test('exits with code 67 when user is not logged in', () async { - when(() => auth.isAuthenticated).thenReturn(false); - - final result = await runWithOverrides(command.run); - - expect(result, ExitCode.noUser.code); - + test('exits when validation fails', () async { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + ), + ).thenThrow(exception); + await expectLater( + runWithOverrides(command.run), + completion(equals(exception.exitCode.code)), + ); verify( - () => logger.err(any(that: contains('You must be logged in to run'))), + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + ), ).called(1); verifyNever(() => codePushClient.createPaymentLink()); }); test('exits with code 70 when getCurrentUser throws an exception', () async { - when(() => codePushClient.getCurrentUser()) - .thenThrow(Exception('oh no!')); + when( + () => codePushClient.getCurrentUser(), + ).thenThrow(Exception('oh no!')); final result = await runWithOverrides(command.run); 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 b291b6e3..9715b89b 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 @@ -6,6 +6,7 @@ import 'package:scoped/scoped.dart'; import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/commands/commands.dart'; import 'package:shorebird_cli/src/logger.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; @@ -19,6 +20,8 @@ class _MockCodePushClient extends Mock implements CodePushClient {} class _MockLogger extends Mock implements Logger {} +class _MockShorebirdValidator extends Mock implements ShorebirdValidator {} + void main() { group(CreateAppCommand, () { const appId = 'app-id'; @@ -29,6 +32,7 @@ void main() { late Auth auth; late Logger logger; late CodePushClient codePushClient; + late ShorebirdValidator shorebirdValidator; late CreateAppCommand command; R runWithOverrides(R Function() body) { @@ -36,7 +40,8 @@ void main() { body, values: { authRef.overrideWith(() => auth), - loggerRef.overrideWith(() => logger) + loggerRef.overrideWith(() => logger), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), }, ); } @@ -47,9 +52,15 @@ void main() { auth = _MockAuth(); logger = _MockLogger(); codePushClient = _MockCodePushClient(); + shorebirdValidator = _MockShorebirdValidator(); when(() => auth.client).thenReturn(httpClient); when(() => auth.isAuthenticated).thenReturn(true); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + ), + ).thenAnswer((_) async {}); command = runWithOverrides( () => CreateAppCommand( @@ -63,14 +74,26 @@ void main() { )..testArgResults = argResults; }); - test('returns correct description', () { + test('has a description', () { expect(command.description, equals('Create a new app on Shorebird.')); }); - test('returns no user error when not logged in', () async { - when(() => auth.isAuthenticated).thenReturn(false); - final result = await runWithOverrides(command.run); - expect(result, ExitCode.noUser.code); + test('exits when validation fails', () async { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + ), + ).thenThrow(exception); + await expectLater( + runWithOverrides(command.run), + completion(equals(exception.exitCode.code)), + ); + verify( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + ), + ).called(1); }); test('prompts for app name when not provided', () async { 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 1e48ff1c..d2bc0c2e 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 @@ -6,6 +6,7 @@ import 'package:scoped/scoped.dart'; import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/commands/commands.dart'; import 'package:shorebird_cli/src/logger.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; @@ -19,6 +20,8 @@ class _MockCodePushClient extends Mock implements CodePushClient {} class _MockLogger extends Mock implements Logger {} +class _MockShorebirdValidator extends Mock implements ShorebirdValidator {} + void main() { group(DeleteAppCommand, () { const appId = 'example'; @@ -28,6 +31,7 @@ void main() { late Auth auth; late Logger logger; late CodePushClient codePushClient; + late ShorebirdValidator shorebirdValidator; late DeleteAppCommand command; R runWithOverrides(R Function() body) { @@ -35,7 +39,8 @@ void main() { body, values: { authRef.overrideWith(() => auth), - loggerRef.overrideWith(() => logger) + loggerRef.overrideWith(() => logger), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), }, ); } @@ -46,9 +51,15 @@ void main() { auth = _MockAuth(); logger = _MockLogger(); codePushClient = _MockCodePushClient(); + shorebirdValidator = _MockShorebirdValidator(); when(() => auth.isAuthenticated).thenReturn(true); when(() => auth.client).thenReturn(httpClient); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + ), + ).thenAnswer((_) async {}); command = runWithOverrides( () => DeleteAppCommand( @@ -62,17 +73,29 @@ void main() { )..testArgResults = argResults; }); - test('returns correct description', () { + test('has a description', () { expect( command.description, equals('Delete an existing app on Shorebird.'), ); }); - test('returns no user error when not logged in', () async { - when(() => auth.isAuthenticated).thenReturn(false); - final result = await runWithOverrides(command.run); - expect(result, ExitCode.noUser.code); + test('exits when validation fails', () async { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + ), + ).thenThrow(exception); + await expectLater( + runWithOverrides(command.run), + completion(equals(exception.exitCode.code)), + ); + verify( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + ), + ).called(1); }); test('prompts for app-id when not provided', () async { 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 0753fb65..961d2076 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 @@ -5,6 +5,7 @@ import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/commands/commands.dart'; import 'package:shorebird_cli/src/logger.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; @@ -15,11 +16,14 @@ class _MockCodePushClientWrapper extends Mock class _MockLogger extends Mock implements Logger {} +class _MockShorebirdValidator extends Mock implements ShorebirdValidator {} + void main() { group(ListAppsCommand, () { late Auth auth; late CodePushClientWrapper codePushClientWrapper; late Logger logger; + late ShorebirdValidator shorebirdValidator; late ListAppsCommand command; R runWithOverrides(R Function() body) { @@ -28,7 +32,8 @@ void main() { values: { authRef.overrideWith(() => auth), codePushClientWrapperRef.overrideWith(() => codePushClientWrapper), - loggerRef.overrideWith(() => logger) + loggerRef.overrideWith(() => logger), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), }, ); } @@ -37,18 +42,37 @@ void main() { auth = _MockAuth(); codePushClientWrapper = _MockCodePushClientWrapper(); logger = _MockLogger(); + shorebirdValidator = _MockShorebirdValidator(); command = ListAppsCommand(); when(() => auth.isAuthenticated).thenReturn(true); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + ), + ).thenAnswer((_) async {}); }); - test('description is correct', () { + test('has a description', () { expect(command.description, equals('List all apps using Shorebird.')); }); - test('returns ExitCode.noUser when not logged in', () async { - when(() => auth.isAuthenticated).thenReturn(false); - expect(await runWithOverrides(command.run), ExitCode.noUser.code); + test('exits when validation fails', () async { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + ), + ).thenThrow(exception); + await expectLater( + runWithOverrides(command.run), + completion(equals(exception.exitCode.code)), + ); + verify( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + ), + ).called(1); }); test('returns ExitCode.success when apps are empty', () async { diff --git a/packages/shorebird_cli/test/src/commands/build/build_aar_command_test.dart b/packages/shorebird_cli/test/src/commands/build/build_aar_command_test.dart index d002f9a1..d340a7e7 100644 --- a/packages/shorebird_cli/test/src/commands/build/build_aar_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/build/build_aar_command_test.dart @@ -10,6 +10,7 @@ import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/commands/build/build.dart'; import 'package:shorebird_cli/src/logger.dart'; import 'package:shorebird_cli/src/process.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:test/test.dart'; class _MockArgResults extends Mock implements ArgResults {} @@ -26,6 +27,8 @@ class _MockProgress extends Mock implements Progress {} class _MockShorebirdProcess extends Mock implements ShorebirdProcess {} +class _MockShorebirdValidator extends Mock implements ShorebirdValidator {} + void main() { group(BuildAarCommand, () { const appId = 'test-app-id'; @@ -61,6 +64,7 @@ flutter: late Progress progress; late ShorebirdProcess shorebirdProcess; late ShorebirdProcessResult processResult; + late ShorebirdValidator shorebirdValidator; late BuildAarCommand command; R runWithOverrides(R Function() body) { @@ -70,6 +74,7 @@ flutter: authRef.overrideWith(() => auth), loggerRef.overrideWith(() => logger), processRef.overrideWith(() => shorebirdProcess), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), }, ); } @@ -95,6 +100,7 @@ flutter: processResult = _MockProcessResult(); progress = _MockProgress(); shorebirdProcess = _MockShorebirdProcess(); + shorebirdValidator = _MockShorebirdValidator(); when(() => argResults['build-number']).thenReturn(buildNumber); when(() => argResults.rest).thenReturn([]); @@ -112,22 +118,38 @@ flutter: return processResult; }); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'), + ), + ).thenAnswer((_) async {}); + command = runWithOverrides(BuildAarCommand.new) ..testArgResults = argResults; }); - test('has correct description', () { + test('has a description', () { expect(command.description, isNotEmpty); }); - test('exits with no user when not logged in', () async { - when(() => auth.isAuthenticated).thenReturn(false); - - final result = await runWithOverrides(command.run); - expect(result, equals(ExitCode.noUser.code)); - + test('exits when validation fails', () async { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'), + ), + ).thenThrow(exception); + await expectLater( + runWithOverrides(command.run), + completion(equals(exception.exitCode.code)), + ); verify( - () => logger.err(any(that: contains('You must be logged in to run'))), + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, + ), ).called(1); }); diff --git a/packages/shorebird_cli/test/src/commands/build/build_apk_command_test.dart b/packages/shorebird_cli/test/src/commands/build/build_apk_command_test.dart index 61f2737d..3be11238 100644 --- a/packages/shorebird_cli/test/src/commands/build/build_apk_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/build/build_apk_command_test.dart @@ -11,6 +11,7 @@ import 'package:shorebird_cli/src/commands/build/build.dart'; import 'package:shorebird_cli/src/doctor.dart'; import 'package:shorebird_cli/src/logger.dart'; import 'package:shorebird_cli/src/process.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_cli/src/validators/validators.dart'; import 'package:test/test.dart'; @@ -33,6 +34,8 @@ class _MockShorebirdFlutterValidator extends Mock class _MockShorebirdProcess extends Mock implements ShorebirdProcess {} +class _MockShorebirdValidator extends Mock implements ShorebirdValidator {} + void main() { group(BuildApkCommand, () { late ArgResults argResults; @@ -44,6 +47,7 @@ void main() { late BuildApkCommand command; late ShorebirdFlutterValidator flutterValidator; late ShorebirdProcess shorebirdProcess; + late ShorebirdValidator shorebirdValidator; R runWithOverrides(R Function() body) { return runScoped( @@ -53,6 +57,7 @@ void main() { doctorRef.overrideWith(() => doctor), loggerRef.overrideWith(() => logger), processRef.overrideWith(() => shorebirdProcess), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), }, ); } @@ -66,6 +71,7 @@ void main() { shorebirdProcess = _MockShorebirdProcess(); processResult = _MockProcessResult(); flutterValidator = _MockShorebirdFlutterValidator(); + shorebirdValidator = _MockShorebirdValidator(); registerFallbackValue(shorebirdProcess); @@ -81,26 +87,44 @@ void main() { when(() => auth.client).thenReturn(httpClient); when(() => logger.progress(any())).thenReturn(_MockProgress()); when(() => logger.info(any())).thenReturn(null); - when(() => doctor.androidCommandValidators) - .thenReturn([flutterValidator]); - when(flutterValidator.validate).thenAnswer((_) async => []); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'), + validators: any(named: 'validators'), + ), + ).thenAnswer((_) async {}); + when( + () => doctor.androidCommandValidators, + ).thenReturn([flutterValidator]); command = runWithOverrides(BuildApkCommand.new) ..testArgResults = argResults; }); - test('has correct description', () { + test('has a description', () { expect(command.description, isNotEmpty); }); - test('exits with no user when not logged in', () async { - when(() => auth.isAuthenticated).thenReturn(false); - - final result = await runWithOverrides(command.run); - expect(result, equals(ExitCode.noUser.code)); - + test('exits when validation fails', () async { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'), + validators: any(named: 'validators'), + ), + ).thenThrow(exception); + await expectLater( + runWithOverrides(command.run), + completion(equals(exception.exitCode.code)), + ); verify( - () => logger.err(any(that: contains('You must be logged in to run'))), + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, + validators: [flutterValidator], + ), ).called(1); }); @@ -187,58 +211,5 @@ ${lightCyan.wrap(p.join('build', 'app', 'outputs', 'apk', flavor, 'release', 'ap ), ).called(1); }); - - test('prints flutter validation warnings', () async { - when(flutterValidator.validate).thenAnswer( - (_) async => [ - const ValidationIssue( - severity: ValidationIssueSeverity.warning, - message: 'Flutter issue 1', - ), - const ValidationIssue( - severity: ValidationIssueSeverity.warning, - message: 'Flutter issue 2', - ), - ], - ); - when(() => processResult.exitCode).thenReturn(ExitCode.success.code); - - final result = await runWithOverrides(command.run); - - expect(result, equals(ExitCode.success.code)); - verify( - () => logger.info(any(that: contains('Flutter issue 1'))), - ).called(1); - verify( - () => logger.info(any(that: contains('Flutter issue 2'))), - ).called(1); - }); - - test('aborts if validation errors are present', () async { - when(flutterValidator.validate).thenAnswer( - (_) async => [ - ValidationIssue( - severity: ValidationIssueSeverity.error, - message: 'There was an issue', - fix: () async {}, - ), - ], - ); - - final result = await runWithOverrides(command.run); - - expect(result, equals(ExitCode.config.code)); - verify(() => logger.err('Aborting due to validation errors.')).called(1); - verify( - () => logger.info( - any( - that: stringContainsInOrder([ - 'issue can be fixed automatically', - 'shorebird doctor --fix', - ]), - ), - ), - ).called(1); - }); }); } diff --git a/packages/shorebird_cli/test/src/commands/build/build_app_bundle_command_test.dart b/packages/shorebird_cli/test/src/commands/build/build_app_bundle_command_test.dart index b510e91d..606c4f1f 100644 --- a/packages/shorebird_cli/test/src/commands/build/build_app_bundle_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/build/build_app_bundle_command_test.dart @@ -11,6 +11,7 @@ import 'package:shorebird_cli/src/commands/build/build.dart'; import 'package:shorebird_cli/src/doctor.dart'; import 'package:shorebird_cli/src/logger.dart'; import 'package:shorebird_cli/src/process.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_cli/src/validators/validators.dart'; import 'package:test/test.dart'; @@ -33,6 +34,10 @@ class _MockShorebirdFlutterValidator extends Mock class _MockShorebirdProcess extends Mock implements ShorebirdProcess {} +class _MockShorebirdValidator extends Mock implements ShorebirdValidator {} + +class _FakeShorebirdProcess extends Fake implements ShorebirdProcess {} + void main() { group(BuildAppBundleCommand, () { late ArgResults argResults; @@ -44,6 +49,7 @@ void main() { late BuildAppBundleCommand command; late ShorebirdFlutterValidator flutterValidator; late ShorebirdProcess shorebirdProcess; + late ShorebirdValidator shorebirdValidator; R runWithOverrides(R Function() body) { return runScoped( @@ -54,10 +60,15 @@ void main() { engineConfigRef.overrideWith(() => const EngineConfig.empty()), loggerRef.overrideWith(() => logger), processRef.overrideWith(() => shorebirdProcess), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), }, ); } + setUpAll(() { + registerFallbackValue(_FakeShorebirdProcess()); + }); + setUp(() { argResults = _MockArgResults(); doctor = _MockDoctor(); @@ -67,8 +78,7 @@ void main() { processResult = _MockProcessResult(); flutterValidator = _MockShorebirdFlutterValidator(); shorebirdProcess = _MockShorebirdProcess(); - - registerFallbackValue(shorebirdProcess); + shorebirdValidator = _MockShorebirdValidator(); when( () => shorebirdProcess.run( @@ -82,26 +92,44 @@ void main() { when(() => auth.client).thenReturn(httpClient); when(() => logger.progress(any())).thenReturn(_MockProgress()); when(() => logger.info(any())).thenReturn(null); - when(flutterValidator.validate).thenAnswer((_) async => []); - when(() => doctor.androidCommandValidators) - .thenReturn([flutterValidator]); + when( + () => doctor.androidCommandValidators, + ).thenReturn([flutterValidator]); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'), + validators: any(named: 'validators'), + ), + ).thenAnswer((_) async {}); command = runWithOverrides(BuildAppBundleCommand.new) ..testArgResults = argResults; }); - test('has correct description', () { + test('has a description', () { expect(command.description, isNotEmpty); }); - test('exits with no user when not logged in', () async { - when(() => auth.isAuthenticated).thenReturn(false); - - final result = await runWithOverrides(command.run); - expect(result, equals(ExitCode.noUser.code)); - + test('exits when validation fails', () async { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'), + validators: any(named: 'validators'), + ), + ).thenThrow(exception); + await expectLater( + runWithOverrides(command.run), + completion(equals(exception.exitCode.code)), + ); verify( - () => logger.err(any(that: contains('You must be logged in to run'))), + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, + validators: [flutterValidator], + ), ).called(1); }); @@ -226,47 +254,5 @@ ${lightCyan.wrap(p.join('build', 'app', 'outputs', 'bundle', '${flavor}Release', throwsException, ); }); - - test('prints flutter validation warnings', () async { - when(flutterValidator.validate).thenAnswer( - (_) async => [ - const ValidationIssue( - severity: ValidationIssueSeverity.warning, - message: 'Flutter issue 1', - ), - const ValidationIssue( - severity: ValidationIssueSeverity.warning, - message: 'Flutter issue 2', - ), - ], - ); - when(() => processResult.exitCode).thenReturn(ExitCode.success.code); - - final result = await runWithOverrides(command.run); - - expect(result, equals(ExitCode.success.code)); - verify( - () => logger.info(any(that: contains('Flutter issue 1'))), - ).called(1); - verify( - () => logger.info(any(that: contains('Flutter issue 2'))), - ).called(1); - }); - - test('aborts if validation errors are present', () async { - when(flutterValidator.validate).thenAnswer( - (_) async => [ - const ValidationIssue( - severity: ValidationIssueSeverity.error, - message: 'There was an issue', - ), - ], - ); - - final result = await runWithOverrides(command.run); - - expect(result, equals(ExitCode.config.code)); - verify(() => logger.err('Aborting due to validation errors.')).called(1); - }); }); } diff --git a/packages/shorebird_cli/test/src/commands/build/build_ipa_command_test.dart b/packages/shorebird_cli/test/src/commands/build/build_ipa_command_test.dart index e2b7b28d..6950ab6a 100644 --- a/packages/shorebird_cli/test/src/commands/build/build_ipa_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/build/build_ipa_command_test.dart @@ -12,6 +12,7 @@ import 'package:shorebird_cli/src/commands/build/build.dart'; import 'package:shorebird_cli/src/doctor.dart'; import 'package:shorebird_cli/src/logger.dart'; import 'package:shorebird_cli/src/process.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_cli/src/validators/validators.dart'; import 'package:test/test.dart'; @@ -34,6 +35,10 @@ class _MockShorebirdFlutterValidator extends Mock class _MockShorebirdProcess extends Mock implements ShorebirdProcess {} +class _MockShorebirdValidator extends Mock implements ShorebirdValidator {} + +class _FakeShorebirdProcess extends Fake implements ShorebirdProcess {} + void main() { group(BuildIpaCommand, () { late ArgResults argResults; @@ -45,6 +50,7 @@ void main() { late BuildIpaCommand command; late ShorebirdFlutterValidator flutterValidator; late ShorebirdProcess shorebirdProcess; + late ShorebirdValidator shorebirdValidator; R runWithOverrides(R Function() body) { return runScoped( @@ -54,10 +60,15 @@ void main() { doctorRef.overrideWith(() => doctor), loggerRef.overrideWith(() => logger), processRef.overrideWith(() => shorebirdProcess), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), }, ); } + setUpAll(() { + registerFallbackValue(_FakeShorebirdProcess()); + }); + setUp(() { argResults = _MockArgResults(); httpClient = _MockHttpClient(); @@ -67,8 +78,7 @@ void main() { shorebirdProcess = _MockShorebirdProcess(); processResult = _MockProcessResult(); flutterValidator = _MockShorebirdFlutterValidator(); - - registerFallbackValue(shorebirdProcess); + shorebirdValidator = _MockShorebirdValidator(); when( () => shorebirdProcess.run( @@ -84,24 +94,41 @@ void main() { when(() => logger.progress(any())).thenReturn(_MockProgress()); when(() => logger.info(any())).thenReturn(null); when(() => doctor.iosCommandValidators).thenReturn([flutterValidator]); - when(flutterValidator.validate).thenAnswer((_) async => []); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'), + validators: any(named: 'validators'), + ), + ).thenAnswer((_) async {}); command = runWithOverrides(BuildIpaCommand.new) ..testArgResults = argResults; }); - test('has correct description', () { + test('has a description', () { expect(command.description, isNotEmpty); }); - test('exits with no user when not logged in', () async { - when(() => auth.isAuthenticated).thenReturn(false); - - final result = await runWithOverrides(command.run); - expect(result, equals(ExitCode.noUser.code)); - + test('exits when validation fails', () async { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'), + validators: any(named: 'validators'), + ), + ).thenThrow(exception); + await expectLater( + runWithOverrides(command.run), + completion(equals(exception.exitCode.code)), + ); verify( - () => logger.err(any(that: contains('You must be logged in to run'))), + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, + validators: [flutterValidator], + ), ).called(1); }); @@ -253,59 +280,6 @@ ${lightCyan.wrap(p.join('build', 'ios', 'ipa', 'Runner.ipa'))}''', ); }); - test('prints flutter validation warnings', () async { - when(flutterValidator.validate).thenAnswer( - (_) async => [ - const ValidationIssue( - severity: ValidationIssueSeverity.warning, - message: 'Flutter issue 1', - ), - const ValidationIssue( - severity: ValidationIssueSeverity.warning, - message: 'Flutter issue 2', - ), - ], - ); - when(() => processResult.exitCode).thenReturn(ExitCode.success.code); - - final result = await runWithOverrides(command.run); - - expect(result, equals(ExitCode.success.code)); - verify( - () => logger.info(any(that: contains('Flutter issue 1'))), - ).called(1); - verify( - () => logger.info(any(that: contains('Flutter issue 2'))), - ).called(1); - }); - - test('aborts if validation errors are present', () async { - when(flutterValidator.validate).thenAnswer( - (_) async => [ - ValidationIssue( - severity: ValidationIssueSeverity.error, - message: 'There was an issue', - fix: () async {}, - ), - ], - ); - - final result = await runWithOverrides(command.run); - - expect(result, equals(ExitCode.config.code)); - verify(() => logger.err('Aborting due to validation errors.')).called(1); - verify( - () => logger.info( - any( - that: stringContainsInOrder([ - 'issue can be fixed automatically', - 'shorebird doctor --fix', - ]), - ), - ), - ).called(1); - }); - test('provides appropriate ExportOptions.plist to build ipa command', () async { when(() => processResult.exitCode).thenReturn(ExitCode.success.code); diff --git a/packages/shorebird_cli/test/src/commands/collaborators/add_collaborators_command_test.dart b/packages/shorebird_cli/test/src/commands/collaborators/add_collaborators_command_test.dart index 921d55ed..d19c9aea 100644 --- a/packages/shorebird_cli/test/src/commands/collaborators/add_collaborators_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/collaborators/add_collaborators_command_test.dart @@ -6,6 +6,7 @@ import 'package:scoped/scoped.dart'; import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/commands/commands.dart'; import 'package:shorebird_cli/src/logger.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; @@ -21,6 +22,8 @@ class _MockLogger extends Mock implements Logger {} class _MockProgress extends Mock implements Progress {} +class _MockShorebirdValidator extends Mock implements ShorebirdValidator {} + void main() { group(AddCollaboratorsCommand, () { const appId = 'test-app-id'; @@ -32,6 +35,7 @@ void main() { late CodePushClient codePushClient; late Logger logger; late Progress progress; + late ShorebirdValidator shorebirdValidator; late AddCollaboratorsCommand command; R runWithOverrides(R Function() body) { @@ -39,7 +43,8 @@ void main() { body, values: { authRef.overrideWith(() => auth), - loggerRef.overrideWith(() => logger) + loggerRef.overrideWith(() => logger), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), }, ); } @@ -51,6 +56,7 @@ void main() { codePushClient = _MockCodePushClient(); logger = _MockLogger(); progress = _MockProgress(); + shorebirdValidator = _MockShorebirdValidator(); when(() => argResults['app-id']).thenReturn(appId); when(() => argResults['email']).thenReturn(email); @@ -58,6 +64,11 @@ void main() { when(() => auth.isAuthenticated).thenReturn(true); when(() => logger.confirm(any())).thenReturn(true); when(() => logger.progress(any())).thenReturn(progress); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + ), + ).thenAnswer((_) async {}); command = runWithOverrides( () => AddCollaboratorsCommand( @@ -82,9 +93,22 @@ void main() { ); }); - test('returns ExitCode.noUser when not logged in', () async { - when(() => auth.isAuthenticated).thenReturn(false); - expect(await runWithOverrides(command.run), ExitCode.noUser.code); + test('exits when validation fails', () async { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + ), + ).thenThrow(exception); + await expectLater( + runWithOverrides(command.run), + completion(equals(exception.exitCode.code)), + ); + verify( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + ), + ).called(1); }); test('returns ExitCode.usage when app id is missing.', () async { diff --git a/packages/shorebird_cli/test/src/commands/collaborators/delete_collaborators_command_test.dart b/packages/shorebird_cli/test/src/commands/collaborators/delete_collaborators_command_test.dart index 14b01ed5..03a98a94 100644 --- a/packages/shorebird_cli/test/src/commands/collaborators/delete_collaborators_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/collaborators/delete_collaborators_command_test.dart @@ -9,6 +9,7 @@ import 'package:scoped/scoped.dart'; import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/commands/commands.dart'; import 'package:shorebird_cli/src/logger.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; @@ -24,6 +25,8 @@ class _MockLogger extends Mock implements Logger {} class _MockProgress extends Mock implements Progress {} +class _MockShorebirdValidator extends Mock implements ShorebirdValidator {} + void main() { group(DeleteCollaboratorsCommand, () { const appId = 'test-app-id'; @@ -40,6 +43,7 @@ void main() { late CodePushClient codePushClient; late Logger logger; late Progress progress; + late ShorebirdValidator shorebirdValidator; late DeleteCollaboratorsCommand command; R runWithOverrides(R Function() body) { @@ -47,7 +51,8 @@ void main() { body, values: { authRef.overrideWith(() => auth), - loggerRef.overrideWith(() => logger) + loggerRef.overrideWith(() => logger), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), }, ); } @@ -59,6 +64,7 @@ void main() { codePushClient = _MockCodePushClient(); logger = _MockLogger(); progress = _MockProgress(); + shorebirdValidator = _MockShorebirdValidator(); when(() => argResults['app-id']).thenReturn(appId); when(() => argResults['email']).thenReturn(email); @@ -75,6 +81,11 @@ void main() { userId: any(named: 'userId'), ), ).thenAnswer((_) async {}); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + ), + ).thenAnswer((_) async {}); command = runWithOverrides( () => DeleteCollaboratorsCommand( @@ -95,9 +106,22 @@ void main() { ); }); - test('returns ExitCode.noUser when not logged in', () async { - when(() => auth.isAuthenticated).thenReturn(false); - expect(await runWithOverrides(command.run), ExitCode.noUser.code); + test('exits when validation fails', () async { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + ), + ).thenThrow(exception); + await expectLater( + runWithOverrides(command.run), + completion(equals(exception.exitCode.code)), + ); + verify( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + ), + ).called(1); }); test('returns ExitCode.usage when app id is missing.', () async { diff --git a/packages/shorebird_cli/test/src/commands/collaborators/list_collaborators_command_test.dart b/packages/shorebird_cli/test/src/commands/collaborators/list_collaborators_command_test.dart index 2eb79c79..0082721b 100644 --- a/packages/shorebird_cli/test/src/commands/collaborators/list_collaborators_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/collaborators/list_collaborators_command_test.dart @@ -6,6 +6,7 @@ import 'package:scoped/scoped.dart'; import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/commands/commands.dart'; import 'package:shorebird_cli/src/logger.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; @@ -19,6 +20,8 @@ class _MockCodePushClient extends Mock implements CodePushClient {} class _MockLogger extends Mock implements Logger {} +class _MockShorebirdValidator extends Mock implements ShorebirdValidator {} + void main() { group(ListCollaboratorsCommand, () { const appId = 'test-app-id'; @@ -28,6 +31,7 @@ void main() { late Auth auth; late CodePushClient codePushClient; late Logger logger; + late ShorebirdValidator shorebirdValidator; late ListCollaboratorsCommand command; R runWithOverrides(R Function() body) { @@ -35,7 +39,8 @@ void main() { body, values: { authRef.overrideWith(() => auth), - loggerRef.overrideWith(() => logger) + loggerRef.overrideWith(() => logger), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), }, ); } @@ -46,10 +51,16 @@ void main() { auth = _MockAuth(); codePushClient = _MockCodePushClient(); logger = _MockLogger(); + shorebirdValidator = _MockShorebirdValidator(); when(() => argResults['app-id']).thenReturn(appId); when(() => auth.client).thenReturn(httpClient); when(() => auth.isAuthenticated).thenReturn(true); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + ), + ).thenAnswer((_) async {}); command = runWithOverrides( () => ListCollaboratorsCommand( @@ -78,9 +89,22 @@ void main() { expect(command.aliases, equals(['ls'])); }); - test('returns ExitCode.noUser when not logged in', () async { - when(() => auth.isAuthenticated).thenReturn(false); - expect(await runWithOverrides(command.run), ExitCode.noUser.code); + test('exits when validation fails', () async { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + ), + ).thenThrow(exception); + await expectLater( + runWithOverrides(command.run), + completion(equals(exception.exitCode.code)), + ); + verify( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + ), + ).called(1); }); test('returns ExitCode.usage when app id is missing.', () async { 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 2af13cae..44fd385d 100644 --- a/packages/shorebird_cli/test/src/commands/init_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/init_command_test.dart @@ -14,6 +14,7 @@ import 'package:shorebird_cli/src/gradlew.dart'; import 'package:shorebird_cli/src/logger.dart'; import 'package:shorebird_cli/src/platform.dart'; import 'package:shorebird_cli/src/process.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_cli/src/xcodebuild.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; @@ -36,6 +37,8 @@ class _MockPlatform extends Mock implements Platform {} class _MockProgress extends Mock implements Progress {} +class _MockShorebirdValidator extends Mock implements ShorebirdValidator {} + class _MockXcodeBuild extends Mock implements XcodeBuild {} void main() { @@ -60,6 +63,7 @@ environment: late Logger logger; late Platform platform; late Progress progress; + late ShorebirdValidator shorebirdValidator; late XcodeBuild xcodeBuild; late InitCommand command; @@ -73,6 +77,7 @@ environment: loggerRef.overrideWith(() => logger), platformRef.overrideWith(() => platform), processRef.overrideWith(() => process), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), xcodeBuildRef.overrideWith(() => xcodeBuild), }, ); @@ -95,6 +100,7 @@ environment: logger = _MockLogger(); platform = _MockPlatform(); progress = _MockProgress(); + shorebirdValidator = _MockShorebirdValidator(); xcodeBuild = _MockXcodeBuild(); when(() => auth.isAuthenticated).thenReturn(true); @@ -115,6 +121,11 @@ environment: when(() => logger.progress(any())).thenReturn(progress); when(() => gradlew.productFlavors(any())).thenAnswer((_) async => {}); when(() => platform.isMacOS).thenReturn(true); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + ), + ).thenAnswer((_) async {}); when( () => xcodeBuild.list(any()), ).thenAnswer((_) async => const XcodeProjectBuildInfo()); @@ -131,10 +142,22 @@ environment: )..testArgResults = argResults; }); - test('returns no user error when not logged in', () async { - when(() => auth.isAuthenticated).thenReturn(false); - final result = await runWithOverrides(command.run); - expect(result, ExitCode.noUser.code); + test('exits when validation fails', () async { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + ), + ).thenThrow(exception); + await expectLater( + runWithOverrides(command.run), + completion(equals(exception.exitCode.code)), + ); + verify( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + ), + ).called(1); }); test('throws no input error when pubspec.yaml is not found.', () async { diff --git a/packages/shorebird_cli/test/src/commands/patch/patch_aar_command_test.dart b/packages/shorebird_cli/test/src/commands/patch/patch_aar_command_test.dart index a3c79de7..850c0b50 100644 --- a/packages/shorebird_cli/test/src/commands/patch/patch_aar_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/patch_aar_command_test.dart @@ -17,6 +17,7 @@ import 'package:shorebird_cli/src/platform.dart'; import 'package:shorebird_cli/src/process.dart'; import 'package:shorebird_cli/src/shorebird_build_mixin.dart'; import 'package:shorebird_cli/src/shorebird_environment.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; @@ -45,6 +46,8 @@ class _MockHttpClient extends Mock implements http.Client {} class _MockShorebirdProcess extends Mock implements ShorebirdProcess {} +class _MockShorebirdValidator extends Mock implements ShorebirdValidator {} + class _FakeShorebirdProcess extends Fake implements ShorebirdProcess {} void main() { @@ -123,8 +126,9 @@ flutter: late ShorebirdProcessResult patchProcessResult; late http.Client httpClient; late Cache cache; - late PatchAarCommand command; late ShorebirdProcess shorebirdProcess; + late ShorebirdValidator shorebirdValidator; + late PatchAarCommand command; R runWithOverrides(R Function() body) { return runScoped( @@ -137,6 +141,7 @@ flutter: loggerRef.overrideWith(() => logger), platformRef.overrideWith(() => platform), processRef.overrideWith(() => shorebirdProcess), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), }, ); } @@ -203,6 +208,7 @@ flutter: httpClient = _MockHttpClient(); cache = _MockCache(); shorebirdProcess = _MockShorebirdProcess(); + shorebirdValidator = _MockShorebirdValidator(); registerFallbackValue(ReleasePlatform.android); @@ -323,6 +329,12 @@ flutter: when( () => cache.getArtifactDirectory(any()), ).thenReturn(Directory.systemTemp.createTempSync()); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'), + ), + ).thenAnswer((_) async {}); command = runWithOverrides( () => PatchAarCommand( @@ -333,32 +345,28 @@ flutter: )..testArgResults = argResults; }); - test('throws config error when shorebird is not initialized', () async { - final tempDir = Directory.systemTemp.createTempSync(); - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - verify( - () => logger.err( - 'Shorebird is not initialized. Did you run "shorebird init"?', - ), - ).called(1); - expect(exitCode, ExitCode.config.code); - }); - test('has a description', () { expect(command.description, isNotEmpty); }); - 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( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, + test('exits when validation fails', () async { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'), + ), + ).thenThrow(exception); + await expectLater( + runWithOverrides(command.run), + completion(equals(exception.exitCode.code)), ); - expect(exitCode, equals(ExitCode.noUser.code)); + verify( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, + ), + ).called(1); }); test('exits with 78 if no module entry exists in pubspec.yaml', () async { diff --git a/packages/shorebird_cli/test/src/commands/patch/patch_android_command_test.dart b/packages/shorebird_cli/test/src/commands/patch/patch_android_command_test.dart index 31752fc6..0a48e590 100644 --- a/packages/shorebird_cli/test/src/commands/patch/patch_android_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/patch_android_command_test.dart @@ -21,6 +21,7 @@ import 'package:shorebird_cli/src/platform.dart'; import 'package:shorebird_cli/src/process.dart'; import 'package:shorebird_cli/src/shorebird_build_mixin.dart'; import 'package:shorebird_cli/src/shorebird_environment.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_cli/src/validators/validators.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; @@ -59,6 +60,8 @@ class _MockShorebirdFlutterValidator extends Mock class _MockShorebirdProcess extends Mock implements ShorebirdProcess {} +class _MockShorebirdValidator extends Mock implements ShorebirdValidator {} + class _FakeShorebirdProcess extends Fake implements ShorebirdProcess {} void main() { @@ -125,9 +128,10 @@ flutter: late ShorebirdProcessResult patchProcessResult; late http.Client httpClient; late Cache cache; - late PatchAndroidCommand command; late ShorebirdFlutterValidator flutterValidator; late ShorebirdProcess shorebirdProcess; + late ShorebirdValidator shorebirdValidator; + late PatchAndroidCommand command; R runWithOverrides(R Function() body) { return runScoped( @@ -143,6 +147,7 @@ flutter: loggerRef.overrideWith(() => logger), platformRef.overrideWith(() => platform), processRef.overrideWith(() => shorebirdProcess), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), }, ); } @@ -203,6 +208,7 @@ flutter: flutterValidator = _MockShorebirdFlutterValidator(); cache = _MockCache(); shorebirdProcess = _MockShorebirdProcess(); + shorebirdValidator = _MockShorebirdValidator(); command = runWithOverrides( () => PatchAndroidCommand( aabDiffer: aabDiffer, @@ -341,34 +347,39 @@ flutter: when(() => bundletool.getVersionCode(any())).thenAnswer( (_) async => versionCode, ); - }); - - test('throws config error when shorebird is not initialized', () async { - final tempDir = Directory.systemTemp.createTempSync(); - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - verify( - () => logger.err( - 'Shorebird is not initialized. Did you run "shorebird init"?', + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'), + validators: any(named: 'validators'), ), - ).called(1); - expect(exitCode, ExitCode.config.code); + ).thenAnswer((_) async {}); }); test('has a description', () { expect(command.description, isNotEmpty); }); - 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( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, + test('exits when validation fails', () async { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'), + validators: any(named: 'validators'), + ), + ).thenThrow(exception); + await expectLater( + runWithOverrides(command.run), + completion(equals(exception.exitCode.code)), ); - expect(exitCode, equals(ExitCode.noUser.code)); + verify( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, + validators: [flutterValidator], + ), + ).called(1); }); test('exits with code 70 when building fails', () async { @@ -860,56 +871,5 @@ flavors: verify(() => logger.success('\nāœ… Published Patch!')).called(1); expect(exitCode, ExitCode.success.code); }); - - test('prints flutter validation warnings', () async { - final tempDir = setUpTempDir(); - setUpTempArtifacts(tempDir); - when(flutterValidator.validate).thenAnswer( - (_) async => [ - const ValidationIssue( - severity: ValidationIssueSeverity.warning, - message: 'Flutter issue 1', - ), - const ValidationIssue( - severity: ValidationIssueSeverity.warning, - message: 'Flutter issue 2', - ), - ], - ); - - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - - expect(exitCode, equals(ExitCode.success.code)); - verify( - () => logger.info(any(that: contains('Flutter issue 1'))), - ).called(1); - verify( - () => logger.info(any(that: contains('Flutter issue 2'))), - ).called(1); - }); - - test('aborts if validation errors are present', () async { - final tempDir = setUpTempDir(); - setUpTempArtifacts(tempDir); - when(flutterValidator.validate).thenAnswer( - (_) async => [ - const ValidationIssue( - severity: ValidationIssueSeverity.error, - message: 'There was an issue', - ), - ], - ); - - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - - expect(exitCode, equals(ExitCode.config.code)); - verify(() => logger.err('Aborting due to validation errors.')).called(1); - }); }); } diff --git a/packages/shorebird_cli/test/src/commands/patch/patch_ios_command_test.dart b/packages/shorebird_cli/test/src/commands/patch/patch_ios_command_test.dart index bd63068e..6fdfb47c 100644 --- a/packages/shorebird_cli/test/src/commands/patch/patch_ios_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/patch_ios_command_test.dart @@ -17,6 +17,7 @@ import 'package:shorebird_cli/src/logger.dart'; import 'package:shorebird_cli/src/platform.dart'; import 'package:shorebird_cli/src/process.dart'; import 'package:shorebird_cli/src/shorebird_environment.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_cli/src/third_party/flutter_tools/lib/flutter_tools.dart'; import 'package:shorebird_cli/src/validators/validators.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; @@ -52,6 +53,8 @@ class _MockShorebirdFlutterValidator extends Mock class _MockShorebirdProcess extends Mock implements ShorebirdProcess {} +class _MockShorebirdValidator extends Mock implements ShorebirdValidator {} + class _FakeShorebirdProcess extends Fake implements ShorebirdProcess {} void main() { @@ -109,6 +112,7 @@ flutter: late http.Client httpClient; late ShorebirdFlutterValidator flutterValidator; late ShorebirdProcess shorebirdProcess; + late ShorebirdValidator shorebirdValidator; late PatchIosCommand command; R runWithOverrides(R Function() body) { @@ -121,6 +125,7 @@ flutter: loggerRef.overrideWith(() => logger), platformRef.overrideWith(() => platform), processRef.overrideWith(() => shorebirdProcess), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), }, ); } @@ -182,6 +187,7 @@ flutter: httpClient = _MockHttpClient(); flutterValidator = _MockShorebirdFlutterValidator(); shorebirdProcess = _MockShorebirdProcess(); + shorebirdValidator = _MockShorebirdValidator(); when(() => argResults['arch']).thenReturn(arch); when(() => argResults['dry-run']).thenReturn(false); @@ -256,6 +262,14 @@ flutter: runInShell: any(named: 'runInShell'), ), ).thenAnswer((_) async => aotBuildProcessResult); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'), + validators: any(named: 'validators'), + supportedOperatingSystems: any(named: 'supportedOperatingSystems'), + ), + ).thenAnswer((_) async {}); command = runWithOverrides(() => PatchIosCommand(ipaReader: ipaReader)) ..testArgResults = argResults; @@ -265,24 +279,28 @@ flutter: expect(command.description, isNotEmpty); }); - test('exits with unavailable code if run on non-macOS platform', () async { - when(() => platform.operatingSystem).thenReturn(Platform.windows); - - final result = await runWithOverrides(command.run); - - expect(result, equals(ExitCode.unavailable.code)); - verify(() => logger.err('This command is only supported on macos.')) - .called(1); - }); - - 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( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, + test('exits when validation fails', () async { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'), + validators: any(named: 'validators'), + supportedOperatingSystems: any(named: 'supportedOperatingSystems'), + ), + ).thenThrow(exception); + await expectLater( + runWithOverrides(command.run), + completion(equals(exception.exitCode.code)), ); - expect(exitCode, equals(ExitCode.noUser.code)); + verify( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, + validators: [flutterValidator], + supportedOperatingSystems: {Platform.macOS}, + ), + ).called(1); }); test('exits with code 70 when building fails', () async { @@ -625,56 +643,5 @@ base_url: $baseUrl''', expect(exportOptionsPlist['uploadBitcode'], isFalse); expect(exportOptionsPlist['method'], 'app-store'); }); - - test('prints flutter validation warnings', () async { - final tempDir = setUpTempDir(); - setUpTempArtifacts(tempDir); - when(flutterValidator.validate).thenAnswer( - (_) async => [ - const ValidationIssue( - severity: ValidationIssueSeverity.warning, - message: 'Flutter issue 1', - ), - const ValidationIssue( - severity: ValidationIssueSeverity.warning, - message: 'Flutter issue 2', - ), - ], - ); - - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - - expect(exitCode, equals(ExitCode.success.code)); - verify( - () => logger.info(any(that: contains('Flutter issue 1'))), - ).called(1); - verify( - () => logger.info(any(that: contains('Flutter issue 2'))), - ).called(1); - }); - - test('aborts if validation errors are present', () async { - final tempDir = setUpTempDir(); - setUpTempArtifacts(tempDir); - when(flutterValidator.validate).thenAnswer( - (_) async => [ - const ValidationIssue( - severity: ValidationIssueSeverity.error, - message: 'There was an issue', - ), - ], - ); - - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - - expect(exitCode, equals(ExitCode.config.code)); - verify(() => logger.err('Aborting due to validation errors.')).called(1); - }); }); } diff --git a/packages/shorebird_cli/test/src/commands/patch/patch_ios_framework_command_test.dart b/packages/shorebird_cli/test/src/commands/patch/patch_ios_framework_command_test.dart index 6135679f..cc0c1986 100644 --- a/packages/shorebird_cli/test/src/commands/patch/patch_ios_framework_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/patch_ios_framework_command_test.dart @@ -14,6 +14,7 @@ import 'package:shorebird_cli/src/logger.dart'; import 'package:shorebird_cli/src/platform.dart'; import 'package:shorebird_cli/src/process.dart'; import 'package:shorebird_cli/src/shorebird_environment.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_cli/src/validators/validators.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; @@ -40,6 +41,8 @@ class _MockShorebirdFlutterValidator extends Mock class _MockShorebirdProcess extends Mock implements ShorebirdProcess {} +class _MockShorebirdValidator extends Mock implements ShorebirdValidator {} + void main() { group(PatchIosFrameworkCommand, () { const appDisplayName = 'Test App'; @@ -79,9 +82,10 @@ flutter: late ShorebirdProcessResult aotBuildProcessResult; late ShorebirdProcessResult flutterBuildProcessResult; late ShorebirdProcessResult flutterRevisionProcessResult; - late PatchIosFrameworkCommand command; late ShorebirdFlutterValidator flutterValidator; late ShorebirdProcess shorebirdProcess; + late ShorebirdValidator shorebirdValidator; + late PatchIosFrameworkCommand command; R runWithOverrides(R Function() body) { return runScoped( @@ -93,6 +97,7 @@ flutter: loggerRef.overrideWith(() => logger), platformRef.overrideWith(() => platform), processRef.overrideWith(() => shorebirdProcess), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), }, ); } @@ -145,6 +150,7 @@ flutter: flutterRevisionProcessResult = _MockProcessResult(); flutterValidator = _MockShorebirdFlutterValidator(); shorebirdProcess = _MockShorebirdProcess(); + shorebirdValidator = _MockShorebirdValidator(); when( () => shorebirdProcess.run( @@ -214,6 +220,14 @@ flutter: patchArtifactBundles: any(named: 'patchArtifactBundles'), ), ).thenAnswer((_) async {}); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'), + validators: any(named: 'validators'), + supportedOperatingSystems: any(named: 'supportedOperatingSystems'), + ), + ).thenAnswer((_) async {}); command = runWithOverrides(PatchIosFrameworkCommand.new) ..testArgResults = argResults; @@ -223,38 +237,28 @@ flutter: expect(command.description, isNotEmpty); }); - test('exits with unavailable code if run on non-macOS platform', () async { - when(() => platform.operatingSystem).thenReturn(Platform.windows); - - final result = await runWithOverrides(command.run); - - expect(result, equals(ExitCode.unavailable.code)); - verify(() => logger.err('This command is only supported on macos.')) - .called(1); - }); - - test('throws config error when shorebird is not initialized', () async { - final tempDir = Directory.systemTemp.createTempSync(); - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, + test('exits when validation fails', () async { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'), + validators: any(named: 'validators'), + supportedOperatingSystems: any(named: 'supportedOperatingSystems'), + ), + ).thenThrow(exception); + await expectLater( + runWithOverrides(command.run), + completion(equals(exception.exitCode.code)), ); verify( - () => logger.err( - 'Shorebird is not initialized. Did you run "shorebird init"?', + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, + validators: [flutterValidator], + supportedOperatingSystems: {Platform.macOS}, ), ).called(1); - expect(exitCode, ExitCode.config.code); - }); - - 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( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - expect(exitCode, equals(ExitCode.noUser.code)); }); test('aborts when user opts out', () async { @@ -477,56 +481,5 @@ https://github.com/shorebirdtech/shorebird/issues/472 verify(() => logger.success('\nāœ… Published Patch!')).called(1); expect(exitCode, ExitCode.success.code); }); - - test('prints flutter validation warnings', () async { - final tempDir = setUpTempDir(); - setUpTempArtifacts(tempDir); - when(flutterValidator.validate).thenAnswer( - (_) async => [ - const ValidationIssue( - severity: ValidationIssueSeverity.warning, - message: 'Flutter issue 1', - ), - const ValidationIssue( - severity: ValidationIssueSeverity.warning, - message: 'Flutter issue 2', - ), - ], - ); - - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - - expect(exitCode, equals(ExitCode.success.code)); - verify( - () => logger.info(any(that: contains('Flutter issue 1'))), - ).called(1); - verify( - () => logger.info(any(that: contains('Flutter issue 2'))), - ).called(1); - }); - - test('aborts if validation errors are present', () async { - final tempDir = setUpTempDir(); - setUpTempArtifacts(tempDir); - when(flutterValidator.validate).thenAnswer( - (_) async => [ - const ValidationIssue( - severity: ValidationIssueSeverity.error, - message: 'There was an issue', - ), - ], - ); - - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - - expect(exitCode, equals(ExitCode.config.code)); - verify(() => logger.err('Aborting due to validation errors.')).called(1); - }); }); } diff --git a/packages/shorebird_cli/test/src/commands/preview_command_test.dart b/packages/shorebird_cli/test/src/commands/preview_command_test.dart index e30ee7b3..33bb6089 100644 --- a/packages/shorebird_cli/test/src/commands/preview_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/preview_command_test.dart @@ -16,6 +16,7 @@ import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/commands/commands.dart'; import 'package:shorebird_cli/src/ios_deploy.dart'; import 'package:shorebird_cli/src/logger.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; @@ -52,6 +53,8 @@ class _MockReleaseArtifact extends Mock implements ReleaseArtifact {} class _MockIOSDeploy extends Mock implements IOSDeploy {} +class _MockShorebirdValidator extends Mock implements ShorebirdValidator {} + void main() { group(PreviewCommand, () { const appId = 'test-app-id'; @@ -72,6 +75,7 @@ void main() { late Progress progress; late Release release; late ReleaseArtifact releaseArtifact; + late ShorebirdValidator shorebirdValidator; late PreviewCommand command; R runWithOverrides(R Function() body) { @@ -83,6 +87,7 @@ void main() { cacheRef.overrideWith(() => cache), codePushClientWrapperRef.overrideWith(() => codePushClientWrapper), loggerRef.overrideWith(() => logger), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), }, ), createHttpClient: (_) => httpClient, @@ -109,6 +114,7 @@ void main() { progress = _MockProgress(); release = _MockRelease(); releaseArtifact = _MockReleaseArtifact(); + shorebirdValidator = _MockShorebirdValidator(); command = PreviewCommand()..testArgResults = argResults; when(() => argResults['app-id']).thenReturn(appId); @@ -138,12 +144,29 @@ void main() { ReleasePlatform.ios: ReleaseStatus.active, }); when(() => logger.progress(any())).thenReturn(progress); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + ), + ).thenAnswer((_) async {}); }); - test('returns no user error when not logged in', () async { - when(() => auth.isAuthenticated).thenReturn(false); - final result = await runWithOverrides(command.run); - expect(result, ExitCode.noUser.code); + test('exits when validation fails', () async { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + ), + ).thenThrow(exception); + await expectLater( + runWithOverrides(command.run), + completion(equals(exception.exitCode.code)), + ); + verify( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + ), + ).called(1); }); test('exits with code 70 when querying for releases fails', () async { @@ -191,6 +214,7 @@ void main() { codePushClientWrapperRef .overrideWith(() => codePushClientWrapper), loggerRef.overrideWith(() => logger), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), }, ), createHttpClient: (_) => httpClient, @@ -485,6 +509,7 @@ void main() { .overrideWith(() => codePushClientWrapper), iosDeployRef.overrideWith(() => iosDeploy), loggerRef.overrideWith(() => logger), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), }, ), createHttpClient: (_) => httpClient, @@ -505,8 +530,8 @@ void main() { .thenAnswer((invocation) async { (invocation.positionalArguments.single as IOSink) .add(ZipEncoder().encode(Archive())!); - // Wait 1 tick for the content to be written. - await Future.delayed(Duration.zero); + // Wait for Isolate to finish. + await Future.delayed(const Duration(milliseconds: 1)); }); when( () => iosDeploy.installAndLaunchApp( diff --git a/packages/shorebird_cli/test/src/commands/release/release_aar_command_test.dart b/packages/shorebird_cli/test/src/commands/release/release_aar_command_test.dart index fffc31f5..a5623aba 100644 --- a/packages/shorebird_cli/test/src/commands/release/release_aar_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/release/release_aar_command_test.dart @@ -15,6 +15,7 @@ import 'package:shorebird_cli/src/logger.dart'; import 'package:shorebird_cli/src/platform.dart'; import 'package:shorebird_cli/src/process.dart'; import 'package:shorebird_cli/src/shorebird_build_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; @@ -39,14 +40,18 @@ class _MockCodePushClientWrapper extends Mock class _MockShorebirdProcess extends Mock implements ShorebirdProcess {} +class _MockShorebirdValidator extends Mock implements ShorebirdValidator {} + +class _FakeRelease extends Fake implements Release {} + +class _FakeShorebirdProcess extends Fake implements ShorebirdProcess {} + void main() { group(ReleaseAarCommand, () { const appDisplayName = 'Test App'; const appId = 'test-app-id'; const appMetadata = AppMetadata(appId: appId, displayName: appDisplayName); - const flutterRevision = '83305b5088e6fe327fb3334a73ff190828d85713'; - const versionName = '1.2.3'; const versionCode = '1'; const version = '$versionName+$versionCode'; @@ -58,7 +63,6 @@ void main() { displayName: '1.2.3+1', platformStatuses: {}, ); - const releasePlatform = ReleasePlatform.android; const buildNumber = '1.0'; const noModulePubspecYamlContent = ''' @@ -96,8 +100,9 @@ flutter: late Logger logger; late ShorebirdProcessResult flutterBuildProcessResult; late ShorebirdProcessResult flutterRevisionProcessResult; - late ReleaseAarCommand command; late ShorebirdProcess shorebirdProcess; + late ShorebirdValidator shorebirdValidator; + late ReleaseAarCommand command; R runWithOverrides(R Function() body) { return runScoped( @@ -110,6 +115,7 @@ flutter: loggerRef.overrideWith(() => logger), platformRef.overrideWith(() => platform), processRef.overrideWith(() => shorebirdProcess), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), }, ); } @@ -158,6 +164,8 @@ flutter: setUpAll(() { registerFallbackValue(ReleasePlatform.android); registerFallbackValue(ReleaseStatus.draft); + registerFallbackValue(_FakeRelease()); + registerFallbackValue(_FakeShorebirdProcess()); }); setUp(() { @@ -173,9 +181,7 @@ flutter: flutterRevisionProcessResult = _MockProcessResult(); shorebirdProcess = _MockShorebirdProcess(); shorebirdRoot = Directory.systemTemp.createTempSync(); - - registerFallbackValue(release); - registerFallbackValue(shorebirdProcess); + shorebirdValidator = _MockShorebirdValidator(); when(() => auth.client).thenReturn(httpClient); when(() => argResults['build-number']).thenReturn(buildNumber); @@ -266,6 +272,13 @@ flutter: ), ).thenAnswer((_) async => {}); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'), + ), + ).thenAnswer((_) async {}); + command = runWithOverrides( () => ReleaseAarCommand(unzipFn: (_, __) async {}), )..testArgResults = argResults; @@ -275,34 +288,23 @@ flutter: expect(command.description, isNotEmpty); }); - test('throws config error when shorebird is not initialized', () async { - final tempDir = Directory.systemTemp.createTempSync(); - - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - - verify( - () => logger.err( - 'Shorebird is not initialized. Did you run "shorebird init"?', + test('exits when validation fails', () async { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'), ), - ).called(1); - expect(exitCode, ExitCode.config.code); - }); - - test('exits with no user when not logged in', () async { - when(() => auth.isAuthenticated).thenReturn(false); - final tempDir = setUpTempDir(includeModule: false); - - final result = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, + ).thenThrow(exception); + await expectLater( + runWithOverrides(command.run), + completion(equals(exception.exitCode.code)), ); - - expect(result, equals(ExitCode.noUser.code)); verify( - () => logger.err(any(that: contains('You must be logged in to run'))), + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, + ), ).called(1); }); diff --git a/packages/shorebird_cli/test/src/commands/release/release_android_command_test.dart b/packages/shorebird_cli/test/src/commands/release/release_android_command_test.dart index 77fb7b79..71fb8344 100644 --- a/packages/shorebird_cli/test/src/commands/release/release_android_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/release/release_android_command_test.dart @@ -17,6 +17,7 @@ import 'package:shorebird_cli/src/java.dart'; import 'package:shorebird_cli/src/logger.dart'; import 'package:shorebird_cli/src/platform.dart'; import 'package:shorebird_cli/src/process.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_cli/src/validators/validators.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; @@ -49,8 +50,14 @@ class _MockShorebirdFlutterValidator extends Mock class _MockShorebirdProcess extends Mock implements ShorebirdProcess {} +class _MockShorebirdValidator extends Mock implements ShorebirdValidator {} + class _MockJava extends Mock implements Java {} +class _FakeRelease extends Fake implements Release {} + +class _FakeShorebirdProcess extends Fake implements ShorebirdProcess {} + void main() { group(ReleaseAndroidCommand, () { const appId = 'test-app-id'; @@ -97,9 +104,10 @@ flutter: late Logger logger; late ShorebirdProcessResult flutterBuildProcessResult; late ShorebirdProcessResult flutterRevisionProcessResult; - late ReleaseAndroidCommand command; late ShorebirdFlutterValidator flutterValidator; late ShorebirdProcess shorebirdProcess; + late ShorebirdValidator shorebirdValidator; + late ReleaseAndroidCommand command; R runWithOverrides(R Function() body) { return runScoped( @@ -115,6 +123,7 @@ flutter: loggerRef.overrideWith(() => logger), platformRef.overrideWith(() => platform), processRef.overrideWith(() => shorebirdProcess), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), }, ); } @@ -133,6 +142,8 @@ flutter: setUpAll(() { registerFallbackValue(ReleasePlatform.android); registerFallbackValue(ReleaseStatus.draft); + registerFallbackValue(_FakeRelease()); + registerFallbackValue(_FakeShorebirdProcess()); }); setUp(() { @@ -152,9 +163,7 @@ flutter: flutterRevisionProcessResult = _MockProcessResult(); flutterValidator = _MockShorebirdFlutterValidator(); shorebirdProcess = _MockShorebirdProcess(); - - registerFallbackValue(release); - registerFallbackValue(shorebirdProcess); + shorebirdValidator = _MockShorebirdValidator(); when(() => platform.script).thenReturn( Uri.file( @@ -256,6 +265,14 @@ flutter: when( () => bundletool.getVersionCode(any()), ).thenAnswer((_) async => versionCode); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'), + validators: any(named: 'validators'), + supportedOperatingSystems: any(named: 'supportedOperatingSystems'), + ), + ).thenAnswer((_) async {}); command = runWithOverrides(ReleaseAndroidCommand.new) ..testArgResults = argResults; @@ -265,28 +282,26 @@ flutter: expect(command.description, isNotEmpty); }); - test('throws config error when shorebird is not initialized', () async { - final tempDir = Directory.systemTemp.createTempSync(); - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, + test('exits when validation fails', () async { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'), + validators: any(named: 'validators'), + ), + ).thenThrow(exception); + await expectLater( + runWithOverrides(command.run), + completion(equals(exception.exitCode.code)), ); verify( - () => logger.err( - 'Shorebird is not initialized. Did you run "shorebird init"?', + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, + validators: [flutterValidator], ), ).called(1); - expect(exitCode, ExitCode.config.code); - }); - - 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( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - expect(exitCode, equals(ExitCode.noUser.code)); }); test('exits with code 70 when building fails', () async { @@ -525,64 +540,5 @@ flavors: ), ); }); - - test('prints flutter validation warnings', () async { - when(flutterValidator.validate).thenAnswer( - (_) async => [ - const ValidationIssue( - severity: ValidationIssueSeverity.warning, - message: 'Flutter issue 1', - ), - const ValidationIssue( - severity: ValidationIssueSeverity.warning, - message: 'Flutter issue 2', - ), - ], - ); - final tempDir = setUpTempDir(); - - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - - verify(() => logger.success('\nāœ… Published Release!')).called(1); - expect(exitCode, ExitCode.success.code); - verify( - () => logger.info(any(that: contains('Flutter issue 1'))), - ).called(1); - verify( - () => logger.info(any(that: contains('Flutter issue 2'))), - ).called(1); - }); - - test('aborts if validation errors are present', () async { - when(flutterValidator.validate).thenAnswer( - (_) async => [ - const ValidationIssue( - severity: ValidationIssueSeverity.error, - message: 'There was an issue', - ), - ], - ); - - final tempDir = setUpTempDir(); - - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - - expect(exitCode, equals(ExitCode.config.code)); - verify(() => logger.err('Aborting due to validation errors.')).called(1); - verifyNever( - () => codePushClientWrapper.updateReleaseStatus( - appId: any(named: 'appId'), - releaseId: any(named: 'releaseId'), - platform: any(named: 'platform'), - status: any(named: 'status'), - ), - ); - }); }); } diff --git a/packages/shorebird_cli/test/src/commands/release/release_ios_command_test.dart b/packages/shorebird_cli/test/src/commands/release/release_ios_command_test.dart index be4f5c75..877e874b 100644 --- a/packages/shorebird_cli/test/src/commands/release/release_ios_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/release/release_ios_command_test.dart @@ -15,6 +15,7 @@ import 'package:shorebird_cli/src/doctor.dart'; import 'package:shorebird_cli/src/logger.dart'; import 'package:shorebird_cli/src/platform.dart'; import 'package:shorebird_cli/src/process.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_cli/src/validators/validators.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; @@ -45,6 +46,12 @@ class _MockShorebirdFlutterValidator extends Mock class _MockShorebirdProcess extends Mock implements ShorebirdProcess {} +class _MockShorebirdValidator extends Mock implements ShorebirdValidator {} + +class _FakeRelease extends Fake implements Release {} + +class _FakeShorebirdProcess extends Fake implements ShorebirdProcess {} + void main() { group(ReleaseIosCommand, () { const appId = 'test-app-id'; @@ -95,9 +102,10 @@ flutter: late Logger logger; late ShorebirdProcessResult flutterBuildProcessResult; late ShorebirdProcessResult flutterRevisionProcessResult; - late ReleaseIosCommand command; late ShorebirdFlutterValidator flutterValidator; late ShorebirdProcess shorebirdProcess; + late ShorebirdValidator shorebirdValidator; + late ReleaseIosCommand command; R runWithOverrides(R Function() body) { return runScoped( @@ -109,6 +117,7 @@ flutter: loggerRef.overrideWith(() => logger), platformRef.overrideWith(() => platform), processRef.overrideWith(() => shorebirdProcess), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), }, ); } @@ -132,6 +141,8 @@ flutter: setUpAll(() { registerFallbackValue(ReleasePlatform.ios); registerFallbackValue(ReleaseStatus.draft); + registerFallbackValue(_FakeRelease()); + registerFallbackValue(_FakeShorebirdProcess()); }); setUp(() { @@ -149,9 +160,7 @@ flutter: flutterRevisionProcessResult = _MockProcessResult(); flutterValidator = _MockShorebirdFlutterValidator(); shorebirdProcess = _MockShorebirdProcess(); - - registerFallbackValue(release); - registerFallbackValue(shorebirdProcess); + shorebirdValidator = _MockShorebirdValidator(); when(() => platform.script).thenReturn( Uri.file( @@ -240,7 +249,14 @@ flutter: ).thenAnswer((_) async => {}); when(() => doctor.iosCommandValidators).thenReturn([flutterValidator]); - when(flutterValidator.validate).thenAnswer((_) async => []); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'), + validators: any(named: 'validators'), + supportedOperatingSystems: any(named: 'supportedOperatingSystems'), + ), + ).thenAnswer((_) async {}); command = runWithOverrides(() => ReleaseIosCommand(ipaReader: ipaReader)) ..testArgResults = argResults; @@ -250,38 +266,28 @@ flutter: expect(command.description, isNotEmpty); }); - test('exits with unavailable code if run on non-macOS platform', () async { - when(() => platform.operatingSystem).thenReturn(Platform.windows); - - final result = await runWithOverrides(command.run); - - expect(result, equals(ExitCode.unavailable.code)); - verify(() => logger.err('This command is only supported on macos.')) - .called(1); - }); - - test('throws config error when shorebird is not initialized', () async { - final tempDir = Directory.systemTemp.createTempSync(); - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, + test('exits when validation fails', () async { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'), + validators: any(named: 'validators'), + supportedOperatingSystems: any(named: 'supportedOperatingSystems'), + ), + ).thenThrow(exception); + await expectLater( + runWithOverrides(command.run), + completion(equals(exception.exitCode.code)), ); verify( - () => logger.err( - 'Shorebird is not initialized. Did you run "shorebird init"?', + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, + validators: [flutterValidator], + supportedOperatingSystems: {Platform.macOS}, ), ).called(1); - expect(exitCode, ExitCode.config.code); - }); - - 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( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - expect(exitCode, equals(ExitCode.noUser.code)); }); test('exits with code 70 when build fails with non-zero exit code', @@ -356,54 +362,6 @@ error: exportArchive: No signing certificate "iOS Distribution" found ).called(1); }); - test('prints flutter validation warnings', () async { - when(flutterValidator.validate).thenAnswer( - (_) async => [ - const ValidationIssue( - severity: ValidationIssueSeverity.warning, - message: 'Flutter issue 1', - ), - const ValidationIssue( - severity: ValidationIssueSeverity.warning, - message: 'Flutter issue 2', - ), - ], - ); - final tempDir = setUpTempDir(); - - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - - expect(exitCode, ExitCode.success.code); - verify( - () => logger.info(any(that: contains('Flutter issue 1'))), - ).called(1); - verify( - () => logger.info(any(that: contains('Flutter issue 2'))), - ).called(1); - }); - - test('aborts if validation errors are present', () async { - when(flutterValidator.validate).thenAnswer( - (_) async => [ - const ValidationIssue( - severity: ValidationIssueSeverity.error, - message: 'There was an issue', - ), - ], - ); - - final tempDir = setUpTempDir(); - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - expect(exitCode, equals(ExitCode.config.code)); - verify(() => logger.err('Aborting due to validation errors.')).called(1); - }); - test('aborts when user opts out', () async { when(() => logger.confirm(any())).thenReturn(false); final tempDir = setUpTempDir(); diff --git a/packages/shorebird_cli/test/src/commands/release/release_ios_framework_command_test.dart b/packages/shorebird_cli/test/src/commands/release/release_ios_framework_command_test.dart index dd2a34c9..9acdac40 100644 --- a/packages/shorebird_cli/test/src/commands/release/release_ios_framework_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/release/release_ios_framework_command_test.dart @@ -13,6 +13,7 @@ import 'package:shorebird_cli/src/doctor.dart'; import 'package:shorebird_cli/src/logger.dart'; import 'package:shorebird_cli/src/platform.dart'; import 'package:shorebird_cli/src/process.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_cli/src/validators/validators.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; @@ -39,6 +40,12 @@ class _MockShorebirdFlutterValidator extends Mock class _MockShorebirdProcess extends Mock implements ShorebirdProcess {} +class _MockShorebirdValidator extends Mock implements ShorebirdValidator {} + +class _FakeRelease extends Fake implements Release {} + +class _FakeShorebirdProcess extends Fake implements ShorebirdProcess {} + void main() { group(ReleaseIosFrameworkCommand, () { const appId = 'test-app-id'; @@ -77,9 +84,10 @@ flutter: late Logger logger; late ShorebirdProcessResult flutterBuildProcessResult; late ShorebirdProcessResult flutterRevisionProcessResult; - late ReleaseIosFrameworkCommand command; late ShorebirdFlutterValidator flutterValidator; late ShorebirdProcess shorebirdProcess; + late ShorebirdValidator shorebirdValidator; + late ReleaseIosFrameworkCommand command; R runWithOverrides(R Function() body) { return runScoped( @@ -91,6 +99,7 @@ flutter: loggerRef.overrideWith(() => logger), platformRef.overrideWith(() => platform), processRef.overrideWith(() => shorebirdProcess), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), }, ); } @@ -109,6 +118,8 @@ flutter: setUpAll(() { registerFallbackValue(ReleasePlatform.ios); registerFallbackValue(ReleaseStatus.draft); + registerFallbackValue(_FakeRelease()); + registerFallbackValue(_FakeShorebirdProcess()); }); setUp(() { @@ -124,9 +135,7 @@ flutter: flutterRevisionProcessResult = _MockProcessResult(); flutterValidator = _MockShorebirdFlutterValidator(); shorebirdProcess = _MockShorebirdProcess(); - - registerFallbackValue(release); - registerFallbackValue(shorebirdProcess); + shorebirdValidator = _MockShorebirdValidator(); when(() => platform.script).thenReturn( Uri.file( @@ -167,7 +176,6 @@ flutter: when( () => flutterRevisionProcessResult.stdout, ).thenReturn(flutterRevision); - when(flutterValidator.validate).thenAnswer((_) async => []); when(() => logger.progress(any())).thenReturn(progress); when(() => logger.confirm(any())).thenReturn(true); when(() => platform.operatingSystem).thenReturn(Platform.macOS); @@ -209,6 +217,14 @@ flutter: status: any(named: 'status'), ), ).thenAnswer((_) async => {}); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'), + validators: any(named: 'validators'), + supportedOperatingSystems: any(named: 'supportedOperatingSystems'), + ), + ).thenAnswer((_) async {}); command = runWithOverrides(ReleaseIosFrameworkCommand.new) ..testArgResults = argResults; @@ -218,38 +234,28 @@ flutter: expect(command.description, isNotEmpty); }); - test('exits with unavailable code if run on non-macOS platform', () async { - when(() => platform.operatingSystem).thenReturn(Platform.windows); - - final result = await runWithOverrides(command.run); - - expect(result, equals(ExitCode.unavailable.code)); - verify(() => logger.err('This command is only supported on macos.')) - .called(1); - }); - - test('throws config error when shorebird is not initialized', () async { - final tempDir = Directory.systemTemp.createTempSync(); - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, + test('exits when validation fails', () async { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'), + validators: any(named: 'validators'), + supportedOperatingSystems: any(named: 'supportedOperatingSystems'), + ), + ).thenThrow(exception); + await expectLater( + runWithOverrides(command.run), + completion(equals(exception.exitCode.code)), ); verify( - () => logger.err( - 'Shorebird is not initialized. Did you run "shorebird init"?', + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, + validators: [flutterValidator], + supportedOperatingSystems: {Platform.macOS}, ), ).called(1); - expect(exitCode, ExitCode.config.code); - }); - - 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( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - expect(exitCode, equals(ExitCode.noUser.code)); }); test('exits with code 70 when build fails with non-zero exit code', @@ -269,54 +275,6 @@ flutter: ).called(1); }); - test('prints flutter validation warnings', () async { - when(flutterValidator.validate).thenAnswer( - (_) async => [ - const ValidationIssue( - severity: ValidationIssueSeverity.warning, - message: 'Flutter issue 1', - ), - const ValidationIssue( - severity: ValidationIssueSeverity.warning, - message: 'Flutter issue 2', - ), - ], - ); - final tempDir = setUpTempDir(); - - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - - expect(exitCode, ExitCode.success.code); - verify( - () => logger.info(any(that: contains('Flutter issue 1'))), - ).called(1); - verify( - () => logger.info(any(that: contains('Flutter issue 2'))), - ).called(1); - }); - - test('aborts if validation errors are present', () async { - when(flutterValidator.validate).thenAnswer( - (_) async => [ - const ValidationIssue( - severity: ValidationIssueSeverity.error, - message: 'There was an issue', - ), - ], - ); - - final tempDir = setUpTempDir(); - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - expect(exitCode, equals(ExitCode.config.code)); - verify(() => logger.err('Aborting due to validation errors.')).called(1); - }); - test('checks that release is not active if release exists', () async { when( () => codePushClientWrapper.maybeGetRelease( diff --git a/packages/shorebird_cli/test/src/commands/releases/delete_releases_command_test.dart b/packages/shorebird_cli/test/src/commands/releases/delete_releases_command_test.dart index d9bc7e5a..70909121 100644 --- a/packages/shorebird_cli/test/src/commands/releases/delete_releases_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/releases/delete_releases_command_test.dart @@ -9,6 +9,7 @@ import 'package:scoped/scoped.dart'; import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/commands/releases/releases.dart'; import 'package:shorebird_cli/src/logger.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; @@ -24,6 +25,8 @@ class _MockLogger extends Mock implements Logger {} class _MockProgress extends Mock implements Progress {} +class _MockShorebirdValidator extends Mock implements ShorebirdValidator {} + void main() { group(DeleteReleasesCommand, () { const appId = 'test-app-id'; @@ -47,6 +50,7 @@ flutter: late Logger logger; late CodePushClient codePushClient; late Progress progress; + late ShorebirdValidator shorebirdValidator; late DeleteReleasesCommand command; R runWithOverrides(R Function() body) { @@ -54,7 +58,8 @@ flutter: body, values: { authRef.overrideWith(() => auth), - loggerRef.overrideWith(() => logger) + loggerRef.overrideWith(() => logger), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), }, ); } @@ -77,6 +82,7 @@ flutter: logger = _MockLogger(); codePushClient = _MockCodePushClient(); progress = _MockProgress(); + shorebirdValidator = _MockShorebirdValidator(); when(() => argResults['version']).thenReturn(versionNumber); @@ -116,6 +122,13 @@ flutter: when(() => logger.confirm(any())).thenReturn(true); when(() => logger.progress(any())).thenReturn(progress); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'), + ), + ).thenAnswer((_) async {}); + command = runWithOverrides( () => DeleteReleasesCommand( buildCodePushClient: ({ @@ -135,18 +148,24 @@ flutter: ); }); - test('returns no user error when not logged in', () async { - when(() => auth.isAuthenticated).thenReturn(false); - - final result = await runWithOverrides(command.run); - - expect(result, ExitCode.noUser.code); - }); - - test('returns config exit code if shorebird.yaml is not present', () async { - final exitCode = await runWithOverrides(command.run); - - expect(exitCode, ExitCode.config.code); + test('exits when validation fails', () async { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'), + ), + ).thenThrow(exception); + await expectLater( + runWithOverrides(command.run), + completion(equals(exception.exitCode.code)), + ); + verify( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, + ), + ).called(1); }); test('prompts for version when not provided', () async { diff --git a/packages/shorebird_cli/test/src/commands/releases/list_releases_command_test.dart b/packages/shorebird_cli/test/src/commands/releases/list_releases_command_test.dart index eab97dfb..7f3e1029 100644 --- a/packages/shorebird_cli/test/src/commands/releases/list_releases_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/releases/list_releases_command_test.dart @@ -9,6 +9,7 @@ import 'package:scoped/scoped.dart'; import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/commands/commands.dart'; import 'package:shorebird_cli/src/logger.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; @@ -22,6 +23,8 @@ class _MockHttpClient extends Mock implements http.Client {} class _MockLogger extends Mock implements Logger {} +class _MockShorebirdValidator extends Mock implements ShorebirdValidator {} + void main() { group(ListReleasesCommand, () { const appId = 'test-app-id'; @@ -32,6 +35,7 @@ void main() { late http.Client httpClient; late CodePushClient codePushClient; late Logger logger; + late ShorebirdValidator shorebirdValidator; late ListReleasesCommand command; const pubspecYamlContent = ''' @@ -49,7 +53,8 @@ flutter: body, values: { authRef.overrideWith(() => auth), - loggerRef.overrideWith(() => logger) + loggerRef.overrideWith(() => logger), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), }, ); } @@ -71,10 +76,18 @@ flutter: codePushClient = _MockCodePushClient(); httpClient = _MockHttpClient(); logger = _MockLogger(); + shorebirdValidator = _MockShorebirdValidator(); when(() => auth.client).thenReturn(httpClient); when(() => auth.isAuthenticated).thenReturn(true); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'), + ), + ).thenAnswer((_) async {}); + command = runWithOverrides( () => ListReleasesCommand( buildCodePushClient: ({required httpClient, hostedUri}) { @@ -88,25 +101,24 @@ flutter: expect(command.description, equals('List all releases for this app.')); }); - test('returns ExitCode.noUser when not logged in', () async { - when(() => auth.isAuthenticated).thenReturn(false); - expect(await runWithOverrides(command.run), ExitCode.noUser.code); - }); - - test('returns ExitCode.config when shorebird is not initialized', () async { - final exitCode = await runWithOverrides(command.run); - + test('exits when validation fails', () async { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'), + ), + ).thenThrow(exception); + await expectLater( + runWithOverrides(command.run), + completion(equals(exception.exitCode.code)), + ); verify( - () => logger.err( - any( - that: stringContainsInOrder([ - 'Shorebird is not initialized. Did you run', - 'shorebird init', - ]), - ), + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, ), ).called(1); - expect(exitCode, ExitCode.config.code); }); test('returns ExitCode.software when unable to get releases', () async { 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 88d5b968..a4944620 100644 --- a/packages/shorebird_cli/test/src/commands/run_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/run_command_test.dart @@ -12,6 +12,7 @@ import 'package:shorebird_cli/src/commands/run_command.dart'; import 'package:shorebird_cli/src/doctor.dart'; import 'package:shorebird_cli/src/logger.dart'; import 'package:shorebird_cli/src/process.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_cli/src/validators/validators.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; @@ -36,6 +37,8 @@ class _MockShorebirdProcess extends Mock implements ShorebirdProcess {} class _MockIOSink extends Mock implements IOSink {} +class _MockShorebirdValidator extends Mock implements ShorebirdValidator {} + class _MockValidator extends Mock implements Validator {} void main() { @@ -48,9 +51,10 @@ void main() { late Process process; late CodePushClient codePushClient; late ShorebirdProcess shorebirdProcess; - late RunCommand command; late IOSink ioSink; + late ShorebirdValidator shorebirdValidator; late Validator validator; + late RunCommand command; R runWithOverrides(R Function() body) { return runScoped( @@ -60,6 +64,7 @@ void main() { doctorRef.overrideWith(() => doctor), loggerRef.overrideWith(() => logger), processRef.overrideWith(() => shorebirdProcess), + shorebirdValidatorRef.overrideWith(() => shorebirdValidator), }, ); } @@ -78,6 +83,7 @@ void main() { shorebirdProcess = _MockShorebirdProcess(); codePushClient = _MockCodePushClient(); ioSink = _MockIOSink(); + shorebirdValidator = _MockShorebirdValidator(); validator = _MockValidator(); when( @@ -93,7 +99,12 @@ void main() { when(() => doctor.allValidators).thenReturn([validator]); when(() => logger.progress(any())).thenReturn(_MockProgress()); when(() => ioSink.addStream(any())).thenAnswer((_) async {}); - when(() => validator.validate()).thenAnswer((_) async => []); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + validators: any(named: 'validators'), + ), + ).thenAnswer((_) async {}); command = runWithOverrides( () => RunCommand( @@ -125,35 +136,24 @@ Please use "shorebird preview" instead.'''), ).called(1); }); - test('exits with no user when not logged in', () async { - when(() => auth.isAuthenticated).thenReturn(false); - - final result = await runWithOverrides(command.run); - expect(result, equals(ExitCode.noUser.code)); - - verify( - () => logger.err(any(that: contains('You must be logged in to run'))), - ).called(1); - }); - - test('aborts on validation errors', () async { - when(() => validator.validate()).thenAnswer( - (_) async => [ - const ValidationIssue( - severity: ValidationIssueSeverity.error, - message: 'Android issue', - ), - ], + test('exits when validation fails', () async { + final exception = ValidationFailedException(); + when( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), + validators: any(named: 'validators'), + ), + ).thenThrow(exception); + await expectLater( + runWithOverrides(command.run), + completion(equals(exception.exitCode.code)), ); - - final result = await runWithOverrides(command.run); - - await expectLater(result, equals(ExitCode.config.code)); verify( - () => logger.info(any(that: contains('Android issue'))), + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + validators: [validator], + ), ).called(1); - verify(() => logger.err('Aborting due to validation errors.')).called(1); - verifyNever(() => logger.info('Running app...')); }); test('exits with code when running the app fails', () async { diff --git a/packages/shorebird_cli/test/src/shorebird_validator_test.dart b/packages/shorebird_cli/test/src/shorebird_validator_test.dart new file mode 100644 index 00000000..e70fcb8e --- /dev/null +++ b/packages/shorebird_cli/test/src/shorebird_validator_test.dart @@ -0,0 +1,150 @@ +import 'package:mason_logger/mason_logger.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:platform/platform.dart'; +import 'package:scoped/scoped.dart'; +import 'package:shorebird_cli/src/auth/auth.dart'; +import 'package:shorebird_cli/src/logger.dart'; +import 'package:shorebird_cli/src/platform.dart'; +import 'package:shorebird_cli/src/shorebird_validator.dart'; +import 'package:shorebird_cli/src/validators/validators.dart'; +import 'package:test/test.dart'; + +class _MockAuth extends Mock implements Auth {} + +class _MockLogger extends Mock implements Logger {} + +class _MockPlatform extends Mock implements Platform {} + +class _MockValidator extends Mock implements Validator {} + +void main() { + group(ShorebirdValidator, () { + late Auth auth; + late Logger logger; + late Platform platform; + late Validator validator; + late ShorebirdValidator shorebirdValidator; + + R runWithOverrides(R Function() body) { + return runScoped( + () => body(), + values: { + authRef.overrideWith(() => auth), + loggerRef.overrideWith(() => logger), + platformRef.overrideWith(() => platform), + }, + ); + } + + setUp(() { + auth = _MockAuth(); + logger = _MockLogger(); + platform = _MockPlatform(); + validator = _MockValidator(); + shorebirdValidator = runWithOverrides(ShorebirdValidator.new); + }); + + group('PreconditionFailedException', () { + test('have correct exit codes', () { + expect(ShorebirdNotInitializedException().exitCode, ExitCode.config); + expect(UserNotAuthorizedException().exitCode, ExitCode.noUser); + expect(ValidationFailedException().exitCode, ExitCode.config); + expect( + UnsupportedOperatingSystemException().exitCode, + ExitCode.unavailable, + ); + }); + }); + + group('validatePreconditions', () { + test( + 'throws UnsupportedOperatingSystemException ' + 'when the operating system is not supported', () async { + when(() => platform.operatingSystem).thenReturn(Platform.linux); + const supportedOperatingSystems = {Platform.macOS, Platform.windows}; + await expectLater( + runWithOverrides( + () => shorebirdValidator.validatePreconditions( + supportedOperatingSystems: supportedOperatingSystems, + ), + ), + throwsA(isA()), + ); + verify( + () => logger.err( + '''This command is only supported on ${supportedOperatingSystems.join(' ,')}.''', + ), + ).called(1); + }); + + test( + 'throws UserNotAuthorizedException ' + 'when user is not authenticated', () async { + when(() => auth.isAuthenticated).thenReturn(false); + await expectLater( + runWithOverrides( + () => shorebirdValidator.validatePreconditions( + checkUserIsAuthenticated: true, + ), + ), + throwsA(isA()), + ); + verifyInOrder([ + () => logger.err('You must be logged in to run this command.'), + () => logger.info( + '''If you already have an account, run ${lightCyan.wrap('shorebird login')} to sign in.''', + ), + () => logger.info( + '''If you don't have a Shorebird account, run ${lightCyan.wrap('shorebird account create')} to create one.''', + ), + ]); + }); + + test( + 'throws ShorebirdNotInitializedException ' + 'when shorebird has not been initialized', () async { + await expectLater( + runWithOverrides( + () => shorebirdValidator.validatePreconditions( + checkShorebirdInitialized: true, + ), + ), + throwsA(isA()), + ); + verify( + () => logger.err( + 'Shorebird is not initialized. Did you run "shorebird init"?', + ), + ).called(1); + }); + + test('throws ValidationFailedException if validator fails', () async { + final issue = ValidationIssue( + message: 'test issue', + severity: ValidationIssueSeverity.error, + fix: () async {}, + ); + when(() => validator.canRunInCurrentContext()).thenReturn(true); + when(() => validator.validate()).thenAnswer((_) async => [issue]); + await expectLater( + runWithOverrides( + () => shorebirdValidator.validatePreconditions( + validators: [validator], + ), + ), + throwsA(isA()), + ); + verify(() => validator.validate()).called(1); + verify( + () => logger.err('Aborting due to validation errors.'), + ).called(1); + verify(() => logger.info('[āœ—] ${issue.message}')).called(1); + verify( + () => logger.info( + '''1 issue can be fixed automatically with ${lightCyan.wrap('shorebird doctor --fix')}.''', + ), + ).called(1); + }); + }); + }); +}