http 4.4.1 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) 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 +8 -110
  8. data/.rubocop_todo.yml +205 -0
  9. data/.yardopts +1 -1
  10. data/CHANGES.md +188 -3
  11. data/Gemfile +18 -10
  12. data/LICENSE.txt +1 -1
  13. data/README.md +47 -86
  14. data/Rakefile +2 -10
  15. data/SECURITY.md +5 -0
  16. data/http.gemspec +9 -8
  17. data/lib/http/chainable.rb +23 -17
  18. data/lib/http/client.rb +44 -34
  19. data/lib/http/connection.rb +11 -7
  20. data/lib/http/content_type.rb +12 -7
  21. data/lib/http/errors.rb +3 -0
  22. data/lib/http/feature.rb +3 -1
  23. data/lib/http/features/auto_deflate.rb +6 -6
  24. data/lib/http/features/auto_inflate.rb +6 -7
  25. data/lib/http/features/instrumentation.rb +1 -1
  26. data/lib/http/features/logging.rb +19 -21
  27. data/lib/http/headers.rb +50 -13
  28. data/lib/http/mime_type/adapter.rb +3 -1
  29. data/lib/http/mime_type/json.rb +1 -0
  30. data/lib/http/options.rb +5 -8
  31. data/lib/http/redirector.rb +51 -2
  32. data/lib/http/request/body.rb +1 -0
  33. data/lib/http/request/writer.rb +9 -4
  34. data/lib/http/request.rb +28 -11
  35. data/lib/http/response/body.rb +6 -4
  36. data/lib/http/response/inflater.rb +1 -1
  37. data/lib/http/response/parser.rb +74 -62
  38. data/lib/http/response/status.rb +4 -3
  39. data/lib/http/response.rb +44 -18
  40. data/lib/http/timeout/global.rb +20 -36
  41. data/lib/http/timeout/null.rb +2 -1
  42. data/lib/http/timeout/per_operation.rb +32 -55
  43. data/lib/http/uri.rb +5 -5
  44. data/lib/http/version.rb +1 -1
  45. data/spec/lib/http/client_spec.rb +153 -30
  46. data/spec/lib/http/connection_spec.rb +8 -5
  47. data/spec/lib/http/features/auto_inflate_spec.rb +3 -2
  48. data/spec/lib/http/features/instrumentation_spec.rb +27 -21
  49. data/spec/lib/http/features/logging_spec.rb +8 -10
  50. data/spec/lib/http/headers_spec.rb +53 -18
  51. data/spec/lib/http/options/headers_spec.rb +1 -1
  52. data/spec/lib/http/options/merge_spec.rb +16 -16
  53. data/spec/lib/http/redirector_spec.rb +107 -3
  54. data/spec/lib/http/request/body_spec.rb +3 -3
  55. data/spec/lib/http/request/writer_spec.rb +25 -2
  56. data/spec/lib/http/request_spec.rb +5 -5
  57. data/spec/lib/http/response/body_spec.rb +5 -5
  58. data/spec/lib/http/response/parser_spec.rb +33 -4
  59. data/spec/lib/http/response/status_spec.rb +3 -3
  60. data/spec/lib/http/response_spec.rb +80 -3
  61. data/spec/lib/http_spec.rb +30 -3
  62. data/spec/spec_helper.rb +21 -21
  63. data/spec/support/black_hole.rb +1 -1
  64. data/spec/support/dummy_server/servlet.rb +17 -6
  65. data/spec/support/dummy_server.rb +7 -7
  66. data/spec/support/fuubar.rb +21 -0
  67. data/spec/support/http_handling_shared.rb +5 -5
  68. data/spec/support/simplecov.rb +19 -0
  69. data/spec/support/ssl_helper.rb +4 -4
  70. metadata +23 -15
  71. data/.coveralls.yml +0 -1
  72. data/.travis.yml +0 -39
@@ -1,46 +1,58 @@
1
- # frozen_string_literal: true
2
1
  # coding: utf-8
