modulesync 2.1.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
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