diff --git a/lib/fastlane/plugin/ddg_apple_automation/actions/asana_add_comment_action.rb b/lib/fastlane/plugin/ddg_apple_automation/actions/asana_add_comment_action.rb index d41420c..d2aaa13 100644 --- a/lib/fastlane/plugin/ddg_apple_automation/actions/asana_add_comment_action.rb +++ b/lib/fastlane/plugin/ddg_apple_automation/actions/asana_add_comment_action.rb @@ -33,7 +33,7 @@ def self.run(params) template_content = Helper::DdgAppleAutomationHelper.load_file(template_file) return unless template_content - html_text = process_template_content(template_content) + html_text = Helper::DdgAppleAutomationHelper.sanitize_html_and_replace_env_vars(template_content) create_story(asana_access_token, task_id, html_text: html_text) end end @@ -110,14 +110,6 @@ def self.create_story(asana_access_token, task_id, text: nil, html_text: nil) UI.user_error!("Failed to post comment: #{e}") end end - - def self.process_template_content(template_content) - template_content.gsub(/\$\{(\w+)\}/) { ENV.fetch($1, '') } # replace environment variables - .gsub(/\s+/, ' ') # replace multiple whitespaces with a single space - .gsub(/>\s+<') # remove spaces between HTML tags - .strip # remove leading and trailing whitespaces - .gsub(%r{}, "\n") # replace
tags with newlines - end end end end diff --git a/lib/fastlane/plugin/ddg_apple_automation/actions/asana_create_action_item_action.rb b/lib/fastlane/plugin/ddg_apple_automation/actions/asana_create_action_item_action.rb new file mode 100644 index 0000000..040410c --- /dev/null +++ b/lib/fastlane/plugin/ddg_apple_automation/actions/asana_create_action_item_action.rb @@ -0,0 +1,135 @@ +require "fastlane/action" +require "fastlane_core/configuration/config_item" +require "asana" +require "yaml" +require_relative "../helper/ddg_apple_automation_helper" +require_relative "../helper/github_actions_helper" +require_relative "asana_add_comment_action" +require_relative "asana_get_release_automation_subtask_id_action" +require_relative "asana_get_user_id_for_github_handle_action" +require_relative "asana_extract_task_id_action" +require_relative "asana_extract_task_assignee_action" + +module Fastlane + module Actions + class AsanaCreateActionItemAction < Action + def self.run(params) + token = params[:asana_access_token] + task_url = params[:task_url] + task_name = params[:task_name] + notes = params[:notes] + html_notes = params[:html_notes] + template_name = params[:template_name] + is_scheduled_release = params[:is_scheduled_release] + github_handle = params[:github_handle] + + task_id = AsanaExtractTaskIdAction.run(task_url: task_url) + automation_subtask_id = AsanaGetReleaseAutomationSubtaskIdAction.run(task_url: task_url, asana_access_token: token) + if is_scheduled_release + assignee_id = AsanaExtractTaskAssigneeAction.run(task_id: task_id, asana_access_token: token) + else + if github_handle.to_s.empty? + UI.user_error!("Github handle cannot be empty for manual release") + return + end + assignee_id = AsanaGetUserIdForGithubHandleAction.run(github_handle: github_handle, asana_access_token: token) + end + + Helper::GitHubActionsHelper.set_output("asana_assignee_id", assignee_id) + + if template_name + template_file = Helper::DdgAppleAutomationHelper.path_for_asset_file("asana_create_action_item/templates/#{template_name}.yml") + template_content = YAML.safe_load(Helper::DdgAppleAutomationHelper.load_file(template_file)) + task_name = Helper::DdgAppleAutomationHelper.sanitize_html_and_replace_env_vars(template_content["name"]) + html_notes = Helper::DdgAppleAutomationHelper.sanitize_html_and_replace_env_vars(template_content["html_notes"]) + end + + begin + subtask = create_subtask( + token: token, + task_id: automation_subtask_id, + assignee_id: assignee_id, + task_name: task_name, + notes: notes, + html_notes: html_notes + ) + rescue StandardError => e + UI.user_error!("Failed to create subtask for task: #{e}") + end + + Helper::GitHubActionsHelper.set_output("asana_new_task_id", subtask.gid) if subtask&.gid + end + + def self.description + "Add a subtask to Asana Release Automation Task" + end + + def self.authors + ["DuckDuckGo"] + end + + def self.return_value + "" + end + + def self.details + "Adds a task with an action item to the Asana release task's 'Automation' subtask" + end + + def self.available_options + [ + FastlaneCore::ConfigItem.asana_access_token, + FastlaneCore::ConfigItem.new(key: :task_url, + description: "Asana release task URL", + optional: false, + type: String), + FastlaneCore::ConfigItem.new(key: :task_name, + description: "Task name", + optional: true, + type: String), + FastlaneCore::ConfigItem.new(key: :notes, + description: "Task notes", + optional: true, + type: String), + FastlaneCore::ConfigItem.new(key: :html_notes, + description: "Task HTML notes", + optional: true, + type: String), + FastlaneCore::ConfigItem.new(key: :template_name, + description: "Name of a template file (without extension) for the task content. Templates can be found in assets/asana_create_action_item/templates subdirectory. + The file is processed before being sent to Asana", + optional: true, + type: String), + FastlaneCore::ConfigItem.new(key: :github_handle, + description: "Github user handle", + optional: true, + type: String), + FastlaneCore::ConfigItem.new(key: :is_scheduled_release, + description: "Indicates whether the release was scheduled or started manually", + optional: true, + type: Boolean, + default_value: false) + ] + end + + def self.is_supported?(platform) + true + end + + def self.create_subtask(token:, task_id:, assignee_id:, task_name:, notes: nil, html_notes: nil) + subtask_options = { + task_gid: task_id, + assignee: assignee_id, + name: task_name + } + subtask_options[:notes] = notes unless notes.nil? + subtask_options[:html_notes] = html_notes unless html_notes.nil? + + asana_client = Asana::Client.new do |c| + c.authentication(:access_token, token) + end + asana_client.tasks.create_subtask_for_task(**subtask_options) + end + end + end +end diff --git a/lib/fastlane/plugin/ddg_apple_automation/actions/asana_upload_action.rb b/lib/fastlane/plugin/ddg_apple_automation/actions/asana_upload_action.rb index 958913a..ff68d49 100644 --- a/lib/fastlane/plugin/ddg_apple_automation/actions/asana_upload_action.rb +++ b/lib/fastlane/plugin/ddg_apple_automation/actions/asana_upload_action.rb @@ -48,7 +48,7 @@ def self.available_options optional: false, type: String), FastlaneCore::ConfigItem.new(key: :file_name, - description: "Path to the file that will be uploaded", + description: "Path to a file that will be uploaded", optional: false, type: String) ] diff --git a/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/appcast-failed-hotfix.yml b/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/appcast-failed-hotfix.yml new file mode 100644 index 0000000..156eb68 --- /dev/null +++ b/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/appcast-failed-hotfix.yml @@ -0,0 +1,33 @@ +name: Generate appcast2.xml for ${TAG} hotfix release and upload assets to S3 +html_notes: | + + Publishing ${TAG} hotfix release failed in CI. Please follow the steps to generate the appcast file and upload files to S3 from your local machine.
+
    +
  1. Create a new file called release-notes.txt on your disk. +
      +
    • Add each release note as a separate line and don't add bullet points (•) – the script will add them automatically.
    • +
  2. +
  3. Run appcastManager: +
      +
    • ./scripts/appcast_manager/appcastManager.swift --release-hotfix-to-public-channel --dmg ~/Downloads/${DMG_NAME} --release-notes release-notes.txt
    • +
  4. +
  5. Verify that the new build is in the appcast file with the latest release notes and no internal channel tag. The phased rollout tag should not be present: +
      +
    • <sparkle:phasedRolloutInterval>43200</sparkle:phasedRolloutInterval>
    • +
  6. +
  7. Run upload_to_s3.sh script: +
      +
    • ./scripts/upload_to_s3/upload_to_s3.sh --run --overwrite-duckduckgo-dmg ${VERSION}
    • +
  8. +