2
+ # frozen_string_literal: true
3
3
 
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
- stubs.fetch(request.uri) { super(request, options) }
14
- 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
15
18
 
16
- def stubs
17
- @stubs ||= {}
18
- end
19
+ def stubs
20
+ @stubs ||= {}
21
+ end
22
+
23
+ def stub(stubs)
24
+ @stubs = stubs.transform_keys do |k|
25
+ HTTP::URI::NORMALIZER.call(k).to_s
26
+ end
19
27
 
20
- def stub(stubs)
21
- @stubs = stubs.each_with_object({}) do |(k, v), o|
22
- o[HTTP::URI.parse k] = v
28
+ self
23
29
  end
30
+ end
24
31
 
25
- self
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
26
42
  end
27
- end
28
43
 
29
- def redirect_response(location, status = 302)
30
- HTTP::Response.new(
31
- :status => status,
32
- :version => "1.1",
33
- :headers => {"Location" => location},
34
- :body => ""
35
- )
36
- end
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
53
+ end
37
54
 
38
- def simple_response(body, status = 200)
39
- HTTP::Response.new(
40
- :status => status,
41
- :version => "1.1",
42
- :body => body
43
- )
55
+ stub_const("StubbedClient", stubbed_client)
44
56
  end
45
57
 
46
58
  describe "following redirects" do
@@ -104,6 +116,39 @@ RSpec.describe HTTP::Client do
104
116
  end
105
117
  end
106
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
+
107
152
  describe "parsing params" do
108
153
  let(:client) { HTTP::Client.new }
109
154
  before { allow(client).to receive :perform }
@@ -193,7 +238,7 @@ RSpec.describe HTTP::Client do
193
238
  context "when passing an HTTP::FormData object directly" do
194
239
  it "creates url encoded form data object" do
195
240
  client = HTTP::Client.new
196
- form_data = HTTP::FormData::Multipart.new(:foo => "bar")
241
+ form_data = HTTP::FormData::Multipart.new({ :foo => "bar" })
197
242
 
198
243
  allow(client).to receive(:perform)
199
244
 
@@ -279,6 +324,75 @@ RSpec.describe HTTP::Client do
279
324
  end
280
325
  end
281
326
  end
327
+
328
+ context "Feature" do
329
+ let(:feature_class) do
330
+ Class.new(HTTP::Feature) do
331
+ attr_reader :captured_request, :captured_response, :captured_error
332
+
333
+ def wrap_request(request)
334
+ @captured_request = request
335
+ end
336
+
337
+ def wrap_response(response)
338
+ @captured_response = response
339
+ end
340
+
341
+ def on_error(request, error)
342
+ @captured_request = request
343
+ @captured_error = error
344
+ end
345
+ end
346
+ end
347
+
348
+ it "is given a chance to wrap the Request" do
349
+ feature_instance = feature_class.new
350
+
351
+ response = client.use(:test_feature => feature_instance).
352
+ request(:get, dummy.endpoint)
353
+
354
+ expect(response.code).to eq(200)
355
+ expect(feature_instance.captured_request.verb).to eq(:get)
356
+ expect(feature_instance.captured_request.uri.to_s).to eq("#{dummy.endpoint}/")
357
+ end
358
+
359
+ it "is given a chance to wrap the Response" do
360
+ feature_instance = feature_class.new
361
+
362
+ response = client.use(:test_feature => feature_instance).
363
+ request(:get, dummy.endpoint)
364
+
365
+ expect(feature_instance.captured_response).to eq(response)
366
+ end
367
+
368
+ it "is given a chance to handle an error" do
369
+ sleep_url = "#{dummy.endpoint}/sleep"
370
+ feature_instance = feature_class.new
371
+
372
+ expect do
373
+ client.use(:test_feature => feature_instance).
374
+ timeout(0.2).
375
+ request(:post, sleep_url)
376
+ end.to raise_error(HTTP::TimeoutError)
377
+
378
+ expect(feature_instance.captured_error).to be_a(HTTP::TimeoutError)
379
+ expect(feature_instance.captured_request.verb).to eq(:post)
380
+ expect(feature_instance.captured_request.uri.to_s).to eq(sleep_url)
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::ConnectTimeoutError)
393
+ expect(feature_instance.captured_error).to be_a(HTTP::ConnectTimeoutError)
394
+ end
395
+ end
282
396
  end
