http 5.3.1 → 6.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.
Files changed (201) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +241 -41
  3. data/LICENSE.txt +1 -1
  4. data/README.md +110 -13
  5. data/UPGRADING.md +491 -0
  6. data/http.gemspec +32 -29
  7. data/lib/http/base64.rb +11 -1
  8. data/lib/http/chainable/helpers.rb +62 -0
  9. data/lib/http/chainable/verbs.rb +136 -0
  10. data/lib/http/chainable.rb +232 -136
  11. data/lib/http/client.rb +158 -127
  12. data/lib/http/connection/internals.rb +141 -0
  13. data/lib/http/connection.rb +126 -97
  14. data/lib/http/content_type.rb +61 -6
  15. data/lib/http/errors.rb +25 -1
  16. data/lib/http/feature.rb +65 -5
  17. data/lib/http/features/auto_deflate.rb +124 -17
  18. data/lib/http/features/auto_inflate.rb +38 -15
  19. data/lib/http/features/caching/entry.rb +178 -0
  20. data/lib/http/features/caching/in_memory_store.rb +63 -0
  21. data/lib/http/features/caching.rb +216 -0
  22. data/lib/http/features/digest_auth.rb +234 -0
  23. data/lib/http/features/instrumentation.rb +97 -17
  24. data/lib/http/features/logging.rb +183 -5
  25. data/lib/http/features/normalize_uri.rb +17 -0
  26. data/lib/http/features/raise_error.rb +18 -3
  27. data/lib/http/form_data/composite_io.rb +106 -0
  28. data/lib/http/form_data/file.rb +95 -0
  29. data/lib/http/form_data/multipart/param.rb +62 -0
  30. data/lib/http/form_data/multipart.rb +106 -0
  31. data/lib/http/form_data/part.rb +52 -0
  32. data/lib/http/form_data/readable.rb +58 -0
  33. data/lib/http/form_data/urlencoded.rb +175 -0
  34. data/lib/http/form_data/version.rb +8 -0
  35. data/lib/http/form_data.rb +102 -0
  36. data/lib/http/headers/known.rb +3 -0
  37. data/lib/http/headers/normalizer.rb +17 -36
  38. data/lib/http/headers.rb +172 -65
  39. data/lib/http/mime_type/adapter.rb +24 -9
  40. data/lib/http/mime_type/json.rb +19 -4
  41. data/lib/http/mime_type.rb +21 -3
  42. data/lib/http/options/definitions.rb +189 -0
  43. data/lib/http/options.rb +172 -125
  44. data/lib/http/redirector.rb +80 -75
  45. data/lib/http/request/body.rb +87 -6
  46. data/lib/http/request/builder.rb +184 -0
  47. data/lib/http/request/proxy.rb +83 -0
  48. data/lib/http/request/writer.rb +76 -16
  49. data/lib/http/request.rb +214 -98
  50. data/lib/http/response/body.rb +103 -18
  51. data/lib/http/response/inflater.rb +35 -7
  52. data/lib/http/response/parser.rb +98 -4
  53. data/lib/http/response/status/reasons.rb +2 -4
  54. data/lib/http/response/status.rb +141 -31
  55. data/lib/http/response.rb +219 -61
  56. data/lib/http/retriable/delay_calculator.rb +38 -11
  57. data/lib/http/retriable/errors.rb +21 -0
  58. data/lib/http/retriable/performer.rb +82 -38
  59. data/lib/http/session.rb +280 -0
  60. data/lib/http/timeout/global.rb +147 -34
  61. data/lib/http/timeout/null.rb +155 -9
  62. data/lib/http/timeout/per_operation.rb +139 -18
  63. data/lib/http/uri/normalizer.rb +82 -0
  64. data/lib/http/uri/parsing.rb +182 -0
  65. data/lib/http/uri.rb +289 -124
  66. data/lib/http/version.rb +2 -1
  67. data/lib/http.rb +11 -2
  68. data/sig/deps.rbs +122 -0
  69. data/sig/http.rbs +1619 -0
  70. data/test/http/base64_test.rb +28 -0
  71. data/test/http/client_test.rb +739 -0
  72. data/test/http/connection_test.rb +1533 -0
  73. data/test/http/content_type_test.rb +190 -0
  74. data/test/http/errors_test.rb +28 -0
  75. data/test/http/feature_test.rb +49 -0
  76. data/test/http/features/auto_deflate_test.rb +317 -0
  77. data/test/http/features/auto_inflate_test.rb +213 -0
  78. data/test/http/features/caching_test.rb +942 -0
  79. data/test/http/features/digest_auth_test.rb +996 -0
  80. data/test/http/features/instrumentation_test.rb +246 -0
  81. data/test/http/features/logging_test.rb +654 -0
  82. data/test/http/features/normalize_uri_test.rb +41 -0
  83. data/test/http/features/raise_error_test.rb +77 -0
  84. data/test/http/form_data/composite_io_test.rb +215 -0
  85. data/test/http/form_data/file_test.rb +255 -0
  86. data/test/http/form_data/fixtures/the-http-gem.info +1 -0
  87. data/test/http/form_data/multipart_test.rb +303 -0
  88. data/test/http/form_data/part_test.rb +90 -0
  89. data/test/http/form_data/urlencoded_test.rb +164 -0
  90. data/test/http/form_data_test.rb +232 -0
  91. data/test/http/headers/normalizer_test.rb +93 -0
  92. data/test/http/headers_test.rb +888 -0
  93. data/test/http/mime_type/json_test.rb +39 -0
  94. data/test/http/mime_type_test.rb +150 -0
  95. data/test/http/options/base_uri_test.rb +148 -0
  96. data/test/http/options/body_test.rb +21 -0
  97. data/test/http/options/features_test.rb +38 -0
  98. data/test/http/options/form_test.rb +21 -0
  99. data/test/http/options/headers_test.rb +32 -0
  100. data/test/http/options/json_test.rb +21 -0
  101. data/test/http/options/merge_test.rb +78 -0
  102. data/test/http/options/new_test.rb +37 -0
  103. data/test/http/options/proxy_test.rb +32 -0
  104. data/test/http/options_test.rb +575 -0
  105. data/test/http/redirector_test.rb +639 -0
  106. data/test/http/request/body_test.rb +318 -0
  107. data/test/http/request/builder_test.rb +623 -0
  108. data/test/http/request/writer_test.rb +391 -0
  109. data/test/http/request_test.rb +1733 -0
  110. data/test/http/response/body_test.rb +292 -0
  111. data/test/http/response/parser_test.rb +105 -0
  112. data/test/http/response/status_test.rb +322 -0
  113. data/test/http/response_test.rb +502 -0
  114. data/test/http/retriable/delay_calculator_test.rb +194 -0
  115. data/test/http/retriable/errors_test.rb +71 -0
  116. data/test/http/retriable/performer_test.rb +551 -0
  117. data/test/http/session_test.rb +424 -0
  118. data/test/http/timeout/global_test.rb +239 -0
  119. data/test/http/timeout/null_test.rb +218 -0
  120. data/test/http/timeout/per_operation_test.rb +220 -0
  121. data/test/http/uri/normalizer_test.rb +89 -0
  122. data/test/http/uri_test.rb +1140 -0
  123. data/test/http/version_test.rb +15 -0
  124. data/test/http_test.rb +818 -0
  125. data/test/regression_tests.rb +27 -0
  126. data/test/support/dummy_server/encoding_routes.rb +47 -0
  127. data/test/support/dummy_server/routes.rb +201 -0
  128. data/test/support/dummy_server/servlet.rb +81 -0
  129. data/test/support/dummy_server.rb +200 -0
  130. data/{spec → test}/support/fakeio.rb +2 -2
  131. data/test/support/http_handling_shared/connection_reuse_tests.rb +97 -0
  132. data/test/support/http_handling_shared/timeout_tests.rb +134 -0
  133. data/test/support/http_handling_shared.rb +11 -0
  134. data/test/support/proxy_server.rb +207 -0
  135. data/test/support/servers/runner.rb +67 -0
  136. data/{spec → test}/support/simplecov.rb +11 -2
  137. data/test/support/ssl_helper.rb +108 -0
  138. data/test/test_helper.rb +38 -0
  139. metadata +108 -168
  140. data/.github/workflows/ci.yml +0 -67
  141. data/.gitignore +0 -15
  142. data/.rspec +0 -1
  143. data/.rubocop/layout.yml +0 -8
  144. data/.rubocop/metrics.yml +0 -4
  145. data/.rubocop/rspec.yml +0 -9
  146. data/.rubocop/style.yml +0 -32
  147. data/.rubocop.yml +0 -11
  148. data/.rubocop_todo.yml +0 -219
  149. data/.yardopts +0 -2
  150. data/CHANGES_OLD.md +0 -1002
  151. data/Gemfile +0 -51
  152. data/Guardfile +0 -18
  153. data/Rakefile +0 -64
  154. data/lib/http/headers/mixin.rb +0 -34
  155. data/lib/http/retriable/client.rb +0 -37
  156. data/logo.png +0 -0
  157. data/spec/lib/http/client_spec.rb +0 -556
  158. data/spec/lib/http/connection_spec.rb +0 -88
  159. data/spec/lib/http/content_type_spec.rb +0 -47
  160. data/spec/lib/http/features/auto_deflate_spec.rb +0 -77
  161. data/spec/lib/http/features/auto_inflate_spec.rb +0 -86
  162. data/spec/lib/http/features/instrumentation_spec.rb +0 -81
  163. data/spec/lib/http/features/logging_spec.rb +0 -65
  164. data/spec/lib/http/features/raise_error_spec.rb +0 -62
  165. data/spec/lib/http/headers/mixin_spec.rb +0 -36
  166. data/spec/lib/http/headers/normalizer_spec.rb +0 -52
  167. data/spec/lib/http/headers_spec.rb +0 -527
  168. data/spec/lib/http/options/body_spec.rb +0 -15
  169. data/spec/lib/http/options/features_spec.rb +0 -33
  170. data/spec/lib/http/options/form_spec.rb +0 -15
  171. data/spec/lib/http/options/headers_spec.rb +0 -24
  172. data/spec/lib/http/options/json_spec.rb +0 -15
  173. data/spec/lib/http/options/merge_spec.rb +0 -68
  174. data/spec/lib/http/options/new_spec.rb +0 -30
  175. data/spec/lib/http/options/proxy_spec.rb +0 -20
  176. data/spec/lib/http/options_spec.rb +0 -13
  177. data/spec/lib/http/redirector_spec.rb +0 -530
  178. data/spec/lib/http/request/body_spec.rb +0 -211
  179. data/spec/lib/http/request/writer_spec.rb +0 -121
  180. data/spec/lib/http/request_spec.rb +0 -234
  181. data/spec/lib/http/response/body_spec.rb +0 -85
  182. data/spec/lib/http/response/parser_spec.rb +0 -74
  183. data/spec/lib/http/response/status_spec.rb +0 -253
  184. data/spec/lib/http/response_spec.rb +0 -262
  185. data/spec/lib/http/retriable/delay_calculator_spec.rb +0 -69
  186. data/spec/lib/http/retriable/performer_spec.rb +0 -302
  187. data/spec/lib/http/uri/normalizer_spec.rb +0 -95
  188. data/spec/lib/http/uri_spec.rb +0 -71
  189. data/spec/lib/http_spec.rb +0 -535
  190. data/spec/regression_specs.rb +0 -24
  191. data/spec/spec_helper.rb +0 -89
  192. data/spec/support/black_hole.rb +0 -13
  193. data/spec/support/dummy_server/servlet.rb +0 -203
  194. data/spec/support/dummy_server.rb +0 -44
  195. data/spec/support/fuubar.rb +0 -21
  196. data/spec/support/http_handling_shared.rb +0 -190
  197. data/spec/support/proxy_server.rb +0 -39
  198. data/spec/support/servers/config.rb +0 -11
  199. data/spec/support/servers/runner.rb +0 -19
  200. data/spec/support/ssl_helper.rb +0 -104
  201. /data/{spec → test}/support/capture_warning.rb +0 -0
