webmock 0.7.1 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/CHANGELOG +9 -0
  2. data/README.md +169 -76
  3. data/Rakefile +2 -1
  4. data/VERSION +1 -1
  5. data/lib/webmock.rb +12 -9
  6. data/lib/webmock/adapters/rspec.rb +20 -22
  7. data/lib/webmock/adapters/rspec/matchers.rb +4 -4
  8. data/lib/webmock/adapters/rspec/webmock_matcher.rb +2 -2
  9. data/lib/webmock/adapters/test_unit.rb +20 -21
  10. data/lib/webmock/http_lib_adapters/net_http.rb +51 -15
  11. data/lib/webmock/request_profile.rb +5 -44
  12. data/lib/webmock/request_registry.rb +10 -11
  13. data/lib/webmock/request_signature.rb +44 -0
  14. data/lib/webmock/request_stub.rb +3 -3
  15. data/lib/webmock/response.rb +1 -1
  16. data/lib/webmock/rspec.rb +1 -0
  17. data/lib/webmock/test_unit.rb +1 -0
  18. data/lib/webmock/util/hash_counter.rb +16 -8
  19. data/lib/webmock/util/headers.rb +23 -0
  20. data/lib/webmock/util/uri.rb +81 -0
  21. data/lib/webmock/webmock.rb +16 -19
  22. data/spec/net_http_spec.rb +10 -9
  23. data/spec/other_net_http_libs_spec.rb +3 -1
  24. data/spec/request_profile_spec.rb +6 -116
  25. data/spec/request_registry_spec.rb +12 -17
  26. data/spec/request_signature_spec.rb +155 -0
  27. data/spec/request_stub_spec.rb +2 -2
  28. data/spec/response_spec.rb +1 -1
  29. data/spec/spec_helper.rb +4 -1
  30. data/spec/util/hash_counter_spec.rb +4 -4
  31. data/spec/util/headers_spec.rb +11 -0
  32. data/spec/util/uri_spec.rb +213 -0
  33. data/spec/vendor/addressable/lib/addressable/uri.rb +8 -0
  34. data/spec/vendor/addressable/lib/uri.rb +0 -0
  35. data/spec/webmock_spec.rb +58 -10
  36. data/test/test_helper.rb +5 -1
  37. data/test/test_webmock.rb +11 -6
  38. data/webmock.gemspec +21 -6
  39. metadata +28 -6
  40. data/lib/webmock/url.rb +0 -46
  41. data/lib/webmock/utility.rb +0 -65
  42. data/spec/utility_spec.rb +0 -70
@@ -8,12 +8,12 @@ module WebMock
8
8
  WebMock::RequestProfileMatcher.new.times(0)
9
9
  end
10
10
 
11
- def have_requested(method, url)
12
- WebMock::WebMockMatcher.new(method, url)
11
+ def have_requested(method, uri)
12
+ WebMock::WebMockMatcher.new(method, uri)
13
13
  end
14
14
 
15
- def have_not_requested(method, url)
16
- WebMock::WebMockMatcher.new(method, url).times(0)
15
+ def have_not_requested(method, uri)
16
+ WebMock::WebMockMatcher.new(method, uri).times(0)
17
17
  end
18
18
  end
19
19
  end
@@ -1,9 +1,9 @@
1
1
  module WebMock
2
2
  class WebMockMatcher
3
3
 
4
- def initialize(method, url)
4
+ def initialize(method, uri)
5
5
  @request_execution_verifier = RequestExecutionVerifier.new
6
- @request_execution_verifier.request_profile = RequestProfile.new(method, url)
6
+ @request_execution_verifier.request_profile = RequestProfile.new(method, uri)
7
7
  end
8
8
 
9
9
  def once
@@ -1,25 +1,24 @@
1
- if defined?(Test::Unit::TestCase)
2
-
3
- class Test::Unit::TestCase
4
- include WebMock
5
- alias setup_without_webmock setup
6
- def setup
7
- reset_webmock
8
- @original_allow_net_connect = WebMock.net_connect_allowed?
9
- WebMock.disable_net_connect!
10
- end
1
+ require 'test/unit'
2
+ require 'webmock'
11
3
 
12
- alias teardown_without_webmock teardown
13
- def teardown
14
- @original_allow_net_connect ? WebMock.allow_net_connect! : WebMock.disable_net_connect!
15
- end
4
+ class Test::Unit::TestCase
5
+ alias setup_without_webmock setup
6
+ def setup
7
+ WebMock.reset_webmock
8
+ @original_allow_net_connect = WebMock.net_connect_allowed?
9
+ WebMock.disable_net_connect!
16
10
  end
