webmock 1.2.2 → 1.3.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.
@@ -0,0 +1,49 @@
1
+ # This code is entierly copied from VCR (http://github.com/myronmarston/vcr) by courtesy of Myron Marston
2
+
3
+ # A Net::HTTP response that has already been read raises an IOError when #read_body
4
+ # is called with a destination string or block.
5
+ #
6
+ # This causes a problem when VCR records a response--it reads the body before yielding
7
+ # the response, and if the code that is consuming the HTTP requests uses #read_body, it
8
+ # can cause an error.
9
+ #
10
+ # This is a bit of a hack, but it allows a Net::HTTP response to be "re-read"
11
+ # after it has aleady been read. This attemps to preserve the behavior of
12
+ # #read_body, acting just as if it had never been read.
13
+
14
+ module WebMock
15
+ module Net
16
+ module HTTPResponse
17
+ def self.extended(object)
18
+ body_object = object.instance_variable_get(:@body)
19
+ object.instance_variable_set(:@__orig_body__,
20
+ case body_object
21
+ when String then body_object
22
+ else raise ArgumentError.new("Unexpected body object: #{body_object}")
23
+ end
24
+ )
25
+ end
26
+
27
+ def read_body(dest = nil, &block)
28
+ if @__orig_body__
29
+ if dest && block
30
+ raise ArgumentError.new("both arg and block given for HTTP method")
31
+ elsif dest
32
+ dest << @__orig_body__
33
+ elsif block
34
+ @body = ::Net::ReadAdapter.new(block)
35
+ @body << @__orig_body__
36
+ @body
37
+ else
38
+ @body = @__orig_body__
39
+ end
40
+ else
41
+ super
42
+ end
43
+ ensure
44
+ # allow subsequent calls to #read_body to proceed as normal, without our hack...
45
+ @__orig_body__ = nil
46
+ end
47
+ end
48
+ end
49
+ end
@@ -11,9 +11,19 @@ if defined?(Patron)
11
11
  if WebMock.registered_request?(request_signature)
12
12
  webmock_response = WebMock.response_for_request(request_signature)
13
13
  handle_file_name(req, webmock_response)
14
- build_patron_response(webmock_response)
14
+ res = build_patron_response(webmock_response)
15
+ WebMock::CallbackRegistry.invoke_callbacks(
16
+ {:lib => :patron}, request_signature, webmock_response)
17
+ res
15
18
  elsif WebMock.net_connect_allowed?(request_signature.uri)
16
- handle_request_without_webmock(req)
19
+ res = handle_request_without_webmock(req)
20
+ if WebMock::CallbackRegistry.any_callbacks?
21
+ webmock_response = build_webmock_response(res)
22
+ WebMock::CallbackRegistry.invoke_callbacks(
23
+ {:lib => :patron, :real_request => true}, request_signature,
24
+ webmock_response)
25
+ end
26
+ res
17
27
  else
18
28
  raise WebMock::NetConnectNotAllowedError.new(request_signature)
19
29
  end
@@ -37,7 +47,7 @@ if defined?(Patron)
37
47
  end
38
48
 
39
49
  def build_request_signature(req)
40
- uri = Addressable::URI.heuristic_parse(req.url)
50
+ uri = WebMock::Util::URI.heuristic_parse(req.url)
41
51
  uri.path = uri.normalized_path.gsub("[^:]//","/")
42
52
  uri.user = req.username
43
53
  uri.password = req.password
@@ -74,6 +84,15 @@ if defined?(Patron)
74
84
  res.instance_variable_set(:@headers, webmock_response.headers)
75
85
  res
76
86
  end
87
+
88
+ def build_webmock_response(patron_response)
89
+ webmock_response = WebMock::Response.new
90
+ reason = patron_response.status_line.scan(%r(\AHTTP/(\d+\.\d+)\s+(\d\d\d)\s*([^\r\n]+)?\r?\z))[0][2]
91
+ webmock_response.status = [patron_response.status, reason]
92
+ webmock_response.body = patron_response.body
93
+ webmock_response.headers = patron_response.headers
94
+ webmock_response
95
+ end
77
96
 
78
97
  end
79
98
  end
@@ -4,7 +4,7 @@ module WebMock
4
4
 
5
5
  def initialize(method, uri, options = {})
6
6
  @method_pattern = MethodPattern.new(method)
7
- @uri_pattern = URIPattern.new(uri)
7
+ @uri_pattern = create_uri_pattern(uri)
8
8
  assign_options(options)
9
9
  end
10
10
 
@@ -15,17 +15,18 @@ module WebMock
15
15
  end
16
16
 
17
17
  def matches?(request_signature)