+ When done, please verify that "Check for Updates" works correctly: +
    +
  1. Launch a debug version of the app with an old version number.
  2. +
  3. Make sure you're not identified as an internal user in the app.
  4. +
  5. Go to Main Menu → DuckDuckGo → Check for Updates...
  6. +
  7. Verify that you're being offered to update to ${TAG}.
  8. +
  9. Verify that the update works.
  10. +

+ 🔗 Workflow URL: ${WORKFLOW_URL}. + + \ No newline at end of file diff --git a/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/appcast-failed-internal.yml b/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/appcast-failed-internal.yml new file mode 100644 index 0000000..88908b3 --- /dev/null +++ b/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/appcast-failed-internal.yml @@ -0,0 +1,34 @@ +name: Generate appcast2.xml for ${TAG} internal release and upload assets to S3 +html_notes: | + + Publishing ${TAG} internal release failed in CI. Please follow the steps to generate the appcast file and upload files to S3 from your local machine.
+
    +
  1. Download the DMG for ${TAG} release.
  2. +
  3. Create a new file called release-notes.txt on your disk. +
      +
    • Add each release note as a separate line and don't add bullet points (•) – the script will add them automatically.
    • +
  4. +
  5. Run appcastManager: +
      +
    • ./scripts/appcast_manager/appcastManager.swift --release-to-internal-channel --dmg ~/Downloads/${DMG_NAME} --release-notes release-notes.txt
    • +
  6. +
  7. Verify that the new build is in the appcast file with the following internal channel tag: +
      +
    • <sparkle:channel>internal-channel</sparkle:channel>
    • +
  8. +
  9. Run upload_to_s3.sh script: +
      +
    • ./scripts/upload_to_s3/upload_to_s3.sh --run
    • +
  10. +