283
397
 
284
398
  include_context "HTTP handling" do
@@ -288,7 +402,8 @@ RSpec.describe HTTP::Client do
288
402
  let(:client) { described_class.new(options.merge(extra_options)) }
289
403
  end
290
404
 
291
- describe "working with SSL" do
405
+ # TODO: https://github.com/httprb/http/issues/627
406
+ xdescribe "working with SSL" do
292
407
  run_server(:dummy_ssl) { DummyServer.new(:ssl => true) }
293
408
 
294
409
  let(:extra_options) { {} }
@@ -331,6 +446,14 @@ RSpec.describe HTTP::Client do
331
446
  client.get(dummy.endpoint).to_s
332
447
  end
333
448
 
449
+ it "provides access to the Request from the Response" do
450
+ unique_value = "20190424"
451
+ response = client.headers("X-Value" => unique_value).get(dummy.endpoint)
452
+
453
+ expect(response.request).to be_a(HTTP::Request)
454
+ expect(response.request.headers["X-Value"]).to eq(unique_value)
455
+ end
456
+
334
457
  context "with HEAD request" do
335
458
  it "does not iterates through body" do
336
459
  expect_any_instance_of(HTTP::Connection).to_not receive(:readpartial)
@@ -421,7 +544,7 @@ RSpec.describe HTTP::Client do
421
544
  BODY
422
545
  end
423
546
 
424
- it "raises HTTP::ConnectionError" do
547
+ xit "raises HTTP::ConnectionError" do
425
548
  expect { client.get(dummy.endpoint).to_s }.to raise_error(HTTP::ConnectionError)
426
549
  end
427
550
  end
@@ -3,9 +3,9 @@
3
3
  RSpec.describe HTTP::Connection do
4
4
  let(:req) do
5
5
  HTTP::Request.new(
6
- :verb => :get,
7
- :uri => "http://example.com/",
8
- :headers => {}
6
+ :verb => :get,
7
+ :uri => "http://example.com/",
8
+ :headers => {}
9
9
  )
10
10
  end
11
11
  let(:socket) { double(:connect => nil) }
@@ -20,14 +20,17 @@ RSpec.describe HTTP::Connection do
20
20
  <<-RESPONSE.gsub(/^\s*\| */, "").gsub(/\n/, "\r\n")
21
21
  | HTTP/1.1 200 OK
22
22
  | Content-Type: text
23
+ | foo_bar: 123
23
24
  |
24
25
  RESPONSE
25
26
  end
26
27
  end
27
28
 
28
- it "reads data in parts" do
29
+ it "populates headers collection, preserving casing" do
29
30
  connection.read_headers!
30
- expect(connection.headers).to eq("Content-Type" => "text")
31
+ expect(connection.headers).to eq("Content-Type" => "text", "foo_bar" => "123")
32
+ expect(connection.headers["Foo-Bar"]).to eq("123")
33
+ expect(connection.headers["foo_bar"]).to eq("123")
31
34
  end
32
35
  end
33
36
 
@@ -11,7 +11,8 @@ RSpec.describe HTTP::Features::AutoInflate do
11
11
  :version => "1.1",
12
12
  :status => 200,
13
13
  :headers => headers,
14
- :connection => connection
14
+ :connection => connection,
15
+ :request => HTTP::Request.new(:verb => :get, :uri => "http://example.com")
15
16
  )
16
17
  end
17
18
 
