##// END OF EJS Templates
Removing shoulda context....
Jean-Philippe Lang -
r11085:14b50dfbabc6
parent child
Show More
@@ -1,285 +1,283
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2013 Jean-Philippe Lang
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; either version 2
9 9 # of the License, or (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 19
20 20 require File.expand_path('../../test_helper', __FILE__)
21 21
22 22 class AttachmentTest < ActiveSupport::TestCase
23 23 fixtures :users, :projects, :roles, :members, :member_roles,
24 24 :enabled_modules, :issues, :trackers, :attachments
25 25
26 26 class MockFile
27 27 attr_reader :original_filename, :content_type, :content, :size
28 28
29 29 def initialize(attributes)
30 30 @original_filename = attributes[:original_filename]
31 31 @content_type = attributes[:content_type]
32 32 @content = attributes[:content] || "Content"
33 33 @size = content.size
34 34 end
35 35 end
36 36
37 37 def setup
38 38 set_tmp_attachments_directory
39 39 end
40 40
41 41 def test_container_for_new_attachment_should_be_nil
42 42 assert_nil Attachment.new.container
43 43 end
44 44
45 45 def test_create
46 46 a = Attachment.new(:container => Issue.find(1),
47 47 :file => uploaded_test_file("testfile.txt", "text/plain"),
48 48 :author => User.find(1))
49 49 assert a.save
50 50 assert_equal 'testfile.txt', a.filename
51 51 assert_equal 59, a.filesize
52 52 assert_equal 'text/plain', a.content_type
53 53 assert_equal 0, a.downloads
54 54 assert_equal '1478adae0d4eb06d35897518540e25d6', a.digest
55 55
56 56 assert a.disk_directory
57 57 assert_match %r{\A\d{4}/\d{2}\z}, a.disk_directory
58 58
59 59 assert File.exist?(a.diskfile)
60 60 assert_equal 59, File.size(a.diskfile)
61 61 end
62 62
63 63 def test_copy_should_preserve_attributes
64 64 a = Attachment.find(1)
65 65 copy = a.copy
66 66
67 67 assert_save copy
68 68 copy = Attachment.order('id DESC').first
69 69 %w(filename filesize content_type author_id created_on description digest disk_filename disk_directory diskfile).each do |attribute|
70 70 assert_equal a.send(attribute), copy.send(attribute), "#{attribute} was different"
71 71 end
72 72 end
73 73
74 74 def test_size_should_be_validated_for_new_file
75 75 with_settings :attachment_max_size => 0 do
76 76 a = Attachment.new(:container => Issue.find(1),
77 77 :file => uploaded_test_file("testfile.txt", "text/plain"),
78 78 :author => User.find(1))
79 79 assert !a.save
80 80 end
81 81 end
82 82
83 83 def test_size_should_not_be_validated_when_copying
84 84 a = Attachment.create!(:container => Issue.find(1),
85 85 :file => uploaded_test_file("testfile.txt", "text/plain"),
86 86 :author => User.find(1))
87 87 with_settings :attachment_max_size => 0 do
88 88 copy = a.copy
89 89 assert copy.save
90 90 end
91 91 end
92 92
93 93 def test_description_length_should_be_validated
94 94 a = Attachment.new(:description => 'a' * 300)
95 95 assert !a.save
96 96 assert_not_nil a.errors[:description]
97 97 end
98 98
99 99 def test_destroy
100 100 a = Attachment.new(:container => Issue.find(1),
101 101 :file => uploaded_test_file("testfile.txt", "text/plain"),
102 102 :author => User.find(1))
103 103 assert a.save
104 104 assert_equal 'testfile.txt', a.filename
105 105 assert_equal 59, a.filesize
106 106 assert_equal 'text/plain', a.content_type
107 107 assert_equal 0, a.downloads
108 108 assert_equal '1478adae0d4eb06d35897518540e25d6', a.digest
109 109 diskfile = a.diskfile
110 110 assert File.exist?(diskfile)
111 111 assert_equal 59, File.size(a.diskfile)
112 112 assert a.destroy
113 113 assert !File.exist?(diskfile)
114 114 end
115 115
116 116 def test_destroy_should_not_delete_file_referenced_by_other_attachment
117 117 a = Attachment.create!(:container => Issue.find(1),
118 118 :file => uploaded_test_file("testfile.txt", "text/plain"),
119 119 :author => User.find(1))
120 120 diskfile = a.diskfile
121 121
122 122 copy = a.copy
123 123 copy.save!
124 124
125 125 assert File.exists?(diskfile)
126 126 a.destroy
127 127 assert File.exists?(diskfile)
128 128 copy.destroy
129 129 assert !File.exists?(diskfile)
130 130 end
131 131
132 132 def test_create_should_auto_assign_content_type
133 133 a = Attachment.new(:container => Issue.find(1),
134 134 :file => uploaded_test_file("testfile.txt", ""),
135 135 :author => User.find(1))
136 136 assert a.save
137 137 assert_equal 'text/plain', a.content_type
138 138 end
139 139
140 140 def test_identical_attachments_at_the_same_time_should_not_overwrite
141 141 a1 = Attachment.create!(:container => Issue.find(1),
142 142 :file => uploaded_test_file("testfile.txt", ""),
143 143 :author => User.find(1))
144 144 a2 = Attachment.create!(:container => Issue.find(1),
145 145 :file => uploaded_test_file("testfile.txt", ""),
146 146 :author => User.find(1))
147 147 assert a1.disk_filename != a2.disk_filename
148 148 end
149 149
150 150 def test_filename_should_be_basenamed
151 151 a = Attachment.new(:file => MockFile.new(:original_filename => "path/to/the/file"))
152 152 assert_equal 'file', a.filename
153 153 end
154 154
155 155 def test_filename_should_be_sanitized
156 156 a = Attachment.new(:file => MockFile.new(:original_filename => "valid:[] invalid:?%*|\"'<>chars"))
157 157 assert_equal 'valid_[] invalid_chars', a.filename
158 158 end
159 159
160 160 def test_diskfilename
161 161 assert Attachment.disk_filename("test_file.txt") =~ /^\d{12}_test_file.txt$/
162 162 assert_equal 'test_file.txt', Attachment.disk_filename("test_file.txt")[13..-1]
163 163 assert_equal '770c509475505f37c2b8fb6030434d6b.txt', Attachment.disk_filename("test_accentuΓ©.txt")[13..-1]
164 164 assert_equal 'f8139524ebb8f32e51976982cd20a85d', Attachment.disk_filename("test_accentuΓ©")[13..-1]
165 165 assert_equal 'cbb5b0f30978ba03731d61f9f6d10011', Attachment.disk_filename("test_accentuΓ©.Γ§a")[13..-1]
166 166 end
167 167
168 168 def test_title
169 169 a = Attachment.new(:filename => "test.png")
170 170 assert_equal "test.png", a.title
171 171
172 172 a = Attachment.new(:filename => "test.png", :description => "Cool image")
173 173 assert_equal "test.png (Cool image)", a.title
174 174 end
175 175
176 176 def test_prune_should_destroy_old_unattached_attachments
177 177 Attachment.create!(:file => uploaded_test_file("testfile.txt", ""), :author_id => 1, :created_on => 2.days.ago)
178 178 Attachment.create!(:file => uploaded_test_file("testfile.txt", ""), :author_id => 1, :created_on => 2.days.ago)
179 179 Attachment.create!(:file => uploaded_test_file("testfile.txt", ""), :author_id => 1)
180 180
181 181 assert_difference 'Attachment.count', -2 do
182 182 Attachment.prune
183 183 end
184 184 end
185 185
186 186 def test_move_from_root_to_target_directory_should_move_root_files
187 187 a = Attachment.find(20)
188 188 assert a.disk_directory.blank?
189 189 # Create a real file for this fixture
190 190 File.open(a.diskfile, "w") do |f|
191 191 f.write "test file at the root of files directory"
192 192 end
193 193 assert a.readable?
194 194 Attachment.move_from_root_to_target_directory
195 195
196 196 a.reload
197 197 assert_equal '2012/05', a.disk_directory
198 198 assert a.readable?
199 199 end
200 200
201 context "Attachmnet.attach_files" do
202 should "attach the file" do
203 issue = Issue.first
204 assert_difference 'Attachment.count' do
205 Attachment.attach_files(issue,
206 '1' => {
207 'file' => uploaded_test_file('testfile.txt', 'text/plain'),
208 'description' => 'test'
209 })
210 end
211
212 attachment = Attachment.first(:order => 'id DESC')
213 assert_equal issue, attachment.container
214 assert_equal 'testfile.txt', attachment.filename
215 assert_equal 59, attachment.filesize
216 assert_equal 'test', attachment.description
217 assert_equal 'text/plain', attachment.content_type
218 assert File.exists?(attachment.diskfile)
219 assert_equal 59, File.size(attachment.diskfile)
201 test "Attachmnet.attach_files should attach the file" do
202 issue = Issue.first
203 assert_difference 'Attachment.count' do
204 Attachment.attach_files(issue,
205 '1' => {
206 'file' => uploaded_test_file('testfile.txt', 'text/plain'),
207 'description' => 'test'
208 })
220 209 end
221 210
222 should "add unsaved files to the object as unsaved attachments" do
223 # Max size of 0 to force Attachment creation failures
224 with_settings(:attachment_max_size => 0) do
225 @project = Project.find(1)
226 response = Attachment.attach_files(@project, {
227 '1' => {'file' => mock_file, 'description' => 'test'},
228 '2' => {'file' => mock_file, 'description' => 'test'}
229 })
230
231 assert response[:unsaved].present?
232 assert_equal 2, response[:unsaved].length
233 assert response[:unsaved].first.new_record?
234 assert response[:unsaved].second.new_record?
235 assert_equal response[:unsaved], @project.unsaved_attachments
236 end
211 attachment = Attachment.first(:order => 'id DESC')
212 assert_equal issue, attachment.container
213 assert_equal 'testfile.txt', attachment.filename
214 assert_equal 59, attachment.filesize
215 assert_equal 'test', attachment.description
216 assert_equal 'text/plain', attachment.content_type
217 assert File.exists?(attachment.diskfile)
218 assert_equal 59, File.size(attachment.diskfile)
219 end
220
221 test "Attachmnet.attach_files should add unsaved files to the object as unsaved attachments" do
222 # Max size of 0 to force Attachment creation failures
223 with_settings(:attachment_max_size => 0) do
224 @project = Project.find(1)
225 response = Attachment.attach_files(@project, {
226 '1' => {'file' => mock_file, 'description' => 'test'},
227 '2' => {'file' => mock_file, 'description' => 'test'}
228 })
229
230 assert response[:unsaved].present?
231 assert_equal 2, response[:unsaved].length
232 assert response[:unsaved].first.new_record?
233 assert response[:unsaved].second.new_record?
234 assert_equal response[:unsaved], @project.unsaved_attachments
237 235 end
238 236 end
239 237
240 238 def test_latest_attach
241 239 set_fixtures_attachments_directory
242 240 a1 = Attachment.find(16)
243 241 assert_equal "testfile.png", a1.filename
244 242 assert a1.readable?
245 243 assert (! a1.visible?(User.anonymous))
246 244 assert a1.visible?(User.find(2))
247 245 a2 = Attachment.find(17)
248 246 assert_equal "testfile.PNG", a2.filename
249 247 assert a2.readable?
250 248 assert (! a2.visible?(User.anonymous))
251 249 assert a2.visible?(User.find(2))
252 250 assert a1.created_on < a2.created_on
253 251
254 252 la1 = Attachment.latest_attach([a1, a2], "testfile.png")
255 253 assert_equal 17, la1.id
256 254 la2 = Attachment.latest_attach([a1, a2], "Testfile.PNG")
257 255 assert_equal 17, la2.id
258 256
259 257 set_tmp_attachments_directory
260 258 end
261 259
262 260 def test_thumbnailable_should_be_true_for_images
263 261 assert_equal true, Attachment.new(:filename => 'test.jpg').thumbnailable?
264 262 end
265 263
266 264 def test_thumbnailable_should_be_true_for_non_images
267 265 assert_equal false, Attachment.new(:filename => 'test.txt').thumbnailable?
268 266 end
269 267
270 268 if convert_installed?
271 269 def test_thumbnail_should_generate_the_thumbnail
272 270 set_fixtures_attachments_directory
273 271 attachment = Attachment.find(16)
274 272 Attachment.clear_thumbnails
275 273
276 274 assert_difference "Dir.glob(File.join(Attachment.thumbnails_storage_path, '*.thumb')).size" do
277 275 thumbnail = attachment.thumbnail
278 276 assert_equal "16_8e0294de2441577c529f170b6fb8f638_100.thumb", File.basename(thumbnail)
279 277 assert File.exists?(thumbnail)
280 278 end
281 279 end
282 280 else
283 281 puts '(ImageMagick convert not available)'
284 282 end
285 283 end
@@ -1,154 +1,141
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class AuthSourceLdapTest < ActiveSupport::TestCase
21 21 include Redmine::I18n
22 22 fixtures :auth_sources
23 23
24 24 def setup
25 25 end
26 26
27 27 def test_create
28 28 a = AuthSourceLdap.new(:name => 'My LDAP', :host => 'ldap.example.net', :port => 389, :base_dn => 'dc=example,dc=net', :attr_login => 'sAMAccountName')
29 29 assert a.save
30 30 end
31 31
32 32 def test_should_strip_ldap_attributes
33 33 a = AuthSourceLdap.new(:name => 'My LDAP', :host => 'ldap.example.net', :port => 389, :base_dn => 'dc=example,dc=net', :attr_login => 'sAMAccountName',
34 34 :attr_firstname => 'givenName ')
35 35 assert a.save
36 36 assert_equal 'givenName', a.reload.attr_firstname
37 37 end
38 38
39 39 def test_replace_port_zero_to_389
40 40 a = AuthSourceLdap.new(
41 41 :name => 'My LDAP', :host => 'ldap.example.net', :port => 0,
42 42 :base_dn => 'dc=example,dc=net', :attr_login => 'sAMAccountName',
43 43 :attr_firstname => 'givenName ')
44 44 assert a.save
45 45 assert_equal 389, a.port
46 46 end
47 47
48 48 def test_filter_should_be_validated
49 49 set_language_if_valid 'en'
50 50
51 51 a = AuthSourceLdap.new(:name => 'My LDAP', :host => 'ldap.example.net', :port => 389, :attr_login => 'sn')
52 52 a.filter = "(mail=*@redmine.org"
53 53 assert !a.valid?
54 54 assert_include "LDAP filter is invalid", a.errors.full_messages
55 55
56 56 a.filter = "(mail=*@redmine.org)"
57 57 assert a.valid?
58 58 end
59 59
60 60 if ldap_configured?
61 context '#authenticate' do
62 setup do
63 @auth = AuthSourceLdap.find(1)
64 @auth.update_attribute :onthefly_register, true
65 end
66
67 context 'with a valid LDAP user' do
68 should 'return the user attributes' do
69 attributes = @auth.authenticate('example1','123456')
70 assert attributes.is_a?(Hash), "An hash was not returned"
71 assert_equal 'Example', attributes[:firstname]
72 assert_equal 'One', attributes[:lastname]
73 assert_equal 'example1@redmine.org', attributes[:mail]
74 assert_equal @auth.id, attributes[:auth_source_id]
75 attributes.keys.each do |attribute|
76 assert User.new.respond_to?("#{attribute}="), "Unexpected :#{attribute} attribute returned"
77 end
78 end
61 test '#authenticate with a valid LDAP user should return the user attributes' do
62 auth = AuthSourceLdap.find(1)
63 auth.update_attribute :onthefly_register, true
64
65 attributes = auth.authenticate('example1','123456')
66 assert attributes.is_a?(Hash), "An hash was not returned"
67 assert_equal 'Example', attributes[:firstname]
68 assert_equal 'One', attributes[:lastname]
69 assert_equal 'example1@redmine.org', attributes[:mail]
70 assert_equal auth.id, attributes[:auth_source_id]
71 attributes.keys.each do |attribute|
72 assert User.new.respond_to?("#{attribute}="), "Unexpected :#{attribute} attribute returned"
79 73 end
74 end
80 75
81 context 'with an invalid LDAP user' do
82 should 'return nil' do
83 assert_equal nil, @auth.authenticate('nouser','123456')
84 end
85 end
76 test '#authenticate with an invalid LDAP user should return nil' do
77 auth = AuthSourceLdap.find(1)
78 assert_equal nil, auth.authenticate('nouser','123456')
79 end
86 80
87 context 'without a login' do
88 should 'return nil' do
89 assert_equal nil, @auth.authenticate('','123456')
90 end
91 end
81 test '#authenticate without a login should return nil' do
82 auth = AuthSourceLdap.find(1)
83 assert_equal nil, auth.authenticate('','123456')
84 end
92 85
93 context 'without a password' do
94 should 'return nil' do
95 assert_equal nil, @auth.authenticate('edavis','')
96 end
97 end
86 test '#authenticate without a password should return nil' do
87 auth = AuthSourceLdap.find(1)
88 assert_equal nil, auth.authenticate('edavis','')
89 end
98 90
99 context 'without filter' do
100 should 'return any user' do
101 assert @auth.authenticate('example1','123456')
102 assert @auth.authenticate('edavis', '123456')
103 end
104 end
91 test '#authenticate without filter should return any user' do
92 auth = AuthSourceLdap.find(1)
93 assert auth.authenticate('example1','123456')
94 assert auth.authenticate('edavis', '123456')
95 end
105 96
106 context 'with filter' do
107 setup do
108 @auth.filter = "(mail=*@redmine.org)"
109 end
97 test '#authenticate with filter should return user who matches the filter only' do
98 auth = AuthSourceLdap.find(1)
99 auth.filter = "(mail=*@redmine.org)"
110 100
111 should 'return user who matches the filter only' do
112 assert @auth.authenticate('example1','123456')
113 assert_nil @auth.authenticate('edavis', '123456')
114 end
115 end
101 assert auth.authenticate('example1','123456')
102 assert_nil auth.authenticate('edavis', '123456')
116 103 end
117 104
118 105 def test_authenticate_should_timeout
119 106 auth_source = AuthSourceLdap.find(1)
120 107 auth_source.timeout = 1
121 108 def auth_source.initialize_ldap_con(*args); sleep(5); end
122 109
123 110 assert_raise AuthSourceTimeoutException do
124 111 auth_source.authenticate 'example1', '123456'
125 112 end
126 113 end
127 114
128 115 def test_search_should_return_matching_entries
129 116 results = AuthSource.search("exa")
130 117 assert_equal 1, results.size
131 118 result = results.first
132 119 assert_kind_of Hash, result
133 120 assert_equal "example1", result[:login]
134 121 assert_equal "Example", result[:firstname]
135 122 assert_equal "One", result[:lastname]
136 123 assert_equal "example1@redmine.org", result[:mail]
137 124 assert_equal 1, result[:auth_source_id]
138 125 end
139 126
140 127 def test_search_with_no_match_should_return_an_empty_array
141 128 results = AuthSource.search("wro")
142 129 assert_equal [], results
143 130 end
144 131
145 132 def test_search_with_exception_should_return_an_empty_array
146 133 Net::LDAP.stubs(:new).raises(Net::LDAP::LdapError, 'Cannot connect')
147 134
148 135 results = AuthSource.search("exa")
149 136 assert_equal [], results
150 137 end
151 138 else
152 139 puts '(Test LDAP server not configured)'
153 140 end
154 141 end
@@ -1,1957 +1,1905
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class IssueTest < ActiveSupport::TestCase
21 21 fixtures :projects, :users, :members, :member_roles, :roles,
22 22 :groups_users,
23 23 :trackers, :projects_trackers,
24 24 :enabled_modules,
25 25 :versions,
26 26 :issue_statuses, :issue_categories, :issue_relations, :workflows,
27 27 :enumerations,
28 28 :issues, :journals, :journal_details,
29 29 :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values,
30 30 :time_entries
31 31
32 32 include Redmine::I18n
33 33
34 34 def teardown
35 35 User.current = nil
36 36 end
37 37
38 38 def test_create
39 39 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
40 40 :status_id => 1, :priority => IssuePriority.all.first,
41 41 :subject => 'test_create',
42 42 :description => 'IssueTest#test_create', :estimated_hours => '1:30')
43 43 assert issue.save
44 44 issue.reload
45 45 assert_equal 1.5, issue.estimated_hours
46 46 end
47 47
48 48 def test_create_minimal
49 49 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
50 50 :status_id => 1, :priority => IssuePriority.all.first,
51 51 :subject => 'test_create')
52 52 assert issue.save
53 53 assert issue.description.nil?
54 54 assert_nil issue.estimated_hours
55 55 end
56 56
57 57 def test_start_date_format_should_be_validated
58 58 set_language_if_valid 'en'
59 59 ['2012', 'ABC', '2012-15-20'].each do |invalid_date|
60 60 issue = Issue.new(:start_date => invalid_date)
61 61 assert !issue.valid?
62 62 assert_include 'Start date is not a valid date', issue.errors.full_messages, "No error found for invalid date #{invalid_date}"
63 63 end
64 64 end
65 65
66 66 def test_due_date_format_should_be_validated
67 67 set_language_if_valid 'en'
68 68 ['2012', 'ABC', '2012-15-20'].each do |invalid_date|
69 69 issue = Issue.new(:due_date => invalid_date)
70 70 assert !issue.valid?
71 71 assert_include 'Due date is not a valid date', issue.errors.full_messages, "No error found for invalid date #{invalid_date}"
72 72 end
73 73 end
74 74
75 75 def test_due_date_lesser_than_start_date_should_not_validate
76 76 set_language_if_valid 'en'
77 77 issue = Issue.new(:start_date => '2012-10-06', :due_date => '2012-10-02')
78 78 assert !issue.valid?
79 79 assert_include 'Due date must be greater than start date', issue.errors.full_messages
80 80 end
81 81
82 82 def test_estimated_hours_should_be_validated
83 83 set_language_if_valid 'en'
84 84 ['-2'].each do |invalid|
85 85 issue = Issue.new(:estimated_hours => invalid)
86 86 assert !issue.valid?
87 87 assert_include 'Estimated time is invalid', issue.errors.full_messages
88 88 end
89 89 end
90 90
91 91 def test_create_with_required_custom_field
92 92 set_language_if_valid 'en'
93 93 field = IssueCustomField.find_by_name('Database')
94 94 field.update_attribute(:is_required, true)
95 95
96 96 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
97 97 :status_id => 1, :subject => 'test_create',
98 98 :description => 'IssueTest#test_create_with_required_custom_field')
99 99 assert issue.available_custom_fields.include?(field)
100 100 # No value for the custom field
101 101 assert !issue.save
102 102 assert_equal ["Database can't be blank"], issue.errors.full_messages
103 103 # Blank value
104 104 issue.custom_field_values = { field.id => '' }
105 105 assert !issue.save
106 106 assert_equal ["Database can't be blank"], issue.errors.full_messages
107 107 # Invalid value
108 108 issue.custom_field_values = { field.id => 'SQLServer' }
109 109 assert !issue.save
110 110 assert_equal ["Database is not included in the list"], issue.errors.full_messages
111 111 # Valid value
112 112 issue.custom_field_values = { field.id => 'PostgreSQL' }
113 113 assert issue.save
114 114 issue.reload
115 115 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
116 116 end
117 117
118 118 def test_create_with_group_assignment
119 119 with_settings :issue_group_assignment => '1' do
120 120 assert Issue.new(:project_id => 2, :tracker_id => 1, :author_id => 1,
121 121 :subject => 'Group assignment',
122 122 :assigned_to_id => 11).save
123 123 issue = Issue.first(:order => 'id DESC')
124 124 assert_kind_of Group, issue.assigned_to
125 125 assert_equal Group.find(11), issue.assigned_to
126 126 end
127 127 end
128 128
129 129 def test_create_with_parent_issue_id
130 130 issue = Issue.new(:project_id => 1, :tracker_id => 1,
131 131 :author_id => 1, :subject => 'Group assignment',
132 132 :parent_issue_id => 1)
133 133 assert_save issue
134 134 assert_equal 1, issue.parent_issue_id
135 135 assert_equal Issue.find(1), issue.parent
136 136 end
137 137
138 138 def test_create_with_sharp_parent_issue_id
139 139 issue = Issue.new(:project_id => 1, :tracker_id => 1,
140 140 :author_id => 1, :subject => 'Group assignment',
141 141 :parent_issue_id => "#1")
142 142 assert_save issue
143 143 assert_equal 1, issue.parent_issue_id
144 144 assert_equal Issue.find(1), issue.parent
145 145 end
146 146
147 147 def test_create_with_invalid_parent_issue_id
148 148 set_language_if_valid 'en'
149 149 issue = Issue.new(:project_id => 1, :tracker_id => 1,
150 150 :author_id => 1, :subject => 'Group assignment',
151 151 :parent_issue_id => '01ABC')
152 152 assert !issue.save
153 153 assert_equal '01ABC', issue.parent_issue_id
154 154 assert_include 'Parent task is invalid', issue.errors.full_messages
155 155 end
156 156
157 157 def test_create_with_invalid_sharp_parent_issue_id
158 158 set_language_if_valid 'en'
159 159 issue = Issue.new(:project_id => 1, :tracker_id => 1,
160 160 :author_id => 1, :subject => 'Group assignment',
161 161 :parent_issue_id => '#01ABC')
162 162 assert !issue.save
163 163 assert_equal '#01ABC', issue.parent_issue_id
164 164 assert_include 'Parent task is invalid', issue.errors.full_messages
165 165 end
166 166
167 167 def assert_visibility_match(user, issues)
168 168 assert_equal issues.collect(&:id).sort, Issue.all.select {|issue| issue.visible?(user)}.collect(&:id).sort
169 169 end
170 170
171 171 def test_visible_scope_for_anonymous
172 172 # Anonymous user should see issues of public projects only
173 173 issues = Issue.visible(User.anonymous).all
174 174 assert issues.any?
175 175 assert_nil issues.detect {|issue| !issue.project.is_public?}
176 176 assert_nil issues.detect {|issue| issue.is_private?}
177 177 assert_visibility_match User.anonymous, issues
178 178 end
179 179
180 180 def test_visible_scope_for_anonymous_without_view_issues_permissions
181 181 # Anonymous user should not see issues without permission
182 182 Role.anonymous.remove_permission!(:view_issues)
183 183 issues = Issue.visible(User.anonymous).all
184 184 assert issues.empty?
185 185 assert_visibility_match User.anonymous, issues
186 186 end
187 187
188 188 def test_anonymous_should_not_see_private_issues_with_issues_visibility_set_to_default
189 189 assert Role.anonymous.update_attribute(:issues_visibility, 'default')
190 190 issue = Issue.generate!(:author => User.anonymous, :assigned_to => User.anonymous, :is_private => true)
191 191 assert_nil Issue.where(:id => issue.id).visible(User.anonymous).first
192 192 assert !issue.visible?(User.anonymous)
193 193 end
194 194
195 195 def test_anonymous_should_not_see_private_issues_with_issues_visibility_set_to_own
196 196 assert Role.anonymous.update_attribute(:issues_visibility, 'own')
197 197 issue = Issue.generate!(:author => User.anonymous, :assigned_to => User.anonymous, :is_private => true)
198 198 assert_nil Issue.where(:id => issue.id).visible(User.anonymous).first
199 199 assert !issue.visible?(User.anonymous)
200 200 end
201 201
202 202 def test_visible_scope_for_non_member
203 203 user = User.find(9)
204 204 assert user.projects.empty?
205 205 # Non member user should see issues of public projects only
206 206 issues = Issue.visible(user).all
207 207 assert issues.any?
208 208 assert_nil issues.detect {|issue| !issue.project.is_public?}
209 209 assert_nil issues.detect {|issue| issue.is_private?}
210 210 assert_visibility_match user, issues
211 211 end
212 212
213 213 def test_visible_scope_for_non_member_with_own_issues_visibility
214 214 Role.non_member.update_attribute :issues_visibility, 'own'
215 215 Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 9, :subject => 'Issue by non member')
216 216 user = User.find(9)
217 217
218 218 issues = Issue.visible(user).all
219 219 assert issues.any?
220 220 assert_nil issues.detect {|issue| issue.author != user}
221 221 assert_visibility_match user, issues
222 222 end
223 223
224 224 def test_visible_scope_for_non_member_without_view_issues_permissions
225 225 # Non member user should not see issues without permission
226 226 Role.non_member.remove_permission!(:view_issues)
227 227 user = User.find(9)
228 228 assert user.projects.empty?
229 229 issues = Issue.visible(user).all
230 230 assert issues.empty?
231 231 assert_visibility_match user, issues
232 232 end
233 233
234 234 def test_visible_scope_for_member
235 235 user = User.find(9)
236 236 # User should see issues of projects for which he has view_issues permissions only
237 237 Role.non_member.remove_permission!(:view_issues)
238 238 Member.create!(:principal => user, :project_id => 3, :role_ids => [2])
239 239 issues = Issue.visible(user).all
240 240 assert issues.any?
241 241 assert_nil issues.detect {|issue| issue.project_id != 3}
242 242 assert_nil issues.detect {|issue| issue.is_private?}
243 243 assert_visibility_match user, issues
244 244 end
245 245
246 246 def test_visible_scope_for_member_with_groups_should_return_assigned_issues
247 247 user = User.find(8)
248 248 assert user.groups.any?
249 249 Member.create!(:principal => user.groups.first, :project_id => 1, :role_ids => [2])
250 250 Role.non_member.remove_permission!(:view_issues)
251 251
252 252 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3,
253 253 :status_id => 1, :priority => IssuePriority.all.first,
254 254 :subject => 'Assignment test',
255 255 :assigned_to => user.groups.first,
256 256 :is_private => true)
257 257
258 258 Role.find(2).update_attribute :issues_visibility, 'default'
259 259 issues = Issue.visible(User.find(8)).all
260 260 assert issues.any?
261 261 assert issues.include?(issue)
262 262
263 263 Role.find(2).update_attribute :issues_visibility, 'own'
264 264 issues = Issue.visible(User.find(8)).all
265 265 assert issues.any?
266 266 assert issues.include?(issue)
267 267 end
268 268
269 269 def test_visible_scope_for_admin
270 270 user = User.find(1)
271 271 user.members.each(&:destroy)
272 272 assert user.projects.empty?
273 273 issues = Issue.visible(user).all
274 274 assert issues.any?
275 275 # Admin should see issues on private projects that he does not belong to
276 276 assert issues.detect {|issue| !issue.project.is_public?}
277 277 # Admin should see private issues of other users
278 278 assert issues.detect {|issue| issue.is_private? && issue.author != user}
279 279 assert_visibility_match user, issues
280 280 end
281 281
282 282 def test_visible_scope_with_project
283 283 project = Project.find(1)
284 284 issues = Issue.visible(User.find(2), :project => project).all
285 285 projects = issues.collect(&:project).uniq
286 286 assert_equal 1, projects.size
287 287 assert_equal project, projects.first
288 288 end
289 289
290 290 def test_visible_scope_with_project_and_subprojects
291 291 project = Project.find(1)
292 292 issues = Issue.visible(User.find(2), :project => project, :with_subprojects => true).all
293 293 projects = issues.collect(&:project).uniq
294 294 assert projects.size > 1
295 295 assert_equal [], projects.select {|p| !p.is_or_is_descendant_of?(project)}
296 296 end
297 297
298 298 def test_visible_and_nested_set_scopes
299 299 assert_equal 0, Issue.find(1).descendants.visible.all.size
300 300 end
301 301
302 302 def test_open_scope
303 303 issues = Issue.open.all
304 304 assert_nil issues.detect(&:closed?)
305 305 end
306 306
307 307 def test_open_scope_with_arg
308 308 issues = Issue.open(false).all
309 309 assert_equal issues, issues.select(&:closed?)
310 310 end
311 311
312 312 def test_fixed_version_scope_with_a_version_should_return_its_fixed_issues
313 313 version = Version.find(2)
314 314 assert version.fixed_issues.any?
315 315 assert_equal version.fixed_issues.to_a.sort, Issue.fixed_version(version).to_a.sort
316 316 end
317 317
318 318 def test_fixed_version_scope_with_empty_array_should_return_no_result
319 319 assert_equal 0, Issue.fixed_version([]).count
320 320 end
321 321
322 322 def test_errors_full_messages_should_include_custom_fields_errors
323 323 field = IssueCustomField.find_by_name('Database')
324 324
325 325 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
326 326 :status_id => 1, :subject => 'test_create',
327 327 :description => 'IssueTest#test_create_with_required_custom_field')
328 328 assert issue.available_custom_fields.include?(field)
329 329 # Invalid value
330 330 issue.custom_field_values = { field.id => 'SQLServer' }
331 331
332 332 assert !issue.valid?
333 333 assert_equal 1, issue.errors.full_messages.size
334 334 assert_equal "Database #{I18n.translate('activerecord.errors.messages.inclusion')}",
335 335 issue.errors.full_messages.first
336 336 end
337 337
338 338 def test_update_issue_with_required_custom_field
339 339 field = IssueCustomField.find_by_name('Database')
340 340 field.update_attribute(:is_required, true)
341 341
342 342 issue = Issue.find(1)
343 343 assert_nil issue.custom_value_for(field)
344 344 assert issue.available_custom_fields.include?(field)
345 345 # No change to custom values, issue can be saved
346 346 assert issue.save
347 347 # Blank value
348 348 issue.custom_field_values = { field.id => '' }
349 349 assert !issue.save
350 350 # Valid value
351 351 issue.custom_field_values = { field.id => 'PostgreSQL' }
352 352 assert issue.save
353 353 issue.reload
354 354 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
355 355 end
356 356
357 357 def test_should_not_update_attributes_if_custom_fields_validation_fails
358 358 issue = Issue.find(1)
359 359 field = IssueCustomField.find_by_name('Database')
360 360 assert issue.available_custom_fields.include?(field)
361 361
362 362 issue.custom_field_values = { field.id => 'Invalid' }
363 363 issue.subject = 'Should be not be saved'
364 364 assert !issue.save
365 365
366 366 issue.reload
367 367 assert_equal "Can't print recipes", issue.subject
368 368 end
369 369
370 370 def test_should_not_recreate_custom_values_objects_on_update
371 371 field = IssueCustomField.find_by_name('Database')
372 372
373 373 issue = Issue.find(1)
374 374 issue.custom_field_values = { field.id => 'PostgreSQL' }
375 375 assert issue.save
376 376 custom_value = issue.custom_value_for(field)
377 377 issue.reload
378 378 issue.custom_field_values = { field.id => 'MySQL' }
379 379 assert issue.save
380 380 issue.reload
381 381 assert_equal custom_value.id, issue.custom_value_for(field).id
382 382 end
383 383
384 384 def test_should_not_update_custom_fields_on_changing_tracker_with_different_custom_fields
385 385 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1,
386 386 :status_id => 1, :subject => 'Test',
387 387 :custom_field_values => {'2' => 'Test'})
388 388 assert !Tracker.find(2).custom_field_ids.include?(2)
389 389
390 390 issue = Issue.find(issue.id)
391 391 issue.attributes = {:tracker_id => 2, :custom_field_values => {'1' => ''}}
392 392
393 393 issue = Issue.find(issue.id)
394 394 custom_value = issue.custom_value_for(2)
395 395 assert_not_nil custom_value
396 396 assert_equal 'Test', custom_value.value
397 397 end
398 398
399 399 def test_assigning_tracker_id_should_reload_custom_fields_values
400 400 issue = Issue.new(:project => Project.find(1))
401 401 assert issue.custom_field_values.empty?
402 402 issue.tracker_id = 1
403 403 assert issue.custom_field_values.any?
404 404 end
405 405
406 406 def test_assigning_attributes_should_assign_project_and_tracker_first
407 407 seq = sequence('seq')
408 408 issue = Issue.new
409 409 issue.expects(:project_id=).in_sequence(seq)
410 410 issue.expects(:tracker_id=).in_sequence(seq)
411 411 issue.expects(:subject=).in_sequence(seq)
412 412 issue.attributes = {:tracker_id => 2, :project_id => 1, :subject => 'Test'}
413 413 end
414 414
415 415 def test_assigning_tracker_and_custom_fields_should_assign_custom_fields
416 416 attributes = ActiveSupport::OrderedHash.new
417 417 attributes['custom_field_values'] = { '1' => 'MySQL' }
418 418 attributes['tracker_id'] = '1'
419 419 issue = Issue.new(:project => Project.find(1))
420 420 issue.attributes = attributes
421 421 assert_equal 'MySQL', issue.custom_field_value(1)
422 422 end
423 423
424 424 def test_should_update_issue_with_disabled_tracker
425 425 p = Project.find(1)
426 426 issue = Issue.find(1)
427 427
428 428 p.trackers.delete(issue.tracker)
429 429 assert !p.trackers.include?(issue.tracker)
430 430
431 431 issue.reload
432 432 issue.subject = 'New subject'
433 433 assert issue.save
434 434 end
435 435
436 436 def test_should_not_set_a_disabled_tracker
437 437 p = Project.find(1)
438 438 p.trackers.delete(Tracker.find(2))
439 439
440 440 issue = Issue.find(1)
441 441 issue.tracker_id = 2
442 442 issue.subject = 'New subject'
443 443 assert !issue.save
444 444 assert_not_nil issue.errors[:tracker_id]
445 445 end
446 446
447 447 def test_category_based_assignment
448 448 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3,
449 449 :status_id => 1, :priority => IssuePriority.all.first,
450 450 :subject => 'Assignment test',
451 451 :description => 'Assignment test', :category_id => 1)
452 452 assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
453 453 end
454 454
455 455 def test_new_statuses_allowed_to
456 456 WorkflowTransition.delete_all
457 457 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
458 458 :old_status_id => 1, :new_status_id => 2,
459 459 :author => false, :assignee => false)
460 460 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
461 461 :old_status_id => 1, :new_status_id => 3,
462 462 :author => true, :assignee => false)
463 463 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1,
464 464 :new_status_id => 4, :author => false,
465 465 :assignee => true)
466 466 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
467 467 :old_status_id => 1, :new_status_id => 5,
468 468 :author => true, :assignee => true)
469 469 status = IssueStatus.find(1)
470 470 role = Role.find(1)
471 471 tracker = Tracker.find(1)
472 472 user = User.find(2)
473 473
474 474 issue = Issue.generate!(:tracker => tracker, :status => status,
475 475 :project_id => 1, :author_id => 1)
476 476 assert_equal [1, 2], issue.new_statuses_allowed_to(user).map(&:id)
477 477
478 478 issue = Issue.generate!(:tracker => tracker, :status => status,
479 479 :project_id => 1, :author => user)
480 480 assert_equal [1, 2, 3, 5], issue.new_statuses_allowed_to(user).map(&:id)
481 481
482 482 issue = Issue.generate!(:tracker => tracker, :status => status,
483 483 :project_id => 1, :author_id => 1,
484 484 :assigned_to => user)
485 485 assert_equal [1, 2, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
486 486
487 487 issue = Issue.generate!(:tracker => tracker, :status => status,
488 488 :project_id => 1, :author => user,
489 489 :assigned_to => user)
490 490 assert_equal [1, 2, 3, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
491 491 end
492 492
493 493 def test_new_statuses_allowed_to_should_return_all_transitions_for_admin
494 494 admin = User.find(1)
495 495 issue = Issue.find(1)
496 496 assert !admin.member_of?(issue.project)
497 497 expected_statuses = [issue.status] +
498 498 WorkflowTransition.find_all_by_old_status_id(
499 499 issue.status_id).map(&:new_status).uniq.sort
500 500 assert_equal expected_statuses, issue.new_statuses_allowed_to(admin)
501 501 end
502 502
503 503 def test_new_statuses_allowed_to_should_return_default_and_current_status_when_copying
504 504 issue = Issue.find(1).copy
505 505 assert_equal [1], issue.new_statuses_allowed_to(User.find(2)).map(&:id)
506 506
507 507 issue = Issue.find(2).copy
508 508 assert_equal [1, 2], issue.new_statuses_allowed_to(User.find(2)).map(&:id)
509 509 end
510 510
511 511 def test_safe_attributes_names_should_not_include_disabled_field
512 512 tracker = Tracker.new(:core_fields => %w(assigned_to_id fixed_version_id))
513 513
514 514 issue = Issue.new(:tracker => tracker)
515 515 assert_include 'tracker_id', issue.safe_attribute_names
516 516 assert_include 'status_id', issue.safe_attribute_names
517 517 assert_include 'subject', issue.safe_attribute_names
518 518 assert_include 'description', issue.safe_attribute_names
519 519 assert_include 'custom_field_values', issue.safe_attribute_names
520 520 assert_include 'custom_fields', issue.safe_attribute_names
521 521 assert_include 'lock_version', issue.safe_attribute_names
522 522
523 523 tracker.core_fields.each do |field|
524 524 assert_include field, issue.safe_attribute_names
525 525 end
526 526
527 527 tracker.disabled_core_fields.each do |field|
528 528 assert_not_include field, issue.safe_attribute_names
529 529 end
530 530 end
531 531
532 532 def test_safe_attributes_should_ignore_disabled_fields
533 533 tracker = Tracker.find(1)
534 534 tracker.core_fields = %w(assigned_to_id due_date)
535 535 tracker.save!
536 536
537 537 issue = Issue.new(:tracker => tracker)
538 538 issue.safe_attributes = {'start_date' => '2012-07-14', 'due_date' => '2012-07-14'}
539 539 assert_nil issue.start_date
540 540 assert_equal Date.parse('2012-07-14'), issue.due_date
541 541 end
542 542
543 543 def test_safe_attributes_should_accept_target_tracker_enabled_fields
544 544 source = Tracker.find(1)
545 545 source.core_fields = []
546 546 source.save!
547 547 target = Tracker.find(2)
548 548 target.core_fields = %w(assigned_to_id due_date)
549 549 target.save!
550 550
551 551 issue = Issue.new(:tracker => source)
552 552 issue.safe_attributes = {'tracker_id' => 2, 'due_date' => '2012-07-14'}
553 553 assert_equal target, issue.tracker
554 554 assert_equal Date.parse('2012-07-14'), issue.due_date
555 555 end
556 556
557 557 def test_safe_attributes_should_not_include_readonly_fields
558 558 WorkflowPermission.delete_all
559 559 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
560 560 :role_id => 1, :field_name => 'due_date',
561 561 :rule => 'readonly')
562 562 user = User.find(2)
563 563
564 564 issue = Issue.new(:project_id => 1, :tracker_id => 1)
565 565 assert_equal %w(due_date), issue.read_only_attribute_names(user)
566 566 assert_not_include 'due_date', issue.safe_attribute_names(user)
567 567
568 568 issue.send :safe_attributes=, {'start_date' => '2012-07-14', 'due_date' => '2012-07-14'}, user
569 569 assert_equal Date.parse('2012-07-14'), issue.start_date
570 570 assert_nil issue.due_date
571 571 end
572 572
573 573 def test_safe_attributes_should_not_include_readonly_custom_fields
574 574 cf1 = IssueCustomField.create!(:name => 'Writable field',
575 575 :field_format => 'string',
576 576 :is_for_all => true, :tracker_ids => [1])
577 577 cf2 = IssueCustomField.create!(:name => 'Readonly field',
578 578 :field_format => 'string',
579 579 :is_for_all => true, :tracker_ids => [1])
580 580 WorkflowPermission.delete_all
581 581 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
582 582 :role_id => 1, :field_name => cf2.id.to_s,
583 583 :rule => 'readonly')
584 584 user = User.find(2)
585 585 issue = Issue.new(:project_id => 1, :tracker_id => 1)
586 586 assert_equal [cf2.id.to_s], issue.read_only_attribute_names(user)
587 587 assert_not_include cf2.id.to_s, issue.safe_attribute_names(user)
588 588
589 589 issue.send :safe_attributes=, {'custom_field_values' => {
590 590 cf1.id.to_s => 'value1', cf2.id.to_s => 'value2'
591 591 }}, user
592 592 assert_equal 'value1', issue.custom_field_value(cf1)
593 593 assert_nil issue.custom_field_value(cf2)
594 594
595 595 issue.send :safe_attributes=, {'custom_fields' => [
596 596 {'id' => cf1.id.to_s, 'value' => 'valuea'},
597 597 {'id' => cf2.id.to_s, 'value' => 'valueb'}
598 598 ]}, user
599 599 assert_equal 'valuea', issue.custom_field_value(cf1)
600 600 assert_nil issue.custom_field_value(cf2)
601 601 end
602 602
603 603 def test_editable_custom_field_values_should_return_non_readonly_custom_values
604 604 cf1 = IssueCustomField.create!(:name => 'Writable field', :field_format => 'string',
605 605 :is_for_all => true, :tracker_ids => [1, 2])
606 606 cf2 = IssueCustomField.create!(:name => 'Readonly field', :field_format => 'string',
607 607 :is_for_all => true, :tracker_ids => [1, 2])
608 608 WorkflowPermission.delete_all
609 609 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1,
610 610 :field_name => cf2.id.to_s, :rule => 'readonly')
611 611 user = User.find(2)
612 612
613 613 issue = Issue.new(:project_id => 1, :tracker_id => 1)
614 614 values = issue.editable_custom_field_values(user)
615 615 assert values.detect {|value| value.custom_field == cf1}
616 616 assert_nil values.detect {|value| value.custom_field == cf2}
617 617
618 618 issue.tracker_id = 2
619 619 values = issue.editable_custom_field_values(user)
620 620 assert values.detect {|value| value.custom_field == cf1}
621 621 assert values.detect {|value| value.custom_field == cf2}
622 622 end
623 623
624 624 def test_safe_attributes_should_accept_target_tracker_writable_fields
625 625 WorkflowPermission.delete_all
626 626 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
627 627 :role_id => 1, :field_name => 'due_date',
628 628 :rule => 'readonly')
629 629 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2,
630 630 :role_id => 1, :field_name => 'start_date',
631 631 :rule => 'readonly')
632 632 user = User.find(2)
633 633
634 634 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
635 635
636 636 issue.send :safe_attributes=, {'start_date' => '2012-07-12',
637 637 'due_date' => '2012-07-14'}, user
638 638 assert_equal Date.parse('2012-07-12'), issue.start_date
639 639 assert_nil issue.due_date
640 640
641 641 issue.send :safe_attributes=, {'start_date' => '2012-07-15',
642 642 'due_date' => '2012-07-16',
643 643 'tracker_id' => 2}, user
644 644 assert_equal Date.parse('2012-07-12'), issue.start_date
645 645 assert_equal Date.parse('2012-07-16'), issue.due_date
646 646 end
647 647
648 648 def test_safe_attributes_should_accept_target_status_writable_fields
649 649 WorkflowPermission.delete_all
650 650 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
651 651 :role_id => 1, :field_name => 'due_date',
652 652 :rule => 'readonly')
653 653 WorkflowPermission.create!(:old_status_id => 2, :tracker_id => 1,
654 654 :role_id => 1, :field_name => 'start_date',
655 655 :rule => 'readonly')
656 656 user = User.find(2)
657 657
658 658 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
659 659
660 660 issue.send :safe_attributes=, {'start_date' => '2012-07-12',
661 661 'due_date' => '2012-07-14'},
662 662 user
663 663 assert_equal Date.parse('2012-07-12'), issue.start_date
664 664 assert_nil issue.due_date
665 665
666 666 issue.send :safe_attributes=, {'start_date' => '2012-07-15',
667 667 'due_date' => '2012-07-16',
668 668 'status_id' => 2},
669 669 user
670 670 assert_equal Date.parse('2012-07-12'), issue.start_date
671 671 assert_equal Date.parse('2012-07-16'), issue.due_date
672 672 end
673 673
674 674 def test_required_attributes_should_be_validated
675 675 cf = IssueCustomField.create!(:name => 'Foo', :field_format => 'string',
676 676 :is_for_all => true, :tracker_ids => [1, 2])
677 677
678 678 WorkflowPermission.delete_all
679 679 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
680 680 :role_id => 1, :field_name => 'due_date',
681 681 :rule => 'required')
682 682 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
683 683 :role_id => 1, :field_name => 'category_id',
684 684 :rule => 'required')
685 685 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
686 686 :role_id => 1, :field_name => cf.id.to_s,
687 687 :rule => 'required')
688 688
689 689 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2,
690 690 :role_id => 1, :field_name => 'start_date',
691 691 :rule => 'required')
692 692 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2,
693 693 :role_id => 1, :field_name => cf.id.to_s,
694 694 :rule => 'required')
695 695 user = User.find(2)
696 696
697 697 issue = Issue.new(:project_id => 1, :tracker_id => 1,
698 698 :status_id => 1, :subject => 'Required fields',
699 699 :author => user)
700 700 assert_equal [cf.id.to_s, "category_id", "due_date"],
701 701 issue.required_attribute_names(user).sort
702 702 assert !issue.save, "Issue was saved"
703 703 assert_equal ["Category can't be blank", "Due date can't be blank", "Foo can't be blank"],
704 704 issue.errors.full_messages.sort
705 705
706 706 issue.tracker_id = 2
707 707 assert_equal [cf.id.to_s, "start_date"], issue.required_attribute_names(user).sort
708 708 assert !issue.save, "Issue was saved"
709 709 assert_equal ["Foo can't be blank", "Start date can't be blank"],
710 710 issue.errors.full_messages.sort
711 711
712 712 issue.start_date = Date.today
713 713 issue.custom_field_values = {cf.id.to_s => 'bar'}
714 714 assert issue.save
715 715 end
716 716
717 717 def test_required_attribute_names_for_multiple_roles_should_intersect_rules
718 718 WorkflowPermission.delete_all
719 719 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
720 720 :role_id => 1, :field_name => 'due_date',
721 721 :rule => 'required')
722 722 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
723 723 :role_id => 1, :field_name => 'start_date',
724 724 :rule => 'required')
725 725 user = User.find(2)
726 726 member = Member.find(1)
727 727 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
728 728
729 729 assert_equal %w(due_date start_date), issue.required_attribute_names(user).sort
730 730
731 731 member.role_ids = [1, 2]
732 732 member.save!
733 733 assert_equal [], issue.required_attribute_names(user.reload)
734 734
735 735 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
736 736 :role_id => 2, :field_name => 'due_date',
737 737 :rule => 'required')
738 738 assert_equal %w(due_date), issue.required_attribute_names(user)
739 739
740 740 member.role_ids = [1, 2, 3]
741 741 member.save!
742 742 assert_equal [], issue.required_attribute_names(user.reload)
743 743
744 744 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
745 745 :role_id => 2, :field_name => 'due_date',
746 746 :rule => 'readonly')
747 747 # required + readonly => required
748 748 assert_equal %w(due_date), issue.required_attribute_names(user)
749 749 end
750 750
751 751 def test_read_only_attribute_names_for_multiple_roles_should_intersect_rules
752 752 WorkflowPermission.delete_all
753 753 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
754 754 :role_id => 1, :field_name => 'due_date',
755 755 :rule => 'readonly')
756 756 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
757 757 :role_id => 1, :field_name => 'start_date',
758 758 :rule => 'readonly')
759 759 user = User.find(2)
760 760 member = Member.find(1)
761 761 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
762 762
763 763 assert_equal %w(due_date start_date), issue.read_only_attribute_names(user).sort
764 764
765 765 member.role_ids = [1, 2]
766 766 member.save!
767 767 assert_equal [], issue.read_only_attribute_names(user.reload)
768 768
769 769 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
770 770 :role_id => 2, :field_name => 'due_date',
771 771 :rule => 'readonly')
772 772 assert_equal %w(due_date), issue.read_only_attribute_names(user)
773 773 end
774 774
775 775 def test_copy
776 776 issue = Issue.new.copy_from(1)
777 777 assert issue.copy?
778 778 assert issue.save
779 779 issue.reload
780 780 orig = Issue.find(1)
781 781 assert_equal orig.subject, issue.subject
782 782 assert_equal orig.tracker, issue.tracker
783 783 assert_equal "125", issue.custom_value_for(2).value
784 784 end
785 785
786 786 def test_copy_should_copy_status
787 787 orig = Issue.find(8)
788 788 assert orig.status != IssueStatus.default
789 789
790 790 issue = Issue.new.copy_from(orig)
791 791 assert issue.save
792 792 issue.reload
793 793 assert_equal orig.status, issue.status
794 794 end
795 795
796 796 def test_copy_should_add_relation_with_copied_issue
797 797 copied = Issue.find(1)
798 798 issue = Issue.new.copy_from(copied)
799 799 assert issue.save
800 800 issue.reload
801 801
802 802 assert_equal 1, issue.relations.size
803 803 relation = issue.relations.first
804 804 assert_equal 'copied_to', relation.relation_type
805 805 assert_equal copied, relation.issue_from
806 806 assert_equal issue, relation.issue_to
807 807 end
808 808
809 809 def test_copy_should_copy_subtasks
810 810 issue = Issue.generate_with_descendants!
811 811
812 812 copy = issue.reload.copy
813 813 copy.author = User.find(7)
814 814 assert_difference 'Issue.count', 1+issue.descendants.count do
815 815 assert copy.save
816 816 end
817 817 copy.reload
818 818 assert_equal %w(Child1 Child2), copy.children.map(&:subject).sort
819 819 child_copy = copy.children.detect {|c| c.subject == 'Child1'}
820 820 assert_equal %w(Child11), child_copy.children.map(&:subject).sort
821 821 assert_equal copy.author, child_copy.author
822 822 end
823 823
824 824 def test_copy_should_copy_subtasks_to_target_project
825 825 issue = Issue.generate_with_descendants!
826 826
827 827 copy = issue.copy(:project_id => 3)
828 828 assert_difference 'Issue.count', 1+issue.descendants.count do
829 829 assert copy.save
830 830 end
831 831 assert_equal [3], copy.reload.descendants.map(&:project_id).uniq
832 832 end
833 833
834 834 def test_copy_should_not_copy_subtasks_twice_when_saving_twice
835 835 issue = Issue.generate_with_descendants!
836 836
837 837 copy = issue.reload.copy
838 838 assert_difference 'Issue.count', 1+issue.descendants.count do
839 839 assert copy.save
840 840 assert copy.save
841 841 end
842 842 end
843 843
844 844 def test_should_not_call_after_project_change_on_creation
845 845 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
846 846 :subject => 'Test', :author_id => 1)
847 847 issue.expects(:after_project_change).never
848 848 issue.save!
849 849 end
850 850
851 851 def test_should_not_call_after_project_change_on_update
852 852 issue = Issue.find(1)
853 853 issue.project = Project.find(1)
854 854 issue.subject = 'No project change'
855 855 issue.expects(:after_project_change).never
856 856 issue.save!
857 857 end
858 858
859 859 def test_should_call_after_project_change_on_project_change
860 860 issue = Issue.find(1)
861 861 issue.project = Project.find(2)
862 862 issue.expects(:after_project_change).once
863 863 issue.save!
864 864 end
865 865
866 866 def test_adding_journal_should_update_timestamp
867 867 issue = Issue.find(1)
868 868 updated_on_was = issue.updated_on
869 869
870 870 issue.init_journal(User.first, "Adding notes")
871 871 assert_difference 'Journal.count' do
872 872 assert issue.save
873 873 end
874 874 issue.reload
875 875
876 876 assert_not_equal updated_on_was, issue.updated_on
877 877 end
878 878
879 879 def test_should_close_duplicates
880 880 # Create 3 issues
881 881 issue1 = Issue.generate!
882 882 issue2 = Issue.generate!
883 883 issue3 = Issue.generate!
884 884
885 885 # 2 is a dupe of 1
886 886 IssueRelation.create!(:issue_from => issue2, :issue_to => issue1,
887 887 :relation_type => IssueRelation::TYPE_DUPLICATES)
888 888 # And 3 is a dupe of 2
889 889 IssueRelation.create!(:issue_from => issue3, :issue_to => issue2,
890 890 :relation_type => IssueRelation::TYPE_DUPLICATES)
891 891 # And 3 is a dupe of 1 (circular duplicates)
892 892 IssueRelation.create!(:issue_from => issue3, :issue_to => issue1,
893 893 :relation_type => IssueRelation::TYPE_DUPLICATES)
894 894
895 895 assert issue1.reload.duplicates.include?(issue2)
896 896
897 897 # Closing issue 1
898 898 issue1.init_journal(User.first, "Closing issue1")
899 899 issue1.status = IssueStatus.where(:is_closed => true).first
900 900 assert issue1.save
901 901 # 2 and 3 should be also closed
902 902 assert issue2.reload.closed?
903 903 assert issue3.reload.closed?
904 904 end
905 905
906 906 def test_should_not_close_duplicated_issue
907 907 issue1 = Issue.generate!
908 908 issue2 = Issue.generate!
909 909
910 910 # 2 is a dupe of 1
911 911 IssueRelation.create(:issue_from => issue2, :issue_to => issue1,
912 912 :relation_type => IssueRelation::TYPE_DUPLICATES)
913 913 # 2 is a dup of 1 but 1 is not a duplicate of 2
914 914 assert !issue2.reload.duplicates.include?(issue1)
915 915
916 916 # Closing issue 2
917 917 issue2.init_journal(User.first, "Closing issue2")
918 918 issue2.status = IssueStatus.where(:is_closed => true).first
919 919 assert issue2.save
920 920 # 1 should not be also closed
921 921 assert !issue1.reload.closed?
922 922 end
923 923
924 924 def test_assignable_versions
925 925 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
926 926 :status_id => 1, :fixed_version_id => 1,
927 927 :subject => 'New issue')
928 928 assert_equal ['open'], issue.assignable_versions.collect(&:status).uniq
929 929 end
930 930
931 931 def test_should_not_be_able_to_assign_a_new_issue_to_a_closed_version
932 932 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
933 933 :status_id => 1, :fixed_version_id => 1,
934 934 :subject => 'New issue')
935 935 assert !issue.save
936 936 assert_not_nil issue.errors[:fixed_version_id]
937 937 end
938 938
939 939 def test_should_not_be_able_to_assign_a_new_issue_to_a_locked_version
940 940 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
941 941 :status_id => 1, :fixed_version_id => 2,
942 942 :subject => 'New issue')
943 943 assert !issue.save
944 944 assert_not_nil issue.errors[:fixed_version_id]
945 945 end
946 946
947 947 def test_should_be_able_to_assign_a_new_issue_to_an_open_version
948 948 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
949 949 :status_id => 1, :fixed_version_id => 3,
950 950 :subject => 'New issue')
951 951 assert issue.save
952 952 end
953 953
954 954 def test_should_be_able_to_update_an_issue_assigned_to_a_closed_version
955 955 issue = Issue.find(11)
956 956 assert_equal 'closed', issue.fixed_version.status
957 957 issue.subject = 'Subject changed'
958 958 assert issue.save
959 959 end
960 960
961 961 def test_should_not_be_able_to_reopen_an_issue_assigned_to_a_closed_version
962 962 issue = Issue.find(11)
963 963 issue.status_id = 1
964 964 assert !issue.save
965 965 assert_not_nil issue.errors[:base]
966 966 end
967 967
968 968 def test_should_be_able_to_reopen_and_reassign_an_issue_assigned_to_a_closed_version
969 969 issue = Issue.find(11)
970 970 issue.status_id = 1
971 971 issue.fixed_version_id = 3
972 972 assert issue.save
973 973 end
974 974
975 975 def test_should_be_able_to_reopen_an_issue_assigned_to_a_locked_version
976 976 issue = Issue.find(12)
977 977 assert_equal 'locked', issue.fixed_version.status
978 978 issue.status_id = 1
979 979 assert issue.save
980 980 end
981 981
982 982 def test_should_not_be_able_to_keep_unshared_version_when_changing_project
983 983 issue = Issue.find(2)
984 984 assert_equal 2, issue.fixed_version_id
985 985 issue.project_id = 3
986 986 assert_nil issue.fixed_version_id
987 987 issue.fixed_version_id = 2
988 988 assert !issue.save
989 989 assert_include 'Target version is not included in the list', issue.errors.full_messages
990 990 end
991 991
992 992 def test_should_keep_shared_version_when_changing_project
993 993 Version.find(2).update_attribute :sharing, 'tree'
994 994
995 995 issue = Issue.find(2)
996 996 assert_equal 2, issue.fixed_version_id
997 997 issue.project_id = 3
998 998 assert_equal 2, issue.fixed_version_id
999 999 assert issue.save
1000 1000 end
1001 1001
1002 1002 def test_allowed_target_projects_on_move_should_include_projects_with_issue_tracking_enabled
1003 1003 assert_include Project.find(2), Issue.allowed_target_projects_on_move(User.find(2))
1004 1004 end
1005 1005
1006 1006 def test_allowed_target_projects_on_move_should_not_include_projects_with_issue_tracking_disabled
1007 1007 Project.find(2).disable_module! :issue_tracking
1008 1008 assert_not_include Project.find(2), Issue.allowed_target_projects_on_move(User.find(2))
1009 1009 end
1010 1010
1011 1011 def test_move_to_another_project_with_same_category
1012 1012 issue = Issue.find(1)
1013 1013 issue.project = Project.find(2)
1014 1014 assert issue.save
1015 1015 issue.reload
1016 1016 assert_equal 2, issue.project_id
1017 1017 # Category changes
1018 1018 assert_equal 4, issue.category_id
1019 1019 # Make sure time entries were move to the target project
1020 1020 assert_equal 2, issue.time_entries.first.project_id
1021 1021 end
1022 1022
1023 1023 def test_move_to_another_project_without_same_category
1024 1024 issue = Issue.find(2)
1025 1025 issue.project = Project.find(2)
1026 1026 assert issue.save
1027 1027 issue.reload
1028 1028 assert_equal 2, issue.project_id
1029 1029 # Category cleared
1030 1030 assert_nil issue.category_id
1031 1031 end
1032 1032
1033 1033 def test_move_to_another_project_should_clear_fixed_version_when_not_shared
1034 1034 issue = Issue.find(1)
1035 1035 issue.update_attribute(:fixed_version_id, 1)
1036 1036 issue.project = Project.find(2)
1037 1037 assert issue.save
1038 1038 issue.reload
1039 1039 assert_equal 2, issue.project_id
1040 1040 # Cleared fixed_version
1041 1041 assert_equal nil, issue.fixed_version
1042 1042 end
1043 1043
1044 1044 def test_move_to_another_project_should_keep_fixed_version_when_shared_with_the_target_project
1045 1045 issue = Issue.find(1)
1046 1046 issue.update_attribute(:fixed_version_id, 4)
1047 1047 issue.project = Project.find(5)
1048 1048 assert issue.save
1049 1049 issue.reload
1050 1050 assert_equal 5, issue.project_id
1051 1051 # Keep fixed_version
1052 1052 assert_equal 4, issue.fixed_version_id
1053 1053 end
1054 1054
1055 1055 def test_move_to_another_project_should_clear_fixed_version_when_not_shared_with_the_target_project
1056 1056 issue = Issue.find(1)
1057 1057 issue.update_attribute(:fixed_version_id, 1)
1058 1058 issue.project = Project.find(5)
1059 1059 assert issue.save
1060 1060 issue.reload
1061 1061 assert_equal 5, issue.project_id
1062 1062 # Cleared fixed_version
1063 1063 assert_equal nil, issue.fixed_version
1064 1064 end
1065 1065
1066 1066 def test_move_to_another_project_should_keep_fixed_version_when_shared_systemwide
1067 1067 issue = Issue.find(1)
1068 1068 issue.update_attribute(:fixed_version_id, 7)
1069 1069 issue.project = Project.find(2)
1070 1070 assert issue.save
1071 1071 issue.reload
1072 1072 assert_equal 2, issue.project_id
1073 1073 # Keep fixed_version
1074 1074 assert_equal 7, issue.fixed_version_id
1075 1075 end
1076 1076
1077 1077 def test_move_to_another_project_should_keep_parent_if_valid
1078 1078 issue = Issue.find(1)
1079 1079 issue.update_attribute(:parent_issue_id, 2)
1080 1080 issue.project = Project.find(3)
1081 1081 assert issue.save
1082 1082 issue.reload
1083 1083 assert_equal 2, issue.parent_id
1084 1084 end
1085 1085
1086 1086 def test_move_to_another_project_should_clear_parent_if_not_valid
1087 1087 issue = Issue.find(1)
1088 1088 issue.update_attribute(:parent_issue_id, 2)
1089 1089 issue.project = Project.find(2)
1090 1090 assert issue.save
1091 1091 issue.reload
1092 1092 assert_nil issue.parent_id
1093 1093 end
1094 1094
1095 1095 def test_move_to_another_project_with_disabled_tracker
1096 1096 issue = Issue.find(1)
1097 1097 target = Project.find(2)
1098 1098 target.tracker_ids = [3]
1099 1099 target.save
1100 1100 issue.project = target
1101 1101 assert issue.save
1102 1102 issue.reload
1103 1103 assert_equal 2, issue.project_id
1104 1104 assert_equal 3, issue.tracker_id
1105 1105 end
1106 1106
1107 1107 def test_copy_to_the_same_project
1108 1108 issue = Issue.find(1)
1109 1109 copy = issue.copy
1110 1110 assert_difference 'Issue.count' do
1111 1111 copy.save!
1112 1112 end
1113 1113 assert_kind_of Issue, copy
1114 1114 assert_equal issue.project, copy.project
1115 1115 assert_equal "125", copy.custom_value_for(2).value
1116 1116 end
1117 1117
1118 1118 def test_copy_to_another_project_and_tracker
1119 1119 issue = Issue.find(1)
1120 1120 copy = issue.copy(:project_id => 3, :tracker_id => 2)
1121 1121 assert_difference 'Issue.count' do
1122 1122 copy.save!
1123 1123 end
1124 1124 copy.reload
1125 1125 assert_kind_of Issue, copy
1126 1126 assert_equal Project.find(3), copy.project
1127 1127 assert_equal Tracker.find(2), copy.tracker
1128 1128 # Custom field #2 is not associated with target tracker
1129 1129 assert_nil copy.custom_value_for(2)
1130 1130 end
1131 1131
1132 context "#copy" do
1133 setup do
1134 @issue = Issue.find(1)
1135 end
1136
1137 should "not create a journal" do
1138 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3)
1139 copy.save!
1140 assert_equal 0, copy.reload.journals.size
1141 end
1132 test "#copy should not create a journal" do
1133 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3)
1134 copy.save!
1135 assert_equal 0, copy.reload.journals.size
1136 end
1142 1137
1143 should "allow assigned_to changes" do
1144 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3)
1145 assert_equal 3, copy.assigned_to_id
1146 end
1138 test "#copy should allow assigned_to changes" do
1139 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3)
1140 assert_equal 3, copy.assigned_to_id
1141 end
1147 1142
1148 should "allow status changes" do
1149 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :status_id => 2)
1150 assert_equal 2, copy.status_id
1151 end
1143 test "#copy should allow status changes" do
1144 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :status_id => 2)
1145 assert_equal 2, copy.status_id
1146 end
1152 1147
1153 should "allow start date changes" do
1154 date = Date.today
1155 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :start_date => date)
1156 assert_equal date, copy.start_date
1157 end
1148 test "#copy should allow start date changes" do
1149 date = Date.today
1150 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :start_date => date)
1151 assert_equal date, copy.start_date
1152 end
1158 1153
1159 should "allow due date changes" do
1160 date = Date.today
1161 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :due_date => date)
1162 assert_equal date, copy.due_date
1163 end
1154 test "#copy should allow due date changes" do
1155 date = Date.today
1156 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :due_date => date)
1157 assert_equal date, copy.due_date
1158 end
1164 1159
1165 should "set current user as author" do
1166 User.current = User.find(9)
1167 copy = @issue.copy(:project_id => 3, :tracker_id => 2)
1168 assert_equal User.current, copy.author
1169 end
1160 test "#copy should set current user as author" do
1161 User.current = User.find(9)
1162 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2)
1163 assert_equal User.current, copy.author
1164 end
1170 1165
1171 should "create a journal with notes" do
1172 date = Date.today
1173 notes = "Notes added when copying"
1174 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :start_date => date)
1175 copy.init_journal(User.current, notes)
1176 copy.save!
1166 test "#copy should create a journal with notes" do
1167 date = Date.today
1168 notes = "Notes added when copying"
1169 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :start_date => date)
1170 copy.init_journal(User.current, notes)
1171 copy.save!
1177 1172
1178 assert_equal 1, copy.journals.size
1179 journal = copy.journals.first
1180 assert_equal 0, journal.details.size
1181 assert_equal notes, journal.notes
1182 end
1173 assert_equal 1, copy.journals.size
1174 journal = copy.journals.first
1175 assert_equal 0, journal.details.size
1176 assert_equal notes, journal.notes
1183 1177 end
1184 1178
1185 1179 def test_valid_parent_project
1186 1180 issue = Issue.find(1)
1187 1181 issue_in_same_project = Issue.find(2)
1188 1182 issue_in_child_project = Issue.find(5)
1189 1183 issue_in_grandchild_project = Issue.generate!(:project_id => 6, :tracker_id => 1)
1190 1184 issue_in_other_child_project = Issue.find(6)
1191 1185 issue_in_different_tree = Issue.find(4)
1192 1186
1193 1187 with_settings :cross_project_subtasks => '' do
1194 1188 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1195 1189 assert_equal false, issue.valid_parent_project?(issue_in_child_project)
1196 1190 assert_equal false, issue.valid_parent_project?(issue_in_grandchild_project)
1197 1191 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1198 1192 end
1199 1193
1200 1194 with_settings :cross_project_subtasks => 'system' do
1201 1195 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1202 1196 assert_equal true, issue.valid_parent_project?(issue_in_child_project)
1203 1197 assert_equal true, issue.valid_parent_project?(issue_in_different_tree)
1204 1198 end
1205 1199
1206 1200 with_settings :cross_project_subtasks => 'tree' do
1207 1201 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1208 1202 assert_equal true, issue.valid_parent_project?(issue_in_child_project)
1209 1203 assert_equal true, issue.valid_parent_project?(issue_in_grandchild_project)
1210 1204 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1211 1205
1212 1206 assert_equal true, issue_in_child_project.valid_parent_project?(issue_in_same_project)
1213 1207 assert_equal true, issue_in_child_project.valid_parent_project?(issue_in_other_child_project)
1214 1208 end
1215 1209
1216 1210 with_settings :cross_project_subtasks => 'descendants' do
1217 1211 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1218 1212 assert_equal false, issue.valid_parent_project?(issue_in_child_project)
1219 1213 assert_equal false, issue.valid_parent_project?(issue_in_grandchild_project)
1220 1214 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1221 1215
1222 1216 assert_equal true, issue_in_child_project.valid_parent_project?(issue)
1223 1217 assert_equal false, issue_in_child_project.valid_parent_project?(issue_in_other_child_project)
1224 1218 end
1225 1219 end
1226 1220
1227 1221 def test_recipients_should_include_previous_assignee
1228 1222 user = User.find(3)
1229 1223 user.members.update_all ["mail_notification = ?", false]
1230 1224 user.update_attribute :mail_notification, 'only_assigned'
1231 1225
1232 1226 issue = Issue.find(2)
1233 1227 issue.assigned_to = nil
1234 1228 assert_include user.mail, issue.recipients
1235 1229 issue.save!
1236 1230 assert !issue.recipients.include?(user.mail)
1237 1231 end
1238 1232
1239 1233 def test_recipients_should_not_include_users_that_cannot_view_the_issue
1240 1234 issue = Issue.find(12)
1241 1235 assert issue.recipients.include?(issue.author.mail)
1242 1236 # copy the issue to a private project
1243 1237 copy = issue.copy(:project_id => 5, :tracker_id => 2)
1244 1238 # author is not a member of project anymore
1245 1239 assert !copy.recipients.include?(copy.author.mail)
1246 1240 end
1247 1241
1248 1242 def test_recipients_should_include_the_assigned_group_members
1249 1243 group_member = User.generate!
1250 1244 group = Group.generate!
1251 1245 group.users << group_member
1252 1246
1253 1247 issue = Issue.find(12)
1254 1248 issue.assigned_to = group
1255 1249 assert issue.recipients.include?(group_member.mail)
1256 1250 end
1257 1251
1258 1252 def test_watcher_recipients_should_not_include_users_that_cannot_view_the_issue
1259 1253 user = User.find(3)
1260 1254 issue = Issue.find(9)
1261 1255 Watcher.create!(:user => user, :watchable => issue)
1262 1256 assert issue.watched_by?(user)
1263 1257 assert !issue.watcher_recipients.include?(user.mail)
1264 1258 end
1265 1259
1266 1260 def test_issue_destroy
1267 1261 Issue.find(1).destroy
1268 1262 assert_nil Issue.find_by_id(1)
1269 1263 assert_nil TimeEntry.find_by_issue_id(1)
1270 1264 end
1271 1265
1272 1266 def test_destroying_a_deleted_issue_should_not_raise_an_error
1273 1267 issue = Issue.find(1)
1274 1268 Issue.find(1).destroy
1275 1269
1276 1270 assert_nothing_raised do
1277 1271 assert_no_difference 'Issue.count' do
1278 1272 issue.destroy
1279 1273 end
1280 1274 assert issue.destroyed?
1281 1275 end
1282 1276 end
1283 1277
1284 1278 def test_destroying_a_stale_issue_should_not_raise_an_error
1285 1279 issue = Issue.find(1)
1286 1280 Issue.find(1).update_attribute :subject, "Updated"
1287 1281
1288 1282 assert_nothing_raised do
1289 1283 assert_difference 'Issue.count', -1 do
1290 1284 issue.destroy
1291 1285 end
1292 1286 assert issue.destroyed?
1293 1287 end
1294 1288 end
1295 1289
1296 1290 def test_blocked
1297 1291 blocked_issue = Issue.find(9)
1298 1292 blocking_issue = Issue.find(10)
1299 1293
1300 1294 assert blocked_issue.blocked?
1301 1295 assert !blocking_issue.blocked?
1302 1296 end
1303 1297
1304 1298 def test_blocked_issues_dont_allow_closed_statuses
1305 1299 blocked_issue = Issue.find(9)
1306 1300
1307 1301 allowed_statuses = blocked_issue.new_statuses_allowed_to(users(:users_002))
1308 1302 assert !allowed_statuses.empty?
1309 1303 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
1310 1304 assert closed_statuses.empty?
1311 1305 end
1312 1306
1313 1307 def test_unblocked_issues_allow_closed_statuses
1314 1308 blocking_issue = Issue.find(10)
1315 1309
1316 1310 allowed_statuses = blocking_issue.new_statuses_allowed_to(users(:users_002))
1317 1311 assert !allowed_statuses.empty?
1318 1312 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
1319 1313 assert !closed_statuses.empty?
1320 1314 end
1321 1315
1322 1316 def test_reschedule_an_issue_without_dates
1323 1317 with_settings :non_working_week_days => [] do
1324 1318 issue = Issue.new(:start_date => nil, :due_date => nil)
1325 1319 issue.reschedule_on '2012-10-09'.to_date
1326 1320 assert_equal '2012-10-09'.to_date, issue.start_date
1327 1321 assert_equal '2012-10-09'.to_date, issue.due_date
1328 1322 end
1329 1323
1330 1324 with_settings :non_working_week_days => %w(6 7) do
1331 1325 issue = Issue.new(:start_date => nil, :due_date => nil)
1332 1326 issue.reschedule_on '2012-10-09'.to_date
1333 1327 assert_equal '2012-10-09'.to_date, issue.start_date
1334 1328 assert_equal '2012-10-09'.to_date, issue.due_date
1335 1329
1336 1330 issue = Issue.new(:start_date => nil, :due_date => nil)
1337 1331 issue.reschedule_on '2012-10-13'.to_date
1338 1332 assert_equal '2012-10-15'.to_date, issue.start_date
1339 1333 assert_equal '2012-10-15'.to_date, issue.due_date
1340 1334 end
1341 1335 end
1342 1336
1343 1337 def test_reschedule_an_issue_with_start_date
1344 1338 with_settings :non_working_week_days => [] do
1345 1339 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1346 1340 issue.reschedule_on '2012-10-13'.to_date
1347 1341 assert_equal '2012-10-13'.to_date, issue.start_date
1348 1342 assert_equal '2012-10-13'.to_date, issue.due_date
1349 1343 end
1350 1344
1351 1345 with_settings :non_working_week_days => %w(6 7) do
1352 1346 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1353 1347 issue.reschedule_on '2012-10-11'.to_date
1354 1348 assert_equal '2012-10-11'.to_date, issue.start_date
1355 1349 assert_equal '2012-10-11'.to_date, issue.due_date
1356 1350
1357 1351 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1358 1352 issue.reschedule_on '2012-10-13'.to_date
1359 1353 assert_equal '2012-10-15'.to_date, issue.start_date
1360 1354 assert_equal '2012-10-15'.to_date, issue.due_date
1361 1355 end
1362 1356 end
1363 1357
1364 1358 def test_reschedule_an_issue_with_start_and_due_dates
1365 1359 with_settings :non_working_week_days => [] do
1366 1360 issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-15')
1367 1361 issue.reschedule_on '2012-10-13'.to_date
1368 1362 assert_equal '2012-10-13'.to_date, issue.start_date
1369 1363 assert_equal '2012-10-19'.to_date, issue.due_date
1370 1364 end
1371 1365
1372 1366 with_settings :non_working_week_days => %w(6 7) do
1373 1367 issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-19') # 8 working days
1374 1368 issue.reschedule_on '2012-10-11'.to_date
1375 1369 assert_equal '2012-10-11'.to_date, issue.start_date
1376 1370 assert_equal '2012-10-23'.to_date, issue.due_date
1377 1371
1378 1372 issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-19')
1379 1373 issue.reschedule_on '2012-10-13'.to_date
1380 1374 assert_equal '2012-10-15'.to_date, issue.start_date
1381 1375 assert_equal '2012-10-25'.to_date, issue.due_date
1382 1376 end
1383 1377 end
1384 1378
1385 1379 def test_rescheduling_an_issue_to_a_later_due_date_should_reschedule_following_issue
1386 1380 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1387 1381 issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1388 1382 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1389 1383 :relation_type => IssueRelation::TYPE_PRECEDES)
1390 1384 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
1391 1385
1392 1386 issue1.due_date = '2012-10-23'
1393 1387 issue1.save!
1394 1388 issue2.reload
1395 1389 assert_equal Date.parse('2012-10-24'), issue2.start_date
1396 1390 assert_equal Date.parse('2012-10-26'), issue2.due_date
1397 1391 end
1398 1392
1399 1393 def test_rescheduling_an_issue_to_an_earlier_due_date_should_reschedule_following_issue
1400 1394 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1401 1395 issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1402 1396 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1403 1397 :relation_type => IssueRelation::TYPE_PRECEDES)
1404 1398 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
1405 1399
1406 1400 issue1.start_date = '2012-09-17'
1407 1401 issue1.due_date = '2012-09-18'
1408 1402 issue1.save!
1409 1403 issue2.reload
1410 1404 assert_equal Date.parse('2012-09-19'), issue2.start_date
1411 1405 assert_equal Date.parse('2012-09-21'), issue2.due_date
1412 1406 end
1413 1407
1414 1408 def test_rescheduling_reschedule_following_issue_earlier_should_consider_other_preceding_issues
1415 1409 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1416 1410 issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1417 1411 issue3 = Issue.generate!(:start_date => '2012-10-01', :due_date => '2012-10-02')
1418 1412 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1419 1413 :relation_type => IssueRelation::TYPE_PRECEDES)
1420 1414 IssueRelation.create!(:issue_from => issue3, :issue_to => issue2,
1421 1415 :relation_type => IssueRelation::TYPE_PRECEDES)
1422 1416 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
1423 1417
1424 1418 issue1.start_date = '2012-09-17'
1425 1419 issue1.due_date = '2012-09-18'
1426 1420 issue1.save!
1427 1421 issue2.reload
1428 1422 # Issue 2 must start after Issue 3
1429 1423 assert_equal Date.parse('2012-10-03'), issue2.start_date
1430 1424 assert_equal Date.parse('2012-10-05'), issue2.due_date
1431 1425 end
1432 1426
1433 1427 def test_rescheduling_a_stale_issue_should_not_raise_an_error
1434 1428 with_settings :non_working_week_days => [] do
1435 1429 stale = Issue.find(1)
1436 1430 issue = Issue.find(1)
1437 1431 issue.subject = "Updated"
1438 1432 issue.save!
1439 1433 date = 10.days.from_now.to_date
1440 1434 assert_nothing_raised do
1441 1435 stale.reschedule_on!(date)
1442 1436 end
1443 1437 assert_equal date, stale.reload.start_date
1444 1438 end
1445 1439 end
1446 1440
1447 1441 def test_overdue
1448 1442 assert Issue.new(:due_date => 1.day.ago.to_date).overdue?
1449 1443 assert !Issue.new(:due_date => Date.today).overdue?
1450 1444 assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue?
1451 1445 assert !Issue.new(:due_date => nil).overdue?
1452 1446 assert !Issue.new(:due_date => 1.day.ago.to_date,
1453 1447 :status => IssueStatus.where(:is_closed => true).first
1454 1448 ).overdue?
1455 1449 end
1456 1450
1457 context "#behind_schedule?" do
1458 should "be false if the issue has no start_date" do
1459 assert !Issue.new(:start_date => nil,
1460 :due_date => 1.day.from_now.to_date,
1461 :done_ratio => 0).behind_schedule?
1462 end
1451 test "#behind_schedule? should be false if the issue has no start_date" do
1452 assert !Issue.new(:start_date => nil,
1453 :due_date => 1.day.from_now.to_date,
1454 :done_ratio => 0).behind_schedule?
1455 end
1463 1456
1464 should "be false if the issue has no end_date" do
1465 assert !Issue.new(:start_date => 1.day.from_now.to_date,
1466 :due_date => nil,
1467 :done_ratio => 0).behind_schedule?
1468 end
1457 test "#behind_schedule? should be false if the issue has no end_date" do
1458 assert !Issue.new(:start_date => 1.day.from_now.to_date,
1459 :due_date => nil,
1460 :done_ratio => 0).behind_schedule?
1461 end
1469 1462
1470 should "be false if the issue has more done than it's calendar time" do
1471 assert !Issue.new(:start_date => 50.days.ago.to_date,
1472 :due_date => 50.days.from_now.to_date,
1473 :done_ratio => 90).behind_schedule?
1474 end
1463 test "#behind_schedule? should be false if the issue has more done than it's calendar time" do
1464 assert !Issue.new(:start_date => 50.days.ago.to_date,
1465 :due_date => 50.days.from_now.to_date,
1466 :done_ratio => 90).behind_schedule?
1467 end
1475 1468
1476 should "be true if the issue hasn't been started at all" do
1477 assert Issue.new(:start_date => 1.day.ago.to_date,
1478 :due_date => 1.day.from_now.to_date,
1479 :done_ratio => 0).behind_schedule?
1480 end
1469 test "#behind_schedule? should be true if the issue hasn't been started at all" do
1470 assert Issue.new(:start_date => 1.day.ago.to_date,
1471 :due_date => 1.day.from_now.to_date,
1472 :done_ratio => 0).behind_schedule?
1473 end
1481 1474
1482 should "be true if the issue has used more calendar time than it's done ratio" do
1483 assert Issue.new(:start_date => 100.days.ago.to_date,
1484 :due_date => Date.today,
1485 :done_ratio => 90).behind_schedule?
1486 end
1475 test "#behind_schedule? should be true if the issue has used more calendar time than it's done ratio" do
1476 assert Issue.new(:start_date => 100.days.ago.to_date,
1477 :due_date => Date.today,
1478 :done_ratio => 90).behind_schedule?
1487 1479 end
1488 1480
1489 context "#assignable_users" do
1490 should "be Users" do
1491 assert_kind_of User, Issue.find(1).assignable_users.first
1492 end
1481 test "#assignable_users should be Users" do
1482 assert_kind_of User, Issue.find(1).assignable_users.first
1483 end
1493 1484
1494 should "include the issue author" do
1495 non_project_member = User.generate!
1496 issue = Issue.generate!(:author => non_project_member)
1485 test "#assignable_users should include the issue author" do
1486 non_project_member = User.generate!
1487 issue = Issue.generate!(:author => non_project_member)
1497 1488
1498 assert issue.assignable_users.include?(non_project_member)
1499 end
1489 assert issue.assignable_users.include?(non_project_member)
1490 end
1500 1491
1501 should "include the current assignee" do
1502 user = User.generate!
1503 issue = Issue.generate!(:assigned_to => user)
1504 user.lock!
1492 test "#assignable_users should include the current assignee" do
1493 user = User.generate!
1494 issue = Issue.generate!(:assigned_to => user)
1495 user.lock!
1505 1496
1506 assert Issue.find(issue.id).assignable_users.include?(user)
1507 end
1497 assert Issue.find(issue.id).assignable_users.include?(user)
1498 end
1508 1499
1509 should "not show the issue author twice" do
1510 assignable_user_ids = Issue.find(1).assignable_users.collect(&:id)
1511 assert_equal 2, assignable_user_ids.length
1500 test "#assignable_users should not show the issue author twice" do
1501 assignable_user_ids = Issue.find(1).assignable_users.collect(&:id)
1502 assert_equal 2, assignable_user_ids.length
1512 1503
1513 assignable_user_ids.each do |user_id|
1514 assert_equal 1, assignable_user_ids.select {|i| i == user_id}.length,
1515 "User #{user_id} appears more or less than once"
1516 end
1504 assignable_user_ids.each do |user_id|
1505 assert_equal 1, assignable_user_ids.select {|i| i == user_id}.length,
1506 "User #{user_id} appears more or less than once"
1517 1507 end
1508 end
1518 1509
1519 context "with issue_group_assignment" do
1520 should "include groups" do
1521 issue = Issue.new(:project => Project.find(2))
1510 test "#assignable_users with issue_group_assignment should include groups" do
1511 issue = Issue.new(:project => Project.find(2))
1522 1512
1523 with_settings :issue_group_assignment => '1' do
1524 assert_equal %w(Group User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
1525 assert issue.assignable_users.include?(Group.find(11))
1526 end
1527 end
1513 with_settings :issue_group_assignment => '1' do
1514 assert_equal %w(Group User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
1515 assert issue.assignable_users.include?(Group.find(11))
1528 1516 end
1517 end
1529 1518
1530 context "without issue_group_assignment" do
1531 should "not include groups" do
1532 issue = Issue.new(:project => Project.find(2))
1519 test "#assignable_users without issue_group_assignment should not include groups" do
1520 issue = Issue.new(:project => Project.find(2))
1533 1521
1534 with_settings :issue_group_assignment => '0' do
1535 assert_equal %w(User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
1536 assert !issue.assignable_users.include?(Group.find(11))
1537 end
1538 end
1522 with_settings :issue_group_assignment => '0' do
1523 assert_equal %w(User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
1524 assert !issue.assignable_users.include?(Group.find(11))
1539 1525 end
1540 1526 end
1541 1527
1542 1528 def test_create_should_send_email_notification
1543 1529 ActionMailer::Base.deliveries.clear
1544 1530 issue = Issue.new(:project_id => 1, :tracker_id => 1,
1545 1531 :author_id => 3, :status_id => 1,
1546 1532 :priority => IssuePriority.all.first,
1547 1533 :subject => 'test_create', :estimated_hours => '1:30')
1548 1534
1549 1535 assert issue.save
1550 1536 assert_equal 1, ActionMailer::Base.deliveries.size
1551 1537 end
1552 1538
1553 1539 def test_stale_issue_should_not_send_email_notification
1554 1540 ActionMailer::Base.deliveries.clear
1555 1541 issue = Issue.find(1)
1556 1542 stale = Issue.find(1)
1557 1543
1558 1544 issue.init_journal(User.find(1))
1559 1545 issue.subject = 'Subjet update'
1560 1546 assert issue.save
1561 1547 assert_equal 1, ActionMailer::Base.deliveries.size
1562 1548 ActionMailer::Base.deliveries.clear
1563 1549
1564 1550 stale.init_journal(User.find(1))
1565 1551 stale.subject = 'Another subjet update'
1566 1552 assert_raise ActiveRecord::StaleObjectError do
1567 1553 stale.save
1568 1554 end
1569 1555 assert ActionMailer::Base.deliveries.empty?
1570 1556 end
1571 1557
1572 1558 def test_journalized_description
1573 1559 IssueCustomField.delete_all
1574 1560
1575 1561 i = Issue.first
1576 1562 old_description = i.description
1577 1563 new_description = "This is the new description"
1578 1564
1579 1565 i.init_journal(User.find(2))
1580 1566 i.description = new_description
1581 1567 assert_difference 'Journal.count', 1 do
1582 1568 assert_difference 'JournalDetail.count', 1 do
1583 1569 i.save!
1584 1570 end
1585 1571 end
1586 1572
1587 1573 detail = JournalDetail.first(:order => 'id DESC')
1588 1574 assert_equal i, detail.journal.journalized
1589 1575 assert_equal 'attr', detail.property
1590 1576 assert_equal 'description', detail.prop_key
1591 1577 assert_equal old_description, detail.old_value
1592 1578 assert_equal new_description, detail.value
1593 1579 end
1594 1580
1595 1581 def test_blank_descriptions_should_not_be_journalized
1596 1582 IssueCustomField.delete_all
1597 1583 Issue.update_all("description = NULL", "id=1")
1598 1584
1599 1585 i = Issue.find(1)
1600 1586 i.init_journal(User.find(2))
1601 1587 i.subject = "blank description"
1602 1588 i.description = "\r\n"
1603 1589
1604 1590 assert_difference 'Journal.count', 1 do
1605 1591 assert_difference 'JournalDetail.count', 1 do
1606 1592 i.save!
1607 1593 end
1608 1594 end
1609 1595 end
1610 1596
1611 1597 def test_journalized_multi_custom_field
1612 1598 field = IssueCustomField.create!(:name => 'filter', :field_format => 'list',
1613 1599 :is_filter => true, :is_for_all => true,
1614 1600 :tracker_ids => [1],
1615 1601 :possible_values => ['value1', 'value2', 'value3'],
1616 1602 :multiple => true)
1617 1603
1618 1604 issue = Issue.create!(:project_id => 1, :tracker_id => 1,
1619 1605 :subject => 'Test', :author_id => 1)
1620 1606
1621 1607 assert_difference 'Journal.count' do
1622 1608 assert_difference 'JournalDetail.count' do
1623 1609 issue.init_journal(User.first)
1624 1610 issue.custom_field_values = {field.id => ['value1']}
1625 1611 issue.save!
1626 1612 end
1627 1613 assert_difference 'JournalDetail.count' do
1628 1614 issue.init_journal(User.first)
1629 1615 issue.custom_field_values = {field.id => ['value1', 'value2']}
1630 1616 issue.save!
1631 1617 end
1632 1618 assert_difference 'JournalDetail.count', 2 do
1633 1619 issue.init_journal(User.first)
1634 1620 issue.custom_field_values = {field.id => ['value3', 'value2']}
1635 1621 issue.save!
1636 1622 end
1637 1623 assert_difference 'JournalDetail.count', 2 do
1638 1624 issue.init_journal(User.first)
1639 1625 issue.custom_field_values = {field.id => nil}
1640 1626 issue.save!
1641 1627 end
1642 1628 end
1643 1629 end
1644 1630
1645 1631 def test_description_eol_should_be_normalized
1646 1632 i = Issue.new(:description => "CR \r LF \n CRLF \r\n")
1647 1633 assert_equal "CR \r\n LF \r\n CRLF \r\n", i.description
1648 1634 end
1649 1635
1650 1636 def test_saving_twice_should_not_duplicate_journal_details
1651 1637 i = Issue.first
1652 1638 i.init_journal(User.find(2), 'Some notes')
1653 1639 # initial changes
1654 1640 i.subject = 'New subject'
1655 1641 i.done_ratio = i.done_ratio + 10
1656 1642 assert_difference 'Journal.count' do
1657 1643 assert i.save
1658 1644 end
1659 1645 # 1 more change
1660 1646 i.priority = IssuePriority.where("id <> ?", i.priority_id).first
1661 1647 assert_no_difference 'Journal.count' do
1662 1648 assert_difference 'JournalDetail.count', 1 do
1663 1649 i.save
1664 1650 end
1665 1651 end
1666 1652 # no more change
1667 1653 assert_no_difference 'Journal.count' do
1668 1654 assert_no_difference 'JournalDetail.count' do
1669 1655 i.save
1670 1656 end
1671 1657 end
1672 1658 end
1673 1659
1674 1660 def test_all_dependent_issues
1675 1661 IssueRelation.delete_all
1676 1662 assert IssueRelation.create!(:issue_from => Issue.find(1),
1677 1663 :issue_to => Issue.find(2),
1678 1664 :relation_type => IssueRelation::TYPE_PRECEDES)
1679 1665 assert IssueRelation.create!(:issue_from => Issue.find(2),
1680 1666 :issue_to => Issue.find(3),
1681 1667 :relation_type => IssueRelation::TYPE_PRECEDES)
1682 1668 assert IssueRelation.create!(:issue_from => Issue.find(3),
1683 1669 :issue_to => Issue.find(8),
1684 1670 :relation_type => IssueRelation::TYPE_PRECEDES)
1685 1671
1686 1672 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
1687 1673 end
1688 1674
1689 1675 def test_all_dependent_issues_with_persistent_circular_dependency
1690 1676 IssueRelation.delete_all
1691 1677 assert IssueRelation.create!(:issue_from => Issue.find(1),
1692 1678 :issue_to => Issue.find(2),
1693 1679 :relation_type => IssueRelation::TYPE_PRECEDES)
1694 1680 assert IssueRelation.create!(:issue_from => Issue.find(2),
1695 1681 :issue_to => Issue.find(3),
1696 1682 :relation_type => IssueRelation::TYPE_PRECEDES)
1697 1683
1698 1684 r = IssueRelation.create!(:issue_from => Issue.find(3),
1699 1685 :issue_to => Issue.find(7),
1700 1686 :relation_type => IssueRelation::TYPE_PRECEDES)
1701 1687 IssueRelation.update_all("issue_to_id = 1", ["id = ?", r.id])
1702 1688
1703 1689 assert_equal [2, 3], Issue.find(1).all_dependent_issues.collect(&:id).sort
1704 1690 end
1705 1691
1706 1692 def test_all_dependent_issues_with_persistent_multiple_circular_dependencies
1707 1693 IssueRelation.delete_all
1708 1694 assert IssueRelation.create!(:issue_from => Issue.find(1),
1709 1695 :issue_to => Issue.find(2),
1710 1696 :relation_type => IssueRelation::TYPE_RELATES)
1711 1697 assert IssueRelation.create!(:issue_from => Issue.find(2),
1712 1698 :issue_to => Issue.find(3),
1713 1699 :relation_type => IssueRelation::TYPE_RELATES)
1714 1700 assert IssueRelation.create!(:issue_from => Issue.find(3),
1715 1701 :issue_to => Issue.find(8),
1716 1702 :relation_type => IssueRelation::TYPE_RELATES)
1717 1703
1718 1704 r = IssueRelation.create!(:issue_from => Issue.find(8),
1719 1705 :issue_to => Issue.find(7),
1720 1706 :relation_type => IssueRelation::TYPE_RELATES)
1721 1707 IssueRelation.update_all("issue_to_id = 2", ["id = ?", r.id])
1722 1708
1723 1709 r = IssueRelation.create!(:issue_from => Issue.find(3),
1724 1710 :issue_to => Issue.find(7),
1725 1711 :relation_type => IssueRelation::TYPE_RELATES)
1726 1712 IssueRelation.update_all("issue_to_id = 1", ["id = ?", r.id])
1727 1713
1728 1714 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
1729 1715 end
1730 1716
1731 context "#done_ratio" do
1732 setup do
1733 @issue = Issue.find(1)
1734 @issue_status = IssueStatus.find(1)
1735 @issue_status.update_attribute(:default_done_ratio, 50)
1736 @issue2 = Issue.find(2)
1737 @issue_status2 = IssueStatus.find(2)
1738 @issue_status2.update_attribute(:default_done_ratio, 0)
1739 end
1717 test "#done_ratio should use the issue_status according to Setting.issue_done_ratio" do
1718 @issue = Issue.find(1)
1719 @issue_status = IssueStatus.find(1)
1720 @issue_status.update_attribute(:default_done_ratio, 50)
1721 @issue2 = Issue.find(2)
1722 @issue_status2 = IssueStatus.find(2)
1723 @issue_status2.update_attribute(:default_done_ratio, 0)
1740 1724
1741 teardown do
1742 Setting.issue_done_ratio = 'issue_field'
1725 with_settings :issue_done_ratio => 'issue_field' do
1726 assert_equal 0, @issue.done_ratio
1727 assert_equal 30, @issue2.done_ratio
1743 1728 end
1744 1729
1745 context "with Setting.issue_done_ratio using the issue_field" do
1746 setup do
1747 Setting.issue_done_ratio = 'issue_field'
1748 end
1749
1750 should "read the issue's field" do
1751 assert_equal 0, @issue.done_ratio
1752 assert_equal 30, @issue2.done_ratio
1753 end
1754 end
1755
1756 context "with Setting.issue_done_ratio using the issue_status" do
1757 setup do
1758 Setting.issue_done_ratio = 'issue_status'
1759 end
1760
1761 should "read the Issue Status's default done ratio" do
1762 assert_equal 50, @issue.done_ratio
1763 assert_equal 0, @issue2.done_ratio
1764 end
1730 with_settings :issue_done_ratio => 'issue_status' do
1731 assert_equal 50, @issue.done_ratio
1732 assert_equal 0, @issue2.done_ratio
1765 1733 end
1766 1734 end
1767 1735
1768 context "#update_done_ratio_from_issue_status" do
1769 setup do
1770 @issue = Issue.find(1)
1771 @issue_status = IssueStatus.find(1)
1772 @issue_status.update_attribute(:default_done_ratio, 50)
1773 @issue2 = Issue.find(2)
1774 @issue_status2 = IssueStatus.find(2)
1775 @issue_status2.update_attribute(:default_done_ratio, 0)
1776 end
1777
1778 context "with Setting.issue_done_ratio using the issue_field" do
1779 setup do
1780 Setting.issue_done_ratio = 'issue_field'
1781 end
1736 test "#update_done_ratio_from_issue_status should update done_ratio according to Setting.issue_done_ratio" do
1737 @issue = Issue.find(1)
1738 @issue_status = IssueStatus.find(1)
1739 @issue_status.update_attribute(:default_done_ratio, 50)
1740 @issue2 = Issue.find(2)
1741 @issue_status2 = IssueStatus.find(2)
1742 @issue_status2.update_attribute(:default_done_ratio, 0)
1782 1743
1783 should "not change the issue" do
1784 @issue.update_done_ratio_from_issue_status
1785 @issue2.update_done_ratio_from_issue_status
1744 with_settings :issue_done_ratio => 'issue_field' do
1745 @issue.update_done_ratio_from_issue_status
1746 @issue2.update_done_ratio_from_issue_status
1786 1747
1787 assert_equal 0, @issue.read_attribute(:done_ratio)
1788 assert_equal 30, @issue2.read_attribute(:done_ratio)
1789 end
1748 assert_equal 0, @issue.read_attribute(:done_ratio)
1749 assert_equal 30, @issue2.read_attribute(:done_ratio)
1790 1750 end
1791 1751
1792 context "with Setting.issue_done_ratio using the issue_status" do
1793 setup do
1794 Setting.issue_done_ratio = 'issue_status'
1795 end
1796
1797 should "change the issue's done ratio" do
1798 @issue.update_done_ratio_from_issue_status
1799 @issue2.update_done_ratio_from_issue_status
1752 with_settings :issue_done_ratio => 'issue_status' do
1753 @issue.update_done_ratio_from_issue_status
1754 @issue2.update_done_ratio_from_issue_status
1800 1755
1801 assert_equal 50, @issue.read_attribute(:done_ratio)
1802 assert_equal 0, @issue2.read_attribute(:done_ratio)
1803 end
1756 assert_equal 50, @issue.read_attribute(:done_ratio)
1757 assert_equal 0, @issue2.read_attribute(:done_ratio)
1804 1758 end
1805 1759 end
1806 1760
1807 1761 test "#by_tracker" do
1808 1762 User.current = User.anonymous
1809 1763 groups = Issue.by_tracker(Project.find(1))
1810 1764 assert_equal 3, groups.size
1811 1765 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1812 1766 end
1813 1767
1814 1768 test "#by_version" do
1815 1769 User.current = User.anonymous
1816 1770 groups = Issue.by_version(Project.find(1))
1817 1771 assert_equal 3, groups.size
1818 1772 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1819 1773 end
1820 1774
1821 1775 test "#by_priority" do
1822 1776 User.current = User.anonymous
1823 1777 groups = Issue.by_priority(Project.find(1))
1824 1778 assert_equal 4, groups.size
1825 1779 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1826 1780 end
1827 1781
1828 1782 test "#by_category" do
1829 1783 User.current = User.anonymous
1830 1784 groups = Issue.by_category(Project.find(1))
1831 1785 assert_equal 2, groups.size
1832 1786 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1833 1787 end
1834 1788
1835 1789 test "#by_assigned_to" do
1836 1790 User.current = User.anonymous
1837 1791 groups = Issue.by_assigned_to(Project.find(1))
1838 1792 assert_equal 2, groups.size
1839 1793 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1840 1794 end
1841 1795
1842 1796 test "#by_author" do
1843 1797 User.current = User.anonymous
1844 1798 groups = Issue.by_author(Project.find(1))
1845 1799 assert_equal 4, groups.size
1846 1800 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1847 1801 end
1848 1802
1849 1803 test "#by_subproject" do
1850 1804 User.current = User.anonymous
1851 1805 groups = Issue.by_subproject(Project.find(1))
1852 1806 # Private descendant not visible
1853 1807 assert_equal 1, groups.size
1854 1808 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1855 1809 end
1856 1810
1857 1811 def test_recently_updated_scope
1858 1812 #should return the last updated issue
1859 1813 assert_equal Issue.reorder("updated_on DESC").first, Issue.recently_updated.limit(1).first
1860 1814 end
1861 1815
1862 1816 def test_on_active_projects_scope
1863 1817 assert Project.find(2).archive
1864 1818
1865 1819 before = Issue.on_active_project.length
1866 1820 # test inclusion to results
1867 1821 issue = Issue.generate!(:tracker => Project.find(2).trackers.first)
1868 1822 assert_equal before + 1, Issue.on_active_project.length
1869 1823
1870 1824 # Move to an archived project
1871 1825 issue.project = Project.find(2)
1872 1826 assert issue.save
1873 1827 assert_equal before, Issue.on_active_project.length
1874 1828 end
1875 1829
1876 context "Issue#recipients" do
1877 setup do
1878 @project = Project.find(1)
1879 @author = User.generate!
1880 @assignee = User.generate!
1881 @issue = Issue.generate!(:project => @project, :assigned_to => @assignee, :author => @author)
1882 end
1883
1884 should "include project recipients" do
1885 assert @project.recipients.present?
1886 @project.recipients.each do |project_recipient|
1887 assert @issue.recipients.include?(project_recipient)
1888 end
1889 end
1890
1891 should "include the author if the author is active" do
1892 assert @issue.author, "No author set for Issue"
1893 assert @issue.recipients.include?(@issue.author.mail)
1894 end
1895
1896 should "include the assigned to user if the assigned to user is active" do
1897 assert @issue.assigned_to, "No assigned_to set for Issue"
1898 assert @issue.recipients.include?(@issue.assigned_to.mail)
1830 test "Issue#recipients should include project recipients" do
1831 issue = Issue.generate!
1832 assert issue.project.recipients.present?
1833 issue.project.recipients.each do |project_recipient|
1834 assert issue.recipients.include?(project_recipient)
1899 1835 end
1836 end
1900 1837
1901 should "not include users who opt out of all email" do
1902 @author.update_attribute(:mail_notification, :none)
1903
1904 assert !@issue.recipients.include?(@issue.author.mail)
1905 end
1838 test "Issue#recipients should include the author if the author is active" do
1839 issue = Issue.generate!(:author => User.generate!)
1840 assert issue.author, "No author set for Issue"
1841 assert issue.recipients.include?(issue.author.mail)
1842 end
1906 1843
1907 should "not include the issue author if they are only notified of assigned issues" do
1908 @author.update_attribute(:mail_notification, :only_assigned)
1844 test "Issue#recipients should include the assigned to user if the assigned to user is active" do
1845 issue = Issue.generate!(:assigned_to => User.generate!)
1846 assert issue.assigned_to, "No assigned_to set for Issue"
1847 assert issue.recipients.include?(issue.assigned_to.mail)
1848 end
1909 1849
1910 assert !@issue.recipients.include?(@issue.author.mail)
1911 end
1850 test "Issue#recipients should not include users who opt out of all email" do
1851 issue = Issue.generate!(:author => User.generate!)
1852 issue.author.update_attribute(:mail_notification, :none)
1853 assert !issue.recipients.include?(issue.author.mail)
1854 end
1912 1855
1913 should "not include the assigned user if they are only notified of owned issues" do
1914 @assignee.update_attribute(:mail_notification, :only_owner)
1856 test "Issue#recipients should not include the issue author if they are only notified of assigned issues" do
1857 issue = Issue.generate!(:author => User.generate!)
1858 issue.author.update_attribute(:mail_notification, :only_assigned)
1859 assert !issue.recipients.include?(issue.author.mail)
1860 end
1915 1861
1916 assert !@issue.recipients.include?(@issue.assigned_to.mail)
1917 end
1862 test "Issue#recipients should not include the assigned user if they are only notified of owned issues" do
1863 issue = Issue.generate!(:assigned_to => User.generate!)
1864 issue.assigned_to.update_attribute(:mail_notification, :only_owner)
1865 assert !issue.recipients.include?(issue.assigned_to.mail)
1918 1866 end
1919 1867
1920 1868 def test_last_journal_id_with_journals_should_return_the_journal_id
1921 1869 assert_equal 2, Issue.find(1).last_journal_id
1922 1870 end
1923 1871
1924 1872 def test_last_journal_id_without_journals_should_return_nil
1925 1873 assert_nil Issue.find(3).last_journal_id
1926 1874 end
1927 1875
1928 1876 def test_journals_after_should_return_journals_with_greater_id
1929 1877 assert_equal [Journal.find(2)], Issue.find(1).journals_after('1')
1930 1878 assert_equal [], Issue.find(1).journals_after('2')
1931 1879 end
1932 1880
1933 1881 def test_journals_after_with_blank_arg_should_return_all_journals
1934 1882 assert_equal [Journal.find(1), Journal.find(2)], Issue.find(1).journals_after('')
1935 1883 end
1936 1884
1937 1885 def test_css_classes_should_include_priority
1938 1886 issue = Issue.new(:priority => IssuePriority.find(8))
1939 1887 classes = issue.css_classes.split(' ')
1940 1888 assert_include 'priority-8', classes
1941 1889 assert_include 'priority-highest', classes
1942 1890 end
1943 1891
1944 1892 def test_save_attachments_with_hash_should_save_attachments_in_keys_order
1945 1893 set_tmp_attachments_directory
1946 1894 issue = Issue.generate!
1947 1895 issue.save_attachments({
1948 1896 'p0' => {'file' => mock_file_with_options(:original_filename => 'upload')},
1949 1897 '3' => {'file' => mock_file_with_options(:original_filename => 'bar')},
1950 1898 '1' => {'file' => mock_file_with_options(:original_filename => 'foo')}
1951 1899 })
1952 1900 issue.attach_saved_attachments
1953 1901
1954 1902 assert_equal 3, issue.reload.attachments.count
1955 1903 assert_equal %w(upload foo bar), issue.attachments.map(&:filename)
1956 1904 end
1957 1905 end
@@ -1,794 +1,776
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2013 Jean-Philippe Lang
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; either version 2
9 9 # of the License, or (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 19
20 20 require File.expand_path('../../test_helper', __FILE__)
21 21
22 22 class MailHandlerTest < ActiveSupport::TestCase
23 23 fixtures :users, :projects, :enabled_modules, :roles,
24 24 :members, :member_roles, :users,
25 25 :issues, :issue_statuses,
26 26 :workflows, :trackers, :projects_trackers,
27 27 :versions, :enumerations, :issue_categories,
28 28 :custom_fields, :custom_fields_trackers, :custom_fields_projects,
29 29 :boards, :messages
30 30
31 31 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
32 32
33 33 def setup
34 34 ActionMailer::Base.deliveries.clear
35 35 Setting.notified_events = Redmine::Notifiable.all.collect(&:name)
36 36 end
37 37
38 38 def teardown
39 39 Setting.clear_cache
40 40 end
41 41
42 42 def test_add_issue
43 43 ActionMailer::Base.deliveries.clear
44 44 # This email contains: 'Project: onlinestore'
45 45 issue = submit_email('ticket_on_given_project.eml')
46 46 assert issue.is_a?(Issue)
47 47 assert !issue.new_record?
48 48 issue.reload
49 49 assert_equal Project.find(2), issue.project
50 50 assert_equal issue.project.trackers.first, issue.tracker
51 51 assert_equal 'New ticket on a given project', issue.subject
52 52 assert_equal User.find_by_login('jsmith'), issue.author
53 53 assert_equal IssueStatus.find_by_name('Resolved'), issue.status
54 54 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
55 55 assert_equal '2010-01-01', issue.start_date.to_s
56 56 assert_equal '2010-12-31', issue.due_date.to_s
57 57 assert_equal User.find_by_login('jsmith'), issue.assigned_to
58 58 assert_equal Version.find_by_name('Alpha'), issue.fixed_version
59 59 assert_equal 2.5, issue.estimated_hours
60 60 assert_equal 30, issue.done_ratio
61 61 assert_equal [issue.id, 1, 2], [issue.root_id, issue.lft, issue.rgt]
62 62 # keywords should be removed from the email body
63 63 assert !issue.description.match(/^Project:/i)
64 64 assert !issue.description.match(/^Status:/i)
65 65 assert !issue.description.match(/^Start Date:/i)
66 66 # Email notification should be sent
67 67 mail = ActionMailer::Base.deliveries.last
68 68 assert_not_nil mail
69 69 assert mail.subject.include?('New ticket on a given project')
70 70 end
71 71
72 72 def test_add_issue_with_default_tracker
73 73 # This email contains: 'Project: onlinestore'
74 74 issue = submit_email(
75 75 'ticket_on_given_project.eml',
76 76 :issue => {:tracker => 'Support request'}
77 77 )
78 78 assert issue.is_a?(Issue)
79 79 assert !issue.new_record?
80 80 issue.reload
81 81 assert_equal 'Support request', issue.tracker.name
82 82 end
83 83
84 84 def test_add_issue_with_status
85 85 # This email contains: 'Project: onlinestore' and 'Status: Resolved'
86 86 issue = submit_email('ticket_on_given_project.eml')
87 87 assert issue.is_a?(Issue)
88 88 assert !issue.new_record?
89 89 issue.reload
90 90 assert_equal Project.find(2), issue.project
91 91 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
92 92 end
93 93
94 94 def test_add_issue_with_attributes_override
95 95 issue = submit_email(
96 96 'ticket_with_attributes.eml',
97 97 :allow_override => 'tracker,category,priority'
98 98 )
99 99 assert issue.is_a?(Issue)
100 100 assert !issue.new_record?
101 101 issue.reload
102 102 assert_equal 'New ticket on a given project', issue.subject
103 103 assert_equal User.find_by_login('jsmith'), issue.author
104 104 assert_equal Project.find(2), issue.project
105 105 assert_equal 'Feature request', issue.tracker.to_s
106 106 assert_equal 'Stock management', issue.category.to_s
107 107 assert_equal 'Urgent', issue.priority.to_s
108 108 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
109 109 end
110 110
111 111 def test_add_issue_with_group_assignment
112 112 with_settings :issue_group_assignment => '1' do
113 113 issue = submit_email('ticket_on_given_project.eml') do |email|
114 114 email.gsub!('Assigned to: John Smith', 'Assigned to: B Team')
115 115 end
116 116 assert issue.is_a?(Issue)
117 117 assert !issue.new_record?
118 118 issue.reload
119 119 assert_equal Group.find(11), issue.assigned_to
120 120 end
121 121 end
122 122
123 123 def test_add_issue_with_partial_attributes_override
124 124 issue = submit_email(
125 125 'ticket_with_attributes.eml',
126 126 :issue => {:priority => 'High'},
127 127 :allow_override => ['tracker']
128 128 )
129 129 assert issue.is_a?(Issue)
130 130 assert !issue.new_record?
131 131 issue.reload
132 132 assert_equal 'New ticket on a given project', issue.subject
133 133 assert_equal User.find_by_login('jsmith'), issue.author
134 134 assert_equal Project.find(2), issue.project
135 135 assert_equal 'Feature request', issue.tracker.to_s
136 136 assert_nil issue.category
137 137 assert_equal 'High', issue.priority.to_s
138 138 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
139 139 end
140 140
141 141 def test_add_issue_with_spaces_between_attribute_and_separator
142 142 issue = submit_email(
143 143 'ticket_with_spaces_between_attribute_and_separator.eml',
144 144 :allow_override => 'tracker,category,priority'
145 145 )
146 146 assert issue.is_a?(Issue)
147 147 assert !issue.new_record?
148 148 issue.reload
149 149 assert_equal 'New ticket on a given project', issue.subject
150 150 assert_equal User.find_by_login('jsmith'), issue.author
151 151 assert_equal Project.find(2), issue.project
152 152 assert_equal 'Feature request', issue.tracker.to_s
153 153 assert_equal 'Stock management', issue.category.to_s
154 154 assert_equal 'Urgent', issue.priority.to_s
155 155 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
156 156 end
157 157
158 158 def test_add_issue_with_attachment_to_specific_project
159 159 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
160 160 assert issue.is_a?(Issue)
161 161 assert !issue.new_record?
162 162 issue.reload
163 163 assert_equal 'Ticket created by email with attachment', issue.subject
164 164 assert_equal User.find_by_login('jsmith'), issue.author
165 165 assert_equal Project.find(2), issue.project
166 166 assert_equal 'This is a new ticket with attachments', issue.description
167 167 # Attachment properties
168 168 assert_equal 1, issue.attachments.size
169 169 assert_equal 'Paella.jpg', issue.attachments.first.filename
170 170 assert_equal 'image/jpeg', issue.attachments.first.content_type
171 171 assert_equal 10790, issue.attachments.first.filesize
172 172 end
173 173
174 174 def test_add_issue_with_custom_fields
175 175 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'onlinestore'})
176 176 assert issue.is_a?(Issue)
177 177 assert !issue.new_record?
178 178 issue.reload
179 179 assert_equal 'New ticket with custom field values', issue.subject
180 180 assert_equal 'PostgreSQL', issue.custom_field_value(1)
181 181 assert_equal 'Value for a custom field', issue.custom_field_value(2)
182 182 assert !issue.description.match(/^searchable field:/i)
183 183 end
184 184
185 185 def test_add_issue_with_version_custom_fields
186 186 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true, :tracker_ids => [1,2,3])
187 187
188 188 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'ecookbook'}) do |email|
189 189 email << "Affected version: 1.0\n"
190 190 end
191 191 assert issue.is_a?(Issue)
192 192 assert !issue.new_record?
193 193 issue.reload
194 194 assert_equal '2', issue.custom_field_value(field)
195 195 end
196 196
197 197 def test_add_issue_should_match_assignee_on_display_name
198 198 user = User.generate!(:firstname => 'Foo Bar', :lastname => 'Foo Baz')
199 199 User.add_to_project(user, Project.find(2))
200 200 issue = submit_email('ticket_on_given_project.eml') do |email|
201 201 email.sub!(/^Assigned to.*$/, 'Assigned to: Foo Bar Foo baz')
202 202 end
203 203 assert issue.is_a?(Issue)
204 204 assert_equal user, issue.assigned_to
205 205 end
206 206
207 207 def test_add_issue_with_cc
208 208 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
209 209 assert issue.is_a?(Issue)
210 210 assert !issue.new_record?
211 211 issue.reload
212 212 assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
213 213 assert_equal 1, issue.watcher_user_ids.size
214 214 end
215 215
216 216 def test_add_issue_by_unknown_user
217 217 assert_no_difference 'User.count' do
218 218 assert_equal false,
219 219 submit_email(
220 220 'ticket_by_unknown_user.eml',
221 221 :issue => {:project => 'ecookbook'}
222 222 )
223 223 end
224 224 end
225 225
226 226 def test_add_issue_by_anonymous_user
227 227 Role.anonymous.add_permission!(:add_issues)
228 228 assert_no_difference 'User.count' do
229 229 issue = submit_email(
230 230 'ticket_by_unknown_user.eml',
231 231 :issue => {:project => 'ecookbook'},
232 232 :unknown_user => 'accept'
233 233 )
234 234 assert issue.is_a?(Issue)
235 235 assert issue.author.anonymous?
236 236 end
237 237 end
238 238
239 239 def test_add_issue_by_anonymous_user_with_no_from_address
240 240 Role.anonymous.add_permission!(:add_issues)
241 241 assert_no_difference 'User.count' do
242 242 issue = submit_email(
243 243 'ticket_by_empty_user.eml',
244 244 :issue => {:project => 'ecookbook'},
245 245 :unknown_user => 'accept'
246 246 )
247 247 assert issue.is_a?(Issue)
248 248 assert issue.author.anonymous?
249 249 end
250 250 end
251 251
252 252 def test_add_issue_by_anonymous_user_on_private_project
253 253 Role.anonymous.add_permission!(:add_issues)
254 254 assert_no_difference 'User.count' do
255 255 assert_no_difference 'Issue.count' do
256 256 assert_equal false,
257 257 submit_email(
258 258 'ticket_by_unknown_user.eml',
259 259 :issue => {:project => 'onlinestore'},
260 260 :unknown_user => 'accept'
261 261 )
262 262 end
263 263 end
264 264 end
265 265
266 266 def test_add_issue_by_anonymous_user_on_private_project_without_permission_check
267 267 assert_no_difference 'User.count' do
268 268 assert_difference 'Issue.count' do
269 269 issue = submit_email(
270 270 'ticket_by_unknown_user.eml',
271 271 :issue => {:project => 'onlinestore'},
272 272 :no_permission_check => '1',
273 273 :unknown_user => 'accept'
274 274 )
275 275 assert issue.is_a?(Issue)
276 276 assert issue.author.anonymous?
277 277 assert !issue.project.is_public?
278 278 assert_equal [issue.id, 1, 2], [issue.root_id, issue.lft, issue.rgt]
279 279 end
280 280 end
281 281 end
282 282
283 283 def test_add_issue_by_created_user
284 284 Setting.default_language = 'en'
285 285 assert_difference 'User.count' do
286 286 issue = submit_email(
287 287 'ticket_by_unknown_user.eml',
288 288 :issue => {:project => 'ecookbook'},
289 289 :unknown_user => 'create'
290 290 )
291 291 assert issue.is_a?(Issue)
292 292 assert issue.author.active?
293 293 assert_equal 'john.doe@somenet.foo', issue.author.mail
294 294 assert_equal 'John', issue.author.firstname
295 295 assert_equal 'Doe', issue.author.lastname
296 296
297 297 # account information
298 298 email = ActionMailer::Base.deliveries.first
299 299 assert_not_nil email
300 300 assert email.subject.include?('account activation')
301 301 login = mail_body(email).match(/\* Login: (.*)$/)[1].strip
302 302 password = mail_body(email).match(/\* Password: (.*)$/)[1].strip
303 303 assert_equal issue.author, User.try_to_login(login, password)
304 304 end
305 305 end
306 306
307 307 def test_add_issue_without_from_header
308 308 Role.anonymous.add_permission!(:add_issues)
309 309 assert_equal false, submit_email('ticket_without_from_header.eml')
310 310 end
311 311
312 312 def test_add_issue_with_invalid_attributes
313 313 issue = submit_email(
314 314 'ticket_with_invalid_attributes.eml',
315 315 :allow_override => 'tracker,category,priority'
316 316 )
317 317 assert issue.is_a?(Issue)
318 318 assert !issue.new_record?
319 319 issue.reload
320 320 assert_nil issue.assigned_to
321 321 assert_nil issue.start_date
322 322 assert_nil issue.due_date
323 323 assert_equal 0, issue.done_ratio
324 324 assert_equal 'Normal', issue.priority.to_s
325 325 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
326 326 end
327 327
328 328 def test_add_issue_with_localized_attributes
329 329 User.find_by_mail('jsmith@somenet.foo').update_attribute 'language', 'fr'
330 330 issue = submit_email(
331 331 'ticket_with_localized_attributes.eml',
332 332 :allow_override => 'tracker,category,priority'
333 333 )
334 334 assert issue.is_a?(Issue)
335 335 assert !issue.new_record?
336 336 issue.reload
337 337 assert_equal 'New ticket on a given project', issue.subject
338 338 assert_equal User.find_by_login('jsmith'), issue.author
339 339 assert_equal Project.find(2), issue.project
340 340 assert_equal 'Feature request', issue.tracker.to_s
341 341 assert_equal 'Stock management', issue.category.to_s
342 342 assert_equal 'Urgent', issue.priority.to_s
343 343 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
344 344 end
345 345
346 346 def test_add_issue_with_japanese_keywords
347 347 ja_dev = "\xe9\x96\x8b\xe7\x99\xba"
348 348 ja_dev.force_encoding('UTF-8') if ja_dev.respond_to?(:force_encoding)
349 349 tracker = Tracker.create!(:name => ja_dev)
350 350 Project.find(1).trackers << tracker
351 351 issue = submit_email(
352 352 'japanese_keywords_iso_2022_jp.eml',
353 353 :issue => {:project => 'ecookbook'},
354 354 :allow_override => 'tracker'
355 355 )
356 356 assert_kind_of Issue, issue
357 357 assert_equal tracker, issue.tracker
358 358 end
359 359
360 360 def test_add_issue_from_apple_mail
361 361 issue = submit_email(
362 362 'apple_mail_with_attachment.eml',
363 363 :issue => {:project => 'ecookbook'}
364 364 )
365 365 assert_kind_of Issue, issue
366 366 assert_equal 1, issue.attachments.size
367 367
368 368 attachment = issue.attachments.first
369 369 assert_equal 'paella.jpg', attachment.filename
370 370 assert_equal 10790, attachment.filesize
371 371 assert File.exist?(attachment.diskfile)
372 372 assert_equal 10790, File.size(attachment.diskfile)
373 373 assert_equal 'caaf384198bcbc9563ab5c058acd73cd', attachment.digest
374 374 end
375 375
376 376 def test_thunderbird_with_attachment_ja
377 377 issue = submit_email(
378 378 'thunderbird_with_attachment_ja.eml',
379 379 :issue => {:project => 'ecookbook'}
380 380 )
381 381 assert_kind_of Issue, issue
382 382 assert_equal 1, issue.attachments.size
383 383 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt"
384 384 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
385 385 attachment = issue.attachments.first
386 386 assert_equal ja, attachment.filename
387 387 assert_equal 5, attachment.filesize
388 388 assert File.exist?(attachment.diskfile)
389 389 assert_equal 5, File.size(attachment.diskfile)
390 390 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
391 391 end
392 392
393 393 def test_gmail_with_attachment_ja
394 394 issue = submit_email(
395 395 'gmail_with_attachment_ja.eml',
396 396 :issue => {:project => 'ecookbook'}
397 397 )
398 398 assert_kind_of Issue, issue
399 399 assert_equal 1, issue.attachments.size
400 400 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt"
401 401 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
402 402 attachment = issue.attachments.first
403 403 assert_equal ja, attachment.filename
404 404 assert_equal 5, attachment.filesize
405 405 assert File.exist?(attachment.diskfile)
406 406 assert_equal 5, File.size(attachment.diskfile)
407 407 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
408 408 end
409 409
410 410 def test_thunderbird_with_attachment_latin1
411 411 issue = submit_email(
412 412 'thunderbird_with_attachment_iso-8859-1.eml',
413 413 :issue => {:project => 'ecookbook'}
414 414 )
415 415 assert_kind_of Issue, issue
416 416 assert_equal 1, issue.attachments.size
417 417 u = ""
418 418 u.force_encoding('UTF-8') if u.respond_to?(:force_encoding)
419 419 u1 = "\xc3\x84\xc3\xa4\xc3\x96\xc3\xb6\xc3\x9c\xc3\xbc"
420 420 u1.force_encoding('UTF-8') if u1.respond_to?(:force_encoding)
421 421 11.times { u << u1 }
422 422 attachment = issue.attachments.first
423 423 assert_equal "#{u}.png", attachment.filename
424 424 assert_equal 130, attachment.filesize
425 425 assert File.exist?(attachment.diskfile)
426 426 assert_equal 130, File.size(attachment.diskfile)
427 427 assert_equal '4d80e667ac37dddfe05502530f152abb', attachment.digest
428 428 end
429 429
430 430 def test_gmail_with_attachment_latin1
431 431 issue = submit_email(
432 432 'gmail_with_attachment_iso-8859-1.eml',
433 433 :issue => {:project => 'ecookbook'}
434 434 )
435 435 assert_kind_of Issue, issue
436 436 assert_equal 1, issue.attachments.size
437 437 u = ""
438 438 u.force_encoding('UTF-8') if u.respond_to?(:force_encoding)
439 439 u1 = "\xc3\x84\xc3\xa4\xc3\x96\xc3\xb6\xc3\x9c\xc3\xbc"
440 440 u1.force_encoding('UTF-8') if u1.respond_to?(:force_encoding)
441 441 11.times { u << u1 }
442 442 attachment = issue.attachments.first
443 443 assert_equal "#{u}.txt", attachment.filename
444 444 assert_equal 5, attachment.filesize
445 445 assert File.exist?(attachment.diskfile)
446 446 assert_equal 5, File.size(attachment.diskfile)
447 447 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
448 448 end
449 449
450 450 def test_add_issue_with_iso_8859_1_subject
451 451 issue = submit_email(
452 452 'subject_as_iso-8859-1.eml',
453 453 :issue => {:project => 'ecookbook'}
454 454 )
455 455 str = "Testmail from Webmail: \xc3\xa4 \xc3\xb6 \xc3\xbc..."
456 456 str.force_encoding('UTF-8') if str.respond_to?(:force_encoding)
457 457 assert_kind_of Issue, issue
458 458 assert_equal str, issue.subject
459 459 end
460 460
461 461 def test_add_issue_with_japanese_subject
462 462 issue = submit_email(
463 463 'subject_japanese_1.eml',
464 464 :issue => {:project => 'ecookbook'}
465 465 )
466 466 assert_kind_of Issue, issue
467 467 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88"
468 468 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
469 469 assert_equal ja, issue.subject
470 470 end
471 471
472 472 def test_add_issue_with_no_subject_header
473 473 issue = submit_email(
474 474 'no_subject_header.eml',
475 475 :issue => {:project => 'ecookbook'}
476 476 )
477 477 assert_kind_of Issue, issue
478 478 assert_equal '(no subject)', issue.subject
479 479 end
480 480
481 481 def test_add_issue_with_mixed_japanese_subject
482 482 issue = submit_email(
483 483 'subject_japanese_2.eml',
484 484 :issue => {:project => 'ecookbook'}
485 485 )
486 486 assert_kind_of Issue, issue
487 487 ja = "Re: \xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88"
488 488 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
489 489 assert_equal ja, issue.subject
490 490 end
491 491
492 492 def test_should_ignore_emails_from_locked_users
493 493 User.find(2).lock!
494 494
495 495 MailHandler.any_instance.expects(:dispatch).never
496 496 assert_no_difference 'Issue.count' do
497 497 assert_equal false, submit_email('ticket_on_given_project.eml')
498 498 end
499 499 end
500 500
501 501 def test_should_ignore_emails_from_emission_address
502 502 Role.anonymous.add_permission!(:add_issues)
503 503 assert_no_difference 'User.count' do
504 504 assert_equal false,
505 505 submit_email(
506 506 'ticket_from_emission_address.eml',
507 507 :issue => {:project => 'ecookbook'},
508 508 :unknown_user => 'create'
509 509 )
510 510 end
511 511 end
512 512
513 513 def test_should_ignore_auto_replied_emails
514 514 MailHandler.any_instance.expects(:dispatch).never
515 515 [
516 516 "X-Auto-Response-Suppress: OOF",
517 517 "Auto-Submitted: auto-replied",
518 518 "Auto-Submitted: Auto-Replied",
519 519 "Auto-Submitted: auto-generated"
520 520 ].each do |header|
521 521 raw = IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
522 522 raw = header + "\n" + raw
523 523
524 524 assert_no_difference 'Issue.count' do
525 525 assert_equal false, MailHandler.receive(raw), "email with #{header} header was not ignored"
526 526 end
527 527 end
528 528 end
529 529
530 530 def test_add_issue_should_send_email_notification
531 531 Setting.notified_events = ['issue_added']
532 532 ActionMailer::Base.deliveries.clear
533 533 # This email contains: 'Project: onlinestore'
534 534 issue = submit_email('ticket_on_given_project.eml')
535 535 assert issue.is_a?(Issue)
536 536 assert_equal 1, ActionMailer::Base.deliveries.size
537 537 end
538 538
539 539 def test_update_issue
540 540 journal = submit_email('ticket_reply.eml')
541 541 assert journal.is_a?(Journal)
542 542 assert_equal User.find_by_login('jsmith'), journal.user
543 543 assert_equal Issue.find(2), journal.journalized
544 544 assert_match /This is reply/, journal.notes
545 545 assert_equal false, journal.private_notes
546 546 assert_equal 'Feature request', journal.issue.tracker.name
547 547 end
548 548
549 549 def test_update_issue_with_attribute_changes
550 550 # This email contains: 'Status: Resolved'
551 551 journal = submit_email('ticket_reply_with_status.eml')
552 552 assert journal.is_a?(Journal)
553 553 issue = Issue.find(journal.issue.id)
554 554 assert_equal User.find_by_login('jsmith'), journal.user
555 555 assert_equal Issue.find(2), journal.journalized
556 556 assert_match /This is reply/, journal.notes
557 557 assert_equal 'Feature request', journal.issue.tracker.name
558 558 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
559 559 assert_equal '2010-01-01', issue.start_date.to_s
560 560 assert_equal '2010-12-31', issue.due_date.to_s
561 561 assert_equal User.find_by_login('jsmith'), issue.assigned_to
562 562 assert_equal "52.6", issue.custom_value_for(CustomField.find_by_name('Float field')).value
563 563 # keywords should be removed from the email body
564 564 assert !journal.notes.match(/^Status:/i)
565 565 assert !journal.notes.match(/^Start Date:/i)
566 566 end
567 567
568 568 def test_update_issue_with_attachment
569 569 assert_difference 'Journal.count' do
570 570 assert_difference 'JournalDetail.count' do
571 571 assert_difference 'Attachment.count' do
572 572 assert_no_difference 'Issue.count' do
573 573 journal = submit_email('ticket_with_attachment.eml') do |raw|
574 574 raw.gsub! /^Subject: .*$/, 'Subject: Re: [Cookbook - Feature #2] (New) Add ingredients categories'
575 575 end
576 576 end
577 577 end
578 578 end
579 579 end
580 580 journal = Journal.first(:order => 'id DESC')
581 581 assert_equal Issue.find(2), journal.journalized
582 582 assert_equal 1, journal.details.size
583 583
584 584 detail = journal.details.first
585 585 assert_equal 'attachment', detail.property
586 586 assert_equal 'Paella.jpg', detail.value
587 587 end
588 588
589 589 def test_update_issue_should_send_email_notification
590 590 ActionMailer::Base.deliveries.clear
591 591 journal = submit_email('ticket_reply.eml')
592 592 assert journal.is_a?(Journal)
593 593 assert_equal 1, ActionMailer::Base.deliveries.size
594 594 end
595 595
596 596 def test_update_issue_should_not_set_defaults
597 597 journal = submit_email(
598 598 'ticket_reply.eml',
599 599 :issue => {:tracker => 'Support request', :priority => 'High'}
600 600 )
601 601 assert journal.is_a?(Journal)
602 602 assert_match /This is reply/, journal.notes
603 603 assert_equal 'Feature request', journal.issue.tracker.name
604 604 assert_equal 'Normal', journal.issue.priority.name
605 605 end
606 606
607 607 def test_replying_to_a_private_note_should_add_reply_as_private
608 608 private_journal = Journal.create!(:notes => 'Private notes', :journalized => Issue.find(1), :private_notes => true, :user_id => 2)
609 609
610 610 assert_difference 'Journal.count' do
611 611 journal = submit_email('ticket_reply.eml') do |email|
612 612 email.sub! %r{^In-Reply-To:.*$}, "In-Reply-To: <redmine.journal-#{private_journal.id}.20060719210421@osiris>"
613 613 end
614 614
615 615 assert_kind_of Journal, journal
616 616 assert_match /This is reply/, journal.notes
617 617 assert_equal true, journal.private_notes
618 618 end
619 619 end
620 620
621 621 def test_reply_to_a_message
622 622 m = submit_email('message_reply.eml')
623 623 assert m.is_a?(Message)
624 624 assert !m.new_record?
625 625 m.reload
626 626 assert_equal 'Reply via email', m.subject
627 627 # The email replies to message #2 which is part of the thread of message #1
628 628 assert_equal Message.find(1), m.parent
629 629 end
630 630
631 631 def test_reply_to_a_message_by_subject
632 632 m = submit_email('message_reply_by_subject.eml')
633 633 assert m.is_a?(Message)
634 634 assert !m.new_record?
635 635 m.reload
636 636 assert_equal 'Reply to the first post', m.subject
637 637 assert_equal Message.find(1), m.parent
638 638 end
639 639
640 640 def test_should_strip_tags_of_html_only_emails
641 641 issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
642 642 assert issue.is_a?(Issue)
643 643 assert !issue.new_record?
644 644 issue.reload
645 645 assert_equal 'HTML email', issue.subject
646 646 assert_equal 'This is a html-only email.', issue.description
647 647 end
648 648
649 context "truncate emails based on the Setting" do
650 context "with no setting" do
651 setup do
652 Setting.mail_handler_body_delimiters = ''
653 end
654
655 should "add the entire email into the issue" do
656 issue = submit_email('ticket_on_given_project.eml')
657 assert_issue_created(issue)
658 assert issue.description.include?('---')
659 assert issue.description.include?('This paragraph is after the delimiter')
660 end
649 test "truncate emails with no setting should add the entire email into the issue" do
650 with_settings :mail_handler_body_delimiters => '' do
651 issue = submit_email('ticket_on_given_project.eml')
652 assert_issue_created(issue)
653 assert issue.description.include?('---')
654 assert issue.description.include?('This paragraph is after the delimiter')
661 655 end
656 end
662 657
663 context "with a single string" do
664 setup do
665 Setting.mail_handler_body_delimiters = '---'
666 end
667 should "truncate the email at the delimiter for the issue" do
668 issue = submit_email('ticket_on_given_project.eml')
669 assert_issue_created(issue)
670 assert issue.description.include?('This paragraph is before delimiters')
671 assert issue.description.include?('--- This line starts with a delimiter')
672 assert !issue.description.match(/^---$/)
673 assert !issue.description.include?('This paragraph is after the delimiter')
674 end
658 test "truncate emails with a single string should truncate the email at the delimiter for the issue" do
659 with_settings :mail_handler_body_delimiters => '---' do
660 issue = submit_email('ticket_on_given_project.eml')
661 assert_issue_created(issue)
662 assert issue.description.include?('This paragraph is before delimiters')
663 assert issue.description.include?('--- This line starts with a delimiter')
664 assert !issue.description.match(/^---$/)
665 assert !issue.description.include?('This paragraph is after the delimiter')
675 666 end
667 end
676 668
677 context "with a single quoted reply (e.g. reply to a Redmine email notification)" do
678 setup do
679 Setting.mail_handler_body_delimiters = '--- Reply above. Do not remove this line. ---'
680 end
681 should "truncate the email at the delimiter with the quoted reply symbols (>)" do
682 journal = submit_email('issue_update_with_quoted_reply_above.eml')
683 assert journal.is_a?(Journal)
684 assert journal.notes.include?('An update to the issue by the sender.')
685 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
686 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
687 end
669 test "truncate emails with a single quoted reply should truncate the email at the delimiter with the quoted reply symbols (>)" do
670 with_settings :mail_handler_body_delimiters => '--- Reply above. Do not remove this line. ---' do
671 journal = submit_email('issue_update_with_quoted_reply_above.eml')
672 assert journal.is_a?(Journal)
673 assert journal.notes.include?('An update to the issue by the sender.')
674 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
675 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
688 676 end
677 end
689 678
690 context "with multiple quoted replies (e.g. reply to a reply of a Redmine email notification)" do
691 setup do
692 Setting.mail_handler_body_delimiters = '--- Reply above. Do not remove this line. ---'
693 end
694 should "truncate the email at the delimiter with the quoted reply symbols (>)" do
695 journal = submit_email('issue_update_with_multiple_quoted_reply_above.eml')
696 assert journal.is_a?(Journal)
697 assert journal.notes.include?('An update to the issue by the sender.')
698 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
699 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
700 end
679 test "truncate emails with multiple quoted replies should truncate the email at the delimiter with the quoted reply symbols (>)" do
680 with_settings :mail_handler_body_delimiters => '--- Reply above. Do not remove this line. ---' do
681 journal = submit_email('issue_update_with_multiple_quoted_reply_above.eml')
682 assert journal.is_a?(Journal)
683 assert journal.notes.include?('An update to the issue by the sender.')
684 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
685 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
701 686 end
687 end
702 688
703 context "with multiple strings" do
704 setup do
705 Setting.mail_handler_body_delimiters = "---\nBREAK"
706 end
707 should "truncate the email at the first delimiter found (BREAK)" do
708 issue = submit_email('ticket_on_given_project.eml')
709 assert_issue_created(issue)
710 assert issue.description.include?('This paragraph is before delimiters')
711 assert !issue.description.include?('BREAK')
712 assert !issue.description.include?('This paragraph is between delimiters')
713 assert !issue.description.match(/^---$/)
714 assert !issue.description.include?('This paragraph is after the delimiter')
715 end
689 test "truncate emails with multiple strings should truncate the email at the first delimiter found (BREAK)" do
690 with_settings :mail_handler_body_delimiters => "---\nBREAK" do
691 issue = submit_email('ticket_on_given_project.eml')
692 assert_issue_created(issue)
693 assert issue.description.include?('This paragraph is before delimiters')
694 assert !issue.description.include?('BREAK')
695 assert !issue.description.include?('This paragraph is between delimiters')
696 assert !issue.description.match(/^---$/)
697 assert !issue.description.include?('This paragraph is after the delimiter')
716 698 end
717 699 end
718 700
719 701 def test_email_with_long_subject_line
720 702 issue = submit_email('ticket_with_long_subject.eml')
721 703 assert issue.is_a?(Issue)
722 704 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]
723 705 end
724 706
725 707 def test_new_user_from_attributes_should_return_valid_user
726 708 to_test = {
727 709 # [address, name] => [login, firstname, lastname]
728 710 ['jsmith@example.net', nil] => ['jsmith@example.net', 'jsmith', '-'],
729 711 ['jsmith@example.net', 'John'] => ['jsmith@example.net', 'John', '-'],
730 712 ['jsmith@example.net', 'John Smith'] => ['jsmith@example.net', 'John', 'Smith'],
731 713 ['jsmith@example.net', 'John Paul Smith'] => ['jsmith@example.net', 'John', 'Paul Smith'],
732 714 ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsTheMaximumLength Smith'] => ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsT', 'Smith'],
733 715 ['jsmith@example.net', 'John AVeryLongLastnameThatExceedsTheMaximumLength'] => ['jsmith@example.net', 'John', 'AVeryLongLastnameThatExceedsTh']
734 716 }
735 717
736 718 to_test.each do |attrs, expected|
737 719 user = MailHandler.new_user_from_attributes(attrs.first, attrs.last)
738 720
739 721 assert user.valid?, user.errors.full_messages.to_s
740 722 assert_equal attrs.first, user.mail
741 723 assert_equal expected[0], user.login
742 724 assert_equal expected[1], user.firstname
743 725 assert_equal expected[2], user.lastname
744 726 end
745 727 end
746 728
747 729 def test_new_user_from_attributes_should_respect_minimum_password_length
748 730 with_settings :password_min_length => 15 do
749 731 user = MailHandler.new_user_from_attributes('jsmith@example.net')
750 732 assert user.valid?
751 733 assert user.password.length >= 15
752 734 end
753 735 end
754 736
755 737 def test_new_user_from_attributes_should_use_default_login_if_invalid
756 738 user = MailHandler.new_user_from_attributes('foo+bar@example.net')
757 739 assert user.valid?
758 740 assert user.login =~ /^user[a-f0-9]+$/
759 741 assert_equal 'foo+bar@example.net', user.mail
760 742 end
761 743
762 744 def test_new_user_with_utf8_encoded_fullname_should_be_decoded
763 745 assert_difference 'User.count' do
764 746 issue = submit_email(
765 747 'fullname_of_sender_as_utf8_encoded.eml',
766 748 :issue => {:project => 'ecookbook'},
767 749 :unknown_user => 'create'
768 750 )
769 751 end
770 752
771 753 user = User.first(:order => 'id DESC')
772 754 assert_equal "foo@example.org", user.mail
773 755 str1 = "\xc3\x84\xc3\xa4"
774 756 str2 = "\xc3\x96\xc3\xb6"
775 757 str1.force_encoding('UTF-8') if str1.respond_to?(:force_encoding)
776 758 str2.force_encoding('UTF-8') if str2.respond_to?(:force_encoding)
777 759 assert_equal str1, user.firstname
778 760 assert_equal str2, user.lastname
779 761 end
780 762
781 763 private
782 764
783 765 def submit_email(filename, options={})
784 766 raw = IO.read(File.join(FIXTURES_PATH, filename))
785 767 yield raw if block_given?
786 768 MailHandler.receive(raw, options)
787 769 end
788 770
789 771 def assert_issue_created(issue)
790 772 assert issue.is_a?(Issue)
791 773 assert !issue.new_record?
792 774 issue.reload
793 775 end
794 776 end
@@ -1,618 +1,614
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class MailerTest < ActiveSupport::TestCase
21 21 include Redmine::I18n
22 22 include ActionDispatch::Assertions::SelectorAssertions
23 23 fixtures :projects, :enabled_modules, :issues, :users, :members,
24 24 :member_roles, :roles, :documents, :attachments, :news,
25 25 :tokens, :journals, :journal_details, :changesets,
26 26 :trackers, :projects_trackers,
27 27 :issue_statuses, :enumerations, :messages, :boards, :repositories,
28 28 :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions,
29 29 :versions,
30 30 :comments
31 31
32 32 def setup
33 33 ActionMailer::Base.deliveries.clear
34 34 Setting.host_name = 'mydomain.foo'
35 35 Setting.protocol = 'http'
36 36 Setting.plain_text_mail = '0'
37 37 end
38 38
39 39 def test_generated_links_in_emails
40 40 Setting.default_language = 'en'
41 41 Setting.host_name = 'mydomain.foo'
42 42 Setting.protocol = 'https'
43 43
44 44 journal = Journal.find(3)
45 45 assert Mailer.issue_edit(journal).deliver
46 46
47 47 mail = last_email
48 48 assert_not_nil mail
49 49
50 50 assert_select_email do
51 51 # link to the main ticket
52 52 assert_select 'a[href=?]',
53 53 'https://mydomain.foo/issues/2#change-3',
54 54 :text => 'Feature request #2: Add ingredients categories'
55 55 # link to a referenced ticket
56 56 assert_select 'a[href=?][title=?]',
57 57 'https://mydomain.foo/issues/1',
58 58 'Can&#x27;t print recipes (New)',
59 59 :text => '#1'
60 60 # link to a changeset
61 61 assert_select 'a[href=?][title=?]',
62 62 'https://mydomain.foo/projects/ecookbook/repository/revisions/2',
63 63 'This commit fixes #1, #2 and references #1 &amp; #3',
64 64 :text => 'r2'
65 65 # link to a description diff
66 66 assert_select 'a[href=?][title=?]',
67 67 'https://mydomain.foo/journals/diff/3?detail_id=4',
68 68 'View differences',
69 69 :text => 'diff'
70 70 # link to an attachment
71 71 assert_select 'a[href=?]',
72 72 'https://mydomain.foo/attachments/download/4/source.rb',
73 73 :text => 'source.rb'
74 74 end
75 75 end
76 76
77 77 def test_generated_links_with_prefix
78 78 Setting.default_language = 'en'
79 79 relative_url_root = Redmine::Utils.relative_url_root
80 80 Setting.host_name = 'mydomain.foo/rdm'
81 81 Setting.protocol = 'http'
82 82
83 83 journal = Journal.find(3)
84 84 assert Mailer.issue_edit(journal).deliver
85 85
86 86 mail = last_email
87 87 assert_not_nil mail
88 88
89 89 assert_select_email do
90 90 # link to the main ticket
91 91 assert_select 'a[href=?]',
92 92 'http://mydomain.foo/rdm/issues/2#change-3',
93 93 :text => 'Feature request #2: Add ingredients categories'
94 94 # link to a referenced ticket
95 95 assert_select 'a[href=?][title=?]',
96 96 'http://mydomain.foo/rdm/issues/1',
97 97 'Can&#x27;t print recipes (New)',
98 98 :text => '#1'
99 99 # link to a changeset
100 100 assert_select 'a[href=?][title=?]',
101 101 'http://mydomain.foo/rdm/projects/ecookbook/repository/revisions/2',
102 102 'This commit fixes #1, #2 and references #1 &amp; #3',
103 103 :text => 'r2'
104 104 # link to a description diff
105 105 assert_select 'a[href=?][title=?]',
106 106 'http://mydomain.foo/rdm/journals/diff/3?detail_id=4',
107 107 'View differences',
108 108 :text => 'diff'
109 109 # link to an attachment
110 110 assert_select 'a[href=?]',
111 111 'http://mydomain.foo/rdm/attachments/download/4/source.rb',
112 112 :text => 'source.rb'
113 113 end
114 114 end
115 115
116 116 def test_generated_links_with_prefix_and_no_relative_url_root
117 117 Setting.default_language = 'en'
118 118 relative_url_root = Redmine::Utils.relative_url_root
119 119 Setting.host_name = 'mydomain.foo/rdm'
120 120 Setting.protocol = 'http'
121 121 Redmine::Utils.relative_url_root = nil
122 122
123 123 journal = Journal.find(3)
124 124 assert Mailer.issue_edit(journal).deliver
125 125
126 126 mail = last_email
127 127 assert_not_nil mail
128 128
129 129 assert_select_email do
130 130 # link to the main ticket
131 131 assert_select 'a[href=?]',
132 132 'http://mydomain.foo/rdm/issues/2#change-3',
133 133 :text => 'Feature request #2: Add ingredients categories'
134 134 # link to a referenced ticket
135 135 assert_select 'a[href=?][title=?]',
136 136 'http://mydomain.foo/rdm/issues/1',
137 137 'Can&#x27;t print recipes (New)',
138 138 :text => '#1'
139 139 # link to a changeset
140 140 assert_select 'a[href=?][title=?]',
141 141 'http://mydomain.foo/rdm/projects/ecookbook/repository/revisions/2',
142 142 'This commit fixes #1, #2 and references #1 &amp; #3',
143 143 :text => 'r2'
144 144 # link to a description diff
145 145 assert_select 'a[href=?][title=?]',
146 146 'http://mydomain.foo/rdm/journals/diff/3?detail_id=4',
147 147 'View differences',
148 148 :text => 'diff'
149 149 # link to an attachment
150 150 assert_select 'a[href=?]',
151 151 'http://mydomain.foo/rdm/attachments/download/4/source.rb',
152 152 :text => 'source.rb'
153 153 end
154 154 ensure
155 155 # restore it
156 156 Redmine::Utils.relative_url_root = relative_url_root
157 157 end
158 158
159 159 def test_email_headers
160 160 issue = Issue.find(1)
161 161 Mailer.issue_add(issue).deliver
162 162 mail = last_email
163 163 assert_not_nil mail
164 164 assert_equal 'OOF', mail.header['X-Auto-Response-Suppress'].to_s
165 165 assert_equal 'auto-generated', mail.header['Auto-Submitted'].to_s
166 166 assert_equal '<redmine.example.net>', mail.header['List-Id'].to_s
167 167 end
168 168
169 169 def test_email_headers_should_include_sender
170 170 issue = Issue.find(1)
171 171 Mailer.issue_add(issue).deliver
172 172 mail = last_email
173 173 assert_equal issue.author.login, mail.header['X-Redmine-Sender'].to_s
174 174 end
175 175
176 176 def test_plain_text_mail
177 177 Setting.plain_text_mail = 1
178 178 journal = Journal.find(2)
179 179 Mailer.issue_edit(journal).deliver
180 180 mail = last_email
181 181 assert_equal "text/plain; charset=UTF-8", mail.content_type
182 182 assert_equal 0, mail.parts.size
183 183 assert !mail.encoded.include?('href')
184 184 end
185 185
186 186 def test_html_mail
187 187 Setting.plain_text_mail = 0
188 188 journal = Journal.find(2)
189 189 Mailer.issue_edit(journal).deliver
190 190 mail = last_email
191 191 assert_equal 2, mail.parts.size
192 192 assert mail.encoded.include?('href')
193 193 end
194 194
195 195 def test_from_header
196 196 with_settings :mail_from => 'redmine@example.net' do
197 197 Mailer.test_email(User.find(1)).deliver
198 198 end
199 199 mail = last_email
200 200 assert_equal 'redmine@example.net', mail.from_addrs.first
201 201 end
202 202
203 203 def test_from_header_with_phrase
204 204 with_settings :mail_from => 'Redmine app <redmine@example.net>' do
205 205 Mailer.test_email(User.find(1)).deliver
206 206 end
207 207 mail = last_email
208 208 assert_equal 'redmine@example.net', mail.from_addrs.first
209 209 assert_equal 'Redmine app <redmine@example.net>', mail.header['From'].to_s
210 210 end
211 211
212 212 def test_should_not_send_email_without_recipient
213 213 news = News.first
214 214 user = news.author
215 215 # Remove members except news author
216 216 news.project.memberships.each {|m| m.destroy unless m.user == user}
217 217
218 218 user.pref[:no_self_notified] = false
219 219 user.pref.save
220 220 User.current = user
221 221 Mailer.news_added(news.reload).deliver
222 222 assert_equal 1, last_email.bcc.size
223 223
224 224 # nobody to notify
225 225 user.pref[:no_self_notified] = true
226 226 user.pref.save
227 227 User.current = user
228 228 ActionMailer::Base.deliveries.clear
229 229 Mailer.news_added(news.reload).deliver
230 230 assert ActionMailer::Base.deliveries.empty?
231 231 end
232 232
233 233 def test_issue_add_message_id
234 234 issue = Issue.find(1)
235 235 Mailer.issue_add(issue).deliver
236 236 mail = last_email
237 237 assert_equal Mailer.message_id_for(issue), mail.message_id
238 238 assert_nil mail.references
239 239 end
240 240
241 241 def test_issue_edit_message_id
242 242 journal = Journal.find(1)
243 243 Mailer.issue_edit(journal).deliver
244 244 mail = last_email
245 245 assert_equal Mailer.message_id_for(journal), mail.message_id
246 246 assert_include Mailer.message_id_for(journal.issue), mail.references
247 247 assert_select_email do
248 248 # link to the update
249 249 assert_select "a[href=?]",
250 250 "http://mydomain.foo/issues/#{journal.journalized_id}#change-#{journal.id}"
251 251 end
252 252 end
253 253
254 254 def test_message_posted_message_id
255 255 message = Message.find(1)
256 256 Mailer.message_posted(message).deliver
257 257 mail = last_email
258 258 assert_equal Mailer.message_id_for(message), mail.message_id
259 259 assert_nil mail.references
260 260 assert_select_email do
261 261 # link to the message
262 262 assert_select "a[href=?]",
263 263 "http://mydomain.foo/boards/#{message.board.id}/topics/#{message.id}",
264 264 :text => message.subject
265 265 end
266 266 end
267 267
268 268 def test_reply_posted_message_id
269 269 message = Message.find(3)
270 270 Mailer.message_posted(message).deliver
271 271 mail = last_email
272 272 assert_equal Mailer.message_id_for(message), mail.message_id
273 273 assert_include Mailer.message_id_for(message.parent), mail.references
274 274 assert_select_email do
275 275 # link to the reply
276 276 assert_select "a[href=?]",
277 277 "http://mydomain.foo/boards/#{message.board.id}/topics/#{message.root.id}?r=#{message.id}#message-#{message.id}",
278 278 :text => message.subject
279 279 end
280 280 end
281 281
282 context("#issue_add") do
283 setup do
284 ActionMailer::Base.deliveries.clear
285 Setting.bcc_recipients = '1'
286 @issue = Issue.find(1)
287 end
282 test "#issue_add should notify project members" do
283 issue = Issue.find(1)
284 assert Mailer.issue_add(issue).deliver
285 assert last_email.bcc.include?('dlopper@somenet.foo')
286 end
288 287
289 should "notify project members" do
290 assert Mailer.issue_add(@issue).deliver
291 assert last_email.bcc.include?('dlopper@somenet.foo')
292 end
288 test "#issue_add should not notify project members that are not allow to view the issue" do
289 issue = Issue.find(1)
290 Role.find(2).remove_permission!(:view_issues)
291 assert Mailer.issue_add(issue).deliver
292 assert !last_email.bcc.include?('dlopper@somenet.foo')
293 end
293 294
294 should "not notify project members that are not allow to view the issue" do
295 Role.find(2).remove_permission!(:view_issues)
296 assert Mailer.issue_add(@issue).deliver
297 assert !last_email.bcc.include?('dlopper@somenet.foo')
298 end
295 test "#issue_add should notify issue watchers" do
296 issue = Issue.find(1)
297 user = User.find(9)
298 # minimal email notification options
299 user.pref[:no_self_notified] = '1'
300 user.pref.save
301 user.mail_notification = false
302 user.save
299 303
300 should "notify issue watchers" do
301 user = User.find(9)
302 # minimal email notification options
303 user.pref[:no_self_notified] = '1'
304 user.pref.save
305 user.mail_notification = false
306 user.save
307
308 Watcher.create!(:watchable => @issue, :user => user)
309 assert Mailer.issue_add(@issue).deliver
310 assert last_email.bcc.include?(user.mail)
311 end
304 Watcher.create!(:watchable => issue, :user => user)
305 assert Mailer.issue_add(issue).deliver
306 assert last_email.bcc.include?(user.mail)
307 end
312 308
313 should "not notify watchers not allowed to view the issue" do
314 user = User.find(9)
315 Watcher.create!(:watchable => @issue, :user => user)
316 Role.non_member.remove_permission!(:view_issues)
317 assert Mailer.issue_add(@issue).deliver
318 assert !last_email.bcc.include?(user.mail)
319 end
309 test "#issue_add should not notify watchers not allowed to view the issue" do
310 issue = Issue.find(1)
311 user = User.find(9)
312 Watcher.create!(:watchable => issue, :user => user)
313 Role.non_member.remove_permission!(:view_issues)
314 assert Mailer.issue_add(issue).deliver
315 assert !last_email.bcc.include?(user.mail)
320 316 end
321 317
322 318 # test mailer methods for each language
323 319 def test_issue_add
324 320 issue = Issue.find(1)
325 321 valid_languages.each do |lang|
326 322 Setting.default_language = lang.to_s
327 323 assert Mailer.issue_add(issue).deliver
328 324 end
329 325 end
330 326
331 327 def test_issue_edit
332 328 journal = Journal.find(1)
333 329 valid_languages.each do |lang|
334 330 Setting.default_language = lang.to_s
335 331 assert Mailer.issue_edit(journal).deliver
336 332 end
337 333 end
338 334
339 335 def test_issue_edit_should_send_private_notes_to_users_with_permission_only
340 336 journal = Journal.find(1)
341 337 journal.private_notes = true
342 338 journal.save!
343 339
344 340 Role.find(2).add_permission! :view_private_notes
345 341 Mailer.issue_edit(journal).deliver
346 342 assert_equal %w(dlopper@somenet.foo jsmith@somenet.foo), ActionMailer::Base.deliveries.last.bcc.sort
347 343
348 344 Role.find(2).remove_permission! :view_private_notes
349 345 Mailer.issue_edit(journal).deliver
350 346 assert_equal %w(jsmith@somenet.foo), ActionMailer::Base.deliveries.last.bcc.sort
351 347 end
352 348
353 349 def test_issue_edit_should_send_private_notes_to_watchers_with_permission_only
354 350 Issue.find(1).set_watcher(User.find_by_login('someone'))
355 351 journal = Journal.find(1)
356 352 journal.private_notes = true
357 353 journal.save!
358 354
359 355 Role.non_member.add_permission! :view_private_notes
360 356 Mailer.issue_edit(journal).deliver
361 357 assert_include 'someone@foo.bar', ActionMailer::Base.deliveries.last.bcc.sort
362 358
363 359 Role.non_member.remove_permission! :view_private_notes
364 360 Mailer.issue_edit(journal).deliver
365 361 assert_not_include 'someone@foo.bar', ActionMailer::Base.deliveries.last.bcc.sort
366 362 end
367 363
368 364 def test_document_added
369 365 document = Document.find(1)
370 366 valid_languages.each do |lang|
371 367 Setting.default_language = lang.to_s
372 368 assert Mailer.document_added(document).deliver
373 369 end
374 370 end
375 371
376 372 def test_attachments_added
377 373 attachements = [ Attachment.find_by_container_type('Document') ]
378 374 valid_languages.each do |lang|
379 375 Setting.default_language = lang.to_s
380 376 assert Mailer.attachments_added(attachements).deliver
381 377 end
382 378 end
383 379
384 380 def test_version_file_added
385 381 attachements = [ Attachment.find_by_container_type('Version') ]
386 382 assert Mailer.attachments_added(attachements).deliver
387 383 assert_not_nil last_email.bcc
388 384 assert last_email.bcc.any?
389 385 assert_select_email do
390 386 assert_select "a[href=?]", "http://mydomain.foo/projects/ecookbook/files"
391 387 end
392 388 end
393 389
394 390 def test_project_file_added
395 391 attachements = [ Attachment.find_by_container_type('Project') ]
396 392 assert Mailer.attachments_added(attachements).deliver
397 393 assert_not_nil last_email.bcc
398 394 assert last_email.bcc.any?
399 395 assert_select_email do
400 396 assert_select "a[href=?]", "http://mydomain.foo/projects/ecookbook/files"
401 397 end
402 398 end
403 399
404 400 def test_news_added
405 401 news = News.first
406 402 valid_languages.each do |lang|
407 403 Setting.default_language = lang.to_s
408 404 assert Mailer.news_added(news).deliver
409 405 end
410 406 end
411 407
412 408 def test_news_comment_added
413 409 comment = Comment.find(2)
414 410 valid_languages.each do |lang|
415 411 Setting.default_language = lang.to_s
416 412 assert Mailer.news_comment_added(comment).deliver
417 413 end
418 414 end
419 415
420 416 def test_message_posted
421 417 message = Message.first
422 418 recipients = ([message.root] + message.root.children).collect {|m| m.author.mail if m.author}
423 419 recipients = recipients.compact.uniq
424 420 valid_languages.each do |lang|
425 421 Setting.default_language = lang.to_s
426 422 assert Mailer.message_posted(message).deliver
427 423 end
428 424 end
429 425
430 426 def test_wiki_content_added
431 427 content = WikiContent.find(1)
432 428 valid_languages.each do |lang|
433 429 Setting.default_language = lang.to_s
434 430 assert_difference 'ActionMailer::Base.deliveries.size' do
435 431 assert Mailer.wiki_content_added(content).deliver
436 432 assert_select_email do
437 433 assert_select 'a[href=?]',
438 434 'http://mydomain.foo/projects/ecookbook/wiki/CookBook_documentation',
439 435 :text => 'CookBook documentation'
440 436 end
441 437 end
442 438 end
443 439 end
444 440
445 441 def test_wiki_content_updated
446 442 content = WikiContent.find(1)
447 443 valid_languages.each do |lang|
448 444 Setting.default_language = lang.to_s
449 445 assert_difference 'ActionMailer::Base.deliveries.size' do
450 446 assert Mailer.wiki_content_updated(content).deliver
451 447 assert_select_email do
452 448 assert_select 'a[href=?]',
453 449 'http://mydomain.foo/projects/ecookbook/wiki/CookBook_documentation',
454 450 :text => 'CookBook documentation'
455 451 end
456 452 end
457 453 end
458 454 end
459 455
460 456 def test_account_information
461 457 user = User.find(2)
462 458 valid_languages.each do |lang|
463 459 user.update_attribute :language, lang.to_s
464 460 user.reload
465 461 assert Mailer.account_information(user, 'pAsswORd').deliver
466 462 end
467 463 end
468 464
469 465 def test_lost_password
470 466 token = Token.find(2)
471 467 valid_languages.each do |lang|
472 468 token.user.update_attribute :language, lang.to_s
473 469 token.reload
474 470 assert Mailer.lost_password(token).deliver
475 471 end
476 472 end
477 473
478 474 def test_register
479 475 token = Token.find(1)
480 476 Setting.host_name = 'redmine.foo'
481 477 Setting.protocol = 'https'
482 478
483 479 valid_languages.each do |lang|
484 480 token.user.update_attribute :language, lang.to_s
485 481 token.reload
486 482 ActionMailer::Base.deliveries.clear
487 483 assert Mailer.register(token).deliver
488 484 mail = last_email
489 485 assert_select_email do
490 486 assert_select "a[href=?]",
491 487 "https://redmine.foo/account/activate?token=#{token.value}",
492 488 :text => "https://redmine.foo/account/activate?token=#{token.value}"
493 489 end
494 490 end
495 491 end
496 492
497 493 def test_test
498 494 user = User.find(1)
499 495 valid_languages.each do |lang|
500 496 user.update_attribute :language, lang.to_s
501 497 assert Mailer.test_email(user).deliver
502 498 end
503 499 end
504 500
505 501 def test_reminders
506 502 Mailer.reminders(:days => 42)
507 503 assert_equal 1, ActionMailer::Base.deliveries.size
508 504 mail = last_email
509 505 assert mail.bcc.include?('dlopper@somenet.foo')
510 506 assert_mail_body_match 'Bug #3: Error 281 when updating a recipe', mail
511 507 assert_equal '1 issue(s) due in the next 42 days', mail.subject
512 508 end
513 509
514 510 def test_reminders_should_not_include_closed_issues
515 511 with_settings :default_language => 'en' do
516 512 Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 5,
517 513 :subject => 'Closed issue', :assigned_to_id => 3,
518 514 :due_date => 5.days.from_now,
519 515 :author_id => 2)
520 516 ActionMailer::Base.deliveries.clear
521 517
522 518 Mailer.reminders(:days => 42)
523 519 assert_equal 1, ActionMailer::Base.deliveries.size
524 520 mail = last_email
525 521 assert mail.bcc.include?('dlopper@somenet.foo')
526 522 assert_mail_body_no_match 'Closed issue', mail
527 523 end
528 524 end
529 525
530 526 def test_reminders_for_users
531 527 Mailer.reminders(:days => 42, :users => ['5'])
532 528 assert_equal 0, ActionMailer::Base.deliveries.size # No mail for dlopper
533 529 Mailer.reminders(:days => 42, :users => ['3'])
534 530 assert_equal 1, ActionMailer::Base.deliveries.size # No mail for dlopper
535 531 mail = last_email
536 532 assert mail.bcc.include?('dlopper@somenet.foo')
537 533 assert_mail_body_match 'Bug #3: Error 281 when updating a recipe', mail
538 534 end
539 535
540 536 def test_reminder_should_include_issues_assigned_to_groups
541 537 with_settings :default_language => 'en' do
542 538 group = Group.generate!
543 539 group.users << User.find(2)
544 540 group.users << User.find(3)
545 541
546 542 Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 1,
547 543 :subject => 'Assigned to group', :assigned_to => group,
548 544 :due_date => 5.days.from_now,
549 545 :author_id => 2)
550 546 ActionMailer::Base.deliveries.clear
551 547
552 548 Mailer.reminders(:days => 7)
553 549 assert_equal 2, ActionMailer::Base.deliveries.size
554 550 assert_equal %w(dlopper@somenet.foo jsmith@somenet.foo), ActionMailer::Base.deliveries.map(&:bcc).flatten.sort
555 551 ActionMailer::Base.deliveries.each do |mail|
556 552 assert_mail_body_match 'Assigned to group', mail
557 553 end
558 554 end
559 555 end
560 556
561 557 def test_mailer_should_not_change_locale
562 558 Setting.default_language = 'en'
563 559 # Set current language to italian
564 560 set_language_if_valid 'it'
565 561 # Send an email to a french user
566 562 user = User.find(1)
567 563 user.language = 'fr'
568 564 Mailer.account_activated(user).deliver
569 565 mail = last_email
570 566 assert_mail_body_match 'Votre compte', mail
571 567
572 568 assert_equal :it, current_language
573 569 end
574 570
575 571 def test_with_deliveries_off
576 572 Mailer.with_deliveries false do
577 573 Mailer.test_email(User.find(1)).deliver
578 574 end
579 575 assert ActionMailer::Base.deliveries.empty?
580 576 # should restore perform_deliveries
581 577 assert ActionMailer::Base.perform_deliveries
582 578 end
583 579
584 580 def test_layout_should_include_the_emails_header
585 581 with_settings :emails_header => "*Header content*" do
586 582 assert Mailer.test_email(User.find(1)).deliver
587 583 assert_select_email do
588 584 assert_select ".header" do
589 585 assert_select "strong", :text => "Header content"
590 586 end
591 587 end
592 588 end
593 589 end
594 590
595 591 def test_should_escape_html_templates_only
596 592 Issue.generate!(:project_id => 1, :tracker_id => 1, :subject => 'Subject with a <tag>')
597 593 mail = last_email
598 594 assert_equal 2, mail.parts.size
599 595 assert_include '<tag>', text_part.body.encoded
600 596 assert_include '&lt;tag&gt;', html_part.body.encoded
601 597 end
602 598
603 599 private
604 600
605 601 def last_email
606 602 mail = ActionMailer::Base.deliveries.last
607 603 assert_not_nil mail
608 604 mail
609 605 end
610 606
611 607 def text_part
612 608 last_email.parts.detect {|part| part.content_type.include?('text/plain')}
613 609 end
614 610
615 611 def html_part
616 612 last_email.parts.detect {|part| part.content_type.include?('text/html')}
617 613 end
618 614 end
@@ -1,132 +1,114
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2013 Jean-Philippe Lang
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; either version 2
9 9 # of the License, or (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 19
20 20 require File.expand_path('../../test_helper', __FILE__)
21 21
22 22 class PrincipalTest < ActiveSupport::TestCase
23 23 fixtures :users, :projects, :members, :member_roles
24 24
25 25 def test_active_scope_should_return_groups_and_active_users
26 26 result = Principal.active.all
27 27 assert_include Group.first, result
28 28 assert_not_nil result.detect {|p| p.is_a?(User)}
29 29 assert_nil result.detect {|p| p.is_a?(User) && !p.active?}
30 30 assert_nil result.detect {|p| p.is_a?(AnonymousUser)}
31 31 end
32 32
33 33 def test_member_of_scope_should_return_the_union_of_all_members
34 34 projects = Project.find_all_by_id(1, 2)
35 35 assert_equal projects.map(&:principals).flatten.sort, Principal.member_of(projects).sort
36 36 end
37 37
38 38 def test_member_of_scope_should_be_empty_for_no_projects
39 39 assert_equal [], Principal.member_of([]).sort
40 40 end
41 41
42 42 def test_not_member_of_scope_should_return_users_that_have_no_memberships
43 43 projects = Project.find_all_by_id(1, 2)
44 44 expected = (Principal.all - projects.map(&:memberships).flatten.map(&:principal)).sort
45 45 assert_equal expected, Principal.not_member_of(projects).sort
46 46 end
47 47
48 48 def test_not_member_of_scope_should_be_empty_for_no_projects
49 49 assert_equal [], Principal.not_member_of([]).sort
50 50 end
51 51
52 52 def test_sorted_scope_should_sort_users_before_groups
53 53 scope = Principal.where("type <> ?", 'AnonymousUser')
54 54 expected_order = scope.all.sort do |a, b|
55 55 if a.is_a?(User) && b.is_a?(Group)
56 56 -1
57 57 elsif a.is_a?(Group) && b.is_a?(User)
58 58 1
59 59 else
60 60 a.name.downcase <=> b.name.downcase
61 61 end
62 62 end
63 63 assert_equal expected_order.map(&:name).map(&:downcase), scope.sorted.all.map(&:name).map(&:downcase)
64 64 end
65 65
66 context "#like" do
67 setup do
68 Principal.create!(:login => 'login')
69 Principal.create!(:login => 'login2')
66 test "like scope should search login" do
67 results = Principal.like('jsmi')
70 68
71 Principal.create!(:firstname => 'firstname')
72 Principal.create!(:firstname => 'firstname2')
73
74 Principal.create!(:lastname => 'lastname')
75 Principal.create!(:lastname => 'lastname2')
76
77 Principal.create!(:mail => 'mail@example.com')
78 Principal.create!(:mail => 'mail2@example.com')
79
80 @palmer = Principal.create!(:firstname => 'David', :lastname => 'Palmer')
81 end
82
83 should "search login" do
84 results = Principal.like('login')
85
86 assert_equal 2, results.count
87 assert results.all? {|u| u.login.match(/login/) }
88 end
69 assert results.any?
70 assert results.all? {|u| u.login.match(/jsmi/i) }
71 end
89 72
90 should "search firstname" do
91 results = Principal.like('firstname')
73 test "like scope should search firstname" do
74 results = Principal.like('john')
92 75
93 assert_equal 2, results.count
94 assert results.all? {|u| u.firstname.match(/firstname/) }
95 end
76 assert results.any?
77 assert results.all? {|u| u.firstname.match(/john/i) }
78 end
96 79
97 should "search lastname" do
98 results = Principal.like('lastname')
80 test "like scope should search lastname" do
81 results = Principal.like('smi')
99 82
100 assert_equal 2, results.count
101 assert results.all? {|u| u.lastname.match(/lastname/) }
102 end
83 assert results.any?
84 assert results.all? {|u| u.lastname.match(/smi/i) }
85 end
103 86
104 should "search mail" do
105 results = Principal.like('mail')
87 test "like scope should search mail" do
88 results = Principal.like('somenet')
106 89
107 assert_equal 2, results.count
108 assert results.all? {|u| u.mail.match(/mail/) }
109 end
90 assert results.any?
91 assert results.all? {|u| u.mail.match(/somenet/i) }
92 end
110 93
111 should "search firstname and lastname" do
112 results = Principal.like('david palm')
94 test "like scope should search firstname and lastname" do
95 results = Principal.like('john smi')
113 96
114 assert_equal 1, results.count
115 assert_equal @palmer, results.first
116 end
97 assert_equal 1, results.count
98 assert_equal User.find(2), results.first
99 end
117 100
118 should "search lastname and firstname" do
119 results = Principal.like('palmer davi')
101 test "like scope should search lastname and firstname" do
102 results = Principal.like('smith joh')
120 103
121 assert_equal 1, results.count
122 assert_equal @palmer, results.first
123 end
104 assert_equal 1, results.count
105 assert_equal User.find(2), results.first
124 106 end
125 107
126 108 def test_like_scope_with_cyrillic_name
127 109 user = User.generate!(:firstname => 'Π‘ΠΎΠ±ΠΎΠ»Π΅Π²', :lastname => 'ДСнис')
128 110 results = Principal.like('Π‘ΠΎΠ±ΠΎ')
129 111 assert_equal 1, results.count
130 112 assert_equal user, results.first
131 113 end
132 114 end
@@ -1,1212 +1,1207
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class ProjectTest < ActiveSupport::TestCase
21 21 fixtures :projects, :trackers, :issue_statuses, :issues,
22 22 :journals, :journal_details,
23 23 :enumerations, :users, :issue_categories,
24 24 :projects_trackers,
25 25 :custom_fields,
26 26 :custom_fields_projects,
27 27 :custom_fields_trackers,
28 28 :custom_values,
29 29 :roles,
30 30 :member_roles,
31 31 :members,
32 32 :enabled_modules,
33 33 :workflows,
34 34 :versions,
35 35 :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions,
36 36 :groups_users,
37 37 :boards, :messages,
38 38 :repositories,
39 39 :news, :comments,
40 40 :documents
41 41
42 42 def setup
43 43 @ecookbook = Project.find(1)
44 44 @ecookbook_sub1 = Project.find(3)
45 45 set_tmp_attachments_directory
46 46 User.current = nil
47 47 end
48 48
49 49 def test_truth
50 50 assert_kind_of Project, @ecookbook
51 51 assert_equal "eCookbook", @ecookbook.name
52 52 end
53 53
54 54 def test_default_attributes
55 55 with_settings :default_projects_public => '1' do
56 56 assert_equal true, Project.new.is_public
57 57 assert_equal false, Project.new(:is_public => false).is_public
58 58 end
59 59
60 60 with_settings :default_projects_public => '0' do
61 61 assert_equal false, Project.new.is_public
62 62 assert_equal true, Project.new(:is_public => true).is_public
63 63 end
64 64
65 65 with_settings :sequential_project_identifiers => '1' do
66 66 assert !Project.new.identifier.blank?
67 67 assert Project.new(:identifier => '').identifier.blank?
68 68 end
69 69
70 70 with_settings :sequential_project_identifiers => '0' do
71 71 assert Project.new.identifier.blank?
72 72 assert !Project.new(:identifier => 'test').blank?
73 73 end
74 74
75 75 with_settings :default_projects_modules => ['issue_tracking', 'repository'] do
76 76 assert_equal ['issue_tracking', 'repository'], Project.new.enabled_module_names
77 77 end
78 78
79 79 assert_equal Tracker.all.sort, Project.new.trackers.sort
80 80 assert_equal Tracker.find(1, 3).sort, Project.new(:tracker_ids => [1, 3]).trackers.sort
81 81 end
82 82
83 83 def test_update
84 84 assert_equal "eCookbook", @ecookbook.name
85 85 @ecookbook.name = "eCook"
86 86 assert @ecookbook.save, @ecookbook.errors.full_messages.join("; ")
87 87 @ecookbook.reload
88 88 assert_equal "eCook", @ecookbook.name
89 89 end
90 90
91 91 def test_validate_identifier
92 92 to_test = {"abc" => true,
93 93 "ab12" => true,
94 94 "ab-12" => true,
95 95 "ab_12" => true,
96 96 "12" => false,
97 97 "new" => false}
98 98
99 99 to_test.each do |identifier, valid|
100 100 p = Project.new
101 101 p.identifier = identifier
102 102 p.valid?
103 103 if valid
104 104 assert p.errors['identifier'].blank?, "identifier #{identifier} was not valid"
105 105 else
106 106 assert p.errors['identifier'].present?, "identifier #{identifier} was valid"
107 107 end
108 108 end
109 109 end
110 110
111 111 def test_identifier_should_not_be_frozen_for_a_new_project
112 112 assert_equal false, Project.new.identifier_frozen?
113 113 end
114 114
115 115 def test_identifier_should_not_be_frozen_for_a_saved_project_with_blank_identifier
116 116 Project.update_all(["identifier = ''"], "id = 1")
117 117
118 118 assert_equal false, Project.find(1).identifier_frozen?
119 119 end
120 120
121 121 def test_identifier_should_be_frozen_for_a_saved_project_with_valid_identifier
122 122 assert_equal true, Project.find(1).identifier_frozen?
123 123 end
124 124
125 125 def test_members_should_be_active_users
126 126 Project.all.each do |project|
127 127 assert_nil project.members.detect {|m| !(m.user.is_a?(User) && m.user.active?) }
128 128 end
129 129 end
130 130
131 131 def test_users_should_be_active_users
132 132 Project.all.each do |project|
133 133 assert_nil project.users.detect {|u| !(u.is_a?(User) && u.active?) }
134 134 end
135 135 end
136 136
137 137 def test_open_scope_on_issues_association
138 138 assert_kind_of Issue, Project.find(1).issues.open.first
139 139 end
140 140
141 141 def test_archive
142 142 user = @ecookbook.members.first.user
143 143 @ecookbook.archive
144 144 @ecookbook.reload
145 145
146 146 assert !@ecookbook.active?
147 147 assert @ecookbook.archived?
148 148 assert !user.projects.include?(@ecookbook)
149 149 # Subproject are also archived
150 150 assert !@ecookbook.children.empty?
151 151 assert @ecookbook.descendants.active.empty?
152 152 end
153 153
154 154 def test_archive_should_fail_if_versions_are_used_by_non_descendant_projects
155 155 # Assign an issue of a project to a version of a child project
156 156 Issue.find(4).update_attribute :fixed_version_id, 4
157 157
158 158 assert_no_difference "Project.count(:all, :conditions => 'status = #{Project::STATUS_ARCHIVED}')" do
159 159 assert_equal false, @ecookbook.archive
160 160 end
161 161 @ecookbook.reload
162 162 assert @ecookbook.active?
163 163 end
164 164
165 165 def test_unarchive
166 166 user = @ecookbook.members.first.user
167 167 @ecookbook.archive
168 168 # A subproject of an archived project can not be unarchived
169 169 assert !@ecookbook_sub1.unarchive
170 170
171 171 # Unarchive project
172 172 assert @ecookbook.unarchive
173 173 @ecookbook.reload
174 174 assert @ecookbook.active?
175 175 assert !@ecookbook.archived?
176 176 assert user.projects.include?(@ecookbook)
177 177 # Subproject can now be unarchived
178 178 @ecookbook_sub1.reload
179 179 assert @ecookbook_sub1.unarchive
180 180 end
181 181
182 182 def test_destroy
183 183 # 2 active members
184 184 assert_equal 2, @ecookbook.members.size
185 185 # and 1 is locked
186 186 assert_equal 3, Member.where('project_id = ?', @ecookbook.id).all.size
187 187 # some boards
188 188 assert @ecookbook.boards.any?
189 189
190 190 @ecookbook.destroy
191 191 # make sure that the project non longer exists
192 192 assert_raise(ActiveRecord::RecordNotFound) { Project.find(@ecookbook.id) }
193 193 # make sure related data was removed
194 194 assert_nil Member.first(:conditions => {:project_id => @ecookbook.id})
195 195 assert_nil Board.first(:conditions => {:project_id => @ecookbook.id})
196 196 assert_nil Issue.first(:conditions => {:project_id => @ecookbook.id})
197 197 end
198 198
199 199 def test_destroy_should_destroy_subtasks
200 200 issues = (0..2).to_a.map {Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :subject => 'test')}
201 201 issues[0].update_attribute :parent_issue_id, issues[1].id
202 202 issues[2].update_attribute :parent_issue_id, issues[1].id
203 203 assert_equal 2, issues[1].children.count
204 204
205 205 assert_nothing_raised do
206 206 Project.find(1).destroy
207 207 end
208 208 assert Issue.find_all_by_id(issues.map(&:id)).empty?
209 209 end
210 210
211 211 def test_destroying_root_projects_should_clear_data
212 212 Project.roots.each do |root|
213 213 root.destroy
214 214 end
215 215
216 216 assert_equal 0, Project.count, "Projects were not deleted: #{Project.all.inspect}"
217 217 assert_equal 0, Member.count, "Members were not deleted: #{Member.all.inspect}"
218 218 assert_equal 0, MemberRole.count
219 219 assert_equal 0, Issue.count
220 220 assert_equal 0, Journal.count
221 221 assert_equal 0, JournalDetail.count
222 222 assert_equal 0, Attachment.count, "Attachments were not deleted: #{Attachment.all.inspect}"
223 223 assert_equal 0, EnabledModule.count
224 224 assert_equal 0, IssueCategory.count
225 225 assert_equal 0, IssueRelation.count
226 226 assert_equal 0, Board.count
227 227 assert_equal 0, Message.count
228 228 assert_equal 0, News.count
229 229 assert_equal 0, Query.count(:conditions => "project_id IS NOT NULL")
230 230 assert_equal 0, Repository.count
231 231 assert_equal 0, Changeset.count
232 232 assert_equal 0, Change.count
233 233 assert_equal 0, Comment.count
234 234 assert_equal 0, TimeEntry.count
235 235 assert_equal 0, Version.count
236 236 assert_equal 0, Watcher.count
237 237 assert_equal 0, Wiki.count
238 238 assert_equal 0, WikiPage.count
239 239 assert_equal 0, WikiContent.count
240 240 assert_equal 0, WikiContent::Version.count
241 241 assert_equal 0, Project.connection.select_all("SELECT * FROM projects_trackers").size
242 242 assert_equal 0, Project.connection.select_all("SELECT * FROM custom_fields_projects").size
243 243 assert_equal 0, CustomValue.count(:conditions => {:customized_type => ['Project', 'Issue', 'TimeEntry', 'Version']})
244 244 end
245 245
246 246 def test_move_an_orphan_project_to_a_root_project
247 247 sub = Project.find(2)
248 248 sub.set_parent! @ecookbook
249 249 assert_equal @ecookbook.id, sub.parent.id
250 250 @ecookbook.reload
251 251 assert_equal 4, @ecookbook.children.size
252 252 end
253 253
254 254 def test_move_an_orphan_project_to_a_subproject
255 255 sub = Project.find(2)
256 256 assert sub.set_parent!(@ecookbook_sub1)
257 257 end
258 258
259 259 def test_move_a_root_project_to_a_project
260 260 sub = @ecookbook
261 261 assert sub.set_parent!(Project.find(2))
262 262 end
263 263
264 264 def test_should_not_move_a_project_to_its_children
265 265 sub = @ecookbook
266 266 assert !(sub.set_parent!(Project.find(3)))
267 267 end
268 268
269 269 def test_set_parent_should_add_roots_in_alphabetical_order
270 270 ProjectCustomField.delete_all
271 271 Project.delete_all
272 272 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(nil)
273 273 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(nil)
274 274 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(nil)
275 275 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(nil)
276 276
277 277 assert_equal 4, Project.count
278 278 assert_equal Project.all.sort_by(&:name), Project.all.sort_by(&:lft)
279 279 end
280 280
281 281 def test_set_parent_should_add_children_in_alphabetical_order
282 282 ProjectCustomField.delete_all
283 283 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
284 284 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(parent)
285 285 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(parent)
286 286 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(parent)
287 287 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(parent)
288 288
289 289 parent.reload
290 290 assert_equal 4, parent.children.size
291 291 assert_equal parent.children.all.sort_by(&:name), parent.children.all
292 292 end
293 293
294 294 def test_set_parent_should_update_issue_fixed_version_associations_when_a_fixed_version_is_moved_out_of_the_hierarchy
295 295 # Parent issue with a hierarchy project's fixed version
296 296 parent_issue = Issue.find(1)
297 297 parent_issue.update_attribute(:fixed_version_id, 4)
298 298 parent_issue.reload
299 299 assert_equal 4, parent_issue.fixed_version_id
300 300
301 301 # Should keep fixed versions for the issues
302 302 issue_with_local_fixed_version = Issue.find(5)
303 303 issue_with_local_fixed_version.update_attribute(:fixed_version_id, 4)
304 304 issue_with_local_fixed_version.reload
305 305 assert_equal 4, issue_with_local_fixed_version.fixed_version_id
306 306
307 307 # Local issue with hierarchy fixed_version
308 308 issue_with_hierarchy_fixed_version = Issue.find(13)
309 309 issue_with_hierarchy_fixed_version.update_attribute(:fixed_version_id, 6)
310 310 issue_with_hierarchy_fixed_version.reload
311 311 assert_equal 6, issue_with_hierarchy_fixed_version.fixed_version_id
312 312
313 313 # Move project out of the issue's hierarchy
314 314 moved_project = Project.find(3)
315 315 moved_project.set_parent!(Project.find(2))
316 316 parent_issue.reload
317 317 issue_with_local_fixed_version.reload
318 318 issue_with_hierarchy_fixed_version.reload
319 319
320 320 assert_equal 4, issue_with_local_fixed_version.fixed_version_id, "Fixed version was not keep on an issue local to the moved project"
321 321 assert_equal nil, issue_with_hierarchy_fixed_version.fixed_version_id, "Fixed version is still set after moving the Project out of the hierarchy where the version is defined in"
322 322 assert_equal nil, parent_issue.fixed_version_id, "Fixed version is still set after moving the Version out of the hierarchy for the issue."
323 323 end
324 324
325 325 def test_parent
326 326 p = Project.find(6).parent
327 327 assert p.is_a?(Project)
328 328 assert_equal 5, p.id
329 329 end
330 330
331 331 def test_ancestors
332 332 a = Project.find(6).ancestors
333 333 assert a.first.is_a?(Project)
334 334 assert_equal [1, 5], a.collect(&:id)
335 335 end
336 336
337 337 def test_root
338 338 r = Project.find(6).root
339 339 assert r.is_a?(Project)
340 340 assert_equal 1, r.id
341 341 end
342 342
343 343 def test_children
344 344 c = Project.find(1).children
345 345 assert c.first.is_a?(Project)
346 346 assert_equal [5, 3, 4], c.collect(&:id)
347 347 end
348 348
349 349 def test_descendants
350 350 d = Project.find(1).descendants
351 351 assert d.first.is_a?(Project)
352 352 assert_equal [5, 6, 3, 4], d.collect(&:id)
353 353 end
354 354
355 355 def test_allowed_parents_should_be_empty_for_non_member_user
356 356 Role.non_member.add_permission!(:add_project)
357 357 user = User.find(9)
358 358 assert user.memberships.empty?
359 359 User.current = user
360 360 assert Project.new.allowed_parents.compact.empty?
361 361 end
362 362
363 363 def test_allowed_parents_with_add_subprojects_permission
364 364 Role.find(1).remove_permission!(:add_project)
365 365 Role.find(1).add_permission!(:add_subprojects)
366 366 User.current = User.find(2)
367 367 # new project
368 368 assert !Project.new.allowed_parents.include?(nil)
369 369 assert Project.new.allowed_parents.include?(Project.find(1))
370 370 # existing root project
371 371 assert Project.find(1).allowed_parents.include?(nil)
372 372 # existing child
373 373 assert Project.find(3).allowed_parents.include?(Project.find(1))
374 374 assert !Project.find(3).allowed_parents.include?(nil)
375 375 end
376 376
377 377 def test_allowed_parents_with_add_project_permission
378 378 Role.find(1).add_permission!(:add_project)
379 379 Role.find(1).remove_permission!(:add_subprojects)
380 380 User.current = User.find(2)
381 381 # new project
382 382 assert Project.new.allowed_parents.include?(nil)
383 383 assert !Project.new.allowed_parents.include?(Project.find(1))
384 384 # existing root project
385 385 assert Project.find(1).allowed_parents.include?(nil)
386 386 # existing child
387 387 assert Project.find(3).allowed_parents.include?(Project.find(1))
388 388 assert Project.find(3).allowed_parents.include?(nil)
389 389 end
390 390
391 391 def test_allowed_parents_with_add_project_and_subprojects_permission
392 392 Role.find(1).add_permission!(:add_project)
393 393 Role.find(1).add_permission!(:add_subprojects)
394 394 User.current = User.find(2)
395 395 # new project
396 396 assert Project.new.allowed_parents.include?(nil)
397 397 assert Project.new.allowed_parents.include?(Project.find(1))
398 398 # existing root project
399 399 assert Project.find(1).allowed_parents.include?(nil)
400 400 # existing child
401 401 assert Project.find(3).allowed_parents.include?(Project.find(1))
402 402 assert Project.find(3).allowed_parents.include?(nil)
403 403 end
404 404
405 405 def test_users_by_role
406 406 users_by_role = Project.find(1).users_by_role
407 407 assert_kind_of Hash, users_by_role
408 408 role = Role.find(1)
409 409 assert_kind_of Array, users_by_role[role]
410 410 assert users_by_role[role].include?(User.find(2))
411 411 end
412 412
413 413 def test_rolled_up_trackers
414 414 parent = Project.find(1)
415 415 parent.trackers = Tracker.find([1,2])
416 416 child = parent.children.find(3)
417 417
418 418 assert_equal [1, 2], parent.tracker_ids
419 419 assert_equal [2, 3], child.trackers.collect(&:id)
420 420
421 421 assert_kind_of Tracker, parent.rolled_up_trackers.first
422 422 assert_equal Tracker.find(1), parent.rolled_up_trackers.first
423 423
424 424 assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id)
425 425 assert_equal [2, 3], child.rolled_up_trackers.collect(&:id)
426 426 end
427 427
428 428 def test_rolled_up_trackers_should_ignore_archived_subprojects
429 429 parent = Project.find(1)
430 430 parent.trackers = Tracker.find([1,2])
431 431 child = parent.children.find(3)
432 432 child.trackers = Tracker.find([1,3])
433 433 parent.children.each(&:archive)
434 434
435 435 assert_equal [1,2], parent.rolled_up_trackers.collect(&:id)
436 436 end
437 437
438 context "#rolled_up_versions" do
439 setup do
440 @project = Project.generate!
441 @parent_version_1 = Version.generate!(:project => @project)
442 @parent_version_2 = Version.generate!(:project => @project)
443 end
444
445 should "include the versions for the current project" do
446 assert_same_elements [@parent_version_1, @parent_version_2], @project.rolled_up_versions
447 end
448
449 should "include versions for a subproject" do
450 @subproject = Project.generate!
451 @subproject.set_parent!(@project)
452 @subproject_version = Version.generate!(:project => @subproject)
453
454 assert_same_elements [
455 @parent_version_1,
456 @parent_version_2,
457 @subproject_version
458 ], @project.rolled_up_versions
459 end
460
461 should "include versions for a sub-subproject" do
462 @subproject = Project.generate!
463 @subproject.set_parent!(@project)
464 @sub_subproject = Project.generate!
465 @sub_subproject.set_parent!(@subproject)
466 @sub_subproject_version = Version.generate!(:project => @sub_subproject)
467
468 @project.reload
469
470 assert_same_elements [
471 @parent_version_1,
472 @parent_version_2,
473 @sub_subproject_version
474 ], @project.rolled_up_versions
475 end
438 test "#rolled_up_versions should include the versions for the current project" do
439 project = Project.generate!
440 parent_version_1 = Version.generate!(:project => project)
441 parent_version_2 = Version.generate!(:project => project)
442 assert_same_elements [parent_version_1, parent_version_2], project.rolled_up_versions
443 end
444
445 test "#rolled_up_versions should include versions for a subproject" do
446 project = Project.generate!
447 parent_version_1 = Version.generate!(:project => project)
448 parent_version_2 = Version.generate!(:project => project)
449 subproject = Project.generate_with_parent!(project)
450 subproject_version = Version.generate!(:project => subproject)
451
452 assert_same_elements [
453 parent_version_1,
454 parent_version_2,
455 subproject_version
456 ], project.rolled_up_versions
457 end
458
459 test "#rolled_up_versions should include versions for a sub-subproject" do
460 project = Project.generate!
461 parent_version_1 = Version.generate!(:project => project)
462 parent_version_2 = Version.generate!(:project => project)
463 subproject = Project.generate_with_parent!(project)
464 sub_subproject = Project.generate_with_parent!(subproject)
465 sub_subproject_version = Version.generate!(:project => sub_subproject)
466 project.reload
476 467
477 should "only check active projects" do
478 @subproject = Project.generate!
479 @subproject.set_parent!(@project)
480 @subproject_version = Version.generate!(:project => @subproject)
481 assert @subproject.archive
468 assert_same_elements [
469 parent_version_1,
470 parent_version_2,
471 sub_subproject_version
472 ], project.rolled_up_versions
473 end
482 474
483 @project.reload
475 test "#rolled_up_versions should only check active projects" do
476 project = Project.generate!
477 parent_version_1 = Version.generate!(:project => project)
478 parent_version_2 = Version.generate!(:project => project)
479 subproject = Project.generate_with_parent!(project)
480 subproject_version = Version.generate!(:project => subproject)
481 assert subproject.archive
482 project.reload
484 483
485 assert !@subproject.active?
486 assert_same_elements [@parent_version_1, @parent_version_2], @project.rolled_up_versions
487 end
484 assert !subproject.active?
485 assert_same_elements [parent_version_1, parent_version_2], project.rolled_up_versions
488 486 end
489 487
490 488 def test_shared_versions_none_sharing
491 489 p = Project.find(5)
492 490 v = Version.create!(:name => 'none_sharing', :project => p, :sharing => 'none')
493 491 assert p.shared_versions.include?(v)
494 492 assert !p.children.first.shared_versions.include?(v)
495 493 assert !p.root.shared_versions.include?(v)
496 494 assert !p.siblings.first.shared_versions.include?(v)
497 495 assert !p.root.siblings.first.shared_versions.include?(v)
498 496 end
499 497
500 498 def test_shared_versions_descendants_sharing
501 499 p = Project.find(5)
502 500 v = Version.create!(:name => 'descendants_sharing', :project => p, :sharing => 'descendants')
503 501 assert p.shared_versions.include?(v)
504 502 assert p.children.first.shared_versions.include?(v)
505 503 assert !p.root.shared_versions.include?(v)
506 504 assert !p.siblings.first.shared_versions.include?(v)
507 505 assert !p.root.siblings.first.shared_versions.include?(v)
508 506 end
509 507
510 508 def test_shared_versions_hierarchy_sharing
511 509 p = Project.find(5)
512 510 v = Version.create!(:name => 'hierarchy_sharing', :project => p, :sharing => 'hierarchy')
513 511 assert p.shared_versions.include?(v)
514 512 assert p.children.first.shared_versions.include?(v)
515 513 assert p.root.shared_versions.include?(v)
516 514 assert !p.siblings.first.shared_versions.include?(v)
517 515 assert !p.root.siblings.first.shared_versions.include?(v)
518 516 end
519 517
520 518 def test_shared_versions_tree_sharing
521 519 p = Project.find(5)
522 520 v = Version.create!(:name => 'tree_sharing', :project => p, :sharing => 'tree')
523 521 assert p.shared_versions.include?(v)
524 522 assert p.children.first.shared_versions.include?(v)
525 523 assert p.root.shared_versions.include?(v)
526 524 assert p.siblings.first.shared_versions.include?(v)
527 525 assert !p.root.siblings.first.shared_versions.include?(v)
528 526 end
529 527
530 528 def test_shared_versions_system_sharing
531 529 p = Project.find(5)
532 530 v = Version.create!(:name => 'system_sharing', :project => p, :sharing => 'system')
533 531 assert p.shared_versions.include?(v)
534 532 assert p.children.first.shared_versions.include?(v)
535 533 assert p.root.shared_versions.include?(v)
536 534 assert p.siblings.first.shared_versions.include?(v)
537 535 assert p.root.siblings.first.shared_versions.include?(v)
538 536 end
539 537
540 538 def test_shared_versions
541 539 parent = Project.find(1)
542 540 child = parent.children.find(3)
543 541 private_child = parent.children.find(5)
544 542
545 543 assert_equal [1,2,3], parent.version_ids.sort
546 544 assert_equal [4], child.version_ids
547 545 assert_equal [6], private_child.version_ids
548 546 assert_equal [7], Version.find_all_by_sharing('system').collect(&:id)
549 547
550 548 assert_equal 6, parent.shared_versions.size
551 549 parent.shared_versions.each do |version|
552 550 assert_kind_of Version, version
553 551 end
554 552
555 553 assert_equal [1,2,3,4,6,7], parent.shared_versions.collect(&:id).sort
556 554 end
557 555
558 556 def test_shared_versions_should_ignore_archived_subprojects
559 557 parent = Project.find(1)
560 558 child = parent.children.find(3)
561 559 child.archive
562 560 parent.reload
563 561
564 562 assert_equal [1,2,3], parent.version_ids.sort
565 563 assert_equal [4], child.version_ids
566 564 assert !parent.shared_versions.collect(&:id).include?(4)
567 565 end
568 566
569 567 def test_shared_versions_visible_to_user
570 568 user = User.find(3)
571 569 parent = Project.find(1)
572 570 child = parent.children.find(5)
573 571
574 572 assert_equal [1,2,3], parent.version_ids.sort
575 573 assert_equal [6], child.version_ids
576 574
577 575 versions = parent.shared_versions.visible(user)
578 576
579 577 assert_equal 4, versions.size
580 578 versions.each do |version|
581 579 assert_kind_of Version, version
582 580 end
583 581
584 582 assert !versions.collect(&:id).include?(6)
585 583 end
586 584
587 585 def test_shared_versions_for_new_project_should_include_system_shared_versions
588 586 p = Project.find(5)
589 587 v = Version.create!(:name => 'system_sharing', :project => p, :sharing => 'system')
590 588
591 589 assert_include v, Project.new.shared_versions
592 590 end
593 591
594 592 def test_next_identifier
595 593 ProjectCustomField.delete_all
596 594 Project.create!(:name => 'last', :identifier => 'p2008040')
597 595 assert_equal 'p2008041', Project.next_identifier
598 596 end
599 597
600 598 def test_next_identifier_first_project
601 599 Project.delete_all
602 600 assert_nil Project.next_identifier
603 601 end
604 602
605 603 def test_enabled_module_names
606 604 with_settings :default_projects_modules => ['issue_tracking', 'repository'] do
607 605 project = Project.new
608 606
609 607 project.enabled_module_names = %w(issue_tracking news)
610 608 assert_equal %w(issue_tracking news), project.enabled_module_names.sort
611 609 end
612 610 end
613 611
614 context "enabled_modules" do
615 setup do
616 @project = Project.find(1)
617 end
618
619 should "define module by names and preserve ids" do
620 # Remove one module
621 modules = @project.enabled_modules.slice(0..-2)
622 assert modules.any?
623 assert_difference 'EnabledModule.count', -1 do
624 @project.enabled_module_names = modules.collect(&:name)
625 end
626 @project.reload
627 # Ids should be preserved
628 assert_equal @project.enabled_module_ids.sort, modules.collect(&:id).sort
629 end
630
631 should "enable a module" do
632 @project.enabled_module_names = []
633 @project.reload
634 assert_equal [], @project.enabled_module_names
635 #with string
636 @project.enable_module!("issue_tracking")
637 assert_equal ["issue_tracking"], @project.enabled_module_names
638 #with symbol
639 @project.enable_module!(:gantt)
640 assert_equal ["issue_tracking", "gantt"], @project.enabled_module_names
641 #don't add a module twice
642 @project.enable_module!("issue_tracking")
643 assert_equal ["issue_tracking", "gantt"], @project.enabled_module_names
644 end
645
646 should "disable a module" do
647 #with string
648 assert @project.enabled_module_names.include?("issue_tracking")
649 @project.disable_module!("issue_tracking")
650 assert ! @project.reload.enabled_module_names.include?("issue_tracking")
651 #with symbol
652 assert @project.enabled_module_names.include?("gantt")
653 @project.disable_module!(:gantt)
654 assert ! @project.reload.enabled_module_names.include?("gantt")
655 #with EnabledModule object
656 first_module = @project.enabled_modules.first
657 @project.disable_module!(first_module)
658 assert ! @project.reload.enabled_module_names.include?(first_module.name)
612 test "enabled_modules should define module by names and preserve ids" do
613 @project = Project.find(1)
614 # Remove one module
615 modules = @project.enabled_modules.slice(0..-2)
616 assert modules.any?
617 assert_difference 'EnabledModule.count', -1 do
618 @project.enabled_module_names = modules.collect(&:name)
659 619 end
620 @project.reload
621 # Ids should be preserved
622 assert_equal @project.enabled_module_ids.sort, modules.collect(&:id).sort
623 end
624
625 test "enabled_modules should enable a module" do
626 @project = Project.find(1)
627 @project.enabled_module_names = []
628 @project.reload
629 assert_equal [], @project.enabled_module_names
630 #with string
631 @project.enable_module!("issue_tracking")
632 assert_equal ["issue_tracking"], @project.enabled_module_names
633 #with symbol
634 @project.enable_module!(:gantt)
635 assert_equal ["issue_tracking", "gantt"], @project.enabled_module_names
636 #don't add a module twice
637 @project.enable_module!("issue_tracking")
638 assert_equal ["issue_tracking", "gantt"], @project.enabled_module_names
639 end
640
641 test "enabled_modules should disable a module" do
642 @project = Project.find(1)
643 #with string
644 assert @project.enabled_module_names.include?("issue_tracking")
645 @project.disable_module!("issue_tracking")
646 assert ! @project.reload.enabled_module_names.include?("issue_tracking")
647 #with symbol
648 assert @project.enabled_module_names.include?("gantt")
649 @project.disable_module!(:gantt)
650 assert ! @project.reload.enabled_module_names.include?("gantt")
651 #with EnabledModule object
652 first_module = @project.enabled_modules.first
653 @project.disable_module!(first_module)
654 assert ! @project.reload.enabled_module_names.include?(first_module.name)
660 655 end
661 656
662 657 def test_enabled_module_names_should_not_recreate_enabled_modules
663 658 project = Project.find(1)
664 659 # Remove one module
665 660 modules = project.enabled_modules.slice(0..-2)
666 661 assert modules.any?
667 662 assert_difference 'EnabledModule.count', -1 do
668 663 project.enabled_module_names = modules.collect(&:name)
669 664 end
670 665 project.reload
671 666 # Ids should be preserved
672 667 assert_equal project.enabled_module_ids.sort, modules.collect(&:id).sort
673 668 end
674 669
675 670 def test_copy_from_existing_project
676 671 source_project = Project.find(1)
677 672 copied_project = Project.copy_from(1)
678 673
679 674 assert copied_project
680 675 # Cleared attributes
681 676 assert copied_project.id.blank?
682 677 assert copied_project.name.blank?
683 678 assert copied_project.identifier.blank?
684 679
685 680 # Duplicated attributes
686 681 assert_equal source_project.description, copied_project.description
687 682 assert_equal source_project.enabled_modules, copied_project.enabled_modules
688 683 assert_equal source_project.trackers, copied_project.trackers
689 684
690 685 # Default attributes
691 686 assert_equal 1, copied_project.status
692 687 end
693 688
694 689 def test_activities_should_use_the_system_activities
695 690 project = Project.find(1)
696 691 assert_equal project.activities, TimeEntryActivity.where(:active => true).all
697 692 end
698 693
699 694
700 695 def test_activities_should_use_the_project_specific_activities
701 696 project = Project.find(1)
702 697 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project})
703 698 assert overridden_activity.save!
704 699
705 700 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
706 701 end
707 702
708 703 def test_activities_should_not_include_the_inactive_project_specific_activities
709 704 project = Project.find(1)
710 705 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.first, :active => false})
711 706 assert overridden_activity.save!
712 707
713 708 assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity found"
714 709 end
715 710
716 711 def test_activities_should_not_include_project_specific_activities_from_other_projects
717 712 project = Project.find(1)
718 713 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(2)})
719 714 assert overridden_activity.save!
720 715
721 716 assert !project.activities.include?(overridden_activity), "Project specific Activity found on a different project"
722 717 end
723 718
724 719 def test_activities_should_handle_nils
725 720 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(1), :parent => TimeEntryActivity.first})
726 721 TimeEntryActivity.delete_all
727 722
728 723 # No activities
729 724 project = Project.find(1)
730 725 assert project.activities.empty?
731 726
732 727 # No system, one overridden
733 728 assert overridden_activity.save!
734 729 project.reload
735 730 assert_equal [overridden_activity], project.activities
736 731 end
737 732
738 733 def test_activities_should_override_system_activities_with_project_activities
739 734 project = Project.find(1)
740 735 parent_activity = TimeEntryActivity.first
741 736 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => parent_activity})
742 737 assert overridden_activity.save!
743 738
744 739 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
745 740 assert !project.activities.include?(parent_activity), "System Activity found when it should have been overridden"
746 741 end
747 742
748 743 def test_activities_should_include_inactive_activities_if_specified
749 744 project = Project.find(1)
750 745 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.first, :active => false})
751 746 assert overridden_activity.save!
752 747
753 748 assert project.activities(true).include?(overridden_activity), "Inactive Project specific Activity not found"
754 749 end
755 750
756 751 test 'activities should not include active System activities if the project has an override that is inactive' do
757 752 project = Project.find(1)
758 753 system_activity = TimeEntryActivity.find_by_name('Design')
759 754 assert system_activity.active?
760 755 overridden_activity = TimeEntryActivity.create!(:name => "Project", :project => project, :parent => system_activity, :active => false)
761 756 assert overridden_activity.save!
762 757
763 758 assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity not found"
764 759 assert !project.activities.include?(system_activity), "System activity found when the project has an inactive override"
765 760 end
766 761
767 762 def test_close_completed_versions
768 763 Version.update_all("status = 'open'")
769 764 project = Project.find(1)
770 765 assert_not_nil project.versions.detect {|v| v.completed? && v.status == 'open'}
771 766 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
772 767 project.close_completed_versions
773 768 project.reload
774 769 assert_nil project.versions.detect {|v| v.completed? && v.status != 'closed'}
775 770 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
776 771 end
777 772
778 773 context "Project#copy" do
779 774 setup do
780 775 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
781 776 Project.destroy_all :identifier => "copy-test"
782 777 @source_project = Project.find(2)
783 778 @project = Project.new(:name => 'Copy Test', :identifier => 'copy-test')
784 779 @project.trackers = @source_project.trackers
785 780 @project.enabled_module_names = @source_project.enabled_modules.collect(&:name)
786 781 end
787 782
788 783 should "copy issues" do
789 784 @source_project.issues << Issue.generate!(:status => IssueStatus.find_by_name('Closed'),
790 785 :subject => "copy issue status",
791 786 :tracker_id => 1,
792 787 :assigned_to_id => 2,
793 788 :project_id => @source_project.id)
794 789 assert @project.valid?
795 790 assert @project.issues.empty?
796 791 assert @project.copy(@source_project)
797 792
798 793 assert_equal @source_project.issues.size, @project.issues.size
799 794 @project.issues.each do |issue|
800 795 assert issue.valid?
801 796 assert ! issue.assigned_to.blank?
802 797 assert_equal @project, issue.project
803 798 end
804 799
805 800 copied_issue = @project.issues.first(:conditions => {:subject => "copy issue status"})
806 801 assert copied_issue
807 802 assert copied_issue.status
808 803 assert_equal "Closed", copied_issue.status.name
809 804 end
810 805
811 806 should "copy issues assigned to a locked version" do
812 807 User.current = User.find(1)
813 808 assigned_version = Version.generate!(:name => "Assigned Issues")
814 809 @source_project.versions << assigned_version
815 810 Issue.generate!(:project => @source_project,
816 811 :fixed_version_id => assigned_version.id,
817 812 :subject => "copy issues assigned to a locked version")
818 813 assigned_version.update_attribute :status, 'locked'
819 814
820 815 assert @project.copy(@source_project)
821 816 @project.reload
822 817 copied_issue = @project.issues.first(:conditions => {:subject => "copy issues assigned to a locked version"})
823 818
824 819 assert copied_issue
825 820 assert copied_issue.fixed_version
826 821 assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name
827 822 assert_equal 'locked', copied_issue.fixed_version.status
828 823 end
829 824
830 825 should "change the new issues to use the copied version" do
831 826 User.current = User.find(1)
832 827 assigned_version = Version.generate!(:name => "Assigned Issues", :status => 'open')
833 828 @source_project.versions << assigned_version
834 829 assert_equal 3, @source_project.versions.size
835 830 Issue.generate!(:project => @source_project,
836 831 :fixed_version_id => assigned_version.id,
837 832 :subject => "change the new issues to use the copied version")
838 833
839 834 assert @project.copy(@source_project)
840 835 @project.reload
841 836 copied_issue = @project.issues.first(:conditions => {:subject => "change the new issues to use the copied version"})
842 837
843 838 assert copied_issue
844 839 assert copied_issue.fixed_version
845 840 assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name
846 841 assert_not_equal assigned_version.id, copied_issue.fixed_version.id # Different record
847 842 end
848 843
849 844 should "keep target shared versions from other project" do
850 845 assigned_version = Version.generate!(:name => "Assigned Issues", :status => 'open', :project_id => 1, :sharing => 'system')
851 846 issue = Issue.generate!(:project => @source_project,
852 847 :fixed_version => assigned_version,
853 848 :subject => "keep target shared versions")
854 849
855 850 assert @project.copy(@source_project)
856 851 @project.reload
857 852 copied_issue = @project.issues.first(:conditions => {:subject => "keep target shared versions"})
858 853
859 854 assert copied_issue
860 855 assert_equal assigned_version, copied_issue.fixed_version
861 856 end
862 857
863 858 should "copy issue relations" do
864 859 Setting.cross_project_issue_relations = '1'
865 860
866 861 second_issue = Issue.generate!(:status_id => 5,
867 862 :subject => "copy issue relation",
868 863 :tracker_id => 1,
869 864 :assigned_to_id => 2,
870 865 :project_id => @source_project.id)
871 866 source_relation = IssueRelation.create!(:issue_from => Issue.find(4),
872 867 :issue_to => second_issue,
873 868 :relation_type => "relates")
874 869 source_relation_cross_project = IssueRelation.create!(:issue_from => Issue.find(1),
875 870 :issue_to => second_issue,
876 871 :relation_type => "duplicates")
877 872
878 873 assert @project.copy(@source_project)
879 874 assert_equal @source_project.issues.count, @project.issues.count
880 875 copied_issue = @project.issues.find_by_subject("Issue on project 2") # Was #4
881 876 copied_second_issue = @project.issues.find_by_subject("copy issue relation")
882 877
883 878 # First issue with a relation on project
884 879 assert_equal 1, copied_issue.relations.size, "Relation not copied"
885 880 copied_relation = copied_issue.relations.first
886 881 assert_equal "relates", copied_relation.relation_type
887 882 assert_equal copied_second_issue.id, copied_relation.issue_to_id
888 883 assert_not_equal source_relation.id, copied_relation.id
889 884
890 885 # Second issue with a cross project relation
891 886 assert_equal 2, copied_second_issue.relations.size, "Relation not copied"
892 887 copied_relation = copied_second_issue.relations.select {|r| r.relation_type == 'duplicates'}.first
893 888 assert_equal "duplicates", copied_relation.relation_type
894 889 assert_equal 1, copied_relation.issue_from_id, "Cross project relation not kept"
895 890 assert_not_equal source_relation_cross_project.id, copied_relation.id
896 891 end
897 892
898 893 should "copy issue attachments" do
899 894 issue = Issue.generate!(:subject => "copy with attachment", :tracker_id => 1, :project_id => @source_project.id)
900 895 Attachment.create!(:container => issue, :file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 1)
901 896 @source_project.issues << issue
902 897 assert @project.copy(@source_project)
903 898
904 899 copied_issue = @project.issues.first(:conditions => {:subject => "copy with attachment"})
905 900 assert_not_nil copied_issue
906 901 assert_equal 1, copied_issue.attachments.count, "Attachment not copied"
907 902 assert_equal "testfile.txt", copied_issue.attachments.first.filename
908 903 end
909 904
910 905 should "copy memberships" do
911 906 assert @project.valid?
912 907 assert @project.members.empty?
913 908 assert @project.copy(@source_project)
914 909
915 910 assert_equal @source_project.memberships.size, @project.memberships.size
916 911 @project.memberships.each do |membership|
917 912 assert membership
918 913 assert_equal @project, membership.project
919 914 end
920 915 end
921 916
922 917 should "copy memberships with groups and additional roles" do
923 918 group = Group.create!(:lastname => "Copy group")
924 919 user = User.find(7)
925 920 group.users << user
926 921 # group role
927 922 Member.create!(:project_id => @source_project.id, :principal => group, :role_ids => [2])
928 923 member = Member.find_by_user_id_and_project_id(user.id, @source_project.id)
929 924 # additional role
930 925 member.role_ids = [1]
931 926
932 927 assert @project.copy(@source_project)
933 928 member = Member.find_by_user_id_and_project_id(user.id, @project.id)
934 929 assert_not_nil member
935 930 assert_equal [1, 2], member.role_ids.sort
936 931 end
937 932
938 933 should "copy project specific queries" do
939 934 assert @project.valid?
940 935 assert @project.queries.empty?
941 936 assert @project.copy(@source_project)
942 937
943 938 assert_equal @source_project.queries.size, @project.queries.size
944 939 @project.queries.each do |query|
945 940 assert query
946 941 assert_equal @project, query.project
947 942 end
948 943 assert_equal @source_project.queries.map(&:user_id).sort, @project.queries.map(&:user_id).sort
949 944 end
950 945
951 946 should "copy versions" do
952 947 @source_project.versions << Version.generate!
953 948 @source_project.versions << Version.generate!
954 949
955 950 assert @project.versions.empty?
956 951 assert @project.copy(@source_project)
957 952
958 953 assert_equal @source_project.versions.size, @project.versions.size
959 954 @project.versions.each do |version|
960 955 assert version
961 956 assert_equal @project, version.project
962 957 end
963 958 end
964 959
965 960 should "copy wiki" do
966 961 assert_difference 'Wiki.count' do
967 962 assert @project.copy(@source_project)
968 963 end
969 964
970 965 assert @project.wiki
971 966 assert_not_equal @source_project.wiki, @project.wiki
972 967 assert_equal "Start page", @project.wiki.start_page
973 968 end
974 969
975 970 should "copy wiki pages and content with hierarchy" do
976 971 assert_difference 'WikiPage.count', @source_project.wiki.pages.size do
977 972 assert @project.copy(@source_project)
978 973 end
979 974
980 975 assert @project.wiki
981 976 assert_equal @source_project.wiki.pages.size, @project.wiki.pages.size
982 977
983 978 @project.wiki.pages.each do |wiki_page|
984 979 assert wiki_page.content
985 980 assert !@source_project.wiki.pages.include?(wiki_page)
986 981 end
987 982
988 983 parent = @project.wiki.find_page('Parent_page')
989 984 child1 = @project.wiki.find_page('Child_page_1')
990 985 child2 = @project.wiki.find_page('Child_page_2')
991 986 assert_equal parent, child1.parent
992 987 assert_equal parent, child2.parent
993 988 end
994 989
995 990 should "copy issue categories" do
996 991 assert @project.copy(@source_project)
997 992
998 993 assert_equal 2, @project.issue_categories.size
999 994 @project.issue_categories.each do |issue_category|
1000 995 assert !@source_project.issue_categories.include?(issue_category)
1001 996 end
1002 997 end
1003 998
1004 999 should "copy boards" do
1005 1000 assert @project.copy(@source_project)
1006 1001
1007 1002 assert_equal 1, @project.boards.size
1008 1003 @project.boards.each do |board|
1009 1004 assert !@source_project.boards.include?(board)
1010 1005 end
1011 1006 end
1012 1007
1013 1008 should "change the new issues to use the copied issue categories" do
1014 1009 issue = Issue.find(4)
1015 1010 issue.update_attribute(:category_id, 3)
1016 1011
1017 1012 assert @project.copy(@source_project)
1018 1013
1019 1014 @project.issues.each do |issue|
1020 1015 assert issue.category
1021 1016 assert_equal "Stock management", issue.category.name # Same name
1022 1017 assert_not_equal IssueCategory.find(3), issue.category # Different record
1023 1018 end
1024 1019 end
1025 1020
1026 1021 should "limit copy with :only option" do
1027 1022 assert @project.members.empty?
1028 1023 assert @project.issue_categories.empty?
1029 1024 assert @source_project.issues.any?
1030 1025
1031 1026 assert @project.copy(@source_project, :only => ['members', 'issue_categories'])
1032 1027
1033 1028 assert @project.members.any?
1034 1029 assert @project.issue_categories.any?
1035 1030 assert @project.issues.empty?
1036 1031 end
1037 1032 end
1038 1033
1039 1034 def test_copy_should_copy_subtasks
1040 1035 source = Project.generate!(:tracker_ids => [1])
1041 1036 issue = Issue.generate_with_descendants!(:project => source)
1042 1037 project = Project.new(:name => 'Copy', :identifier => 'copy', :tracker_ids => [1])
1043 1038
1044 1039 assert_difference 'Project.count' do
1045 1040 assert_difference 'Issue.count', 1+issue.descendants.count do
1046 1041 assert project.copy(source.reload)
1047 1042 end
1048 1043 end
1049 1044 copy = Issue.where(:parent_id => nil).order("id DESC").first
1050 1045 assert_equal project, copy.project
1051 1046 assert_equal issue.descendants.count, copy.descendants.count
1052 1047 child_copy = copy.children.detect {|c| c.subject == 'Child1'}
1053 1048 assert child_copy.descendants.any?
1054 1049 end
1055 1050
1056 1051 context "#start_date" do
1057 1052 setup do
1058 1053 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
1059 1054 @project = Project.generate!(:identifier => 'test0')
1060 1055 @project.trackers << Tracker.generate!
1061 1056 end
1062 1057
1063 1058 should "be nil if there are no issues on the project" do
1064 1059 assert_nil @project.start_date
1065 1060 end
1066 1061
1067 1062 should "be tested when issues have no start date"
1068 1063
1069 1064 should "be the earliest start date of it's issues" do
1070 1065 early = 7.days.ago.to_date
1071 1066 Issue.generate!(:project => @project, :start_date => Date.today)
1072 1067 Issue.generate!(:project => @project, :start_date => early)
1073 1068
1074 1069 assert_equal early, @project.start_date
1075 1070 end
1076 1071
1077 1072 end
1078 1073
1079 1074 context "#due_date" do
1080 1075 setup do
1081 1076 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
1082 1077 @project = Project.generate!(:identifier => 'test0')
1083 1078 @project.trackers << Tracker.generate!
1084 1079 end
1085 1080
1086 1081 should "be nil if there are no issues on the project" do
1087 1082 assert_nil @project.due_date
1088 1083 end
1089 1084
1090 1085 should "be tested when issues have no due date"
1091 1086
1092 1087 should "be the latest due date of it's issues" do
1093 1088 future = 7.days.from_now.to_date
1094 1089 Issue.generate!(:project => @project, :due_date => future)
1095 1090 Issue.generate!(:project => @project, :due_date => Date.today)
1096 1091
1097 1092 assert_equal future, @project.due_date
1098 1093 end
1099 1094
1100 1095 should "be the latest due date of it's versions" do
1101 1096 future = 7.days.from_now.to_date
1102 1097 @project.versions << Version.generate!(:effective_date => future)
1103 1098 @project.versions << Version.generate!(:effective_date => Date.today)
1104 1099
1105 1100
1106 1101 assert_equal future, @project.due_date
1107 1102
1108 1103 end
1109 1104
1110 1105 should "pick the latest date from it's issues and versions" do
1111 1106 future = 7.days.from_now.to_date
1112 1107 far_future = 14.days.from_now.to_date
1113 1108 Issue.generate!(:project => @project, :due_date => far_future)
1114 1109 @project.versions << Version.generate!(:effective_date => future)
1115 1110
1116 1111 assert_equal far_future, @project.due_date
1117 1112 end
1118 1113
1119 1114 end
1120 1115
1121 1116 context "Project#completed_percent" do
1122 1117 setup do
1123 1118 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
1124 1119 @project = Project.generate!(:identifier => 'test0')
1125 1120 @project.trackers << Tracker.generate!
1126 1121 end
1127 1122
1128 1123 context "no versions" do
1129 1124 should "be 100" do
1130 1125 assert_equal 100, @project.completed_percent
1131 1126 end
1132 1127 end
1133 1128
1134 1129 context "with versions" do
1135 1130 should "return 0 if the versions have no issues" do
1136 1131 Version.generate!(:project => @project)
1137 1132 Version.generate!(:project => @project)
1138 1133
1139 1134 assert_equal 0, @project.completed_percent
1140 1135 end
1141 1136
1142 1137 should "return 100 if the version has only closed issues" do
1143 1138 v1 = Version.generate!(:project => @project)
1144 1139 Issue.generate!(:project => @project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v1)
1145 1140 v2 = Version.generate!(:project => @project)
1146 1141 Issue.generate!(:project => @project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v2)
1147 1142
1148 1143 assert_equal 100, @project.completed_percent
1149 1144 end
1150 1145
1151 1146 should "return the averaged completed percent of the versions (not weighted)" do
1152 1147 v1 = Version.generate!(:project => @project)
1153 1148 Issue.generate!(:project => @project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v1)
1154 1149 v2 = Version.generate!(:project => @project)
1155 1150 Issue.generate!(:project => @project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v2)
1156 1151
1157 1152 assert_equal 50, @project.completed_percent
1158 1153 end
1159 1154
1160 1155 end
1161 1156 end
1162 1157
1163 1158 context "#notified_users" do
1164 1159 setup do
1165 1160 @project = Project.generate!
1166 1161 @role = Role.generate!
1167 1162
1168 1163 @user_with_membership_notification = User.generate!(:mail_notification => 'selected')
1169 1164 Member.create!(:project => @project, :roles => [@role], :principal => @user_with_membership_notification, :mail_notification => true)
1170 1165
1171 1166 @all_events_user = User.generate!(:mail_notification => 'all')
1172 1167 Member.create!(:project => @project, :roles => [@role], :principal => @all_events_user)
1173 1168
1174 1169 @no_events_user = User.generate!(:mail_notification => 'none')
1175 1170 Member.create!(:project => @project, :roles => [@role], :principal => @no_events_user)
1176 1171
1177 1172 @only_my_events_user = User.generate!(:mail_notification => 'only_my_events')
1178 1173 Member.create!(:project => @project, :roles => [@role], :principal => @only_my_events_user)
1179 1174
1180 1175 @only_assigned_user = User.generate!(:mail_notification => 'only_assigned')
1181 1176 Member.create!(:project => @project, :roles => [@role], :principal => @only_assigned_user)
1182 1177
1183 1178 @only_owned_user = User.generate!(:mail_notification => 'only_owner')
1184 1179 Member.create!(:project => @project, :roles => [@role], :principal => @only_owned_user)
1185 1180 end
1186 1181
1187 1182 should "include members with a mail notification" do
1188 1183 assert @project.notified_users.include?(@user_with_membership_notification)
1189 1184 end
1190 1185
1191 1186 should "include users with the 'all' notification option" do
1192 1187 assert @project.notified_users.include?(@all_events_user)
1193 1188 end
1194 1189
1195 1190 should "not include users with the 'none' notification option" do
1196 1191 assert !@project.notified_users.include?(@no_events_user)
1197 1192 end
1198 1193
1199 1194 should "not include users with the 'only_my_events' notification option" do
1200 1195 assert !@project.notified_users.include?(@only_my_events_user)
1201 1196 end
1202 1197
1203 1198 should "not include users with the 'only_assigned' notification option" do
1204 1199 assert !@project.notified_users.include?(@only_assigned_user)
1205 1200 end
1206 1201
1207 1202 should "not include users with the 'only_owner' notification option" do
1208 1203 assert !@project.notified_users.include?(@only_owned_user)
1209 1204 end
1210 1205 end
1211 1206
1212 1207 end
General Comments 0
You need to be logged in to leave comments. Login now