To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.

Statistics Download as Zip
| Branch: | Tag: | Revision:

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