##// END OF EJS Templates
adjust tests to awesome_nested_set new node lft and rgt value behavior change...
Toshi MARUYAMA -
r12418:17a5f26e5027
parent child
Show More
@@ -1,468 +1,480
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 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 'shoulda'
19 19 ENV["RAILS_ENV"] = "test"
20 20 require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
21 21 require 'rails/test_help'
22 22 require Rails.root.join('test', 'mocks', 'open_id_authentication_mock.rb').to_s
23 23
24 24 require File.expand_path(File.dirname(__FILE__) + '/object_helpers')
25 25 include ObjectHelpers
26 26
27 require 'awesome_nested_set/version'
28
27 29 class ActiveSupport::TestCase
28 30 include ActionDispatch::TestProcess
29 31
30 32 self.use_transactional_fixtures = true
31 33 self.use_instantiated_fixtures = false
32 34
33 35 def log_user(login, password)
34 36 User.anonymous
35 37 get "/login"
36 38 assert_equal nil, session[:user_id]
37 39 assert_response :success
38 40 assert_template "account/login"
39 41 post "/login", :username => login, :password => password
40 42 assert_equal login, User.find(session[:user_id]).login
41 43 end
42 44
43 45 def uploaded_test_file(name, mime)
44 46 fixture_file_upload("files/#{name}", mime, true)
45 47 end
46 48
47 49 def credentials(user, password=nil)
48 50 {'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)}
49 51 end
50 52
51 53 # Mock out a file
52 54 def self.mock_file
53 55 file = 'a_file.png'
54 56 file.stubs(:size).returns(32)
55 57 file.stubs(:original_filename).returns('a_file.png')
56 58 file.stubs(:content_type).returns('image/png')
57 59 file.stubs(:read).returns(false)
58 60 file
59 61 end
60 62
61 63 def mock_file
62 64 self.class.mock_file
63 65 end
64 66
65 67 def mock_file_with_options(options={})
66 68 file = ''
67 69 file.stubs(:size).returns(32)
68 70 original_filename = options[:original_filename] || nil
69 71 file.stubs(:original_filename).returns(original_filename)
70 72 content_type = options[:content_type] || nil
71 73 file.stubs(:content_type).returns(content_type)
72 74 file.stubs(:read).returns(false)
73 75 file
74 76 end
75 77
76 78 # Use a temporary directory for attachment related tests
77 79 def set_tmp_attachments_directory
78 80 Dir.mkdir "#{Rails.root}/tmp/test" unless File.directory?("#{Rails.root}/tmp/test")
79 81 unless File.directory?("#{Rails.root}/tmp/test/attachments")
80 82 Dir.mkdir "#{Rails.root}/tmp/test/attachments"
81 83 end
82 84 Attachment.storage_path = "#{Rails.root}/tmp/test/attachments"
83 85 end
84 86
85 87 def set_fixtures_attachments_directory
86 88 Attachment.storage_path = "#{Rails.root}/test/fixtures/files"
87 89 end
88 90
89 91 def with_settings(options, &block)
90 92 saved_settings = options.keys.inject({}) do |h, k|
91 93 h[k] = case Setting[k]
92 94 when Symbol, false, true, nil
93 95 Setting[k]
94 96 else
95 97 Setting[k].dup
96 98 end
97 99 h
98 100 end
99 101 options.each {|k, v| Setting[k] = v}
100 102 yield
101 103 ensure
102 104 saved_settings.each {|k, v| Setting[k] = v} if saved_settings
103 105 end
104 106
105 107 # Yields the block with user as the current user
106 108 def with_current_user(user, &block)
107 109 saved_user = User.current
108 110 User.current = user
109 111 yield
110 112 ensure
111 113 User.current = saved_user
112 114 end
113 115
114 116 def change_user_password(login, new_password)
115 117 user = User.where(:login => login).first
116 118 user.password, user.password_confirmation = new_password, new_password
117 119 user.save!
118 120 end
119 121
120 122 def self.ldap_configured?
121 123 @test_ldap = Net::LDAP.new(:host => '127.0.0.1', :port => 389)
122 124 return @test_ldap.bind
123 125 rescue Exception => e
124 126 # LDAP is not listening
125 127 return nil
126 128 end
127 129
128 130 def self.convert_installed?
129 131 Redmine::Thumbnail.convert_available?
130 132 end
131 133
132 134 # Returns the path to the test +vendor+ repository
133 135 def self.repository_path(vendor)
134 136 Rails.root.join("tmp/test/#{vendor.downcase}_repository").to_s
135 137 end
136 138
137 139 # Returns the url of the subversion test repository
138 140 def self.subversion_repository_url
139 141 path = repository_path('subversion')
140 142 path = '/' + path unless path.starts_with?('/')
141 143 "file://#{path}"
142 144 end
143 145
144 146 # Returns true if the +vendor+ test repository is configured
145 147 def self.repository_configured?(vendor)
146 148 File.directory?(repository_path(vendor))
147 149 end
148 150
149 151 def repository_path_hash(arr)
150 152 hs = {}
151 153 hs[:path] = arr.join("/")
152 154 hs[:param] = arr.join("/")
153 155 hs
154 156 end
155 157
156 158 def assert_save(object)
157 159 saved = object.save
158 160 message = "#{object.class} could not be saved"
159 161 errors = object.errors.full_messages.map {|m| "- #{m}"}
160 162 message << ":\n#{errors.join("\n")}" if errors.any?
161 163 assert_equal true, saved, message
162 164 end
163 165
164 166 def assert_error_tag(options={})
165 167 assert_tag({:attributes => { :id => 'errorExplanation' }}.merge(options))
166 168 end
167 169
168 170 def assert_include(expected, s, message=nil)
169 171 assert s.include?(expected), (message || "\"#{expected}\" not found in \"#{s}\"")
170 172 end
171 173
172 174 def assert_not_include(expected, s, message=nil)
173 175 assert !s.include?(expected), (message || "\"#{expected}\" found in \"#{s}\"")
174 176 end
175 177
176 178 def assert_select_in(text, *args, &block)
177 179 d = HTML::Document.new(CGI::unescapeHTML(String.new(text))).root
178 180 assert_select(d, *args, &block)
179 181 end
180 182
181 183 def assert_mail_body_match(expected, mail, message=nil)
182 184 if expected.is_a?(String)
183 185 assert_include expected, mail_body(mail), message
184 186 else
185 187 assert_match expected, mail_body(mail), message
186 188 end
187 189 end
188 190
189 191 def assert_mail_body_no_match(expected, mail, message=nil)
190 192 if expected.is_a?(String)
191 193 assert_not_include expected, mail_body(mail), message
192 194 else
193 195 assert_no_match expected, mail_body(mail), message
194 196 end
195 197 end
196 198
197 199 def mail_body(mail)
198 200 mail.parts.first.body.encoded
199 201 end
202
203 # awesome_nested_set new node lft and rgt value changed this refactor revision.
204 # https://github.com/collectiveidea/awesome_nested_set/commit/199fca9bb938e40200cd90714dc69247ef017c61
205 # The reason of behavior change is "self.class.base_class.unscoped" added this line.
206 # https://github.com/collectiveidea/awesome_nested_set/commit/199fca9bb9#diff-f61b59a5e6319024e211b0ffdd0e4ef1R273
207 # It seems correct behavior because of this line comment.
208 # https://github.com/collectiveidea/awesome_nested_set/blame/199fca9bb9/lib/awesome_nested_set/model.rb#L278
209 def new_issue_lft
210 ::AwesomeNestedSet::VERSION > "2.1.6" ? Issue.maximum(:rgt) + 1 : 1
211 end
200 212 end
201 213
202 214 module Redmine
203 215 module ApiTest
204 216 # Base class for API tests
205 217 class Base < ActionDispatch::IntegrationTest
206 218 # Test that a request allows the three types of API authentication
207 219 #
208 220 # * HTTP Basic with username and password
209 221 # * HTTP Basic with an api key for the username
210 222 # * Key based with the key=X parameter
211 223 #
212 224 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
213 225 # @param [String] url the request url
214 226 # @param [optional, Hash] parameters additional request parameters
215 227 # @param [optional, Hash] options additional options
216 228 # @option options [Symbol] :success_code Successful response code (:success)
217 229 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
218 230 def self.should_allow_api_authentication(http_method, url, parameters={}, options={})
219 231 should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters, options)
220 232 should_allow_http_basic_auth_with_key(http_method, url, parameters, options)
221 233 should_allow_key_based_auth(http_method, url, parameters, options)
222 234 end
223 235
224 236 # Test that a request allows the username and password for HTTP BASIC
225 237 #
226 238 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
227 239 # @param [String] url the request url
228 240 # @param [optional, Hash] parameters additional request parameters
229 241 # @param [optional, Hash] options additional options
230 242 # @option options [Symbol] :success_code Successful response code (:success)
231 243 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
232 244 def self.should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters={}, options={})
233 245 success_code = options[:success_code] || :success
234 246 failure_code = options[:failure_code] || :unauthorized
235 247
236 248 context "should allow http basic auth using a username and password for #{http_method} #{url}" do
237 249 context "with a valid HTTP authentication" do
238 250 setup do
239 251 @user = User.generate! do |user|
240 252 user.admin = true
241 253 user.password = 'my_password'
242 254 end
243 255 send(http_method, url, parameters, credentials(@user.login, 'my_password'))
244 256 end
245 257
246 258 should_respond_with success_code
247 259 should_respond_with_content_type_based_on_url(url)
248 260 should "login as the user" do
249 261 assert_equal @user, User.current
250 262 end
251 263 end
252 264
253 265 context "with an invalid HTTP authentication" do
254 266 setup do
255 267 @user = User.generate!
256 268 send(http_method, url, parameters, credentials(@user.login, 'wrong_password'))
257 269 end
258 270
259 271 should_respond_with failure_code
260 272 should_respond_with_content_type_based_on_url(url)
261 273 should "not login as the user" do
262 274 assert_equal User.anonymous, User.current
263 275 end
264 276 end
265 277
266 278 context "without credentials" do
267 279 setup do
268 280 send(http_method, url, parameters)
269 281 end
270 282
271 283 should_respond_with failure_code
272 284 should_respond_with_content_type_based_on_url(url)
273 285 should "include_www_authenticate_header" do
274 286 assert @controller.response.headers.has_key?('WWW-Authenticate')
275 287 end
276 288 end
277 289 end
278 290 end
279 291
280 292 # Test that a request allows the API key with HTTP BASIC
281 293 #
282 294 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
283 295 # @param [String] url the request url
284 296 # @param [optional, Hash] parameters additional request parameters
285 297 # @param [optional, Hash] options additional options
286 298 # @option options [Symbol] :success_code Successful response code (:success)
287 299 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
288 300 def self.should_allow_http_basic_auth_with_key(http_method, url, parameters={}, options={})
289 301 success_code = options[:success_code] || :success
290 302 failure_code = options[:failure_code] || :unauthorized
291 303
292 304 context "should allow http basic auth with a key for #{http_method} #{url}" do
293 305 context "with a valid HTTP authentication using the API token" do
294 306 setup do
295 307 @user = User.generate! do |user|
296 308 user.admin = true
297 309 end
298 310 @token = Token.create!(:user => @user, :action => 'api')
299 311 send(http_method, url, parameters, credentials(@token.value, 'X'))
300 312 end
301 313 should_respond_with success_code
302 314 should_respond_with_content_type_based_on_url(url)
303 315 should_be_a_valid_response_string_based_on_url(url)
304 316 should "login as the user" do
305 317 assert_equal @user, User.current
306 318 end
307 319 end
308 320
309 321 context "with an invalid HTTP authentication" do
310 322 setup do
311 323 @user = User.generate!
312 324 @token = Token.create!(:user => @user, :action => 'feeds')
313 325 send(http_method, url, parameters, credentials(@token.value, 'X'))
314 326 end
315 327 should_respond_with failure_code
316 328 should_respond_with_content_type_based_on_url(url)
317 329 should "not login as the user" do
318 330 assert_equal User.anonymous, User.current
319 331 end
320 332 end
321 333 end
322 334 end
323 335
324 336 # Test that a request allows full key authentication
325 337 #
326 338 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
327 339 # @param [String] url the request url, without the key=ZXY parameter
328 340 # @param [optional, Hash] parameters additional request parameters
329 341 # @param [optional, Hash] options additional options
330 342 # @option options [Symbol] :success_code Successful response code (:success)
331 343 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
332 344 def self.should_allow_key_based_auth(http_method, url, parameters={}, options={})
333 345 success_code = options[:success_code] || :success
334 346 failure_code = options[:failure_code] || :unauthorized
335 347
336 348 context "should allow key based auth using key=X for #{http_method} #{url}" do
337 349 context "with a valid api token" do
338 350 setup do
339 351 @user = User.generate! do |user|
340 352 user.admin = true
341 353 end
342 354 @token = Token.create!(:user => @user, :action => 'api')
343 355 # Simple url parse to add on ?key= or &key=
344 356 request_url = if url.match(/\?/)
345 357 url + "&key=#{@token.value}"
346 358 else
347 359 url + "?key=#{@token.value}"
348 360 end
349 361 send(http_method, request_url, parameters)
350 362 end
351 363 should_respond_with success_code
352 364 should_respond_with_content_type_based_on_url(url)
353 365 should_be_a_valid_response_string_based_on_url(url)
354 366 should "login as the user" do
355 367 assert_equal @user, User.current
356 368 end
357 369 end
358 370
359 371 context "with an invalid api token" do
360 372 setup do
361 373 @user = User.generate! do |user|
362 374 user.admin = true
363 375 end
364 376 @token = Token.create!(:user => @user, :action => 'feeds')
365 377 # Simple url parse to add on ?key= or &key=
366 378 request_url = if url.match(/\?/)
367 379 url + "&key=#{@token.value}"
368 380 else
369 381 url + "?key=#{@token.value}"
370 382 end
371 383 send(http_method, request_url, parameters)
372 384 end
373 385 should_respond_with failure_code
374 386 should_respond_with_content_type_based_on_url(url)
375 387 should "not login as the user" do
376 388 assert_equal User.anonymous, User.current
377 389 end
378 390 end
379 391 end
380 392
381 393 context "should allow key based auth using X-Redmine-API-Key header for #{http_method} #{url}" do
382 394 setup do
383 395 @user = User.generate! do |user|
384 396 user.admin = true
385 397 end
386 398 @token = Token.create!(:user => @user, :action => 'api')
387 399 send(http_method, url, parameters, {'X-Redmine-API-Key' => @token.value.to_s})
388 400 end
389 401 should_respond_with success_code
390 402 should_respond_with_content_type_based_on_url(url)
391 403 should_be_a_valid_response_string_based_on_url(url)
392 404 should "login as the user" do
393 405 assert_equal @user, User.current
394 406 end
395 407 end
396 408 end
397 409
398 410 # Uses should_respond_with_content_type based on what's in the url:
399 411 #
400 412 # '/project/issues.xml' => should_respond_with_content_type :xml
401 413 # '/project/issues.json' => should_respond_with_content_type :json
402 414 #
403 415 # @param [String] url Request
404 416 def self.should_respond_with_content_type_based_on_url(url)
405 417 case
406 418 when url.match(/xml/i)
407 419 should "respond with XML" do
408 420 assert_equal 'application/xml', @response.content_type
409 421 end
410 422 when url.match(/json/i)
411 423 should "respond with JSON" do
412 424 assert_equal 'application/json', @response.content_type
413 425 end
414 426 else
415 427 raise "Unknown content type for should_respond_with_content_type_based_on_url: #{url}"
416 428 end
417 429 end
418 430
419 431 # Uses the url to assert which format the response should be in
420 432 #
421 433 # '/project/issues.xml' => should_be_a_valid_xml_string
422 434 # '/project/issues.json' => should_be_a_valid_json_string
423 435 #
424 436 # @param [String] url Request
425 437 def self.should_be_a_valid_response_string_based_on_url(url)
426 438 case
427 439 when url.match(/xml/i)
428 440 should_be_a_valid_xml_string
429 441 when url.match(/json/i)
430 442 should_be_a_valid_json_string
431 443 else
432 444 raise "Unknown content type for should_be_a_valid_response_based_on_url: #{url}"
433 445 end
434 446 end
435 447
436 448 # Checks that the response is a valid JSON string
437 449 def self.should_be_a_valid_json_string
438 450 should "be a valid JSON string (or empty)" do
439 451 assert(response.body.blank? || ActiveSupport::JSON.decode(response.body))
440 452 end
441 453 end
442 454
443 455 # Checks that the response is a valid XML string
444 456 def self.should_be_a_valid_xml_string
445 457 should "be a valid XML string" do
446 458 assert REXML::Document.new(response.body)
447 459 end
448 460 end
449 461
450 462 def self.should_respond_with(status)
451 463 should "respond with #{status}" do
452 464 assert_response status
453 465 end
454 466 end
455 467 end
456 468 end
457 469 end
458 470
459 471 # URL helpers do not work with config.threadsafe!
460 472 # https://github.com/rspec/rspec-rails/issues/476#issuecomment-4705454
461 473 ActionView::TestCase::TestController.instance_eval do
462 474 helper Rails.application.routes.url_helpers
463 475 end
464 476 ActionView::TestCase::TestController.class_eval do
465 477 def _routes
466 478 Rails.application.routes
467 479 end
468 480 end
@@ -1,409 +1,402
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 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 IssueNestedSetTest < ActiveSupport::TestCase
21 21 fixtures :projects, :users, :roles,
22 22 :trackers, :projects_trackers,
23 23 :issue_statuses, :issue_categories, :issue_relations,
24 24 :enumerations,
25 25 :issues
26 26
27 27 def test_new_record_is_leaf
28 28 i = Issue.new
29 29 assert i.leaf?
30 30 end
31 31
32 32 def test_create_root_issue
33 lft1 = new_issue_lft
33 34 issue1 = Issue.generate!
35 lft2 = new_issue_lft
34 36 issue2 = Issue.generate!
35 37 issue1.reload
36 38 issue2.reload
37
38 assert_equal [issue1.id, nil, 1, 2], [issue1.root_id, issue1.parent_id, issue1.lft, issue1.rgt]
39 assert_equal [issue2.id, nil, 1, 2], [issue2.root_id, issue2.parent_id, issue2.lft, issue2.rgt]
39 assert_equal [issue1.id, nil, lft1, lft1 + 1], [issue1.root_id, issue1.parent_id, issue1.lft, issue1.rgt]
40 assert_equal [issue2.id, nil, lft2, lft2 + 1], [issue2.root_id, issue2.parent_id, issue2.lft, issue2.rgt]
40 41 end
41 42
42 43 def test_create_child_issue
44 lft = new_issue_lft
43 45 parent = Issue.generate!
44 46 child = Issue.generate!(:parent_issue_id => parent.id)
45 47 parent.reload
46 48 child.reload
47
48 assert_equal [parent.id, nil, 1, 4], [parent.root_id, parent.parent_id, parent.lft, parent.rgt]
49 assert_equal [parent.id, parent.id, 2, 3], [child.root_id, child.parent_id, child.lft, child.rgt]
49 assert_equal [parent.id, nil, lft, lft + 3], [parent.root_id, parent.parent_id, parent.lft, parent.rgt]
50 assert_equal [parent.id, parent.id, lft + 1, lft + 2], [child.root_id, child.parent_id, child.lft, child.rgt]
50 51 end
51 52
52 53 def test_creating_a_child_in_a_subproject_should_validate
53 54 issue = Issue.generate!
54 55 child = Issue.new(:project_id => 3, :tracker_id => 2, :author_id => 1,
55 56 :subject => 'child', :parent_issue_id => issue.id)
56 57 assert_save child
57 58 assert_equal issue, child.reload.parent
58 59 end
59 60
60 61 def test_creating_a_child_in_an_invalid_project_should_not_validate
61 62 issue = Issue.generate!
62 63 child = Issue.new(:project_id => 2, :tracker_id => 1, :author_id => 1,
63 64 :subject => 'child', :parent_issue_id => issue.id)
64 65 assert !child.save
65 66 assert_not_equal [], child.errors[:parent_issue_id]
66 67 end
67 68
68 69 def test_move_a_root_to_child
70 lft = new_issue_lft
69 71 parent1 = Issue.generate!
70 72 parent2 = Issue.generate!
71 73 child = Issue.generate!(:parent_issue_id => parent1.id)
72
73 74 parent2.parent_issue_id = parent1.id
74 75 parent2.save!
75 76 child.reload
76 77 parent1.reload
77 78 parent2.reload
78
79 assert_equal [parent1.id, 1, 6], [parent1.root_id, parent1.lft, parent1.rgt]
80 assert_equal [parent1.id, 4, 5], [parent2.root_id, parent2.lft, parent2.rgt]
81 assert_equal [parent1.id, 2, 3], [child.root_id, child.lft, child.rgt]
79 assert_equal [parent1.id, lft, lft + 5], [parent1.root_id, parent1.lft, parent1.rgt]
80 assert_equal [parent1.id, lft + 3, lft + 4], [parent2.root_id, parent2.lft, parent2.rgt]
81 assert_equal [parent1.id, lft + 1, lft + 2], [child.root_id, child.lft, child.rgt]
82 82 end
83 83
84 84 def test_move_a_child_to_root
85 lft1 = new_issue_lft
85 86 parent1 = Issue.generate!
87 lft2 = new_issue_lft
86 88 parent2 = Issue.generate!
87 89 child = Issue.generate!(:parent_issue_id => parent1.id)
88
89 90 child.parent_issue_id = nil
90 91 child.save!
91 92 child.reload
92 93 parent1.reload
93 94 parent2.reload
94
95 assert_equal [parent1.id, 1, 2], [parent1.root_id, parent1.lft, parent1.rgt]
96 assert_equal [parent2.id, 1, 2], [parent2.root_id, parent2.lft, parent2.rgt]
95 assert_equal [parent1.id, lft1, lft1 + 1], [parent1.root_id, parent1.lft, parent1.rgt]
96 assert_equal [parent2.id, lft2, lft2 + 1], [parent2.root_id, parent2.lft, parent2.rgt]
97 97 assert_equal [child.id, 1, 2], [child.root_id, child.lft, child.rgt]
98 98 end
99 99
100 100 def test_move_a_child_to_another_issue
101 lft1 = new_issue_lft
101 102 parent1 = Issue.generate!
103 lft2 = new_issue_lft
102 104 parent2 = Issue.generate!
103 105 child = Issue.generate!(:parent_issue_id => parent1.id)
104
105 106 child.parent_issue_id = parent2.id
106 107 child.save!
107 108 child.reload
108 109 parent1.reload
109 110 parent2.reload
110
111 assert_equal [parent1.id, 1, 2], [parent1.root_id, parent1.lft, parent1.rgt]
112 assert_equal [parent2.id, 1, 4], [parent2.root_id, parent2.lft, parent2.rgt]
113 assert_equal [parent2.id, 2, 3], [child.root_id, child.lft, child.rgt]
111 assert_equal [parent1.id, lft1, lft1 + 1], [parent1.root_id, parent1.lft, parent1.rgt]
112 assert_equal [parent2.id, lft2, lft2 + 3], [parent2.root_id, parent2.lft, parent2.rgt]
113 assert_equal [parent2.id, lft2 + 1, lft2 + 2], [child.root_id, child.lft, child.rgt]
114 114 end
115 115
116 116 def test_move_a_child_with_descendants_to_another_issue
117 lft1 = new_issue_lft
117 118 parent1 = Issue.generate!
119 lft2 = new_issue_lft
118 120 parent2 = Issue.generate!
119 121 child = Issue.generate!(:parent_issue_id => parent1.id)
120 122 grandchild = Issue.generate!(:parent_issue_id => child.id)
121
122 123 parent1.reload
123 124 parent2.reload
124 125 child.reload
125 126 grandchild.reload
126
127 assert_equal [parent1.id, 1, 6], [parent1.root_id, parent1.lft, parent1.rgt]
128 assert_equal [parent2.id, 1, 2], [parent2.root_id, parent2.lft, parent2.rgt]
129 assert_equal [parent1.id, 2, 5], [child.root_id, child.lft, child.rgt]
130 assert_equal [parent1.id, 3, 4], [grandchild.root_id, grandchild.lft, grandchild.rgt]
131
127 assert_equal [parent1.id, lft1, lft1 + 5], [parent1.root_id, parent1.lft, parent1.rgt]
128 assert_equal [parent2.id, lft2, lft2 + 1], [parent2.root_id, parent2.lft, parent2.rgt]
129 assert_equal [parent1.id, lft1 + 1, lft1 + 4], [child.root_id, child.lft, child.rgt]
130 assert_equal [parent1.id, lft1 + 2, lft1 + 3], [grandchild.root_id, grandchild.lft, grandchild.rgt]
132 131 child.reload.parent_issue_id = parent2.id
133 132 child.save!
134 133 child.reload
135 134 grandchild.reload
136 135 parent1.reload
137 136 parent2.reload
138
139 assert_equal [parent1.id, 1, 2], [parent1.root_id, parent1.lft, parent1.rgt]
140 assert_equal [parent2.id, 1, 6], [parent2.root_id, parent2.lft, parent2.rgt]
141 assert_equal [parent2.id, 2, 5], [child.root_id, child.lft, child.rgt]
142 assert_equal [parent2.id, 3, 4], [grandchild.root_id, grandchild.lft, grandchild.rgt]
137 assert_equal [parent1.id, lft1, lft1 + 1], [parent1.root_id, parent1.lft, parent1.rgt]
138 assert_equal [parent2.id, lft2, lft2 + 5], [parent2.root_id, parent2.lft, parent2.rgt]
139 assert_equal [parent2.id, lft2 + 1, lft2 + 4], [child.root_id, child.lft, child.rgt]
140 assert_equal [parent2.id, lft2 + 2, lft2 + 3], [grandchild.root_id, grandchild.lft, grandchild.rgt]
143 141 end
144 142
145 143 def test_move_a_child_with_descendants_to_another_project
144 lft1 = new_issue_lft
146 145 parent1 = Issue.generate!
147 146 child = Issue.generate!(:parent_issue_id => parent1.id)
148 147 grandchild = Issue.generate!(:parent_issue_id => child.id)
149
150 148 child.reload
151 149 child.project = Project.find(2)
152 150 assert child.save
153 151 child.reload
154 152 grandchild.reload
155 153 parent1.reload
156
157 assert_equal [1, parent1.id, 1, 2], [parent1.project_id, parent1.root_id, parent1.lft, parent1.rgt]
154 assert_equal [1, parent1.id, lft1, lft1 + 1], [parent1.project_id, parent1.root_id, parent1.lft, parent1.rgt]
158 155 assert_equal [2, child.id, 1, 4], [child.project_id, child.root_id, child.lft, child.rgt]
159 156 assert_equal [2, child.id, 2, 3], [grandchild.project_id, grandchild.root_id, grandchild.lft, grandchild.rgt]
160 157 end
161 158
162 159 def test_moving_an_issue_to_a_descendant_should_not_validate
163 160 parent1 = Issue.generate!
164 161 parent2 = Issue.generate!
165 162 child = Issue.generate!(:parent_issue_id => parent1.id)
166 163 grandchild = Issue.generate!(:parent_issue_id => child.id)
167 164
168 165 child.reload
169 166 child.parent_issue_id = grandchild.id
170 167 assert !child.save
171 168 assert_not_equal [], child.errors[:parent_issue_id]
172 169 end
173 170
174 171 def test_updating_a_root_issue_should_not_trigger_update_nested_set_attributes_on_parent_change
175 172 issue = Issue.find(Issue.generate!.id)
176 173 issue.parent_issue_id = ""
177 174 issue.expects(:update_nested_set_attributes_on_parent_change).never
178 175 issue.save!
179 176 end
180 177
181 178 def test_updating_a_child_issue_should_not_trigger_update_nested_set_attributes_on_parent_change
182 179 issue = Issue.find(Issue.generate!(:parent_issue_id => 1).id)
183 180 issue.parent_issue_id = "1"
184 181 issue.expects(:update_nested_set_attributes_on_parent_change).never
185 182 issue.save!
186 183 end
187 184
188 185 def test_moving_a_root_issue_should_trigger_update_nested_set_attributes_on_parent_change
189 186 issue = Issue.find(Issue.generate!.id)
190 187 issue.parent_issue_id = "1"
191 188 issue.expects(:update_nested_set_attributes_on_parent_change).once
192 189 issue.save!
193 190 end
194 191
195 192 def test_moving_a_child_issue_to_another_parent_should_trigger_update_nested_set_attributes_on_parent_change
196 193 issue = Issue.find(Issue.generate!(:parent_issue_id => 1).id)
197 194 issue.parent_issue_id = "2"
198 195 issue.expects(:update_nested_set_attributes_on_parent_change).once
199 196 issue.save!
200 197 end
201 198
202 199 def test_moving_a_child_issue_to_root_should_trigger_update_nested_set_attributes_on_parent_change
203 200 issue = Issue.find(Issue.generate!(:parent_issue_id => 1).id)
204 201 issue.parent_issue_id = ""
205 202 issue.expects(:update_nested_set_attributes_on_parent_change).once
206 203 issue.save!
207 204 end
208 205
209 206 def test_destroy_should_destroy_children
207 lft1 = new_issue_lft
210 208 issue1 = Issue.generate!
211 209 issue2 = Issue.generate!
212 210 issue3 = Issue.generate!(:parent_issue_id => issue2.id)
213 211 issue4 = Issue.generate!(:parent_issue_id => issue1.id)
214
215 212 issue3.init_journal(User.find(2))
216 213 issue3.subject = 'child with journal'
217 214 issue3.save!
218
219 215 assert_difference 'Issue.count', -2 do
220 216 assert_difference 'Journal.count', -1 do
221 217 assert_difference 'JournalDetail.count', -1 do
222 218 Issue.find(issue2.id).destroy
223 219 end
224 220 end
225 221 end
226
227 222 issue1.reload
228 223 issue4.reload
229 224 assert !Issue.exists?(issue2.id)
230 225 assert !Issue.exists?(issue3.id)
231 assert_equal [issue1.id, 1, 4], [issue1.root_id, issue1.lft, issue1.rgt]
232 assert_equal [issue1.id, 2, 3], [issue4.root_id, issue4.lft, issue4.rgt]
226 assert_equal [issue1.id, lft1, lft1 + 3], [issue1.root_id, issue1.lft, issue1.rgt]
227 assert_equal [issue1.id, lft1 + 1, lft1 + 2], [issue4.root_id, issue4.lft, issue4.rgt]
233 228 end
234 229
235 230 def test_destroy_child_should_update_parent
231 lft1 = new_issue_lft
236 232 issue = Issue.generate!
237 233 child1 = Issue.generate!(:parent_issue_id => issue.id)
238 234 child2 = Issue.generate!(:parent_issue_id => issue.id)
239
240 235 issue.reload
241 assert_equal [issue.id, 1, 6], [issue.root_id, issue.lft, issue.rgt]
242
236 assert_equal [issue.id, lft1, lft1 + 5], [issue.root_id, issue.lft, issue.rgt]
243 237 child2.reload.destroy
244
245 238 issue.reload
246 assert_equal [issue.id, 1, 4], [issue.root_id, issue.lft, issue.rgt]
239 assert_equal [issue.id, lft1, lft1 + 3], [issue.root_id, issue.lft, issue.rgt]
247 240 end
248 241
249 242 def test_destroy_parent_issue_updated_during_children_destroy
250 243 parent = Issue.generate!
251 244 Issue.generate!(:start_date => Date.today, :parent_issue_id => parent.id)
252 245 Issue.generate!(:start_date => 2.days.from_now, :parent_issue_id => parent.id)
253 246
254 247 assert_difference 'Issue.count', -3 do
255 248 Issue.find(parent.id).destroy
256 249 end
257 250 end
258 251
259 252 def test_destroy_child_issue_with_children
260 253 root = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'root')
261 254 child = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => root.id)
262 255 leaf = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'leaf', :parent_issue_id => child.id)
263 256 leaf.init_journal(User.find(2))
264 257 leaf.subject = 'leaf with journal'
265 258 leaf.save!
266 259
267 260 assert_difference 'Issue.count', -2 do
268 261 assert_difference 'Journal.count', -1 do
269 262 assert_difference 'JournalDetail.count', -1 do
270 263 Issue.find(child.id).destroy
271 264 end
272 265 end
273 266 end
274 267
275 268 root = Issue.find(root.id)
276 269 assert root.leaf?, "Root issue is not a leaf (lft: #{root.lft}, rgt: #{root.rgt})"
277 270 end
278 271
279 272 def test_destroy_issue_with_grand_child
273 lft1 = new_issue_lft
280 274 parent = Issue.generate!
281 275 issue = Issue.generate!(:parent_issue_id => parent.id)
282 276 child = Issue.generate!(:parent_issue_id => issue.id)
283 277 grandchild1 = Issue.generate!(:parent_issue_id => child.id)
284 278 grandchild2 = Issue.generate!(:parent_issue_id => child.id)
285
286 279 assert_difference 'Issue.count', -4 do
287 280 Issue.find(issue.id).destroy
288 281 parent.reload
289 assert_equal [1, 2], [parent.lft, parent.rgt]
282 assert_equal [lft1, lft1 + 1], [parent.lft, parent.rgt]
290 283 end
291 284 end
292 285
293 286 def test_parent_priority_should_be_the_highest_child_priority
294 287 parent = Issue.generate!(:priority => IssuePriority.find_by_name('Normal'))
295 288 # Create children
296 289 child1 = Issue.generate!(:priority => IssuePriority.find_by_name('High'), :parent_issue_id => parent.id)
297 290 assert_equal 'High', parent.reload.priority.name
298 291 child2 = Issue.generate!(:priority => IssuePriority.find_by_name('Immediate'), :parent_issue_id => child1.id)
299 292 assert_equal 'Immediate', child1.reload.priority.name
300 293 assert_equal 'Immediate', parent.reload.priority.name
301 294 child3 = Issue.generate!(:priority => IssuePriority.find_by_name('Low'), :parent_issue_id => parent.id)
302 295 assert_equal 'Immediate', parent.reload.priority.name
303 296 # Destroy a child
304 297 child1.destroy
305 298 assert_equal 'Low', parent.reload.priority.name
306 299 # Update a child
307 300 child3.reload.priority = IssuePriority.find_by_name('Normal')
308 301 child3.save!
309 302 assert_equal 'Normal', parent.reload.priority.name
310 303 end
311 304
312 305 def test_parent_dates_should_be_lowest_start_and_highest_due_dates
313 306 parent = Issue.generate!
314 307 Issue.generate!(:start_date => '2010-01-25', :due_date => '2010-02-15', :parent_issue_id => parent.id)
315 308 Issue.generate!( :due_date => '2010-02-13', :parent_issue_id => parent.id)
316 309 Issue.generate!(:start_date => '2010-02-01', :due_date => '2010-02-22', :parent_issue_id => parent.id)
317 310 parent.reload
318 311 assert_equal Date.parse('2010-01-25'), parent.start_date
319 312 assert_equal Date.parse('2010-02-22'), parent.due_date
320 313 end
321 314
322 315 def test_parent_done_ratio_should_be_average_done_ratio_of_leaves
323 316 parent = Issue.generate!
324 317 Issue.generate!(:done_ratio => 20, :parent_issue_id => parent.id)
325 318 assert_equal 20, parent.reload.done_ratio
326 319 Issue.generate!(:done_ratio => 70, :parent_issue_id => parent.id)
327 320 assert_equal 45, parent.reload.done_ratio
328 321
329 322 child = Issue.generate!(:done_ratio => 0, :parent_issue_id => parent.id)
330 323 assert_equal 30, parent.reload.done_ratio
331 324
332 325 Issue.generate!(:done_ratio => 30, :parent_issue_id => child.id)
333 326 assert_equal 30, child.reload.done_ratio
334 327 assert_equal 40, parent.reload.done_ratio
335 328 end
336 329
337 330 def test_parent_done_ratio_should_be_weighted_by_estimated_times_if_any
338 331 parent = Issue.generate!
339 332 Issue.generate!(:estimated_hours => 10, :done_ratio => 20, :parent_issue_id => parent.id)
340 333 assert_equal 20, parent.reload.done_ratio
341 334 Issue.generate!(:estimated_hours => 20, :done_ratio => 50, :parent_issue_id => parent.id)
342 335 assert_equal (50 * 20 + 20 * 10) / 30, parent.reload.done_ratio
343 336 end
344 337
345 338 def test_parent_done_ratio_with_child_estimate_to_0_should_reach_100
346 339 parent = Issue.generate!
347 340 issue1 = Issue.generate!(:parent_issue_id => parent.id)
348 341 issue2 = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 0)
349 342 assert_equal 0, parent.reload.done_ratio
350 343 issue1.reload.update_attribute :status_id, 5
351 344 assert_equal 50, parent.reload.done_ratio
352 345 issue2.reload.update_attribute :status_id, 5
353 346 assert_equal 100, parent.reload.done_ratio
354 347 end
355 348
356 349 def test_parent_estimate_should_be_sum_of_leaves
357 350 parent = Issue.generate!
358 351 Issue.generate!(:estimated_hours => nil, :parent_issue_id => parent.id)
359 352 assert_equal nil, parent.reload.estimated_hours
360 353 Issue.generate!(:estimated_hours => 5, :parent_issue_id => parent.id)
361 354 assert_equal 5, parent.reload.estimated_hours
362 355 Issue.generate!(:estimated_hours => 7, :parent_issue_id => parent.id)
363 356 assert_equal 12, parent.reload.estimated_hours
364 357 end
365 358
366 359 def test_move_parent_updates_old_parent_attributes
367 360 first_parent = Issue.generate!
368 361 second_parent = Issue.generate!
369 362 child = Issue.generate!(:estimated_hours => 5, :parent_issue_id => first_parent.id)
370 363 assert_equal 5, first_parent.reload.estimated_hours
371 364 child.update_attributes(:estimated_hours => 7, :parent_issue_id => second_parent.id)
372 365 assert_equal 7, second_parent.reload.estimated_hours
373 366 assert_nil first_parent.reload.estimated_hours
374 367 end
375 368
376 369 def test_reschuling_a_parent_should_reschedule_subtasks
377 370 parent = Issue.generate!
378 371 c1 = Issue.generate!(:start_date => '2010-05-12', :due_date => '2010-05-18', :parent_issue_id => parent.id)
379 372 c2 = Issue.generate!(:start_date => '2010-06-03', :due_date => '2010-06-10', :parent_issue_id => parent.id)
380 373 parent.reload
381 374 parent.reschedule_on!(Date.parse('2010-06-02'))
382 375 c1.reload
383 376 assert_equal [Date.parse('2010-06-02'), Date.parse('2010-06-08')], [c1.start_date, c1.due_date]
384 377 c2.reload
385 378 assert_equal [Date.parse('2010-06-03'), Date.parse('2010-06-10')], [c2.start_date, c2.due_date] # no change
386 379 parent.reload
387 380 assert_equal [Date.parse('2010-06-02'), Date.parse('2010-06-10')], [parent.start_date, parent.due_date]
388 381 end
389 382
390 383 def test_project_copy_should_copy_issue_tree
391 384 p = Project.create!(:name => 'Tree copy', :identifier => 'tree-copy', :tracker_ids => [1, 2])
392 385 i1 = Issue.generate!(:project => p, :subject => 'i1')
393 386 i2 = Issue.generate!(:project => p, :subject => 'i2', :parent_issue_id => i1.id)
394 387 i3 = Issue.generate!(:project => p, :subject => 'i3', :parent_issue_id => i1.id)
395 388 i4 = Issue.generate!(:project => p, :subject => 'i4', :parent_issue_id => i2.id)
396 389 i5 = Issue.generate!(:project => p, :subject => 'i5')
397 390 c = Project.new(:name => 'Copy', :identifier => 'copy', :tracker_ids => [1, 2])
398 391 c.copy(p, :only => 'issues')
399 392 c.reload
400 393
401 394 assert_equal 5, c.issues.count
402 395 ic1, ic2, ic3, ic4, ic5 = c.issues.order('subject').all
403 396 assert ic1.root?
404 397 assert_equal ic1, ic2.parent
405 398 assert_equal ic1, ic3.parent
406 399 assert_equal ic2, ic4.parent
407 400 assert ic5.root?
408 401 end
409 402 end
@@ -1,54 +1,52
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 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 IssueTransactionTest < ActiveSupport::TestCase
21 21 fixtures :projects, :users, :members, :member_roles, :roles,
22 22 :trackers, :projects_trackers,
23 23 :versions,
24 24 :issue_statuses, :issue_categories, :issue_relations, :workflows,
25 25 :enumerations,
26 26 :issues,
27 27 :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values,
28 28 :time_entries
29 29
30 30 self.use_transactional_fixtures = false
31 31
32 32 def test_invalid_move_to_another_project
33 lft1 = new_issue_lft
33 34 parent1 = Issue.generate!
34 35 child = Issue.generate!(:parent_issue_id => parent1.id)
35 36 grandchild = Issue.generate!(:parent_issue_id => child.id, :tracker_id => 2)
36 37 Project.find(2).tracker_ids = [1]
37
38 38 parent1.reload
39 assert_equal [1, parent1.id, 1, 6], [parent1.project_id, parent1.root_id, parent1.lft, parent1.rgt]
40
39 assert_equal [1, parent1.id, lft1, lft1 + 5], [parent1.project_id, parent1.root_id, parent1.lft, parent1.rgt]
41 40 # child can not be moved to Project 2 because its child is on a disabled tracker
42 41 child = Issue.find(child.id)
43 42 child.project = Project.find(2)
44 43 assert !child.save
45 44 child.reload
46 45 grandchild.reload
47 46 parent1.reload
48
49 47 # no change
50 assert_equal [1, parent1.id, 1, 6], [parent1.project_id, parent1.root_id, parent1.lft, parent1.rgt]
51 assert_equal [1, parent1.id, 2, 5], [child.project_id, child.root_id, child.lft, child.rgt]
52 assert_equal [1, parent1.id, 3, 4], [grandchild.project_id, grandchild.root_id, grandchild.lft, grandchild.rgt]
48 assert_equal [1, parent1.id, lft1, lft1 + 5], [parent1.project_id, parent1.root_id, parent1.lft, parent1.rgt]
49 assert_equal [1, parent1.id, lft1 + 1, lft1 + 4], [child.project_id, child.root_id, child.lft, child.rgt]
50 assert_equal [1, parent1.id, lft1 + 2, lft1 + 3], [grandchild.project_id, grandchild.root_id, grandchild.lft, grandchild.rgt]
53 51 end
54 52 end
@@ -1,885 +1,887
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2013 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 teardown
39 39 Setting.clear_cache
40 40 end
41 41
42 42 def test_add_issue
43 43 ActionMailer::Base.deliveries.clear
44 lft1 = new_issue_lft
44 45 # This email contains: 'Project: onlinestore'
45 46 issue = submit_email('ticket_on_given_project.eml')
46 47 assert issue.is_a?(Issue)
47 48 assert !issue.new_record?
48 49 issue.reload
49 50 assert_equal Project.find(2), issue.project
50 51 assert_equal issue.project.trackers.first, issue.tracker
51 52 assert_equal 'New ticket on a given project', issue.subject
52 53 assert_equal User.find_by_login('jsmith'), issue.author
53 54 assert_equal IssueStatus.find_by_name('Resolved'), issue.status
54 55 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
55 56 assert_equal '2010-01-01', issue.start_date.to_s
56 57 assert_equal '2010-12-31', issue.due_date.to_s
57 58 assert_equal User.find_by_login('jsmith'), issue.assigned_to
58 59 assert_equal Version.find_by_name('Alpha'), issue.fixed_version
59 60 assert_equal 2.5, issue.estimated_hours
60 61 assert_equal 30, issue.done_ratio
61 assert_equal [issue.id, 1, 2], [issue.root_id, issue.lft, issue.rgt]
62 assert_equal [issue.id, lft1, lft1 + 1], [issue.root_id, issue.lft, issue.rgt]
62 63 # keywords should be removed from the email body
63 64 assert !issue.description.match(/^Project:/i)
64 65 assert !issue.description.match(/^Status:/i)
65 66 assert !issue.description.match(/^Start Date:/i)
66 67 # Email notification should be sent
67 68 mail = ActionMailer::Base.deliveries.last
68 69 assert_not_nil mail
69 70 assert mail.subject.include?('New ticket on a given project')
70 71 end
71 72
72 73 def test_add_issue_with_default_tracker
73 74 # This email contains: 'Project: onlinestore'
74 75 issue = submit_email(
75 76 'ticket_on_given_project.eml',
76 77 :issue => {:tracker => 'Support request'}
77 78 )
78 79 assert issue.is_a?(Issue)
79 80 assert !issue.new_record?
80 81 issue.reload
81 82 assert_equal 'Support request', issue.tracker.name
82 83 end
83 84
84 85 def test_add_issue_with_status
85 86 # This email contains: 'Project: onlinestore' and 'Status: Resolved'
86 87 issue = submit_email('ticket_on_given_project.eml')
87 88 assert issue.is_a?(Issue)
88 89 assert !issue.new_record?
89 90 issue.reload
90 91 assert_equal Project.find(2), issue.project
91 92 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
92 93 end
93 94
94 95 def test_add_issue_with_attributes_override
95 96 issue = submit_email(
96 97 'ticket_with_attributes.eml',
97 98 :allow_override => 'tracker,category,priority'
98 99 )
99 100 assert issue.is_a?(Issue)
100 101 assert !issue.new_record?
101 102 issue.reload
102 103 assert_equal 'New ticket on a given project', issue.subject
103 104 assert_equal User.find_by_login('jsmith'), issue.author
104 105 assert_equal Project.find(2), issue.project
105 106 assert_equal 'Feature request', issue.tracker.to_s
106 107 assert_equal 'Stock management', issue.category.to_s
107 108 assert_equal 'Urgent', issue.priority.to_s
108 109 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
109 110 end
110 111
111 112 def test_add_issue_with_group_assignment
112 113 with_settings :issue_group_assignment => '1' do
113 114 issue = submit_email('ticket_on_given_project.eml') do |email|
114 115 email.gsub!('Assigned to: John Smith', 'Assigned to: B Team')
115 116 end
116 117 assert issue.is_a?(Issue)
117 118 assert !issue.new_record?
118 119 issue.reload
119 120 assert_equal Group.find(11), issue.assigned_to
120 121 end
121 122 end
122 123
123 124 def test_add_issue_with_partial_attributes_override
124 125 issue = submit_email(
125 126 'ticket_with_attributes.eml',
126 127 :issue => {:priority => 'High'},
127 128 :allow_override => ['tracker']
128 129 )
129 130 assert issue.is_a?(Issue)
130 131 assert !issue.new_record?
131 132 issue.reload
132 133 assert_equal 'New ticket on a given project', issue.subject
133 134 assert_equal User.find_by_login('jsmith'), issue.author
134 135 assert_equal Project.find(2), issue.project
135 136 assert_equal 'Feature request', issue.tracker.to_s
136 137 assert_nil issue.category
137 138 assert_equal 'High', issue.priority.to_s
138 139 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
139 140 end
140 141
141 142 def test_add_issue_with_spaces_between_attribute_and_separator
142 143 issue = submit_email(
143 144 'ticket_with_spaces_between_attribute_and_separator.eml',
144 145 :allow_override => 'tracker,category,priority'
145 146 )
146 147 assert issue.is_a?(Issue)
147 148 assert !issue.new_record?
148 149 issue.reload
149 150 assert_equal 'New ticket on a given project', issue.subject
150 151 assert_equal User.find_by_login('jsmith'), issue.author
151 152 assert_equal Project.find(2), issue.project
152 153 assert_equal 'Feature request', issue.tracker.to_s
153 154 assert_equal 'Stock management', issue.category.to_s
154 155 assert_equal 'Urgent', issue.priority.to_s
155 156 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
156 157 end
157 158
158 159 def test_add_issue_with_attachment_to_specific_project
159 160 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
160 161 assert issue.is_a?(Issue)
161 162 assert !issue.new_record?
162 163 issue.reload
163 164 assert_equal 'Ticket created by email with attachment', issue.subject
164 165 assert_equal User.find_by_login('jsmith'), issue.author
165 166 assert_equal Project.find(2), issue.project
166 167 assert_equal 'This is a new ticket with attachments', issue.description
167 168 # Attachment properties
168 169 assert_equal 1, issue.attachments.size
169 170 assert_equal 'Paella.jpg', issue.attachments.first.filename
170 171 assert_equal 'image/jpeg', issue.attachments.first.content_type
171 172 assert_equal 10790, issue.attachments.first.filesize
172 173 end
173 174
174 175 def test_add_issue_with_custom_fields
175 176 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'onlinestore'})
176 177 assert issue.is_a?(Issue)
177 178 assert !issue.new_record?
178 179 issue.reload
179 180 assert_equal 'New ticket with custom field values', issue.subject
180 181 assert_equal 'PostgreSQL', issue.custom_field_value(1)
181 182 assert_equal 'Value for a custom field', issue.custom_field_value(2)
182 183 assert !issue.description.match(/^searchable field:/i)
183 184 end
184 185
185 186 def test_add_issue_with_version_custom_fields
186 187 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true, :tracker_ids => [1,2,3])
187 188
188 189 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'ecookbook'}) do |email|
189 190 email << "Affected version: 1.0\n"
190 191 end
191 192 assert issue.is_a?(Issue)
192 193 assert !issue.new_record?
193 194 issue.reload
194 195 assert_equal '2', issue.custom_field_value(field)
195 196 end
196 197
197 198 def test_add_issue_should_match_assignee_on_display_name
198 199 user = User.generate!(:firstname => 'Foo Bar', :lastname => 'Foo Baz')
199 200 User.add_to_project(user, Project.find(2))
200 201 issue = submit_email('ticket_on_given_project.eml') do |email|
201 202 email.sub!(/^Assigned to.*$/, 'Assigned to: Foo Bar Foo baz')
202 203 end
203 204 assert issue.is_a?(Issue)
204 205 assert_equal user, issue.assigned_to
205 206 end
206 207
207 208 def test_add_issue_with_cc
208 209 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
209 210 assert issue.is_a?(Issue)
210 211 assert !issue.new_record?
211 212 issue.reload
212 213 assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
213 214 assert_equal 1, issue.watcher_user_ids.size
214 215 end
215 216
216 217 def test_add_issue_by_unknown_user
217 218 assert_no_difference 'User.count' do
218 219 assert_equal false,
219 220 submit_email(
220 221 'ticket_by_unknown_user.eml',
221 222 :issue => {:project => 'ecookbook'}
222 223 )
223 224 end
224 225 end
225 226
226 227 def test_add_issue_by_anonymous_user
227 228 Role.anonymous.add_permission!(:add_issues)
228 229 assert_no_difference 'User.count' do
229 230 issue = submit_email(
230 231 'ticket_by_unknown_user.eml',
231 232 :issue => {:project => 'ecookbook'},
232 233 :unknown_user => 'accept'
233 234 )
234 235 assert issue.is_a?(Issue)
235 236 assert issue.author.anonymous?
236 237 end
237 238 end
238 239
239 240 def test_add_issue_by_anonymous_user_with_no_from_address
240 241 Role.anonymous.add_permission!(:add_issues)
241 242 assert_no_difference 'User.count' do
242 243 issue = submit_email(
243 244 'ticket_by_empty_user.eml',
244 245 :issue => {:project => 'ecookbook'},
245 246 :unknown_user => 'accept'
246 247 )
247 248 assert issue.is_a?(Issue)
248 249 assert issue.author.anonymous?
249 250 end
250 251 end
251 252
252 253 def test_add_issue_by_anonymous_user_on_private_project
253 254 Role.anonymous.add_permission!(:add_issues)
254 255 assert_no_difference 'User.count' do
255 256 assert_no_difference 'Issue.count' do
256 257 assert_equal false,
257 258 submit_email(
258 259 'ticket_by_unknown_user.eml',
259 260 :issue => {:project => 'onlinestore'},
260 261 :unknown_user => 'accept'
261 262 )
262 263 end
263 264 end
264 265 end
265 266
266 267 def test_add_issue_by_anonymous_user_on_private_project_without_permission_check
268 lft1 = new_issue_lft
267 269 assert_no_difference 'User.count' do
268 270 assert_difference 'Issue.count' do
269 271 issue = submit_email(
270 272 'ticket_by_unknown_user.eml',
271 273 :issue => {:project => 'onlinestore'},
272 274 :no_permission_check => '1',
273 275 :unknown_user => 'accept'
274 276 )
275 277 assert issue.is_a?(Issue)
276 278 assert issue.author.anonymous?
277 279 assert !issue.project.is_public?
278 assert_equal [issue.id, 1, 2], [issue.root_id, issue.lft, issue.rgt]
280 assert_equal [issue.id, lft1, lft1 + 1], [issue.root_id, issue.lft, issue.rgt]
279 281 end
280 282 end
281 283 end
282 284
283 285 def test_add_issue_by_created_user
284 286 Setting.default_language = 'en'
285 287 assert_difference 'User.count' do
286 288 issue = submit_email(
287 289 'ticket_by_unknown_user.eml',
288 290 :issue => {:project => 'ecookbook'},
289 291 :unknown_user => 'create'
290 292 )
291 293 assert issue.is_a?(Issue)
292 294 assert issue.author.active?
293 295 assert_equal 'john.doe@somenet.foo', issue.author.mail
294 296 assert_equal 'John', issue.author.firstname
295 297 assert_equal 'Doe', issue.author.lastname
296 298
297 299 # account information
298 300 email = ActionMailer::Base.deliveries.first
299 301 assert_not_nil email
300 302 assert email.subject.include?('account activation')
301 303 login = mail_body(email).match(/\* Login: (.*)$/)[1].strip
302 304 password = mail_body(email).match(/\* Password: (.*)$/)[1].strip
303 305 assert_equal issue.author, User.try_to_login(login, password)
304 306 end
305 307 end
306 308
307 309 def test_created_user_should_be_added_to_groups
308 310 group1 = Group.generate!
309 311 group2 = Group.generate!
310 312
311 313 assert_difference 'User.count' do
312 314 submit_email(
313 315 'ticket_by_unknown_user.eml',
314 316 :issue => {:project => 'ecookbook'},
315 317 :unknown_user => 'create',
316 318 :default_group => "#{group1.name},#{group2.name}"
317 319 )
318 320 end
319 321 user = User.order('id DESC').first
320 322 assert_same_elements [group1, group2], user.groups
321 323 end
322 324
323 325 def test_created_user_should_not_receive_account_information_with_no_account_info_option
324 326 assert_difference 'User.count' do
325 327 submit_email(
326 328 'ticket_by_unknown_user.eml',
327 329 :issue => {:project => 'ecookbook'},
328 330 :unknown_user => 'create',
329 331 :no_account_notice => '1'
330 332 )
331 333 end
332 334
333 335 # only 1 email for the new issue notification
334 336 assert_equal 1, ActionMailer::Base.deliveries.size
335 337 email = ActionMailer::Base.deliveries.first
336 338 assert_include 'Ticket by unknown user', email.subject
337 339 end
338 340
339 341 def test_created_user_should_have_mail_notification_to_none_with_no_notification_option
340 342 assert_difference 'User.count' do
341 343 submit_email(
342 344 'ticket_by_unknown_user.eml',
343 345 :issue => {:project => 'ecookbook'},
344 346 :unknown_user => 'create',
345 347 :no_notification => '1'
346 348 )
347 349 end
348 350 user = User.order('id DESC').first
349 351 assert_equal 'none', user.mail_notification
350 352 end
351 353
352 354 def test_add_issue_without_from_header
353 355 Role.anonymous.add_permission!(:add_issues)
354 356 assert_equal false, submit_email('ticket_without_from_header.eml')
355 357 end
356 358
357 359 def test_add_issue_with_invalid_attributes
358 360 issue = submit_email(
359 361 'ticket_with_invalid_attributes.eml',
360 362 :allow_override => 'tracker,category,priority'
361 363 )
362 364 assert issue.is_a?(Issue)
363 365 assert !issue.new_record?
364 366 issue.reload
365 367 assert_nil issue.assigned_to
366 368 assert_nil issue.start_date
367 369 assert_nil issue.due_date
368 370 assert_equal 0, issue.done_ratio
369 371 assert_equal 'Normal', issue.priority.to_s
370 372 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
371 373 end
372 374
373 375 def test_add_issue_with_invalid_project_should_be_assigned_to_default_project
374 376 issue = submit_email('ticket_on_given_project.eml', :issue => {:project => 'ecookbook'}, :allow_override => 'project') do |email|
375 377 email.gsub!(/^Project:.+$/, 'Project: invalid')
376 378 end
377 379 assert issue.is_a?(Issue)
378 380 assert !issue.new_record?
379 381 assert_equal 'ecookbook', issue.project.identifier
380 382 end
381 383
382 384 def test_add_issue_with_localized_attributes
383 385 User.find_by_mail('jsmith@somenet.foo').update_attribute 'language', 'fr'
384 386 issue = submit_email(
385 387 'ticket_with_localized_attributes.eml',
386 388 :allow_override => 'tracker,category,priority'
387 389 )
388 390 assert issue.is_a?(Issue)
389 391 assert !issue.new_record?
390 392 issue.reload
391 393 assert_equal 'New ticket on a given project', issue.subject
392 394 assert_equal User.find_by_login('jsmith'), issue.author
393 395 assert_equal Project.find(2), issue.project
394 396 assert_equal 'Feature request', issue.tracker.to_s
395 397 assert_equal 'Stock management', issue.category.to_s
396 398 assert_equal 'Urgent', issue.priority.to_s
397 399 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
398 400 end
399 401
400 402 def test_add_issue_with_japanese_keywords
401 403 ja_dev = "\xe9\x96\x8b\xe7\x99\xba"
402 404 ja_dev.force_encoding('UTF-8') if ja_dev.respond_to?(:force_encoding)
403 405 tracker = Tracker.create!(:name => ja_dev)
404 406 Project.find(1).trackers << tracker
405 407 issue = submit_email(
406 408 'japanese_keywords_iso_2022_jp.eml',
407 409 :issue => {:project => 'ecookbook'},
408 410 :allow_override => 'tracker'
409 411 )
410 412 assert_kind_of Issue, issue
411 413 assert_equal tracker, issue.tracker
412 414 end
413 415
414 416 def test_add_issue_from_apple_mail
415 417 issue = submit_email(
416 418 'apple_mail_with_attachment.eml',
417 419 :issue => {:project => 'ecookbook'}
418 420 )
419 421 assert_kind_of Issue, issue
420 422 assert_equal 1, issue.attachments.size
421 423
422 424 attachment = issue.attachments.first
423 425 assert_equal 'paella.jpg', attachment.filename
424 426 assert_equal 10790, attachment.filesize
425 427 assert File.exist?(attachment.diskfile)
426 428 assert_equal 10790, File.size(attachment.diskfile)
427 429 assert_equal 'caaf384198bcbc9563ab5c058acd73cd', attachment.digest
428 430 end
429 431
430 432 def test_thunderbird_with_attachment_ja
431 433 issue = submit_email(
432 434 'thunderbird_with_attachment_ja.eml',
433 435 :issue => {:project => 'ecookbook'}
434 436 )
435 437 assert_kind_of Issue, issue
436 438 assert_equal 1, issue.attachments.size
437 439 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt"
438 440 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
439 441 attachment = issue.attachments.first
440 442 assert_equal ja, attachment.filename
441 443 assert_equal 5, attachment.filesize
442 444 assert File.exist?(attachment.diskfile)
443 445 assert_equal 5, File.size(attachment.diskfile)
444 446 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
445 447 end
446 448
447 449 def test_gmail_with_attachment_ja
448 450 issue = submit_email(
449 451 'gmail_with_attachment_ja.eml',
450 452 :issue => {:project => 'ecookbook'}
451 453 )
452 454 assert_kind_of Issue, issue
453 455 assert_equal 1, issue.attachments.size
454 456 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt"
455 457 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
456 458 attachment = issue.attachments.first
457 459 assert_equal ja, attachment.filename
458 460 assert_equal 5, attachment.filesize
459 461 assert File.exist?(attachment.diskfile)
460 462 assert_equal 5, File.size(attachment.diskfile)
461 463 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
462 464 end
463 465
464 466 def test_thunderbird_with_attachment_latin1
465 467 issue = submit_email(
466 468 'thunderbird_with_attachment_iso-8859-1.eml',
467 469 :issue => {:project => 'ecookbook'}
468 470 )
469 471 assert_kind_of Issue, issue
470 472 assert_equal 1, issue.attachments.size
471 473 u = ""
472 474 u.force_encoding('UTF-8') if u.respond_to?(:force_encoding)
473 475 u1 = "\xc3\x84\xc3\xa4\xc3\x96\xc3\xb6\xc3\x9c\xc3\xbc"
474 476 u1.force_encoding('UTF-8') if u1.respond_to?(:force_encoding)
475 477 11.times { u << u1 }
476 478 attachment = issue.attachments.first
477 479 assert_equal "#{u}.png", attachment.filename
478 480 assert_equal 130, attachment.filesize
479 481 assert File.exist?(attachment.diskfile)
480 482 assert_equal 130, File.size(attachment.diskfile)
481 483 assert_equal '4d80e667ac37dddfe05502530f152abb', attachment.digest
482 484 end
483 485
484 486 def test_gmail_with_attachment_latin1
485 487 issue = submit_email(
486 488 'gmail_with_attachment_iso-8859-1.eml',
487 489 :issue => {:project => 'ecookbook'}
488 490 )
489 491 assert_kind_of Issue, issue
490 492 assert_equal 1, issue.attachments.size
491 493 u = ""
492 494 u.force_encoding('UTF-8') if u.respond_to?(:force_encoding)
493 495 u1 = "\xc3\x84\xc3\xa4\xc3\x96\xc3\xb6\xc3\x9c\xc3\xbc"
494 496 u1.force_encoding('UTF-8') if u1.respond_to?(:force_encoding)
495 497 11.times { u << u1 }
496 498 attachment = issue.attachments.first
497 499 assert_equal "#{u}.txt", attachment.filename
498 500 assert_equal 5, attachment.filesize
499 501 assert File.exist?(attachment.diskfile)
500 502 assert_equal 5, File.size(attachment.diskfile)
501 503 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
502 504 end
503 505
504 506 def test_multiple_inline_text_parts_should_be_appended_to_issue_description
505 507 issue = submit_email('multiple_text_parts.eml', :issue => {:project => 'ecookbook'})
506 508 assert_include 'first', issue.description
507 509 assert_include 'second', issue.description
508 510 assert_include 'third', issue.description
509 511 end
510 512
511 513 def test_attachment_text_part_should_be_added_as_issue_attachment
512 514 issue = submit_email('multiple_text_parts.eml', :issue => {:project => 'ecookbook'})
513 515 assert_not_include 'Plain text attachment', issue.description
514 516 attachment = issue.attachments.detect {|a| a.filename == 'textfile.txt'}
515 517 assert_not_nil attachment
516 518 assert_include 'Plain text attachment', File.read(attachment.diskfile)
517 519 end
518 520
519 521 def test_add_issue_with_iso_8859_1_subject
520 522 issue = submit_email(
521 523 'subject_as_iso-8859-1.eml',
522 524 :issue => {:project => 'ecookbook'}
523 525 )
524 526 str = "Testmail from Webmail: \xc3\xa4 \xc3\xb6 \xc3\xbc..."
525 527 str.force_encoding('UTF-8') if str.respond_to?(:force_encoding)
526 528 assert_kind_of Issue, issue
527 529 assert_equal str, issue.subject
528 530 end
529 531
530 532 def test_add_issue_with_japanese_subject
531 533 issue = submit_email(
532 534 'subject_japanese_1.eml',
533 535 :issue => {:project => 'ecookbook'}
534 536 )
535 537 assert_kind_of Issue, issue
536 538 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88"
537 539 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
538 540 assert_equal ja, issue.subject
539 541 end
540 542
541 543 def test_add_issue_with_korean_body
542 544 # Make sure mail bodies with a charset unknown to Ruby
543 545 # but known to the Mail gem 2.5.4 are handled correctly
544 546 kr = "\xEA\xB3\xA0\xEB\xA7\x99\xEC\x8A\xB5\xEB\x8B\x88\xEB\x8B\xA4."
545 547 if !kr.respond_to?(:force_encoding)
546 548 puts "\nOn Ruby 1.8, skip Korean encoding mail body test"
547 549 else
548 550 kr.force_encoding('UTF-8')
549 551 issue = submit_email(
550 552 'body_ks_c_5601-1987.eml',
551 553 :issue => {:project => 'ecookbook'}
552 554 )
553 555 assert_kind_of Issue, issue
554 556 assert_equal kr, issue.description
555 557 end
556 558 end
557 559
558 560 def test_add_issue_with_no_subject_header
559 561 issue = submit_email(
560 562 'no_subject_header.eml',
561 563 :issue => {:project => 'ecookbook'}
562 564 )
563 565 assert_kind_of Issue, issue
564 566 assert_equal '(no subject)', issue.subject
565 567 end
566 568
567 569 def test_add_issue_with_mixed_japanese_subject
568 570 issue = submit_email(
569 571 'subject_japanese_2.eml',
570 572 :issue => {:project => 'ecookbook'}
571 573 )
572 574 assert_kind_of Issue, issue
573 575 ja = "Re: \xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88"
574 576 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
575 577 assert_equal ja, issue.subject
576 578 end
577 579
578 580 def test_should_ignore_emails_from_locked_users
579 581 User.find(2).lock!
580 582
581 583 MailHandler.any_instance.expects(:dispatch).never
582 584 assert_no_difference 'Issue.count' do
583 585 assert_equal false, submit_email('ticket_on_given_project.eml')
584 586 end
585 587 end
586 588
587 589 def test_should_ignore_emails_from_emission_address
588 590 Role.anonymous.add_permission!(:add_issues)
589 591 assert_no_difference 'User.count' do
590 592 assert_equal false,
591 593 submit_email(
592 594 'ticket_from_emission_address.eml',
593 595 :issue => {:project => 'ecookbook'},
594 596 :unknown_user => 'create'
595 597 )
596 598 end
597 599 end
598 600
599 601 def test_should_ignore_auto_replied_emails
600 602 MailHandler.any_instance.expects(:dispatch).never
601 603 [
602 604 "X-Auto-Response-Suppress: OOF",
603 605 "Auto-Submitted: auto-replied",
604 606 "Auto-Submitted: Auto-Replied",
605 607 "Auto-Submitted: auto-generated"
606 608 ].each do |header|
607 609 raw = IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
608 610 raw = header + "\n" + raw
609 611
610 612 assert_no_difference 'Issue.count' do
611 613 assert_equal false, MailHandler.receive(raw), "email with #{header} header was not ignored"
612 614 end
613 615 end
614 616 end
615 617
616 618 def test_add_issue_should_send_email_notification
617 619 Setting.notified_events = ['issue_added']
618 620 ActionMailer::Base.deliveries.clear
619 621 # This email contains: 'Project: onlinestore'
620 622 issue = submit_email('ticket_on_given_project.eml')
621 623 assert issue.is_a?(Issue)
622 624 assert_equal 1, ActionMailer::Base.deliveries.size
623 625 end
624 626
625 627 def test_update_issue
626 628 journal = submit_email('ticket_reply.eml')
627 629 assert journal.is_a?(Journal)
628 630 assert_equal User.find_by_login('jsmith'), journal.user
629 631 assert_equal Issue.find(2), journal.journalized
630 632 assert_match /This is reply/, journal.notes
631 633 assert_equal false, journal.private_notes
632 634 assert_equal 'Feature request', journal.issue.tracker.name
633 635 end
634 636
635 637 def test_update_issue_with_attribute_changes
636 638 # This email contains: 'Status: Resolved'
637 639 journal = submit_email('ticket_reply_with_status.eml')
638 640 assert journal.is_a?(Journal)
639 641 issue = Issue.find(journal.issue.id)
640 642 assert_equal User.find_by_login('jsmith'), journal.user
641 643 assert_equal Issue.find(2), journal.journalized
642 644 assert_match /This is reply/, journal.notes
643 645 assert_equal 'Feature request', journal.issue.tracker.name
644 646 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
645 647 assert_equal '2010-01-01', issue.start_date.to_s
646 648 assert_equal '2010-12-31', issue.due_date.to_s
647 649 assert_equal User.find_by_login('jsmith'), issue.assigned_to
648 650 assert_equal "52.6", issue.custom_value_for(CustomField.find_by_name('Float field')).value
649 651 # keywords should be removed from the email body
650 652 assert !journal.notes.match(/^Status:/i)
651 653 assert !journal.notes.match(/^Start Date:/i)
652 654 end
653 655
654 656 def test_update_issue_with_attachment
655 657 assert_difference 'Journal.count' do
656 658 assert_difference 'JournalDetail.count' do
657 659 assert_difference 'Attachment.count' do
658 660 assert_no_difference 'Issue.count' do
659 661 journal = submit_email('ticket_with_attachment.eml') do |raw|
660 662 raw.gsub! /^Subject: .*$/, 'Subject: Re: [Cookbook - Feature #2] (New) Add ingredients categories'
661 663 end
662 664 end
663 665 end
664 666 end
665 667 end
666 668 journal = Journal.order('id DESC').first
667 669 assert_equal Issue.find(2), journal.journalized
668 670 assert_equal 1, journal.details.size
669 671
670 672 detail = journal.details.first
671 673 assert_equal 'attachment', detail.property
672 674 assert_equal 'Paella.jpg', detail.value
673 675 end
674 676
675 677 def test_update_issue_should_send_email_notification
676 678 ActionMailer::Base.deliveries.clear
677 679 journal = submit_email('ticket_reply.eml')
678 680 assert journal.is_a?(Journal)
679 681 assert_equal 1, ActionMailer::Base.deliveries.size
680 682 end
681 683
682 684 def test_update_issue_should_not_set_defaults
683 685 journal = submit_email(
684 686 'ticket_reply.eml',
685 687 :issue => {:tracker => 'Support request', :priority => 'High'}
686 688 )
687 689 assert journal.is_a?(Journal)
688 690 assert_match /This is reply/, journal.notes
689 691 assert_equal 'Feature request', journal.issue.tracker.name
690 692 assert_equal 'Normal', journal.issue.priority.name
691 693 end
692 694
693 695 def test_replying_to_a_private_note_should_add_reply_as_private
694 696 private_journal = Journal.create!(:notes => 'Private notes', :journalized => Issue.find(1), :private_notes => true, :user_id => 2)
695 697
696 698 assert_difference 'Journal.count' do
697 699 journal = submit_email('ticket_reply.eml') do |email|
698 700 email.sub! %r{^In-Reply-To:.*$}, "In-Reply-To: <redmine.journal-#{private_journal.id}.20060719210421@osiris>"
699 701 end
700 702
701 703 assert_kind_of Journal, journal
702 704 assert_match /This is reply/, journal.notes
703 705 assert_equal true, journal.private_notes
704 706 end
705 707 end
706 708
707 709 def test_reply_to_a_message
708 710 m = submit_email('message_reply.eml')
709 711 assert m.is_a?(Message)
710 712 assert !m.new_record?
711 713 m.reload
712 714 assert_equal 'Reply via email', m.subject
713 715 # The email replies to message #2 which is part of the thread of message #1
714 716 assert_equal Message.find(1), m.parent
715 717 end
716 718
717 719 def test_reply_to_a_message_by_subject
718 720 m = submit_email('message_reply_by_subject.eml')
719 721 assert m.is_a?(Message)
720 722 assert !m.new_record?
721 723 m.reload
722 724 assert_equal 'Reply to the first post', m.subject
723 725 assert_equal Message.find(1), m.parent
724 726 end
725 727
726 728 def test_should_strip_tags_of_html_only_emails
727 729 issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
728 730 assert issue.is_a?(Issue)
729 731 assert !issue.new_record?
730 732 issue.reload
731 733 assert_equal 'HTML email', issue.subject
732 734 assert_equal 'This is a html-only email.', issue.description
733 735 end
734 736
735 737 test "truncate emails with no setting should add the entire email into the issue" do
736 738 with_settings :mail_handler_body_delimiters => '' do
737 739 issue = submit_email('ticket_on_given_project.eml')
738 740 assert_issue_created(issue)
739 741 assert issue.description.include?('---')
740 742 assert issue.description.include?('This paragraph is after the delimiter')
741 743 end
742 744 end
743 745
744 746 test "truncate emails with a single string should truncate the email at the delimiter for the issue" do
745 747 with_settings :mail_handler_body_delimiters => '---' do
746 748 issue = submit_email('ticket_on_given_project.eml')
747 749 assert_issue_created(issue)
748 750 assert issue.description.include?('This paragraph is before delimiters')
749 751 assert issue.description.include?('--- This line starts with a delimiter')
750 752 assert !issue.description.match(/^---$/)
751 753 assert !issue.description.include?('This paragraph is after the delimiter')
752 754 end
753 755 end
754 756
755 757 test "truncate emails with a single quoted reply should truncate the email at the delimiter with the quoted reply symbols (>)" do
756 758 with_settings :mail_handler_body_delimiters => '--- Reply above. Do not remove this line. ---' do
757 759 journal = submit_email('issue_update_with_quoted_reply_above.eml')
758 760 assert journal.is_a?(Journal)
759 761 assert journal.notes.include?('An update to the issue by the sender.')
760 762 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
761 763 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
762 764 end
763 765 end
764 766
765 767 test "truncate emails with multiple quoted replies should truncate the email at the delimiter with the quoted reply symbols (>)" do
766 768 with_settings :mail_handler_body_delimiters => '--- Reply above. Do not remove this line. ---' do
767 769 journal = submit_email('issue_update_with_multiple_quoted_reply_above.eml')
768 770 assert journal.is_a?(Journal)
769 771 assert journal.notes.include?('An update to the issue by the sender.')
770 772 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
771 773 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
772 774 end
773 775 end
774 776
775 777 test "truncate emails with multiple strings should truncate the email at the first delimiter found (BREAK)" do
776 778 with_settings :mail_handler_body_delimiters => "---\nBREAK" do
777 779 issue = submit_email('ticket_on_given_project.eml')
778 780 assert_issue_created(issue)
779 781 assert issue.description.include?('This paragraph is before delimiters')
780 782 assert !issue.description.include?('BREAK')
781 783 assert !issue.description.include?('This paragraph is between delimiters')
782 784 assert !issue.description.match(/^---$/)
783 785 assert !issue.description.include?('This paragraph is after the delimiter')
784 786 end
785 787 end
786 788
787 789 def test_attachments_that_match_mail_handler_excluded_filenames_should_be_ignored
788 790 with_settings :mail_handler_excluded_filenames => '*.vcf, *.jpg' do
789 791 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
790 792 assert issue.is_a?(Issue)
791 793 assert !issue.new_record?
792 794 assert_equal 0, issue.reload.attachments.size
793 795 end
794 796 end
795 797
796 798 def test_attachments_that_do_not_match_mail_handler_excluded_filenames_should_be_attached
797 799 with_settings :mail_handler_excluded_filenames => '*.vcf, *.gif' do
798 800 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
799 801 assert issue.is_a?(Issue)
800 802 assert !issue.new_record?
801 803 assert_equal 1, issue.reload.attachments.size
802 804 end
803 805 end
804 806
805 807 def test_email_with_long_subject_line
806 808 issue = submit_email('ticket_with_long_subject.eml')
807 809 assert issue.is_a?(Issue)
808 810 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]
809 811 end
810 812
811 813 def test_new_user_from_attributes_should_return_valid_user
812 814 to_test = {
813 815 # [address, name] => [login, firstname, lastname]
814 816 ['jsmith@example.net', nil] => ['jsmith@example.net', 'jsmith', '-'],
815 817 ['jsmith@example.net', 'John'] => ['jsmith@example.net', 'John', '-'],
816 818 ['jsmith@example.net', 'John Smith'] => ['jsmith@example.net', 'John', 'Smith'],
817 819 ['jsmith@example.net', 'John Paul Smith'] => ['jsmith@example.net', 'John', 'Paul Smith'],
818 820 ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsTheMaximumLength Smith'] => ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsT', 'Smith'],
819 821 ['jsmith@example.net', 'John AVeryLongLastnameThatExceedsTheMaximumLength'] => ['jsmith@example.net', 'John', 'AVeryLongLastnameThatExceedsTh']
820 822 }
821 823
822 824 to_test.each do |attrs, expected|
823 825 user = MailHandler.new_user_from_attributes(attrs.first, attrs.last)
824 826
825 827 assert user.valid?, user.errors.full_messages.to_s
826 828 assert_equal attrs.first, user.mail
827 829 assert_equal expected[0], user.login
828 830 assert_equal expected[1], user.firstname
829 831 assert_equal expected[2], user.lastname
830 832 assert_equal 'only_my_events', user.mail_notification
831 833 end
832 834 end
833 835
834 836 def test_new_user_from_attributes_should_use_default_login_if_invalid
835 837 user = MailHandler.new_user_from_attributes('foo+bar@example.net')
836 838 assert user.valid?
837 839 assert user.login =~ /^user[a-f0-9]+$/
838 840 assert_equal 'foo+bar@example.net', user.mail
839 841 end
840 842
841 843 def test_new_user_with_utf8_encoded_fullname_should_be_decoded
842 844 assert_difference 'User.count' do
843 845 issue = submit_email(
844 846 'fullname_of_sender_as_utf8_encoded.eml',
845 847 :issue => {:project => 'ecookbook'},
846 848 :unknown_user => 'create'
847 849 )
848 850 end
849 851 user = User.order('id DESC').first
850 852 assert_equal "foo@example.org", user.mail
851 853 str1 = "\xc3\x84\xc3\xa4"
852 854 str2 = "\xc3\x96\xc3\xb6"
853 855 str1.force_encoding('UTF-8') if str1.respond_to?(:force_encoding)
854 856 str2.force_encoding('UTF-8') if str2.respond_to?(:force_encoding)
855 857 assert_equal str1, user.firstname
856 858 assert_equal str2, user.lastname
857 859 end
858 860
859 861 def test_extract_options_from_env_should_return_options
860 862 options = MailHandler.extract_options_from_env({
861 863 'tracker' => 'defect',
862 864 'project' => 'foo',
863 865 'unknown_user' => 'create'
864 866 })
865 867
866 868 assert_equal({
867 869 :issue => {:tracker => 'defect', :project => 'foo'},
868 870 :unknown_user => 'create'
869 871 }, options)
870 872 end
871 873
872 874 private
873 875
874 876 def submit_email(filename, options={})
875 877 raw = IO.read(File.join(FIXTURES_PATH, filename))
876 878 yield raw if block_given?
877 879 MailHandler.receive(raw, options)
878 880 end
879 881
880 882 def assert_issue_created(issue)
881 883 assert issue.is_a?(Issue)
882 884 assert !issue.new_record?
883 885 issue.reload
884 886 end
885 887 end
General Comments 0
You need to be logged in to leave comments. Login now