test_helper.rb 16 KB
Newer Older
Jean-Philippe Lang's avatar
Jean-Philippe Lang committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
# redMine - project management software
# Copyright (C) 2006  Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

18
ENV["RAILS_ENV"] = "test"
Jean-Philippe Lang's avatar
Jean-Philippe Lang committed
19 20
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
require 'test_help'
21
require File.expand_path(File.dirname(__FILE__) + '/helper_testcase')
22
require File.join(RAILS_ROOT,'test', 'mocks', 'open_id_authentication_mock.rb')
Jean-Philippe Lang's avatar
Jean-Philippe Lang committed
23

24 25
require File.expand_path(File.dirname(__FILE__) + '/object_daddy_helpers')
include ObjectDaddyHelpers
26

27
class ActiveSupport::TestCase
Jean-Philippe Lang's avatar
Jean-Philippe Lang committed
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
  # Transactional fixtures accelerate your tests by wrapping each test method
  # in a transaction that's rolled back on completion.  This ensures that the
  # test database remains unchanged so your fixtures don't have to be reloaded
  # between every test method.  Fewer database queries means faster tests.
  #
  # Read Mike Clark's excellent walkthrough at
  #   http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
  #
  # Every Active Record database supports transactions except MyISAM tables
  # in MySQL.  Turn off transactional fixtures in this case; however, if you
  # don't care one way or the other, switching from MyISAM to InnoDB tables
  # is recommended.
  self.use_transactional_fixtures = true

  # Instantiated fixtures are slow, but give you @david where otherwise you
  # would need people(:david).  If you don't want to migrate your existing
  # test cases which use the @david style and don't mind the speed hit (each
  # instantiated fixtures translates to a database query per test method),
  # then set this back to true.
  self.use_instantiated_fixtures  = false

  # Add more helper methods to be used by all tests here...
Jean-Philippe Lang's avatar
Jean-Philippe Lang committed
50 51
  
  def log_user(login, password)
52
    User.anonymous
53
    get "/login"
Jean-Philippe Lang's avatar
Jean-Philippe Lang committed
54
    assert_equal nil, session[:user_id]
Jean-Philippe Lang's avatar
Jean-Philippe Lang committed
55 56
    assert_response :success
    assert_template "account/login"
57
    post "/login", :username => login, :password => password
Jean-Philippe Lang's avatar
Jean-Philippe Lang committed
58
    assert_equal login, User.find(session[:user_id]).login
Jean-Philippe Lang's avatar
Jean-Philippe Lang committed
59
  end
60
  
61
  def uploaded_test_file(name, mime)
62
    ActionController::TestUploadedFile.new(ActiveSupport::TestCase.fixture_path + "/files/#{name}", mime)
63
  end
64 65

  # Mock out a file
66
  def self.mock_file
67 68 69 70 71 72 73
    file = 'a_file.png'
    file.stubs(:size).returns(32)
    file.stubs(:original_filename).returns('a_file.png')
    file.stubs(:content_type).returns('image/png')
    file.stubs(:read).returns(false)
    file
  end
74 75 76 77 78

  def mock_file
    self.class.mock_file
  end

79 80
  # Use a temporary directory for attachment related tests
  def set_tmp_attachments_directory
81
    Dir.mkdir "#{RAILS_ROOT}/tmp/test" unless File.directory?("#{RAILS_ROOT}/tmp/test")
82 83 84
    Dir.mkdir "#{RAILS_ROOT}/tmp/test/attachments" unless File.directory?("#{RAILS_ROOT}/tmp/test/attachments")
    Attachment.storage_path = "#{RAILS_ROOT}/tmp/test/attachments"
  end
85 86 87 88 89 90 91
  
  def with_settings(options, &block)
    saved_settings = options.keys.inject({}) {|h, k| h[k] = Setting[k].dup; h}
    options.each {|k, v| Setting[k] = v}
    yield
    saved_settings.each {|k, v| Setting[k] = v}
  end
92

93 94 95 96 97 98
  def change_user_password(login, new_password)
    user = User.first(:conditions => {:login => login})
    user.password, user.password_confirmation = new_password, new_password
    user.save!
  end

99 100 101
  def self.ldap_configured?
    @test_ldap = Net::LDAP.new(:host => '127.0.0.1', :port => 389)
    return @test_ldap.bind
102 103 104
  rescue Exception => e
    # LDAP is not listening
    return nil
105
  end
106 107 108 109 110 111 112 113 114 115
  
  # Returns the path to the test +vendor+ repository
  def self.repository_path(vendor)
    File.join(RAILS_ROOT.gsub(%r{config\/\.\.}, ''), "/tmp/test/#{vendor.downcase}_repository")
  end
  
  # Returns true if the +vendor+ test repository is configured
  def self.repository_configured?(vendor)
    File.directory?(repository_path(vendor))
  end
116 117 118 119
  
  def assert_error_tag(options={})
    assert_tag({:tag => 'p', :attributes => { :id => 'errorExplanation' }}.merge(options))
  end
120

121 122 123
  # Shoulda macros
  def self.should_render_404
    should_respond_with :not_found
