r10k 3.9.3 → 3.10.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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rspec_tests.yml +1 -1
  3. data/CHANGELOG.mkd +9 -0
  4. data/doc/dynamic-environments/configuration.mkd +7 -0
  5. data/integration/Rakefile +1 -1
  6. data/integration/tests/user_scenario/basic_workflow/negative/neg_specify_deleted_forge_module.rb +3 -9
  7. data/integration/tests/user_scenario/basic_workflow/single_env_purge_unmanaged_modules.rb +8 -14
  8. data/lib/r10k/action/deploy/environment.rb +3 -0
  9. data/lib/r10k/action/runner.rb +11 -6
  10. data/lib/r10k/git/cache.rb +1 -1
  11. data/lib/r10k/initializers.rb +7 -0
  12. data/lib/r10k/module/forge.rb +5 -1
  13. data/lib/r10k/module_loader/puppetfile.rb +195 -0
  14. data/lib/r10k/module_loader/puppetfile/dsl.rb +37 -0
  15. data/lib/r10k/puppetfile.rb +77 -153
  16. data/lib/r10k/settings.rb +3 -0
  17. data/lib/r10k/source/base.rb +10 -0
  18. data/lib/r10k/source/git.rb +5 -0
  19. data/lib/r10k/source/svn.rb +4 -0
  20. data/lib/r10k/util/purgeable.rb +70 -8
  21. data/lib/r10k/version.rb +1 -1
  22. data/locales/r10k.pot +37 -33
  23. data/spec/fixtures/unit/action/r10k_forge_auth.yaml +4 -0
  24. data/spec/fixtures/unit/action/r10k_forge_auth_no_url.yaml +3 -0
  25. data/spec/fixtures/unit/util/purgeable/managed_one/managed_subdir_1/managed_subdir_2/ignored_1 +0 -0
  26. data/spec/fixtures/unit/util/purgeable/managed_two/.hidden/unmanaged_3 +0 -0
  27. data/spec/unit/action/deploy/environment_spec.rb +9 -0
  28. data/spec/unit/action/deploy/module_spec.rb +38 -14
  29. data/spec/unit/action/runner_spec.rb +49 -25
  30. data/spec/unit/git/cache_spec.rb +14 -0
  31. data/spec/unit/module/forge_spec.rb +8 -1
  32. data/spec/unit/module_loader/puppetfile_spec.rb +330 -0
  33. data/spec/unit/puppetfile_spec.rb +99 -193
  34. data/spec/unit/settings_spec.rb +6 -2
  35. data/spec/unit/util/purgeable_spec.rb +38 -6
  36. metadata +10 -3
data/lib/r10k/version.rb CHANGED
@@ -2,5 +2,5 @@ module R10K
2
2
  # When updating to a new major (X) or minor (Y) version, include `#major` or
3
3
  # `#minor` (respectively) in your commit message to trigger the appropriate
4
4
  # release. Otherwise, a new patch (Z) version will be released.
5
- VERSION = '3.9.3'
5
+ VERSION = '3.10.0'
6
6
  end
data/locales/r10k.pot CHANGED
@@ -6,11 +6,11 @@
6
6
  #, fuzzy
7
7
  msgid ""
8
8
  msgstr ""
9
- "Project-Id-Version: r10k 3.4.1-231-g5574915\n"
9
+ "Project-Id-Version: r10k 3.9.3-10-gfedc81a\n"
10
10
  "\n"
11
11
  "Report-Msgid-Bugs-To: docs@puppetlabs.com\n"
12
- "POT-Creation-Date: 2021-05-10 23:18+0000\n"
13
- "PO-Revision-Date: 2021-05-10 23:18+0000\n"
12
+ "POT-Creation-Date: 2021-07-07 21:13+0000\n"
13
+ "PO-Revision-Date: 2021-07-07 21:13+0000\n"
14
14
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
15
15
  "Language-Team: LANGUAGE <LL@li.org>\n"
16
16
  "Language: \n"
@@ -31,31 +31,31 @@ msgstr ""
31
31
  msgid "Reason: %{write_lock}"
32
32
  msgstr ""
33
33
 
34
- #: ../lib/r10k/action/deploy/environment.rb:109
34
+ #: ../lib/r10k/action/deploy/environment.rb:112
35
35
  msgid "Environment(s) \\'%{environments}\\' cannot be found in any source and will not be deployed."
