modulesync 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +29 -9
  3. data/.github/workflows/release.yml +7 -7
  4. data/.gitignore +1 -0
  5. data/.rubocop.yml +14 -8
  6. data/.rubocop_todo.yml +25 -17
  7. data/.simplecov +46 -0
  8. data/CHANGELOG.md +58 -0
  9. data/Gemfile +5 -3
  10. data/LICENSE +173 -12
  11. data/README.md +30 -0
  12. data/bin/msync +17 -1
  13. data/features/cli.feature +12 -6
  14. data/features/execute.feature +51 -0
  15. data/features/hook.feature +5 -8
  16. data/features/push.feature +46 -0
  17. data/features/reset.feature +57 -0
  18. data/features/step_definitions/git_steps.rb +29 -1
  19. data/features/support/env.rb +9 -0
  20. data/features/update/bump_version.feature +8 -12
  21. data/features/update/dot_sync.feature +52 -0
  22. data/features/update/pull_request.feature +180 -0
  23. data/features/update.feature +74 -103
  24. data/lib/modulesync/cli/thor.rb +12 -0
  25. data/lib/modulesync/cli.rb +122 -28
  26. data/lib/modulesync/git_service/base.rb +63 -0
  27. data/lib/modulesync/git_service/factory.rb +28 -0
  28. data/lib/modulesync/{pr → git_service}/github.rb +23 -21
  29. data/lib/modulesync/git_service/gitlab.rb +62 -0
  30. data/lib/modulesync/git_service.rb +96 -0
  31. data/lib/modulesync/hook.rb +11 -13
  32. data/lib/modulesync/renderer.rb +3 -6
  33. data/lib/modulesync/repository.rb +78 -28
  34. data/lib/modulesync/settings.rb +0 -1
  35. data/lib/modulesync/source_code.rb +28 -2
  36. data/lib/modulesync/util.rb +4 -4
  37. data/lib/modulesync.rb +104 -66
  38. data/modulesync.gemspec +9 -5
  39. data/spec/helpers/faker/puppet_module_remote_repo.rb +16 -1
  40. data/spec/spec_helper.rb +2 -0
  41. data/spec/unit/modulesync/git_service/factory_spec.rb +16 -0
  42. data/spec/unit/modulesync/git_service/github_spec.rb +81 -0
  43. data/spec/unit/modulesync/git_service/gitlab_spec.rb +90 -0
  44. data/spec/unit/modulesync/git_service_spec.rb +201 -0
  45. data/spec/unit/modulesync/source_code_spec.rb +22 -0
  46. data/spec/unit/modulesync_spec.rb +0 -12
  47. metadata +96 -14
  48. data/lib/modulesync/pr/gitlab.rb +0 -54
  49. data/spec/unit/modulesync/pr/github_spec.rb +0 -49
  50. data/spec/unit/modulesync/pr/gitlab_spec.rb +0 -81
data/lib/modulesync.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'English'
1
2
  require 'fileutils'
2
3
  require 'pathname'
3
4
 
@@ -18,10 +19,10 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
18
19
 
19
20
  def self.config_defaults
20
21
  {
21
- :project_root => 'modules/',
22
+ :project_root => 'modules/',
22
23
  :managed_modules_conf => 'managed_modules.yml',
23
- :configs => '.',
24
- :tag_pattern => '%s'
24
+ :configs => '.',
25
+ :tag_pattern => '%s',
25
26
  }
26
27
  end
27
28
 
@@ -44,8 +45,8 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
44
45
  .to_a
45
46
  else
46
47
  $stderr.puts "#{local_template_dir} does not exist." \
47
- ' Check that you are working in your module configs directory or' \
48
- ' that you have passed in the correct directory with -c.'
48
+ ' Check that you are working in your module configs directory or' \
49
+ ' that you have passed in the correct directory with -c.'
49
50
  exit 1
50
51
  end
51
52
  end
@@ -62,7 +63,7 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
62
63
  managed_modules = Util.parse_config(config_file)
63
64
  if managed_modules.empty?
64
65
  $stderr.puts "No modules found in #{config_file}." \
65
- ' Check that you specified the right :configs directory and :managed_modules_conf file.'
66
+ ' Check that you specified the right :configs directory and :managed_modules_conf file.'
66
67
  exit 1
