paquette 0.1.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 (38) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.md +10 -0
  3. data/README.md +90 -0
  4. data/lib/paquette/gem_server/directory_gem_repository.rb +154 -0
  5. data/lib/paquette/gem_server/gem_repacker.rb +170 -0
  6. data/lib/paquette/gem_server/gem_repository.rb +53 -0
  7. data/lib/paquette/gem_server/personalizer.rb +66 -0
  8. data/lib/paquette/gem_server/read_gated_repository.rb +74 -0
  9. data/lib/paquette/gem_server.rb +393 -0
  10. data/lib/paquette/npm_repacker.rb +153 -0
  11. data/lib/paquette/npm_server/directory_npm_repository.rb +127 -0
  12. data/lib/paquette/npm_server/gated_npm_repository.rb +66 -0
  13. data/lib/paquette/npm_server/npm_repository.rb +53 -0
  14. data/lib/paquette/npm_server.rb +98 -0
  15. data/lib/paquette/routes.rb +78 -0
  16. data/lib/paquette/subdomain_router.rb +59 -0
  17. data/lib/paquette/version.rb +3 -0
  18. data/lib/paquette.rb +12 -0
  19. data/test/fixtures/gems/minuscule_test/minuscule_test-0.1.0.gem +0 -0
  20. data/test/fixtures/gems/zip_kit/zip_kit-6.2.0.gem +0 -0
  21. data/test/fixtures/gems/zip_kit/zip_kit-6.2.1.gem +0 -0
  22. data/test/fixtures/npm/react-dropzone/react-dropzone-14.3.8.tgz +0 -0
  23. data/test/gem_server/bundler_install_test.rb +86 -0
  24. data/test/gem_server/directory_gem_repository_test.rb +208 -0
  25. data/test/gem_server/gem_repacker_test.rb +289 -0
  26. data/test/gem_server/personalizer_metadata_test.rb +49 -0
  27. data/test/gem_server/personalizer_test.rb +66 -0
  28. data/test/gem_server/read_gated_repository_test.rb +254 -0
  29. data/test/gem_server/repository_stack_test.rb +71 -0
  30. data/test/gem_server_test.rb +339 -0
  31. data/test/npm_repacker_sourcemap_test.rb +108 -0
  32. data/test/npm_repacker_test.rb +117 -0
  33. data/test/npm_server_test.rb +140 -0
  34. data/test/routes_test.rb +142 -0
  35. data/test/subdomain_router_integration_test.rb +126 -0
  36. data/test/subdomain_router_test.rb +125 -0
  37. data/test/test_helper.rb +12 -0
  38. metadata +117 -0