36
36
  msgstr ""
37
37
 
38
- #: ../lib/r10k/action/deploy/environment.rb:139
38
+ #: ../lib/r10k/action/deploy/environment.rb:145
39
39
  msgid "Environment %{env_dir} does not match environment name filter, skipping"
40
40
  msgstr ""
41
41
 
42
- #: ../lib/r10k/action/deploy/environment.rb:147
42
+ #: ../lib/r10k/action/deploy/environment.rb:153
43
43
  msgid "Deploying environment %{env_path}"
44
44
  msgstr ""
45
45
 
46
- #: ../lib/r10k/action/deploy/environment.rb:150
46
+ #: ../lib/r10k/action/deploy/environment.rb:156
47
47
  msgid "Environment %{env_dir} is now at %{env_signature}"
48
48
  msgstr ""
49
49
 
50
- #: ../lib/r10k/action/deploy/environment.rb:154
50
+ #: ../lib/r10k/action/deploy/environment.rb:160
51
51
  msgid "Environment %{env_dir} is new, updating all modules"
52
52
  msgstr ""
53
53
 
54
- #: ../lib/r10k/action/deploy/module.rb:78
54
+ #: ../lib/r10k/action/deploy/module.rb:81
55
55
  msgid "Only updating modules in environment(s) %{opt_env} skipping environment %{env_path}"
56
56
  msgstr ""
57
57
 
58
- #: ../lib/r10k/action/deploy/module.rb:80
58
+ #: ../lib/r10k/action/deploy/module.rb:83
59
59
  msgid "Updating modules %{modules} in environment %{env_path}"
60
60
  msgstr ""
61
61
 
@@ -75,15 +75,15 @@ msgstr ""
75
75
  msgid "No config file explicitly given and no default config file could be found, default settings will be used."
76
76
  msgstr ""
77
77
 
78
- #: ../lib/r10k/content_synchronizer.rb:13
78
+ #: ../lib/r10k/content_synchronizer.rb:27
79
79
  msgid "Updating modules with %{pool_size} threads"
80
80
  msgstr ""
81
81
 
82
- #: ../lib/r10k/content_synchronizer.rb:24
82
+ #: ../lib/r10k/content_synchronizer.rb:37
83
83
  msgid "Error during concurrent deploy of a module: %{message}"
84
84
  msgstr ""
85
85
 
86
- #: ../lib/r10k/content_synchronizer.rb:52
86
+ #: ../lib/r10k/content_synchronizer.rb:75
87
87
  msgid "Module thread %{id} exiting: %{message}"
88
88
  msgstr ""
89
89
 
@@ -95,7 +95,7 @@ msgstr ""
95
95
  msgid "Unable to load sources; the supplied configuration does not define the 'sources' key"
96
96
  msgstr ""
97
97
 
98
- #: ../lib/r10k/environment/base.rb:65 ../lib/r10k/environment/base.rb:81 ../lib/r10k/environment/base.rb:90 ../lib/r10k/source/base.rb:69
98
+ #: ../lib/r10k/environment/base.rb:68 ../lib/r10k/environment/base.rb:84 ../lib/r10k/environment/base.rb:93 ../lib/r10k/source/base.rb:83
99
99
  msgid "%{class} has not implemented method %{method}"
100
100
  msgstr ""
101
101
 
@@ -103,15 +103,15 @@ msgstr ""
103
103
  msgid "Improper configuration value given for strip_component setting in %{src} source. Value must be a string, a /regex/, false, or omitted. Got \"%{val}\" (%{type})"
104
104
  msgstr ""
105
105
 
106
- #: ../lib/r10k/environment/with_modules.rb:60
106
+ #: ../lib/r10k/environment/with_modules.rb:57
107
107
  msgid "Environment and %{src} both define the \"%{name}\" module"
108
108
  msgstr ""
109
109
 
110
- #: ../lib/r10k/environment/with_modules.rb:61
110
+ #: ../lib/r10k/environment/with_modules.rb:58
111
111
  msgid "#{msg_error}. The %{src} definition will be ignored"
112
112
  msgstr ""
113
113
 
114
- #: ../lib/r10k/environment/with_modules.rb:71
114
+ #: ../lib/r10k/environment/with_modules.rb:68
115
115
  msgid "Unexpected value for `module_conflicts` setting in %{env} environment: %{val}"