@@ -73,7 +74,7 @@ RSpec.describe HTTP::Features::AutoInflate do
73
74
  :status => 200,
74
75
  :headers => {:content_encoding => "gzip"},
75
76
  :connection => connection,
76
- :uri => "https://example.com"
77
+ :request => HTTP::Request.new(:verb => :get, :uri => "https://example.com")
77
78
  )
78
79
  end
79
80
 
@@ -2,15 +2,36 @@
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(
10
- :verb => :post,
11
- :uri => "https://example.com/",
31
+ :verb => :post,
32
+ :uri => "https://example.com/",
12
33
  :headers => {:accept => "application/json"},
13
- :body => '{"hello": "world!"}'
34
+ :body => '{"hello": "world!"}'
14
35
  )
15
36
  end
16
37
 
@@ -25,10 +46,10 @@ 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
- :status => 200,
49
+ :status => 200,
30
50
  :headers => {:content_type => "application/json"},
31
- :body => '{"success": true}'
51
+ :body => '{"success": true}',
52
+ :request => HTTP::Request.new(:verb => :get, :uri => "https://example.com")
32
53
  )
33
54
  end
34
55
 
@@ -38,19 +59,4 @@ RSpec.describe HTTP::Features::Instrumentation do
38
59
  expect(instrumenter.output[:finish]).to eq(:response => response)
39
60
  end
40
61
  end
41
-
42
- class TestInstrumenter < HTTP::Features::Instrumentation::NullInstrumenter
43
- attr_reader :output
44
- def initialize
45
- @output = {}
46
- end
47
-
48
- def start(_name, payload)
49
- output[:start] = payload
50
- end
51
-
52
- def finish(_name, payload)
53
- output[:finish] = payload
54
- end
55
- end
56
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
@@ -17,10 +15,10 @@ RSpec.describe HTTP::Features::Logging do
17
15
  describe "logging the request" do
18
16
  let(:request) do
19
17
  HTTP::Request.new(
20
- :verb => :post,
21
- :uri => "https://example.com/",
22
- :headers => {:accept => "application/json"},
23
- :body => '{"hello": "world!"}'
18
+ :verb => :post,
19
+ :uri => "https://example.com/",
20
+ :headers => {:accept => "application/json"},
21
+ :body => '{"hello": "world!"}'
24
22
  )
25
23
  end
26
24
 
@@ -44,10 +42,10 @@ 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
- :body => '{"success": true}'
47
+ :body => '{"success": true}',
48
+ :request => HTTP::Request.new(:verb => :get, :uri => "https://example.com")
51
49
  )
52
50
  end
53
51
 
@@ -13,7 +13,7 @@ RSpec.describe HTTP::Headers do
13
13
  expect(headers["Accept"]).to eq "application/json"
14
14
  end
15
15
 
16
- it "normalizes header name" do
16
+ it "allows retrieval via normalized header name" do
17
17
  headers.set :content_type, "application/json"
18
18
  expect(headers["Content-Type"]).to eq "application/json"
19
19
  end
@@ -35,8 +35,15 @@ RSpec.describe HTTP::Headers do
35
35
  to raise_error HTTP::HeaderError
36
36
  end
37
37
 
38
- it "fails with invalid header name" do
39
- expect { headers.set "foo bar", "baz" }.
38
+ ["foo bar", "foo bar: ok\nfoo", "evil-header: evil-value\nfoo"].each do |name|
39
+ it "fails with invalid header name (#{name.inspect})" do
40
+ expect { headers.set name, "baz" }.
41
+ to raise_error HTTP::HeaderError
42
+ end
43
+ end
44
+
45
+ it "fails with invalid header value" do
46
+ expect { headers.set "foo", "bar\nEvil-Header: evil-value" }.
40
47
  to raise_error HTTP::HeaderError
41
48
  end
42
49
  end
@@ -47,7 +54,7 @@ RSpec.describe HTTP::Headers do
47
54
  expect(headers["Accept"]).to eq "application/json"
