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 18KB

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