17
-
18
- module WebMock
19
- private
20
- def assertion_failure(message)
21
- raise Test::Unit::AssertionFailedError.new(message)
22
- end
11
+
12
+ alias teardown_without_webmock teardown
13
+ def teardown
14
+ @original_allow_net_connect ? WebMock.allow_net_connect! : WebMock.disable_net_connect!
23
15
  end
24
-
25
16
  end
17
+
18
+ module WebMock
19
+ private
20
+ def assertion_failure(message)
21
+ raise Test::Unit::AssertionFailedError.new(message)
22
+ end
23
+ end
24
+
@@ -2,7 +2,6 @@ require 'net/http'
2
2
  require 'net/https'
3
3
  require 'stringio'
4
4
 
5
-
6
5
  class StubSocket #:nodoc:
7
6
 
8
7
  def initialize(*args)
@@ -61,11 +60,11 @@ module Net #:nodoc: all
61
60
  protocol = use_ssl? ? "https" : "http"
62
61
 
63
62
  path = request.path
64
- path = URI.parse(request.path).request_uri if request.path =~ /^http/
63
+ path = Addressable::URI.heuristic_parse(request.path).request_uri if request.path =~ /^http/
65
64
 
66
65
  if request["authorization"] =~ /^Basic /
67
- userinfo = WebMock::Utility.decode_userinfo_from_header(request["authorization"])
68
- userinfo = WebMock::Utility.encode_unsafe_chars_in_userinfo(userinfo) + "@"
66
+ userinfo = WebMock::Util::Headers.decode_userinfo_from_header(request["authorization"])
67
+ userinfo = WebMock::Util::URI.encode_unsafe_chars_in_userinfo(userinfo) + "@"
69
68
  else
70
69
  userinfo = ""
71
70
  end
@@ -74,24 +73,21 @@ module Net #:nodoc: all
74
73
  method = request.method.downcase.to_sym
75
74
 
76
75
  headers = Hash[*request.to_hash.map {|k,v| [k, v.flatten]}.flatten]
77
- headers.reject! {|k,v| k =~ /[Aa]ccept/ && v = '*/*'}
76
+ headers.reject! {|k,v| k =~ /[Aa]ccept/ && v = '*/*'} #removing header added by Net::HTTP
77
+
78
+ request_signature = WebMock::RequestSignature.new(method, uri, body, headers)
78
79
 
79
- request_profile = WebMock::RequestProfile.new(method, uri, body, headers)
80
+ WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
80
81
 
81
- if WebMock.registered_request?(request_profile)
82
+ if WebMock.registered_request?(request_signature)
82
83
  @socket = Net::HTTP.socket_type.new
83
- webmock_response = WebMock.response_for_request(request_profile)
84
+ webmock_response = WebMock.response_for_request(request_signature)
84
85
  build_net_http_response(webmock_response, &block)
85
86
  elsif WebMock.net_connect_allowed?
86
- WebMock::RequestRegistry.instance.requested.put(request_profile)
87
87
  connect_without_webmock
88
88
  request_without_webmock(request, body, &block)
89
89
  else
90
- uri = WebMock::Utility.strip_default_port_from_uri(uri)
91
- message = "Real HTTP connections are disabled. Unregistered request: #{request.method} #{uri}"
92
- message << " with body '#{body}'" if body
93
- message << " with headers #{WebMock::Utility.normalize_headers(headers).inspect.gsub("\"","'")}" if
94
- headers && !headers.empty?
90
+ message = "Real HTTP connections are disabled. Unregistered request: #{request_signature}"
95
91
  raise WebMock::NetConnectNotAllowedError, message
96
92
  end
97
93
  end
@@ -101,7 +97,7 @@ headers && !headers.empty?
101
97
 
102
98
  def connect_with_webmock
103
99
  unless @@alredy_checked_for_net_http_replacement_libs ||= false
104
- WebMock::Utility.puts_warning_for_net_http_replacement_libs_if_needed
100
+ WebMock::NetHTTPUtility.puts_warning_for_net_http_replacement_libs_if_needed
105
101
  @@alredy_checked_for_net_http_replacement_libs = true
106
102
  end
107
103
  nil
@@ -127,3 +123,43 @@ headers && !headers.empty?
127
123
  end
128
124
 
129
125
  end