48
55
  end
49
56
 
50
- it "normalizes header name" do
57
+ it "allows retrieval via normalized header name" do
51
58
  headers[:content_type] = "application/json"
52
59
  expect(headers["Content-Type"]).to eq "application/json"
53
60
  end
@@ -73,7 +80,7 @@ RSpec.describe HTTP::Headers do
73
80
  expect(headers["Content-Type"]).to be_nil
74
81
  end
75
82
 
76
- it "normalizes header name" do
83
+ it "removes header that matches normalized version of specified name" do
77
84
  headers.delete :content_type
78
85
  expect(headers["Content-Type"]).to be_nil
79
86
  end
@@ -83,9 +90,11 @@ RSpec.describe HTTP::Headers do
83
90
  to raise_error HTTP::HeaderError
84
91
  end
85
92
 
86
- it "fails with invalid header name" do
87
- expect { headers.delete "foo bar" }.
88
- to raise_error HTTP::HeaderError
93
+ ["foo bar", "foo bar: ok\nfoo"].each do |name|
94
+ it "fails with invalid header name (#{name.inspect})" do
95
+ expect { headers.delete name }.
96
+ to raise_error HTTP::HeaderError
97
+ end
89
98
  end
90
99
  end
91
100
 
@@ -95,13 +104,13 @@ RSpec.describe HTTP::Headers do
95
104
  expect(headers["Accept"]).to eq "application/json"
96
105
  end
97
106
 
98
- it "normalizes header name" do
107
+ it "allows retrieval via normalized header name" do
99
108
  headers.add :content_type, "application/json"
100
109
  expect(headers["Content-Type"]).to eq "application/json"
101
110
  end
102
111
 
103
112
  it "appends new value if header exists" do
104
- headers.add :set_cookie, "hoo=ray"
113
+ headers.add "Set-Cookie", "hoo=ray"
105
114
  headers.add :set_cookie, "woo=hoo"
106
115
  expect(headers["Set-Cookie"]).to eq %w[hoo=ray woo=hoo]
107
116
  end
@@ -117,8 +126,20 @@ RSpec.describe HTTP::Headers do
117
126
  to raise_error HTTP::HeaderError
118
127
  end
119
128
 
120
- it "fails with invalid header name" do
121
- expect { headers.add "foo bar", "baz" }.
129
+ ["foo bar", "foo bar: ok\nfoo"].each do |name|
130
+ it "fails with invalid header name (#{name.inspect})" do
131
+ expect { headers.add name, "baz" }.
132
+ to raise_error HTTP::HeaderError
133
+ end
134
+ end
135
+
136
+ it "fails with invalid header value" do
137
+ expect { headers.add "foo", "bar\nEvil-Header: evil-value" }.
138
+ to raise_error HTTP::HeaderError
139
+ end
140
+
141
+ it "fails when header name is not a String or Symbol" do
142
+ expect { headers.add 2, "foo" }.
122
143
  to raise_error HTTP::HeaderError
123
144
  end
124
145
  end
@@ -145,9 +166,11 @@ RSpec.describe HTTP::Headers do
145
166
  to raise_error HTTP::HeaderError
146
167
  end
147
168
 
148
- it "fails with invalid header name" do
149
- expect { headers.get("foo bar") }.
150
- to raise_error HTTP::HeaderError
169
+ ["foo bar", "foo bar: ok\nfoo"].each do |name|
170
+ it "fails with invalid header name (#{name.inspect})" do
171
+ expect { headers.get name }.
172
+ to raise_error HTTP::HeaderError
173
+ end
151
174
  end
152
175
  end
153
176
 
@@ -296,8 +319,19 @@ RSpec.describe HTTP::Headers do
296
319
  )
297
320
  end
298
321
 