116
116
  msgstr ""
117
117
 
@@ -331,11 +331,15 @@ msgstr ""
331
331
  msgid "Module name (%{title}) must match either 'modulename' or 'owner/modulename'"
332
332
  msgstr ""
333
333
 
334
- #: ../lib/r10k/module/forge.rb:88 ../lib/r10k/module/forge.rb:117
334
+ #: ../lib/r10k/module/forge.rb:89
335
+ msgid "The module %{title} does not appear to have any published releases, cannot determine latest version."
336
+ msgstr ""
337
+
338
+ #: ../lib/r10k/module/forge.rb:92 ../lib/r10k/module/forge.rb:121
335
339
  msgid "The module %{title} does not exist on %{url}."
336
340
  msgstr ""
337
341
 
338
- #: ../lib/r10k/module/forge.rb:192
342
+ #: ../lib/r10k/module/forge.rb:196
339
343
  msgid "Forge module names must match 'owner/modulename', instead got #{title}"
340
344
  msgstr ""
341
345
 
@@ -371,7 +375,7 @@ msgstr ""
371
375
  msgid "Remove the duplicates of the following modules: %{dupes}"
372
376
  msgstr ""
373
377
 
374
- #: ../lib/r10k/puppetfile.rb:276
378
+ #: ../lib/r10k/puppetfile.rb:285
375
379
  msgid "unrecognized declaration '%{method}'"
376
380
  msgstr ""
377
381
 
@@ -454,27 +458,27 @@ msgid ""
454
458
  "Returned: %{data}"
455
459
  msgstr ""
456
460
 
457
- #: ../lib/r10k/source/git.rb:77
461
+ #: ../lib/r10k/source/git.rb:75
458
462
  msgid "Fetching '%{remote}' to determine current branches."
459
463
  msgstr ""
460
464
 
461
- #: ../lib/r10k/source/git.rb:80
465
+ #: ../lib/r10k/source/git.rb:78
462
466
  msgid "Unable to determine current branches for Git source '%{name}' (%{basedir})"
463
467
  msgstr ""
464
468
 
465
- #: ../lib/r10k/source/git.rb:110
469
+ #: ../lib/r10k/source/git.rb:113
466
470
  msgid "Environment %{env_name} contained non-word characters, correcting name to %{corrected_env_name}"
467
471
  msgstr ""
468
472
 
469
- #: ../lib/r10k/source/git.rb:119
473
+ #: ../lib/r10k/source/git.rb:122
470
474
  msgid "Environment %{env_name} contained non-word characters, ignoring it."
471
475
  msgstr ""
472
476
 
473
- #: ../lib/r10k/source/git.rb:138 ../lib/r10k/source/svn.rb:113
477
+ #: ../lib/r10k/source/git.rb:141 ../lib/r10k/source/svn.rb:115
474
478
  msgid "Branch %{branch} filtered out by ignore_branch_prefixes %{ibp}"
475
479
  msgstr ""
476
480
 
477
- #: ../lib/r10k/source/git.rb:149
481
+ #: ../lib/r10k/source/git.rb:152
478
482
  msgid "Branch `%{name}:%{branch}` filtered out by filter_command %{cmd}"
479
483
  msgstr ""
480
484
 
@@ -518,23 +522,23 @@ msgstr ""
518
522
  msgid "pe_license feature is not available, PE only Puppet modules will not be downloadable."
519
523
  msgstr ""
520
524
 
521
- #: ../lib/r10k/util/purgeable.rb:52
522
- msgid "Not purging %{item} due to internal exclusion match: %{exclusion_match}"
525
+ #: ../lib/r10k/util/purgeable.rb:87
526
+ msgid "Not purging %{path} due to internal exclusion match: %{exclusion_match}"
523
527
  msgstr ""
524
528
 
525
- #: ../lib/r10k/util/purgeable.rb:54
526
- msgid "Not purging %{item} due to whitelist match: %{whitelist_match}"
529
+ #: ../lib/r10k/util/purgeable.rb:89
530
+ msgid "Not purging %{path} due to whitelist match: %{allowlist_match}"
527
531
  msgstr ""
528
532
 
529
- #: ../lib/r10k/util/purgeable.rb:71
533
+ #: ../lib/r10k/util/purgeable.rb:133
530
534
  msgid "No unmanaged contents in %{managed_dirs}, nothing to purge"