124
    should_render_template 'common/error'
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
  end

  def self.should_have_before_filter(expected_method, options = {})
    should_have_filter('before', expected_method, options)
  end

  def self.should_have_after_filter(expected_method, options = {})
    should_have_filter('after', expected_method, options)
  end

  def self.should_have_filter(filter_type, expected_method, options)
    description = "have #{filter_type}_filter :#{expected_method}"
    description << " with #{options.inspect}" unless options.empty?

    should description do
      klass = "action_controller/filters/#{filter_type}_filter".classify.constantize
      expected = klass.new(:filter, expected_method.to_sym, options)
      assert_equal 1, @controller.class.filter_chain.select { |filter|
        filter.method == expected.method && filter.kind == expected.kind &&
        filter.options == expected.options && filter.class == expected.class
      }.size
    end
  end
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178

  def self.should_show_the_old_and_new_values_for(prop_key, model, &block)
    context "" do
      setup do
        if block_given?
          instance_eval &block
        else
          @old_value = model.generate!
          @new_value = model.generate!
        end
      end

      should "use the new value's name" do
        @detail = JournalDetail.generate!(:property => 'attr',
                                          :old_value => @old_value.id,
                                          :value => @new_value.id,
                                          :prop_key => prop_key)
        
        assert_match @new_value.name, show_detail(@detail, true)
      end

      should "use the old value's name" do
        @detail = JournalDetail.generate!(:property => 'attr',
                                          :old_value => @old_value.id,
                                          :value => @new_value.id,
                                          :prop_key => prop_key)
        
        assert_match @old_value.name, show_detail(@detail, true)
      end
    end
  end
179 180 181 182 183 184 185 186 187

  def self.should_create_a_new_user(&block)
    should "create a new user" do
      user = instance_eval &block
      assert user
      assert_kind_of User, user
      assert !user.new_record?
    end
  end
188

189 190 191 192 193 194 195 196 197
  # Test that a request allows the three types of API authentication
  #
  # * HTTP Basic with username and password
  # * HTTP Basic with an api key for the username
  # * Key based with the key=X parameter
  #
  # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
  # @param [String] url the request url
  # @param [optional, Hash] parameters additional request parameters
198 199 200 201 202 203 204
  # @param [optional, Hash] options additional options
  # @option options [Symbol] :success_code Successful response code (:success)
  # @option options [Symbol] :failure_code Failure response code (:unauthorized)
  def self.should_allow_api_authentication(http_method, url, parameters={}, options={})
    should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters, options)
    should_allow_http_basic_auth_with_key(http_method, url, parameters, options)
    should_allow_key_based_auth(http_method, url, parameters, options)
205 206
  end

207 208 209 210 211
  # Test that a request allows the username and password for HTTP BASIC
  #
  # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
  # @param [String] url the request url
  # @param [optional, Hash] parameters additional request parameters
212 213 214 215 216 217 218
  # @param [optional, Hash] options additional options
  # @option options [Symbol] :success_code Successful response code (:success)
  # @option options [Symbol] :failure_code Failure response code (:unauthorized)
  def self.should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters={}, options={})
    success_code = options[:success_code] || :success
    failure_code = options[:failure_code] || :unauthorized
    
219 220 221 222 223 224 225 226
    context "should allow http basic auth using a username and password for #{http_method} #{url}" do
      context "with a valid HTTP authentication" do
        setup do
          @user = User.generate_with_protected!(:password => 'my_password', :password_confirmation => 'my_password', :admin => true) # Admin so they can access the project
          @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'my_password')
          send(http_method, url, parameters, {:authorization => @authorization})
        end
        
227
        should_respond_with success_code
228 229 230 231 232 233 234 235 236 237 238 239 240
        should_respond_with_content_type_based_on_url(url)
        should "login as the user" do
          assert_equal @user, User.current
        end
      end

      context "with an invalid HTTP authentication" do
        setup do
          @user = User.generate_with_protected!
          @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'wrong_password')
          send(http_method, url, parameters, {:authorization => @authorization})
        end
        
241
        should_respond_with failure_code
242 243 244 245 246 247 248 249 250 251 252
        should_respond_with_content_type_based_on_url(url)
        should "not login as the user" do
          assert_equal User.anonymous, User.current
        end
      end
      
      context "without credentials" do
        setup do
          send(http_method, url, parameters, {:authorization => ''})
        end

253
        should_respond_with failure_code
254 255 256 257 258 259 260 261 262
        should_respond_with_content_type_based_on_url(url)
        should "include_www_authenticate_header" do
          assert @controller.response.headers.has_key?('WWW-Authenticate')
        end
      end
    end

  end

263 264 265 266 267
  # Test that a request allows the API key with HTTP BASIC
  #
  # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
  # @param [String] url the request url
  # @param [optional, Hash] parameters additional request parameters
268 269 270 271 272 273 274
  # @param [optional, Hash] options additional options
  # @option options [Symbol] :success_code Successful response code (:success)
  # @option options [Symbol] :failure_code Failure response code (:unauthorized)
  def self.should_allow_http_basic_auth_with_key(http_method, url, parameters={}, options={})
    success_code = options[:success_code] || :success
    failure_code = options[:failure_code] || :unauthorized

