diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b74c6826..831e8d9d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,9 @@ ### Breaking Changes -_None_ +- Removed support for the deprecated `GHHELPER_ACCESS` in favor of `GITHUB_TOKEN` as the default environment variable to set the GitHub API token. [#420] +- The `github_token:` parameter (aka `ConfigItem`)–or using the corresponding `GITHUB_TOKEN` env var to provide it a value–is now mandatory for all Fastlane actions that use the GitHub API. [#420] +- The Fastlane action `comment_on_pr` has the parameter `access_key:` replaced by `github_token:`. [#420] ### New Features diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_download_file_by_version.rb b/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_download_file_by_version.rb index 859862182..6dbbc8762 100644 --- a/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_download_file_by_version.rb +++ b/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_download_file_by_version.rb @@ -9,7 +9,8 @@ def self.run(params) UI.user_error!("Can't find any reference for key #{params[:import_key]}") if version.nil? UI.message "Downloading #{params[:file_path]} from #{params[:repository]} at version #{version} to #{params[:download_folder]}" - Fastlane::Helper::GithubHelper.download_file_from_tag( + github_helper = Fastlane::Helper::GithubHelper.new(github_token: params[:github_token]) + github_helper.download_file_from_tag( repository: params[:repository], tag: "#{params[:github_release_prefix]}#{version}", file_path: params[:file_path], @@ -57,6 +58,7 @@ def self.available_options description: 'The prefix which is used in the GitHub release title', type: String, optional: true), + Fastlane::Helper::GithubHelper.github_token_config_item, ] end diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/close_milestone_action.rb b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/close_milestone_action.rb index 217f7e834..f85d18dcd 100644 --- a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/close_milestone_action.rb +++ b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/close_milestone_action.rb @@ -10,10 +10,12 @@ def self.run(params) repository = params[:repository] milestone_title = params[:milestone] - milestone = Fastlane::Helper::GithubHelper.get_milestone(repository, milestone_title) + github_helper = Fastlane::Helper::GithubHelper.new(github_token: params[:github_token]) + milestone = github_helper.get_milestone(repository, milestone_title) + UI.user_error!("Milestone #{milestone_title} not found.") if milestone.nil? - Fastlane::Helper::GithubHelper.github_client().update_milestone(repository, milestone[:number], state: 'closed') + github_helper.update_milestone(repository: repository, number: milestone[:number], options: { state: 'closed' }) end def self.description @@ -45,6 +47,7 @@ def self.available_options description: 'The GitHub milestone', optional: false, type: String), + Fastlane::Helper::GithubHelper.github_token_config_item, ] end diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/comment_on_pr.rb b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/comment_on_pr.rb index e3b82532b..9335f37be 100644 --- a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/comment_on_pr.rb +++ b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/comment_on_pr.rb @@ -10,7 +10,8 @@ class CommentOnPrAction < Action def self.run(params) require_relative '../../helper/github_helper' - reuse_identifier = Fastlane::Helper::GithubHelper.comment_on_pr( + github_helper = Fastlane::Helper::GithubHelper.new(github_token: params[:github_token]) + reuse_identifier = github_helper.comment_on_pr( project_slug: params[:project], pr_number: params[:pr_number], body: params[:body], @@ -41,12 +42,7 @@ def self.details def self.available_options [ - FastlaneCore::ConfigItem.new( - key: :access_token, - env_name: 'GITHUB_TOKEN', - description: 'The GitHub token to use for posting the comment', - type: String - ), + Fastlane::Helper::GithubHelper.github_token_config_item, FastlaneCore::ConfigItem.new( key: :reuse_identifier, description: 'If provided, the reuse identifier can identify an existing comment to overwrite', diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/create_new_milestone_action.rb b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/create_new_milestone_action.rb index 0155c0d82..9f3e1ea32 100644 --- a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/create_new_milestone_action.rb +++ b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/create_new_milestone_action.rb @@ -9,7 +9,8 @@ class CreateNewMilestoneAction < Action def self.run(params) repository = params[:repository] - last_stone = Fastlane::Helper::GithubHelper.get_last_milestone(repository) + github_helper = Fastlane::Helper::GithubHelper.new(github_token: params[:github_token]) + last_stone = github_helper.get_last_milestone(repository) UI.message("Last detected milestone: #{last_stone[:title]} due on #{last_stone[:due_on]}.") milestone_duedate = last_stone[:due_on] milestone_duration = params[:milestone_duration] @@ -17,7 +18,7 @@ def self.run(params) newmilestone_number = Fastlane::Helper::Ios::VersionHelper.calc_next_release_version(last_stone[:title]) number_of_days_from_code_freeze_to_release = params[:number_of_days_from_code_freeze_to_release] UI.message("Next milestone: #{newmilestone_number} due on #{newmilestone_duedate}.") - Fastlane::Helper::GithubHelper.create_milestone(repository, newmilestone_number, newmilestone_duedate, milestone_duration, number_of_days_from_code_freeze_to_release, params[:need_appstore_submission]) + github_helper.create_milestone(repository, newmilestone_number, newmilestone_duedate, milestone_duration, number_of_days_from_code_freeze_to_release, params[:need_appstore_submission]) end def self.description @@ -62,6 +63,7 @@ def self.available_options optional: true, is_string: false, default_value: 14), + Fastlane::Helper::GithubHelper.github_token_config_item, ] end diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/create_release_action.rb b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/create_release_action.rb index d0e9094ed..844d89c91 100644 --- a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/create_release_action.rb +++ b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/create_release_action.rb @@ -21,7 +21,8 @@ def self.run(params) UI.user_error!("Can't find file #{file_path}!") unless File.exist?(file_path) end - Fastlane::Helper::GithubHelper.create_release( + github_helper = Fastlane::Helper::GithubHelper.new(github_token: params[:github_token]) + github_helper.create_release( repository: repository, version: version, target: params[:target], @@ -82,6 +83,7 @@ def self.available_options optional: true, default_value: false, is_string: false), + Fastlane::Helper::GithubHelper.github_token_config_item, ] end diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/get_prs_list_action.rb b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/get_prs_list_action.rb index 1a2b2679e..7b9a40e55 100644 --- a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/get_prs_list_action.rb +++ b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/get_prs_list_action.rb @@ -10,7 +10,8 @@ def self.run(params) milestone = params[:milestone] # Get commit list - pr_list = Fastlane::Helper::GithubHelper.get_prs_for_milestone(repository, milestone) + github_helper = Fastlane::Helper::GithubHelper.new(github_token: params[:github_token]) + pr_list = github_helper.get_prs_for_milestone(repository, milestone) File.open(report_path, 'w') do |file| pr_list.each do |data| @@ -53,6 +54,7 @@ def self.available_options description: 'The name of the milestone we want to fetch the list of PRs for (e.g.: `16.9`)', optional: false, is_string: true), + Fastlane::Helper::GithubHelper.github_token_config_item, ] end diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/removebranchprotection_action.rb b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/removebranchprotection_action.rb index defbfc207..9d33e5083 100644 --- a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/removebranchprotection_action.rb +++ b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/removebranchprotection_action.rb @@ -7,14 +7,15 @@ class RemovebranchprotectionAction < Action def self.run(params) repository = params[:repository] branch_name = params[:branch] - branch_prot = {} + branch_prot = {} branch_url = "https://api.github.com/repos/#{repository}/branches/#{branch_name}" branch_prot[:restrictions] = { url: "#{branch_url}/protection/restrictions", users_url: "#{branch_url}/protection/restrictions/users", teams_url: "#{branch_url}/protection/restrictions/teams", users: [], teams: [] } branch_prot[:enforce_admins] = nil branch_prot[:required_pull_request_reviews] = { url: "#{branch_url}/protection/required_pull_request_reviews", dismiss_stale_reviews: false, require_code_owner_reviews: false } - Fastlane::Helper::GithubHelper.github_client().unprotect_branch(repository, branch_name, branch_prot) + github_helper = Fastlane::Helper::GithubHelper.new(github_token: params[:github_token]) + github_helper.remove_branch_protection(repository: repository, branch: branch_name, options: branch_prot) end def self.description @@ -46,6 +47,7 @@ def self.available_options description: 'The branch to unprotect', optional: false, type: String), + Fastlane::Helper::GithubHelper.github_token_config_item, ] end diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/setbranchprotection_action.rb b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/setbranchprotection_action.rb index 8026b860d..41a11e5bd 100644 --- a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/setbranchprotection_action.rb +++ b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/setbranchprotection_action.rb @@ -7,13 +7,15 @@ class SetbranchprotectionAction < Action def self.run(params) repository = params[:repository] branch_name = params[:branch] - branch_prot = {} + branch_prot = {} branch_url = "https://api.github.com/repos/#{repository}/branches/#{branch_name}" branch_prot[:restrictions] = { url: "#{branch_url}/protection/restrictions", users_url: "#{branch_url}/protection/restrictions/users", teams_url: "#{branch_url}/protection/restrictions/teams", users: [], teams: [] } branch_prot[:enforce_admins] = nil branch_prot[:required_pull_request_reviews] = { url: "#{branch_url}/protection/required_pull_request_reviews", dismiss_stale_reviews: false, require_code_owner_reviews: false } - Fastlane::Helper::GithubHelper.github_client().protect_branch(repository, branch_name, branch_prot) + + github_helper = Fastlane::Helper::GithubHelper.new(github_token: params[:github_token]) + github_helper.set_branch_protection(repository: repository, branch: branch_name, options: branch_prot) end def self.description @@ -45,6 +47,7 @@ def self.available_options description: 'The branch to protect', optional: false, type: String), + Fastlane::Helper::GithubHelper.github_token_config_item, ] end diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/setfrozentag_action.rb b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/setfrozentag_action.rb index ca145bcac..780ffede1 100644 --- a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/setfrozentag_action.rb +++ b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/setfrozentag_action.rb @@ -9,7 +9,9 @@ def self.run(params) milestone_title = params[:milestone] freeze = params[:freeze] - milestone = Fastlane::Helper::GithubHelper.get_milestone(repository, milestone_title) + github_helper = Fastlane::Helper::GithubHelper.new(github_token: params[:github_token]) + milestone = github_helper.get_milestone(repository, milestone_title) + UI.user_error!("Milestone #{milestone_title} not found.") if milestone.nil? mile_title = milestone[:title] @@ -27,7 +29,7 @@ def self.run(params) end UI.message("New milestone: #{mile_title}") - Fastlane::Helper::GithubHelper.github_client().update_milestone(repository, milestone[:number], title: mile_title) + github_helper.update_milestone(repository: repository, number: milestone[:number], options: { title: mile_title }) end def self.is_frozen(milestone) @@ -70,6 +72,7 @@ def self.available_options optional: false, default_value: true, is_string: false), + Fastlane::Helper::GithubHelper.github_token_config_item, ] end diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/helper/github_helper.rb b/lib/fastlane/plugin/wpmreleasetoolkit/helper/github_helper.rb index 3100213b0..e8d35e754 100644 --- a/lib/fastlane/plugin/wpmreleasetoolkit/helper/github_helper.rb +++ b/lib/fastlane/plugin/wpmreleasetoolkit/helper/github_helper.rb @@ -8,34 +8,25 @@ module Fastlane module Helper class GithubHelper - def self.github_token! - token = [ - 'GHHELPER_ACCESS', # For historical reasons / backward compatibility - 'GITHUB_TOKEN', # Used by the `gh` CLI tool - ].map { |key| ENV[key] } - .compact - .first - - token || UI.user_error!('Please provide a GitHub authentication token via the `GITHUB_TOKEN` environment variable') - end - - def self.github_client - @@client ||= begin - client = Octokit::Client.new(access_token: github_token!) + attr_reader :client - # Fetch the current user - user = client.user - UI.message("Logged in as: #{user.name}") + # Helper for GitHub Actions + # + # @param [String?] github_token GitHub OAuth access token + # + def initialize(github_token:) + @client = Octokit::Client.new(access_token: github_token) - # Auto-paginate to ensure we're not missing data - client.auto_paginate = true + # Fetch the current user + user = @client.user + UI.message("Logged in as: #{user.name}") - client - end + # Auto-paginate to ensure we're not missing data + @client.auto_paginate = true end - def self.get_milestone(repository, release) - miles = github_client().list_milestones(repository) + def get_milestone(repository, release) + miles = client.list_milestones(repository) mile = nil miles&.each do |mm| @@ -51,15 +42,15 @@ def self.get_milestone(repository, release) # @param [String] milestone The name of the milestone we want to fetch the list of PRs for (e.g.: `16.9`) # @return [] A list of the PRs for the given milestone, sorted by number # - def self.get_prs_for_milestone(repository, milestone) - github_client.search_issues(%(type:pr milestone:"#{milestone}" repo:#{repository}))[:items].sort_by(&:number) + def get_prs_for_milestone(repository, milestone) + client.search_issues(%(type:pr milestone:"#{milestone}" repo:#{repository}))[:items].sort_by(&:number) end - def self.get_last_milestone(repository) + def get_last_milestone(repository) options = {} options[:state] = 'open' - milestones = github_client().list_milestones(repository, options) + milestones = client.list_milestones(repository, options) return nil if milestones.nil? last_stone = nil @@ -80,7 +71,7 @@ def self.get_last_milestone(repository) last_stone end - def self.create_milestone(repository, newmilestone_number, newmilestone_duedate, newmilestone_duration, number_of_days_from_code_freeze_to_release, need_submission) + def create_milestone(repository, newmilestone_number, newmilestone_duedate, newmilestone_duration, number_of_days_from_code_freeze_to_release, need_submission) # If there is a review process, we want to submit the binary 3 days before its release # # Using 3 days is mostly for historical reasons where we release the apps on Monday and submit them on Friday. @@ -107,7 +98,7 @@ def self.create_milestone(repository, newmilestone_number, newmilestone_duedate, # To solve this, we trick it by forcing the time component of the ISO date we send to be `12:00:00Z`. options[:due_on] = newmilestone_duedate.strftime('%Y-%m-%dT12:00:00Z') options[:description] = comment - github_client().create_milestone(repository, newmilestone_number, options) + client.create_milestone(repository, newmilestone_number, options) end # Creates a Release on GitHub as a Draft @@ -121,8 +112,8 @@ def self.create_milestone(repository, newmilestone_number, newmilestone_duedate, # @param [Array] assets List of file paths to attach as assets to the release # @param [TrueClass|FalseClass] prerelease Indicates if this should be created as a pre-release (i.e. for alpha/beta) # - def self.create_release(repository:, version:, target: nil, description:, assets:, prerelease:) - release = github_client().create_release( + def create_release(repository:, version:, target: nil, description:, assets:, prerelease:) + release = client.create_release( repository, version, # tag name name: version, # release name @@ -132,7 +123,7 @@ def self.create_release(repository:, version:, target: nil, description:, assets body: description ) assets.each do |file_path| - github_client().upload_asset(release[:url], file_path, content_type: 'application/octet-stream') + client.upload_asset(release[:url], file_path, content_type: 'application/octet-stream') end end @@ -144,15 +135,14 @@ def self.create_release(repository:, version:, target: nil, description:, assets # @param [String] download_folder The folder which the file should be downloaded into # @return [String] The path of the downloaded file, or nil if something went wrong # - def self.download_file_from_tag(repository:, tag:, file_path:, download_folder:) + def download_file_from_tag(repository:, tag:, file_path:, download_folder:) repository = repository.delete_prefix('/').chomp('/') file_path = file_path.delete_prefix('/').chomp('/') file_name = File.basename(file_path) download_path = File.join(download_folder, file_name) - download_url = github_client.contents(repository, - path: file_path, - ref: tag).download_url + download_url = client.contents(repository, path: file_path, ref: tag).download_url + begin uri = URI.parse(download_url) uri.open do |remote_file| @@ -166,8 +156,7 @@ def self.download_file_from_tag(repository:, tag:, file_path:, download_folder:) end # Creates (or updates an existing) GitHub PR Comment - def self.comment_on_pr(project_slug:, pr_number:, body:, reuse_identifier: SecureRandom.uuid) - client = github_client + def comment_on_pr(project_slug:, pr_number:, body:, reuse_identifier: SecureRandom.uuid) comments = client.issue_comments(project_slug, pr_number) reuse_marker = "" @@ -187,6 +176,58 @@ def self.comment_on_pr(project_slug:, pr_number:, body:, reuse_identifier: Secur reuse_identifier end + + # Update a milestone for a repository + # + # @param [String] repository The repository name (including the organization) + # @param [String] number The number of the milestone we want to fetch + # @param options [Hash] A customizable set of options. + # @option options [String] :title A unique title. + # @option options [String] :state + # @option options [String] :description A meaningful description + # @option options [Time] :due_on Set if the milestone has a due date + # @return [Milestone] A single milestone object + # @see http://developer.github.com/v3/issues/milestones/#update-a-milestone + # + def update_milestone(repository:, number:, options:) + client.update_milestone(repository, number, options) + end + + # Remove the protection of a single branch from a repository + # + # @param [String] repository The repository name (including the organization) + # @param [String] branch The branch name + # @param [Hash] options A customizable set of options. + # @see https://docs.github.com/en/rest/branches/branch-protection#update-branch-protection + # + def remove_branch_protection(repository:, branch:, options:) + client.unprotect_branch(repository, branch, options) + end + + # Protects a single branch from a repository + # + # @param [String] repository The repository name (including the organization) + # @param [String] branch The branch name + # @param options [Hash] A customizable set of options. + # @see https://docs.github.com/en/rest/branches/branch-protection#update-branch-protection + # + def set_branch_protection(repository:, branch:, options:) + client.protect_branch(repository, branch, options) + end + + # Creates a GithubToken Fastlane ConfigItem + # + # @return [FastlaneCore::ConfigItem] The Fastlane ConfigItem for GitHub OAuth access token + # + def self.github_token_config_item + FastlaneCore::ConfigItem.new( + key: :github_token, + env_name: 'GITHUB_TOKEN', + description: 'The GitHub OAuth access token', + optional: false, + type: String + ) + end end end end diff --git a/spec/github_helper_spec.rb b/spec/github_helper_spec.rb index 18875fcbe..d6051da3c 100644 --- a/spec/github_helper_spec.rb +++ b/spec/github_helper_spec.rb @@ -10,17 +10,20 @@ let(:client) do instance_double( Octokit::Client, - contents: double(download_url: content_url) # rubocop:disable RSpec/VerifiedDoubles + contents: double(download_url: content_url), # rubocop:disable RSpec/VerifiedDoubles + user: instance_double('User', name: 'test'), + 'auto_paginate=': nil ) end before do - allow(described_class).to receive(:github_client).and_return(client) + allow(Octokit::Client).to receive(:new).and_return(client) end it 'fails if it does not find the right release on GitHub' do stub = stub_request(:get, content_url).to_return(status: [404, 'Not Found']) - expect(described_class.download_file_from_tag(repository: test_repo, tag: test_tag, file_path: test_file, download_folder: './')).to be_nil + downloaded_file = download_file_from_tag(download_folder: './') + expect(downloaded_file).to be_nil expect(stub).to have_been_made.once end @@ -28,67 +31,16 @@ stub = stub_request(:get, content_url).to_return(status: 200, body: 'my-test-content') Dir.mktmpdir('a8c-download-repo-file-') do |tmpdir| dst_file = File.join(tmpdir, 'test-file.xml') - expect(described_class.download_file_from_tag(repository: test_repo, tag: test_tag, file_path: test_file, download_folder: tmpdir)).to eq(dst_file) + downloaded_file = download_file_from_tag(download_folder: tmpdir) + expect(downloaded_file).to eq(dst_file) expect(stub).to have_been_made.once expect(File.read(dst_file)).to eq('my-test-content') end end - end - - describe 'github_token' do - after do - ENV['GHHELPER_ACCESS'] = nil - ENV['GITHUB_TOKEN'] = nil - end - - it 'can use `GHHELPER_ACCESS`' do - ENV['GHHELPER_ACCESS'] = 'GHHELPER_ACCESS' - expect(described_class.github_token!).to eq('GHHELPER_ACCESS') - end - - it 'can use `GITHUB_TOKEN`' do - ENV['GITHUB_TOKEN'] = 'GITHUB_TOKEN' - expect(described_class.github_token!).to eq('GITHUB_TOKEN') - end - - it 'prioritizes GHHELPER_ACCESS` over `GITHUB_TOKEN` if both are present' do - ENV['GITHUB_TOKEN'] = 'GITHUB_TOKEN' - ENV['GHHELPER_ACCESS'] = 'GHHELPER_ACCESS' - expect(described_class.github_token!).to eq('GHHELPER_ACCESS') - end - - it 'prints an error if no environment variable is present' do - expect { described_class.github_token! }.to raise_error(FastlaneCore::Interface::FastlaneError) - end - end - - describe 'github_client' do - let(:client) do - instance_double( - Octokit::Client, - user: instance_double('User', name: 'test'), - 'auto_paginate=': nil - ) - end - before do - allow(described_class).to receive(:github_token!).and_return('') - allow(Octokit::Client).to receive(:new).and_return(client) - end - - after do - # Clean up the client memoization between runs to ensure it's re-initialized in each test - described_class.remove_class_variable(:@@client) if described_class.class_variable_defined?(:@@client) - end - - it 'is not nil' do - expect(described_class.github_client).not_to be_nil - end - - it 'memoizes the client' do - expect(Octokit::Client).to receive(:new).once - described_class.github_client - described_class.github_client + def download_file_from_tag(download_folder:) + helper = described_class.new(github_token: 'Fake-GitHubToken-123') + helper.download_file_from_tag(repository: test_repo, tag: test_tag, file_path: test_file, download_folder: download_folder) end end @@ -98,22 +50,30 @@ let(:client) do instance_double( Octokit::Client, - list_milestones: ['9.8 ❄️', '9.9'].map { |title| mock_milestone(title) }.append(last_stone) + list_milestones: ['9.8 ❄️', '9.9'].map { |title| mock_milestone(title) }.append(last_stone), + user: instance_double('User', name: 'test'), + 'auto_paginate=': nil ) end before do - allow(described_class).to receive(:github_client).and_return(client) + allow(Octokit::Client).to receive(:new).and_return(client) end it 'returns correct milestone' do expect(client).to receive(:list_milestones) - expect(described_class.get_last_milestone(repository: test_repo)).to eq(last_stone) + last_milestone = get_last_milestone(repository: test_repo) + expect(last_milestone).to eq(last_stone) end def mock_milestone(title) { title: title } end + + def get_last_milestone(repository:) + helper = described_class.new(github_token: 'Fake-GitHubToken-123') + helper.get_last_milestone(repository: repository) + end end describe 'comment_on_pr' do @@ -123,12 +83,13 @@ def mock_milestone(title) issue_comments: [], add_comment: nil, update_comment: nil, - user: instance_double('User', id: 1234) + user: instance_double('User', id: 1234, name: 'test'), + 'auto_paginate=': nil ) end before do - allow(described_class).to receive(:github_client).and_return(client) + allow(Octokit::Client).to receive(:new).and_return(client) end it 'will create a new comment if an existing one is not found' do @@ -159,7 +120,8 @@ def mock_milestone(title) end def comment_on_pr - described_class.comment_on_pr( + helper = described_class.new(github_token: 'Fake-GitHubToken-123') + helper.comment_on_pr( project_slug: 'test/test', pr_number: 1234, body: 'Test', @@ -171,4 +133,184 @@ def mock_comment(body: ' Test', user_id: 1234) instance_double('Comment', id: 1234, body: body, user: instance_double('User', id: user_id)) end end + + describe '#initialize' do + let(:client) do + instance_double( + Octokit::Client, + user: instance_double('User', name: 'test'), + 'auto_paginate=': nil + ) + end + + it 'properly passes the token all the way down to the Octokit::Client' do + allow(Octokit::Client).to receive(:new).and_return(client) + expect(Octokit::Client).to receive(:new).with(access_token: 'Fake-GitHubToken-123') + described_class.new(github_token: 'Fake-GitHubToken-123') + end + end + + describe 'get_milestone' do + let(:test_repo) { 'repo-test/project-test' } + let(:test_milestones) { [{ title: '9.8' }, { title: '10.1' }, { title: '10.1.3 ❄️' }] } + let(:client) do + instance_double( + Octokit::Client, + list_milestones: [], + user: instance_double('User', name: 'test'), + 'auto_paginate=': nil + ) + end + + before do + allow(Octokit::Client).to receive(:new).and_return(client) + end + + it 'properly passes the repository all the way down to the Octokit::Client' do + expect(client).to receive(:list_milestones).with(test_repo) + get_milestone(milestone_name: 'test') + end + + it 'returns nil when no milestone is returned from the api' do + milestone = get_milestone(milestone_name: '10') + expect(milestone).to be_nil + end + + it 'returns nil when no milestone title starts with the searched term' do + allow(client).to receive(:list_milestones).and_return(test_milestones) + milestone = get_milestone(milestone_name: '8.5') + expect(milestone).to be_nil + end + + it 'returns a milestone when the milestone title starts with search term' do + allow(client).to receive(:list_milestones).and_return(test_milestones) + milestone = get_milestone(milestone_name: '9') + expect(milestone).to eq({ title: '9.8' }) + end + + it 'returns the milestone with the latest due date matching the search term when there are more than one' do + allow(client).to receive(:list_milestones).and_return(test_milestones) + milestone = get_milestone(milestone_name: '10.1') + expect(milestone).to eq({ title: '10.1.3 ❄️' }) + end + + def get_milestone(milestone_name:) + helper = described_class.new(github_token: 'Fake-GitHubToken-123') + helper.get_milestone(test_repo, milestone_name) + end + end + + describe 'create_milestone' do + let(:test_repo) { 'repo-test/project-test' } + let(:test_milestone_number) { '10.0' } + let(:test_milestone_duedate) { '2022-10-22T23:39:01Z' } + let(:client) do + instance_double( + Octokit::Client, + create_milestone: nil, + user: instance_double('User', name: 'test'), + 'auto_paginate=': nil + ) + end + + before do + allow(Octokit::Client).to receive(:new).and_return(client) + end + + it 'has the correct dates to code freeze without submission' do + comment = "Code freeze: October 22, 2022\nApp Store submission: November 15, 2022\nRelease: October 25, 2022\n" + options = { due_on: '2022-10-22T12:00:00Z', description: comment } + + expect(client).to receive(:create_milestone).with(test_repo, test_milestone_number, options) + create_milestone(need_submission: false, milestone_duration: 24, days_code_freeze: 3) + end + + it 'has the correct dates to code freeze with submission' do + comment = "Code freeze: October 22, 2022\nApp Store submission: October 22, 2022\nRelease: October 25, 2022\n" + options = { due_on: '2022-10-22T12:00:00Z', description: comment } + + expect(client).to receive(:create_milestone).with(test_repo, test_milestone_number, options) + create_milestone(need_submission: true, milestone_duration: 19, days_code_freeze: 3) + end + + def create_milestone(need_submission:, milestone_duration:, days_code_freeze:) + helper = described_class.new(github_token: 'Fake-GitHubToken-123') + helper.create_milestone( + test_repo, + test_milestone_number, + test_milestone_duedate.to_time.utc, + milestone_duration, + days_code_freeze, + need_submission + ) + end + end + + describe 'create_release' do + let(:test_repo) { 'repo-test/project-test' } + let(:test_tag) { '1.0' } + let(:test_target) { 'dummysha123456' } + let(:test_description) { 'Hey Im a Test Description' } + let(:client) do + instance_double( + Octokit::Client, + create_release: nil, + user: instance_double('User', name: 'test'), + 'auto_paginate=': nil + ) + end + + before do + allow(Octokit::Client).to receive(:new).and_return(client) + end + + it 'has the correct options' do + options = { body: test_description, draft: true, name: test_tag, prerelease: false, target_commitish: test_target } + expect(client).to receive(:create_release).with(test_repo, test_tag, options) + create_release + end + + it 'uploads the assets to the correct location' do + test_assets = 'test-file.xml' + test_url = '/test/url' + + allow(client).to receive(:create_release).and_return({ url: test_url }) + expect(client).to receive(:upload_asset).with(test_url, test_assets, { content_type: 'application/octet-stream' }) + create_release(assets: [test_assets]) + end + + def create_release(assets: []) + helper = described_class.new(github_token: 'Fake-GitHubToken-123') + helper.create_release( + repository: test_repo, + version: test_tag, + target: test_target, + description: test_description, + assets: assets, + prerelease: false + ) + end + end + + describe 'github_token_config_item' do + it 'has the correct key' do + expect(described_class.github_token_config_item.key).to eq(:github_token) + end + + it 'has the correct env_name' do + expect(described_class.github_token_config_item.env_name).to eq('GITHUB_TOKEN') + end + + it 'has the correct description' do + expect(described_class.github_token_config_item.description).to eq('The GitHub OAuth access token') + end + + it 'is not optional' do + expect(described_class.github_token_config_item.optional).to be(false) + end + + it 'has String as data_type' do + expect(described_class.github_token_config_item.data_type).to eq(String) + end + end end