-
Notifications
You must be signed in to change notification settings - Fork 9
Add Unit tests for milestone actions #425
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Changes from all commits
c37f29e
6dfd767
22b9d44
96acb93
f695b04
3472f9e
cdd96c2
3a5a240
5e656dd
b6de4cd
9eb95c2
87ae1d4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Self-Review: As the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is great! It's even a small step towards addressing #278 that we opened a while ago about this 🙂 |
||
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, | ||
] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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.') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gosh we really need to refactor the API of this action to use nicer and more comprehensible / consistent One day… in another PR… in the far future… maybe… |
||
default_code_freeze_days = 14 | ||
expect(github_helper).to receive(:create_milestone).with( | ||
anything, | ||
anything, | ||
anything, | ||
anything, | ||
default_code_freeze_days, | ||
anything | ||
) | ||
Comment on lines
+86
to
+93
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if it wouldn't be the occasion to make this But that means a small refactoring of all the call sites though (which might be outside of this PR's scope… but depending on how big a change it would be—not sure there are that many call sites, the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From what I see, the only places that will be affected are the However, TBH I do not feel that I should do it on this PR, as it is a bit out of scope (although is a small change). So, I'll open another PR to deal with this, and update these tests here once this new PR is merged 😄 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update: This is addressed on PR #426 😄 |
||
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
Comment on lines
+37
to
+57
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
|
||
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This shared example relies on the spec including it ( E.g. if we rename
In Swift, we'd do something like constraining this to tests that conform to a certain There might be a way to update At this point, the only thing I can think of is to document the requirement as a comment at the start of the file. What do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shared examples can also contain metadata, that you can then pass as additional params when you call As for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Indeed, when I try to use the shared examples passing the In that case, I don't know what is the best alternative here, if is to keep the shared examples relying on the existence of a variable named describe 'initialize' do
include_examples 'github_token_initialization' do
let(:params) { default_params }
end
end
Yeah, I have that "dilemma" when I was implementing this, I was first defaulting to those approaches of having the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, there is this third option to do it, which is cleaner than the other one, but I find it a little bit confusing the way that it used the shared_examples 'github_token_initialization' do |params:|
let(:params) { send(params) }
....
end
# spec/test.rb
describe 'initialize' do
include_examples 'github_token_initialization', params: :default_params
end But again, although I feel that this third option is cleaner, I have no strong option about which one is the right one that we should use here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tbh I'm not a fan of using What about using metadata, but workaround the limitation that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I find the use of However, even if I change def default_params
{
repository: 'test-repository',
milestone: '10.1',
github_token: 'Test-GithubToken-1234'
}
end
#When I call
describe 'initialize' do
include_examples 'github_token_initialization', default_params
end
# I receive the same error as the let
Failure/Error: include_examples 'github_token_initialization', default_params
`default_params` is not available on an example group (e.g. a `describe` or `context` block). It is only available from within individual examples (e.g. `it` blocks) or from constructs that run in the scope of an example (e.g. `before`, `let`, etc). The only way that seems to do the job (although is not a DRY solution), without using the describe 'initialize' do
include_examples 'github_token_initialization', { repository: 'test-repository', milestone: '10.1', github_token: 'Test-GithubToken-1234' }
end Let me know what you think about this suggestion 😄 |
||
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 |
Uh oh!
There was an error while loading. Please reload this page.