From d7fc90128a8f9624d12f9fe9f870e91192072004 Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Wed, 19 Feb 2025 13:18:18 +0100 Subject: [PATCH] Revert "Refactor `prototype_build_details_comment` for Firebase App Distribution (#630)" This reverts commit 9c1a377094b11916771f994c0e8270d32a2ee668, reversing changes made to bcc522ba0951c8932833b0af3c70cc87127d85d0. --- CHANGELOG.md | 7 +- MIGRATION.md | 6 - .../prototype_build_details_comment_action.rb | 246 +++--- ...otype_build_details_comment_action_spec.rb | 794 ++++++++++-------- 4 files changed, 566 insertions(+), 487 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ed0b723e..a56e2944a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,7 @@ ### Breaking Changes -- The `prototype_build_details_comment` action now relies on the `lane_context` of `firebase_app_distribution` instead of the one from `appcenter_upload` for the implicit metadata and download URL to use in the comment. - This is in the context of us moving from App Center (which will be shut down in March 2025) to Firebase App Distribution as our primary mechanism to distribute prototype builds. [#630] +_None_ ### New Features @@ -19,7 +18,7 @@ _None_ ### Internal Changes -- The library now uses immutable literals, via `# frozen_strings_literal: true`. This may result in runtime issues that we will address ASAP once discovered. [#626] +- The library now uses immutable literals, via `# frozen_strings_literal: true`. This may result in runtime issues that we will address ASAP once discovered [#626] ## 12.4.0 @@ -30,7 +29,7 @@ _None_ ### Bug Fixes -- Remove period from "Update draft release notes..." commit message. [#622] +- Remove period from "Update draft release notes..." commit message [#622] ## 12.3.4 diff --git a/MIGRATION.md b/MIGRATION.md index 690bcdcdf..5912da7fb 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,11 +1,5 @@ # Migration Instructions for Major Releases -## From 12.x to 13.0.0 - -- The `prototype_build_details_comment` action have been updated to work with Firebase App Distribution instead of App Center [#630]. - - If you were using the `prototype_build_details_comment` action in concert with the `appcenter_upload` action, you'll need to migrate to use `firebase_app_distribution` action instead, then adjust the parameters for `prototype_build_details_comment` accordingly (mostly removing the `appcenter_upload`-related ones like `app_center_org_name`). - - If you were using the `prototype_build_details_comment` action out of the context of App Center but with a `download_url` instead (e.g. Cloudfront URL), no update of the call site is needed. - ## From 11.x to 12.0.0 - `android_current_branch_is_hotfix` no longer supports the `build_gradle_path` parameter. Convert the project to define `versionName` and `versionCode` in `version.properties` and call `android_current_branch_is_hotfix` with `version_properties_path`. diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/prototype_build_details_comment_action.rb b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/prototype_build_details_comment_action.rb index 5118edef9..cdd45a851 100644 --- a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/prototype_build_details_comment_action.rb +++ b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/prototype_build_details_comment_action.rb @@ -1,35 +1,26 @@ # frozen_string_literal: true -require 'cgi' -require 'uri' - module Fastlane module Actions class PrototypeBuildDetailsCommentAction < Action def self.run(params) app_display_name = params[:app_display_name] - download_url = params[:download_url] - release_info = FirebaseReleaseInfo.from_lane_context + app_center_info = AppCenterInfo.from_params(params) + metadata = consolidate_metadata(params, app_center_info) - # Merge explicit extra metadata passed from params with ones derived from FirebaseReleaseInfo - metadata = generate_metadata_hash(params: params, release_info: release_info) - # Build the installation link, QR code URL and extra metadata for download links from the available info - qr_code_url, extra_metadata = install_links(release_info: release_info, download_url: download_url) + qr_code_url, extra_metadata = build_install_links(app_center_info, params[:download_url]) metadata.merge!(extra_metadata) - # Build the comment parts and body - app_icon = params[:app_icon] - app_icon ||= ':firebase:' if !release_info.nil? || (download_url && is_firebase_url?(download_url)) - intro = "#{img_tag(app_icon)}📲 You can test the changes from this Pull Request in #{CGI.escape_html(app_display_name)} by scanning the QR code below to install the corresponding build." + # Build the comment parts + icon_img_tag = img_tag(params[:app_icon] || app_center_info.icon, alt: app_display_name) metadata_rows = metadata.compact.map { |key, value| "#{key}#{value}" } - footnote = params[:footnote] - footnote ||= DEFAULT_FOOTNOTE if !release_info.nil? || (download_url && is_firebase_url?(download_url)) - - body = <<~COMMENT_BODY.chomp('') + intro = "#{icon_img_tag}📲 You can test the changes from this Pull Request in #{app_display_name} by scanning the QR code below to install the corresponding build." + footnote = params[:footnote] || (app_center_info.org_name.nil? ? '' : DEFAULT_APP_CENTER_FOOTNOTE) + body = <<~COMMENT_BODY - + #{metadata_rows.join("\n")}
App Name#{CGI.escape_html(app_display_name)}App Name#{icon_img_tag} #{app_display_name}
@@ -37,9 +28,9 @@ def self.run(params) COMMENT_BODY if params[:fold] - "
#{intro}\n#{body}\n
\n" + "
#{intro}\n#{body}
\n" else - "

#{intro}

\n#{body}\n" + "

#{intro}

