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