531
535
  msgstr ""
532
536
 
533
- #: ../lib/r10k/util/purgeable.rb:76
537
+ #: ../lib/r10k/util/purgeable.rb:138
534
538
  msgid "Removing unmanaged path %{path}"
535
539
  msgstr ""
536
540
 
537
- #: ../lib/r10k/util/purgeable.rb:81
541
+ #: ../lib/r10k/util/purgeable.rb:143
538
542
  msgid "Unable to remove unmanaged path: %{path}"
539
543
  msgstr ""
540
544
 
@@ -0,0 +1,4 @@
1
+ ---
2
+ forge:
3
+ baseurl: 'http://private-forge.com'
4
+ authorization_token: 'faketoken'
@@ -0,0 +1,3 @@
1
+ ---
2
+ forge:
3
+ authorization_token: 'faketoken'
@@ -265,6 +265,15 @@ describe R10K::Action::Deploy::Environment do
265
265
  describe "deployment purge level" do
266
266
  let(:purge_levels) { [:deployment] }
267
267
 
268
+
269
+ it "updates the source's cache before it purges environments" do
270
+ deployment.sources.each do |source|
271
+ expect(source).to receive(:reload!).ordered
272
+ end
273
+ expect(deployment).to receive(:purge!).ordered
274
+ subject.call
275
+ end
276
+
268
277
  it "only logs about purging deployment" do
269
278
  expect(subject).to receive(:visit_environment).and_wrap_original do |original, env, &block|
270
279
  expect(env.logger).to_not receive(:debug).with(/purging unmanaged puppetfile content/i)
@@ -182,7 +182,7 @@ describe R10K::Action::Deploy::Module do
182
182
  subject { described_class.new({ config: '/some/nonexistent/path' }, ['mod1', 'mod2'], {}) }
183
183
 
184
184
  let(:cache) { instance_double("R10K::Git::Cache", 'sanitized_dirname' => 'foo', 'cached?' => true, 'sync' => true) }
185
- let(:repo) { instance_double("R10K::Git::StatefulRepository", cache: cache, resolve: 'main') }
185
+ let(:repo) { instance_double("R10K::Git::StatefulRepository", cache: cache, resolve: 'main', tracked_paths: []) }
186
186
 
187
187
  it 'does not sync modules not given' do
188
188
  allow(R10K::Deployment).to receive(:new).and_wrap_original do |original, settings, &block|
@@ -202,14 +202,26 @@ describe R10K::Action::Deploy::Module do
202
202
  allow_any_instance_of(R10K::Source::Git).to receive(:branch_names).and_return([R10K::Environment::Name.new('first', {})])
203
203
 
204
204
  expect(subject).to receive(:visit_environment).and_wrap_original do |original, environment, &block|
205
- pf = environment.puppetfile
206
- expect(pf).to receive(:load) do
207
- pf.add_module('mod1', { git: 'git://remote' })
208
- pf.add_module('mod2', { git: 'git://remote' })
209
- pf.add_module('mod3', { git: 'git://remote' })
205
+ # For this test we want to have realistic Modules and access to
206
+ # their internal Repos to validate the sync. Unfortunately, to
207
+ # do so we do some invasive mocking, effectively implementing
208
+ # our own R10K::Puppetfile#load. We directly update the Puppetfile's
209
+ # internal ModuleLoader and then call `load` on it so it will create
210
+ # the correct loaded_content.
211
+ puppetfile = environment.puppetfile
212
+ loader = puppetfile.loader
213
+ expect(puppetfile).to receive(:load) do
214
+ loader.add_module('mod1', { git: 'git://remote' })
215
+ loader.add_module('mod2', { git: 'git://remote' })
216
+ loader.add_module('mod3', { git: 'git://remote' })
217
+
218
+ allow(loader).to receive(:puppetfile_content).and_return('')
219
+ loaded_content = loader.load
220
+ puppetfile.instance_variable_set(:@loaded_content, loaded_content)
221
+ puppetfile.instance_variable_set(:@loaded, true)
210
222
  end
211
223
 
212
- pf.modules.each do |mod|
224
+ puppetfile.modules.each do |mod|
213
225
  if ['mod1', 'mod2'].include?(mod.name)
214
226
  expect(mod.should_sync?).to be(true)
