##// END OF EJS Templates
Adds an helper to get the body of an email in tests....
Jean-Philippe Lang -
r8972:df89c24e37af
parent child
Show More
@@ -1,481 +1,485
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 Rails.root.join('test', 'mocks', 'open_id_authentication_mock.rb').to_s
21 require Rails.root.join('test', 'mocks', 'open_id_authentication_mock.rb').to_s
22
22
23 require File.expand_path(File.dirname(__FILE__) + '/object_daddy_helpers')
23 require File.expand_path(File.dirname(__FILE__) + '/object_daddy_helpers')
24 include ObjectDaddyHelpers
24 include ObjectDaddyHelpers
25
25
26 class ActiveSupport::TestCase
26 class ActiveSupport::TestCase
27 # Transactional fixtures accelerate your tests by wrapping each test method
27 # Transactional fixtures accelerate your tests by wrapping each test method
28 # in a transaction that's rolled back on completion. This ensures that the
28 # in a transaction that's rolled back on completion. This ensures that the
29 # test database remains unchanged so your fixtures don't have to be reloaded
29 # test database remains unchanged so your fixtures don't have to be reloaded
30 # between every test method. Fewer database queries means faster tests.
30 # between every test method. Fewer database queries means faster tests.
31 #
31 #
32 # Read Mike Clark's excellent walkthrough at
32 # Read Mike Clark's excellent walkthrough at
33 # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
33 # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
34 #
34 #
35 # Every Active Record database supports transactions except MyISAM tables
35 # Every Active Record database supports transactions except MyISAM tables
36 # in MySQL. Turn off transactional fixtures in this case; however, if you
36 # in MySQL. Turn off transactional fixtures in this case; however, if you
37 # don't care one way or the other, switching from MyISAM to InnoDB tables
37 # don't care one way or the other, switching from MyISAM to InnoDB tables
38 # is recommended.
38 # is recommended.
39 self.use_transactional_fixtures = true
39 self.use_transactional_fixtures = true
40
40
41 # Instantiated fixtures are slow, but give you @david where otherwise you
41 # Instantiated fixtures are slow, but give you @david where otherwise you
42 # would need people(:david). If you don't want to migrate your existing
42 # would need people(:david). If you don't want to migrate your existing
43 # test cases which use the @david style and don't mind the speed hit (each
43 # test cases which use the @david style and don't mind the speed hit (each
44 # instantiated fixtures translates to a database query per test method),
44 # instantiated fixtures translates to a database query per test method),
45 # then set this back to true.
45 # then set this back to true.
46 self.use_instantiated_fixtures = false
46 self.use_instantiated_fixtures = false
47
47
48 # Add more helper methods to be used by all tests here...
48 # Add more helper methods to be used by all tests here...
49
49
50 def log_user(login, password)
50 def log_user(login, password)
51 User.anonymous
51 User.anonymous
52 get "/login"
52 get "/login"
53 assert_equal nil, session[:user_id]
53 assert_equal nil, session[:user_id]
54 assert_response :success
54 assert_response :success
55 assert_template "account/login"
55 assert_template "account/login"
56 post "/login", :username => login, :password => password
56 post "/login", :username => login, :password => password
57 assert_equal login, User.find(session[:user_id]).login
57 assert_equal login, User.find(session[:user_id]).login
58 end
58 end
59
59
60 def uploaded_test_file(name, mime)
60 def uploaded_test_file(name, mime)
61 ActionController::TestUploadedFile.new(
61 ActionController::TestUploadedFile.new(
62 ActiveSupport::TestCase.fixture_path + "/files/#{name}", mime, true)
62 ActiveSupport::TestCase.fixture_path + "/files/#{name}", mime, true)
63 end
63 end
64
64
65 def credentials(user, password=nil)
65 def credentials(user, password=nil)
66 {:authorization => ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)}
66 {:authorization => ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)}
67 end
67 end
68
68
69 # Mock out a file
69 # Mock out a file
70 def self.mock_file
70 def self.mock_file
71 file = 'a_file.png'
71 file = 'a_file.png'
72 file.stubs(:size).returns(32)
72 file.stubs(:size).returns(32)
73 file.stubs(:original_filename).returns('a_file.png')
73 file.stubs(:original_filename).returns('a_file.png')
74 file.stubs(:content_type).returns('image/png')
74 file.stubs(:content_type).returns('image/png')
75 file.stubs(:read).returns(false)
75 file.stubs(:read).returns(false)
76 file
76 file
77 end
77 end
78
78
79 def mock_file
79 def mock_file
80 self.class.mock_file
80 self.class.mock_file
81 end
81 end
82
82
83 def mock_file_with_options(options={})
83 def mock_file_with_options(options={})
84 file = ''
84 file = ''
85 file.stubs(:size).returns(32)
85 file.stubs(:size).returns(32)
86 original_filename = options[:original_filename] || nil
86 original_filename = options[:original_filename] || nil
87 file.stubs(:original_filename).returns(original_filename)
87 file.stubs(:original_filename).returns(original_filename)
88 content_type = options[:content_type] || nil
88 content_type = options[:content_type] || nil
89 file.stubs(:content_type).returns(content_type)
89 file.stubs(:content_type).returns(content_type)
90 file.stubs(:read).returns(false)
90 file.stubs(:read).returns(false)
91 file
91 file
92 end
92 end
93
93
94 # Use a temporary directory for attachment related tests
94 # Use a temporary directory for attachment related tests
95 def set_tmp_attachments_directory
95 def set_tmp_attachments_directory
96 Dir.mkdir "#{Rails.root}/tmp/test" unless File.directory?("#{Rails.root}/tmp/test")
96 Dir.mkdir "#{Rails.root}/tmp/test" unless File.directory?("#{Rails.root}/tmp/test")
97 unless File.directory?("#{Rails.root}/tmp/test/attachments")
97 unless File.directory?("#{Rails.root}/tmp/test/attachments")
98 Dir.mkdir "#{Rails.root}/tmp/test/attachments"
98 Dir.mkdir "#{Rails.root}/tmp/test/attachments"
99 end
99 end
100 Attachment.storage_path = "#{Rails.root}/tmp/test/attachments"
100 Attachment.storage_path = "#{Rails.root}/tmp/test/attachments"
101 end
101 end
102
102
103 def set_fixtures_attachments_directory
103 def set_fixtures_attachments_directory
104 Attachment.storage_path = "#{Rails.root}/test/fixtures/files"
104 Attachment.storage_path = "#{Rails.root}/test/fixtures/files"
105 end
105 end
106
106
107 def with_settings(options, &block)
107 def with_settings(options, &block)
108 saved_settings = options.keys.inject({}) {|h, k| h[k] = Setting[k].is_a?(Symbol) ? Setting[k] : Setting[k].dup; h}
108 saved_settings = options.keys.inject({}) {|h, k| h[k] = Setting[k].is_a?(Symbol) ? Setting[k] : Setting[k].dup; h}
109 options.each {|k, v| Setting[k] = v}
109 options.each {|k, v| Setting[k] = v}
110 yield
110 yield
111 ensure
111 ensure
112 saved_settings.each {|k, v| Setting[k] = v} if saved_settings
112 saved_settings.each {|k, v| Setting[k] = v} if saved_settings
113 end
113 end
114
114
115 def change_user_password(login, new_password)
115 def change_user_password(login, new_password)
116 user = User.first(:conditions => {:login => login})
116 user = User.first(:conditions => {:login => login})
117 user.password, user.password_confirmation = new_password, new_password
117 user.password, user.password_confirmation = new_password, new_password
118 user.save!
118 user.save!
119 end
119 end
120
120
121 def self.ldap_configured?
121 def self.ldap_configured?
122 @test_ldap = Net::LDAP.new(:host => '127.0.0.1', :port => 389)
122 @test_ldap = Net::LDAP.new(:host => '127.0.0.1', :port => 389)
123 return @test_ldap.bind
123 return @test_ldap.bind
124 rescue Exception => e
124 rescue Exception => e
125 # LDAP is not listening
125 # LDAP is not listening
126 return nil
126 return nil
127 end
127 end
128
128
129 # Returns the path to the test +vendor+ repository
129 # Returns the path to the test +vendor+ repository
130 def self.repository_path(vendor)
130 def self.repository_path(vendor)
131 Rails.root.join("tmp/test/#{vendor.downcase}_repository").to_s
131 Rails.root.join("tmp/test/#{vendor.downcase}_repository").to_s
132 end
132 end
133
133
134 # Returns the url of the subversion test repository
134 # Returns the url of the subversion test repository
135 def self.subversion_repository_url
135 def self.subversion_repository_url
136 path = repository_path('subversion')
136 path = repository_path('subversion')
137 path = '/' + path unless path.starts_with?('/')
137 path = '/' + path unless path.starts_with?('/')
138 "file://#{path}"
138 "file://#{path}"
139 end
139 end
140
140
141 # Returns true if the +vendor+ test repository is configured
141 # Returns true if the +vendor+ test repository is configured
142 def self.repository_configured?(vendor)
142 def self.repository_configured?(vendor)
143 File.directory?(repository_path(vendor))
143 File.directory?(repository_path(vendor))
144 end
144 end
145
145
146 def repository_path_hash(arr)
146 def repository_path_hash(arr)
147 hs = {}
147 hs = {}
148 hs[:path] = arr.join("/")
148 hs[:path] = arr.join("/")
149 hs[:param] = arr
149 hs[:param] = arr
150 hs
150 hs
151 end
151 end
152
152
153 def assert_error_tag(options={})
153 def assert_error_tag(options={})
154 assert_tag({:attributes => { :id => 'errorExplanation' }}.merge(options))
154 assert_tag({:attributes => { :id => 'errorExplanation' }}.merge(options))
155 end
155 end
156
156
157 def assert_include(expected, s)
157 def assert_include(expected, s)
158 assert s.include?(expected), "\"#{expected}\" not found in \"#{s}\""
158 assert s.include?(expected), "\"#{expected}\" not found in \"#{s}\""
159 end
159 end
160
160
161 def assert_not_include(expected, s)
161 def assert_not_include(expected, s)
162 assert !s.include?(expected), "\"#{expected}\" found in \"#{s}\""
162 assert !s.include?(expected), "\"#{expected}\" found in \"#{s}\""
163 end
163 end
164
164
165 def assert_mail_body_match(expected, mail)
165 def assert_mail_body_match(expected, mail)
166 if expected.is_a?(String)
166 if expected.is_a?(String)
167 assert_include expected, mail.body
167 assert_include expected, mail_body(mail)
168 else
168 else
169 assert_match expected, mail.body
169 assert_match expected, mail_body(mail)
170 end
170 end
171 end
171 end
172
172
173 def assert_mail_body_no_match(expected, mail)
173 def assert_mail_body_no_match(expected, mail)
174 if expected.is_a?(String)
174 if expected.is_a?(String)
175 assert_not_include expected, mail.body
175 assert_not_include expected, mail_body(mail)
176 else
176 else
177 assert_no_match expected, mail.body
177 assert_no_match expected, mail_body(mail)
178 end
178 end
179 end
179 end
180
180
181 def mail_body(mail)
182 mail.body
183 end
184
181 # Shoulda macros
185 # Shoulda macros
182 def self.should_render_404
186 def self.should_render_404
183 should_respond_with :not_found
187 should_respond_with :not_found
184 should_render_template 'common/error'
188 should_render_template 'common/error'
185 end
189 end
186
190
187 def self.should_have_before_filter(expected_method, options = {})
191 def self.should_have_before_filter(expected_method, options = {})
188 should_have_filter('before', expected_method, options)
192 should_have_filter('before', expected_method, options)
189 end
193 end
190
194
191 def self.should_have_after_filter(expected_method, options = {})
195 def self.should_have_after_filter(expected_method, options = {})
192 should_have_filter('after', expected_method, options)
196 should_have_filter('after', expected_method, options)
193 end
197 end
194
198
195 def self.should_have_filter(filter_type, expected_method, options)
199 def self.should_have_filter(filter_type, expected_method, options)
196 description = "have #{filter_type}_filter :#{expected_method}"
200 description = "have #{filter_type}_filter :#{expected_method}"
197 description << " with #{options.inspect}" unless options.empty?
201 description << " with #{options.inspect}" unless options.empty?
198
202
199 should description do
203 should description do
200 klass = "action_controller/filters/#{filter_type}_filter".classify.constantize
204 klass = "action_controller/filters/#{filter_type}_filter".classify.constantize
201 expected = klass.new(:filter, expected_method.to_sym, options)
205 expected = klass.new(:filter, expected_method.to_sym, options)
202 assert_equal 1, @controller.class.filter_chain.select { |filter|
206 assert_equal 1, @controller.class.filter_chain.select { |filter|
203 filter.method == expected.method && filter.kind == expected.kind &&
207 filter.method == expected.method && filter.kind == expected.kind &&
204 filter.options == expected.options && filter.class == expected.class
208 filter.options == expected.options && filter.class == expected.class
205 }.size
209 }.size
206 end
210 end
207 end
211 end
208
212
209 def self.should_show_the_old_and_new_values_for(prop_key, model, &block)
213 def self.should_show_the_old_and_new_values_for(prop_key, model, &block)
210 context "" do
214 context "" do
211 setup do
215 setup do
212 if block_given?
216 if block_given?
213 instance_eval &block
217 instance_eval &block
214 else
218 else
215 @old_value = model.generate!
219 @old_value = model.generate!
216 @new_value = model.generate!
220 @new_value = model.generate!
217 end
221 end
218 end
222 end
219
223
220 should "use the new value's name" do
224 should "use the new value's name" do
221 @detail = JournalDetail.generate!(:property => 'attr',
225 @detail = JournalDetail.generate!(:property => 'attr',
222 :old_value => @old_value.id,
226 :old_value => @old_value.id,
223 :value => @new_value.id,
227 :value => @new_value.id,
224 :prop_key => prop_key)
228 :prop_key => prop_key)
225
229
226 assert_match @new_value.name, show_detail(@detail, true)
230 assert_match @new_value.name, show_detail(@detail, true)
227 end
231 end
228
232
229 should "use the old value's name" do
233 should "use the old value's name" do
230 @detail = JournalDetail.generate!(:property => 'attr',
234 @detail = JournalDetail.generate!(:property => 'attr',
231 :old_value => @old_value.id,
235 :old_value => @old_value.id,
232 :value => @new_value.id,
236 :value => @new_value.id,
233 :prop_key => prop_key)
237 :prop_key => prop_key)
234
238
235 assert_match @old_value.name, show_detail(@detail, true)
239 assert_match @old_value.name, show_detail(@detail, true)
236 end
240 end
237 end
241 end
238 end
242 end
239
243
240 # Test that a request allows the three types of API authentication
244 # Test that a request allows the three types of API authentication
241 #
245 #
242 # * HTTP Basic with username and password
246 # * HTTP Basic with username and password
243 # * HTTP Basic with an api key for the username
247 # * HTTP Basic with an api key for the username
244 # * Key based with the key=X parameter
248 # * Key based with the key=X parameter
245 #
249 #
246 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
250 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
247 # @param [String] url the request url
251 # @param [String] url the request url
248 # @param [optional, Hash] parameters additional request parameters
252 # @param [optional, Hash] parameters additional request parameters
249 # @param [optional, Hash] options additional options
253 # @param [optional, Hash] options additional options
250 # @option options [Symbol] :success_code Successful response code (:success)
254 # @option options [Symbol] :success_code Successful response code (:success)
251 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
255 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
252 def self.should_allow_api_authentication(http_method, url, parameters={}, options={})
256 def self.should_allow_api_authentication(http_method, url, parameters={}, options={})
253 should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters, options)
257 should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters, options)
254 should_allow_http_basic_auth_with_key(http_method, url, parameters, options)
258 should_allow_http_basic_auth_with_key(http_method, url, parameters, options)
255 should_allow_key_based_auth(http_method, url, parameters, options)
259 should_allow_key_based_auth(http_method, url, parameters, options)
256 end
260 end
257
261
258 # Test that a request allows the username and password for HTTP BASIC
262 # Test that a request allows the username and password for HTTP BASIC
259 #
263 #
260 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
264 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
261 # @param [String] url the request url
265 # @param [String] url the request url
262 # @param [optional, Hash] parameters additional request parameters
266 # @param [optional, Hash] parameters additional request parameters
263 # @param [optional, Hash] options additional options
267 # @param [optional, Hash] options additional options
264 # @option options [Symbol] :success_code Successful response code (:success)
268 # @option options [Symbol] :success_code Successful response code (:success)
265 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
269 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
266 def self.should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters={}, options={})
270 def self.should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters={}, options={})
267 success_code = options[:success_code] || :success
271 success_code = options[:success_code] || :success
268 failure_code = options[:failure_code] || :unauthorized
272 failure_code = options[:failure_code] || :unauthorized
269
273
270 context "should allow http basic auth using a username and password for #{http_method} #{url}" do
274 context "should allow http basic auth using a username and password for #{http_method} #{url}" do
271 context "with a valid HTTP authentication" do
275 context "with a valid HTTP authentication" do
272 setup do
276 setup do
273 @user = User.generate_with_protected!(:password => 'my_password', :password_confirmation => 'my_password', :admin => true) # Admin so they can access the project
277 @user = User.generate_with_protected!(:password => 'my_password', :password_confirmation => 'my_password', :admin => true) # Admin so they can access the project
274 send(http_method, url, parameters, credentials(@user.login, 'my_password'))
278 send(http_method, url, parameters, credentials(@user.login, 'my_password'))
275 end
279 end
276
280
277 should_respond_with success_code
281 should_respond_with success_code
278 should_respond_with_content_type_based_on_url(url)
282 should_respond_with_content_type_based_on_url(url)
279 should "login as the user" do
283 should "login as the user" do
280 assert_equal @user, User.current
284 assert_equal @user, User.current
281 end
285 end
282 end
286 end
283
287
284 context "with an invalid HTTP authentication" do
288 context "with an invalid HTTP authentication" do
285 setup do
289 setup do
286 @user = User.generate_with_protected!
290 @user = User.generate_with_protected!
287 send(http_method, url, parameters, credentials(@user.login, 'wrong_password'))
291 send(http_method, url, parameters, credentials(@user.login, 'wrong_password'))
288 end
292 end
289
293
290 should_respond_with failure_code
294 should_respond_with failure_code
291 should_respond_with_content_type_based_on_url(url)
295 should_respond_with_content_type_based_on_url(url)
292 should "not login as the user" do
296 should "not login as the user" do
293 assert_equal User.anonymous, User.current
297 assert_equal User.anonymous, User.current
294 end
298 end
295 end
299 end
296
300
297 context "without credentials" do
301 context "without credentials" do
298 setup do
302 setup do
299 send(http_method, url, parameters)
303 send(http_method, url, parameters)
300 end
304 end
301
305
302 should_respond_with failure_code
306 should_respond_with failure_code
303 should_respond_with_content_type_based_on_url(url)
307 should_respond_with_content_type_based_on_url(url)
304 should "include_www_authenticate_header" do
308 should "include_www_authenticate_header" do
305 assert @controller.response.headers.has_key?('WWW-Authenticate')
309 assert @controller.response.headers.has_key?('WWW-Authenticate')
306 end
310 end
307 end
311 end
308 end
312 end
309
313
310 end
314 end
311
315
312 # Test that a request allows the API key with HTTP BASIC
316 # Test that a request allows the API key with HTTP BASIC
313 #
317 #
314 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
318 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
315 # @param [String] url the request url
319 # @param [String] url the request url
316 # @param [optional, Hash] parameters additional request parameters
320 # @param [optional, Hash] parameters additional request parameters
317 # @param [optional, Hash] options additional options
321 # @param [optional, Hash] options additional options
318 # @option options [Symbol] :success_code Successful response code (:success)
322 # @option options [Symbol] :success_code Successful response code (:success)
319 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
323 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
320 def self.should_allow_http_basic_auth_with_key(http_method, url, parameters={}, options={})
324 def self.should_allow_http_basic_auth_with_key(http_method, url, parameters={}, options={})
321 success_code = options[:success_code] || :success
325 success_code = options[:success_code] || :success
322 failure_code = options[:failure_code] || :unauthorized
326 failure_code = options[:failure_code] || :unauthorized
323
327
324 context "should allow http basic auth with a key for #{http_method} #{url}" do
328 context "should allow http basic auth with a key for #{http_method} #{url}" do
325 context "with a valid HTTP authentication using the API token" do
329 context "with a valid HTTP authentication using the API token" do
326 setup do
330 setup do
327 @user = User.generate_with_protected!(:admin => true)
331 @user = User.generate_with_protected!(:admin => true)
328 @token = Token.generate!(:user => @user, :action => 'api')
332 @token = Token.generate!(:user => @user, :action => 'api')
329 send(http_method, url, parameters, credentials(@token.value, 'X'))
333 send(http_method, url, parameters, credentials(@token.value, 'X'))
330 end
334 end
331
335
332 should_respond_with success_code
336 should_respond_with success_code
333 should_respond_with_content_type_based_on_url(url)
337 should_respond_with_content_type_based_on_url(url)
334 should_be_a_valid_response_string_based_on_url(url)
338 should_be_a_valid_response_string_based_on_url(url)
335 should "login as the user" do
339 should "login as the user" do
336 assert_equal @user, User.current
340 assert_equal @user, User.current
337 end
341 end
338 end
342 end
339
343
340 context "with an invalid HTTP authentication" do
344 context "with an invalid HTTP authentication" do
341 setup do
345 setup do
342 @user = User.generate_with_protected!
346 @user = User.generate_with_protected!
343 @token = Token.generate!(:user => @user, :action => 'feeds')
347 @token = Token.generate!(:user => @user, :action => 'feeds')
344 send(http_method, url, parameters, credentials(@token.value, 'X'))
348 send(http_method, url, parameters, credentials(@token.value, 'X'))
345 end
349 end
346
350
347 should_respond_with failure_code
351 should_respond_with failure_code
348 should_respond_with_content_type_based_on_url(url)
352 should_respond_with_content_type_based_on_url(url)
349 should "not login as the user" do
353 should "not login as the user" do
350 assert_equal User.anonymous, User.current
354 assert_equal User.anonymous, User.current
351 end
355 end
352 end
356 end
353 end
357 end
354 end
358 end
355
359
356 # Test that a request allows full key authentication
360 # Test that a request allows full key authentication
357 #
361 #
358 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
362 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
359 # @param [String] url the request url, without the key=ZXY parameter
363 # @param [String] url the request url, without the key=ZXY parameter
360 # @param [optional, Hash] parameters additional request parameters
364 # @param [optional, Hash] parameters additional request parameters
361 # @param [optional, Hash] options additional options
365 # @param [optional, Hash] options additional options
362 # @option options [Symbol] :success_code Successful response code (:success)
366 # @option options [Symbol] :success_code Successful response code (:success)
363 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
367 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
364 def self.should_allow_key_based_auth(http_method, url, parameters={}, options={})
368 def self.should_allow_key_based_auth(http_method, url, parameters={}, options={})
365 success_code = options[:success_code] || :success
369 success_code = options[:success_code] || :success
366 failure_code = options[:failure_code] || :unauthorized
370 failure_code = options[:failure_code] || :unauthorized
367
371
368 context "should allow key based auth using key=X for #{http_method} #{url}" do
372 context "should allow key based auth using key=X for #{http_method} #{url}" do
369 context "with a valid api token" do
373 context "with a valid api token" do
370 setup do
374 setup do
371 @user = User.generate_with_protected!(:admin => true)
375 @user = User.generate_with_protected!(:admin => true)
372 @token = Token.generate!(:user => @user, :action => 'api')
376 @token = Token.generate!(:user => @user, :action => 'api')
373 # Simple url parse to add on ?key= or &key=
377 # Simple url parse to add on ?key= or &key=
374 request_url = if url.match(/\?/)
378 request_url = if url.match(/\?/)
375 url + "&key=#{@token.value}"
379 url + "&key=#{@token.value}"
376 else
380 else
377 url + "?key=#{@token.value}"
381 url + "?key=#{@token.value}"
378 end
382 end
379 send(http_method, request_url, parameters)
383 send(http_method, request_url, parameters)
380 end
384 end
381
385
382 should_respond_with success_code
386 should_respond_with success_code
383 should_respond_with_content_type_based_on_url(url)
387 should_respond_with_content_type_based_on_url(url)
384 should_be_a_valid_response_string_based_on_url(url)
388 should_be_a_valid_response_string_based_on_url(url)
385 should "login as the user" do
389 should "login as the user" do
386 assert_equal @user, User.current
390 assert_equal @user, User.current
387 end
391 end
388 end
392 end
389
393
390 context "with an invalid api token" do
394 context "with an invalid api token" do
391 setup do
395 setup do
392 @user = User.generate_with_protected!
396 @user = User.generate_with_protected!
393 @token = Token.generate!(:user => @user, :action => 'feeds')
397 @token = Token.generate!(:user => @user, :action => 'feeds')
394 # Simple url parse to add on ?key= or &key=
398 # Simple url parse to add on ?key= or &key=
395 request_url = if url.match(/\?/)
399 request_url = if url.match(/\?/)
396 url + "&key=#{@token.value}"
400 url + "&key=#{@token.value}"
397 else
401 else
398 url + "?key=#{@token.value}"
402 url + "?key=#{@token.value}"
399 end
403 end
400 send(http_method, request_url, parameters)
404 send(http_method, request_url, parameters)
401 end
405 end
402
406
403 should_respond_with failure_code
407 should_respond_with failure_code
404 should_respond_with_content_type_based_on_url(url)
408 should_respond_with_content_type_based_on_url(url)
405 should "not login as the user" do
409 should "not login as the user" do
406 assert_equal User.anonymous, User.current
410 assert_equal User.anonymous, User.current
407 end
411 end
408 end
412 end
409 end
413 end
410
414
411 context "should allow key based auth using X-Redmine-API-Key header for #{http_method} #{url}" do
415 context "should allow key based auth using X-Redmine-API-Key header for #{http_method} #{url}" do
412 setup do
416 setup do
413 @user = User.generate_with_protected!(:admin => true)
417 @user = User.generate_with_protected!(:admin => true)
414 @token = Token.generate!(:user => @user, :action => 'api')
418 @token = Token.generate!(:user => @user, :action => 'api')
415 send(http_method, url, parameters, {'X-Redmine-API-Key' => @token.value.to_s})
419 send(http_method, url, parameters, {'X-Redmine-API-Key' => @token.value.to_s})
416 end
420 end
417
421
418 should_respond_with success_code
422 should_respond_with success_code
419 should_respond_with_content_type_based_on_url(url)
423 should_respond_with_content_type_based_on_url(url)
420 should_be_a_valid_response_string_based_on_url(url)
424 should_be_a_valid_response_string_based_on_url(url)
421 should "login as the user" do
425 should "login as the user" do
422 assert_equal @user, User.current
426 assert_equal @user, User.current
423 end
427 end
424 end
428 end
425 end
429 end
426
430
427 # Uses should_respond_with_content_type based on what's in the url:
431 # Uses should_respond_with_content_type based on what's in the url:
428 #
432 #
429 # '/project/issues.xml' => should_respond_with_content_type :xml
433 # '/project/issues.xml' => should_respond_with_content_type :xml
430 # '/project/issues.json' => should_respond_with_content_type :json
434 # '/project/issues.json' => should_respond_with_content_type :json
431 #
435 #
432 # @param [String] url Request
436 # @param [String] url Request
433 def self.should_respond_with_content_type_based_on_url(url)
437 def self.should_respond_with_content_type_based_on_url(url)
434 case
438 case
435 when url.match(/xml/i)
439 when url.match(/xml/i)
436 should_respond_with_content_type :xml
440 should_respond_with_content_type :xml
437 when url.match(/json/i)
441 when url.match(/json/i)
438 should_respond_with_content_type :json
442 should_respond_with_content_type :json
439 else
443 else
440 raise "Unknown content type for should_respond_with_content_type_based_on_url: #{url}"
444 raise "Unknown content type for should_respond_with_content_type_based_on_url: #{url}"
441 end
445 end
442
446
443 end
447 end
444
448
445 # Uses the url to assert which format the response should be in
449 # Uses the url to assert which format the response should be in
446 #
450 #
447 # '/project/issues.xml' => should_be_a_valid_xml_string
451 # '/project/issues.xml' => should_be_a_valid_xml_string
448 # '/project/issues.json' => should_be_a_valid_json_string
452 # '/project/issues.json' => should_be_a_valid_json_string
449 #
453 #
450 # @param [String] url Request
454 # @param [String] url Request
451 def self.should_be_a_valid_response_string_based_on_url(url)
455 def self.should_be_a_valid_response_string_based_on_url(url)
452 case
456 case
453 when url.match(/xml/i)
457 when url.match(/xml/i)
454 should_be_a_valid_xml_string
458 should_be_a_valid_xml_string
455 when url.match(/json/i)
459 when url.match(/json/i)
456 should_be_a_valid_json_string
460 should_be_a_valid_json_string
457 else
461 else
458 raise "Unknown content type for should_be_a_valid_response_based_on_url: #{url}"
462 raise "Unknown content type for should_be_a_valid_response_based_on_url: #{url}"
459 end
463 end
460
464
461 end
465 end
462
466
463 # Checks that the response is a valid JSON string
467 # Checks that the response is a valid JSON string
464 def self.should_be_a_valid_json_string
468 def self.should_be_a_valid_json_string
465 should "be a valid JSON string (or empty)" do
469 should "be a valid JSON string (or empty)" do
466 assert(response.body.blank? || ActiveSupport::JSON.decode(response.body))
470 assert(response.body.blank? || ActiveSupport::JSON.decode(response.body))
467 end
471 end
468 end
472 end
469
473
470 # Checks that the response is a valid XML string
474 # Checks that the response is a valid XML string
471 def self.should_be_a_valid_xml_string
475 def self.should_be_a_valid_xml_string
472 should "be a valid XML string" do
476 should "be a valid XML string" do
473 assert REXML::Document.new(response.body)
477 assert REXML::Document.new(response.body)
474 end
478 end
475 end
479 end
476
480
477 end
481 end
478
482
479 # Simple module to "namespace" all of the API tests
483 # Simple module to "namespace" all of the API tests
480 module ApiTest
484 module ApiTest
481 end
485 end
@@ -1,172 +1,172
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 require File.expand_path('../../../../test_helper', __FILE__)
18 require File.expand_path('../../../../test_helper', __FILE__)
19
19
20 class Redmine::Hook::ManagerTest < ActiveSupport::TestCase
20 class Redmine::Hook::ManagerTest < ActiveSupport::TestCase
21
21
22 fixtures :issues
22 fixtures :issues
23
23
24 # Some hooks that are manually registered in these tests
24 # Some hooks that are manually registered in these tests
25 class TestHook < Redmine::Hook::ViewListener; end
25 class TestHook < Redmine::Hook::ViewListener; end
26
26
27 class TestHook1 < TestHook
27 class TestHook1 < TestHook
28 def view_layouts_base_html_head(context)
28 def view_layouts_base_html_head(context)
29 'Test hook 1 listener.'
29 'Test hook 1 listener.'
30 end
30 end
31 end
31 end
32
32
33 class TestHook2 < TestHook
33 class TestHook2 < TestHook
34 def view_layouts_base_html_head(context)
34 def view_layouts_base_html_head(context)
35 'Test hook 2 listener.'
35 'Test hook 2 listener.'
36 end
36 end
37 end
37 end
38
38
39 class TestHook3 < TestHook
39 class TestHook3 < TestHook
40 def view_layouts_base_html_head(context)
40 def view_layouts_base_html_head(context)
41 "Context keys: #{context.keys.collect(&:to_s).sort.join(', ')}."
41 "Context keys: #{context.keys.collect(&:to_s).sort.join(', ')}."
42 end
42 end
43 end
43 end
44
44
45 class TestLinkToHook < TestHook
45 class TestLinkToHook < TestHook
46 def view_layouts_base_html_head(context)
46 def view_layouts_base_html_head(context)
47 link_to('Issues', :controller => 'issues')
47 link_to('Issues', :controller => 'issues')
48 end
48 end
49 end
49 end
50
50
51 class TestHookHelperController < ActionController::Base
51 class TestHookHelperController < ActionController::Base
52 include Redmine::Hook::Helper
52 include Redmine::Hook::Helper
53 end
53 end
54
54
55 class TestHookHelperView < ActionView::Base
55 class TestHookHelperView < ActionView::Base
56 include Redmine::Hook::Helper
56 include Redmine::Hook::Helper
57 end
57 end
58
58
59 Redmine::Hook.clear_listeners
59 Redmine::Hook.clear_listeners
60
60
61 def setup
61 def setup
62 @hook_module = Redmine::Hook
62 @hook_module = Redmine::Hook
63 end
63 end
64
64
65 def teardown
65 def teardown
66 @hook_module.clear_listeners
66 @hook_module.clear_listeners
67 end
67 end
68
68
69 def test_clear_listeners
69 def test_clear_listeners
70 assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size
70 assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size
71 @hook_module.add_listener(TestHook1)
71 @hook_module.add_listener(TestHook1)
72 @hook_module.add_listener(TestHook2)
72 @hook_module.add_listener(TestHook2)
73 assert_equal 2, @hook_module.hook_listeners(:view_layouts_base_html_head).size
73 assert_equal 2, @hook_module.hook_listeners(:view_layouts_base_html_head).size
74
74
75 @hook_module.clear_listeners
75 @hook_module.clear_listeners
76 assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size
76 assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size
77 end
77 end
78
78
79 def test_add_listener
79 def test_add_listener
80 assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size
80 assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size
81 @hook_module.add_listener(TestHook1)
81 @hook_module.add_listener(TestHook1)
82 assert_equal 1, @hook_module.hook_listeners(:view_layouts_base_html_head).size
82 assert_equal 1, @hook_module.hook_listeners(:view_layouts_base_html_head).size
83 end
83 end
84
84
85 def test_call_hook
85 def test_call_hook
86 @hook_module.add_listener(TestHook1)
86 @hook_module.add_listener(TestHook1)
87 assert_equal ['Test hook 1 listener.'], hook_helper.call_hook(:view_layouts_base_html_head)
87 assert_equal ['Test hook 1 listener.'], hook_helper.call_hook(:view_layouts_base_html_head)
88 end
88 end
89
89
90 def test_call_hook_with_context
90 def test_call_hook_with_context
91 @hook_module.add_listener(TestHook3)
91 @hook_module.add_listener(TestHook3)
92 assert_equal ['Context keys: bar, controller, foo, project, request.'],
92 assert_equal ['Context keys: bar, controller, foo, project, request.'],
93 hook_helper.call_hook(:view_layouts_base_html_head, :foo => 1, :bar => 'a')
93 hook_helper.call_hook(:view_layouts_base_html_head, :foo => 1, :bar => 'a')
94 end
94 end
95
95
96 def test_call_hook_with_multiple_listeners
96 def test_call_hook_with_multiple_listeners
97 @hook_module.add_listener(TestHook1)
97 @hook_module.add_listener(TestHook1)
98 @hook_module.add_listener(TestHook2)
98 @hook_module.add_listener(TestHook2)
99 assert_equal ['Test hook 1 listener.', 'Test hook 2 listener.'], hook_helper.call_hook(:view_layouts_base_html_head)
99 assert_equal ['Test hook 1 listener.', 'Test hook 2 listener.'], hook_helper.call_hook(:view_layouts_base_html_head)
100 end
100 end
101
101
102 # Context: Redmine::Hook::Helper.call_hook default_url
102 # Context: Redmine::Hook::Helper.call_hook default_url
103 def test_call_hook_default_url_options
103 def test_call_hook_default_url_options
104 @hook_module.add_listener(TestLinkToHook)
104 @hook_module.add_listener(TestLinkToHook)
105
105
106 assert_equal ['<a href="/issues">Issues</a>'], hook_helper.call_hook(:view_layouts_base_html_head)
106 assert_equal ['<a href="/issues">Issues</a>'], hook_helper.call_hook(:view_layouts_base_html_head)
107 end
107 end
108
108
109 # Context: Redmine::Hook::Helper.call_hook
109 # Context: Redmine::Hook::Helper.call_hook
110 def test_call_hook_with_project_added_to_context
110 def test_call_hook_with_project_added_to_context
111 @hook_module.add_listener(TestHook3)
111 @hook_module.add_listener(TestHook3)
112 assert_match /project/i, hook_helper.call_hook(:view_layouts_base_html_head)[0]
112 assert_match /project/i, hook_helper.call_hook(:view_layouts_base_html_head)[0]
113 end
113 end
114
114
115 def test_call_hook_from_controller_with_controller_added_to_context
115 def test_call_hook_from_controller_with_controller_added_to_context
116 @hook_module.add_listener(TestHook3)
116 @hook_module.add_listener(TestHook3)
117 assert_match /controller/i, hook_helper.call_hook(:view_layouts_base_html_head)[0]
117 assert_match /controller/i, hook_helper.call_hook(:view_layouts_base_html_head)[0]
118 end
118 end
119
119
120 def test_call_hook_from_controller_with_request_added_to_context
120 def test_call_hook_from_controller_with_request_added_to_context
121 @hook_module.add_listener(TestHook3)
121 @hook_module.add_listener(TestHook3)
122 assert_match /request/i, hook_helper.call_hook(:view_layouts_base_html_head)[0]
122 assert_match /request/i, hook_helper.call_hook(:view_layouts_base_html_head)[0]
123 end
123 end
124
124
125 def test_call_hook_from_view_with_project_added_to_context
125 def test_call_hook_from_view_with_project_added_to_context
126 @hook_module.add_listener(TestHook3)
126 @hook_module.add_listener(TestHook3)
127 assert_match /project/i, view_hook_helper.call_hook(:view_layouts_base_html_head)
127 assert_match /project/i, view_hook_helper.call_hook(:view_layouts_base_html_head)
128 end
128 end
129
129
130 def test_call_hook_from_view_with_controller_added_to_context
130 def test_call_hook_from_view_with_controller_added_to_context
131 @hook_module.add_listener(TestHook3)
131 @hook_module.add_listener(TestHook3)
132 assert_match /controller/i, view_hook_helper.call_hook(:view_layouts_base_html_head)
132 assert_match /controller/i, view_hook_helper.call_hook(:view_layouts_base_html_head)
133 end
133 end
134
134
135 def test_call_hook_from_view_with_request_added_to_context
135 def test_call_hook_from_view_with_request_added_to_context
136 @hook_module.add_listener(TestHook3)
136 @hook_module.add_listener(TestHook3)
137 assert_match /request/i, view_hook_helper.call_hook(:view_layouts_base_html_head)
137 assert_match /request/i, view_hook_helper.call_hook(:view_layouts_base_html_head)
138 end
138 end
139
139
140 def test_call_hook_from_view_should_join_responses_with_a_space
140 def test_call_hook_from_view_should_join_responses_with_a_space
141 @hook_module.add_listener(TestHook1)
141 @hook_module.add_listener(TestHook1)
142 @hook_module.add_listener(TestHook2)
142 @hook_module.add_listener(TestHook2)
143 assert_equal 'Test hook 1 listener. Test hook 2 listener.',
143 assert_equal 'Test hook 1 listener. Test hook 2 listener.',
144 view_hook_helper.call_hook(:view_layouts_base_html_head)
144 view_hook_helper.call_hook(:view_layouts_base_html_head)
145 end
145 end
146
146
147 def test_call_hook_should_not_change_the_default_url_for_email_notifications
147 def test_call_hook_should_not_change_the_default_url_for_email_notifications
148 issue = Issue.find(1)
148 issue = Issue.find(1)
149
149
150 ActionMailer::Base.deliveries.clear
150 ActionMailer::Base.deliveries.clear
151 Mailer.deliver_issue_add(issue)
151 Mailer.deliver_issue_add(issue)
152 mail = ActionMailer::Base.deliveries.last
152 mail = ActionMailer::Base.deliveries.last
153
153
154 @hook_module.add_listener(TestLinkToHook)
154 @hook_module.add_listener(TestLinkToHook)
155 hook_helper.call_hook(:view_layouts_base_html_head)
155 hook_helper.call_hook(:view_layouts_base_html_head)
156
156
157 ActionMailer::Base.deliveries.clear
157 ActionMailer::Base.deliveries.clear
158 Mailer.deliver_issue_add(issue)
158 Mailer.deliver_issue_add(issue)
159 mail2 = ActionMailer::Base.deliveries.last
159 mail2 = ActionMailer::Base.deliveries.last
160
160
161 assert_equal mail.body, mail2.body
161 assert_equal mail_body(mail), mail_body(mail2)
162 end
162 end
163
163
164 def hook_helper
164 def hook_helper
165 @hook_helper ||= TestHookHelperController.new
165 @hook_helper ||= TestHookHelperController.new
166 end
166 end
167
167
168 def view_hook_helper
168 def view_hook_helper
169 @view_hook_helper ||= TestHookHelperView.new(Rails.root.to_s + '/app/views')
169 @view_hook_helper ||= TestHookHelperView.new(Rails.root.to_s + '/app/views')
170 end
170 end
171 end
171 end
172
172
@@ -1,607 +1,607
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2011 Jean-Philippe Lang
4 # Copyright (C) 2006-2011 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 require File.expand_path('../../test_helper', __FILE__)
20 require File.expand_path('../../test_helper', __FILE__)
21
21
22 class MailHandlerTest < ActiveSupport::TestCase
22 class MailHandlerTest < ActiveSupport::TestCase
23 fixtures :users, :projects, :enabled_modules, :roles,
23 fixtures :users, :projects, :enabled_modules, :roles,
24 :members, :member_roles, :users,
24 :members, :member_roles, :users,
25 :issues, :issue_statuses,
25 :issues, :issue_statuses,
26 :workflows, :trackers, :projects_trackers,
26 :workflows, :trackers, :projects_trackers,
27 :versions, :enumerations, :issue_categories,
27 :versions, :enumerations, :issue_categories,
28 :custom_fields, :custom_fields_trackers, :custom_fields_projects,
28 :custom_fields, :custom_fields_trackers, :custom_fields_projects,
29 :boards, :messages
29 :boards, :messages
30
30
31 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
31 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
32
32
33 def setup
33 def setup
34 ActionMailer::Base.deliveries.clear
34 ActionMailer::Base.deliveries.clear
35 Setting.notified_events = Redmine::Notifiable.all.collect(&:name)
35 Setting.notified_events = Redmine::Notifiable.all.collect(&:name)
36 end
36 end
37
37
38 def test_add_issue
38 def test_add_issue
39 ActionMailer::Base.deliveries.clear
39 ActionMailer::Base.deliveries.clear
40 # This email contains: 'Project: onlinestore'
40 # This email contains: 'Project: onlinestore'
41 issue = submit_email('ticket_on_given_project.eml')
41 issue = submit_email('ticket_on_given_project.eml')
42 assert issue.is_a?(Issue)
42 assert issue.is_a?(Issue)
43 assert !issue.new_record?
43 assert !issue.new_record?
44 issue.reload
44 issue.reload
45 assert_equal Project.find(2), issue.project
45 assert_equal Project.find(2), issue.project
46 assert_equal issue.project.trackers.first, issue.tracker
46 assert_equal issue.project.trackers.first, issue.tracker
47 assert_equal 'New ticket on a given project', issue.subject
47 assert_equal 'New ticket on a given project', issue.subject
48 assert_equal User.find_by_login('jsmith'), issue.author
48 assert_equal User.find_by_login('jsmith'), issue.author
49 assert_equal IssueStatus.find_by_name('Resolved'), issue.status
49 assert_equal IssueStatus.find_by_name('Resolved'), issue.status
50 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
50 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
51 assert_equal '2010-01-01', issue.start_date.to_s
51 assert_equal '2010-01-01', issue.start_date.to_s
52 assert_equal '2010-12-31', issue.due_date.to_s
52 assert_equal '2010-12-31', issue.due_date.to_s
53 assert_equal User.find_by_login('jsmith'), issue.assigned_to
53 assert_equal User.find_by_login('jsmith'), issue.assigned_to
54 assert_equal Version.find_by_name('Alpha'), issue.fixed_version
54 assert_equal Version.find_by_name('Alpha'), issue.fixed_version
55 assert_equal 2.5, issue.estimated_hours
55 assert_equal 2.5, issue.estimated_hours
56 assert_equal 30, issue.done_ratio
56 assert_equal 30, issue.done_ratio
57 assert_equal [issue.id, 1, 2], [issue.root_id, issue.lft, issue.rgt]
57 assert_equal [issue.id, 1, 2], [issue.root_id, issue.lft, issue.rgt]
58 # keywords should be removed from the email body
58 # keywords should be removed from the email body
59 assert !issue.description.match(/^Project:/i)
59 assert !issue.description.match(/^Project:/i)
60 assert !issue.description.match(/^Status:/i)
60 assert !issue.description.match(/^Status:/i)
61 assert !issue.description.match(/^Start Date:/i)
61 assert !issue.description.match(/^Start Date:/i)
62 # Email notification should be sent
62 # Email notification should be sent
63 mail = ActionMailer::Base.deliveries.last
63 mail = ActionMailer::Base.deliveries.last
64 assert_not_nil mail
64 assert_not_nil mail
65 assert mail.subject.include?('New ticket on a given project')
65 assert mail.subject.include?('New ticket on a given project')
66 end
66 end
67
67
68 def test_add_issue_with_default_tracker
68 def test_add_issue_with_default_tracker
69 # This email contains: 'Project: onlinestore'
69 # This email contains: 'Project: onlinestore'
70 issue = submit_email(
70 issue = submit_email(
71 'ticket_on_given_project.eml',
71 'ticket_on_given_project.eml',
72 :issue => {:tracker => 'Support request'}
72 :issue => {:tracker => 'Support request'}
73 )
73 )
74 assert issue.is_a?(Issue)
74 assert issue.is_a?(Issue)
75 assert !issue.new_record?
75 assert !issue.new_record?
76 issue.reload
76 issue.reload
77 assert_equal 'Support request', issue.tracker.name
77 assert_equal 'Support request', issue.tracker.name
78 end
78 end
79
79
80 def test_add_issue_with_status
80 def test_add_issue_with_status
81 # This email contains: 'Project: onlinestore' and 'Status: Resolved'
81 # This email contains: 'Project: onlinestore' and 'Status: Resolved'
82 issue = submit_email('ticket_on_given_project.eml')
82 issue = submit_email('ticket_on_given_project.eml')
83 assert issue.is_a?(Issue)
83 assert issue.is_a?(Issue)
84 assert !issue.new_record?
84 assert !issue.new_record?
85 issue.reload
85 issue.reload
86 assert_equal Project.find(2), issue.project
86 assert_equal Project.find(2), issue.project
87 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
87 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
88 end
88 end
89
89
90 def test_add_issue_with_attributes_override
90 def test_add_issue_with_attributes_override
91 issue = submit_email(
91 issue = submit_email(
92 'ticket_with_attributes.eml',
92 'ticket_with_attributes.eml',
93 :allow_override => 'tracker,category,priority'
93 :allow_override => 'tracker,category,priority'
94 )
94 )
95 assert issue.is_a?(Issue)
95 assert issue.is_a?(Issue)
96 assert !issue.new_record?
96 assert !issue.new_record?
97 issue.reload
97 issue.reload
98 assert_equal 'New ticket on a given project', issue.subject
98 assert_equal 'New ticket on a given project', issue.subject
99 assert_equal User.find_by_login('jsmith'), issue.author
99 assert_equal User.find_by_login('jsmith'), issue.author
100 assert_equal Project.find(2), issue.project
100 assert_equal Project.find(2), issue.project
101 assert_equal 'Feature request', issue.tracker.to_s
101 assert_equal 'Feature request', issue.tracker.to_s
102 assert_equal 'Stock management', issue.category.to_s
102 assert_equal 'Stock management', issue.category.to_s
103 assert_equal 'Urgent', issue.priority.to_s
103 assert_equal 'Urgent', issue.priority.to_s
104 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
104 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
105 end
105 end
106
106
107 def test_add_issue_with_group_assignment
107 def test_add_issue_with_group_assignment
108 with_settings :issue_group_assignment => '1' do
108 with_settings :issue_group_assignment => '1' do
109 issue = submit_email('ticket_on_given_project.eml') do |email|
109 issue = submit_email('ticket_on_given_project.eml') do |email|
110 email.gsub!('Assigned to: John Smith', 'Assigned to: B Team')
110 email.gsub!('Assigned to: John Smith', 'Assigned to: B Team')
111 end
111 end
112 assert issue.is_a?(Issue)
112 assert issue.is_a?(Issue)
113 assert !issue.new_record?
113 assert !issue.new_record?
114 issue.reload
114 issue.reload
115 assert_equal Group.find(11), issue.assigned_to
115 assert_equal Group.find(11), issue.assigned_to
116 end
116 end
117 end
117 end
118
118
119 def test_add_issue_with_partial_attributes_override
119 def test_add_issue_with_partial_attributes_override
120 issue = submit_email(
120 issue = submit_email(
121 'ticket_with_attributes.eml',
121 'ticket_with_attributes.eml',
122 :issue => {:priority => 'High'},
122 :issue => {:priority => 'High'},
123 :allow_override => ['tracker']
123 :allow_override => ['tracker']
124 )
124 )
125 assert issue.is_a?(Issue)
125 assert issue.is_a?(Issue)
126 assert !issue.new_record?
126 assert !issue.new_record?
127 issue.reload
127 issue.reload
128 assert_equal 'New ticket on a given project', issue.subject
128 assert_equal 'New ticket on a given project', issue.subject
129 assert_equal User.find_by_login('jsmith'), issue.author
129 assert_equal User.find_by_login('jsmith'), issue.author
130 assert_equal Project.find(2), issue.project
130 assert_equal Project.find(2), issue.project
131 assert_equal 'Feature request', issue.tracker.to_s
131 assert_equal 'Feature request', issue.tracker.to_s
132 assert_nil issue.category
132 assert_nil issue.category
133 assert_equal 'High', issue.priority.to_s
133 assert_equal 'High', issue.priority.to_s
134 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
134 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
135 end
135 end
136
136
137 def test_add_issue_with_spaces_between_attribute_and_separator
137 def test_add_issue_with_spaces_between_attribute_and_separator
138 issue = submit_email(
138 issue = submit_email(
139 'ticket_with_spaces_between_attribute_and_separator.eml',
139 'ticket_with_spaces_between_attribute_and_separator.eml',
140 :allow_override => 'tracker,category,priority'
140 :allow_override => 'tracker,category,priority'
141 )
141 )
142 assert issue.is_a?(Issue)
142 assert issue.is_a?(Issue)
143 assert !issue.new_record?
143 assert !issue.new_record?
144 issue.reload
144 issue.reload
145 assert_equal 'New ticket on a given project', issue.subject
145 assert_equal 'New ticket on a given project', issue.subject
146 assert_equal User.find_by_login('jsmith'), issue.author
146 assert_equal User.find_by_login('jsmith'), issue.author
147 assert_equal Project.find(2), issue.project
147 assert_equal Project.find(2), issue.project
148 assert_equal 'Feature request', issue.tracker.to_s
148 assert_equal 'Feature request', issue.tracker.to_s
149 assert_equal 'Stock management', issue.category.to_s
149 assert_equal 'Stock management', issue.category.to_s
150 assert_equal 'Urgent', issue.priority.to_s
150 assert_equal 'Urgent', issue.priority.to_s
151 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
151 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
152 end
152 end
153
153
154 def test_add_issue_with_attachment_to_specific_project
154 def test_add_issue_with_attachment_to_specific_project
155 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
155 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
156 assert issue.is_a?(Issue)
156 assert issue.is_a?(Issue)
157 assert !issue.new_record?
157 assert !issue.new_record?
158 issue.reload
158 issue.reload
159 assert_equal 'Ticket created by email with attachment', issue.subject
159 assert_equal 'Ticket created by email with attachment', issue.subject
160 assert_equal User.find_by_login('jsmith'), issue.author
160 assert_equal User.find_by_login('jsmith'), issue.author
161 assert_equal Project.find(2), issue.project
161 assert_equal Project.find(2), issue.project
162 assert_equal 'This is a new ticket with attachments', issue.description
162 assert_equal 'This is a new ticket with attachments', issue.description
163 # Attachment properties
163 # Attachment properties
164 assert_equal 1, issue.attachments.size
164 assert_equal 1, issue.attachments.size
165 assert_equal 'Paella.jpg', issue.attachments.first.filename
165 assert_equal 'Paella.jpg', issue.attachments.first.filename
166 assert_equal 'image/jpeg', issue.attachments.first.content_type
166 assert_equal 'image/jpeg', issue.attachments.first.content_type
167 assert_equal 10790, issue.attachments.first.filesize
167 assert_equal 10790, issue.attachments.first.filesize
168 end
168 end
169
169
170 def test_add_issue_with_custom_fields
170 def test_add_issue_with_custom_fields
171 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'onlinestore'})
171 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'onlinestore'})
172 assert issue.is_a?(Issue)
172 assert issue.is_a?(Issue)
173 assert !issue.new_record?
173 assert !issue.new_record?
174 issue.reload
174 issue.reload
175 assert_equal 'New ticket with custom field values', issue.subject
175 assert_equal 'New ticket with custom field values', issue.subject
176 assert_equal 'Value for a custom field',
176 assert_equal 'Value for a custom field',
177 issue.custom_value_for(CustomField.find_by_name('Searchable field')).value
177 issue.custom_value_for(CustomField.find_by_name('Searchable field')).value
178 assert !issue.description.match(/^searchable field:/i)
178 assert !issue.description.match(/^searchable field:/i)
179 end
179 end
180
180
181 def test_add_issue_with_cc
181 def test_add_issue_with_cc
182 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
182 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
183 assert issue.is_a?(Issue)
183 assert issue.is_a?(Issue)
184 assert !issue.new_record?
184 assert !issue.new_record?
185 issue.reload
185 issue.reload
186 assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
186 assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
187 assert_equal 1, issue.watcher_user_ids.size
187 assert_equal 1, issue.watcher_user_ids.size
188 end
188 end
189
189
190 def test_add_issue_by_unknown_user
190 def test_add_issue_by_unknown_user
191 assert_no_difference 'User.count' do
191 assert_no_difference 'User.count' do
192 assert_equal false,
192 assert_equal false,
193 submit_email(
193 submit_email(
194 'ticket_by_unknown_user.eml',
194 'ticket_by_unknown_user.eml',
195 :issue => {:project => 'ecookbook'}
195 :issue => {:project => 'ecookbook'}
196 )
196 )
197 end
197 end
198 end
198 end
199
199
200 def test_add_issue_by_anonymous_user
200 def test_add_issue_by_anonymous_user
201 Role.anonymous.add_permission!(:add_issues)
201 Role.anonymous.add_permission!(:add_issues)
202 assert_no_difference 'User.count' do
202 assert_no_difference 'User.count' do
203 issue = submit_email(
203 issue = submit_email(
204 'ticket_by_unknown_user.eml',
204 'ticket_by_unknown_user.eml',
205 :issue => {:project => 'ecookbook'},
205 :issue => {:project => 'ecookbook'},
206 :unknown_user => 'accept'
206 :unknown_user => 'accept'
207 )
207 )
208 assert issue.is_a?(Issue)
208 assert issue.is_a?(Issue)
209 assert issue.author.anonymous?
209 assert issue.author.anonymous?
210 end
210 end
211 end
211 end
212
212
213 def test_add_issue_by_anonymous_user_with_no_from_address
213 def test_add_issue_by_anonymous_user_with_no_from_address
214 Role.anonymous.add_permission!(:add_issues)
214 Role.anonymous.add_permission!(:add_issues)
215 assert_no_difference 'User.count' do
215 assert_no_difference 'User.count' do
216 issue = submit_email(
216 issue = submit_email(
217 'ticket_by_empty_user.eml',
217 'ticket_by_empty_user.eml',
218 :issue => {:project => 'ecookbook'},
218 :issue => {:project => 'ecookbook'},
219 :unknown_user => 'accept'
219 :unknown_user => 'accept'
220 )
220 )
221 assert issue.is_a?(Issue)
221 assert issue.is_a?(Issue)
222 assert issue.author.anonymous?
222 assert issue.author.anonymous?
223 end
223 end
224 end
224 end
225
225
226 def test_add_issue_by_anonymous_user_on_private_project
226 def test_add_issue_by_anonymous_user_on_private_project
227 Role.anonymous.add_permission!(:add_issues)
227 Role.anonymous.add_permission!(:add_issues)
228 assert_no_difference 'User.count' do
228 assert_no_difference 'User.count' do
229 assert_no_difference 'Issue.count' do
229 assert_no_difference 'Issue.count' do
230 assert_equal false,
230 assert_equal false,
231 submit_email(
231 submit_email(
232 'ticket_by_unknown_user.eml',
232 'ticket_by_unknown_user.eml',
233 :issue => {:project => 'onlinestore'},
233 :issue => {:project => 'onlinestore'},
234 :unknown_user => 'accept'
234 :unknown_user => 'accept'
235 )
235 )
236 end
236 end
237 end
237 end
238 end
238 end
239
239
240 def test_add_issue_by_anonymous_user_on_private_project_without_permission_check
240 def test_add_issue_by_anonymous_user_on_private_project_without_permission_check
241 assert_no_difference 'User.count' do
241 assert_no_difference 'User.count' do
242 assert_difference 'Issue.count' do
242 assert_difference 'Issue.count' do
243 issue = submit_email(
243 issue = submit_email(
244 'ticket_by_unknown_user.eml',
244 'ticket_by_unknown_user.eml',
245 :issue => {:project => 'onlinestore'},
245 :issue => {:project => 'onlinestore'},
246 :no_permission_check => '1',
246 :no_permission_check => '1',
247 :unknown_user => 'accept'
247 :unknown_user => 'accept'
248 )
248 )
249 assert issue.is_a?(Issue)
249 assert issue.is_a?(Issue)
250 assert issue.author.anonymous?
250 assert issue.author.anonymous?
251 assert !issue.project.is_public?
251 assert !issue.project.is_public?
252 assert_equal [issue.id, 1, 2], [issue.root_id, issue.lft, issue.rgt]
252 assert_equal [issue.id, 1, 2], [issue.root_id, issue.lft, issue.rgt]
253 end
253 end
254 end
254 end
255 end
255 end
256
256
257 def test_add_issue_by_created_user
257 def test_add_issue_by_created_user
258 Setting.default_language = 'en'
258 Setting.default_language = 'en'
259 assert_difference 'User.count' do
259 assert_difference 'User.count' do
260 issue = submit_email(
260 issue = submit_email(
261 'ticket_by_unknown_user.eml',
261 'ticket_by_unknown_user.eml',
262 :issue => {:project => 'ecookbook'},
262 :issue => {:project => 'ecookbook'},
263 :unknown_user => 'create'
263 :unknown_user => 'create'
264 )
264 )
265 assert issue.is_a?(Issue)
265 assert issue.is_a?(Issue)
266 assert issue.author.active?
266 assert issue.author.active?
267 assert_equal 'john.doe@somenet.foo', issue.author.mail
267 assert_equal 'john.doe@somenet.foo', issue.author.mail
268 assert_equal 'John', issue.author.firstname
268 assert_equal 'John', issue.author.firstname
269 assert_equal 'Doe', issue.author.lastname
269 assert_equal 'Doe', issue.author.lastname
270
270
271 # account information
271 # account information
272 email = ActionMailer::Base.deliveries.first
272 email = ActionMailer::Base.deliveries.first
273 assert_not_nil email
273 assert_not_nil email
274 assert email.subject.include?('account activation')
274 assert email.subject.include?('account activation')
275 login = email.body.match(/\* Login: (.*)$/)[1]
275 login = mail_body(email).match(/\* Login: (.*)$/)[1]
276 password = email.body.match(/\* Password: (.*)$/)[1]
276 password = mail_body(email).match(/\* Password: (.*)$/)[1]
277 assert_equal issue.author, User.try_to_login(login, password)
277 assert_equal issue.author, User.try_to_login(login, password)
278 end
278 end
279 end
279 end
280
280
281 def test_add_issue_without_from_header
281 def test_add_issue_without_from_header
282 Role.anonymous.add_permission!(:add_issues)
282 Role.anonymous.add_permission!(:add_issues)
283 assert_equal false, submit_email('ticket_without_from_header.eml')
283 assert_equal false, submit_email('ticket_without_from_header.eml')
284 end
284 end
285
285
286 def test_add_issue_with_invalid_attributes
286 def test_add_issue_with_invalid_attributes
287 issue = submit_email(
287 issue = submit_email(
288 'ticket_with_invalid_attributes.eml',
288 'ticket_with_invalid_attributes.eml',
289 :allow_override => 'tracker,category,priority'
289 :allow_override => 'tracker,category,priority'
290 )
290 )
291 assert issue.is_a?(Issue)
291 assert issue.is_a?(Issue)
292 assert !issue.new_record?
292 assert !issue.new_record?
293 issue.reload
293 issue.reload
294 assert_nil issue.assigned_to
294 assert_nil issue.assigned_to
295 assert_nil issue.start_date
295 assert_nil issue.start_date
296 assert_nil issue.due_date
296 assert_nil issue.due_date
297 assert_equal 0, issue.done_ratio
297 assert_equal 0, issue.done_ratio
298 assert_equal 'Normal', issue.priority.to_s
298 assert_equal 'Normal', issue.priority.to_s
299 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
299 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
300 end
300 end
301
301
302 def test_add_issue_with_localized_attributes
302 def test_add_issue_with_localized_attributes
303 User.find_by_mail('jsmith@somenet.foo').update_attribute 'language', 'fr'
303 User.find_by_mail('jsmith@somenet.foo').update_attribute 'language', 'fr'
304 issue = submit_email(
304 issue = submit_email(
305 'ticket_with_localized_attributes.eml',
305 'ticket_with_localized_attributes.eml',
306 :allow_override => 'tracker,category,priority'
306 :allow_override => 'tracker,category,priority'
307 )
307 )
308 assert issue.is_a?(Issue)
308 assert issue.is_a?(Issue)
309 assert !issue.new_record?
309 assert !issue.new_record?
310 issue.reload
310 issue.reload
311 assert_equal 'New ticket on a given project', issue.subject
311 assert_equal 'New ticket on a given project', issue.subject
312 assert_equal User.find_by_login('jsmith'), issue.author
312 assert_equal User.find_by_login('jsmith'), issue.author
313 assert_equal Project.find(2), issue.project
313 assert_equal Project.find(2), issue.project
314 assert_equal 'Feature request', issue.tracker.to_s
314 assert_equal 'Feature request', issue.tracker.to_s
315 assert_equal 'Stock management', issue.category.to_s
315 assert_equal 'Stock management', issue.category.to_s
316 assert_equal 'Urgent', issue.priority.to_s
316 assert_equal 'Urgent', issue.priority.to_s
317 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
317 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
318 end
318 end
319
319
320 def test_add_issue_with_japanese_keywords
320 def test_add_issue_with_japanese_keywords
321 ja_dev = "\xe9\x96\x8b\xe7\x99\xba"
321 ja_dev = "\xe9\x96\x8b\xe7\x99\xba"
322 ja_dev.force_encoding('UTF-8') if ja_dev.respond_to?(:force_encoding)
322 ja_dev.force_encoding('UTF-8') if ja_dev.respond_to?(:force_encoding)
323 tracker = Tracker.create!(:name => ja_dev)
323 tracker = Tracker.create!(:name => ja_dev)
324 Project.find(1).trackers << tracker
324 Project.find(1).trackers << tracker
325 issue = submit_email(
325 issue = submit_email(
326 'japanese_keywords_iso_2022_jp.eml',
326 'japanese_keywords_iso_2022_jp.eml',
327 :issue => {:project => 'ecookbook'},
327 :issue => {:project => 'ecookbook'},
328 :allow_override => 'tracker'
328 :allow_override => 'tracker'
329 )
329 )
330 assert_kind_of Issue, issue
330 assert_kind_of Issue, issue
331 assert_equal tracker, issue.tracker
331 assert_equal tracker, issue.tracker
332 end
332 end
333
333
334 def test_add_issue_from_apple_mail
334 def test_add_issue_from_apple_mail
335 issue = submit_email(
335 issue = submit_email(
336 'apple_mail_with_attachment.eml',
336 'apple_mail_with_attachment.eml',
337 :issue => {:project => 'ecookbook'}
337 :issue => {:project => 'ecookbook'}
338 )
338 )
339 assert_kind_of Issue, issue
339 assert_kind_of Issue, issue
340 assert_equal 1, issue.attachments.size
340 assert_equal 1, issue.attachments.size
341
341
342 attachment = issue.attachments.first
342 attachment = issue.attachments.first
343 assert_equal 'paella.jpg', attachment.filename
343 assert_equal 'paella.jpg', attachment.filename
344 assert_equal 10790, attachment.filesize
344 assert_equal 10790, attachment.filesize
345 assert File.exist?(attachment.diskfile)
345 assert File.exist?(attachment.diskfile)
346 assert_equal 10790, File.size(attachment.diskfile)
346 assert_equal 10790, File.size(attachment.diskfile)
347 assert_equal 'caaf384198bcbc9563ab5c058acd73cd', attachment.digest
347 assert_equal 'caaf384198bcbc9563ab5c058acd73cd', attachment.digest
348 end
348 end
349
349
350 def test_should_ignore_emails_from_emission_address
350 def test_should_ignore_emails_from_emission_address
351 Role.anonymous.add_permission!(:add_issues)
351 Role.anonymous.add_permission!(:add_issues)
352 assert_no_difference 'User.count' do
352 assert_no_difference 'User.count' do
353 assert_equal false,
353 assert_equal false,
354 submit_email(
354 submit_email(
355 'ticket_from_emission_address.eml',
355 'ticket_from_emission_address.eml',
356 :issue => {:project => 'ecookbook'},
356 :issue => {:project => 'ecookbook'},
357 :unknown_user => 'create'
357 :unknown_user => 'create'
358 )
358 )
359 end
359 end
360 end
360 end
361
361
362 def test_add_issue_should_send_email_notification
362 def test_add_issue_should_send_email_notification
363 Setting.notified_events = ['issue_added']
363 Setting.notified_events = ['issue_added']
364 ActionMailer::Base.deliveries.clear
364 ActionMailer::Base.deliveries.clear
365 # This email contains: 'Project: onlinestore'
365 # This email contains: 'Project: onlinestore'
366 issue = submit_email('ticket_on_given_project.eml')
366 issue = submit_email('ticket_on_given_project.eml')
367 assert issue.is_a?(Issue)
367 assert issue.is_a?(Issue)
368 assert_equal 1, ActionMailer::Base.deliveries.size
368 assert_equal 1, ActionMailer::Base.deliveries.size
369 end
369 end
370
370
371 def test_update_issue
371 def test_update_issue
372 journal = submit_email('ticket_reply.eml')
372 journal = submit_email('ticket_reply.eml')
373 assert journal.is_a?(Journal)
373 assert journal.is_a?(Journal)
374 assert_equal User.find_by_login('jsmith'), journal.user
374 assert_equal User.find_by_login('jsmith'), journal.user
375 assert_equal Issue.find(2), journal.journalized
375 assert_equal Issue.find(2), journal.journalized
376 assert_match /This is reply/, journal.notes
376 assert_match /This is reply/, journal.notes
377 assert_equal 'Feature request', journal.issue.tracker.name
377 assert_equal 'Feature request', journal.issue.tracker.name
378 end
378 end
379
379
380 def test_update_issue_with_attribute_changes
380 def test_update_issue_with_attribute_changes
381 # This email contains: 'Status: Resolved'
381 # This email contains: 'Status: Resolved'
382 journal = submit_email('ticket_reply_with_status.eml')
382 journal = submit_email('ticket_reply_with_status.eml')
383 assert journal.is_a?(Journal)
383 assert journal.is_a?(Journal)
384 issue = Issue.find(journal.issue.id)
384 issue = Issue.find(journal.issue.id)
385 assert_equal User.find_by_login('jsmith'), journal.user
385 assert_equal User.find_by_login('jsmith'), journal.user
386 assert_equal Issue.find(2), journal.journalized
386 assert_equal Issue.find(2), journal.journalized
387 assert_match /This is reply/, journal.notes
387 assert_match /This is reply/, journal.notes
388 assert_equal 'Feature request', journal.issue.tracker.name
388 assert_equal 'Feature request', journal.issue.tracker.name
389 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
389 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
390 assert_equal '2010-01-01', issue.start_date.to_s
390 assert_equal '2010-01-01', issue.start_date.to_s
391 assert_equal '2010-12-31', issue.due_date.to_s
391 assert_equal '2010-12-31', issue.due_date.to_s
392 assert_equal User.find_by_login('jsmith'), issue.assigned_to
392 assert_equal User.find_by_login('jsmith'), issue.assigned_to
393 assert_equal "52.6", issue.custom_value_for(CustomField.find_by_name('Float field')).value
393 assert_equal "52.6", issue.custom_value_for(CustomField.find_by_name('Float field')).value
394 # keywords should be removed from the email body
394 # keywords should be removed from the email body
395 assert !journal.notes.match(/^Status:/i)
395 assert !journal.notes.match(/^Status:/i)
396 assert !journal.notes.match(/^Start Date:/i)
396 assert !journal.notes.match(/^Start Date:/i)
397 end
397 end
398
398
399 def test_update_issue_with_attachment
399 def test_update_issue_with_attachment
400 assert_difference 'Journal.count' do
400 assert_difference 'Journal.count' do
401 assert_difference 'JournalDetail.count' do
401 assert_difference 'JournalDetail.count' do
402 assert_difference 'Attachment.count' do
402 assert_difference 'Attachment.count' do
403 assert_no_difference 'Issue.count' do
403 assert_no_difference 'Issue.count' do
404 journal = submit_email('ticket_with_attachment.eml') do |raw|
404 journal = submit_email('ticket_with_attachment.eml') do |raw|
405 raw.gsub! /^Subject: .*$/, 'Subject: Re: [Cookbook - Feature #2] (New) Add ingredients categories'
405 raw.gsub! /^Subject: .*$/, 'Subject: Re: [Cookbook - Feature #2] (New) Add ingredients categories'
406 end
406 end
407 end
407 end
408 end
408 end
409 end
409 end
410 end
410 end
411 journal = Journal.first(:order => 'id DESC')
411 journal = Journal.first(:order => 'id DESC')
412 assert_equal Issue.find(2), journal.journalized
412 assert_equal Issue.find(2), journal.journalized
413 assert_equal 1, journal.details.size
413 assert_equal 1, journal.details.size
414
414
415 detail = journal.details.first
415 detail = journal.details.first
416 assert_equal 'attachment', detail.property
416 assert_equal 'attachment', detail.property
417 assert_equal 'Paella.jpg', detail.value
417 assert_equal 'Paella.jpg', detail.value
418 end
418 end
419
419
420 def test_update_issue_should_send_email_notification
420 def test_update_issue_should_send_email_notification
421 ActionMailer::Base.deliveries.clear
421 ActionMailer::Base.deliveries.clear
422 journal = submit_email('ticket_reply.eml')
422 journal = submit_email('ticket_reply.eml')
423 assert journal.is_a?(Journal)
423 assert journal.is_a?(Journal)
424 assert_equal 1, ActionMailer::Base.deliveries.size
424 assert_equal 1, ActionMailer::Base.deliveries.size
425 end
425 end
426
426
427 def test_update_issue_should_not_set_defaults
427 def test_update_issue_should_not_set_defaults
428 journal = submit_email(
428 journal = submit_email(
429 'ticket_reply.eml',
429 'ticket_reply.eml',
430 :issue => {:tracker => 'Support request', :priority => 'High'}
430 :issue => {:tracker => 'Support request', :priority => 'High'}
431 )
431 )
432 assert journal.is_a?(Journal)
432 assert journal.is_a?(Journal)
433 assert_match /This is reply/, journal.notes
433 assert_match /This is reply/, journal.notes
434 assert_equal 'Feature request', journal.issue.tracker.name
434 assert_equal 'Feature request', journal.issue.tracker.name
435 assert_equal 'Normal', journal.issue.priority.name
435 assert_equal 'Normal', journal.issue.priority.name
436 end
436 end
437
437
438 def test_reply_to_a_message
438 def test_reply_to_a_message
439 m = submit_email('message_reply.eml')
439 m = submit_email('message_reply.eml')
440 assert m.is_a?(Message)
440 assert m.is_a?(Message)
441 assert !m.new_record?
441 assert !m.new_record?
442 m.reload
442 m.reload
443 assert_equal 'Reply via email', m.subject
443 assert_equal 'Reply via email', m.subject
444 # The email replies to message #2 which is part of the thread of message #1
444 # The email replies to message #2 which is part of the thread of message #1
445 assert_equal Message.find(1), m.parent
445 assert_equal Message.find(1), m.parent
446 end
446 end
447
447
448 def test_reply_to_a_message_by_subject
448 def test_reply_to_a_message_by_subject
449 m = submit_email('message_reply_by_subject.eml')
449 m = submit_email('message_reply_by_subject.eml')
450 assert m.is_a?(Message)
450 assert m.is_a?(Message)
451 assert !m.new_record?
451 assert !m.new_record?
452 m.reload
452 m.reload
453 assert_equal 'Reply to the first post', m.subject
453 assert_equal 'Reply to the first post', m.subject
454 assert_equal Message.find(1), m.parent
454 assert_equal Message.find(1), m.parent
455 end
455 end
456
456
457 def test_should_strip_tags_of_html_only_emails
457 def test_should_strip_tags_of_html_only_emails
458 issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
458 issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
459 assert issue.is_a?(Issue)
459 assert issue.is_a?(Issue)
460 assert !issue.new_record?
460 assert !issue.new_record?
461 issue.reload
461 issue.reload
462 assert_equal 'HTML email', issue.subject
462 assert_equal 'HTML email', issue.subject
463 assert_equal 'This is a html-only email.', issue.description
463 assert_equal 'This is a html-only email.', issue.description
464 end
464 end
465
465
466 context "truncate emails based on the Setting" do
466 context "truncate emails based on the Setting" do
467 context "with no setting" do
467 context "with no setting" do
468 setup do
468 setup do
469 Setting.mail_handler_body_delimiters = ''
469 Setting.mail_handler_body_delimiters = ''
470 end
470 end
471
471
472 should "add the entire email into the issue" do
472 should "add the entire email into the issue" do
473 issue = submit_email('ticket_on_given_project.eml')
473 issue = submit_email('ticket_on_given_project.eml')
474 assert_issue_created(issue)
474 assert_issue_created(issue)
475 assert issue.description.include?('---')
475 assert issue.description.include?('---')
476 assert issue.description.include?('This paragraph is after the delimiter')
476 assert issue.description.include?('This paragraph is after the delimiter')
477 end
477 end
478 end
478 end
479
479
480 context "with a single string" do
480 context "with a single string" do
481 setup do
481 setup do
482 Setting.mail_handler_body_delimiters = '---'
482 Setting.mail_handler_body_delimiters = '---'
483 end
483 end
484 should "truncate the email at the delimiter for the issue" do
484 should "truncate the email at the delimiter for the issue" do
485 issue = submit_email('ticket_on_given_project.eml')
485 issue = submit_email('ticket_on_given_project.eml')
486 assert_issue_created(issue)
486 assert_issue_created(issue)
487 assert issue.description.include?('This paragraph is before delimiters')
487 assert issue.description.include?('This paragraph is before delimiters')
488 assert issue.description.include?('--- This line starts with a delimiter')
488 assert issue.description.include?('--- This line starts with a delimiter')
489 assert !issue.description.match(/^---$/)
489 assert !issue.description.match(/^---$/)
490 assert !issue.description.include?('This paragraph is after the delimiter')
490 assert !issue.description.include?('This paragraph is after the delimiter')
491 end
491 end
492 end
492 end
493
493
494 context "with a single quoted reply (e.g. reply to a Redmine email notification)" do
494 context "with a single quoted reply (e.g. reply to a Redmine email notification)" do
495 setup do
495 setup do
496 Setting.mail_handler_body_delimiters = '--- Reply above. Do not remove this line. ---'
496 Setting.mail_handler_body_delimiters = '--- Reply above. Do not remove this line. ---'
497 end
497 end
498 should "truncate the email at the delimiter with the quoted reply symbols (>)" do
498 should "truncate the email at the delimiter with the quoted reply symbols (>)" do
499 journal = submit_email('issue_update_with_quoted_reply_above.eml')
499 journal = submit_email('issue_update_with_quoted_reply_above.eml')
500 assert journal.is_a?(Journal)
500 assert journal.is_a?(Journal)
501 assert journal.notes.include?('An update to the issue by the sender.')
501 assert journal.notes.include?('An update to the issue by the sender.')
502 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
502 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
503 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
503 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
504 end
504 end
505 end
505 end
506
506
507 context "with multiple quoted replies (e.g. reply to a reply of a Redmine email notification)" do
507 context "with multiple quoted replies (e.g. reply to a reply of a Redmine email notification)" do
508 setup do
508 setup do
509 Setting.mail_handler_body_delimiters = '--- Reply above. Do not remove this line. ---'
509 Setting.mail_handler_body_delimiters = '--- Reply above. Do not remove this line. ---'
510 end
510 end
511 should "truncate the email at the delimiter with the quoted reply symbols (>)" do
511 should "truncate the email at the delimiter with the quoted reply symbols (>)" do
512 journal = submit_email('issue_update_with_multiple_quoted_reply_above.eml')
512 journal = submit_email('issue_update_with_multiple_quoted_reply_above.eml')
513 assert journal.is_a?(Journal)
513 assert journal.is_a?(Journal)
514 assert journal.notes.include?('An update to the issue by the sender.')
514 assert journal.notes.include?('An update to the issue by the sender.')
515 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
515 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
516 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
516 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
517 end
517 end
518 end
518 end
519
519
520 context "with multiple strings" do
520 context "with multiple strings" do
521 setup do
521 setup do
522 Setting.mail_handler_body_delimiters = "---\nBREAK"
522 Setting.mail_handler_body_delimiters = "---\nBREAK"
523 end
523 end
524 should "truncate the email at the first delimiter found (BREAK)" do
524 should "truncate the email at the first delimiter found (BREAK)" do
525 issue = submit_email('ticket_on_given_project.eml')
525 issue = submit_email('ticket_on_given_project.eml')
526 assert_issue_created(issue)
526 assert_issue_created(issue)
527 assert issue.description.include?('This paragraph is before delimiters')
527 assert issue.description.include?('This paragraph is before delimiters')
528 assert !issue.description.include?('BREAK')
528 assert !issue.description.include?('BREAK')
529 assert !issue.description.include?('This paragraph is between delimiters')
529 assert !issue.description.include?('This paragraph is between delimiters')
530 assert !issue.description.match(/^---$/)
530 assert !issue.description.match(/^---$/)
531 assert !issue.description.include?('This paragraph is after the delimiter')
531 assert !issue.description.include?('This paragraph is after the delimiter')
532 end
532 end
533 end
533 end
534 end
534 end
535
535
536 def test_email_with_long_subject_line
536 def test_email_with_long_subject_line
537 issue = submit_email('ticket_with_long_subject.eml')
537 issue = submit_email('ticket_with_long_subject.eml')
538 assert issue.is_a?(Issue)
538 assert issue.is_a?(Issue)
539 assert_equal issue.subject, 'New ticket on a given project with a very long subject line which exceeds 255 chars and should not be ignored but chopped off. And if the subject line is still not long enough, we just add more text. And more text. Wow, this is really annoying. Especially, if you have nothing to say...'[0,255]
539 assert_equal issue.subject, 'New ticket on a given project with a very long subject line which exceeds 255 chars and should not be ignored but chopped off. And if the subject line is still not long enough, we just add more text. And more text. Wow, this is really annoying. Especially, if you have nothing to say...'[0,255]
540 end
540 end
541
541
542 def test_new_user_from_attributes_should_return_valid_user
542 def test_new_user_from_attributes_should_return_valid_user
543 to_test = {
543 to_test = {
544 # [address, name] => [login, firstname, lastname]
544 # [address, name] => [login, firstname, lastname]
545 ['jsmith@example.net', nil] => ['jsmith@example.net', 'jsmith', '-'],
545 ['jsmith@example.net', nil] => ['jsmith@example.net', 'jsmith', '-'],
546 ['jsmith@example.net', 'John'] => ['jsmith@example.net', 'John', '-'],
546 ['jsmith@example.net', 'John'] => ['jsmith@example.net', 'John', '-'],
547 ['jsmith@example.net', 'John Smith'] => ['jsmith@example.net', 'John', 'Smith'],
547 ['jsmith@example.net', 'John Smith'] => ['jsmith@example.net', 'John', 'Smith'],
548 ['jsmith@example.net', 'John Paul Smith'] => ['jsmith@example.net', 'John', 'Paul Smith'],
548 ['jsmith@example.net', 'John Paul Smith'] => ['jsmith@example.net', 'John', 'Paul Smith'],
549 ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsTheMaximumLength Smith'] => ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsT', 'Smith'],
549 ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsTheMaximumLength Smith'] => ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsT', 'Smith'],
550 ['jsmith@example.net', 'John AVeryLongLastnameThatExceedsTheMaximumLength'] => ['jsmith@example.net', 'John', 'AVeryLongLastnameThatExceedsTh']
550 ['jsmith@example.net', 'John AVeryLongLastnameThatExceedsTheMaximumLength'] => ['jsmith@example.net', 'John', 'AVeryLongLastnameThatExceedsTh']
551 }
551 }
552
552
553 to_test.each do |attrs, expected|
553 to_test.each do |attrs, expected|
554 user = MailHandler.new_user_from_attributes(attrs.first, attrs.last)
554 user = MailHandler.new_user_from_attributes(attrs.first, attrs.last)
555
555
556 assert user.valid?, user.errors.full_messages.to_s
556 assert user.valid?, user.errors.full_messages.to_s
557 assert_equal attrs.first, user.mail
557 assert_equal attrs.first, user.mail
558 assert_equal expected[0], user.login
558 assert_equal expected[0], user.login
559 assert_equal expected[1], user.firstname
559 assert_equal expected[1], user.firstname
560 assert_equal expected[2], user.lastname
560 assert_equal expected[2], user.lastname
561 end
561 end
562 end
562 end
563
563
564 def test_new_user_from_attributes_should_respect_minimum_password_length
564 def test_new_user_from_attributes_should_respect_minimum_password_length
565 with_settings :password_min_length => 15 do
565 with_settings :password_min_length => 15 do
566 user = MailHandler.new_user_from_attributes('jsmith@example.net')
566 user = MailHandler.new_user_from_attributes('jsmith@example.net')
567 assert user.valid?
567 assert user.valid?
568 assert user.password.length >= 15
568 assert user.password.length >= 15
569 end
569 end
570 end
570 end
571
571
572 def test_new_user_from_attributes_should_use_default_login_if_invalid
572 def test_new_user_from_attributes_should_use_default_login_if_invalid
573 user = MailHandler.new_user_from_attributes('foo+bar@example.net')
573 user = MailHandler.new_user_from_attributes('foo+bar@example.net')
574 assert user.valid?
574 assert user.valid?
575 assert user.login =~ /^user[a-f0-9]+$/
575 assert user.login =~ /^user[a-f0-9]+$/
576 assert_equal 'foo+bar@example.net', user.mail
576 assert_equal 'foo+bar@example.net', user.mail
577 end
577 end
578
578
579 def test_new_user_with_utf8_encoded_fullname_should_be_decoded
579 def test_new_user_with_utf8_encoded_fullname_should_be_decoded
580 assert_difference 'User.count' do
580 assert_difference 'User.count' do
581 issue = submit_email(
581 issue = submit_email(
582 'fullname_of_sender_as_utf8_encoded.eml',
582 'fullname_of_sender_as_utf8_encoded.eml',
583 :issue => {:project => 'ecookbook'},
583 :issue => {:project => 'ecookbook'},
584 :unknown_user => 'create'
584 :unknown_user => 'create'
585 )
585 )
586 end
586 end
587
587
588 user = User.first(:order => 'id DESC')
588 user = User.first(:order => 'id DESC')
589 assert_equal "foo@example.org", user.mail
589 assert_equal "foo@example.org", user.mail
590 assert_equal "Ää", user.firstname
590 assert_equal "Ää", user.firstname
591 assert_equal "Öö", user.lastname
591 assert_equal "Öö", user.lastname
592 end
592 end
593
593
594 private
594 private
595
595
596 def submit_email(filename, options={})
596 def submit_email(filename, options={})
597 raw = IO.read(File.join(FIXTURES_PATH, filename))
597 raw = IO.read(File.join(FIXTURES_PATH, filename))
598 yield raw if block_given?
598 yield raw if block_given?
599 MailHandler.receive(raw, options)
599 MailHandler.receive(raw, options)
600 end
600 end
601
601
602 def assert_issue_created(issue)
602 def assert_issue_created(issue)
603 assert issue.is_a?(Issue)
603 assert issue.is_a?(Issue)
604 assert !issue.new_record?
604 assert !issue.new_record?
605 issue.reload
605 issue.reload
606 end
606 end
607 end
607 end
General Comments 0
You need to be logged in to leave comments. Login now