webmock 0.7.3 → 0.8.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 +15 -0
- data/README.md +80 -68
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/lib/webmock.rb +2 -0
- data/lib/webmock/adapters/rspec/webmock_matcher.rb +1 -4
- data/lib/webmock/http_lib_adapters/httpclient.rb +96 -0
- data/lib/webmock/http_lib_adapters/net_http.rb +4 -7
- data/lib/webmock/request.rb +29 -0
- data/lib/webmock/request_profile.rb +28 -15
- data/lib/webmock/request_registry.rb +12 -2
- data/lib/webmock/request_signature.rb +2 -1
- data/lib/webmock/request_stub.rb +1 -2
- data/lib/webmock/response.rb +18 -6
- data/lib/webmock/util/uri.rb +2 -2
- data/lib/webmock/webmock.rb +2 -2
- data/spec/httpclient_spec.rb +36 -0
- data/spec/httpclient_spec_helper.rb +57 -0
- data/spec/net_http_spec.rb +15 -24
- data/spec/net_http_spec_helper.rb +53 -0
- data/spec/request_execution_verifier_spec.rb +5 -5
- data/spec/request_profile_spec.rb +17 -10
- data/spec/request_registry_spec.rb +15 -15
- data/spec/request_signature_spec.rb +71 -56
- data/spec/request_stub_spec.rb +3 -3
- data/spec/response_spec.rb +7 -1
- data/spec/spec_helper.rb +5 -33
- data/spec/util/uri_spec.rb +67 -40
- data/spec/webmock_spec.rb +179 -164
- data/test/test_webmock.rb +25 -19
- data/webmock.gemspec +14 -3
- metadata +20 -2
@@ -0,0 +1,29 @@
|
|
1
|
+
module WebMock
|
2
|
+
|
3
|
+
class Request
|
4
|
+
attr_accessor :method, :uri, :body, :headers
|
5
|
+
|
6
|
+
def initialize(method, uri, options = {})
|
7
|
+
self.method = method
|
8
|
+
self.uri = uri.is_a?(Addressable::URI) ? uri : WebMock::Util::URI.normalize_uri(uri)
|
9
|
+
assign_options(options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
string = "#{self.method.to_s.upcase} #{WebMock::Util::URI.strip_default_port_from_uri_string(self.uri.to_s)}"
|
14
|
+
string << " with body '#{body.to_s}'" if body && body.to_s != ''
|
15
|
+
if headers && !headers.empty?
|
16
|
+
string << " with headers #{WebMock::Util::Headers.normalize_headers(headers).inspect.gsub("\"","'")}"
|
17
|
+
end
|
18
|
+
string
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def assign_options(options)
|
24
|
+
self.body = options[:body] if options.has_key?(:body)
|
25
|
+
self.headers = WebMock::Util::Headers.normalize_headers(options[:headers]) if options.has_key?(:headers)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -1,28 +1,41 @@
|
|
1
1
|
module WebMock
|
2
2
|
|
3
|
-
class RequestProfile <
|
4
|
-
|
5
|
-
def initialize(method, uri, body = nil, headers = nil)
|
6
|
-
super
|
7
|
-
self.uri = WebMock::Util::URI.normalize_uri(self.uri) unless self.uri.is_a?(Addressable::URI)
|
8
|
-
self.headers = WebMock::Util::Headers.normalize_headers(self.headers)
|
9
|
-
end
|
3
|
+
class RequestProfile < Request
|
10
4
|
|
11
5
|
def with(options)
|
12
|
-
|
13
|
-
self.headers = WebMock::Util::Headers.normalize_headers(options[:headers]) if options.has_key?(:headers)
|
6
|
+
assign_options(options)
|
14
7
|
self
|
15
8
|
end
|
9
|
+
|
10
|
+
def body=(body)
|
11
|
+
@body = Body.new(body)
|
12
|
+
end
|
13
|
+
|
14
|
+
class Body
|
15
|
+
|
16
|
+
attr_reader :data
|
17
|
+
|
18
|
+
def initialize(data)
|
19
|
+
@data = data
|
20
|
+
end
|
16
21
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
string << " with headers #{WebMock::Util::Headers.normalize_headers(headers).inspect.gsub("\"","'")}"
|
22
|
+
def ==(other)
|
23
|
+
other = Body.new(other) unless other.is_a?(Body)
|
24
|
+
other.is_a?(Body) &&
|
25
|
+
(other.is_empty? && self.is_empty? || other.data == self.data)
|
22
26
|
end
|
23
|
-
|
27
|
+
|
28
|
+
def is_empty?
|
29
|
+
@data.nil? || @data == ""
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
@data
|
34
|
+
end
|
35
|
+
|
24
36
|
end
|
25
37
|
|
26
38
|
end
|
27
39
|
|
40
|
+
|
28
41
|
end
|
@@ -25,9 +25,9 @@ module WebMock
|
|
25
25
|
|
26
26
|
def response_for_request(request_signature)
|
27
27
|
stub = request_stub_for(request_signature)
|
28
|
-
stub ? stub.response : nil
|
28
|
+
stub ? evaluate_response_for_request(stub.response, request_signature) : nil
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
def times_executed(request_profile)
|
32
32
|
self.requested_signatures.hash.select { |request_signature, times_executed|
|
33
33
|
request_signature.match(request_profile)
|
@@ -42,5 +42,15 @@ module WebMock
|
|
42
42
|
}
|
43
43
|
end
|
44
44
|
|
45
|
+
def evaluate_response_for_request(response, request_signature)
|
46
|
+
evaluated_response = response.dup
|
47
|
+
[:body, :headers].each do |attribute|
|
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
|
53
|
+
end
|
54
|
+
|
45
55
|
end
|
46
56
|
end
|
@@ -1,8 +1,9 @@
|
|
1
1
|
module WebMock
|
2
2
|
|
3
|
-
class RequestSignature <
|
3
|
+
class RequestSignature < Request
|
4
4
|
|
5
5
|
def match(request_profile)
|
6
|
+
raise ArgumentError unless request_profile.is_a?(RequestProfile)
|
6
7
|
match_method(request_profile) &&
|
7
8
|
match_body(request_profile) &&
|
8
9
|
match_headers(request_profile) &&
|
data/lib/webmock/request_stub.rb
CHANGED
data/lib/webmock/response.rb
CHANGED
@@ -1,24 +1,26 @@
|
|
1
1
|
module WebMock
|
2
2
|
class Response
|
3
|
+
attr_accessor :options
|
3
4
|
|
4
5
|
def initialize(options = {})
|
5
6
|
@options = options
|
7
|
+
@options[:headers] = Util::Headers.normalize_headers(@options[:headers]) unless @options[:headers].is_a?(Proc)
|
6
8
|
end
|
7
9
|
|
8
10
|
def headers
|
9
|
-
|
11
|
+
@options[:headers]
|
10
12
|
end
|
11
13
|
|
12
14
|
def body
|
13
15
|
return '' unless @options.has_key?(:body)
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
16
|
+
case @options[:body]
|
17
|
+
when IO
|
18
|
+
@options[:body].read
|
19
|
+
when String
|
18
20
|
@options[:body]
|
19
21
|
end
|
20
22
|
end
|
21
|
-
|
23
|
+
|
22
24
|
def status
|
23
25
|
@options.has_key?(:status) ? @options[:status] : 200
|
24
26
|
end
|
@@ -27,5 +29,15 @@ module WebMock
|
|
27
29
|
raise @options[:exception].new('Exception from WebMock') if @options.has_key?(:exception)
|
28
30
|
end
|
29
31
|
|
32
|
+
def dup
|
33
|
+
dup_response = super
|
34
|
+
dup_response.options = options.dup
|
35
|
+
dup_response
|
36
|
+
end
|
37
|
+
|
38
|
+
def ==(other)
|
39
|
+
options == other.options
|
40
|
+
end
|
41
|
+
|
30
42
|
end
|
31
43
|
end
|
data/lib/webmock/util/uri.rb
CHANGED
@@ -18,7 +18,7 @@ module WebMock
|
|
18
18
|
normalized_uri = Addressable::URI.heuristic_parse(uri)
|
19
19
|
normalized_uri.query_values = normalized_uri.query_values if normalized_uri.query_values
|
20
20
|
normalized_uri.normalize!
|
21
|
-
normalized_uri.port = normalized_uri.inferred_port unless normalized_uri.port
|
21
|
+
normalized_uri.port = normalized_uri.inferred_port unless normalized_uri.port
|
22
22
|
normalized_uri
|
23
23
|
end
|
24
24
|
|
@@ -34,7 +34,7 @@ module WebMock
|
|
34
34
|
uris = uris_with_scheme_and_without(uris)
|
35
35
|
end
|
36
36
|
|
37
|
-
if normalized_uri.path == '/'
|
37
|
+
if normalized_uri.path == '/'
|
38
38
|
uris = uris_with_trailing_slash_and_without(uris)
|
39
39
|
end
|
40
40
|
|
data/lib/webmock/webmock.rb
CHANGED
@@ -13,13 +13,13 @@ module WebMock
|
|
13
13
|
|
14
14
|
def assert_requested(method, uri, options = {})
|
15
15
|
expected_times_executed = options.delete(:times) || 1
|
16
|
-
request = RequestProfile.new(method, uri, options
|
16
|
+
request = RequestProfile.new(method, uri, options)
|
17
17
|
verifier = RequestExecutionVerifier.new(request, expected_times_executed)
|
18
18
|
assertion_failure(verifier.failure_message) unless verifier.matches?
|
19
19
|
end
|
20
20
|
|
21
21
|
def assert_not_requested(method, uri, options = {})
|
22
|
-
request = RequestProfile.new(method, uri, options
|
22
|
+
request = RequestProfile.new(method, uri, options)
|
23
23
|
verifier = RequestExecutionVerifier.new(request, options.delete(:times))
|
24
24
|
assertion_failure(verifier.negative_failure_message) unless verifier.does_not_match?
|
25
25
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
require 'webmock_spec'
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
require 'httpclient'
|
6
|
+
require 'httpclient_spec_helper'
|
7
|
+
|
8
|
+
describe "Webmock with HTTPClient" do
|
9
|
+
include HTTPClientSpecHelper
|
10
|
+
|
11
|
+
before(:each) do
|
12
|
+
HTTPClientSpecHelper.async_mode = false
|
13
|
+
end
|
14
|
+
|
15
|
+
it_should_behave_like "WebMock"
|
16
|
+
|
17
|
+
it "should yield block on response if block provided" do
|
18
|
+
stub_http_request(:get, "www.example.com").to_return(:body => "abc")
|
19
|
+
response_body = ""
|
20
|
+
http_request(:get, "http://www.example.com/") do |body|
|
21
|
+
response_body = body
|
22
|
+
end
|
23
|
+
response_body.should == "abc"
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "async requests" do
|
27
|
+
|
28
|
+
before(:each) do
|
29
|
+
HTTPClientSpecHelper.async_mode = true
|
30
|
+
end
|
31
|
+
|
32
|
+
it_should_behave_like "WebMock"
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module HTTPClientSpecHelper
|
2
|
+
class << self
|
3
|
+
attr_accessor :async_mode
|
4
|
+
end
|
5
|
+
|
6
|
+
def http_request(method, uri, options = {}, &block)
|
7
|
+
uri = Addressable::URI.heuristic_parse(uri)
|
8
|
+
c = HTTPClient.new
|
9
|
+
c.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
10
|
+
c.set_basic_auth(nil, uri.user, uri.password) if uri.user
|
11
|
+
params = [method, "#{uri.omit(:userinfo, :query).normalize.to_s}",
|
12
|
+
uri.query_values, options[:body], options[:headers] || {}]
|
13
|
+
if HTTPClientSpecHelper.async_mode
|
14
|
+
connection = c.request_async(*params)
|
15
|
+
connection.join
|
16
|
+
response = connection.pop
|
17
|
+
else
|
18
|
+
response = c.request(*params, &block)
|
19
|
+
end
|
20
|
+
OpenStruct.new({
|
21
|
+
:body => HTTPClientSpecHelper.async_mode ? response.content.read : response.content,
|
22
|
+
:headers => Hash[response.header.all],
|
23
|
+
:status => response.code.to_s })
|
24
|
+
end
|
25
|
+
|
26
|
+
def setup_expectations_for_real_request(options = {})
|
27
|
+
socket = mock("TCPSocket")
|
28
|
+
TCPSocket.should_receive(:new).
|
29
|
+
with(options[:host], options[:port]).at_least(:once).and_return(socket)
|
30
|
+
|
31
|
+
socket.stub!(:closed?).and_return(false)
|
32
|
+
socket.stub!(:close).and_return(true)
|
33
|
+
|
34
|
+
request_parts = ["#{options[:method]} #{options[:path]} HTTP/1.1", "Host: #{options[:host]}"]
|
35
|
+
|
36
|
+
if options[:port] == 443
|
37
|
+
OpenSSL::SSL::SSLSocket.should_receive(:new).
|
38
|
+
with(socket, instance_of(OpenSSL::SSL::SSLContext)).
|
39
|
+
at_least(:once).and_return(socket = mock("SSLSocket"))
|
40
|
+
socket.should_receive(:connect).at_least(:once).with()
|
41
|
+
socket.should_receive(:peer_cert).and_return(mock('peer cert', :extensions => []))
|
42
|
+
socket.should_receive(:write).with(/#{request_parts[0]}.*#{request_parts[1]}.*/m).and_return(100)
|
43
|
+
else
|
44
|
+
socket.should_receive(:<<).with(/#{request_parts[0]}.*#{request_parts[1]}.*/m).and_return(100)
|
45
|
+
end
|
46
|
+
|
47
|
+
socket.stub!(:sync=).with(true)
|
48
|
+
|
49
|
+
socket.should_receive(:gets).with("\n").once.and_return("HTTP/1.1 #{options[:response_code]} #{options[:response_message]}\nContent-Length: #{options[:response_body].length}\n\n#{options[:response_body]}")
|
50
|
+
|
51
|
+
socket.stub!(:eof?).and_return(true)
|
52
|
+
socket.stub!(:close).and_return(true)
|
53
|
+
|
54
|
+
socket.should_receive(:readpartial).any_number_of_times.and_raise(EOFError)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
data/spec/net_http_spec.rb
CHANGED
@@ -1,34 +1,25 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
2
|
require 'webmock_spec'
|
3
3
|
require 'ostruct'
|
4
|
+
require 'net_http_spec_helper'
|
4
5
|
|
5
|
-
|
6
|
-
begin
|
7
|
-
uri = URI.parse(uri)
|
8
|
-
rescue
|
9
|
-
uri = Addressable::URI.heuristic_parse(uri)
|
10
|
-
end
|
11
|
-
response = nil
|
12
|
-
clazz = Net::HTTP.const_get("#{method.to_s.capitalize}")
|
13
|
-
req = clazz.new("#{uri.path}#{uri.query ? '?' : ''}#{uri.query}", options[:headers])
|
14
|
-
req.basic_auth uri.user, uri.password if uri.user
|
15
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
16
|
-
http.use_ssl = true if uri.scheme == "https"
|
17
|
-
response = http.start {|http|
|
18
|
-
http.request(req, options[:body])
|
19
|
-
}
|
20
|
-
OpenStruct.new({
|
21
|
-
:body => response.body,
|
22
|
-
:headers => response,
|
23
|
-
:status => response.code })
|
24
|
-
end
|
6
|
+
include NetHTTPSpecHelper
|
25
7
|
|
26
8
|
describe "Webmock with Net:HTTP" do
|
27
|
-
|
9
|
+
|
28
10
|
it_should_behave_like "WebMock"
|
29
|
-
|
11
|
+
|
30
12
|
it "should work with block provided" do
|
31
|
-
stub_http_request(:get, "www.
|
32
|
-
Net::HTTP.start("www.
|
13
|
+
stub_http_request(:get, "www.example.com").to_return(:body => "abc"*100000)
|
14
|
+
Net::HTTP.start("www.example.com") { |query| query.get("/") }.body.should == "abc"*100000
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should yield block on response" do
|
18
|
+
stub_http_request(:get, "www.example.com").to_return(:body => "abc")
|
19
|
+
response_body = ""
|
20
|
+
http_request(:get, "http://www.example.com/") do |response|
|
21
|
+
response_body = response.body
|
22
|
+
end
|
23
|
+
response_body.should == "abc"
|
33
24
|
end
|
34
25
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module NetHTTPSpecHelper
|
2
|
+
def http_request(method, uri, options = {}, &block)
|
3
|
+
begin
|
4
|
+
uri = URI.parse(uri)
|
5
|
+
rescue
|
6
|
+
uri = Addressable::URI.heuristic_parse(uri)
|
7
|
+
end
|
8
|
+
response = nil
|
9
|
+
clazz = Net::HTTP.const_get("#{method.to_s.capitalize}")
|
10
|
+
req = clazz.new("#{uri.path}#{uri.query ? '?' : ''}#{uri.query}", options[:headers])
|
11
|
+
|
12
|
+
req.basic_auth uri.user, uri.password if uri.user
|
13
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
14
|
+
http.use_ssl = true if uri.scheme == "https"
|
15
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
16
|
+
response = http.start {|http|
|
17
|
+
http.request(req, options[:body], &block)
|
18
|
+
}
|
19
|
+
OpenStruct.new({
|
20
|
+
:body => response.body,
|
21
|
+
:headers => response,
|
22
|
+
:status => response.code })
|
23
|
+
end
|
24
|
+
|
25
|
+
# Sets several expectations that a real HTTP request makes it
|
26
|
+
# past WebMock to the socket layer. You can use this when you need to check
|
27
|
+
# that a request isn't handled by WebMock
|
28
|
+
#This solution is copied from FakeWeb project
|
29
|
+
def setup_expectations_for_real_request(options = {})
|
30
|
+
# Socket handling
|
31
|
+
if options[:port] == 443
|
32
|
+
socket = mock("SSLSocket")
|
33
|
+
OpenSSL::SSL::SSLSocket.should_receive(:===).with(socket).at_least(:once).and_return(true)
|
34
|
+
OpenSSL::SSL::SSLSocket.should_receive(:new).with(socket, instance_of(OpenSSL::SSL::SSLContext)).at_least(:once).and_return(socket)
|
35
|
+
socket.stub!(:sync_close=).and_return(true)
|
36
|
+
socket.should_receive(:connect).at_least(:once).with()
|
37
|
+
else
|
38
|
+
socket = mock("TCPSocket")
|
39
|
+
Socket.should_receive(:===).with(socket).at_least(:once).and_return(true)
|
40
|
+
end
|
41
|
+
|
42
|
+
TCPSocket.should_receive(:open).with(options[:host], options[:port]).at_least(:once).and_return(socket)
|
43
|
+
socket.stub!(:closed?).and_return(false)
|
44
|
+
socket.stub!(:close).and_return(true)
|
45
|
+
|
46
|
+
# Request/response handling
|
47
|
+
request_parts = ["#{options[:method]} #{options[:path]} HTTP/1.1", "Host: #{options[:host]}"]
|
48
|
+
socket.should_receive(:write).with(/#{request_parts[0]}.*#{request_parts[1]}.*/m).and_return(100)
|
49
|
+
|
50
|
+
socket.should_receive(:sysread).once.and_return("HTTP/1.1 #{options[:response_code]} #{options[:response_message]}\nContent-Length: #{options[:response_body].length}\n\n#{options[:response_body]}")
|
51
|
+
socket.should_receive(:sysread).any_number_of_times.and_raise(EOFError)
|
52
|
+
end
|
53
|
+
end
|
@@ -3,7 +3,7 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
|
3
3
|
describe RequestExecutionVerifier do
|
4
4
|
before(:each) do
|
5
5
|
@verifier = RequestExecutionVerifier.new
|
6
|
-
@request_profile = mock(RequestProfile, :to_s => "www.
|
6
|
+
@request_profile = mock(RequestProfile, :to_s => "www.example.com")
|
7
7
|
@verifier.request_profile = @request_profile
|
8
8
|
end
|
9
9
|
|
@@ -13,13 +13,13 @@ describe RequestExecutionVerifier do
|
|
13
13
|
it "should report failure message" do
|
14
14
|
@verifier.times_executed = 0
|
15
15
|
@verifier.expected_times_executed = 2
|
16
|
-
@verifier.failure_message.should == "The request www.
|
16
|
+
@verifier.failure_message.should == "The request www.example.com was expected to execute 2 times but it executed 0 times"
|
17
17
|
end
|
18
18
|
|
19
19
|
it "should report failure message correctly when executed times is one" do
|
20
20
|
@verifier.times_executed = 1
|
21
21
|
@verifier.expected_times_executed = 1
|
22
|
-
@verifier.failure_message.should == "The request www.
|
22
|
+
@verifier.failure_message.should == "The request www.example.com was expected to execute 1 time but it executed 1 time"
|
23
23
|
end
|
24
24
|
|
25
25
|
end
|
@@ -29,12 +29,12 @@ describe RequestExecutionVerifier do
|
|
29
29
|
it "should report failure message if it executed number of times specified" do
|
30
30
|
@verifier.times_executed = 2
|
31
31
|
@verifier.expected_times_executed = 2
|
32
|
-
@verifier.negative_failure_message.should == "The request www.
|
32
|
+
@verifier.negative_failure_message.should == "The request www.example.com was not expected to execute 2 times but it executed 2 times"
|
33
33
|
end
|
34
34
|
|
35
35
|
it "should report failure message when not expected request but it executed" do
|
36
36
|
@verifier.times_executed = 1
|
37
|
-
@verifier.negative_failure_message.should == "The request www.
|
37
|
+
@verifier.negative_failure_message.should == "The request www.example.com was expected to execute 0 times but it executed 1 time"
|
38
38
|
end
|
39
39
|
|
40
40
|
end
|