rack-proxy 0.8.2 → 1.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.
@@ -1,321 +0,0 @@
1
- require "test_helper"
2
- require "rack/proxy"
3
-
4
- class RackProxyTest < Test::Unit::TestCase
5
- class HostProxy < Rack::Proxy
6
- attr_accessor :host
7
-
8
- def rewrite_env(env)
9
- env["HTTP_HOST"] = self.host || 'example.com'
10
- env
11
- end
12
- end
13
-
14
- def app(opts = {})
15
- return @app ||= HostProxy.new(opts)
16
- end
17
-
18
- def test_http_streaming
19
- get "/"
20
- assert last_response.ok?
21
-
22
- assert_match(/Example Domain/, last_response.body)
23
- end
24
-
25
- def test_http_full_request
26
- app(:streaming => false)
27
- get "/"
28
- assert last_response.ok?
29
- assert_match(/Example Domain/, last_response.body)
30
- end
31
-
32
- def test_http_full_request_headers
33
- app(:streaming => false)
34
- app.host = 'httpbin.org'
35
- get "/cookies/set?test=1"
36
- assert !Array(last_response['Set-Cookie']).empty?, 'httpbin.org/cookies/set should set a cookie'
37
- end
38
-
39
- def test_https_streaming
40
- app.host = 'www.apple.com'
41
- get 'https://example.com'
42
- assert last_response.ok?
43
- assert_match(/(itunes|iphone|ipod|mac|ipad)/, last_response.body)
44
- end
45
-
46
- def test_https_streaming_tls
47
- app(:ssl_version => :TLSv1_2).host = 'www.apple.com'
48
- get 'https://example.com'
49
- assert last_response.ok?
50
- assert_match(/(itunes|iphone|ipod|mac|ipad)/, last_response.body)
51
- end
52
-
53
- def test_https_full_request
54
- app(:streaming => false).host = 'www.apple.com'
55
- get 'https://example.com'
56
- assert last_response.ok?
57
- assert_match(/(itunes|iphone|ipod|mac|ipad)/, last_response.body)
58
- end
59
-
60
- def test_https_full_request_tls
61
- app({:streaming => false, :ssl_version => :TLSv1_2}).host = 'www.apple.com'
62
- get 'https://example.com'
63
- assert last_response.ok?
64
- assert_match(/(itunes|iphone|ipod|mac|ipad)/, last_response.body)
65
- end
66
-
67
- def test_normalize_headers
68
- proxy_class = Rack::Proxy
69
- headers = { 'header_array' => ['first_entry'], 'header_non_array' => :entry }
70
-
71
- normalized_headers = proxy_class.send(:normalize_headers, headers)
72
- expected_class = Rack.const_defined?(:Headers) ? Rack::Headers : Rack::Utils::HeaderHash
73
- assert normalized_headers.instance_of?(expected_class)
74
- assert normalized_headers['header_array'] == 'first_entry'
75
- assert normalized_headers['header_non_array'] == :entry
76
- end
77
-
78
- def test_header_reconstruction
79
- proxy_class = Rack::Proxy
80
-
81
- header = proxy_class.send(:reconstruct_header_name, "HTTP_ABC")
82
- assert header == "Abc"
83
-
84
- header = proxy_class.send(:reconstruct_header_name, "HTTP_ABC_D")
85
- assert header == "Abc-D"
86
- end
87
-
88
- def test_extract_http_request_headers
89
- proxy_class = Rack::Proxy
90
- env = {
91
- 'NOT-HTTP-HEADER' => 'test-value',
92
- 'HTTP_ACCEPT' => 'text/html',
93
- 'HTTP_CONNECTION' => nil,
94
- 'HTTP_CONTENT_MD5' => 'deadbeef',
95
- 'HTTP_HEADER.WITH.PERIODS' => 'stillmooing'
96
- }
97
-
98
- headers = proxy_class.extract_http_request_headers(env)
99
- assert headers.key?('ACCEPT')
100
- assert headers.key?('CONTENT-MD5')
101
- assert headers.key?('HEADER.WITH.PERIODS')
102
- assert !headers.key?('CONNECTION')
103
- assert !headers.key?('NOT-HTTP-HEADER')
104
- end
105
-
106
- def test_duplicate_headers
107
- proxy_class = Rack::Proxy
108
- env = { 'Set-Cookie' => ["cookie1=foo", "cookie2=bar"] }
109
-
110
- headers = proxy_class.normalize_headers(env)
111
- assert headers['Set-Cookie'].include?('cookie1=foo'), "Include the first value"
112
- assert headers['Set-Cookie'].include?("\n"), "Join multiple cookies with newlines"
113
- assert headers['Set-Cookie'].include?('cookie2=bar'), "Include the second value"
114
- end
115
-
116
-
117
- def test_handles_missing_content_length
118
- assert_nothing_thrown do
119
- post "/", nil, "CONTENT_LENGTH" => nil
120
- end
121
- end
122
-
123
- def test_response_header_included_Hop_by_hop
124
- app({:streaming => true}).host = 'mockapi.io'
125
- get 'https://example.com/oauth2/token/info?access_token=123'
126
- assert !last_response.headers.key?('transfer-encoding')
127
- end
128
-
129
- # Issue #58: connection errors should return 502, not raise.
130
- def test_connection_refused_returns_502
131
- # Bind a socket to find a free port, then close it so connection is refused.
132
- server = TCPServer.new('127.0.0.1', 0)
133
- closed_port = server.addr[1]
134
- server.close
135
-
136
- app({:streaming => false}).host = "127.0.0.1:#{closed_port}"
137
- get '/'
138
- assert_equal 502, last_response.status
139
- assert_equal '', last_response.body
140
- end
141
-
142
- def test_connection_refused_returns_502_streaming
143
- server = TCPServer.new('127.0.0.1', 0)
144
- closed_port = server.addr[1]
145
- server.close
146
-
147
- app({:streaming => true}).host = "127.0.0.1:#{closed_port}"
148
- get '/'
149
- assert_equal 502, last_response.status
150
- assert_equal '', last_response.body
151
- end
152
-
153
- def test_unknown_host_returns_502
154
- app({:streaming => false}).host = 'no-such-host.invalid'
155
- get '/'
156
- assert_equal 502, last_response.status
157
- end
158
-
159
- # Issues #122/#123: body should be [] for empty responses and for status
160
- # codes that don't allow an entity body (1xx, 204, 304).
161
- def test_no_entity_body_for_204
162
- with_webrick_proxy(streaming: false) do |port, proxy|
163
- proxy.host = "127.0.0.1:#{port}"
164
- get '/no-content'
165
- assert_equal 204, last_response.status
166
- assert_equal '', last_response.body
167
- end
168
- end
169
-
170
- def test_no_entity_body_for_304
171
- with_webrick_proxy(streaming: false) do |port, proxy|
172
- proxy.host = "127.0.0.1:#{port}"
173
- get '/not-modified'
174
- assert_equal 304, last_response.status
175
- assert_equal '', last_response.body
176
- end
177
- end
178
-
179
- def test_empty_body_is_not_array_with_empty_string
180
- with_webrick_proxy(streaming: false) do |port, proxy|
181
- proxy.host = "127.0.0.1:#{port}"
182
- get '/empty'
183
- assert_equal 200, last_response.status
184
- assert_equal '', last_response.body
185
- end
186
- end
187
-
188
- # Issue #65: header values must be strings, not single-element arrays,
189
- # for both streaming and non-streaming paths.
190
- def test_header_values_are_strings_streaming
191
- assert_no_array_header_values(streaming: true)
192
- end
193
-
194
- def test_header_values_are_strings_non_streaming
195
- assert_no_array_header_values(streaming: false)
196
- end
197
-
198
- # Issue #113: SSL cert verification must default to VERIFY_PEER (Ruby's
199
- # Net::HTTP default), not VERIFY_NONE.
200
- def test_ssl_default_is_verify_peer
201
- proxy = Rack::Proxy.new
202
- assert_nil proxy.instance_variable_get(:@verify_mode),
203
- "@verify_mode should be unset by default so VERIFY_PEER applies at request time"
204
- end
205
-
206
- def test_ssl_verify_none_opt_in
207
- proxy = Rack::Proxy.new(ssl_verify_none: true)
208
- assert_equal OpenSSL::SSL::VERIFY_NONE, proxy.instance_variable_get(:@verify_mode)
209
- end
210
-
211
- def test_explicit_verify_mode_wins_over_ssl_verify_none
212
- proxy = Rack::Proxy.new(ssl_verify_none: true, verify_mode: OpenSSL::SSL::VERIFY_PEER)
213
- assert_equal OpenSSL::SSL::VERIFY_PEER, proxy.instance_variable_get(:@verify_mode)
214
- end
215
-
216
- def test_https_default_rejects_invalid_certificate
217
- # self-signed cert on a public test host should be rejected with the new default
218
- app({:streaming => false}).host = 'self-signed.badssl.com'
219
- error = assert_raise(OpenSSL::SSL::SSLError) { get 'https://example.com/' }
220
- assert_match(/certificate verify failed/, error.message)
221
- end
222
-
223
- def test_https_with_ssl_verify_none_accepts_invalid_certificate
224
- app({:streaming => false, :ssl_verify_none => true}).host = 'self-signed.badssl.com'
225
- get 'https://example.com/'
226
- assert last_response.ok?
227
- end
228
-
229
- # Issue #80: a :logger option should pipe Net::HTTP debug output to the
230
- # given sink (anything responding to #<<). We use a StringIO to capture it.
231
- def test_logger_captures_request_in_non_streaming
232
- sink = StringIO.new
233
- with_webrick_proxy(streaming: false, logger: sink) do |port, proxy|
234
- proxy.host = "127.0.0.1:#{port}"
235
- get '/empty'
236
- assert last_response.ok?
237
- end
238
- assert_match(/GET \/empty/, sink.string,
239
- "expected debug output to include request line, got: #{sink.string.inspect}")
240
- end
241
-
242
- def test_logger_captures_request_in_streaming
243
- sink = StringIO.new
244
- with_webrick_proxy(streaming: true, logger: sink) do |port, proxy|
245
- proxy.host = "127.0.0.1:#{port}"
246
- get '/empty'
247
- assert last_response.ok?
248
- end
249
- assert_match(/GET \/empty/, sink.string,
250
- "expected debug output to include request line, got: #{sink.string.inspect}")
251
- end
252
-
253
- # Regression: build_header_hash must not match a top-level ::Headers
254
- # constant defined by the host app (would happen with inherit: true).
255
- def test_build_header_hash_ignores_toplevel_headers_constant
256
- Object.send(:remove_const, :Headers) if Object.const_defined?(:Headers, false)
257
- Object.const_set(:Headers, Class.new)
258
- begin
259
- result = Rack::Proxy.send(:build_header_hash, [['X-Test', 'value']])
260
- # On Rack 3+ we get Rack::Headers; on Rack 2 we get Rack::Utils::HeaderHash.
261
- # In neither case should we get the bogus top-level ::Headers.
262
- assert_not_equal ::Headers, result.class,
263
- "build_header_hash leaked into top-level ::Headers"
264
- ensure
265
- Object.send(:remove_const, :Headers)
266
- end
267
- end
268
-
269
- def test_no_logger_means_no_debug_output
270
- # Without a :logger option, Net::HTTP's set_debug_output should never be
271
- # called. We can't directly assert that, but we can confirm requests still
272
- # work when no logger is configured (covered by every other test).
273
- with_webrick_proxy(streaming: false) do |port, proxy|
274
- proxy.host = "127.0.0.1:#{port}"
275
- get '/empty'
276
- assert last_response.ok?
277
- end
278
- end
279
-
280
- private
281
-
282
- def assert_no_array_header_values(streaming:)
283
- with_webrick_proxy(streaming: streaming) do |port, proxy|
284
- proxy.host = "127.0.0.1:#{port}"
285
- get '/echo-headers'
286
- array_valued = last_response.headers.select { |_, v| v.is_a?(Array) }
287
- assert_empty array_valued,
288
- "expected no Array-valued headers (#65), got: #{array_valued.inspect}"
289
- assert_equal 'value-here', last_response['x-custom']
290
- end
291
- end
292
-
293
-
294
- # Spin up a tiny WEBrick server with fixed routes so we can exercise the
295
- # proxy against real Net::HTTP requests without depending on a remote host.
296
- def with_webrick_proxy(**proxy_opts)
297
- require 'webrick'
298
- server = WEBrick::HTTPServer.new(
299
- Port: 0,
300
- BindAddress: '127.0.0.1',
301
- Logger: WEBrick::Log.new(File::NULL),
302
- AccessLog: []
303
- )
304
- server.mount_proc('/no-content') { |_req, res| res.status = 204 }
305
- server.mount_proc('/not-modified') { |_req, res| res.status = 304 }
306
- server.mount_proc('/empty') { |_req, res| res.body = '' }
307
- server.mount_proc('/echo-headers') do |_req, res|
308
- res['x-custom'] = 'value-here'
309
- res.body = 'ok'
310
- end
311
- Thread.new { server.start }
312
- port = server.config[:Port]
313
-
314
- proxy = HostProxy.new(**proxy_opts)
315
- @app = proxy
316
- yield port, proxy
317
- ensure
318
- server&.shutdown
319
- @app = nil
320
- end
321
- end
data/test/test_helper.rb DELETED
@@ -1,11 +0,0 @@
1
- require "rubygems"
2
- require 'bundler/setup'
3
- require 'bundler/gem_tasks'
4
- require "test/unit"
5
-
6
- require "rack"
7
- require "rack/test"
8
-
9
- Test::Unit::TestCase.class_eval do
10
- include Rack::Test::Methods
11
- end