322
+ it "yields header keys specified as symbols in normalized form" do
323
+ keys = headers.each.map(&:first)
324
+ expect(keys).to eq(%w[Set-Cookie Content-Type Set-Cookie])
325
+ end
326
+
327
+ it "yields headers specified as strings without conversion" do
328
+ headers.add "X_kEy", "value"
329
+ keys = headers.each.map(&:first)
330
+ expect(keys).to eq(%w[Set-Cookie Content-Type Set-Cookie X_kEy])
331
+ end
332
+
299
333
  it "returns self instance if block given" do
300
- expect(headers.each { |*| }).to be headers
334
+ expect(headers.each { |*| }).to be headers # rubocop:disable Lint/EmptyBlock
301
335
  end
302
336
 
303
337
  it "returns Enumerator if no block given" do
@@ -472,14 +506,15 @@ RSpec.describe HTTP::Headers do
472
506
  end
473
507
 
474
508
  context "with duplicate header keys (mixed case)" do
475
- let(:headers) { {"Set-Cookie" => "hoo=ray", "set-cookie" => "woo=hoo"} }
509
+ let(:headers) { {"Set-Cookie" => "hoo=ray", "set_cookie" => "woo=hoo", :set_cookie => "ta=da"} }
476
510
 
477
511
  it "adds all headers" do
478
512
  expect(described_class.coerce(headers).to_a).
479
513
  to match_array(
480
514
  [
481
515
  %w[Set-Cookie hoo=ray],
482
- %w[Set-Cookie woo=hoo]
516
+ %w[set_cookie woo=hoo],
517
+ %w[Set-Cookie ta=da]
483
518
  ]
484
519
  )
485
520
  end
@@ -8,7 +8,7 @@ RSpec.describe HTTP::Options, "headers" do
8
8
  end
9
9
 
10
10
  it "may be specified with with_headers" do
11
- opts2 = opts.with_headers("accept" => "json")
11
+ opts2 = opts.with_headers(:accept => "json")
12
12
  expect(opts.headers).to be_empty
13
13
  expect(opts2.headers).to eq([%w[Accept json]])
14
14
  end
@@ -18,28 +18,28 @@ RSpec.describe HTTP::Options, "merge" do
18
18
  # FIXME: yuck :(
19
19
 
20
20
  foo = HTTP::Options.new(
21
- :response => :body,
22
- :params => {:baz => "bar"},
23
- :form => {:foo => "foo"},
24
- :body => "body-foo",
25
- :json => {:foo => "foo"},
26
- :headers => {:accept => "json", :foo => "foo"},
27
- :proxy => {},
28
- :features => {}
21
+ :response => :body,
22
+ :params => {:baz => "bar"},
23
+ :form => {:foo => "foo"},
24
+ :body => "body-foo",
25
+ :json => {:foo => "foo"},
26
+ :headers => {:accept => "json", :foo => "foo"},
27
+ :proxy => {},
28
+ :features => {}
29
29
  )
30
30
 
31
31
  bar = HTTP::Options.new(
32
- :response => :parsed_body,
33
- :persistent => "https://www.googe.com",
34
- :params => {:plop => "plip"},
35
- :form => {:bar => "bar"},
36
- :body => "body-bar",
37
- :json => {:bar => "bar"},
32
+ :response => :parsed_body,
33
+ :persistent => "https://www.googe.com",
34
+ :params => {:plop => "plip"},
35
+ :form => {:bar => "bar"},
36
+ :body => "body-bar",
37
+ :json => {:bar => "bar"},
38
38
  :keep_alive_timeout => 10,
39
39
  :headers => {:accept => "xml", :bar => "bar"},
40
40
  :timeout_options => {:foo => :bar},
41
- :ssl => {:foo => "bar"},
42
- :proxy => {:proxy_address => "127.0.0.1", :proxy_port => 8080}
41
+ :ssl => {:foo => "bar"},
42
+ :proxy => {:proxy_address => "127.0.0.1", :proxy_port => 8080}
43
43
  )
44
44
 
45
45
  expect(foo.merge(bar).to_hash).to eq(