215
227
  else
@@ -231,7 +243,7 @@ describe R10K::Action::Deploy::Module do
231
243
  subject { described_class.new({ config: '/some/nonexistent/path', environment: 'first' }, ['mod1'], {}) }
232
244
 
233
245
  let(:cache) { instance_double("R10K::Git::Cache", 'sanitized_dirname' => 'foo', 'cached?' => true, 'sync' => true) }
234
- let(:repo) { instance_double("R10K::Git::StatefulRepository", cache: cache, resolve: 'main') }
246
+ let(:repo) { instance_double("R10K::Git::StatefulRepository", cache: cache, resolve: 'main', tracked_paths: []) }
235
247
 
236
248
  it 'only syncs to the given environments' do
237
249
  allow(R10K::Deployment).to receive(:new).and_wrap_original do |original, settings, &block|
@@ -252,15 +264,27 @@ describe R10K::Action::Deploy::Module do
252
264
  R10K::Environment::Name.new('second', {})])
253
265
 
254
266
  expect(subject).to receive(:visit_environment).and_wrap_original do |original, environment, &block|
255
- pf = environment.puppetfile
267
+ puppetfile = environment.puppetfile
256
268
 
257
269
  if environment.name == 'first'
258
- expect(pf).to receive(:load) do
259
- pf.add_module('mod1', { git: 'git://remote' })
260
- pf.add_module('mod2', { git: 'git://remote' })
270
+ # For this test we want to have realistic Modules and access to
271
+ # their internal Repos to validate the sync. Unfortunately, to
272
+ # do so we do some invasive mocking, effectively implementing
273
+ # our own R10K::Puppetfile#load. We directly update the Puppetfile's
274
+ # internal ModuleLoader and then call `load` on it so it will create
275
+ # the correct loaded_content.
276
+ loader = puppetfile.loader
277
+ expect(puppetfile).to receive(:load) do
278
+ loader.add_module('mod1', { git: 'git://remote' })
279
+ loader.add_module('mod2', { git: 'git://remote' })
280
+
281
+ allow(loader).to receive(:puppetfile_content).and_return('')
282
+ loaded_content = loader.load
283
+ puppetfile.instance_variable_set(:@loaded_content, loaded_content)
284
+ puppetfile.instance_variable_set(:@loaded, true)
261
285
  end
262
286
 
263
- pf.modules.each do |mod|
287
+ puppetfile.modules.each do |mod|
264
288
  if mod.name == 'mod1'
265
289
  expect(mod.should_sync?).to be(true)
266
290
  else
@@ -269,7 +293,7 @@ describe R10K::Action::Deploy::Module do
269
293
  expect(mod).to receive(:sync).and_call_original
270
294
  end
271
295
  else
272
- expect(pf).not_to receive(:load)
296
+ expect(puppetfile).not_to receive(:load)
273
297
  end
274
298
 
275
299
  original.call(environment, &block)
@@ -218,42 +218,66 @@ describe R10K::Action::Runner do
218
218
  end
219
219
 
220
220
  describe "configuration authorization" do
221
- context "when license is not present" do
222
- before(:each) do
223
- expect(R10K::Util::License).to receive(:load).and_return(nil)
221
+ context "settings auth" do
222
+ it "sets the configured token as the forge authorization header" do
223
+ options = { config: "spec/fixtures/unit/action/r10k_forge_auth.yaml" }
224
+ runner = described_class.new(options, %w[args yes], action_class)
225
+
226
+ expect(PuppetForge).to receive(:host=).with('http://private-forge.com')
227
+ expect(PuppetForge::Connection).to receive(:authorization=).with('faketoken')
228
+ expect(PuppetForge::Connection).to receive(:authorization).and_return('faketoken')
229
+ expect(R10K::Util::License).not_to receive(:load)
230
+ runner.setup_settings
231
+ runner.setup_authorization
224
232
  end
225
233
 
226
- it "does not set authorization header on connection class" do
227
- expect(PuppetForge::Connection).not_to receive(:authorization=)
228
- runner.setup_authorization
234
+ it 'errors if no custom forge URL is set' do
235
+ options = { config: "spec/fixtures/unit/action/r10k_forge_auth_no_url.yaml" }
236
+ runner = described_class.new(options, %w[args yes], action_class)
237
+ expect(PuppetForge::Connection).not_to receive(:authorization=).with('faketoken')
238
+
239
+ expect { runner.setup_settings }.to raise_error(R10K::Error, /Cannot specify a Forge auth/)
229
240
  end
