webmock 3.11.1 → 3.18.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/CI.yml +38 -0
  3. data/CHANGELOG.md +126 -2
  4. data/Gemfile +1 -1
  5. data/README.md +33 -16
  6. data/Rakefile +12 -2
  7. data/lib/webmock/http_lib_adapters/async_http_client_adapter.rb +9 -2
  8. data/lib/webmock/http_lib_adapters/curb_adapter.rb +2 -2
  9. data/lib/webmock/http_lib_adapters/em_http_request_adapter.rb +6 -3
  10. data/lib/webmock/http_lib_adapters/http_rb/client.rb +1 -3
  11. data/lib/webmock/http_lib_adapters/http_rb/response.rb +17 -3
  12. data/lib/webmock/http_lib_adapters/http_rb/streamer.rb +4 -2
  13. data/lib/webmock/http_lib_adapters/http_rb/webmock.rb +6 -2
  14. data/lib/webmock/http_lib_adapters/manticore_adapter.rb +8 -1
  15. data/lib/webmock/http_lib_adapters/net_http.rb +29 -115
  16. data/lib/webmock/request_pattern.rb +30 -8
  17. data/lib/webmock/request_signature.rb +2 -2
  18. data/lib/webmock/request_stub.rb +15 -0
  19. data/lib/webmock/response.rb +8 -8
  20. data/lib/webmock/version.rb +1 -1
  21. data/lib/webmock/webmock.rb +10 -0
  22. data/minitest/webmock_spec.rb +1 -1
  23. data/spec/acceptance/async_http_client/async_http_client_spec.rb +27 -5
  24. data/spec/acceptance/curb/curb_spec.rb +11 -0
  25. data/spec/acceptance/em_http_request/em_http_request_spec.rb +57 -1
  26. data/spec/acceptance/em_http_request/em_http_request_spec_helper.rb +1 -1
  27. data/spec/acceptance/excon/excon_spec.rb +2 -2
  28. data/spec/acceptance/manticore/manticore_spec.rb +32 -0
  29. data/spec/acceptance/net_http/net_http_shared.rb +46 -9
  30. data/spec/acceptance/net_http/net_http_spec.rb +75 -23
  31. data/spec/acceptance/net_http/real_net_http_spec.rb +1 -1
  32. data/spec/acceptance/patron/patron_spec.rb +19 -21
  33. data/spec/acceptance/patron/patron_spec_helper.rb +2 -2
  34. data/spec/acceptance/shared/allowing_and_disabling_net_connect.rb +14 -14
  35. data/spec/acceptance/shared/callbacks.rb +2 -2
  36. data/spec/acceptance/shared/complex_cross_concern_behaviors.rb +1 -1
  37. data/spec/unit/request_pattern_spec.rb +82 -46
  38. data/spec/unit/request_signature_spec.rb +21 -1
  39. data/spec/unit/request_stub_spec.rb +35 -0
  40. data/spec/unit/response_spec.rb +29 -1
  41. data/spec/unit/webmock_spec.rb +54 -0
  42. data/webmock.gemspec +6 -5
  43. metadata +46 -32
  44. data/.travis.yml +0 -24
@@ -1,3 +1,5 @@
1
+ require 'set'
2
+
1
3
  shared_examples_for "Net::HTTP" do
2
4
  describe "when making real requests", net_connect: true do
3
5
  let(:port){ WebMockServer.instance.port }
@@ -26,21 +28,56 @@ shared_examples_for "Net::HTTP" do
26
28
 
27
29
  it "should connect only once when connected on start", net_connect: true do
28
30
  @http = Net::HTTP.new('localhost', port)
29
- socket_id_before_request = socket_id_after_request = nil
31
+ socket_before_request = socket_after_request = nil
30
32
  @http.start {|conn|
31
- socket_id_before_request = conn.instance_variable_get(:@socket).object_id
33
+ socket_before_request = conn.instance_variable_get(:@socket)
32
34
  conn.request(Net::HTTP::Get.new("/"))
33
- socket_id_after_request = conn.instance_variable_get(:@socket).object_id
35
+ socket_after_request = conn.instance_variable_get(:@socket)
34
36
  }
35
37
 