\n#{body}" end end @@ -49,123 +40,76 @@ def self.run(params) NO_INSTALL_URL_ERROR_MESSAGE = <<~NO_URL_ERROR No URL provided to download or install the app. - - Either use this action right after using `firebase_app_distribution` so this action can extract the download URL from the `lane_context` + - Either use this action right after using `appcenter_upload` and provide an `app_center_org_name` (so that this action can use the link to the App Center build) - Or provide an explicit value for the `download_url` parameter NO_URL_ERROR - DEFAULT_FOOTNOTE = 'Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.' + DEFAULT_APP_CENTER_FOOTNOTE = 'Automatticians: You can use our internal self-serve MC tool to give yourself access to App Center if needed.' - # Parse and validate a URL string - # - # @param [String] url The URL string to parse and validate - # @return [URI] The parsed URI object - # @raise [FastlaneCore::Interface::FastlaneError] if the URL is invalid + # A small model struct to consolidate and pack all the values related to App Center # - def self.parse_url!(url) - URI.parse(url).tap do |uri| - raise URI::InvalidURIError unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS) - end - rescue URI::InvalidURIError - UI.user_error!("Invalid URL: #{url}") - end - - # A small model/struct representing values exposed by Firebase App Distribution for a given release - # - FirebaseReleaseInfo = Struct.new(:display_version, :build_version, :testing_url, :os, :bundle_id, :release_id, keyword_init: true) do - def self.from_lane_context - return nil unless defined?(SharedValues::FIREBASE_APP_DISTRO_RELEASE) - - ctx = Fastlane::Actions.lane_context[SharedValues::FIREBASE_APP_DISTRO_RELEASE] - return nil if ctx.nil? - - # Extract platform info from Firebase Console URI - if ctx[:firebaseConsoleUri] - uri = URI(ctx[:firebaseConsoleUri]) - os, bundle_id, release_id = uri.path.match(%r{project/.*/appdistribution/app/([^:]*):([^/]*)/releases/(.*)})&.captures - end - + AppCenterInfo = Struct.new(:org_name, :app_name, :display_name, :release_id, :icon, :version, :short_version, :os, :bundle_id) do + # A method to construct an AppCenterInfo instance from the action params, and infer the rest from the `lane_context` if available + def self.from_params(params) + org_name = params[:app_center_org_name] + ctx = if org_name && defined?(SharedValues::APPCENTER_BUILD_INFORMATION) + Fastlane::Actions.lane_context[SharedValues::APPCENTER_BUILD_INFORMATION] || {} + else + {} + end + app_name = params[:app_center_app_name] || ctx['app_name'] new( - display_version: ctx[:displayVersion], - build_version: ctx[:buildVersion], - testing_url: ctx[:testingUri], - os: os, - bundle_id: bundle_id, - release_id: release_id + org_name, + app_name, + ctx['app_display_name'] || app_name, + params[:app_center_release_id] || ctx['id'], + ctx['app_icon_url'], + ctx['version'], + ctx['short_version'], + ctx['app_os'], + ctx['bundle_identifier'] ) end end - # Constructs the Hash of metadata, based on the explicit ones passed by the user as parameter + the implicit ones from `FirebaseReleaseInfo` + # Builds the installation link, QR code URL and extra metadata for download links from the available info # - # @param [Hash] params The action's parameters, as received by `self.run` - # @param [FirebaseReleaseInfo?] release_info The information about the Firebase Release extracted from the `lane_context` - # @return [Hash] A hash of all the metadata, consolidated from both the explicit and the implicit ones - # - def self.generate_metadata_hash(params:, release_info:) - metadata = params[:metadata]&.transform_keys(&:to_s) || {} - - # Add Firebase-specific metadata if available - metadata['Build Number'] ||= "#{release_info&.build_version}" - metadata['Version'] ||= "#{release_info&.display_version}" - metadata[release_info&.os == 'ios' ? 'Bundle ID' : 'Application ID'] ||= "#{release_info&.bundle_id}" - - # Add git metadata - metadata['Commit'] ||= ENV.fetch('BUILDKITE_COMMIT', nil) || other_action.last_git_commit[:abbreviated_commit_hash] - metadata - end - - # Constructs the installation link, QR code URL and extra metadata for download links from the available info - # - # @param [FirebaseReleaseInfo?] release_info The information about the Firebase Release extracted from the `lane_context` - # @param [String] download_url The `download_url` parameter passed to the action, if one was provided + # @param [AppCenterInfo] app_center_info The struct containing all the values related to App Center info + # @param [String] download_url The `download_url` parameter passed to the action, if one exists # @return [(String, Hash)] A tuple containing: # - The URL for the QR Code # - A Hash of the extra metadata key/value pairs to add to the existing metadata, to enrich them with download/install links - # @raise [FastlaneCore::Interface::FastlaneError] if no valid installation URL could be determined # - def self.install_links(release_info:, download_url:) + def self.build_install_links(app_center_info, download_url) install_url = nil extra_metadata = {} - firebase_release_id = nil - - # Validate and process direct download URL if provided if download_url - uri = parse_url!(download_url) install_url = download_url - - if is_firebase_url?(uri) - firebase_release_id = File.basename(uri.path) - else - filename = File.basename(uri.path) - extra_metadata['Direct Download'] = "#{CGI.escape_html(filename)}" - end + extra_metadata['Direct Download'] = "#{File.basename(install_url)}" end - - # Process Firebase testing URL if available from release_info - if release_info&.testing_url - install_url = release_info.testing_url - firebase_release_id = release_info.release_id + if app_center_info.org_name && app_center_info.app_name + install_url = "https://install.appcenter.ms/orgs/#{app_center_info.org_name}/apps/#{app_center_info.app_name}/releases/#{app_center_info.release_id}" + extra_metadata['App Center Build'] = "#{app_center_info.display_name} ##{app_center_info.release_id}" end - UI.user_error!(NO_INSTALL_URL_ERROR_MESSAGE) if install_url.nil? - - # Add Installation URL metadata if we have a release_id - extra_metadata['Installation URL'] = "#{CGI.escape_html(firebase_release_id)}" if firebase_release_id - - # Generate QR code URL with proper escaping qr_code_url = "https://api.qrserver.com/v1/create-qr-code/?size=500x500&qzone=4&data=#{CGI.escape(install_url)}" [qr_code_url, extra_metadata] end - # Determines if a given URI is a Firebase App Distribution URL + # A method to build the Hash of metadata, based on the explicit ones passed by the user as parameter + the implicit ones from `AppCenterInfo` # - # @param [String, URI] url The URL to check, either as a String or an already-parsed URI - # @return [Boolean] true if the URL is a Firebase App Distribution URL - # @raise [FastlaneCore::Interface::FastlaneError] if the URL is invalid + # @param [Hash] params The action's parameters, as received by `self.run` + # @param [AppCenterInfo] app_center_info The model object containing all the values related to App Center information + # @return [Hash] A hash of all the metadata, gathered from both the explicit and the implicit ones # - def self.is_firebase_url?(url) - uri = url.is_a?(URI) ? url : parse_url!(url) - uri.host == 'appdistribution.firebase.google.com' && uri.path.start_with?('/testerapps/') + def self.consolidate_metadata(params, app_center_info) + metadata = params[:metadata]&.transform_keys(&:to_s) || {} + metadata['Build Number'] ||= app_center_info.version + metadata['Version'] ||= app_center_info.short_version + metadata[app_center_info.os == 'Android' ? 'Application ID' : 'Bundle ID'] ||= app_center_info.bundle_id + # (Feel free to add more CI-specific env vars in the line below to support other CI providers if you need) + metadata['Commit'] ||= ENV.fetch('BUILDKITE_COMMIT', nil) || other_action.last_git_commit[:abbreviated_commit_hash] + metadata end # Creates an HTML `` tag for an icon URL or the image URL to represent a given Buildkite emoji @@ -173,19 +117,19 @@ def self.is_firebase_url?(url) # @param [String] url_or_emoji A `String` which can be: # - Either a valid URI to an image # - Or a string formatted like `:emojiname:`, using a valid Buildite emoji name as defined in https://github.com/buildkite/emojis + # @param [String] alt The alt text to use for the `` tag # @return [String] The `` tag with the proper image and alt tag - # @raise [FastlaneCore::Interface::FastlaneError] if the URL is invalid # - def self.img_tag(url_or_emoji) + def self.img_tag(url_or_emoji, alt: '') return nil if url_or_emoji.nil? emoji = url_or_emoji.match(/:(.*):/)&.captures&.first app_icon_url = if emoji "https://raw.githubusercontent.com/buildkite/emojis/main/img-buildkite-64/#{emoji}.png" - else - url_or_emoji.tap { parse_url!(url_or_emoji) } + elsif URI(url_or_emoji) + url_or_emoji end - app_icon_url ? "App Icon" : '' + app_icon_url ? "#{alt}" : '' end ##################################################### @@ -201,72 +145,98 @@ def self.details Generates a string providing all the details of a prototype build, nicely-formatted as HTML. The returned string will typically be subsequently used by the `comment_on_pr` action to post that HTML as comment on a PR. - If you used the `firebase_app_distribution` action (to upload the Prototype build to Firebase App Distribution) before calling this action, - then many of the metadata will be automatically extracted from the `lane_context` it exposed: + If you used the `appcenter_upload` lane (to upload the Prototype build to App Center) before calling this action, and pass + a value to the `app_center_org_name` parameter, then many of the parameters and metadata will be automatically extracted + from the `lane_context` provided by `appcenter_upload`, including: - - "Version" (from `:displayVersion`) and "Build Number" (from `:buildVersion`) - - "Bundle ID" (extracted from `:firebaseConsoleUri`) - - "Commit" (from `BUILDKITE_COMMIT` environment variable or last git commit) - - "Installation URL" (from `:testingUri`) + - The `app_center_app_name`, `app_center_release_id` and installation URL to use for the QR code to point to that release in App Center + - The `app_icon` + - The app's Build Number / versionCode + - The app's Version / versionName + - The app's Bundle ID / Application ID + - A `footnote` mentioning the MC tool for Automatticians to add themselves to App Center - You can also pass additional metadata to this action via the `metadata` parameter, and they will also be included in the HTML table of the comment. + This means that if you are using App Center to distribute your Prototype Build, the only parameters you *have* to provide + to this action are `app_display_name` and `app_center_org_name`; plus, for `metadata` most of the interesting values will already be pre-filled. - This means that if you are using Firebase App Distribution to distribute your Prototype Build, the can just provide - `app_display_name` and optionally `app_icon`, and the rest will be automatically inferred from the `lane_context`. - - If you are not using Firebase App Distribution, you can pass an explicit value for the `download_url` parameter, - and the action will use it to generate the installation link and QR code. + Any of those implicit default values/metadata can of course be overridden by passing an explicit value to the appropriate parameter(s). DESC end def self.available_options + app_center_auto = '(will be automatically extracted from `lane_context if you used `appcenter_upload` to distribute your Prototype build)' [ FastlaneCore::ConfigItem.new( key: :app_display_name, + env_name: 'FL_PROTOTYPE_BUILD_DETAILS_COMMENT_APP_DISPLAY_NAME', description: 'The display name to use for the app in the comment message', optional: false, type: String ), + FastlaneCore::ConfigItem.new( + key: :app_center_org_name, + env_name: 'APPCENTER_OWNER_NAME', # Intentionally the same as the one used by the `appcenter_upload` action + description: 'The name of the organization in App Center (if you used `appcenter_upload` to distribute your Prototype build)', + type: String, + optional: true + ), + FastlaneCore::ConfigItem.new( + key: :app_center_app_name, + env_name: 'APPCENTER_APP_NAME', # Intentionally the same as the one used by the `appcenter_upload` action + description: "The name of the app in App Center #{app_center_auto}", + type: String, + optional: true, + default_value_dynamic: true # As it will be extracted from the `lane_context`` if you used `appcenter_upload`` + ), + FastlaneCore::ConfigItem.new( + key: :app_center_release_id, + env_name: 'APPCENTER_RELEASE_ID', + description: "The release ID/Number in App Center #{app_center_auto}", + type: String, + optional: true, + default_value_dynamic: true # As it will be extracted from the `lane_context`` if you used `appcenter_upload`` + ), FastlaneCore::ConfigItem.new( key: :app_icon, - description: 'The name of an emoji from the https://github.com/buildkite/emojis list or the full image URL to use for the icon of the app in the message', + env_name: 'FL_PROTOTYPE_BUILD_DETAILS_COMMENT_APP_ICON', + description: "The name of an emoji from the https://github.com/buildkite/emojis list or the full image URL to use for the icon of the app in the message. #{app_center_auto}", type: String, optional: true, - default_value_dynamic: true # Defaults to `:firebase:` only if `firebase_app_distribution` was used + default_value_dynamic: true # As it will be extracted from the `lane_context`` if you used `appcenter_upload`` ), FastlaneCore::ConfigItem.new( key: :download_url, - description: <<~DESC, - The URL to use to download/install the build. - - If you used `firebase_app_distribution` to upload the build during the same `fastlane` run, you should leave this nil - - If you used `firebase_app_distribution` during a separate CI job, you can store the `:testingUri` of that call's returned hash (in e.g. Buildkite metadata), then pass that URI to this parameter - - Otherwise, you can provide a direct download URL for the build (e.g. link to Cloudfront or AppsCDN URL) - DESC + env_name: 'FL_PROTOTYPE_BUILD_DETAILS_COMMENT_DOWNLOAD_URL', + description: 'The URL to download the build as a direct download. ' \ + + 'If you uploaded the build to App Center, we recommend leaving this nil (the comment will use the URL to the App Center build for the QR code)', type: String, optional: true, default_value: nil ), FastlaneCore::ConfigItem.new( key: :fold, + env_name: 'FL_PROTOTYPE_BUILD_DETAILS_COMMENT_FOLD', description: 'If true, will wrap the HTML table inside a
block (hidden by default)', type: Boolean, default_value: false ), FastlaneCore::ConfigItem.new( key: :metadata, + env_name: 'FL_PROTOTYPE_BUILD_DETAILS_COMMENT_METADATA', description: 'All additional metadata (as key/value pairs) you want to include in the HTML table of the comment. ' \ - + 'If you are running this action after `firebase_app_distribution`, some metadata will automatically be added and merged with this list', + + 'If you are running this action after `appcenter_upload`, some metadata will automatically be added to this list too', type: Hash, optional: true, - default_value_dynamic: true # As some metadata will be auto-filled if you used `firebase_app_distribution` + default_value_dynamic: true # As some metadata will be auto-filled if you used `appcenter_upload` ), FastlaneCore::ConfigItem.new( key: :footnote, + env_name: 'FL_PROTOTYPE_BUILD_DETAILS_COMMENT_FOOTNOTE', description: 'Optional footnote to add below the HTML table of the comment. ' \ - + 'If you are running this action after `firebase_app_distribution`, a default footnote for Automatticians will be used unless you provide an explicit value', + + 'If you are running this action after `appcenter_upload`, a default footnote for Automatticians will be used unless you provide an explicit value', type: String, optional: true, - default_value_dynamic: true # We have a default footnote for the case when you used Firebase App Distribution + default_value_dynamic: true # We have a default footnote for the case when you used App Center ), ] end diff --git a/spec/prototype_build_details_comment_action_spec.rb b/spec/prototype_build_details_comment_action_spec.rb index 4ec67a251..5728c05e9 100644 --- a/spec/prototype_build_details_comment_action_spec.rb +++ b/spec/prototype_build_details_comment_action_spec.rb @@ -7,107 +7,22 @@ ENV['BUILDKITE_COMMIT'] = 'a1b2c3f' end - let(:custom_footnote) { 'Note: Google Sign-In is not available in those builds' } - let(:valid_download_url) { 'https://example.com/myapp.apk' } - let(:valid_app_icon_url) { 'https://localhost/foo.png' } - let(:base_params) do - { - app_display_name: 'My App', - download_url: valid_download_url - } - end - - describe 'error handling' do - it 'raises an error if neither Firebase info nor download_url is provided' do - allow(Fastlane::Actions).to receive(:lane_context).and_return({}) - expect do - run_described_fastlane_action(app_display_name: 'My App') - end.to raise_error(FastlaneCore::Interface::FastlaneError, described_class::NO_INSTALL_URL_ERROR_MESSAGE) - end - - describe 'URL validation' do - it 'raises an error for invalid download URLs' do - expect do - run_described_fastlane_action(base_params.merge(download_url: 'not-a-url')) - end.to raise_error(FastlaneCore::Interface::FastlaneError, /Invalid URL/) - end - - it 'raises an error for invalid app icon URLs' do - expect do - run_described_fastlane_action(base_params.merge(app_icon: 'not-a-url')) - end.to raise_error(FastlaneCore::Interface::FastlaneError, /Invalid URL/) - end - - it 'accepts valid URLs with special characters' do - url_with_special_chars = 'https://example.com/path%20with%20spaces.apk' - expect do - run_described_fastlane_action(base_params.merge(download_url: url_with_special_chars)) - end.not_to raise_error - end - end - end - - describe 'HTML escaping' do - let(:app_name_with_html) { 'My Cool App' } - let(:metadata_with_html) do - { - 'HTML Key': 'bold', - Link: 'example.com', - 'Mixed & Content': 'Version & Build 1.0' - } - end - - it 'properly escapes HTML in app display name' do - comment = run_described_fastlane_action(base_params.merge(app_display_name: app_name_with_html)) - expect(comment).to include '<em>Cool</em>' - expect(comment).not_to include '' - end - - it 'does not escape HTML in metadata' do - comment = run_described_fastlane_action(base_params.merge(metadata: metadata_with_html)) - # HTML in metadata should be preserved as-is, so we can e.g. use links or tags - expect(comment).to include 'HTML Keybold' - expect(comment).to include 'Linkexample.com' - expect(comment).to include 'Mixed & ContentVersion & Build 1.0' - end - end - - describe 'metadata handling' do - it 'handles empty metadata gracefully' do - comment = run_described_fastlane_action(base_params.merge(metadata: {})) - # Should still include default metadata - expect(comment).to include 'App Name' - expect(comment).to include 'Commit' - end - - it 'handles nil metadata values' do - comment = run_described_fastlane_action(base_params.merge(metadata: { 'Nil Value': nil })) - expect(comment).not_to include 'Nil Value' - end - - it 'handles very long metadata values' do - long_value = 'a' * 1000 - comment = run_described_fastlane_action(base_params.merge(metadata: { 'Long Value': long_value })) - expect(comment).to include long_value - end - end - describe 'cases common to all operating modes' do describe 'app_display_name' do it 'includes the app display name as part of the intro text' do comment = run_described_fastlane_action( - app_display_name: 'My Cool App & Co.', + app_display_name: 'My Cool App', download_url: 'https://localhost/foo.apk' ) - expect(comment).to include '📲 You can test the changes from this Pull Request in My Cool App & Co.' + expect(comment).to include '📲 You can test the changes from this Pull Request in My Cool App' end it 'includes the app display name as part of implicit metadata' do comment = run_described_fastlane_action( - app_display_name: 'My Cool App & Co.', + app_display_name: 'My Cool App', download_url: 'https://localhost/foo.apk' ) - expect(comment).to include 'App NameMy Cool App & Co.' + expect(comment).to include 'App Name My Cool App' end end @@ -119,7 +34,16 @@ app_icon: 'https://localhost/foo.png', download_url: 'https://localhost/foo.apk' ) - expect(comment).to include "App Icon📲 " + expect(comment).to include "My Cool App📲 " + end + + it 'includes the icon next to the App Name in metadata' do + comment = run_described_fastlane_action( + app_display_name: 'My Cool App', + app_icon: 'https://localhost/foo.png', + download_url: 'https://localhost/foo.apk' + ) + expect(comment).to include "App NameMy Cool App My Cool App" end end @@ -130,7 +54,16 @@ app_icon: ':jetpack:', download_url: 'https://localhost/foo.apk' ) - expect(comment).to include "App Icon📲 " + expect(comment).to include "My Cool App📲 " + end + + it 'includes the icon next to the App Name in metadata' do + comment = run_described_fastlane_action( + app_display_name: 'My Cool App', + app_icon: ':jetpack:', + download_url: 'https://localhost/foo.apk' + ) + expect(comment).to include "App NameMy Cool App My Cool App" end end end @@ -144,6 +77,7 @@ end it 'includes the provided footnote if one was provided explicitly' do + custom_footnote = 'Note that Google Sign-In in not available in those builds' comment = run_described_fastlane_action( app_display_name: 'My App', download_url: 'https://localhost/foo.apk', @@ -153,307 +87,489 @@ end end - context 'when using Firebase App Distribution' do - let(:firebase_release_info) do - { - displayVersion: '28.7', - buildVersion: '1287003', - testingUri: 'https://appdistribution.firebase.google.com/testerapps/1:123456:ios:abcdef/releases/xyz', - firebaseConsoleUri: 'https://console.firebase.google.com/project/apps-test/appdistribution/app/ios:com.example.myapp/releases/xyz' - } + context 'when using App Center with explicit parameters' do + it 'raises an error if neither `app_center_app_name` nor `download_url` is provided' do + expect do + run_described_fastlane_action( + app_display_name: 'My App', + app_center_org_name: 'BestOrg' + ) + end.to raise_error(FastlaneCore::Interface::FastlaneError, described_class::NO_INSTALL_URL_ERROR_MESSAGE) end - before do - stub_const('Fastlane::Actions::SharedValues::FIREBASE_APP_DISTRO_RELEASE', :firebase_app_distro_release) - allow(Fastlane::Actions).to receive(:lane_context).and_return({ firebase_app_distro_release: firebase_release_info }) - end + describe 'checking specific content is present' do + it 'generates the proper App Center link and QR code given an org, app name and release ID' do + comment = run_described_fastlane_action( + app_display_name: 'My App', + app_center_org_name: 'My-Org', + app_center_app_name: 'My-App', + app_center_release_id: '1337' + ) + expect(comment).to include "My-App #1337" + expect(comment).to include 'https://api.qrserver.com/v1/create-qr-code/?size=500x500&qzone=4&data=https%3A%2F%2Finstall.appcenter.ms%2Forgs%2FMy-Org%2Fapps%2FMy-App%2Freleases%2F1337' + end - it 'extracts metadata from Firebase release info' do - comment = run_described_fastlane_action( - app_display_name: 'My App' - ) - expect(comment).to include 'Version28.7' - expect(comment).to include 'Build Number1287003' - expect(comment).to include 'Bundle IDcom.example.myapp' - expect(comment).to include 'Installation URLxyz' + it 'uses the App Center link for the QR code even if a `download_url` is provided' do + comment = run_described_fastlane_action( + app_display_name: 'My App', + app_center_org_name: 'My-Org', + app_center_app_name: 'My-App', + app_center_release_id: '1337', + download_url: 'https://foo.cloudfront.net/someuuid/myapp-prototype-build-pr1337-a1b2c3f.apk' + ) + expect(comment).to include "Direct Downloadmyapp-prototype-build-pr1337-a1b2c3f.apk" + expect(comment).to include 'https://api.qrserver.com/v1/create-qr-code/?size=500x500&qzone=4&data=https%3A%2F%2Finstall.appcenter.ms%2Forgs%2FMy-Org%2Fapps%2FMy-App%2Freleases%2F1337' + # Inferred metadata rows: App Name, Commit, Direct Download, App Center Build + expect(comment).to include "App Name My App' + expect(comment).to include 'Version:Short28.1' + expect(comment).to include 'Version:Long281003' + expect(comment).to include 'Build ConfigPrototype' + expect(comment).to include 'Commita1b2c3f' + expect(comment).to include "App Center BuildMy-App #1337" + # Additional inferred metadata rows: App Name, Commit, App Center Build + expect(comment).to include "Note: Google Sign-In in not available in those builds' + ) + + expect(comment).to eq <<~EXPECTED_COMMENT +

