r10k 3.8.0 → 3.9.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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/docker.yml +4 -1
  3. data/.github/workflows/release.yml +3 -2
  4. data/.github/workflows/rspec_tests.yml +1 -1
  5. data/.travis.yml +8 -1
  6. data/CHANGELOG.mkd +10 -0
  7. data/CODEOWNERS +1 -1
  8. data/doc/common-patterns.mkd +1 -0
  9. data/doc/dynamic-environments/configuration.mkd +90 -43
  10. data/doc/dynamic-environments/usage.mkd +7 -7
  11. data/doc/puppetfile.mkd +23 -3
  12. data/docker/Gemfile +1 -1
  13. data/docker/Makefile +4 -3
  14. data/docker/docker-compose.yml +18 -0
  15. data/docker/r10k/Dockerfile +1 -1
  16. data/docker/r10k/docker-entrypoint.sh +0 -1
  17. data/docker/r10k/release.Dockerfile +1 -1
  18. data/docker/spec/dockerfile_spec.rb +26 -32
  19. data/integration/tests/git_source/git_source_repeated_remote.rb +2 -2
  20. data/integration/tests/user_scenario/basic_workflow/multi_env_custom_forge_git_module.rb +2 -1
  21. data/integration/tests/user_scenario/basic_workflow/multi_env_custom_forge_git_module_static.rb +2 -1
  22. data/integration/tests/user_scenario/basic_workflow/multi_source_custom_forge_git_module.rb +1 -1
  23. data/integration/tests/user_scenario/basic_workflow/single_env_custom_forge_git_module.rb +2 -1
  24. data/lib/r10k/action/deploy/display.rb +9 -3
  25. data/lib/r10k/action/deploy/environment.rb +36 -14
  26. data/lib/r10k/cli/deploy.rb +5 -3
  27. data/lib/r10k/environment/base.rb +1 -1
  28. data/lib/r10k/environment/git.rb +17 -2
  29. data/lib/r10k/environment/name.rb +22 -4
  30. data/lib/r10k/environment/svn.rb +11 -2
  31. data/lib/r10k/environment/with_modules.rb +1 -1
  32. data/lib/r10k/git/rugged/credentials.rb +22 -15
  33. data/lib/r10k/module/forge.rb +15 -3
  34. data/lib/r10k/module/git.rb +24 -23
  35. data/lib/r10k/module/local.rb +1 -1
  36. data/lib/r10k/module/svn.rb +14 -11
  37. data/lib/r10k/settings.rb +6 -1
  38. data/lib/r10k/source/base.rb +5 -0
  39. data/lib/r10k/source/git.rb +4 -1
  40. data/lib/r10k/source/hash.rb +4 -2
  41. data/lib/r10k/source/svn.rb +5 -1
  42. data/lib/r10k/util/setopts.rb +33 -12
  43. data/lib/r10k/version.rb +1 -1
  44. data/locales/r10k.pot +22 -18
  45. data/r10k.gemspec +1 -1
  46. data/spec/unit/action/deploy/display_spec.rb +4 -0
  47. data/spec/unit/action/deploy/environment_spec.rb +111 -10
  48. data/spec/unit/environment/git_spec.rb +16 -0
  49. data/spec/unit/environment/name_spec.rb +28 -0
  50. data/spec/unit/environment/svn_spec.rb +12 -0
  51. data/spec/unit/git/rugged/credentials_spec.rb +10 -0
  52. data/spec/unit/module/forge_spec.rb +6 -0
  53. data/spec/unit/module/git_spec.rb +1 -1
  54. data/spec/unit/module_spec.rb +59 -9
  55. data/spec/unit/util/setopts_spec.rb +25 -1
  56. metadata +5 -11
  57. data/azure-pipelines.yml +0 -87
data/r10k.gemspec CHANGED
@@ -23,7 +23,7 @@ Gem::Specification.new do |s|
23
23
  s.license = 'Apache-2.0'
24
24
 
25
25
  s.add_dependency 'colored2', '3.1.2'
26
- s.add_dependency 'cri', ['>= 2.15.10', '< 3.0.0']
26
+ s.add_dependency 'cri', '2.15.10'
27
27
 
28
28
  s.add_dependency 'log4r', '1.1.10'
29
29
  s.add_dependency 'multi_json', '~> 1.10'
@@ -8,6 +8,10 @@ describe R10K::Action::Deploy::Display do
8
8
  described_class.new({puppetfile: true}, [])
