typhoeus 0.1.31 → 0.2.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.
@@ -0,0 +1,58 @@
1
+ module Typhoeus
2
+ class NormalizedHeaderHash < ::Hash
3
+ def initialize(constructor = {})
4
+ if constructor.is_a?(Hash)
5
+ super
6
+ update(constructor)
7
+ else
8
+ super(constructor)
9
+ end
10
+ end
11
+
12
+ def fetch(key, *extras)
13
+ super(convert_key(key), *extras)
14
+ end
15
+
16
+ def key?(key)
17
+ super(convert_key(key))
18
+ end
19
+
20
+ [:include?, :has_key?, :member?].each do |method|
21
+ alias_method method, :key?
22
+ end
23
+
24
+ def [](key)
25
+ super(convert_key(key))
26
+ end
27
+
28
+ def []=(key, value)
29
+ super(convert_key(key), value)
30
+ end
31
+
32
+ def update(other_hash)
33
+ other_hash.each_pair do |key, value|
34
+ self[convert_key(key)] = value
35
+ end
36
+ self
37
+ end
38
+
39
+ alias_method :merge!, :update
40
+
41
+ def dup
42
+ self.class.new(self)
43
+ end
44
+
45
+ def merge(hash)
46
+ self.dup.update(hash)
47
+ end
48
+
49
+ def delete(key)
50
+ super(convert_key(key))
51
+ end
52
+
53
+ private
54
+ def convert_key(key)
55
+ key.to_s.split(/_|-/).map { |segment| segment.capitalize }.join("-")
56
+ end
57
+ end
58
+ end
@@ -1,9 +1,15 @@
1
+ require 'uri'
2
+
1
3
  module Typhoeus
2
4
  class Request
3
- attr_accessor :method, :params, :body, :headers, :connect_timeout, :timeout, :user_agent, :response, :cache_timeout, :follow_location, :max_redirects, :proxy, :disable_ssl_peer_verification, :ssl_cert, :ssl_cert_type, :ssl_key, :ssl_key_type, :ssl_key_password, :ssl_cacert, :ssl_capath, :verbose, :username, :password,
4
- :auth_method
5
-
6
5
  attr_reader :url
6
+ attr_writer :headers
7
+ attr_accessor :method, :params, :body, :connect_timeout, :timeout,
8
+ :user_agent, :response, :cache_timeout, :follow_location,
9
+ :max_redirects, :proxy, :disable_ssl_peer_verification,
10
+ :ssl_cert, :ssl_cert_type, :ssl_key, :ssl_key_type,
11
+ :ssl_key_password, :ssl_cacert, :ssl_capath, :verbose,
12
+ :username, :password, :auth_method, :user_agent
7
13
 
8
14
  # Initialize a new Request
9
15
  #
@@ -64,11 +70,18 @@ module Typhoeus
64
70
  else
65
71
  @url = @params ? "#{url}?#{params_string}" : url
66
72
  end
73
+
74
+ @parsed_uri = URI.parse(@url)
75
+
67
76
  @on_complete = nil
68
77
  @after_complete = nil
69
78
  @handled_response = nil
70
79
  end
71
80
 
81
+ def localhost?
82
+ %(localhost 127.0.0.1 0.0.0.0).include?(@parsed_uri.host)
83
+ end
84
+
72
85
  def host
73
86
  slash_location = @url.index('/', 8)
74
87
  if slash_location
@@ -88,12 +101,12 @@ module Typhoeus
88
101
  params.keys.sort { |a, b| a.to_s <=> b.to_s }.collect do |k|
89
102
  value = params[k]
90
103
  if value.is_a? Hash
91
- value.keys.collect {|sk| Rack::Utils.escape("#{k}[#{sk}]") + "=" + Rack::Utils.escape(value[sk].to_s)}
104
+ value.keys.collect {|sk| Typhoeus::Utils.escape("#{k}[#{sk}]") + "=" + Typhoeus::Utils.escape(value[sk].to_s)}
92
105
  elsif value.is_a? Array
93
- key = Rack::Utils.escape(k.to_s)
94
- value.collect { |v| "#{key}=#{Rack::Utils.escape(v.to_s)}" }.join('&')
106
+ key = Typhoeus::Utils.escape(k.to_s)
107
+ value.collect { |v| "#{key}[]=#{Typhoeus::Utils.escape(v.to_s)}" }.join('&')
95
108
  else