+ When done, please verify that "Check for Updates" works correctly: +
    +
  1. Launch a debug version of the app with an old version number.
  2. +
  3. Identify as an internal user in the app.
  4. +
  5. Go to Main Menu → DuckDuckGo → Check for Updates...
  6. +
  7. Verify that you're being offered to update to ${TAG}.
  8. +
  9. Verify that the update works.
  10. +

+ 🔗 Workflow URL: ${WORKFLOW_URL}. + + \ No newline at end of file diff --git a/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/appcast-failed-public.yml b/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/appcast-failed-public.yml new file mode 100644 index 0000000..ca4d4e2 --- /dev/null +++ b/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/appcast-failed-public.yml @@ -0,0 +1,32 @@ +name: Generate appcast2.xml for ${TAG} public release and upload assets to S3 +html_notes: | + + Publishing ${TAG} release failed in CI. Please follow the steps to generate the appcast file and upload files to S3 from your local machine.
+
    +
  1. Create a new file called release-notes.txt on your disk. +
      +
    • Add each release note as a separate line and don't add bullet points (•) – the script will add them automatically.
    • +
  2. +
  3. Run appcastManager: +
      +
    • ./scripts/appcast_manager/appcastManager.swift --release-to-public-channel --version ${VERSION} --release-notes release-notes.txt
    • +
  4. +
  5. Verify that the new build is in the appcast file with the latest release notes, the phased rollout tag (below) and no internal channel tag: +
      +
    • <sparkle:phasedRolloutInterval>43200</sparkle:phasedRolloutInterval>
    • +
  6. +
  7. Run upload_to_s3.sh script: +
      +
    • ./scripts/upload_to_s3/upload_to_s3.sh --run --overwrite-duckduckgo-dmg ${VERSION}
    • +
  8. +
+ When done, please verify that "Check for Updates" works correctly: +
    +
  1. Launch a debug version of the app with an old version number.
  2. +
  3. Make sure you're not identified as an internal user in the app.
  4. +
  5. Go to Main Menu → DuckDuckGo → Check for Updates...
  6. +
  7. Verify that you're being offered to update to ${TAG}.
  8. +
  9. Verify that the update works.
  10. +

