faraday 0.7.4 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +276 -0
  3. data/LICENSE.md +1 -1
  4. data/README.md +40 -153
  5. data/Rakefile +4 -139
  6. data/examples/client_spec.rb +65 -0
  7. data/examples/client_test.rb +79 -0
  8. data/lib/faraday/adapter/em_http.rb +286 -0
  9. data/lib/faraday/adapter/em_http_ssl_patch.rb +62 -0
  10. data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +69 -0
  11. data/lib/faraday/adapter/em_synchrony.rb +120 -36
  12. data/lib/faraday/adapter/excon.rb +108 -12
  13. data/lib/faraday/adapter/httpclient.rb +152 -0
  14. data/lib/faraday/adapter/net_http.rb +187 -43
  15. data/lib/faraday/adapter/net_http_persistent.rb +91 -0
  16. data/lib/faraday/adapter/patron.rb +106 -10
  17. data/lib/faraday/adapter/rack.rb +75 -0
  18. data/lib/faraday/adapter/test.rb +160 -61
  19. data/lib/faraday/adapter/typhoeus.rb +7 -46
  20. data/lib/faraday/adapter.rb +105 -33
  21. data/lib/faraday/adapter_registry.rb +30 -0
  22. data/lib/faraday/autoload.rb +95 -0
  23. data/lib/faraday/connection.rb +525 -157
  24. data/lib/faraday/dependency_loader.rb +37 -0
  25. data/lib/faraday/encoders/flat_params_encoder.rb +98 -0
  26. data/lib/faraday/encoders/nested_params_encoder.rb +171 -0
  27. data/lib/faraday/error.rb +122 -30
  28. data/lib/faraday/file_part.rb +128 -0
  29. data/lib/faraday/logging/formatter.rb +105 -0
  30. data/lib/faraday/middleware.rb +14 -22
  31. data/lib/faraday/middleware_registry.rb +129 -0
  32. data/lib/faraday/options/connection_options.rb +22 -0
  33. data/lib/faraday/options/env.rb +181 -0
  34. data/lib/faraday/options/proxy_options.rb +28 -0
  35. data/lib/faraday/options/request_options.rb +22 -0
  36. data/lib/faraday/options/ssl_options.rb +59 -0
  37. data/lib/faraday/options.rb +222 -0
  38. data/lib/faraday/param_part.rb +53 -0
  39. data/lib/faraday/parameters.rb +5 -0
  40. data/lib/faraday/rack_builder.rb +248 -0
  41. data/lib/faraday/request/authorization.rb +55 -0
  42. data/lib/faraday/request/basic_authentication.rb +20 -0
  43. data/lib/faraday/request/instrumentation.rb +54 -0
  44. data/lib/faraday/request/multipart.rb +84 -48
  45. data/lib/faraday/request/retry.rb +239 -0
  46. data/lib/faraday/request/token_authentication.rb +20 -0
  47. data/lib/faraday/request/url_encoded.rb +46 -27
  48. data/lib/faraday/request.rb +112 -50
  49. data/lib/faraday/response/logger.rb +24 -25
  50. data/lib/faraday/response/raise_error.rb +40 -11
  51. data/lib/faraday/response.rb +44 -35
  52. data/lib/faraday/utils/headers.rb +139 -0
  53. data/lib/faraday/utils/params_hash.rb +61 -0
  54. data/lib/faraday/utils.rb +72 -117
  55. data/lib/faraday.rb +142 -64
  56. data/spec/external_adapters/faraday_specs_setup.rb +14 -0
  57. data/spec/faraday/adapter/em_http_spec.rb +47 -0
  58. data/spec/faraday/adapter/em_synchrony_spec.rb +16 -0
  59. data/spec/faraday/adapter/excon_spec.rb +49 -0
  60. data/spec/faraday/adapter/httpclient_spec.rb +73 -0
  61. data/spec/faraday/adapter/net_http_persistent_spec.rb +57 -0
  62. data/spec/faraday/adapter/net_http_spec.rb +64 -0
  63. data/spec/faraday/adapter/patron_spec.rb +18 -0
  64. data/spec/faraday/adapter/rack_spec.rb +8 -0
  65. data/spec/faraday/adapter/typhoeus_spec.rb +7 -0
  66. data/spec/faraday/adapter_registry_spec.rb +28 -0
  67. data/spec/faraday/adapter_spec.rb +55 -0
  68. data/spec/faraday/composite_read_io_spec.rb +80 -0
  69. data/spec/faraday/connection_spec.rb +691 -0
  70. data/spec/faraday/error_spec.rb +45 -0
  71. data/spec/faraday/middleware_spec.rb +26 -0
  72. data/spec/faraday/options/env_spec.rb +70 -0
  73. data/spec/faraday/options/options_spec.rb +297 -0
  74. data/spec/faraday/options/proxy_options_spec.rb +37 -0
  75. data/spec/faraday/options/request_options_spec.rb +19 -0
  76. data/spec/faraday/params_encoders/flat_spec.rb +34 -0
  77. data/spec/faraday/params_encoders/nested_spec.rb +134 -0
  78. data/spec/faraday/rack_builder_spec.rb +196 -0
  79. data/spec/faraday/request/authorization_spec.rb +88 -0
  80. data/spec/faraday/request/instrumentation_spec.rb +76 -0
  81. data/spec/faraday/request/multipart_spec.rb +274 -0
  82. data/spec/faraday/request/retry_spec.rb +242 -0
  83. data/spec/faraday/request/url_encoded_spec.rb +83 -0
  84. data/spec/faraday/request_spec.rb +109 -0
  85. data/spec/faraday/response/logger_spec.rb +220 -0
  86. data/spec/faraday/response/middleware_spec.rb +68 -0
  87. data/spec/faraday/response/raise_error_spec.rb +106 -0
  88. data/spec/faraday/response_spec.rb +75 -0
  89. data/spec/faraday/utils/headers_spec.rb +82 -0
  90. data/spec/faraday/utils_spec.rb +56 -0
  91. data/spec/faraday_spec.rb +37 -0
  92. data/spec/spec_helper.rb +132 -0
  93. data/spec/support/disabling_stub.rb +14 -0
  94. data/spec/support/fake_safe_buffer.rb +15 -0
  95. data/spec/support/helper_methods.rb +133 -0
  96. data/spec/support/shared_examples/adapter.rb +104 -0
  97. data/spec/support/shared_examples/params_encoder.rb +18 -0
  98. data/spec/support/shared_examples/request_method.rb +234 -0
  99. data/spec/support/streaming_response_checker.rb +35 -0
  100. data/spec/support/webmock_rack_app.rb +68 -0
  101. metadata +126 -126
  102. data/Gemfile +0 -29
  103. data/config.ru +0 -6
  104. data/faraday.gemspec +0 -92
  105. data/lib/faraday/adapter/action_dispatch.rb +0 -29
  106. data/lib/faraday/builder.rb +0 -160
  107. data/lib/faraday/request/json.rb +0 -35
  108. data/lib/faraday/upload_io.rb +0 -23
  109. data/test/adapters/live_test.rb +0 -205
  110. data/test/adapters/logger_test.rb +0 -37
  111. data/test/adapters/net_http_test.rb +0 -33
  112. data/test/adapters/test_middleware_test.rb +0 -70
  113. data/test/connection_test.rb +0 -254
  114. data/test/env_test.rb +0 -158
  115. data/test/helper.rb +0 -41
  116. data/test/live_server.rb +0 -45
  117. data/test/middleware_stack_test.rb +0 -118
  118. data/test/request_middleware_test.rb +0 -116
  119. data/test/response_middleware_test.rb +0 -74
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'multipart_parser/reader'
4
+
5
+ module Faraday
6
+ module HelperMethods
7
+ def self.included(base)
8
+ base.extend ClassMethods
9
+ end
10
+
11
+ module ClassMethods
12
+ def features(*features)
13
+ @features = features
14
+ end
15
+
16
+ def on_feature(name)
17
+ yield if block_given? && feature?(name)
18
+ end
19
+
20
+ def feature?(name)
21
+ if @features.nil?
22
+ superclass.feature?(name) if superclass.respond_to?(:feature?)
23
+ elsif @features.include?(name)
24
+ true
25
+ end
26
+ end
27
+
28
+ def method_with_body?(method)
29
+ METHODS_WITH_BODY.include?(method.to_s)
30
+ end
31
+ end
32
+
33
+ def ssl_mode?
34
+ ENV['SSL'] == 'yes'
35
+ end
36
+
37
+ def normalize(url)
38
+ Faraday::Utils::URI(url)
39
+ end
40
+
41
+ def with_default_uri_parser(parser)
42
+ old_parser = Faraday::Utils.default_uri_parser
43
+ begin
44
+ Faraday::Utils.default_uri_parser = parser
45
+ yield
46
+ ensure
47
+ Faraday::Utils.default_uri_parser = old_parser
48
+ end
49
+ end
50
+
51
+ def with_env(new_env)
52
+ old_env = {}
53
+
54
+ new_env.each do |key, value|
55
+ old_env[key] = ENV.fetch(key, false)
56
+ ENV[key] = value
57
+ end
58
+
59
+ begin
60
+ yield
61
+ ensure
62
+ old_env.each do |key, value|
63
+ value == false ? ENV.delete(key) : ENV[key] = value
64
+ end
65
+ end
66
+ end
67
+
68
+ def with_env_proxy_disabled
69
+ Faraday.ignore_env_proxy = true
70
+
71
+ begin
72
+ yield
73
+ ensure
74
+ Faraday.ignore_env_proxy = false
75
+ end
76
+ end
77
+
78
+ def capture_warnings
79
+ old = $stderr
80
+ $stderr = StringIO.new
81
+ begin
82
+ yield
83
+ $stderr.string
84
+ ensure
85
+ $stderr = old
86
+ end
87
+ end
88
+
89
+ def multipart_file
90
+ Faraday::FilePart.new(__FILE__, 'text/x-ruby')
91
+ end
92
+
93
+ # parse boundary out of a Content-Type header like:
94
+ # Content-Type: multipart/form-data; boundary=gc0p4Jq0M2Yt08jU534c0p
95
+ def parse_multipart_boundary(ctype)
96
+ MultipartParser::Reader.extract_boundary_value(ctype)
97
+ end
98
+
99
+ # parse a multipart MIME message, returning a hash of any multipart errors
100
+ def parse_multipart(boundary, body)
101
+ reader = MultipartParser::Reader.new(boundary)
102
+ result = { errors: [], parts: [] }
103
+ def result.part(name)
104
+ hash = self[:parts].detect { |h| h[:part].name == name }
105
+ [hash[:part], hash[:body].join]
106
+ end
107
+
108
+ reader.on_part do |part|
109
+ result[:parts] << thispart = {
110
+ part: part,
111
+ body: []
112
+ }
113
+ part.on_data do |chunk|
114
+ thispart[:body] << chunk
115
+ end
116
+ end
117
+ reader.on_error do |msg|
118
+ result[:errors] << msg
119
+ end
120
+ reader.write(body)
121
+ result
122
+ end
123
+
124
+ def method_with_body?(method)
125
+ self.class.method_with_body?(method)
126
+ end
127
+
128
+ def big_string
129
+ kb = 1024
130
+ (32..126).map(&:chr).cycle.take(50 * kb).join
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ shared_examples 'an adapter' do |**options|
4
+ before { skip } if options[:skip]
5
+
6
+ context 'with SSL enabled' do
7
+ before { ENV['SSL'] = 'yes' }
8
+ include_examples 'adapter examples', options
9
+ end
10
+
11
+ context 'with SSL disabled' do
12
+ before { ENV['SSL'] = 'no' }
13
+ include_examples 'adapter examples', options
14
+ end
15
+ end
16
+
17
+ shared_examples 'adapter examples' do |**options|
18
+ include Faraday::StreamingResponseChecker
19
+
20
+ let(:adapter) { described_class.name.split('::').last }
21
+
22
+ let(:conn_options) { { headers: { 'X-Faraday-Adapter' => adapter } }.merge(options[:conn_options] || {}) }
23
+
24
+ let(:adapter_options) do
25
+ return [] unless options[:adapter_options]
26
+
27
+ if options[:adapter_options].is_a?(Array)
28
+ options[:adapter_options]
29
+ else
30
+ [options[:adapter_options]]
31
+ end
32
+ end
33
+
34
+ let(:protocol) { ssl_mode? ? 'https' : 'http' }
35
+ let(:remote) { "#{protocol}://example.com" }
36
+
37
+ let(:conn) do
38
+ conn_options[:ssl] ||= {}
39
+ conn_options[:ssl][:ca_file] ||= ENV['SSL_FILE']
40
+
41
+ Faraday.new(remote, conn_options) do |conn|
42
+ conn.request :multipart
43
+ conn.request :url_encoded
44
+ conn.response :raise_error
45
+ conn.adapter described_class, *adapter_options
46
+ end
47
+ end
48
+
49
+ let!(:request_stub) { stub_request(http_method, remote) }
50
+
51
+ after do
52
+ expect(request_stub).to have_been_requested unless request_stub.disabled?
53
+ end
54
+
55
+ describe '#delete' do
56
+ let(:http_method) { :delete }
57
+
58
+ it_behaves_like 'a request method', :delete
59
+ end
60
+
61
+ describe '#get' do
62
+ let(:http_method) { :get }
63
+
64
+ it_behaves_like 'a request method', :get
65
+ end
66
+
67
+ describe '#head' do
68
+ let(:http_method) { :head }
69
+
70
+ it_behaves_like 'a request method', :head
71
+ end
72
+
73
+ describe '#options' do
74
+ let(:http_method) { :options }
75
+
76
+ it_behaves_like 'a request method', :options
77
+ end
78
+
79
+ describe '#patch' do
80
+ let(:http_method) { :patch }
81
+
82
+ it_behaves_like 'a request method', :patch
83
+ end
84
+
85
+ describe '#post' do
86
+ let(:http_method) { :post }
87
+
88
+ it_behaves_like 'a request method', :post
89
+ end
90
+
91
+ describe '#put' do
92
+ let(:http_method) { :put }
93
+
94
+ it_behaves_like 'a request method', :put
95
+ end
96
+
97
+ on_feature :trace_method do
98
+ describe '#trace' do
99
+ let(:http_method) { :trace }
100
+
101
+ it_behaves_like 'a request method', :trace
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ shared_examples 'a params encoder' do
4
+ it 'escapes safe buffer' do
5
+ monies = FakeSafeBuffer.new('$32,000.00')
6
+ expect(subject.encode('a' => monies)).to eq('a=%2432%2C000.00')
7
+ end
8
+
9
+ it 'raises type error for empty string' do
10
+ expect { subject.encode('') }.to raise_error(TypeError) do |error|
11
+ expect(error.message).to eq("Can't convert String into Hash.")
12
+ end
13
+ end
14
+
15
+ it 'encodes nil' do
16
+ expect(subject.encode('a' => nil)).to eq('a')
17
+ end
18
+ end
@@ -0,0 +1,234 @@
1
+ # frozen_string_literal: true
2
+
3
+ shared_examples 'a request method' do |http_method|
4
+ let(:query_or_body) { method_with_body?(http_method) ? :body : :query }
5
+ let(:response) { conn.public_send(http_method, '/') }
6
+
7
+ unless http_method == :head && feature?(:skip_response_body_on_head)
8
+ it 'retrieves the response body' do
9
+ res_body = 'test'
10
+ request_stub.to_return(body: res_body)
11
+ expect(conn.public_send(http_method, '/').body).to eq(res_body)
12
+ end
13
+ end
14
+
15
+ it 'handles headers with multiple values' do
16
+ request_stub.to_return(headers: { 'Set-Cookie' => 'one, two' })
17
+ expect(response.headers['set-cookie']).to eq('one, two')
18
+ end
19
+
20
+ it 'retrieves the response headers' do
21
+ request_stub.to_return(headers: { 'Content-Type' => 'text/plain' })
22
+ expect(response.headers['Content-Type']).to match(%r{text/plain})
23
+ expect(response.headers['content-type']).to match(%r{text/plain})
24
+ end
25
+
26
+ it 'sends user agent' do
27
+ request_stub.with(headers: { 'User-Agent' => 'Agent Faraday' })
28
+ conn.public_send(http_method, '/', nil, user_agent: 'Agent Faraday')
29
+ end
30
+
31
+ it 'represents empty body response as blank string' do
32
+ expect(response.body).to eq('')
33
+ end
34
+
35
+ it 'handles connection error' do
36
+ request_stub.disable
37
+ expect { conn.public_send(http_method, 'http://localhost:4') }.to raise_error(Faraday::ConnectionFailed)
38
+ end
39
+
40
+ on_feature :local_socket_binding do
41
+ it 'binds local socket' do
42
+ stub_request(http_method, 'http://example.com')
43
+
44
+ host = '1.2.3.4'
45
+ port = 1234
46
+ conn_options[:request] = { bind: { host: host, port: port } }
47
+
48
+ conn.public_send(http_method, '/')
49
+
50
+ expect(conn.options[:bind][:host]).to eq(host)
51
+ expect(conn.options[:bind][:port]).to eq(port)
52
+ end
53
+ end
54
+
55
+ # context 'when wrong ssl certificate is provided' do
56
+ # let(:ca_file_path) { 'tmp/faraday-different-ca-cert.crt' }
57
+ # before { conn_options.merge!(ssl: { ca_file: ca_file_path }) }
58
+ #
59
+ # it do
60
+ # expect { conn.public_send(http_method, '/') }.to raise_error(Faraday::SSLError) # do |ex|
61
+ # expect(ex.message).to include?('certificate')
62
+ # end
63
+ # end
64
+ # end
65
+
66
+ on_feature :request_body_on_query_methods do
67
+ it 'sends request body' do
68
+ request_stub.with(Hash[:body, 'test'])
69
+ res = if query_or_body == :body
70
+ conn.public_send(http_method, '/', 'test')
71
+ else
72
+ conn.public_send(http_method, '/') do |req|
73
+ req.body = 'test'
74
+ end
75
+ end
76
+ expect(res.env.request_body).to eq('test')
77
+ end
78
+ end
79
+
80
+ it 'sends url encoded parameters' do
81
+ payload = { name: 'zack' }
82
+ request_stub.with(Hash[query_or_body, payload])
83
+ res = conn.public_send(http_method, '/', payload)
84
+ if query_or_body == :query
85
+ expect(res.env.request_body).to be_nil
86
+ else
87
+ expect(res.env.request_body).to eq('name=zack')
88
+ end
89
+ end
90
+
91
+ it 'sends url encoded nested parameters' do
92
+ payload = { name: { first: 'zack' } }
93
+ request_stub.with(Hash[query_or_body, payload])
94
+ conn.public_send(http_method, '/', payload)
95
+ end
96
+
97
+ # TODO: This needs reimplementation: see https://github.com/lostisland/faraday/issues/718
98
+ # Should raise Faraday::TimeoutError
99
+ it 'supports timeout option' do
100
+ conn_options[:request] = { timeout: 1 }
101
+ request_stub.to_timeout
102
+ exc = adapter == 'NetHttp' ? Faraday::ConnectionFailed : Faraday::TimeoutError
103
+ expect { conn.public_send(http_method, '/') }.to raise_error(exc)
104
+ end
105
+
106
+ # TODO: This needs reimplementation: see https://github.com/lostisland/faraday/issues/718
107
+ # Should raise Faraday::ConnectionFailed
108
+ it 'supports open_timeout option' do
109
+ conn_options[:request] = { open_timeout: 1 }
110
+ request_stub.to_timeout
111
+ exc = adapter == 'NetHttp' ? Faraday::ConnectionFailed : Faraday::TimeoutError
112
+ expect { conn.public_send(http_method, '/') }.to raise_error(exc)
113
+ end
114
+
115
+ # Can't send files on get, head and delete methods
116
+ if method_with_body?(http_method)
117
+ it 'sends files' do
118
+ payload = { uploaded_file: multipart_file }
119
+ request_stub.with(headers: { 'Content-Type' => %r{\Amultipart/form-data} }) do |request|
120
+ # WebMock does not support matching body for multipart/form-data requests yet :(
121
+ # https://github.com/bblimke/webmock/issues/623
122
+ request.body =~ /RubyMultipartPost/
123
+ end
124
+ conn.public_send(http_method, '/', payload)
125
+ end
126
+ end
127
+
128
+ on_feature :reason_phrase_parse do
129
+ it 'parses the reason phrase' do
130
+ request_stub.to_return(status: [200, 'OK'])
131
+ expect(response.reason_phrase).to eq('OK')
132
+ end
133
+ end
134
+
135
+ on_feature :compression do
136
+ # Accept-Encoding header not sent for HEAD requests as body is not expected in the response.
137
+ unless http_method == :head
138
+ it 'handles gzip compression' do
139
+ request_stub.with(headers: { 'Accept-Encoding' => /\bgzip\b/ })
140
+ conn.public_send(http_method, '/')
141
+ end
142
+
143
+ it 'handles deflate compression' do
144
+ request_stub.with(headers: { 'Accept-Encoding' => /\bdeflate\b/ })
145
+ conn.public_send(http_method, '/')
146
+ end
147
+ end
148
+ end
149
+
150
+ on_feature :streaming do
151
+ describe 'streaming' do
152
+ let(:streamed) { [] }
153
+
154
+ context 'when response is empty' do
155
+ it do
156
+ conn.public_send(http_method, '/') do |req|
157
+ req.options.on_data = proc { |*args| streamed << args }
158
+ end
159
+
160
+ expect(streamed).to eq([['', 0]])
161
+ end
162
+ end
163
+
164
+ context 'when response contains big data' do
165
+ before { request_stub.to_return(body: big_string) }
166
+
167
+ it 'handles streaming' do
168
+ response = conn.public_send(http_method, '/') do |req|
169
+ req.options.on_data = proc { |*args| streamed << args }
170
+ end
171
+
172
+ expect(response.body).to eq('')
173
+ check_streaming_response(streamed, chunk_size: 16 * 1024)
174
+ end
175
+ end
176
+ end
177
+ end
178
+
179
+ on_feature :parallel do
180
+ context 'with parallel setup' do
181
+ before do
182
+ @resp1 = nil
183
+ @resp2 = nil
184
+ @payload1 = { a: '1' }
185
+ @payload2 = { b: '2' }
186
+
187
+ request_stub
188
+ .with(Hash[query_or_body, @payload1])
189
+ .to_return(body: @payload1.to_json)
190
+
191
+ stub_request(http_method, remote)
192
+ .with(Hash[query_or_body, @payload2])
193
+ .to_return(body: @payload2.to_json)
194
+
195
+ conn.in_parallel do
196
+ @resp1 = conn.public_send(http_method, '/', @payload1)
197
+ @resp2 = conn.public_send(http_method, '/', @payload2)
198
+
199
+ expect(conn.in_parallel?).to be_truthy
200
+ expect(@resp1.body).to be_nil
201
+ expect(@resp2.body).to be_nil
202
+ end
203
+
204
+ expect(conn.in_parallel?).to be_falsey
205
+ end
206
+
207
+ it 'handles parallel requests status' do
208
+ expect(@resp1&.status).to eq(200)
209
+ expect(@resp2&.status).to eq(200)
210
+ end
211
+
212
+ unless http_method == :head && feature?(:skip_response_body_on_head)
213
+ it 'handles parallel requests body' do
214
+ expect(@resp1&.body).to eq(@payload1.to_json)
215
+ expect(@resp2&.body).to eq(@payload2.to_json)
216
+ end
217
+ end
218
+ end
219
+ end
220
+
221
+ it 'handles requests with proxy' do
222
+ conn_options[:proxy] = 'http://google.co.uk'
223
+
224
+ res = conn.public_send(http_method, '/')
225
+ expect(res.status).to eq(200)
226
+ end
227
+
228
+ it 'handles proxy failures' do
229
+ conn_options[:proxy] = 'http://google.co.uk'
230
+ request_stub.to_return(status: 407)
231
+
232
+ expect { conn.public_send(http_method, '/') }.to raise_error(Faraday::ProxyAuthError)
233
+ end
234
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Faraday
4
+ module StreamingResponseChecker
5
+ def check_streaming_response(streamed, options = {})
6
+ opts = {
7
+ prefix: '',
8
+ streaming?: true
9
+ }.merge(options)
10
+
11
+ expected_response = opts[:prefix] + big_string
12
+
13
+ chunks, sizes = streamed.transpose
14
+
15
+ # Check that the total size of the chunks (via the last size returned)
16
+ # is the same size as the expected_response
17
+ expect(sizes.last).to eq(expected_response.bytesize)
18
+
19
+ start_index = 0
20
+ expected_chunks = []
21
+ chunks.each do |actual_chunk|
22
+ expected_chunk = expected_response[start_index..((start_index + actual_chunk.bytesize) - 1)]
23
+ expected_chunks << expected_chunk
24
+ start_index += expected_chunk.bytesize
25
+ end
26
+
27
+ # it's easier to read a smaller portion, so we check that first
28
+ expect(expected_chunks[0][0..255]).to eq(chunks[0][0..255])
29
+
30
+ [expected_chunks, chunks].transpose.each do |expected, actual|
31
+ expect(actual).to eq(expected)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Rack app used to test the Rack adapter.
4
+ # Uses Webmock to check if requests are registered, in which case it returns
5
+ # the registered response.
6
+ class WebmockRackApp
7
+ def call(env)
8
+ req_signature = WebMock::RequestSignature.new(
9
+ req_method(env),
10
+ req_uri(env),
11
+ body: req_body(env),
12
+ headers: req_headers(env)
13
+ )
14
+
15
+ WebMock::RequestRegistry
16
+ .instance
17
+ .requested_signatures
18
+ .put(req_signature)
19
+
20
+ process_response(req_signature)
21
+ end
22
+
23
+ def req_method(env)
24
+ env['REQUEST_METHOD'].downcase.to_sym
25
+ end
26
+
27
+ def req_uri(env)
28
+ scheme = env['rack.url_scheme']
29
+ host = env['SERVER_NAME']
30
+ port = env['SERVER_PORT']
31
+ path = env['PATH_INFO']
32
+ query = env['QUERY_STRING']
33
+
34
+ url = +"#{scheme}://#{host}:#{port}#{path}"
35
+ url += "?#{query}" if query
36
+
37
+ uri = WebMock::Util::URI.heuristic_parse(url)
38
+ uri.path = uri.normalized_path.gsub('[^:]//', '/')
39
+ uri
40
+ end
41
+
42
+ def req_headers(env)
43
+ http_headers = env.select { |k, _| k.start_with?('HTTP_') }
44
+ .map { |k, v| [k[5..-1], v] }
45
+ .to_h
46
+
47
+ special_headers = Faraday::Adapter::Rack::SPECIAL_HEADERS
48
+ http_headers.merge(env.select { |k, _| special_headers.include?(k) })
49
+ end
50
+
51
+ def req_body(env)
52
+ env['rack.input'].read
53
+ end
54
+
55
+ def process_response(req_signature)
56
+ res = WebMock::StubRegistry.instance.response_for_request(req_signature)
57
+
58
+ if res.nil? && req_signature.uri.host == 'localhost'
59
+ raise Faraday::ConnectionFailed, 'Trying to connect to localhost'
60
+ end
61
+
62
+ raise WebMock::NetConnectNotAllowedError, req_signature unless res
63
+
64
+ raise Faraday::TimeoutError if res.should_timeout
65
+
66
+ [res.status[0], res.headers || {}, [res.body || '']]
67
+ end
68
+ end