gemirro 1.6.0 → 3.0.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +1 -1
  3. data/.gitignore +2 -0
  4. data/.rubocop.yml +3 -1
  5. data/Gemfile +2 -1
  6. data/Gemfile.lock +73 -61
  7. data/MANIFEST +0 -3
  8. data/bin/gemirro +1 -8
  9. data/gemirro.gemspec +4 -6
  10. data/lib/gemirro/cli/index.rb +48 -27
  11. data/lib/gemirro/cli/init.rb +48 -29
  12. data/lib/gemirro/cli/list.rb +37 -17
  13. data/lib/gemirro/cli/server.rb +173 -114
  14. data/lib/gemirro/cli/update.rb +42 -17
  15. data/lib/gemirro/cli.rb +64 -21
  16. data/lib/gemirro/configuration.rb +26 -20
  17. data/lib/gemirro/gem_version.rb +1 -0
  18. data/lib/gemirro/gem_version_collection.rb +4 -4
  19. data/lib/gemirro/gems_fetcher.rb +9 -9
  20. data/lib/gemirro/http.rb +115 -41
  21. data/lib/gemirro/indexer.rb +33 -232
  22. data/lib/gemirro/mirror_file.rb +3 -10
  23. data/lib/gemirro/server.rb +12 -43
  24. data/lib/gemirro/source.rb +2 -15
  25. data/lib/gemirro/utils.rb +22 -12
  26. data/lib/gemirro/version.rb +1 -1
  27. data/lib/gemirro/versions_fetcher.rb +7 -12
  28. data/lib/gemirro/versions_file.rb +26 -24
  29. data/lib/gemirro.rb +3 -7
  30. data/spec/gemirro/cli_spec.rb +49 -15
  31. data/spec/gemirro/configuration_spec.rb +9 -2
  32. data/spec/gemirro/gems_fetcher_spec.rb +2 -2
  33. data/spec/gemirro/http_spec.rb +58 -54
  34. data/spec/gemirro/indexer_spec.rb +5 -5
  35. data/spec/gemirro/server_spec.rb +30 -52
  36. data/spec/gemirro/source_spec.rb +2 -10
  37. data/spec/gemirro/versions_fetcher_spec.rb +3 -4
  38. data/spec/gemirro/versions_file_spec.rb +8 -32
  39. data/template/config.rb +1 -1
  40. metadata +29 -69
  41. data/template/public/latest_specs.4.8 +0 -0
  42. data/template/public/prerelease_specs.4.8 +0 -0
  43. data/template/public/specs.4.8 +0 -0
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'sinatra/base'
4
- require 'thin'
4
+ require 'puma'
5
5
  require 'uri'
6
6
  require 'addressable/uri'
7
+ require 'base64'
7
8
 
8
9
  module Gemirro
9
10
  ##
@@ -32,6 +33,7 @@ module Gemirro
32
33
  config.server.port = '2000' if config.server.port.nil?
33
34
 
34
35
  set :static, true
36
+ set :server, 'puma'
35
37
 
36
38
  set :views, Gemirro::Configuration.views_directory
37
39
  set :port, config.server.port
@@ -78,39 +80,6 @@ module Gemirro
78
80
  erb(:index, {}, gems: Utils.gems_collection)
79
81
  end
80
82
 
81
- ##
82
- # Return gem dependencies as marshaled binary
83
- #
84
- # @return [nil]
85
- #
86
- get '/api/v1/dependencies' do
87
- content_type 'application/octet-stream'
88
- if params[:gems].to_s != '' && params[:gems].to_s.split(',').any?
89
- Marshal.dump(dependencies_loader(params[:gems].to_s.split(',')))
90
- else
91
- 200
92
- end
93
- end
94
-
95
- ##
96
- # Return gem dependencies as json
97
- #
98
- # @return [nil]
99
- #
100
- get '/api/v1/dependencies.json' do
101
- content_type 'application/json'
102
-
103
- return '[]' unless params[:gems]
104
-
105
- gem_names = params[:gems].to_s
106
- .split(',')
107
- .map(&:strip)
108
- .reject(&:empty?)
109
- return '[]' if gem_names.empty?
110
-
111
- JSON.dump(dependencies_loader(gem_names))
112
- end
113
-
114
83
  ##
