webmock 1.6.4 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. data/.gemtest +0 -0
  2. data/.gitignore +3 -1
  3. data/.travis.yml +6 -0
  4. data/CHANGELOG.md +211 -118
  5. data/Gemfile +16 -1
  6. data/Guardfile +24 -0
  7. data/LICENSE +1 -1
  8. data/README.md +64 -15
  9. data/Rakefile +19 -6
  10. data/lib/webmock.rb +9 -4
  11. data/lib/webmock/api.rb +5 -4
  12. data/lib/webmock/assertion_failure.rb +1 -1
  13. data/lib/webmock/callback_registry.rb +1 -1
  14. data/lib/webmock/config.rb +2 -2
  15. data/lib/webmock/cucumber.rb +1 -1
  16. data/lib/webmock/errors.rb +17 -5
  17. data/lib/webmock/http_lib_adapters/{curb.rb → curb_adapter.rb} +79 -49
  18. data/lib/webmock/http_lib_adapters/{em_http_request.rb → em_http_request/em_http_request_0_x.rb} +20 -15
  19. data/lib/webmock/http_lib_adapters/em_http_request/em_http_request_1_x.rb +201 -0
  20. data/lib/webmock/http_lib_adapters/em_http_request_adapter.rb +11 -0
  21. data/lib/webmock/http_lib_adapters/http_lib_adapter.rb +7 -0
  22. data/lib/webmock/http_lib_adapters/http_lib_adapter_registry.rb +19 -0
  23. data/lib/webmock/http_lib_adapters/{httpclient.rb → httpclient_adapter.rb} +35 -8
  24. data/lib/webmock/http_lib_adapters/net_http.rb +84 -25
  25. data/lib/webmock/http_lib_adapters/net_http_response.rb +17 -17
  26. data/lib/webmock/http_lib_adapters/patron_adapter.rb +124 -0
  27. data/lib/webmock/http_lib_adapters/typhoeus_hydra_adapter.rb +166 -0
  28. data/lib/webmock/minitest.rb +15 -0
  29. data/lib/webmock/rack_response.rb +52 -0
  30. data/lib/webmock/request_pattern.rb +4 -2
  31. data/lib/webmock/request_signature.rb +4 -0
  32. data/lib/webmock/request_stub.rb +35 -2
  33. data/lib/webmock/responses_sequence.rb +2 -2
  34. data/lib/webmock/rspec.rb +2 -2
  35. data/lib/webmock/rspec/matchers.rb +9 -4
  36. data/lib/webmock/rspec/matchers/webmock_matcher.rb +1 -1
  37. data/lib/webmock/stub_registry.rb +1 -1
  38. data/lib/webmock/stub_request_snippet.rb +14 -11
  39. data/lib/webmock/util/hash_keys_stringifier.rb +4 -4
  40. data/lib/webmock/util/headers.rb +3 -3
  41. data/lib/webmock/util/json.rb +54 -0
  42. data/lib/webmock/util/uri.rb +1 -1
  43. data/lib/webmock/version.rb +1 -1
  44. data/lib/webmock/webmock.rb +20 -3
  45. data/minitest/test_helper.rb +29 -0
  46. data/minitest/test_webmock.rb +6 -0
  47. data/minitest/webmock_spec.rb +30 -0
  48. data/spec/curb_spec.rb +26 -8
  49. data/spec/curb_spec_helper.rb +6 -6
  50. data/spec/em_http_request_spec.rb +95 -1
  51. data/spec/em_http_request_spec_helper.rb +16 -16
  52. data/spec/errors_spec.rb +19 -4
  53. data/spec/example_curl_output.txt +22 -22
  54. data/spec/http_lib_adapters/http_lib_adapter_registry_spec.rb +17 -0
  55. data/spec/http_lib_adapters/http_lib_adapter_spec.rb +12 -0
  56. data/spec/httpclient_spec.rb +1 -1
  57. data/spec/httpclient_spec_helper.rb +3 -38
  58. data/spec/my_rack_app.rb +18 -0
  59. data/spec/net_http_shared.rb +125 -0
  60. data/spec/net_http_spec.rb +27 -31
  61. data/spec/net_http_spec_helper.rb +4 -34
  62. data/spec/network_connection.rb +1 -1
  63. data/spec/patron_spec_helper.rb +4 -7
  64. data/spec/quality_spec.rb +60 -0
  65. data/spec/rack_response_spec.rb +33 -0
  66. data/spec/real_net_http_spec.rb +20 -0
  67. data/spec/request_execution_verifier_spec.rb +8 -8
  68. data/spec/request_pattern_spec.rb +3 -3
  69. data/spec/request_stub_spec.rb +19 -19
  70. data/spec/response_spec.rb +8 -8
  71. data/spec/spec_helper.rb +14 -11
  72. data/spec/stub_request_snippet_spec.rb +85 -37
  73. data/spec/support/webmock_server.rb +62 -0
  74. data/spec/typhoeus_hydra_spec.rb +53 -0
  75. data/spec/typhoeus_hydra_spec_helper.rb +50 -0
  76. data/spec/util/headers_spec.rb +5 -5
  77. data/spec/util/json_spec.rb +7 -0
  78. data/spec/util/uri_spec.rb +1 -1
  79. data/spec/vendor/addressable/lib/uri.rb +1 -0
  80. data/spec/vendor/crack/lib/crack.rb +1 -0
  81. data/spec/vendor/right_http_connection-1.2.4/History.txt +4 -4
  82. data/spec/vendor/right_http_connection-1.2.4/README.txt +4 -4
  83. data/spec/vendor/right_http_connection-1.2.4/Rakefile +3 -3
  84. data/spec/vendor/right_http_connection-1.2.4/lib/net_fix.rb +4 -4
  85. data/spec/vendor/right_http_connection-1.2.4/setup.rb +4 -4
  86. data/spec/webmock_shared.rb +375 -143
  87. data/spec/webmock_spec.rb +7 -0
  88. data/test/http_request.rb +24 -0
  89. data/test/shared_test.rb +47 -0
  90. data/test/test_helper.rb +6 -3
  91. data/test/test_webmock.rb +2 -67
  92. data/webmock.gemspec +8 -7
  93. metadata +153 -88
  94. data/lib/webmock/http_lib_adapters/patron.rb +0 -100
  95. data/spec/other_net_http_libs_spec.rb +0 -30