275 276 277
    context "should allow http basic auth with a key for #{http_method} #{url}" do
      context "with a valid HTTP authentication using the API token" do
        setup do
278
          @user = User.generate_with_protected!(:admin => true)
279 280 281 282 283
          @token = Token.generate!(:user => @user, :action => 'api')
          @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@token.value, 'X')
          send(http_method, url, parameters, {:authorization => @authorization})
        end
        
284
        should_respond_with success_code
285
        should_respond_with_content_type_based_on_url(url)
286
        should_be_a_valid_response_string_based_on_url(url)
287 288 289 290 291 292 293 294 295 296 297 298 299
        should "login as the user" do
          assert_equal @user, User.current
        end
      end

      context "with an invalid HTTP authentication" do
        setup do
          @user = User.generate_with_protected!
          @token = Token.generate!(:user => @user, :action => 'feeds')
          @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@token.value, 'X')
          send(http_method, url, parameters, {:authorization => @authorization})
        end

300
        should_respond_with failure_code
301 302 303 304 305 306 307 308
        should_respond_with_content_type_based_on_url(url)
        should "not login as the user" do
          assert_equal User.anonymous, User.current
        end
      end
    end
  end
  
309 310 311 312
  # Test that a request allows full key authentication
  #
  # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
  # @param [String] url the request url, without the key=ZXY parameter
313
  # @param [optional, Hash] parameters additional request parameters
314 315 316 317 318 319 320
  # @param [optional, Hash] options additional options
  # @option options [Symbol] :success_code Successful response code (:success)
  # @option options [Symbol] :failure_code Failure response code (:unauthorized)
  def self.should_allow_key_based_auth(http_method, url, parameters={}, options={})
    success_code = options[:success_code] || :success
    failure_code = options[:failure_code] || :unauthorized

321
    context "should allow key based auth using key=X for #{http_method} #{url}" do
322 323
      context "with a valid api token" do
        setup do
324
          @user = User.generate_with_protected!(:admin => true)
325
          @token = Token.generate!(:user => @user, :action => 'api')
326 327 328 329 330 331 332
          # Simple url parse to add on ?key= or &key=
          request_url = if url.match(/\?/)
                          url + "&key=#{@token.value}"
                        else
                          url + "?key=#{@token.value}"
                        end
          send(http_method, request_url, parameters)
333 334
        end
        
335
        should_respond_with success_code
336
        should_respond_with_content_type_based_on_url(url)
337
        should_be_a_valid_response_string_based_on_url(url)
338 339 340 341 342 343 344 345 346
        should "login as the user" do
          assert_equal @user, User.current
        end
      end

      context "with an invalid api token" do
        setup do
          @user = User.generate_with_protected!
          @token = Token.generate!(:user => @user, :action => 'feeds')
347 348 349 350 351 352 353
          # Simple url parse to add on ?key= or &key=
          request_url = if url.match(/\?/)
                          url + "&key=#{@token.value}"
                        else
                          url + "?key=#{@token.value}"
                        end
          send(http_method, request_url, parameters)
354 355
        end
        
356
        should_respond_with failure_code
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382
        should_respond_with_content_type_based_on_url(url)
        should "not login as the user" do
          assert_equal User.anonymous, User.current
        end
      end
    end
    
  end

  # Uses should_respond_with_content_type based on what's in the url:
  #
  # '/project/issues.xml' => should_respond_with_content_type :xml
  # '/project/issues.json' => should_respond_with_content_type :json
  #
  # @param [String] url Request
  def self.should_respond_with_content_type_based_on_url(url)
    case
    when url.match(/xml/i)
      should_respond_with_content_type :xml
    when url.match(/json/i)
      should_respond_with_content_type :json
    else
      raise "Unknown content type for should_respond_with_content_type_based_on_url: #{url}"
    end
    
  end
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415

  # Uses the url to assert which format the response should be in
  #
  # '/project/issues.xml' => should_be_a_valid_xml_string
  # '/project/issues.json' => should_be_a_valid_json_string
  #
  # @param [String] url Request
  def self.should_be_a_valid_response_string_based_on_url(url)
    case
    when url.match(/xml/i)
      should_be_a_valid_xml_string
    when url.match(/json/i)
      should_be_a_valid_json_string
    else
      raise "Unknown content type for should_be_a_valid_response_based_on_url: #{url}"
    end
    
  end
  
  # Checks that the response is a valid JSON string
  def self.should_be_a_valid_json_string
    should "be a valid JSON string" do
      assert ActiveSupport::JSON.decode(response.body)
    end
  end

  # Checks that the response is a valid XML string
  def self.should_be_a_valid_xml_string
    should "be a valid XML string" do
      assert REXML::Document.new(response.body)
    end
  end
  
Jean-Philippe Lang's avatar
Jean-Philippe Lang committed
416
end
417 418 419 420

# Simple module to "namespace" all of the API tests
module ApiTest
end