126
+
127
+ module WebMock
128
+ module NetHTTPUtility
129
+ def self.puts_warning_for_net_http_around_advice_libs_if_needed
130
+ libs = {"Samuel" => defined?(Samuel)}
131
+ warnings = libs.select { |_, loaded| loaded }.map do |name, _|
132
+ <<-TEXT.gsub(/ {10}/, '')
133
+ \e[1mWarning: WebMock was loaded after #{name}\e[0m
134
+ * #{name}'s code is being ignored when a request is handled by WebMock,
135
+ because both libraries work by patching Net::HTTP.
136
+ * To fix this, just reorder your requires so that WebMock is before #{name}.
137
+ TEXT
138
+ end
139
+ $stderr.puts "\n" + warnings.join("\n") + "\n" if warnings.any?
140
+ end
141
+
142
+ def self.record_loaded_net_http_replacement_libs
143
+ libs = {"RightHttpConnection" => defined?(RightHttpConnection)}
144
+ @loaded_net_http_replacement_libs = libs.map { |name, loaded| name if loaded }.compact
145
+ end
146
+
147
+ def self.puts_warning_for_net_http_replacement_libs_if_needed
148
+ libs = {"RightHttpConnection" => defined?(RightHttpConnection)}
149
+ warnings = libs.select { |_, loaded| loaded }.
150
+ reject { |name, _| @loaded_net_http_replacement_libs.include?(name) }.
151
+ map do |name, _|
152
+ <<-TEXT.gsub(/ {10}/, '')
153
+ \e[1mWarning: #{name} was loaded after WebMock\e[0m
154
+ * WebMock's code is being ignored, because #{name} replaces parts of
155
+ Net::HTTP without deferring to other libraries. This will break Net::HTTP requests.
156
+ * To fix this, just reorder your requires so that #{name} is before WebMock.
157
+ TEXT
158
+ end
159
+ $stderr.puts "\n" + warnings.join("\n") + "\n" if warnings.any?
160
+ end
161
+ end
162
+ end
163
+
164
+ WebMock::NetHTTPUtility.record_loaded_net_http_replacement_libs
165
+ WebMock::NetHTTPUtility.puts_warning_for_net_http_around_advice_libs_if_needed
@@ -4,64 +4,25 @@ module WebMock
4
4
 
5
5
  def initialize(method, uri, body = nil, headers = nil)
6
6
  super
7
- self.uri = WebMock::URL.normalize_uri(self.uri) unless self.uri.is_a?(URI)
8
- self.headers = Utility.normalize_headers(self.headers)
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
9
  end
10
10
 
11
11
  def with(options)
12
12
  self.body = options[:body] if options.has_key?(:body)
13
- self.headers = Utility.normalize_headers(options[:headers]) if options.has_key?(:headers)
13
+ self.headers = WebMock::Util::Headers.normalize_headers(options[:headers]) if options.has_key?(:headers)
14
14
  self
15
15
  end
16
16
 
17
- #self needs to be a subset of other. Other needs to be more general.
18
- def match(other)
19
- match_method(other) &&
20
- match_body(other) &&
21
- match_headers(other) &&
22
- match_url(other)
23
- end
24
-
25
17
  def to_s
26
- string = "#{self.method.to_s.upcase} #{self.uri}"
18
+ string = "#{self.method.to_s.upcase} #{WebMock::Util::URI.strip_default_port_from_uri_string(self.uri.to_s)}"
27
19
  string << " with body '#{body}'" if body
28
20
  if headers && !headers.empty?
29
- string << " with headers #{WebMock::Utility.normalize_headers(headers).inspect.gsub("\"","'")}"
21
+ string << " with headers #{WebMock::Util::Headers.normalize_headers(headers).inspect.gsub("\"","'")}"
30
22
  end
31
23
  string
32
24
  end
33
25
 
34
- private
35
-
36
- def match_url(other)
37
- raise "Can't match regexp request profile" if self.uri.is_a?(Regexp)
38
- @uris_to_check ||= WebMock::URL.variations_of_uri_as_strings(self.uri)
39
- if other.uri.is_a?(URI)
40
- @uris_to_check.include?(other.uri.to_s)
41
- elsif other.uri.is_a?(Regexp)
42
- @uris_to_check.any? { |u| u.match(other.uri) }
43
- else
44
- false
45
- end
46
- end
47
-
48
- def match_headers(other)
49
- return false if self.headers && !self.headers.empty? && other.headers && other.headers.empty?
50
- if other.headers && !other.headers.empty?
51
- other.headers.each do | key, value |
52
- return false unless (self.headers && self.headers.has_key?(key) && value == self.headers[key])
53
- end
54
- end
55
- return true
56
- end
57
-
58
- def match_body(other)
59
- other.body == self.body || other.body.nil?
60
- end
61
-
62
- def match_method(other)
63
- other.method == self.method || other.method == :any
64
- end
65
26
  end
