gemvault 0.1.1

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.
@@ -0,0 +1,605 @@
1
+ # Container Integration Tests — Remaining Work
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Migrate all subprocess-based integration tests from `test/integration_test.rb` and `test/rubygems_plugin_test.rb` into Podman container tests, then remove the old tests.
6
+
7
+ **Architecture:** Each container test is a self-contained minitest file in `test/containers/` that runs inside a disposable `podman run --rm` container. The project is mounted read-only at `/gem`. A cached Docker image (`gemvault-test:latest`) has gemvault pre-installed; tests detect this and skip the build/install step. The host-side `rake test:containers` task orchestrates execution.
8
+
9
+ **Tech Stack:** Podman, minitest (stdlib), Docker image `ruby:4.0.1-slim`
10
+
11
+ ---
12
+
13
+ ## File Map
14
+
15
+ | File | Action | Responsibility |
16
+ |------|--------|----------------|
17
+ | `test/containers/test_helper.rb` | Modify | Add `run_bundle` and `run_bundle!` helpers |
18
+ | `test/containers/test_bundle_install.rb` | Rewrite | Expand from spike to cover all Bundler integration scenarios |
19
+ | `test/containers/test_gem_install.rb` | Rewrite | Expand from spike to cover all RubyGems plugin scenarios |
20
+ | `test/containers/test_bundler_inline.rb` | Create | Bundler inline (gemfile block) test |
21
+ | `test/containers/test_plugin_root_deps.rb` | Create | plugins.rb preamble test (Bundler::Plugin.root workaround) |
22
+ | `test/integration_test.rb` | Delete | Replaced by container tests |
23
+ | `test/rubygems_plugin_test.rb` | Modify | Remove `RubygemsPluginIntegrationTest` class (keep unit test classes) |
24
+
25
+ ## Test Migration Map
26
+
27
+ Each old test maps to a container test method:
28
+
29
+ **`test/integration_test.rb` → `test/containers/test_bundle_install.rb`:**
30
+
31
+ | Old test | New method | Notes |
32
+ |----------|-----------|-------|
33
+ | `test_basic_install` | Already done in spike | |
34
+ | `test_multiple_gems` | `test_multiple_gems` | Three gems, all install |
35
+ | `test_lockfile_correct` | `test_lockfile_format` | Assert PLUGIN SOURCE, type, version |
36
+ | `test_lockfile_round_trip` | `test_lockfile_idempotent` | Two installs, same lockfile |
37
+ | `test_gem_loadable` | Already done in spike (asserts `bundle exec ruby -e`) | |
38
+ | `test_alongside_rubygems_source` | `test_mixed_sources` | Vault + rubygems.org together |
39
+ | `test_subset_of_vault` | `test_partial_vault_install` | 3 in vault, only 2 in Gemfile |
40
+ | `test_dependency_resolution` | `test_intra_vault_dependency` | gem A depends on gem B, both in vault |
41
+ | `test_multi_version_resolution` | `test_version_constraint` | Two versions, `~> 2.0` picks correct one |
42
+ | `test_version_constraint_unsatisfied` | `test_unsatisfied_constraint` | Expect failure |
43
+
44
+ **`test/integration_test.rb` → `test/containers/test_bundler_inline.rb`:**
45
+
46
+ | Old test | New method | Notes |
47
+ |----------|-----------|-------|
48
+ | `test_bundler_inline` | `test_bundler_inline_with_vault` | No `plugin path:` hack needed — gem is installed |
49
+
50
+ **`test/integration_test.rb` → `test/containers/test_plugin_root_deps.rb`:**
51
+
52
+ | Old test | New method | Notes |
53
+ |----------|-----------|-------|
54
+ | `test_plugins_rb_makes_plugin_root_deps_activatable` | `test_plugin_root_specs_discoverable` | Same preamble-extraction approach |
55
+
56
+ **`test/rubygems_plugin_test.rb` → `test/containers/test_gem_install.rb`:**
57
+
58
+ | Old test | New method | Notes |
59
+ |----------|-----------|-------|
60
+ | `test_gem_install_from_vault` | Already done in spike | |
61
+ | `test_gem_install_verbose_shows_vault_messages` | `test_verbose_output` | `--verbose` flag shows vault messages |
62
+ | `test_gem_install_file_uri_source` | `test_file_uri_source` | `file://` prefix works |
63
+
64
+ ---
65
+
66
+ ### Task 1: Add bundle helpers to test_helper.rb
67
+
68
+ **Files:**
69
+ - Modify: `test/containers/test_helper.rb`
70
+
71
+ The existing Bundler tests in `test_bundle_install.rb` call `Open3.capture2e("bundle", "install", chdir:)` inline. Multiple tests need this — extract `run_bundle` and `run_bundle!` into the helper, matching the old tests' pattern.
72
+
73
+ - [ ] **Step 1: Add run_bundle and run_bundle! to ContainerTestHelper**
74
+
75
+ ```ruby
76
+ # In test/containers/test_helper.rb, add to ContainerTestHelper before `private`:
77
+
78
+ def run_bundle(*args, chdir:)
79
+ Open3.capture2e("bundle", *args, chdir: chdir.to_s)
80
+ end
81
+
82
+ def run_bundle!(*args, chdir:)
83
+ output, status = run_bundle(*args, chdir: chdir)
84
+ assert_predicate status, :success?, "bundle #{args.join(" ")} failed:\n#{output}"
85
+ [output, status]
86
+ end
87
+ ```
88
+
89
+ - [ ] **Step 2: Update test_bundle_install.rb to use the helper**
90
+
91
+ Replace the inline `Open3.capture2e("bundle", ...)` calls in the existing spike test with `run_bundle` / `run_bundle!`.
92
+
93
+ - [ ] **Step 3: Run container tests to verify refactor**
94
+
95
+ Run: `bundle exec rake test:containers`
96
+ Expected: both existing tests still pass.
97
+
98
+ - [ ] **Step 4: Commit**
99
+
100
+ ```bash
101
+ git add test/containers/test_helper.rb test/containers/test_bundle_install.rb
102
+ git commit -m "Extract run_bundle helpers into container test helper"
103
+ ```
104
+
105
+ ---
106
+
107
+ ### Task 2: Expand test_bundle_install.rb with all Bundler scenarios
108
+
109
+ **Files:**
110
+ - Rewrite: `test/containers/test_bundle_install.rb`
111
+
112
+ Add the remaining 8 Bundler integration tests. Each test follows the same pattern: build fixture gems, create vault, write Gemfile, `run_bundle!("install", chdir:)`, assert results.
113
+
114
+ - [ ] **Step 1: Write test_multiple_gems**
115
+
116
+ ```ruby
117
+ def test_multiple_gems
118
+ gem1 = build_gem("alpha_vault", "1.0.0", dir: @gem_build_dir)
119
+ gem2 = build_gem("beta_vault", "2.0.0", dir: @gem_build_dir / "b")
120
+ gem3 = build_gem("gamma_vault", "3.0.0", dir: @gem_build_dir / "c")
121
+
122
+ vault_path = create_vault(@workdir / "multi.gemv", gem1, gem2, gem3)
123
+
124
+ (@workdir / "Gemfile").write(<<~GEMFILE)
125
+ source "#{vault_path}", type: :vault do
126
+ gem "alpha_vault"
127
+ gem "beta_vault"
128
+ gem "gamma_vault"
129
+ end
130
+ GEMFILE
131
+
132
+ run_bundle!("install", chdir: @workdir)
133
+
134
+ %w[alpha_vault-1.0.0 beta_vault-2.0.0 gamma_vault-3.0.0].each do |name|
135
+ dirs = @workdir.glob("**/gems/#{name}")
136
+ refute_empty dirs, "Expected #{name} to be installed"
137
+ end
138
+ end
139
+ ```
140
+
141
+ - [ ] **Step 2: Write test_lockfile_format**
142
+
143
+ ```ruby
144
+ def test_lockfile_format
145
+ gem_path = build_gem("locktest", "1.0.0", dir: @gem_build_dir)
146
+ vault_path = create_vault(@workdir / "lock.gemv", gem_path)
147
+
148
+ (@workdir / "Gemfile").write(<<~GEMFILE)
149
+ source "#{vault_path}", type: :vault do
150
+ gem "locktest"
151
+ end
152
+ GEMFILE
153
+
154
+ run_bundle!("install", chdir: @workdir)
155
+
156
+ lockfile = (@workdir / "Gemfile.lock").read
157
+ assert_includes lockfile, "PLUGIN SOURCE"
158
+ assert_includes lockfile, "type: vault"
159
+ assert_includes lockfile, "locktest (1.0.0)"
160
+ end
161
+ ```
162
+
163
+ - [ ] **Step 3: Write test_lockfile_idempotent**
164
+
165
+ ```ruby
166
+ def test_lockfile_idempotent
167
+ gem_path = build_gem("roundtrip", "1.0.0", dir: @gem_build_dir)
168
+ vault_path = create_vault(@workdir / "rt.gemv", gem_path)
169
+
170
+ (@workdir / "Gemfile").write(<<~GEMFILE)
171
+ source "#{vault_path}", type: :vault do
172
+ gem "roundtrip"
173
+ end
174
+ GEMFILE
175
+
176
+ run_bundle!("install", chdir: @workdir)
177
+ lockfile1 = (@workdir / "Gemfile.lock").read
178
+
179
+ run_bundle!("install", chdir: @workdir)
180
+ lockfile2 = (@workdir / "Gemfile.lock").read
181
+
182
+ assert_equal lockfile1, lockfile2, "Lockfile changed after second install"
183
+ end
184
+ ```
185
+
186
+ - [ ] **Step 4: Write test_mixed_sources**
187
+
188
+ ```ruby
189
+ def test_mixed_sources
190
+ gem_path = build_gem("vaultgem", "1.0.0", dir: @gem_build_dir)
191
+ vault_path = create_vault(@workdir / "mixed.gemv", gem_path)
192
+
193
+ (@workdir / "Gemfile").write(<<~GEMFILE)
194
+ source "https://rubygems.org"
195
+
196
+ source "#{vault_path}", type: :vault do
197
+ gem "vaultgem"
198
+ end
199
+ GEMFILE
200
+
201
+ output, status = run_bundle("install", chdir: @workdir)
202
+ assert_predicate status, :success?, "bundle install with mixed sources failed:\n#{output}"
203
+ assert_match(/Bundle complete!/, output)
204
+ end
205
+ ```
206
+
207
+ - [ ] **Step 5: Write test_partial_vault_install**
208
+
209
+ ```ruby
210
+ def test_partial_vault_install
211
+ gem1 = build_gem("want1", "1.0.0", dir: @gem_build_dir)
212
+ gem2 = build_gem("want2", "1.0.0", dir: @gem_build_dir / "w2")
213
+ gem3 = build_gem("skipme", "1.0.0", dir: @gem_build_dir / "sk")
214
+
215
+ vault_path = create_vault(@workdir / "subset.gemv", gem1, gem2, gem3)
216
+
217
+ (@workdir / "Gemfile").write(<<~GEMFILE)
218
+ source "#{vault_path}", type: :vault do
219
+ gem "want1"
220
+ gem "want2"
221
+ end
222
+ GEMFILE
223
+
224
+ run_bundle!("install", chdir: @workdir)
225
+
226
+ refute_empty @workdir.glob("**/gems/want1-1.0.0")
227
+ refute_empty @workdir.glob("**/gems/want2-1.0.0")
228
+ assert_empty @workdir.glob("**/gems/skipme-1.0.0"), "skipme should not be installed"
229
+ end
230
+ ```
231
+
232
+ - [ ] **Step 6: Write test_intra_vault_dependency**
233
+
234
+ ```ruby
235
+ def test_intra_vault_dependency
236
+ gem_b = build_gem("depb", "1.0.0", dir: @gem_build_dir / "b")
237
+ gem_a = build_gem("depa", "1.0.0", dir: @gem_build_dir,
238
+ files: {"lib/depa.rb" => "require 'depb'; module Depa; end"},
239
+ dependencies: [["depb", "~> 1.0"]])
240
+
241
+ vault_path = create_vault(@workdir / "deps.gemv", gem_a, gem_b)
242
+
243
+ (@workdir / "Gemfile").write(<<~GEMFILE)
244
+ source "#{vault_path}", type: :vault do
245
+ gem "depa"
246
+ gem "depb"
247
+ end
248
+ GEMFILE
249
+
250
+ output, status = run_bundle("install", chdir: @workdir)
251
+ assert_predicate status, :success?, "bundle install with dependencies failed:\n#{output}"
252
+
253
+ refute_empty @workdir.glob("**/gems/depa-1.0.0")
254
+ refute_empty @workdir.glob("**/gems/depb-1.0.0")
255
+ end
256
+ ```
257
+
258
+ - [ ] **Step 7: Write test_version_constraint**
259
+
260
+ ```ruby
261
+ def test_version_constraint
262
+ gem_v1 = build_gem("multiver", "1.0.0", dir: @gem_build_dir / "v1",
263
+ files: {"lib/multiver.rb" => 'module Multiver; VERSION = "1.0.0"; end'})
264
+ gem_v2 = build_gem("multiver", "2.0.0", dir: @gem_build_dir / "v2",
265
+ files: {"lib/multiver.rb" => 'module Multiver; VERSION = "2.0.0"; end'})
266
+
267
+ vault_path = create_vault(@workdir / "mv.gemv", gem_v1, gem_v2)
268
+
269
+ (@workdir / "Gemfile").write(<<~GEMFILE)
270
+ source "#{vault_path}", type: :vault do
271
+ gem "multiver", "~> 2.0"
272
+ end
273
+ GEMFILE
274
+
275
+ run_bundle!("install", chdir: @workdir)
276
+
277
+ refute_empty @workdir.glob("**/gems/multiver-2.0.0")
278
+ assert_empty @workdir.glob("**/gems/multiver-1.0.0"), "1.0.0 should not be installed"
279
+
280
+ output, status = run_bundle("exec", "ruby", "-e",
281
+ "require 'multiver'; puts Multiver::VERSION", chdir: @workdir)
282
+ assert_predicate status, :success?, "bundle exec failed:\n#{output}"
283
+ assert_match(/2\.0\.0/, output)
284
+ end
285
+ ```
286
+
287
+ - [ ] **Step 8: Write test_unsatisfied_constraint**
288
+
289
+ ```ruby
290
+ def test_unsatisfied_constraint
291
+ gem_path = build_gem("constrained", "1.0.0", dir: @gem_build_dir)
292
+ vault_path = create_vault(@workdir / "constraint.gemv", gem_path)
293
+
294
+ (@workdir / "Gemfile").write(<<~GEMFILE)
295
+ source "#{vault_path}", type: :vault do
296
+ gem "constrained", "~> 2.0"
297
+ end
298
+ GEMFILE
299
+
300
+ output, status = run_bundle("install", chdir: @workdir)
301
+ refute_predicate status, :success?, "Expected bundle install to fail with unsatisfied constraint"
302
+ assert_match(/could not find/i, output)
303
+ end
304
+ ```
305
+
306
+ - [ ] **Step 9: Run container tests**
307
+
308
+ Run: `bundle exec rake test:containers`
309
+ Expected: all tests in `test_bundle_install.rb` pass (9 total methods).
310
+
311
+ - [ ] **Step 10: Commit**
312
+
313
+ ```bash
314
+ git add test/containers/test_bundle_install.rb
315
+ git commit -m "Add full Bundler integration suite to container tests"
316
+ ```
317
+
318
+ ---
319
+
320
+ ### Task 3: Expand test_gem_install.rb with RubyGems plugin scenarios
321
+
322
+ **Files:**
323
+ - Modify: `test/containers/test_gem_install.rb`
324
+
325
+ Add verbose output and file:// URI tests from `RubygemsPluginIntegrationTest`.
326
+
327
+ - [ ] **Step 1: Write test_verbose_output**
328
+
329
+ ```ruby
330
+ def test_verbose_output
331
+ gem_path = build_gem("vault_verbose", "1.0.0", dir: @gem_build_dir,
332
+ files: {"lib/vault_verbose.rb" => 'module VaultVerbose; VERSION = "1.0.0"; end'})
333
+
334
+ vault_path = create_vault(@workdir / "verbose.gemv", gem_path)
335
+
336
+ env = gem_env_for(@gem_home)
337
+ output, status = Open3.capture2e(
338
+ env,
339
+ "gem", "install", "--verbose", "--source", vault_path.to_s,
340
+ "--no-document", "vault_verbose",
341
+ )
342
+
343
+ assert_predicate status, :success?, "gem install --verbose failed:\n#{output}"
344
+ assert_match(/Loading .* specs from vault at/, output)
345
+ assert_match(/Extracting vault_verbose-1\.0\.0\.gem from vault at/, output)
346
+ end
347
+ ```
348
+
349
+ - [ ] **Step 2: Write test_file_uri_source**
350
+
351
+ ```ruby
352
+ def test_file_uri_source
353
+ gem_path = build_gem("vault_fileuri", "1.0.0", dir: @gem_build_dir,
354
+ files: {"lib/vault_fileuri.rb" => 'module VaultFileuri; VERSION = "1.0.0"; end'})
355
+
356
+ vault_path = create_vault(@workdir / "fileuri.gemv", gem_path)
357
+
358
+ env = gem_env_for(@gem_home)
359
+ output, status = Open3.capture2e(
360
+ env,
361
+ "gem", "install", "--source", "file://#{vault_path}",
362
+ "--no-document", "vault_fileuri",
363
+ )
364
+
365
+ assert_predicate status, :success?, "gem install with file:// URI failed:\n#{output}"
366
+ assert_match(/installed vault_fileuri/i, output)
367
+ end
368
+ ```
369
+
370
+ - [ ] **Step 3: Run container tests**
371
+
372
+ Run: `bundle exec rake test:containers`
373
+ Expected: all `test_gem_install.rb` tests pass (3 total methods).
374
+
375
+ - [ ] **Step 4: Commit**
376
+
377
+ ```bash
378
+ git add test/containers/test_gem_install.rb
379
+ git commit -m "Add verbose and file:// URI tests to container gem install suite"
380
+ ```
381
+
382
+ ---
383
+
384
+ ### Task 4: Write test_bundler_inline.rb
385
+
386
+ **Files:**
387
+ - Create: `test/containers/test_bundler_inline.rb`
388
+
389
+ The inline test is special: `bundler/inline` handles its own plugin installation. In the container, the `bundler-source-vault` gem is already installed, so we don't need the `plugin "...", path:` hack. The inline gemfile block should discover the plugin automatically.
390
+
391
+ - [ ] **Step 1: Write the test file**
392
+
393
+ ```ruby
394
+ require_relative "test_helper"
395
+
396
+ class BundlerInlineTest < Minitest::Test
397
+ include ContainerTestHelper
398
+
399
+ def setup
400
+ install_gemvault!
401
+ @workdir = Pathname(Dir.mktmpdir("bundler_inline_test"))
402
+ @gem_build_dir = @workdir / "gems"
403
+ @gem_build_dir.mkpath
404
+ end
405
+
406
+ def teardown
407
+ @workdir.rmtree
408
+ end
409
+
410
+ def test_bundler_inline_with_vault
411
+ gem_path = build_gem("inline_gem", "1.0.0", dir: @gem_build_dir,
412
+ files: {"lib/inline_gem.rb" => 'module InlineGem; VERSION = "1.0.0"; end'})
413
+
414
+ vault_path = create_vault(@workdir / "inline.gemv", gem_path)
415
+
416
+ script = <<~RUBY
417
+ require "bundler/inline"
418
+
419
+ gemfile(true) do
420
+ source "#{vault_path}", type: :vault do
421
+ gem "inline_gem"
422
+ end
423
+ end
424
+
425
+ require "inline_gem"
426
+ puts InlineGem::VERSION
427
+ RUBY
428
+
429
+ script_path = @workdir / "inline_test.rb"
430
+ script_path.write(script)
431
+
432
+ output, status = Open3.capture2e("ruby", script_path.to_s, chdir: @workdir.to_s)
433
+ assert_predicate status, :success?, "bundler/inline failed:\n#{output}"
434
+ assert_match(/1\.0\.0/, output)
435
+ end
436
+ end
437
+ ```
438
+
439
+ - [ ] **Step 2: Run container tests**
440
+
441
+ Run: `bundle exec rake test:containers`
442
+ Expected: `test_bundler_inline.rb` passes. If `bundler/inline` cannot auto-discover the plugin from installed gems, this test will reveal it — that itself is a valuable finding.
443
+
444
+ - [ ] **Step 3: Commit**
445
+
446
+ ```bash
447
+ git add test/containers/test_bundler_inline.rb
448
+ git commit -m "Add bundler/inline container test"
449
+ ```
450
+
451
+ ---
452
+
453
+ ### Task 5: Write test_plugin_root_deps.rb
454
+
455
+ **Files:**
456
+ - Create: `test/containers/test_plugin_root_deps.rb`
457
+
458
+ This test verifies the `shim/plugins.rb` preamble that patches `Gem::Specification.dirs` to include `Bundler::Plugin.root`. The test extracts the preamble (code before the first `require` line), fakes a Plugin.root with a phantom gem spec, runs the preamble, and asserts the phantom dep becomes discoverable.
459
+
460
+ - [ ] **Step 1: Write the test file**
461
+
462
+ ```ruby
463
+ require_relative "test_helper"
464
+
465
+ class PluginRootDepsTest < Minitest::Test
466
+ include ContainerTestHelper
467
+
468
+ def setup
469
+ install_gemvault!
470
+ @workdir = Pathname(Dir.mktmpdir("plugin_root_test"))
471
+ end
472
+
473
+ def teardown
474
+ @workdir.rmtree
475
+ end
476
+
477
+ def test_plugin_root_specs_discoverable
478
+ fake_root = @workdir / "fake_plugin_root"
479
+ fake_specs = fake_root / "specifications"
480
+ fake_specs.mkpath
481
+
482
+ plugins_rb = GEM_SOURCE.join("shim", "plugins.rb").read
483
+ preamble = plugins_rb.lines.take_while { |l| !l.match?(/^require\b/) }.join
484
+
485
+ preamble_path = @workdir / "preamble.rb"
486
+ preamble_path.write(preamble)
487
+
488
+ script_path = @workdir / "test_plugin_root_deps.rb"
489
+ script_path.write(<<~RUBY)
490
+ require "bundler"
491
+
492
+ fake_spec = Gem::Specification.new do |s|
493
+ s.name = "phantom_dep"
494
+ s.version = "1.0.0"
495
+ s.summary = "Simulated plugin dependency"
496
+ s.authors = ["Test"]
497
+ s.files = []
498
+ end
499
+ File.write("#{fake_specs / "phantom_dep-1.0.0.gemspec"}", fake_spec.to_ruby)
500
+
501
+ begin
502
+ Gem::Specification.find_by_name("phantom_dep")
503
+ $stderr.puts "SETUP ERROR: phantom_dep already visible"
504
+ exit 2
505
+ rescue Gem::MissingSpecError
506
+ # Expected
507
+ end
508
+
509
+ module Bundler::Plugin
510
+ remove_method :root if method_defined?(:root)
511
+ define_method(:root) { Pathname.new("#{fake_root}") }
512
+ module_function :root
513
+ end
514
+
515
+ load "#{preamble_path}"
516
+
517
+ begin
518
+ Gem::Specification.find_by_name("phantom_dep")
519
+ puts "PASS"
520
+ rescue Gem::MissingSpecError
521
+ $stderr.puts "FAIL: phantom_dep not findable after plugins.rb preamble"
522
+ exit 1
523
+ end
524
+ RUBY
525
+
526
+ output, status = Open3.capture2e("ruby", script_path.to_s)
527
+ assert_predicate status, :success?,
528
+ "plugins.rb should make Plugin.root deps findable:\n#{output}"
529
+ assert_match(/PASS/, output)
530
+ end
531
+ end
532
+ ```
533
+
534
+ - [ ] **Step 2: Run container tests**
535
+
536
+ Run: `bundle exec rake test:containers`
537
+ Expected: `test_plugin_root_deps.rb` passes.
538
+
539
+ - [ ] **Step 3: Commit**
540
+
541
+ ```bash
542
+ git add test/containers/test_plugin_root_deps.rb
543
+ git commit -m "Add plugin root dependency discovery container test"
544
+ ```
545
+
546
+ ---
547
+
548
+ ### Task 6: Remove old integration tests
549
+
550
+ **Files:**
551
+ - Delete: `test/integration_test.rb`
552
+ - Modify: `test/rubygems_plugin_test.rb` (remove `RubygemsPluginIntegrationTest`, keep other 3 classes)
553
+
554
+ - [ ] **Step 1: Delete test/integration_test.rb**
555
+
556
+ ```bash
557
+ rm test/integration_test.rb
558
+ ```
559
+
560
+ - [ ] **Step 2: Remove RubygemsPluginIntegrationTest from rubygems_plugin_test.rb**
561
+
562
+ Delete lines 281-378 (`class RubygemsPluginIntegrationTest ... end`). Keep:
563
+ - `RubygemsSourceVaultTest` (lines 9-184) — unit tests for `Gem::Source::Vault`
564
+ - `RubygemsResolverVaultSetTest` (lines 186-249) — unit tests for `Gem::Resolver::VaultSet`
565
+ - `RubygemsPluginMonkeyPatchTest` (lines 251-279) — unit tests for monkey-patches
566
+
567
+ - [ ] **Step 3: Run all test suites to confirm nothing broke**
568
+
569
+ Run: `bundle exec rake test && bundle exec rspec && bundle exec rake test:containers`
570
+ Expected:
571
+ - `rake test`: ~107 runs (was 122 — 12 integration + 3 rubygems integration removed)
572
+ - `rspec`: 1 example, 0 failures
573
+ - `rake test:containers`: all container tests pass
574
+
575
+ - [ ] **Step 4: Commit**
576
+
577
+ ```bash
578
+ git add -A
579
+ git commit -m "Remove old integration tests, replaced by container tests"
580
+ ```
581
+
582
+ ---
583
+
584
+ ### Task 7: Rebuild cached Docker image
585
+
586
+ After all test files are finalized, rebuild the cached image so CI can use it.
587
+
588
+ - [ ] **Step 1: Rebuild the image**
589
+
590
+ Run: `bundle exec rake test:containers:build`
591
+
592
+ - [ ] **Step 2: Run full container suite with cached image**
593
+
594
+ Run: `bundle exec rake test:containers`
595
+ Expected: all container tests pass, output shows `(cached)`.
596
+
597
+ - [ ] **Step 3: Final verification — all three suites green**
598
+
599
+ ```bash
600
+ bundle exec rake test
601
+ bundle exec rspec
602
+ bundle exec rake test:containers
603
+ ```
604
+
605
+ All three must pass with zero failures.
data/exe/gemvault ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative "../lib/gemvault"
3
+ require_relative "../lib/gemvault/cli"
4
+
5
+ Gemvault::CLI.start