http 3.3.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe HTTP::Features::Instrumentation do
4
+ subject(:feature) { HTTP::Features::Instrumentation.new(:instrumenter => instrumenter) }
5
+ let(:instrumenter) { TestInstrumenter.new }
6
+
7
+ describe "logging the request" do
8
+ let(:request) do
9
+ HTTP::Request.new(
10
+ :verb => :post,
11
+ :uri => "https://example.com/",
12
+ :headers => {:accept => "application/json"},
13
+ :body => '{"hello": "world!"}'
14
+ )
15
+ end
16
+
17
+ it "should log the request" do
18
+ feature.wrap_request(request)
19
+
20
+ expect(instrumenter.output[:start]).to eq(:request => request)
21
+ end
22
+ end
23
+
24
+ describe "logging the response" do
25
+ let(:response) do
26
+ HTTP::Response.new(
27
+ :version => "1.1",
28
+ :uri => "https://example.com",
29
+ :status => 200,
30
+ :headers => {:content_type => "application/json"},
31
+ :body => '{"success": true}'
32
+ )
33
+ end
34
+
35
+ it "should log the response" do
36
+ feature.wrap_response(response)
37
+
38
+ expect(instrumenter.output[:finish]).to eq(:response => response)
39
+ end
40
+ 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
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe HTTP::Features::Logging do
4
+ subject(:feature) { HTTP::Features::Logging.new(:logger => logger) }
5
+ let(:logger) { TestLogger.new }
6
+
7
+ describe "logging the request" do
8
+ let(:request) do
9
+ HTTP::Request.new(
10
+ :verb => :post,
11
+ :uri => "https://example.com/",
12
+ :headers => {:accept => "application/json"},
13
+ :body => '{"hello": "world!"}'
14
+ )
15
+ end
16
+
17
+ it "should log the request" do
18
+ feature.wrap_request(request)
19
+
20
+ expect(logger.output).to eq(
21
+ [
22
+ "> POST https://example.com/",
23
+ <<~REQ.strip
24
+ Accept: application/json
25
+ Host: example.com
26
+ User-Agent: http.rb/4.0.0.dev
27
+
28
+ {"hello": "world!"}
29
+ REQ
30
+ ]
31
+ )
32
+ end
33
+ end
34
+
35
+ describe "logging the response" do
36
+ let(:response) do
37
+ HTTP::Response.new(
38
+ :version => "1.1",
39
+ :uri => "https://example.com",
40
+ :status => 200,
41
+ :headers => {:content_type => "application/json"},
42
+ :body => '{"success": true}'
43
+ )
44
+ end
45
+
46
+ it "should log the response" do
47
+ feature.wrap_response(response)
48
+
49
+ expect(logger.output).to eq(
50
+ [
51
+ "< 200 OK",
52
+ <<~REQ.strip
53
+ Content-Type: application/json
54
+
55
+ {"success": true}
56
+ REQ
57
+ ]
58
+ )
59
+ end
60
+ end
61
+
62
+ class TestLogger
63
+ attr_reader :output
64
+ def initialize
65
+ @output = []
66
+ end
67
+
68
+ %w[fatal error warn info debug].each do |level|
69
+ define_method(level.to_sym) do |*args, &block|
70
+ @output << (block ? block.call : args)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -157,4 +157,33 @@ RSpec.describe HTTP::Request::Body do
157
157
  end
158
158
  end
159
159
  end
160
+
161
+ describe "#==" do
162
+ context "when sources are equivalent" do
163
+ let(:body1) { HTTP::Request::Body.new("content") }
164
+ let(:body2) { HTTP::Request::Body.new("content") }
165
+
166
+ it "returns true" do
167
+ expect(body1).to eq body2
168
+ end
169
+ end
170
+
171
+ context "when sources are not equivalent" do
172
+ let(:body1) { HTTP::Request::Body.new("content") }
173
+ let(:body2) { HTTP::Request::Body.new(nil) }
174
+
175
+ it "returns false" do
176
+ expect(body1).not_to eq body2
177
+ end
178
+ end
179
+
180
+ context "when objects are not of the same class" do
181
+ let(:body1) { HTTP::Request::Body.new("content") }
182
+ let(:body2) { "content" }
183
+
184
+ it "returns false" do
185
+ expect(body1).not_to eq body2
186
+ end
187
+ end
188
+ end
160
189
  end
@@ -74,5 +74,25 @@ RSpec.describe HTTP::Request::Writer do
74
74
  ].join
75
75
  end
76
76
  end
