webmock 0.7.1 → 0.7.2

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.
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