@@ -1,4 +1,4 @@
1
- # This code is entierly copied from VCR (http://github.com/myronmarston/vcr) by courtesy of Myron Marston
1
+ # This code is entierly copied from VCR (http://github.com/myronmarston/vcr) by courtesy of Myron Marston
2
2
 
3
3
  # A Net::HTTP response that has already been read raises an IOError when #read_body
4
4
  # is called with a destination string or block.
@@ -11,22 +11,22 @@
11
11
  # after it has aleady been read. This attemps to preserve the behavior of
12
12
  # #read_body, acting just as if it had never been read.
13
13
 
14
- module WebMock
15
- module Net
16
- module HTTPResponse
17
- def read_body(dest = nil, &block)
18
- return super if @__read_body_previously_called
19
- return @body if dest.nil? && block.nil?
20
- raise ArgumentError.new("both arg and block given for HTTP method") if dest && block
21
- return nil if @body.nil?
22
14
 
23
- dest ||= ::Net::ReadAdapter.new(block)
24
- dest << @body
25
- @body = dest
26
- ensure
27
- # allow subsequent calls to #read_body to proceed as normal, without our hack...
28
- @__read_body_previously_called = true
29
- end
15
+ module Net
16
+ module WebMockHTTPResponse
17
+ def read_body(dest = nil, &block)
18
+ return super if @__read_body_previously_called
19
+ return @body if dest.nil? && block.nil?
20
+ raise ArgumentError.new("both arg and block given for HTTP method") if dest && block
21
+ return nil if @body.nil?
22
+
23
+ dest ||= ::Net::ReadAdapter.new(block)
24
+ dest << @body
25
+ @body = dest
26
+ ensure
27
+ # allow subsequent calls to #read_body to proceed as normal, without our hack...
28
+ @__read_body_previously_called = true
30
29
  end