+ 🔗 Workflow URL: ${WORKFLOW_URL}. + diff --git a/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/delete-branch-failed.yml b/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/delete-branch-failed.yml new file mode 100644 index 0000000..69d8440 --- /dev/null +++ b/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/delete-branch-failed.yml @@ -0,0 +1,12 @@ +name: Delete ${BRANCH} branch +html_notes: | + + The ${TAG} public release has been successfully tagged and published in GitHub releases, + but deleting ${BRANCH} branch failed. Please delete it manually: + + Complete this task when ready, or if the release branch has already been deleted.
+
+ 🔗 Workflow URL: ${WORKFLOW_URL}. + diff --git a/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/internal-release-tag-failed.yml b/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/internal-release-tag-failed.yml new file mode 100644 index 0000000..9266785 --- /dev/null +++ b/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/internal-release-tag-failed.yml @@ -0,0 +1,39 @@ +name: Tag ${BRANCH} branch and create GitHub release +html_notes: | + + Failed to tag the release with ${TAG} tag.
+ Please follow instructions below to tag the branch, make GitHub release and merge release branch to ${BASE_BRANCH} manually.
+
+ Issue the following git commands to tag the release and merge the branch: +
+ To create GitHub release: +
+ Complete this task when ready and proceed with testing the build. If you're bumping an internal release, you should get another task asking you to publish the release in Sparkle. + Look for other tasks in
task and handle them as needed.
+
+ 🔗 Workflow URL:
${WORKFLOW_URL}. + diff --git a/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/merge-failed.yml b/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/merge-failed.yml new file mode 100644 index 0000000..87f016f --- /dev/null +++ b/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/merge-failed.yml @@ -0,0 +1,28 @@ +name: Merge ${BRANCH} to ${BASE_BRANCH} +html_notes: | + + The ${TAG} release has been successfully tagged and published in GitHub releases, + but merging to ${BASE_BRANCH} failed. Please resolve conflicts and merge ${BRANCH} to ${BASE_BRANCH} manually.
+
+ Issue the following git commands: + + Complete this task when ready and proceed with testing the build.
+
+ 🔗 Workflow URL: ${WORKFLOW_URL}. + diff --git a/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/public-release-tag-failed.yml b/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/public-release-tag-failed.yml new file mode 100644 index 0000000..bbfb11c --- /dev/null +++ b/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/public-release-tag-failed.yml @@ -0,0 +1,31 @@ +name: Tag ${BRANCH} branch, delete it, and create GitHub release +html_notes: | + + Failed to tag the release with ${TAG} tag.
+ Please follow instructions below to tag the branch, make GitHub release and delete the release branch manually. +
+ Issue the following git commands to tag the release and delete the branch: +
+ To create GitHub release: +
+ Complete this task when ready.
+
+ 🔗 Workflow URL:
${WORKFLOW_URL}. + diff --git a/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/run-publish-dmg-release.yml b/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/run-publish-dmg-release.yml new file mode 100644 index 0000000..f337159 --- /dev/null +++ b/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/run-publish-dmg-release.yml @@ -0,0 +1,32 @@ +name: Run Publish DMG Release GitHub Actions workflow +html_notes: | + +

Using GH CLI

+ Run the following command:
+
+ gh workflow run publish_dmg_release.yml --ref ${BRANCH} -f asana-task-url=${ASANA_TASK_URL} -f tag=${TAG} -f release-type=internal +

Using GitHub web UI

+
    +
  1. Open Publish DMG Release workflow page.
  2. +
  3. Click "Run Workflow" and fill in the form as follows: +
      +
    • Branch ${BRANCH}
    • +
    • Asana release task URL ${ASANA_TASK_URL}
    • +
    • Tag to publish ${TAG}
    • +
    • Release Type internal
    • +
  4. +