36
- if !defined?(WebMock::Config) || WebMock::Config.instance.net_http_connect_on_start
37
- expect(socket_id_before_request).not_to eq(nil.object_id)
38
- expect(socket_id_after_request).not_to eq(nil.object_id)
39
- expect(socket_id_after_request).to eq(socket_id_before_request)
38
+ if !defined?(WebMock::NetHTTPUtility) || WebMock::Config.instance.net_http_connect_on_start
39
+ expect(socket_before_request).to be_a(Net::BufferedIO)
40
+ expect(socket_after_request).to be_a(Net::BufferedIO)
41
+ expect(socket_after_request).to be(socket_before_request)
42
+ else
43
+ expect(socket_before_request).to be_a(StubSocket)
44
+ expect(socket_after_request).to be_a(Net::BufferedIO)
45
+ end
46
+ end
47
+
48
+ it "should allow sending multiple requests when persisted", net_connect: true do
49
+ @http = Net::HTTP.new('example.org')
50
+ @http.start
51
+ expect(@http.get("/")).to be_a(Net::HTTPSuccess)
52
+ expect(@http.get("/")).to be_a(Net::HTTPSuccess)
53
+ expect(@http.get("/")).to be_a(Net::HTTPSuccess)
54
+ @http.finish
55
+ end
56
+
57
+ it "should not leak file descriptors", net_connect: true do
58
+ sockets = Set.new
59
+
60
+ @http = Net::HTTP.new('example.org')
61
+ @http.start
62
+ sockets << @http.instance_variable_get(:@socket)
63
+ @http.get("/")
64
+ sockets << @http.instance_variable_get(:@socket)
65
+ @http.get("/")
66
+ sockets << @http.instance_variable_get(:@socket)
67
+ @http.get("/")
68
+ sockets << @http.instance_variable_get(:@socket)
69
+ @http.finish
70
+
71
+ if !defined?(WebMock::NetHTTPUtility) || WebMock.net_http_connect_on_start?(Addressable::URI.parse("http://example.com/"))
72
+ expect(sockets.length).to eq(1)
73
+ expect(sockets.to_a[0]).to be_a(Net::BufferedIO)
40
74
  else
41
- expect(socket_id_before_request).to eq(nil.object_id)
42
- expect(socket_id_after_request).not_to eq(nil.object_id)
75
+ expect(sockets.length).to eq(2)
76
+ expect(sockets.to_a[0]).to be_a(StubSocket)
77
+ expect(sockets.to_a[1]).to be_a(Net::BufferedIO)
43
78
  end
79
+
80
+ expect(sockets.all?(&:closed?)).to be(true)
44
81
  end
45
82
 
46
83
  it "should pass the read_timeout value on", net_connect: true do
@@ -115,33 +115,16 @@ describe "Net:HTTP" do
115
115
  expect(Net::HTTP.start("www.example.com") { |http| http.request(req)}.body).to eq("abc")
116
116
  end
117
117
 
118
- it "raises an ArgumentError if passed headers as symbols if RUBY_VERSION < 2.3.0" do
118
+ it "raises an ArgumentError if passed headers as symbols" do
119
119
  uri = URI.parse("http://google.com/")
120
120
  http = Net::HTTP.new(uri.host, uri.port)
121
121
  request = Net::HTTP::Get.new(uri.request_uri)
122
+ request[:InvalidHeaderSinceItsASymbol] = "this will not be valid"
122
123
 
123
- # Net::HTTP calls downcase on header keys assigned with []=
124
- # In Ruby 1.8.7 symbols do not respond to downcase
125
- #
126
- # Meaning you can not assign header keys as symbols in ruby 1.8.7 using []=
127
- if :symbol.respond_to?(:downcase)
128
- request[:InvalidHeaderSinceItsASymbol] = "this will not be valid"
129
- else
130
- request.instance_eval do
131
- @header = request.to_hash.merge({InvalidHeaderSinceItsASymbol: "this will not be valid"})
132
- end
133
- end
134
-
135
- if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.3.0')
136
- expect do
137
- http.request(request)
138
- end.to raise_error ArgumentError, "Net:HTTP does not accept headers as symbols"
139
- else
140
- stub_http_request(:get, "google.com").with(headers: { InvalidHeaderSinceItsASymbol: "this will not be valid" })
141
- expect do
142
- http.request(request)
143
- end.not_to raise_error
144
- end
124
+ stub_http_request(:get, "google.com").with(headers: { InvalidHeaderSinceItsASymbol: "this will not be valid" })
125
+ expect do
126
+ http.request(request)
127
+ end.not_to raise_error
145
128
  end
146
129
 
147
130
  it "should handle multiple values for the same response header" do
@@ -213,6 +196,22 @@ describe "Net:HTTP" do
213
196
  end
214
197
  end
215
198
 
199
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.7.0')
200
+ it "uses the StubSocket to provide IP address" do
201
+ Net::HTTP.start("http://example.com") do |http|
202
+ expect(http.ipaddr).to eq("127.0.0.1")
203
+ end
204
+ end
205
+ end
206
+
207
+ it "defines common socket methods" do
208
+ Net::HTTP.start("http://example.com") do |http|
209
+ socket = http.instance_variable_get(:@socket)
210
+ expect(socket.io.ssl_version).to eq("TLSv1.3")
211
+ expect(socket.io.cipher).to eq(["TLS_AES_128_GCM_SHA256", "TLSv1.3", 128, 128])
212
+ end
213
+ end
214
+
216
215
  describe "connecting on Net::HTTP.start" do