31
30
  end
32
- end
31
+ end
32
+
@@ -0,0 +1,124 @@
1
+ begin
2
+ require 'patron'
3
+ rescue LoadError
4
+ # patron not found
5
+ end
6
+
7
+ if defined?(::Patron)
8
+ module WebMock
9
+ module HttpLibAdapters
10
+ class PatronAdapter < ::WebMock::HttpLibAdapter
11
+ adapter_for :patron
12
+
13
+ OriginalPatronSession = ::Patron::Session unless const_defined?(:OriginalPatronSession)
14
+
15
+ class WebMockPatronSession < ::Patron::Session
16
+ def handle_request_with_webmock(req)
17
+ request_signature =
18
+ WebMock::HttpLibAdapters::PatronAdapter.build_request_signature(req)
19
+
20
+ WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
21
+
22
+ if WebMock::StubRegistry.instance.registered_request?(request_signature)
23
+ webmock_response =
24
+ WebMock::StubRegistry.instance.response_for_request(request_signature)
25
+ WebMock::HttpLibAdapters::PatronAdapter.
26
+ handle_file_name(req, webmock_response)
27
+ res = WebMock::HttpLibAdapters::PatronAdapter.
28
+ build_patron_response(webmock_response)
29
+ WebMock::CallbackRegistry.invoke_callbacks(
30
+ {:lib => :patron}, request_signature, webmock_response)
31
+ res
32
+ elsif WebMock.net_connect_allowed?(request_signature.uri)
33
+ res = handle_request_without_webmock(req)
34
+ if WebMock::CallbackRegistry.any_callbacks?
35
+ webmock_response = WebMock::HttpLibAdapters::PatronAdapter.
36
+ build_webmock_response(res)
37
+ WebMock::CallbackRegistry.invoke_callbacks(
38
+ {:lib => :patron, :real_request => true}, request_signature,
39
+ webmock_response)
40
+ end
41
+ res
42
+ else
43
+ raise WebMock::NetConnectNotAllowedError.new(request_signature)
44
+ end
45
+ end
46
+
47
+ alias_method :handle_request_without_webmock, :handle_request
48
+ alias_method :handle_request, :handle_request_with_webmock
49
+ end
50
+
51
+ def self.enable!
52
+ Patron.send(:remove_const, :Session)
53
+ Patron.send(:const_set, :Session, WebMockPatronSession)
54
+ end
55
+
56
+ def self.disable!
57
+ Patron.send(:remove_const, :Session)
58
+ Patron.send(:const_set, :Session, OriginalPatronSession)
59
+ end
60
+
61
+ def self.handle_file_name(req, webmock_response)
62
+ if req.action == :get && req.file_name
63
+ begin
64
+ File.open(req.file_name, "w") do |f|
65
+ f.write webmock_response.body
66
+ end
67
+ rescue Errno::EACCES
68
+ raise ArgumentError.new("Unable to open specified file.")
69
+ end
70
+ end
71
+ end
72
+
73
+ def self.build_request_signature(req)
74
+ uri = WebMock::Util::URI.heuristic_parse(req.url)
75
+ uri.path = uri.normalized_path.gsub("[^:]//","/")
76
+ uri.user = req.username
77
+ uri.password = req.password
78
+
79
+ if [:put, :post].include?(req.action)
80
+ if req.file_name
81
+ if !File.exist?(req.file_name) || !File.readable?(req.file_name)
82
+ raise ArgumentError.new("Unable to open specified file.")
83
+ end
84
+ request_body = File.read(req.file_name)
85
+ elsif req.upload_data
86
+ request_body = req.upload_data
87
+ else
88
+ raise ArgumentError.new("Must provide either data or a filename when doing a PUT or POST")
89
+ end
90
+ end
91
+
92
+ request_signature = WebMock::RequestSignature.new(
93
+ req.action,
94
+ uri.to_s,
95
+ :body => request_body,
96
+ :headers => req.headers
97
+ )
98
+ request_signature
99
+ end
100
+
101
+ def self.build_patron_response(webmock_response)
102
+ raise ::Patron::TimeoutError if webmock_response.should_timeout
103
+ webmock_response.raise_error_if_any
104
+ res = ::Patron::Response.new
105
+ res.instance_variable_set(:@body, webmock_response.body)
106
+ res.instance_variable_set(:@status, webmock_response.status[0])
107
+ res.instance_variable_set(:@status_line, webmock_response.status[1])
108
+ res.instance_variable_set(:@headers, webmock_response.headers)
109
+ res
110
+ end
111
+
112
+ def self.build_webmock_response(patron_response)
113
+ webmock_response = WebMock::Response.new
114
+ reason = patron_response.status_line.
115
+ scan(%r(\AHTTP/(\d+\.\d+)\s+(\d\d\d)\s*([^\r\n]+)?))[0][2]
116
+ webmock_response.status = [patron_response.status, reason]
117
+ webmock_response.body = patron_response.body
118
+ webmock_response.headers = patron_response.headers
119
+ webmock_response
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,166 @@
1
+ begin
2
+ require 'typhoeus'
3
+ rescue LoadError
4
+ # typhoeus not found
5
+ end
6
+
7
+ if defined?(Typhoeus)
8
+
9
+ module WebMock
10
+ module HttpLibAdapters
11
+ class TyphoeusAdapter < HttpLibAdapter
12
+ adapter_for :typhoeus
13
+
14
+ def self.enable!
15
+ @disabled = false
16
+ add_after_request_callback
17
+ ::Typhoeus::Hydra.allow_net_connect = true
18
+ end
19
+
20
+ def self.disable!
21
+ @disabled = true
22
+ remove_after_request_callback
23
+ ::Typhoeus::Hydra.allow_net_connect = true
24
+ end
25
+
26
+ def self.disabled?
27
+ !!@disabled
28
+ end
29
+
30
+ def self.add_after_request_callback
31
+ unless Typhoeus::Hydra.
32
+ global_hooks[:after_request_before_on_complete].
33
+ include?(AFTER_REQUEST_CALLBACK)
34
+ Typhoeus::Hydra.
35
+ global_hooks[:after_request_before_on_complete] << AFTER_REQUEST_CALLBACK
36
+ end
37
+ end
38
+
39
+ def self.remove_after_request_callback
40
+ Typhoeus::Hydra.global_hooks[:after_request_before_on_complete].
41
+ delete_if {|v| v == AFTER_REQUEST_CALLBACK }
42
+ end
43
+
44
+ def self.build_request_signature(req)
45
+ uri = WebMock::Util::URI.heuristic_parse(req.url)
46
+ uri.path = uri.normalized_path.gsub("[^:]//","/")
47
+ if req.username
48
+ uri.user = req.username
49
+ uri.password = req.password
50
+ end
51
+
52
+ request_signature = WebMock::RequestSignature.new(
53
+ req.method,
54
+ uri.to_s,
55
+ :body => req.body,
56
+ :headers => req.headers
57
+ )
58
+ request_signature
59
+ end
60
+
61
+ def self.build_webmock_response(typhoeus_response)
62
+ webmock_response = WebMock::Response.new
63
+ webmock_response.status = [typhoeus_response.code, typhoeus_response.status_message]
64
+ webmock_response.body = typhoeus_response.body
65
+ webmock_response.headers = typhoeus_response.headers_hash
66
+ webmock_response
67
+ end
68
+
69
+ def self.stub_typhoeus(request_signature, webmock_response, typhoeus)
70
+ response = if webmock_response.should_timeout
71
+ ::Typhoeus::Response.new(
72
+ :code => 0,
73
+ :status_message => "",
74
+ :body => "",
75
+ :headers_hash => {}
76
+ )
77
+ else
78
+ ::Typhoeus::Response.new(
79
+ :code => webmock_response.status[0],
80
+ :status_message => webmock_response.status[1],
81
+ :body => webmock_response.body,
82
+ :headers_hash => webmock_response.headers
83
+ )
84
+ end
85
+
86
+
87
+ typhoeus.stub(
88
+ request_signature.method || :any,
89
+ /.*/,
90
+ :webmock_stub => true
91
+ ).and_return(response)
92
+ end
93
+
94
+ def self.request_hash(request_signature)
95
+ hash = {}
96
+
97
+ hash[:body] = request_signature.body
98
+ hash[:headers] = request_signature.headers
99
+
100
+ hash
101
+ end
102
+
103
+ AFTER_REQUEST_CALLBACK = Proc.new do |request|
104
+ request_signature =
105
+ ::WebMock::HttpLibAdapters::TyphoeusAdapter.
106
+ build_request_signature(request)
107
+ webmock_response =
108
+ ::WebMock::HttpLibAdapters::TyphoeusAdapter.
109
+ build_webmock_response(request.response)
110
+ if request.response.mock?
111
+ WebMock::CallbackRegistry.invoke_callbacks(
112
+ {:lib => :typhoeus},
113
+ request_signature,
114
+ webmock_response
115
+ )
116
+ else
117
+ WebMock::CallbackRegistry.invoke_callbacks(
118
+ {:lib => :typhoeus, :real_request => true},
119
+ request_signature,
120
+ webmock_response
121
+ )
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+
129
+ module Typhoeus
130
+ class Hydra
131
+ def queue_with_webmock(request)
132
+ self.clear_webmock_stubs
133
+
134
+ if WebMock::HttpLibAdapters::TyphoeusAdapter.disabled?
135
+ return queue_without_webmock(request)
136
+ end
137
+
138
+ request_signature =
139
+ ::WebMock::HttpLibAdapters::TyphoeusAdapter.build_request_signature(request)
140
+
141
+ ::WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
142
+
143
+ if ::WebMock::StubRegistry.instance.registered_request?(request_signature)
144
+ webmock_response =
145
+ ::WebMock::StubRegistry.instance.response_for_request(request_signature)
146
+ ::WebMock::HttpLibAdapters::TyphoeusAdapter.
147
+ stub_typhoeus(request_signature, webmock_response, self)
148
+ webmock_response.raise_error_if_any
149
+ elsif !WebMock.net_connect_allowed?(request_signature.uri)
150
+ raise WebMock::NetConnectNotAllowedError.new(request_signature)
151
+ end
152
+ queue_without_webmock(request)
153
+ end
154
+
155
+ alias_method :queue_without_webmock, :queue
156
+ alias_method :queue, :queue_with_webmock
157
+
158
+ def clear_webmock_stubs
159
+ self.stubs = [] unless self.stubs
160
+ self.stubs.delete_if {|s|
161
+ s.instance_variable_get(:@options)[:webmock_stub]
162
+ }
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,15 @@
1
+ require 'minitest/unit'
2
+ require 'webmock'
3
+
4
+ MiniTest::Unit::TestCase.class_eval do
5
+ include WebMock::API
6
+
7
+ alias_method :teardown_without_webmock, :teardown
8
+ def teardown_with_webmock
9
+ teardown_without_webmock
10
+ WebMock.reset!
11
+ end
12
+ alias_method :teardown, :teardown_with_webmock
13
+ end
14
+
15
+ WebMock::AssertionFailure.error_class = MiniTest::Assertion
@@ -0,0 +1,52 @@
1
+ module WebMock
2
+ class RackResponse < Response
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def evaluate(request)
8
+ env = build_rack_env(request)
9
+
10
+ status, headers, response = @app.call(env)
11
+
12
+ Response.new(
13
+ :body => response.join,
14
+ :headers => headers,
15
+ :status => status
16
+ )
17
+ end
18
+
19
+ def build_rack_env(request)
20
+ uri = request.uri
21
+ headers = request.headers || {}
22
+ body = request.body || ''
23
+
24
+ env = {
25
+ # CGI variables specified by Rack
26
+ 'REQUEST_METHOD' => request.method.to_s.upcase,
27
+ 'CONTENT_TYPE' => headers.delete('Content-Type'),
28
+ 'CONTENT_LENGTH' => body.size,
29
+ 'PATH_INFO' => uri.path,
30
+ 'QUERY_STRING' => uri.query || '',
31
+ 'SERVER_NAME' => uri.host
32
+ }
33
+
34
+ # Rack-specific variables
35
+ env['rack.input'] = StringIO.new(body)
36
+ env['rack.version'] = Rack::VERSION
37
+ env['rack.url_scheme'] = uri.scheme
38
+ env['rack.run_once'] = true
39
+ env['rack.session'] = session
40
+
41
+ headers.each do |k, v|
42
+ env["HTTP_#{k.tr('-','_').upcase}"] = v
43
+ end
44
+
45
+ env
46
+ end
47
+
48
+ def session
49
+ @session ||= {}
50
+ end
51
+ end
52
+ end
@@ -2,6 +2,8 @@ module WebMock
2
2
 