+ The GitHub Action workflow does the following: +
+ Complete this task when ready and proceed with testing the build. If GitHub Actions is unavailable, you'll find manual instructions in the Run Publish DMG Release GitHub Actions workflow subtask of Make Internal Release task.
+
+ 🔗 Workflow URL: ${WORKFLOW_URL}. + diff --git a/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/update-asana-for-public-release.yml b/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/update-asana-for-public-release.yml new file mode 100644 index 0000000..8cbd68c --- /dev/null +++ b/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/update-asana-for-public-release.yml @@ -0,0 +1,15 @@ +name: Move release task and included items to "Done" section in macOS App Board and close them if possible +html_notes: | + + Automation failed to update Asana for the public release. Please follow the steps below. +
    +
  1. Open and select the List view
  2. +
  3. Scroll to the "Validation" section.
  4. +
  5. Select all the tasks in that section.
  6. +
  7. Drag and drop all the selected tasks to the "Done" section
  8. +
  9. Close all tasks that are not incidents and don't belong to project, including the release task itself.
  10. +

+ Complete this task when ready.
+
+ 🔗 Workflow URL:
${WORKFLOW_URL}. + diff --git a/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/validate-check-for-updates-internal.yml b/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/validate-check-for-updates-internal.yml new file mode 100644 index 0000000..d114a6d --- /dev/null +++ b/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/validate-check-for-updates-internal.yml @@ -0,0 +1,37 @@ +name: Validate that 'Check For Updates' upgrades to ${TAG} for internal users +html_notes: | + +

Build ${TAG} has been released internally via Sparkle 🎉

+ Please verify that "Check for Updates" works correctly: +
    +
  1. Launch a debug version of the app with an old version number.
  2. +
  3. Identify as an internal user in the app.
  4. +
  5. Go to Main Menu → DuckDuckGo → Check for Updates...
  6. +
  7. Verify that you're being offered to update to ${TAG}.
  8. +
  9. Verify that the update works.
  10. +
+

🚨In case "Check for Updates" is broken

+ You can restore previous version of the appcast2.xml: +
    +
  1. Download the ${OLD_APPCAST_NAME} file attached to this task.
  2. +
  3. Log in to AWS session: +
      +
    • aws --profile ddg-macos sso login
    • +
  4. +
  5. Overwrite appcast2.xml with the old version: +
      +
    • aws --profile ddg-macos s3 cp ${OLD_APPCAST_NAME} s3://${RELEASE_BUCKET_NAME}/${RELEASE_BUCKET_PREFIX}/appcast2.xml --acl public-read
    • +
  6. +

+
+

Summary of automated changes

+

Changes to appcast2.xml

+ See the attached ${APPCAST_PATCH_NAME} file. +

Release notes

+ See the attached ${RELEASE_NOTES_FILE} file for release notes extracted automatically from the release task description. +

List of files uploaded to S3

+
    + ${FILES_UPLOADED} +

+ 🔗 Workflow URL: ${WORKFLOW_URL}. + diff --git a/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/validate-check-for-updates-public.yml b/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/validate-check-for-updates-public.yml new file mode 100644 index 0000000..2cdad74 --- /dev/null +++ b/lib/fastlane/plugin/ddg_apple_automation/assets/asana_create_action_item/templates/validate-check-for-updates-public.yml @@ -0,0 +1,37 @@ +name: Validate that 'Check For Updates' upgrades to ${TAG} +html_notes: | + +

Build ${TAG} has been released publicly via Sparkle 🎉

+ Please verify that "Check for Updates" works correctly: +
    +
  1. Launch a debug version of the app with an old version number.
  2. +
  3. Make sure you're not identified as an internal user in the app.
  4. +
  5. Go to Main Menu → DuckDuckGo → Check for Updates...
  6. +
  7. Verify that you're being offered to update to ${TAG}.
  8. +
  9. Verify that the update works.
  10. +
+

🚨In case "Check for Updates" is broken

