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 9f3e1ea32..0319057ba 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 @@ -11,6 +11,10 @@ def self.run(params) github_helper = Fastlane::Helper::GithubHelper.new(github_token: params[:github_token]) last_stone = github_helper.get_last_milestone(repository) + + UI.user_error!('No milestone found on the repository.') if last_stone.nil? + UI.user_error!("Milestone #{last_stone[:title]} has no due date.") if last_stone[:due_on].nil? + 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] @@ -49,19 +53,19 @@ def self.available_options env_name: 'GHHELPER_NEED_APPSTORE_SUBMISSION', description: 'True if the app needs to be submitted', optional: true, - is_string: false, + type: Boolean, default_value: false), FastlaneCore::ConfigItem.new(key: :milestone_duration, env_name: 'GHHELPER_MILESTONE_DURATION', description: 'Milestone duration in number of days', optional: true, - is_string: false, + type: Integer, default_value: 14), FastlaneCore::ConfigItem.new(key: :number_of_days_from_code_freeze_to_release, env_name: 'GHHELPER_NUMBER_OF_DAYS_FROM_CODE_FREEZE_TO_RELEASE', description: 'Number of days from code freeze to release', optional: true, - is_string: false, + type: Integer, default_value: 14), Fastlane::Helper::GithubHelper.github_token_config_item, ] diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/setfrozentag_action.rb b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/setfrozentag_action.rb index 55d6f8a89..06a2d7595 100644 --- a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/setfrozentag_action.rb +++ b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/setfrozentag_action.rb @@ -15,7 +15,6 @@ def self.run(params) UI.user_error!("Milestone #{milestone_title} not found.") if milestone.nil? mile_title = milestone[:title] - puts freeze if freeze # Check if the state needs changes if is_frozen(milestone) @@ -71,7 +70,7 @@ def self.available_options description: 'The GitHub milestone', optional: false, default_value: true, - is_string: false), + type: Boolean), Fastlane::Helper::GithubHelper.github_token_config_item, ] end diff --git a/spec/close_milestone_action_spec.rb b/spec/close_milestone_action_spec.rb new file mode 100644 index 000000000..f6d7b0780 --- /dev/null +++ b/spec/close_milestone_action_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' +require 'shared_examples_for_actions_with_github_token' + +describe Fastlane::Actions::CloseMilestoneAction do + let(:test_repository) { 'test-repository' } + let(:test_token) { 'Test-GithubToken-1234' } + let(:test_milestone) do + { title: '10.1', number: '1234' } + end + let(:default_params) do + { repository: test_repository, + milestone: test_milestone[:title], + github_token: 'Test-GithubToken-1234' } + end + let(:client) do + instance_double( + Octokit::Client, + list_milestones: [test_milestone], + update_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 'closes the expected milestone on the expected repository' do + expect(client).to receive(:update_milestone).with(test_repository, test_milestone[:number], state: 'closed') + run_described_fastlane_action(default_params) + end + + it 'raises an error when the milestone is not found or does not exist' do + allow(client).to receive(:list_milestones).and_return([]) + expect { run_described_fastlane_action(default_params) }.to raise_error(FastlaneCore::Interface::FastlaneError, 'Milestone 10.1 not found.') + end + + describe 'initialize' do + include_examples 'github_token_initialization' + end + + describe 'calling the action validates input' do + it 'raises an error if no `GHHELPER_REPOSITORY` environment variable nor parameter `:repository` is present' do + expect { run_action_without(:repository) }.to raise_error(FastlaneCore::Interface::FastlaneError, "No value found for 'repository'") + end + + it 'raises an error if no `GHHELPER_MILESTONE` environment variable nor parameter `:milestone` is present' do + expect { run_action_without(:milestone) }.to raise_error(FastlaneCore::Interface::FastlaneError, "No value found for 'milestone'") + end + + it 'raises an error if `milestone:` parameter is passed as Integer' do + expect { run_action_with(milestone: 10) }.to raise_error "'milestone' value must be a String! Found Integer instead." + end + end + + def run_action_with(**keys_and_values) + params = default_params.merge(keys_and_values) + run_described_fastlane_action(params) + end + + def run_action_without(key) + run_described_fastlane_action(default_params.except(key)) + end +end diff --git a/spec/create_new_milestone_action_spec.rb b/spec/create_new_milestone_action_spec.rb new file mode 100644 index 000000000..1fac210e1 --- /dev/null +++ b/spec/create_new_milestone_action_spec.rb @@ -0,0 +1,138 @@ +require 'spec_helper' +require 'shared_examples_for_actions_with_github_token' + +describe Fastlane::Actions::CreateNewMilestoneAction do + let(:test_repository) { 'test-repository' } + let(:test_milestone) do + { title: '10.1', number: '1234', due_on: '2022-10-31T07:00:00Z' } + end + let(:milestone_list) do + [ + { title: '10.2', number: '1234', due_on: '2022-10-31T12:00:00Z' }, + { title: '10.3', number: '4567', due_on: '2022-11-02T15:00:00Z' }, + { title: '10.4', number: '7890', due_on: '2022-11-04T23:59:00Z' }, + ] + end + let(:default_params) do + { repository: test_repository, + need_appstore_submission: false, + github_token: 'Test-GithubToken-1234' } + end + let(:client) do + instance_double( + Octokit::Client, + list_milestones: [test_milestone], + 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 + + describe 'date computation is correct' do + it 'computes the correct due date and milestone description' do + comment = "Code freeze: November 14, 2022\nApp Store submission: November 28, 2022\nRelease: November 28, 2022\n" + expect(client).to receive(:create_milestone).with(test_repository, '10.2', due_on: '2022-11-14T12:00:00Z', description: comment) + run_described_fastlane_action(default_params) + end + + it 'removes 3 days from the AppStore submission date when `:need_appstore_submission` is true' do + comment = "Code freeze: November 14, 2022\nApp Store submission: November 25, 2022\nRelease: November 28, 2022\n" + expect(client).to receive(:create_milestone).with(test_repository, '10.2', due_on: '2022-11-14T12:00:00Z', description: comment) + run_action_with(need_appstore_submission: true) + end + + it 'uses the most recent milestone date to calculate the due date and version of new milestone' do + comment = "Code freeze: November 18, 2022\nApp Store submission: December 02, 2022\nRelease: December 02, 2022\n" + allow(client).to receive(:list_milestones).and_return(milestone_list) + expect(client).to receive(:create_milestone).with(test_repository, '10.5', due_on: '2022-11-18T12:00:00Z', description: comment) + run_described_fastlane_action(default_params) + end + + context 'when last milestone cannot be used' do + it 'raises an error when the due date of milestone does not exists' do + allow(client).to receive(:list_milestones).and_return([{ title: '10.1', number: '1234' }]) + expect { run_described_fastlane_action(default_params) }.to raise_error(FastlaneCore::Interface::FastlaneError, 'Milestone 10.1 has no due date.') + end + + it 'raises an error when the milestone is not found or does not exist on the repository' do + allow(client).to receive(:list_milestones).and_return([]) + expect { run_described_fastlane_action(default_params) }.to raise_error(FastlaneCore::Interface::FastlaneError, 'No milestone found on the repository.') + end + end + end + + describe 'initialize' do + include_examples 'github_token_initialization' + + context 'when using default parameters' do + let(:github_helper) do + instance_double( + Fastlane::Helper::GithubHelper, + get_last_milestone: test_milestone, + create_milestone: nil + ) + end + + before do + allow(Fastlane::Helper::GithubHelper).to receive(:new).and_return(github_helper) + end + + it 'uses default value when neither `GHHELPER_NUMBER_OF_DAYS_FROM_CODE_FREEZE_TO_RELEASE` environment variable nor parameter `:number_of_days_from_code_freeze_to_release` is present' do + default_code_freeze_days = 14 + expect(github_helper).to receive(:create_milestone).with( + anything, + anything, + anything, + anything, + default_code_freeze_days, + anything + ) + run_described_fastlane_action(default_params) + end + + it 'uses default value when neither `GHHELPER_MILESTONE_DURATION` environment variable nor parameter `:milestone_duration` is present' do + default_milestone_duration = 14 + expect(github_helper).to receive(:create_milestone).with( + anything, + anything, + anything, + default_milestone_duration, + anything, + anything + ) + run_described_fastlane_action(default_params) + end + end + end + + describe 'calling the action validates input' do + it 'raises an error if no `GHHELPER_REPOSITORY` environment variable nor parameter `:repository` is present' do + expect { run_action_without(:repository) }.to raise_error(FastlaneCore::Interface::FastlaneError, "No value found for 'repository'") + end + + it 'raises an error if `need_appstore_submission:` parameter is passed as String' do + expect { run_action_with(need_appstore_submission: 'foo') }.to raise_error "'need_appstore_submission' value must be either `true` or `false`! Found String instead." + end + + it 'raises an error if `milestone_duration:` parameter is passed as String' do + expect { run_action_with(milestone_duration: 'foo') }.to raise_error "'milestone_duration' value must be a Integer! Found String instead." + end + + it 'raises an error if `number_of_days_from_code_freeze_to_release:` parameter is passed as String' do + expect { run_action_with(number_of_days_from_code_freeze_to_release: 'foo') }.to raise_error "'number_of_days_from_code_freeze_to_release' value must be a Integer! Found String instead." + end + end + + def run_action_with(**keys_and_values) + params = default_params.merge(keys_and_values) + run_described_fastlane_action(params) + end + + def run_action_without(key) + run_described_fastlane_action(default_params.except(key)) + end +end diff --git a/spec/setfrozentag_action_spec.rb b/spec/setfrozentag_action_spec.rb new file mode 100644 index 000000000..30fb70c8c --- /dev/null +++ b/spec/setfrozentag_action_spec.rb @@ -0,0 +1,89 @@ +require 'spec_helper' +require 'shared_examples_for_actions_with_github_token' + +describe Fastlane::Actions::SetfrozentagAction do + let(:test_repository) { 'test-repository' } + let(:test_token) { 'Test-GithubToken-1234' } + let(:test_milestone) do + { title: '10.1', number: '1234' } + end + let(:default_params) do + { + repository: test_repository, + milestone: test_milestone[:title], + freeze: true, + github_token: 'Test-GithubToken-1234' + } + end + let(:client) do + instance_double( + Octokit::Client, + list_milestones: [test_milestone], + update_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 'raises an error when the milestone is not found or does not exist' do + allow(client).to receive(:list_milestones).and_return([]) + expect { run_described_fastlane_action(default_params) }.to raise_error(FastlaneCore::Interface::FastlaneError, 'Milestone 10.1 not found.') + end + + it 'freezes the milestone adding ❄️ to the title' do + expect(client).to receive(:update_milestone).with(test_repository, test_milestone[:number], title: '10.1 ❄️') + run_action_with(freeze: true) + end + + it 'remove any existing ❄️ emoji from a frozen milestone' do + allow(client).to receive(:list_milestones).and_return([{ title: '10.2 ❄️', number: '1234' }]) + expect(client).to receive(:update_milestone).with(test_repository, test_milestone[:number], title: '10.2') + run_action_with(freeze: false, milestone: '10.2') + end + + it 'does not change a milestone that is already frozen' do + allow(client).to receive(:list_milestones).and_return([{ title: '10.2 ❄️', number: '1234' }]) + expect(client).not_to receive(:update_milestone) + run_action_with(milestone: '10.2 ❄️') + end + + it 'does not change an unfrozen milestone if :freeze parameter is false' do + expect(client).to receive(:update_milestone).with(test_repository, test_milestone[:number], title: '10.1') + run_action_with(freeze: false) + end + + describe 'initialize' do + include_examples 'github_token_initialization' + end + + describe 'Calling the Action validates input' do + it 'raises an error if no `GHHELPER_REPOSITORY` environment variable nor parameter `:repository` is present' do + expect { run_action_without(:repository) }.to raise_error(FastlaneCore::Interface::FastlaneError, "No value found for 'repository'") + end + + it 'raises an error if no `GHHELPER_MILESTORE` environment variable nor parameter `:milestone` is present' do + expect { run_action_without(:milestone) }.to raise_error(FastlaneCore::Interface::FastlaneError, "No value found for 'milestone'") + end + + it 'raises an error if `:freeze` parameter is passed as String' do + expect { run_action_with(freeze: 'foo') }.to raise_error "'freeze' value must be either `true` or `false`! Found String instead." + end + + it 'raises an error if `:milestone` parameter is passed as Integer' do + expect { run_action_with(milestone: 10) }.to raise_error "'milestone' value must be a String! Found Integer instead." + end + end + + def run_action_with(**keys_and_values) + params = default_params.merge(keys_and_values) + run_described_fastlane_action(params) + end + + def run_action_without(key) + run_described_fastlane_action(default_params.except(key)) + end +end diff --git a/spec/shared_examples_for_actions_with_github_token.rb b/spec/shared_examples_for_actions_with_github_token.rb new file mode 100644 index 000000000..68a7c17c8 --- /dev/null +++ b/spec/shared_examples_for_actions_with_github_token.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +RSpec.shared_examples 'github_token_initialization' do + let(:test_token) { 'Test-GithubToken-1234' } + + describe 'GitHub Token is properly passed to the client' do + it 'properly passes the environment variable `GITHUB_TOKEN` to Octokit::Client' do + ENV['GITHUB_TOKEN'] = test_token + expect(Octokit::Client).to receive(:new).with(access_token: test_token) + run_action_without(:github_token) + end + + it 'properly passes the parameter `:github_token` to Octokit::Client' do + expect(Octokit::Client).to receive(:new).with(access_token: test_token) + run_described_fastlane_action(default_params) + end + + it 'prioritizes `:github_token` parameter over `GITHUB_TOKEN` environment variable if both are present' do + ENV['GITHUB_TOKEN'] = 'Test-EnvGithubToken-1234' + expect(Octokit::Client).to receive(:new).with(access_token: test_token) + run_described_fastlane_action(default_params) + end + + it 'raises an error if no `GITHUB_TOKEN` environment variable nor parameter `:github_token` is present' do + ENV['GITHUB_TOKEN'] = nil + expect { run_action_without(:github_token) }.to raise_error(FastlaneCore::Interface::FastlaneError, "No value found for 'github_token'") + end + end +end