webmock 0.9.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|