230
241
  end
231
242
 
232
- context "when license is present but invalid" do
233
- before(:each) do
234
- expect(R10K::Util::License).to receive(:load).and_raise(R10K::Error.new('invalid license'))
235
- end
243
+ context "license auth" do
244
+ context "when license is not present" do
245
+ before(:each) do
246
+ expect(R10K::Util::License).to receive(:load).and_return(nil)
247
+ end
236
248
 
237
- it "issues warning to logger" do
238
- expect(runner.logger).to receive(:warn).with(/invalid license/)
239
- runner.setup_authorization
249
+ it "does not set authorization header on connection class" do
250
+ expect(PuppetForge::Connection).not_to receive(:authorization=)
251
+ runner.setup_authorization
252
+ end
240
253
  end
241
254
 
242
- it "does not set authorization header on connection class" do
243
- expect(PuppetForge::Connection).not_to receive(:authorization=)
244
- runner.setup_authorization
245
- end
246
- end
255
+ context "when license is present but invalid" do
256
+ before(:each) do
257
+ expect(R10K::Util::License).to receive(:load).and_raise(R10K::Error.new('invalid license'))
258
+ end
247
259
 
248
- context "when license is present and valid" do
249
- before(:each) do
250
- mock_license = double('pe-license', :authorization_token => 'test token')
251
- expect(R10K::Util::License).to receive(:load).and_return(mock_license)
260
+ it "issues warning to logger" do
261
+ expect(runner.logger).to receive(:warn).with(/invalid license/)
262
+ runner.setup_authorization
263
+ end
264
+
265
+ it "does not set authorization header on connection class" do
266
+ expect(PuppetForge::Connection).not_to receive(:authorization=)
267
+ runner.setup_authorization
268
+ end
252
269
  end
253
270
 
254
- it "sets authorization header on connection class" do
255
- expect(PuppetForge::Connection).to receive(:authorization=).with('test token')
256
- runner.setup_authorization
271
+ context "when license is present and valid" do
272
+ before(:each) do
273
+ mock_license = double('pe-license', :authorization_token => 'test token')
274
+ expect(R10K::Util::License).to receive(:load).and_return(mock_license)
275
+ end
276
+
277
+ it "sets authorization header on connection class" do
278
+ expect(PuppetForge::Connection).to receive(:authorization=).with('test token')
279
+ runner.setup_authorization
280
+ end
257
281
  end
258
282
  end
259
283
  end
@@ -62,4 +62,18 @@ describe R10K::Git::Cache do
62
62
  subject.cached?
63
63
  end
64
64
  end
65
+
66
+ describe "dirname sanitization" do
67
+ it 'sanitizes cache directory name' do
68
+ expect(subject.sanitized_dirname).to eq('git---some-git-remote')
69
+ end
70
+
71
+ context 'with username and password' do
72
+ subject { subclass.new('https://"user:pa$$w0rd:@some/git/remote') }
73
+
74
+ it 'sanitizes cache directory name' do
75
+ expect(subject.sanitized_dirname).to eq('https---some-git-remote')
76
+ end
77
+ end
78
+ end
65
79
  end
@@ -104,9 +104,16 @@ describe R10K::Module::Forge do
104
104
 
105
105
  it "uses the latest version from the forge when the version is :latest" do
106
106
  subject = described_class.new('branan/eight_hundred', fixture_modulepath, { version: :latest })
107
- expect(subject.v3_module).to receive_message_chain(:current_release, :version).and_return('8.8.8')
107
+ release = double("Module Release", version: '8.8.8')
108
+ expect(subject.v3_module).to receive(:current_release).and_return(release).twice
108
109
  expect(subject.expected_version).to eq '8.8.8'
109
110
  end
111
+
112
+ it "throws when there are no available versions" do
113
+ subject = described_class.new('branan/eight_hundred', fixture_modulepath, { version: :latest })
114
+ expect(subject.v3_module).to receive(:current_release).and_return(nil)
115
+ expect { subject.expected_version }.to raise_error(PuppetForge::ReleaseNotFound)
116
+ end
110
117
  end
111
118
 
112
119
  describe "determining the status" do