diff --git a/.gitignore b/.gitignore index 6a5f884..bf9ebe9 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ tmtags coverage rdoc pkg +.byebug_history #bundler Gemfile.lock diff --git a/README.md b/README.md index 804f4d5..40d8571 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ Test the token with the [`git findpr`](https://github.com/socialcast/socialcast- ### Options * ```--quiet```: suppress posting message in Socialcast +* config/scgitx.yml option `share_via_pr_comments`: Set to `true` to post reviewrequest and integration messages +as pull request comments instead of Socialcast posts. ## git start @@ -50,9 +52,17 @@ Find pull requests on github including a given commit List branches merged into remote origin/`branch` and not also merged into origin/`base_branch` +## git createpr + +create a pull request on github for the current branch, without assigning it for review. + ## git reviewrequest -create a pull request on github for peer review of the current branch. +create and assign a pull request on github for peer review of the current branch. See `assignpr` for additional options. + +## git assignpr + +assign the pull request on github for the current branch for peer review. ### Optional: Specify a Review Buddy mapping that will reference the local Github username and @mention a pre-assigned review buddy in the Socialcast Review Request message. Specify the mapping by creating a .scgitx YML file relative to the Repo Root: config/scgitx.yml with the following format: diff --git a/bin/git-assignpr b/bin/git-assignpr new file mode 100755 index 0000000..fe1dc88 --- /dev/null +++ b/bin/git-assignpr @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require File.join(File.dirname(__FILE__), '..', 'lib', 'socialcast-git-extensions', 'cli.rb') +Socialcast::Gitx::CLI.start (['assignpr'] + ARGV) diff --git a/bin/git-createpr b/bin/git-createpr new file mode 100755 index 0000000..23ee63c --- /dev/null +++ b/bin/git-createpr @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require File.join(File.dirname(__FILE__), '..', 'lib', 'socialcast-git-extensions', 'cli.rb') +Socialcast::Gitx::CLI.start (['createpr'] + ARGV) diff --git a/lib/socialcast-git-extensions/cli.rb b/lib/socialcast-git-extensions/cli.rb index 3f17512..3425294 100644 --- a/lib/socialcast-git-extensions/cli.rb +++ b/lib/socialcast-git-extensions/cli.rb @@ -28,13 +28,24 @@ def initialize(*args) RestClient.log = Logger.new(STDOUT) if options[:trace] end - desc "reviewrequest", "Create a pull request on github" + desc "createpr", "Create a pull request on github" method_option :description, :type => :string, :aliases => '-d', :desc => 'pull request description' + # @see http://developer.github.com/v3/pulls/ + def createpr + update unless @skip_update + description = options[:description] || editor_input(PULL_REQUEST_DESCRIPTION) + branch = current_branch + repo = current_repo + url = create_pull_request(branch, repo, description)['html_url'] + say "Pull request created: #{url}" + end + + desc "assignpr", "Assign the pull request on github for review" method_option :additional_reviewers, :type => :string, :aliases => '-a', :desc => 'add additional reviewers to mention automatically, and skips the prompt' method_option :skip_additional_reviewers, :type => :string, :aliases => '-s', :desc => 'Skips adding additional reviewers' # @see http://developer.github.com/v3/pulls/ - def reviewrequest(*additional_reviewers) - update + def assignpr(*additional_reviewers) + update unless @skip_update primary_mention = if buddy = socialcast_review_buddy(current_user) "assigned to @#{buddy}" @@ -58,16 +69,34 @@ def reviewrequest(*additional_reviewers) end end - assignee = github_review_buddy(current_user) - - description = options[:description] || editor_input(PULL_REQUEST_DESCRIPTION) branch = current_branch repo = current_repo - url = create_pull_request branch, repo, description, assignee - say "Pull request created: #{url}" + current_pr = current_pr_for_branch(repo, branch) + issue_url = current_pr['issue_url'] + url = current_pr['html_url'] + + assignee = github_review_buddy(current_user) + assign_pull_request(assignee, issue_url) if assignee - review_message = ["#reviewrequest for #{branch} in #{current_repo}", "PR #{url} #{primary_mention}", '', description, '', secondary_mention, "/cc @#{developer_group} #scgitx", '', changelog_summary(branch)].compact.join("\n").gsub(/\n{2,}/, "\n\n") - post review_message, :message_type => 'review_request' + if use_pr_comments? + issue_message = ['#reviewrequest', primary_mention, secondary_mention, "\n/cc @#{developer_group} #scgitx"].compact.join(' ') + comment_on_issue(issue_url, issue_message) + else + review_message = ["#reviewrequest for #{branch} in #{current_repo}", "PR #{url} #{primary_mention}", '', current_pr['body'], '', secondary_mention, "/cc @#{developer_group} #scgitx", '', changelog_summary(branch)].compact.join("\n").gsub(/\n{2,}/, "\n\n") + post review_message, :message_type => 'review_request' + end + end + + desc "reviewrequest", "Create and assign a pull request on github" + method_option :description, :type => :string, :aliases => '-d', :desc => 'pull request description' + method_option :additional_reviewers, :type => :string, :aliases => '-a', :desc => 'add additional reviewers to mention automatically, and skips the prompt' + method_option :skip_additional_reviewers, :type => :string, :aliases => '-s', :desc => 'Skips adding additional reviewers' + # @see http://developer.github.com/v3/pulls/ + def reviewrequest(*additional_reviewers) + update + @skip_update = true + createpr + assignpr(*additional_reviewers) end desc "findpr", "Find pull requests including a given commit" @@ -104,14 +133,21 @@ def backportpr(pull_request_num, maintenance_branch) maintenance_branch_url = "https://github.com/#{repo}/tree/#{maintenance_branch}" description = "Backport ##{pull_request_num} to #{maintenance_branch_url}\n***\n#{pull_request_data['body']}" - pull_request_url = create_pull_request(backport_branch, repo, description, assignee) + pr_hash = create_pull_request(backport_branch, repo, description) + assign_pull_request(assignee, pr_hash['issue_url']) if assignee - review_message = ["#reviewrequest backport ##{pull_request_num} to #{maintenance_branch} in #{current_repo} #scgitx"] - if socialcast_reviewer - review_message << "/cc @#{socialcast_reviewer} for #backport track" + reviewer_mention = "@#{socialcast_reviewer}" if socialcast_reviewer + if use_pr_comments? + issue_message = ['#reviewrequest backport', reviewer_mention, "/cc @#{developer_group} #scgitx"].compact.join(' ') + comment_on_issue(pr_hash['issue_url'], issue_message) + else + review_message = ["#reviewrequest backport ##{pull_request_num} to #{maintenance_branch} in #{current_repo} #scgitx"] + if socialcast_reviewer + review_message << "/cc #{reviewer_mention} for #backport track" + end + review_message << "/cc @#{developer_group}" + post review_message.join("\n\n"), :url => pr_hash['html_url'], :message_type => 'review_request' end - review_message << "/cc @#{developer_group}" - post review_message.join("\n\n"), :url => pull_request_url, :message_type => 'review_request' ensure ENV['BASE_BRANCH'] = original_base_branch end @@ -192,12 +228,26 @@ def integrate(target_branch = prototype_branch) integrate_branch(target_branch, prototype_branch) if target_branch == staging_branch run_cmd "git checkout #{branch}" - message = <<-EOS.strip_heredoc - #worklog integrating #{branch} into #{target_branch} in #{current_repo} #scgitx - /cc @#{developer_group} - EOS + current_pr = begin + current_pr_for_branch(current_repo, current_branch) + rescue => e + say e.message.to_s + nil + end - post message.strip + say("WARNING: Unable to find current pull request. Use `git createpr` to create one.", :red) unless current_pr + + if use_pr_comments? && current_pr + issue_message = "Integrated into #{target_branch}" + comment_on_issue(current_pr['issue_url'], issue_message) unless options[:quiet] + else + message = <<-EOS.strip_heredoc + #worklog integrating #{branch} into #{target_branch} in #{current_repo} #scgitx + /cc @#{developer_group} + EOS + + post message.strip + end end desc 'promote', '(DEPRECATED) promote the current branch into staging' @@ -264,12 +314,14 @@ def release integrate_branch(base_branch, staging_branch) cleanup - message = <<-EOS.strip_heredoc - #worklog releasing #{branch} to #{base_branch} in #{current_repo} #scgitx - /cc @#{developer_group} - EOS + unless use_pr_comments? + message = <<-EOS.strip_heredoc + #worklog releasing #{branch} to #{base_branch} in #{current_repo} #scgitx + /cc @#{developer_group} + EOS - post message.strip + post message.strip + end end private @@ -282,6 +334,10 @@ def enforce_staging_before_release? !!config['enforce_staging_before_release'] end + def use_pr_comments? + config['share_via_pr_comments'] == true + end + # post a message in socialcast # skip sharing message if CLI quiet option is present def post(message, params = {}) diff --git a/lib/socialcast-git-extensions/github.rb b/lib/socialcast-git-extensions/github.rb index 615da9b..9d6c764 100644 --- a/lib/socialcast-git-extensions/github.rb +++ b/lib/socialcast-git-extensions/github.rb @@ -31,9 +31,8 @@ def authorization_token throw e end - # returns the url of the created pull request # @see http://developer.github.com/v3/pulls/ - def create_pull_request(branch, repo, body, assignee) + def create_pull_request(branch, repo, body) payload = {:title => branch, :base => base_branch, :head => branch, :body => body}.to_json say "Creating pull request for " say "#{branch} ", :green @@ -41,11 +40,7 @@ def create_pull_request(branch, repo, body, assignee) say "#{base_branch} ", :green say "in " say repo, :green - data = github_api_request("POST", "repos/#{repo}/pulls", payload) - assign_pull_request(branch, assignee, data) if assignee ## Unfortunately this needs to be done in a seperate request. - - url = data['html_url'] - url + github_api_request("POST", "repos/#{repo}/pulls", payload) end # find the PRs matching the given commit hash @@ -56,13 +51,33 @@ def pull_requests_for_commit(repo, commit_hash) github_api_request "GET", "search/issues?q=#{query}" end - def assign_pull_request(branch, assignee, data) - issue_payload = { :title => branch, :assignee => assignee }.to_json - github_api_request "PATCH", data['issue_url'], issue_payload + # find the PRs for a given branch + # https://developer.github.com/v3/pulls/#list-pull-requests + def pull_requests_for_branch(repo, branch) + head_name = "#{repo.split('/').first}:#{branch}" + github_api_request "GET", "repos/#{repo}/pulls?head=#{head_name}" + end + + # find the current PR for a given branch + def current_pr_for_branch(repo, branch) + prs = pull_requests_for_branch(repo, branch) + raise "Multiple (#{prs.size}) open PRs for #{branch} found in #{repo}, unable to proceed" if prs.size > 1 + prs.first + end + + def assign_pull_request(assignee, issue_url) + issue_payload = { :assignee => assignee }.to_json + github_api_request "PATCH", issue_url, issue_payload rescue => e say "Failed to assign pull request: #{e.message}", :red end + # post a comment on an issue + # https://developer.github.com/v3/issues/comments/#create-a-comment + def comment_on_issue(issue_url, comment_body) + github_api_request 'POST', "#{issue_url}/comments", { :body => comment_body }.to_json + end + # @returns [String] socialcast username to assign the review to # @returns [nil] when no buddy system configured or user not found def socialcast_review_buddy(current_user) diff --git a/lib/socialcast-git-extensions/version.rb b/lib/socialcast-git-extensions/version.rb index 46d9b20..b69da6a 100644 --- a/lib/socialcast-git-extensions/version.rb +++ b/lib/socialcast-git-extensions/version.rb @@ -1,5 +1,5 @@ module Socialcast module Gitx - VERSION = "3.3" + VERSION = "4.0" end end diff --git a/socialcast-git-extensions.gemspec b/socialcast-git-extensions.gemspec index 445b988..5d562d1 100644 --- a/socialcast-git-extensions.gemspec +++ b/socialcast-git-extensions.gemspec @@ -16,13 +16,14 @@ Gem::Specification.new do |s| s.add_runtime_dependency 'rugged', '>= 0.23' s.add_runtime_dependency 'socialcast', '~> 1.3.0' - s.add_runtime_dependency 'activesupport', '~> 4.0' + s.add_runtime_dependency 'activesupport', '>= 4.0' s.add_runtime_dependency 'rest-client', '~> 1.7' s.add_runtime_dependency 'thor', '~> 0.19.1' s.add_runtime_dependency 'rake', '~> 10.3' s.add_development_dependency 'rspec', '~> 3.0' s.add_development_dependency 'pry', '~> 0.9.12.6' s.add_development_dependency 'webmock', '~> 1.21' + s.add_development_dependency 'byebug' s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") diff --git a/spec/cli_spec.rb b/spec/cli_spec.rb index 5df5890..e964f9d 100644 --- a/spec/cli_spec.rb +++ b/spec/cli_spec.rb @@ -3,10 +3,18 @@ describe Socialcast::Gitx::CLI do let(:stubbed_executed_commands) { [] } - def stub_message(message_body, params = {}) + def expect_message(message_body, params = {}) expect(Socialcast::CommandLine::Message).to receive(:create).with(params.merge(:body => message_body)).and_return(double(:permalink_url => 'https://community.socialcast.com/messages/1234')) end + let(:git_update_commands) do + [ + "git pull origin FOO", + "git pull origin master", + "git push origin HEAD" + ] + end + before do Socialcast::Gitx::CLI.instance_eval do # to supress warning from stubbing ldap_config @no_tasks = @no_commands = true @@ -25,12 +33,10 @@ def stub_message(message_body, params = {}) end describe '#update' do - before do + it do expect_any_instance_of(Socialcast::Gitx::CLI).not_to receive(:post) Socialcast::Gitx::CLI.start ['update'] - end - it 'should not post message to socialcast' do end # see expectations - it 'should run expected commands' do + expect(stubbed_executed_commands).to eq([ 'git pull origin FOO', 'git pull origin master', @@ -40,107 +46,163 @@ def stub_message(message_body, params = {}) end describe '#integrate' do - context 'when target branch is ommitted' do - before do - stub_message "#worklog integrating FOO into prototype in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" - - Socialcast::Gitx::CLI.start ['integrate'] + context 'with no existing pull request' do + let!(:github_api_pulls_list) do + stub_request(:get, "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls?head=socialcast:FOO") + .to_return(:status => 200, :body => "[]", :headers => {}) end - it 'should post message to socialcast' do end # see expectations - it 'should default to prototype' do - expect(stubbed_executed_commands).to eq([ - "git pull origin FOO", - "git pull origin master", - "git push origin HEAD", - "git branch -D prototype", - "git fetch origin", - "git checkout prototype", - "git pull . FOO", - "git push origin HEAD", - "git checkout FOO", - "git checkout FOO" - ]) + context 'when target branch is omitted' do + it 'defaults to prototype' do + expect_message "#worklog integrating FOO into prototype in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" + + Socialcast::Gitx::CLI.start ['integrate'] + + expect(stubbed_executed_commands).to eq([ + "git pull origin FOO", + "git pull origin master", + "git push origin HEAD", + "git branch -D prototype", + "git fetch origin", + "git checkout prototype", + "git pull . FOO", + "git push origin HEAD", + "git checkout FOO", + "git checkout FOO" + ]) + expect(github_api_pulls_list).to have_been_requested + end end - end - context 'when target branch is ommitted with custom prototype branch' do - before do - allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:prototype_branch).and_return('special-prototype') + context 'when target branch is omitted with custom prototype branch' do + it 'defaults to the custom prototype branch' do + allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:prototype_branch).and_return('special-prototype') - stub_message "#worklog integrating FOO into special-prototype in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" + expect_message "#worklog integrating FOO into special-prototype in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" - Socialcast::Gitx::CLI.start ['integrate'] + Socialcast::Gitx::CLI.start ['integrate'] + + expect(stubbed_executed_commands).to eq([ + "git pull origin FOO", + "git pull origin master", + "git push origin HEAD", + "git branch -D special-prototype", + "git fetch origin", + "git checkout special-prototype", + "git pull . FOO", + "git push origin HEAD", + "git checkout FOO", + "git checkout FOO" + ]) + end end - it 'should post message to socialcast' do end # see expectations - it 'should default to prototype' do - expect(stubbed_executed_commands).to eq([ - "git pull origin FOO", - "git pull origin master", - "git push origin HEAD", - "git branch -D special-prototype", - "git fetch origin", - "git checkout special-prototype", - "git pull . FOO", - "git push origin HEAD", - "git checkout FOO", - "git checkout FOO" - ]) + context 'when target branch == prototype' do + it do + expect_message "#worklog integrating FOO into prototype in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" + + Socialcast::Gitx::CLI.start ['integrate', 'prototype'] + + expect(stubbed_executed_commands).to eq([ + "git pull origin FOO", + "git pull origin master", + "git push origin HEAD", + "git branch -D prototype", + "git fetch origin", + "git checkout prototype", + "git pull . FOO", + "git push origin HEAD", + "git checkout FOO", + "git checkout FOO" + ]) + end end - end - context 'when target branch == prototype' do - before do - stub_message "#worklog integrating FOO into prototype in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" + context 'when target branch == staging' do + it do + expect_message "#worklog integrating FOO into staging in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" - Socialcast::Gitx::CLI.start ['integrate', 'prototype'] + Socialcast::Gitx::CLI.start ['integrate', 'staging'] + + expect(stubbed_executed_commands).to eq([ + "git pull origin FOO", + "git pull origin master", + "git push origin HEAD", + "git branch -D staging", + "git fetch origin", + "git checkout staging", + "git pull . FOO", + "git push origin HEAD", + "git checkout FOO", + "git branch -D prototype", + "git fetch origin", + "git checkout prototype", + "git pull . staging", + "git push origin HEAD", + "git checkout staging", + "git checkout FOO" + ]) + end end - it 'should post message to socialcast' do end # see expectations - it 'should run expected commands' do - expect(stubbed_executed_commands).to eq([ - "git pull origin FOO", - "git pull origin master", - "git push origin HEAD", - "git branch -D prototype", - "git fetch origin", - "git checkout prototype", - "git pull . FOO", - "git push origin HEAD", - "git checkout FOO", - "git checkout FOO" - ]) + context 'when target branch != staging || prototype' do + it do + expect { + Socialcast::Gitx::CLI.start ['integrate', 'asdfasdfasdf'] + }.to raise_error(/Only aggregate branches are allowed for integration/) + end end end - context 'when target branch == staging' do + context 'with an existing pull request' do + let!(:github_api_pulls_list) do + stub_request(:get, "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls?head=socialcast:FOO") + .to_return(:status => 200, :body => %q([{"html_url": "http://github.com/repo/project/pulls/1", "issue_url": "http://api.github.com/repos/repo/project/issues/1", "body":"testing"}])) + end + let!(:github_api_comments_create) do + stub_request(:post, "http://api.github.com/repos/repo/project/issues/1/comments") + .with(:body => "{\"body\":\"Integrated into prototype\"}") + .to_return(:status => 200, :body => "{}", :headers => {}) + end before do - stub_message "#worklog integrating FOO into staging in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" - - Socialcast::Gitx::CLI.start ['integrate', 'staging'] + allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:use_pr_comments?).and_return(use_pr_comments) end - it 'should post message to socialcast' do end # see expectations - it 'should also integrate into prototype and run expected commands' do - expect(stubbed_executed_commands).to eq([ - "git pull origin FOO", - "git pull origin master", - "git push origin HEAD", - "git branch -D staging", - "git fetch origin", - "git checkout staging", - "git pull . FOO", - "git push origin HEAD", - "git checkout FOO", - "git branch -D prototype", - "git fetch origin", - "git checkout prototype", - "git pull . staging", - "git push origin HEAD", - "git checkout staging", - "git checkout FOO" - ]) + context 'when use_pr_comments? is false' do + let(:use_pr_comments) { false } + it 'does not comment on the PR posts a message' do + expect_message "#worklog integrating FOO into prototype in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" + Socialcast::Gitx::CLI.start ['integrate'] + + expect(stubbed_executed_commands).to eq([ + "git pull origin FOO", + "git pull origin master", + "git push origin HEAD", + "git branch -D prototype", + "git fetch origin", + "git checkout prototype", + "git pull . FOO", + "git push origin HEAD", + "git checkout FOO", + "git checkout FOO" + ]) + expect(github_api_pulls_list).to have_been_requested + expect(github_api_comments_create).to_not have_been_requested + end end - end - context 'when target branch != staging || prototype' do - it 'should raise an error' do - expect { - Socialcast::Gitx::CLI.start ['integrate', 'asdfasdfasdf'] - }.to raise_error(/Only aggregate branches are allowed for integration/) + context 'when use_pr_comments? is true' do + let(:use_pr_comments) { true } + it 'comments on the PR and does not post a message' do + Socialcast::Gitx::CLI.start ['integrate'] + + expect(stubbed_executed_commands).to eq([ + "git pull origin FOO", + "git pull origin master", + "git push origin HEAD", + "git branch -D prototype", + "git fetch origin", + "git checkout prototype", + "git pull . FOO", + "git push origin HEAD", + "git checkout FOO", + "git checkout FOO" + ]) + expect(github_api_pulls_list).to have_been_requested + expect(github_api_comments_create).to have_been_requested + end end end end @@ -163,38 +225,52 @@ def stub_message(message_body, params = {}) end context 'when user confirms release' do before do - stub_message "#worklog releasing FOO to master in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" - expect_any_instance_of(Socialcast::Gitx::CLI).to receive(:yes?).and_return(true) expect_any_instance_of(Socialcast::Gitx::CLI).to receive(:cleanup) - Socialcast::Gitx::CLI.start ['release'] end - it 'should post message to socialcast' do end # see expectations - it 'should run expected commands' do - expect(stubbed_executed_commands).to eq([ - "git branch -D last_known_good_staging", - "git fetch origin", - "git checkout last_known_good_staging", - "git checkout FOO", - "git pull origin FOO", - "git pull origin master", - "git push origin HEAD", - "git checkout master", - "git pull origin master", - "git pull . FOO", - "git push origin HEAD", - "git branch -D staging", - "git fetch origin", - "git checkout staging", - "git pull . master", - "git push origin HEAD", - "git checkout master" - ]) + shared_examples_for 'releasing the branch' do + it do + expect(stubbed_executed_commands).to eq([ + "git branch -D last_known_good_staging", + "git fetch origin", + "git checkout last_known_good_staging", + "git checkout FOO", + "git pull origin FOO", + "git pull origin master", + "git push origin HEAD", + "git checkout master", + "git pull origin master", + "git pull . FOO", + "git push origin HEAD", + "git branch -D staging", + "git fetch origin", + "git checkout staging", + "git pull . master", + "git push origin HEAD", + "git checkout master" + ]) + end + end + context 'when use_pr_comments? is false' do + before do + allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:use_pr_comments?).and_return(false) + expect_message "#worklog releasing FOO to master in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" + Socialcast::Gitx::CLI.start ['release'] + end + it_behaves_like 'releasing the branch' + end + context 'when use_pr_comments? is true' do + before do + allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:use_pr_comments?).and_return(true) + Socialcast::Gitx::CLI.start ['release'] + end + # does not post a message nor a pr comment (github reports "merged" event via webhook)' + it_behaves_like 'releasing the branch' end end context 'when the branch is not in last_known_good_staging' do - context 'and enforce_staging_before_release = true' do + context 'when enforce_staging_before_release = true' do let(:branches_in_last_known_good_staging) { ['another-branch'] } before do expect_any_instance_of(Socialcast::Gitx::CLI).to receive(:enforce_staging_before_release?).and_return(true) @@ -204,16 +280,16 @@ def stub_message(message_body, params = {}) expect { Socialcast::Gitx::CLI.start ['release'] }.to raise_error(RuntimeError, 'Cannot release FOO unless it has already been promoted separately to staging and the build has passed.') end end - context 'and enforce_staging_before_release = false' do + context 'when enforce_staging_before_release = false' do let(:branches_in_last_known_good_staging) { ['another-branch'] } before do - stub_message "#worklog releasing FOO to master in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" + expect_message "#worklog releasing FOO to master in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" expect_any_instance_of(Socialcast::Gitx::CLI).to receive(:enforce_staging_before_release?).and_return(false) expect_any_instance_of(Socialcast::Gitx::CLI).to receive(:yes?).and_return(true) expect_any_instance_of(Socialcast::Gitx::CLI).to receive(:cleanup) Socialcast::Gitx::CLI.start ['release'] end - it 'should run expected commands' do + it do expect(stubbed_executed_commands).to eq([ "git pull origin FOO", "git pull origin master", @@ -235,9 +311,9 @@ def stub_message(message_body, params = {}) context 'with reserved_branches via config file' do before do - stub_message "#worklog releasing FOO to master in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" + expect_message "#worklog releasing FOO to master in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" expect_any_instance_of(Socialcast::Gitx::CLI).to receive(:yes?).and_return(true) - allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:config).and_return( { 'reserved_branches' => ['dont-del-me','dont-del-me-2'] }) + allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:config).and_return('reserved_branches' => ['dont-del-me', 'dont-del-me-2']) expect_any_instance_of(Socialcast::Gitx::CLI).to receive(:cleanup) Socialcast::Gitx::CLI.start ['release'] end @@ -248,19 +324,16 @@ def stub_message(message_body, params = {}) end context 'with alternative base branch via config file' do - before do - stub_message "#worklog releasing FOO to special-master in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" + it do + expect_message "#worklog releasing FOO to special-master in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" expect_any_instance_of(Socialcast::Gitx::CLI).to receive(:yes?).and_return(true) - allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:config).and_return( { 'base_branch' => 'special-master' }) + allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:config).and_return('base_branch' => 'special-master') expect_any_instance_of(Socialcast::Gitx::CLI).to receive(:cleanup) Socialcast::Gitx::CLI.start ['release'] - end - it 'should post message to socialcast' do end # see expectations - it "treats the alternative base branch as reserved" do + expect(Socialcast::Gitx::CLI.new.send(:reserved_branches)).to include 'special-master' - end - it 'should run expected commands' do + expect(stubbed_executed_commands).to eq([ "git branch -D last_known_good_staging", "git fetch origin", @@ -285,7 +358,7 @@ def stub_message(message_body, params = {}) context 'with alternative base branch via environment variable' do before do - stub_message "#worklog releasing FOO to special-master in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" + expect_message "#worklog releasing FOO to special-master in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" expect_any_instance_of(Socialcast::Gitx::CLI).to receive(:yes?).and_return(true) allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:config).and_return({}) @@ -296,11 +369,9 @@ def stub_message(message_body, params = {}) after do ENV.delete('BASE_BRANCH') end - it "treats the alternative base branch as reserved" do + it do expect(Socialcast::Gitx::CLI.new.send(:reserved_branches)).to include 'special-master' - end - it 'should post message to socialcast' do end # see expectations - it 'should run expected commands' do + expect(stubbed_executed_commands).to eq([ "git branch -D last_known_good_staging", "git fetch origin", @@ -325,10 +396,10 @@ def stub_message(message_body, params = {}) context 'with alternative base branch via environment variable overriding base branch in config' do before do - stub_message "#worklog releasing FOO to special-master in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" + expect_message "#worklog releasing FOO to special-master in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" expect_any_instance_of(Socialcast::Gitx::CLI).to receive(:yes?).and_return(true) - allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:config).and_return({ 'base_branch' => 'extra-special-master' }) + allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:config).and_return('base_branch' => 'extra-special-master') expect_any_instance_of(Socialcast::Gitx::CLI).to receive(:cleanup) ENV['BASE_BRANCH'] = 'special-master' Socialcast::Gitx::CLI.start ['release'] @@ -339,9 +410,7 @@ def stub_message(message_body, params = {}) it "treats the alternative base branch as reserved" do expect(Socialcast::Gitx::CLI.new.send(:reserved_branches)).to include 'special-master' expect(Socialcast::Gitx::CLI.new.send(:reserved_branches)).to include 'extra-special-master' - end - it 'should post message to socialcast' do end # see expectations - it 'should run expected commands' do + expect(stubbed_executed_commands).to eq([ "git branch -D last_known_good_staging", "git fetch origin", @@ -410,10 +479,10 @@ def stub_message(message_body, params = {}) before { allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:branches).and_return([]) } context 'when target branch == staging and --destination == last_known_good_staging' do before do - stub_message "#worklog resetting staging branch to last_known_good_staging in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" + expect_message "#worklog resetting staging branch to last_known_good_staging in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" Socialcast::Gitx::CLI.start ['nuke', 'staging', '--destination', 'last_known_good_staging'] end - it 'should run expected commands' do + it do expect(stubbed_executed_commands).to eq([ "git checkout master", "git branch -D last_known_good_staging", @@ -430,12 +499,12 @@ def stub_message(message_body, params = {}) end context 'when target branch == qa and destination prompt == nil and using custom aggregate_branches config' do before do - stub_message "#worklog resetting qa branch to last_known_good_qa in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" - allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:config).and_return( { 'aggregate_branches' => ['staging', 'qa'] }) + expect_message "#worklog resetting qa branch to last_known_good_qa in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" + allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:config).and_return('aggregate_branches' => ['staging', 'qa']) expect_any_instance_of(Socialcast::Gitx::CLI).to receive(:ask).and_return('') Socialcast::Gitx::CLI.start ['nuke', 'qa'] end - it 'defaults to last_known_good_qa and should run expected commands' do + it 'defaults to last_known_good_qa' do expect(stubbed_executed_commands).to eq([ "git checkout master", "git branch -D last_known_good_qa", @@ -452,12 +521,12 @@ def stub_message(message_body, params = {}) end context 'when target branch == qa and destination prompt = master and using custom aggregate_branches config' do before do - stub_message "#worklog resetting qa branch to last_known_good_master in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" - allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:config).and_return( { 'aggregate_branches' => ['staging', 'qa'] }) + expect_message "#worklog resetting qa branch to last_known_good_master in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" + allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:config).and_return('aggregate_branches' => ['staging', 'qa']) expect_any_instance_of(Socialcast::Gitx::CLI).to receive(:ask).and_return('master') Socialcast::Gitx::CLI.start ['nuke', 'qa'] end - it 'should run expected commands' do + it do expect(stubbed_executed_commands).to eq([ "git checkout master", "git branch -D last_known_good_master", @@ -483,17 +552,17 @@ def stub_message(message_body, params = {}) end end it 'raises an error when target branch is not an aggregate branch' do + expect_any_instance_of(Socialcast::Gitx::CLI).to receive(:ask).and_return('master') expect { - expect_any_instance_of(Socialcast::Gitx::CLI).to receive(:ask).and_return('master') Socialcast::Gitx::CLI.start ['nuke', 'asdfasdf'] }.to raise_error(/Only aggregate branches are allowed to be reset/) end end describe '#backportpr' do - before do + let(:pr_response) do # https://developer.github.com/v3/search/#search-issues - pr_response = { + { "url" => "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls/59", "id" => 13712197, "html_url" => "https://github.com/socialcast/socialcast-git-extensions/pull/59", @@ -792,8 +861,9 @@ def stub_message(message_body, params = {}) "deletions" => 2, "changed_files" => 2 } - - commits_response = [ + end + let(:commits_response) do + [ { "sha" => "5e30d5af3f4d1bb3a34cc97568299be028b65f6f", "commit" => { @@ -865,30 +935,87 @@ def stub_message(message_body, params = {}) ] } ] + end + let!(:github_pr_show) do + stub_request(:get, "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls/59") + .to_return(:status => 200, :body => pr_response.to_json, :headers => {}) + end + let!(:github_pr_commits_list) do + stub_request(:get, "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls/59/commits") + .to_return(:status => 200, :body => commits_response.to_json, :headers => {}) + end + let!(:github_pr_create) do + stub_request(:post, "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls") + .with(:body => "{\"title\":\"backport_59_to_v1.x\",\"base\":\"v1.x\",\"head\":\"backport_59_to_v1.x\",\"body\":\"Backport #59 to https://github.com/socialcast/socialcast-git-extensions/tree/v1.x\\n***\\nsimply testing this out\"}") + .to_return(:status => 200, :body => '{"html_url": "https://github.com/socialcast/socialcast-git-extensions/pulls/60", "issue_url": "https://api.github.com/repos/repo/project/issues/60"}', :headers => { 'Content-Type' => 'application/json' }) + end + let!(:github_pr_comment_create) do + stub_request(:post, "https://api.github.com/repos/repo/project/issues/60/comments") + .with(:body => "{\"body\":\"#reviewrequest backport /cc @SocialcastDevelopers #scgitx\"}") + .to_return(:status => 200, :body => "{}", :headers => {}) + end + let!(:socialcast_message_create) do + stub_request(:post, "https://testuser:testpassword@testdomain/api/messages.json") + .with(:body => "{\"message\":{\"url\":\"https://github.com/socialcast/socialcast-git-extensions/pulls/60\",\"message_type\":\"review_request\",\"body\":\"#reviewrequest backport #59 to v1.x in socialcast/socialcast-git-extensions #scgitx\\n\\n/cc @SocialcastDevelopers\"}}") + .to_return(:status => 200, :body => "", :headers => {}) + end + before do + expect_any_instance_of(Socialcast::Gitx::CLI).to receive(:backportpr).and_call_original + allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:use_pr_comments?).and_return(use_pr_comments) + end + context 'when use_pr_comments? is false' do + let(:use_pr_comments) { false } + it 'creates a branch based on v1.x and cherry-picks in PR 59' do + Socialcast::Gitx::CLI.start ['backportpr', '59', 'v1.x'] + expect(github_pr_show).to have_been_requested + expect(github_pr_commits_list).to have_been_requested + expect(github_pr_create).to have_been_requested - stub_request(:get, "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls/59"). - with(:headers => { 'Accept' => 'application/json', 'Accept-Encoding' => 'gzip, deflate', 'Authorization' => /token\s\w+/, 'Content-Type' => 'application/json', 'User-Agent' => 'socialcast-git-extensions' }). - to_return(:status => 200, :body => pr_response.to_json, :headers => {}) - stub_request(:get, "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls/59/commits"). - with(:headers => { 'Accept' => 'application/json', 'Accept-Encoding' => 'gzip, deflate', 'Authorization' => /token\s\w+/, 'Content-Type' => 'application/json', 'User-Agent' => 'socialcast-git-extensions' }). - to_return(:status => 200, :body => commits_response.to_json, :headers => {}) - stub_request(:post, "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls"). - with(:body => "{\"title\":\"backport_59_to_v1.x\",\"base\":\"v1.x\",\"head\":\"backport_59_to_v1.x\",\"body\":\"Backport #59 to https://github.com/socialcast/socialcast-git-extensions/tree/v1.x\\n***\\nsimply testing this out\"}", - :headers => { 'Accept' => 'application/json', 'Accept-Encoding' => 'gzip, deflate', 'Authorization' => /token\s\w+/, 'Content-Type' => 'application/json', 'User-Agent'=>'socialcast-git-extensions' }). - to_return(:status => 200, :body => '{"html_url": "https://github.com/socialcast/socialcast-git-extensions/pulls/60"}', :headers => { 'Content-Type' => 'application/json' }) + expect(github_pr_comment_create).to_not have_been_requested + expect(socialcast_message_create).to have_been_requested + end + end + context 'when use_pr_comments? is true' do + let(:use_pr_comments) { true } + it 'posts a pr comment instead of posting a socialcast message' do + Socialcast::Gitx::CLI.start ['backportpr', '59', 'v1.x'] + expect(github_pr_show).to have_been_requested + expect(github_pr_commits_list).to have_been_requested + expect(github_pr_create).to have_been_requested - stub_message "#reviewrequest backport #59 to v1.x in socialcast/socialcast-git-extensions #scgitx\n\n/cc @SocialcastDevelopers", :url => 'https://github.com/socialcast/socialcast-git-extensions/pulls/60', :message_type => 'review_request' + expect(github_pr_comment_create).to have_been_requested + expect(socialcast_message_create).to_not have_been_requested + end + end + context 'when a backport reviewer is configured' do + before do + allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:github_track_reviewer).with('Backport').and_return('janedoe') + allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:socialcast_track_reviewer).with('Backport').and_return('JaneDoe') + end + let(:use_pr_comments) { true } + let!(:github_pr_comment_create) do + stub_request(:post, "https://api.github.com/repos/repo/project/issues/60/comments") + .with(:body => "{\"body\":\"#reviewrequest backport @JaneDoe /cc @SocialcastDevelopers #scgitx\"}") + .to_return(:status => 200, :body => "{}", :headers => {}) + end + let!(:github_assign_pr) { stub_request(:patch, "https://api.github.com/repos/repo/project/issues/60").to_return(:status => 200) } + it 'mentions the track reviewer' do + Socialcast::Gitx::CLI.start ['backportpr', '59', 'v1.x'] + expect(github_pr_show).to have_been_requested + expect(github_pr_commits_list).to have_been_requested + expect(github_pr_create).to have_been_requested + expect(github_assign_pr).to have_been_requested - expect_any_instance_of(Socialcast::Gitx::CLI).to receive(:backportpr).and_call_original - Socialcast::Gitx::CLI.start ['backportpr', '59', 'v1.x'] + expect(github_pr_comment_create).to have_been_requested + expect(socialcast_message_create).to_not have_been_requested + end end - it 'creates a branch based on v1.x and cherry-picks in PR 59' do end end describe '#findpr' do - before do + let(:issue_response) do # https://developer.github.com/v3/search/#search-issues - stub_response = { + { "total_count"=> 280, "items"=> [{ "url" => "https://api.github.com/repos/batterseapower/pinyin-toolkit/issues/132", @@ -938,98 +1065,300 @@ def stub_message(message_body, params = {}) "score" => 1.3859273 }] } - - stub_request(:get, "https://api.github.com/search/issues?q=abc123%20type:pr%20repo:socialcast/socialcast-git-extensions"). - with(:headers => { 'Accept' => 'application/json', 'Accept-Encoding' => 'gzip, deflate', 'Authorization' => /token\s\w+/, 'Content-Type' => 'application/json', 'User-Agent' => 'socialcast-git-extensions'}). - to_return(:status => 200, :body => stub_response.to_json, :headers => {}) + end + let!(:github_issue_search) do + stub_request(:get, "https://api.github.com/search/issues?q=abc123%20type:pr%20repo:socialcast/socialcast-git-extensions") + .to_return(:status => 200, :body => issue_response.to_json, :headers => {}) + end + it do expect_any_instance_of(Socialcast::Gitx::CLI).to receive(:findpr).and_call_original allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:say).with("Searching github pull requests for abc123") allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:say).with("\nhttps://github.com/batterseapower/pinyin-toolkit/issues/132\n\tLine Number Indexes Beyond 20 Not Displayed\n\tNick3C 2009-07-12T20:10:41Z") Socialcast::Gitx::CLI.start ['findpr', 'abc123'] + expect(github_issue_search).to have_been_requested end - it 'fetches the data from github and prints it out' do end end describe '#reviewrequest' do + let!(:github_create_pr) do + stub_request(:post, "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls") + .to_return(:status => 200, :body => %q({"html_url": "http://github.com/repo/project/pulls/1", "issue_url": "http://api.github.com/repos/repo/project/issues/1"}), :headers => {}) + end + let!(:github_find_pr_for_branch) do + stub_request(:get, "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls?head=socialcast:FOO") + .to_return(:status => 200, :body => %q([{"html_url": "http://github.com/repo/project/pulls/1", "issue_url": "http://api.github.com/repos/repo/project/issues/1", "body":"testing"}]), :headers => {}) + end + let(:stub_github_create_pr_comment) do + stub_request(:post, "http://api.github.com/repos/repo/project/issues/1/comments") + .with(:body => pr_comment_body) + .to_return(:status => 200, :body => "{}", :headers => {}) + end + let(:use_pr_comments) { false } + before do + stub_github_create_pr_comment + allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:use_pr_comments?).and_return(use_pr_comments) + allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:changelog_summary).and_return('1 file changed') + end context 'when there are no review_buddies specified' do before do allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:config_file).and_return(Pathname('')) end - context 'when description != nil' do - before do - stub_request(:post, "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls"). - to_return(:status => 200, :body => %q({"html_url": "http://github.com/repo/project/pulls/1"}), :headers => {}) - - stub_message "#reviewrequest for FOO in socialcast/socialcast-git-extensions\nPR http://github.com/repo/project/pulls/1 \n\ntesting\n\n/cc @SocialcastDevelopers #scgitx\n\n1 file changed", :message_type => 'review_request' - allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:changelog_summary).and_return('1 file changed') + let(:pr_comment_body) { "{\"body\":\"#reviewrequest \\n/cc @SocialcastDevelopers #scgitx\"}" } + context 'when use_pr_comments? is false' do + it do + expect_message "#reviewrequest for FOO in socialcast/socialcast-git-extensions\nPR http://github.com/repo/project/pulls/1 \n\ntesting\n\n/cc @SocialcastDevelopers #scgitx\n\n1 file changed", :message_type => 'review_request' Socialcast::Gitx::CLI.start ['reviewrequest', '--description', 'testing', '-s'] + + expect(stubbed_executed_commands).to eq(git_update_commands) + expect(github_create_pr).to have_been_requested + expect(github_find_pr_for_branch).to have_been_requested + + expect(stub_github_create_pr_comment).to_not have_been_requested end - it 'should create github pull request' do end # see expectations - it 'should post socialcast message' do end # see expectations - it 'should run expected commands' do - expect(stubbed_executed_commands).to eq([ - "git pull origin FOO", - "git pull origin master", - "git push origin HEAD" - ]) + end + context 'when use_pr_comments? is true' do + let(:use_pr_comments) { true } + it do + Socialcast::Gitx::CLI.start ['reviewrequest', '--description', 'testing', '-s'] + + expect(stubbed_executed_commands).to eq(git_update_commands) + expect(github_create_pr).to have_been_requested + expect(github_find_pr_for_branch).to have_been_requested + + expect(stub_github_create_pr_comment).to have_been_requested end end end context 'when review_buddies are specified via a /config YML file' do + let!(:github_assign_pr) { stub_request(:patch, "http://api.github.com/repos/repo/project/issues/1").to_return(:status => 200) } before do - stub_request(:post, "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls"). - to_return(:status => 200, :body => %q({"html_url": "http://github.com/repo/project/pulls/1", "issue_url": "http://github.com/repos/repo/project/issues/1"}), :headers => {}) - stub_request(:patch, "http://github.com/repos/repo/project/issues/1").to_return(:status => 200) allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:socialcast_review_buddy).and_return('JaneDoe') - end + allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:github_review_buddy).and_return('janedoe') + end context 'and additional reviewers are specified' do let(:message_body) { "#reviewrequest for FOO in socialcast/socialcast-git-extensions\nPR http://github.com/repo/project/pulls/1 assigned to @JaneDoe\n\ntesting\n\nAssigned additionally to @JohnSmith for API review\n/cc @SocialcastDevelopers #scgitx\n\n1 file changed" } - before do - allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:changelog_summary).and_return('1 file changed') - stub_message message_body, :message_type => 'review_request' - Socialcast::Gitx::CLI.start ['reviewrequest', '--description', 'testing', '-a', 'a'] + let(:pr_comment_body) { "{\"body\":\"#reviewrequest assigned to @JaneDoe \\nAssigned additionally to @JohnSmith for API review \\n/cc @SocialcastDevelopers #scgitx\"}" } + context 'when use_pr_comments? is false' do + it do + expect_message message_body, :message_type => 'review_request' + Socialcast::Gitx::CLI.start ['reviewrequest', '--description', 'testing', '-a', 'a'] + + expect(stubbed_executed_commands).to eq(git_update_commands) + expect(github_create_pr).to have_been_requested + expect(github_assign_pr).to have_been_requested + + expect(stub_github_create_pr_comment).to_not have_been_requested + end end - it 'should create github pull request' do end # see expectations - it 'should post socialcast message' do end # see expectations - it 'should run expected commands' do - expect(stubbed_executed_commands).to eq([ - "git pull origin FOO", - "git pull origin master", - "git push origin HEAD" - ]) + context 'when use_pr_comments? is true' do + let(:use_pr_comments) { true } + it do + Socialcast::Gitx::CLI.start ['reviewrequest', '--description', 'testing', '-a', 'a'] + + expect(stubbed_executed_commands).to eq(git_update_commands) + expect(github_create_pr).to have_been_requested + expect(github_assign_pr).to have_been_requested + + expect(stub_github_create_pr_comment).to have_been_requested + end end end context 'and a developer group is specified' do let(:message_body) { "#reviewrequest for FOO in socialcast/socialcast-git-extensions\nPR http://github.com/repo/project/pulls/1 assigned to @JaneDoe\n\ntesting\n\n/cc @#{another_group} #scgitx\n\n1 file changed" } + let(:pr_comment_body) { "{\"body\":\"#reviewrequest assigned to @JaneDoe \\n/cc @#{another_group} #scgitx\"}" } let(:another_group) { 'AnotherDeveloperGroup' } before do - allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:changelog_summary).and_return('1 file changed') allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:config).and_return({'developer_group' => another_group}) - stub_message message_body, :message_type => 'review_request' - Socialcast::Gitx::CLI.start ['reviewrequest', '--description', 'testing', '-s'] end - it 'should create github pull request with a different group mentioned' do end # see expectations + context 'when use_pr_comments? is false' do + it do + expect_message message_body, :message_type => 'review_request' + Socialcast::Gitx::CLI.start ['reviewrequest', '--description', 'testing', '-s'] + expect(github_create_pr).to have_been_requested + expect(github_assign_pr).to have_been_requested + expect(stub_github_create_pr_comment).to_not have_been_requested + end + end + context 'when use_pr_comments? is true' do + let(:use_pr_comments) { true } + it do + Socialcast::Gitx::CLI.start ['reviewrequest', '--description', 'testing', '-s'] + expect(github_create_pr).to have_been_requested + expect(github_assign_pr).to have_been_requested + expect(stub_github_create_pr_comment).to have_been_requested + end + end end context 'and additional reviewers are not specified' do let(:message_body) { "#reviewrequest for FOO in socialcast/socialcast-git-extensions\nPR http://github.com/repo/project/pulls/1 assigned to @JaneDoe\n\ntesting\n\n/cc @SocialcastDevelopers #scgitx\n\n1 file changed" } + let(:pr_comment_body) { "{\"body\":\"#reviewrequest assigned to @JaneDoe \\n/cc @SocialcastDevelopers #scgitx\"}" } + context 'when use_pr_comments? is false' do + it do + expect_message message_body, :message_type => 'review_request' + Socialcast::Gitx::CLI.start ['reviewrequest', '--description', 'testing', '-s'] + expect(github_create_pr).to have_been_requested + expect(github_assign_pr).to have_been_requested + expect(stub_github_create_pr_comment).to_not have_been_requested + end + end + context 'when use_pr_comments? is true' do + let(:use_pr_comments) { true } + it do + Socialcast::Gitx::CLI.start ['reviewrequest', '--description', 'testing', '-s'] + expect(github_create_pr).to have_been_requested + expect(github_assign_pr).to have_been_requested + expect(stub_github_create_pr_comment).to have_been_requested + end + end + end + end + end + + describe '#createpr' do + let!(:github_pr_create) do + stub_request(:post, "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls") + .to_return(:status => 200, :body => %q({"html_url": "http://github.com/repo/project/pulls/1", "issue_url": "http://api.github.com/repos/repo/project/issues/1"}), :headers => {}) + end + it do + Socialcast::Gitx::CLI.start ['createpr', '--description', 'testing'] + + expect(stubbed_executed_commands).to eq git_update_commands + expect(github_pr_create).to have_been_requested + end + end + + describe '#assignpr' do + let!(:github_find_pr_for_branch) do + stub_request(:get, "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls?head=socialcast:FOO") + .to_return(:status => 200, :body => %q([{"html_url": "http://github.com/repo/project/pulls/1", "issue_url": "http://api.github.com/repos/repo/project/issues/1", "body":"testing"}]), :headers => {}) + end + let(:stub_github_create_pr_comment) do + stub_request(:post, "http://api.github.com/repos/repo/project/issues/1/comments") + .with(:body => pr_comment_body) + .to_return(:status => 200, :body => "{}", :headers => {}) + end + let(:use_pr_comments) { false } + before do + stub_github_create_pr_comment + allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:use_pr_comments?).and_return(use_pr_comments) + allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:changelog_summary).and_return('1 file changed') + end + context 'when there are no review_buddies specified' do + before do + allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:config_file).and_return(Pathname('')) + end + let(:pr_comment_body) { "{\"body\":\"#reviewrequest \\n/cc @SocialcastDevelopers #scgitx\"}" } + context 'when use_pr_comments? is false' do + it do + expect_message "#reviewrequest for FOO in socialcast/socialcast-git-extensions\nPR http://github.com/repo/project/pulls/1 \n\ntesting\n\n/cc @SocialcastDevelopers #scgitx\n\n1 file changed", :message_type => 'review_request' + Socialcast::Gitx::CLI.start ['assignpr', '-s'] + + expect(stubbed_executed_commands).to eq(git_update_commands) + expect(github_find_pr_for_branch).to have_been_requested + + expect(stub_github_create_pr_comment).to_not have_been_requested + end + end + context 'when use_pr_comments? is true' do + let(:use_pr_comments) { true } + it do + Socialcast::Gitx::CLI.start ['assignpr', '-s'] + + expect(stubbed_executed_commands).to eq(git_update_commands) + expect(github_find_pr_for_branch).to have_been_requested + + expect(stub_github_create_pr_comment).to have_been_requested + end + end + end + + context 'when review_buddies are specified via a /config YML file' do + let!(:github_assign_pr) { stub_request(:patch, "http://api.github.com/repos/repo/project/issues/1").to_return(:status => 200) } + before do + allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:socialcast_review_buddy).and_return('JaneDoe') + allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:github_review_buddy).and_return('janedoe') + end + context 'and additional reviewers are specified' do + let(:message_body) { "#reviewrequest for FOO in socialcast/socialcast-git-extensions\nPR http://github.com/repo/project/pulls/1 assigned to @JaneDoe\n\ntesting\n\nAssigned additionally to @JohnSmith for API review\n/cc @SocialcastDevelopers #scgitx\n\n1 file changed" } + let(:pr_comment_body) { "{\"body\":\"#reviewrequest assigned to @JaneDoe \\nAssigned additionally to @JohnSmith for API review \\n/cc @SocialcastDevelopers #scgitx\"}" } + context 'when use_pr_comments? is false' do + it do + expect_message message_body, :message_type => 'review_request' + Socialcast::Gitx::CLI.start ['assignpr', '-a', 'a'] + + expect(stubbed_executed_commands).to eq(git_update_commands) + expect(github_assign_pr).to have_been_requested + + expect(stub_github_create_pr_comment).to_not have_been_requested + end + end + context 'when use_pr_comments? is true' do + let(:use_pr_comments) { true } + it do + Socialcast::Gitx::CLI.start ['assignpr', '-a', 'a'] + + expect(stubbed_executed_commands).to eq(git_update_commands) + expect(github_assign_pr).to have_been_requested + + expect(stub_github_create_pr_comment).to have_been_requested + end + end + end + context 'and a developer group is specified' do + let(:message_body) { "#reviewrequest for FOO in socialcast/socialcast-git-extensions\nPR http://github.com/repo/project/pulls/1 assigned to @JaneDoe\n\ntesting\n\n/cc @#{another_group} #scgitx\n\n1 file changed" } + let(:pr_comment_body) { "{\"body\":\"#reviewrequest assigned to @JaneDoe \\n/cc @#{another_group} #scgitx\"}" } + let(:another_group) { 'AnotherDeveloperGroup' } before do - allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:changelog_summary).and_return('1 file changed') - stub_message message_body, :message_type => 'review_request' - Socialcast::Gitx::CLI.start ['reviewrequest', '--description', 'testing', '-s'] + allow_any_instance_of(Socialcast::Gitx::CLI).to receive(:config).and_return({'developer_group' => another_group}) + end + context 'when use_pr_comments? is false' do + it do + expect_message message_body, :message_type => 'review_request' + Socialcast::Gitx::CLI.start ['assignpr', '-s'] + expect(github_assign_pr).to have_been_requested + expect(stub_github_create_pr_comment).to_not have_been_requested + end + end + context 'when use_pr_comments? is true' do + let(:use_pr_comments) { true } + it do + Socialcast::Gitx::CLI.start ['assignpr', '-s'] + expect(github_assign_pr).to have_been_requested + expect(stub_github_create_pr_comment).to have_been_requested + end + end + end + context 'and additional reviewers are not specified' do + let(:message_body) { "#reviewrequest for FOO in socialcast/socialcast-git-extensions\nPR http://github.com/repo/project/pulls/1 assigned to @JaneDoe\n\ntesting\n\n/cc @SocialcastDevelopers #scgitx\n\n1 file changed" } + let(:pr_comment_body) { "{\"body\":\"#reviewrequest assigned to @JaneDoe \\n/cc @SocialcastDevelopers #scgitx\"}" } + context 'when use_pr_comments? is false' do + it do + expect_message message_body, :message_type => 'review_request' + Socialcast::Gitx::CLI.start ['assignpr', '-s'] + expect(github_assign_pr).to have_been_requested + expect(stub_github_create_pr_comment).to_not have_been_requested + end + end + context 'when use_pr_comments? is true' do + let(:use_pr_comments) { true } + it do + Socialcast::Gitx::CLI.start ['assignpr', '-s'] + expect(github_assign_pr).to have_been_requested + expect(stub_github_create_pr_comment).to have_been_requested + end end - it 'should create github pull request' do end # see expectations end end end describe '#promote' do before do - stub_message "#worklog integrating FOO into staging in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" - Socialcast::Gitx::CLI.start ['promote'] + stub_request(:get, "https://api.github.com/repos/socialcast/socialcast-git-extensions/pulls?head=socialcast:FOO") + .to_return(:status => 200, :body => "[]", :headers => {}) end - it 'should integrate into staging' do - expect(stubbed_executed_commands).to eq([ + let(:staging_integration_commands) do + [ "git pull origin FOO", "git pull origin master", "git push origin HEAD", @@ -1046,7 +1375,12 @@ def stub_message(message_body, params = {}) "git push origin HEAD", "git checkout staging", "git checkout FOO" - ]) + ] + end + it do + expect_message "#worklog integrating FOO into staging in socialcast/socialcast-git-extensions #scgitx\n/cc @SocialcastDevelopers" + Socialcast::Gitx::CLI.start ['promote'] + expect(stubbed_executed_commands).to eq staging_integration_commands end end @@ -1056,7 +1390,7 @@ def stub_message(message_body, params = {}) expect_any_instance_of(Socialcast::Gitx::CLI).to receive(:branches).with(:merged => true).and_return(['staging', 'bazquux', 'last_known_good_prototype']) Socialcast::Gitx::CLI.start ['cleanup'] end - it 'should only cleanup non-reserved branches' do + it 'only cleans up non-reserved branches' do expect(stubbed_executed_commands).to eq([ "git checkout master", "git pull", diff --git a/spec/github_spec.rb b/spec/github_spec.rb new file mode 100644 index 0000000..e386b4e --- /dev/null +++ b/spec/github_spec.rb @@ -0,0 +1,98 @@ +require 'spec_helper' + +describe Socialcast::Gitx::Github do + let(:test_class) do + Class.new do |k| + include Socialcast::Gitx::Github + + def say(message); end + end + end + let(:said_messages) { [] } + before do + stub_const('TestClass', test_class) + allow_any_instance_of(TestClass).to receive(:'`') do |_instance, _cmd| + raise 'Unstubbed backticks detected' + end + allow_any_instance_of(TestClass).to receive(:say) do |_instance, message| + said_messages << message + end + end + let(:test_instance) { TestClass.new } + let(:branch) { 'my-branch' } + let(:base_branch) { 'master' } + let(:repo) { 'ownername/projectname' } + + describe '#create_pull_request' do + subject { test_instance.send(:create_pull_request, branch, repo, body) } + let(:create_pr_payload) do + { + :title => branch, + :base => base_branch, + :head => branch, + :body => body + } + end + let(:dummy_pr_created_response) { { :dummy => :response } } + let(:body) { 'This is my pull request.' } + before do + allow(test_instance).to receive(:base_branch).and_return(base_branch) + expect(test_instance).to receive(:github_api_request).with('POST', 'repos/ownername/projectname/pulls', create_pr_payload.to_json).and_return(dummy_pr_created_response) + end + it { is_expected.to eq dummy_pr_created_response } + end + + describe '#pull_requests_for_branch' do + subject { test_instance.send(:pull_requests_for_branch, repo, branch) } + let(:dummy_pr_list_response) { [{ :dummy => :response }] } + before do + expect(test_instance).to receive(:github_api_request).with('GET', 'repos/ownername/projectname/pulls?head=ownername:my-branch').and_return(dummy_pr_list_response) + end + it { is_expected.to eq dummy_pr_list_response } + end + + describe '#current_pr_for_branch' do + subject(:current_pr) { test_instance.send(:current_pr_for_branch, repo, branch) } + let(:dummy_pr_one) { { :dummy => :pr1 } } + let(:dummy_pr_two) { { :dummy => :pr2 } } + before do + expect(test_instance).to receive(:pull_requests_for_branch).with(repo, branch).and_return(dummy_pr_list_response) + end + context 'when an empty pr list is returned' do + let(:dummy_pr_list_response) { [] } + it { is_expected.to be_nil } + end + context 'when a single pr is returned' do + let(:dummy_pr_list_response) { [dummy_pr_one] } + it { is_expected.to eq dummy_pr_one } + end + context 'when multiple prs are returned' do + let(:dummy_pr_list_response) { [dummy_pr_one, dummy_pr_two] } + it { expect { current_pr }.to raise_error 'Multiple (2) open PRs for my-branch found in ownername/projectname, unable to proceed' } + end + end + + describe '#assign_pull_request' do + subject { test_instance.send(:assign_pull_request, assignee, issue_url) } + let(:assignee) { 'janedoe' } + let(:issue_url) { 'repos/ownername/projectname/issues/1' } + let(:dummy_assignment_response) { [{ :dummy => :response }] } + let(:assign_payload) { { :assignee => assignee } } + before do + expect(test_instance).to receive(:github_api_request).with('PATCH', 'repos/ownername/projectname/issues/1', assign_payload.to_json).and_return(dummy_assignment_response) + end + it { is_expected.to eq dummy_assignment_response } + end + + describe '#comment_on_issue' do + subject { test_instance.send(:comment_on_issue, issue_url, comment_body) } + let(:issue_url) { 'repos/ownername/projectname/issues/1' } + let(:dummy_comment_response) { { :dummy => :response } } + let(:comment_payload) { { :body => comment_body } } + let(:comment_body) { 'Integrating into staging' } + before do + expect(test_instance).to receive(:github_api_request).with('POST', 'repos/ownername/projectname/issues/1/comments', comment_payload.to_json).and_return(dummy_comment_response) + end + it { is_expected.to eq dummy_comment_response } + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1070a31..f7a0308 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -4,7 +4,7 @@ require 'rspec/mocks' require 'webmock/rspec' require 'pry' - +require 'byebug' require 'socialcast-git-extensions/cli' ENV['SCGITX_TEST'] = 'true'