You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

test_helper.rb 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. # Redmine - project management software
  2. # Copyright (C) 2006-2012 Jean-Philippe Lang
  3. #
  4. # This program is free software; you can redistribute it and/or
  5. # modify it under the terms of the GNU General Public License
  6. # as published by the Free Software Foundation; either version 2
  7. # of the License, or (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program; if not, write to the Free Software
  16. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  17. #require 'shoulda'
  18. ENV["RAILS_ENV"] = "test"
  19. require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
  20. require 'rails/test_help'
  21. require Rails.root.join('test', 'mocks', 'open_id_authentication_mock.rb').to_s
  22. require File.expand_path(File.dirname(__FILE__) + '/object_helpers')
  23. include ObjectHelpers
  24. class ActiveSupport::TestCase
  25. include ActionDispatch::TestProcess
  26. # Transactional fixtures accelerate your tests by wrapping each test method
  27. # in a transaction that's rolled back on completion. This ensures that the
  28. # test database remains unchanged so your fixtures don't have to be reloaded
  29. # between every test method. Fewer database queries means faster tests.
  30. #
  31. # Read Mike Clark's excellent walkthrough at
  32. # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
  33. #
  34. # Every Active Record database supports transactions except MyISAM tables
  35. # in MySQL. Turn off transactional fixtures in this case; however, if you
  36. # don't care one way or the other, switching from MyISAM to InnoDB tables
  37. # is recommended.
  38. self.use_transactional_fixtures = true
  39. # Instantiated fixtures are slow, but give you @david where otherwise you
  40. # would need people(:david). If you don't want to migrate your existing
  41. # test cases which use the @david style and don't mind the speed hit (each
  42. # instantiated fixtures translates to a database query per test method),
  43. # then set this back to true.
  44. self.use_instantiated_fixtures = false
  45. # Add more helper methods to be used by all tests here...
  46. def log_user(login, password)
  47. User.anonymous
  48. get "/login"
  49. assert_equal nil, session[:user_id]
  50. assert_response :success
  51. assert_template "account/login"
  52. post "/login", :username => login, :password => password
  53. assert_equal login, User.find(session[:user_id]).login
  54. end
  55. def uploaded_test_file(name, mime)
  56. fixture_file_upload("files/#{name}", mime, true)
  57. end
  58. def credentials(user, password=nil)
  59. {'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)}
  60. end
  61. # Mock out a file
  62. def self.mock_file
  63. file = 'a_file.png'
  64. file.stubs(:size).returns(32)
  65. file.stubs(:original_filename).returns('a_file.png')
  66. file.stubs(:content_type).returns('image/png')
  67. file.stubs(:read).returns(false)
  68. file
  69. end
  70. def mock_file
  71. self.class.mock_file
  72. end
  73. def mock_file_with_options(options={})
  74. file = ''
  75. file.stubs(:size).returns(32)
  76. original_filename = options[:original_filename] || nil
  77. file.stubs(:original_filename).returns(original_filename)
  78. content_type = options[:content_type] || nil
  79. file.stubs(:content_type).returns(content_type)
  80. file.stubs(:read).returns(false)
  81. file
  82. end
  83. # Use a temporary directory for attachment related tests
  84. def set_tmp_attachments_directory
  85. Dir.mkdir "#{Rails.root}/tmp/test" unless File.directory?("#{Rails.root}/tmp/test")
  86. unless File.directory?("#{Rails.root}/tmp/test/attachments")
  87. Dir.mkdir "#{Rails.root}/tmp/test/attachments"
  88. end
  89. Attachment.storage_path = "#{Rails.root}/tmp/test/attachments"
  90. end
  91. def set_fixtures_attachments_directory
  92. Attachment.storage_path = "#{Rails.root}/test/fixtures/files"
  93. end
  94. def with_settings(options, &block)
  95. saved_settings = options.keys.inject({}) {|h, k| h[k] = Setting[k].is_a?(Symbol) ? Setting[k] : Setting[k].dup; h}
  96. options.each {|k, v| Setting[k] = v}
  97. yield
  98. ensure
  99. saved_settings.each {|k, v| Setting[k] = v} if saved_settings
  100. end
  101. def change_user_password(login, new_password)
  102. user = User.first(:conditions => {:login => login})
  103. user.password, user.password_confirmation = new_password, new_password
  104. user.save!
  105. end
  106. def self.ldap_configured?
  107. @test_ldap = Net::LDAP.new(:host => '127.0.0.1', :port => 389)
  108. return @test_ldap.bind
  109. rescue Exception => e
  110. # LDAP is not listening
  111. return nil
  112. end
  113. # Returns the path to the test +vendor+ repository
  114. def self.repository_path(vendor)
  115. Rails.root.join("tmp/test/#{vendor.downcase}_repository").to_s
  116. end
  117. # Returns the url of the subversion test repository
  118. def self.subversion_repository_url
  119. path = repository_path('subversion')
  120. path = '/' + path unless path.starts_with?('/')
  121. "file://#{path}"
  122. end
  123. # Returns true if the +vendor+ test repository is configured
  124. def self.repository_configured?(vendor)
  125. File.directory?(repository_path(vendor))
  126. end
  127. def repository_path_hash(arr)
  128. hs = {}
  129. hs[:path] = arr.join("/")
  130. hs[:param] = arr.join("/")
  131. hs
  132. end
  133. def assert_error_tag(options={})
  134. assert_tag({:attributes => { :id => 'errorExplanation' }}.merge(options))
  135. end
  136. def assert_include(expected, s)
  137. assert s.include?(expected), "\"#{expected}\" not found in \"#{s}\""
  138. end
  139. def assert_not_include(expected, s)
  140. assert !s.include?(expected), "\"#{expected}\" found in \"#{s}\""
  141. end
  142. def assert_mail_body_match(expected, mail)
  143. if expected.is_a?(String)
  144. assert_include expected, mail_body(mail)
  145. else
  146. assert_match expected, mail_body(mail)
  147. end
  148. end
  149. def assert_mail_body_no_match(expected, mail)
  150. if expected.is_a?(String)
  151. assert_not_include expected, mail_body(mail)
  152. else
  153. assert_no_match expected, mail_body(mail)
  154. end
  155. end
  156. def mail_body(mail)
  157. mail.parts.first.body.encoded
  158. end
  159. # Shoulda macros
  160. def self.should_render_404
  161. should_respond_with :not_found
  162. should_render_template 'common/error'
  163. end
  164. def self.should_have_before_filter(expected_method, options = {})
  165. should_have_filter('before', expected_method, options)
  166. end
  167. def self.should_have_after_filter(expected_method, options = {})
  168. should_have_filter('after', expected_method, options)
  169. end
  170. def self.should_have_filter(filter_type, expected_method, options)
  171. description = "have #{filter_type}_filter :#{expected_method}"
  172. description << " with #{options.inspect}" unless options.empty?
  173. should description do
  174. klass = "action_controller/filters/#{filter_type}_filter".classify.constantize
  175. expected = klass.new(:filter, expected_method.to_sym, options)
  176. assert_equal 1, @controller.class.filter_chain.select { |filter|
  177. filter.method == expected.method && filter.kind == expected.kind &&
  178. filter.options == expected.options && filter.class == expected.class
  179. }.size
  180. end
  181. end
  182. # Test that a request allows the three types of API authentication
  183. #
  184. # * HTTP Basic with username and password
  185. # * HTTP Basic with an api key for the username
  186. # * Key based with the key=X parameter
  187. #
  188. # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
  189. # @param [String] url the request url
  190. # @param [optional, Hash] parameters additional request parameters
  191. # @param [optional, Hash] options additional options
  192. # @option options [Symbol] :success_code Successful response code (:success)
  193. # @option options [Symbol] :failure_code Failure response code (:unauthorized)
  194. def self.should_allow_api_authentication(http_method, url, parameters={}, options={})
  195. should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters, options)
  196. should_allow_http_basic_auth_with_key(http_method, url, parameters, options)
  197. should_allow_key_based_auth(http_method, url, parameters, options)
  198. end
  199. # Test that a request allows the username and password for HTTP BASIC
  200. #
  201. # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
  202. # @param [String] url the request url
  203. # @param [optional, Hash] parameters additional request parameters
  204. # @param [optional, Hash] options additional options
  205. # @option options [Symbol] :success_code Successful response code (:success)
  206. # @option options [Symbol] :failure_code Failure response code (:unauthorized)
  207. def self.should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters={}, options={})
  208. success_code = options[:success_code] || :success
  209. failure_code = options[:failure_code] || :unauthorized
  210. context "should allow http basic auth using a username and password for #{http_method} #{url}" do
  211. context "with a valid HTTP authentication" do
  212. setup do
  213. @user = User.generate! do |user|
  214. user.admin = true
  215. user.password = 'my_password'
  216. end
  217. send(http_method, url, parameters, credentials(@user.login, 'my_password'))
  218. end
  219. should_respond_with success_code
  220. should_respond_with_content_type_based_on_url(url)
  221. should "login as the user" do
  222. assert_equal @user, User.current
  223. end
  224. end
  225. context "with an invalid HTTP authentication" do
  226. setup do
  227. @user = User.generate!
  228. send(http_method, url, parameters, credentials(@user.login, 'wrong_password'))
  229. end
  230. should_respond_with failure_code
  231. should_respond_with_content_type_based_on_url(url)
  232. should "not login as the user" do
  233. assert_equal User.anonymous, User.current
  234. end
  235. end
  236. context "without credentials" do
  237. setup do
  238. send(http_method, url, parameters)
  239. end
  240. should_respond_with failure_code
  241. should_respond_with_content_type_based_on_url(url)
  242. should "include_www_authenticate_header" do
  243. assert @controller.response.headers.has_key?('WWW-Authenticate')
  244. end
  245. end
  246. end
  247. end
  248. # Test that a request allows the API key with HTTP BASIC
  249. #
  250. # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
  251. # @param [String] url the request url
  252. # @param [optional, Hash] parameters additional request parameters
  253. # @param [optional, Hash] options additional options
  254. # @option options [Symbol] :success_code Successful response code (:success)
  255. # @option options [Symbol] :failure_code Failure response code (:unauthorized)
  256. def self.should_allow_http_basic_auth_with_key(http_method, url, parameters={}, options={})
  257. success_code = options[:success_code] || :success
  258. failure_code = options[:failure_code] || :unauthorized
  259. context "should allow http basic auth with a key for #{http_method} #{url}" do
  260. context "with a valid HTTP authentication using the API token" do
  261. setup do
  262. @user = User.generate! do |user|
  263. user.admin = true
  264. end
  265. @token = Token.create!(:user => @user, :action => 'api')
  266. send(http_method, url, parameters, credentials(@token.value, 'X'))
  267. end
  268. should_respond_with success_code
  269. should_respond_with_content_type_based_on_url(url)
  270. should_be_a_valid_response_string_based_on_url(url)
  271. should "login as the user" do
  272. assert_equal @user, User.current
  273. end
  274. end
  275. context "with an invalid HTTP authentication" do
  276. setup do
  277. @user = User.generate!
  278. @token = Token.create!(:user => @user, :action => 'feeds')
  279. send(http_method, url, parameters, credentials(@token.value, 'X'))
  280. end
  281. should_respond_with failure_code
  282. should_respond_with_content_type_based_on_url(url)
  283. should "not login as the user" do
  284. assert_equal User.anonymous, User.current
  285. end
  286. end
  287. end
  288. end
  289. # Test that a request allows full key authentication
  290. #
  291. # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
  292. # @param [String] url the request url, without the key=ZXY parameter
  293. # @param [optional, Hash] parameters additional request parameters
  294. # @param [optional, Hash] options additional options
  295. # @option options [Symbol] :success_code Successful response code (:success)
  296. # @option options [Symbol] :failure_code Failure response code (:unauthorized)
  297. def self.should_allow_key_based_auth(http_method, url, parameters={}, options={})
  298. success_code = options[:success_code] || :success
  299. failure_code = options[:failure_code] || :unauthorized
  300. context "should allow key based auth using key=X for #{http_method} #{url}" do
  301. context "with a valid api token" do
  302. setup do
  303. @user = User.generate! do |user|
  304. user.admin = true
  305. end
  306. @token = Token.create!(:user => @user, :action => 'api')
  307. # Simple url parse to add on ?key= or &key=
  308. request_url = if url.match(/\?/)
  309. url + "&key=#{@token.value}"
  310. else
  311. url + "?key=#{@token.value}"
  312. end
  313. send(http_method, request_url, parameters)
  314. end
  315. should_respond_with success_code
  316. should_respond_with_content_type_based_on_url(url)
  317. should_be_a_valid_response_string_based_on_url(url)
  318. should "login as the user" do
  319. assert_equal @user, User.current
  320. end
  321. end
  322. context "with an invalid api token" do
  323. setup do
  324. @user = User.generate! do |user|
  325. user.admin = true
  326. end
  327. @token = Token.create!(:user => @user, :action => 'feeds')
  328. # Simple url parse to add on ?key= or &key=
  329. request_url = if url.match(/\?/)
  330. url + "&key=#{@token.value}"
  331. else
  332. url + "?key=#{@token.value}"
  333. end
  334. send(http_method, request_url, parameters)
  335. end
  336. should_respond_with failure_code
  337. should_respond_with_content_type_based_on_url(url)
  338. should "not login as the user" do
  339. assert_equal User.anonymous, User.current
  340. end
  341. end
  342. end
  343. context "should allow key based auth using X-Redmine-API-Key header for #{http_method} #{url}" do
  344. setup do
  345. @user = User.generate! do |user|
  346. user.admin = true
  347. end
  348. @token = Token.create!(:user => @user, :action => 'api')
  349. send(http_method, url, parameters, {'X-Redmine-API-Key' => @token.value.to_s})
  350. end
  351. should_respond_with success_code
  352. should_respond_with_content_type_based_on_url(url)
  353. should_be_a_valid_response_string_based_on_url(url)
  354. should "login as the user" do
  355. assert_equal @user, User.current
  356. end
  357. end
  358. end
  359. # Uses should_respond_with_content_type based on what's in the url:
  360. #
  361. # '/project/issues.xml' => should_respond_with_content_type :xml
  362. # '/project/issues.json' => should_respond_with_content_type :json
  363. #
  364. # @param [String] url Request
  365. def self.should_respond_with_content_type_based_on_url(url)
  366. case
  367. when url.match(/xml/i)
  368. should "respond with XML" do
  369. assert_equal 'application/xml', @response.content_type
  370. end
  371. when url.match(/json/i)
  372. should "respond with JSON" do
  373. assert_equal 'application/json', @response.content_type
  374. end
  375. else
  376. raise "Unknown content type for should_respond_with_content_type_based_on_url: #{url}"
  377. end
  378. end
  379. # Uses the url to assert which format the response should be in
  380. #
  381. # '/project/issues.xml' => should_be_a_valid_xml_string
  382. # '/project/issues.json' => should_be_a_valid_json_string
  383. #
  384. # @param [String] url Request
  385. def self.should_be_a_valid_response_string_based_on_url(url)
  386. case
  387. when url.match(/xml/i)
  388. should_be_a_valid_xml_string
  389. when url.match(/json/i)
  390. should_be_a_valid_json_string
  391. else
  392. raise "Unknown content type for should_be_a_valid_response_based_on_url: #{url}"
  393. end
  394. end
  395. # Checks that the response is a valid JSON string
  396. def self.should_be_a_valid_json_string
  397. should "be a valid JSON string (or empty)" do
  398. assert(response.body.blank? || ActiveSupport::JSON.decode(response.body))
  399. end
  400. end
  401. # Checks that the response is a valid XML string
  402. def self.should_be_a_valid_xml_string
  403. should "be a valid XML string" do
  404. assert REXML::Document.new(response.body)
  405. end
  406. end
  407. def self.should_respond_with(status)
  408. should "respond with #{status}" do
  409. assert_response status
  410. end
  411. end
  412. end
  413. # Simple module to "namespace" all of the API tests
  414. module ApiTest
  415. end