webmock 1.21.0 → 1.22.1

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 (44) hide show
  1. data/.travis.yml +8 -0
  2. data/CHANGELOG.md +51 -1
  3. data/Gemfile +0 -1
  4. data/README.md +17 -6
  5. data/lib/webmock.rb +4 -1
  6. data/lib/webmock/config.rb +2 -0
  7. data/lib/webmock/errors.rb +3 -21
  8. data/lib/webmock/http_lib_adapters/em_http_request/em_http_request_1_x.rb +5 -0
  9. data/lib/webmock/http_lib_adapters/manticore_adapter.rb +123 -0
  10. data/lib/webmock/http_lib_adapters/net_http.rb +16 -2
  11. data/lib/webmock/request_body_diff.rb +63 -0
  12. data/lib/webmock/request_execution_verifier.rb +24 -21
  13. data/lib/webmock/request_pattern.rb +2 -0
  14. data/lib/webmock/request_signature.rb +5 -1
  15. data/lib/webmock/request_signature_snippet.rb +61 -0
  16. data/lib/webmock/rspec/matchers.rb +0 -1
  17. data/lib/webmock/rspec/matchers/request_pattern_matcher.rb +4 -0
  18. data/lib/webmock/rspec/matchers/webmock_matcher.rb +4 -0
  19. data/lib/webmock/stub_request_snippet.rb +4 -0
  20. data/lib/webmock/util/json.rb +25 -6
  21. data/lib/webmock/util/query_mapper.rb +6 -4
  22. data/lib/webmock/version.rb +1 -1
  23. data/lib/webmock/webmock.rb +12 -0
  24. data/spec/acceptance/em_http_request/em_http_request_spec.rb +60 -0
  25. data/spec/acceptance/http_rb/http_rb_spec.rb +2 -2
  26. data/spec/acceptance/http_rb/http_rb_spec_helper.rb +1 -1
  27. data/spec/acceptance/httpclient/httpclient_spec.rb +8 -7
  28. data/spec/acceptance/manticore/manticore_spec.rb +56 -0
  29. data/spec/acceptance/manticore/manticore_spec_helper.rb +31 -0
  30. data/spec/acceptance/net_http/net_http_spec.rb +24 -1
  31. data/spec/acceptance/shared/request_expectations.rb +10 -10
  32. data/spec/spec_helper.rb +4 -0
  33. data/spec/unit/errors_spec.rb +45 -4
  34. data/spec/unit/request_body_diff_spec.rb +90 -0
  35. data/spec/unit/request_execution_verifier_spec.rb +48 -11
  36. data/spec/unit/request_signature_snippet_spec.rb +89 -0
  37. data/spec/unit/request_signature_spec.rb +61 -23
  38. data/spec/unit/stub_registry_spec.rb +1 -1
  39. data/spec/unit/util/json_spec.rb +29 -3
  40. data/spec/unit/util/query_mapper_spec.rb +15 -4
  41. data/spec/unit/webmock_spec.rb +4 -0
  42. data/test/shared_test.rb +2 -2
  43. data/webmock.gemspec +4 -1
  44. metadata +63 -24
@@ -33,32 +33,16 @@ module WebMock
33
33
  end
34
34
  end
35
35
 
36
+ def description
37
+ "request #{request_pattern.to_s} #{quantity_phrase.strip}"
38
+ end
36
39
 
37
40
  def failure_message