77
+
78
+ context "when server won't accept any more data" do
79
+ before do
80
+ expect(io).to receive(:write).and_raise(Errno::EPIPE)
81
+ end
82
+
83
+ it "aborts silently" do
84
+ writer.stream
85
+ end
86
+ end
87
+
88
+ context "when writing to socket raises an exception" do
89
+ before do
90
+ expect(io).to receive(:write).and_raise(Errno::ECONNRESET)
91
+ end
92
+
93
+ it "raises a ConnectionError" do
94
+ expect { writer.stream }.to raise_error(HTTP::ConnectionError)
95
+ end
96
+ end
77
97
  end
78
98
  end
@@ -55,38 +55,17 @@ RSpec.describe HTTP do
55
55
  end
56
56
 
57
57
  context "with a large request body" do
58
- %w[global null per_operation].each do |timeout|
59
- context "with a #{timeout} timeout" do
60
- [16_000, 16_500, 17_000, 34_000, 68_000].each do |size|
61
- [0, rand(0..100), rand(100..1000)].each do |fuzzer|
62
- context "with a #{size} body and #{fuzzer} of fuzzing" do
63
- let(:client) { HTTP.timeout(timeout, :read => 2, :write => 2, :connect => 2) }
64
-
65
- let(:characters) { ("A".."Z").to_a }
66
- let(:request_body) do
67
- Array.new(size + fuzzer) { |i| characters[i % characters.length] }.join
68
- end
69
-
70
- it "returns a large body" do
71
- response = client.post("#{dummy.endpoint}/echo-body", :body => request_body)
72
-
73
- expect(response.body.to_s).to eq(request_body)
74
- expect(response.headers["Content-Length"].to_i).to eq(request_body.bytesize)
75
- end
76
-
77
- context "when bytesize != length" do
78
- let(:characters) { ("A".."Z").to_a.push("“") }
79
-
80
- it "returns a large body" do
81
- body = {:data => request_body}
82
- response = client.post("#{dummy.endpoint}/echo-body", :json => body)
83
-
84
- expect(CGI.unescape(response.body.to_s)).to eq(body.to_json)
85
- expect(response.headers["Content-Length"].to_i).to eq(body.to_json.bytesize)
86
- end
87
- end
88
- end
89
- end
58
+ let(:request_body) { "“" * 1_000_000 } # use multi-byte character
59
+
60
+ [:null, 6, {:read => 2, :write => 2, :connect => 2}].each do |timeout|
61
+ context "with `.timeout(#{timeout.inspect})`" do
62
+ let(:client) { HTTP.timeout(timeout) }
63
+
64
+ it "writes the whole body" do
65
+ response = client.post "#{dummy.endpoint}/echo-body", :body => request_body
66
+
67
+ expect(response.body.to_s).to eq(request_body.b)
68
+ expect(response.headers["Content-Length"].to_i).to eq request_body.bytesize
90
69
  end
91
70
  end
92
71
  end
@@ -194,7 +173,7 @@ RSpec.describe HTTP do
194
173
 
195
174
  context "with encoding option" do
196
175
  it "respects option" do
197
- response = HTTP.get "#{dummy.endpoint}/iso-8859-1", "encoding" => Encoding::BINARY
176
+ response = HTTP.get "#{dummy.endpoint}/iso-8859-1", :encoding => Encoding::BINARY
198
177
  expect(response.to_s.encoding).to eq(Encoding::BINARY)
199
178
  end
200
179
  end
@@ -202,7 +181,7 @@ RSpec.describe HTTP do
202
181
 
203
182
  context "passing a string encoding type" do
204
183
  it "finds encoding" do
205
- response = HTTP.get dummy.endpoint, "encoding" => "ascii"
184
+ response = HTTP.get dummy.endpoint, :encoding => "ascii"
206
185
  expect(response.to_s.encoding).to eq(Encoding::ASCII)
207
186
  end
208
187
  end
@@ -255,15 +234,15 @@ RSpec.describe HTTP do
255
234
 
256
235
  describe ".basic_auth" do
257
236
  it "fails when options is not a Hash" do
258
- expect { HTTP.basic_auth "[FOOBAR]" }.to raise_error
237
+ expect { HTTP.basic_auth "[FOOBAR]" }.to raise_error(NoMethodError)
259
238
  end
260
239
 
261
240
  it "fails when :pass is not given" do
262
- expect { HTTP.basic_auth :user => "[USER]" }.to raise_error
241
+ expect { HTTP.basic_auth :user => "[USER]" }.to raise_error(KeyError)
263
242
  end
264
243
 
265
244
  it "fails when :user is not given" do