9
9
  end
10
10
 
11
+ it "accepts a modules option" do
12
+ described_class.new({modules: true}, [])
13
+ end
14
+
11
15
  it "accepts a detail option" do
12
16
  described_class.new({detail: true}, [])
13
17
  end
@@ -19,6 +19,10 @@ describe R10K::Action::Deploy::Environment do
19
19
  described_class.new({puppetfile: true}, [])
20
20
  end
21
21
 
22
+ it "can accept a modules option" do
23
+ described_class.new({modules: true}, [])
24
+ end
25
+
22
26
  it "can accept a default_branch_override option" do
23
27
  described_class.new({:'default-branch-override' => 'default_branch_override_name'}, [])
24
28
  end
@@ -42,6 +46,17 @@ describe R10K::Action::Deploy::Environment do
42
46
  it 'can accept a token option' do
43
47
  described_class.new({ 'oauth-token': '/nonexistent' }, [])
44
48
  end
49
+
50
+ describe "initializing errors" do
51
+ let (:settings) { { deploy: { purge_levels: [:environment],
52
+ purge_whitelist: ['coolfile', 'coolfile2'],
53
+ purge_allowlist: ['anothercoolfile']}}}
54
+
55
+ subject { described_class.new({config: "/some/nonexistent/path"}, [], settings)}
56
+ it 'errors out when both purge_whitelist and purge_allowlist are set' do
57
+ expect{subject}.to raise_error(R10K::Error, /Values found for both purge_whitelist and purge_allowlist./)
58
+ end
59
+ end
45
60
  end
46
61
 
47
62
  describe "when called" do
@@ -58,6 +73,29 @@ describe R10K::Action::Deploy::Environment do
58
73
  )
59
74
  end
60
75
 
76
+ describe "with puppetfile or modules flag" do
77
+ let(:deployment) { R10K::Deployment.new(mock_config) }
78
+ let(:puppetfile) { instance_double("R10K::Puppetfile", modules: []).as_null_object }
79
+
80
+ before do
81
+ expect(R10K::Deployment).to receive(:new).and_return(deployment)
82
+ expect(R10K::Puppetfile).to receive(:new).and_return(puppetfile).at_least(:once)
83
+ end
84
+
85
+ it "syncs the puppetfile when given the puppetfile flag" do
86
+ expect(puppetfile).to receive(:accept).and_return([])
87
+ action = described_class.new({config: "/some/nonexistent/path", puppetfile: true}, [])
88
+ action.call
89
+ end
90
+
91
+ it "syncs the puppetfile when given the modules flag" do
92
+ expect(puppetfile).to receive(:accept).and_return([])
93
+ action = described_class.new({config: "/some/nonexistent/path", modules: true}, [])
94
+ action.call
95
+ end
96
+
97
+ end
98
+
61
99
  describe "with an environment that doesn't exist" do
62
100
  let(:deployment) do
63
101
  R10K::Deployment.new(mock_config)
@@ -77,7 +115,7 @@ describe R10K::Action::Deploy::Environment do
77
115
  end
78
116
 
79
117
  describe "with no-force" do
80
- subject { described_class.new({ config: "/some/nonexistent/path", puppetfile: true, :'no-force' => true}, %w[first]) }
118
+ subject { described_class.new({ config: "/some/nonexistent/path", modules: true, :'no-force' => true}, %w[first]) }
81
119
 
82
120
  it "tries to preserve local modifications" do
83
121
  expect(subject.force).to equal(false)
@@ -175,6 +213,37 @@ describe R10K::Action::Deploy::Environment do
175
213
 
176
214
  end
177
215
 
216
+ describe "Purging white/allowlist" do
217
+
218
+ let(:settings) { { deploy: { purge_levels: [:environment], purge_allowlist: ['coolfile', 'coolfile2'] } } }
219
+
220
+ let(:deployment) do
221
+ R10K::Deployment.new(mock_config.merge(settings))
222
+ end
223
+
224
+ before do
225
+ expect(R10K::Deployment).to receive(:new).and_return(deployment)
226
+ end
227
+
228
+ subject { described_class.new({ config: "/some/nonexistent/path", modules: true }, %w[PREFIX_first], settings) }
229
+
230
+ it "reads in the purge_allowlist setting and purges accordingly" do
231
+ expect(subject.logger).to receive(:debug).with(/purging unmanaged content for environment/i)
232
+ expect(subject.instance_variable_get(:@user_purge_allowlist)).to eq(['coolfile', 'coolfile2'])
233
+ subject.call
234
+ end
235
+
236
+ describe "purge_whitelist" do
237
+ let (:settings) { { deploy: { purge_levels: [:environment], purge_whitelist: ['coolfile', 'coolfile2'] } } }
238
+
239
+ it "reads in the purge_whitelist setting and still sets it to purge_allowlist and purges accordingly" do
240
+ expect(subject.logger).to receive(:debug).with(/purging unmanaged content for environment/i)
241
+ expect(subject.instance_variable_get(:@user_purge_allowlist)).to eq(['coolfile', 'coolfile2'])
242
+ subject.call
243
+ end
244
+ end
245
+ end
246
+
178
247
  describe "purge_levels" do