38
- expected_times_executed = @expected_times_executed || 1
39
- text = if @at_least_times_executed
40
- %Q(The request #{request_pattern.to_s} was expected to execute at least #{times(@at_least_times_executed)} but it executed #{times(times_executed)})
41
- elsif @at_most_times_executed
42
- %Q(The request #{request_pattern.to_s} was expected to execute at most #{times(@at_most_times_executed)} but it executed #{times(times_executed)})
43
- else
44
- %Q(The request #{request_pattern.to_s} was expected to execute #{times(expected_times_executed)} but it executed #{times(times_executed)})
45
- end
46
- text << self.class.executed_requests_message
47
- text
41
+ failure_message_phrase(is_negated=false)
48
42
  end
49
43
 
50
44
  def failure_message_when_negated
51
- text = if @at_least_times_executed
52
- %Q(The request #{request_pattern.to_s} was not expected to execute at least #{times(@at_least_times_executed)} but it executed #{times(times_executed)})
53
- elsif @at_most_times_executed
54
- %Q(The request #{request_pattern.to_s} was not expected to execute at most #{times(@at_most_times_executed)} but it executed #{times(times_executed)})
55
- elsif @expected_times_executed
56
- %Q(The request #{request_pattern.to_s} was not expected to execute #{times(expected_times_executed)} but it executed #{times(times_executed)})
57
- else
58
- %Q(The request #{request_pattern.to_s} was expected to execute 0 times but it executed #{times(times_executed)})
59
- end
60
- text << self.class.executed_requests_message
61
- text
45
+ failure_message_phrase(is_negated=true)
62
46
  end
63
47
 
64
48
  def self.executed_requests_message
@@ -67,6 +51,25 @@ module WebMock
67
51
 
68
52
  private
69
53
 
54
+ def failure_message_phrase(is_negated=false)
55
+ negation = is_negated ? "was not" : "was"
56
+ text = "The request #{request_pattern.to_s} #{negation} expected to execute #{quantity_phrase(is_negated)}but it executed #{times(times_executed)}"
57
+ text << self.class.executed_requests_message
58
+ text
59
+ end
60
+
61
+ def quantity_phrase(is_negated=false)
62
+ if @at_least_times_executed
63
+ "at least #{times(@at_least_times_executed)} "
64
+ elsif @at_most_times_executed
65
+ "at most #{times(@at_most_times_executed)} "
66
+ elsif @expected_times_executed
67
+ "#{times(@expected_times_executed)} "
68
+ else
69
+ is_negated ? "" : "#{times(1)} "
70
+ end
71
+ end
72
+
70
73
  def times(times)
71
74
  "#{times} time#{ (times == 1) ? '' : 's'}"
72
75
  end
@@ -200,6 +200,8 @@ module WebMock
200
200
  'text/plain' => :plain
201
201
  }
202
202
 
203
+ attr_reader :pattern
204
+
203
205
  def initialize(pattern)
204
206
  @pattern = if pattern.is_a?(Hash)
205
207
  normalize_hash(pattern)
@@ -35,7 +35,11 @@ module WebMock
35
35
  alias == eql?
36
36
 
37
37
  def url_encoded?
38
- headers && headers['Content-Type'] == 'application/x-www-form-urlencoded'
38
+ !!(headers && headers['Content-Type'] == 'application/x-www-form-urlencoded')
39
+ end
40
+
41
+ def json_headers?
42
+ !!(headers && headers['Content-Type'] == 'application/json')
39
43
  end
40
44
 
41
45
  private
@@ -0,0 +1,61 @@
1
+ require "pp"
2
+
3
+ module WebMock
4
+ class RequestSignatureSnippet
5
+
6
+ attr_reader :request_signature, :request_stub
7
+
8
+ def initialize(request_signature)
9
+ @request_signature = request_signature
10
+ @request_stub = RequestStub.from_request_signature(request_signature)
11
+ end
12
+
13
+ def stubbing_instructions
14
+ return unless WebMock.show_stubbing_instructions?
15
+
16
+ text = "You can stub this request with the following snippet:\n\n"
17
+ text << WebMock::StubRequestSnippet.new(request_stub).to_s
18
+ end
19
+
20
+ def request_stubs
21
+ return if WebMock::StubRegistry.instance.request_stubs.empty?
22
+
23
+ text = "registered request stubs:\n"
24
+ WebMock::StubRegistry.instance.request_stubs.each do |stub|
25
+ text << "\n#{WebMock::StubRequestSnippet.new(stub).to_s(false)}"
26
+ add_body_diff(stub, text) if WebMock.show_body_diff?
27
+ end
28
+ text
29
+ end
30
+
31
+ private
32
+
33
+ def add_body_diff(stub, text)
34
+ body_diff_str = signature_stub_body_diff(stub)
35
+ text << "\n\n#{body_diff_str}" unless body_diff_str.empty?
36
+ end
37
+
38
+ def signature_stub_body_diff(stub)
39
+ diff = RequestBodyDiff.new(request_signature, stub).body_diff
40
+ diff.empty? ? "" : "Body diff:\n #{pretty_print_to_string(diff)}"
41
+ end
42
+
43
+ def request_params
44
+ @request_params ||=
45
+ if request_signature.json_headers?
46
+ JSON.parse(request_signature.body)
47
+ else
48
+ ""
49
+ end
50
+ end
51
+
52
+ def pretty_print_to_string(string_to_print)
53
+ StringIO.open("") do |stream|
54
+ PP.pp(string_to_print, stream)
55
+ stream.rewind
56
+ stream.read
57
+ end
58
+ end
59
+
60
+ end
61
+ end
@@ -12,7 +12,6 @@ module WebMock
12
12
  WebMock::RequestPatternMatcher.new
13
13
  end
14
14
 
15
-
16
15
  def have_not_been_made
17
16
  WebMock::RequestPatternMatcher.new.times(0)
18
17
  end
@@ -68,6 +68,10 @@ module WebMock
68
68
  @request_execution_verifier.failure_message_when_negated
69
69
  end
70
70
 
71
+ def description
72
+ @request_execution_verifier.description
73
+ end
74
+
71
75
  # RSpec 2 compatibility:
72
76
  alias_method :negative_failure_message, :failure_message_when_negated
73
77
  end
@@ -42,6 +42,10 @@ module WebMock
42
42
  @request_execution_verifier.failure_message_when_negated
43
43
  end
44
44
 
45
+ def description
46
+ @request_execution_verifier.description
47
+ end
48
+
45
49
  # RSpec 2 compatibility:
46
50
  alias_method :negative_failure_message, :failure_message_when_negated
47
51
  end
@@ -4,6 +4,10 @@ module WebMock
4
4
  @request_stub = request_stub
5
5
  end
6
6
 
7
+ def body_pattern
8
+ request_pattern.body_pattern
9
+ end
10
+
7
11
  def to_s(with_response = true)
8
12
  request_pattern = @request_stub.request_pattern
9
13
  string = "stub_request(:#{request_pattern.method_pattern.to_s},"
@@ -8,10 +8,13 @@
8
8
  module WebMock
9
9
  module Util
10
10
  class JSON
11
+ class ParseError < StandardError; end
12
+
11
13
  def self.parse(json)
12
- YAML.load(unescape(convert_json_to_yaml(json)))
13
- rescue ArgumentError
14
- raise ParseError, "Invalid JSON string"
14
+ yaml = unescape(convert_json_to_yaml(json))
15
+ YAML.load(yaml)
16
+ rescue ArgumentError => e
17
+ raise ParseError, "Invalid JSON string: #{yaml}, Error: #{e.inspect}"
15
18
  end
16
19
 
17
20
  protected
@@ -42,11 +45,27 @@ module WebMock
42
45
  json.gsub(/\\\//, '/')
43
46
  else
44
47
  left_pos = [-1].push(*marks)
45
- right_pos = marks << json.length
48
+ right_pos = marks << json.bytesize
46
49
  output = []
47
- left_pos.each_with_index do |left, i|
48
- output << json[left.succ..right_pos[i]]
50
+
51
+ if RUBY_VERSION != "1.9.2"
52
+ left_pos.each_with_index do |left, i|
53
+ if json.respond_to?(:byteslice)
54
+ output << json.byteslice(left.succ..right_pos[i])
55
+ else
56
+ output << json[left.succ..right_pos[i]]
57
+ end
58
+ end
59
+ else
60
+ json_as_binary = json.force_encoding("binary")
61
+ left_pos.each_with_index do |left, i|
62
+ output << json_as_binary[left.succ..right_pos[i]]
63
+ end
64
+ output.map! do |binary_str|
65
+ binary_str.force_encoding("UTF-8")
66
+ end
49
67
  end
68
+
50
69
  output = output * " "
51
70
 
52
71
  times.each { |i| output[i-1] = ' ' }
@@ -196,7 +196,11 @@ module WebMock::Util
196
196
  end
197
197
  # Useful default for OAuth and caching.
198
198
  # Only to be used for non-Array inputs. Arrays should preserve order.
199
- new_query_values.sort!
199
+ begin
200
+ new_query_values.sort! # may raise for non-comparable values
201
+ rescue NoMethodError, ArgumentError
202
+ # ignore
203
+ end
200
204
  end
201
205
 
202
206
  buffer = ''
@@ -241,7 +245,7 @@ module WebMock::Util
241
245
  when ::Hash
242
246
  value = value.map do |key, val|
243
247
  [
244
- ::Addressable::URI.encode_component(key.dup, ::Addressable::URI::CharacterClasses::UNRESERVED),
248
+ ::Addressable::URI.encode_component(key.to_s.dup, ::Addressable::URI::CharacterClasses::UNRESERVED),
245
249
  val
246
250
  ]
247
251
  end
@@ -259,8 +263,6 @@ module WebMock::Util
259
263
  buffer << "#{to_query(new_parent, val, options)}&"
260
264
  end
261
265
  buffer.chop
262
- when TrueClass
263
- parent
264
266
  else
265
267
  encoded_value = Addressable::URI.encode_component(
266
268
  value.to_s.dup, Addressable::URI::CharacterClasses::UNRESERVED
@@ -1,3 +1,3 @@
1
1
  module WebMock
2
- VERSION = '1.21.0' unless defined?(::WebMock::VERSION)
2
+ VERSION = '1.22.1' unless defined?(::WebMock::VERSION)
3
3
  end
@@ -75,6 +75,18 @@ module WebMock
75
75
  end
76
76
  end
77
77
 
78
+ def self.show_body_diff!
79
+ Config.instance.show_body_diff = true
80
+ end
81
+
82
+ def self.hide_body_diff!
83
+ Config.instance.show_body_diff = false
84
+ end
85
+
86
+ def self.show_body_diff?
87
+ Config.instance.show_body_diff
88
+ end
89
+
78
90
  def self.hide_stubbing_instructions!
79
91
  Config.instance.show_stubbing_instructions = false
80
92
  end
@@ -229,6 +229,66 @@ unless RUBY_PLATFORM =~ /java/
229
229
  expect(http_request(:post, "http://www.example.com").body.bytesize).to eq(body.bytesize)
230
230
  end
231
231
 
232
+ it "should work with multiple requests to the same connection" do
233
+ stub_request(:get, "www.example.com/foo").to_return(:body => "bar")
234
+ stub_request(:get, "www.example.com/baz").to_return(:body => "wombat")
235
+ err1 = nil
236
+ err2 = nil
237
+ body1 = nil
238
+ body2 = nil
239
+ i = 0
240
+
241
+ EM.run do
242
+ conn = EM::HttpRequest.new("http://www.example.com")
243
+ conn.get(:path => "/foo").callback do |resp|
244
+ body1 = resp.response
245
+ i += 1; EM.stop if i == 2
246
+ end.errback do |resp|
247
+ err1 = resp.error
248
+ i += 1; EM.stop if i == 2
249
+ end
250
+
251
+ conn.get(:path => "/baz").callback do |resp|
252
+ body2 = resp.response
253
+ i += 1; EM.stop if i == 2
254
+ end.errback do |resp|
255
+ err2 = resp.error
256
+ i += 1; EM.stop if i == 2
257
+ end
258
+ end
259
+
260
+ expect(err1).to be(nil)
261
+ expect(err2).to be(nil)
262
+ expect(body1).to eq("bar")
263
+ expect(body2).to eq("wombat")
264
+ end
265
+
266
+ it "should work with multiple requests to the same connection when the first request times out" do
267
+ stub_request(:get, "www.example.com/foo").to_timeout.then.to_return(:status => 200, :body => "wombat")
268
+ err = nil
269
+ body = nil
270
+
271
+ EM.run do
272
+ conn = EM::HttpRequest.new("http://www.example.com")
273
+ conn.get(:path => "/foo").callback do |resp|
274
+ err = :success_from_timeout
275
+ EM.stop
276
+ end.errback do |resp|
277
+ conn.get(:path => "/foo").callback do |resp|
278
+ expect(resp.response_header.status).to eq(200)
279
+ body = resp.response
280
+ EM.stop
281
+ end.errback do |resp|
282
+ err = resp.error
283
+ EM.stop
284
+ end
285
+ end
286
+ end
287
+
288
+ expect(err).to be(nil)
289
+ expect(body).to eq("wombat")
290
+ end
291
+
232
292
  describe "mocking EM::HttpClient API" do
233
293
  let(:uri) { "http://www.example.com/" }
234
294
 
@@ -49,11 +49,11 @@ describe "HTTP.rb" do
49
49
  end
50
50
 
51
51
  it "restores request uri on replayed response object" do
52
- uri = URI "http://example.com/foo"
52
+ uri = Addressable::URI.parse "http://example.com/foo"
53
53
 
54
54
  stub_request :get, "example.com/foo"
55
55
  response = HTTP.get uri
56
56
 
57
- expect(response.uri).to eq uri
57
+ expect(response.uri.to_s).to eq uri.to_s
58
58
  end
59
59
  end
@@ -7,7 +7,7 @@ module HttpRbSpecHelper
7
7
  OpenStruct.new({
8
8
  :body => response.body.to_s,
9
9
  :headers => normalize_headers(response.headers.to_h),
10
- :status => response.status.to_s,
10
+ :status => response.code.to_s,
11
11
  :message => response.reason
12
12
  })
13
13
  end
@@ -113,13 +113,14 @@ describe "HTTPClient" do
113
113
  nil # to let the request be made for real
114
114
  end
115
115
 
116
- # To make two requests that have the same request signature, the headers must match.
117
- # Since the webmock server has a Set-Cookie header, the 2nd request will automatically
118
- # include a Cookie header (due to how httpclient works), so we have to set the header
119
- # manually on the first request but not on the 2nd request.
120
- http_request(:get, webmock_server_url, :client => client,
121
- :headers => { "Cookie" => "bar=; foo=" })
122
- http_request(:get, webmock_server_url, :client => client)
116
+ http_request(:get, webmock_server_url, :client => client, :headers => { "Cookie" => "bar=; foo=" })
117
+
118
+ if defined? HTTP::CookieJar
119
+ http_request(:get, webmock_server_url, :client => client, :headers => { "Cookie" => "bar=; foo=" })
120
+ else
121
+ # If http-cookie is not present, then the cookie headers will saved between requests
122
+ http_request(:get, webmock_server_url, :client => client)
123
+ end
123
124
 
124
125
  expect(request_signatures.size).to eq(2)
125
126
  # Verify the request signatures were identical as needed by this example
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+ require 'acceptance/webmock_shared'
3
+
4
+ if RUBY_PLATFORM =~ /java/
5
+ require 'acceptance/manticore/manticore_spec_helper'
6
+
7
+ describe "Manticore" do
8
+ include ManticoreSpecHelper
9
+
10
+ include_context "with WebMock", :no_status_message
11
+
12
+ context "calling http methods on Manticore directly using Manticore's facade" do
13
+ it "handles GET" do
14
+ stub_request(:get, "http://example-foo.com").to_return(:status => 301)
15
+ response = Manticore.get("http://example-foo.com")
16
+ expect(response.code).to eq(301)
17
+ end
18
+
19
+ it "handles POST" do
20
+ stub_request(:post, "http://example-foo.com").to_return(:status => 201)
21
+ response = Manticore.post("http://example-foo.com", {:hello => "world"})
22
+ expect(response.code).to eq(201)
23
+ end
24
+
25
+ it "handles PUT" do
26
+ stub_request(:put, "http://example-foo.com").to_return(:status => 409)
27
+ response = Manticore.put("http://example-foo.com", {:hello => "world"})
28
+ expect(response.code).to eq(409)
29
+ end
30
+
31
+ it "handles PATCH" do
32
+ stub_request(:patch, "http://example-foo.com").to_return(:status => 409)
33
+ response = Manticore.patch("http://example-foo.com", {:hello => "world"})
34
+ expect(response.code).to eq(409)
35
+ end
36
+
37
+ it "handles DELETE" do
38
+ stub_request(:delete, "http://example-foo.com").to_return(:status => 204)
39
+ response = Manticore.delete("http://example-foo.com", {:id => 1})
40
+ expect(response.code).to eq(204)
41
+ end
42
+
43
+ it "handles OPTIONS" do
44
+ stub_request(:options, "http://example-foo.com").to_return(:status => 200)
45
+ response = Manticore.options("http://example-foo.com")
46
+ expect(response.code).to eq(200)
47
+ end
48
+
49
+ it "handles HEAD" do
50
+ stub_request(:head, "http://example-foo.com").to_return(:status => 204)
51
+ response = Manticore.head("http://example-foo.com")
52
+ expect(response.code).to eq(204)
53
+ end
54
+ end
55
+ end
56
+ end