115
84
  # compact_index, Return list of available gem names
116
85
  #
@@ -120,10 +89,10 @@ module Gemirro
120
89
  content_type 'text/plain'
121
90
 
122
91
  content_path = Dir.glob(File.join(Gemirro.configuration.destination, 'names.*.*.list')).last
123
- _, etag, repr_digest, _ = content_path.split('.', -4)
92
+ _, etag, repr_digest, _ = File.basename(content_path).split('.')
124
93
 
125
- headers 'etag' => etag
126
- headers 'repr-digest' => %(sha-256="#{repr_digest}")
94
+ headers 'etag' => %("#{etag}")
95
+ headers 'repr-digest' => %(sha-256=#{Base64.strict_encode64([repr_digest].pack('H*'))})
127
96
  send_file content_path
128
97
  end
129
98
 
@@ -136,10 +105,10 @@ module Gemirro
136
105
  content_type 'text/plain'
137
106
 
138
107
  content_path = Dir.glob(File.join(Utils.configuration.destination, 'versions.*.*.list')).last
139
- _, etag, repr_digest, _ = content_path.split('.', -4)
108
+ _, etag, repr_digest, _ = File.basename(content_path).split('.')
140
109
 
141
- headers 'etag' => etag
142
- headers 'repr-digest' => %(sha-256="#{repr_digest}")
110
+ headers 'etag' => %("#{etag}")
111
+ headers 'repr-digest' => %(sha-256=#{Base64.strict_encode64([repr_digest].pack('H*'))})
143
112
  send_file content_path
144
113
  end
145
114
 
@@ -155,10 +124,10 @@ module Gemirro
155
124
  content_type 'text/plain'
156
125
 
157
126
  content_path = Dir.glob(File.join(Utils.configuration.destination, 'info', "#{params[:gemname]}.*.*.list")).last
158
- _, etag, repr_digest, _ = content_path.split('.', -4)
127
+ _, etag, repr_digest, _ = File.basename(content_path).split('.')
159
128
 
160
- headers 'etag' => etag
161
- headers 'repr-digest' => %(sha-256="#{repr_digest}")
129
+ headers 'etag' => %("#{etag}")
130
+ headers 'repr-digest' => %(sha-256=#{Base64.strict_encode64([repr_digest].pack('H*'))})
162
131
  send_file content_path
163
132
  end
164
133
 
@@ -33,23 +33,10 @@ module Gemirro
33
33
  #
34
34
  def fetch_versions
35
35
  Utils.logger.info(
36
- "Fetching #{Configuration.versions_file} on #{@name} (#{@host})"
36
+ "Fetching versions on #{@name} (#{@host})"
37
37
  )
38
38
 
39
- Http.get("#{host}/#{Configuration.versions_file}").body
40
- end
41
-
42
- ##
43
- # Fetches a list of all the available Gems and their versions.
44
- #
45
- # @return [String]
46
- #
47
- def fetch_prerelease_versions
48
- Utils.logger.info(
49
- "Fetching #{Configuration.prerelease_versions_file} " \
50
- "on #{@name} (#{@host})"
51
- )
52
- Http.get("#{host}/#{Configuration.prerelease_versions_file}").body
39
+ Http.get("#{host}/versions").body
53
40
  end
54
41
 
55
42
  ##
data/lib/gemirro/utils.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'gemirro/gem_version'
4
+
3
5
  module Gemirro
4
6
  ##
5
7
  # The Utils class is responsible for executing specific traitments
@@ -33,12 +35,10 @@ module Gemirro
33
35
  @gems_collection ||= { files: {}, values: nil }
34
36
 
35
37
  file_paths =
36
- %i[specs prerelease_specs].collect do |specs_file_type|
37
- File.join(
38
- configuration.destination,
39
- "#{specs_file_type}.#{Gemirro::Configuration.marshal_version}.gz.local"
40
- )
41
- end
38
+ Dir.glob(File.join(
39
+ Gemirro.configuration.destination,
40
+ 'versions.*.*.list'
41
+ ))
42
42
 
43
43
  has_file_changed =
44
44
  @gems_collection[:files] != file_paths.each_with_object({}) do |f, r|
@@ -50,12 +50,22 @@ module Gemirro
50
50
 
51
51
  gems = []
52
52
 
53
- # parallel is not for mtime, it's for the Marshal.
54
- Parallel.map(file_paths, in_threads: Utils.configuration.update_thread_count) do |file_path|
55
- next unless File.exist?(file_path)
53
+ CompactIndex::VersionsFile.new(file_paths.last).contents.each_line.with_index do |line, index|
54
+ next if index < 2
55
+
56
+ gem_name = line.split[0]
57
+ versions = line.split[1..-2].collect { |x| x.split(',') }.flatten # All except first and last
56
58
 
57
- gems.concat(Marshal.load(Zlib::GzipReader.open(file_path).read))
58
- @gems_collection[:files][file_path] = File.mtime(file_path)
59
+ versions.each do |ver|
60
+ version, platform =
61
+ if ver.include?('-')
62
+ ver.split('-', 2)
63
+ else
64
+ [ver, 'ruby']
65
+ end
66
+
67
+ gems << Gemirro::GemVersion.new(gem_name, version, platform)
68
+ end
59
69
  end
60
70
 
61
71
  @gems_collection[:values] = GemVersionCollection.new(gems)
@@ -187,7 +197,7 @@ module Gemirro
187
197
  # @return [Indexer]
188
198
  #
189
199
  def self.update_indexes
190
- indexer = Gemirro::Indexer.new(Utils.configuration.destination)
200
+ indexer = Gemirro::Indexer.new
191
201
  indexer.only_origin = true
192
202
  indexer.ui = ::Gem::SilentUI.new
193
203
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Gemirro Version
4
4
  module Gemirro
5
- VERSION = '1.6.0'
5
+ VERSION = '3.0.0'
6
6
  end
@@ -22,29 +22,24 @@ module Gemirro
22
22
  # @return [Gemirro::VersionsFile]
23
23
  #
24
24
  def fetch
25
- VersionsFile.load(
26
- read_file(Configuration.versions_file),
27
- read_file(Configuration.prerelease_versions_file, true)
28
- )
25
+ return unless Gemirro.configuration.versions_file
26
+
27
+ VersionsFile.new(read_file(Gemirro.configuration.versions_file))
29
28
  end
30
29
 
31
30
  ##
32
31
  # Read file if exists otherwise download its from source
33
32
  #
34
33
  # @param [String] file name
35
- # @param [TrueClass|FalseClass] prerelease Is prerelease or not
36
34
  #
37
- def read_file(file, prerelease = false)
38
- destination = Gemirro.configuration.destination
39
- file_dst = File.join(destination, file)
40
- unless File.exist?(file_dst)
35
+ def read_file(file)
36
+ unless File.exist?(file)
41
37
  throw 'No source defined' unless @source
42
38
 
43
- File.write(file_dst, @source.fetch_versions) unless prerelease
44
- File.write(file_dst, @source.fetch_prerelease_versions) if prerelease
39
+ File.write(file, @source.fetch_versions)
45
40
  end
46
41
 
47
- File.read(file_dst)
42
+ File.read(file)
48
43
  end
49
44
  end
50
45
  end
@@ -11,36 +11,24 @@ module Gemirro
11
11
  # @return [Hash]
12
12
  #
13
13
  class VersionsFile
14
- attr_reader :versions, :versions_hash
14
+ attr_reader :versions_string, :versions_hash
15
15
 
16
16
  ##
17
17
  # Reads the versions file from the specified String.
18
18
  #
19
- # @param [String] spec_content
20
- # @param [String] prerelease_content
19
+ # @param [String] versions_content
21
20
  # @return [Gemirro::VersionsFile]
22
21
  #
23
- def self.load(spec_content, prerelease_content)
24
- buffer = StringIO.new(spec_content)
25
- reader = Zlib::GzipReader.new(buffer)
26
- versions = Marshal.load(reader.read)
27
-
28
- buffer = StringIO.new(prerelease_content)
29
- reader = Zlib::GzipReader.new(buffer)
30
- versions.concat(Marshal.load(reader.read))
31
-
32
- instance = new(versions)
33
-
34
- reader.close
35
-
36
- instance
37
- end
38
22
 
39
23
  ##
40
- # @param [Array] versions
24
+ # @param [String] versions
41
25
  #
42
- def initialize(versions)
43
- @versions = versions
26
+ def initialize(versions_string)
27
+ unless versions_string.is_a? String
28
+ throw "#{versions_string.class} is wrong format, expect String; #{versions_string.inspect}"
29
+ end
30
+
31
+ @versions_string = versions_string
44
32
  @versions_hash = create_versions_hash
45
33
  end
46
34
 
@@ -53,10 +41,24 @@ module Gemirro
53
41
  def create_versions_hash
54
42
  hash = Hash.new { |h, k| h[k] = [] }
55
43
 
56
- versions.each do |version|
57
- hash[version[0]] << version
58
- end
44
+ versions_string.each_line.with_index do |line, index|
45
+ next if index < 2
59
46
 
47
+ parts = line.split
48
+ gem_name = parts[0]
49
+ parts[-1]
50
+ versions = parts[1..-2].collect { |x| x.split(',') }.flatten # All except first and last
51
+
52
+ versions.each do |ver|
53
+ version, platform =
54
+ if ver.include?('-')
55
+ ver.split('-', 2)
56
+ else
57
+ [ver, 'ruby']
58
+ end
59
+ hash[gem_name] << [gem_name, ::Gem::Version.new(version), platform]
60
+ end
61
+ end
60
62
  hash
61
63
  end
62
64
 
data/lib/gemirro.rb CHANGED
@@ -4,14 +4,15 @@ require 'builder'
4
4
  require 'confstruct'
5
5
  require 'digest/sha2'
6
6
  require 'fileutils'
7
- require 'httpclient'
8
7
  require 'json'
9
8
  require 'logger'
9
+ require 'net/http'
10
+ require 'openssl'
11
+ require 'optparse'
10
12
  require 'parallel'
11
13
  require 'rubygems'
12
14
  require 'rubygems/indexer'
13
15
  require 'rubygems/user_interaction'
14
- require 'slop'
15
16
  require 'stringio'
16
17
  require 'tempfile'
17
18
  require 'zlib'
@@ -34,8 +35,3 @@ require 'gemirro/versions_fetcher'
34
35
  require 'gemirro/gems_fetcher'
35
36
 
36
37
  require 'gemirro/cli'
37
- require 'gemirro/cli/index'
38
- require 'gemirro/cli/init'
39
- require 'gemirro/cli/list'
40
- require 'gemirro/cli/server'
41
- require 'gemirro/cli/update'
@@ -1,7 +1,6 @@
1
1
  require 'spec_helper'
2
2
  require 'gemirro/cli'
3
3
  require 'gemirro/mirror_file'
4
- require 'slop'
5
4
 
6
5
  # Gemirro tests
7
6
  module Gemirro
@@ -10,21 +9,47 @@ module Gemirro
10
9
  describe 'CLI' do
11
10
  include FakeFS::SpecHelpers
12
11
 
13
- it 'should return options' do
14
- options = CLI.options
15
- expect(options).to be_a(::Slop)
16
- expect(options.config[:strict]).to be_truthy
17
- expect(options.config[:banner])
18
- .to eq('Usage: gemirro [COMMAND] [OPTIONS]')
19
- expect(options.to_s)
20
- .to match(/-v, --version(\s+)Shows the current version/)
21
- expect(options.to_s)
22
- .to match(/-h, --help(\s+)Display this help message./)
12
+ it 'should print version information for -v' do
13
+ expect { CLI.run(['-v']) }.to output(/gemirro v.* on ruby/).to_stdout
14
+ end
15
+
16
+ it 'should print version information for --version' do
17
+ expect { CLI.run(['--version']) }.to output(/gemirro v.* on ruby/).to_stdout
18
+ end
19
+
20
+ it 'should print usage for -h' do
21
+ expect { CLI.run(['-h']) }.to output(/Usage: gemirro \[COMMAND\] \[OPTIONS\]/).to_stdout
22
+ end
23
+
24
+ it 'should print usage for --help' do
25
+ expect { CLI.run(['--help']) }.to output(/Usage: gemirro \[COMMAND\] \[OPTIONS\]/).to_stdout
26
+ end
27
+
28
+ it 'should print usage when no argument is given' do
29
+ expect { CLI.run([]) }.to output(/Usage: gemirro \[COMMAND\] \[OPTIONS\]/).to_stdout
30
+ end
23
31
 
24
- version = options.fetch_option(:v)
25
- expect(version.short).to eq('v')
26
- expect(version.long).to eq('version')
27
- expect { version.call }.to output(/gemirro v.* on ruby/).to_stdout
32
+ it 'should list the available commands in the usage banner' do
33
+ output = capture_stdout { CLI.run(['-h']) }
34
+
35
+ CLI::COMMANDS.each do |name, description|
36
+ expect(output).to match(/#{name}(\s+)#{Regexp.escape(description)}/)
37
+ end
38
+ end
39
+
40
+ it 'should set $PROGRAM_NAME' do
41
+ $PROGRAM_NAME = 'rspec'
42
+ CLI.run(['-v'])
43
+ expect($PROGRAM_NAME).to eq('gemirro')
44
+ end
45
+
46
+ it 'should print an error and exit for an unknown command' do
47
+ expect { CLI.run(['nope']) }.to output(/Unknown command: nope/).to_stdout.and raise_error(SystemExit)
48
+ end
49
+
50
+ it 'should print an option error for an unknown flag' do
51
+ expect { CLI.run(['list', '--nope']) }
52
+ .to output(/invalid option: --nope/).to_stdout.and raise_error(SystemExit)
28
53
  end
29
54
 
30
55
  it 'should retrieve version information' do
@@ -45,6 +70,15 @@ module Gemirro
45
70
  file.write('test')
46
71
  expect { CLI.load_configuration('config.rb') }.to raise_error LoadError
47
72
  end
73
+
74
+ def capture_stdout
75
+ original = $stdout
76
+ $stdout = StringIO.new
77
+ yield
78
+ $stdout.string
79
+ ensure
80
+ $stdout = original
81
+ end
48
82
  end
49
83
  end
50
84
  end
@@ -39,8 +39,8 @@ module Gemirro
39
39
  expect(Configuration.marshal_identifier).to match(/Marshal\.(\d+)\.(\d+)/)
40
40
  end
41
41
 
42
- it 'should return versions file' do
43
- expect(Configuration.versions_file).to match(/specs\.(\d+)\.(\d+).gz/)
42
+ it 'should error on version since no source is defined' do
43
+ expect(Gemirro.configuration.versions_file).to match(nil)
44
44
  end
45
45
 
46
46
  it 'should return marshal file' do
@@ -84,5 +84,12 @@ module Gemirro
84
84
  expect(result.host).to eq('https://rubygems.org')
85
85
  expect(result.name).to eq('rubygems')
86
86
  end
87
+
88
+
89
+ it 'should return versions file with source' do
90
+ result = @config.define_source('RubyGems', 'https://rubygems.org') do
91
+ end
92
+ expect(@config.versions_file).to match(/rubygems_org_versions$/)
93
+ end
87
94
  end
88
95
  end
@@ -12,7 +12,7 @@ module Gemirro
12
12
 
13
13
  before(:each) do
14
14
  @source = Source.new('RubyGems', 'https://rubygems.org')
15
- @versions_file = VersionsFile.new(['0.0.1', '0.0.2'])
15
+ @versions_file = VersionsFile.new(%(created_at: 2025-04-24T03:46:59Z\n---\nrack 3.0.0,3.0.1 d545a45462d63b1b4865bbb89a109366))
16
16
  @fetcher = GemsFetcher.new(@source, @versions_file)
17
17
  Gemirro.configuration.ignored_gems.clear
18
18
  end
@@ -47,7 +47,7 @@ module Gemirro
47
47
  version = ::Gem::Version.new('0.0.1')
48
48
  Utils.configuration.ignore_gem('gemirro', '0.0.1', 'ruby')
49
49
  allow(@source).to receive(:fetch_gem)
50
- .once.with('gemirro', version).and_raise(ArgumentError)
50
+ .once.with('gemirro-0.0.1.gem').and_raise(ArgumentError)
51
51
  allow(Utils.logger).to receive(:error)
52
52
  .once.with(/Failed to retrieve/)
53
53
  allow(Utils.logger).to receive(:debug)
@@ -1,5 +1,6 @@
1
1
  require 'spec_helper'
2
- require 'httpclient'
2
+ require 'webmock/rspec'
3
+ require 'gemirro/utils'
3
4
  require 'gemirro/http'
4
5
 
5
6
  module Gemirro
@@ -7,88 +8,91 @@ module Gemirro
7
8
  let(:config) { double('Configuration') }
8
9
 
9
10
  before do
10
- Http.instance_variable_set(:@client, nil)
11
+ Http.reset!
11
12
  allow(Utils).to receive(:configuration).and_return(config)
12
13
  end
13
14
 
14
- describe '.client' do
15
- it 'initializes a new HTTPClient' do
16
- expect(Http.client).to be_a(HTTPClient)
17
- end
15
+ after { Http.reset! }
18
16
 
19
- context 'with proxy configuration' do
20
- it 'sets proxy configuration' do
21
- allow(config).to receive(:proxy).and_return('http://proxy.example.com:8080')
17
+ describe '.get' do
18
+ context 'with successful response' do
19
+ it 'returns the response' do
20
+ stub_request(:get, 'http://example.com/').to_return(status: 200, body: 'content')
21
+
22
+ response = Http.get('http://example.com/')
23
+ expect(response).to be_a(Net::HTTPSuccess)
24
+ expect(response.body).to eq('content')
25
+ end
26
+
27
+ it 'reuses the connection for a second request on the same host' do
28
+ stub_request(:get, 'http://example.com/one').to_return(status: 200, body: 'one')
29
+ stub_request(:get, 'http://example.com/two').to_return(status: 200, body: 'two')
30
+
31
+ Http.get('http://example.com/one')
32
+ Http.get('http://example.com/two')
22
33
 
23
- expect(Http.client.proxy.to_s).to eq('http://proxy.example.com:8080')
34
+ expect(a_request(:get, 'http://example.com/one')).to have_been_made.once
35
+ expect(a_request(:get, 'http://example.com/two')).to have_been_made.once
24
36
  end
25
37
  end
26
38
 
27
- context 'with SSL configuration' do
28
- context 'with invalid root CA path' do
29
- before do
30
- allow(config).to receive(:rootca).and_return('/nonexistent/ca.crt')
31
- allow(File).to receive(:file?).with('/nonexistent/ca.crt').and_return(false)
32
- end
39
+ context 'with a redirect' do
40
+ it 'follows it and returns the final response' do
41
+ stub_request(:get, 'http://example.com/old')
42
+ .to_return(status: 302, headers: { 'Location' => 'http://example.com/new' })
43
+ stub_request(:get, 'http://example.com/new').to_return(status: 200, body: 'moved')
33
44
 
34
- it 'aborts with error message' do
35
- expect { Http.client }.to raise_error(SystemExit)
36
- end
45
+ expect(Http.get('http://example.com/old').body).to eq('moved')
37
46
  end
38
47
 
39
- context 'with verify_mode disabled' do
40
- before do
41
- allow(config).to receive(:verify_mode).and_return(false)
42
- end
48
+ it 'raises after too many redirects' do
49
+ stub_request(:get, 'http://example.com/loop')
50
+ .to_return(status: 302, headers: { 'Location' => 'http://example.com/loop' })
43
51
 
44
- it 'sets SSL verify mode to VERIFY_NONE' do
45
- expect(Http.client.ssl_config.verify_mode).to eq(OpenSSL::SSL::VERIFY_NONE)
46
- end
52
+ expect { Http.get('http://example.com/loop') }
53
+ .to raise_error(Http::Error, /Too many redirects/)
47
54
  end
48
55
  end
49
56
 
50
- context 'with basic auth forced' do
51
- before do
52
- allow(config).to receive(:basic_auth).and_return(true)
53
- end
57
+ context 'with an error response' do
58
+ it 'raises Http::Error for a failed request' do
59
+ stub_request(:get, 'http://example.com/missing').to_return(status: [404, 'Not Found'])
54
60
 
55
- it 'forces basic authentication' do
56
- expect(Http.client.www_auth.basic_auth.force_auth).to be true
61
+ expect { Http.get('http://example.com/missing') }
62
+ .to raise_error(Http::Error, '404 Not Found')
57
63
  end
58
64
  end
59
- end
60
65
 
61
- describe '.get' do
62
- let(:client) { instance_double(HTTPClient) }
66
+ context 'with upstream authentication configured' do
67
+ it 'sends a basic auth header on every request' do
68
+ allow(config).to receive(:upstream_user).and_return('user')
69
+ allow(config).to receive(:upstream_password).and_return('pass')
70
+ stub_request(:get, 'http://example.com/private')
71
+ .with(basic_auth: %w[user pass])
72
+ .to_return(status: 200, body: 'ok')
63
73
 
64
- before do
65
- allow(Http).to receive(:client).and_return(client)
74
+ expect(Http.get('http://example.com/private').body).to eq('ok')
75
+ end
66
76
  end
67
77
 
68
- context 'with successful response' do
69
- let(:response) { double('Response', status: 200, body: 'content') }
78
+ context 'with proxy configuration' do
79
+ it 'routes the request through the configured proxy' do
80
+ allow(config).to receive(:proxy).and_return('http://proxy.example.com:8080')
81
+ stub_request(:get, 'http://example.com/').to_return(status: 200, body: 'content')
70
82
 
71
- it 'returns response for successful request' do
72
- allow(client).to receive(:get)
73
- .with('http://example.com', follow_redirect: true)
74
- .and_return(response)
83
+ Http.get('http://example.com/')
75
84
 
76
- expect(Http.get('http://example.com')).to eq(response)
85
+ expect(WebMock).to have_requested(:get, 'http://example.com/')
77
86
  end
78
87
  end
79
88
 
80
- context 'with error response' do
81
- let(:response) { double('Response', status: 404, reason: 'Not Found') }
82
-
83
- it 'raises BadResponseError for failed request' do
84
- allow(client).to receive(:get)
85
- .with('http://example.com', follow_redirect: true)
86
- .and_return(response)
89
+ context 'with an invalid rootca path' do
90
+ it 'aborts with an error message' do
91
+ allow(config).to receive(:rootca).and_return('/nonexistent/ca.crt')
87
92
 
88
- expect { Http.get('http://example.com') }
89
- .to raise_error(HTTPClient::BadResponseError, 'Not Found')
93
+ expect { Http.get('https://example.com/') }.to raise_error(SystemExit)
90
94
  end
91
95
  end
92
96
  end
93
97
  end
94
- end
98
+ end
@@ -24,7 +24,7 @@ module Gemirro
24
24
 
25
25
  dir = MirrorDirectory.new('/tmp')
26
26
  dir.add_directory('test')
27
- indexer = Indexer.new('/tmp/test')
27
+ indexer = Indexer.new
28
28
 
29
29
  Struct.new('HttpGet', :code, :body)
30
30
  http_get = Struct::HttpGet.new(200, 'bad')
@@ -43,7 +43,7 @@ module Gemirro
43
43
  allow(::Gem.configuration).to receive(:really_verbose)
44
44
  .once.and_return(true)
45
45
 
46
- indexer = Indexer.new('/tmp/test')
46
+ indexer = Indexer.new
47
47
  indexer.quick_marshal_dir = '/tmp/gem_generate_index/quick/Marshal.4.8'
48
48
  indexer.dest_directory = '/tmp/test'
49
49
  indexer.directory = '/tmp/gem_generate_index'
@@ -97,7 +97,7 @@ module Gemirro
97
97
  end
98
98
 
99
99
  it 'should build indices' do
100
- indexer = Indexer.new('/')
100
+ indexer = Indexer.new
101
101
  dir = MirrorDirectory.new('/')
102
102
  dir.add_directory('gems')
103
103
  dir.add_directory('quick')
@@ -140,7 +140,7 @@ module Gemirro
140
140
  end
141
141
 
142
142
  it 'should update index and exit ruby gems' do
143
- indexer = Indexer.new('/')
143
+ indexer = Indexer.new
144
144
  MirrorDirectory.new('/')
145
145
  MirrorFile.new('/specs.4.8').write('')
146
146
  expect { indexer.update_index }.to raise_error(::Gem::SystemExitException)
@@ -152,7 +152,7 @@ module Gemirro
152
152
  dir.add_directory('test/gems')
153
153
  dir.add_directory('test/quick')
154
154
 
155
- indexer = Indexer.new('/tmp/test')
155
+ indexer = Indexer.new
156
156
  indexer.quick_marshal_dir = '/tmp/gem_generate_index/quick/Marshal.4.8'
157
157
  indexer.dest_directory = '/tmp/test'
158
158
  indexer.directory = '/tmp/gem_generate_index'