http 5.0.0.pre3 → 5.0.3

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 (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