266
- expect { HTTP.basic_auth :pass => "[PASS]" }.to raise_error
245
+ expect { HTTP.basic_auth :pass => "[PASS]" }.to raise_error(KeyError)
267
246
  end
268
247
 
269
248
  it "sets Authorization header with proper BasicAuth value" do
@@ -305,22 +284,8 @@ RSpec.describe HTTP do
305
284
  end
306
285
 
307
286
  describe ".timeout" do
308
- context "without timeout type" do
309
- subject(:client) { HTTP.timeout :read => 123 }
310
-
311
- it "sets timeout_class to PerOperation" do
312
- expect(client.default_options.timeout_class).
313
- to be HTTP::Timeout::PerOperation
314
- end
315
-
316
- it "sets given timeout options" do
317
- expect(client.default_options.timeout_options).
318
- to eq :read_timeout => 123
319
- end
320
- end
321
-
322
- context "with :null type" do
323
- subject(:client) { HTTP.timeout :null, :read => 123 }
287
+ context "specifying a null timeout" do
288
+ subject(:client) { HTTP.timeout :null }
324
289
 
325
290
  it "sets timeout_class to Null" do
326
291
  expect(client.default_options.timeout_class).
@@ -328,8 +293,8 @@ RSpec.describe HTTP do
328
293
  end
329
294
  end
330
295
 
331
- context "with :per_operation type" do
332
- subject(:client) { HTTP.timeout :per_operation, :read => 123 }
296
+ context "specifying per operation timeouts" do
297
+ subject(:client) { HTTP.timeout :read => 123 }
333
298
 
334
299
  it "sets timeout_class to PerOperation" do
335
300
  expect(client.default_options.timeout_class).
@@ -342,24 +307,19 @@ RSpec.describe HTTP do
342
307
  end
343
308
  end
344
309
 
345
- context "with :global type" do
346
- subject(:client) { HTTP.timeout :global, :read => 123 }
310
+ context "specifying a global timeout" do
311
+ subject(:client) { HTTP.timeout 123 }
347
312
 
348
313
  it "sets timeout_class to Global" do
349
314
  expect(client.default_options.timeout_class).
350
315
  to be HTTP::Timeout::Global
351
316
  end
352
317
 
353
- it "sets given timeout options" do
318
+ it "sets given timeout option" do
354
319
  expect(client.default_options.timeout_options).
355
- to eq :read_timeout => 123
320
+ to eq :global_timeout => 123
356
321
  end
357
322
  end
358
-
359
- it "fails with unknown timeout type" do
360
- expect { HTTP.timeout(:foobar, :read => 123) }.
361
- to raise_error(ArgumentError, /foobar/)
362
- end
363
323
  end
364
324
 
365
325
  describe ".cookies" do
@@ -476,7 +436,7 @@ RSpec.describe HTTP do
476
436
  expect { HTTP.get "http://thishostshouldnotexists.com" }.
477
437
  to raise_error HTTP::ConnectionError
478
438
 
479
- expect { HTTP.get "http://127.0.0.1:000" }.
439
+ expect { HTTP.get "http://127.0.0.1:111" }.
480
440
  to raise_error HTTP::ConnectionError
481
441
  end
482
442
  end
@@ -1,14 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  RSpec.shared_context "HTTP handling" do
4
- describe "timeouts" do
5
- let(:conn_timeout) { 1 }
6
- let(:read_timeout) { 1 }
7
- let(:write_timeout) { 1 }
4
+ context "without timeouts" do
5
+ let(:options) { {:timeout_class => HTTP::Timeout::Null, :timeout_options => {}} }
6
+
7
+ it "works" do
8
+ expect(client.get(server.endpoint).body.to_s).to eq("<!doctype html>")
9
+ end
10
+ end
11
+
12
+ context "with a per operation timeout" do
13
+ let(:response) { client.get(server.endpoint).body.to_s }
8
14
 
9
15
  let(:options) do
10
16
  {
11
- :timeout_class => timeout_class,
17
+ :timeout_class => HTTP::Timeout::PerOperation,
12
18
  :timeout_options => {
13
19
  :connect_timeout => conn_timeout,
14
20
  :read_timeout => read_timeout,
@@ -16,87 +22,77 @@ RSpec.shared_context "HTTP handling" do
16
22
  }
17
23
  }
18
24
  end
25
+ let(:conn_timeout) { 1 }
26
+ let(:read_timeout) { 1 }
27
+ let(:write_timeout) { 1 }
19
28
 