179
248
  let(:settings) { { deploy: { purge_levels: purge_levels } } }
180
249
 
@@ -186,7 +255,7 @@ describe R10K::Action::Deploy::Environment do
186
255
  expect(R10K::Deployment).to receive(:new).and_return(deployment)
187
256
  end
188
257
 
189
- subject { described_class.new({ config: "/some/nonexistent/path", puppetfile: true }, %w[PREFIX_first], settings) }
258
+ subject { described_class.new({ config: "/some/nonexistent/path", modules: true }, %w[PREFIX_first], settings) }
190
259
 
191
260
  describe "deployment purge level" do
192
261
  let(:purge_levels) { [:deployment] }
@@ -262,7 +331,7 @@ describe R10K::Action::Deploy::Environment do
262
331
  described_class.new(
263
332
  {
264
333
  config: '/some/nonexistent/path',
265
- puppetfile: true,
334
+ modules: true,
266
335
  'generate-types': true
267
336
  },
268
337
  %w[first second]
@@ -316,7 +385,7 @@ describe R10K::Action::Deploy::Environment do
316
385
  described_class.new(
317
386
  {
318
387
  config: '/some/nonexistent/path',
319
- puppetfile: true,
388
+ modules: true,
320
389
  'generate-types': false
321
390
  },
322
391
  %w[first]
@@ -390,9 +459,25 @@ describe R10K::Action::Deploy::Environment do
390
459
 
391
460
  let(:mock_stateful_repo_1) { instance_double("R10K::Git::StatefulRepository", :head => "123456") }
392
461
  let(:mock_stateful_repo_2) { instance_double("R10K::Git::StatefulRepository", :head => "654321") }
393
- let(:mock_git_module_1) { instance_double("R10K::Module::Git", :name => "my_cool_module", :version => "1.0", :repo => mock_stateful_repo_1) }
394
- let(:mock_git_module_2) { instance_double("R10K::Module::Git", :name => "my_lame_module", :version => "0.0.1", :repo => mock_stateful_repo_2) }
395
- let(:mock_forge_module_1) { double(:name => "their_shiny_module", :version => "2.0.0") }
462
+ let(:mock_git_module_1) do
463
+ instance_double("R10K::Module::Git",
464
+ :name => "my_cool_module",
465
+ :properties => {
466
+ :type => :git,
467
+ :expected => "1.0",
468
+ :actual => mock_stateful_repo_1.head
469
+ })
470
+ end
471
+ let(:mock_git_module_2) do
472
+ instance_double("R10K::Module::Git",
473
+ :name => "my_uncool_module",
474
+ :properties => {
475
+ :type => :git,
476
+ :expected => "0.0.1",
477
+ :actual => mock_stateful_repo_2.head
478
+ })
479
+ end
480
+ let(:mock_forge_module_1) { double(:name => "their_shiny_module", :properties => { :expected => "2.0.0" }) }
396
481
  let(:mock_puppetfile) { instance_double("R10K::Puppetfile", :modules => [mock_git_module_1, mock_git_module_2, mock_forge_module_1]) }
397
482
 
398
483
  before(:all) do
@@ -405,9 +490,8 @@ describe R10K::Action::Deploy::Environment do
405
490
  Dir.delete(@tmp_path)
406
491
  end
407
492
 
408
- it "writes the .r10k-deploy file correctly" do
493
+ it "writes the .r10k-deploy file correctly if all goes well" do
409
494
  allow(R10K::Puppetfile).to receive(:new).and_return(mock_puppetfile)
410
- allow(mock_forge_module_1).to receive(:repo).and_raise(NoMethodError)
411
495
 
412
496
  fake_env = Fake_Environment.new(@tmp_path, {:name => "my_cool_environment", :signature => "pablo picasso"})
413
497
  allow(fake_env).to receive(:modules).and_return(mock_puppetfile.modules)
@@ -424,13 +508,30 @@ describe R10K::Action::Deploy::Environment do
424
508
  expect(r10k_deploy['module_deploys'][0]['name']).to eq("my_cool_module")
425
509
  expect(r10k_deploy['module_deploys'][0]['version']).to eq("1.0")
426
510
  expect(r10k_deploy['module_deploys'][0]['sha']).to eq("123456")
427
- expect(r10k_deploy['module_deploys'][1]['name']).to eq("my_lame_module")
511
+ expect(r10k_deploy['module_deploys'][1]['name']).to eq("my_uncool_module")
428
512
  expect(r10k_deploy['module_deploys'][1]['version']).to eq("0.0.1")
429
513
  expect(r10k_deploy['module_deploys'][1]['sha']).to eq("654321")
430
514
  expect(r10k_deploy['module_deploys'][2]['name']).to eq("their_shiny_module")
431
515
  expect(r10k_deploy['module_deploys'][2]['version']).to eq("2.0.0")
432
516
  expect(r10k_deploy['module_deploys'][2]['sha']).to eq(nil)
517
+ end
433
518
 
519
+ it "writes the .r10k-deploy file correctly if there's a failure" do
520
+ allow(R10K::Puppetfile).to receive(:new).and_return(mock_puppetfile)
521
+
522
+ fake_env = Fake_Environment.new(@tmp_path, {:name => "my_cool_environment", :signature => "pablo picasso"})
523
+ allow(fake_env).to receive(:modules).and_return(mock_puppetfile.modules)
524
+ allow(mock_forge_module_1).to receive(:properties).and_raise(StandardError)
525
+ subject.send(:write_environment_info!, fake_env, "2019-01-01 23:23:22 +0000", true)
526
+
527
+ file_contents = File.read("#{@tmp_path}/.r10k-deploy.json")
528
+ r10k_deploy = JSON.parse(file_contents)
529
+
530
+ expect(r10k_deploy['name']).to eq("my_cool_environment")
531
+ expect(r10k_deploy['signature']).to eq("pablo picasso")
532
+ expect(r10k_deploy['started_at']).to eq("2019-01-01 23:23:22 +0000")
533
+ expect(r10k_deploy['deploy_success']).to eq(true)
534
+ expect(r10k_deploy['module_deploys'].length).to eq(0)
434
535
  end
435
536
  end
436
537
  end
@@ -15,6 +15,22 @@ describe R10K::Environment::Git do
15
15
  )
16
16
  end
17
17
 
18
+ describe "initializing" do
19
+ subject do
20
+ described_class.new('name', '/dir', 'ref', {
21
+ :remote => 'url',
22
+ :ref => 'value',
23
+ :puppetfile_name => 'Puppetfile',
24
+ :moduledir => 'modules',
25
+ :modules => { },
26
+ })
27
+ end
28
+
29
+ it "accepts valid base class initialization arguments" do
30
+ expect(subject.name).to eq 'name'
31
+ end
32
+ end
33
+
18
34
  describe "storing attributes" do
19
35
  it "can return the environment name" do
20
36
  expect(subject.name).to eq 'myenv'
@@ -2,6 +2,34 @@ require 'spec_helper'
2
2
  require 'r10k/environment/name'
3
3
 
4
4
  describe R10K::Environment::Name do
5
+ describe "strip_component" do
6
+ it "does not modify the given name when no strip_component is given" do
7
+ bn = described_class.new('myenv', source: 'source', prefix: false)
8
+ expect(bn.dirname).to eq 'myenv'
9
+ end
10
+
11
+ it "removes the first occurance of a regex match when a regex is given" do
12
+ bn = described_class.new('myenv', source: 'source', prefix: false, strip_component: '/env/')
13
+ expect(bn.dirname).to eq 'my'
14
+ end
15
+
16
+ it "does not modify the given name when there is no regex match" do
17
+ bn = described_class.new('myenv', source: 'source', prefix: false, strip_component: '/bar/')
18
+ expect(bn.dirname).to eq 'myenv'
19
+ end
20
+
21
+ it "removes the given name's prefix when it matches strip_component" do
22
+ bn = described_class.new('env/prod', source: 'source', prefix: false, strip_component: 'env/')
23
+ expect(bn.dirname).to eq 'prod'
24
+ end
25
+
26
+ it "raises an error when given an integer" do
27
+ expect {
28
+ described_class.new('env/prod', source: 'source', prefix: false, strip_component: 4)
29
+ }.to raise_error(%r{Improper.*"4"})
30
+ end
31
+ end
32
+
5
33
  describe "prefixing" do
6
34
  it "uses the branch name as the dirname when prefixing is off" do
7
35
  bn = described_class.new('mybranch', :source => 'source', :prefix => false)
@@ -16,6 +16,18 @@ describe R10K::Environment::SVN do
16
16
 
17
17
  let(:working_dir) { subject.working_dir }
18
18
 
19
+ describe "initializing" do
20
+ subject do
21
+ described_class.new('name', '/dir', 'ref', {
22
+ :puppetfile_name => 'Puppetfile',
23
+ })
24
+ end
25
+
26
+ it "accepts valid base class initialization arguments" do
27
+ expect(subject.name).to eq 'name'
28
+ end
29
+ end
30
+
19
31
  describe "storing attributes" do
20
32
  it "can return the environment name" do
21
33
  expect(subject.name).to eq 'myenv'
@@ -143,6 +143,16 @@ describe R10K::Git::Rugged::Credentials, :unless => R10K::Util::Platform.jruby?
143
143
  expect(creds.instance_variable_get(:@password)).to eq("my_token")
144
144
  expect(creds.instance_variable_get(:@username)).to eq("x-oauth-token")
145
145
  end
146
+
147
+ it 'only reads the token in once' do
148
+ expect($stdin).to receive(:read).and_return("my_token").once
149
+ R10K::Git.settings[:oauth_token] = '-'
150
+ R10K::Git.settings[:repositories] = [{remote: "https://tessier-ashpool.freeside/repo.git"}]
151
+ creds = subject.get_plaintext_credentials("https://tessier-ashpool.freeside/repo.git", nil)
152
+ expect(creds.instance_variable_get(:@password)).to eq("my_token")
153
+ creds = subject.get_plaintext_credentials("https://tessier-ashpool.freeside/repo.git", nil)
154
+ expect(creds.instance_variable_get(:@password)).to eq("my_token")
155
+ end
146
156
  end
147
157
 
148
158
  describe "generating default credentials" do
@@ -23,6 +23,12 @@ describe R10K::Module::Forge do
23
23
  end
24
24
  end
25
25
 
26
+ describe "implementing the standard options interface" do
27
+ it "should implement {type: forge}" do
28
+ expect(described_class).to be_implement('branan-eight_hundred', {type: 'forge', version: '8.0.0', source: 'not implemented'})
29
+ end
30
+ end
31
+
26
32
  describe "setting attributes" do
27
33
  subject { described_class.new('branan/eight_hundred', '/moduledir', '8.0.0') }
28
34
 
@@ -123,7 +123,7 @@ describe R10K::Module::Git do
123
123
  let(:opts) { { unrecognized: true } }
124
124
 
125
125
  it "raises an error" do
126
- expect { test_module(opts) }.to raise_error(ArgumentError, /unhandled options.*unrecognized/i)
126
+ expect { test_module(opts) }.to raise_error(ArgumentError, /cannot handle option 'unrecognized'/)
127
127
  end
128
128
  end
129
129
 
@@ -3,20 +3,70 @@ require 'r10k/module'
3
3
 
4
4
  describe R10K::Module do
5
5
  describe 'delegating to R10K::Module::Git' do
6
- it "accepts args {:git => 'git url}" do
7
- obj = R10K::Module.new('foo', '/modulepath', :git => 'git url')
8
- expect(obj).to be_a_kind_of(R10K::Module::Git)
6
+ [ {git: 'git url'},
7
+ {type: 'git', source: 'git url'},
8
+ ].each do |scenario|
9
+ it "accepts a name matching 'test' and args #{scenario.inspect}" do
10
+ obj = R10K::Module.new('test', '/modulepath', scenario)
11
+ expect(obj).to be_a_kind_of(R10K::Module::Git)
12
+ expect(obj.send(:instance_variable_get, :'@remote')).to eq('git url')
13
+ end
14
+ end
15
+ end
16
+
17
+ describe 'delegating to R10K::Module::Svn' do
18
+ [ {svn: 'svn url'},
19
+ {type: 'svn', source: 'svn url'},
20
+ ].each do |scenario|
21
+ it "accepts a name matching 'test' and args #{scenario.inspect}" do
22
+ obj = R10K::Module.new('test', '/modulepath', scenario)
23
+ expect(obj).to be_a_kind_of(R10K::Module::SVN)
24
+ expect(obj.send(:instance_variable_get, :'@url')).to eq('svn url')
25
+ end
9
26
  end
10
27
  end
11
28
 
12
29
  describe 'delegating to R10K::Module::Forge' do
13
- [
14
- ['bar/quux', nil],
15
- ['bar-quux', nil],
16
- ['bar/quux', '8.0.0'],
30
+ [ 'bar/quux',
31
+ 'bar-quux',
17
32
  ].each do |scenario|
18
- it "accepts a name matching #{scenario[0]} and args #{scenario[1].inspect}" do
19
- expect(R10K::Module.new(scenario[0], '/modulepath', scenario[1])).to be_a_kind_of(R10K::Module::Forge)
33
+ it "accepts a name matching #{scenario} and args nil" do
34
+ obj = R10K::Module.new(scenario, '/modulepath', nil)
35
+ expect(obj).to be_a_kind_of(R10K::Module::Forge)
36
+ end
37
+ end
38
+ [ '8.0.0',
39
+ {type: 'forge', version: '8.0.0'},
40
+ ].each do |scenario|
41
+ it "accepts a name matching bar-quux and args #{scenario.inspect}" do
42
+ obj = R10K::Module.new('bar-quux', '/modulepath', scenario)
43
+ expect(obj).to be_a_kind_of(R10K::Module::Forge)
44
+ expect(obj.send(:instance_variable_get, :'@expected_version')).to eq('8.0.0')
45
+ end
46
+ end
47
+
48
+ describe 'when the module is ostensibly on disk' do
49
+ before do
50
+ owner = 'theowner'
51
+ module_name = 'themodulename'
52
+ @title = "#{owner}-#{module_name}"
53
+ metadata = <<~METADATA
54
+ {
55
+ "name": "#{@title}",
56
+ "version": "1.2.0"
57
+ }
58
+ METADATA
59
+ @dirname = Dir.mktmpdir
60
+ module_path = File.join(@dirname, module_name)
61
+ FileUtils.mkdir(module_path)
62
+ File.open("#{module_path}/metadata.json", 'w') do |file|
63
+ file.write(metadata)
64
+ end
65
+ end
66
+
67
+ it 'sets the expected version to what is found in the metadata' do
68
+ obj = R10K::Module.new(@title, @dirname, nil)
69
+ expect(obj.send(:instance_variable_get, :'@expected_version')).to eq('1.2.0')
20
70
  end
21
71
  end
22
72
  end
@@ -10,7 +10,10 @@ describe R10K::Util::Setopts do
10
10
 
11
11
  def initialize(opts = {})
12
12
  setopts(opts, {
13
- :valid => :self, :alsovalid => :self, :truthyvalid => true,
13
+ :valid => :self,
14
+ :duplicate => :valid,
15
+ :alsovalid => :self,
16
+ :truthyvalid => true,
14
17
  :validalias => :valid,
15
18
  :ignoreme => nil
16
19
  })
@@ -53,7 +56,28 @@ describe R10K::Util::Setopts do
53
56
  }.to raise_error(ArgumentError, /cannot handle option 'notvalid'/)
54
57
  end
55
58
 
59
+ it "warns when given an unhandled option and raise_on_unhandled=false" do
60
+ test = Class.new { include R10K::Util::Setopts }.new
61
+ allow(test).to receive(:logger).and_return(spy)
62
+
63
+ test.send(:setopts, {valid: :value, invalid: :value},
64
+ {valid: :self},
65
+ raise_on_unhandled: false)
66
+
67
+ expect(test.logger).to have_received(:warn).with(%r{cannot handle option 'invalid'})
68
+ end
69
+
56
70
  it "ignores values that are marked as unhandled" do
57
71
  klass.new(:ignoreme => "IGNORE ME!")
58
72
  end
73
+
74
+ it "warns when given conflicting options" do
75
+ test = Class.new { include R10K::Util::Setopts }.new
76
+ allow(test).to receive(:logger).and_return(spy)
77
+
78
+ test.send(:setopts, {valid: :one, duplicate: :two},
79
+ {valid: :arg, duplicate: :arg})
80
+
81
+ expect(test.logger).to have_received(:warn).with(%r{valid.*duplicate.*conflict.*not both})
82
+ end
59
83
  end