+ You can restore previous version of the appcast2.xml: +
    +
  1. Download the ${OLD_APPCAST_NAME} file attached to this task.
  2. +
  3. Log in to AWS session: +
      +
    • aws --profile ddg-macos sso login
    • +
  4. +
  5. Overwrite appcast2.xml with the old version: +
      +
    • aws --profile ddg-macos s3 cp ${OLD_APPCAST_NAME} s3://${RELEASE_BUCKET_NAME}/${RELEASE_BUCKET_PREFIX}/appcast2.xml --acl public-read
    • +
  6. +

+
+

Summary of automated changes

+

Changes to appcast2.xml

+ See the attached ${APPCAST_PATCH_NAME} file. +

Release notes

+ See the attached ${RELEASE_NOTES_FILE} file for release notes extracted automatically from the release task description. +

List of files uploaded to S3

+
    + ${FILES_UPLOADED} +

+ 🔗 Workflow URL: ${WORKFLOW_URL}. + diff --git a/lib/fastlane/plugin/ddg_apple_automation/helper/ddg_apple_automation_helper.rb b/lib/fastlane/plugin/ddg_apple_automation/helper/ddg_apple_automation_helper.rb index f027f3c..d8b30e8 100644 --- a/lib/fastlane/plugin/ddg_apple_automation/helper/ddg_apple_automation_helper.rb +++ b/lib/fastlane/plugin/ddg_apple_automation/helper/ddg_apple_automation_helper.rb @@ -27,6 +27,14 @@ def self.load_file(file) rescue StandardError UI.user_error!("Error: The file '#{file}' does not exist.") end + + def self.sanitize_html_and_replace_env_vars(content) + content.gsub(/\$\{(\w+)\}/) { ENV.fetch($1, '') } # replace environment variables + .gsub(/\s+/, ' ') # replace multiple whitespaces with a single space + .gsub(/>\s+<') # remove spaces between HTML tags + .strip # remove leading and trailing whitespaces + .gsub(%r{}, "\n") # replace
tags with newlines + end end end end diff --git a/lib/fastlane/plugin/ddg_apple_automation/version.rb b/lib/fastlane/plugin/ddg_apple_automation/version.rb index e6daca4..ba1dfa6 100644 --- a/lib/fastlane/plugin/ddg_apple_automation/version.rb +++ b/lib/fastlane/plugin/ddg_apple_automation/version.rb @@ -1,5 +1,5 @@ module Fastlane module DdgAppleAutomation - VERSION = "0.8.1" + VERSION = "0.9.0" end end diff --git a/spec/asana_add_comment_action_spec.rb b/spec/asana_add_comment_action_spec.rb index f5fc15c..17f4aa3 100644 --- a/spec/asana_add_comment_action_spec.rb +++ b/spec/asana_add_comment_action_spec.rb @@ -46,32 +46,6 @@ end end - it "shows error if provided template does not exist" do - allow(File).to receive(:read).and_raise(Errno::ENOENT) - allow(Fastlane::Helper::DdgAppleAutomationHelper).to receive(:path_for_asset_file).and_return("non-existing.html") - expect(Fastlane::UI).to receive(:user_error!).with("Error: The file 'non-existing.html' does not exist.") - expect(@asana_client_stories).not_to receive(:create_story_for_task) - test_action(task_id: "123", template_name: "non-existing") - end - - it "correctly substitutes all variables" do - template_content = "

${ASSIGNEE_ID} is publishing ${TAG} hotfix release

" - ClimateControl.modify( - ASSIGNEE_ID: '12345', - TAG: 'v1.0.0' - ) do - result = Fastlane::Actions::AsanaAddCommentAction.process_template_content(template_content) - expected_output = "

12345 is publishing v1.0.0 hotfix release

