refactor(shorebird_cli): use scoped ShorebirdValidator (#996)

This commit is contained in:
Felix Angelov
2023-08-02 13:28:25 -05:00
committed by GitHub
parent e722a0b704
commit 7a4e4ee834
56 changed files with 1082 additions and 978 deletions
@@ -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<void> main(List<String> args) async {
loggerRef,
platformRef,
processRef,
shorebirdValidatorRef,
shorebirdVersionManagerRef,
xcodeBuildRef,
},
@@ -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<int> run() async {
try {
await validatePreconditions(checkUserIsAuthenticated: true);
await shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: true,
);
} on PreconditionFailedException catch (e) {
return e.exitCode.code;
}
@@ -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) {
@@ -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<int>? run() async {
try {
await validatePreconditions(
await shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: true,
);
} on PreconditionFailedException catch (e) {
@@ -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) {
@@ -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) {
@@ -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<int> run() async {
try {
await validatePreconditions(
await shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: true,
checkShorebirdInitialized: true,
);
@@ -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<int> run() async {
try {
await validatePreconditions(
await shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: true,
checkShorebirdInitialized: true,
validators: doctor.androidCommandValidators,
);
} on PreconditionFailedException catch (e) {
@@ -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<int> run() async {
try {
await validatePreconditions(
await shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: true,
checkShorebirdInitialized: true,
validators: doctor.androidCommandValidators,
);
} on PreconditionFailedException catch (e) {
@@ -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<int> run() async {
try {
await validatePreconditions(
await shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: true,
checkShorebirdInitialized: true,
validators: doctor.iosCommandValidators,
);
} on PreconditionFailedException catch (e) {
@@ -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<int>? run() async {
try {
await validatePreconditions(
await shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: true,
);
} on PreconditionFailedException catch (e) {
@@ -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<int>? run() async {
try {
await validatePreconditions(
await shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: true,
);
} on PreconditionFailedException catch (e) {
@@ -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<int>? run() async {
try {
await validatePreconditions(
await shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: true,
);
} on PreconditionFailedException catch (e) {
@@ -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<int> 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);
@@ -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<int> run() async {
try {
await validatePreconditions(
await shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: true,
checkShorebirdInitialized: true,
);
@@ -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<int> run() async {
try {
await validatePreconditions(
await shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: true,
checkShorebirdInitialized: true,
validators: doctor.androidCommandValidators,
@@ -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<int> run() async {
try {
await validatePreconditions(
await shorebirdValidator.validatePreconditions(
checkShorebirdInitialized: true,
checkUserIsAuthenticated: true,
validators: doctor.iosCommandValidators,
@@ -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<int> run() async {
try {
await validatePreconditions(
await shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: true,
checkShorebirdInitialized: true,
validators: doctor.iosCommandValidators,
@@ -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;
}
@@ -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<int> run() async {
try {
await validatePreconditions(
await shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: true,
checkShorebirdInitialized: true,
);
@@ -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<int> run() async {
try {
await validatePreconditions(
await shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: true,
checkShorebirdInitialized: true,
validators: doctor.androidCommandValidators,
@@ -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<int> run() async {
try {
await validatePreconditions(
await shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: true,
checkShorebirdInitialized: true,
validators: doctor.iosCommandValidators,
@@ -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<int> run() async {
try {
await validatePreconditions(
await shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: true,
checkShorebirdInitialized: true,
validators: doctor.iosCommandValidators,
@@ -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<int> run() async {
try {
await validatePreconditions(
await shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: true,
checkShorebirdInitialized: true,
);
@@ -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,
);
@@ -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,
);
+2 -2
View File
@@ -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);
@@ -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?;
}
@@ -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.
@@ -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<void> 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"?',
);
@@ -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>(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);
});
@@ -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);
@@ -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>(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 {
@@ -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>(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 {
@@ -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>(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 {
@@ -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>(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);
});
@@ -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>(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);
});
});
}
@@ -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>(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);
});
});
}
@@ -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>(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);
@@ -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>(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 {
@@ -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>(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 {
@@ -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>(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 {
@@ -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 {
@@ -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>(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 {
@@ -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>(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);
});
});
}
@@ -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>(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);
});
});
}
@@ -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>(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);
});
});
}
@@ -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>(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<void>.delayed(Duration.zero);
// Wait for Isolate to finish.
await Future<void>.delayed(const Duration(milliseconds: 1));
});
when(
() => iosDeploy.installAndLaunchApp(
@@ -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>(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);
});
@@ -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>(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'),
),
);
});
});
}
@@ -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>(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();
@@ -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>(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(
@@ -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>(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 {
@@ -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 {
@@ -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>(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 {
@@ -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>(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<UnsupportedOperatingSystemException>()),
);
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<UserNotAuthorizedException>()),
);
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<ShorebirdNotInitializedException>()),
);
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<ValidationFailedException>()),
);
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);
});
});
});
}