📲 You can test the changes from this Pull Request in The Best App by scanning the QR code below to install the corresponding build.

+ + + + + + + + + + +
App Name The Best App
Version:Short28.2
Version:Long28.2.0.108
FlavorCelray
Commita1b2c3f
App Center BuildBestApp #8888
+ Note: Google Sign-In in not available in those builds + EXPECTED_COMMENT + end + + it 'generates a HTML table comment including the direct link if provided' do + metadata = { + 'Version:Short': '28.2', + 'Version:Long': '28.2.0.108' + } + + comment = run_described_fastlane_action( + app_display_name: 'The Best App', + app_center_org_name: 'BestOrg', + app_center_app_name: 'BestApp', + app_center_release_id: '8888', + download_url: 'https://bestfront.cloudfront.net/feed42/bestapp-pr1357-a1b2c3f.apk', + metadata: metadata + ) + + expect(comment).to eq <<~EXPECTED_COMMENT +

📲 You can test the changes from this Pull Request in The Best App by scanning the QR code below to install the corresponding build.

+ + + + + + + + + + +
App Name The Best App
Version:Short28.2
Version:Long28.2.0.108
Commita1b2c3f
Direct Downloadbestapp-pr1357-a1b2c3f.apk
App Center BuildBestApp #8888
+ Automatticians: You can use our internal self-serve MC tool to give yourself access to App Center if needed. + EXPECTED_COMMENT + end + + it 'generates a HTML table in a spoiler block if fold is true' do + metadata = { + 'Version:Short': '28.2', + 'Version:Long': '28.2.0.108', + Flavor: 'Celray', + Configuration: 'Debug' + } + + comment = run_described_fastlane_action( + app_display_name: 'The Best App', + app_center_org_name: 'BestOrg', + app_center_app_name: 'BestApp', + app_center_release_id: '1234', + fold: true, + metadata: metadata, + footnote: 'Note: Google Sign-In in not available in those builds' + ) + + expect(comment).to eq <<~EXPECTED_COMMENT +
📲 You can test the changes from this Pull Request in The Best App by scanning the QR code below to install the corresponding build. + + + + + + + + + + + +
App Name The Best App
Version:Short28.2
Version:Long28.2.0.108
FlavorCelray
ConfigurationDebug
Commita1b2c3f
App Center BuildBestApp #1234
+ Note: Google Sign-In in not available in those builds +
+ EXPECTED_COMMENT + end end + end - it 'includes and prioritizes user-provided metadata over implicit ones' do - metadata = { - Version: '42.3', - 'Build Number': '4203008', - 'Build Config': 'Prototype' - } - comment = run_described_fastlane_action( - app_display_name: 'My App', - metadata: metadata - ) - expect(comment).to include 'Version42.3' # explicitly provided, overriding the implicit value - expect(comment).not_to include 'Version28.7' # otherwise implicitly added if it were not overridden - expect(comment).to include 'Build Number4203008' # explicitly provided, overriding the implicit value - expect(comment).not_to include 'Build Number1287003' # otherwise implicitly added if it were not overridden - expect(comment).to include 'Build ConfigPrototype' # not overriding any implicit one - # Additional inferred metadata rows: App Name, Bundle ID, Commit, Installation URL - expect(comment).to include "App NameMy App' - expect(comment).to include 'Version:Short28.1' - expect(comment).to include 'Version:Long281003' - expect(comment).to include 'Build ConfigPrototype' - expect(comment).to include 'Bundle IDcom.example.myapp' - expect(comment).to include 'Commita1b2c3f' - expect(comment).to include "Installation URLxyz" - # Additional inferred metadata rows: Build Number, Version - expect(comment).to include "My App (Alpha) #1337" + expect(comment).to include 'https://api.qrserver.com/v1/create-qr-code/?size=500x500&qzone=4&data=https%3A%2F%2Finstall.appcenter.ms%2Forgs%2FMy-Org%2Fapps%2FMy-App-Alpha%2Freleases%2F1337' + end - it 'uses "Application ID" as the name for the bundle identifier' do - comment = run_described_fastlane_action( - app_display_name: 'My App' - ) - expect(comment).to include 'Application IDcom.example.myapp' - expect(comment).not_to include 'Bundle ID' - end + it 'uses the App Center link for the QR code even if a `download_url` is provided' do + comment = run_described_fastlane_action( + app_display_name: 'My App', + app_center_org_name: 'My-Org', + download_url: 'https://foo.cloudfront.net/someuuid/myapp-prototype-build-pr1337-a1b2c3f.apk' + ) + expect(comment).to include 'https://api.qrserver.com/v1/create-qr-code/?size=500x500&qzone=4&data=https%3A%2F%2Finstall.appcenter.ms%2Forgs%2FMy-Org%2Fapps%2FMy-App-Alpha%2Freleases%2F1337' + # Inferred metadata rows: App Name, Build Number, Version, Application ID, Commit, Direct Download, App Center Build + expect(comment).to include "Version42.3' # explicitly provided, overriding the implicit value + expect(comment).not_to include 'Version28.7' # otherwise implicitly added if it were not overridden + expect(comment).to include 'Build Number4203008' # explicitly provided, overriding the implicit value + expect(comment).not_to include 'Build Number1287003' # otherwise implicitly added if it were not overridden + expect(comment).to include 'Build ConfigPrototype' # not overriding any implicit one + # Additional inferred metadata rows: App Name, Application ID, Commit, App Center Build + expect(comment).to include "Bundle IDcom.example.myapp' - expect(comment).not_to include 'Application ID' - end + it 'uses "Application ID" as the name for the `bundle_identifier` value if using Android', app_os: 'Android' do + comment = run_described_fastlane_action( + app_display_name: 'My App', + app_center_org_name: 'My-Org' + ) + expect(comment).to include 'Application IDcom.stubfactory.myapp' + expect(comment).not_to include 'Bundle ID' end - end - describe 'footnote behavior' do - it 'includes the default Firebase footnote if no explicit footnote is provided' do + it 'uses "Bundle ID" as the name for the `bundle_identifier` value if using iOS', app_os: 'iOS' do comment = run_described_fastlane_action( - app_display_name: 'My App' + app_display_name: 'My App', + app_center_org_name: 'My-Org' ) - expect(comment).to include described_class::DEFAULT_FOOTNOTE + expect(comment).to include 'Bundle IDcom.stubfactory.myapp' + expect(comment).not_to include 'Application ID' + end + + it 'includes the direct link if one is provided' do + comment = run_described_fastlane_action( + app_display_name: 'My App', + app_center_org_name: 'My-Org', + download_url: 'https://foo.cloudfront.net/someuuid/myapp-prototype-build-pr1337-a1b2c3f.apk' + ) + expect(comment).to include "Direct Downloadmyapp-prototype-build-pr1337-a1b2c3f.apk" + # Inferred metadata rows: App Name, Build Number, Version, Application ID, Commit, Direct Download, App Center Build + expect(comment).to include "Direct Downloadmyapp.apk" - end + comment = run_described_fastlane_action( + app_display_name: 'The Best App', + app_center_org_name: 'BestOrg', + metadata: metadata, + footnote: 'Note: Google Sign-In in not available in those builds' + ) - it 'does not include any default footnote if no explicit footnote is provided' do - comment = run_described_fastlane_action( - app_display_name: 'My App', - download_url: 'https://example.com/myapp.apk' - ) - expect(comment).not_to include described_class::DEFAULT_FOOTNOTE - end + expect(comment).to eq <<~EXPECTED_COMMENT +