217
216
  before(:each) do
218
217
  @http = Net::HTTP.new('www.google.com', 443)
@@ -254,6 +253,21 @@ describe "Net:HTTP" do
254
253
  }
255
254
  end
256
255
 
256
+ it "should connect to the server on start when allowlisted", net_connect: true do
257
+ WebMock.disable_net_connect!(allow: "www.google.com", net_http_connect_on_start: "www.google.com")
258
+ @http.start {|conn|
259
+ cert = OpenSSL::X509::Certificate.new conn.peer_cert
260
+ expect(cert).to be_a(OpenSSL::X509::Certificate)
261
+ }
262
+ end
263
+
264
+ it "should not connect to the server on start when not allowlisted", net_connect: true do
265
+ WebMock.disable_net_connect!(allow: "www.google.com", net_http_connect_on_start: "www.yahoo.com")
266
+ @http.start {|conn|
267
+ expect(conn.peer_cert).to be_nil
268
+ }
269
+ end
270
+
257
271
  it "should connect to the server if the URI matches an regex", net_connect: true do
258
272
  WebMock.disable_net_connect!(allow: /google.com/)
259
273
  Net::HTTP.get('www.google.com','/')
@@ -282,6 +296,13 @@ describe "Net:HTTP" do
282
296
  it_should_behave_like "Net::HTTP"
283
297
  end
284
298
 
299
+ describe "when net_http_connect_on_start is a specific host" do
300
+ before(:each) do
301
+ WebMock.allow_net_connect!(net_http_connect_on_start: "localhost")
302
+ end
303
+ it_should_behave_like "Net::HTTP"
304
+ end
305
+
285
306
  describe 'after_request callback support', net_connect: true do
286
307
  let(:expected_body_regex) { /hello world/ }
287
308
 
@@ -340,4 +361,35 @@ describe "Net:HTTP" do
340
361
  http.request(req, '')
341
362
  end
342
363
  end
364
+
365
+ describe "hostname handling" do
366
+ it "should set brackets around the hostname if it is an IPv6 address" do
367
+ net_http = Net::HTTP.new('b2dc:5bdf:4f0d::3014:e0ca', 80)
368
+ path = '/example.jpg'
369
+ expect(WebMock::NetHTTPUtility.get_uri(net_http, path)).to eq('http://[b2dc:5bdf:4f0d::3014:e0ca]:80/example.jpg')
370
+ end
371
+
372
+ it "should not set brackets around the hostname if it is already wrapped by brackets" do
373
+ net_http = Net::HTTP.new('[b2dc:5bdf:4f0d::3014:e0ca]', 80)
374
+ path = '/example.jpg'
375
+ expect(WebMock::NetHTTPUtility.get_uri(net_http, path)).to eq('http://[b2dc:5bdf:4f0d::3014:e0ca]:80/example.jpg')
376
+ end
377
+
378
+ it "should not set brackets around the hostname if it is an IPv4 address" do
379
+ net_http = Net::HTTP.new('181.152.137.168', 80)
380
+ path = '/example.jpg'
381
+ expect(WebMock::NetHTTPUtility.get_uri(net_http, path)).to eq('http://181.152.137.168:80/example.jpg')
382
+ end
383
+
384
+ it "should not set brackets around the hostname if it is a domain" do
385
+ net_http = Net::HTTP.new('www.example.com', 80)
386
+ path = '/example.jpg'
387
+ expect(WebMock::NetHTTPUtility.get_uri(net_http, path)).to eq('http://www.example.com:80/example.jpg')
388
+ end
389
+
390
+ it "does not require a path" do
391
+ net_http = Net::HTTP.new('www.example.com', 80)
392
+ expect(WebMock::NetHTTPUtility.get_uri(net_http)).to eq('http://www.example.com:80')
393
+ end
394
+ end
343
395
  end
@@ -17,4 +17,4 @@ describe "Real Net:HTTP without webmock", without_webmock: true do
17
17
  end
18
18
 
19
19
  it_should_behave_like "Net::HTTP"
20
- end
20
+ end
@@ -93,31 +93,29 @@ unless RUBY_PLATFORM =~ /java/
93
93
  @sess.copy("/abc", "/def")
94
94
  end
95
95
 
