diff --git a/.rubocop.yml b/.rubocop.yml index 0a5cec9..0c49799 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -3,7 +3,7 @@ require: - rubocop/require_tools - rubocop-performance AllCops: - TargetRubyVersion: 2.6 + TargetRubyVersion: 3.2.2 NewCops: enable Include: - "**/*.rb" @@ -70,6 +70,7 @@ Require/MissingRequireStatement: - "**/Rakefile" - fastlane/**/* - supply/**/* + - "lib/fastlane/plugin/**/*.rb" Layout/FirstHashElementIndentation: Enabled: false Layout/HashAlignment: @@ -87,6 +88,8 @@ Style/MixinGrouping: - "**/spec/**/*" Lint/SuppressedException: Enabled: false +Lint/IneffectiveAccessModifier: + Enabled: false Lint/UnusedBlockArgument: Enabled: false Lint/AmbiguousBlockAssociation: @@ -120,6 +123,8 @@ Layout/LineLength: Max: 370 Metrics/ParameterLists: Max: 17 +Metrics/PerceivedComplexity: + Max: 10 Style/GuardClause: Enabled: false Style/StringLiterals: @@ -192,5 +197,4 @@ Bundler/OrderedGems: Enabled: true TreatCommentsAsGroupSeparators: false Gemspec/DevelopmentDependencies: - Enabled: true - EnforcedStyle: Gemfile + Enabled: false diff --git a/README.md b/README.md index 2637a7a..53b2547 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ -# instabug-stores-upload plugin +# instabug_stores_upload plugin -[![fastlane Plugin Badge](https://rawcdn.githack.com/fastlane/fastlane/master/fastlane/assets/plugin-badge.svg)](https://rubygems.org/gems/fastlane-plugin-instabug-stores-upload) +[![fastlane Plugin Badge](https://rawcdn.githack.com/fastlane/fastlane/master/fastlane/assets/plugin-badge.svg)](https://rubygems.org/gems/fastlane-plugin-instabug_stores_upload) ## Getting Started -This project is a [_fastlane_](https://github.com/fastlane/fastlane) plugin. To get started with `fastlane-plugin-instabug-stores-upload`, add it to your project by running: +This project is a [_fastlane_](https://github.com/fastlane/fastlane) plugin. To get started with `fastlane-plugin-instabug_stores_upload`, add it to your project by running: ```bash -fastlane add_plugin instabug-stores-upload +fastlane add_plugin instabug_stores_upload ``` -## About instabug-stores-upload +## About instabug_stores_upload Wrapper plugin for uploading builds to App Store and Play Store with Instabug-specific metadata reporting. This plugin provides custom actions that wrap the standard Fastlane actions and automatically report build and upload events to Instabug systems for better observability and integration into internal pipelines. diff --git a/fastlane-plugin-instabug-stores-upload.gemspec b/fastlane-plugin-instabug-stores-upload.gemspec index 899ae23..3e55709 100644 --- a/fastlane-plugin-instabug-stores-upload.gemspec +++ b/fastlane-plugin-instabug-stores-upload.gemspec @@ -3,7 +3,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'fastlane/plugin/instabug_stores_upload/version' Gem::Specification.new do |spec| - spec.name = 'fastlane-plugin-instabug-stores-upload' + spec.name = 'fastlane-plugin-instabug_stores_upload' spec.version = Fastlane::InstabugStoresUpload::VERSION spec.author = 'Instabug' spec.email = 'backend-team@instabug.com' @@ -17,11 +17,12 @@ Gem::Specification.new do |spec| spec.metadata['rubygems_mfa_required'] = 'true' spec.required_ruby_version = '>= 3.2.2' - spec.add_development_dependency 'bundler' - spec.add_development_dependency 'rspec' - spec.add_development_dependency 'rake' - spec.add_development_dependency 'rubocop', '1.50.2' - spec.add_development_dependency 'rubocop-require_tools' - spec.add_development_dependency 'simplecov' - spec.add_development_dependency 'fastlane', '~> 2.228.0' + spec.add_development_dependency('bundler') + spec.add_development_dependency('fastlane', '~> 2.228.0') + spec.add_development_dependency('rake') + spec.add_development_dependency('rspec') + spec.add_development_dependency('rubocop', '1.50.2') + spec.add_development_dependency('rubocop-performance') + spec.add_development_dependency('rubocop-require_tools') + spec.add_development_dependency('simplecov') end diff --git a/fastlane/Fastfile b/fastlane/Fastfile index d02d45a..ed491e0 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -3,10 +3,10 @@ # Example lane for building iOS app with Instabug reporting lane :build_ios_app do |options| branch_name = options[:branch_name] - + instabug_build_ios_app( - branch_name: branch_name, - instabug_api_key: ENV["INSTABUG_API_KEY"], + branch_name:, + instabug_api_key: ENV.fetch("INSTABUG_API_KEY", nil), # All standard build_ios_app parameters are supported workspace: "MyApp.xcworkspace", scheme: "MyApp", @@ -18,18 +18,18 @@ end # Example lane for building Android app with Instabug reporting lane :build_android_app do |options| branch_name = options[:branch_name] - + instabug_build_android_app( - branch_name: branch_name, - instabug_api_key: ENV["INSTABUG_API_KEY"], + branch_name:, + instabug_api_key: ENV.fetch("INSTABUG_API_KEY", nil), # All standard gradle parameters are supported task: "assembleRelease", project_dir: "android/", properties: { "android.injected.signing.store.file" => "keystore.jks", - "android.injected.signing.store.password" => ENV["KEYSTORE_PASSWORD"], + "android.injected.signing.store.password" => ENV.fetch("KEYSTORE_PASSWORD", nil), "android.injected.signing.key.alias" => "key0", - "android.injected.signing.key.password" => ENV["KEY_PASSWORD"] + "android.injected.signing.key.password" => ENV.fetch("KEY_PASSWORD", nil) } ) end @@ -37,23 +37,23 @@ end # Example lane for uploading to App Store with Instabug reporting lane :upload_to_app_store do |options| branch_name = options[:branch_name] - + instabug_upload_to_app_store( - branch_name: branch_name, - instabug_api_key: ENV["INSTABUG_API_KEY"], + branch_name:, + instabug_api_key: ENV.fetch("INSTABUG_API_KEY", nil), # All standard upload_to_app_store parameters are supported ipa: "path/to/your/app.ipa", skip_waiting_for_build_processing: true ) end -# Example lane for uploading to Play Store with Instabug reporting +# Example lane for uploading to Play Store with Instabug reporting lane :upload_to_play_store do |options| - branch_name = options[:branch_name] - + branch_name = options[:branch_name] + instabug_upload_to_play_store( - branch_name: branch_name, - instabug_api_key: ENV["INSTABUG_API_KEY"], + branch_name:, + instabug_api_key: ENV.fetch("INSTABUG_API_KEY", nil), # All standard upload_to_play_store parameters are supported aab: "path/to/your/app.aab", track: "internal" diff --git a/lib/fastlane/plugin/instabug_stores_upload/actions/instabug_build_android_app_action.rb b/lib/fastlane/plugin/instabug_stores_upload/actions/instabug_build_android_app_action.rb index db9e8f1..fcf2274 100644 --- a/lib/fastlane/plugin/instabug_stores_upload/actions/instabug_build_android_app_action.rb +++ b/lib/fastlane/plugin/instabug_stores_upload/actions/instabug_build_android_app_action.rb @@ -6,47 +6,71 @@ module Actions class InstabugBuildAndroidAppAction < Action def self.run(params) UI.message("Starting Instabug Android build...") - + # Extract Instabug-specific parameters - branch_name = params.delete(:branch_name) - instabug_api_key = params.delete(:instabug_api_key) - + branch_name = params[:branch_name] + instabug_api_key = params[:instabug_api_key] + # Validate required parameters if branch_name.nil? || branch_name.empty? UI.user_error!("branch_name is required for Instabug reporting") end - + + # Filter out Instabug-specific parameters before passing to gradle + filtered_params = Helper::InstabugStoresUploadHelper.filter_instabug_params(params, Actions::GradleAction) + begin # Report build start to Instabug Helper::InstabugStoresUploadHelper.report_status( - branch_name: branch_name, + branch_name:, api_key: instabug_api_key, status: "inprogress", step: "build_app" ) + # Start timing the build + build_start_time = Time.now + # Execute the actual Android build using gradle - result = Actions::GradleAction.run(params) + result = Actions::GradleAction.run(filtered_params) + + # Calculate build time in seconds + build_time = (Time.now - build_start_time).round + + # Extract Android build path (APK or AAB) + build_path = fetch_android_build_path(Actions.lane_context) + + if build_path.nil? || build_path.empty? + UI.user_error!("Could not find any generated APK or AAB. Please check your gradle settings.") + else + UI.success("Successfully found build artifact(s) at: #{build_path}") + end # Report build success to Instabug Helper::InstabugStoresUploadHelper.report_status( - branch_name: branch_name, + branch_name:, api_key: instabug_api_key, status: "success", - step: "build_app" + step: "build_app", + extras: { + build_time:, + build_path: Array(build_path) + } ) UI.success("Android build completed successfully!") result - rescue => e - UI.error("Android build failed: #{e.message}") + rescue StandardError => e + error_message = Helper::InstabugStoresUploadHelper.extract_error_message(e.message) + UI.error("Android build failed: #{error_message}") # Report build failure to Instabug Helper::InstabugStoresUploadHelper.report_status( - branch_name: branch_name, + branch_name:, api_key: instabug_api_key, status: "failure", - step: "build_app" + step: "build_app", + error_message: error_message ) raise e end @@ -71,7 +95,7 @@ def self.details def self.available_options # Start with the original gradle options options = Actions::GradleAction.available_options - + # Add Instabug-specific options instabug_options = [ FastlaneCore::ConfigItem.new( @@ -88,9 +112,17 @@ def self.available_options optional: false, type: String, sensitive: true - ) + ), + FastlaneCore::ConfigItem.new( + key: :instabug_api_base_url, + env_name: "INSTABUG_API_BASE_URL", + description: "Instabug API base URL (defaults to https://api.instabug.com)", + optional: true, + type: String, + skip_type_validation: true # Since we don't extract this param + ) ] - + # Combine both sets of options options + instabug_options end @@ -119,6 +151,24 @@ def self.example_code def self.category :building end + + # This helper method provides a clean and prioritized way to get the Android build output. + # It checks for the most common output types in a specific order. + # This is used to get the build path for the Android build artifact. + def self.fetch_android_build_path(lane_context) + build_keys = [ + SharedValues::GRADLE_ALL_APK_OUTPUT_PATHS, + SharedValues::GRADLE_APK_OUTPUT_PATH, + SharedValues::GRADLE_ALL_AAB_OUTPUT_PATHS, + SharedValues::GRADLE_AAB_OUTPUT_PATH + ] + build_keys.each do |build_key| + build_path = lane_context[build_key] + return build_path if build_path && !build_path.empty? + end + + nil + end end end -end \ No newline at end of file +end diff --git a/lib/fastlane/plugin/instabug_stores_upload/actions/instabug_build_ios_app_action.rb b/lib/fastlane/plugin/instabug_stores_upload/actions/instabug_build_ios_app_action.rb index 54c2707..e6923bd 100644 --- a/lib/fastlane/plugin/instabug_stores_upload/actions/instabug_build_ios_app_action.rb +++ b/lib/fastlane/plugin/instabug_stores_upload/actions/instabug_build_ios_app_action.rb @@ -6,47 +6,71 @@ module Actions class InstabugBuildIosAppAction < Action def self.run(params) UI.message("Starting Instabug iOS build...") - + # Extract Instabug-specific parameters - branch_name = params.delete(:branch_name) - instabug_api_key = params.delete(:instabug_api_key) - + branch_name = params[:branch_name] + instabug_api_key = params[:instabug_api_key] + # Validate required parameters if branch_name.nil? || branch_name.empty? UI.user_error!("branch_name is required for Instabug reporting") end - + + # Filter out Instabug-specific parameters before passing to build_ios_app + filtered_params = Helper::InstabugStoresUploadHelper.filter_instabug_params(params, Actions::BuildIosAppAction) + begin # Report build start to Instabug Helper::InstabugStoresUploadHelper.report_status( - branch_name: branch_name, + branch_name:, api_key: instabug_api_key, status: "inprogress", step: "build_app" ) + # Start timing the build + build_start_time = Time.now + # Execute the actual iOS build - result = Actions::BuildIosAppAction.run(params) + result = Actions::BuildIosAppAction.run(filtered_params) + + # Calculate build time in seconds + build_time = (Time.now - build_start_time).round + + # Extract IPA path from Fastlane environment + build_path = Actions.lane_context[SharedValues::IPA_OUTPUT_PATH] + + if build_path + UI.success("IPA Output Path: #{build_path}") + else + UI.error("No IPA path found.") + end # Report build success to Instabug Helper::InstabugStoresUploadHelper.report_status( - branch_name: branch_name, + branch_name:, api_key: instabug_api_key, status: "success", - step: "build_app" + step: "build_app", + extras: { + build_time:, + build_path: Array(build_path) + } ) UI.success("iOS build completed successfully!") result - rescue => e - UI.error("iOS build failed: #{e.message}") + rescue StandardError => e + error_message = Helper::InstabugStoresUploadHelper.extract_error_message(e.message) + UI.error("iOS build failed: #{error_message}") # Report build failure to Instabug Helper::InstabugStoresUploadHelper.report_status( - branch_name: branch_name, + branch_name:, api_key: instabug_api_key, status: "failure", - step: "build_app" + step: "build_app", + error_message: error_message ) raise e end @@ -71,7 +95,7 @@ def self.details def self.available_options # Start with the original build_ios_app options options = Actions::BuildIosAppAction.available_options - + # Add Instabug-specific options instabug_options = [ FastlaneCore::ConfigItem.new( @@ -88,9 +112,17 @@ def self.available_options optional: false, type: String, sensitive: true - ) + ), + FastlaneCore::ConfigItem.new( + key: :instabug_api_base_url, + env_name: "INSTABUG_API_BASE_URL", + description: "Instabug API base URL (defaults to https://api.instabug.com)", + optional: true, + type: String, + skip_type_validation: true # Since we don't extract this param + ) ] - + # Combine both sets of options options + instabug_options end @@ -117,4 +149,4 @@ def self.category end end end -end \ No newline at end of file +end diff --git a/lib/fastlane/plugin/instabug_stores_upload/actions/instabug_stores_upload_action.rb b/lib/fastlane/plugin/instabug_stores_upload/actions/instabug_stores_upload_action.rb index 288263f..4c1f6d2 100644 --- a/lib/fastlane/plugin/instabug_stores_upload/actions/instabug_stores_upload_action.rb +++ b/lib/fastlane/plugin/instabug_stores_upload/actions/instabug_stores_upload_action.rb @@ -5,7 +5,7 @@ module Fastlane module Actions class InstabugStoresUploadAction < Action def self.run(params) - UI.message("The instabug-stores-upload plugin is working!") + UI.message("The instabug_stores_upload plugin is working!") end def self.description diff --git a/lib/fastlane/plugin/instabug_stores_upload/actions/instabug_upload_to_app_store_action.rb b/lib/fastlane/plugin/instabug_stores_upload/actions/instabug_upload_to_app_store_action.rb index c1b512f..ef14569 100644 --- a/lib/fastlane/plugin/instabug_stores_upload/actions/instabug_upload_to_app_store_action.rb +++ b/lib/fastlane/plugin/instabug_stores_upload/actions/instabug_upload_to_app_store_action.rb @@ -6,31 +6,34 @@ module Actions class InstabugUploadToAppStoreAction < Action def self.run(params) UI.message("Starting Instabug App Store upload...") - + # Extract Instabug-specific parameters - branch_name = params.delete(:branch_name) - instabug_api_key = params.delete(:instabug_api_key) - + branch_name = params[:branch_name] + instabug_api_key = params[:instabug_api_key] + # Validate required parameters if branch_name.nil? || branch_name.empty? UI.user_error!("branch_name is required for Instabug reporting") end - + + # Filter out Instabug-specific parameters before passing to upload_to_app_store + filtered_params = Helper::InstabugStoresUploadHelper.filter_instabug_params(params, Actions::UploadToAppStoreAction) + begin # Report upload start to Instabug Helper::InstabugStoresUploadHelper.report_status( - branch_name: branch_name, + branch_name:, api_key: instabug_api_key, status: "inprogress", step: "upload_to_the_store" ) # Execute the actual upload to App Store - result = Actions::UploadToAppStoreAction.run(params) + result = Actions::UploadToAppStoreAction.run(filtered_params) # Report upload success to Instabug Helper::InstabugStoresUploadHelper.report_status( - branch_name: branch_name, + branch_name:, api_key: instabug_api_key, status: "success", step: "upload_to_the_store" @@ -38,15 +41,18 @@ def self.run(params) UI.success("App Store upload completed successfully!") result - rescue => e - UI.error("App Store upload failed: #{e.message}") + rescue StandardError => e + error_message = Helper::InstabugStoresUploadHelper.extract_error_message(e.message) + + UI.error("App Store upload failed: #{error_message}") # Report upload failure to Instabug Helper::InstabugStoresUploadHelper.report_status( - branch_name: branch_name, + branch_name:, api_key: instabug_api_key, status: "failure", - step: "upload_to_the_store" + step: "upload_to_the_store", + error_message: e.message ) raise e end @@ -71,7 +77,7 @@ def self.details def self.available_options # Start with the original upload_to_app_store options options = Actions::UploadToAppStoreAction.available_options - + # Add Instabug-specific options instabug_options = [ FastlaneCore::ConfigItem.new( @@ -88,9 +94,9 @@ def self.available_options optional: false, type: String, sensitive: true - ) + ) ] - + # Combine both sets of options options + instabug_options end @@ -116,4 +122,4 @@ def self.category end end end -end \ No newline at end of file +end diff --git a/lib/fastlane/plugin/instabug_stores_upload/actions/instabug_upload_to_play_store_action.rb b/lib/fastlane/plugin/instabug_stores_upload/actions/instabug_upload_to_play_store_action.rb index c13d788..b183c3d 100644 --- a/lib/fastlane/plugin/instabug_stores_upload/actions/instabug_upload_to_play_store_action.rb +++ b/lib/fastlane/plugin/instabug_stores_upload/actions/instabug_upload_to_play_store_action.rb @@ -6,48 +6,52 @@ module Actions class InstabugUploadToPlayStoreAction < Action def self.run(params) UI.message("Starting Instabug Play Store upload...") - + # Extract Instabug-specific parameters - branch_name = params.delete(:branch_name) - instabug_api_key = params.delete(:instabug_api_key) + branch_name = params[:branch_name] + instabug_api_key = params[:instabug_api_key] # Validate required parameters if branch_name.nil? || branch_name.empty? UI.user_error!("branch_name is required for Instabug reporting") end + # Filter out Instabug-specific parameters before passing to upload_to_play_store + filtered_params = Helper::InstabugStoresUploadHelper.filter_instabug_params(params, Actions::UploadToPlayStoreAction) + begin # Report upload start to Instabug Helper::InstabugStoresUploadHelper.report_status( - branch_name: branch_name, + branch_name:, api_key: instabug_api_key, status: "inprogress", step: "upload_to_the_store" ) # Execute the actual upload to Play Store - result = Actions::UploadToPlayStoreAction.run(params) - + result = Actions::UploadToPlayStoreAction.run(filtered_params) + # Report upload success to Instabug Helper::InstabugStoresUploadHelper.report_status( - branch_name: branch_name, + branch_name:, api_key: instabug_api_key, status: "success", step: "upload_to_the_store" ) - + UI.success("Play Store upload completed successfully!") result - - rescue => e - UI.error("Play Store upload failed: #{e.message}") + rescue StandardError => e + error_message = Helper::InstabugStoresUploadHelper.extract_error_message(e.message) + UI.error("Play Store upload failed: #{error_message}") # Report upload failure to Instabug Helper::InstabugStoresUploadHelper.report_status( - branch_name: branch_name, + branch_name:, api_key: instabug_api_key, status: "failure", - step: "upload_to_the_store" + step: "upload_to_the_store", + error_message: e.message ) raise e end @@ -72,7 +76,7 @@ def self.details def self.available_options # Start with the original upload_to_play_store options options = Actions::UploadToPlayStoreAction.available_options - + # Add Instabug-specific options instabug_options = [ FastlaneCore::ConfigItem.new( @@ -91,7 +95,7 @@ def self.available_options sensitive: true ) ] - + # Combine both sets of options options + instabug_options end @@ -118,4 +122,4 @@ def self.category end end end -end \ No newline at end of file +end diff --git a/lib/fastlane/plugin/instabug_stores_upload/helper/instabug_stores_upload_helper.rb b/lib/fastlane/plugin/instabug_stores_upload/helper/instabug_stores_upload_helper.rb index 25a772e..804d83d 100644 --- a/lib/fastlane/plugin/instabug_stores_upload/helper/instabug_stores_upload_helper.rb +++ b/lib/fastlane/plugin/instabug_stores_upload/helper/instabug_stores_upload_helper.rb @@ -8,60 +8,95 @@ module Fastlane module Helper class InstabugStoresUploadHelper - # Base URL for Instabug API we will need to adjust it for STs - INSTABUG_API_BASE_URL = "https://api.instabug.com".freeze + # Default Base URL for Instabug API + DEFAULT_INSTABUG_API_BASE_URL = "https://api.instabug.com".freeze + INSTABUG_KEYS = %i[branch_name instabug_api_key instabug_api_base_url].freeze + + # Extract the important part of an error message + def self.extract_error_message(error_message) + return error_message unless error_message.is_a?(String) + + lines = error_message.split("\n") + start_index = lines.find_index { |line| line.strip.start_with?("* What went wrong:") } + end_index = lines.find_index { |line| line.strip.start_with?("* Try:") } + + if start_index && end_index && end_index > start_index + extracted_lines = lines[(start_index + 1)...end_index].map(&:strip).reject(&:empty?) + return extracted_lines.join(" ")[0, 250] unless extracted_lines.empty? + end + + # Fallback message + "Your build was triggered but failed during execution. " \ + "This could be due to missing environment variables or incorrect build credentials. " \ + "Check CI logs for full details." + end def self.show_message UI.message("Hello from the instabug_stores_upload plugin helper!") end - def self.report_status(branch_name:, api_key:, status:, step:) + # Filters out Instabug-specific parameters from the params configuration + # and returns a new FastlaneCore::Configuration object with only the target action's parameters + def self.filter_instabug_params(params, target_action_class) + filtered_config = {} + params.available_options.each do |option| + key = option.key + filtered_config[key] = params[key] unless INSTABUG_KEYS.include?(key) + end + + FastlaneCore::Configuration.create(target_action_class.available_options, filtered_config) + end + + def self.report_status(branch_name:, api_key:, status:, step:, extras: {}, error_message: nil) return unless branch_name.start_with?('crash-fix/instabug-crash-') - + UI.message("📡 Reporting #{step} status to Instabug for #{branch_name}/#{status}") - + make_api_request( - branch_name: branch_name, - status: status, - api_key: api_key, - step: step + branch_name:, + status:, + api_key:, + step:, + extras:, + error_message: ) end - private - - def self.make_api_request(branch_name:, status:, api_key:, step:) + def self.make_api_request(branch_name:, status:, api_key:, step:, extras: {}, error_message: nil) return unless api_key - uri = URI.parse("#{INSTABUG_API_BASE_URL}/api/web/public/agent_fastlane/status") - + # Determine API base URL from env var or default + base_url = ENV['INSTABUG_API_BASE_URL'] || DEFAULT_INSTABUG_API_BASE_URL + uri = URI.parse("#{base_url}/api/web/public/agent_fastlane/status") + payload = { - branch_name: branch_name, - status: status, - step: step + branch_name:, + status:, + step:, + extras:, + error_message: } - + begin http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true http.read_timeout = 30 http.open_timeout = 30 - + request = Net::HTTP::Patch.new(uri.path) request['Content-Type'] = 'application/json' request['Authorization'] = "Bearer #{api_key}" - request['User-Agent'] = "fastlane-plugin-instabug-stores-upload" + request['User-Agent'] = "fastlane-plugin-instabug_stores_upload" request.body = payload.to_json - + response = http.request(request) - + case response.code.to_i when 200..299 UI.success("✅ Successfully reported to Instabug") else UI.error("❌ Unknown error reporting to Instabug: #{response.code} #{response.message}") end - rescue Net::TimeoutError UI.error("❌ Timeout while reporting to Instabug") rescue Net::OpenTimeout diff --git a/spec/instabug_build_android_app_action_spec.rb b/spec/instabug_build_android_app_action_spec.rb index e4e22aa..e2fd800 100644 --- a/spec/instabug_build_android_app_action_spec.rb +++ b/spec/instabug_build_android_app_action_spec.rb @@ -23,7 +23,12 @@ describe '#run' do context 'when build succeeds' do - it 'reports inprogress, calls build action, and reports success' do + it 'reports inprogress, calls build action, and reports success with timing and path' do + # Mock the lane context to return a build path + allow(Fastlane::Actions).to receive(:lane_context).and_return({ + Fastlane::Actions::SharedValues::GRADLE_APK_OUTPUT_PATH => '/path/to/app.apk' + }) + expect(Fastlane::Actions::GradleAction).to receive(:run) .with(hash_including(task: 'assembleRelease', project_dir: 'android/')) .and_return('build_result') @@ -36,28 +41,38 @@ body: { branch_name: 'crash-fix/instabug-crash-456', status: 'inprogress', - step: 'build_app' + step: 'build_app', + extras: {}, + error_message: nil }.to_json, headers: { 'Content-Type' => 'application/json', 'Authorization' => 'Bearer test-api-key', - 'User-Agent' => 'fastlane-plugin-instabug-stores-upload' + 'User-Agent' => 'fastlane-plugin-instabug_stores_upload' } ).once expect(WebMock).to have_requested(:patch, api_endpoint) - .with( - body: { - branch_name: 'crash-fix/instabug-crash-456', - status: 'success', - step: 'build_app' - }.to_json, - headers: { - 'Content-Type' => 'application/json', - 'Authorization' => 'Bearer test-api-key', - 'User-Agent' => 'fastlane-plugin-instabug-stores-upload' - } - ).once + .with { |req| + body = JSON.parse(req.body) + body['status'] == 'success' && + body['branch_name'] == 'crash-fix/instabug-crash-456' && + body['step'] == 'build_app' && + body['extras']['build_path'] == '/path/to/app.apk' && + body['extras']['build_time'].kind_of?(Integer) + }.once + end + + it 'fails when no build artifact is found' do + # Mock empty lane context + allow(Fastlane::Actions).to receive(:lane_context).and_return({}) + + expect(Fastlane::Actions::GradleAction).to receive(:run) + .and_return('build_result') + + expect do + described_class.run(valid_params) + end.to raise_error(FastlaneCore::Interface::FastlaneError, /Could not find any generated APK or AAB/) end end @@ -67,16 +82,18 @@ expect(Fastlane::Actions::GradleAction).to receive(:run) .and_raise(error) - expect { + expect do described_class.run(valid_params) - }.to raise_error(StandardError, 'Build failed') + end.to raise_error(StandardError, 'Build failed') expect(WebMock).to have_requested(:patch, api_endpoint) .with( body: { branch_name: 'crash-fix/instabug-crash-456', status: 'failure', - step: 'build_app' + step: 'build_app', + extras: {}, + error_message: 'Build failed' }.to_json ) end @@ -86,9 +103,9 @@ it 'raises user error' do params = valid_params.merge(branch_name: nil) - expect { + expect do described_class.run(params) - }.to raise_error(FastlaneCore::Interface::FastlaneError, 'branch_name is required for Instabug reporting') + end.to raise_error(FastlaneCore::Interface::FastlaneError, 'branch_name is required for Instabug reporting') end end @@ -96,16 +113,21 @@ it 'raises user error' do params = valid_params.merge(branch_name: '') - expect { + expect do described_class.run(params) - }.to raise_error(FastlaneCore::Interface::FastlaneError, 'branch_name is required for Instabug reporting') + end.to raise_error(FastlaneCore::Interface::FastlaneError, 'branch_name is required for Instabug reporting') end end context 'when branch name does not match instabug pattern' do it 'does not make API calls but still runs build' do params = valid_params.merge(branch_name: 'feature/new-feature') - + + # Mock successful build with a valid build path to avoid validation error + allow(Fastlane::Actions).to receive(:lane_context).and_return({ + Fastlane::Actions::SharedValues::GRADLE_APK_OUTPUT_PATH => '/path/to/app.apk' + }) + expect(Fastlane::Actions::GradleAction).to receive(:run) .and_return('build_result') @@ -117,6 +139,58 @@ end end + describe '.fetch_android_build_path' do + let(:lane_context) { {} } + + context 'when all AAB output paths are available' do + it 'returns all AAB paths' do + lane_context[Fastlane::Actions::SharedValues::GRADLE_ALL_AAB_OUTPUT_PATHS] = ['/path/to/app1.aab', '/path/to/app2.aab'] + + result = described_class.fetch_android_build_path(lane_context) + + expect(result).to eq(['/path/to/app1.aab', '/path/to/app2.aab']) + end + end + + context 'when single AAB output path is available' do + it 'returns single AAB path' do + lane_context[Fastlane::Actions::SharedValues::GRADLE_AAB_OUTPUT_PATH] = '/path/to/app.aab' + + result = described_class.fetch_android_build_path(lane_context) + + expect(result).to eq('/path/to/app.aab') + end + end + + context 'when all APK output paths are available' do + it 'returns all APK paths' do + lane_context[Fastlane::Actions::SharedValues::GRADLE_ALL_APK_OUTPUT_PATHS] = ['/path/to/app1.apk', '/path/to/app2.apk'] + + result = described_class.fetch_android_build_path(lane_context) + + expect(result).to eq(['/path/to/app1.apk', '/path/to/app2.apk']) + end + end + + context 'when single APK output path is available' do + it 'returns single APK path' do + lane_context[Fastlane::Actions::SharedValues::GRADLE_APK_OUTPUT_PATH] = '/path/to/app.apk' + + result = described_class.fetch_android_build_path(lane_context) + + expect(result).to eq('/path/to/app.apk') + end + end + + context 'when no build paths are available' do + it 'returns nil' do + result = described_class.fetch_android_build_path(lane_context) + + expect(result).to be_nil + end + end + end + describe 'metadata' do it 'has correct description' do expect(described_class.description).to eq('Build Android app with Instabug metadata reporting') @@ -132,4 +206,4 @@ expect(described_class.category).to eq(:building) end end -end \ No newline at end of file +end diff --git a/spec/instabug_build_ios_app_action_spec.rb b/spec/instabug_build_ios_app_action_spec.rb index e7e6337..902f7d2 100644 --- a/spec/instabug_build_ios_app_action_spec.rb +++ b/spec/instabug_build_ios_app_action_spec.rb @@ -20,7 +20,12 @@ describe '#run' do context 'when build succeeds' do - it 'reports inprogress, calls build action, and reports success' do + it 'reports inprogress, calls build action, and reports success with timing and path' do + # Mock the lane context to return an IPA path + allow(Fastlane::Actions).to receive(:lane_context).and_return({ + Fastlane::Actions::SharedValues::IPA_OUTPUT_PATH => '/path/to/app.ipa' + }) + expect(Fastlane::Actions::BuildIosAppAction).to receive(:run) .with(hash_including(workspace: 'Test.xcworkspace', scheme: 'Test', export_method: 'app-store')) .and_return('build_result') @@ -33,28 +38,47 @@ body: { branch_name: 'crash-fix/instabug-crash-123', status: 'inprogress', - step: 'build_app' + step: 'build_app', + extras: {}, + error_message: nil }.to_json, headers: { 'Content-Type' => 'application/json', 'Authorization' => 'Bearer test-api-key', - 'User-Agent' => 'fastlane-plugin-instabug-stores-upload' + 'User-Agent' => 'fastlane-plugin-instabug_stores_upload' } ).once expect(WebMock).to have_requested(:patch, api_endpoint) - .with( - body: { - branch_name: 'crash-fix/instabug-crash-123', - status: 'success', - step: 'build_app' - }.to_json, - headers: { - 'Content-Type' => 'application/json', - 'Authorization' => 'Bearer test-api-key', - 'User-Agent' => 'fastlane-plugin-instabug-stores-upload' - } - ).once + .with { |req| + body = JSON.parse(req.body) + body['status'] == 'success' && + body['branch_name'] == 'crash-fix/instabug-crash-123' && + body['step'] == 'build_app' && + body['extras']['build_path'] == '/path/to/app.ipa' && + body['extras']['build_time'].kind_of?(Integer) + }.once + end + + it 'handles missing IPA path gracefully' do + # Mock empty lane context + allow(Fastlane::Actions).to receive(:lane_context).and_return({}) + + expect(Fastlane::Actions::BuildIosAppAction).to receive(:run) + .and_return('build_result') + + result = described_class.run(valid_params) + + expect(result).to eq('build_result') + expect(WebMock).to have_requested(:patch, api_endpoint) + .with { |req| + body = JSON.parse(req.body) + body['status'] == 'success' && + body['branch_name'] == 'crash-fix/instabug-crash-123' && + body['step'] == 'build_app' && + body['extras']['build_path'].nil? && + body['extras']['build_time'].kind_of?(Integer) + }.once end end @@ -64,16 +88,18 @@ expect(Fastlane::Actions::BuildIosAppAction).to receive(:run) .and_raise(error) - expect { + expect do described_class.run(valid_params) - }.to raise_error(StandardError, 'Build failed') + end.to raise_error(StandardError, 'Build failed') expect(WebMock).to have_requested(:patch, api_endpoint) .with( body: { branch_name: 'crash-fix/instabug-crash-123', status: 'failure', - step: 'build_app' + step: 'build_app', + extras: {}, + error_message: 'Build failed' }.to_json ) end @@ -83,9 +109,9 @@ it 'raises user error' do params = valid_params.merge(branch_name: nil) - expect { + expect do described_class.run(params) - }.to raise_error(FastlaneCore::Interface::FastlaneError, 'branch_name is required for Instabug reporting') + end.to raise_error(FastlaneCore::Interface::FastlaneError, 'branch_name is required for Instabug reporting') end end @@ -93,16 +119,16 @@ it 'raises user error' do params = valid_params.merge(branch_name: '') - expect { + expect do described_class.run(params) - }.to raise_error(FastlaneCore::Interface::FastlaneError, 'branch_name is required for Instabug reporting') + end.to raise_error(FastlaneCore::Interface::FastlaneError, 'branch_name is required for Instabug reporting') end end context 'when branch name does not match instabug pattern' do it 'does not make API calls but still runs build' do params = valid_params.merge(branch_name: 'feature/new-feature') - + expect(Fastlane::Actions::BuildIosAppAction).to receive(:run) .and_return('build_result') @@ -129,4 +155,4 @@ expect(described_class.category).to eq(:building) end end -end \ No newline at end of file +end diff --git a/spec/instabug_stores_upload_action_spec.rb b/spec/instabug_stores_upload_action_spec.rb index c1fcd60..20eeadf 100644 --- a/spec/instabug_stores_upload_action_spec.rb +++ b/spec/instabug_stores_upload_action_spec.rb @@ -1,7 +1,7 @@ describe Fastlane::Actions::InstabugStoresUploadAction do describe '#run' do it 'prints a message' do - expect(Fastlane::UI).to receive(:message).with("The instabug-stores-upload plugin is working!") + expect(Fastlane::UI).to receive(:message).with("The instabug_stores_upload plugin is working!") Fastlane::Actions::InstabugStoresUploadAction.run(nil) end diff --git a/spec/instabug_stores_upload_helper_spec.rb b/spec/instabug_stores_upload_helper_spec.rb new file mode 100644 index 0000000..575b14f --- /dev/null +++ b/spec/instabug_stores_upload_helper_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' +require 'webmock/rspec' + +describe Fastlane::Helper::InstabugStoresUploadHelper do + describe '.report_status' do + let(:api_endpoint) { 'https://api.instabug.com/api/web/public/agent_fastlane/status' } + + before do + stub_request(:patch, api_endpoint) + .to_return(status: 200, body: '{}', headers: {}) + end + + context 'when branch name matches instabug pattern' do + it 'makes API request for instabug crash branch' do + described_class.report_status( + branch_name: 'crash-fix/instabug-crash-123', + api_key: 'test-key', + status: 'success', + step: 'build_app' + ) + + expect(WebMock).to have_requested(:patch, api_endpoint) + .with( + body: { + branch_name: 'crash-fix/instabug-crash-123', + status: 'success', + step: 'build_app', + extras: {}, + error_message: nil + }.to_json, + headers: { + 'Content-Type' => 'application/json', + 'Authorization' => 'Bearer test-key', + 'User-Agent' => 'fastlane-plugin-instabug_stores_upload' + } + ).once + end + end + + context 'when branch name does not match instabug pattern' do + it 'does not make API request' do + described_class.report_status( + branch_name: 'feature/new-feature', + api_key: 'test-key', + status: 'success', + step: 'build_app' + ) + + expect(WebMock).not_to have_requested(:patch, api_endpoint) + end + end + + context 'when no api_key is provided' do + it 'does not make API request' do + described_class.report_status( + branch_name: 'crash-fix/instabug-crash-123', + api_key: nil, + status: 'success', + step: 'build_app' + ) + + expect(WebMock).not_to have_requested(:patch, api_endpoint) + end + end + end +end diff --git a/spec/instabug_upload_to_app_store_action_spec.rb b/spec/instabug_upload_to_app_store_action_spec.rb index 9fa4c46..430cfc1 100644 --- a/spec/instabug_upload_to_app_store_action_spec.rb +++ b/spec/instabug_upload_to_app_store_action_spec.rb @@ -32,12 +32,14 @@ body: { branch_name: 'crash-fix/instabug-crash-123', status: 'inprogress', - step: 'upload_to_the_store' + step: 'upload_to_the_store', + extras: {}, + error_message: nil }.to_json, headers: { 'Content-Type' => 'application/json', 'Authorization' => 'Bearer test-api-key', - 'User-Agent' => 'fastlane-plugin-instabug-stores-upload' + 'User-Agent' => 'fastlane-plugin-instabug_stores_upload' } ).once @@ -46,12 +48,14 @@ body: { branch_name: 'crash-fix/instabug-crash-123', status: 'success', - step: 'upload_to_the_store' + step: 'upload_to_the_store', + extras: {}, + error_message: nil }.to_json, headers: { 'Content-Type' => 'application/json', 'Authorization' => 'Bearer test-api-key', - 'User-Agent' => 'fastlane-plugin-instabug-stores-upload' + 'User-Agent' => 'fastlane-plugin-instabug_stores_upload' } ).once end @@ -63,16 +67,18 @@ expect(Fastlane::Actions::UploadToAppStoreAction).to receive(:run) .and_raise(error) - expect { + expect do described_class.run(valid_params) - }.to raise_error(StandardError, 'Upload failed') + end.to raise_error(StandardError, 'Upload failed') expect(WebMock).to have_requested(:patch, api_endpoint) .with( body: { branch_name: 'crash-fix/instabug-crash-123', status: 'failure', - step: 'upload_to_the_store' + step: 'upload_to_the_store', + extras: {}, + error_message: 'Upload failed' }.to_json ) end @@ -82,9 +88,9 @@ it 'raises user error' do params = valid_params.merge(branch_name: nil) - expect { + expect do described_class.run(params) - }.to raise_error(FastlaneCore::Interface::FastlaneError, 'branch_name is required for Instabug reporting') + end.to raise_error(FastlaneCore::Interface::FastlaneError, 'branch_name is required for Instabug reporting') end end @@ -92,16 +98,16 @@ it 'raises user error' do params = valid_params.merge(branch_name: '') - expect { + expect do described_class.run(params) - }.to raise_error(FastlaneCore::Interface::FastlaneError, 'branch_name is required for Instabug reporting') + end.to raise_error(FastlaneCore::Interface::FastlaneError, 'branch_name is required for Instabug reporting') end end context 'when branch name does not match instabug pattern' do it 'does not make API calls but still runs upload' do params = valid_params.merge(branch_name: 'feature/new-feature') - + expect(Fastlane::Actions::UploadToAppStoreAction).to receive(:run) .and_return('upload_result') @@ -128,4 +134,4 @@ expect(described_class.category).to eq(:app_store_connect) end end -end \ No newline at end of file +end diff --git a/spec/instabug_upload_to_play_store_action_spec.rb b/spec/instabug_upload_to_play_store_action_spec.rb index ff0ae09..9d43e4a 100644 --- a/spec/instabug_upload_to_play_store_action_spec.rb +++ b/spec/instabug_upload_to_play_store_action_spec.rb @@ -33,12 +33,14 @@ body: { branch_name: 'crash-fix/instabug-crash-456', status: 'inprogress', - step: 'upload_to_the_store' + step: 'upload_to_the_store', + extras: {}, + error_message: nil }.to_json, headers: { 'Content-Type' => 'application/json', 'Authorization' => 'Bearer test-api-key', - 'User-Agent' => 'fastlane-plugin-instabug-stores-upload' + 'User-Agent' => 'fastlane-plugin-instabug_stores_upload' } ).once @@ -47,12 +49,14 @@ body: { branch_name: 'crash-fix/instabug-crash-456', status: 'success', - step: 'upload_to_the_store' + step: 'upload_to_the_store', + extras: {}, + error_message: nil }.to_json, headers: { 'Content-Type' => 'application/json', 'Authorization' => 'Bearer test-api-key', - 'User-Agent' => 'fastlane-plugin-instabug-stores-upload' + 'User-Agent' => 'fastlane-plugin-instabug_stores_upload' } ).once end @@ -64,16 +68,18 @@ expect(Fastlane::Actions::UploadToPlayStoreAction).to receive(:run) .and_raise(error) - expect { + expect do described_class.run(valid_params) - }.to raise_error(StandardError, 'Upload failed') + end.to raise_error(StandardError, 'Upload failed') expect(WebMock).to have_requested(:patch, api_endpoint) .with( body: { branch_name: 'crash-fix/instabug-crash-456', status: 'failure', - step: 'upload_to_the_store' + step: 'upload_to_the_store', + extras: {}, + error_message: 'Upload failed' }.to_json ) end @@ -83,9 +89,9 @@ it 'raises user error' do params = valid_params.merge(branch_name: nil) - expect { + expect do described_class.run(params) - }.to raise_error(FastlaneCore::Interface::FastlaneError, 'branch_name is required for Instabug reporting') + end.to raise_error(FastlaneCore::Interface::FastlaneError, 'branch_name is required for Instabug reporting') end end @@ -93,16 +99,16 @@ it 'raises user error' do params = valid_params.merge(branch_name: '') - expect { + expect do described_class.run(params) - }.to raise_error(FastlaneCore::Interface::FastlaneError, 'branch_name is required for Instabug reporting') + end.to raise_error(FastlaneCore::Interface::FastlaneError, 'branch_name is required for Instabug reporting') end end context 'when branch name does not match instabug pattern' do it 'does not make API calls but still runs upload' do params = valid_params.merge(branch_name: 'feature/new-feature') - + expect(Fastlane::Actions::UploadToPlayStoreAction).to receive(:run) .and_return('upload_result') @@ -129,4 +135,4 @@ expect(described_class.category).to eq(:google_play_console) end end -end \ No newline at end of file +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 5e2f9a6..d3f0aef 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -13,6 +13,14 @@ module SpecHelper require 'fastlane/plugin/instabug_stores_upload' # import the actual plugin require 'webmock/rspec' +# Override the helper method for tests to handle plain hashes +class Fastlane::Helper::InstabugStoresUploadHelper + def self.filter_instabug_params(params, target_action_class) + # In test environment, params are plain hashes - just filter them + return params.reject { |key, _value| INSTABUG_KEYS.include?(key) } + end +end + Fastlane.load_actions # load other actions (in case your plugin calls other actions or shared values) WebMock.disable_net_connect!(allow_localhost: true)