96
- "#{Rack::Utils.escape(k.to_s)}=#{Rack::Utils.escape(params[k].to_s)}"
109
+ "#{Typhoeus::Utils.escape(k.to_s)}=#{Typhoeus::Utils.escape(params[k].to_s)}"
97
110
  end
98
111
  end.flatten.join("&")
99
112
  end
@@ -122,7 +135,7 @@ module Typhoeus
122
135
  end
123
136
 
124
137
  def call_after_complete
125
- @after_complete.call(@handled_response) if @after_complete
138
+ @after_complete.call(@handled_response) if @after_complete
126
139
  end
127
140
 
128
141
  def handled_response=(val)
@@ -133,6 +146,24 @@ module Typhoeus
133
146
  @handled_response || response
134
147
  end
135
148
 
149
+ def inspect
150
+ result = ":method => #{self.method.inspect},\n" <<
151
+ "\t:url => #{URI.parse(self.url).to_s}"
152
+ if self.body and !self.body.empty?
153
+ result << ",\n\t:body => #{self.body.inspect}"
154
+ end
155
+
156
+ if self.params and !self.params.empty?
157
+ result << ",\n\t:params => #{self.params.inspect}"
158
+ end
159
+
160
+ if self.headers and !self.headers.empty?
161
+ result << ",\n\t:headers => #{self.headers.inspect}"
162
+ end
163
+
164
+ result
165
+ end
166
+
136
167
  def cache_key
137
168
  Digest::SHA1.hexdigest(url)
138
169
  end
@@ -1,14 +1,17 @@
1
1
  module Typhoeus
2
2
  class Response
3
- attr_accessor :request
3
+ attr_accessor :request, :mock
4
4
  attr_reader :code, :headers, :body, :time,
5
5
  :requested_url, :requested_remote_method,
6
6
  :requested_http_method, :start_time,
7
7
  :effective_url
8
+ attr_writer :headers_hash
8
9
 
9
10
  def initialize(params = {})
10
11
  @code = params[:code]
11
- @headers = params[:headers]
12
+ @status_message = params[:status_message]
13
+ @http_version = params[:http_version]
14
+ @headers = params[:headers] || ''
12
15
  @body = params[:body]
13
16
  @time = params[:time]
14
17
  @requested_url = params[:requested_url]
@@ -16,28 +19,46 @@ module Typhoeus
16
19
  @start_time = params[:start_time]
17
20
  @request = params[:request]
18
21
  @effective_url = params[:effective_url]
22
+ @mock = params[:mock] || false # default
23
+ @headers_hash = NormalizedHeaderHash.new(params[:headers_hash]) if params[:headers_hash]
24
+ end
25
+
26
+ # Returns true if this is a mock response.
27
+ def mock?
28
+ @mock
19
29
  end
20
30
 
21
31
  def headers_hash
22
- headers.split("\n").map {|o| o.strip}.inject({}) do |hash, o|
23
- if o.empty?
24
- hash
25
- else
26
- i = o.index(":") || o.size
27
- key = o.slice(0, i)
28
- value = o.slice(i + 1, o.size)
29
- value = value.strip unless value.nil?
30
- if hash.has_key? key
31
- hash[key] = [hash[key], value].flatten
32
+ @headers_hash ||= begin
33
+ headers.split("\n").map {|o| o.strip}.inject(Typhoeus::NormalizedHeaderHash.new) do |hash, o|
34
+ if o.empty? || o =~ /^HTTP\/[\d\.]+/
35
+ hash
32
36
  else
33
- hash[key] = value
34
- end
37
+ i = o.index(":") || o.size
38
+ key = o.slice(0, i)
39
+ value = o.slice(i + 1, o.size)
40
+ value = value.strip unless value.nil?
41
+ if hash.has_key? key
42
+ hash[key] = [hash[key], value].flatten
43
+ else
44
+ hash[key] = value
45
+ end
35
46
 
36
- hash
47
+ hash
48
+ end
37
49
  end
38
50
  end
39
51
  end
40
52
 
53
+ def status_message
54
+ # http://rubular.com/r/eAr1oVYsVa
55
+ @status_message ||= first_header_line ? first_header_line[/\d{3} (.*)$/, 1].chomp : nil
56
+ end
57
+
58
+ def http_version
59
+ @http_version ||= first_header_line ? first_header_line[/HTTP\/(\S+)/, 1] : nil
60
+ end
61
+
41
62
  def success?