96
- if /^1\.9/ === RUBY_VERSION
97
- describe "handling encoding same way as patron" do
98
- around(:each) do |example|
99
- @encoding = Encoding.default_internal
100
- Encoding.default_internal = "UTF-8"
101
- example.run
102
- Encoding.default_internal = @encoding
103
- end
96
+ describe "handling encoding same way as patron" do
97
+ around(:each) do |example|
98
+ @encoding = Encoding.default_internal
99
+ Encoding.default_internal = "UTF-8"
100
+ example.run
101
+ Encoding.default_internal = @encoding
102
+ end
104
103
 
105
- it "should not encode body with default encoding" do
106
- stub_request(:get, "www.example.com").
107
- to_return(body: "Øl")
104
+ it "should not encode body with default encoding" do
105
+ stub_request(:get, "www.example.com").
106
+ to_return(body: "Øl")
108
107
 
109
- expect(@sess.get("").body.encoding).to eq(Encoding::ASCII_8BIT)
110
- expect(@sess.get("").inspectable_body.encoding).to eq(Encoding::UTF_8)
111
- end
108
+ expect(@sess.get("").body.encoding).to eq(Encoding::ASCII_8BIT)
109
+ expect(@sess.get("").inspectable_body.encoding).to eq(Encoding::UTF_8)
110
+ end
112
111
 
113
- it "should not encode body to default internal" do
114
- stub_request(:get, "www.example.com").
115
- to_return(headers: {'Content-Type' => 'text/html; charset=iso-8859-1'},
116
- body: "Øl".encode("iso-8859-1"))
112
+ it "should not encode body to default internal" do
113
+ stub_request(:get, "www.example.com").
114
+ to_return(headers: {'Content-Type' => 'text/html; charset=iso-8859-1'},
115
+ body: "Øl".encode("iso-8859-1"))
117
116
 
118
- expect(@sess.get("").body.encoding).to eq(Encoding::ASCII_8BIT)
119
- expect(@sess.get("").decoded_body.encoding).to eq(Encoding.default_internal)
120
- end
117
+ expect(@sess.get("").body.encoding).to eq(Encoding::ASCII_8BIT)
118
+ expect(@sess.get("").decoded_body.encoding).to eq(Encoding.default_internal)
121
119
  end
122
120
  end
123
121
  end
@@ -28,8 +28,8 @@ module PatronSpecHelper
28
28
  end
29
29
  end
30
30
 
31
- status_line_pattern = %r(\AHTTP/(\d+\.\d+)\s+(\d\d\d)\s*([^\r\n]+)?)
32
- message = response.status_line.match(status_line_pattern)[3] || ""
31
+ status_line_pattern = %r(\AHTTP/(\d+(\.\d+)?)\s+(\d\d\d)\s*([^\r\n]+)?)
32
+ message = response.status_line.match(status_line_pattern)[4] || ""
33
33
 
