webmock 0.9.1 → 1.0.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 +47 -2
- data/README.md +68 -6
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/lib/webmock.rb +2 -2
- data/lib/webmock/adapters/rspec.rb +1 -1
- data/lib/webmock/adapters/rspec/matchers.rb +2 -2
- data/lib/webmock/adapters/rspec/{request_profile_matcher.rb → request_pattern_matcher.rb} +5 -5
- data/lib/webmock/adapters/rspec/webmock_matcher.rb +2 -2
- data/lib/webmock/config.rb +2 -1
- data/lib/webmock/http_lib_adapters/httpclient.rb +5 -4
- data/lib/webmock/http_lib_adapters/net_http.rb +5 -3
- data/lib/webmock/http_lib_adapters/patron.rb +82 -0
- data/lib/webmock/request_execution_verifier.rb +8 -8
- data/lib/webmock/request_pattern.rb +130 -0
- data/lib/webmock/request_registry.rb +4 -9
- data/lib/webmock/request_signature.rb +18 -37
- data/lib/webmock/request_stub.rb +17 -6
- data/lib/webmock/response.rb +87 -31
- data/lib/webmock/util/headers.rb +5 -0
- data/lib/webmock/webmock.rb +10 -6
- data/spec/httpclient_spec.rb +0 -1
- data/spec/httpclient_spec_helper.rb +11 -1
- data/spec/net_http_spec.rb +8 -1
- data/spec/net_http_spec_helper.rb +11 -1
- data/spec/patron_spec.rb +83 -0
- data/spec/patron_spec_helper.rb +44 -0
- data/spec/request_execution_verifier_spec.rb +8 -8
- data/spec/request_pattern_spec.rb +243 -0
- data/spec/request_registry_spec.rb +34 -19
- data/spec/request_signature_spec.rb +23 -191
- data/spec/request_stub_spec.rb +32 -11
- data/spec/response_spec.rb +98 -5
- data/spec/spec_helper.rb +8 -16
- data/spec/webmock_spec.rb +154 -49
- data/webmock.gemspec +14 -7
- metadata +21 -7
- data/lib/webmock/request.rb +0 -29
- data/lib/webmock/request_profile.rb +0 -50
- data/spec/request_profile_spec.rb +0 -68
@@ -1,22 +1,22 @@
|
|
1
1
|
module WebMock
|
2
2
|
class RequestExecutionVerifier
|
3
3
|
|
4
|
-
attr_accessor :
|
4
|
+
attr_accessor :request_pattern, :expected_times_executed, :times_executed
|
5
5
|
|
6
|
-
def initialize(
|
7
|
-
@
|
6
|
+
def initialize(request_pattern = nil, expected_times_executed = nil)
|
7
|
+
@request_pattern = request_pattern
|
8
8
|
@expected_times_executed = expected_times_executed
|
9
9
|
end
|
10
10
|
|
11
11
|
def matches?
|
12
12
|
@times_executed =
|
13
|
-
RequestRegistry.instance.times_executed(@
|
13
|
+
RequestRegistry.instance.times_executed(@request_pattern)
|
14
14
|
@times_executed == (@expected_times_executed || 1)
|
15
15
|
end
|
16
16
|
|
17
17
|
def does_not_match?
|
18
18
|
@times_executed =
|
19
|
-
RequestRegistry.instance.times_executed(@
|
19
|
+
RequestRegistry.instance.times_executed(@request_pattern)
|
20
20
|
if @expected_times_executed
|
21
21
|
@times_executed != @expected_times_executed
|
22
22
|
else
|
@@ -27,14 +27,14 @@ module WebMock
|
|
27
27
|
|
28
28
|
def failure_message
|
29
29
|
expected_times_executed = @expected_times_executed || 1
|
30
|
-
%Q(The request #{
|
30
|
+
%Q(The request #{request_pattern.to_s} was expected to execute #{expected_times_executed} time#{ (expected_times_executed == 1) ? '' : 's'} but it executed #{times_executed} time#{ (times_executed == 1) ? '' : 's'})
|
31
31
|
end
|
32
32
|
|
33
33
|
def negative_failure_message
|
34
34
|
if @expected_times_executed
|
35
|
-
%Q(The request #{
|
35
|
+
%Q(The request #{request_pattern.to_s} was not expected to execute #{expected_times_executed} time#{ (expected_times_executed == 1) ? '' : 's'} but it executed #{times_executed} time#{ (times_executed == 1) ? '' : 's'})
|
36
36
|
else
|
37
|
-
%Q(The request #{
|
37
|
+
%Q(The request #{request_pattern.to_s} was expected to execute 0 times but it executed #{times_executed} time#{ (times_executed == 1) ? '' : 's'})
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module WebMock
|
2
|
+
|
3
|
+
class RequestPattern
|
4
|
+
|
5
|
+
def initialize(method, uri, options = {})
|
6
|
+
@method_pattern = MethodPattern.new(method)
|
7
|
+
@uri_pattern = URIPattern.new(uri)
|
8
|
+
assign_options(options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def with(options = {}, &block)
|
12
|
+
assign_options(options)
|
13
|
+
@with_block = block
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def matches?(request_signature)
|
18
|
+
@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))
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
string = "#{@method_pattern.to_s.upcase}"
|
27
|
+
string << " #{@uri_pattern.to_s}"
|
28
|
+
string << " with body '#{@body_pattern.to_s}'" if @body_pattern
|
29
|
+
string << " with headers #{@headers_pattern.to_s}" if @headers_pattern
|
30
|
+
string << " with given block" if @with_block
|
31
|
+
string
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
|
37
|
+
def assign_options(options)
|
38
|
+
@body_pattern = BodyPattern.new(options[:body]) if options.has_key?(:body)
|
39
|
+
@headers_pattern = HeadersPattern.new(options[:headers]) if options.has_key?(:headers)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
class MethodPattern
|
46
|
+
def initialize(pattern)
|
47
|
+
@pattern = pattern
|
48
|
+
end
|
49
|
+
|
50
|
+
def matches?(method)
|
51
|
+
@pattern == method || @pattern == :any
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_s
|
55
|
+
@pattern.to_s
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class URIPattern
|
60
|
+
def initialize(pattern)
|
61
|
+
@pattern = pattern.is_a?(Addressable::URI) ? pattern : WebMock::Util::URI.normalize_uri(pattern)
|
62
|
+
end
|
63
|
+
|
64
|
+
def matches?(uri)
|
65
|
+
if @pattern.is_a?(Addressable::URI)
|
66
|
+
##TODO : do I need to normalize again??
|
67
|
+
WebMock::Util::URI.normalize_uri(uri) === WebMock::Util::URI.normalize_uri(@pattern)
|
68
|
+
elsif @pattern.is_a?(Regexp)
|
69
|
+
WebMock::Util::URI.variations_of_uri_as_strings(uri).any? { |u| u.match(@pattern) }
|
70
|
+
else
|
71
|
+
false
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def to_s
|
76
|
+
WebMock::Util::URI.strip_default_port_from_uri_string(@pattern.to_s)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class BodyPattern
|
81
|
+
def initialize(pattern)
|
82
|
+
@pattern = pattern
|
83
|
+
end
|
84
|
+
|
85
|
+
def matches?(uri)
|
86
|
+
empty_string?(@pattern) && empty_string?(uri) ||
|
87
|
+
@pattern == uri ||
|
88
|
+
@pattern === uri
|
89
|
+
end
|
90
|
+
|
91
|
+
def to_s
|
92
|
+
@pattern.to_s
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def empty_string?(string)
|
98
|
+
string.nil? || string == ""
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class HeadersPattern
|
103
|
+
def initialize(pattern)
|
104
|
+
@pattern = WebMock::Util::Headers.normalize_headers(pattern) || {}
|
105
|
+
end
|
106
|
+
|
107
|
+
def matches?(headers)
|
108
|
+
if empty_headers?(@pattern)
|
109
|
+
empty_headers?(headers)
|
110
|
+
else
|
111
|
+
return false if empty_headers?(headers)
|
112
|
+
@pattern.each do |key, value|
|
113
|
+
return false unless headers.has_key?(key) && value === headers[key]
|
114
|
+
end
|
115
|
+
true
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def to_s
|
120
|
+
WebMock::Util::Headers.sorted_headers_string(@pattern)
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
def empty_headers?(headers)
|
126
|
+
headers.nil? || headers == {}
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
@@ -28,9 +28,9 @@ module WebMock
|
|
28
28
|
stub ? evaluate_response_for_request(stub.response, request_signature) : nil
|
29
29
|
end
|
30
30
|
|
31
|
-
def times_executed(
|
31
|
+
def times_executed(request_pattern)
|
32
32
|
self.requested_signatures.hash.select { |request_signature, times_executed|
|
33
|
-
|
33
|
+
request_pattern.matches?(request_signature)
|
34
34
|
}.inject(0) {|sum, (_, times_executed)| sum + times_executed }
|
35
35
|
end
|
36
36
|
|
@@ -38,18 +38,13 @@ module WebMock
|
|
38
38
|
|
39
39
|
def request_stub_for(request_signature)
|
40
40
|
request_stubs.detect { |registered_request_stub|
|
41
|
-
|
41
|
+
registered_request_stub.request_pattern.matches?(request_signature)
|
42
42
|
}
|
43
43
|
end
|
44
44
|
|
45
45
|
def evaluate_response_for_request(response, request_signature)
|
46
46
|
evaluated_response = response.dup
|
47
|
-
|
48
|
-
if response.options[attribute].is_a?(Proc)
|
49
|
-
evaluated_response.options[attribute] = response.options[attribute].call(request_signature)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
evaluated_response
|
47
|
+
evaluated_response.evaluate!(request_signature)
|
53
48
|
end
|
54
49
|
|
55
50
|
end
|
@@ -1,51 +1,32 @@
|
|
1
1
|
module WebMock
|
2
2
|
|
3
|
-
class RequestSignature
|
4
|
-
|
5
|
-
def match(request_profile)
|
6
|
-
raise ArgumentError unless request_profile.is_a?(RequestProfile)
|
7
|
-
match_method(request_profile) &&
|
8
|
-
match_body(request_profile) &&
|
9
|
-
match_headers(request_profile) &&
|
10
|
-
match_uri(request_profile) &&
|
11
|
-
match_with_block(request_profile)
|
12
|
-
end
|
3
|
+
class RequestSignature
|
13
4
|
|
14
|
-
|
5
|
+
attr_accessor :method, :uri, :body, :headers
|
15
6
|
|
16
|
-
def
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
WebMock::Util::URI.variations_of_uri_as_strings(self.uri).any? { |u| u.match(request_profile.uri) }
|
21
|
-
else
|
22
|
-
false
|
23
|
-
end
|
7
|
+
def initialize(method, uri, options = {})
|
8
|
+
self.method = method
|
9
|
+
self.uri = uri.is_a?(Addressable::URI) ? uri : WebMock::Util::URI.normalize_uri(uri)
|
10
|
+
assign_options(options)
|
24
11
|
end
|
25
12
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
13
|
+
def to_s
|
14
|
+
string = "#{self.method.to_s.upcase}"
|
15
|
+
string << " #{WebMock::Util::URI.strip_default_port_from_uri_string(self.uri.to_s)}"
|
16
|
+
string << " with body '#{body.to_s}'" if body && body.to_s != ''
|
17
|
+
if headers && !headers.empty?
|
18
|
+
string << " with headers #{WebMock::Util::Headers.sorted_headers_string(headers)}"
|
32
19
|
end
|
33
|
-
|
20
|
+
string
|
34
21
|
end
|
35
22
|
|
36
|
-
|
37
|
-
request_profile.body == self.body || request_profile.body.nil?
|
38
|
-
end
|
23
|
+
private
|
39
24
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
def match_with_block(request_profile)
|
45
|
-
return true unless request_profile.with_block
|
46
|
-
request_profile.with_block.call(self)
|
25
|
+
def assign_options(options)
|
26
|
+
self.body = options[:body] if options.has_key?(:body)
|
27
|
+
self.headers = WebMock::Util::Headers.normalize_headers(options[:headers]) if options.has_key?(:headers)
|
47
28
|
end
|
48
|
-
end
|
49
29
|
|
30
|
+
end
|
50
31
|
|
51
32
|
end
|
data/lib/webmock/request_stub.rb
CHANGED
@@ -1,26 +1,37 @@
|
|
1
1
|
module WebMock
|
2
2
|
class RequestStub
|
3
3
|
|
4
|
-
attr_accessor :
|
4
|
+
attr_accessor :request_pattern
|
5
5
|
|
6
6
|
def initialize(method, uri)
|
7
|
-
@
|
7
|
+
@request_pattern = RequestPattern.new(method, uri)
|
8
8
|
@responses_sequences = []
|
9
9
|
self
|
10
10
|
end
|
11
11
|
|
12
12
|
def with(params = {}, &block)
|
13
|
-
@
|
13
|
+
@request_pattern.with(params, &block)
|
14
14
|
self
|
15
15
|
end
|
16
16
|
|
17
|
-
def to_return(*response_hashes)
|
18
|
-
|
17
|
+
def to_return(*response_hashes, &block)
|
18
|
+
if block
|
19
|
+
@responses_sequences << ResponsesSequence.new([ResponseFactory.response_for(block)])
|
20
|
+
else
|
21
|
+
@responses_sequences << ResponsesSequence.new([*response_hashes].flatten.map {|r| ResponseFactory.response_for(r)})
|
22
|
+
end
|
19
23
|
self
|
20
24
|
end
|
21
25
|
|
22
26
|
def to_raise(*exceptions)
|
23
|
-
@responses_sequences << ResponsesSequence.new([*exceptions].flatten.map {|e|
|
27
|
+
@responses_sequences << ResponsesSequence.new([*exceptions].flatten.map {|e|
|
28
|
+
ResponseFactory.response_for(:exception => e)
|
29
|
+
})
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_timeout
|
34
|
+
@responses_sequences << ResponsesSequence.new([ResponseFactory.response_for(:should_timeout => true)])
|
24
35
|
self
|
25
36
|
end
|
26
37
|
|
data/lib/webmock/response.rb
CHANGED
@@ -4,81 +4,137 @@ class StringIO
|
|
4
4
|
end
|
5
5
|
|
6
6
|
module WebMock
|
7
|
+
|
8
|
+
class ResponseFactory
|
9
|
+
def self.response_for(options)
|
10
|
+
if options.respond_to?(:call)
|
11
|
+
WebMock::DynamicResponse.new(options)
|
12
|
+
else
|
13
|
+
WebMock::Response.new(options)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
7
17
|
|
8
18
|
class Response
|
9
|
-
attr_reader :options
|
10
|
-
|
11
19
|
def initialize(options = {})
|
12
|
-
if options.is_a?(IO) || options.is_a?(String)
|
13
|
-
|
14
|
-
|
15
|
-
|
20
|
+
if options.is_a?(IO) || options.is_a?(String)
|
21
|
+
self.options = read_raw_response(options)
|
22
|
+
else
|
23
|
+
self.options = options
|
16
24
|
end
|
17
|
-
@options[:headers] = Util::Headers.normalize_headers(@options[:headers]) unless @options[:headers].is_a?(Proc)
|
18
25
|
end
|
19
26
|
|
20
27
|
def headers
|
21
|
-
@
|
28
|
+
@headers
|
29
|
+
end
|
30
|
+
|
31
|
+
def headers=(headers)
|
32
|
+
@headers = headers
|
33
|
+
if @headers && !@headers.is_a?(Proc)
|
34
|
+
@headers = Util::Headers.normalize_headers(@headers)
|
35
|
+
end
|
22
36
|
end
|
23
37
|
|
24
38
|
def body
|
25
|
-
|
39
|
+
@body || ''
|
40
|
+
end
|
41
|
+
|
42
|
+
def body=(body)
|
43
|
+
@body = body
|
26
44
|
stringify_body!
|
27
|
-
@options[:body]
|
28
45
|
end
|
29
46
|
|
30
47
|
def status
|
31
|
-
@
|
48
|
+
@status || [200, ""]
|
49
|
+
end
|
50
|
+
|
51
|
+
def status=(status)
|
52
|
+
@status = status.is_a?(Integer) ? [status, ""] : status
|
53
|
+
end
|
54
|
+
|
55
|
+
def exception
|
56
|
+
@exception
|
32
57
|
end
|
33
58
|
|
34
59
|
def raise_error_if_any
|
35
|
-
raise @
|
60
|
+
raise @exception.new('Exception from WebMock') if @exception
|
61
|
+
end
|
62
|
+
|
63
|
+
def should_timeout
|
64
|
+
@should_timeout == true
|
36
65
|
end
|
37
66
|
|
38
67
|
def options=(options)
|
39
|
-
|
40
|
-
|
68
|
+
self.headers = options[:headers]
|
69
|
+
self.status = options[:status]
|
70
|
+
self.body = options[:body]
|
71
|
+
@exception = options[:exception]
|
72
|
+
@should_timeout = options[:should_timeout]
|
41
73
|
end
|
42
74
|
|
43
|
-
def
|
44
|
-
|
45
|
-
|
46
|
-
|
75
|
+
def evaluate!(request_signature)
|
76
|
+
self.body = @body.call(request_signature) if @body.is_a?(Proc)
|
77
|
+
self.headers = @headers.call(request_signature) if @headers.is_a?(Proc)
|
78
|
+
self.status = @status.call(request_signature) if @status.is_a?(Proc)
|
79
|
+
@should_timeout = @should_timeout.call(request_signature) if @should_timeout.is_a?(Proc)
|
80
|
+
@exception = @exception.call(request_signature) if @exception.is_a?(Proc)
|
81
|
+
self
|
47
82
|
end
|
48
83
|
|
49
84
|
def ==(other)
|
50
|
-
|
85
|
+
self.body == other.body &&
|
86
|
+
self.headers === other.headers &&
|
87
|
+
self.status == other.status &&
|
88
|
+
self.exception == other.exception &&
|
89
|
+
self.should_timeout == other.should_timeout
|
51
90
|
end
|
52
|
-
|
53
|
-
private
|
91
|
+
|
92
|
+
private
|
54
93
|
|
55
94
|
def stringify_body!
|
56
|
-
if @
|
57
|
-
io = @
|
58
|
-
@
|
95
|
+
if @body.is_a?(IO)
|
96
|
+
io = @body
|
97
|
+
@body = io.read
|
59
98
|
io.close
|
60
99
|
end
|
61
100
|
end
|
62
|
-
|
101
|
+
|
63
102
|
def read_raw_response(raw_response)
|
64
103
|
if raw_response.is_a?(IO)
|
65
|
-
string = raw_response.read
|
104
|
+
string = raw_response.read
|
66
105
|
raw_response.close
|
67
106
|
raw_response = string
|
68
|
-
end
|
69
|
-
socket = Net::BufferedIO.new(raw_response)
|
107
|
+
end
|
108
|
+
socket = Net::BufferedIO.new(raw_response)
|
70
109
|
response = Net::HTTPResponse.read_new(socket)
|
71
110
|
transfer_encoding = response.delete('transfer-encoding') #chunks were already read by curl
|
72
111
|
response.reading_body(socket, true) {}
|
73
|
-
|
112
|
+
|
74
113
|
options = {}
|
75
114
|
options[:headers] = {}
|
76
115
|
response.each_header {|name, value| options[:headers][name] = value}
|
77
116
|
options[:headers]['transfer-encoding'] = transfer_encoding if transfer_encoding
|
78
117
|
options[:body] = response.read_body
|
79
|
-
options[:status] = response.code.to_i
|
80
|
-
options
|
118
|
+
options[:status] = [response.code.to_i, response.message]
|
119
|
+
options
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
class DynamicResponse < Response
|
125
|
+
attr_accessor :responder
|
126
|
+
|
127
|
+
def initialize(responder)
|
128
|
+
@responder = responder
|
81
129
|
end
|
82
130
|
|
131
|
+
def dup
|
132
|
+
self.class.new(@responder)
|
133
|
+
end
|
134
|
+
|
135
|
+
def evaluate!(request_signature)
|
136
|
+
self.options = @responder.call(request_signature)
|
137
|
+
self
|
138
|
+
end
|
83
139
|
end
|
84
140
|
end
|