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