18
+ content_type = request_signature.headers['Content-Type'] if request_signature.headers
18
19
  @method_pattern.matches?(request_signature.method) &&
19
- @uri_pattern.matches?(request_signature.uri) &&
20
- (@body_pattern.nil? || @body_pattern.matches?(request_signature.body)) &&
21
- (@headers_pattern.nil? || @headers_pattern.matches?(request_signature.headers)) &&
22
- (@with_block.nil? || @with_block.call(request_signature))
20
+ @uri_pattern.matches?(request_signature.uri) &&
21
+ (@body_pattern.nil? || @body_pattern.matches?(request_signature.body, content_type || "")) &&
22
+ (@headers_pattern.nil? || @headers_pattern.matches?(request_signature.headers)) &&
23
+ (@with_block.nil? || @with_block.call(request_signature))
23
24
  end
24
25
 
25
26
  def to_s
26
27
  string = "#{@method_pattern.to_s.upcase}"
27
28
  string << " #{@uri_pattern.to_s}"
28
- string << " with body '#{@body_pattern.to_s}'" if @body_pattern
29
+ string << " with body #{@body_pattern.to_s}" if @body_pattern
29
30
  string << " with headers #{@headers_pattern.to_s}" if @headers_pattern
30
31
  string << " with given block" if @with_block
31
32
  string
@@ -37,10 +38,19 @@ module WebMock
37
38
  def assign_options(options)
38
39
  @body_pattern = BodyPattern.new(options[:body]) if options.has_key?(:body)
39
40
  @headers_pattern = HeadersPattern.new(options[:headers]) if options.has_key?(:headers)
41
+ @uri_pattern.add_query_params(options[:query]) if options.has_key?(:query)
42
+ end
43
+
44
+ def create_uri_pattern(uri)
45
+ if uri.is_a?(Regexp)
46
+ URIRegexpPattern.new(uri)
47
+ else
48
+ URIStringPattern.new(uri)
49
+ end
40
50
  end
41
51
 
42
52
  end
43
-
53
+
44
54
 
45
55
  class MethodPattern
46
56
  def initialize(pattern)
@@ -56,40 +66,98 @@ module WebMock
56
66
  end
57
67
  end
58
68
 
69
+
59
70
  class URIPattern
60
71
  def initialize(pattern)
61
72
  @pattern = pattern.is_a?(Addressable::URI) ? pattern : WebMock::Util::URI.normalize_uri(pattern)
62
73
  end
74
+ end
75
+
76
+ class URIRegexpPattern < URIPattern
77
+
78
+ def matches?(uri)
79
+ WebMock::Util::URI.variations_of_uri_as_strings(uri).any? { |u| u.match(@pattern) } &&
80
+ (@query_params.nil? || @query_params == uri.query_values)
81
+ end
82
+
83
+ def to_s
84
+ str = @pattern.inspect
85
+ str += " with query params #{@query_params.inspect}" if @query_params
86
+ str
87
+ end
88
+
89
+ def add_query_params(query_params)
90
+ @query_params = query_params.is_a?(Hash) ? query_params : Addressable::URI.parse('?' + query_params).query_values
91
+ end
92
+
93
+ end
63
94
 
95
+ class URIStringPattern < URIPattern
64
96
  def matches?(uri)
65
97
  if @pattern.is_a?(Addressable::URI)
66
- ##TODO : do I need to normalize again??
67
98
  uri === @pattern
68
- elsif @pattern.is_a?(Regexp)
69
- WebMock::Util::URI.variations_of_uri_as_strings(uri).any? { |u| u.match(@pattern) }
70
99
  else
71
100
  false
72
101
  end
73
102
  end
74
103
 
104
+ def add_query_params(query_params)
105
+ if !query_params.is_a?(Hash)
106
+ query_params = Addressable::URI.parse('?' + query_params).query_values
107
+ end
108
+ @pattern.query_values = (@pattern.query_values || {}).merge(query_params)
109
+ end
110
+
75
111
  def to_s
76
112
  WebMock::Util::URI.strip_default_port_from_uri_string(@pattern.to_s)
77
113
  end
114
+
78
115
  end
79
116
 
117
+
80
118
  class BodyPattern
119
+
120
+ BODY_FORMATS = {
121
+ 'text/xml' => :xml,
122
+ 'application/xml' => :xml,
123
+ 'application/json' => :json,
124
+ 'text/json' => :json,
125
+ 'application/javascript' => :json,
126
+ 'text/javascript' => :json,
127
+ 'text/html' => :html,
128
+ 'application/x-yaml' => :yaml,
129
+ 'text/yaml' => :yaml,
130
+ 'text/plain' => :plain
131
+ }
132
+
81
133
  def initialize(pattern)