The Best App📲 You can test the changes from this Pull Request in The Best App by scanning the QR code below to install the corresponding build.

+ + + + + + + + + + + +
App NameThe Best App The Best App
ConfigurationDebug
Build Number1287003
Version28.7
Application IDcom.stubfactory.myapp
Commita1b2c3f
App Center BuildMy App (Alpha) #1337
+ Note: Google Sign-In in not available in those builds + EXPECTED_COMMENT + end - it 'includes the provided footnote if one was provided explicitly' do - comment = run_described_fastlane_action( - app_display_name: 'My App', - download_url: 'https://example.com/myapp.apk', - footnote: custom_footnote - ) - expect(comment).to include custom_footnote - end + it 'generates a HTML table comment including the direct link if provided' do + comment = run_described_fastlane_action( + app_display_name: 'The Best App', + app_center_org_name: 'BestOrg', + download_url: 'https://bestfront.cloudfront.net/feed42/bestapp-pr1357-a1b2c3f.apk' + ) - it 'includes the default footnote if no explicit one is provided but the download URL is a Firebase URL' do - comment = run_described_fastlane_action( - app_display_name: 'My App', - download_url: 'https://appdistribution.firebase.google.com/testerapps/1:123456:ios:abcdef/releases/xyz' - ) - expect(comment).to include described_class::DEFAULT_FOOTNOTE - end - end + expect(comment).to eq <<~EXPECTED_COMMENT +