" - expect(result).to eq(expected_output) - end - end - - it "removes newlines and leading/trailing spaces" do - template_content = " \nHello, \n\n World!\n This is a test. \n" - result = Fastlane::Actions::AsanaAddCommentAction.process_template_content(template_content) - expect(result).to eq("Hello, World! This is a test.") - end - it "correctly builds html_text payload" do allow(File).to receive(:read).and_return(" \nHello, \n World!\n This is a test. \n") allow(@asana_client_stories).to receive(:create_story_for_task) diff --git a/spec/asana_create_action_item_action_spec.rb b/spec/asana_create_action_item_action_spec.rb new file mode 100644 index 0000000..2f0a300 --- /dev/null +++ b/spec/asana_create_action_item_action_spec.rb @@ -0,0 +1,109 @@ +describe Fastlane::Actions::AsanaCreateActionItemAction do + describe "#run" do + let(:task_url) { "https://app.asana.com/4/753241/9999" } + let(:task_id) { "1" } + let(:automation_subtask_id) { "2" } + let(:assignee_id) { "11" } + let(:github_handle) { "user" } + let(:task_name) { "example name" } + + let(:parsed_yaml_content) { { 'name' => 'test task', 'html_notes' => '

Some notes

' } } + + before do + @asana_client_tasks = double + asana_client = double("Asana::Client") + allow(Asana::Client).to receive(:new).and_return(asana_client) + allow(asana_client).to receive(:tasks).and_return(@asana_client_tasks) + + allow(Fastlane::Actions::AsanaExtractTaskIdAction).to receive(:run).and_return(task_id) + allow(Fastlane::Actions::AsanaExtractTaskAssigneeAction).to receive(:run).and_return(assignee_id) + allow(Fastlane::Actions::AsanaGetReleaseAutomationSubtaskIdAction).to receive(:run).with(task_url: task_url, asana_access_token: anything).and_return(automation_subtask_id) + allow(Fastlane::Actions::AsanaGetUserIdForGithubHandleAction).to receive(:run).and_return(assignee_id) + allow(@asana_client_tasks).to receive(:create_subtask_for_task) + + allow(Fastlane::Helper::GitHubActionsHelper).to receive(:set_output) + end + + it "extracts assignee id from release task when is scheduled release" do + expect(Fastlane::Actions::AsanaExtractTaskIdAction).to receive(:run).with(task_url: task_url) + expect(Fastlane::Actions::AsanaExtractTaskAssigneeAction).to receive(:run).with( + task_id: task_id, + asana_access_token: anything + ) + test_action(task_url: task_url, is_scheduled_release: true) + expect(Fastlane::Helper::GitHubActionsHelper).to have_received(:set_output).with("asana_assignee_id", "11") + end + + it "takes assignee id from github handle when is manual release" do + expect(Fastlane::Actions::AsanaGetUserIdForGithubHandleAction).to receive(:run).with( + github_handle: github_handle, + asana_access_token: anything + ) + test_action(task_url: task_url, is_scheduled_release: false, github_handle: github_handle) + expect(Fastlane::Helper::GitHubActionsHelper).to have_received(:set_output).with("asana_assignee_id", "11") + end + + it "raises an error when github handle is empty and is manual release" do + expect(Fastlane::UI).to receive(:user_error!).with("Github handle cannot be empty for manual release") + test_action(task_url: task_url, is_scheduled_release: false, github_handle: "") + expect(Fastlane::Helper::GitHubActionsHelper).not_to have_received(:set_output) + end + + it "correctly builds payload if notes input is given" do + test_action(task_url: task_url, task_name: task_name, notes: "notes", is_scheduled_release: true) + expect(@asana_client_tasks).to have_received(:create_subtask_for_task).with( + task_gid: automation_subtask_id, + name: task_name, + notes: "notes", + assignee: assignee_id + ) + end + + it "correctly builds payload if html_notes input is given" do + test_action(task_url: task_url, task_name: task_name, html_notes: "html_notes", is_scheduled_release: true) + expect(@asana_client_tasks).to have_received(:create_subtask_for_task).with( + task_gid: automation_subtask_id, + name: task_name, + html_notes: "html_notes", + assignee: assignee_id + ) + end + + it "correctly builds payload if template_name input is given" do + allow(File).to receive(:read) + allow(YAML).to receive(:safe_load).and_return(parsed_yaml_content) + test_action(task_url: task_url, task_name: task_name, template_name: "template_name", is_scheduled_release: true) + expect(@asana_client_tasks).to have_received(:create_subtask_for_task).with( + task_gid: automation_subtask_id, + name: "test task", + html_notes: "

