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.
- data/CHANGELOG.md +55 -0
- data/README.md +63 -4
- data/Rakefile +2 -0
- data/VERSION +1 -1
- data/lib/webmock.rb +3 -0
- data/lib/webmock/callback_registry.rb +35 -0
- data/lib/webmock/http_lib_adapters/em_http_request.rb +114 -0
- data/lib/webmock/http_lib_adapters/httpclient.rb +34 -7
- data/lib/webmock/http_lib_adapters/net_http.rb +24 -2
- data/lib/webmock/http_lib_adapters/net_http_response.rb +49 -0
- data/lib/webmock/http_lib_adapters/patron.rb +22 -3
- data/lib/webmock/request_pattern.rb +90 -17
- data/lib/webmock/response.rb +15 -7
- data/lib/webmock/util/uri.rb +18 -7
- data/lib/webmock/webmock.rb +8 -0
- data/spec/benchmark.rb +61 -0
- data/spec/em_http_request_spec.rb +29 -0
- data/spec/em_http_request_spec_helper.rb +64 -0
- data/spec/httpclient_spec_helper.rb +4 -0
- data/spec/net_http_spec.rb +46 -1
- data/spec/net_http_spec_helper.rb +4 -0
- data/spec/other_net_http_libs_spec.rb +1 -0
- data/spec/patron_spec_helper.rb +4 -0
- data/spec/request_pattern_spec.rb +143 -32
- data/spec/response_spec.rb +14 -0
- data/spec/spec_helper.rb +4 -1
- data/spec/vendor/crack/lib/crack.rb +0 -0
- data/spec/webmock_spec.rb +362 -13
- data/test/test_webmock.rb +2 -2
- data/webmock.gemspec +21 -5
- metadata +58 -11
@@ -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 =
|
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 =
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
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?(
|
86
|
-
|
87
|
-
|
88
|
-
|
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.
|
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
|
data/lib/webmock/response.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
|
data/lib/webmock/util/uri.rb
CHANGED
@@ -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
|
-
|
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)
|
data/lib/webmock/webmock.rb
CHANGED
@@ -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
|
data/spec/benchmark.rb
ADDED
@@ -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)
|