http 5.0.0.pre3 → 5.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +65 -0
  3. data/.gitignore +6 -10
  4. data/.rspec +0 -4
  5. data/.rubocop/layout.yml +8 -0
  6. data/.rubocop/style.yml +32 -0
  7. data/.rubocop.yml +7 -124
  8. data/.rubocop_todo.yml +192 -0
  9. data/CHANGES.md +123 -1
  10. data/Gemfile +18 -11
  11. data/LICENSE.txt +1 -1
  12. data/README.md +47 -87
  13. data/Rakefile +2 -10
  14. data/http.gemspec +3 -3
  15. data/lib/http/chainable.rb +15 -14
  16. data/lib/http/client.rb +26 -15
  17. data/lib/http/connection.rb +7 -3
  18. data/lib/http/content_type.rb +10 -5
  19. data/lib/http/feature.rb +1 -1
  20. data/lib/http/features/auto_inflate.rb +0 -2
  21. data/lib/http/features/instrumentation.rb +1 -1
  22. data/lib/http/features/logging.rb +19 -21
  23. data/lib/http/headers.rb +3 -3
  24. data/lib/http/mime_type/adapter.rb +2 -0
  25. data/lib/http/options.rb +2 -2
  26. data/lib/http/redirector.rb +1 -1
  27. data/lib/http/request/writer.rb +6 -2
  28. data/lib/http/request.rb +22 -5
  29. data/lib/http/response/body.rb +5 -4
  30. data/lib/http/response/inflater.rb +1 -1
  31. data/lib/http/response/parser.rb +72 -64
  32. data/lib/http/response/status.rb +2 -2
  33. data/lib/http/response.rb +24 -6
  34. data/lib/http/timeout/global.rb +18 -30
  35. data/lib/http/timeout/null.rb +2 -1
  36. data/lib/http/timeout/per_operation.rb +31 -55
  37. data/lib/http/version.rb +1 -1
  38. data/spec/lib/http/client_spec.rb +109 -41
  39. data/spec/lib/http/features/auto_inflate_spec.rb +0 -1
  40. data/spec/lib/http/features/instrumentation_spec.rb +21 -16
  41. data/spec/lib/http/features/logging_spec.rb +2 -5
  42. data/spec/lib/http/headers_spec.rb +3 -3
  43. data/spec/lib/http/redirector_spec.rb +44 -0
  44. data/spec/lib/http/request/body_spec.rb +3 -3
  45. data/spec/lib/http/request/writer_spec.rb +12 -1
  46. data/spec/lib/http/response/body_spec.rb +5 -5
  47. data/spec/lib/http/response/parser_spec.rb +5 -5
  48. data/spec/lib/http/response_spec.rb +62 -10
  49. data/spec/lib/http_spec.rb +20 -2
  50. data/spec/spec_helper.rb +21 -21
  51. data/spec/support/black_hole.rb +1 -1
  52. data/spec/support/dummy_server/servlet.rb +14 -2
  53. data/spec/support/dummy_server.rb +1 -1
  54. data/spec/support/fuubar.rb +21 -0
  55. data/spec/support/simplecov.rb +19 -0
  56. metadata +23 -18
  57. data/.coveralls.yml +0 -1
  58. data/.travis.yml +0 -38
@@ -4,50 +4,55 @@
4
4
  require "support/http_handling_shared"
5
5
  require "support/dummy_server"
6
6
  require "support/ssl_helper"
7
+ require "logger"
7
8
 
8
9
  RSpec.describe HTTP::Client do
9
10
  run_server(:dummy) { DummyServer.new }
10
11
 
11
- StubbedClient = Class.new(HTTP::Client) do
12
- def perform(request, options)
13
- stubbed = stubs[request.uri]
14
- stubbed ? stubbed.call(request) : super(request, options)
15
- end
16
-
17
- def stubs
18
- @stubs ||= {}
19
- end
12
+ before do
13
+ stubbed_client = Class.new(HTTP::Client) do
14
+ def perform(request, options)
15
+ stubbed = stubs[HTTP::URI::NORMALIZER.call(request.uri).to_s]
16
+ stubbed ? stubbed.call(request) : super(request, options)
17
+ end
20
18
 