42
63
  @code >= 200 && @code < 300
43
64
  end
@@ -45,5 +66,11 @@ module Typhoeus
45
66
  def modified?
46
67
  @code != 304
47
68
  end
69
+
70
+ private
71
+
72
+ def first_header_line
73
+ @first_header_line ||= headers.split("\n").first
74
+ end
48
75
  end
49
76
  end
@@ -0,0 +1,24 @@
1
+ module Typhoeus
2
+ module Utils
3
+ # Taken from Rack::Utils, 1.2.1 to remove Rack dependency.
4
+ def escape(s)
5
+ s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/u) {
6
+ '%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
7
+ }.tr(' ', '+')
8
+ end
9
+ module_function :escape
10
+
11
+ # Return the bytesize of String; uses String#size under Ruby 1.8 and
12
+ # String#bytesize under 1.9.
13
+ if ''.respond_to?(:bytesize)
14
+ def bytesize(string)
15
+ string.bytesize
16
+ end
17
+ else
18
+ def bytesize(string)
19
+ string.size
20
+ end
21
+ end
22
+ module_function :bytesize
23
+ end
24
+ end
data/spec/spec_helper.rb CHANGED
@@ -8,4 +8,4 @@ begin require "redgreen" unless ENV['TM_CURRENT_LINE']; rescue LoadError; end
8
8
  path = File.expand_path(File.dirname(__FILE__) + "/../lib/")
9
9
  $LOAD_PATH.unshift(path) unless $LOAD_PATH.include?(path)
10
10
 
