diff --git a/lib/smart_todo/events/issue_close.rb b/lib/smart_todo/events/issue_close.rb index d057e6b..3ddda52 100644 --- a/lib/smart_todo/events/issue_close.rb +++ b/lib/smart_todo/events/issue_close.rb @@ -11,6 +11,11 @@ module Events # If the Pull Request or Issue is on a private repository, exporting a token # with the `repos` scope in the +SMART_TODO_GITHUB_TOKEN+ environment variable # is required. + # + # You can also set a per-org or per-repo token by exporting more specific environment variables: + # +SMART_TODO_GITHUB_TOKEN__+ and +SMART_TODO_GITHUB_TOKEN____+ + # The ++ and ++ parts should be uppercased and use underscores. + # For example, +Shopify/my-repo+ would become +SMART_TODO_GITHUB_TOKEN__SHOPIFY__MY_REPO=...+. class IssueClose TOKEN_ENV = "SMART_TODO_GITHUB_TOKEN" @@ -78,8 +83,30 @@ def pull_request_closed?(pull_request) # @return [Hash] def default_headers { "Accept" => "application/vnd.github.v3+json" }.tap do |headers| - headers["Authorization"] = "token #{ENV[TOKEN_ENV]}" if ENV[TOKEN_ENV] + token = authorization_token + headers["Authorization"] = "token #{token}" if token + end + end + + # @return [String, nil] + def authorization_token + # Will look in order for: + # SMART_TODO_GITHUB_TOKEN__ORG__REPO + # SMART_TODO_GITHUB_TOKEN__ORG + # SMART_TODO_GITHUB_TOKEN + parts = [ + TOKEN_ENV, + @organization.upcase.gsub(/[^A-Z0-9]/, "_"), + @repo.upcase.gsub(/[^A-Z0-9]/, "_"), + ] + + (parts.size - 1).downto(0).each do |i| + key = parts[0..i].join("__") + token = ENV[key] + return token unless token.nil? || token.empty? end + + nil end end end diff --git a/test/smart_todo/events/issue_close_test.rb b/test/smart_todo/events/issue_close_test.rb index 2612682..f94f98b 100644 --- a/test/smart_todo/events/issue_close_test.rb +++ b/test/smart_todo/events/issue_close_test.rb @@ -50,18 +50,49 @@ def test_when_token_env_is_not_present end def test_when_token_env_is_present - ENV[IssueClose::TOKEN_ENV] = "abc" + with_env(IssueClose::TOKEN_ENV => "abc") do + stub_request(:get, /api.github.com/) + .to_return(body: JSON.dump(state: "open")) - stub_request(:get, /api.github.com/) - .to_return(body: JSON.dump(state: "open")) + assert_equal(false, IssueClose.new("rails", "rails", "123", type: "pulls").met?) - assert_equal(false, IssueClose.new("rails", "rails", "123", type: "pulls").met?) + assert_requested(:get, /api.github.com/) do |request| + assert_equal("token abc", request.headers["Authorization"]) + end + end + end - assert_requested(:get, /api.github.com/) do |request| - assert(request.headers.key?("Authorization")) + def test_when_org_token_env_is_present + with_env( + IssueClose::TOKEN_ENV + "__RAILS" => "abcd", + IssueClose::TOKEN_ENV => "abc", + ) do + stub_request(:get, /api.github.com/) + .to_return(body: JSON.dump(state: "open")) + + assert_equal(false, IssueClose.new("rails", "rails", "123", type: "pulls").met?) + + assert_requested(:get, /api.github.com/) do |request| + assert_equal("token abcd", request.headers["Authorization"]) + end + end + end + + def test_when_repo_org_token_env_is_present + with_env( + IssueClose::TOKEN_ENV + "__SHOPIFY__SMART_TODO" => "abcde", + IssueClose::TOKEN_ENV + "__SHOPIFY" => "abcd", + IssueClose::TOKEN_ENV => "abc", + ) do + stub_request(:get, /api.github.com/) + .to_return(body: JSON.dump(state: "open")) + + assert_equal(false, IssueClose.new("Shopify", "smart-todo", "123", type: "pulls").met?) + + assert_requested(:get, /api.github.com/) do |request| + assert_equal("token abcde", request.headers["Authorization"]) + end end - ensure - ENV.delete(IssueClose::TOKEN_ENV) end def test_calls_the_right_endpoint_when_type_is_pull_request @@ -81,6 +112,16 @@ def test_calls_the_right_endpoint_when_type_is_issue assert_equal(false, IssueClose.new("rails", "rails", "123", type: "issues").met?) assert_requested(:get, expected_endpoint) end + + private + + def with_env(env = {}) + original_env = ENV.to_h + ENV.merge!(env) + yield + ensure + ENV.replace(original_env) + end end end end