webmock 1.2.2 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)