67
68
  end
68
69
  managed_modules.select! { |m| m =~ Regexp.new(filter) } unless filter.nil?
@@ -82,7 +83,6 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
82
83
  end
83
84
 
84
85
  def self.manage_file(puppet_module, filename, settings, options)
85
- namespace = settings.additional_settings[:namespace]
86
86
  module_name = settings.additional_settings[:puppet_module]
87
87
  configs = settings.build_file_configs(filename)
88
88
  target_file = puppet_module.path(filename)
@@ -95,12 +95,12 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
95
95
  # Meta data passed to the template as @metadata[:name]
96
96
  metadata = {
97
97
  :module_name => module_name,
98
- :workdir => puppet_module.working_directory,
98
+ :workdir => puppet_module.working_directory,
99
99
  :target_file => target_file,
100
100
  }
101
101
  template = Renderer.render(erb, configs, metadata)
102
102
  Renderer.sync(template, target_file)
103
- rescue StandardError => e
103
+ rescue StandardError
104
104
  $stderr.puts "#{puppet_module.given_name}: Error while rendering file: '#{filename}'"
105
105
  raise
106
106
  end
@@ -109,7 +109,12 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
109
109
 
110
110
  def self.manage_module(puppet_module, module_files, defaults)
111
111
  puts "Syncing '#{puppet_module.given_name}'"
112
- puppet_module.repository.prepare_workspace(options[:branch]) unless options[:offline]
112
+ # NOTE: #prepare_workspace now supports to execute only offline operations
113
+ # but we totally skip the workspace preparation to keep the current behavior
114
+ unless options[:offline]
115
+ puppet_module.repository.prepare_workspace(branch: options[:branch],
116
+ operate_offline: false)
117
+ end
113
118
 
114
119
  module_configs = Util.parse_config puppet_module.path(MODULE_CONF_FILE)