34
34
  OpenStruct.new({
35
35
  body: response.body,
@@ -91,7 +91,7 @@ shared_context "allowing and disabling net connect" do |*adapter_info|
91
91
  describe "is not allowed, with exceptions" do
92
92
  describe "allowing by host string" do
93
93
  before :each do
94
- WebMock.disable_net_connect!(allow: 'httpstat.us')
94
+ WebMock.disable_net_connect!(allow: 'https://httpstat.us')
95
95
  end
96
96
 
97
97
  context "when the host is not allowed" do
@@ -109,13 +109,13 @@ shared_context "allowing and disabling net connect" do |*adapter_info|
109
109
 
110
110
  context "when the host is allowed" do
111
111
  it "should return stubbed response if request was stubbed" do
112
- stub_request(:get, 'httpstat.us/200').to_return(body: "abc")
113
- expect(http_request(:get, "http://httpstat.us/200").body).to eq("abc")
112
+ stub_request(:get, 'https://httpstat.us/200').to_return(body: "abc")
113
+ expect(http_request(:get, "https://httpstat.us/200").body).to eq("abc")
114
114
  end
115
115
 
116
116
  # WARNING: this makes a real HTTP request!
117
117
  it "should make a real request to allowed host", net_connect: true do
118
- expect(http_request(:get, "http://httpstat.us/200").status).to eq('200')
118
+ expect(http_request(:get, "https://httpstat.us/200").status).to eq('200')
119
119
  end
120
120
  end
121
121
  end
@@ -229,13 +229,13 @@ shared_context "allowing and disabling net connect" do |*adapter_info|
229
229
 
230
230
  context "when the host is allowed" do
231
231
  it "should return stubbed response if request was stubbed" do
232
- stub_request(:get, 'httpstat.us/200').to_return(body: "abc")
233
- expect(http_request(:get, "http://httpstat.us/200").body).to eq("abc")
232
+ stub_request(:get, 'https://httpstat.us/200').to_return(body: "abc")
233
+ expect(http_request(:get, "https://httpstat.us/200").body).to eq("abc")
234
234
  end
235
235
 
236
236
  # WARNING: this makes a real HTTP request!
237
237
  it "should make a real request to allowed host", net_connect: true do
238
- expect(http_request(:get, "http://httpstat.us/200").status).to eq('200')
238
+ expect(http_request(:get, "https://httpstat.us/200").status).to eq('200')
239
239
  end
240
240
 
241
241
  it "should make a real request if request is allowed by path regexp and url contains default port", net_connect: true do
@@ -266,20 +266,20 @@ shared_context "allowing and disabling net connect" do |*adapter_info|
266
266
 
267
267
  context "when the host is allowed" do
268
268
  it "should return stubbed response if request was stubbed" do
269
- stub_request(:get, 'httpstat.us/200').to_return(body: "abc")
270
- expect(http_request(:get, "http://httpstat.us/200").body).to eq("abc")
269
+ stub_request(:get, 'https://httpstat.us/200').to_return(body: "abc")
270
+ expect(http_request(:get, "https://httpstat.us/200").body).to eq("abc")
271
271
  end
272
272
 
273
273
  # WARNING: this makes a real HTTP request!
274
274
  it "should make a real request to allowed host", net_connect: true do
275
- expect(http_request(:get, "http://httpstat.us/200").status).to eq('200')
275
+ expect(http_request(:get, "https://httpstat.us/200").status).to eq('200')
276
276
  end
277
277
  end
278
278
  end
279
279
 
280
280
  describe "allowing by a list of the above" do
281
281
  before :each do
282
- WebMock.disable_net_connect!(allow: [lambda{|_| false }, %r{foobar}, 'httpstat.us'])
282
+ WebMock.disable_net_connect!(allow: [lambda{|_| false }, %r{foobar}, 'https://httpstat.us'])
283
283
  end
284
284
 
285
285
  context "when the host is not allowed" do
@@ -297,13 +297,13 @@ shared_context "allowing and disabling net connect" do |*adapter_info|
297
297
 
298
298
  context "when the host is allowed" do
299
299
  it "should return stubbed response if request was stubbed" do
300
- stub_request(:get, 'httpstat.us/200').to_return(body: "abc")
301
- expect(http_request(:get, "http://httpstat.us/200").body).to eq("abc")
300
+ stub_request(:get, 'https://httpstat.us/200').to_return(body: "abc")
301
+ expect(http_request(:get, "https://httpstat.us/200").body).to eq("abc")
302
302
  end
303
303
 
304
304
  # WARNING: this makes a real HTTP request!
305
305
  it "should make a real request to allowed host", net_connect: true do
306
- expect(http_request(:get, "http://httpstat.us/200").status).to eq('200')
306
+ expect(http_request(:get, "https://httpstat.us/200").status).to eq('200')
307
307
  end
308
308
  end
309
309
  end
@@ -102,7 +102,7 @@ shared_context "callbacks" do |*adapter_info|
102
102
  WebMock.after_request(except: [:other_lib]) do |_, response|
103
103
  @response = response
104
104
  end
105
- http_request(:get, "http://httpstat.us/201", headers: { "Accept" => "*" })
105
+ http_request(:get, "https://httpstat.us/201", headers: { "Accept" => "*" })
106
106
  end
107
107
 
108
108
  it "should pass real response to callback with status and message" do
@@ -111,7 +111,7 @@ shared_context "callbacks" do |*adapter_info|
111
111
  end
112
112
 
113
113
  it "should pass real response to callback with headers" do
114
- expect(@response.headers["X-Powered-By"]).to eq( "ASP.NET")
114
+ expect(@response.headers["Server"]).to eq( "Kestrel")
115
115
  expect(@response.headers["Content-Length"]).to eq("11") unless adapter_info.include?(:no_content_length_header)
116
116
  end
117
117
 
@@ -18,7 +18,7 @@ shared_context "complex cross-concern behaviors" do |*adapter_info|
18
18
  expect(played_back_response).to eq(real_response)
19
19
  end
20
20
 
21
- let(:no_content_url) { 'http://httpstat.us/204' }
21
+ let(:no_content_url) { 'https://httpstat.us/204' }
22
22
  [nil, ''].each do |stub_val|
23
23
  it "returns the same value (nil or "") for a request stubbed as #{stub_val.inspect} that a real empty response has", net_connect: true do
24
24
  unless http_library == :curb
@@ -534,69 +534,105 @@ describe WebMock::RequestPattern do
534
534
  end
535
535
 
536
536
  describe "for request with json body and content type is set to json" do
537
- it "should match when hash matches body" do
538
- expect(WebMock::RequestPattern.new(:post, 'www.example.com', body: body_hash)).
539
- to match(WebMock::RequestSignature.new(:post, "www.example.com", headers: {content_type: 'application/json'},
540
- body: "{\"a\":\"1\",\"c\":{\"d\":[\"e\",\"f\"]},\"b\":\"five\"}"))
541
- end
537
+ shared_examples "a json body" do
538
+ it "should match when hash matches body" do
539
+ expect(WebMock::RequestPattern.new(:post, 'www.example.com', body: body_hash)).
540
+ to match(WebMock::RequestSignature.new(:post, "www.example.com", headers: {content_type: content_type},
541
+ body: "{\"a\":\"1\",\"c\":{\"d\":[\"e\",\"f\"]},\"b\":\"five\"}"))
542
+ end
542
543
 
543
- it "should match if hash matches body in different form" do
544
- expect(WebMock::RequestPattern.new(:post, 'www.example.com', body: body_hash)).
545
- to match(WebMock::RequestSignature.new(:post, "www.example.com", headers: {content_type: 'application/json'},
546
- body: "{\"a\":\"1\",\"b\":\"five\",\"c\":{\"d\":[\"e\",\"f\"]}}"))
547
- end
544
+ it "should match if hash matches body in different form" do
545
+ expect(WebMock::RequestPattern.new(:post, 'www.example.com', body: body_hash)).
546
+ to match(WebMock::RequestSignature.new(:post, "www.example.com", headers: {content_type: content_type},
547
+ body: "{\"a\":\"1\",\"b\":\"five\",\"c\":{\"d\":[\"e\",\"f\"]}}"))
548
+ end
549
+
550
+ it "should match if the request body has a top level array" do
551
+ expect(WebMock::RequestPattern.new(:post, 'www.example.com', body: [{a: 1}])).
552
+ to match(WebMock::RequestSignature.new(:post, "www.example.com",
553
+ headers: {content_type: content_type}, body: "[{\"a\":1}]"))
554
+ end
548
555
 
549
- it "should not match when body is not json" do
550
- expect(WebMock::RequestPattern.new(:post, 'www.example.com', body: body_hash)).
551
- not_to match(WebMock::RequestSignature.new(:post, "www.example.com",
552
- headers: {content_type: 'application/json'}, body: "foo bar"))
553
- end
556
+ it "should not match if the request body has a different top level array" do
557
+ expect(WebMock::RequestPattern.new(:post, 'www.example.com', body: ["a", "b"])).
558
+ not_to match(WebMock::RequestSignature.new(:post, "www.example.com",
559
+ headers: {content_type: content_type}, body: "[\"a\", \"c\"]"))
560
+ end
561
+
562
+ it "should not match when body is not json" do
563
+ expect(WebMock::RequestPattern.new(:post, 'www.example.com', body: body_hash)).
564
+ not_to match(WebMock::RequestSignature.new(:post, "www.example.com",
565
+ headers: {content_type: content_type}, body: "foo bar"))
566
+ end
567
+
568
+ it "should not match if request body is different" do
569
+ expect(WebMock::RequestPattern.new(:post, 'www.example.com', body: {a: 1, b: 2})).
570
+ not_to match(WebMock::RequestSignature.new(:post, "www.example.com",
571
+ headers: {content_type: content_type}, body: "{\"a\":1,\"c\":null}"))
572
+ end
573
+
574
+ it "should not match if request body is has less params than pattern" do
575
+ expect(WebMock::RequestPattern.new(:post, 'www.example.com', body: {a: 1, b: 2})).
576
+ not_to match(WebMock::RequestSignature.new(:post, "www.example.com",
577
+ headers: {content_type: content_type}, body: "{\"a\":1}"))
578
+ end
554
579
 
555
- it "should not match if request body is different" do
556
- expect(WebMock::RequestPattern.new(:post, 'www.example.com', body: {a: 1, b: 2})).
557
- not_to match(WebMock::RequestSignature.new(:post, "www.example.com",
558
- headers: {content_type: 'application/json'}, body: "{\"a\":1,\"c\":null}"))
580
+ it "should not match if request body is has more params than pattern" do
581
+ expect(WebMock::RequestPattern.new(:post, 'www.example.com', body: {a: 1})).
582
+ not_to match(WebMock::RequestSignature.new(:post, "www.example.com",
583
+ headers: {content_type: content_type}, body: "{\"a\":1,\"c\":1}"))
584
+ end
559
585
  end
560
586
 
561
- it "should not match if request body is has less params than pattern" do
562
- expect(WebMock::RequestPattern.new(:post, 'www.example.com', body: {a: 1, b: 2})).
563
- not_to match(WebMock::RequestSignature.new(:post, "www.example.com",
564
- headers: {content_type: 'application/json'}, body: "{\"a\":1}"))
587
+ context "standard application/json" do
588
+ let(:content_type) { 'application/json' }
589
+ it_behaves_like "a json body"
565
590
  end
566
591
 
567
- it "should not match if request body is has more params than pattern" do
568
- expect(WebMock::RequestPattern.new(:post, 'www.example.com', body: {a: 1})).
569
- not_to match(WebMock::RequestSignature.new(:post, "www.example.com",
570
- headers: {content_type: 'application/json'}, body: "{\"a\":1,\"c\":1}"))
592
+ context "custom json content type" do
593
+ let(:content_type) { 'application/vnd.api+json' }
594
+ it_behaves_like "a json body"
571
595
  end
572
596
  end
573
597
 
574
598
  describe "for request with xml body and content type is set to xml" do
575
599
  let(:body_hash) { {"opt" => {:a => '1', :b => 'five', 'c' => {'d' => ['e', 'f']}}} }
576
600
 
577
- it "should match when hash matches body" do
578
- expect(WebMock::RequestPattern.new(:post, 'www.example.com', body: body_hash)).
579
- to match(WebMock::RequestSignature.new(:post, "www.example.com", headers: {content_type: 'application/xml'},
580
- body: "<opt a=\"1\" b=\"five\">\n <c>\n <d>e</d>\n <d>f</d>\n </c>\n</opt>\n"))
581
- end
601
+ shared_examples "a xml body" do
602
+ it "should match when hash matches body" do
603
+ expect(WebMock::RequestPattern.new(:post, 'www.example.com', body: body_hash)).
604
+ to match(WebMock::RequestSignature.new(:post, "www.example.com", headers: {content_type: content_type},
605
+ body: "<opt a=\"1\" b=\"five\">\n <c>\n <d>e</d>\n <d>f</d>\n </c>\n</opt>\n"))
606
+ end
582
607
 
583
- it "should match if hash matches body in different form" do
584
- expect(WebMock::RequestPattern.new(:post, 'www.example.com', body: body_hash)).
585
- to match(WebMock::RequestSignature.new(:post, "www.example.com", headers: {content_type: 'application/xml'},
586
- body: "<opt b=\"five\" a=\"1\">\n <c>\n <d>e</d>\n <d>f</d>\n </c>\n</opt>\n"))
587
- end
608
+ it "should match if hash matches body in different form" do
609
+ expect(WebMock::RequestPattern.new(:post, 'www.example.com', body: body_hash)).
610
+ to match(WebMock::RequestSignature.new(:post, "www.example.com", headers: {content_type: content_type},
611
+ body: "<opt b=\"five\" a=\"1\">\n <c>\n <d>e</d>\n <d>f</d>\n </c>\n</opt>\n"))
612
+ end
588
613
 
589
- it "should not match when body is not xml" do
590
- expect(WebMock::RequestPattern.new(:post, 'www.example.com', body: body_hash)).
591
- not_to match(WebMock::RequestSignature.new(:post, "www.example.com",
592
- headers: {content_type: 'application/xml'}, body: "foo bar"))
593
- end
614
+ it "should not match when body is not xml" do
615
+ expect(WebMock::RequestPattern.new(:post, 'www.example.com', body: body_hash)).
616
+ not_to match(WebMock::RequestSignature.new(:post, "www.example.com",
617
+ headers: {content_type: content_type}, body: "foo bar"))
618
+ end
594
619
 
595
- it "matches when the content type include a charset" do
596
- expect(WebMock::RequestPattern.new(:post, 'www.example.com', body: body_hash)).
597
- to match(WebMock::RequestSignature.new(:post, "www.example.com", headers: {content_type: 'application/xml;charset=UTF-8'},
598
- body: "<opt a=\"1\" b=\"five\">\n <c>\n <d>e</d>\n <d>f</d>\n </c>\n</opt>\n"))
620
+ it "matches when the content type include a charset" do
621
+ expect(WebMock::RequestPattern.new(:post, 'www.example.com', body: body_hash)).
622
+ to match(WebMock::RequestSignature.new(:post, "www.example.com", headers: {content_type: "#{content_type};charset=UTF-8"},
623
+ body: "<opt a=\"1\" b=\"five\">\n <c>\n <d>e</d>\n <d>f</d>\n </c>\n</opt>\n"))
624
+
625
+ end
626
+ end
627
+
628
+ context "standard application/xml" do
629
+ let(:content_type) { 'application/xml' }
630
+ it_behaves_like "a xml body"
631
+ end
599
632
 
633
+ context "custom xml content type" do
634
+ let(:content_type) { 'application/atom+xml' }
635
+ it_behaves_like "a xml body"
600
636
  end
601
637
  end
602
638
  end
@@ -18,7 +18,7 @@ describe WebMock::RequestSignature do
18
18
  end
19
19
 
20
20
  it "assigns normalized headers" do
21
- expect(WebMock::Util::Headers).to receive(:normalize_headers).with('A' => 'a').and_return('B' => 'b')
21
+ allow(WebMock::Util::Headers).to receive(:normalize_headers).with({'A' => 'a'}.freeze).and_return('B' => 'b')
22
22
  expect(
23
23
  WebMock::RequestSignature.new(:get, "www.example.com", headers: {'A' => 'a'}).headers
24
24
  ).to eq({'B' => 'b'})
@@ -125,11 +125,21 @@ describe WebMock::RequestSignature do
125
125
  expect(subject.url_encoded?).to be true
126
126
  end
127
127
 
128
+ it "returns true if the headers are urlencoded with a specified charset" do
129
+ subject.headers = { "Content-Type" => "application/x-www-form-urlencoded; charset=UTF-8" }
130
+ expect(subject.url_encoded?).to be true
131
+ end
132
+
128
133
  it "returns false if the headers are NOT urlencoded" do
129
134
  subject.headers = { "Content-Type" => "application/made-up-format" }
130
135
  expect(subject.url_encoded?).to be false
131
136
  end
132
137
 
138
+ it "returns false when no content type header is present" do
139
+ subject.headers = { "Some-Header" => "some-value" }
140
+ expect(subject.url_encoded?).to be false
141
+ end
142
+
133
143
  it "returns false when no headers are set" do
134
144
  subject.headers = nil
135
145
  expect(subject.url_encoded?).to be false
@@ -142,11 +152,21 @@ describe WebMock::RequestSignature do
142
152
  expect(subject.json_headers?).to be true
143
153
  end
144
154
 
155
+ it "returns true if the headers are json with a specified charset" do
156
+ subject.headers = { "Content-Type" => "application/json; charset=UTF-8" }
157
+ expect(subject.json_headers?).to be true
158
+ end
159
+
145
160
  it "returns false if the headers are NOT json" do
146
161
  subject.headers = { "Content-Type" => "application/made-up-format" }
147
162
  expect(subject.json_headers?).to be false
148
163
  end
149
164
 
165
+ it "returns false when no content type header is present" do
166
+ subject.headers = { "Some-Header" => "some-value" }
167
+ expect(subject.json_headers?).to be false
168
+ end
169
+
150
170
  it "returns false when no headers are set" do
151
171
  subject.headers = nil
152
172
  expect(subject.json_headers?).to be false
@@ -50,6 +50,41 @@ describe WebMock::RequestStub do
50
50
 
51
51
  end
52
52
 
53
+ describe "to_return_json" do
54
+
55
+ it "should raise if a block is given" do
56
+ expect {
57
+ @request_stub.to_return_json(body: "abc", status: 500) { puts "don't call me" }
58
+ }.to raise_error(ArgumentError, '#to_return_json does not support passing a block')
59
+ end
60
+
61
+ it "should assign responses normally" do
62
+ @request_stub.to_return_json([{body: "abc"}, {body: "def"}])
63
+ expect([@request_stub.response.body, @request_stub.response.body]).to eq(["abc", "def"])
64
+ end
65
+
66
+ it "should json-ify a Hash body" do
67
+ @request_stub.to_return_json(body: {abc: "def"}, status: 500)
68
+ expect(@request_stub.response.body).to eq({abc: "def"}.to_json)
69
+ expect(@request_stub.response.status).to eq([500, ""])
70
+ end
71
+
72
+ it "should apply the content_type header" do
73
+ @request_stub.to_return_json(body: {abc: "def"}, status: 500)
74
+ expect(@request_stub.response.headers).to eq({"Content-Type"=>"application/json"})
75
+ end
76
+
77
+ it "should preserve existing headers" do
78
+ @request_stub.to_return_json(headers: {"A" => "a"}, body: "")
79
+ expect(@request_stub.response.headers).to eq({"A"=>"a", "Content-Type"=>"application/json"})
80
+ end
81
+
82
+ it "should allow callsites to override content_type header" do
83
+ @request_stub.to_return_json(headers: {content_type: 'application/super-special-json'})
84
+ expect(@request_stub.response.headers).to eq({"Content-Type"=>"application/super-special-json"})
85
+ end
86
+ end
87
+
53
88
  describe "then" do
54
89
  it "should return stub without any modifications, acting as syntactic sugar" do
55
90
  expect(@request_stub.then).to eq(@request_stub)