82
134
  @pattern = pattern
135
+ if (@pattern).is_a?(Hash)
136
+ @pattern = normalize_hash(@pattern)
137
+ end
83
138
  end
84
139
 
85
- def matches?(uri)
86
- empty_string?(@pattern) && empty_string?(uri) ||
87
- @pattern == uri ||
88
- @pattern === uri
140
+ def matches?(body, content_type = "")
141
+ if (@pattern).is_a?(Hash)
142
+ return true if @pattern.empty?
143
+
144
+ case BODY_FORMATS[content_type]
145
+ when :json then
146
+ Crack::JSON.parse(body) == @pattern
147
+ when :xml then
148
+ Crack::XML.parse(body) == @pattern
149
+ else
150
+ Addressable::URI.parse('?' + body).query_values == @pattern
151
+ end
152
+ else
153
+ empty_string?(@pattern) && empty_string?(body) ||
154
+ @pattern == body ||
155
+ @pattern === body
156
+ end
89
157
  end
90
-
158
+
91
159
  def to_s
92
- @pattern.to_s
160
+ @pattern.inspect
93
161
  end
94
162
 
95
163
  private
@@ -97,6 +165,11 @@ module WebMock
97
165
  def empty_string?(string)
98
166
  string.nil? || string == ""
99
167
  end
168
+
169
+ def normalize_hash(hash)
170
+ JSON.parse(JSON.generate(hash))
171
+ end
172
+
100
173
  end
101
174
 
102
175
  class HeadersPattern
@@ -115,7 +188,7 @@ module WebMock
115
188
  true
116
189
  end
117
190
  end
118
-
191
+
119
192
  def to_s
120
193
  WebMock::Util::Headers.sorted_headers_string(@pattern)
121
194
  end
@@ -14,7 +14,7 @@ module WebMock
14
14
  end
15
15
  end
16
16
  end
17
-
17
+
18
18
  class Response
19
19
  def initialize(options = {})
20
20
  if options.is_a?(IO) || options.is_a?(String)
@@ -27,7 +27,7 @@ module WebMock
27
27
  def headers
28
28
  @headers
29
29
  end
30
-
30
+
31
31
  def headers=(headers)
32
32
  @headers = headers
33
33
  if @headers && !@headers.is_a?(Proc)
@@ -38,7 +38,7 @@ module WebMock
38
38
  def body
39
39
  @body || ''
40
40
  end
41
-
41
+
42
42
  def body=(body)
43
43
  @body = body
44
44
  stringify_body!
@@ -56,8 +56,16 @@ module WebMock
56
56
  @exception
57
57
  end
58
58
 
59
+ def exception=(exception)
60
+ @exception = case exception
61
+ when String then StandardError.new(exception)
62
+ when Class then exception.new('Exception from WebMock')
63
+ when Exception then exception
64
+ end
65
+ end
66
+
59
67
  def raise_error_if_any
60
- raise @exception.new('Exception from WebMock') if @exception
68
+ raise @exception if @exception
61
69
  end
62
70
 
63
71
  def should_timeout
@@ -68,7 +76,7 @@ module WebMock
68
76
  self.headers = options[:headers]
69
77
  self.status = options[:status]
70
78
  self.body = options[:body]
71
- @exception = options[:exception]
79
+ self.exception = options[:exception]
72
80
  @should_timeout = options[:should_timeout]
73
81
  end
74
82
 
@@ -105,8 +113,8 @@ module WebMock
105
113
  raw_response.close
106
114
  raw_response = string
107
115
  end
108
- socket = Net::BufferedIO.new(raw_response)
109
- response = Net::HTTPResponse.read_new(socket)
116
+ socket = ::Net::BufferedIO.new(raw_response)
117
+ response = ::Net::HTTPResponse.read_new(socket)
110
118
  transfer_encoding = response.delete('transfer-encoding') #chunks were already read by curl
111
119
  response.reading_body(socket, true) {}
112
120
 
@@ -11,15 +11,26 @@ module WebMock
11
11
  module Util
12
12
 
13
13
  class URI
14
+ ADDRESSABLE_URIS = Hash.new do |hash, key|
15
+ hash[key] = Addressable::URI.heuristic_parse(key)
16
+ end
17
+
18
+ NORMALIZED_URIS = Hash.new do |hash, uri|
19
+ normalized_uri = WebMock::Util::URI.heuristic_parse(uri)
20
+ normalized_uri.query_values = sort_query_values(normalized_uri.query_values) if normalized_uri.query_values
21
+ normalized_uri = normalized_uri.normalize #normalize! is slower
22
+ normalized_uri.port = normalized_uri.inferred_port unless normalized_uri.port
23
+ hash[uri] = normalized_uri
24
+ end
25
+
26
+ def self.heuristic_parse(uri)
27
+ ADDRESSABLE_URIS[uri].dup
28
+ end
14
29
 