115
120
  settings = Settings.new(defaults[GLOBAL_DEFAULTS_KEY] || {},
@@ -129,9 +134,8 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
129
134
 
130
135
  if options[:noop]
131
136
  puts "Using no-op. Files in '#{puppet_module.given_name}' may be changed but will not be committed."
132
- puppet_module.repository.show_changes(options)
133
- options[:pr] && \
134
- pr(puppet_module).manage(puppet_module.repository_namespace, puppet_module.repository_name, options)
137
+ changed = puppet_module.repository.show_changes(options)
138
+ changed && options[:pr] && puppet_module.open_pull_request
135
139
  elsif !options[:offline]
136
140
  pushed = puppet_module.repository.submit_changes(files_to_manage, options)
137
141
  # Only bump/tag if pushing didn't fail (i.e. there were changes)
@@ -139,13 +143,13 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
139
143
  new = puppet_module.bump(options[:message], options[:changelog])
140
144
  puppet_module.repository.tag(new, options[:tag_pattern]) if options[:tag]
141
145
  end
142
- pushed && options[:pr] && \
143
- pr(puppet_module).manage(puppet_module.repository_namespace, puppet_module.repository_name, options)
146
+ pushed && options[:pr] && puppet_module.open_pull_request
144
147
  end
145
148
  end
146
149
 
147
150
  def self.config_path(file, options)
148
151
  return file if Pathname.new(file).absolute?
152
+
149
153
  File.join(options[:configs], file)
150
154
  end
151
155
 
@@ -157,15 +161,6 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
157
161
  @options = config_defaults.merge(cli_options)
158
162
  defaults = Util.parse_config(config_path(CONF_FILE, options))
159
163
 
160
- if options[:pr]
161
- unless options[:branch]
162
- $stderr.puts 'A branch must be specified with --branch to use --pr!'
163
- raise
164
- end
165
-
166
- @pr = create_pr_manager if options[:pr]
167
- end
168
-
169
164
  local_template_dir = config_path(MODULE_FILES_DIR, options)
170
165
  local_files = find_template_files(local_template_dir)
171
166
  module_files = relative_names(local_files, local_template_dir)
@@ -173,56 +168,99 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
173
168
  errors = false
174
169
  # managed_modules is either an array or a hash
175
170
  managed_modules.each do |puppet_module|
176
- begin
177
- manage_module(puppet_module, module_files, defaults)
178
- rescue ModuleSync::Error, Git::GitExecuteError => e
179
- message = e.message || "Error during '#{options[:command]}'"
180
- $stderr.puts "#{puppet_module.given_name}: #{message}"
181
- exit 1 unless options[:skip_broken]
182
- errors = true
183
- $stdout.puts "Skipping '#{puppet_module.given_name}' as update process failed"
184
- rescue StandardError => e
185
- raise unless options[:skip_broken]
186
- errors = true
187
- $stdout.puts "Skipping '#{puppet_module.given_name}' as update process failed"
188
- end
171
+ manage_module(puppet_module, module_files, defaults)
172
+ rescue ModuleSync::Error, Git::GitExecuteError => e
173
+ message = e.message || 'Error during `update`'
174
+ $stderr.puts "#{puppet_module.given_name}: #{message}"
175
+ exit 1 unless options[:skip_broken]
176
+ errors = true
177
+ $stdout.puts "Skipping '#{puppet_module.given_name}' as update process failed"
178
+ rescue StandardError
179
+ raise unless options[:skip_broken]
180
+
181
+ errors = true
182
+ $stdout.puts "Skipping '#{puppet_module.given_name}' as update process failed"
189
183
  end
190
184
  exit 1 if errors && options[:fail_on_warnings]
191
185
  end
192
186
 
193
- def self.pr(puppet_module)
194
- module_options = puppet_module.options
195
- github_conf = module_options[:github]
196
- gitlab_conf = module_options[:gitlab]
197
-
198
- if !github_conf.nil?
199
- base_url = github_conf[:base_url] || ENV.fetch('GITHUB_BASE_URL', 'https://api.github.com')
200
- require 'modulesync/pr/github'
201
- ModuleSync::PR::GitHub.new(github_conf[:token], base_url)
202
- elsif !gitlab_conf.nil?
203
- base_url = gitlab_conf[:base_url] || ENV.fetch('GITLAB_BASE_URL', 'https://gitlab.com/api/v4')
204
- require 'modulesync/pr/gitlab'
205
- ModuleSync::PR::GitLab.new(gitlab_conf[:token], base_url)
206
- elsif @pr.nil?
207
- $stderr.puts 'No GitHub or GitLab token specified for --pr!'
208
- raise
209
- else
210
- @pr
187
+ def self.clone(cli_options)
188
+ @options = config_defaults.merge(cli_options)
189
+
190
+ managed_modules.each do |puppet_module|
191
+ puppet_module.repository.clone unless puppet_module.repository.cloned?
211
192
  end
212
193
  end
213
194
 
214
- def self.create_pr_manager
215
- github_token = ENV.fetch('GITHUB_TOKEN', '')
216
- gitlab_token = ENV.fetch('GITLAB_TOKEN', '')
195
+ def self.execute(cli_options)
196
+ @options = config_defaults.merge(cli_options)
217
197
 
218
- if !github_token.empty?
219
- require 'modulesync/pr/github'
220
- ModuleSync::PR::GitHub.new(github_token, ENV.fetch('GITHUB_BASE_URL', 'https://api.github.com'))
221
- elsif !gitlab_token.empty?
222
- require 'modulesync/pr/gitlab'
223
- ModuleSync::PR::GitLab.new(gitlab_token, ENV.fetch('GITLAB_BASE_URL', 'https://gitlab.com/api/v4'))
224
- else
225
- warn '--pr specified without environment variables GITHUB_TOKEN or GITLAB_TOKEN'
198
+ errors = {}
199
+ managed_modules.each do |puppet_module|
200
+ $stdout.puts "#{puppet_module.given_name}:"
201
+
202
+ puppet_module.repository.clone unless puppet_module.repository.cloned?
203
+ puppet_module.repository.switch branch: @options[:branch]
204
+
205
+ command_args = cli_options[:command_args]
206
+ local_script = File.expand_path command_args[0]
207
+ command_args[0] = local_script if File.exist?(local_script)
208
+
209
+ # Remove bundler-related env vars to allow the subprocess to run `bundle`
210
+ command_env = ENV.reject { |k, _v| k.match?(/(^BUNDLE|^SOURCE_DATE_EPOCH$|^GEM_|RUBY)/) }
211
+
212
+ result = system command_env, *command_args, unsetenv_others: true, chdir: puppet_module.working_directory
213
+ unless result
214
+ message = "Command execution failed ('#{@options[:command_args].join ' '}': #{$CHILD_STATUS})"
215
+ raise Thor::Error, message if @options[:fail_fast]
216
+
217
+ errors.merge!(
218
+ puppet_module.given_name => message,
219
+ )
220
+ $stderr.puts message
221
+ end
222
+
223
+ $stdout.puts ''
224
+ end
225
+
226
+ unless errors.empty?
227
+ raise Thor::Error, <<~MSG
228
+ Error(s) during `execute` command:
229
+ #{errors.map { |name, message| " * #{name}: #{message}" }.join "\n"}
230
+ MSG
231
+ end
232
+
233
+ exit 1 unless errors.empty?
234
+ end
235
+
236
+ def self.reset(cli_options)
237
+ @options = config_defaults.merge(cli_options)
238
+ if @options[:branch].nil?
239
+ raise Thor::Error,
240
+ "Error: 'branch' option is missing, please set it in configuration or in command line."
241
+ end
242
+
243
+ managed_modules.each do |puppet_module|
244
+ puppet_module.repository.reset_workspace(
245
+ branch: @options[:branch],
246
+ source_branch: @options[:source_branch],
247
+ operate_offline: @options[:offline],
248
+ )
249
+ end
250
+ end
251
+
252
+ def self.push(cli_options)
253
+ @options = config_defaults.merge(cli_options)
254
+
255
+ if @options[:branch].nil?
256
+ raise Thor::Error,
257
+ "Error: 'branch' option is missing, please set it in configuration or in command line."
258
+ end
259
+
260
+ managed_modules.each do |puppet_module|
261
+ puppet_module.repository.push branch: @options[:branch], remote_branch: @options[:remote_branch]
262
+ rescue ModuleSync::Error => e
263
+ raise Thor::Error, "#{puppet_module.given_name}: #{e.message}"
226
264
  end
227
265
  end
228
266
  end
data/modulesync.gemspec CHANGED
@@ -1,14 +1,14 @@
1
- lib = File.expand_path('../lib', __FILE__)
1
+ lib = File.expand_path('lib', __dir__)
2
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
3
 
4
4
  Gem::Specification.new do |spec|
5
5
  spec.name = 'modulesync'
6
- spec.version = '2.1.0'
6
+ spec.version = '2.3.0'
7
7
  spec.authors = ['Vox Pupuli']
8
8
  spec.email = ['voxpupuli@groups.io']
9
9
  spec.summary = 'Puppet Module Synchronizer'
10
10
  spec.description = 'Utility to synchronize common files across puppet modules in Github.'
11
- spec.homepage = 'http://github.com/voxpupuli/modulesync'
11
+ spec.homepage = 'https://github.com/voxpupuli/modulesync'
12
12
  spec.license = 'Apache-2.0'
13
13
  spec.required_ruby_version = '>= 2.5.0'
14
14
 
@@ -17,11 +17,15 @@ Gem::Specification.new do |spec|
17
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
18
  spec.require_paths = ['lib']
19
19
 
20
- spec.add_development_dependency 'aruba', '~> 0.14'
20
+ spec.add_development_dependency 'aruba', '>= 0.14', '< 2'
21
21
  spec.add_development_dependency 'bundler'
22
+ spec.add_development_dependency 'cucumber'
22
23
  spec.add_development_dependency 'rake'
23
24
  spec.add_development_dependency 'rspec'
24
- spec.add_development_dependency 'rubocop', '~> 0.50.0'
25
+ spec.add_development_dependency 'rubocop', '~> 1.2'
26
+ spec.add_development_dependency 'rubocop-rake'
27
+ spec.add_development_dependency 'rubocop-rspec'
28
+ spec.add_development_dependency 'simplecov'
25
29
 
26
30
  spec.add_runtime_dependency 'git', '~>1.7'
27
31
  spec.add_runtime_dependency 'gitlab', '~>4.0'
@@ -77,6 +77,7 @@ module ModuleSync
77
77
  branch ||= default_branch
78
78
  FileUtils.chdir(tmp_repo_dir) do
79
79
  run %W[git checkout #{branch}]
80
+ run %w[git pull --force --prune]
80
81
  File.write filename, content
81
82
  run %W[git add #{filename}]
82
83
  run %w[git commit --message] << "Add file: '#{filename}'"
@@ -94,7 +95,7 @@ module ModuleSync
94
95
  def commit_count_by(author, commit = nil)
95
96
  FileUtils.chdir(bare_repo_dir) do
96
97
  commit ||= '--all'
97
- stdout = run %W[git rev-list #{commit} --author #{author} --count]
98
+ stdout = run %W[git rev-list --author #{author} --count #{commit} --]
98
99
  return Integer(stdout)
99
100
  end
100
101
  end
@@ -105,6 +106,20 @@ module ModuleSync
105
106
  end
106
107
  end
107
108
 
109
+ def delete_branch(branch)
110
+ FileUtils.chdir(bare_repo_dir) do
111
+ run %W{git branch -D #{branch}}
112
+ end
113
+ end
114
+
115
+ def create_branch(branch, from = nil)
116
+ from ||= default_branch
117
+ FileUtils.chdir(tmp_repo_dir) do
118
+ run %W{git branch -c #{from} #{branch}}
119
+ run %W{git push --set-upstream origin #{branch}}
120
+ end
121
+ end
122
+
108
123
  def remote_url
109
124
  "file://#{bare_repo_dir}"
110
125
  end
data/spec/spec_helper.rb CHANGED
@@ -1 +1,3 @@
1
+ require 'simplecov'
2
+
1
3
  require 'modulesync'
@@ -0,0 +1,16 @@
1
+ require 'modulesync'
2
+ require 'modulesync/git_service/factory'
3
+
4
+ describe ModuleSync::GitService::Factory do
5
+ context 'when instantiate a GitHub service without credentials' do
6
+ it 'raises an error' do
7
+ expect { ModuleSync::GitService::Factory.instantiate(type: :github, endpoint: nil, token: nil) }.to raise_error(ModuleSync::GitService::MissingCredentialsError)
8
+ end
9
+ end
10
+
11
+ context 'when instantiate a GitLab service without credentials' do
12
+ it 'raises an error' do
13
+ expect { ModuleSync::GitService::Factory.instantiate(type: :gitlab, endpoint: nil, token: nil) }.to raise_error(ModuleSync::GitService::MissingCredentialsError)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+
3
+ require 'modulesync/git_service/github'
4
+
5
+ describe ModuleSync::GitService::GitHub do
6
+ context '::open_pull_request' do
7
+ before(:each) do
8
+ @client = double()
9
+ allow(Octokit::Client).to receive(:new).and_return(@client)
10
+ @it = ModuleSync::GitService::GitHub.new('test', 'https://api.github.com')
11
+ end
12
+
13
+ let(:args) do
14
+ {
15
+ repo_path: 'test/modulesync',
16
+ namespace: 'test',
17
+ title: 'Test PR is submitted',
18
+ message: 'Hello world',
19
+ source_branch: 'test',
20
+ target_branch: 'master',
21
+ labels: labels,
22
+ noop: false,
23
+ }
24
+ end
25
+
26
+ let(:labels) { [] }
27
+
28
+ it 'submits PR when --pr is set' do
29
+ allow(@client).to receive(:pull_requests)
30
+ .with(args[:repo_path],
31
+ :state => 'open',
32
+ :base => 'master',
33
+ :head => "#{args[:namespace]}:#{args[:source_branch]}"
34
+ ).and_return([])
35
+ expect(@client).to receive(:create_pull_request)
36
+ .with(args[:repo_path],
37
+ 'master',
38
+ args[:source_branch],
39
+ args[:title],
40
+ args[:message]
41
+ ).and_return({"html_url" => "http://example.com/pulls/22"})
42
+ expect { @it.open_pull_request(**args) }.to output(/Submitted PR/).to_stdout
43
+ end
44
+
45
+ it 'skips submitting PR if one has already been issued' do
46
+ pr = {
47
+ "title" => "Test title",
48
+ "html_url" => "https://example.com/pulls/44",
49
+ "number" => "44"
50
+ }
51
+
52
+ expect(@client).to receive(:pull_requests)
53
+ .with(args[:repo_path],
54
+ :state => 'open',
55
+ :base => 'master',
56
+ :head => "#{args[:namespace]}:#{args[:source_branch]}"
57
+ ).and_return([pr])
58
+ expect { @it.open_pull_request(**args) }.to output("Skipped! 1 PRs found for branch 'test'\n").to_stdout
59
+ end
60
+
61
+ context 'when labels are set' do
62
+ let(:labels) { %w{HELLO WORLD} }
63
+
64
+ it 'adds labels to PR' do
65
+ allow(@client).to receive(:create_pull_request).and_return({"html_url" => "http://example.com/pulls/22", "number" => "44"})
66
+ allow(@client).to receive(:pull_requests)
67
+ .with(args[:repo_path],
68
+ :state => 'open',
69
+ :base => 'master',
70
+ :head => "#{args[:namespace]}:#{args[:source_branch]}"
71
+ ).and_return([])
72
+ expect(@client).to receive(:add_labels_to_an_issue)
73
+ .with(args[:repo_path],
74
+ "44",
75
+ ["HELLO", "WORLD"]
76
+ )
77
+ expect { @it.open_pull_request(**args) }.to output(/Attaching the following labels to PR 44: HELLO, WORLD/).to_stdout
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+
3
+ require 'modulesync/git_service/gitlab'
4
+
5
+ describe ModuleSync::GitService::GitLab do
6
+ context '::open_pull_request' do
7
+ before(:each) do
8
+ @client = double()
9
+ allow(Gitlab::Client).to receive(:new).and_return(@client)
10
+ @it = ModuleSync::GitService::GitLab.new('test', 'https://gitlab.com/api/v4')
11
+ end
12
+
13
+ let(:args) do
14
+ {
15
+ repo_path: 'test/modulesync',
16
+ namespace: 'test',
17
+ title: 'Test MR is submitted',
18
+ message: 'Hello world',
19
+ source_branch: 'test',
20
+ target_branch: 'master',
21
+ labels: labels,
22
+ noop: false,
23
+ }
24
+ end
25
+
26
+ let(:labels) { [] }
27
+
28
+ it 'submits MR when --pr is set' do
29
+ allow(@client).to receive(:merge_requests)
30
+ .with(args[:repo_path],
31
+ :state => 'opened',
32
+ :source_branch => args[:source_branch],
33
+ :target_branch => 'master',
34
+ ).and_return([])
35
+
36
+ expect(@client).to receive(:create_merge_request)
37
+ .with(args[:repo_path],
38
+ args[:title],
39
+ :labels => [],
40
+ :source_branch => args[:source_branch],
41
+ :target_branch => 'master',
42
+ ).and_return({"html_url" => "http://example.com/pulls/22"})
43
+
44
+ expect { @it.open_pull_request(**args) }.to output(/Submitted MR/).to_stdout
45
+ end
46
+
47
+ it 'skips submitting MR if one has already been issued' do
48
+ mr = {
49
+ "title" => "Test title",
50
+ "html_url" => "https://example.com/pulls/44",
51
+ "iid" => "44"
52
+ }
53
+
54
+ expect(@client).to receive(:merge_requests)
55
+ .with(args[:repo_path],
56
+ :state => 'opened',
57
+ :source_branch => args[:source_branch],
58
+ :target_branch => 'master',
59
+ ).and_return([mr])
60
+
61
+ expect { @it.open_pull_request(**args) }.to output("Skipped! 1 MRs found for branch 'test'\n").to_stdout
62
+ end
63
+
64
+ context 'when labels are set' do
65
+ let(:labels) { %w{HELLO WORLD} }
66
+
67
+ it 'adds labels to MR' do
68
+ mr = double()
69
+ allow(mr).to receive(:iid).and_return("42")
70
+
71
+ expect(@client).to receive(:create_merge_request)
72
+ .with(args[:repo_path],
73
+ args[:title],
74
+ :labels => ["HELLO", "WORLD"],
75
+ :source_branch => args[:source_branch],
76
+ :target_branch => 'master',
77
+ ).and_return(mr)
78
+
79
+ allow(@client).to receive(:merge_requests)
80
+ .with(args[:repo_path],
81
+ :state => 'opened',
82
+ :source_branch => args[:source_branch],
83
+ :target_branch => 'master',
84
+ ).and_return([])
85
+
86
+ expect { @it.open_pull_request(**args) }.to output(/Attached the following labels to MR 42: HELLO, WORLD/).to_stdout
87
+ end
88
+ end
89
+ end
90
+ end