@@ -0,0 +1,289 @@
1
+ require_relative "../test_helper"
2
+ require "open3"
3
+
4
+ class GemRepackerTest < Minitest::Test
5
+ def setup
6
+ @test_gem_path = File.join(FIXTURE_GEMS_DIR, "minuscule_test", "minuscule_test-0.1.0.gem")
7
+ end
8
+
9
+ def test_repack_with_line_transformation
10
+ assert File.exist?(@test_gem_path), "Test gem not found at #{@test_gem_path}"
11
+
12
+ # Generate random characters for the replacement
13
+ random_chars = (0...8).map { ("a".."z").to_a[rand(26)] }.join
14
+
15
+ # Use magic comment replacements to replace '# paquette_license_info' with '# LIC#{random_chars}'
16
+ new_gem_path = Paquette::GemServer::GemRepacker.repack(@test_gem_path,
17
+ gemspec_extras: {},
18
+ magic_comment_replacements: {"# paquette_license_info" => "LIC#{random_chars}"})
19
+
20
+ assert File.exist?(new_gem_path)
21
+ assert new_gem_path.end_with?("-repacked.gem")
22
+
23
+ # Unpack the produced gem and verify the replacement
24
+ verify_repacking(new_gem_path, random_chars)
25
+
26
+ # Clean up
27
+ File.delete(new_gem_path) if File.exist?(new_gem_path)
28
+ end
29
+
30
+ def test_repack_with_metadata_keys
31
+ assert File.exist?(@test_gem_path), "Test gem not found at #{@test_gem_path}"
32
+
33
+ # Test adding metadata keys to the gemspec
34
+ additional_metadata = {
35
+ "paquette.license_key" => "TEST-LICENSE-123",
36
+ "paquette.custom_field" => "custom_value"
37
+ }
38
+
39
+ new_gem_path = Paquette::GemServer::GemRepacker.repack(@test_gem_path,
40
+ gemspec_extras: additional_metadata,
41
+ magic_comment_replacements: {})
42
+
43
+ assert File.exist?(new_gem_path)
44
+ assert new_gem_path.end_with?("-repacked.gem")
45
+
46
+ # Verify the metadata was added to the gem spec
47
+ verify_metadata_in_gem(new_gem_path, additional_metadata)
48
+
49
+ # Clean up
50
+ File.delete(new_gem_path) if File.exist?(new_gem_path)
51
+ end
52
+
53
+ def test_repack_with_personalizer_metadata
54
+ assert File.exist?(@test_gem_path), "Test gem not found at #{@test_gem_path}"
55
+
56
+ # Test that Personalizer-style metadata addition works
57
+ personalized_gem_path = create_personalized_gem
58
+
59
+ # Verify the metadata was added
60
+ verify_metadata_in_gem(personalized_gem_path, {"paquette.license_key" => "TEST-LICENSE-456"})
61
+
62
+ # Clean up
63
+ File.delete(personalized_gem_path) if File.exist?(personalized_gem_path)
64
+ end
65
+
66
+ def test_repack_with_file_injection
67
+ assert File.exist?(@test_gem_path), "Test gem not found at #{@test_gem_path}"
68
+
69
+ # Test injecting new files into the gem
70
+ files_to_inject = {
71
+ "LICENSE" => "MIT License\nCopyright (c) 2024 Test Company",
72
+ "README.md" => "# Test Gem\n\nThis is a test gem with injected files.",
73
+ "docs/CHANGELOG.md" => "# Changelog\n\n## 0.1.0\n- Initial release"
74
+ }
75
+
76
+ new_gem_path = Paquette::GemServer::GemRepacker.repack(@test_gem_path,
77
+ gemspec_extras: {},
78
+ magic_comment_replacements: {},
79
+ files: files_to_inject)
80
+
81
+ assert File.exist?(new_gem_path)
82
+ assert new_gem_path.end_with?("-repacked.gem")
83
+
84
+ # Verify the injected files exist in the repacked gem
85
+ verify_injected_files(new_gem_path, files_to_inject)
86
+
87
+ # Clean up
88
+ File.delete(new_gem_path) if File.exist?(new_gem_path)
89
+ end
90
+
91
+ def test_repack_with_file_replacement
92
+ assert File.exist?(@test_gem_path), "Test gem not found at #{@test_gem_path}"
93
+
94
+ # Test replacing existing files in the gem
95
+ files_to_replace = {
96
+ "lib/minuscule_test.rb" => "# Replaced content\nmodule MinusculeTest\n VERSION = '0.1.0-replaced'\nend"
97
+ }
98
+
99
+ new_gem_path = Paquette::GemServer::GemRepacker.repack(@test_gem_path,
100
+ gemspec_extras: {},
101
+ magic_comment_replacements: {},
102
+ files: files_to_replace)
103
+
104
+ assert File.exist?(new_gem_path)
105
+ assert new_gem_path.end_with?("-repacked.gem")
106
+
107
+ # Verify the file was replaced
108
+ verify_injected_files(new_gem_path, files_to_replace)
109
+
110
+ # Clean up
111
+ File.delete(new_gem_path) if File.exist?(new_gem_path)
112
+ end
113
+
114
+ def test_repack_with_combined_features
115
+ assert File.exist?(@test_gem_path), "Test gem not found at #{@test_gem_path}"
116
+
117
+ # Test combining file injection with other features
118
+ files_to_inject = {
119
+ "LICENSE" => "MIT License\nCopyright (c) 2024 Test Company",
120
+ "docs/README.md" => "# Test Gem\n\nThis gem has been personalized."
121
+ }
122
+
123
+ magic_comment_replacements = {
124
+ "# paquette_license_info" => "LICENSE-INJECTED"
125
+ }
126
+
127
+ gemspec_extras = {
128
+ "paquette.license_key" => "TEST-LICENSE-COMBINED"
129
+ }
130
+
131
+ new_gem_path = Paquette::GemServer::GemRepacker.repack(@test_gem_path,
132
+ gemspec_extras: gemspec_extras,
133
+ magic_comment_replacements: magic_comment_replacements,
134
+ files: files_to_inject)
135
+
136
+ assert File.exist?(new_gem_path)
137
+ assert new_gem_path.end_with?("-repacked.gem")
138
+
139
+ # Verify all features work together
140
+ verify_metadata_in_gem(new_gem_path, gemspec_extras)
141
+ verify_injected_files(new_gem_path, files_to_inject)
142
+
143
+ # Clean up
144
+ File.delete(new_gem_path) if File.exist?(new_gem_path)
145
+ end
146
+
147
+ def test_repack_with_magic_comment_replacements
148
+ assert File.exist?(@test_gem_path), "Test gem not found at #{@test_gem_path}"
149
+
150
+ # Test adding magic comment replacements
151
+ magic_comment_replacements = {
152
+ "# paquette_license_info" => "TEST-LICENSE-789",
153
+ "# paquette_custom_field" => "CUSTOM-VALUE-123"
154
+ }
155
+
156
+ new_gem_path = Paquette::GemServer::GemRepacker.repack(@test_gem_path,
157
+ gemspec_extras: {"paquette.license_key" => "TEST-LICENSE-789"},
158
+ magic_comment_replacements: magic_comment_replacements)
159
+
160
+ assert File.exist?(new_gem_path)
161
+ assert new_gem_path.end_with?("-repacked.gem")
162
+
163
+ # Verify the metadata was added to the gem spec
164
+ verify_metadata_in_gem(new_gem_path, {"paquette.license_key" => "TEST-LICENSE-789"})
165
+
166
+ # Clean up
167
+ File.delete(new_gem_path) if File.exist?(new_gem_path)
168
+ end
169
+
170
+ def test_personalizer_with_flexible_comment_replacements
171
+ assert File.exist?(@test_gem_path), "Test gem not found at #{@test_gem_path}"
172
+
173
+ # Test the Personalizer with custom magic comment replacements
174
+ dir_repository = Paquette::GemServer::DirectoryGemRepository.new(FIXTURE_GEMS_DIR)
175
+
176
+ custom_replacements = {
177
+ "# paquette_license_info" => "CUSTOM-LICENSE-123",
178
+ "# paquette_custom_field" => "CUSTOM-VALUE-456"
179
+ }
180
+
181
+ personalizer = Paquette::GemServer::Personalizer.new(dir_repository,
182
+ license_key: "CUSTOM-LICENSE-123",
183
+ magic_comment_replacements: custom_replacements)
184
+
185
+ # Get the personalized gem path
186
+ personalized_gem_path = personalizer.gem_file_path("minuscule_test", "0.1.0")
187
+ assert File.exist?(personalized_gem_path)
188
+
189
+ # Verify the metadata was added
190
+ verify_metadata_in_gem(personalized_gem_path, {"paquette.license_key" => "CUSTOM-LICENSE-123"})
191
+
192
+ # Clean up
193
+ File.delete(personalized_gem_path) if File.exist?(personalized_gem_path)
194
+ end
195
+
196
+ private
197
+
198
+ def verify_repacking(gem_path, expected_random_chars)
199
+ # Create a temporary directory to unpack the new gem
200
+ temp_dir = Dir.mktmpdir("verify_repacking")
201
+ unpacked_dir = File.join(temp_dir, "unpacked")
202
+ FileUtils.mkdir_p(unpacked_dir)
203
+
204
+ begin
205
+ # Unpack the new gem
206
+ _, stderr, status = Open3.capture3("gem unpack #{gem_path} --target=#{unpacked_dir}")
207
+ assert status.success?, "Failed to unpack the repacked gem: #{stderr}"
208
+
209
+ # Find the unpacked gem directory
210
+ gem_dir = Dir.glob(File.join(unpacked_dir, "*")).find { |path| File.directory?(path) }
211
+ assert gem_dir, "Could not find unpacked gem directory"
212
+
213
+ # Check all Ruby files for the replacement
214
+ found_replacement = false
215
+ Dir.glob(File.join(gem_dir, "**", "*.rb")).each do |rb_file|
216
+ File.readlines(rb_file).each do |line|
217
+ if line.chomp == "# LIC#{expected_random_chars}"
218
+ found_replacement = true
219
+ end
220
+ # Ensure the original line is gone
221
+ refute_equal line.chomp, "# paquette_license_info", "Original line still present in #{rb_file}"
222
+ end
223
+ end
224
+
225
+ assert found_replacement, "Replacement line '# LIC#{expected_random_chars}' not found in any Ruby file"
226
+ ensure
227
+ FileUtils.rm_rf(temp_dir)
228
+ end
229
+ end
230
+
231
+ def create_personalized_gem
232
+ # Create a personalized gem using the Personalizer approach
233
+ personalized_dir = File.join(Dir.tmpdir, "paquette_personalized")
234
+ FileUtils.mkdir_p(personalized_dir)
235
+
236
+ personalized_path = File.join(personalized_dir, "minuscule_test-0.1.0-personalized.gem")
237
+
238
+ Paquette::GemServer::GemRepacker.repack(@test_gem_path,
239
+ gemspec_extras: {"paquette.license_key" => "TEST-LICENSE-456"},
240
+ magic_comment_replacements: {"# paquette_license_info" => "TEST-LICENSE-456"})
241
+
242
+ # Move the repacked gem to our personalized location
243
+ temp_personalized = File.join(Dir.tmpdir, "minuscule_test-0.1.0-repacked.gem")
244
+ if File.exist?(temp_personalized)
245
+ FileUtils.mv(temp_personalized, personalized_path)
246
+ end
247
+
248
+ personalized_path
249
+ end
250
+
251
+ def verify_metadata_in_gem(gem_path, expected_metadata)
252
+ # Read the gem specification directly from the gem file
253
+ gem_package = Gem::Package.new(gem_path)
254
+ spec = gem_package.spec
255
+
256
+ # Verify each expected metadata key is present in the spec
257
+ expected_metadata.each do |key, value|
258
+ assert_equal value, spec.metadata[key], "Expected metadata key '#{key}' to have value '#{value}', but got '#{spec.metadata[key]}'"
259
+ end
260
+ end
261
+
262
+ def verify_injected_files(gem_path, expected_files)
263
+ # Create a temporary directory to unpack the new gem
264
+ temp_dir = Dir.mktmpdir("verify_injected_files")
265
+ unpacked_dir = File.join(temp_dir, "unpacked")
266
+ FileUtils.mkdir_p(unpacked_dir)
267
+
268
+ begin
269
+ # Unpack the new gem
270
+ _, stderr, status = Open3.capture3("gem unpack #{gem_path} --target=#{unpacked_dir}")
271
+ assert status.success?, "Failed to unpack the gem: #{stderr}"
272
+
273
+ # Find the unpacked gem directory
274
+ gem_dir = Dir.glob(File.join(unpacked_dir, "*")).find { |path| File.directory?(path) }
275
+ assert gem_dir, "Could not find unpacked gem directory"
276
+
277
+ # Verify each expected file exists and has the correct content
278
+ expected_files.each do |file_path, expected_content|
279
+ full_file_path = File.join(gem_dir, file_path)
280
+ assert File.exist?(full_file_path), "Expected file '#{file_path}' not found in unpacked gem"
281
+
282
+ actual_content = File.read(full_file_path)
283
+ assert_equal expected_content, actual_content, "File '#{file_path}' content doesn't match expected content"
284
+ end
285
+ ensure
286
+ FileUtils.rm_rf(temp_dir)
287
+ end
288
+ end
289
+ end
@@ -0,0 +1,49 @@
1
+ require_relative "../test_helper"
2
+
3
+ class PersonalizerMetadataTest < Minitest::Test
4
+ def setup
5
+ @gems_dir = FIXTURE_GEMS_DIR
6
+ @dir_repository = Paquette::GemServer::DirectoryGemRepository.new(@gems_dir)
7
+ @personalizer = Paquette::GemServer::Personalizer.new(@dir_repository, license_key: "TEST-LICENSE-789")
8
+ end
9
+
10
+ def test_personalizer_adds_license_metadata_to_gem_spec
11
+ skip "Test gem not found" unless @personalizer.gem_exists?("minuscule_test", "0.1.0")
12
+
13
+ # Get the personalized gem path
14
+ personalized_gem_path = @personalizer.gem_file_path("minuscule_test", "0.1.0")
15
+ assert File.exist?(personalized_gem_path)
16
+
17
+ # Read the gem specification and verify the license key metadata
18
+ gem_package = Gem::Package.new(personalized_gem_path)
19
+ spec = gem_package.spec
20
+
21
+ # Verify the license key is in the metadata
22
+ assert_equal "TEST-LICENSE-789", spec.metadata["paquette.license_key"], "Expected paquette.license_key metadata to be set"
23
+
24
+ # Verify the original gem doesn't have this metadata
25
+ original_gem_path = @dir_repository.gem_file_path("minuscule_test", "0.1.0")
26
+ original_gem_package = Gem::Package.new(original_gem_path)
27
+ original_spec = original_gem_package.spec
28
+
29
+ assert_nil original_spec.metadata["paquette.license_key"], "Original gem should not have paquette.license_key metadata"
30
+ end
31
+
32
+ def test_metadata_persistence_through_repository_stack
33
+ skip "Test gem not found" unless @personalizer.gem_exists?("minuscule_test", "0.1.0")
34
+
35
+ # Test that the metadata persists through the repository stack
36
+ personalized_repository = Paquette::GemServer::Personalizer.new(@dir_repository, license_key: "STACK-TEST-123")
37
+ gated_repository = Paquette::GemServer::ReadGatedRepository.new(personalized_repository) { |name:, version: nil| true }
38
+
39
+ # Get gem through the stack
40
+ gem_path = gated_repository.gem_file_path("minuscule_test", "0.1.0")
41
+ assert File.exist?(gem_path)
42
+
43
+ # Verify metadata is present
44
+ gem_package = Gem::Package.new(gem_path)
45
+ spec = gem_package.spec
46
+
47
+ assert_equal "STACK-TEST-123", spec.metadata["paquette.license_key"], "Expected paquette.license_key metadata to be set through repository stack"
48
+ end
49
+ end
@@ -0,0 +1,66 @@
1
+ require_relative "../test_helper"
2
+
3
+ class PersonalizerTest < Minitest::Test
4
+ def setup
5
+ @gems_dir = FIXTURE_GEMS_DIR
6
+ @dir_repository = Paquette::GemServer::DirectoryGemRepository.new(@gems_dir)
7
+ @personalizer = Paquette::GemServer::Personalizer.new(@dir_repository,
8
+ license_key: "TEST-LICENSE-123",
9
+ magic_comment_replacements: {"# paquette_license_info" => "TEST-LICENSE-123"})
10
+ end
11
+
12
+ def test_personalizer_delegates_to_repository
13
+ # Test that basic repository methods still work
14
+ assert @personalizer.gem_names.include?("minuscule_test")
15
+ assert @personalizer.versions_for_gem("minuscule_test").include?("0.1.0")
16
+ assert @personalizer.gem_exists?("minuscule_test", "0.1.0")
17
+ end
18
+
19
+ def test_gem_file_path_returns_personalized_gem
20
+ skip "Test gem not found" unless @personalizer.gem_exists?("minuscule_test", "0.1.0")
21
+
22
+ # Get the personalized gem path
23
+ personalized_path = @personalizer.gem_file_path("minuscule_test", "0.1.0")
24
+
25
+ # Should be a different path than the original
26
+ original_path = @dir_repository.gem_file_path("minuscule_test", "0.1.0")
27
+ refute_equal original_path, personalized_path
28
+
29
+ # Should exist and be a file
30
+ assert File.exist?(personalized_path)
31
+ assert File.file?(personalized_path)
32
+
33
+ # Should have a different checksum than the original
34
+ require "digest"
35
+ original_checksum = Digest::SHA256.file(original_path).hexdigest
36
+ personalized_checksum = Digest::SHA256.file(personalized_path).hexdigest
37
+ refute_equal original_checksum, personalized_checksum
38
+ end
39
+
40
+ def test_compact_info_uses_personalized_checksums
41
+ skip "Test gem not found" unless @personalizer.gem_exists?("minuscule_test", "0.1.0")
42
+
43
+ # Get compact info from personalizer
44
+ personalized_info = @personalizer.compact_info("minuscule_test")
45
+
46
+ # Get compact info from original repository
47
+ original_info = @dir_repository.compact_info("minuscule_test")
48
+
49
+ # Should have same number of versions
50
+ assert_equal original_info.length, personalized_info.length
51
+
52
+ # But checksums should be different
53
+ original_checksum = extract_checksum(original_info.first)
54
+ personalized_checksum = extract_checksum(personalized_info.first)
55
+
56
+ refute_equal original_checksum, personalized_checksum
57
+ end
58
+
59
+ private
60
+
61
+ def extract_checksum(info_line)
62
+ if (match = info_line.match(/checksum:([a-f0-9]+)/))
63
+ match[1]
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,254 @@
1
+ require_relative "../test_helper"
2
+
3
+ class ReadGatedRepositoryTest < Minitest::Test
4
+ def setup
5
+ @gems_dir = FIXTURE_GEMS_DIR
6
+ @directory_repository = Paquette::GemServer::DirectoryGemRepository.new(@gems_dir)
7
+ end
8
+
9
+ def test_gem_names_with_all_gems_entitled
10
+ entitler = ->(name:, version: nil) { true }
11
+ repository = Paquette::GemServer::ReadGatedRepository.new(@directory_repository, &entitler)
12
+
13
+ assert_equal ["minuscule_test", "zip_kit"], repository.gem_names.sort
14
+ end
15
+
16
+ def test_gem_names_with_only_zip_kit_entitled
17
+ entitler = ->(name:, version: nil) { name == "zip_kit" }
18
+ repository = Paquette::GemServer::ReadGatedRepository.new(@directory_repository, &entitler)
19
+
20
+ assert_equal ["zip_kit"], repository.gem_names
21
+ end
22
+
23
+ def test_gem_names_with_no_gems_entitled
24
+ entitler = ->(name:, version: nil) { false }
25
+ repository = Paquette::GemServer::ReadGatedRepository.new(@directory_repository, &entitler)
26
+
27
+ assert_equal [], repository.gem_names
28
+ end
29
+
30
+ def test_gem_versions_with_all_entitled
31
+ entitler = ->(name:, version: nil) { true }
32
+ repository = Paquette::GemServer::ReadGatedRepository.new(@directory_repository, &entitler)
33
+
34
+ versions = repository.gem_versions
35
+ assert_equal 3, versions.length
36
+
37
+ # standard:disable Style/HashSlice
38
+ zip_kit_versions = versions.select { |name, _| name == "zip_kit" }
39
+ # standard:enable Style/HashSlice
40
+ assert_equal 2, zip_kit_versions.length
41
+ assert_includes zip_kit_versions.map { |_, v| v }, "6.2.0"
42
+ assert_includes zip_kit_versions.map { |_, v| v }, "6.2.1"
43
+ end
44
+
45
+ def test_gem_versions_with_only_zip_kit_entitled
46
+ entitler = ->(name:, version: nil) { name == "zip_kit" }
47
+ repository = Paquette::GemServer::ReadGatedRepository.new(@directory_repository, &entitler)
48
+
49
+ versions = repository.gem_versions
50
+ assert_equal 2, versions.length
51
+ versions.each do |name, _|
52
+ assert_equal "zip_kit", name
53
+ end
54
+
55
+ zip_kit_versions = versions.map { |_, v| v }
56
+ assert_includes zip_kit_versions, "6.2.0"
57
+ assert_includes zip_kit_versions, "6.2.1"
58
+ end
59
+
60
+ def test_gem_versions_with_subset_of_zip_kit_versions_entitled
61
+ entitler = ->(name:, version: nil) do
62
+ name == "zip_kit" && (version.nil? || version == "6.2.0")
63
+ end
64
+ repository = Paquette::GemServer::ReadGatedRepository.new(@directory_repository, &entitler)
65
+
66
+ versions = repository.gem_versions
67
+ assert_equal 1, versions.length
68
+ assert_equal "zip_kit", versions.first.first
69
+ assert_equal "6.2.0", versions.first.last
70
+ end
71
+
72
+ def test_versions_for_gem_with_all_versions_entitled
73
+ entitler = ->(name:, version: nil) { true }
74
+ repository = Paquette::GemServer::ReadGatedRepository.new(@directory_repository, &entitler)
75
+
76
+ zip_kit_versions = repository.versions_for_gem("zip_kit")
77
+ assert_equal 2, zip_kit_versions.length
78
+ assert_includes zip_kit_versions, "6.2.0"
79
+ assert_includes zip_kit_versions, "6.2.1"
80
+ end
81
+
82
+ def test_versions_for_gem_with_only_zip_kit_entitled
83
+ entitler = ->(name:, version: nil) { name == "zip_kit" }
84
+ repository = Paquette::GemServer::ReadGatedRepository.new(@directory_repository, &entitler)
85
+
86
+ zip_kit_versions = repository.versions_for_gem("zip_kit")
87
+ assert_equal 2, zip_kit_versions.length
88
+ assert_includes zip_kit_versions, "6.2.0"
89
+ assert_includes zip_kit_versions, "6.2.1"
90
+
91
+ assert_equal [], repository.versions_for_gem("minuscule_test")
92
+ end
93
+
94
+ def test_versions_for_gem_with_subset_of_versions_entitled
95
+ entitler = ->(name:, version: nil) do
96
+ name == "zip_kit" && (version.nil? || version == "6.2.0")
97
+ end
98
+ repository = Paquette::GemServer::ReadGatedRepository.new(@directory_repository, &entitler)
99
+
100
+ zip_kit_versions = repository.versions_for_gem("zip_kit")
101
+ assert_equal ["6.2.0"], zip_kit_versions
102
+ end
103
+
104
+ def test_versions_for_gem_with_gem_not_entitled
105
+ entitler = ->(name:, version: nil) { name == "zip_kit" }
106
+ repository = Paquette::GemServer::ReadGatedRepository.new(@directory_repository, &entitler)
107
+
108
+ assert_equal [], repository.versions_for_gem("minuscule_test")
109
+ end
110
+
111
+ def test_gem_file_path_with_entitled_version
112
+ entitler = ->(name:, version: nil) { true }
113
+ repository = Paquette::GemServer::ReadGatedRepository.new(@directory_repository, &entitler)
114
+
115
+ expected_path = File.join(@gems_dir, "zip_kit", "zip_kit-6.2.0.gem")
116
+ assert_equal expected_path, repository.gem_file_path("zip_kit", "6.2.0")
117
+ end
118
+
119
+ def test_gem_file_path_with_not_entitled_version
120
+ entitler = ->(name:, version: nil) do
121
+ name == "zip_kit" && (version.nil? || version == "6.2.0")
122
+ end
123
+ repository = Paquette::GemServer::ReadGatedRepository.new(@directory_repository, &entitler)
124
+
125
+ expected_path = File.join(@gems_dir, "zip_kit", "zip_kit-6.2.0.gem")
126
+ assert_equal expected_path, repository.gem_file_path("zip_kit", "6.2.0")
127
+
128
+ assert_nil repository.gem_file_path("zip_kit", "6.2.1")
129
+ end
130
+
131
+ def test_gem_file_path_with_not_entitled_gem
132
+ entitler = ->(name:, version: nil) { name == "zip_kit" }
133
+ repository = Paquette::GemServer::ReadGatedRepository.new(@directory_repository, &entitler)
134
+
135
+ assert_nil repository.gem_file_path("minuscule_test", "0.1.0")
136
+ end
137
+
138
+ def test_gem_exists_with_entitled_version
139
+ entitler = ->(name:, version: nil) { true }
140
+ repository = Paquette::GemServer::ReadGatedRepository.new(@directory_repository, &entitler)
141
+
142
+ assert repository.gem_exists?("zip_kit", "6.2.0")
143
+ assert repository.gem_exists?("zip_kit", "6.2.1")
144
+ assert repository.gem_exists?("minuscule_test", "0.1.0")
145
+ end
146
+
147
+ def test_gem_exists_with_not_entitled_version
148
+ entitler = ->(name:, version: nil) do
149
+ name == "zip_kit" && (version.nil? || version == "6.2.0")
150
+ end
151
+ repository = Paquette::GemServer::ReadGatedRepository.new(@directory_repository, &entitler)
152
+
153
+ assert repository.gem_exists?("zip_kit", "6.2.0")
154
+ assert_nil repository.gem_exists?("zip_kit", "6.2.1")
155
+ end
156
+
157
+ def test_gem_exists_with_not_entitled_gem
158
+ entitler = ->(name:, version: nil) { name == "zip_kit" }
159
+ repository = Paquette::GemServer::ReadGatedRepository.new(@directory_repository, &entitler)
160
+
161
+ assert_nil repository.gem_exists?("minuscule_test", "0.1.0")
162
+ end
163
+
164
+ def test_compact_info_with_entitled_gem
165
+ entitler = ->(name:, version: nil) { true }
166
+ repository = Paquette::GemServer::ReadGatedRepository.new(@directory_repository, &entitler)
167
+
168
+ info = repository.compact_info("zip_kit")
169
+ assert info.is_a?(Array)
170
+ assert_equal 2, info.length
171
+ info.each do |line|
172
+ assert_match(/^\S+\s+\|checksum:[a-f0-9]{64},ruby:.+$/, line)
173
+ end
174
+
175
+ versions = info.map { |line| line.split(" ")[0] }
176
+ assert_includes versions, "6.2.0"
177
+ assert_includes versions, "6.2.1"
178
+ end
179
+
180
+ def test_compact_info_with_not_entitled_gem
181
+ entitler = ->(name:, version: nil) { name == "zip_kit" }
182
+ repository = Paquette::GemServer::ReadGatedRepository.new(@directory_repository, &entitler)
183
+
184
+ info = repository.compact_info("zip_kit")
185
+ assert info.is_a?(Array)
186
+ assert_equal 2, info.length
187
+
188
+ assert_nil repository.compact_info("minuscule_test")
189
+ end
190
+
191
+ def test_compact_info_with_subset_of_versions_entitled
192
+ entitler = ->(name:, version: nil) do
193
+ name == "zip_kit" && (version.nil? || version == "6.2.0")
194
+ end
195
+ repository = Paquette::GemServer::ReadGatedRepository.new(@directory_repository, &entitler)
196
+
197
+ info = repository.compact_info("zip_kit")
198
+ assert info.is_a?(Array)
199
+ assert_equal 1, info.length
200
+ assert_equal "6.2.0", info.first.split(" ")[0]
201
+ end
202
+
203
+ def test_add_gem_raises_write_not_allowed
204
+ repository = Paquette::GemServer::ReadGatedRepository.new(@directory_repository) { |**| true }
205
+
206
+ assert_raises(Paquette::GemServer::ReadGatedRepository::WriteNotAllowed) do
207
+ repository.add_gem("anything")
208
+ end
209
+ end
210
+
211
+ def test_yank_gem_raises_write_not_allowed
212
+ repository = Paquette::GemServer::ReadGatedRepository.new(@directory_repository) { |**| true }
213
+
214
+ assert_raises(Paquette::GemServer::ReadGatedRepository::WriteNotAllowed) do
215
+ repository.yank_gem("zip_kit", "6.2.0")
216
+ end
217
+ end
218
+
219
+ def test_delegated_methods_still_work
220
+ # Test that methods not overridden in ReadGatedRepository still delegate correctly
221
+ entitler = ->(name:, version: nil) { true }
222
+ repository = Paquette::GemServer::ReadGatedRepository.new(@directory_repository, &entitler)
223
+
224
+ spec = repository.gem_spec("zip_kit", "6.2.0")
225
+ refute_nil spec
226
+ assert_equal "zip_kit", spec.name
227
+ assert_equal "6.2.0", spec.version.to_s
228
+
229
+ deps = repository.gem_dependencies("zip_kit", "6.2.0")
230
+ assert deps.is_a?(Array)
231
+ end
232
+
233
+ def test_complex_entitlement_logic
234
+ # Allow zip_kit >= 6.2.1 and all minuscule_test
235
+ entitler = ->(name:, version: nil) do
236
+ if name == "zip_kit"
237
+ version.nil? || Gem::Version.new(version) >= Gem::Version.new("6.2.1")
238
+ else
239
+ name == "minuscule_test"
240
+ end
241
+ end
242
+ repository = Paquette::GemServer::ReadGatedRepository.new(@directory_repository, &entitler)
243
+
244
+ names = repository.gem_names
245
+ assert_includes names, "zip_kit"
246
+ assert_includes names, "minuscule_test"
247
+
248
+ zip_kit_versions = repository.versions_for_gem("zip_kit")
249
+ assert_equal ["6.2.1"], zip_kit_versions
250
+
251
+ minuscule_versions = repository.versions_for_gem("minuscule_test")
252
+ assert_equal ["0.1.0"], minuscule_versions
253
+ end
254
+ end