3
3
  class RequestPattern
4
4
 
5
+ attr_reader :method_pattern, :uri_pattern, :body_pattern, :headers_pattern
6
+
5
7
  def initialize(method, uri, options = {})
6
8
  @method_pattern = MethodPattern.new(method)
7
9
  @uri_pattern = create_uri_pattern(uri)
@@ -40,7 +42,7 @@ module WebMock
40
42
  @headers_pattern = HeadersPattern.new(options[:headers]) if options.has_key?(:headers)
41
43
  @uri_pattern.add_query_params(options[:query]) if options.has_key?(:query)
42
44
  end
43
-
45
+
44
46
  def create_uri_pattern(uri)
45
47
  if uri.is_a?(Regexp)
46
48
  URIRegexpPattern.new(uri)
@@ -143,7 +145,7 @@ module WebMock
143
145
 
144
146
  case BODY_FORMATS[content_type]
145
147
  when :json then
146
- matching_hashes?(Crack::JSON.parse(body), @pattern)
148
+ matching_hashes?(WebMock::Util::JSON.parse(body), @pattern)
147
149
  when :xml then
148
150
  matching_hashes?(Crack::XML.parse(body), @pattern)
149
151
  else
@@ -33,6 +33,10 @@ module WebMock
33
33
  self.to_s == other.to_s