15
30
  def self.normalize_uri(uri)
16
31
  return uri if uri.is_a?(Regexp)
17
32
  uri = 'http://' + uri unless uri.match('^https?://') if uri.is_a?(String)
18
- normalized_uri = Addressable::URI.heuristic_parse(uri)
19
- normalized_uri.query_values = sort_query_values(normalized_uri.query_values) if normalized_uri.query_values
20
- normalized_uri = normalized_uri.normalize #normalize! is slower
21
- normalized_uri.port = normalized_uri.inferred_port unless normalized_uri.port
22
- normalized_uri
33
+ NORMALIZED_URIS[uri].dup
23
34
  end
24
35
 
25
36
  def self.variations_of_uri_as_strings(uri_object)
@@ -35,7 +46,7 @@ module WebMock
35
46
  if normalized_uri.port == Addressable::URI.port_mapping[normalized_uri.scheme]
36
47
  uris = uris_with_inferred_port_and_without(uris)
37
48
  end
38
-
49
+
39
50
  if normalized_uri.scheme == "http"
40
51
  uris = uris_with_scheme_and_without(uris)
41
52
  end
@@ -58,7 +69,7 @@ module WebMock
58
69
  private
59
70
 
60
71
  def self.sort_query_values(query_values)
61
- Hash[*query_values.sort.inject([]) { |values, pair| values + pair}]
72
+ Hash[*query_values.sort.inject([]) { |values, pair| values + pair}]
62
73
  end
63
74
 
64
75
  def self.uris_with_inferred_port_and_without(uris)
@@ -59,8 +59,16 @@ module WebMock
59
59
  WebMock::RequestRegistry.instance.reset_webmock
60
60
  end
61
61
 
62
+ def reset_callbacks
63
+ WebMock::CallbackRegistry.reset
64
+ end
65
+
62
66
  def assertion_failure(message)
63
67
  raise message
64
68
  end
69
+
70
+ def after_request(options={}, &block)
71
+ CallbackRegistry.add_callback(options, block)
72
+ end
65
73
 
66
74
  end
@@ -0,0 +1,61 @@
1
+ # require 'rubygems'
2
+ require 'benchmark'
3
+ require 'net/http'
4
+ # require 'fakeweb'
5
+ require 'spec/spec_helper'
6
+
7
+ def http_request
8
+ res = Net::HTTP.get_response(URI.parse('http://example.com'))
9
+ raise "Body should be 'Hello'" unless res.body == 'Hello'
10
+ end
11
+
12
+ def fakeweb
13
+ FakeWeb.register_uri(:get, 'http://example.com', :body => 'Hello')
14
+ yield
15
+ ensure
16
+ FakeWeb.clean_registry
17
+ end
18
+
19
+ def webmock
20
+ WebMock.stub_request(:get, 'http://example.com').to_return(:body => 'Hello')
21
+ yield
22
+ ensure
23
+ WebMock.reset_webmock
24
+ end
25
+
26
+ def perform_benchmark(name)
27
+ puts "\n\nBenchmarking #{name}:"
28
+ Benchmark.benchmark do |b|
29
+ %w(webmock).each do |type|
30
+ b.report(type) do
31
+ # this is a bit convoluted, but we want to ensure that each benchmark runs without the other library loaded,
32
+ # so we fork off a sub-process before requiring the libraries.
33
+ Process.fork do
34
+ require type
35
+ yield type
36
+ end
37
+ Process.wait
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ n = 5000
44
+ perform_benchmark("Single setup/teardown") do |type|
45
+ send(type) { n.times { http_request } }
46
+ end
47
+
48
+ perform_benchmark("Setup/teardown for each http request") do |type|
49
+ n.times { send(type) { http_request } }
50
+ end
51
+
52
+ # Output on my machine:
53
+ #
54
+ # Benchmarking Single setup/teardown:
55
+ # webmock 0.000000 0.000000 18.830000 ( 19.350281)
56
+ # fakeweb 0.000000 0.000000 1.930000 ( 2.049569)
57
+ #
58
+ #
59
+ # Benchmarking Setup/teardown for each http request:
60
+ # webmock 0.000000 0.000000 21.350000 ( 21.795557)
61
+ # fakeweb 0.000000 0.000000 2.490000 ( 2.707574)