diff --git a/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_action.rb b/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_action.rb index e70395e..69b62fc 100644 --- a/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_action.rb +++ b/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_action.rb @@ -56,7 +56,7 @@ def self.run(params) binary_type = binary_type_from_path(binary_path) UI.message("📡 Uploading the #{binary_type}.") - operation = upload_binary(app_name, binary_path, client, timeout) + operation = upload_binary(app_name, binary_path, binary_type, client, timeout) UI.message("🕵️ Validating upload…") release = poll_upload_release_operation(client, operation, binary_type) @@ -268,7 +268,7 @@ def self.poll_upload_release_operation(client, operation, binary_type) extract_release(operation) end - def self.upload_binary(app_name, binary_path, client, timeout) + def self.upload_binary(app_name, binary_path, binary_type, client, timeout) options = Google::Apis::RequestOptions.new options.max_elapsed_time = timeout # includes retries (default = no retries) options.header = { @@ -282,12 +282,21 @@ def self.upload_binary(app_name, binary_path, client, timeout) # standard http call instead and convert it to a long running object # https://github.com/googleapis/google-api-ruby-client/blob/main/generated/google-apis-firebaseappdistribution_v1/lib/google/apis/firebaseappdistribution_v1/service.rb#L79 # TODO(kbolay): Prefer client.upload_medium - response = client.http( - :post, - "https://firebaseappdistribution.googleapis.com/upload/v1/#{app_name}/releases:upload", - body: File.open(binary_path, 'rb'), - options: options - ) + response = begin + client.http( + :post, + "https://firebaseappdistribution.googleapis.com/upload/v1/#{app_name}/releases:upload", + body: File.open(binary_path, 'rb'), + options: options + ) + rescue Google::Apis::Error => err + case err.status_code.to_i + when 403 + UI.crash!(ErrorMessage::PERMISSION_DENIED_ERROR) + else + UI.crash!("#{ErrorMessage.upload_binary_error(binary_type)} (#{err}, status_code: #{err.status_code})") + end + end Google::Apis::FirebaseappdistributionV1::GoogleLongrunningOperation.from_json(response) end diff --git a/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_error_message.rb b/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_error_message.rb index bd4766e..f2e7dfe 100644 --- a/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_error_message.rb +++ b/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_error_message.rb @@ -4,6 +4,8 @@ module ErrorMessage SERVICE_CREDENTIALS_NOT_FOUND = "Service credentials file does not exist. Please check the service credentials path and try again." PARSE_SERVICE_CREDENTIALS_ERROR = "Failed to extract service account information from the service credentials file." PARSE_FIREBASE_TOOLS_JSON_ERROR = "Encountered error parsing json file. Ensure the firebase-tools.json file is formatted correctly." + PERMISSION_DENIED_ERROR = "The authenticated user does not have the required permissions on the Firebase project" + UPLOAD_BINARY_ERROR = "App Distribution halted because it had a problem uploading the app binary." UPLOAD_RELEASE_NOTES_ERROR = "App Distribution halted because it had a problem uploading release notes." UPLOAD_TESTERS_ERROR = "App Distribution halted because it had a problem adding testers/groups." GET_RELEASE_TIMEOUT = "App Distribution failed to fetch release information." diff --git a/spec/firebase_app_distribution_action_spec.rb b/spec/firebase_app_distribution_action_spec.rb index 1beb5f8..2013e58 100644 --- a/spec/firebase_app_distribution_action_spec.rb +++ b/spec/firebase_app_distribution_action_spec.rb @@ -307,7 +307,7 @@ def stub_get_aab_info(integration_state = 'INTEGRATED') end end - describe 'with a successful upload' do + describe 'with a binary file available' do let(:fake_binary_contents) { "Hello World" } let(:fake_binary) { double("Binary") } @@ -319,6 +319,26 @@ def stub_get_aab_info(integration_state = 'INTEGRATED') .and_return(fake_binary_contents) end + it 'raises permission denied error if upload returns a 403', :focus do + allow_any_instance_of(V1Api::FirebaseAppDistributionService) + .to receive(:http) + .and_raise(Google::Apis::Error.new('error', status_code: '403')) + + expect do + action.run(params) + end.to raise_error(ErrorMessage::PERMISSION_DENIED_ERROR) + end + + it 'raises error with status code if upload returns an unexpected error', :focus do + allow_any_instance_of(V1Api::FirebaseAppDistributionService) + .to receive(:http) + .and_raise(Google::Apis::Error.new({}, status_code: '404')) + + expect do + action.run(params) + end.to raise_error(/404/) + end + it 'crashes if it exceeds polling threshold' do stub_const('Fastlane::Actions::FirebaseAppDistributionAction::UPLOAD_MAX_POLLING_RETRIES', 0) allow_any_instance_of(V1Api::FirebaseAppDistributionService)