The Best App📲 You can test the changes from this Pull Request in The Best App by scanning the QR code below to install the corresponding build.

+ + + + + + + + + + + +
App NameThe Best App The Best App
Build Number1287003
Version28.7
Application IDcom.stubfactory.myapp
Commita1b2c3f
Direct Downloadbestapp-pr1357-a1b2c3f.apk
App Center BuildMy App (Alpha) #1337
+ Automatticians: You can use our internal self-serve MC tool to give yourself access to App Center if needed. + EXPECTED_COMMENT + end - describe 'validating full comment' do - it 'generates a standard HTML table comment by default' do - metadata = { - 'Version Name': '28.2', - 'Version Code': '1280200108', - Flavor: 'Debug' - } + it 'generates a HTML table in a spoiler block if fold is true' do + metadata = { + 'Google Login': 'Disabled' + } - comment = run_described_fastlane_action( - app_display_name: 'The Best App', - download_url: 'https://example.com/bestapp.apk', - metadata: metadata, - app_icon: ':jetpack:' - ) + comment = run_described_fastlane_action( + app_display_name: 'The Best App', + app_center_org_name: 'BestOrg', + fold: true, + metadata: metadata, + footnote: 'Note: Google Sign-In in not available in those builds' + ) - expect(comment).to eq <<~EXPECTED_COMMENT -