21
- def stub(stubs)
22
- @stubs = stubs.each_with_object({}) do |(k, v), o|
23
- o[HTTP::URI.parse k] = v
19
+ def stubs
20
+ @stubs ||= {}
24
21
  end
25
22
 
26
- self
23
+ def stub(stubs)
24
+ @stubs = stubs.transform_keys do |k|
25
+ HTTP::URI::NORMALIZER.call(k).to_s
26
+ end
27
+
28
+ self
29
+ end
27
30
  end
28
- end
29
31
 
30
- def redirect_response(location, status = 302)
31
- lambda do |request|
32
- HTTP::Response.new(
33
- :status => status,
34
- :version => "1.1",
35
- :headers => {"Location" => location},
36
- :body => "",
37
- :request => request
38
- )
32
+ def redirect_response(location, status = 302)
33
+ lambda do |request|
34
+ HTTP::Response.new(
35
+ :status => status,
36
+ :version => "1.1",
37
+ :headers => {"Location" => location},
38
+ :body => "",
39
+ :request => request
40
+ )
41
+ end
39
42
  end
40
- end
41
43
 
42
- def simple_response(body, status = 200)
43
- lambda do |request|
44
- HTTP::Response.new(
45
- :status => status,
46
- :version => "1.1",
47
- :body => body,
48
- :request => request
49
- )
44
+ def simple_response(body, status = 200)
45
+ lambda do |request|
46
+ HTTP::Response.new(
47
+ :status => status,
48
+ :version => "1.1",
49
+ :body => body,
50
+ :request => request
51
+ )
52
+ end
50
53
  end
54
+
55
+ stub_const("StubbedClient", stubbed_client)
51
56
  end
52
57
 
53
58
  describe "following redirects" do
@@ -105,13 +110,45 @@ RSpec.describe HTTP::Client do
105
110
  end
106
111
 
107
112
  it "works like a charm in real world" do
108
- url = "http://git.io/jNeY"
109
- client = HTTP.follow
110
- expect(client.get(url).to_s).to include "support for non-ascii URIs"
113
+ expect(HTTP.follow.get("https://bit.ly/2UaBT4R").parse(:json)).
114
+ to include("url" => "https://httpbin.org/anything/könig")
111
115
  end
112
116
  end
113
117
  end
114
118
 
119
+ describe "following redirects with logging" do
120
+ let(:logger) do
121
+ logger = Logger.new(logdev)
122
+ logger.formatter = ->(severity, _, _, message) { format("** %s **\n%s\n", severity, message) }
123
+ logger.level = Logger::INFO
124
+ logger
125
+ end
126
+
127
+ let(:logdev) { StringIO.new }
128
+
129
+ it "logs all requests" do
130
+ client = StubbedClient.new(:follow => true, :features => { :logging => { :logger => logger } }).stub(
131
+ "http://example.com/" => redirect_response("/1"),
132
+ "http://example.com/1" => redirect_response("/2"),
133
+ "http://example.com/2" => redirect_response("/3"),
134
+ "http://example.com/3" => simple_response("OK")
135
+ )
136
+
137
+ expect { client.get("http://example.com/") }.not_to raise_error
138
+
139
+ expect(logdev.string).to eq <<~OUTPUT
140
+ ** INFO **
141
+ > GET http://example.com/
142
+ ** INFO **
143
+ > GET http://example.com/1
144
+ ** INFO **
145
+ > GET http://example.com/2
146
+ ** INFO **
147
+ > GET http://example.com/3
148
+ OUTPUT
149
+ end
150
+ end
151
+
115
152
  describe "parsing params" do
116
153
  let(:client) { HTTP::Client.new }
117
154
  before { allow(client).to receive :perform }
@@ -197,6 +234,22 @@ RSpec.describe HTTP::Client do
197
234
 
198
235
  client.get("http://example.com/", :form => {:foo => HTTP::FormData::Part.new("content")})
199
236
  end
237
+
238
+ context "when passing an HTTP::FormData object directly" do
239
+ it "creates url encoded form data object" do
240
+ client = HTTP::Client.new
241
+ form_data = HTTP::FormData::Multipart.new({ :foo => "bar" })
242
+
243
+ allow(client).to receive(:perform)
244
+
245
+ expect(HTTP::Request).to receive(:new) do |opts|
246
+ expect(opts[:body]).to be form_data
247
+ expect(opts[:body].to_s).to match(/^Content-Disposition: form-data; name="foo"\r\n\r\nbar\r\n/m)
248
+ end
249
+
250
+ client.get("http://example.com/", :form => form_data)
251
+ end
252
+ end
200
253
  end
