##// END OF EJS Templates
Refactor: convert username/password http basic auth api tests to shoulda macros #6447...
Eric Davis -
r4246:a04d64881cca
parent child
Show More
@@ -1,103 +1,31
1 require "#{File.dirname(__FILE__)}/../../test_helper"
1 require "#{File.dirname(__FILE__)}/../../test_helper"
2
2
3 class ApiTest::HttpBasicLoginTest < ActionController::IntegrationTest
3 class ApiTest::HttpBasicLoginTest < ActionController::IntegrationTest
4 fixtures :all
4 fixtures :all
5
5
6 def setup
6 def setup
7 Setting.rest_api_enabled = '1'
7 Setting.rest_api_enabled = '1'
8 Setting.login_required = '1'
8 Setting.login_required = '1'
9 end
9 end
10
10
11 def teardown
11 def teardown
12 Setting.rest_api_enabled = '0'
12 Setting.rest_api_enabled = '0'
13 Setting.login_required = '0'
13 Setting.login_required = '0'
14 end
14 end
15
15
16 # Using the NewsController because it's a simple API.
16 # Using the NewsController because it's a simple API.
17 context "get /news" do
17 context "get /news" do
18
19 context "in :xml format" do
20 context "with a valid HTTP authentication" do
21 setup do
18 setup do
22 @user = User.generate_with_protected!(:password => 'my_password', :password_confirmation => 'my_password')
19 project = Project.find('onlinestore')
23 @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'my_password')
20 EnabledModule.create(:project => project, :name => 'news')
24 get "/news.xml", nil, :authorization => @authorization
25 end
26
27 should_respond_with :success
28 should_respond_with_content_type :xml
29 should "login as the user" do
30 assert_equal @user, User.current
31 end
32 end
21 end
33
22
34 context "with an invalid HTTP authentication" do
23 context "in :xml format" do
35 setup do
24 should_allow_http_basic_auth_with_username_and_password(:get, "/projects/onlinestore/news.xml")
36 @user = User.generate_with_protected!
37 @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'wrong_password')
38 get "/news.xml", nil, :authorization => @authorization
39 end
40
41 should_respond_with :unauthorized
42 should_respond_with_content_type :xml
43 should "not login as the user" do
44 assert_equal User.anonymous, User.current
45 end
46 end
47
48 context "without credentials" do
49 setup do
50 get "/projects/onlinestore/news.xml"
51 end
52
53 should_respond_with :unauthorized
54 should_respond_with_content_type :xml
55 should "include_www_authenticate_header" do
56 assert @controller.response.headers.has_key?('WWW-Authenticate')
57 end
58 end
59 end
25 end
60
26
61 context "in :json format" do
27 context "in :json format" do
62 context "with a valid HTTP authentication" do
28 should_allow_http_basic_auth_with_username_and_password(:get, "/projects/onlinestore/news.json")
63 setup do
64 @user = User.generate_with_protected!(:password => 'my_password', :password_confirmation => 'my_password')
65 @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'my_password')
66 get "/news.json", nil, :authorization => @authorization
67 end
68
69 should_respond_with :success
70 should_respond_with_content_type :json
71 should "login as the user" do
72 assert_equal @user, User.current
73 end
74 end
75
76 context "with an invalid HTTP authentication" do
77 setup do
78 @user = User.generate_with_protected!
79 @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'wrong_password')
80 get "/news.json", nil, :authorization => @authorization
81 end
82
83 should_respond_with :unauthorized
84 should_respond_with_content_type :json
85 should "not login as the user" do
86 assert_equal User.anonymous, User.current
87 end
88 end
89 end
90
91 context "without credentials" do
92 setup do
93 get "/projects/onlinestore/news.json"
94 end
95
96 should_respond_with :unauthorized
97 should_respond_with_content_type :json
98 should "include_www_authenticate_header" do
99 assert @controller.response.headers.has_key?('WWW-Authenticate')
100 end
101 end
29 end
102 end
30 end
103 end
31 end
@@ -1,247 +1,297
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 ENV["RAILS_ENV"] = "test"
18 ENV["RAILS_ENV"] = "test"
19 require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
19 require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
20 require 'test_help'
20 require 'test_help'
21 require File.expand_path(File.dirname(__FILE__) + '/helper_testcase')
21 require File.expand_path(File.dirname(__FILE__) + '/helper_testcase')
22 require File.join(RAILS_ROOT,'test', 'mocks', 'open_id_authentication_mock.rb')
22 require File.join(RAILS_ROOT,'test', 'mocks', 'open_id_authentication_mock.rb')
23
23
24 require File.expand_path(File.dirname(__FILE__) + '/object_daddy_helpers')
24 require File.expand_path(File.dirname(__FILE__) + '/object_daddy_helpers')
25 include ObjectDaddyHelpers
25 include ObjectDaddyHelpers
26
26
27 class ActiveSupport::TestCase
27 class ActiveSupport::TestCase
28 # Transactional fixtures accelerate your tests by wrapping each test method
28 # Transactional fixtures accelerate your tests by wrapping each test method
29 # in a transaction that's rolled back on completion. This ensures that the
29 # in a transaction that's rolled back on completion. This ensures that the
30 # test database remains unchanged so your fixtures don't have to be reloaded
30 # test database remains unchanged so your fixtures don't have to be reloaded
31 # between every test method. Fewer database queries means faster tests.
31 # between every test method. Fewer database queries means faster tests.
32 #
32 #
33 # Read Mike Clark's excellent walkthrough at
33 # Read Mike Clark's excellent walkthrough at
34 # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
34 # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
35 #
35 #
36 # Every Active Record database supports transactions except MyISAM tables
36 # Every Active Record database supports transactions except MyISAM tables
37 # in MySQL. Turn off transactional fixtures in this case; however, if you
37 # in MySQL. Turn off transactional fixtures in this case; however, if you
38 # don't care one way or the other, switching from MyISAM to InnoDB tables
38 # don't care one way or the other, switching from MyISAM to InnoDB tables
39 # is recommended.
39 # is recommended.
40 self.use_transactional_fixtures = true
40 self.use_transactional_fixtures = true
41
41
42 # Instantiated fixtures are slow, but give you @david where otherwise you
42 # Instantiated fixtures are slow, but give you @david where otherwise you
43 # would need people(:david). If you don't want to migrate your existing
43 # would need people(:david). If you don't want to migrate your existing
44 # test cases which use the @david style and don't mind the speed hit (each
44 # test cases which use the @david style and don't mind the speed hit (each
45 # instantiated fixtures translates to a database query per test method),
45 # instantiated fixtures translates to a database query per test method),
46 # then set this back to true.
46 # then set this back to true.
47 self.use_instantiated_fixtures = false
47 self.use_instantiated_fixtures = false
48
48
49 # Add more helper methods to be used by all tests here...
49 # Add more helper methods to be used by all tests here...
50
50
51 def log_user(login, password)
51 def log_user(login, password)
52 User.anonymous
52 User.anonymous
53 get "/login"
53 get "/login"
54 assert_equal nil, session[:user_id]
54 assert_equal nil, session[:user_id]
55 assert_response :success
55 assert_response :success
56 assert_template "account/login"
56 assert_template "account/login"
57 post "/login", :username => login, :password => password
57 post "/login", :username => login, :password => password
58 assert_equal login, User.find(session[:user_id]).login
58 assert_equal login, User.find(session[:user_id]).login
59 end
59 end
60
60
61 def uploaded_test_file(name, mime)
61 def uploaded_test_file(name, mime)
62 ActionController::TestUploadedFile.new(ActiveSupport::TestCase.fixture_path + "/files/#{name}", mime)
62 ActionController::TestUploadedFile.new(ActiveSupport::TestCase.fixture_path + "/files/#{name}", mime)
63 end
63 end
64
64
65 # Mock out a file
65 # Mock out a file
66 def self.mock_file
66 def self.mock_file
67 file = 'a_file.png'
67 file = 'a_file.png'
68 file.stubs(:size).returns(32)
68 file.stubs(:size).returns(32)
69 file.stubs(:original_filename).returns('a_file.png')
69 file.stubs(:original_filename).returns('a_file.png')
70 file.stubs(:content_type).returns('image/png')
70 file.stubs(:content_type).returns('image/png')
71 file.stubs(:read).returns(false)
71 file.stubs(:read).returns(false)
72 file
72 file
73 end
73 end
74
74
75 def mock_file
75 def mock_file
76 self.class.mock_file
76 self.class.mock_file
77 end
77 end
78
78
79 # Use a temporary directory for attachment related tests
79 # Use a temporary directory for attachment related tests
80 def set_tmp_attachments_directory
80 def set_tmp_attachments_directory
81 Dir.mkdir "#{RAILS_ROOT}/tmp/test" unless File.directory?("#{RAILS_ROOT}/tmp/test")
81 Dir.mkdir "#{RAILS_ROOT}/tmp/test" unless File.directory?("#{RAILS_ROOT}/tmp/test")
82 Dir.mkdir "#{RAILS_ROOT}/tmp/test/attachments" unless File.directory?("#{RAILS_ROOT}/tmp/test/attachments")
82 Dir.mkdir "#{RAILS_ROOT}/tmp/test/attachments" unless File.directory?("#{RAILS_ROOT}/tmp/test/attachments")
83 Attachment.storage_path = "#{RAILS_ROOT}/tmp/test/attachments"
83 Attachment.storage_path = "#{RAILS_ROOT}/tmp/test/attachments"
84 end
84 end
85
85
86 def with_settings(options, &block)
86 def with_settings(options, &block)
87 saved_settings = options.keys.inject({}) {|h, k| h[k] = Setting[k].dup; h}
87 saved_settings = options.keys.inject({}) {|h, k| h[k] = Setting[k].dup; h}
88 options.each {|k, v| Setting[k] = v}
88 options.each {|k, v| Setting[k] = v}
89 yield
89 yield
90 saved_settings.each {|k, v| Setting[k] = v}
90 saved_settings.each {|k, v| Setting[k] = v}
91 end
91 end
92
92
93 def change_user_password(login, new_password)
93 def change_user_password(login, new_password)
94 user = User.first(:conditions => {:login => login})
94 user = User.first(:conditions => {:login => login})
95 user.password, user.password_confirmation = new_password, new_password
95 user.password, user.password_confirmation = new_password, new_password
96 user.save!
96 user.save!
97 end
97 end
98
98
99 def self.ldap_configured?
99 def self.ldap_configured?
100 @test_ldap = Net::LDAP.new(:host => '127.0.0.1', :port => 389)
100 @test_ldap = Net::LDAP.new(:host => '127.0.0.1', :port => 389)
101 return @test_ldap.bind
101 return @test_ldap.bind
102 rescue Exception => e
102 rescue Exception => e
103 # LDAP is not listening
103 # LDAP is not listening
104 return nil
104 return nil
105 end
105 end
106
106
107 # Returns the path to the test +vendor+ repository
107 # Returns the path to the test +vendor+ repository
108 def self.repository_path(vendor)
108 def self.repository_path(vendor)
109 File.join(RAILS_ROOT.gsub(%r{config\/\.\.}, ''), "/tmp/test/#{vendor.downcase}_repository")
109 File.join(RAILS_ROOT.gsub(%r{config\/\.\.}, ''), "/tmp/test/#{vendor.downcase}_repository")
110 end
110 end
111
111
112 # Returns true if the +vendor+ test repository is configured
112 # Returns true if the +vendor+ test repository is configured
113 def self.repository_configured?(vendor)
113 def self.repository_configured?(vendor)
114 File.directory?(repository_path(vendor))
114 File.directory?(repository_path(vendor))
115 end
115 end
116
116
117 def assert_error_tag(options={})
117 def assert_error_tag(options={})
118 assert_tag({:tag => 'p', :attributes => { :id => 'errorExplanation' }}.merge(options))
118 assert_tag({:tag => 'p', :attributes => { :id => 'errorExplanation' }}.merge(options))
119 end
119 end
120
120
121 # Shoulda macros
121 # Shoulda macros
122 def self.should_render_404
122 def self.should_render_404
123 should_respond_with :not_found
123 should_respond_with :not_found
124 should_render_template 'common/error'
124 should_render_template 'common/error'
125 end
125 end
126
126
127 def self.should_have_before_filter(expected_method, options = {})
127 def self.should_have_before_filter(expected_method, options = {})
128 should_have_filter('before', expected_method, options)
128 should_have_filter('before', expected_method, options)
129 end
129 end
130
130
131 def self.should_have_after_filter(expected_method, options = {})
131 def self.should_have_after_filter(expected_method, options = {})
132 should_have_filter('after', expected_method, options)
132 should_have_filter('after', expected_method, options)
133 end
133 end
134
134
135 def self.should_have_filter(filter_type, expected_method, options)
135 def self.should_have_filter(filter_type, expected_method, options)
136 description = "have #{filter_type}_filter :#{expected_method}"
136 description = "have #{filter_type}_filter :#{expected_method}"
137 description << " with #{options.inspect}" unless options.empty?
137 description << " with #{options.inspect}" unless options.empty?
138
138
139 should description do
139 should description do
140 klass = "action_controller/filters/#{filter_type}_filter".classify.constantize
140 klass = "action_controller/filters/#{filter_type}_filter".classify.constantize
141 expected = klass.new(:filter, expected_method.to_sym, options)
141 expected = klass.new(:filter, expected_method.to_sym, options)
142 assert_equal 1, @controller.class.filter_chain.select { |filter|
142 assert_equal 1, @controller.class.filter_chain.select { |filter|
143 filter.method == expected.method && filter.kind == expected.kind &&
143 filter.method == expected.method && filter.kind == expected.kind &&
144 filter.options == expected.options && filter.class == expected.class
144 filter.options == expected.options && filter.class == expected.class
145 }.size
145 }.size
146 end
146 end
147 end
147 end
148
148
149 def self.should_show_the_old_and_new_values_for(prop_key, model, &block)
149 def self.should_show_the_old_and_new_values_for(prop_key, model, &block)
150 context "" do
150 context "" do
151 setup do
151 setup do
152 if block_given?
152 if block_given?
153 instance_eval &block
153 instance_eval &block
154 else
154 else
155 @old_value = model.generate!
155 @old_value = model.generate!
156 @new_value = model.generate!
156 @new_value = model.generate!
157 end
157 end
158 end
158 end
159
159
160 should "use the new value's name" do
160 should "use the new value's name" do
161 @detail = JournalDetail.generate!(:property => 'attr',
161 @detail = JournalDetail.generate!(:property => 'attr',
162 :old_value => @old_value.id,
162 :old_value => @old_value.id,
163 :value => @new_value.id,
163 :value => @new_value.id,
164 :prop_key => prop_key)
164 :prop_key => prop_key)
165
165
166 assert_match @new_value.name, show_detail(@detail, true)
166 assert_match @new_value.name, show_detail(@detail, true)
167 end
167 end
168
168
169 should "use the old value's name" do
169 should "use the old value's name" do
170 @detail = JournalDetail.generate!(:property => 'attr',
170 @detail = JournalDetail.generate!(:property => 'attr',
171 :old_value => @old_value.id,
171 :old_value => @old_value.id,
172 :value => @new_value.id,
172 :value => @new_value.id,
173 :prop_key => prop_key)
173 :prop_key => prop_key)
174
174
175 assert_match @old_value.name, show_detail(@detail, true)
175 assert_match @old_value.name, show_detail(@detail, true)
176 end
176 end
177 end
177 end
178 end
178 end
179
179
180 def self.should_create_a_new_user(&block)
180 def self.should_create_a_new_user(&block)
181 should "create a new user" do
181 should "create a new user" do
182 user = instance_eval &block
182 user = instance_eval &block
183 assert user
183 assert user
184 assert_kind_of User, user
184 assert_kind_of User, user
185 assert !user.new_record?
185 assert !user.new_record?
186 end
186 end
187 end
187 end
188
188
189 # Test that a request allows the username and password for HTTP BASIC
190 #
191 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
192 # @param [String] url the request url
193 # @param [optional, Hash] parameters additional request parameters
194 def self.should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters={})
195 context "should allow http basic auth using a username and password for #{http_method} #{url}" do
196 context "with a valid HTTP authentication" do
197 setup do
198 @user = User.generate_with_protected!(:password => 'my_password', :password_confirmation => 'my_password', :admin => true) # Admin so they can access the project
199 @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'my_password')
200 send(http_method, url, parameters, {:authorization => @authorization})
201 end
202
203 should_respond_with :success
204 should_respond_with_content_type_based_on_url(url)
205 should "login as the user" do
206 assert_equal @user, User.current
207 end
208 end
209
210 context "with an invalid HTTP authentication" do
211 setup do
212 @user = User.generate_with_protected!
213 @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'wrong_password')
214 send(http_method, url, parameters, {:authorization => @authorization})
215 end
216
217 should_respond_with :unauthorized
218 should_respond_with_content_type_based_on_url(url)
219 should "not login as the user" do
220 assert_equal User.anonymous, User.current
221 end
222 end
223
224 context "without credentials" do
225 setup do
226 send(http_method, url, parameters, {:authorization => ''})
227 end
228
229 should_respond_with :unauthorized
230 should_respond_with_content_type_based_on_url(url)
231 should "include_www_authenticate_header" do
232 assert @controller.response.headers.has_key?('WWW-Authenticate')
233 end
234 end
235 end
236
237 end
238
189 # Test that a request allows full key authentication
239 # Test that a request allows full key authentication
190 #
240 #
191 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
241 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
192 # @param [String] url the request url, without the key=ZXY parameter
242 # @param [String] url the request url, without the key=ZXY parameter
193 def self.should_allow_key_based_auth(http_method, url)
243 def self.should_allow_key_based_auth(http_method, url)
194 context "should allow key based auth using key=X for #{url}" do
244 context "should allow key based auth using key=X for #{http_method} #{url}" do
195 context "with a valid api token" do
245 context "with a valid api token" do
196 setup do
246 setup do
197 @user = User.generate_with_protected!
247 @user = User.generate_with_protected!
198 @token = Token.generate!(:user => @user, :action => 'api')
248 @token = Token.generate!(:user => @user, :action => 'api')
199 send(http_method, url + "?key=#{@token.value}")
249 send(http_method, url + "?key=#{@token.value}")
200 end
250 end
201
251
202 should_respond_with :success
252 should_respond_with :success
203 should_respond_with_content_type_based_on_url(url)
253 should_respond_with_content_type_based_on_url(url)
204 should "login as the user" do
254 should "login as the user" do
205 assert_equal @user, User.current
255 assert_equal @user, User.current
206 end
256 end
207 end
257 end
208
258
209 context "with an invalid api token" do
259 context "with an invalid api token" do
210 setup do
260 setup do
211 @user = User.generate_with_protected!
261 @user = User.generate_with_protected!
212 @token = Token.generate!(:user => @user, :action => 'feeds')
262 @token = Token.generate!(:user => @user, :action => 'feeds')
213 send(http_method, url + "?key=#{@token.value}")
263 send(http_method, url + "?key=#{@token.value}")
214 end
264 end
215
265
216 should_respond_with :unauthorized
266 should_respond_with :unauthorized
217 should_respond_with_content_type_based_on_url(url)
267 should_respond_with_content_type_based_on_url(url)
218 should "not login as the user" do
268 should "not login as the user" do
219 assert_equal User.anonymous, User.current
269 assert_equal User.anonymous, User.current
220 end
270 end
221 end
271 end
222 end
272 end
223
273
224 end
274 end
225
275
226 # Uses should_respond_with_content_type based on what's in the url:
276 # Uses should_respond_with_content_type based on what's in the url:
227 #
277 #
228 # '/project/issues.xml' => should_respond_with_content_type :xml
278 # '/project/issues.xml' => should_respond_with_content_type :xml
229 # '/project/issues.json' => should_respond_with_content_type :json
279 # '/project/issues.json' => should_respond_with_content_type :json
230 #
280 #
231 # @param [String] url Request
281 # @param [String] url Request
232 def self.should_respond_with_content_type_based_on_url(url)
282 def self.should_respond_with_content_type_based_on_url(url)
233 case
283 case
234 when url.match(/xml/i)
284 when url.match(/xml/i)
235 should_respond_with_content_type :xml
285 should_respond_with_content_type :xml
236 when url.match(/json/i)
286 when url.match(/json/i)
237 should_respond_with_content_type :json
287 should_respond_with_content_type :json
238 else
288 else
239 raise "Unknown content type for should_respond_with_content_type_based_on_url: #{url}"
289 raise "Unknown content type for should_respond_with_content_type_based_on_url: #{url}"
240 end
290 end
241
291
242 end
292 end
243 end
293 end
244
294
245 # Simple module to "namespace" all of the API tests
295 # Simple module to "namespace" all of the API tests
246 module ApiTest
296 module ApiTest
247 end
297 end
General Comments 0
You need to be logged in to leave comments. Login now