App Icon📲 You can test the changes from this Pull Request in The Best App by scanning the QR code below to install the corresponding build.

- - - - - - - - - - - - - -
App NameThe Best App
Version Name28.2
Version Code1280200108
FlavorDebug
Build Number
Version
Application ID
Commita1b2c3f
Direct Downloadbestapp.apk
- EXPECTED_COMMENT + expect(comment).to eq <<~EXPECTED_COMMENT +
The Best App📲 You can test the changes from this Pull Request in The Best App by scanning the QR code below to install the corresponding build. + + + + + + + + + + + +
App NameThe Best App The Best App
Google LoginDisabled
Build Number1287003
Version28.7
Application IDcom.stubfactory.myapp
Commita1b2c3f
App Center BuildMy App (Alpha) #1337
+ Note: Google Sign-In in not available in those builds +
+ EXPECTED_COMMENT + end end + end - it 'generates a HTML table in a spoiler block if fold is true' do - metadata = { - 'Version Name': '28.2', - 'Version Code': '1280200108' - } - - comment = run_described_fastlane_action( - app_display_name: 'The Best App', - download_url: 'https://example.com/bestapp.apk', - fold: true, - metadata: metadata, - footnote: custom_footnote - ) - - expect(comment).to eq <<~EXPECTED_COMMENT -
📲 You can test the changes from this Pull Request in The Best App by scanning the QR code below to install the corresponding build. - - - - - - - - - - - - -
App NameThe Best App
Version Name28.2
Version Code1280200108
Build Number
Version
Application ID
Commita1b2c3f
Direct Downloadbestapp.apk
- Note: Google Sign-In is not available in those builds -
- EXPECTED_COMMENT + context 'when not using App Center' do + it 'raises an error if no `download_url` is provided' do + expect do + run_described_fastlane_action( + app_display_name: 'My App' + ) + end.to raise_error(FastlaneCore::Interface::FastlaneError, described_class::NO_INSTALL_URL_ERROR_MESSAGE) end - end - describe 'app_icon handling' do - context 'when providing an URL' do - it 'includes the icon in the intro text' do - comment = run_described_fastlane_action(base_params.merge(app_icon: valid_app_icon_url)) - expect(comment).to include "App Icon📲 " + describe 'checking specific content is present' do + it 'generates the proper QR code from the download url' do + comment = run_described_fastlane_action( + app_display_name: 'My App', + download_url: 'https://foo.cloudfront.net/someuuid/myapp-prototype-build-pr1337-a1b2c3f.apk' + ) + expect(comment).to include 'https://api.qrserver.com/v1/create-qr-code/?size=500x500&qzone=4&data=https%3A%2F%2Ffoo.cloudfront.net%2Fsomeuuid%2Fmyapp-prototype-build-pr1337-a1b2c3f.apk' end - end - context 'when providing an emoji code' do - it 'includes the icon in the intro text' do - comment = run_described_fastlane_action(base_params.merge(app_icon: ':jetpack:')) - expect(comment).to include "App Icon📲 " + it 'includes the direct link as metadata' do + comment = run_described_fastlane_action( + app_display_name: 'My App', + download_url: 'https://foo.cloudfront.net/someuuid/myapp-prototype-build-pr1337-a1b2c3f.apk' + ) + expect(comment).to include "Direct Downloadmyapp-prototype-build-pr1337-a1b2c3f.apk" end - it 'handles emoji codes with special characters' do - comment = run_described_fastlane_action(base_params.merge(app_icon: ':plus-one:')) - expect(comment).to include 'plus-one.png' + it 'does not include the App Center default footnote if no explicit footnote is provided' do + comment = run_described_fastlane_action( + app_display_name: 'My App', + download_url: 'https://localhost/foo.apk' + ) + expect(comment).not_to include described_class::DEFAULT_APP_CENTER_FOOTNOTE + end + + it 'includes the provided footnote if one was provided explicitly' do + comment = run_described_fastlane_action( + app_display_name: 'My App', + download_url: 'https://localhost/foo.apk', + footnote: 'The link to this APK might stop working after a retention delay of 30 days.' + ) + expect(comment).to include 'The link to this APK might stop working after a retention delay of 30 days.' end end - context 'when no icon is provided' do - context 'when using Firebase App Distribution' do - let(:firebase_release_info) do - { - displayVersion: '28.7', - buildVersion: '1287003', - testingUri: 'https://appdistribution.firebase.google.com/testerapps/1:123456:ios:abcdef/releases/xyz', - firebaseConsoleUri: 'https://console.firebase.google.com/project/apps-test/appdistribution/app/ios:com.example.myapp/releases/xyz' - } - end + describe 'validating full comment' do + it 'generates a standard HTML table comment by default, with all the information provided' do + metadata = { + 'Version Name': '28.2', + 'Version Code': '1280200108', + Flavor: 'Celray' + } - before do - stub_const('Fastlane::Actions::SharedValues::FIREBASE_APP_DISTRO_RELEASE', :firebase_app_distro_release) - allow(Fastlane::Actions).to receive(:lane_context).and_return({ firebase_app_distro_release: firebase_release_info }) - end + comment = run_described_fastlane_action( + app_display_name: 'The Best App', + download_url: 'https://bestfront.cloudfront.net/feed42/bestapp-pr1357-a1b2c3f.apk', + metadata: metadata, + footnote: 'Note: Google Sign-In in not available in those builds' + ) - it 'uses the firebase icon' do - comment = run_described_fastlane_action(app_display_name: 'My App') - expect(comment).to include 'firebase.png' - end + expect(comment).to eq <<~EXPECTED_COMMENT +