34
34
  end
35
35
 
36
+ def url_encoded?
37
+ headers && headers['Content-Type'] == 'application/x-www-form-urlencoded'
38
+ end
39
+
36
40
  private
37
41
 
38
42
  def assign_options(options)
@@ -23,13 +23,17 @@ module WebMock
23
23
  self
24
24
  end
25
25
 
26
+ def to_rack(app, options={})
27
+ @responses_sequences << ResponsesSequence.new([RackResponse.new(app)])
28
+ end
29
+
26
30
  def to_raise(*exceptions)
27
- @responses_sequences << ResponsesSequence.new([*exceptions].flatten.map {|e|
31
+ @responses_sequences << ResponsesSequence.new([*exceptions].flatten.map {|e|
28
32
  ResponseFactory.response_for(:exception => e)
29
33
  })
30
34
  self
31
35
  end
32
-
36
+
33
37
  def to_timeout
34
38
  @responses_sequences << ResponsesSequence.new([ResponseFactory.response_for(:should_timeout => true)])
35
39
  self
@@ -46,6 +50,10 @@ module WebMock
46
50
  end
47
51
  end
48
52
 
53
+ def has_responses?
54
+ !@responses_sequences.empty?
55
+ end
56
+
49
57
  def then
50
58
  self
51
59
  end
@@ -60,5 +68,30 @@ module WebMock
60
68
  self
61
69
  end
62
70
 
71
+ def matches?(request_signature)
72
+ self.request_pattern.matches?(request_signature)
73
+ end
74
+
75
+ def to_s
76
+ self.request_pattern.to_s
77
+ end
78
+
79
+ def self.from_request_signature(signature)
80
+ stub = self.new(signature.method.to_sym, signature.uri.to_s)
81
+
82
+ if signature.body.to_s != ''
83
+ body = if signature.url_encoded?
84
+ Addressable::URI.parse('?' + signature.body).query_values
85
+ else
86
+ signature.body
87
+ end
88
+ stub.with(:body => body)
89
+ end
90
+
91
+ if (signature.headers && !signature.headers.empty?)
92
+ stub.with(:headers => signature.headers)
93
+ end
94
+ stub
95
+ end
63
96
  end
64
97
  end