PageRenderTime 257ms CodeModel.GetById 33ms RepoModel.GetById 14ms app.codeStats 1ms

/test/test_client.rb

https://github.com/earth2marsh/grackle
Ruby | 359 lines | 329 code | 22 blank | 8 comment | 7 complexity | 9b1edbfe0e7142d76f654aa5343b17fe MD5 | raw file
  1. require File.dirname(__FILE__) + '/test_helper'
  2. class TestClient < Test::Unit::TestCase
  3. #Used for mocking HTTP requests
  4. class Net::HTTP
  5. class << self
  6. attr_accessor :response, :request, :last_instance, :responder
  7. end
  8. def connect
  9. # This needs to be overridden so SSL requests can be mocked
  10. end
  11. def request(req)
  12. self.class.last_instance = self
  13. if self.class.responder
  14. self.class.responder.call(self,req)
  15. else
  16. self.class.request = req
  17. self.class.response
  18. end
  19. end
  20. end
  21. class MockProxy < Net::HTTP
  22. class << self
  23. attr_accessor :started
  24. [:response,:request,:last_instance,:responder].each do |m|
  25. class_eval "
  26. def #{m}; Net::HTTP.#{m}; end
  27. def #{m}=(val); Net::HTTP.#{m} = val; end
  28. "
  29. end
  30. end
  31. def start
  32. self.class.started = true
  33. super
  34. end
  35. end
  36. #Mock responses that conform to HTTPResponse's interface
  37. class MockResponse < Net::HTTPResponse
  38. #include Net::HTTPHeader
  39. attr_accessor :code, :body
  40. def initialize(code,body,headers={})
  41. super
  42. self.code = code
  43. self.body = body
  44. headers.each do |name, value|
  45. self[name] = value
  46. end
  47. end
  48. end
  49. #Transport that collects info on requests and responses for testing purposes
  50. class MockTransport < Grackle::Transport
  51. attr_accessor :status, :body, :method, :url, :options, :timeout
  52. def initialize(status,body,headers={})
  53. Net::HTTP.response = MockResponse.new(status,body,headers)
  54. end
  55. def request(method, string_url, options)
  56. self.method = method
  57. self.url = URI.parse(string_url)
  58. self.options = options
  59. super(method,string_url,options)
  60. end
  61. end
  62. class TestHandler
  63. attr_accessor :decode_value
  64. def initialize(value)
  65. self.decode_value = value
  66. end
  67. def decode_response(body)
  68. decode_value
  69. end
  70. end
  71. def test_redirects
  72. redirects = 2 #Check that we can follow 2 redirects before getting to original request
  73. req_count = 0
  74. responder = Proc.new do |inst, req|
  75. req_count += 1
  76. #Store the original request
  77. if req_count == 1
  78. inst.class.request = req
  79. else
  80. assert_equal("/somewhere_else#{req_count-1}.json",req.path)
  81. end
  82. if req_count <= redirects
  83. MockResponse.new(302,"You are being redirected",'location'=>"http://twitter.com/somewhere_else#{req_count}.json")
  84. else
  85. inst.class.response
  86. end
  87. end
  88. with_http_responder(responder) do
  89. test_simple_get_request
  90. end
  91. assert_equal(redirects+1,req_count)
  92. end
  93. def test_timeouts
  94. client = new_client(200,'{"id":12345,"screen_name":"test_user"}')
  95. assert_equal(60, client.timeout)
  96. client.timeout = 30
  97. assert_equal(30, client.timeout)
  98. end
  99. def test_simple_get_request
  100. client = new_client(200,'{"id":12345,"screen_name":"test_user"}')
  101. value = client.users.show.json? :screen_name=>'test_user'
  102. assert_equal(:get,client.transport.method)
  103. assert_equal('http',client.transport.url.scheme)
  104. assert(!Net::HTTP.last_instance.use_ssl?,'Net::HTTP instance should not be set to use SSL')
  105. assert_equal('api.twitter.com',client.transport.url.host)
  106. assert_equal('/1/users/show.json',client.transport.url.path)
  107. assert_equal('test_user',client.transport.options[:params][:screen_name])
  108. assert_equal('screen_name=test_user',Net::HTTP.request.path.split(/\?/)[1])
  109. assert_equal(12345,value.id)
  110. end
  111. def test_simple_post_request_with_basic_auth
  112. client = Grackle::Client.new(:auth=>{:type=>:basic,:username=>'fake_user',:password=>'fake_pass'})
  113. test_simple_post(client) do
  114. assert_match(/Basic/i,Net::HTTP.request['Authorization'],"Request should include Authorization header for basic auth")
  115. end
  116. end
  117. def test_simple_post_request_with_oauth
  118. client = Grackle::Client.new(:auth=>{:type=>:oauth,:consumer_key=>'12345',:consumer_secret=>'abc',:token=>'wxyz',:token_secret=>'98765'})
  119. test_simple_post(client) do
  120. auth = Net::HTTP.request['Authorization']
  121. assert_match(/OAuth/i,auth,"Request should include Authorization header for OAuth")
  122. assert_match(/oauth_consumer_key="12345"/,auth,"Auth header should include consumer key")
  123. assert_match(/oauth_token="wxyz"/,auth,"Auth header should include token")
  124. assert_match(/oauth_signature_method="HMAC-SHA1"/,auth,"Auth header should include HMAC-SHA1 signature method as that's what Twitter supports")
  125. end
  126. end
  127. def test_ssl
  128. client = new_client(200,'[{"id":1,"text":"test 1"}]',:ssl=>true)
  129. client.statuses.public_timeline?
  130. assert_equal("https",client.transport.url.scheme)
  131. assert(Net::HTTP.last_instance.use_ssl?,'Net::HTTP instance should be set to use SSL')
  132. end
  133. def test_ssl_with_ca_cert_file
  134. MockTransport.ca_cert_file = "some_ca_certs.pem"
  135. client = new_client(200,'[{"id":1,"text":"test 1"}]',:ssl=>true)
  136. client.statuses.public_timeline?
  137. assert_equal(OpenSSL::SSL::VERIFY_PEER,Net::HTTP.last_instance.verify_mode,'Net::HTTP instance should use OpenSSL::SSL::VERIFY_PEER mode')
  138. assert_equal(MockTransport.ca_cert_file,Net::HTTP.last_instance.ca_file,'Net::HTTP instance should have cert file set')
  139. end
  140. def test_default_format
  141. client = new_client(200,'[{"id":1,"text":"test 1"}]',:default_format=>:json)
  142. client.statuses.public_timeline?
  143. assert_match(/\.json$/,client.transport.url.path)
  144. client = new_client(200,'<statuses type="array"><status><id>1</id><text>test 1</text></status></statuses>',:default_format=>:xml)
  145. client.statuses.public_timeline?
  146. assert_match(/\.xml$/,client.transport.url.path)
  147. end
  148. def test_api
  149. client = new_client(200,'[{"id":1,"text":"test 1"}]',:api=>:search)
  150. client.search? :q=>'test'
  151. assert_equal('search.twitter.com',client.transport.url.host)
  152. client[:rest].users.show.some_user?
  153. assert_equal('api.twitter.com',client.transport.url.host)
  154. client.api = :search
  155. client.trends?
  156. assert_equal('search.twitter.com',client.transport.url.host)
  157. client.api = :v1
  158. client.search? :q=>'test'
  159. assert_equal('api.twitter.com',client.transport.url.host)
  160. assert_match(%r{^/1/search},client.transport.url.path)
  161. client.api = :rest
  162. client[:v1].users.show.some_user?
  163. assert_equal('api.twitter.com',client.transport.url.host)
  164. assert_match(%r{^/1/users/show/some_user},client.transport.url.path)
  165. end
  166. def test_headers
  167. client = new_client(200,'[{"id":1,"text":"test 1"}]',:headers=>{'User-Agent'=>'TestAgent/1.0','X-Test-Header'=>'Header Value'})
  168. client.statuses.public_timeline?
  169. assert_equal('TestAgent/1.0',Net::HTTP.request['User-Agent'],"Custom User-Agent header should have been set")
  170. assert_equal('Header Value',Net::HTTP.request['X-Test-Header'],"Custom X-Test-Header header should have been set")
  171. end
  172. def test_custom_handlers
  173. client = new_client(200,'[{"id":1,"text":"test 1"}]',:handlers=>{:json=>TestHandler.new(42)})
  174. value = client.statuses.public_timeline.json?
  175. assert_equal(42,value)
  176. end
  177. def test_clear
  178. client = new_client(200,'[{"id":1,"text":"test 1"}]')
  179. client.some.url.that.does.not.exist
  180. assert_equal('/some/url/that/does/not/exist',client.send(:request).path,"An unexecuted path should be built up")
  181. client.clear
  182. assert_equal('',client.send(:request).path,"The path shoudl be cleared")
  183. end
  184. def test_file_param_triggers_multipart_encoding
  185. client = new_client(200,'[{"id":1,"text":"test 1"}]')
  186. client.account.update_profile_image! :image=>File.new(__FILE__)
  187. assert_match(/multipart\/form-data/,Net::HTTP.request['Content-Type'])
  188. end
  189. def test_time_param_is_http_encoded_and_escaped
  190. client = new_client(200,'[{"id":1,"text":"test 1"}]')
  191. time = Time.now-60*60
  192. client.statuses.public_timeline? :since=>time
  193. assert_equal("/1/statuses/public_timeline.json?since=#{CGI::escape(time.httpdate)}",Net::HTTP.request.path)
  194. end
  195. def test_simple_http_method_block
  196. client = new_client(200,'[{"id":1,"text":"test 1"}]')
  197. client.delete { direct_messages.destroy :id=>1, :other=>'value' }
  198. assert_equal(:delete,client.transport.method, "delete block should use delete method")
  199. assert_equal("/1/direct_messages/destroy/1.json",Net::HTTP.request.path)
  200. assert_equal('value',client.transport.options[:params][:other])
  201. client = new_client(200,'{"id":54321,"screen_name":"test_user"}')
  202. value = client.get { users.show.json? :screen_name=>'test_user' }
  203. assert_equal(:get,client.transport.method)
  204. assert_equal('http',client.transport.url.scheme)
  205. assert(!Net::HTTP.last_instance.use_ssl?,'Net::HTTP instance should not be set to use SSL')
  206. assert_equal('api.twitter.com',client.transport.url.host)
  207. assert_equal('/1/users/show.json',client.transport.url.path)
  208. assert_equal('test_user',client.transport.options[:params][:screen_name])
  209. assert_equal('screen_name=test_user',Net::HTTP.request.path.split(/\?/)[1])
  210. assert_equal(54321,value.id)
  211. end
  212. def test_http_method_blocks_choose_right_method
  213. client = new_client(200,'[{"id":1,"text":"test 1"}]')
  214. client.get { search :q=>'test' }
  215. assert_equal(:get,client.transport.method, "Get block should choose get method")
  216. client.delete { direct_messages.destroy :id=>1 }
  217. assert_equal(:delete,client.transport.method, "Delete block should choose delete method")
  218. client.post { direct_messages.destroy :id=>1 }
  219. assert_equal(:post,client.transport.method, "Post block should choose post method")
  220. client.put { direct_messages :id=>1 }
  221. assert_equal(:put,client.transport.method, "Put block should choose put method")
  222. end
  223. def test_http_method_selection_precedence
  224. client = new_client(200,'[{"id":1,"text":"test 1"}]')
  225. client.get { search! :q=>'test' }
  226. assert_equal(:get,client.transport.method, "Get block should override method even if post bang is used")
  227. client.delete { search? :q=>'test', :__method=>:post }
  228. assert_equal(:post,client.transport.method, ":__method=>:post should override block setting and method suffix")
  229. end
  230. def test_underscore_method_works_with_numbers
  231. client = new_client(200,'{"id":12345,"screen_name":"test_user"}')
  232. value = client.users.show._(12345).json?
  233. assert_equal(:get,client.transport.method)
  234. assert_equal('http',client.transport.url.scheme)
  235. assert(!Net::HTTP.last_instance.use_ssl?,'Net::HTTP instance should not be set to use SSL')
  236. assert_equal('api.twitter.com',client.transport.url.host)
  237. assert_equal('/1/users/show/12345.json',client.transport.url.path)
  238. assert_equal(12345,value.id)
  239. end
  240. def test_transport_proxy_setting_is_used
  241. client = new_client(200,'{"id":12345,"screen_name":"test_user"}')
  242. called = false
  243. call_trans = nil
  244. client.transport.proxy = Proc.new {|trans| call_trans = trans; called = true; MockProxy }
  245. client.users.show._(12345).json?
  246. assert(called,"Proxy proc should be called during request")
  247. assert(MockProxy.started,"Proxy should have been called")
  248. assert_equal(client.transport,call_trans,"Proxy should have been called with transport")
  249. MockProxy.started = false
  250. client.transport.proxy = MockProxy
  251. client.users.show._(12345).json?
  252. assert(MockProxy.started,"Proxy should have been called")
  253. MockProxy.started = false
  254. client.transport.proxy = nil
  255. assert_equal(false,MockProxy.started,"Proxy should not have been called")
  256. end
  257. def test_auto_append_ids_is_honored
  258. client = new_client(200,'{"id":12345,"screen_name":"test_user"}')
  259. client.users.show.json? :id=>12345
  260. assert_equal('/1/users/show/12345.json',client.transport.url.path,"Id should be appended by default")
  261. client.auto_append_ids = false
  262. client.users.show.json? :id=>12345
  263. assert_equal('/1/users/show.json',client.transport.url.path,"Id should not be appended")
  264. assert_equal(12345,client.transport.options[:params][:id], "Id should be treated as a parameter")
  265. assert_equal("id=#{12345}",Net::HTTP.request.path.split(/\?/)[1],"id should be part of the query string")
  266. end
  267. def test_auto_append_ids_can_be_set_in_constructor
  268. client = new_client(200,'{"id":12345,"screen_name":"test_user"}',:auto_append_ids=>false)
  269. client.users.show.json? :id=>12345
  270. assert_equal('/1/users/show.json',client.transport.url.path,"Id should not be appended")
  271. assert_equal(12345,client.transport.options[:params][:id], "Id should be treated as a parameter")
  272. assert_equal("id=#{12345}",Net::HTTP.request.path.split(/\?/)[1],"id should be part of the query string")
  273. end
  274. def test_default_api
  275. client = Grackle::Client.new
  276. assert_equal(:v1,client.api,":v1 should be default api")
  277. end
  278. # Methods like Twitter's DELETE list membership expect that the user id will
  279. # be form encoded like a POST request in the body. Net::HTTP seems to think
  280. # that DELETEs can't have body parameters so we have to work around that.
  281. def test_delete_can_send_body_parameters
  282. client = new_client(200,'{"id":12345,"name":"Test List","members":0}')
  283. client.delete { some_user.some_list.members? :user_id=>12345 }
  284. assert_equal(:delete,client.transport.method,"Expected delete request")
  285. assert_equal('http',client.transport.url.scheme,"Expected scheme to be http")
  286. assert_equal('api.twitter.com',client.transport.url.host,"Expected request to be against twitter.com")
  287. assert_equal('/1/some_user/some_list/members.json',client.transport.url.path)
  288. assert_match(/user_id=12345/,Net::HTTP.request.body,"Parameters should be form encoded")
  289. end
  290. private
  291. def with_http_responder(responder)
  292. Net::HTTP.responder = responder
  293. yield
  294. ensure
  295. Net::HTTP.responder = nil
  296. end
  297. def new_client(response_status, response_body, client_opts={})
  298. client = Grackle::Client.new(client_opts)
  299. client.transport = MockTransport.new(response_status,response_body)
  300. client
  301. end
  302. def test_simple_post(client)
  303. client.transport = MockTransport.new(200,'{"id":12345,"text":"test status"}')
  304. value = client.statuses.update! :status=>'test status'
  305. assert_equal(:post,client.transport.method,"Expected post request")
  306. assert_equal('http',client.transport.url.scheme,"Expected scheme to be http")
  307. assert_equal('api.twitter.com',client.transport.url.host,"Expected request to be against twitter.com")
  308. assert_equal('/1/statuses/update.json',client.transport.url.path)
  309. assert_match(/status=test%20status/,Net::HTTP.request.body,"Parameters should be form encoded")
  310. assert_equal(12345,value.id)
  311. yield(client) if block_given?
  312. end
  313. end