201
254
 
202
255
  describe "passing json" do
@@ -220,9 +273,9 @@ RSpec.describe HTTP::Client do
220
273
  end
221
274
 
222
275
  it "works like a charm in real world" do
223
- url = "https://github.com/httprb/http.rb/pull/197/ö無"
224
- client = HTTP.follow
225
- expect(client.get(url).to_s).to include "support for non-ascii URIs"
276
+ url = "https://httpbin.org/anything/ö無"
277
+
278
+ expect(HTTP.follow.get(url).parse(:json)).to include("url" => url)
226
279
  end
227
280
  end
228
281
 
@@ -291,6 +344,7 @@ RSpec.describe HTTP::Client do
291
344
  end
292
345
  end
293
346
  end
347
+
294
348
  it "is given a chance to wrap the Request" do
295
349
  feature_instance = feature_class.new
296
350
 
@@ -299,7 +353,7 @@ RSpec.describe HTTP::Client do
299
353
 
300
354
  expect(response.code).to eq(200)
301
355
  expect(feature_instance.captured_request.verb).to eq(:get)
302
- expect(feature_instance.captured_request.uri.to_s).to eq(dummy.endpoint + "/")
356
+ expect(feature_instance.captured_request.uri.to_s).to eq("#{dummy.endpoint}/")
303
357
  end
304
358
 
305
359
  it "is given a chance to wrap the Response" do
@@ -325,6 +379,19 @@ RSpec.describe HTTP::Client do
325
379
  expect(feature_instance.captured_request.verb).to eq(:post)
326
380
  expect(feature_instance.captured_request.uri.to_s).to eq(sleep_url)
327
381
  end
382
+
383
+ it "is given a chance to handle a connection timeout error" do
384
+ allow(TCPSocket).to receive(:open) { sleep 1 }
385
+ sleep_url = "#{dummy.endpoint}/sleep"
386
+ feature_instance = feature_class.new
387
+
388
+ expect do
389
+ client.use(:test_feature => feature_instance).
390
+ timeout(0.001).
391
+ request(:post, sleep_url)
392
+ end.to raise_error(HTTP::TimeoutError)
393
+ expect(feature_instance.captured_error).to be_a(HTTP::TimeoutError)
394
+ end
328
395
  end
329
396
  end
330
397
 
@@ -335,7 +402,8 @@ RSpec.describe HTTP::Client do
335
402
  let(:client) { described_class.new(options.merge(extra_options)) }
336
403
  end
337
404
 
338
- describe "working with SSL" do
405
+ # TODO: https://github.com/httprb/http/issues/627
406
+ xdescribe "working with SSL" do
339
407
  run_server(:dummy_ssl) { DummyServer.new(:ssl => true) }
340
408
 
341
409
  let(:extra_options) { {} }
@@ -476,7 +544,7 @@ RSpec.describe HTTP::Client do
476
544
  BODY
477
545
  end
478
546
 
479
- it "raises HTTP::ConnectionError" do
547
+ xit "raises HTTP::ConnectionError" do
480
548
  expect { client.get(dummy.endpoint).to_s }.to raise_error(HTTP::ConnectionError)
481
549
  end
482
550
  end
@@ -74,7 +74,6 @@ RSpec.describe HTTP::Features::AutoInflate do
74
74
  :status => 200,
75
75
  :headers => {:content_encoding => "gzip"},
76
76
  :connection => connection,
77
- :uri => "https://example.com",
78
77
  :request => HTTP::Request.new(:verb => :get, :uri => "https://example.com")
79
78
  )
80
79
  end
@@ -2,8 +2,29 @@
2
2
 
3
3
  RSpec.describe HTTP::Features::Instrumentation do
4
4
  subject(:feature) { HTTP::Features::Instrumentation.new(:instrumenter => instrumenter) }
5
+
5
6
  let(:instrumenter) { TestInstrumenter.new }
6
7
 