11
- require "lib/typhoeus"
11
+ require path + '/typhoeus'
@@ -0,0 +1,300 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+
3
+ describe Typhoeus::HydraMock do
4
+ it "should mark all responses as mocks" do
5
+ response = Typhoeus::Response.new(:mock => false)
6
+ response.should_not be_mock
7
+
8
+ mock = Typhoeus::HydraMock.new("http://localhost", :get)
9
+ mock.and_return(response)
10
+
11
+ mock.response.should be_mock
12
+ response.should be_mock
13
+ end
14
+
15
+ describe "stubbing response values" do
16
+ before(:each) do
17
+ @stub = Typhoeus::HydraMock.new('http://localhost:3000', :get)
18
+ end
19
+
20
+ describe "with a single response" do
21
+ it "should always return that response" do
22
+ response = Typhoeus::Response.new
23
+ @stub.and_return(response)
24
+
25
+ 5.times do
26
+ @stub.response.should == response
27
+ end
28
+ end
29
+ end
30
+
31
+ describe "with multiple responses" do
32
+ it "should return consecutive responses in the array, then keep returning the last one" do
33
+ responses = []
34
+ 3.times do |i|
35
+ responses << Typhoeus::Response.new(:body => "response #{i}")
36
+ end
37
+
38
+ # Stub 3 consecutive responses.
39
+ @stub.and_return(responses)
40
+
41
+ 0.upto(2) do |i|
42
+ @stub.response.should == responses[i]
43
+ end
44
+
45
+ 5.times do
46
+ @stub.response.should == responses.last
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ describe "#matches?" do
53
+ describe "basic matching" do
54
+ it "should not match if the HTTP verbs are different" do
55
+ request = Typhoeus::Request.new("http://localhost:3000",
56
+ :method => :get)
57
+ mock = Typhoeus::HydraMock.new("http://localhost:3000", :post)
58
+ mock.matches?(request).should be_false
59
+ end
60
+ end
61
+
62
+ describe "matching on ports" do
63
+ it "should handle default port 80 sanely" do
64
+ mock = Typhoeus::HydraMock.new('http://www.example.com:80/', :get,
65
+ :headers => { 'user-agent' => 'test' })
66
+ request = Typhoeus::Request.new('http://www.example.com/',
67
+ :method => :get,
68
+ :user_agent => 'test')
69
+ mock.matches?(request).should be_true
70
+ end
71
+
72
+ it "should handle default port 443 sanely" do
73
+ mock = Typhoeus::HydraMock.new('https://www.example.com:443/', :get,
74
+ :headers => { 'user-agent' => 'test' })
75
+ request = Typhoeus::Request.new('https://www.example.com/',
76
+ :method => :get,
77
+ :user_agent => 'test')
78
+ mock.matches?(request).should be_true
79
+ end
80
+ end
81
+
82
+
83
+ describe "any HTTP verb" do
84
+ it "should match any verb" do
85
+ mock = Typhoeus::HydraMock.new("http://localhost:3000", :any,
86
+ :headers => { 'user-agent' => 'test' })
87
+ [:get, :post, :delete, :put].each do |verb|
88
+ request = Typhoeus::Request.new("http://localhost:3000",
89
+ :method => verb,
90
+ :user_agent => 'test')
91
+ mock.matches?(request).should be_true
92
+ end
93
+ end
94
+ end
95
+
96
+ describe "header matching" do
97
+ def request(options = {})
98
+ Typhoeus::Request.new("http://localhost:3000", options.merge(:method => :get))
99
+ end
100
+
101
+ def mock(options = {})
102
+ Typhoeus::HydraMock.new("http://localhost:3000", :get, options)
103
+ end
104
+
105
+ context 'when no :headers option is given' do
106
+ subject { mock }
107
+
108
+ it "matches regardless of whether or not the request has headers" do
109
+ subject.matches?(request(:headers => nil)).should be_true
110
+ subject.matches?(request(:headers => {})).should be_true
111
+ subject.matches?(request(:headers => { 'a' => 'b' })).should be_true
112
+ end
113
+ end
114
+
115
+ [nil, {}].each do |value|
116
+ context "for :headers => #{value.inspect}" do
117
+ subject { mock(:headers => value) }
118
+
119
+ it "matches when the request has no headers" do
120
+ subject.matches?(request(:headers => nil)).should be_true
121
+ subject.matches?(request(:headers => {})).should be_true
122
+ end
123
+
124
+ it "does not match when the request has headers" do
125
+ subject.matches?(request(:headers => { 'a' => 'b' })).should be_false
126
+ end
127
+ end
128
+ end
129
+
130
+ context 'for :headers => [a hash]' do
131
+ it 'does not match if the request has no headers' do
132
+ m = mock(:headers => { 'A' => 'B', 'C' => 'D' })
133
+
134
+ m.matches?(request).should be_false
135
+ m.matches?(request(:headers => nil)).should be_false
136
+ m.matches?(request(:headers => {})).should be_false
137
+ end
138
+
139
+ it 'does not match if the request lacks any of the given headers' do
140
+ mock(
141
+ :headers => { 'A' => 'B', 'C' => 'D' }
142
+ ).matches?(request(
143
+ :headers => { 'A' => 'B' }
144
+ )).should be_false
145
+ end
146
+
147
+ it 'does not match if any of the specified values are different from the request value' do
148
+ mock(
149
+ :headers => { 'A' => 'B', 'C' => 'D' }
150
+ ).matches?(request(
151
+ :headers => { 'A' => 'B', 'C' => 'E' }
152
+ )).should be_false
153
+ end
154
+
155
+ it 'matches if the given hash is exactly equal to the request headers' do
156
+ mock(
157
+ :headers => { 'A' => 'B', 'C' => 'D' }
158
+ ).matches?(request(
159
+ :headers => { 'A' => 'B', 'C' => 'D' }
160
+ )).should be_true
161
+ end
162
+
163
+ it 'matches even if the request has additional headers not specified in the mock' do
164
+ mock(
165
+ :headers => { 'A' => 'B', 'C' => 'D' }
166
+ ).matches?(request(
167
+ :headers => { 'A' => 'B', 'C' => 'D', 'E' => 'F' }
168
+ )).should be_true
169
+ end
170
+
171
+ it 'matches even if the casing of the header keys is different between the mock and request' do
172
+ mock(
173
+ :headers => { 'A' => 'B', 'c' => 'D' }
174
+ ).matches?(request(
175
+ :headers => { 'a' => 'B', 'C' => 'D' }
176
+ )).should be_true
177
+ end
178
+
179
+ it 'matches if the mocked values are regexes and match the request values' do
180
+ mock(
181
+ :headers => { 'A' => /foo/, }
182
+ ).matches?(request(
183
+ :headers => { 'A' => 'foo bar' }
184
+ )).should be_true
185
+ end
186
+
187
+ it 'does not match if the mocked values are regexes and do not match the request values' do
188
+ mock(
189
+ :headers => { 'A' => /foo/, }
190
+ ).matches?(request(
191
+ :headers => { 'A' => 'bar' }
192
+ )).should be_false
193
+ end
194
+
195
+ context 'when a header is specified as an array' do
196
+ it 'matches when the request header has the same array' do
197
+ mock(
198
+ :headers => { 'Accept' => ['text/html', 'text/plain'] }
199
+ ).matches?(request(
200
+ :headers => { 'Accept' => ['text/html', 'text/plain'] }
201
+ )).should be_true
202
+ end
203
+
204
+ it 'matches when the request header is a single value and the mock array has the same value' do
205
+ mock(
206
+ :headers => { 'Accept' => ['text/html'] }
207
+ ).matches?(request(
208
+ :headers => { 'Accept' => 'text/html' }
209
+ )).should be_true
210
+ end
211
+
212
+ it 'matches even when the request header array is ordered differently' do
213
+ mock(
214
+ :headers => { 'Accept' => ['text/html', 'text/plain'] }
215
+ ).matches?(request(
216
+ :headers => { 'Accept' => ['text/plain', 'text/html'] }
217
+ )).should be_true
218
+ end
219
+
220
+ it 'does not match when the request header array lacks a value' do
221
+ mock(
222
+ :headers => { 'Accept' => ['text/html', 'text/plain'] }
223
+ ).matches?(request(
224
+ :headers => { 'Accept' => ['text/plain'] }
225
+ )).should be_false
226
+ end
227
+
228
+ it 'does not match when the request header array has an extra value' do
229
+ mock(
230
+ :headers => { 'Accept' => ['text/html', 'text/plain'] }
231
+ ).matches?(request(
232
+ :headers => { 'Accept' => ['text/html', 'text/plain', 'application/xml'] }
233
+ )).should be_false
234
+ end
235
+
236
+ it 'does not match when the request header is not an array' do
237
+ mock(
238
+ :headers => { 'Accept' => ['text/html', 'text/plain'] }
239
+ ).matches?(request(
240
+ :headers => { 'Accept' => 'text/html' }
241
+ )).should be_false
242
+ end
243
+ end
244
+ end
245
+ end
246
+
247
+ describe "post body matching" do
248
+ it "should not bother matching on body if we don't turn the option on" do
249
+ request = Typhoeus::Request.new("http://localhost:3000",
250
+ :method => :get,
251
+ :user_agent => 'test',
252
+ :body => "fdsafdsa")
253
+ mock = Typhoeus::HydraMock.new("http://localhost:3000", :get,
254
+ :headers => { 'user-agent' => 'test' })
255
+ mock.matches?(request).should be_true
256
+ end
257
+
258
+ it "should match nil correctly" do
259
+ request = Typhoeus::Request.new("http://localhost:3000",
260
+ :method => :get,
261
+ :body => "fdsafdsa")
262
+ mock = Typhoeus::HydraMock.new("http://localhost:3000", :get,
263
+ :body => nil)
264
+ mock.matches?(request).should be_false
265
+ end
266
+
267
+ it "should not match if the bodies do not match" do
268
+ request = Typhoeus::Request.new("http://localhost:3000",
269
+ :method => :get,
270
+ :body => "ffdsadsafdsa")
271
+ mock = Typhoeus::HydraMock.new("http://localhost:3000", :get,
272
+ :body => 'fdsafdsa')
273
+ mock.matches?(request).should be_false
274
+ end
275
+
276
+ it "should match on optional body parameter" do
277
+ request = Typhoeus::Request.new("http://localhost:3000",
278
+ :method => :get,
279
+ :user_agent => 'test',
280
+ :body => "fdsafdsa")
281
+ mock = Typhoeus::HydraMock.new("http://localhost:3000", :get,
282
+ :body => 'fdsafdsa',
283
+ :headers => {
284
+ 'User-Agent' => 'test'
285
+ })
286
+ mock.matches?(request).should be_true
287
+ end
288
+
289
+ it "should regex match" do
290
+ request = Typhoeus::Request.new("http://localhost:3000/whatever/fdsa",
291
+ :method => :get,
292
+ :user_agent => 'test')
293
+ mock = Typhoeus::HydraMock.new(/fdsa/, :get,
294
+ :headers => { 'user-agent' => 'test' })
295
+ mock.matches?(request).should be_true
296
+ end
297
+ end
298
+ end
299
+ end
300
+