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