66
27
 
67
28
  end
@@ -3,7 +3,7 @@ module WebMock
3
3
  class RequestRegistry
4
4
  include Singleton
5
5
 
6
- attr_accessor :request_stubs, :requested
6
+ attr_accessor :request_stubs, :requested_signatures
7
7
 
8
8
  def initialize
9
9
  reset_webmock
@@ -11,7 +11,7 @@ module WebMock
11
11
 
12
12
  def reset_webmock
13
13
  self.request_stubs = []
14
- self.requested = HashCounter.new
14
+ self.requested_signatures = Util::HashCounter.new
15
15
  end
16
16
 
17
17
  def register_request_stub(stub)
@@ -19,27 +19,26 @@ module WebMock
19
19
  stub
20
20
  end
21
21
 
22
- def registered_request?(request_profile)
23
- stub_for(request_profile)
22
+ def registered_request?(request_signature)
23
+ request_stub_for(request_signature)
24
24
  end
25
25
 
26
- def response_for_request(request_profile)
27
- stub = stub_for(request_profile)
28
- self.requested.put(request_profile)
26
+ def response_for_request(request_signature)
27
+ stub = request_stub_for(request_signature)
29
28
  stub ? stub.response : nil
30
29
  end
31
30
 
32
31
  def times_executed(request_profile)
33
- self.requested.hash.select { |executed_request_profile, times_executed|
34
- executed_request_profile.match(request_profile)
32
+ self.requested_signatures.hash.select { |request_signature, times_executed|
33
+ request_signature.match(request_profile)
35
34
  }.inject(0) {|sum, (_, times_executed)| sum + times_executed }
36
35
  end
37
36
 
38
37
  private
39
38
 
40
- def stub_for(request_profile)
39
+ def request_stub_for(request_signature)
41
40
  request_stubs.detect { |registered_request_stub|
42
- request_profile.match(registered_request_stub.request_profile)
41
+ request_signature.match(registered_request_stub.request_profile)
43
42
  }
44
43
  end
45
44
 
@@ -0,0 +1,44 @@
1
+ module WebMock
2
+
3
+ class RequestSignature < RequestProfile
4
+
5
+ def match(request_profile)
6
+ match_method(request_profile) &&
7
+ match_body(request_profile) &&
8
+ match_headers(request_profile) &&
9
+ match_uri(request_profile)
10
+ end
11
+
12
+ private
13
+
14
+ def match_uri(request_profile)
15
+ if request_profile.uri.is_a?(Addressable::URI)
16
+ WebMock::Util::URI.normalize_uri(uri) === WebMock::Util::URI.normalize_uri(request_profile.uri)
17
+ elsif request_profile.uri.is_a?(Regexp)
18
+ WebMock::Util::URI.variations_of_uri_as_strings(self.uri).any? { |u| u.match(request_profile.uri) }
19
+ else
20
+ false
21
+ end
22
+ end
23
+
24
+ def match_headers(request_profile)
25
+ return false if self.headers && !self.headers.empty? && request_profile.headers && request_profile.headers.empty?
26
+ if request_profile.headers && !request_profile.headers.empty?
27
+ request_profile.headers.each do | key, value |
28
+ return false unless (self.headers && self.headers.has_key?(key) && value == self.headers[key])
29
+ end
30
+ end
31
+ return true
32
+ end
33
+
34
+ def match_body(request_profile)
35
+ request_profile.body == self.body || request_profile.body.nil?
36
+ end
37
+
38
+ def match_method(request_profile)
39
+ request_profile.method == self.method || request_profile.method == :any
40
+ end
41
+ end
42
+
43
+
44
+ end
@@ -2,15 +2,15 @@ module WebMock
2
2
  class RequestStub
3
3
  attr_accessor :request_profile, :response
4
4
 
5
- def initialize(method, url)
6
- @request_profile = RequestProfile.new(method, url)
5
+ def initialize(method, uri)
6
+ @request_profile = RequestProfile.new(method, uri)
7
7
  @response = WebMock::Response.new
8
8
  self
9
9
  end
10
10
 
11
11
  def with(params)
12
12
  @request_profile.body = params[:body]
13
- @request_profile.headers = Utility.normalize_headers(params[:headers])
13
+ @request_profile.headers = Util::Headers.normalize_headers(params[:headers])
14
14
  self
15
15
  end
16
16
 
@@ -6,7 +6,7 @@ module WebMock
6
6
  end
7
7
 
8
8
  def headers
9
- Utility.normalize_headers(@options[:headers])
9
+ Util::Headers.normalize_headers(@options[:headers])
10
10
  end
11
11
 
12
12
  def body
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), "adapters/rspec")
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), "adapters/test_unit")
@@ -1,12 +1,20 @@
1
- class HashCounter
2
- attr_accessor :hash
3
- def initialize
1
+ module WebMock
2
+
3
+ module Util
4
+
5
+ class Util::HashCounter
6
+ attr_accessor :hash
7
+ def initialize
4
8
  self.hash = {}