📲 You can test the changes from this Pull Request in The Best App by scanning the QR code below to install the corresponding build.

+ + + + + + + + + + +
App Name The Best App
Version Name28.2
Version Code1280200108
FlavorCelray
Commita1b2c3f
Direct Downloadbestapp-pr1357-a1b2c3f.apk
+ Note: Google Sign-In in not available in those builds + EXPECTED_COMMENT end - context 'when using a Firebase download URL' do - it 'uses the firebase icon' do - comment = run_described_fastlane_action( - app_display_name: 'My App', - download_url: 'https://appdistribution.firebase.google.com/testerapps/1:123456:ios:abcdef/releases/xyz' - ) - expect(comment).to include 'firebase.png' - end - end + it 'generates a HTML table in a spoiler block if fold is true' do + metadata = { + 'Version Name': '28.2', + 'Version Code': '1280200108', + Flavor: 'Celray', + Configuration: 'Debug' + } - context 'when using a non-Firebase download URL' do - it 'does not include any icon' do - comment = run_described_fastlane_action(base_params.merge(app_icon: nil)) - expect(comment).not_to include 'firebase.png' - expect(comment).to match(/^

📲 You can test/) - end + comment = run_described_fastlane_action( + app_display_name: 'The Best App', + download_url: 'https://bestfront.cloudfront.net/feed42/bestapp-pr1357-a1b2c3f.apk', + fold: true, + metadata: metadata, + footnote: 'Note: Google Sign-In in not available in those builds' + ) + + expect(comment).to eq <<~EXPECTED_COMMENT +

📲 You can test the changes from this Pull Request in The Best App by scanning the QR code below to install the corresponding build. + + + + + + + + + + + +
App Name The Best App
Version Name28.2
Version Code1280200108
FlavorCelray
ConfigurationDebug
Commita1b2c3f
Direct Downloadbestapp-pr1357-a1b2c3f.apk
+ Note: Google Sign-In in not available in those builds +
+ EXPECTED_COMMENT end end end