8
+ before do
9
+ test_instrumenter = Class.new(HTTP::Features::Instrumentation::NullInstrumenter) do
10
+ attr_reader :output
11
+
12
+ def initialize
13
+ @output = {}
14
+ end
15
+
16
+ def start(_name, payload)
17
+ output[:start] = payload
18
+ end
19
+
20
+ def finish(_name, payload)
21
+ output[:finish] = payload
22
+ end
23
+ end
24
+
25
+ stub_const("TestInstrumenter", test_instrumenter)
26
+ end
27
+
7
28
  describe "logging the request" do
8
29
  let(:request) do
9
30
  HTTP::Request.new(
@@ -25,7 +46,6 @@ RSpec.describe HTTP::Features::Instrumentation do
25
46
  let(:response) do
26
47
  HTTP::Response.new(
27
48
  :version => "1.1",
28
- :uri => "https://example.com",
29
49
  :status => 200,
30
50
  :headers => {:content_type => "application/json"},
31
51
  :body => '{"success": true}',
@@ -39,19 +59,4 @@ RSpec.describe HTTP::Features::Instrumentation do
39
59
  expect(instrumenter.output[:finish]).to eq(:response => response)
40
60
  end
41
61
  end
42
-
43
- class TestInstrumenter < HTTP::Features::Instrumentation::NullInstrumenter
44
- attr_reader :output
45
- def initialize
46
- @output = {}
47
- end
48
-
49
- def start(_name, payload)
50
- output[:start] = payload
51
- end
52
-
53
- def finish(_name, payload)
54
- output[:finish] = payload
55
- end
56
- end
57
62
  end
@@ -4,10 +4,8 @@ require "logger"
4
4
 
5
5
  RSpec.describe HTTP::Features::Logging do
6
6
  subject(:feature) do
7
- logger = Logger.new(logdev)
8
- logger.formatter = ->(severity, _, _, message) do
9
- format("** %s **\n%s\n", severity, message)
10
- end
7
+ logger = Logger.new(logdev)
8
+ logger.formatter = ->(severity, _, _, message) { format("** %s **\n%s\n", severity, message) }
11
9
 
12
10
  described_class.new(:logger => logger)
13
11
  end
@@ -44,7 +42,6 @@ RSpec.describe HTTP::Features::Logging do
44
42
  let(:response) do
45
43
  HTTP::Response.new(
46
44
  :version => "1.1",
47
- :uri => "https://example.com",
48
45
  :status => 200,
49
46
  :headers => {:content_type => "application/json"},
50
47
  :body => '{"success": true}',
@@ -321,17 +321,17 @@ RSpec.describe HTTP::Headers do
321
321
 
322
322
  it "yields header keys specified as symbols in normalized form" do
323
323
  keys = headers.each.map(&:first)
324
- expect(keys).to eq(["Set-Cookie", "Content-Type", "Set-Cookie"])
324
+ expect(keys).to eq(%w[Set-Cookie Content-Type Set-Cookie])
325
325
  end
326
326
 
327
327
  it "yields headers specified as strings without conversion" do
328
328
  headers.add "X_kEy", "value"
329
329
  keys = headers.each.map(&:first)
330
- expect(keys).to eq(["Set-Cookie", "Content-Type", "Set-Cookie", "X_kEy"])
330
+ expect(keys).to eq(%w[Set-Cookie Content-Type Set-Cookie X_kEy])
331
331
  end
332
332
 
333
333
  it "returns self instance if block given" do
334
- expect(headers.each { |*| }).to be headers
334
+ expect(headers.each { |*| }).to be headers # rubocop:disable Lint/EmptyBlock
335
335
  end
336
336
 
337
337
  it "returns Enumerator if no block given" do
@@ -396,5 +396,49 @@ RSpec.describe HTTP::Redirector do
396
396
  end
397
397
  end
398
398
  end
399
+
400
+ describe "changing verbs during redirects" do
401
+ let(:options) { {:strict => false} }
402
+ let(:post_body) { HTTP::Request::Body.new("i might be way longer in real life") }
403
+ let(:cookie) { "dont eat my cookies" }
404
+
405
+ def a_dangerous_request(verb)
406
+ HTTP::Request.new(
407
+ :verb => verb, :uri => "http://example.com",
408
+ :body => post_body, :headers => {
409
+ "Content-Type" => "meme",
410
+ "Cookie" => cookie
411
+ }
412
+ )
413
+ end
414
+
415
+ def empty_body
416
+ HTTP::Request::Body.new(nil)
417
+ end
418
+
419
+ it "follows without body/content type if it has to change verb" do
420
+ req = a_dangerous_request(:post)
421
+ res = redirect_response 302, "http://example.com/1"
422
+
423
+ redirector.perform(req, res) do |prev_req, _|
424
+ expect(prev_req.body).to eq(empty_body)
425
+ expect(prev_req.headers["Cookie"]).to eq(cookie)
426
+ expect(prev_req.headers["Content-Type"]).to eq(nil)
427
+ simple_response 200
428
+ end
429
+ end
430
+
431
+ it "leaves body/content-type intact if it does not have to change verb" do
432
+ req = a_dangerous_request(:post)
433
+ res = redirect_response 307, "http://example.com/1"
434
+
435
+ redirector.perform(req, res) do |prev_req, _|
436
+ expect(prev_req.body).to eq(post_body)
437
+ expect(prev_req.headers["Cookie"]).to eq(cookie)
438
+ expect(prev_req.headers["Content-Type"]).to eq("meme")
439
+ simple_response 200
440
+ end
441
+ end
442
+ end
399
443
  end
400
444
  end
@@ -118,10 +118,10 @@ RSpec.describe HTTP::Request::Body do
118
118
  end
119
119
 
120
120
  context "when body is a non-Enumerable IO" do
121
- let(:body) { FakeIO.new("a" * 16 * 1024 + "b" * 10 * 1024) }
121
+ let(:body) { FakeIO.new(("a" * 16 * 1024) + ("b" * 10 * 1024)) }
122
122
 
123
123
  it "yields chunks of content" do
124
- expect(chunks.inject("", :+)).to eq "a" * 16 * 1024 + "b" * 10 * 1024
124
+ expect(chunks.inject("", :+)).to eq ("a" * 16 * 1024) + ("b" * 10 * 1024)
125
125
  end
126
126
  end
127
127
 
@@ -148,7 +148,7 @@ RSpec.describe HTTP::Request::Body do
148
148
  end
149
149
 
150
150
  context "when body is an Enumerable IO" do
151
- let(:data) { "a" * 16 * 1024 + "b" * 10 * 1024 }
151
+ let(:data) { ("a" * 16 * 1024) + ("b" * 10 * 1024) }
152
152
  let(:body) { StringIO.new data }
153
153
 
154
154
  it "yields chunks of content" do
@@ -47,9 +47,20 @@ RSpec.describe HTTP::Request::Writer do
47
47
  end
48
48
  end
49
49
 
50
- context "when body is empty" do
50
+ context "when body is not set" do
51
51
  let(:body) { HTTP::Request::Body.new(nil) }
52
52
 
53
+ it "doesn't write anything to the socket and doesn't set Content-Length" do
54
+ writer.stream
55
+ expect(io.string).to eq [
56
+ "#{headerstart}\r\n\r\n"
57
+ ].join
58
+ end
59
+ end
60
+
61
+ context "when body is empty" do
62
+ let(:body) { HTTP::Request::Body.new("") }
63
+
53
64
  it "doesn't write anything to the socket and sets Content-Length" do
54
65
  writer.stream
55
66
  expect(io.string).to eq [
@@ -2,7 +2,7 @@
2
2
 
3
3
  RSpec.describe HTTP::Response::Body do
4
4
  let(:connection) { double(:sequence_id => 0) }
5
- let(:chunks) { [String.new("Hello, "), String.new("World!")] }
5
+ let(:chunks) { ["Hello, ", "World!"] }
6
6
 
7
7
  before do
8
8
  allow(connection).to receive(:readpartial) { chunks.shift }
@@ -16,7 +16,7 @@ RSpec.describe HTTP::Response::Body do
16
16
  end
17
17
 
18
18
  context "when body empty" do
19
- let(:chunks) { [String.new("")] }
19
+ let(:chunks) { [""] }
20
20
 
21
21
  it "returns responds to empty? with true" do
22
22
  expect(subject).to be_empty
@@ -45,12 +45,12 @@ RSpec.describe HTTP::Response::Body do
45
45
  it "returns content in specified encoding" do
46
46
  body = described_class.new(connection)
47
47
  expect(connection).to receive(:readpartial).
48
- and_return(String.new("content").force_encoding(Encoding::UTF_8))
48
+ and_return(String.new("content", :encoding => Encoding::UTF_8))
49
49
  expect(body.readpartial.encoding).to eq Encoding::BINARY
50
50
 
51
51
  body = described_class.new(connection, :encoding => Encoding::UTF_8)
52
52
  expect(connection).to receive(:readpartial).
53
- and_return(String.new("content").force_encoding(Encoding::BINARY))
53
+ and_return(String.new("content", :encoding => Encoding::BINARY))
54
54
  expect(body.readpartial.encoding).to eq Encoding::UTF_8
55
55
  end
56
56
  end
@@ -59,7 +59,7 @@ RSpec.describe HTTP::Response::Body do
59
59
  let(:chunks) do
60
60
  body = Zlib::Deflate.deflate("Hi, HTTP here ☺")
61
61
  len = body.length
62
- [String.new(body[0, len / 2]), String.new(body[(len / 2)..-1])]
62
+ [body[0, len / 2], body[(len / 2)..-1]]
63
63
  end
64
64
  subject(:body) do
65
65
  inflater = HTTP::Response::Inflater.new(connection)
@@ -32,7 +32,7 @@ RSpec.describe HTTP::Response::Parser do
32
32
  end
33
33
 
34
34
  context "response in many parts" do
35
- let(:parts) { raw_response.split(//) }
35
+ let(:parts) { raw_response.chars }
36
36
 
37
37
  it "parses headers" do
38
38
  expect(subject.headers.to_h).to eq(expected_headers)
@@ -46,9 +46,9 @@ RSpec.describe HTTP::Response::Parser do
46
46
  context "when got 100 Continue response" do
47
47
  let :raw_response do
48
48
  "HTTP/1.1 100 Continue\r\n\r\n" \
49
- "HTTP/1.1 200 OK\r\n" \
50
- "Content-Length: 12\r\n\r\n" \
51
- "Hello World!"
49
+ "HTTP/1.1 200 OK\r\n" \
50
+ "Content-Length: 12\r\n\r\n" \
51
+ "Hello World!"
52
52
  end
53
53
 
54
54
  context "when response is feeded in one part" do
@@ -62,7 +62,7 @@ RSpec.describe HTTP::Response::Parser do
62
62
  end
63
63
 
64
64
  context "when response is feeded in many parts" do
65
- let(:parts) { raw_response.split(//) }
65
+ let(:parts) { raw_response.chars }
66
66
 
67
67
  it "skips to next non-info response" do
68
68
  expect(subject.status_code).to eq(200)
@@ -4,6 +4,7 @@ RSpec.describe HTTP::Response do
4
4
  let(:body) { "Hello world!" }
5
5
  let(:uri) { "http://example.com/" }
6
6
  let(:headers) { {} }
7
+ let(:request) { HTTP::Request.new(:verb => :get, :uri => uri) }
7
8
 
8
9
  subject(:response) do
9
10
  HTTP::Response.new(
@@ -11,8 +12,7 @@ RSpec.describe HTTP::Response do
11
12
  :version => "1.1",
12
13
  :headers => headers,
13
14
  :body => body,
14
- :uri => uri,
15
- :request => HTTP::Request.new(:verb => :get, :uri => "http://example.com")
15
+ :request => request
16
16
  )
17
17
  end
18
18
 
@@ -87,19 +87,32 @@ RSpec.describe HTTP::Response do
87
87
  end
88
88
 
89
89
  describe "#parse" do
90
- let(:headers) { {"Content-Type" => "application/json"} }
90
+ let(:headers) { {"Content-Type" => content_type} }
91
91
  let(:body) { '{"foo":"bar"}' }
92
92
 
93
- it "fails if MIME type decoder is not found" do
94
- expect { response.parse "text/html" }.to raise_error(HTTP::Error)
93
+ context "with known content type" do
94
+ let(:content_type) { "application/json" }
95
+ it "returns parsed body" do
96
+ expect(response.parse).to eq "foo" => "bar"
97
+ end
95
98
  end
96
99
 
97
- it "uses decoder found by given MIME type" do
98
- expect(response.parse("application/json")).to eq("foo" => "bar")
100
+ context "with unknown content type" do
101
+ let(:content_type) { "application/deadbeef" }
102
+ it "raises HTTP::Error" do
103
+ expect { response.parse }.to raise_error HTTP::Error
104
+ end
99
105
  end
100
106
 
101
- it "uses decoder found by given MIME type alias" do
102
- expect(response.parse(:json)).to eq("foo" => "bar")
107
+ context "with explicitly given mime type" do
108
+ let(:content_type) { "application/deadbeef" }
109
+ it "ignores mime_type of response" do
110
+ expect(response.parse("application/json")).to eq "foo" => "bar"
111
+ end
112
+
113
+ it "supports mime type aliases" do
114
+ expect(response.parse(:json)).to eq "foo" => "bar"
115
+ end
103
116
  end
104
117
  end
105
118
 
@@ -154,7 +167,7 @@ RSpec.describe HTTP::Response do
154
167
  :version => "1.1",
155
168
  :status => 200,
156
169
  :connection => connection,
157
- :request => HTTP::Request.new(:verb => :get, :uri => "http://example.com")
170
+ :request => request
158
171
  )
159
172
  end
160
173
 
@@ -171,4 +184,43 @@ RSpec.describe HTTP::Response do
171
184
  end
172
185
  it { is_expected.not_to be_chunked }
173
186
  end
187
+
188
+ describe "backwards compatibilty with :uri" do
189
+ context "with no :verb" do
190
+ subject(:response) do
191
+ HTTP::Response.new(
192
+ :status => 200,
193
+ :version => "1.1",
194
+ :headers => headers,
195
+ :body => body,
196
+ :uri => uri
197
+ )
198
+ end
199
+
200
+ it "defaults the uri to :uri" do
201
+ expect(response.request.uri.to_s).to eq uri
202
+ end
203
+
204
+ it "defaults to the verb to :get" do
205
+ expect(response.request.verb).to eq :get
206
+ end
207
+ end
208
+
209
+ context "with both a :request and :uri" do
210
+ subject(:response) do
211
+ HTTP::Response.new(
212
+ :status => 200,
213
+ :version => "1.1",
214
+ :headers => headers,
215
+ :body => body,
216
+ :uri => uri,
217
+ :request => request
218
+ )
219
+ end
220
+
221
+ it "raises ArgumentError" do
222
+ expect { response }.to raise_error(ArgumentError)
223
+ end
224
+ end
225
+ end
174
226
  end
@@ -95,7 +95,8 @@ RSpec.describe HTTP do
95
95
  expect(response.to_s).to match(/<!doctype html>/)
96
96
  end
97
97
 
98
- context "ssl" do
98
+ # TODO: htt:s://github.com/httprb/http/issues/627
99
+ xcontext "ssl" do
99
100
  it "responds with the endpoint's body" do
100
101
  response = ssl_client.via(proxy.addr, proxy.port).get dummy_ssl.endpoint
101
102
  expect(response.to_s).to match(/<!doctype html>/)
@@ -131,7 +132,8 @@ RSpec.describe HTTP do
131
132
  expect(response.status).to eq(407)
132
133
  end
133
134
 
134
- context "ssl" do
135
+ # TODO: htt:s://github.com/httprb/http/issues/627
136
+ xcontext "ssl" do
135
137
  it "responds with the endpoint's body" do
136
138
  response = ssl_client.via(proxy.addr, proxy.port, "username", "password").get dummy_ssl.endpoint
137
139
  expect(response.to_s).to match(/<!doctype html>/)
@@ -438,6 +440,22 @@ RSpec.describe HTTP do
438
440
 
439
441
  expect(response.to_s).to eq("#{body}-deflated")
440
442
  end
443
+
444
+ it "returns empty body for no content response where Content-Encoding is gzip" do
445
+ client = HTTP.use(:auto_inflate).headers("Accept-Encoding" => "gzip")
446
+ body = "Hello!"
447
+ response = client.post("#{dummy.endpoint}/no-content-204", :body => body)
448
+
449
+ expect(response.to_s).to eq("")
450
+ end
451
+
452
+ it "returns empty body for no content response where Content-Encoding is deflate" do
453
+ client = HTTP.use(:auto_inflate).headers("Accept-Encoding" => "deflate")
454
+ body = "Hello!"
455
+ response = client.post("#{dummy.endpoint}/no-content-204", :body => body)
456
+
457
+ expect(response.to_s).to eq("")
458
+ end
441
459
  end
442
460
 
443
461
  context "with :normalize_uri" do