@@ -1,77 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe HTTP::Features::AutoDeflate do
4
- subject { HTTP::Features::AutoDeflate.new }
5
-
6
- it "raises error for wrong type" do
7
- expect { HTTP::Features::AutoDeflate.new(:method => :wrong) }.
8
- to raise_error(HTTP::Error) { |error|
9
- expect(error.message).to eq("Only gzip and deflate methods are supported")
10
- }
11
- end
12
-
13
- it "accepts gzip method" do
14
- expect(HTTP::Features::AutoDeflate.new(:method => :gzip).method).to eq "gzip"
15
- end
16
-
17
- it "accepts deflate method" do
18
- expect(HTTP::Features::AutoDeflate.new(:method => :deflate).method).to eq "deflate"
19
- end
20
-
21
- it "accepts string as method" do
22
- expect(HTTP::Features::AutoDeflate.new(:method => "gzip").method).to eq "gzip"
23
- end
24
-
25
- it "uses gzip by default" do
26
- expect(subject.method).to eq("gzip")
27
- end
28
-
29
- describe "#deflated_body" do
30
- let(:body) { %w[bees cows] }
31
- let(:deflated_body) { subject.deflated_body(body) }
32
-
33
- context "when method is gzip" do
34
- subject { HTTP::Features::AutoDeflate.new(:method => :gzip) }
35
-
36
- it "returns object which yields gzipped content of the given body" do
37
- io = StringIO.new
38
- io.set_encoding(Encoding::BINARY)
39
- gzip = Zlib::GzipWriter.new(io)
40
- gzip.write("beescows")
41
- gzip.close
42
- gzipped = io.string
43
-
44
- expect(deflated_body.each.to_a.join).to eq gzipped
45
- end
46
-
47
- it "caches compressed content when size is called" do
48
- io = StringIO.new
49
- io.set_encoding(Encoding::BINARY)
50
- gzip = Zlib::GzipWriter.new(io)
51
- gzip.write("beescows")
52
- gzip.close
53
- gzipped = io.string
54
-
55
- expect(deflated_body.size).to eq gzipped.bytesize
56
- expect(deflated_body.each.to_a.join).to eq gzipped
57
- end
58
- end
59
-
60
- context "when method is deflate" do
61
- subject { HTTP::Features::AutoDeflate.new(:method => :deflate) }
62
-
63
- it "returns object which yields deflated content of the given body" do
64
- deflated = Zlib::Deflate.deflate("beescows")
65
-
66
- expect(deflated_body.each.to_a.join).to eq deflated
67
- end
68
-
69
- it "caches compressed content when size is called" do
70
- deflated = Zlib::Deflate.deflate("beescows")
71
-
72
- expect(deflated_body.size).to eq deflated.bytesize
73
- expect(deflated_body.each.to_a.join).to eq deflated
74
- end
75
- end
76
- end
77
- end
@@ -1,86 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe HTTP::Features::AutoInflate do
4
- subject(:feature) { HTTP::Features::AutoInflate.new }
5
-
6
- let(:connection) { double }
7
- let(:headers) { {} }
8
-
9
- let(:response) do
10
- HTTP::Response.new(
11
- :version => "1.1",
12
- :status => 200,
13
- :headers => headers,
14
- :connection => connection,
15
- :request => HTTP::Request.new(:verb => :get, :uri => "http://example.com")
16
- )
17
- end
18
-
19
- describe "#wrap_response" do
20
- subject(:result) { feature.wrap_response(response) }
21
-
22
- context "when there is no Content-Encoding header" do
23
- it "returns original request" do
24
- expect(result).to be response
25
- end
26
- end
27
-
28
- context "for identity Content-Encoding header" do
29
- let(:headers) { {:content_encoding => "identity"} }
30
-
31
- it "returns original request" do
32
- expect(result).to be response
33
- end
34
- end
35
-
36
- context "for unknown Content-Encoding header" do
37
- let(:headers) { {:content_encoding => "not-supported"} }
38
-
39
- it "returns original request" do
40
- expect(result).to be response
41
- end
42
- end
43
-
44
- context "for deflate Content-Encoding header" do
45
- let(:headers) { {:content_encoding => "deflate"} }
46
-
47
- it "returns a HTTP::Response wrapping the inflated response body" do
48
- expect(result.body).to be_instance_of HTTP::Response::Body
49
- end
50
- end
51
-
52
- context "for gzip Content-Encoding header" do
53
- let(:headers) { {:content_encoding => "gzip"} }
54
-
55
- it "returns a HTTP::Response wrapping the inflated response body" do
56
- expect(result.body).to be_instance_of HTTP::Response::Body
57
- end
58
- end
59
-
60
- context "for x-gzip Content-Encoding header" do
61
- let(:headers) { {:content_encoding => "x-gzip"} }
62
-
63
- it "returns a HTTP::Response wrapping the inflated response body" do
64
- expect(result.body).to be_instance_of HTTP::Response::Body
65
- end
66
- end
67
-
68
- # TODO(ixti): We should refactor API to either make uri non-optional,
69
- # or add reference to request into response object (better).
70
- context "when response has uri" do
71
- let(:response) do
72
- HTTP::Response.new(
73
- :version => "1.1",
74
- :status => 200,
75
- :headers => {:content_encoding => "gzip"},
76
- :connection => connection,
77
- :request => HTTP::Request.new(:verb => :get, :uri => "https://example.com")
78
- )
79
- end
80
-
81
- it "preserves uri in wrapped response" do
82
- expect(result.uri).to eq HTTP::URI.parse("https://example.com")
83
- end
84
- end
85
- end
86
- end
@@ -1,81 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe HTTP::Features::Instrumentation do
4
- subject(:feature) { HTTP::Features::Instrumentation.new(:instrumenter => instrumenter) }
5
-
6
- let(:instrumenter) { TestInstrumenter.new }
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
-
28
- describe "logging the request" do
29
- let(:request) do
30
- HTTP::Request.new(
31
- :verb => :post,
32
- :uri => "https://example.com/",
33
- :headers => {:accept => "application/json"},
34
- :body => '{"hello": "world!"}'
35
- )
36
- end
37
-
38
- it "should log the request" do
39
- feature.wrap_request(request)
40
-
41
- expect(instrumenter.output[:start]).to eq(:request => request)
42
- end
43
- end
44
-
45
- describe "logging the response" do
46
- let(:response) do
47
- HTTP::Response.new(
48
- :version => "1.1",
49
- :status => 200,
50
- :headers => {:content_type => "application/json"},
51
- :body => '{"success": true}',
52
- :request => HTTP::Request.new(:verb => :get, :uri => "https://example.com")
53
- )
54
- end
55
-
56
- it "should log the response" do
57
- feature.wrap_response(response)
58
-
59
- expect(instrumenter.output[:finish]).to eq(:response => response)
60
- end
61
- end
62
-
63
- describe "logging errors" do
64
- let(:request) do
65
- HTTP::Request.new(
66
- :verb => :post,
67
- :uri => "https://example.com/",
68
- :headers => {:accept => "application/json"},
69
- :body => '{"hello": "world!"}'
70
- )
71
- end
72
-
73
- let(:error) { HTTP::TimeoutError.new }
74
-
75
- it "should log the error" do
76
- feature.on_error(request, error)
77
-
78
- expect(instrumenter.output[:finish]).to eq(:request => request, :error => error)
79
- end
80
- end
81
- end
@@ -1,65 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "logger"
4
-
5
- RSpec.describe HTTP::Features::Logging do
6
- subject(:feature) do
7
- logger = Logger.new(logdev)
8
- logger.formatter = ->(severity, _, _, message) { format("** %s **\n%s\n", severity, message) }
9
-
10
- described_class.new(:logger => logger)
11
- end
12
-
13
- let(:logdev) { StringIO.new }
14
-
15
- describe "logging the request" do
16
- let(:request) do
17
- HTTP::Request.new(
18
- :verb => :post,
19
- :uri => "https://example.com/",
20
- :headers => {:accept => "application/json"},
21
- :body => '{"hello": "world!"}'
22
- )
23
- end
24
-
25
- it "should log the request" do
26
- feature.wrap_request(request)
27
-
28
- expect(logdev.string).to eq <<~OUTPUT
29
- ** INFO **
30
- > POST https://example.com/
31
- ** DEBUG **
32
- Accept: application/json
33
- Host: example.com
34
- User-Agent: http.rb/#{HTTP::VERSION}
35
-
36
- {"hello": "world!"}
37
- OUTPUT
38
- end
39
- end
40
-
41
- describe "logging the response" do
42
- let(:response) do
43
- HTTP::Response.new(
44
- :version => "1.1",
45
- :status => 200,
46
- :headers => {:content_type => "application/json"},
47
- :body => '{"success": true}',
48
- :request => HTTP::Request.new(:verb => :get, :uri => "https://example.com")
49
- )
50
- end
51
-
52
- it "should log the response" do
53
- feature.wrap_response(response)
54
-
55
- expect(logdev.string).to eq <<~OUTPUT
56
- ** INFO **
57
- < 200 OK
58
- ** DEBUG **
59
- Content-Type: application/json
60
-
61
- {"success": true}
62
- OUTPUT
63
- end
64
- end
65
- end
@@ -1,62 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe HTTP::Features::RaiseError do
4
- subject(:feature) { described_class.new(ignore: ignore) }
5
-
6
- let(:connection) { double }
7
- let(:status) { 200 }
8
- let(:ignore) { [] }
9
-
10
- describe "#wrap_response" do
11
- subject(:result) { feature.wrap_response(response) }
12
-
13
- let(:response) do
14
- HTTP::Response.new(
15
- version: "1.1",
16
- status: status,
17
- headers: {},
18
- connection: connection,
19
- request: HTTP::Request.new(verb: :get, uri: "https://example.com")
20
- )
21
- end
22
-
23
- context "when status is 200" do
24
- it "returns original request" do
25
- expect(result).to be response
26
- end
27
- end
28
-
29
- context "when status is 399" do
30
- let(:status) { 399 }
31
-
32
- it "returns original request" do
33
- expect(result).to be response
34
- end
35
- end
36
-
37
- context "when status is 400" do
38
- let(:status) { 400 }
39
-
40
- it "raises" do
41
- expect { result }.to raise_error(HTTP::StatusError, "Unexpected status code 400")
42
- end
43
- end
44
-
45
- context "when status is 599" do
46
- let(:status) { 599 }
47
-
48
- it "raises" do
49
- expect { result }.to raise_error(HTTP::StatusError, "Unexpected status code 599")
50
- end
51
- end
52
-
53
- context "when error status is ignored" do
54
- let(:status) { 500 }
55
- let(:ignore) { [500] }
56
-
57
- it "returns original request" do
58
- expect(result).to be response
59
- end
60
- end
61
- end
62
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe HTTP::Headers::Mixin do
4
- let :dummy_class do
5
- Class.new do
6
- include HTTP::Headers::Mixin
7
-
8
- def initialize(headers)
9
- @headers = headers
10
- end
11
- end
12
- end
13
-
14
- let(:headers) { HTTP::Headers.new }
15
- let(:dummy) { dummy_class.new headers }
16
-
17
- describe "#headers" do
18
- it "returns @headers instance variable" do
19
- expect(dummy.headers).to be headers
20
- end
21
- end
22
-
23
- describe "#[]" do
24
- it "proxies to headers#[]" do
25
- expect(headers).to receive(:[]).with(:accept)
26
- dummy[:accept]
27
- end
28
- end
29
-
30
- describe "#[]=" do
31
- it "proxies to headers#[]" do
32
- expect(headers).to receive(:[]=).with(:accept, "text/plain")
33
- dummy[:accept] = "text/plain"
34
- end
35
- end
36
- end
@@ -1,52 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe HTTP::Headers::Normalizer do
4
- subject(:normalizer) { described_class.new }
5
-
6
- include_context RSpec::Memory
7
-
8
- describe "#call" do
9
- it "normalizes the header" do
10
- expect(normalizer.call("content_type")).to eq "Content-Type"
11
- end
12
-
13
- it "returns a non-frozen string" do
14
- expect(normalizer.call("content_type")).not_to be_frozen
15
- end
16
-
17
- it "evicts the oldest item when cache is full" do
18
- max_headers = (1..described_class::Cache::MAX_SIZE).map { |i| "Header#{i}" }
19
- max_headers.each { |header| normalizer.call(header) }
20
- normalizer.call("New-Header")
21
- cache_store = normalizer.instance_variable_get(:@cache).instance_variable_get(:@store)
22
- expect(cache_store.keys).to eq(max_headers[1..] + ["New-Header"])
23
- end
24
-
25
- it "retuns mutable strings" do
26
- normalized_headers = Array.new(3) { normalizer.call("content_type") }
27
-
28
- expect(normalized_headers)
29
- .to satisfy { |arr| arr.uniq.size == 1 }
30
- .and(satisfy { |arr| arr.map(&:object_id).uniq.size == normalized_headers.size })
31
- .and(satisfy { |arr| arr.none?(&:frozen?) })
32
- end
33
-
34
- it "allocates minimal memory for normalization of the same header" do
35
- normalizer.call("accept") # XXX: Ensure normalizer is pre-allocated
36
-
37
- # On first call it is expected to allocate during normalization
38
- expect { normalizer.call("content_type") }.to limit_allocations(
39
- Array => 1,
40
- MatchData => 1,
41
- String => 6
42
- )
43
-
44
- # On subsequent call it is expected to only allocate copy of a cached string
45
- expect { normalizer.call("content_type") }.to limit_allocations(
46
- Array => 0,
47
- MatchData => 0,
48
- String => 1
49
- )
50
- end
51
- end
52
- end