20
- context "without timeouts" do
21
- let(:timeout_class) { HTTP::Timeout::Null }
22
- let(:conn_timeout) { 0 }
23
- let(:read_timeout) { 0 }
24
- let(:write_timeout) { 0 }
25
-
26
- it "works" do
27
- expect(client.get(server.endpoint).body.to_s).to eq("<!doctype html>")
28
- end
29
+ it "works" do
30
+ expect(response).to eq("<!doctype html>")
29
31
  end
30
32
 
31
- context "with a per operation timeout" do
32
- let(:timeout_class) { HTTP::Timeout::PerOperation }
33
-
34
- let(:response) { client.get(server.endpoint).body.to_s }
35
-
36
- it "works" do
37
- expect(response).to eq("<!doctype html>")
38
- end
33
+ context "connection" do
34
+ context "of 1" do
35
+ let(:conn_timeout) { 1 }
39
36
 
40
- context "connection" do
41
- context "of 1" do
42
- let(:conn_timeout) { 1 }
43
-
44
- it "does not time out" do
45
- expect { response }.to_not raise_error
46
- end
37
+ it "does not time out" do
38
+ expect { response }.to_not raise_error
47
39
  end
48
40
  end
41
+ end
49
42
 
50
- context "read" do
51
- context "of 0" do
52
- let(:read_timeout) { 0 }
43
+ context "read" do
44
+ context "of 0" do
45
+ let(:read_timeout) { 0 }
53
46
 
54
- it "times out", :flaky do
55
- expect { response }.to raise_error(HTTP::TimeoutError, /Read/i)
56
- end
47
+ it "times out", :flaky do
48
+ expect { response }.to raise_error(HTTP::TimeoutError, /Read/i)
57
49
  end
50
+ end
58
51
 
59
- context "of 2.5" do
60
- let(:read_timeout) { 2.5 }
52
+ context "of 2.5" do
53
+ let(:read_timeout) { 2.5 }
61
54
 
62
- it "does not time out", :flaky do
63
- expect { client.get("#{server.endpoint}/sleep").body.to_s }.to_not raise_error
64
- end
55
+ it "does not time out", :flaky do
56
+ expect { client.get("#{server.endpoint}/sleep").body.to_s }.to_not raise_error
65
57
  end
66
58
  end
67
59
  end
60
+ end
68
61
 
69
- context "with a global timeout" do
70
- let(:timeout_class) { HTTP::Timeout::Global }
71
-
72
- let(:conn_timeout) { 0 }
73
- let(:read_timeout) { 1 }
74
- let(:write_timeout) { 0 }
75
-
76
- let(:response) { client.get(server.endpoint).body.to_s }
62
+ context "with a global timeout" do
63
+ let(:options) do
64
+ {
65
+ :timeout_class => HTTP::Timeout::Global,
66
+ :timeout_options => {
67
+ :global_timeout => global_timeout
68
+ }
69
+ }
70
+ end
71
+ let(:global_timeout) { 1 }
77
72
 
78
- it "errors if connecting takes too long" do
79
- expect(TCPSocket).to receive(:open) do
80
- sleep 1.25
81
- end
73
+ let(:response) { client.get(server.endpoint).body.to_s }
82
74
 
83
- expect { response }.to raise_error(HTTP::TimeoutError, /execution/)
75
+ it "errors if connecting takes too long" do
76
+ expect(TCPSocket).to receive(:open) do
77
+ sleep 1.25
84
78
  end
85
79
 
86
- it "errors if reading takes too long" do
87
- expect { client.get("#{server.endpoint}/sleep").body.to_s }.
88
- to raise_error(HTTP::TimeoutError, /Timed out/)
89
- end
80
+ expect { response }.to raise_error(HTTP::TimeoutError, /execution/)
81
+ end
90
82
 
91
- context "it resets state when reusing connections" do
92
- let(:extra_options) { {:persistent => server.endpoint} }
83
+ it "errors if reading takes too long" do
84
+ expect { client.get("#{server.endpoint}/sleep").body.to_s }.
85
+ to raise_error(HTTP::TimeoutError, /Timed out/)
86
+ end
93
87
 
94
- let(:read_timeout) { 2.5 }
88
+ context "it resets state when reusing connections" do
89
+ let(:extra_options) { {:persistent => server.endpoint} }
95
90
 
96
- it "does not timeout", :flaky do
97
- client.get("#{server.endpoint}/sleep").body.to_s
98
- client.get("#{server.endpoint}/sleep").body.to_s
99
- end
91
+ let(:global_timeout) { 2.5 }
92
+
93
+ it "does not timeout", :flaky do
94
+ client.get("#{server.endpoint}/sleep").body.to_s
95
+ client.get("#{server.endpoint}/sleep").body.to_s
100
96
  end
101
97
  end
102
98
  end