5
- end
6
- def put key, num=1
9
+ end
10
+ def put key, num=1
7
11
  hash[key] = (hash[key] || 0) + num
8
- end
9
- def get key
12
+ end
13
+ def get key
10
14
  hash[key] || 0
15
+ end
11
16
  end
12
- end
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,23 @@
1
+ module WebMock
2
+
3
+ module Util
4
+
5
+ class Headers
6
+
7
+ def self.normalize_headers(headers)
8
+ return nil unless headers
9
+ array = headers.map { |name, value|
10
+ [name.to_s.split(/_|-/).map { |segment| segment.capitalize }.join("-"), value.to_s]
11
+ }
12
+ Hash[*array.flatten]
13
+ end
14
+
15
+ def self.decode_userinfo_from_header(header)
16
+ header.sub(/^Basic /, "").unpack("m").first
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,81 @@
1
+ module Addressable
2
+ class URI
3
+ module CharacterClasses
4
+ USERINFO = UNRESERVED + SUB_DELIMS + "\\:"
5
+ end
6
+ end
7
+ end
8
+
9
+ module WebMock
10
+
11
+ module Util
12
+
13
+ class URI
14
+
15
+ def self.normalize_uri(uri)
16
+ return uri if uri.is_a?(Regexp)
17
+ 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 = normalized_uri.query_values if normalized_uri.query_values
20
+ normalized_uri.normalize!
21
+ normalized_uri.port = normalized_uri.inferred_port unless normalized_uri.port && normalized_uri.inferred_port
22
+ normalized_uri
23
+ end
24
+
25
+ def self.variations_of_uri_as_strings(uri_object)
26
+ normalized_uri = normalize_uri(uri_object.dup).freeze
27
+ uris = [ normalized_uri ]
28
+
29
+ if normalized_uri.port == Addressable::URI.port_mapping[normalized_uri.scheme]
30
+ uris = uris_with_inferred_port_and_without(uris)
31
+ end
32
+
33
+ if normalized_uri.scheme == "http"
34
+ uris = uris_with_scheme_and_without(uris)
35
+ end
36
+
37
+ if normalized_uri.path == '/' && normalized_uri.query == nil
38
+ uris = uris_with_trailing_slash_and_without(uris)
39
+ end
40
+
41
+ uris = uris_encoded_and_unencoded(uris)
42
+
43
+ uris.map {|uri| uri.to_s.gsub(/^\/\//,'') }.uniq
44
+ end
45
+
46
+ def self.strip_default_port_from_uri_string(uri_string)
47
+ case uri_string
48
+ when %r{^http://} then uri_string.sub(%r{:80(/|$)}, '\1')
49
+ when %r{^https://} then uri_string.sub(%r{:443(/|$)}, '\1')
50
+ else uri_string
51
+ end
52
+ end
53
+
54
+ def self.encode_unsafe_chars_in_userinfo(userinfo)
55
+ Addressable::URI.encode_component(userinfo, Addressable::URI::CharacterClasses::USERINFO)
56
+ end
57
+
58
+ private
59
+
60
+ def self.uris_with_inferred_port_and_without(uris)
61
+ uris.map { |uri| [ uri, uri.omit(:port).freeze ] }.flatten
62
+ end
63
+
64
+ def self.uris_encoded_and_unencoded(uris)
65
+ uris.map do |uri|
66
+ [ uri, Addressable::URI.heuristic_parse(Addressable::URI.unencode(uri)).freeze ]
67
+ end.flatten
68
+ end
69
+
70
+ def self.uris_with_scheme_and_without(uris)
71
+ uris.map { |uri| [ uri, uri.omit(:scheme).freeze ] }.flatten
72
+ end
73
+
74
+ def self.uris_with_trailing_slash_and_without(uris)
75
+ uris = uris.map { |uri| [ uri, uri.omit(:path).freeze ] }.flatten
76
+ end
77
+
78
+ end
79
+ end
80
+
81
+ end