Some notes

", + assignee: assignee_id + ) + end + + it "raises an error if adding subtask fails" do + allow(Fastlane::UI).to receive(:user_error!) + allow(@asana_client_tasks).to receive(:create_subtask_for_task).and_raise(StandardError, 'API Error') + test_action(task_url: task_url, task_name: task_name, notes: "notes", is_scheduled_release: true) + expect(Fastlane::UI).to have_received(:user_error!).with("Failed to create subtask for task: API Error") + end + + it "correctly sets output" do + allow(@asana_client_tasks).to receive(:create_subtask_for_task).and_return(double('subtask', gid: "42")) + test_action(task_url: task_url, task_name: task_name, notes: "notes", is_scheduled_release: true) + expect(Fastlane::Helper::GitHubActionsHelper).to have_received(:set_output).with("asana_new_task_id", "42") + end + end + + def test_action(task_url:, task_name: nil, notes: nil, html_notes: nil, template_name: nil, is_scheduled_release: false, github_handle: nil) + Fastlane::Actions::AsanaCreateActionItemAction.run( + task_url: task_url, + task_name: task_name, + notes: notes, + html_notes: html_notes, + template_name: template_name, + is_scheduled_release: is_scheduled_release, + github_handle: github_handle + ) + end +end diff --git a/spec/ddg_apple_automation_helper_spec.rb b/spec/ddg_apple_automation_helper_spec.rb index e80f24f..41b4563 100644 --- a/spec/ddg_apple_automation_helper_spec.rb +++ b/spec/ddg_apple_automation_helper_spec.rb @@ -1,3 +1,5 @@ +require "climate_control" + describe Fastlane::Helper::DdgAppleAutomationHelper do describe "#asana_task_url" do it "constructs Asana task URL" do @@ -15,4 +17,58 @@ def asana_task_url(task_id) Fastlane::Helper::DdgAppleAutomationHelper.asana_task_url(task_id) end end + + describe "#load_file" do + it "shows error if provided file does not exist" do + allow(Fastlane::UI).to receive(:user_error!) + allow(File).to receive(:read).and_raise(Errno::ENOENT) + load_file("file") + expect(Fastlane::UI).to have_received(:user_error!).with("Error: The file 'file' does not exist.") + end + + def load_file(file) + Fastlane::Helper::DdgAppleAutomationHelper.load_file(file) + end + end + + describe "#sanitize_html_and_replace_env_vars" do + it "substitutes all env variables" do + content = "

${ASSIGNEE_ID} is publishing ${TAG} hotfix release

" + ClimateControl.modify( + ASSIGNEE_ID: '12345', + TAG: 'v1.0.0' + ) do + expect(sanitize_html_and_replace_env_vars(content)).to eq("

12345 is publishing v1.0.0 hotfix release

") + end + end + + it "removes newlines and leading/trailing spaces" do + content = " \nHello, \n\n World!\n This is a test. \n" + expect(sanitize_html_and_replace_env_vars(content)).to eq("Hello, World! This is a test.") + end + + it "removes spaces between html tags" do + content = "

Hello, World! This is a test.

" + expect(sanitize_html_and_replace_env_vars(content)).to eq("

Hello, World! This is a test.

") + end + + it "replaces multiple whitespaces with a single space" do + content = "

Hello, World! This is a test.

" + expect(sanitize_html_and_replace_env_vars(content)).to eq("

Hello, World! This is a test.

") + end + + it "replaces
tags with new lines" do + content = "

Hello, World!
This is a test.

" + expect(sanitize_html_and_replace_env_vars(content)).to eq("

Hello, World!\n This is a test.

") + end + + it "preserves HTML-escaped characters" do + content = "Hello -> World!" + expect(sanitize_html_and_replace_env_vars(content)).to eq("Hello -> World!") + end + + def sanitize_html_and_replace_env_vars(content) + Fastlane::Helper::DdgAppleAutomationHelper.sanitize_html_and_replace_env_vars(content) + end + end end