##// END OF EJS Templates
Remove Issue.generate_for_project! test helper and use Issue.generate! instead....
Jean-Philippe Lang -
r10400:8bde60dc56e4
parent child
Show More
@@ -1,132 +1,121
1 1 module ObjectHelpers
2 2 def User.generate!(attributes={})
3 3 @generated_user_login ||= 'user0'
4 4 @generated_user_login.succ!
5 5 user = User.new(attributes)
6 6 user.login = @generated_user_login if user.login.blank?
7 7 user.mail = "#{@generated_user_login}@example.com" if user.mail.blank?
8 8 user.firstname = "Bob" if user.firstname.blank?
9 9 user.lastname = "Doe" if user.lastname.blank?
10 10 yield user if block_given?
11 11 user.save!
12 12 user
13 13 end
14 14
15 15 def User.add_to_project(user, project, roles=nil)
16 16 roles = Role.find(1) if roles.nil?
17 17 roles = [roles] unless roles.is_a?(Array)
18 18 Member.create!(:principal => user, :project => project, :roles => roles)
19 19 end
20 20
21 21 def Group.generate!(attributes={})
22 22 @generated_group_name ||= 'Group 0'
23 23 @generated_group_name.succ!
24 24 group = Group.new(attributes)
25 25 group.name = @generated_group_name if group.name.blank?
26 26 yield group if block_given?
27 27 group.save!
28 28 group
29 29 end
30 30
31 31 def Project.generate!(attributes={})
32 32 @generated_project_identifier ||= 'project-0000'
33 33 @generated_project_identifier.succ!
34 34 project = Project.new(attributes)
35 35 project.name = @generated_project_identifier if project.name.blank?
36 36 project.identifier = @generated_project_identifier if project.identifier.blank?
37 37 yield project if block_given?
38 38 project.save!
39 39 project
40 40 end
41 41
42 42 def Tracker.generate!(attributes={})
43 43 @generated_tracker_name ||= 'Tracker 0'
44 44 @generated_tracker_name.succ!
45 45 tracker = Tracker.new(attributes)
46 46 tracker.name = @generated_tracker_name if tracker.name.blank?
47 47 yield tracker if block_given?
48 48 tracker.save!
49 49 tracker
50 50 end
51 51
52 52 def Role.generate!(attributes={})
53 53 @generated_role_name ||= 'Role 0'
54 54 @generated_role_name.succ!
55 55 role = Role.new(attributes)
56 56 role.name = @generated_role_name if role.name.blank?
57 57 yield role if block_given?
58 58 role.save!
59 59 role
60 60 end
61 61
62 62 def Issue.generate!(attributes={})
63 63 issue = Issue.new(attributes)
64 issue.project ||= Project.find(1)
65 issue.tracker ||= issue.project.trackers.first
64 66 issue.subject = 'Generated' if issue.subject.blank?
65 67 issue.author ||= User.find(2)
66 68 yield issue if block_given?
67 69 issue.save!
68 70 issue
69 71 end
70 72
71 # Generate an issue for a project, using its trackers
72 def Issue.generate_for_project!(project, attributes={})
73 issue = Issue.new(attributes) do |issue|
74 issue.project = project
75 issue.tracker = project.trackers.first unless project.trackers.empty?
76 issue.subject = 'Generated' if issue.subject.blank?
77 issue.author ||= User.find(2)
78 yield issue if block_given?
79 end
80 issue.save!
81 issue
82 end
83
84 73 # Generates an issue with some children and a grandchild
85 74 def Issue.generate_with_descendants!(project, attributes={})
86 issue = Issue.generate_for_project!(project, attributes)
87 child = Issue.generate_for_project!(project, :subject => 'Child1', :parent_issue_id => issue.id)
88 Issue.generate_for_project!(project, :subject => 'Child2', :parent_issue_id => issue.id)
89 Issue.generate_for_project!(project, :subject => 'Child11', :parent_issue_id => child.id)
75 issue = Issue.generate!(attributes.merge(:project => project))
76 child = Issue.generate!(:project => project, :subject => 'Child1', :parent_issue_id => issue.id)
77 Issue.generate!(:project => project, :subject => 'Child2', :parent_issue_id => issue.id)
78 Issue.generate!(:project => project, :subject => 'Child11', :parent_issue_id => child.id)
90 79 issue.reload
91 80 end
92 81
93 82 def Journal.generate!(attributes={})
94 83 journal = Journal.new(attributes)
95 84 journal.user ||= User.first
96 85 journal.journalized ||= Issue.first
97 86 yield journal if block_given?
98 87 journal.save!
99 88 journal
100 89 end
101 90
102 91 def Version.generate!(attributes={})
103 92 @generated_version_name ||= 'Version 0'
104 93 @generated_version_name.succ!
105 94 version = Version.new(attributes)
106 95 version.name = @generated_version_name if version.name.blank?
107 96 yield version if block_given?
108 97 version.save!
109 98 version
110 99 end
111 100
112 101 def AuthSource.generate!(attributes={})
113 102 @generated_auth_source_name ||= 'Auth 0'
114 103 @generated_auth_source_name.succ!
115 104 source = AuthSource.new(attributes)
116 105 source.name = @generated_auth_source_name if source.name.blank?
117 106 yield source if block_given?
118 107 source.save!
119 108 source
120 109 end
121 110
122 111 def Board.generate!(attributes={})
123 112 @generated_board_name ||= 'Forum 0'
124 113 @generated_board_name.succ!
125 114 board = Board.new(attributes)
126 115 board.name = @generated_board_name if board.name.blank?
127 116 board.description = @generated_board_name if board.description.blank?
128 117 yield board if block_given?
129 118 board.save!
130 119 board
131 120 end
132 121 end
@@ -1,1652 +1,1648
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2012 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_create_with_required_custom_field
58 58 set_language_if_valid 'en'
59 59 field = IssueCustomField.find_by_name('Database')
60 60 field.update_attribute(:is_required, true)
61 61
62 62 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
63 63 :status_id => 1, :subject => 'test_create',
64 64 :description => 'IssueTest#test_create_with_required_custom_field')
65 65 assert issue.available_custom_fields.include?(field)
66 66 # No value for the custom field
67 67 assert !issue.save
68 68 assert_equal ["Database can't be blank"], issue.errors.full_messages
69 69 # Blank value
70 70 issue.custom_field_values = { field.id => '' }
71 71 assert !issue.save
72 72 assert_equal ["Database can't be blank"], issue.errors.full_messages
73 73 # Invalid value
74 74 issue.custom_field_values = { field.id => 'SQLServer' }
75 75 assert !issue.save
76 76 assert_equal ["Database is not included in the list"], issue.errors.full_messages
77 77 # Valid value
78 78 issue.custom_field_values = { field.id => 'PostgreSQL' }
79 79 assert issue.save
80 80 issue.reload
81 81 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
82 82 end
83 83
84 84 def test_create_with_group_assignment
85 85 with_settings :issue_group_assignment => '1' do
86 86 assert Issue.new(:project_id => 2, :tracker_id => 1, :author_id => 1,
87 87 :subject => 'Group assignment',
88 88 :assigned_to_id => 11).save
89 89 issue = Issue.first(:order => 'id DESC')
90 90 assert_kind_of Group, issue.assigned_to
91 91 assert_equal Group.find(11), issue.assigned_to
92 92 end
93 93 end
94 94
95 95 def assert_visibility_match(user, issues)
96 96 assert_equal issues.collect(&:id).sort, Issue.all.select {|issue| issue.visible?(user)}.collect(&:id).sort
97 97 end
98 98
99 99 def test_visible_scope_for_anonymous
100 100 # Anonymous user should see issues of public projects only
101 101 issues = Issue.visible(User.anonymous).all
102 102 assert issues.any?
103 103 assert_nil issues.detect {|issue| !issue.project.is_public?}
104 104 assert_nil issues.detect {|issue| issue.is_private?}
105 105 assert_visibility_match User.anonymous, issues
106 106 end
107 107
108 108 def test_visible_scope_for_anonymous_without_view_issues_permissions
109 109 # Anonymous user should not see issues without permission
110 110 Role.anonymous.remove_permission!(:view_issues)
111 111 issues = Issue.visible(User.anonymous).all
112 112 assert issues.empty?
113 113 assert_visibility_match User.anonymous, issues
114 114 end
115 115
116 116 def test_anonymous_should_not_see_private_issues_with_issues_visibility_set_to_default
117 117 assert Role.anonymous.update_attribute(:issues_visibility, 'default')
118 issue = Issue.generate_for_project!(Project.find(1), :author => User.anonymous, :assigned_to => User.anonymous, :is_private => true)
118 issue = Issue.generate!(:author => User.anonymous, :assigned_to => User.anonymous, :is_private => true)
119 119 assert_nil Issue.where(:id => issue.id).visible(User.anonymous).first
120 120 assert !issue.visible?(User.anonymous)
121 121 end
122 122
123 123 def test_anonymous_should_not_see_private_issues_with_issues_visibility_set_to_own
124 124 assert Role.anonymous.update_attribute(:issues_visibility, 'own')
125 issue = Issue.generate_for_project!(Project.find(1), :author => User.anonymous, :assigned_to => User.anonymous, :is_private => true)
125 issue = Issue.generate!(:author => User.anonymous, :assigned_to => User.anonymous, :is_private => true)
126 126 assert_nil Issue.where(:id => issue.id).visible(User.anonymous).first
127 127 assert !issue.visible?(User.anonymous)
128 128 end
129 129
130 130 def test_visible_scope_for_non_member
131 131 user = User.find(9)
132 132 assert user.projects.empty?
133 133 # Non member user should see issues of public projects only
134 134 issues = Issue.visible(user).all
135 135 assert issues.any?
136 136 assert_nil issues.detect {|issue| !issue.project.is_public?}
137 137 assert_nil issues.detect {|issue| issue.is_private?}
138 138 assert_visibility_match user, issues
139 139 end
140 140
141 141 def test_visible_scope_for_non_member_with_own_issues_visibility
142 142 Role.non_member.update_attribute :issues_visibility, 'own'
143 143 Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 9, :subject => 'Issue by non member')
144 144 user = User.find(9)
145 145
146 146 issues = Issue.visible(user).all
147 147 assert issues.any?
148 148 assert_nil issues.detect {|issue| issue.author != user}
149 149 assert_visibility_match user, issues
150 150 end
151 151
152 152 def test_visible_scope_for_non_member_without_view_issues_permissions
153 153 # Non member user should not see issues without permission
154 154 Role.non_member.remove_permission!(:view_issues)
155 155 user = User.find(9)
156 156 assert user.projects.empty?
157 157 issues = Issue.visible(user).all
158 158 assert issues.empty?
159 159 assert_visibility_match user, issues
160 160 end
161 161
162 162 def test_visible_scope_for_member
163 163 user = User.find(9)
164 164 # User should see issues of projects for which he has view_issues permissions only
165 165 Role.non_member.remove_permission!(:view_issues)
166 166 Member.create!(:principal => user, :project_id => 3, :role_ids => [2])
167 167 issues = Issue.visible(user).all
168 168 assert issues.any?
169 169 assert_nil issues.detect {|issue| issue.project_id != 3}
170 170 assert_nil issues.detect {|issue| issue.is_private?}
171 171 assert_visibility_match user, issues
172 172 end
173 173
174 174 def test_visible_scope_for_member_with_groups_should_return_assigned_issues
175 175 user = User.find(8)
176 176 assert user.groups.any?
177 177 Member.create!(:principal => user.groups.first, :project_id => 1, :role_ids => [2])
178 178 Role.non_member.remove_permission!(:view_issues)
179 179
180 180 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3,
181 181 :status_id => 1, :priority => IssuePriority.all.first,
182 182 :subject => 'Assignment test',
183 183 :assigned_to => user.groups.first,
184 184 :is_private => true)
185 185
186 186 Role.find(2).update_attribute :issues_visibility, 'default'
187 187 issues = Issue.visible(User.find(8)).all
188 188 assert issues.any?
189 189 assert issues.include?(issue)
190 190
191 191 Role.find(2).update_attribute :issues_visibility, 'own'
192 192 issues = Issue.visible(User.find(8)).all
193 193 assert issues.any?
194 194 assert issues.include?(issue)
195 195 end
196 196
197 197 def test_visible_scope_for_admin
198 198 user = User.find(1)
199 199 user.members.each(&:destroy)
200 200 assert user.projects.empty?
201 201 issues = Issue.visible(user).all
202 202 assert issues.any?
203 203 # Admin should see issues on private projects that he does not belong to
204 204 assert issues.detect {|issue| !issue.project.is_public?}
205 205 # Admin should see private issues of other users
206 206 assert issues.detect {|issue| issue.is_private? && issue.author != user}
207 207 assert_visibility_match user, issues
208 208 end
209 209
210 210 def test_visible_scope_with_project
211 211 project = Project.find(1)
212 212 issues = Issue.visible(User.find(2), :project => project).all
213 213 projects = issues.collect(&:project).uniq
214 214 assert_equal 1, projects.size
215 215 assert_equal project, projects.first
216 216 end
217 217
218 218 def test_visible_scope_with_project_and_subprojects
219 219 project = Project.find(1)
220 220 issues = Issue.visible(User.find(2), :project => project, :with_subprojects => true).all
221 221 projects = issues.collect(&:project).uniq
222 222 assert projects.size > 1
223 223 assert_equal [], projects.select {|p| !p.is_or_is_descendant_of?(project)}
224 224 end
225 225
226 226 def test_visible_and_nested_set_scopes
227 227 assert_equal 0, Issue.find(1).descendants.visible.all.size
228 228 end
229 229
230 230 def test_open_scope
231 231 issues = Issue.open.all
232 232 assert_nil issues.detect(&:closed?)
233 233 end
234 234
235 235 def test_open_scope_with_arg
236 236 issues = Issue.open(false).all
237 237 assert_equal issues, issues.select(&:closed?)
238 238 end
239 239
240 240 def test_errors_full_messages_should_include_custom_fields_errors
241 241 field = IssueCustomField.find_by_name('Database')
242 242
243 243 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
244 244 :status_id => 1, :subject => 'test_create',
245 245 :description => 'IssueTest#test_create_with_required_custom_field')
246 246 assert issue.available_custom_fields.include?(field)
247 247 # Invalid value
248 248 issue.custom_field_values = { field.id => 'SQLServer' }
249 249
250 250 assert !issue.valid?
251 251 assert_equal 1, issue.errors.full_messages.size
252 252 assert_equal "Database #{I18n.translate('activerecord.errors.messages.inclusion')}",
253 253 issue.errors.full_messages.first
254 254 end
255 255
256 256 def test_update_issue_with_required_custom_field
257 257 field = IssueCustomField.find_by_name('Database')
258 258 field.update_attribute(:is_required, true)
259 259
260 260 issue = Issue.find(1)
261 261 assert_nil issue.custom_value_for(field)
262 262 assert issue.available_custom_fields.include?(field)
263 263 # No change to custom values, issue can be saved
264 264 assert issue.save
265 265 # Blank value
266 266 issue.custom_field_values = { field.id => '' }
267 267 assert !issue.save
268 268 # Valid value
269 269 issue.custom_field_values = { field.id => 'PostgreSQL' }
270 270 assert issue.save
271 271 issue.reload
272 272 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
273 273 end
274 274
275 275 def test_should_not_update_attributes_if_custom_fields_validation_fails
276 276 issue = Issue.find(1)
277 277 field = IssueCustomField.find_by_name('Database')
278 278 assert issue.available_custom_fields.include?(field)
279 279
280 280 issue.custom_field_values = { field.id => 'Invalid' }
281 281 issue.subject = 'Should be not be saved'
282 282 assert !issue.save
283 283
284 284 issue.reload
285 285 assert_equal "Can't print recipes", issue.subject
286 286 end
287 287
288 288 def test_should_not_recreate_custom_values_objects_on_update
289 289 field = IssueCustomField.find_by_name('Database')
290 290
291 291 issue = Issue.find(1)
292 292 issue.custom_field_values = { field.id => 'PostgreSQL' }
293 293 assert issue.save
294 294 custom_value = issue.custom_value_for(field)
295 295 issue.reload
296 296 issue.custom_field_values = { field.id => 'MySQL' }
297 297 assert issue.save
298 298 issue.reload
299 299 assert_equal custom_value.id, issue.custom_value_for(field).id
300 300 end
301 301
302 302 def test_should_not_update_custom_fields_on_changing_tracker_with_different_custom_fields
303 303 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => 'Test', :custom_field_values => {'2' => 'Test'})
304 304 assert !Tracker.find(2).custom_field_ids.include?(2)
305 305
306 306 issue = Issue.find(issue.id)
307 307 issue.attributes = {:tracker_id => 2, :custom_field_values => {'1' => ''}}
308 308
309 309 issue = Issue.find(issue.id)
310 310 custom_value = issue.custom_value_for(2)
311 311 assert_not_nil custom_value
312 312 assert_equal 'Test', custom_value.value
313 313 end
314 314
315 315 def test_assigning_tracker_id_should_reload_custom_fields_values
316 316 issue = Issue.new(:project => Project.find(1))
317 317 assert issue.custom_field_values.empty?
318 318 issue.tracker_id = 1
319 319 assert issue.custom_field_values.any?
320 320 end
321 321
322 322 def test_assigning_attributes_should_assign_project_and_tracker_first
323 323 seq = sequence('seq')
324 324 issue = Issue.new
325 325 issue.expects(:project_id=).in_sequence(seq)
326 326 issue.expects(:tracker_id=).in_sequence(seq)
327 327 issue.expects(:subject=).in_sequence(seq)
328 328 issue.attributes = {:tracker_id => 2, :project_id => 1, :subject => 'Test'}
329 329 end
330 330
331 331 def test_assigning_tracker_and_custom_fields_should_assign_custom_fields
332 332 attributes = ActiveSupport::OrderedHash.new
333 333 attributes['custom_field_values'] = { '1' => 'MySQL' }
334 334 attributes['tracker_id'] = '1'
335 335 issue = Issue.new(:project => Project.find(1))
336 336 issue.attributes = attributes
337 337 assert_equal 'MySQL', issue.custom_field_value(1)
338 338 end
339 339
340 340 def test_should_update_issue_with_disabled_tracker
341 341 p = Project.find(1)
342 342 issue = Issue.find(1)
343 343
344 344 p.trackers.delete(issue.tracker)
345 345 assert !p.trackers.include?(issue.tracker)
346 346
347 347 issue.reload
348 348 issue.subject = 'New subject'
349 349 assert issue.save
350 350 end
351 351
352 352 def test_should_not_set_a_disabled_tracker
353 353 p = Project.find(1)
354 354 p.trackers.delete(Tracker.find(2))
355 355
356 356 issue = Issue.find(1)
357 357 issue.tracker_id = 2
358 358 issue.subject = 'New subject'
359 359 assert !issue.save
360 360 assert_not_nil issue.errors[:tracker_id]
361 361 end
362 362
363 363 def test_category_based_assignment
364 364 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3,
365 365 :status_id => 1, :priority => IssuePriority.all.first,
366 366 :subject => 'Assignment test',
367 367 :description => 'Assignment test', :category_id => 1)
368 368 assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
369 369 end
370 370
371 371 def test_new_statuses_allowed_to
372 372 WorkflowTransition.delete_all
373 373
374 374 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2, :author => false, :assignee => false)
375 375 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3, :author => true, :assignee => false)
376 376 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4, :author => false, :assignee => true)
377 377 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5, :author => true, :assignee => true)
378 378 status = IssueStatus.find(1)
379 379 role = Role.find(1)
380 380 tracker = Tracker.find(1)
381 381 user = User.find(2)
382 382
383 383 issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :author_id => 1)
384 384 assert_equal [1, 2], issue.new_statuses_allowed_to(user).map(&:id)
385 385
386 386 issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :author => user)
387 387 assert_equal [1, 2, 3, 5], issue.new_statuses_allowed_to(user).map(&:id)
388 388
389 389 issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :author_id => 1, :assigned_to => user)
390 390 assert_equal [1, 2, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
391 391
392 392 issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :author => user, :assigned_to => user)
393 393 assert_equal [1, 2, 3, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
394 394 end
395 395
396 396 def test_new_statuses_allowed_to_should_return_all_transitions_for_admin
397 397 admin = User.find(1)
398 398 issue = Issue.find(1)
399 399 assert !admin.member_of?(issue.project)
400 400 expected_statuses = [issue.status] + WorkflowTransition.find_all_by_old_status_id(issue.status_id).map(&:new_status).uniq.sort
401 401
402 402 assert_equal expected_statuses, issue.new_statuses_allowed_to(admin)
403 403 end
404 404
405 405 def test_new_statuses_allowed_to_should_return_default_and_current_status_when_copying
406 406 issue = Issue.find(1).copy
407 407 assert_equal [1], issue.new_statuses_allowed_to(User.find(2)).map(&:id)
408 408
409 409 issue = Issue.find(2).copy
410 410 assert_equal [1, 2], issue.new_statuses_allowed_to(User.find(2)).map(&:id)
411 411 end
412 412
413 413 def test_safe_attributes_names_should_not_include_disabled_field
414 414 tracker = Tracker.new(:core_fields => %w(assigned_to_id fixed_version_id))
415 415
416 416 issue = Issue.new(:tracker => tracker)
417 417 assert_include 'tracker_id', issue.safe_attribute_names
418 418 assert_include 'status_id', issue.safe_attribute_names
419 419 assert_include 'subject', issue.safe_attribute_names
420 420 assert_include 'description', issue.safe_attribute_names
421 421 assert_include 'custom_field_values', issue.safe_attribute_names
422 422 assert_include 'custom_fields', issue.safe_attribute_names
423 423 assert_include 'lock_version', issue.safe_attribute_names
424 424
425 425 tracker.core_fields.each do |field|
426 426 assert_include field, issue.safe_attribute_names
427 427 end
428 428
429 429 tracker.disabled_core_fields.each do |field|
430 430 assert_not_include field, issue.safe_attribute_names
431 431 end
432 432 end
433 433
434 434 def test_safe_attributes_should_ignore_disabled_fields
435 435 tracker = Tracker.find(1)
436 436 tracker.core_fields = %w(assigned_to_id due_date)
437 437 tracker.save!
438 438
439 439 issue = Issue.new(:tracker => tracker)
440 440 issue.safe_attributes = {'start_date' => '2012-07-14', 'due_date' => '2012-07-14'}
441 441 assert_nil issue.start_date
442 442 assert_equal Date.parse('2012-07-14'), issue.due_date
443 443 end
444 444
445 445 def test_safe_attributes_should_accept_target_tracker_enabled_fields
446 446 source = Tracker.find(1)
447 447 source.core_fields = []
448 448 source.save!
449 449 target = Tracker.find(2)
450 450 target.core_fields = %w(assigned_to_id due_date)
451 451 target.save!
452 452
453 453 issue = Issue.new(:tracker => source)
454 454 issue.safe_attributes = {'tracker_id' => 2, 'due_date' => '2012-07-14'}
455 455 assert_equal target, issue.tracker
456 456 assert_equal Date.parse('2012-07-14'), issue.due_date
457 457 end
458 458
459 459 def test_safe_attributes_should_not_include_readonly_fields
460 460 WorkflowPermission.delete_all
461 461 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'readonly')
462 462 user = User.find(2)
463 463
464 464 issue = Issue.new(:project_id => 1, :tracker_id => 1)
465 465 assert_equal %w(due_date), issue.read_only_attribute_names(user)
466 466 assert_not_include 'due_date', issue.safe_attribute_names(user)
467 467
468 468 issue.send :safe_attributes=, {'start_date' => '2012-07-14', 'due_date' => '2012-07-14'}, user
469 469 assert_equal Date.parse('2012-07-14'), issue.start_date
470 470 assert_nil issue.due_date
471 471 end
472 472
473 473 def test_safe_attributes_should_not_include_readonly_custom_fields
474 474 cf1 = IssueCustomField.create!(:name => 'Writable field', :field_format => 'string', :is_for_all => true, :tracker_ids => [1])
475 475 cf2 = IssueCustomField.create!(:name => 'Readonly field', :field_format => 'string', :is_for_all => true, :tracker_ids => [1])
476 476
477 477 WorkflowPermission.delete_all
478 478 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'readonly')
479 479 user = User.find(2)
480 480
481 481 issue = Issue.new(:project_id => 1, :tracker_id => 1)
482 482 assert_equal [cf2.id.to_s], issue.read_only_attribute_names(user)
483 483 assert_not_include cf2.id.to_s, issue.safe_attribute_names(user)
484 484
485 485 issue.send :safe_attributes=, {'custom_field_values' => {cf1.id.to_s => 'value1', cf2.id.to_s => 'value2'}}, user
486 486 assert_equal 'value1', issue.custom_field_value(cf1)
487 487 assert_nil issue.custom_field_value(cf2)
488 488
489 489 issue.send :safe_attributes=, {'custom_fields' => [{'id' => cf1.id.to_s, 'value' => 'valuea'}, {'id' => cf2.id.to_s, 'value' => 'valueb'}]}, user
490 490 assert_equal 'valuea', issue.custom_field_value(cf1)
491 491 assert_nil issue.custom_field_value(cf2)
492 492 end
493 493
494 494 def test_editable_custom_field_values_should_return_non_readonly_custom_values
495 495 cf1 = IssueCustomField.create!(:name => 'Writable field', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
496 496 cf2 = IssueCustomField.create!(:name => 'Readonly field', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
497 497
498 498 WorkflowPermission.delete_all
499 499 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'readonly')
500 500 user = User.find(2)
501 501
502 502 issue = Issue.new(:project_id => 1, :tracker_id => 1)
503 503 values = issue.editable_custom_field_values(user)
504 504 assert values.detect {|value| value.custom_field == cf1}
505 505 assert_nil values.detect {|value| value.custom_field == cf2}
506 506
507 507 issue.tracker_id = 2
508 508 values = issue.editable_custom_field_values(user)
509 509 assert values.detect {|value| value.custom_field == cf1}
510 510 assert values.detect {|value| value.custom_field == cf2}
511 511 end
512 512
513 513 def test_safe_attributes_should_accept_target_tracker_writable_fields
514 514 WorkflowPermission.delete_all
515 515 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'readonly')
516 516 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => 'start_date', :rule => 'readonly')
517 517 user = User.find(2)
518 518
519 519 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
520 520
521 521 issue.send :safe_attributes=, {'start_date' => '2012-07-12', 'due_date' => '2012-07-14'}, user
522 522 assert_equal Date.parse('2012-07-12'), issue.start_date
523 523 assert_nil issue.due_date
524 524
525 525 issue.send :safe_attributes=, {'start_date' => '2012-07-15', 'due_date' => '2012-07-16', 'tracker_id' => 2}, user
526 526 assert_equal Date.parse('2012-07-12'), issue.start_date
527 527 assert_equal Date.parse('2012-07-16'), issue.due_date
528 528 end
529 529
530 530 def test_safe_attributes_should_accept_target_status_writable_fields
531 531 WorkflowPermission.delete_all
532 532 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'readonly')
533 533 WorkflowPermission.create!(:old_status_id => 2, :tracker_id => 1, :role_id => 1, :field_name => 'start_date', :rule => 'readonly')
534 534 user = User.find(2)
535 535
536 536 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
537 537
538 538 issue.send :safe_attributes=, {'start_date' => '2012-07-12', 'due_date' => '2012-07-14'}, user
539 539 assert_equal Date.parse('2012-07-12'), issue.start_date
540 540 assert_nil issue.due_date
541 541
542 542 issue.send :safe_attributes=, {'start_date' => '2012-07-15', 'due_date' => '2012-07-16', 'status_id' => 2}, user
543 543 assert_equal Date.parse('2012-07-12'), issue.start_date
544 544 assert_equal Date.parse('2012-07-16'), issue.due_date
545 545 end
546 546
547 547 def test_required_attributes_should_be_validated
548 548 cf = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
549 549
550 550 WorkflowPermission.delete_all
551 551 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'required')
552 552 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'category_id', :rule => 'required')
553 553 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => cf.id.to_s, :rule => 'required')
554 554
555 555 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => 'start_date', :rule => 'required')
556 556 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => cf.id.to_s, :rule => 'required')
557 557 user = User.find(2)
558 558
559 559 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1, :subject => 'Required fields', :author => user)
560 560 assert_equal [cf.id.to_s, "category_id", "due_date"], issue.required_attribute_names(user).sort
561 561 assert !issue.save, "Issue was saved"
562 562 assert_equal ["Category can't be blank", "Due date can't be blank", "Foo can't be blank"], issue.errors.full_messages.sort
563 563
564 564 issue.tracker_id = 2
565 565 assert_equal [cf.id.to_s, "start_date"], issue.required_attribute_names(user).sort
566 566 assert !issue.save, "Issue was saved"
567 567 assert_equal ["Foo can't be blank", "Start date can't be blank"], issue.errors.full_messages.sort
568 568
569 569 issue.start_date = Date.today
570 570 issue.custom_field_values = {cf.id.to_s => 'bar'}
571 571 assert issue.save
572 572 end
573 573
574 574 def test_required_attribute_names_for_multiple_roles_should_intersect_rules
575 575 WorkflowPermission.delete_all
576 576 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'required')
577 577 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'start_date', :rule => 'required')
578 578 user = User.find(2)
579 579 member = Member.find(1)
580 580 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
581 581
582 582 assert_equal %w(due_date start_date), issue.required_attribute_names(user).sort
583 583
584 584 member.role_ids = [1, 2]
585 585 member.save!
586 586 assert_equal [], issue.required_attribute_names(user.reload)
587 587
588 588 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 2, :field_name => 'due_date', :rule => 'required')
589 589 assert_equal %w(due_date), issue.required_attribute_names(user)
590 590
591 591 member.role_ids = [1, 2, 3]
592 592 member.save!
593 593 assert_equal [], issue.required_attribute_names(user.reload)
594 594
595 595 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 2, :field_name => 'due_date', :rule => 'readonly')
596 596 # required + readonly => required
597 597 assert_equal %w(due_date), issue.required_attribute_names(user)
598 598 end
599 599
600 600 def test_read_only_attribute_names_for_multiple_roles_should_intersect_rules
601 601 WorkflowPermission.delete_all
602 602 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'readonly')
603 603 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'start_date', :rule => 'readonly')
604 604 user = User.find(2)
605 605 member = Member.find(1)
606 606 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
607 607
608 608 assert_equal %w(due_date start_date), issue.read_only_attribute_names(user).sort
609 609
610 610 member.role_ids = [1, 2]
611 611 member.save!
612 612 assert_equal [], issue.read_only_attribute_names(user.reload)
613 613
614 614 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 2, :field_name => 'due_date', :rule => 'readonly')
615 615 assert_equal %w(due_date), issue.read_only_attribute_names(user)
616 616 end
617 617
618 618 def test_copy
619 619 issue = Issue.new.copy_from(1)
620 620 assert issue.copy?
621 621 assert issue.save
622 622 issue.reload
623 623 orig = Issue.find(1)
624 624 assert_equal orig.subject, issue.subject
625 625 assert_equal orig.tracker, issue.tracker
626 626 assert_equal "125", issue.custom_value_for(2).value
627 627 end
628 628
629 629 def test_copy_should_copy_status
630 630 orig = Issue.find(8)
631 631 assert orig.status != IssueStatus.default
632 632
633 633 issue = Issue.new.copy_from(orig)
634 634 assert issue.save
635 635 issue.reload
636 636 assert_equal orig.status, issue.status
637 637 end
638 638
639 639 def test_copy_should_add_relation_with_copied_issue
640 640 copied = Issue.find(1)
641 641 issue = Issue.new.copy_from(copied)
642 642 assert issue.save
643 643 issue.reload
644 644
645 645 assert_equal 1, issue.relations.size
646 646 relation = issue.relations.first
647 647 assert_equal 'copied_to', relation.relation_type
648 648 assert_equal copied, relation.issue_from
649 649 assert_equal issue, relation.issue_to
650 650 end
651 651
652 652 def test_copy_should_copy_subtasks
653 653 issue = Issue.generate_with_descendants!(Project.find(1), :subject => 'Parent')
654 654
655 655 copy = issue.reload.copy
656 656 copy.author = User.find(7)
657 657 assert_difference 'Issue.count', 1+issue.descendants.count do
658 658 assert copy.save
659 659 end
660 660 copy.reload
661 661 assert_equal %w(Child1 Child2), copy.children.map(&:subject).sort
662 662 child_copy = copy.children.detect {|c| c.subject == 'Child1'}
663 663 assert_equal %w(Child11), child_copy.children.map(&:subject).sort
664 664 assert_equal copy.author, child_copy.author
665 665 end
666 666
667 667 def test_copy_should_copy_subtasks_to_target_project
668 668 issue = Issue.generate_with_descendants!(Project.find(1), :subject => 'Parent')
669 669
670 670 copy = issue.copy(:project_id => 3)
671 671 assert_difference 'Issue.count', 1+issue.descendants.count do
672 672 assert copy.save
673 673 end
674 674 assert_equal [3], copy.reload.descendants.map(&:project_id).uniq
675 675 end
676 676
677 677 def test_copy_should_not_copy_subtasks_twice_when_saving_twice
678 678 issue = Issue.generate_with_descendants!(Project.find(1), :subject => 'Parent')
679 679
680 680 copy = issue.reload.copy
681 681 assert_difference 'Issue.count', 1+issue.descendants.count do
682 682 assert copy.save
683 683 assert copy.save
684 684 end
685 685 end
686 686
687 687 def test_should_not_call_after_project_change_on_creation
688 688 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1, :subject => 'Test', :author_id => 1)
689 689 issue.expects(:after_project_change).never
690 690 issue.save!
691 691 end
692 692
693 693 def test_should_not_call_after_project_change_on_update
694 694 issue = Issue.find(1)
695 695 issue.project = Project.find(1)
696 696 issue.subject = 'No project change'
697 697 issue.expects(:after_project_change).never
698 698 issue.save!
699 699 end
700 700
701 701 def test_should_call_after_project_change_on_project_change
702 702 issue = Issue.find(1)
703 703 issue.project = Project.find(2)
704 704 issue.expects(:after_project_change).once
705 705 issue.save!
706 706 end
707 707
708 708 def test_adding_journal_should_update_timestamp
709 709 issue = Issue.find(1)
710 710 updated_on_was = issue.updated_on
711 711
712 712 issue.init_journal(User.first, "Adding notes")
713 713 assert_difference 'Journal.count' do
714 714 assert issue.save
715 715 end
716 716 issue.reload
717 717
718 718 assert_not_equal updated_on_was, issue.updated_on
719 719 end
720 720
721 721 def test_should_close_duplicates
722 722 # Create 3 issues
723 project = Project.find(1)
724 issue1 = Issue.generate_for_project!(project)
725 issue2 = Issue.generate_for_project!(project)
726 issue3 = Issue.generate_for_project!(project)
723 issue1 = Issue.generate!
724 issue2 = Issue.generate!
725 issue3 = Issue.generate!
727 726
728 727 # 2 is a dupe of 1
729 728 IssueRelation.create!(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
730 729 # And 3 is a dupe of 2
731 730 IssueRelation.create!(:issue_from => issue3, :issue_to => issue2, :relation_type => IssueRelation::TYPE_DUPLICATES)
732 731 # And 3 is a dupe of 1 (circular duplicates)
733 732 IssueRelation.create!(:issue_from => issue3, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
734 733
735 734 assert issue1.reload.duplicates.include?(issue2)
736 735
737 736 # Closing issue 1
738 737 issue1.init_journal(User.find(:first), "Closing issue1")
739 738 issue1.status = IssueStatus.find :first, :conditions => {:is_closed => true}
740 739 assert issue1.save
741 740 # 2 and 3 should be also closed
742 741 assert issue2.reload.closed?
743 742 assert issue3.reload.closed?
744 743 end
745 744
746 745 def test_should_not_close_duplicated_issue
747 project = Project.find(1)
748 issue1 = Issue.generate_for_project!(project)
749 issue2 = Issue.generate_for_project!(project)
746 issue1 = Issue.generate!
747 issue2 = Issue.generate!
750 748
751 749 # 2 is a dupe of 1
752 750 IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
753 751 # 2 is a dup of 1 but 1 is not a duplicate of 2
754 752 assert !issue2.reload.duplicates.include?(issue1)
755 753
756 754 # Closing issue 2
757 755 issue2.init_journal(User.find(:first), "Closing issue2")
758 756 issue2.status = IssueStatus.find :first, :conditions => {:is_closed => true}
759 757 assert issue2.save
760 758 # 1 should not be also closed
761 759 assert !issue1.reload.closed?
762 760 end
763 761
764 762 def test_assignable_versions
765 763 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue')
766 764 assert_equal ['open'], issue.assignable_versions.collect(&:status).uniq
767 765 end
768 766
769 767 def test_should_not_be_able_to_assign_a_new_issue_to_a_closed_version
770 768 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue')
771 769 assert !issue.save
772 770 assert_not_nil issue.errors[:fixed_version_id]
773 771 end
774 772
775 773 def test_should_not_be_able_to_assign_a_new_issue_to_a_locked_version
776 774 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 2, :subject => 'New issue')
777 775 assert !issue.save
778 776 assert_not_nil issue.errors[:fixed_version_id]
779 777 end
780 778
781 779 def test_should_be_able_to_assign_a_new_issue_to_an_open_version
782 780 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 3, :subject => 'New issue')
783 781 assert issue.save
784 782 end
785 783
786 784 def test_should_be_able_to_update_an_issue_assigned_to_a_closed_version
787 785 issue = Issue.find(11)
788 786 assert_equal 'closed', issue.fixed_version.status
789 787 issue.subject = 'Subject changed'
790 788 assert issue.save
791 789 end
792 790
793 791 def test_should_not_be_able_to_reopen_an_issue_assigned_to_a_closed_version
794 792 issue = Issue.find(11)
795 793 issue.status_id = 1
796 794 assert !issue.save
797 795 assert_not_nil issue.errors[:base]
798 796 end
799 797
800 798 def test_should_be_able_to_reopen_and_reassign_an_issue_assigned_to_a_closed_version
801 799 issue = Issue.find(11)
802 800 issue.status_id = 1
803 801 issue.fixed_version_id = 3
804 802 assert issue.save
805 803 end
806 804
807 805 def test_should_be_able_to_reopen_an_issue_assigned_to_a_locked_version
808 806 issue = Issue.find(12)
809 807 assert_equal 'locked', issue.fixed_version.status
810 808 issue.status_id = 1
811 809 assert issue.save
812 810 end
813 811
814 812 def test_should_not_be_able_to_keep_unshared_version_when_changing_project
815 813 issue = Issue.find(2)
816 814 assert_equal 2, issue.fixed_version_id
817 815 issue.project_id = 3
818 816 assert_nil issue.fixed_version_id
819 817 issue.fixed_version_id = 2
820 818 assert !issue.save
821 819 assert_include 'Target version is not included in the list', issue.errors.full_messages
822 820 end
823 821
824 822 def test_should_keep_shared_version_when_changing_project
825 823 Version.find(2).update_attribute :sharing, 'tree'
826 824
827 825 issue = Issue.find(2)
828 826 assert_equal 2, issue.fixed_version_id
829 827 issue.project_id = 3
830 828 assert_equal 2, issue.fixed_version_id
831 829 assert issue.save
832 830 end
833 831
834 832 def test_allowed_target_projects_on_move_should_include_projects_with_issue_tracking_enabled
835 833 assert_include Project.find(2), Issue.allowed_target_projects_on_move(User.find(2))
836 834 end
837 835
838 836 def test_allowed_target_projects_on_move_should_not_include_projects_with_issue_tracking_disabled
839 837 Project.find(2).disable_module! :issue_tracking
840 838 assert_not_include Project.find(2), Issue.allowed_target_projects_on_move(User.find(2))
841 839 end
842 840
843 841 def test_move_to_another_project_with_same_category
844 842 issue = Issue.find(1)
845 843 issue.project = Project.find(2)
846 844 assert issue.save
847 845 issue.reload
848 846 assert_equal 2, issue.project_id
849 847 # Category changes
850 848 assert_equal 4, issue.category_id
851 849 # Make sure time entries were move to the target project
852 850 assert_equal 2, issue.time_entries.first.project_id
853 851 end
854 852
855 853 def test_move_to_another_project_without_same_category
856 854 issue = Issue.find(2)
857 855 issue.project = Project.find(2)
858 856 assert issue.save
859 857 issue.reload
860 858 assert_equal 2, issue.project_id
861 859 # Category cleared
862 860 assert_nil issue.category_id
863 861 end
864 862
865 863 def test_move_to_another_project_should_clear_fixed_version_when_not_shared
866 864 issue = Issue.find(1)
867 865 issue.update_attribute(:fixed_version_id, 1)
868 866 issue.project = Project.find(2)
869 867 assert issue.save
870 868 issue.reload
871 869 assert_equal 2, issue.project_id
872 870 # Cleared fixed_version
873 871 assert_equal nil, issue.fixed_version
874 872 end
875 873
876 874 def test_move_to_another_project_should_keep_fixed_version_when_shared_with_the_target_project
877 875 issue = Issue.find(1)
878 876 issue.update_attribute(:fixed_version_id, 4)
879 877 issue.project = Project.find(5)
880 878 assert issue.save
881 879 issue.reload
882 880 assert_equal 5, issue.project_id
883 881 # Keep fixed_version
884 882 assert_equal 4, issue.fixed_version_id
885 883 end
886 884
887 885 def test_move_to_another_project_should_clear_fixed_version_when_not_shared_with_the_target_project
888 886 issue = Issue.find(1)
889 887 issue.update_attribute(:fixed_version_id, 1)
890 888 issue.project = Project.find(5)
891 889 assert issue.save
892 890 issue.reload
893 891 assert_equal 5, issue.project_id
894 892 # Cleared fixed_version
895 893 assert_equal nil, issue.fixed_version
896 894 end
897 895
898 896 def test_move_to_another_project_should_keep_fixed_version_when_shared_systemwide
899 897 issue = Issue.find(1)
900 898 issue.update_attribute(:fixed_version_id, 7)
901 899 issue.project = Project.find(2)
902 900 assert issue.save
903 901 issue.reload
904 902 assert_equal 2, issue.project_id
905 903 # Keep fixed_version
906 904 assert_equal 7, issue.fixed_version_id
907 905 end
908 906
909 907 def test_move_to_another_project_should_keep_parent_if_valid
910 908 issue = Issue.find(1)
911 909 issue.update_attribute(:parent_issue_id, 2)
912 910 issue.project = Project.find(3)
913 911 assert issue.save
914 912 issue.reload
915 913 assert_equal 2, issue.parent_id
916 914 end
917 915
918 916 def test_move_to_another_project_should_clear_parent_if_not_valid
919 917 issue = Issue.find(1)
920 918 issue.update_attribute(:parent_issue_id, 2)
921 919 issue.project = Project.find(2)
922 920 assert issue.save
923 921 issue.reload
924 922 assert_nil issue.parent_id
925 923 end
926 924
927 925 def test_move_to_another_project_with_disabled_tracker
928 926 issue = Issue.find(1)
929 927 target = Project.find(2)
930 928 target.tracker_ids = [3]
931 929 target.save
932 930 issue.project = target
933 931 assert issue.save
934 932 issue.reload
935 933 assert_equal 2, issue.project_id
936 934 assert_equal 3, issue.tracker_id
937 935 end
938 936
939 937 def test_copy_to_the_same_project
940 938 issue = Issue.find(1)
941 939 copy = issue.copy
942 940 assert_difference 'Issue.count' do
943 941 copy.save!
944 942 end
945 943 assert_kind_of Issue, copy
946 944 assert_equal issue.project, copy.project
947 945 assert_equal "125", copy.custom_value_for(2).value
948 946 end
949 947
950 948 def test_copy_to_another_project_and_tracker
951 949 issue = Issue.find(1)
952 950 copy = issue.copy(:project_id => 3, :tracker_id => 2)
953 951 assert_difference 'Issue.count' do
954 952 copy.save!
955 953 end
956 954 copy.reload
957 955 assert_kind_of Issue, copy
958 956 assert_equal Project.find(3), copy.project
959 957 assert_equal Tracker.find(2), copy.tracker
960 958 # Custom field #2 is not associated with target tracker
961 959 assert_nil copy.custom_value_for(2)
962 960 end
963 961
964 962 context "#copy" do
965 963 setup do
966 964 @issue = Issue.find(1)
967 965 end
968 966
969 967 should "not create a journal" do
970 968 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3)
971 969 copy.save!
972 970 assert_equal 0, copy.reload.journals.size
973 971 end
974 972
975 973 should "allow assigned_to changes" do
976 974 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3)
977 975 assert_equal 3, copy.assigned_to_id
978 976 end
979 977
980 978 should "allow status changes" do
981 979 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :status_id => 2)
982 980 assert_equal 2, copy.status_id
983 981 end
984 982
985 983 should "allow start date changes" do
986 984 date = Date.today
987 985 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :start_date => date)
988 986 assert_equal date, copy.start_date
989 987 end
990 988
991 989 should "allow due date changes" do
992 990 date = Date.today
993 991 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :due_date => date)
994 992 assert_equal date, copy.due_date
995 993 end
996 994
997 995 should "set current user as author" do
998 996 User.current = User.find(9)
999 997 copy = @issue.copy(:project_id => 3, :tracker_id => 2)
1000 998 assert_equal User.current, copy.author
1001 999 end
1002 1000
1003 1001 should "create a journal with notes" do
1004 1002 date = Date.today
1005 1003 notes = "Notes added when copying"
1006 1004 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :start_date => date)
1007 1005 copy.init_journal(User.current, notes)
1008 1006 copy.save!
1009 1007
1010 1008 assert_equal 1, copy.journals.size
1011 1009 journal = copy.journals.first
1012 1010 assert_equal 0, journal.details.size
1013 1011 assert_equal notes, journal.notes
1014 1012 end
1015 1013 end
1016 1014
1017 1015 def test_valid_parent_project
1018 1016 issue = Issue.find(1)
1019 1017 issue_in_same_project = Issue.find(2)
1020 1018 issue_in_child_project = Issue.find(5)
1021 1019 issue_in_grandchild_project = Issue.generate!(:project_id => 6, :tracker_id => 1)
1022 1020 issue_in_other_child_project = Issue.find(6)
1023 1021 issue_in_different_tree = Issue.find(4)
1024 1022
1025 1023 with_settings :cross_project_subtasks => '' do
1026 1024 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1027 1025 assert_equal false, issue.valid_parent_project?(issue_in_child_project)
1028 1026 assert_equal false, issue.valid_parent_project?(issue_in_grandchild_project)
1029 1027 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1030 1028 end
1031 1029
1032 1030 with_settings :cross_project_subtasks => 'system' do
1033 1031 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1034 1032 assert_equal true, issue.valid_parent_project?(issue_in_child_project)
1035 1033 assert_equal true, issue.valid_parent_project?(issue_in_different_tree)
1036 1034 end
1037 1035
1038 1036 with_settings :cross_project_subtasks => 'tree' do
1039 1037 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1040 1038 assert_equal true, issue.valid_parent_project?(issue_in_child_project)
1041 1039 assert_equal true, issue.valid_parent_project?(issue_in_grandchild_project)
1042 1040 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1043 1041
1044 1042 assert_equal true, issue_in_child_project.valid_parent_project?(issue_in_same_project)
1045 1043 assert_equal true, issue_in_child_project.valid_parent_project?(issue_in_other_child_project)
1046 1044 end
1047 1045
1048 1046 with_settings :cross_project_subtasks => 'descendants' do
1049 1047 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1050 1048 assert_equal false, issue.valid_parent_project?(issue_in_child_project)
1051 1049 assert_equal false, issue.valid_parent_project?(issue_in_grandchild_project)
1052 1050 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1053 1051
1054 1052 assert_equal true, issue_in_child_project.valid_parent_project?(issue)
1055 1053 assert_equal false, issue_in_child_project.valid_parent_project?(issue_in_other_child_project)
1056 1054 end
1057 1055 end
1058 1056
1059 1057 def test_recipients_should_include_previous_assignee
1060 1058 user = User.find(3)
1061 1059 user.members.update_all ["mail_notification = ?", false]
1062 1060 user.update_attribute :mail_notification, 'only_assigned'
1063 1061
1064 1062 issue = Issue.find(2)
1065 1063 issue.assigned_to = nil
1066 1064 assert_include user.mail, issue.recipients
1067 1065 issue.save!
1068 1066 assert !issue.recipients.include?(user.mail)
1069 1067 end
1070 1068
1071 1069 def test_recipients_should_not_include_users_that_cannot_view_the_issue
1072 1070 issue = Issue.find(12)
1073 1071 assert issue.recipients.include?(issue.author.mail)
1074 1072 # copy the issue to a private project
1075 1073 copy = issue.copy(:project_id => 5, :tracker_id => 2)
1076 1074 # author is not a member of project anymore
1077 1075 assert !copy.recipients.include?(copy.author.mail)
1078 1076 end
1079 1077
1080 1078 def test_recipients_should_include_the_assigned_group_members
1081 1079 group_member = User.generate!
1082 1080 group = Group.generate!
1083 1081 group.users << group_member
1084 1082
1085 1083 issue = Issue.find(12)
1086 1084 issue.assigned_to = group
1087 1085 assert issue.recipients.include?(group_member.mail)
1088 1086 end
1089 1087
1090 1088 def test_watcher_recipients_should_not_include_users_that_cannot_view_the_issue
1091 1089 user = User.find(3)
1092 1090 issue = Issue.find(9)
1093 1091 Watcher.create!(:user => user, :watchable => issue)
1094 1092 assert issue.watched_by?(user)
1095 1093 assert !issue.watcher_recipients.include?(user.mail)
1096 1094 end
1097 1095
1098 1096 def test_issue_destroy
1099 1097 Issue.find(1).destroy
1100 1098 assert_nil Issue.find_by_id(1)
1101 1099 assert_nil TimeEntry.find_by_issue_id(1)
1102 1100 end
1103 1101
1104 1102 def test_destroying_a_deleted_issue_should_not_raise_an_error
1105 1103 issue = Issue.find(1)
1106 1104 Issue.find(1).destroy
1107 1105
1108 1106 assert_nothing_raised do
1109 1107 assert_no_difference 'Issue.count' do
1110 1108 issue.destroy
1111 1109 end
1112 1110 assert issue.destroyed?
1113 1111 end
1114 1112 end
1115 1113
1116 1114 def test_destroying_a_stale_issue_should_not_raise_an_error
1117 1115 issue = Issue.find(1)
1118 1116 Issue.find(1).update_attribute :subject, "Updated"
1119 1117
1120 1118 assert_nothing_raised do
1121 1119 assert_difference 'Issue.count', -1 do
1122 1120 issue.destroy
1123 1121 end
1124 1122 assert issue.destroyed?
1125 1123 end
1126 1124 end
1127 1125
1128 1126 def test_blocked
1129 1127 blocked_issue = Issue.find(9)
1130 1128 blocking_issue = Issue.find(10)
1131 1129
1132 1130 assert blocked_issue.blocked?
1133 1131 assert !blocking_issue.blocked?
1134 1132 end
1135 1133
1136 1134 def test_blocked_issues_dont_allow_closed_statuses
1137 1135 blocked_issue = Issue.find(9)
1138 1136
1139 1137 allowed_statuses = blocked_issue.new_statuses_allowed_to(users(:users_002))
1140 1138 assert !allowed_statuses.empty?
1141 1139 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
1142 1140 assert closed_statuses.empty?
1143 1141 end
1144 1142
1145 1143 def test_unblocked_issues_allow_closed_statuses
1146 1144 blocking_issue = Issue.find(10)
1147 1145
1148 1146 allowed_statuses = blocking_issue.new_statuses_allowed_to(users(:users_002))
1149 1147 assert !allowed_statuses.empty?
1150 1148 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
1151 1149 assert !closed_statuses.empty?
1152 1150 end
1153 1151
1154 1152 def test_rescheduling_an_issue_should_reschedule_following_issue
1155 1153 issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => '-', :start_date => Date.today, :due_date => Date.today + 2)
1156 1154 issue2 = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => '-', :start_date => Date.today, :due_date => Date.today + 2)
1157 1155 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
1158 1156 assert_equal issue1.due_date + 1, issue2.reload.start_date
1159 1157
1160 1158 issue1.due_date = Date.today + 5
1161 1159 issue1.save!
1162 1160 assert_equal issue1.due_date + 1, issue2.reload.start_date
1163 1161 end
1164 1162
1165 1163 def test_rescheduling_a_stale_issue_should_not_raise_an_error
1166 1164 stale = Issue.find(1)
1167 1165 issue = Issue.find(1)
1168 1166 issue.subject = "Updated"
1169 1167 issue.save!
1170 1168
1171 1169 date = 10.days.from_now.to_date
1172 1170 assert_nothing_raised do
1173 1171 stale.reschedule_after(date)
1174 1172 end
1175 1173 assert_equal date, stale.reload.start_date
1176 1174 end
1177 1175
1178 1176 def test_overdue
1179 1177 assert Issue.new(:due_date => 1.day.ago.to_date).overdue?
1180 1178 assert !Issue.new(:due_date => Date.today).overdue?
1181 1179 assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue?
1182 1180 assert !Issue.new(:due_date => nil).overdue?
1183 1181 assert !Issue.new(:due_date => 1.day.ago.to_date, :status => IssueStatus.find(:first, :conditions => {:is_closed => true})).overdue?
1184 1182 end
1185 1183
1186 1184 context "#behind_schedule?" do
1187 1185 should "be false if the issue has no start_date" do
1188 1186 assert !Issue.new(:start_date => nil, :due_date => 1.day.from_now.to_date, :done_ratio => 0).behind_schedule?
1189 1187 end
1190 1188
1191 1189 should "be false if the issue has no end_date" do
1192 1190 assert !Issue.new(:start_date => 1.day.from_now.to_date, :due_date => nil, :done_ratio => 0).behind_schedule?
1193 1191 end
1194 1192
1195 1193 should "be false if the issue has more done than it's calendar time" do
1196 1194 assert !Issue.new(:start_date => 50.days.ago.to_date, :due_date => 50.days.from_now.to_date, :done_ratio => 90).behind_schedule?
1197 1195 end
1198 1196
1199 1197 should "be true if the issue hasn't been started at all" do
1200 1198 assert Issue.new(:start_date => 1.day.ago.to_date, :due_date => 1.day.from_now.to_date, :done_ratio => 0).behind_schedule?
1201 1199 end
1202 1200
1203 1201 should "be true if the issue has used more calendar time than it's done ratio" do
1204 1202 assert Issue.new(:start_date => 100.days.ago.to_date, :due_date => Date.today, :done_ratio => 90).behind_schedule?
1205 1203 end
1206 1204 end
1207 1205
1208 1206 context "#assignable_users" do
1209 1207 should "be Users" do
1210 1208 assert_kind_of User, Issue.find(1).assignable_users.first
1211 1209 end
1212 1210
1213 1211 should "include the issue author" do
1214 project = Project.find(1)
1215 1212 non_project_member = User.generate!
1216 issue = Issue.generate_for_project!(project, :author => non_project_member)
1213 issue = Issue.generate!(:author => non_project_member)
1217 1214
1218 1215 assert issue.assignable_users.include?(non_project_member)
1219 1216 end
1220 1217
1221 1218 should "include the current assignee" do
1222 project = Project.find(1)
1223 1219 user = User.generate!
1224 issue = Issue.generate_for_project!(project, :assigned_to => user)
1220 issue = Issue.generate!(:assigned_to => user)
1225 1221 user.lock!
1226 1222
1227 1223 assert Issue.find(issue.id).assignable_users.include?(user)
1228 1224 end
1229 1225
1230 1226 should "not show the issue author twice" do
1231 1227 assignable_user_ids = Issue.find(1).assignable_users.collect(&:id)
1232 1228 assert_equal 2, assignable_user_ids.length
1233 1229
1234 1230 assignable_user_ids.each do |user_id|
1235 1231 assert_equal 1, assignable_user_ids.select {|i| i == user_id}.length, "User #{user_id} appears more or less than once"
1236 1232 end
1237 1233 end
1238 1234
1239 1235 context "with issue_group_assignment" do
1240 1236 should "include groups" do
1241 1237 issue = Issue.new(:project => Project.find(2))
1242 1238
1243 1239 with_settings :issue_group_assignment => '1' do
1244 1240 assert_equal %w(Group User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
1245 1241 assert issue.assignable_users.include?(Group.find(11))
1246 1242 end
1247 1243 end
1248 1244 end
1249 1245
1250 1246 context "without issue_group_assignment" do
1251 1247 should "not include groups" do
1252 1248 issue = Issue.new(:project => Project.find(2))
1253 1249
1254 1250 with_settings :issue_group_assignment => '0' do
1255 1251 assert_equal %w(User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
1256 1252 assert !issue.assignable_users.include?(Group.find(11))
1257 1253 end
1258 1254 end
1259 1255 end
1260 1256 end
1261 1257
1262 1258 def test_create_should_send_email_notification
1263 1259 ActionMailer::Base.deliveries.clear
1264 1260 issue = Issue.new(:project_id => 1, :tracker_id => 1,
1265 1261 :author_id => 3, :status_id => 1,
1266 1262 :priority => IssuePriority.all.first,
1267 1263 :subject => 'test_create', :estimated_hours => '1:30')
1268 1264
1269 1265 assert issue.save
1270 1266 assert_equal 1, ActionMailer::Base.deliveries.size
1271 1267 end
1272 1268
1273 1269 def test_stale_issue_should_not_send_email_notification
1274 1270 ActionMailer::Base.deliveries.clear
1275 1271 issue = Issue.find(1)
1276 1272 stale = Issue.find(1)
1277 1273
1278 1274 issue.init_journal(User.find(1))
1279 1275 issue.subject = 'Subjet update'
1280 1276 assert issue.save
1281 1277 assert_equal 1, ActionMailer::Base.deliveries.size
1282 1278 ActionMailer::Base.deliveries.clear
1283 1279
1284 1280 stale.init_journal(User.find(1))
1285 1281 stale.subject = 'Another subjet update'
1286 1282 assert_raise ActiveRecord::StaleObjectError do
1287 1283 stale.save
1288 1284 end
1289 1285 assert ActionMailer::Base.deliveries.empty?
1290 1286 end
1291 1287
1292 1288 def test_journalized_description
1293 1289 IssueCustomField.delete_all
1294 1290
1295 1291 i = Issue.first
1296 1292 old_description = i.description
1297 1293 new_description = "This is the new description"
1298 1294
1299 1295 i.init_journal(User.find(2))
1300 1296 i.description = new_description
1301 1297 assert_difference 'Journal.count', 1 do
1302 1298 assert_difference 'JournalDetail.count', 1 do
1303 1299 i.save!
1304 1300 end
1305 1301 end
1306 1302
1307 1303 detail = JournalDetail.first(:order => 'id DESC')
1308 1304 assert_equal i, detail.journal.journalized
1309 1305 assert_equal 'attr', detail.property
1310 1306 assert_equal 'description', detail.prop_key
1311 1307 assert_equal old_description, detail.old_value
1312 1308 assert_equal new_description, detail.value
1313 1309 end
1314 1310
1315 1311 def test_blank_descriptions_should_not_be_journalized
1316 1312 IssueCustomField.delete_all
1317 1313 Issue.update_all("description = NULL", "id=1")
1318 1314
1319 1315 i = Issue.find(1)
1320 1316 i.init_journal(User.find(2))
1321 1317 i.subject = "blank description"
1322 1318 i.description = "\r\n"
1323 1319
1324 1320 assert_difference 'Journal.count', 1 do
1325 1321 assert_difference 'JournalDetail.count', 1 do
1326 1322 i.save!
1327 1323 end
1328 1324 end
1329 1325 end
1330 1326
1331 1327 def test_journalized_multi_custom_field
1332 1328 field = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
1333 1329 :tracker_ids => [1], :possible_values => ['value1', 'value2', 'value3'], :multiple => true)
1334 1330
1335 1331 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :subject => 'Test', :author_id => 1)
1336 1332
1337 1333 assert_difference 'Journal.count' do
1338 1334 assert_difference 'JournalDetail.count' do
1339 1335 issue.init_journal(User.first)
1340 1336 issue.custom_field_values = {field.id => ['value1']}
1341 1337 issue.save!
1342 1338 end
1343 1339 assert_difference 'JournalDetail.count' do
1344 1340 issue.init_journal(User.first)
1345 1341 issue.custom_field_values = {field.id => ['value1', 'value2']}
1346 1342 issue.save!
1347 1343 end
1348 1344 assert_difference 'JournalDetail.count', 2 do
1349 1345 issue.init_journal(User.first)
1350 1346 issue.custom_field_values = {field.id => ['value3', 'value2']}
1351 1347 issue.save!
1352 1348 end
1353 1349 assert_difference 'JournalDetail.count', 2 do
1354 1350 issue.init_journal(User.first)
1355 1351 issue.custom_field_values = {field.id => nil}
1356 1352 issue.save!
1357 1353 end
1358 1354 end
1359 1355 end
1360 1356
1361 1357 def test_description_eol_should_be_normalized
1362 1358 i = Issue.new(:description => "CR \r LF \n CRLF \r\n")
1363 1359 assert_equal "CR \r\n LF \r\n CRLF \r\n", i.description
1364 1360 end
1365 1361
1366 1362 def test_saving_twice_should_not_duplicate_journal_details
1367 1363 i = Issue.find(:first)
1368 1364 i.init_journal(User.find(2), 'Some notes')
1369 1365 # initial changes
1370 1366 i.subject = 'New subject'
1371 1367 i.done_ratio = i.done_ratio + 10
1372 1368 assert_difference 'Journal.count' do
1373 1369 assert i.save
1374 1370 end
1375 1371 # 1 more change
1376 1372 i.priority = IssuePriority.find(:first, :conditions => ["id <> ?", i.priority_id])
1377 1373 assert_no_difference 'Journal.count' do
1378 1374 assert_difference 'JournalDetail.count', 1 do
1379 1375 i.save
1380 1376 end
1381 1377 end
1382 1378 # no more change
1383 1379 assert_no_difference 'Journal.count' do
1384 1380 assert_no_difference 'JournalDetail.count' do
1385 1381 i.save
1386 1382 end
1387 1383 end
1388 1384 end
1389 1385
1390 1386 def test_all_dependent_issues
1391 1387 IssueRelation.delete_all
1392 1388 assert IssueRelation.create!(:issue_from => Issue.find(1),
1393 1389 :issue_to => Issue.find(2),
1394 1390 :relation_type => IssueRelation::TYPE_PRECEDES)
1395 1391 assert IssueRelation.create!(:issue_from => Issue.find(2),
1396 1392 :issue_to => Issue.find(3),
1397 1393 :relation_type => IssueRelation::TYPE_PRECEDES)
1398 1394 assert IssueRelation.create!(:issue_from => Issue.find(3),
1399 1395 :issue_to => Issue.find(8),
1400 1396 :relation_type => IssueRelation::TYPE_PRECEDES)
1401 1397
1402 1398 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
1403 1399 end
1404 1400
1405 1401 def test_all_dependent_issues_with_persistent_circular_dependency
1406 1402 IssueRelation.delete_all
1407 1403 assert IssueRelation.create!(:issue_from => Issue.find(1),
1408 1404 :issue_to => Issue.find(2),
1409 1405 :relation_type => IssueRelation::TYPE_PRECEDES)
1410 1406 assert IssueRelation.create!(:issue_from => Issue.find(2),
1411 1407 :issue_to => Issue.find(3),
1412 1408 :relation_type => IssueRelation::TYPE_PRECEDES)
1413 1409
1414 1410 r = IssueRelation.create!(:issue_from => Issue.find(3),
1415 1411 :issue_to => Issue.find(7),
1416 1412 :relation_type => IssueRelation::TYPE_PRECEDES)
1417 1413 IssueRelation.update_all("issue_to_id = 1", ["id = ?", r.id])
1418 1414
1419 1415 assert_equal [2, 3], Issue.find(1).all_dependent_issues.collect(&:id).sort
1420 1416 end
1421 1417
1422 1418 def test_all_dependent_issues_with_persistent_multiple_circular_dependencies
1423 1419 IssueRelation.delete_all
1424 1420 assert IssueRelation.create!(:issue_from => Issue.find(1),
1425 1421 :issue_to => Issue.find(2),
1426 1422 :relation_type => IssueRelation::TYPE_RELATES)
1427 1423 assert IssueRelation.create!(:issue_from => Issue.find(2),
1428 1424 :issue_to => Issue.find(3),
1429 1425 :relation_type => IssueRelation::TYPE_RELATES)
1430 1426 assert IssueRelation.create!(:issue_from => Issue.find(3),
1431 1427 :issue_to => Issue.find(8),
1432 1428 :relation_type => IssueRelation::TYPE_RELATES)
1433 1429
1434 1430 r = IssueRelation.create!(:issue_from => Issue.find(8),
1435 1431 :issue_to => Issue.find(7),
1436 1432 :relation_type => IssueRelation::TYPE_RELATES)
1437 1433 IssueRelation.update_all("issue_to_id = 2", ["id = ?", r.id])
1438 1434
1439 1435 r = IssueRelation.create!(:issue_from => Issue.find(3),
1440 1436 :issue_to => Issue.find(7),
1441 1437 :relation_type => IssueRelation::TYPE_RELATES)
1442 1438 IssueRelation.update_all("issue_to_id = 1", ["id = ?", r.id])
1443 1439
1444 1440 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
1445 1441 end
1446 1442
1447 1443 context "#done_ratio" do
1448 1444 setup do
1449 1445 @issue = Issue.find(1)
1450 1446 @issue_status = IssueStatus.find(1)
1451 1447 @issue_status.update_attribute(:default_done_ratio, 50)
1452 1448 @issue2 = Issue.find(2)
1453 1449 @issue_status2 = IssueStatus.find(2)
1454 1450 @issue_status2.update_attribute(:default_done_ratio, 0)
1455 1451 end
1456 1452
1457 1453 teardown do
1458 1454 Setting.issue_done_ratio = 'issue_field'
1459 1455 end
1460 1456
1461 1457 context "with Setting.issue_done_ratio using the issue_field" do
1462 1458 setup do
1463 1459 Setting.issue_done_ratio = 'issue_field'
1464 1460 end
1465 1461
1466 1462 should "read the issue's field" do
1467 1463 assert_equal 0, @issue.done_ratio
1468 1464 assert_equal 30, @issue2.done_ratio
1469 1465 end
1470 1466 end
1471 1467
1472 1468 context "with Setting.issue_done_ratio using the issue_status" do
1473 1469 setup do
1474 1470 Setting.issue_done_ratio = 'issue_status'
1475 1471 end
1476 1472
1477 1473 should "read the Issue Status's default done ratio" do
1478 1474 assert_equal 50, @issue.done_ratio
1479 1475 assert_equal 0, @issue2.done_ratio
1480 1476 end
1481 1477 end
1482 1478 end
1483 1479
1484 1480 context "#update_done_ratio_from_issue_status" do
1485 1481 setup do
1486 1482 @issue = Issue.find(1)
1487 1483 @issue_status = IssueStatus.find(1)
1488 1484 @issue_status.update_attribute(:default_done_ratio, 50)
1489 1485 @issue2 = Issue.find(2)
1490 1486 @issue_status2 = IssueStatus.find(2)
1491 1487 @issue_status2.update_attribute(:default_done_ratio, 0)
1492 1488 end
1493 1489
1494 1490 context "with Setting.issue_done_ratio using the issue_field" do
1495 1491 setup do
1496 1492 Setting.issue_done_ratio = 'issue_field'
1497 1493 end
1498 1494
1499 1495 should "not change the issue" do
1500 1496 @issue.update_done_ratio_from_issue_status
1501 1497 @issue2.update_done_ratio_from_issue_status
1502 1498
1503 1499 assert_equal 0, @issue.read_attribute(:done_ratio)
1504 1500 assert_equal 30, @issue2.read_attribute(:done_ratio)
1505 1501 end
1506 1502 end
1507 1503
1508 1504 context "with Setting.issue_done_ratio using the issue_status" do
1509 1505 setup do
1510 1506 Setting.issue_done_ratio = 'issue_status'
1511 1507 end
1512 1508
1513 1509 should "change the issue's done ratio" do
1514 1510 @issue.update_done_ratio_from_issue_status
1515 1511 @issue2.update_done_ratio_from_issue_status
1516 1512
1517 1513 assert_equal 50, @issue.read_attribute(:done_ratio)
1518 1514 assert_equal 0, @issue2.read_attribute(:done_ratio)
1519 1515 end
1520 1516 end
1521 1517 end
1522 1518
1523 1519 test "#by_tracker" do
1524 1520 User.current = User.anonymous
1525 1521 groups = Issue.by_tracker(Project.find(1))
1526 1522 assert_equal 3, groups.size
1527 1523 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1528 1524 end
1529 1525
1530 1526 test "#by_version" do
1531 1527 User.current = User.anonymous
1532 1528 groups = Issue.by_version(Project.find(1))
1533 1529 assert_equal 3, groups.size
1534 1530 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1535 1531 end
1536 1532
1537 1533 test "#by_priority" do
1538 1534 User.current = User.anonymous
1539 1535 groups = Issue.by_priority(Project.find(1))
1540 1536 assert_equal 4, groups.size
1541 1537 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1542 1538 end
1543 1539
1544 1540 test "#by_category" do
1545 1541 User.current = User.anonymous
1546 1542 groups = Issue.by_category(Project.find(1))
1547 1543 assert_equal 2, groups.size
1548 1544 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1549 1545 end
1550 1546
1551 1547 test "#by_assigned_to" do
1552 1548 User.current = User.anonymous
1553 1549 groups = Issue.by_assigned_to(Project.find(1))
1554 1550 assert_equal 2, groups.size
1555 1551 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1556 1552 end
1557 1553
1558 1554 test "#by_author" do
1559 1555 User.current = User.anonymous
1560 1556 groups = Issue.by_author(Project.find(1))
1561 1557 assert_equal 4, groups.size
1562 1558 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1563 1559 end
1564 1560
1565 1561 test "#by_subproject" do
1566 1562 User.current = User.anonymous
1567 1563 groups = Issue.by_subproject(Project.find(1))
1568 1564 # Private descendant not visible
1569 1565 assert_equal 1, groups.size
1570 1566 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1571 1567 end
1572 1568
1573 1569 def test_recently_updated_scope
1574 1570 #should return the last updated issue
1575 1571 assert_equal Issue.reorder("updated_on DESC").first, Issue.recently_updated.limit(1).first
1576 1572 end
1577 1573
1578 1574 def test_on_active_projects_scope
1579 1575 assert Project.find(2).archive
1580 1576
1581 1577 before = Issue.on_active_project.length
1582 1578 # test inclusion to results
1583 issue = Issue.generate_for_project!(Project.find(1), :tracker => Project.find(2).trackers.first)
1579 issue = Issue.generate!(:tracker => Project.find(2).trackers.first)
1584 1580 assert_equal before + 1, Issue.on_active_project.length
1585 1581
1586 1582 # Move to an archived project
1587 1583 issue.project = Project.find(2)
1588 1584 assert issue.save
1589 1585 assert_equal before, Issue.on_active_project.length
1590 1586 end
1591 1587
1592 1588 context "Issue#recipients" do
1593 1589 setup do
1594 1590 @project = Project.find(1)
1595 1591 @author = User.generate!
1596 1592 @assignee = User.generate!
1597 @issue = Issue.generate_for_project!(@project, :assigned_to => @assignee, :author => @author)
1593 @issue = Issue.generate!(:project => @project, :assigned_to => @assignee, :author => @author)
1598 1594 end
1599 1595
1600 1596 should "include project recipients" do
1601 1597 assert @project.recipients.present?
1602 1598 @project.recipients.each do |project_recipient|
1603 1599 assert @issue.recipients.include?(project_recipient)
1604 1600 end
1605 1601 end
1606 1602
1607 1603 should "include the author if the author is active" do
1608 1604 assert @issue.author, "No author set for Issue"
1609 1605 assert @issue.recipients.include?(@issue.author.mail)
1610 1606 end
1611 1607
1612 1608 should "include the assigned to user if the assigned to user is active" do
1613 1609 assert @issue.assigned_to, "No assigned_to set for Issue"
1614 1610 assert @issue.recipients.include?(@issue.assigned_to.mail)
1615 1611 end
1616 1612
1617 1613 should "not include users who opt out of all email" do
1618 1614 @author.update_attribute(:mail_notification, :none)
1619 1615
1620 1616 assert !@issue.recipients.include?(@issue.author.mail)
1621 1617 end
1622 1618
1623 1619 should "not include the issue author if they are only notified of assigned issues" do
1624 1620 @author.update_attribute(:mail_notification, :only_assigned)
1625 1621
1626 1622 assert !@issue.recipients.include?(@issue.author.mail)
1627 1623 end
1628 1624
1629 1625 should "not include the assigned user if they are only notified of owned issues" do
1630 1626 @assignee.update_attribute(:mail_notification, :only_owner)
1631 1627
1632 1628 assert !@issue.recipients.include?(@issue.assigned_to.mail)
1633 1629 end
1634 1630 end
1635 1631
1636 1632 def test_last_journal_id_with_journals_should_return_the_journal_id
1637 1633 assert_equal 2, Issue.find(1).last_journal_id
1638 1634 end
1639 1635
1640 1636 def test_last_journal_id_without_journals_should_return_nil
1641 1637 assert_nil Issue.find(3).last_journal_id
1642 1638 end
1643 1639
1644 1640 def test_journals_after_should_return_journals_with_greater_id
1645 1641 assert_equal [Journal.find(2)], Issue.find(1).journals_after('1')
1646 1642 assert_equal [], Issue.find(1).journals_after('2')
1647 1643 end
1648 1644
1649 1645 def test_journals_after_with_blank_arg_should_return_all_journals
1650 1646 assert_equal [Journal.find(1), Journal.find(2)], Issue.find(1).journals_after('')
1651 1647 end
1652 1648 end
@@ -1,749 +1,749
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../../../../test_helper', __FILE__)
19 19
20 20 class Redmine::Helpers::GanttHelperTest < ActionView::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 :roles,
26 26 :member_roles,
27 27 :members,
28 28 :enabled_modules,
29 29 :workflows,
30 30 :versions,
31 31 :groups_users
32 32
33 33 include ApplicationHelper
34 34 include ProjectsHelper
35 35 include IssuesHelper
36 36 include ERB::Util
37 37
38 38 def setup
39 39 setup_with_controller
40 40 User.current = User.find(1)
41 41 end
42 42
43 43 def today
44 44 @today ||= Date.today
45 45 end
46 46
47 47 # Creates a Gantt chart for a 4 week span
48 48 def create_gantt(project=Project.generate!, options={})
49 49 @project = project
50 50 @gantt = Redmine::Helpers::Gantt.new(options)
51 51 @gantt.project = @project
52 52 @gantt.query = Query.create!(:project => @project, :name => 'Gantt')
53 53 @gantt.view = self
54 54 @gantt.instance_variable_set('@date_from', options[:date_from] || (today - 14))
55 55 @gantt.instance_variable_set('@date_to', options[:date_to] || (today + 14))
56 56 end
57 57
58 58 context "#number_of_rows" do
59 59 context "with one project" do
60 60 should "return the number of rows just for that project"
61 61 end
62 62
63 63 context "with no project" do
64 64 should "return the total number of rows for all the projects, resursively"
65 65 end
66 66
67 67 should "not exceed max_rows option" do
68 68 p = Project.generate!
69 69 5.times do
70 Issue.generate_for_project!(p)
70 Issue.generate!(:project => p)
71 71 end
72 72 create_gantt(p)
73 73 @gantt.render
74 74 assert_equal 6, @gantt.number_of_rows
75 75 assert !@gantt.truncated
76 76 create_gantt(p, :max_rows => 3)
77 77 @gantt.render
78 78 assert_equal 3, @gantt.number_of_rows
79 79 assert @gantt.truncated
80 80 end
81 81 end
82 82
83 83 context "#number_of_rows_on_project" do
84 84 setup do
85 85 create_gantt
86 86 end
87 87
88 88 should "count 0 for an empty the project" do
89 89 assert_equal 0, @gantt.number_of_rows_on_project(@project)
90 90 end
91 91
92 92 should "count the number of issues without a version" do
93 @project.issues << Issue.generate_for_project!(@project, :fixed_version => nil)
93 @project.issues << Issue.generate!(:project => @project, :fixed_version => nil)
94 94 assert_equal 2, @gantt.number_of_rows_on_project(@project)
95 95 end
96 96
97 97 should "count the number of issues on versions, including cross-project" do
98 98 version = Version.generate!
99 99 @project.versions << version
100 @project.issues << Issue.generate_for_project!(@project, :fixed_version => version)
100 @project.issues << Issue.generate!(:project => @project, :fixed_version => version)
101 101 assert_equal 3, @gantt.number_of_rows_on_project(@project)
102 102 end
103 103 end
104 104
105 105 # TODO: more of an integration test
106 106 context "#subjects" do
107 107 setup do
108 108 create_gantt
109 109 @project.enabled_module_names = [:issue_tracking]
110 110 @tracker = Tracker.generate!
111 111 @project.trackers << @tracker
112 112 @version = Version.generate!(:effective_date => (today + 7), :sharing => 'none')
113 113 @project.versions << @version
114 114 @issue = Issue.generate!(:fixed_version => @version,
115 115 :subject => "gantt#line_for_project",
116 116 :tracker => @tracker,
117 117 :project => @project,
118 118 :done_ratio => 30,
119 119 :start_date => (today - 1),
120 120 :due_date => (today + 7))
121 121 @project.issues << @issue
122 122 end
123 123
124 124 context "project" do
125 125 should "be rendered" do
126 126 @output_buffer = @gantt.subjects
127 127 assert_select "div.project-name a", /#{@project.name}/
128 128 end
129 129
130 130 should "have an indent of 4" do
131 131 @output_buffer = @gantt.subjects
132 132 assert_select "div.project-name[style*=left:4px]"
133 133 end
134 134 end
135 135
136 136 context "version" do
137 137 should "be rendered" do
138 138 @output_buffer = @gantt.subjects
139 139 assert_select "div.version-name a", /#{@version.name}/
140 140 end
141 141
142 142 should "be indented 24 (one level)" do
143 143 @output_buffer = @gantt.subjects
144 144 assert_select "div.version-name[style*=left:24px]"
145 145 end
146 146
147 147 context "without assigned issues" do
148 148 setup do
149 149 @version = Version.generate!(:effective_date => (today + 14),
150 150 :sharing => 'none',
151 151 :name => 'empty_version')
152 152 @project.versions << @version
153 153 end
154 154
155 155 should "not be rendered" do
156 156 @output_buffer = @gantt.subjects
157 157 assert_select "div.version-name a", :text => /#{@version.name}/, :count => 0
158 158 end
159 159 end
160 160 end
161 161
162 162 context "issue" do
163 163 should "be rendered" do
164 164 @output_buffer = @gantt.subjects
165 165 assert_select "div.issue-subject", /#{@issue.subject}/
166 166 end
167 167
168 168 should "be indented 44 (two levels)" do
169 169 @output_buffer = @gantt.subjects
170 170 assert_select "div.issue-subject[style*=left:44px]"
171 171 end
172 172
173 173 context "assigned to a shared version of another project" do
174 174 setup do
175 175 p = Project.generate!
176 176 p.enabled_module_names = [:issue_tracking]
177 177 @shared_version = Version.generate!(:sharing => 'system')
178 178 p.versions << @shared_version
179 179 # Reassign the issue to a shared version of another project
180 180 @issue = Issue.generate!(:fixed_version => @shared_version,
181 181 :subject => "gantt#assigned_to_shared_version",
182 182 :tracker => @tracker,
183 183 :project => @project,
184 184 :done_ratio => 30,
185 185 :start_date => (today - 1),
186 186 :due_date => (today + 7))
187 187 @project.issues << @issue
188 188 end
189 189
190 190 should "be rendered" do
191 191 @output_buffer = @gantt.subjects
192 192 assert_select "div.issue-subject", /#{@issue.subject}/
193 193 end
194 194 end
195 195
196 196 context "with subtasks" do
197 197 setup do
198 198 attrs = {:project => @project, :tracker => @tracker, :fixed_version => @version}
199 199 @child1 = Issue.generate!(
200 200 attrs.merge(:subject => 'child1',
201 201 :parent_issue_id => @issue.id,
202 202 :start_date => (today - 1),
203 203 :due_date => (today + 2))
204 204 )
205 205 @child2 = Issue.generate!(
206 206 attrs.merge(:subject => 'child2',
207 207 :parent_issue_id => @issue.id,
208 208 :start_date => today,
209 209 :due_date => (today + 7))
210 210 )
211 211 @grandchild = Issue.generate!(
212 212 attrs.merge(:subject => 'grandchild',
213 213 :parent_issue_id => @child1.id,
214 214 :start_date => (today - 1),
215 215 :due_date => (today + 2))
216 216 )
217 217 end
218 218
219 219 should "indent subtasks" do
220 220 @output_buffer = @gantt.subjects
221 221 # parent task 44px
222 222 assert_select "div.issue-subject[style*=left:44px]", /#{@issue.subject}/
223 223 # children 64px
224 224 assert_select "div.issue-subject[style*=left:64px]", /child1/
225 225 assert_select "div.issue-subject[style*=left:64px]", /child2/
226 226 # grandchild 84px
227 227 assert_select "div.issue-subject[style*=left:84px]", /grandchild/, @output_buffer
228 228 end
229 229 end
230 230 end
231 231 end
232 232
233 233 context "#lines" do
234 234 setup do
235 235 create_gantt
236 236 @project.enabled_module_names = [:issue_tracking]
237 237 @tracker = Tracker.generate!
238 238 @project.trackers << @tracker
239 239 @version = Version.generate!(:effective_date => (today + 7))
240 240 @project.versions << @version
241 241 @issue = Issue.generate!(:fixed_version => @version,
242 242 :subject => "gantt#line_for_project",
243 243 :tracker => @tracker,
244 244 :project => @project,
245 245 :done_ratio => 30,
246 246 :start_date => (today - 1),
247 247 :due_date => (today + 7))
248 248 @project.issues << @issue
249 249 @output_buffer = @gantt.lines
250 250 end
251 251
252 252 context "project" do
253 253 should "be rendered" do
254 254 assert_select "div.project.task_todo"
255 255 assert_select "div.project.starting"
256 256 assert_select "div.project.ending"
257 257 assert_select "div.label.project", /#{@project.name}/
258 258 end
259 259 end
260 260
261 261 context "version" do
262 262 should "be rendered" do
263 263 assert_select "div.version.task_todo"
264 264 assert_select "div.version.starting"
265 265 assert_select "div.version.ending"
266 266 assert_select "div.label.version", /#{@version.name}/
267 267 end
268 268 end
269 269
270 270 context "issue" do
271 271 should "be rendered" do
272 272 assert_select "div.task_todo"
273 273 assert_select "div.task.label", /#{@issue.done_ratio}/
274 274 assert_select "div.tooltip", /#{@issue.subject}/
275 275 end
276 276 end
277 277 end
278 278
279 279 context "#render_project" do
280 280 should "be tested"
281 281 end
282 282
283 283 context "#render_issues" do
284 284 should "be tested"
285 285 end
286 286
287 287 context "#render_version" do
288 288 should "be tested"
289 289 end
290 290
291 291 context "#subject_for_project" do
292 292 setup do
293 293 create_gantt
294 294 end
295 295
296 296 context ":html format" do
297 297 should "add an absolute positioned div" do
298 298 @output_buffer = @gantt.subject_for_project(@project, {:format => :html})
299 299 assert_select "div[style*=absolute]"
300 300 end
301 301
302 302 should "use the indent option to move the div to the right" do
303 303 @output_buffer = @gantt.subject_for_project(@project, {:format => :html, :indent => 40})
304 304 assert_select "div[style*=left:40]"
305 305 end
306 306
307 307 should "include the project name" do
308 308 @output_buffer = @gantt.subject_for_project(@project, {:format => :html})
309 309 assert_select 'div', :text => /#{@project.name}/
310 310 end
311 311
312 312 should "include a link to the project" do
313 313 @output_buffer = @gantt.subject_for_project(@project, {:format => :html})
314 314 assert_select 'a[href=?]', "/projects/#{@project.identifier}", :text => /#{@project.name}/
315 315 end
316 316
317 317 should "style overdue projects" do
318 318 @project.enabled_module_names = [:issue_tracking]
319 319 @project.versions << Version.generate!(:effective_date => (today - 1))
320 320 assert @project.reload.overdue?, "Need an overdue project for this test"
321 321 @output_buffer = @gantt.subject_for_project(@project, {:format => :html})
322 322 assert_select 'div span.project-overdue'
323 323 end
324 324 end
325 325 should "test the PNG format"
326 326 should "test the PDF format"
327 327 end
328 328
329 329 context "#line_for_project" do
330 330 setup do
331 331 create_gantt
332 332 @project.enabled_module_names = [:issue_tracking]
333 333 @tracker = Tracker.generate!
334 334 @project.trackers << @tracker
335 335 @version = Version.generate!(:effective_date => (today - 1))
336 336 @project.versions << @version
337 337 @project.issues << Issue.generate!(:fixed_version => @version,
338 338 :subject => "gantt#line_for_project",
339 339 :tracker => @tracker,
340 340 :project => @project,
341 341 :done_ratio => 30,
342 342 :start_date => (today - 7),
343 343 :due_date => (today + 7))
344 344 end
345 345
346 346 context ":html format" do
347 347 context "todo line" do
348 348 should "start from the starting point on the left" do
349 349 @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
350 350 assert_select "div.project.task_todo[style*=left:28px]", true, @output_buffer
351 351 end
352 352
353 353 should "be the total width of the project" do
354 354 @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
355 355 assert_select "div.project.task_todo[style*=width:58px]", true, @output_buffer
356 356 end
357 357 end
358 358
359 359 context "late line" do
360 360 should_eventually "start from the starting point on the left" do
361 361 @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
362 362 assert_select "div.project.task_late[style*=left:28px]", true, @output_buffer
363 363 end
364 364
365 365 should_eventually "be the total delayed width of the project" do
366 366 @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
367 367 assert_select "div.project.task_late[style*=width:30px]", true, @output_buffer
368 368 end
369 369 end
370 370
371 371 context "done line" do
372 372 should_eventually "start from the starting point on the left" do
373 373 @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
374 374 assert_select "div.project.task_done[style*=left:28px]", true, @output_buffer
375 375 end
376 376
377 377 should_eventually "Be the total done width of the project" do
378 378 @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
379 379 assert_select "div.project.task_done[style*=width:18px]", true, @output_buffer
380 380 end
381 381 end
382 382
383 383 context "starting marker" do
384 384 should "not appear if the starting point is off the gantt chart" do
385 385 # Shift the date range of the chart
386 386 @gantt.instance_variable_set('@date_from', today)
387 387 @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
388 388 assert_select "div.project.starting", false, @output_buffer
389 389 end
390 390
391 391 should "appear at the starting point" do
392 392 @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
393 393 assert_select "div.project.starting[style*=left:28px]", true, @output_buffer
394 394 end
395 395 end
396 396
397 397 context "ending marker" do
398 398 should "not appear if the starting point is off the gantt chart" do
399 399 # Shift the date range of the chart
400 400 @gantt.instance_variable_set('@date_to', (today - 14))
401 401 @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
402 402 assert_select "div.project.ending", false, @output_buffer
403 403 end
404 404
405 405 should "appear at the end of the date range" do
406 406 @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
407 407 assert_select "div.project.ending[style*=left:88px]", true, @output_buffer
408 408 end
409 409 end
410 410
411 411 context "status content" do
412 412 should "appear at the far left, even if it's far in the past" do
413 413 @gantt.instance_variable_set('@date_to', (today - 14))
414 414 @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
415 415 assert_select "div.project.label", /#{@project.name}/
416 416 end
417 417
418 418 should "show the project name" do
419 419 @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
420 420 assert_select "div.project.label", /#{@project.name}/
421 421 end
422 422
423 423 should_eventually "show the percent complete" do
424 424 @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
425 425 assert_select "div.project.label", /0%/
426 426 end
427 427 end
428 428 end
429 429 should "test the PNG format"
430 430 should "test the PDF format"
431 431 end
432 432
433 433 context "#subject_for_version" do
434 434 setup do
435 435 create_gantt
436 436 @project.enabled_module_names = [:issue_tracking]
437 437 @tracker = Tracker.generate!
438 438 @project.trackers << @tracker
439 439 @version = Version.generate!(:effective_date => (today - 1))
440 440 @project.versions << @version
441 441 @project.issues << Issue.generate!(:fixed_version => @version,
442 442 :subject => "gantt#subject_for_version",
443 443 :tracker => @tracker,
444 444 :project => @project,
445 445 :start_date => today)
446 446
447 447 end
448 448
449 449 context ":html format" do
450 450 should "add an absolute positioned div" do
451 451 @output_buffer = @gantt.subject_for_version(@version, {:format => :html})
452 452 assert_select "div[style*=absolute]"
453 453 end
454 454
455 455 should "use the indent option to move the div to the right" do
456 456 @output_buffer = @gantt.subject_for_version(@version, {:format => :html, :indent => 40})
457 457 assert_select "div[style*=left:40]"
458 458 end
459 459
460 460 should "include the version name" do
461 461 @output_buffer = @gantt.subject_for_version(@version, {:format => :html})
462 462 assert_select 'div', :text => /#{@version.name}/
463 463 end
464 464
465 465 should "include a link to the version" do
466 466 @output_buffer = @gantt.subject_for_version(@version, {:format => :html})
467 467 assert_select 'a[href=?]', Regexp.escape("/versions/#{@version.to_param}"), :text => /#{@version.name}/
468 468 end
469 469
470 470 should "style late versions" do
471 471 assert @version.overdue?, "Need an overdue version for this test"
472 472 @output_buffer = @gantt.subject_for_version(@version, {:format => :html})
473 473 assert_select 'div span.version-behind-schedule'
474 474 end
475 475
476 476 should "style behind schedule versions" do
477 477 assert @version.behind_schedule?, "Need a behind schedule version for this test"
478 478 @output_buffer = @gantt.subject_for_version(@version, {:format => :html})
479 479 assert_select 'div span.version-behind-schedule'
480 480 end
481 481 end
482 482 should "test the PNG format"
483 483 should "test the PDF format"
484 484 end
485 485
486 486 context "#line_for_version" do
487 487 setup do
488 488 create_gantt
489 489 @project.enabled_module_names = [:issue_tracking]
490 490 @tracker = Tracker.generate!
491 491 @project.trackers << @tracker
492 492 @version = Version.generate!(:effective_date => (today + 7))
493 493 @project.versions << @version
494 494 @project.issues << Issue.generate!(:fixed_version => @version,
495 495 :subject => "gantt#line_for_project",
496 496 :tracker => @tracker,
497 497 :project => @project,
498 498 :done_ratio => 30,
499 499 :start_date => (today - 7),
500 500 :due_date => (today + 7))
501 501 end
502 502
503 503 context ":html format" do
504 504 context "todo line" do
505 505 should "start from the starting point on the left" do
506 506 @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
507 507 assert_select "div.version.task_todo[style*=left:28px]", true, @output_buffer
508 508 end
509 509
510 510 should "be the total width of the version" do
511 511 @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
512 512 assert_select "div.version.task_todo[style*=width:58px]", true, @output_buffer
513 513 end
514 514 end
515 515
516 516 context "late line" do
517 517 should "start from the starting point on the left" do
518 518 @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
519 519 assert_select "div.version.task_late[style*=left:28px]", true, @output_buffer
520 520 end
521 521
522 522 should "be the total delayed width of the version" do
523 523 @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
524 524 assert_select "div.version.task_late[style*=width:30px]", true, @output_buffer
525 525 end
526 526 end
527 527
528 528 context "done line" do
529 529 should "start from the starting point on the left" do
530 530 @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
531 531 assert_select "div.version.task_done[style*=left:28px]", true, @output_buffer
532 532 end
533 533
534 534 should "be the total done width of the version" do
535 535 @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
536 536 assert_select "div.version.task_done[style*=width:16px]", true, @output_buffer
537 537 end
538 538 end
539 539
540 540 context "starting marker" do
541 541 should "not appear if the starting point is off the gantt chart" do
542 542 # Shift the date range of the chart
543 543 @gantt.instance_variable_set('@date_from', today)
544 544 @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
545 545 assert_select "div.version.starting", false
546 546 end
547 547
548 548 should "appear at the starting point" do
549 549 @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
550 550 assert_select "div.version.starting[style*=left:28px]", true, @output_buffer
551 551 end
552 552 end
553 553
554 554 context "ending marker" do
555 555 should "not appear if the starting point is off the gantt chart" do
556 556 # Shift the date range of the chart
557 557 @gantt.instance_variable_set('@date_to', (today - 14))
558 558 @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
559 559 assert_select "div.version.ending", false
560 560 end
561 561
562 562 should "appear at the end of the date range" do
563 563 @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
564 564 assert_select "div.version.ending[style*=left:88px]", true, @output_buffer
565 565 end
566 566 end
567 567
568 568 context "status content" do
569 569 should "appear at the far left, even if it's far in the past" do
570 570 @gantt.instance_variable_set('@date_to', (today - 14))
571 571 @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
572 572 assert_select "div.version.label", /#{@version.name}/
573 573 end
574 574
575 575 should "show the version name" do
576 576 @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
577 577 assert_select "div.version.label", /#{@version.name}/
578 578 end
579 579
580 580 should "show the percent complete" do
581 581 @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
582 582 assert_select "div.version.label", /30%/
583 583 end
584 584 end
585 585 end
586 586 should "test the PNG format"
587 587 should "test the PDF format"
588 588 end
589 589
590 590 context "#subject_for_issue" do
591 591 setup do
592 592 create_gantt
593 593 @project.enabled_module_names = [:issue_tracking]
594 594 @tracker = Tracker.generate!
595 595 @project.trackers << @tracker
596 596 @issue = Issue.generate!(:subject => "gantt#subject_for_issue",
597 597 :tracker => @tracker,
598 598 :project => @project,
599 599 :start_date => (today - 3),
600 600 :due_date => (today - 1))
601 601 @project.issues << @issue
602 602 end
603 603
604 604 context ":html format" do
605 605 should "add an absolute positioned div" do
606 606 @output_buffer = @gantt.subject_for_issue(@issue, {:format => :html})
607 607 assert_select "div[style*=absolute]"
608 608 end
609 609
610 610 should "use the indent option to move the div to the right" do
611 611 @output_buffer = @gantt.subject_for_issue(@issue, {:format => :html, :indent => 40})
612 612 assert_select "div[style*=left:40]"
613 613 end
614 614
615 615 should "include the issue subject" do
616 616 @output_buffer = @gantt.subject_for_issue(@issue, {:format => :html})
617 617 assert_select 'div', :text => /#{@issue.subject}/
618 618 end
619 619
620 620 should "include a link to the issue" do
621 621 @output_buffer = @gantt.subject_for_issue(@issue, {:format => :html})
622 622 assert_select 'a[href=?]', Regexp.escape("/issues/#{@issue.to_param}"), :text => /#{@tracker.name} ##{@issue.id}/
623 623 end
624 624
625 625 should "style overdue issues" do
626 626 assert @issue.overdue?, "Need an overdue issue for this test"
627 627 @output_buffer = @gantt.subject_for_issue(@issue, {:format => :html})
628 628 assert_select 'div span.issue-overdue'
629 629 end
630 630 end
631 631 should "test the PNG format"
632 632 should "test the PDF format"
633 633 end
634 634
635 635 context "#line_for_issue" do
636 636 setup do
637 637 create_gantt
638 638 @project.enabled_module_names = [:issue_tracking]
639 639 @tracker = Tracker.generate!
640 640 @project.trackers << @tracker
641 641 @version = Version.generate!(:effective_date => (today + 7))
642 642 @project.versions << @version
643 643 @issue = Issue.generate!(:fixed_version => @version,
644 644 :subject => "gantt#line_for_project",
645 645 :tracker => @tracker,
646 646 :project => @project,
647 647 :done_ratio => 30,
648 648 :start_date => (today - 7),
649 649 :due_date => (today + 7))
650 650 @project.issues << @issue
651 651 end
652 652
653 653 context ":html format" do
654 654 context "todo line" do
655 655 should "start from the starting point on the left" do
656 656 @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
657 657 assert_select "div.task_todo[style*=left:28px]", true, @output_buffer
658 658 end
659 659
660 660 should "be the total width of the issue" do
661 661 @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
662 662 assert_select "div.task_todo[style*=width:58px]", true, @output_buffer
663 663 end
664 664 end
665 665
666 666 context "late line" do
667 667 should "start from the starting point on the left" do
668 668 @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
669 669 assert_select "div.task_late[style*=left:28px]", true, @output_buffer
670 670 end
671 671
672 672 should "be the total delayed width of the issue" do
673 673 @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
674 674 assert_select "div.task_late[style*=width:30px]", true, @output_buffer
675 675 end
676 676 end
677 677
678 678 context "done line" do
679 679 should "start from the starting point on the left" do
680 680 @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
681 681 assert_select "div.task_done[style*=left:28px]", true, @output_buffer
682 682 end
683 683
684 684 should "be the total done width of the issue" do
685 685 @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
686 686 # 15 days * 4 px * 30% - 2 px for borders = 16 px
687 687 assert_select "div.task_done[style*=width:16px]", true, @output_buffer
688 688 end
689 689
690 690 should "not be the total done width if the chart starts after issue start date" do
691 691 create_gantt(@project, :date_from => (today - 5))
692 692 @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
693 693 assert_select "div.task_done[style*=left:0px]", true, @output_buffer
694 694 assert_select "div.task_done[style*=width:8px]", true, @output_buffer
695 695 end
696 696
697 697 context "for completed issue" do
698 698 setup do
699 699 @issue.done_ratio = 100
700 700 end
701 701
702 702 should "be the total width of the issue" do
703 703 @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
704 704 assert_select "div.task_done[style*=width:58px]", true, @output_buffer
705 705 end
706 706
707 707 should "be the total width of the issue with due_date=start_date" do
708 708 @issue.due_date = @issue.start_date
709 709 @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
710 710 assert_select "div.task_done[style*=width:2px]", true, @output_buffer
711 711 end
712 712 end
713 713 end
714 714
715 715 context "status content" do
716 716 should "appear at the far left, even if it's far in the past" do
717 717 @gantt.instance_variable_set('@date_to', (today - 14))
718 718 @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
719 719 assert_select "div.task.label", true, @output_buffer
720 720 end
721 721
722 722 should "show the issue status" do
723 723 @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
724 724 assert_select "div.task.label", /#{@issue.status.name}/
725 725 end
726 726
727 727 should "show the percent complete" do
728 728 @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
729 729 assert_select "div.task.label", /30%/
730 730 end
731 731 end
732 732 end
733 733
734 734 should "have an issue tooltip" do
735 735 @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
736 736 assert_select "div.tooltip", /#{@issue.subject}/
737 737 end
738 738 should "test the PNG format"
739 739 should "test the PDF format"
740 740 end
741 741
742 742 context "#to_image" do
743 743 should "be tested"
744 744 end
745 745
746 746 context "#to_pdf" do
747 747 should "be tested"
748 748 end
749 749 end
@@ -1,1234 +1,1228
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2012 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 :documents
40 40
41 41 def setup
42 42 @ecookbook = Project.find(1)
43 43 @ecookbook_sub1 = Project.find(3)
44 44 set_tmp_attachments_directory
45 45 User.current = nil
46 46 end
47 47
48 48 def test_truth
49 49 assert_kind_of Project, @ecookbook
50 50 assert_equal "eCookbook", @ecookbook.name
51 51 end
52 52
53 53 def test_default_attributes
54 54 with_settings :default_projects_public => '1' do
55 55 assert_equal true, Project.new.is_public
56 56 assert_equal false, Project.new(:is_public => false).is_public
57 57 end
58 58
59 59 with_settings :default_projects_public => '0' do
60 60 assert_equal false, Project.new.is_public
61 61 assert_equal true, Project.new(:is_public => true).is_public
62 62 end
63 63
64 64 with_settings :sequential_project_identifiers => '1' do
65 65 assert !Project.new.identifier.blank?
66 66 assert Project.new(:identifier => '').identifier.blank?
67 67 end
68 68
69 69 with_settings :sequential_project_identifiers => '0' do
70 70 assert Project.new.identifier.blank?
71 71 assert !Project.new(:identifier => 'test').blank?
72 72 end
73 73
74 74 with_settings :default_projects_modules => ['issue_tracking', 'repository'] do
75 75 assert_equal ['issue_tracking', 'repository'], Project.new.enabled_module_names
76 76 end
77 77
78 78 assert_equal Tracker.all.sort, Project.new.trackers.sort
79 79 assert_equal Tracker.find(1, 3).sort, Project.new(:tracker_ids => [1, 3]).trackers.sort
80 80 end
81 81
82 82 def test_update
83 83 assert_equal "eCookbook", @ecookbook.name
84 84 @ecookbook.name = "eCook"
85 85 assert @ecookbook.save, @ecookbook.errors.full_messages.join("; ")
86 86 @ecookbook.reload
87 87 assert_equal "eCook", @ecookbook.name
88 88 end
89 89
90 90 def test_validate_identifier
91 91 to_test = {"abc" => true,
92 92 "ab12" => true,
93 93 "ab-12" => true,
94 94 "ab_12" => true,
95 95 "12" => false,
96 96 "new" => false}
97 97
98 98 to_test.each do |identifier, valid|
99 99 p = Project.new
100 100 p.identifier = identifier
101 101 p.valid?
102 102 if valid
103 103 assert p.errors['identifier'].blank?, "identifier #{identifier} was not valid"
104 104 else
105 105 assert p.errors['identifier'].present?, "identifier #{identifier} was valid"
106 106 end
107 107 end
108 108 end
109 109
110 110 def test_identifier_should_not_be_frozen_for_a_new_project
111 111 assert_equal false, Project.new.identifier_frozen?
112 112 end
113 113
114 114 def test_identifier_should_not_be_frozen_for_a_saved_project_with_blank_identifier
115 115 Project.update_all(["identifier = ''"], "id = 1")
116 116
117 117 assert_equal false, Project.find(1).identifier_frozen?
118 118 end
119 119
120 120 def test_identifier_should_be_frozen_for_a_saved_project_with_valid_identifier
121 121 assert_equal true, Project.find(1).identifier_frozen?
122 122 end
123 123
124 124 def test_members_should_be_active_users
125 125 Project.all.each do |project|
126 126 assert_nil project.members.detect {|m| !(m.user.is_a?(User) && m.user.active?) }
127 127 end
128 128 end
129 129
130 130 def test_users_should_be_active_users
131 131 Project.all.each do |project|
132 132 assert_nil project.users.detect {|u| !(u.is_a?(User) && u.active?) }
133 133 end
134 134 end
135 135
136 136 def test_open_scope_on_issues_association
137 137 assert_kind_of Issue, Project.find(1).issues.open.first
138 138 end
139 139
140 140 def test_archive
141 141 user = @ecookbook.members.first.user
142 142 @ecookbook.archive
143 143 @ecookbook.reload
144 144
145 145 assert !@ecookbook.active?
146 146 assert @ecookbook.archived?
147 147 assert !user.projects.include?(@ecookbook)
148 148 # Subproject are also archived
149 149 assert !@ecookbook.children.empty?
150 150 assert @ecookbook.descendants.active.empty?
151 151 end
152 152
153 153 def test_archive_should_fail_if_versions_are_used_by_non_descendant_projects
154 154 # Assign an issue of a project to a version of a child project
155 155 Issue.find(4).update_attribute :fixed_version_id, 4
156 156
157 157 assert_no_difference "Project.count(:all, :conditions => 'status = #{Project::STATUS_ARCHIVED}')" do
158 158 assert_equal false, @ecookbook.archive
159 159 end
160 160 @ecookbook.reload
161 161 assert @ecookbook.active?
162 162 end
163 163
164 164 def test_unarchive
165 165 user = @ecookbook.members.first.user
166 166 @ecookbook.archive
167 167 # A subproject of an archived project can not be unarchived
168 168 assert !@ecookbook_sub1.unarchive
169 169
170 170 # Unarchive project
171 171 assert @ecookbook.unarchive
172 172 @ecookbook.reload
173 173 assert @ecookbook.active?
174 174 assert !@ecookbook.archived?
175 175 assert user.projects.include?(@ecookbook)
176 176 # Subproject can now be unarchived
177 177 @ecookbook_sub1.reload
178 178 assert @ecookbook_sub1.unarchive
179 179 end
180 180
181 181 def test_destroy
182 182 # 2 active members
183 183 assert_equal 2, @ecookbook.members.size
184 184 # and 1 is locked
185 185 assert_equal 3, Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).size
186 186 # some boards
187 187 assert @ecookbook.boards.any?
188 188
189 189 @ecookbook.destroy
190 190 # make sure that the project non longer exists
191 191 assert_raise(ActiveRecord::RecordNotFound) { Project.find(@ecookbook.id) }
192 192 # make sure related data was removed
193 193 assert_nil Member.first(:conditions => {:project_id => @ecookbook.id})
194 194 assert_nil Board.first(:conditions => {:project_id => @ecookbook.id})
195 195 assert_nil Issue.first(:conditions => {:project_id => @ecookbook.id})
196 196 end
197 197
198 198 def test_destroy_should_destroy_subtasks
199 199 issues = (0..2).to_a.map {Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :subject => 'test')}
200 200 issues[0].update_attribute :parent_issue_id, issues[1].id
201 201 issues[2].update_attribute :parent_issue_id, issues[1].id
202 202 assert_equal 2, issues[1].children.count
203 203
204 204 assert_nothing_raised do
205 205 Project.find(1).destroy
206 206 end
207 207 assert Issue.find_all_by_id(issues.map(&:id)).empty?
208 208 end
209 209
210 210 def test_destroying_root_projects_should_clear_data
211 211 Project.roots.each do |root|
212 212 root.destroy
213 213 end
214 214
215 215 assert_equal 0, Project.count, "Projects were not deleted: #{Project.all.inspect}"
216 216 assert_equal 0, Member.count, "Members were not deleted: #{Member.all.inspect}"
217 217 assert_equal 0, MemberRole.count
218 218 assert_equal 0, Issue.count
219 219 assert_equal 0, Journal.count
220 220 assert_equal 0, JournalDetail.count
221 221 assert_equal 0, Attachment.count, "Attachments were not deleted: #{Attachment.all.inspect}"
222 222 assert_equal 0, EnabledModule.count
223 223 assert_equal 0, IssueCategory.count
224 224 assert_equal 0, IssueRelation.count
225 225 assert_equal 0, Board.count
226 226 assert_equal 0, Message.count
227 227 assert_equal 0, News.count
228 228 assert_equal 0, Query.count(:conditions => "project_id IS NOT NULL")
229 229 assert_equal 0, Repository.count
230 230 assert_equal 0, Changeset.count
231 231 assert_equal 0, Change.count
232 232 assert_equal 0, Comment.count
233 233 assert_equal 0, TimeEntry.count
234 234 assert_equal 0, Version.count
235 235 assert_equal 0, Watcher.count
236 236 assert_equal 0, Wiki.count
237 237 assert_equal 0, WikiPage.count
238 238 assert_equal 0, WikiContent.count
239 239 assert_equal 0, WikiContent::Version.count
240 240 assert_equal 0, Project.connection.select_all("SELECT * FROM projects_trackers").size
241 241 assert_equal 0, Project.connection.select_all("SELECT * FROM custom_fields_projects").size
242 242 assert_equal 0, CustomValue.count(:conditions => {:customized_type => ['Project', 'Issue', 'TimeEntry', 'Version']})
243 243 end
244 244
245 245 def test_move_an_orphan_project_to_a_root_project
246 246 sub = Project.find(2)
247 247 sub.set_parent! @ecookbook
248 248 assert_equal @ecookbook.id, sub.parent.id
249 249 @ecookbook.reload
250 250 assert_equal 4, @ecookbook.children.size
251 251 end
252 252
253 253 def test_move_an_orphan_project_to_a_subproject
254 254 sub = Project.find(2)
255 255 assert sub.set_parent!(@ecookbook_sub1)
256 256 end
257 257
258 258 def test_move_a_root_project_to_a_project
259 259 sub = @ecookbook
260 260 assert sub.set_parent!(Project.find(2))
261 261 end
262 262
263 263 def test_should_not_move_a_project_to_its_children
264 264 sub = @ecookbook
265 265 assert !(sub.set_parent!(Project.find(3)))
266 266 end
267 267
268 268 def test_set_parent_should_add_roots_in_alphabetical_order
269 269 ProjectCustomField.delete_all
270 270 Project.delete_all
271 271 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(nil)
272 272 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(nil)
273 273 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(nil)
274 274 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(nil)
275 275
276 276 assert_equal 4, Project.count
277 277 assert_equal Project.all.sort_by(&:name), Project.all.sort_by(&:lft)
278 278 end
279 279
280 280 def test_set_parent_should_add_children_in_alphabetical_order
281 281 ProjectCustomField.delete_all
282 282 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
283 283 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(parent)
284 284 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(parent)
285 285 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(parent)
286 286 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(parent)
287 287
288 288 parent.reload
289 289 assert_equal 4, parent.children.size
290 290 assert_equal parent.children.all.sort_by(&:name), parent.children.all
291 291 end
292 292
293 293 def test_rebuild_should_sort_children_alphabetically
294 294 ProjectCustomField.delete_all
295 295 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
296 296 Project.create!(:name => 'Project C', :identifier => 'project-c').move_to_child_of(parent)
297 297 Project.create!(:name => 'Project B', :identifier => 'project-b').move_to_child_of(parent)
298 298 Project.create!(:name => 'Project D', :identifier => 'project-d').move_to_child_of(parent)
299 299 Project.create!(:name => 'Project A', :identifier => 'project-a').move_to_child_of(parent)
300 300
301 301 Project.update_all("lft = NULL, rgt = NULL")
302 302 Project.rebuild!
303 303
304 304 parent.reload
305 305 assert_equal 4, parent.children.size
306 306 assert_equal parent.children.all.sort_by(&:name), parent.children.all
307 307 end
308 308
309 309
310 310 def test_set_parent_should_update_issue_fixed_version_associations_when_a_fixed_version_is_moved_out_of_the_hierarchy
311 311 # Parent issue with a hierarchy project's fixed version
312 312 parent_issue = Issue.find(1)
313 313 parent_issue.update_attribute(:fixed_version_id, 4)
314 314 parent_issue.reload
315 315 assert_equal 4, parent_issue.fixed_version_id
316 316
317 317 # Should keep fixed versions for the issues
318 318 issue_with_local_fixed_version = Issue.find(5)
319 319 issue_with_local_fixed_version.update_attribute(:fixed_version_id, 4)
320 320 issue_with_local_fixed_version.reload
321 321 assert_equal 4, issue_with_local_fixed_version.fixed_version_id
322 322
323 323 # Local issue with hierarchy fixed_version
324 324 issue_with_hierarchy_fixed_version = Issue.find(13)
325 325 issue_with_hierarchy_fixed_version.update_attribute(:fixed_version_id, 6)
326 326 issue_with_hierarchy_fixed_version.reload
327 327 assert_equal 6, issue_with_hierarchy_fixed_version.fixed_version_id
328 328
329 329 # Move project out of the issue's hierarchy
330 330 moved_project = Project.find(3)
331 331 moved_project.set_parent!(Project.find(2))
332 332 parent_issue.reload
333 333 issue_with_local_fixed_version.reload
334 334 issue_with_hierarchy_fixed_version.reload
335 335
336 336 assert_equal 4, issue_with_local_fixed_version.fixed_version_id, "Fixed version was not keep on an issue local to the moved project"
337 337 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"
338 338 assert_equal nil, parent_issue.fixed_version_id, "Fixed version is still set after moving the Version out of the hierarchy for the issue."
339 339 end
340 340
341 341 def test_parent
342 342 p = Project.find(6).parent
343 343 assert p.is_a?(Project)
344 344 assert_equal 5, p.id
345 345 end
346 346
347 347 def test_ancestors
348 348 a = Project.find(6).ancestors
349 349 assert a.first.is_a?(Project)
350 350 assert_equal [1, 5], a.collect(&:id)
351 351 end
352 352
353 353 def test_root
354 354 r = Project.find(6).root
355 355 assert r.is_a?(Project)
356 356 assert_equal 1, r.id
357 357 end
358 358
359 359 def test_children
360 360 c = Project.find(1).children
361 361 assert c.first.is_a?(Project)
362 362 assert_equal [5, 3, 4], c.collect(&:id)
363 363 end
364 364
365 365 def test_descendants
366 366 d = Project.find(1).descendants
367 367 assert d.first.is_a?(Project)
368 368 assert_equal [5, 6, 3, 4], d.collect(&:id)
369 369 end
370 370
371 371 def test_allowed_parents_should_be_empty_for_non_member_user
372 372 Role.non_member.add_permission!(:add_project)
373 373 user = User.find(9)
374 374 assert user.memberships.empty?
375 375 User.current = user
376 376 assert Project.new.allowed_parents.compact.empty?
377 377 end
378 378
379 379 def test_allowed_parents_with_add_subprojects_permission
380 380 Role.find(1).remove_permission!(:add_project)
381 381 Role.find(1).add_permission!(:add_subprojects)
382 382 User.current = User.find(2)
383 383 # new project
384 384 assert !Project.new.allowed_parents.include?(nil)
385 385 assert Project.new.allowed_parents.include?(Project.find(1))
386 386 # existing root project
387 387 assert Project.find(1).allowed_parents.include?(nil)
388 388 # existing child
389 389 assert Project.find(3).allowed_parents.include?(Project.find(1))
390 390 assert !Project.find(3).allowed_parents.include?(nil)
391 391 end
392 392
393 393 def test_allowed_parents_with_add_project_permission
394 394 Role.find(1).add_permission!(:add_project)
395 395 Role.find(1).remove_permission!(:add_subprojects)
396 396 User.current = User.find(2)
397 397 # new project
398 398 assert Project.new.allowed_parents.include?(nil)
399 399 assert !Project.new.allowed_parents.include?(Project.find(1))
400 400 # existing root project
401 401 assert Project.find(1).allowed_parents.include?(nil)
402 402 # existing child
403 403 assert Project.find(3).allowed_parents.include?(Project.find(1))
404 404 assert Project.find(3).allowed_parents.include?(nil)
405 405 end
406 406
407 407 def test_allowed_parents_with_add_project_and_subprojects_permission
408 408 Role.find(1).add_permission!(:add_project)
409 409 Role.find(1).add_permission!(:add_subprojects)
410 410 User.current = User.find(2)
411 411 # new project
412 412 assert Project.new.allowed_parents.include?(nil)
413 413 assert Project.new.allowed_parents.include?(Project.find(1))
414 414 # existing root project
415 415 assert Project.find(1).allowed_parents.include?(nil)
416 416 # existing child
417 417 assert Project.find(3).allowed_parents.include?(Project.find(1))
418 418 assert Project.find(3).allowed_parents.include?(nil)
419 419 end
420 420
421 421 def test_users_by_role
422 422 users_by_role = Project.find(1).users_by_role
423 423 assert_kind_of Hash, users_by_role
424 424 role = Role.find(1)
425 425 assert_kind_of Array, users_by_role[role]
426 426 assert users_by_role[role].include?(User.find(2))
427 427 end
428 428
429 429 def test_rolled_up_trackers
430 430 parent = Project.find(1)
431 431 parent.trackers = Tracker.find([1,2])
432 432 child = parent.children.find(3)
433 433
434 434 assert_equal [1, 2], parent.tracker_ids
435 435 assert_equal [2, 3], child.trackers.collect(&:id)
436 436
437 437 assert_kind_of Tracker, parent.rolled_up_trackers.first
438 438 assert_equal Tracker.find(1), parent.rolled_up_trackers.first
439 439
440 440 assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id)
441 441 assert_equal [2, 3], child.rolled_up_trackers.collect(&:id)
442 442 end
443 443
444 444 def test_rolled_up_trackers_should_ignore_archived_subprojects
445 445 parent = Project.find(1)
446 446 parent.trackers = Tracker.find([1,2])
447 447 child = parent.children.find(3)
448 448 child.trackers = Tracker.find([1,3])
449 449 parent.children.each(&:archive)
450 450
451 451 assert_equal [1,2], parent.rolled_up_trackers.collect(&:id)
452 452 end
453 453
454 454 context "#rolled_up_versions" do
455 455 setup do
456 456 @project = Project.generate!
457 457 @parent_version_1 = Version.generate!(:project => @project)
458 458 @parent_version_2 = Version.generate!(:project => @project)
459 459 end
460 460
461 461 should "include the versions for the current project" do
462 462 assert_same_elements [@parent_version_1, @parent_version_2], @project.rolled_up_versions
463 463 end
464 464
465 465 should "include versions for a subproject" do
466 466 @subproject = Project.generate!
467 467 @subproject.set_parent!(@project)
468 468 @subproject_version = Version.generate!(:project => @subproject)
469 469
470 470 assert_same_elements [
471 471 @parent_version_1,
472 472 @parent_version_2,
473 473 @subproject_version
474 474 ], @project.rolled_up_versions
475 475 end
476 476
477 477 should "include versions for a sub-subproject" do
478 478 @subproject = Project.generate!
479 479 @subproject.set_parent!(@project)
480 480 @sub_subproject = Project.generate!
481 481 @sub_subproject.set_parent!(@subproject)
482 482 @sub_subproject_version = Version.generate!(:project => @sub_subproject)
483 483
484 484 @project.reload
485 485
486 486 assert_same_elements [
487 487 @parent_version_1,
488 488 @parent_version_2,
489 489 @sub_subproject_version
490 490 ], @project.rolled_up_versions
491 491 end
492 492
493 493 should "only check active projects" do
494 494 @subproject = Project.generate!
495 495 @subproject.set_parent!(@project)
496 496 @subproject_version = Version.generate!(:project => @subproject)
497 497 assert @subproject.archive
498 498
499 499 @project.reload
500 500
501 501 assert !@subproject.active?
502 502 assert_same_elements [@parent_version_1, @parent_version_2], @project.rolled_up_versions
503 503 end
504 504 end
505 505
506 506 def test_shared_versions_none_sharing
507 507 p = Project.find(5)
508 508 v = Version.create!(:name => 'none_sharing', :project => p, :sharing => 'none')
509 509 assert p.shared_versions.include?(v)
510 510 assert !p.children.first.shared_versions.include?(v)
511 511 assert !p.root.shared_versions.include?(v)
512 512 assert !p.siblings.first.shared_versions.include?(v)
513 513 assert !p.root.siblings.first.shared_versions.include?(v)
514 514 end
515 515
516 516 def test_shared_versions_descendants_sharing
517 517 p = Project.find(5)
518 518 v = Version.create!(:name => 'descendants_sharing', :project => p, :sharing => 'descendants')
519 519 assert p.shared_versions.include?(v)
520 520 assert p.children.first.shared_versions.include?(v)
521 521 assert !p.root.shared_versions.include?(v)
522 522 assert !p.siblings.first.shared_versions.include?(v)
523 523 assert !p.root.siblings.first.shared_versions.include?(v)
524 524 end
525 525
526 526 def test_shared_versions_hierarchy_sharing
527 527 p = Project.find(5)
528 528 v = Version.create!(:name => 'hierarchy_sharing', :project => p, :sharing => 'hierarchy')
529 529 assert p.shared_versions.include?(v)
530 530 assert p.children.first.shared_versions.include?(v)
531 531 assert p.root.shared_versions.include?(v)
532 532 assert !p.siblings.first.shared_versions.include?(v)
533 533 assert !p.root.siblings.first.shared_versions.include?(v)
534 534 end
535 535
536 536 def test_shared_versions_tree_sharing
537 537 p = Project.find(5)
538 538 v = Version.create!(:name => 'tree_sharing', :project => p, :sharing => 'tree')
539 539 assert p.shared_versions.include?(v)
540 540 assert p.children.first.shared_versions.include?(v)
541 541 assert p.root.shared_versions.include?(v)
542 542 assert p.siblings.first.shared_versions.include?(v)
543 543 assert !p.root.siblings.first.shared_versions.include?(v)
544 544 end
545 545
546 546 def test_shared_versions_system_sharing
547 547 p = Project.find(5)
548 548 v = Version.create!(:name => 'system_sharing', :project => p, :sharing => 'system')
549 549 assert p.shared_versions.include?(v)
550 550 assert p.children.first.shared_versions.include?(v)
551 551 assert p.root.shared_versions.include?(v)
552 552 assert p.siblings.first.shared_versions.include?(v)
553 553 assert p.root.siblings.first.shared_versions.include?(v)
554 554 end
555 555
556 556 def test_shared_versions
557 557 parent = Project.find(1)
558 558 child = parent.children.find(3)
559 559 private_child = parent.children.find(5)
560 560
561 561 assert_equal [1,2,3], parent.version_ids.sort
562 562 assert_equal [4], child.version_ids
563 563 assert_equal [6], private_child.version_ids
564 564 assert_equal [7], Version.find_all_by_sharing('system').collect(&:id)
565 565
566 566 assert_equal 6, parent.shared_versions.size
567 567 parent.shared_versions.each do |version|
568 568 assert_kind_of Version, version
569 569 end
570 570
571 571 assert_equal [1,2,3,4,6,7], parent.shared_versions.collect(&:id).sort
572 572 end
573 573
574 574 def test_shared_versions_should_ignore_archived_subprojects
575 575 parent = Project.find(1)
576 576 child = parent.children.find(3)
577 577 child.archive
578 578 parent.reload
579 579
580 580 assert_equal [1,2,3], parent.version_ids.sort
581 581 assert_equal [4], child.version_ids
582 582 assert !parent.shared_versions.collect(&:id).include?(4)
583 583 end
584 584
585 585 def test_shared_versions_visible_to_user
586 586 user = User.find(3)
587 587 parent = Project.find(1)
588 588 child = parent.children.find(5)
589 589
590 590 assert_equal [1,2,3], parent.version_ids.sort
591 591 assert_equal [6], child.version_ids
592 592
593 593 versions = parent.shared_versions.visible(user)
594 594
595 595 assert_equal 4, versions.size
596 596 versions.each do |version|
597 597 assert_kind_of Version, version
598 598 end
599 599
600 600 assert !versions.collect(&:id).include?(6)
601 601 end
602 602
603 603 def test_shared_versions_for_new_project_should_include_system_shared_versions
604 604 p = Project.find(5)
605 605 v = Version.create!(:name => 'system_sharing', :project => p, :sharing => 'system')
606 606
607 607 assert_include v, Project.new.shared_versions
608 608 end
609 609
610 610 def test_next_identifier
611 611 ProjectCustomField.delete_all
612 612 Project.create!(:name => 'last', :identifier => 'p2008040')
613 613 assert_equal 'p2008041', Project.next_identifier
614 614 end
615 615
616 616 def test_next_identifier_first_project
617 617 Project.delete_all
618 618 assert_nil Project.next_identifier
619 619 end
620 620
621 621 def test_enabled_module_names
622 622 with_settings :default_projects_modules => ['issue_tracking', 'repository'] do
623 623 project = Project.new
624 624
625 625 project.enabled_module_names = %w(issue_tracking news)
626 626 assert_equal %w(issue_tracking news), project.enabled_module_names.sort
627 627 end
628 628 end
629 629
630 630 context "enabled_modules" do
631 631 setup do
632 632 @project = Project.find(1)
633 633 end
634 634
635 635 should "define module by names and preserve ids" do
636 636 # Remove one module
637 637 modules = @project.enabled_modules.slice(0..-2)
638 638 assert modules.any?
639 639 assert_difference 'EnabledModule.count', -1 do
640 640 @project.enabled_module_names = modules.collect(&:name)
641 641 end
642 642 @project.reload
643 643 # Ids should be preserved
644 644 assert_equal @project.enabled_module_ids.sort, modules.collect(&:id).sort
645 645 end
646 646
647 647 should "enable a module" do
648 648 @project.enabled_module_names = []
649 649 @project.reload
650 650 assert_equal [], @project.enabled_module_names
651 651 #with string
652 652 @project.enable_module!("issue_tracking")
653 653 assert_equal ["issue_tracking"], @project.enabled_module_names
654 654 #with symbol
655 655 @project.enable_module!(:gantt)
656 656 assert_equal ["issue_tracking", "gantt"], @project.enabled_module_names
657 657 #don't add a module twice
658 658 @project.enable_module!("issue_tracking")
659 659 assert_equal ["issue_tracking", "gantt"], @project.enabled_module_names
660 660 end
661 661
662 662 should "disable a module" do
663 663 #with string
664 664 assert @project.enabled_module_names.include?("issue_tracking")
665 665 @project.disable_module!("issue_tracking")
666 666 assert ! @project.reload.enabled_module_names.include?("issue_tracking")
667 667 #with symbol
668 668 assert @project.enabled_module_names.include?("gantt")
669 669 @project.disable_module!(:gantt)
670 670 assert ! @project.reload.enabled_module_names.include?("gantt")
671 671 #with EnabledModule object
672 672 first_module = @project.enabled_modules.first
673 673 @project.disable_module!(first_module)
674 674 assert ! @project.reload.enabled_module_names.include?(first_module.name)
675 675 end
676 676 end
677 677
678 678 def test_enabled_module_names_should_not_recreate_enabled_modules
679 679 project = Project.find(1)
680 680 # Remove one module
681 681 modules = project.enabled_modules.slice(0..-2)
682 682 assert modules.any?
683 683 assert_difference 'EnabledModule.count', -1 do
684 684 project.enabled_module_names = modules.collect(&:name)
685 685 end
686 686 project.reload
687 687 # Ids should be preserved
688 688 assert_equal project.enabled_module_ids.sort, modules.collect(&:id).sort
689 689 end
690 690
691 691 def test_copy_from_existing_project
692 692 source_project = Project.find(1)
693 693 copied_project = Project.copy_from(1)
694 694
695 695 assert copied_project
696 696 # Cleared attributes
697 697 assert copied_project.id.blank?
698 698 assert copied_project.name.blank?
699 699 assert copied_project.identifier.blank?
700 700
701 701 # Duplicated attributes
702 702 assert_equal source_project.description, copied_project.description
703 703 assert_equal source_project.enabled_modules, copied_project.enabled_modules
704 704 assert_equal source_project.trackers, copied_project.trackers
705 705
706 706 # Default attributes
707 707 assert_equal 1, copied_project.status
708 708 end
709 709
710 710 def test_activities_should_use_the_system_activities
711 711 project = Project.find(1)
712 712 assert_equal project.activities, TimeEntryActivity.find(:all, :conditions => {:active => true} )
713 713 end
714 714
715 715
716 716 def test_activities_should_use_the_project_specific_activities
717 717 project = Project.find(1)
718 718 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project})
719 719 assert overridden_activity.save!
720 720
721 721 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
722 722 end
723 723
724 724 def test_activities_should_not_include_the_inactive_project_specific_activities
725 725 project = Project.find(1)
726 726 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
727 727 assert overridden_activity.save!
728 728
729 729 assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity found"
730 730 end
731 731
732 732 def test_activities_should_not_include_project_specific_activities_from_other_projects
733 733 project = Project.find(1)
734 734 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(2)})
735 735 assert overridden_activity.save!
736 736
737 737 assert !project.activities.include?(overridden_activity), "Project specific Activity found on a different project"
738 738 end
739 739
740 740 def test_activities_should_handle_nils
741 741 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(1), :parent => TimeEntryActivity.find(:first)})
742 742 TimeEntryActivity.delete_all
743 743
744 744 # No activities
745 745 project = Project.find(1)
746 746 assert project.activities.empty?
747 747
748 748 # No system, one overridden
749 749 assert overridden_activity.save!
750 750 project.reload
751 751 assert_equal [overridden_activity], project.activities
752 752 end
753 753
754 754 def test_activities_should_override_system_activities_with_project_activities
755 755 project = Project.find(1)
756 756 parent_activity = TimeEntryActivity.find(:first)
757 757 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => parent_activity})
758 758 assert overridden_activity.save!
759 759
760 760 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
761 761 assert !project.activities.include?(parent_activity), "System Activity found when it should have been overridden"
762 762 end
763 763
764 764 def test_activities_should_include_inactive_activities_if_specified
765 765 project = Project.find(1)
766 766 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
767 767 assert overridden_activity.save!
768 768
769 769 assert project.activities(true).include?(overridden_activity), "Inactive Project specific Activity not found"
770 770 end
771 771
772 772 test 'activities should not include active System activities if the project has an override that is inactive' do
773 773 project = Project.find(1)
774 774 system_activity = TimeEntryActivity.find_by_name('Design')
775 775 assert system_activity.active?
776 776 overridden_activity = TimeEntryActivity.create!(:name => "Project", :project => project, :parent => system_activity, :active => false)
777 777 assert overridden_activity.save!
778 778
779 779 assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity not found"
780 780 assert !project.activities.include?(system_activity), "System activity found when the project has an inactive override"
781 781 end
782 782
783 783 def test_close_completed_versions
784 784 Version.update_all("status = 'open'")
785 785 project = Project.find(1)
786 786 assert_not_nil project.versions.detect {|v| v.completed? && v.status == 'open'}
787 787 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
788 788 project.close_completed_versions
789 789 project.reload
790 790 assert_nil project.versions.detect {|v| v.completed? && v.status != 'closed'}
791 791 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
792 792 end
793 793
794 794 context "Project#copy" do
795 795 setup do
796 796 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
797 797 Project.destroy_all :identifier => "copy-test"
798 798 @source_project = Project.find(2)
799 799 @project = Project.new(:name => 'Copy Test', :identifier => 'copy-test')
800 800 @project.trackers = @source_project.trackers
801 801 @project.enabled_module_names = @source_project.enabled_modules.collect(&:name)
802 802 end
803 803
804 804 should "copy issues" do
805 805 @source_project.issues << Issue.generate!(:status => IssueStatus.find_by_name('Closed'),
806 806 :subject => "copy issue status",
807 807 :tracker_id => 1,
808 808 :assigned_to_id => 2,
809 809 :project_id => @source_project.id)
810 810 assert @project.valid?
811 811 assert @project.issues.empty?
812 812 assert @project.copy(@source_project)
813 813
814 814 assert_equal @source_project.issues.size, @project.issues.size
815 815 @project.issues.each do |issue|
816 816 assert issue.valid?
817 817 assert ! issue.assigned_to.blank?
818 818 assert_equal @project, issue.project
819 819 end
820 820
821 821 copied_issue = @project.issues.first(:conditions => {:subject => "copy issue status"})
822 822 assert copied_issue
823 823 assert copied_issue.status
824 824 assert_equal "Closed", copied_issue.status.name
825 825 end
826 826
827 827 should "copy issues assigned to a locked version" do
828 828 User.current = User.find(1)
829 829 assigned_version = Version.generate!(:name => "Assigned Issues")
830 830 @source_project.versions << assigned_version
831 Issue.generate_for_project!(@source_project,
832 :fixed_version_id => assigned_version.id,
833 :subject => "copy issues assigned to a locked version",
834 :tracker_id => 1,
835 :project_id => @source_project.id)
831 Issue.generate!(:project => @source_project,
832 :fixed_version_id => assigned_version.id,
833 :subject => "copy issues assigned to a locked version")
836 834 assigned_version.update_attribute :status, 'locked'
837 835
838 836 assert @project.copy(@source_project)
839 837 @project.reload
840 838 copied_issue = @project.issues.first(:conditions => {:subject => "copy issues assigned to a locked version"})
841 839
842 840 assert copied_issue
843 841 assert copied_issue.fixed_version
844 842 assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name
845 843 assert_equal 'locked', copied_issue.fixed_version.status
846 844 end
847 845
848 846 should "change the new issues to use the copied version" do
849 847 User.current = User.find(1)
850 848 assigned_version = Version.generate!(:name => "Assigned Issues", :status => 'open')
851 849 @source_project.versions << assigned_version
852 850 assert_equal 3, @source_project.versions.size
853 Issue.generate_for_project!(@source_project,
854 :fixed_version_id => assigned_version.id,
855 :subject => "change the new issues to use the copied version",
856 :tracker_id => 1,
857 :project_id => @source_project.id)
851 Issue.generate!(:project => @source_project,
852 :fixed_version_id => assigned_version.id,
853 :subject => "change the new issues to use the copied version")
858 854
859 855 assert @project.copy(@source_project)
860 856 @project.reload
861 857 copied_issue = @project.issues.first(:conditions => {:subject => "change the new issues to use the copied version"})
862 858
863 859 assert copied_issue
864 860 assert copied_issue.fixed_version
865 861 assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name
866 862 assert_not_equal assigned_version.id, copied_issue.fixed_version.id # Different record
867 863 end
868 864
869 865 should "keep target shared versions from other project" do
870 866 assigned_version = Version.generate!(:name => "Assigned Issues", :status => 'open', :project_id => 1, :sharing => 'system')
871 issue = Issue.generate_for_project!(@source_project,
872 :fixed_version => assigned_version,
873 :subject => "keep target shared versions",
874 :tracker_id => 1,
875 :project_id => @source_project.id)
867 issue = Issue.generate!(:project => @source_project,
868 :fixed_version => assigned_version,
869 :subject => "keep target shared versions")
876 870
877 871 assert @project.copy(@source_project)
878 872 @project.reload
879 873 copied_issue = @project.issues.first(:conditions => {:subject => "keep target shared versions"})
880 874
881 875 assert copied_issue
882 876 assert_equal assigned_version, copied_issue.fixed_version
883 877 end
884 878
885 879 should "copy issue relations" do
886 880 Setting.cross_project_issue_relations = '1'
887 881
888 882 second_issue = Issue.generate!(:status_id => 5,
889 883 :subject => "copy issue relation",
890 884 :tracker_id => 1,
891 885 :assigned_to_id => 2,
892 886 :project_id => @source_project.id)
893 887 source_relation = IssueRelation.create!(:issue_from => Issue.find(4),
894 888 :issue_to => second_issue,
895 889 :relation_type => "relates")
896 890 source_relation_cross_project = IssueRelation.create!(:issue_from => Issue.find(1),
897 891 :issue_to => second_issue,
898 892 :relation_type => "duplicates")
899 893
900 894 assert @project.copy(@source_project)
901 895 assert_equal @source_project.issues.count, @project.issues.count
902 896 copied_issue = @project.issues.find_by_subject("Issue on project 2") # Was #4
903 897 copied_second_issue = @project.issues.find_by_subject("copy issue relation")
904 898
905 899 # First issue with a relation on project
906 900 assert_equal 1, copied_issue.relations.size, "Relation not copied"
907 901 copied_relation = copied_issue.relations.first
908 902 assert_equal "relates", copied_relation.relation_type
909 903 assert_equal copied_second_issue.id, copied_relation.issue_to_id
910 904 assert_not_equal source_relation.id, copied_relation.id
911 905
912 906 # Second issue with a cross project relation
913 907 assert_equal 2, copied_second_issue.relations.size, "Relation not copied"
914 908 copied_relation = copied_second_issue.relations.select {|r| r.relation_type == 'duplicates'}.first
915 909 assert_equal "duplicates", copied_relation.relation_type
916 910 assert_equal 1, copied_relation.issue_from_id, "Cross project relation not kept"
917 911 assert_not_equal source_relation_cross_project.id, copied_relation.id
918 912 end
919 913
920 914 should "copy issue attachments" do
921 915 issue = Issue.generate!(:subject => "copy with attachment", :tracker_id => 1, :project_id => @source_project.id)
922 916 Attachment.create!(:container => issue, :file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 1)
923 917 @source_project.issues << issue
924 918 assert @project.copy(@source_project)
925 919
926 920 copied_issue = @project.issues.first(:conditions => {:subject => "copy with attachment"})
927 921 assert_not_nil copied_issue
928 922 assert_equal 1, copied_issue.attachments.count, "Attachment not copied"
929 923 assert_equal "testfile.txt", copied_issue.attachments.first.filename
930 924 end
931 925
932 926 should "copy memberships" do
933 927 assert @project.valid?
934 928 assert @project.members.empty?
935 929 assert @project.copy(@source_project)
936 930
937 931 assert_equal @source_project.memberships.size, @project.memberships.size
938 932 @project.memberships.each do |membership|
939 933 assert membership
940 934 assert_equal @project, membership.project
941 935 end
942 936 end
943 937
944 938 should "copy memberships with groups and additional roles" do
945 939 group = Group.create!(:lastname => "Copy group")
946 940 user = User.find(7)
947 941 group.users << user
948 942 # group role
949 943 Member.create!(:project_id => @source_project.id, :principal => group, :role_ids => [2])
950 944 member = Member.find_by_user_id_and_project_id(user.id, @source_project.id)
951 945 # additional role
952 946 member.role_ids = [1]
953 947
954 948 assert @project.copy(@source_project)
955 949 member = Member.find_by_user_id_and_project_id(user.id, @project.id)
956 950 assert_not_nil member
957 951 assert_equal [1, 2], member.role_ids.sort
958 952 end
959 953
960 954 should "copy project specific queries" do
961 955 assert @project.valid?
962 956 assert @project.queries.empty?
963 957 assert @project.copy(@source_project)
964 958
965 959 assert_equal @source_project.queries.size, @project.queries.size
966 960 @project.queries.each do |query|
967 961 assert query
968 962 assert_equal @project, query.project
969 963 end
970 964 assert_equal @source_project.queries.map(&:user_id).sort, @project.queries.map(&:user_id).sort
971 965 end
972 966
973 967 should "copy versions" do
974 968 @source_project.versions << Version.generate!
975 969 @source_project.versions << Version.generate!
976 970
977 971 assert @project.versions.empty?
978 972 assert @project.copy(@source_project)
979 973
980 974 assert_equal @source_project.versions.size, @project.versions.size
981 975 @project.versions.each do |version|
982 976 assert version
983 977 assert_equal @project, version.project
984 978 end
985 979 end
986 980
987 981 should "copy wiki" do
988 982 assert_difference 'Wiki.count' do
989 983 assert @project.copy(@source_project)
990 984 end
991 985
992 986 assert @project.wiki
993 987 assert_not_equal @source_project.wiki, @project.wiki
994 988 assert_equal "Start page", @project.wiki.start_page
995 989 end
996 990
997 991 should "copy wiki pages and content with hierarchy" do
998 992 assert_difference 'WikiPage.count', @source_project.wiki.pages.size do
999 993 assert @project.copy(@source_project)
1000 994 end
1001 995
1002 996 assert @project.wiki
1003 997 assert_equal @source_project.wiki.pages.size, @project.wiki.pages.size
1004 998
1005 999 @project.wiki.pages.each do |wiki_page|
1006 1000 assert wiki_page.content
1007 1001 assert !@source_project.wiki.pages.include?(wiki_page)
1008 1002 end
1009 1003
1010 1004 parent = @project.wiki.find_page('Parent_page')
1011 1005 child1 = @project.wiki.find_page('Child_page_1')
1012 1006 child2 = @project.wiki.find_page('Child_page_2')
1013 1007 assert_equal parent, child1.parent
1014 1008 assert_equal parent, child2.parent
1015 1009 end
1016 1010
1017 1011 should "copy issue categories" do
1018 1012 assert @project.copy(@source_project)
1019 1013
1020 1014 assert_equal 2, @project.issue_categories.size
1021 1015 @project.issue_categories.each do |issue_category|
1022 1016 assert !@source_project.issue_categories.include?(issue_category)
1023 1017 end
1024 1018 end
1025 1019
1026 1020 should "copy boards" do
1027 1021 assert @project.copy(@source_project)
1028 1022
1029 1023 assert_equal 1, @project.boards.size
1030 1024 @project.boards.each do |board|
1031 1025 assert !@source_project.boards.include?(board)
1032 1026 end
1033 1027 end
1034 1028
1035 1029 should "change the new issues to use the copied issue categories" do
1036 1030 issue = Issue.find(4)
1037 1031 issue.update_attribute(:category_id, 3)
1038 1032
1039 1033 assert @project.copy(@source_project)
1040 1034
1041 1035 @project.issues.each do |issue|
1042 1036 assert issue.category
1043 1037 assert_equal "Stock management", issue.category.name # Same name
1044 1038 assert_not_equal IssueCategory.find(3), issue.category # Different record
1045 1039 end
1046 1040 end
1047 1041
1048 1042 should "limit copy with :only option" do
1049 1043 assert @project.members.empty?
1050 1044 assert @project.issue_categories.empty?
1051 1045 assert @source_project.issues.any?
1052 1046
1053 1047 assert @project.copy(@source_project, :only => ['members', 'issue_categories'])
1054 1048
1055 1049 assert @project.members.any?
1056 1050 assert @project.issue_categories.any?
1057 1051 assert @project.issues.empty?
1058 1052 end
1059 1053 end
1060 1054
1061 1055 def test_copy_should_copy_subtasks
1062 1056 source = Project.generate!(:tracker_ids => [1])
1063 1057 issue = Issue.generate_with_descendants!(source, :subject => 'Parent')
1064 1058 project = Project.new(:name => 'Copy', :identifier => 'copy', :tracker_ids => [1])
1065 1059
1066 1060 assert_difference 'Project.count' do
1067 1061 assert_difference 'Issue.count', 1+issue.descendants.count do
1068 1062 assert project.copy(source.reload)
1069 1063 end
1070 1064 end
1071 1065 copy = Issue.where(:parent_id => nil).order("id DESC").first
1072 1066 assert_equal project, copy.project
1073 1067 assert_equal issue.descendants.count, copy.descendants.count
1074 1068 child_copy = copy.children.detect {|c| c.subject == 'Child1'}
1075 1069 assert child_copy.descendants.any?
1076 1070 end
1077 1071
1078 1072 context "#start_date" do
1079 1073 setup do
1080 1074 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
1081 1075 @project = Project.generate!(:identifier => 'test0')
1082 1076 @project.trackers << Tracker.generate!
1083 1077 end
1084 1078
1085 1079 should "be nil if there are no issues on the project" do
1086 1080 assert_nil @project.start_date
1087 1081 end
1088 1082
1089 1083 should "be tested when issues have no start date"
1090 1084
1091 1085 should "be the earliest start date of it's issues" do
1092 1086 early = 7.days.ago.to_date
1093 Issue.generate_for_project!(@project, :start_date => Date.today)
1094 Issue.generate_for_project!(@project, :start_date => early)
1087 Issue.generate!(:project => @project, :start_date => Date.today)
1088 Issue.generate!(:project => @project, :start_date => early)
1095 1089
1096 1090 assert_equal early, @project.start_date
1097 1091 end
1098 1092
1099 1093 end
1100 1094
1101 1095 context "#due_date" do
1102 1096 setup do
1103 1097 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
1104 1098 @project = Project.generate!(:identifier => 'test0')
1105 1099 @project.trackers << Tracker.generate!
1106 1100 end
1107 1101
1108 1102 should "be nil if there are no issues on the project" do
1109 1103 assert_nil @project.due_date
1110 1104 end
1111 1105
1112 1106 should "be tested when issues have no due date"
1113 1107
1114 1108 should "be the latest due date of it's issues" do
1115 1109 future = 7.days.from_now.to_date
1116 Issue.generate_for_project!(@project, :due_date => future)
1117 Issue.generate_for_project!(@project, :due_date => Date.today)
1110 Issue.generate!(:project => @project, :due_date => future)
1111 Issue.generate!(:project => @project, :due_date => Date.today)
1118 1112
1119 1113 assert_equal future, @project.due_date
1120 1114 end
1121 1115
1122 1116 should "be the latest due date of it's versions" do
1123 1117 future = 7.days.from_now.to_date
1124 1118 @project.versions << Version.generate!(:effective_date => future)
1125 1119 @project.versions << Version.generate!(:effective_date => Date.today)
1126 1120
1127 1121
1128 1122 assert_equal future, @project.due_date
1129 1123
1130 1124 end
1131 1125
1132 1126 should "pick the latest date from it's issues and versions" do
1133 1127 future = 7.days.from_now.to_date
1134 1128 far_future = 14.days.from_now.to_date
1135 Issue.generate_for_project!(@project, :due_date => far_future)
1129 Issue.generate!(:project => @project, :due_date => far_future)
1136 1130 @project.versions << Version.generate!(:effective_date => future)
1137 1131
1138 1132 assert_equal far_future, @project.due_date
1139 1133 end
1140 1134
1141 1135 end
1142 1136
1143 1137 context "Project#completed_percent" do
1144 1138 setup do
1145 1139 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
1146 1140 @project = Project.generate!(:identifier => 'test0')
1147 1141 @project.trackers << Tracker.generate!
1148 1142 end
1149 1143
1150 1144 context "no versions" do
1151 1145 should "be 100" do
1152 1146 assert_equal 100, @project.completed_percent
1153 1147 end
1154 1148 end
1155 1149
1156 1150 context "with versions" do
1157 1151 should "return 0 if the versions have no issues" do
1158 1152 Version.generate!(:project => @project)
1159 1153 Version.generate!(:project => @project)
1160 1154
1161 1155 assert_equal 0, @project.completed_percent
1162 1156 end
1163 1157
1164 1158 should "return 100 if the version has only closed issues" do
1165 1159 v1 = Version.generate!(:project => @project)
1166 Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v1)
1160 Issue.generate!(:project => @project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v1)
1167 1161 v2 = Version.generate!(:project => @project)
1168 Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v2)
1162 Issue.generate!(:project => @project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v2)
1169 1163
1170 1164 assert_equal 100, @project.completed_percent
1171 1165 end
1172 1166
1173 1167 should "return the averaged completed percent of the versions (not weighted)" do
1174 1168 v1 = Version.generate!(:project => @project)
1175 Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v1)
1169 Issue.generate!(:project => @project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v1)
1176 1170 v2 = Version.generate!(:project => @project)
1177 Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v2)
1171 Issue.generate!(:project => @project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v2)
1178 1172
1179 1173 assert_equal 50, @project.completed_percent
1180 1174 end
1181 1175
1182 1176 end
1183 1177 end
1184 1178
1185 1179 context "#notified_users" do
1186 1180 setup do
1187 1181 @project = Project.generate!
1188 1182 @role = Role.generate!
1189 1183
1190 1184 @user_with_membership_notification = User.generate!(:mail_notification => 'selected')
1191 1185 Member.create!(:project => @project, :roles => [@role], :principal => @user_with_membership_notification, :mail_notification => true)
1192 1186
1193 1187 @all_events_user = User.generate!(:mail_notification => 'all')
1194 1188 Member.create!(:project => @project, :roles => [@role], :principal => @all_events_user)
1195 1189
1196 1190 @no_events_user = User.generate!(:mail_notification => 'none')
1197 1191 Member.create!(:project => @project, :roles => [@role], :principal => @no_events_user)
1198 1192
1199 1193 @only_my_events_user = User.generate!(:mail_notification => 'only_my_events')
1200 1194 Member.create!(:project => @project, :roles => [@role], :principal => @only_my_events_user)
1201 1195
1202 1196 @only_assigned_user = User.generate!(:mail_notification => 'only_assigned')
1203 1197 Member.create!(:project => @project, :roles => [@role], :principal => @only_assigned_user)
1204 1198
1205 1199 @only_owned_user = User.generate!(:mail_notification => 'only_owner')
1206 1200 Member.create!(:project => @project, :roles => [@role], :principal => @only_owned_user)
1207 1201 end
1208 1202
1209 1203 should "include members with a mail notification" do
1210 1204 assert @project.notified_users.include?(@user_with_membership_notification)
1211 1205 end
1212 1206
1213 1207 should "include users with the 'all' notification option" do
1214 1208 assert @project.notified_users.include?(@all_events_user)
1215 1209 end
1216 1210
1217 1211 should "not include users with the 'none' notification option" do
1218 1212 assert !@project.notified_users.include?(@no_events_user)
1219 1213 end
1220 1214
1221 1215 should "not include users with the 'only_my_events' notification option" do
1222 1216 assert !@project.notified_users.include?(@only_my_events_user)
1223 1217 end
1224 1218
1225 1219 should "not include users with the 'only_assigned' notification option" do
1226 1220 assert !@project.notified_users.include?(@only_assigned_user)
1227 1221 end
1228 1222
1229 1223 should "not include users with the 'only_owner' notification option" do
1230 1224 assert !@project.notified_users.include?(@only_owned_user)
1231 1225 end
1232 1226 end
1233 1227
1234 1228 end
@@ -1,1215 +1,1215
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2012 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 QueryTest < ActiveSupport::TestCase
21 21 include Redmine::I18n
22 22
23 23 fixtures :projects, :enabled_modules, :users, :members,
24 24 :member_roles, :roles, :trackers, :issue_statuses,
25 25 :issue_categories, :enumerations, :issues,
26 26 :watchers, :custom_fields, :custom_values, :versions,
27 27 :queries,
28 28 :projects_trackers,
29 29 :custom_fields_trackers
30 30
31 31 def test_custom_fields_for_all_projects_should_be_available_in_global_queries
32 32 query = Query.new(:project => nil, :name => '_')
33 33 assert query.available_filters.has_key?('cf_1')
34 34 assert !query.available_filters.has_key?('cf_3')
35 35 end
36 36
37 37 def test_system_shared_versions_should_be_available_in_global_queries
38 38 Version.find(2).update_attribute :sharing, 'system'
39 39 query = Query.new(:project => nil, :name => '_')
40 40 assert query.available_filters.has_key?('fixed_version_id')
41 41 assert query.available_filters['fixed_version_id'][:values].detect {|v| v.last == '2'}
42 42 end
43 43
44 44 def test_project_filter_in_global_queries
45 45 query = Query.new(:project => nil, :name => '_')
46 46 project_filter = query.available_filters["project_id"]
47 47 assert_not_nil project_filter
48 48 project_ids = project_filter[:values].map{|p| p[1]}
49 49 assert project_ids.include?("1") #public project
50 50 assert !project_ids.include?("2") #private project user cannot see
51 51 end
52 52
53 53 def find_issues_with_query(query)
54 54 Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where(
55 55 query.statement
56 56 ).all
57 57 end
58 58
59 59 def assert_find_issues_with_query_is_successful(query)
60 60 assert_nothing_raised do
61 61 find_issues_with_query(query)
62 62 end
63 63 end
64 64
65 65 def assert_query_statement_includes(query, condition)
66 66 assert query.statement.include?(condition), "Query statement condition not found in: #{query.statement}"
67 67 end
68 68
69 69 def assert_query_result(expected, query)
70 70 assert_nothing_raised do
71 71 assert_equal expected.map(&:id).sort, query.issues.map(&:id).sort
72 72 assert_equal expected.size, query.issue_count
73 73 end
74 74 end
75 75
76 76 def test_query_should_allow_shared_versions_for_a_project_query
77 77 subproject_version = Version.find(4)
78 78 query = Query.new(:project => Project.find(1), :name => '_')
79 79 query.add_filter('fixed_version_id', '=', [subproject_version.id.to_s])
80 80
81 81 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IN ('4')")
82 82 end
83 83
84 84 def test_query_with_multiple_custom_fields
85 85 query = Query.find(1)
86 86 assert query.valid?
87 87 assert query.statement.include?("#{CustomValue.table_name}.value IN ('MySQL')")
88 88 issues = find_issues_with_query(query)
89 89 assert_equal 1, issues.length
90 90 assert_equal Issue.find(3), issues.first
91 91 end
92 92
93 93 def test_operator_none
94 94 query = Query.new(:project => Project.find(1), :name => '_')
95 95 query.add_filter('fixed_version_id', '!*', [''])
96 96 query.add_filter('cf_1', '!*', [''])
97 97 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL")
98 98 assert query.statement.include?("#{CustomValue.table_name}.value IS NULL OR #{CustomValue.table_name}.value = ''")
99 99 find_issues_with_query(query)
100 100 end
101 101
102 102 def test_operator_none_for_integer
103 103 query = Query.new(:project => Project.find(1), :name => '_')
104 104 query.add_filter('estimated_hours', '!*', [''])
105 105 issues = find_issues_with_query(query)
106 106 assert !issues.empty?
107 107 assert issues.all? {|i| !i.estimated_hours}
108 108 end
109 109
110 110 def test_operator_none_for_date
111 111 query = Query.new(:project => Project.find(1), :name => '_')
112 112 query.add_filter('start_date', '!*', [''])
113 113 issues = find_issues_with_query(query)
114 114 assert !issues.empty?
115 115 assert issues.all? {|i| i.start_date.nil?}
116 116 end
117 117
118 118 def test_operator_none_for_string_custom_field
119 119 query = Query.new(:project => Project.find(1), :name => '_')
120 120 query.add_filter('cf_2', '!*', [''])
121 121 assert query.has_filter?('cf_2')
122 122 issues = find_issues_with_query(query)
123 123 assert !issues.empty?
124 124 assert issues.all? {|i| i.custom_field_value(2).blank?}
125 125 end
126 126
127 127 def test_operator_all
128 128 query = Query.new(:project => Project.find(1), :name => '_')
129 129 query.add_filter('fixed_version_id', '*', [''])
130 130 query.add_filter('cf_1', '*', [''])
131 131 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL")
132 132 assert query.statement.include?("#{CustomValue.table_name}.value IS NOT NULL AND #{CustomValue.table_name}.value <> ''")
133 133 find_issues_with_query(query)
134 134 end
135 135
136 136 def test_operator_all_for_date
137 137 query = Query.new(:project => Project.find(1), :name => '_')
138 138 query.add_filter('start_date', '*', [''])
139 139 issues = find_issues_with_query(query)
140 140 assert !issues.empty?
141 141 assert issues.all? {|i| i.start_date.present?}
142 142 end
143 143
144 144 def test_operator_all_for_string_custom_field
145 145 query = Query.new(:project => Project.find(1), :name => '_')
146 146 query.add_filter('cf_2', '*', [''])
147 147 assert query.has_filter?('cf_2')
148 148 issues = find_issues_with_query(query)
149 149 assert !issues.empty?
150 150 assert issues.all? {|i| i.custom_field_value(2).present?}
151 151 end
152 152
153 153 def test_numeric_filter_should_not_accept_non_numeric_values
154 154 query = Query.new(:name => '_')
155 155 query.add_filter('estimated_hours', '=', ['a'])
156 156
157 157 assert query.has_filter?('estimated_hours')
158 158 assert !query.valid?
159 159 end
160 160
161 161 def test_operator_is_on_float
162 162 Issue.update_all("estimated_hours = 171.2", "id=2")
163 163
164 164 query = Query.new(:name => '_')
165 165 query.add_filter('estimated_hours', '=', ['171.20'])
166 166 issues = find_issues_with_query(query)
167 167 assert_equal 1, issues.size
168 168 assert_equal 2, issues.first.id
169 169 end
170 170
171 171 def test_operator_is_on_integer_custom_field
172 172 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true)
173 173 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
174 174 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
175 175 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
176 176
177 177 query = Query.new(:name => '_')
178 178 query.add_filter("cf_#{f.id}", '=', ['12'])
179 179 issues = find_issues_with_query(query)
180 180 assert_equal 1, issues.size
181 181 assert_equal 2, issues.first.id
182 182 end
183 183
184 184 def test_operator_is_on_integer_custom_field_should_accept_negative_value
185 185 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true)
186 186 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
187 187 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12')
188 188 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
189 189
190 190 query = Query.new(:name => '_')
191 191 query.add_filter("cf_#{f.id}", '=', ['-12'])
192 192 assert query.valid?
193 193 issues = find_issues_with_query(query)
194 194 assert_equal 1, issues.size
195 195 assert_equal 2, issues.first.id
196 196 end
197 197
198 198 def test_operator_is_on_float_custom_field
199 199 f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true)
200 200 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
201 201 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12.7')
202 202 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
203 203
204 204 query = Query.new(:name => '_')
205 205 query.add_filter("cf_#{f.id}", '=', ['12.7'])
206 206 issues = find_issues_with_query(query)
207 207 assert_equal 1, issues.size
208 208 assert_equal 2, issues.first.id
209 209 end
210 210
211 211 def test_operator_is_on_float_custom_field_should_accept_negative_value
212 212 f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true)
213 213 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
214 214 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12.7')
215 215 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
216 216
217 217 query = Query.new(:name => '_')
218 218 query.add_filter("cf_#{f.id}", '=', ['-12.7'])
219 219 assert query.valid?
220 220 issues = find_issues_with_query(query)
221 221 assert_equal 1, issues.size
222 222 assert_equal 2, issues.first.id
223 223 end
224 224
225 225 def test_operator_is_on_multi_list_custom_field
226 226 f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
227 227 :possible_values => ['value1', 'value2', 'value3'], :multiple => true)
228 228 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1')
229 229 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
230 230 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')
231 231
232 232 query = Query.new(:name => '_')
233 233 query.add_filter("cf_#{f.id}", '=', ['value1'])
234 234 issues = find_issues_with_query(query)
235 235 assert_equal [1, 3], issues.map(&:id).sort
236 236
237 237 query = Query.new(:name => '_')
238 238 query.add_filter("cf_#{f.id}", '=', ['value2'])
239 239 issues = find_issues_with_query(query)
240 240 assert_equal [1], issues.map(&:id).sort
241 241 end
242 242
243 243 def test_operator_is_not_on_multi_list_custom_field
244 244 f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
245 245 :possible_values => ['value1', 'value2', 'value3'], :multiple => true)
246 246 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1')
247 247 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
248 248 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')
249 249
250 250 query = Query.new(:name => '_')
251 251 query.add_filter("cf_#{f.id}", '!', ['value1'])
252 252 issues = find_issues_with_query(query)
253 253 assert !issues.map(&:id).include?(1)
254 254 assert !issues.map(&:id).include?(3)
255 255
256 256 query = Query.new(:name => '_')
257 257 query.add_filter("cf_#{f.id}", '!', ['value2'])
258 258 issues = find_issues_with_query(query)
259 259 assert !issues.map(&:id).include?(1)
260 260 assert issues.map(&:id).include?(3)
261 261 end
262 262
263 263 def test_operator_is_on_is_private_field
264 264 # is_private filter only available for those who can set issues private
265 265 User.current = User.find(2)
266 266
267 267 query = Query.new(:name => '_')
268 268 assert query.available_filters.key?('is_private')
269 269
270 270 query.add_filter("is_private", '=', ['1'])
271 271 issues = find_issues_with_query(query)
272 272 assert issues.any?
273 273 assert_nil issues.detect {|issue| !issue.is_private?}
274 274 ensure
275 275 User.current = nil
276 276 end
277 277
278 278 def test_operator_is_not_on_is_private_field
279 279 # is_private filter only available for those who can set issues private
280 280 User.current = User.find(2)
281 281
282 282 query = Query.new(:name => '_')
283 283 assert query.available_filters.key?('is_private')
284 284
285 285 query.add_filter("is_private", '!', ['1'])
286 286 issues = find_issues_with_query(query)
287 287 assert issues.any?
288 288 assert_nil issues.detect {|issue| issue.is_private?}
289 289 ensure
290 290 User.current = nil
291 291 end
292 292
293 293 def test_operator_greater_than
294 294 query = Query.new(:project => Project.find(1), :name => '_')
295 295 query.add_filter('done_ratio', '>=', ['40'])
296 296 assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40.0")
297 297 find_issues_with_query(query)
298 298 end
299 299
300 300 def test_operator_greater_than_a_float
301 301 query = Query.new(:project => Project.find(1), :name => '_')
302 302 query.add_filter('estimated_hours', '>=', ['40.5'])
303 303 assert query.statement.include?("#{Issue.table_name}.estimated_hours >= 40.5")
304 304 find_issues_with_query(query)
305 305 end
306 306
307 307 def test_operator_greater_than_on_int_custom_field
308 308 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
309 309 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
310 310 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
311 311 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
312 312
313 313 query = Query.new(:project => Project.find(1), :name => '_')
314 314 query.add_filter("cf_#{f.id}", '>=', ['8'])
315 315 issues = find_issues_with_query(query)
316 316 assert_equal 1, issues.size
317 317 assert_equal 2, issues.first.id
318 318 end
319 319
320 320 def test_operator_lesser_than
321 321 query = Query.new(:project => Project.find(1), :name => '_')
322 322 query.add_filter('done_ratio', '<=', ['30'])
323 323 assert query.statement.include?("#{Issue.table_name}.done_ratio <= 30.0")
324 324 find_issues_with_query(query)
325 325 end
326 326
327 327 def test_operator_lesser_than_on_custom_field
328 328 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
329 329 query = Query.new(:project => Project.find(1), :name => '_')
330 330 query.add_filter("cf_#{f.id}", '<=', ['30'])
331 331 assert query.statement.include?("CAST(custom_values.value AS decimal(60,3)) <= 30.0")
332 332 find_issues_with_query(query)
333 333 end
334 334
335 335 def test_operator_between
336 336 query = Query.new(:project => Project.find(1), :name => '_')
337 337 query.add_filter('done_ratio', '><', ['30', '40'])
338 338 assert_include "#{Issue.table_name}.done_ratio BETWEEN 30.0 AND 40.0", query.statement
339 339 find_issues_with_query(query)
340 340 end
341 341
342 342 def test_operator_between_on_custom_field
343 343 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
344 344 query = Query.new(:project => Project.find(1), :name => '_')
345 345 query.add_filter("cf_#{f.id}", '><', ['30', '40'])
346 346 assert_include "CAST(custom_values.value AS decimal(60,3)) BETWEEN 30.0 AND 40.0", query.statement
347 347 find_issues_with_query(query)
348 348 end
349 349
350 350 def test_date_filter_should_not_accept_non_date_values
351 351 query = Query.new(:name => '_')
352 352 query.add_filter('created_on', '=', ['a'])
353 353
354 354 assert query.has_filter?('created_on')
355 355 assert !query.valid?
356 356 end
357 357
358 358 def test_date_filter_should_not_accept_invalid_date_values
359 359 query = Query.new(:name => '_')
360 360 query.add_filter('created_on', '=', ['2011-01-34'])
361 361
362 362 assert query.has_filter?('created_on')
363 363 assert !query.valid?
364 364 end
365 365
366 366 def test_relative_date_filter_should_not_accept_non_integer_values
367 367 query = Query.new(:name => '_')
368 368 query.add_filter('created_on', '>t-', ['a'])
369 369
370 370 assert query.has_filter?('created_on')
371 371 assert !query.valid?
372 372 end
373 373
374 374 def test_operator_date_equals
375 375 query = Query.new(:name => '_')
376 376 query.add_filter('due_date', '=', ['2011-07-10'])
377 377 assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
378 378 find_issues_with_query(query)
379 379 end
380 380
381 381 def test_operator_date_lesser_than
382 382 query = Query.new(:name => '_')
383 383 query.add_filter('due_date', '<=', ['2011-07-10'])
384 384 assert_match /issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
385 385 find_issues_with_query(query)
386 386 end
387 387
388 388 def test_operator_date_greater_than
389 389 query = Query.new(:name => '_')
390 390 query.add_filter('due_date', '>=', ['2011-07-10'])
391 391 assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?'/, query.statement
392 392 find_issues_with_query(query)
393 393 end
394 394
395 395 def test_operator_date_between
396 396 query = Query.new(:name => '_')
397 397 query.add_filter('due_date', '><', ['2011-06-23', '2011-07-10'])
398 398 assert_match /issues\.due_date > '2011-06-22 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
399 399 find_issues_with_query(query)
400 400 end
401 401
402 402 def test_operator_in_more_than
403 403 Issue.find(7).update_attribute(:due_date, (Date.today + 15))
404 404 query = Query.new(:project => Project.find(1), :name => '_')
405 405 query.add_filter('due_date', '>t+', ['15'])
406 406 issues = find_issues_with_query(query)
407 407 assert !issues.empty?
408 408 issues.each {|issue| assert(issue.due_date >= (Date.today + 15))}
409 409 end
410 410
411 411 def test_operator_in_less_than
412 412 query = Query.new(:project => Project.find(1), :name => '_')
413 413 query.add_filter('due_date', '<t+', ['15'])
414 414 issues = find_issues_with_query(query)
415 415 assert !issues.empty?
416 416 issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))}
417 417 end
418 418
419 419 def test_operator_less_than_ago
420 420 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
421 421 query = Query.new(:project => Project.find(1), :name => '_')
422 422 query.add_filter('due_date', '>t-', ['3'])
423 423 issues = find_issues_with_query(query)
424 424 assert !issues.empty?
425 425 issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)}
426 426 end
427 427
428 428 def test_operator_more_than_ago
429 429 Issue.find(7).update_attribute(:due_date, (Date.today - 10))
430 430 query = Query.new(:project => Project.find(1), :name => '_')
431 431 query.add_filter('due_date', '<t-', ['10'])
432 432 assert query.statement.include?("#{Issue.table_name}.due_date <=")
433 433 issues = find_issues_with_query(query)
434 434 assert !issues.empty?
435 435 issues.each {|issue| assert(issue.due_date <= (Date.today - 10))}
436 436 end
437 437
438 438 def test_operator_in
439 439 Issue.find(7).update_attribute(:due_date, (Date.today + 2))
440 440 query = Query.new(:project => Project.find(1), :name => '_')
441 441 query.add_filter('due_date', 't+', ['2'])
442 442 issues = find_issues_with_query(query)
443 443 assert !issues.empty?
444 444 issues.each {|issue| assert_equal((Date.today + 2), issue.due_date)}
445 445 end
446 446
447 447 def test_operator_ago
448 448 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
449 449 query = Query.new(:project => Project.find(1), :name => '_')
450 450 query.add_filter('due_date', 't-', ['3'])
451 451 issues = find_issues_with_query(query)
452 452 assert !issues.empty?
453 453 issues.each {|issue| assert_equal((Date.today - 3), issue.due_date)}
454 454 end
455 455
456 456 def test_operator_today
457 457 query = Query.new(:project => Project.find(1), :name => '_')
458 458 query.add_filter('due_date', 't', [''])
459 459 issues = find_issues_with_query(query)
460 460 assert !issues.empty?
461 461 issues.each {|issue| assert_equal Date.today, issue.due_date}
462 462 end
463 463
464 464 def test_operator_this_week_on_date
465 465 query = Query.new(:project => Project.find(1), :name => '_')
466 466 query.add_filter('due_date', 'w', [''])
467 467 find_issues_with_query(query)
468 468 end
469 469
470 470 def test_operator_this_week_on_datetime
471 471 query = Query.new(:project => Project.find(1), :name => '_')
472 472 query.add_filter('created_on', 'w', [''])
473 473 find_issues_with_query(query)
474 474 end
475 475
476 476 def test_operator_contains
477 477 query = Query.new(:project => Project.find(1), :name => '_')
478 478 query.add_filter('subject', '~', ['uNable'])
479 479 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) LIKE '%unable%'")
480 480 result = find_issues_with_query(query)
481 481 assert result.empty?
482 482 result.each {|issue| assert issue.subject.downcase.include?('unable') }
483 483 end
484 484
485 485 def test_range_for_this_week_with_week_starting_on_monday
486 486 I18n.locale = :fr
487 487 assert_equal '1', I18n.t(:general_first_day_of_week)
488 488
489 489 Date.stubs(:today).returns(Date.parse('2011-04-29'))
490 490
491 491 query = Query.new(:project => Project.find(1), :name => '_')
492 492 query.add_filter('due_date', 'w', [''])
493 493 assert query.statement.match(/issues\.due_date > '2011-04-24 23:59:59(\.9+)?' AND issues\.due_date <= '2011-05-01 23:59:59(\.9+)?/), "range not found in #{query.statement}"
494 494 I18n.locale = :en
495 495 end
496 496
497 497 def test_range_for_this_week_with_week_starting_on_sunday
498 498 I18n.locale = :en
499 499 assert_equal '7', I18n.t(:general_first_day_of_week)
500 500
501 501 Date.stubs(:today).returns(Date.parse('2011-04-29'))
502 502
503 503 query = Query.new(:project => Project.find(1), :name => '_')
504 504 query.add_filter('due_date', 'w', [''])
505 505 assert query.statement.match(/issues\.due_date > '2011-04-23 23:59:59(\.9+)?' AND issues\.due_date <= '2011-04-30 23:59:59(\.9+)?/), "range not found in #{query.statement}"
506 506 end
507 507
508 508 def test_operator_does_not_contains
509 509 query = Query.new(:project => Project.find(1), :name => '_')
510 510 query.add_filter('subject', '!~', ['uNable'])
511 511 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) NOT LIKE '%unable%'")
512 512 find_issues_with_query(query)
513 513 end
514 514
515 515 def test_filter_assigned_to_me
516 516 user = User.find(2)
517 517 group = Group.find(10)
518 518 User.current = user
519 519 i1 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => user)
520 520 i2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => group)
521 521 i3 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => Group.find(11))
522 522 group.users << user
523 523
524 524 query = Query.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}})
525 525 result = query.issues
526 526 assert_equal Issue.visible.all(:conditions => {:assigned_to_id => ([2] + user.reload.group_ids)}).sort_by(&:id), result.sort_by(&:id)
527 527
528 528 assert result.include?(i1)
529 529 assert result.include?(i2)
530 530 assert !result.include?(i3)
531 531 end
532 532
533 533 def test_user_custom_field_filtered_on_me
534 534 User.current = User.find(2)
535 535 cf = IssueCustomField.create!(:field_format => 'user', :is_for_all => true, :is_filter => true, :name => 'User custom field', :tracker_ids => [1])
536 536 issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '2'}, :subject => 'Test', :author_id => 1)
537 537 issue2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '3'})
538 538
539 539 query = Query.new(:name => '_', :project => Project.find(1))
540 540 filter = query.available_filters["cf_#{cf.id}"]
541 541 assert_not_nil filter
542 542 assert_include 'me', filter[:values].map{|v| v[1]}
543 543
544 544 query.filters = { "cf_#{cf.id}" => {:operator => '=', :values => ['me']}}
545 545 result = query.issues
546 546 assert_equal 1, result.size
547 547 assert_equal issue1, result.first
548 548 end
549 549
550 550 def test_filter_my_projects
551 551 User.current = User.find(2)
552 552 query = Query.new(:name => '_')
553 553 filter = query.available_filters['project_id']
554 554 assert_not_nil filter
555 555 assert_include 'mine', filter[:values].map{|v| v[1]}
556 556
557 557 query.filters = { 'project_id' => {:operator => '=', :values => ['mine']}}
558 558 result = query.issues
559 559 assert_nil result.detect {|issue| !User.current.member_of?(issue.project)}
560 560 end
561 561
562 562 def test_filter_watched_issues
563 563 User.current = User.find(1)
564 564 query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}})
565 565 result = find_issues_with_query(query)
566 566 assert_not_nil result
567 567 assert !result.empty?
568 568 assert_equal Issue.visible.watched_by(User.current).sort_by(&:id), result.sort_by(&:id)
569 569 User.current = nil
570 570 end
571 571
572 572 def test_filter_unwatched_issues
573 573 User.current = User.find(1)
574 574 query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}})
575 575 result = find_issues_with_query(query)
576 576 assert_not_nil result
577 577 assert !result.empty?
578 578 assert_equal((Issue.visible - Issue.watched_by(User.current)).sort_by(&:id).size, result.sort_by(&:id).size)
579 579 User.current = nil
580 580 end
581 581
582 582 def test_filter_on_project_custom_field
583 583 field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
584 584 CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo')
585 585 CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo')
586 586
587 587 query = Query.new(:name => '_')
588 588 filter_name = "project.cf_#{field.id}"
589 589 assert_include filter_name, query.available_filters.keys
590 590 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
591 591 assert_equal [3, 5], find_issues_with_query(query).map(&:project_id).uniq.sort
592 592 end
593 593
594 594 def test_filter_on_author_custom_field
595 595 field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
596 596 CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo')
597 597
598 598 query = Query.new(:name => '_')
599 599 filter_name = "author.cf_#{field.id}"
600 600 assert_include filter_name, query.available_filters.keys
601 601 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
602 602 assert_equal [3], find_issues_with_query(query).map(&:author_id).uniq.sort
603 603 end
604 604
605 605 def test_filter_on_assigned_to_custom_field
606 606 field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
607 607 CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo')
608 608
609 609 query = Query.new(:name => '_')
610 610 filter_name = "assigned_to.cf_#{field.id}"
611 611 assert_include filter_name, query.available_filters.keys
612 612 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
613 613 assert_equal [3], find_issues_with_query(query).map(&:assigned_to_id).uniq.sort
614 614 end
615 615
616 616 def test_filter_on_fixed_version_custom_field
617 617 field = VersionCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
618 618 CustomValue.create!(:custom_field => field, :customized => Version.find(2), :value => 'Foo')
619 619
620 620 query = Query.new(:name => '_')
621 621 filter_name = "fixed_version.cf_#{field.id}"
622 622 assert_include filter_name, query.available_filters.keys
623 623 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
624 624 assert_equal [2], find_issues_with_query(query).map(&:fixed_version_id).uniq.sort
625 625 end
626 626
627 627 def test_filter_on_relations_with_a_specific_issue
628 628 IssueRelation.delete_all
629 629 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
630 630 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
631 631
632 632 query = Query.new(:name => '_')
633 633 query.filters = {"relates" => {:operator => '=', :values => ['1']}}
634 634 assert_equal [2, 3], find_issues_with_query(query).map(&:id).sort
635 635
636 636 query = Query.new(:name => '_')
637 637 query.filters = {"relates" => {:operator => '=', :values => ['2']}}
638 638 assert_equal [1], find_issues_with_query(query).map(&:id).sort
639 639 end
640 640
641 641 def test_filter_on_relations_with_any_issues_in_a_project
642 642 IssueRelation.delete_all
643 643 with_settings :cross_project_issue_relations => '1' do
644 644 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
645 645 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(2).issues.first)
646 646 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first)
647 647 end
648 648
649 649 query = Query.new(:name => '_')
650 650 query.filters = {"relates" => {:operator => '=p', :values => ['2']}}
651 651 assert_equal [1, 2], find_issues_with_query(query).map(&:id).sort
652 652
653 653 query = Query.new(:name => '_')
654 654 query.filters = {"relates" => {:operator => '=p', :values => ['3']}}
655 655 assert_equal [1], find_issues_with_query(query).map(&:id).sort
656 656
657 657 query = Query.new(:name => '_')
658 658 query.filters = {"relates" => {:operator => '=p', :values => ['4']}}
659 659 assert_equal [], find_issues_with_query(query).map(&:id).sort
660 660 end
661 661
662 662 def test_filter_on_relations_with_any_issues_not_in_a_project
663 663 IssueRelation.delete_all
664 664 with_settings :cross_project_issue_relations => '1' do
665 665 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
666 666 #IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(1).issues.first)
667 667 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first)
668 668 end
669 669
670 670 query = Query.new(:name => '_')
671 671 query.filters = {"relates" => {:operator => '=!p', :values => ['1']}}
672 672 assert_equal [1], find_issues_with_query(query).map(&:id).sort
673 673 end
674 674
675 675 def test_filter_on_relations_with_no_issues_in_a_project
676 676 IssueRelation.delete_all
677 677 with_settings :cross_project_issue_relations => '1' do
678 678 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
679 679 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(3).issues.first)
680 680 IssueRelation.create!(:relation_type => "relates", :issue_to => Project.find(2).issues.first, :issue_from => Issue.find(3))
681 681 end
682 682
683 683 query = Query.new(:name => '_')
684 684 query.filters = {"relates" => {:operator => '!p', :values => ['2']}}
685 685 ids = find_issues_with_query(query).map(&:id).sort
686 686 assert_include 2, ids
687 687 assert_not_include 1, ids
688 688 assert_not_include 3, ids
689 689 end
690 690
691 691 def test_filter_on_relations_with_no_issues
692 692 IssueRelation.delete_all
693 693 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
694 694 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
695 695
696 696 query = Query.new(:name => '_')
697 697 query.filters = {"relates" => {:operator => '!*', :values => ['']}}
698 698 ids = find_issues_with_query(query).map(&:id)
699 699 assert_equal [], ids & [1, 2, 3]
700 700 assert_include 4, ids
701 701 end
702 702
703 703 def test_filter_on_relations_with_any_issues
704 704 IssueRelation.delete_all
705 705 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
706 706 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
707 707
708 708 query = Query.new(:name => '_')
709 709 query.filters = {"relates" => {:operator => '*', :values => ['']}}
710 710 assert_equal [1, 2, 3], find_issues_with_query(query).map(&:id).sort
711 711 end
712 712
713 713 def test_statement_should_be_nil_with_no_filters
714 714 q = Query.new(:name => '_')
715 715 q.filters = {}
716 716
717 717 assert q.valid?
718 718 assert_nil q.statement
719 719 end
720 720
721 721 def test_default_columns
722 722 q = Query.new
723 723 assert !q.columns.empty?
724 724 end
725 725
726 726 def test_set_column_names
727 727 q = Query.new
728 728 q.column_names = ['tracker', :subject, '', 'unknonw_column']
729 729 assert_equal [:tracker, :subject], q.columns.collect {|c| c.name}
730 730 c = q.columns.first
731 731 assert q.has_column?(c)
732 732 end
733 733
734 734 def test_query_should_preload_spent_hours
735 735 q = Query.new(:name => '_', :column_names => [:subject, :spent_hours])
736 736 assert q.has_column?(:spent_hours)
737 737 issues = q.issues
738 738 assert_not_nil issues.first.instance_variable_get("@spent_hours")
739 739 end
740 740
741 741 def test_groupable_columns_should_include_custom_fields
742 742 q = Query.new
743 743 column = q.groupable_columns.detect {|c| c.name == :cf_1}
744 744 assert_not_nil column
745 745 assert_kind_of QueryCustomFieldColumn, column
746 746 end
747 747
748 748 def test_groupable_columns_should_not_include_multi_custom_fields
749 749 field = CustomField.find(1)
750 750 field.update_attribute :multiple, true
751 751
752 752 q = Query.new
753 753 column = q.groupable_columns.detect {|c| c.name == :cf_1}
754 754 assert_nil column
755 755 end
756 756
757 757 def test_groupable_columns_should_include_user_custom_fields
758 758 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'user')
759 759
760 760 q = Query.new
761 761 assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
762 762 end
763 763
764 764 def test_groupable_columns_should_include_version_custom_fields
765 765 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'version')
766 766
767 767 q = Query.new
768 768 assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
769 769 end
770 770
771 771 def test_grouped_with_valid_column
772 772 q = Query.new(:group_by => 'status')
773 773 assert q.grouped?
774 774 assert_not_nil q.group_by_column
775 775 assert_equal :status, q.group_by_column.name
776 776 assert_not_nil q.group_by_statement
777 777 assert_equal 'status', q.group_by_statement
778 778 end
779 779
780 780 def test_grouped_with_invalid_column
781 781 q = Query.new(:group_by => 'foo')
782 782 assert !q.grouped?
783 783 assert_nil q.group_by_column
784 784 assert_nil q.group_by_statement
785 785 end
786 786
787 787 def test_sortable_columns_should_sort_assignees_according_to_user_format_setting
788 788 with_settings :user_format => 'lastname_coma_firstname' do
789 789 q = Query.new
790 790 assert q.sortable_columns.has_key?('assigned_to')
791 791 assert_equal %w(users.lastname users.firstname users.id), q.sortable_columns['assigned_to']
792 792 end
793 793 end
794 794
795 795 def test_sortable_columns_should_sort_authors_according_to_user_format_setting
796 796 with_settings :user_format => 'lastname_coma_firstname' do
797 797 q = Query.new
798 798 assert q.sortable_columns.has_key?('author')
799 799 assert_equal %w(authors.lastname authors.firstname authors.id), q.sortable_columns['author']
800 800 end
801 801 end
802 802
803 803 def test_sortable_columns_should_include_custom_field
804 804 q = Query.new
805 805 assert q.sortable_columns['cf_1']
806 806 end
807 807
808 808 def test_sortable_columns_should_not_include_multi_custom_field
809 809 field = CustomField.find(1)
810 810 field.update_attribute :multiple, true
811 811
812 812 q = Query.new
813 813 assert !q.sortable_columns['cf_1']
814 814 end
815 815
816 816 def test_default_sort
817 817 q = Query.new
818 818 assert_equal [], q.sort_criteria
819 819 end
820 820
821 821 def test_set_sort_criteria_with_hash
822 822 q = Query.new
823 823 q.sort_criteria = {'0' => ['priority', 'desc'], '2' => ['tracker']}
824 824 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
825 825 end
826 826
827 827 def test_set_sort_criteria_with_array
828 828 q = Query.new
829 829 q.sort_criteria = [['priority', 'desc'], 'tracker']
830 830 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
831 831 end
832 832
833 833 def test_create_query_with_sort
834 834 q = Query.new(:name => 'Sorted')
835 835 q.sort_criteria = [['priority', 'desc'], 'tracker']
836 836 assert q.save
837 837 q.reload
838 838 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
839 839 end
840 840
841 841 def test_sort_by_string_custom_field_asc
842 842 q = Query.new
843 843 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
844 844 assert c
845 845 assert c.sortable
846 846 issues = Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where(
847 847 q.statement
848 848 ).order("#{c.sortable} ASC").all
849 849 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
850 850 assert !values.empty?
851 851 assert_equal values.sort, values
852 852 end
853 853
854 854 def test_sort_by_string_custom_field_desc
855 855 q = Query.new
856 856 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
857 857 assert c
858 858 assert c.sortable
859 859 issues = Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where(
860 860 q.statement
861 861 ).order("#{c.sortable} DESC").all
862 862 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
863 863 assert !values.empty?
864 864 assert_equal values.sort.reverse, values
865 865 end
866 866
867 867 def test_sort_by_float_custom_field_asc
868 868 q = Query.new
869 869 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'float' }
870 870 assert c
871 871 assert c.sortable
872 872 issues = Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where(
873 873 q.statement
874 874 ).order("#{c.sortable} ASC").all
875 875 values = issues.collect {|i| begin; Kernel.Float(i.custom_value_for(c.custom_field).to_s); rescue; nil; end}.compact
876 876 assert !values.empty?
877 877 assert_equal values.sort, values
878 878 end
879 879
880 880 def test_invalid_query_should_raise_query_statement_invalid_error
881 881 q = Query.new
882 882 assert_raise Query::StatementInvalid do
883 883 q.issues(:conditions => "foo = 1")
884 884 end
885 885 end
886 886
887 887 def test_issue_count
888 888 q = Query.new(:name => '_')
889 889 issue_count = q.issue_count
890 890 assert_equal q.issues.size, issue_count
891 891 end
892 892
893 893 def test_issue_count_with_archived_issues
894 894 p = Project.generate! do |project|
895 895 project.status = Project::STATUS_ARCHIVED
896 896 end
897 897 i = Issue.generate!( :project => p, :tracker => p.trackers.first )
898 898 assert !i.visible?
899 899
900 900 test_issue_count
901 901 end
902 902
903 903 def test_issue_count_by_association_group
904 904 q = Query.new(:name => '_', :group_by => 'assigned_to')
905 905 count_by_group = q.issue_count_by_group
906 906 assert_kind_of Hash, count_by_group
907 907 assert_equal %w(NilClass User), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
908 908 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
909 909 assert count_by_group.has_key?(User.find(3))
910 910 end
911 911
912 912 def test_issue_count_by_list_custom_field_group
913 913 q = Query.new(:name => '_', :group_by => 'cf_1')
914 914 count_by_group = q.issue_count_by_group
915 915 assert_kind_of Hash, count_by_group
916 916 assert_equal %w(NilClass String), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
917 917 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
918 918 assert count_by_group.has_key?('MySQL')
919 919 end
920 920
921 921 def test_issue_count_by_date_custom_field_group
922 922 q = Query.new(:name => '_', :group_by => 'cf_8')
923 923 count_by_group = q.issue_count_by_group
924 924 assert_kind_of Hash, count_by_group
925 925 assert_equal %w(Date NilClass), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
926 926 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
927 927 end
928 928
929 929 def test_issue_count_with_nil_group_only
930 930 Issue.update_all("assigned_to_id = NULL")
931 931
932 932 q = Query.new(:name => '_', :group_by => 'assigned_to')
933 933 count_by_group = q.issue_count_by_group
934 934 assert_kind_of Hash, count_by_group
935 935 assert_equal 1, count_by_group.keys.size
936 936 assert_nil count_by_group.keys.first
937 937 end
938 938
939 939 def test_issue_ids
940 940 q = Query.new(:name => '_')
941 941 order = "issues.subject, issues.id"
942 942 issues = q.issues(:order => order)
943 943 assert_equal issues.map(&:id), q.issue_ids(:order => order)
944 944 end
945 945
946 946 def test_label_for
947 947 set_language_if_valid 'en'
948 948 q = Query.new
949 949 assert_equal 'Assignee', q.label_for('assigned_to_id')
950 950 end
951 951
952 952 def test_label_for_fr
953 953 set_language_if_valid 'fr'
954 954 q = Query.new
955 955 s = "Assign\xc3\xa9 \xc3\xa0"
956 956 s.force_encoding('UTF-8') if s.respond_to?(:force_encoding)
957 957 assert_equal s, q.label_for('assigned_to_id')
958 958 end
959 959
960 960 def test_editable_by
961 961 admin = User.find(1)
962 962 manager = User.find(2)
963 963 developer = User.find(3)
964 964
965 965 # Public query on project 1
966 966 q = Query.find(1)
967 967 assert q.editable_by?(admin)
968 968 assert q.editable_by?(manager)
969 969 assert !q.editable_by?(developer)
970 970
971 971 # Private query on project 1
972 972 q = Query.find(2)
973 973 assert q.editable_by?(admin)
974 974 assert !q.editable_by?(manager)
975 975 assert q.editable_by?(developer)
976 976
977 977 # Private query for all projects
978 978 q = Query.find(3)
979 979 assert q.editable_by?(admin)
980 980 assert !q.editable_by?(manager)
981 981 assert q.editable_by?(developer)
982 982
983 983 # Public query for all projects
984 984 q = Query.find(4)
985 985 assert q.editable_by?(admin)
986 986 assert !q.editable_by?(manager)
987 987 assert !q.editable_by?(developer)
988 988 end
989 989
990 990 def test_visible_scope
991 991 query_ids = Query.visible(User.anonymous).map(&:id)
992 992
993 993 assert query_ids.include?(1), 'public query on public project was not visible'
994 994 assert query_ids.include?(4), 'public query for all projects was not visible'
995 995 assert !query_ids.include?(2), 'private query on public project was visible'
996 996 assert !query_ids.include?(3), 'private query for all projects was visible'
997 997 assert !query_ids.include?(7), 'public query on private project was visible'
998 998 end
999 999
1000 1000 context "#available_filters" do
1001 1001 setup do
1002 1002 @query = Query.new(:name => "_")
1003 1003 end
1004 1004
1005 1005 should "include users of visible projects in cross-project view" do
1006 1006 users = @query.available_filters["assigned_to_id"]
1007 1007 assert_not_nil users
1008 1008 assert users[:values].map{|u|u[1]}.include?("3")
1009 1009 end
1010 1010
1011 1011 should "include users of subprojects" do
1012 1012 user1 = User.generate!
1013 1013 user2 = User.generate!
1014 1014 project = Project.find(1)
1015 1015 Member.create!(:principal => user1, :project => project.children.visible.first, :role_ids => [1])
1016 1016 @query.project = project
1017 1017
1018 1018 users = @query.available_filters["assigned_to_id"]
1019 1019 assert_not_nil users
1020 1020 assert users[:values].map{|u|u[1]}.include?(user1.id.to_s)
1021 1021 assert !users[:values].map{|u|u[1]}.include?(user2.id.to_s)
1022 1022 end
1023 1023
1024 1024 should "include visible projects in cross-project view" do
1025 1025 projects = @query.available_filters["project_id"]
1026 1026 assert_not_nil projects
1027 1027 assert projects[:values].map{|u|u[1]}.include?("1")
1028 1028 end
1029 1029
1030 1030 context "'member_of_group' filter" do
1031 1031 should "be present" do
1032 1032 assert @query.available_filters.keys.include?("member_of_group")
1033 1033 end
1034 1034
1035 1035 should "be an optional list" do
1036 1036 assert_equal :list_optional, @query.available_filters["member_of_group"][:type]
1037 1037 end
1038 1038
1039 1039 should "have a list of the groups as values" do
1040 1040 Group.destroy_all # No fixtures
1041 1041 group1 = Group.generate!.reload
1042 1042 group2 = Group.generate!.reload
1043 1043
1044 1044 expected_group_list = [
1045 1045 [group1.name, group1.id.to_s],
1046 1046 [group2.name, group2.id.to_s]
1047 1047 ]
1048 1048 assert_equal expected_group_list.sort, @query.available_filters["member_of_group"][:values].sort
1049 1049 end
1050 1050
1051 1051 end
1052 1052
1053 1053 context "'assigned_to_role' filter" do
1054 1054 should "be present" do
1055 1055 assert @query.available_filters.keys.include?("assigned_to_role")
1056 1056 end
1057 1057
1058 1058 should "be an optional list" do
1059 1059 assert_equal :list_optional, @query.available_filters["assigned_to_role"][:type]
1060 1060 end
1061 1061
1062 1062 should "have a list of the Roles as values" do
1063 1063 assert @query.available_filters["assigned_to_role"][:values].include?(['Manager','1'])
1064 1064 assert @query.available_filters["assigned_to_role"][:values].include?(['Developer','2'])
1065 1065 assert @query.available_filters["assigned_to_role"][:values].include?(['Reporter','3'])
1066 1066 end
1067 1067
1068 1068 should "not include the built in Roles as values" do
1069 1069 assert ! @query.available_filters["assigned_to_role"][:values].include?(['Non member','4'])
1070 1070 assert ! @query.available_filters["assigned_to_role"][:values].include?(['Anonymous','5'])
1071 1071 end
1072 1072
1073 1073 end
1074 1074
1075 1075 end
1076 1076
1077 1077 context "#statement" do
1078 1078 context "with 'member_of_group' filter" do
1079 1079 setup do
1080 1080 Group.destroy_all # No fixtures
1081 1081 @user_in_group = User.generate!
1082 1082 @second_user_in_group = User.generate!
1083 1083 @user_in_group2 = User.generate!
1084 1084 @user_not_in_group = User.generate!
1085 1085
1086 1086 @group = Group.generate!.reload
1087 1087 @group.users << @user_in_group
1088 1088 @group.users << @second_user_in_group
1089 1089
1090 1090 @group2 = Group.generate!.reload
1091 1091 @group2.users << @user_in_group2
1092 1092
1093 1093 end
1094 1094
1095 1095 should "search assigned to for users in the group" do
1096 1096 @query = Query.new(:name => '_')
1097 1097 @query.add_filter('member_of_group', '=', [@group.id.to_s])
1098 1098
1099 1099 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}')"
1100 1100 assert_find_issues_with_query_is_successful @query
1101 1101 end
1102 1102
1103 1103 should "search not assigned to any group member (none)" do
1104 1104 @query = Query.new(:name => '_')
1105 1105 @query.add_filter('member_of_group', '!*', [''])
1106 1106
1107 1107 # Users not in a group
1108 1108 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IS NULL OR #{Issue.table_name}.assigned_to_id NOT IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}')"
1109 1109 assert_find_issues_with_query_is_successful @query
1110 1110 end
1111 1111
1112 1112 should "search assigned to any group member (all)" do
1113 1113 @query = Query.new(:name => '_')
1114 1114 @query.add_filter('member_of_group', '*', [''])
1115 1115
1116 1116 # Only users in a group
1117 1117 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}')"
1118 1118 assert_find_issues_with_query_is_successful @query
1119 1119 end
1120 1120
1121 1121 should "return an empty set with = empty group" do
1122 1122 @empty_group = Group.generate!
1123 1123 @query = Query.new(:name => '_')
1124 1124 @query.add_filter('member_of_group', '=', [@empty_group.id.to_s])
1125 1125
1126 1126 assert_equal [], find_issues_with_query(@query)
1127 1127 end
1128 1128
1129 1129 should "return issues with ! empty group" do
1130 1130 @empty_group = Group.generate!
1131 1131 @query = Query.new(:name => '_')
1132 1132 @query.add_filter('member_of_group', '!', [@empty_group.id.to_s])
1133 1133
1134 1134 assert_find_issues_with_query_is_successful @query
1135 1135 end
1136 1136 end
1137 1137
1138 1138 context "with 'assigned_to_role' filter" do
1139 1139 setup do
1140 1140 @manager_role = Role.find_by_name('Manager')
1141 1141 @developer_role = Role.find_by_name('Developer')
1142 1142
1143 1143 @project = Project.generate!
1144 1144 @manager = User.generate!
1145 1145 @developer = User.generate!
1146 1146 @boss = User.generate!
1147 1147 @guest = User.generate!
1148 1148 User.add_to_project(@manager, @project, @manager_role)
1149 1149 User.add_to_project(@developer, @project, @developer_role)
1150 1150 User.add_to_project(@boss, @project, [@manager_role, @developer_role])
1151 1151
1152 @issue1 = Issue.generate_for_project!(@project, :assigned_to_id => @manager.id)
1153 @issue2 = Issue.generate_for_project!(@project, :assigned_to_id => @developer.id)
1154 @issue3 = Issue.generate_for_project!(@project, :assigned_to_id => @boss.id)
1155 @issue4 = Issue.generate_for_project!(@project, :assigned_to_id => @guest.id)
1156 @issue5 = Issue.generate_for_project!(@project)
1152 @issue1 = Issue.generate!(:project => @project, :assigned_to_id => @manager.id)
1153 @issue2 = Issue.generate!(:project => @project, :assigned_to_id => @developer.id)
1154 @issue3 = Issue.generate!(:project => @project, :assigned_to_id => @boss.id)
1155 @issue4 = Issue.generate!(:project => @project, :assigned_to_id => @guest.id)
1156 @issue5 = Issue.generate!(:project => @project)
1157 1157 end
1158 1158
1159 1159 should "search assigned to for users with the Role" do
1160 1160 @query = Query.new(:name => '_', :project => @project)
1161 1161 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
1162 1162
1163 1163 assert_query_result [@issue1, @issue3], @query
1164 1164 end
1165 1165
1166 1166 should "search assigned to for users with the Role on the issue project" do
1167 1167 other_project = Project.generate!
1168 1168 User.add_to_project(@developer, other_project, @manager_role)
1169 1169
1170 1170 @query = Query.new(:name => '_', :project => @project)
1171 1171 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
1172 1172
1173 1173 assert_query_result [@issue1, @issue3], @query
1174 1174 end
1175 1175
1176 1176 should "return an empty set with empty role" do
1177 1177 @empty_role = Role.generate!
1178 1178 @query = Query.new(:name => '_', :project => @project)
1179 1179 @query.add_filter('assigned_to_role', '=', [@empty_role.id.to_s])
1180 1180
1181 1181 assert_query_result [], @query
1182 1182 end
1183 1183
1184 1184 should "search assigned to for users without the Role" do
1185 1185 @query = Query.new(:name => '_', :project => @project)
1186 1186 @query.add_filter('assigned_to_role', '!', [@manager_role.id.to_s])
1187 1187
1188 1188 assert_query_result [@issue2, @issue4, @issue5], @query
1189 1189 end
1190 1190
1191 1191 should "search assigned to for users not assigned to any Role (none)" do
1192 1192 @query = Query.new(:name => '_', :project => @project)
1193 1193 @query.add_filter('assigned_to_role', '!*', [''])
1194 1194
1195 1195 assert_query_result [@issue4, @issue5], @query
1196 1196 end
1197 1197
1198 1198 should "search assigned to for users assigned to any Role (all)" do
1199 1199 @query = Query.new(:name => '_', :project => @project)
1200 1200 @query.add_filter('assigned_to_role', '*', [''])
1201 1201
1202 1202 assert_query_result [@issue1, @issue2, @issue3], @query
1203 1203 end
1204 1204
1205 1205 should "return issues with ! empty role" do
1206 1206 @empty_role = Role.generate!
1207 1207 @query = Query.new(:name => '_', :project => @project)
1208 1208 @query.add_filter('assigned_to_role', '!', [@empty_role.id.to_s])
1209 1209
1210 1210 assert_query_result [@issue1, @issue2, @issue3, @issue4, @issue5], @query
1211 1211 end
1212 1212 end
1213 1213 end
1214 1214
1215 1215 end
@@ -1,1074 +1,1074
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2012 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 UserTest < ActiveSupport::TestCase
21 21 fixtures :users, :members, :projects, :roles, :member_roles, :auth_sources,
22 22 :trackers, :issue_statuses,
23 23 :projects_trackers,
24 24 :watchers,
25 25 :issue_categories, :enumerations, :issues,
26 26 :journals, :journal_details,
27 27 :groups_users,
28 28 :enabled_modules,
29 29 :workflows
30 30
31 31 def setup
32 32 @admin = User.find(1)
33 33 @jsmith = User.find(2)
34 34 @dlopper = User.find(3)
35 35 end
36 36
37 37 def test_generate
38 38 User.generate!(:firstname => 'Testing connection')
39 39 User.generate!(:firstname => 'Testing connection')
40 40 assert_equal 2, User.count(:all, :conditions => {:firstname => 'Testing connection'})
41 41 end
42 42
43 43 def test_truth
44 44 assert_kind_of User, @jsmith
45 45 end
46 46
47 47 def test_mail_should_be_stripped
48 48 u = User.new
49 49 u.mail = " foo@bar.com "
50 50 assert_equal "foo@bar.com", u.mail
51 51 end
52 52
53 53 def test_mail_validation
54 54 u = User.new
55 55 u.mail = ''
56 56 assert !u.valid?
57 57 assert_include I18n.translate('activerecord.errors.messages.blank'), u.errors[:mail]
58 58 end
59 59
60 60 def test_login_length_validation
61 61 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
62 62 user.login = "x" * (User::LOGIN_LENGTH_LIMIT+1)
63 63 assert !user.valid?
64 64
65 65 user.login = "x" * (User::LOGIN_LENGTH_LIMIT)
66 66 assert user.valid?
67 67 assert user.save
68 68 end
69 69
70 70 def test_create
71 71 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
72 72
73 73 user.login = "jsmith"
74 74 user.password, user.password_confirmation = "password", "password"
75 75 # login uniqueness
76 76 assert !user.save
77 77 assert_equal 1, user.errors.count
78 78
79 79 user.login = "newuser"
80 80 user.password, user.password_confirmation = "passwd", "password"
81 81 # password confirmation
82 82 assert !user.save
83 83 assert_equal 1, user.errors.count
84 84
85 85 user.password, user.password_confirmation = "password", "password"
86 86 assert user.save
87 87 end
88 88
89 89 def test_user_before_create_should_set_the_mail_notification_to_the_default_setting
90 90 @user1 = User.generate!
91 91 assert_equal 'only_my_events', @user1.mail_notification
92 92 with_settings :default_notification_option => 'all' do
93 93 @user2 = User.generate!
94 94 assert_equal 'all', @user2.mail_notification
95 95 end
96 96 end
97 97
98 98 def test_user_login_should_be_case_insensitive
99 99 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
100 100 u.login = 'newuser'
101 101 u.password, u.password_confirmation = "password", "password"
102 102 assert u.save
103 103 u = User.new(:firstname => "Similar", :lastname => "User", :mail => "similaruser@somenet.foo")
104 104 u.login = 'NewUser'
105 105 u.password, u.password_confirmation = "password", "password"
106 106 assert !u.save
107 107 assert_include I18n.translate('activerecord.errors.messages.taken'), u.errors[:login]
108 108 end
109 109
110 110 def test_mail_uniqueness_should_not_be_case_sensitive
111 111 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
112 112 u.login = 'newuser1'
113 113 u.password, u.password_confirmation = "password", "password"
114 114 assert u.save
115 115
116 116 u = User.new(:firstname => "new", :lastname => "user", :mail => "newUser@Somenet.foo")
117 117 u.login = 'newuser2'
118 118 u.password, u.password_confirmation = "password", "password"
119 119 assert !u.save
120 120 assert_include I18n.translate('activerecord.errors.messages.taken'), u.errors[:mail]
121 121 end
122 122
123 123 def test_update
124 124 assert_equal "admin", @admin.login
125 125 @admin.login = "john"
126 126 assert @admin.save, @admin.errors.full_messages.join("; ")
127 127 @admin.reload
128 128 assert_equal "john", @admin.login
129 129 end
130 130
131 131 def test_update_should_not_fail_for_legacy_user_with_different_case_logins
132 132 u1 = User.new(:firstname => "new", :lastname => "user", :mail => "newuser1@somenet.foo")
133 133 u1.login = 'newuser1'
134 134 assert u1.save
135 135
136 136 u2 = User.new(:firstname => "new", :lastname => "user", :mail => "newuser2@somenet.foo")
137 137 u2.login = 'newuser1'
138 138 assert u2.save(:validate => false)
139 139
140 140 user = User.find(u2.id)
141 141 user.firstname = "firstname"
142 142 assert user.save, "Save failed"
143 143 end
144 144
145 145 def test_destroy_should_delete_members_and_roles
146 146 members = Member.find_all_by_user_id(2)
147 147 ms = members.size
148 148 rs = members.collect(&:roles).flatten.size
149 149
150 150 assert_difference 'Member.count', - ms do
151 151 assert_difference 'MemberRole.count', - rs do
152 152 User.find(2).destroy
153 153 end
154 154 end
155 155
156 156 assert_nil User.find_by_id(2)
157 157 assert Member.find_all_by_user_id(2).empty?
158 158 end
159 159
160 160 def test_destroy_should_update_attachments
161 161 attachment = Attachment.create!(:container => Project.find(1),
162 162 :file => uploaded_test_file("testfile.txt", "text/plain"),
163 163 :author_id => 2)
164 164
165 165 User.find(2).destroy
166 166 assert_nil User.find_by_id(2)
167 167 assert_equal User.anonymous, attachment.reload.author
168 168 end
169 169
170 170 def test_destroy_should_update_comments
171 171 comment = Comment.create!(
172 172 :commented => News.create!(:project_id => 1, :author_id => 1, :title => 'foo', :description => 'foo'),
173 173 :author => User.find(2),
174 174 :comments => 'foo'
175 175 )
176 176
177 177 User.find(2).destroy
178 178 assert_nil User.find_by_id(2)
179 179 assert_equal User.anonymous, comment.reload.author
180 180 end
181 181
182 182 def test_destroy_should_update_issues
183 183 issue = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'foo')
184 184
185 185 User.find(2).destroy
186 186 assert_nil User.find_by_id(2)
187 187 assert_equal User.anonymous, issue.reload.author
188 188 end
189 189
190 190 def test_destroy_should_unassign_issues
191 191 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo', :assigned_to_id => 2)
192 192
193 193 User.find(2).destroy
194 194 assert_nil User.find_by_id(2)
195 195 assert_nil issue.reload.assigned_to
196 196 end
197 197
198 198 def test_destroy_should_update_journals
199 199 issue = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'foo')
200 200 issue.init_journal(User.find(2), "update")
201 201 issue.save!
202 202
203 203 User.find(2).destroy
204 204 assert_nil User.find_by_id(2)
205 205 assert_equal User.anonymous, issue.journals.first.reload.user
206 206 end
207 207
208 208 def test_destroy_should_update_journal_details_old_value
209 209 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo', :assigned_to_id => 2)
210 210 issue.init_journal(User.find(1), "update")
211 211 issue.assigned_to_id = nil
212 212 assert_difference 'JournalDetail.count' do
213 213 issue.save!
214 214 end
215 215 journal_detail = JournalDetail.first(:order => 'id DESC')
216 216 assert_equal '2', journal_detail.old_value
217 217
218 218 User.find(2).destroy
219 219 assert_nil User.find_by_id(2)
220 220 assert_equal User.anonymous.id.to_s, journal_detail.reload.old_value
221 221 end
222 222
223 223 def test_destroy_should_update_journal_details_value
224 224 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo')
225 225 issue.init_journal(User.find(1), "update")
226 226 issue.assigned_to_id = 2
227 227 assert_difference 'JournalDetail.count' do
228 228 issue.save!
229 229 end
230 230 journal_detail = JournalDetail.first(:order => 'id DESC')
231 231 assert_equal '2', journal_detail.value
232 232
233 233 User.find(2).destroy
234 234 assert_nil User.find_by_id(2)
235 235 assert_equal User.anonymous.id.to_s, journal_detail.reload.value
236 236 end
237 237
238 238 def test_destroy_should_update_messages
239 239 board = Board.create!(:project_id => 1, :name => 'Board', :description => 'Board')
240 240 message = Message.create!(:board_id => board.id, :author_id => 2, :subject => 'foo', :content => 'foo')
241 241
242 242 User.find(2).destroy
243 243 assert_nil User.find_by_id(2)
244 244 assert_equal User.anonymous, message.reload.author
245 245 end
246 246
247 247 def test_destroy_should_update_news
248 248 news = News.create!(:project_id => 1, :author_id => 2, :title => 'foo', :description => 'foo')
249 249
250 250 User.find(2).destroy
251 251 assert_nil User.find_by_id(2)
252 252 assert_equal User.anonymous, news.reload.author
253 253 end
254 254
255 255 def test_destroy_should_delete_private_queries
256 256 query = Query.new(:name => 'foo', :is_public => false)
257 257 query.project_id = 1
258 258 query.user_id = 2
259 259 query.save!
260 260
261 261 User.find(2).destroy
262 262 assert_nil User.find_by_id(2)
263 263 assert_nil Query.find_by_id(query.id)
264 264 end
265 265
266 266 def test_destroy_should_update_public_queries
267 267 query = Query.new(:name => 'foo', :is_public => true)
268 268 query.project_id = 1
269 269 query.user_id = 2
270 270 query.save!
271 271
272 272 User.find(2).destroy
273 273 assert_nil User.find_by_id(2)
274 274 assert_equal User.anonymous, query.reload.user
275 275 end
276 276
277 277 def test_destroy_should_update_time_entries
278 278 entry = TimeEntry.new(:hours => '2', :spent_on => Date.today, :activity => TimeEntryActivity.create!(:name => 'foo'))
279 279 entry.project_id = 1
280 280 entry.user_id = 2
281 281 entry.save!
282 282
283 283 User.find(2).destroy
284 284 assert_nil User.find_by_id(2)
285 285 assert_equal User.anonymous, entry.reload.user
286 286 end
287 287
288 288 def test_destroy_should_delete_tokens
289 289 token = Token.create!(:user_id => 2, :value => 'foo')
290 290
291 291 User.find(2).destroy
292 292 assert_nil User.find_by_id(2)
293 293 assert_nil Token.find_by_id(token.id)
294 294 end
295 295
296 296 def test_destroy_should_delete_watchers
297 297 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo')
298 298 watcher = Watcher.create!(:user_id => 2, :watchable => issue)
299 299
300 300 User.find(2).destroy
301 301 assert_nil User.find_by_id(2)
302 302 assert_nil Watcher.find_by_id(watcher.id)
303 303 end
304 304
305 305 def test_destroy_should_update_wiki_contents
306 306 wiki_content = WikiContent.create!(
307 307 :text => 'foo',
308 308 :author_id => 2,
309 309 :page => WikiPage.create!(:title => 'Foo', :wiki => Wiki.create!(:project_id => 1, :start_page => 'Start'))
310 310 )
311 311 wiki_content.text = 'bar'
312 312 assert_difference 'WikiContent::Version.count' do
313 313 wiki_content.save!
314 314 end
315 315
316 316 User.find(2).destroy
317 317 assert_nil User.find_by_id(2)
318 318 assert_equal User.anonymous, wiki_content.reload.author
319 319 wiki_content.versions.each do |version|
320 320 assert_equal User.anonymous, version.reload.author
321 321 end
322 322 end
323 323
324 324 def test_destroy_should_nullify_issue_categories
325 325 category = IssueCategory.create!(:project_id => 1, :assigned_to_id => 2, :name => 'foo')
326 326
327 327 User.find(2).destroy
328 328 assert_nil User.find_by_id(2)
329 329 assert_nil category.reload.assigned_to_id
330 330 end
331 331
332 332 def test_destroy_should_nullify_changesets
333 333 changeset = Changeset.create!(
334 334 :repository => Repository::Subversion.create!(
335 335 :project_id => 1,
336 336 :url => 'file:///tmp',
337 337 :identifier => 'tmp'
338 338 ),
339 339 :revision => '12',
340 340 :committed_on => Time.now,
341 341 :committer => 'jsmith'
342 342 )
343 343 assert_equal 2, changeset.user_id
344 344
345 345 User.find(2).destroy
346 346 assert_nil User.find_by_id(2)
347 347 assert_nil changeset.reload.user_id
348 348 end
349 349
350 350 def test_anonymous_user_should_not_be_destroyable
351 351 assert_no_difference 'User.count' do
352 352 assert_equal false, User.anonymous.destroy
353 353 end
354 354 end
355 355
356 356 def test_validate_login_presence
357 357 @admin.login = ""
358 358 assert !@admin.save
359 359 assert_equal 1, @admin.errors.count
360 360 end
361 361
362 362 def test_validate_mail_notification_inclusion
363 363 u = User.new
364 364 u.mail_notification = 'foo'
365 365 u.save
366 366 assert_not_nil u.errors[:mail_notification]
367 367 end
368 368
369 369 context "User#try_to_login" do
370 370 should "fall-back to case-insensitive if user login is not found as-typed." do
371 371 user = User.try_to_login("AdMin", "admin")
372 372 assert_kind_of User, user
373 373 assert_equal "admin", user.login
374 374 end
375 375
376 376 should "select the exact matching user first" do
377 377 case_sensitive_user = User.generate! do |user|
378 378 user.password = "admin"
379 379 end
380 380 # bypass validations to make it appear like existing data
381 381 case_sensitive_user.update_attribute(:login, 'ADMIN')
382 382
383 383 user = User.try_to_login("ADMIN", "admin")
384 384 assert_kind_of User, user
385 385 assert_equal "ADMIN", user.login
386 386
387 387 end
388 388 end
389 389
390 390 def test_password
391 391 user = User.try_to_login("admin", "admin")
392 392 assert_kind_of User, user
393 393 assert_equal "admin", user.login
394 394 user.password = "hello"
395 395 assert user.save
396 396
397 397 user = User.try_to_login("admin", "hello")
398 398 assert_kind_of User, user
399 399 assert_equal "admin", user.login
400 400 end
401 401
402 402 def test_validate_password_length
403 403 with_settings :password_min_length => '100' do
404 404 user = User.new(:firstname => "new100", :lastname => "user100", :mail => "newuser100@somenet.foo")
405 405 user.login = "newuser100"
406 406 user.password, user.password_confirmation = "password100", "password100"
407 407 assert !user.save
408 408 assert_equal 1, user.errors.count
409 409 end
410 410 end
411 411
412 412 def test_name_format
413 413 assert_equal 'Smith, John', @jsmith.name(:lastname_coma_firstname)
414 414 with_settings :user_format => :firstname_lastname do
415 415 assert_equal 'John Smith', @jsmith.reload.name
416 416 end
417 417 with_settings :user_format => :username do
418 418 assert_equal 'jsmith', @jsmith.reload.name
419 419 end
420 420 with_settings :user_format => :lastname do
421 421 assert_equal 'Smith', @jsmith.reload.name
422 422 end
423 423 end
424 424
425 425 def test_today_should_return_the_day_according_to_user_time_zone
426 426 preference = User.find(1).pref
427 427 date = Date.new(2012, 05, 15)
428 428 time = Time.gm(2012, 05, 15, 23, 30).utc # 2012-05-15 23:30 UTC
429 429 Date.stubs(:today).returns(date)
430 430 Time.stubs(:now).returns(time)
431 431
432 432 preference.update_attribute :time_zone, 'Baku' # UTC+4
433 433 assert_equal '2012-05-16', User.find(1).today.to_s
434 434
435 435 preference.update_attribute :time_zone, 'La Paz' # UTC-4
436 436 assert_equal '2012-05-15', User.find(1).today.to_s
437 437
438 438 preference.update_attribute :time_zone, ''
439 439 assert_equal '2012-05-15', User.find(1).today.to_s
440 440 end
441 441
442 442 def test_time_to_date_should_return_the_date_according_to_user_time_zone
443 443 preference = User.find(1).pref
444 444 time = Time.gm(2012, 05, 15, 23, 30).utc # 2012-05-15 23:30 UTC
445 445
446 446 preference.update_attribute :time_zone, 'Baku' # UTC+4
447 447 assert_equal '2012-05-16', User.find(1).time_to_date(time).to_s
448 448
449 449 preference.update_attribute :time_zone, 'La Paz' # UTC-4
450 450 assert_equal '2012-05-15', User.find(1).time_to_date(time).to_s
451 451
452 452 preference.update_attribute :time_zone, ''
453 453 assert_equal '2012-05-15', User.find(1).time_to_date(time).to_s
454 454 end
455 455
456 456 def test_fields_for_order_statement_should_return_fields_according_user_format_setting
457 457 with_settings :user_format => 'lastname_coma_firstname' do
458 458 assert_equal ['users.lastname', 'users.firstname', 'users.id'], User.fields_for_order_statement
459 459 end
460 460 end
461 461
462 462 def test_fields_for_order_statement_width_table_name_should_prepend_table_name
463 463 with_settings :user_format => 'lastname_firstname' do
464 464 assert_equal ['authors.lastname', 'authors.firstname', 'authors.id'], User.fields_for_order_statement('authors')
465 465 end
466 466 end
467 467
468 468 def test_fields_for_order_statement_with_blank_format_should_return_default
469 469 with_settings :user_format => '' do
470 470 assert_equal ['users.firstname', 'users.lastname', 'users.id'], User.fields_for_order_statement
471 471 end
472 472 end
473 473
474 474 def test_fields_for_order_statement_with_invalid_format_should_return_default
475 475 with_settings :user_format => 'foo' do
476 476 assert_equal ['users.firstname', 'users.lastname', 'users.id'], User.fields_for_order_statement
477 477 end
478 478 end
479 479
480 480 def test_lock
481 481 user = User.try_to_login("jsmith", "jsmith")
482 482 assert_equal @jsmith, user
483 483
484 484 @jsmith.status = User::STATUS_LOCKED
485 485 assert @jsmith.save
486 486
487 487 user = User.try_to_login("jsmith", "jsmith")
488 488 assert_equal nil, user
489 489 end
490 490
491 491 context ".try_to_login" do
492 492 context "with good credentials" do
493 493 should "return the user" do
494 494 user = User.try_to_login("admin", "admin")
495 495 assert_kind_of User, user
496 496 assert_equal "admin", user.login
497 497 end
498 498 end
499 499
500 500 context "with wrong credentials" do
501 501 should "return nil" do
502 502 assert_nil User.try_to_login("admin", "foo")
503 503 end
504 504 end
505 505 end
506 506
507 507 if ldap_configured?
508 508 context "#try_to_login using LDAP" do
509 509 context "with failed connection to the LDAP server" do
510 510 should "return nil" do
511 511 @auth_source = AuthSourceLdap.find(1)
512 512 AuthSource.any_instance.stubs(:initialize_ldap_con).raises(Net::LDAP::LdapError, 'Cannot connect')
513 513
514 514 assert_equal nil, User.try_to_login('edavis', 'wrong')
515 515 end
516 516 end
517 517
518 518 context "with an unsuccessful authentication" do
519 519 should "return nil" do
520 520 assert_equal nil, User.try_to_login('edavis', 'wrong')
521 521 end
522 522 end
523 523
524 524 context "binding with user's account" do
525 525 setup do
526 526 @auth_source = AuthSourceLdap.find(1)
527 527 @auth_source.account = "uid=$login,ou=Person,dc=redmine,dc=org"
528 528 @auth_source.account_password = ''
529 529 @auth_source.save!
530 530
531 531 @ldap_user = User.new(:mail => 'example1@redmine.org', :firstname => 'LDAP', :lastname => 'user', :auth_source_id => 1)
532 532 @ldap_user.login = 'example1'
533 533 @ldap_user.save!
534 534 end
535 535
536 536 context "with a successful authentication" do
537 537 should "return the user" do
538 538 assert_equal @ldap_user, User.try_to_login('example1', '123456')
539 539 end
540 540 end
541 541
542 542 context "with an unsuccessful authentication" do
543 543 should "return nil" do
544 544 assert_nil User.try_to_login('example1', '11111')
545 545 end
546 546 end
547 547 end
548 548
549 549 context "on the fly registration" do
550 550 setup do
551 551 @auth_source = AuthSourceLdap.find(1)
552 552 @auth_source.update_attribute :onthefly_register, true
553 553 end
554 554
555 555 context "with a successful authentication" do
556 556 should "create a new user account if it doesn't exist" do
557 557 assert_difference('User.count') do
558 558 user = User.try_to_login('edavis', '123456')
559 559 assert !user.admin?
560 560 end
561 561 end
562 562
563 563 should "retrieve existing user" do
564 564 user = User.try_to_login('edavis', '123456')
565 565 user.admin = true
566 566 user.save!
567 567
568 568 assert_no_difference('User.count') do
569 569 user = User.try_to_login('edavis', '123456')
570 570 assert user.admin?
571 571 end
572 572 end
573 573 end
574 574
575 575 context "binding with user's account" do
576 576 setup do
577 577 @auth_source = AuthSourceLdap.find(1)
578 578 @auth_source.account = "uid=$login,ou=Person,dc=redmine,dc=org"
579 579 @auth_source.account_password = ''
580 580 @auth_source.save!
581 581 end
582 582
583 583 context "with a successful authentication" do
584 584 should "create a new user account if it doesn't exist" do
585 585 assert_difference('User.count') do
586 586 user = User.try_to_login('example1', '123456')
587 587 assert_kind_of User, user
588 588 end
589 589 end
590 590 end
591 591
592 592 context "with an unsuccessful authentication" do
593 593 should "return nil" do
594 594 assert_nil User.try_to_login('example1', '11111')
595 595 end
596 596 end
597 597 end
598 598 end
599 599 end
600 600
601 601 else
602 602 puts "Skipping LDAP tests."
603 603 end
604 604
605 605 def test_create_anonymous
606 606 AnonymousUser.delete_all
607 607 anon = User.anonymous
608 608 assert !anon.new_record?
609 609 assert_kind_of AnonymousUser, anon
610 610 end
611 611
612 612 def test_ensure_single_anonymous_user
613 613 AnonymousUser.delete_all
614 614 anon1 = User.anonymous
615 615 assert !anon1.new_record?
616 616 assert_kind_of AnonymousUser, anon1
617 617 anon2 = AnonymousUser.create(
618 618 :lastname => 'Anonymous', :firstname => '',
619 619 :mail => '', :login => '', :status => 0)
620 620 assert_equal 1, anon2.errors.count
621 621 end
622 622
623 623 def test_rss_key
624 624 assert_nil @jsmith.rss_token
625 625 key = @jsmith.rss_key
626 626 assert_equal 40, key.length
627 627
628 628 @jsmith.reload
629 629 assert_equal key, @jsmith.rss_key
630 630 end
631 631
632 632 def test_rss_key_should_not_be_generated_twice
633 633 assert_difference 'Token.count', 1 do
634 634 key1 = @jsmith.rss_key
635 635 key2 = @jsmith.rss_key
636 636 assert_equal key1, key2
637 637 end
638 638 end
639 639
640 640 def test_api_key_should_not_be_generated_twice
641 641 assert_difference 'Token.count', 1 do
642 642 key1 = @jsmith.api_key
643 643 key2 = @jsmith.api_key
644 644 assert_equal key1, key2
645 645 end
646 646 end
647 647
648 648 context "User#api_key" do
649 649 should "generate a new one if the user doesn't have one" do
650 650 user = User.generate!(:api_token => nil)
651 651 assert_nil user.api_token
652 652
653 653 key = user.api_key
654 654 assert_equal 40, key.length
655 655 user.reload
656 656 assert_equal key, user.api_key
657 657 end
658 658
659 659 should "return the existing api token value" do
660 660 user = User.generate!
661 661 token = Token.create!(:action => 'api')
662 662 user.api_token = token
663 663 assert user.save
664 664
665 665 assert_equal token.value, user.api_key
666 666 end
667 667 end
668 668
669 669 context "User#find_by_api_key" do
670 670 should "return nil if no matching key is found" do
671 671 assert_nil User.find_by_api_key('zzzzzzzzz')
672 672 end
673 673
674 674 should "return nil if the key is found for an inactive user" do
675 675 user = User.generate!
676 676 user.status = User::STATUS_LOCKED
677 677 token = Token.create!(:action => 'api')
678 678 user.api_token = token
679 679 user.save
680 680
681 681 assert_nil User.find_by_api_key(token.value)
682 682 end
683 683
684 684 should "return the user if the key is found for an active user" do
685 685 user = User.generate!
686 686 token = Token.create!(:action => 'api')
687 687 user.api_token = token
688 688 user.save
689 689
690 690 assert_equal user, User.find_by_api_key(token.value)
691 691 end
692 692 end
693 693
694 694 def test_default_admin_account_changed_should_return_false_if_account_was_not_changed
695 695 user = User.find_by_login("admin")
696 696 user.password = "admin"
697 697 user.save!
698 698
699 699 assert_equal false, User.default_admin_account_changed?
700 700 end
701 701
702 702 def test_default_admin_account_changed_should_return_true_if_password_was_changed
703 703 user = User.find_by_login("admin")
704 704 user.password = "newpassword"
705 705 user.save!
706 706
707 707 assert_equal true, User.default_admin_account_changed?
708 708 end
709 709
710 710 def test_default_admin_account_changed_should_return_true_if_account_is_disabled
711 711 user = User.find_by_login("admin")
712 712 user.password = "admin"
713 713 user.status = User::STATUS_LOCKED
714 714 user.save!
715 715
716 716 assert_equal true, User.default_admin_account_changed?
717 717 end
718 718
719 719 def test_default_admin_account_changed_should_return_true_if_account_does_not_exist
720 720 user = User.find_by_login("admin")
721 721 user.destroy
722 722
723 723 assert_equal true, User.default_admin_account_changed?
724 724 end
725 725
726 726 def test_roles_for_project
727 727 # user with a role
728 728 roles = @jsmith.roles_for_project(Project.find(1))
729 729 assert_kind_of Role, roles.first
730 730 assert_equal "Manager", roles.first.name
731 731
732 732 # user with no role
733 733 assert_nil @dlopper.roles_for_project(Project.find(2)).detect {|role| role.member?}
734 734 end
735 735
736 736 def test_projects_by_role_for_user_with_role
737 737 user = User.find(2)
738 738 assert_kind_of Hash, user.projects_by_role
739 739 assert_equal 2, user.projects_by_role.size
740 740 assert_equal [1,5], user.projects_by_role[Role.find(1)].collect(&:id).sort
741 741 assert_equal [2], user.projects_by_role[Role.find(2)].collect(&:id).sort
742 742 end
743 743
744 744 def test_accessing_projects_by_role_with_no_projects_should_return_an_empty_array
745 745 user = User.find(2)
746 746 assert_equal [], user.projects_by_role[Role.find(3)]
747 747 # should not update the hash
748 748 assert_nil user.projects_by_role.values.detect(&:blank?)
749 749 end
750 750
751 751 def test_projects_by_role_for_user_with_no_role
752 752 user = User.generate!
753 753 assert_equal({}, user.projects_by_role)
754 754 end
755 755
756 756 def test_projects_by_role_for_anonymous
757 757 assert_equal({}, User.anonymous.projects_by_role)
758 758 end
759 759
760 760 def test_valid_notification_options
761 761 # without memberships
762 762 assert_equal 5, User.find(7).valid_notification_options.size
763 763 # with memberships
764 764 assert_equal 6, User.find(2).valid_notification_options.size
765 765 end
766 766
767 767 def test_valid_notification_options_class_method
768 768 assert_equal 5, User.valid_notification_options.size
769 769 assert_equal 5, User.valid_notification_options(User.find(7)).size
770 770 assert_equal 6, User.valid_notification_options(User.find(2)).size
771 771 end
772 772
773 773 def test_mail_notification_all
774 774 @jsmith.mail_notification = 'all'
775 775 @jsmith.notified_project_ids = []
776 776 @jsmith.save
777 777 @jsmith.reload
778 778 assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
779 779 end
780 780
781 781 def test_mail_notification_selected
782 782 @jsmith.mail_notification = 'selected'
783 783 @jsmith.notified_project_ids = [1]
784 784 @jsmith.save
785 785 @jsmith.reload
786 786 assert Project.find(1).recipients.include?(@jsmith.mail)
787 787 end
788 788
789 789 def test_mail_notification_only_my_events
790 790 @jsmith.mail_notification = 'only_my_events'
791 791 @jsmith.notified_project_ids = []
792 792 @jsmith.save
793 793 @jsmith.reload
794 794 assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
795 795 end
796 796
797 797 def test_comments_sorting_preference
798 798 assert !@jsmith.wants_comments_in_reverse_order?
799 799 @jsmith.pref.comments_sorting = 'asc'
800 800 assert !@jsmith.wants_comments_in_reverse_order?
801 801 @jsmith.pref.comments_sorting = 'desc'
802 802 assert @jsmith.wants_comments_in_reverse_order?
803 803 end
804 804
805 805 def test_find_by_mail_should_be_case_insensitive
806 806 u = User.find_by_mail('JSmith@somenet.foo')
807 807 assert_not_nil u
808 808 assert_equal 'jsmith@somenet.foo', u.mail
809 809 end
810 810
811 811 def test_random_password
812 812 u = User.new
813 813 u.random_password
814 814 assert !u.password.blank?
815 815 assert !u.password_confirmation.blank?
816 816 end
817 817
818 818 context "#change_password_allowed?" do
819 819 should "be allowed if no auth source is set" do
820 820 user = User.generate!
821 821 assert user.change_password_allowed?
822 822 end
823 823
824 824 should "delegate to the auth source" do
825 825 user = User.generate!
826 826
827 827 allowed_auth_source = AuthSource.generate!
828 828 def allowed_auth_source.allow_password_changes?; true; end
829 829
830 830 denied_auth_source = AuthSource.generate!
831 831 def denied_auth_source.allow_password_changes?; false; end
832 832
833 833 assert user.change_password_allowed?
834 834
835 835 user.auth_source = allowed_auth_source
836 836 assert user.change_password_allowed?, "User not allowed to change password, though auth source does"
837 837
838 838 user.auth_source = denied_auth_source
839 839 assert !user.change_password_allowed?, "User allowed to change password, though auth source does not"
840 840 end
841 841 end
842 842
843 843 def test_own_account_deletable_should_be_true_with_unsubscrive_enabled
844 844 with_settings :unsubscribe => '1' do
845 845 assert_equal true, User.find(2).own_account_deletable?
846 846 end
847 847 end
848 848
849 849 def test_own_account_deletable_should_be_false_with_unsubscrive_disabled
850 850 with_settings :unsubscribe => '0' do
851 851 assert_equal false, User.find(2).own_account_deletable?
852 852 end
853 853 end
854 854
855 855 def test_own_account_deletable_should_be_false_for_a_single_admin
856 856 User.delete_all(["admin = ? AND id <> ?", true, 1])
857 857
858 858 with_settings :unsubscribe => '1' do
859 859 assert_equal false, User.find(1).own_account_deletable?
860 860 end
861 861 end
862 862
863 863 def test_own_account_deletable_should_be_true_for_an_admin_if_other_admin_exists
864 864 User.generate! do |user|
865 865 user.admin = true
866 866 end
867 867
868 868 with_settings :unsubscribe => '1' do
869 869 assert_equal true, User.find(1).own_account_deletable?
870 870 end
871 871 end
872 872
873 873 context "#allowed_to?" do
874 874 context "with a unique project" do
875 875 should "return false if project is archived" do
876 876 project = Project.find(1)
877 877 Project.any_instance.stubs(:status).returns(Project::STATUS_ARCHIVED)
878 878 assert ! @admin.allowed_to?(:view_issues, Project.find(1))
879 879 end
880 880
881 881 should "return false for write action if project is closed" do
882 882 project = Project.find(1)
883 883 Project.any_instance.stubs(:status).returns(Project::STATUS_CLOSED)
884 884 assert ! @admin.allowed_to?(:edit_project, Project.find(1))
885 885 end
886 886
887 887 should "return true for read action if project is closed" do
888 888 project = Project.find(1)
889 889 Project.any_instance.stubs(:status).returns(Project::STATUS_CLOSED)
890 890 assert @admin.allowed_to?(:view_project, Project.find(1))
891 891 end
892 892
893 893 should "return false if related module is disabled" do
894 894 project = Project.find(1)
895 895 project.enabled_module_names = ["issue_tracking"]
896 896 assert @admin.allowed_to?(:add_issues, project)
897 897 assert ! @admin.allowed_to?(:view_wiki_pages, project)
898 898 end
899 899
900 900 should "authorize nearly everything for admin users" do
901 901 project = Project.find(1)
902 902 assert ! @admin.member_of?(project)
903 903 %w(edit_issues delete_issues manage_news manage_documents manage_wiki).each do |p|
904 904 assert @admin.allowed_to?(p.to_sym, project)
905 905 end
906 906 end
907 907
908 908 should "authorize normal users depending on their roles" do
909 909 project = Project.find(1)
910 910 assert @jsmith.allowed_to?(:delete_messages, project) #Manager
911 911 assert ! @dlopper.allowed_to?(:delete_messages, project) #Developper
912 912 end
913 913 end
914 914
915 915 context "with multiple projects" do
916 916 should "return false if array is empty" do
917 917 assert ! @admin.allowed_to?(:view_project, [])
918 918 end
919 919
920 920 should "return true only if user has permission on all these projects" do
921 921 assert @admin.allowed_to?(:view_project, Project.all)
922 922 assert ! @dlopper.allowed_to?(:view_project, Project.all) #cannot see Project(2)
923 923 assert @jsmith.allowed_to?(:edit_issues, @jsmith.projects) #Manager or Developer everywhere
924 924 assert ! @jsmith.allowed_to?(:delete_issue_watchers, @jsmith.projects) #Dev cannot delete_issue_watchers
925 925 end
926 926
927 927 should "behave correctly with arrays of 1 project" do
928 928 assert ! User.anonymous.allowed_to?(:delete_issues, [Project.first])
929 929 end
930 930 end
931 931
932 932 context "with options[:global]" do
933 933 should "authorize if user has at least one role that has this permission" do
934 934 @dlopper2 = User.find(5) #only Developper on a project, not Manager anywhere
935 935 @anonymous = User.find(6)
936 936 assert @jsmith.allowed_to?(:delete_issue_watchers, nil, :global => true)
937 937 assert ! @dlopper2.allowed_to?(:delete_issue_watchers, nil, :global => true)
938 938 assert @dlopper2.allowed_to?(:add_issues, nil, :global => true)
939 939 assert ! @anonymous.allowed_to?(:add_issues, nil, :global => true)
940 940 assert @anonymous.allowed_to?(:view_issues, nil, :global => true)
941 941 end
942 942 end
943 943 end
944 944
945 945 context "User#notify_about?" do
946 946 context "Issues" do
947 947 setup do
948 948 @project = Project.find(1)
949 949 @author = User.generate!
950 950 @assignee = User.generate!
951 @issue = Issue.generate_for_project!(@project, :assigned_to => @assignee, :author => @author)
951 @issue = Issue.generate!(:project => @project, :assigned_to => @assignee, :author => @author)
952 952 end
953 953
954 954 should "be true for a user with :all" do
955 955 @author.update_attribute(:mail_notification, 'all')
956 956 assert @author.notify_about?(@issue)
957 957 end
958 958
959 959 should "be false for a user with :none" do
960 960 @author.update_attribute(:mail_notification, 'none')
961 961 assert ! @author.notify_about?(@issue)
962 962 end
963 963
964 964 should "be false for a user with :only_my_events and isn't an author, creator, or assignee" do
965 965 @user = User.generate!(:mail_notification => 'only_my_events')
966 966 Member.create!(:user => @user, :project => @project, :role_ids => [1])
967 967 assert ! @user.notify_about?(@issue)
968 968 end
969 969
970 970 should "be true for a user with :only_my_events and is the author" do
971 971 @author.update_attribute(:mail_notification, 'only_my_events')
972 972 assert @author.notify_about?(@issue)
973 973 end
974 974
975 975 should "be true for a user with :only_my_events and is the assignee" do
976 976 @assignee.update_attribute(:mail_notification, 'only_my_events')
977 977 assert @assignee.notify_about?(@issue)
978 978 end
979 979
980 980 should "be true for a user with :only_assigned and is the assignee" do
981 981 @assignee.update_attribute(:mail_notification, 'only_assigned')
982 982 assert @assignee.notify_about?(@issue)
983 983 end
984 984
985 985 should "be false for a user with :only_assigned and is not the assignee" do
986 986 @author.update_attribute(:mail_notification, 'only_assigned')
987 987 assert ! @author.notify_about?(@issue)
988 988 end
989 989
990 990 should "be true for a user with :only_owner and is the author" do
991 991 @author.update_attribute(:mail_notification, 'only_owner')
992 992 assert @author.notify_about?(@issue)
993 993 end
994 994
995 995 should "be false for a user with :only_owner and is not the author" do
996 996 @assignee.update_attribute(:mail_notification, 'only_owner')
997 997 assert ! @assignee.notify_about?(@issue)
998 998 end
999 999
1000 1000 should "be true for a user with :selected and is the author" do
1001 1001 @author.update_attribute(:mail_notification, 'selected')
1002 1002 assert @author.notify_about?(@issue)
1003 1003 end
1004 1004
1005 1005 should "be true for a user with :selected and is the assignee" do
1006 1006 @assignee.update_attribute(:mail_notification, 'selected')
1007 1007 assert @assignee.notify_about?(@issue)
1008 1008 end
1009 1009
1010 1010 should "be false for a user with :selected and is not the author or assignee" do
1011 1011 @user = User.generate!(:mail_notification => 'selected')
1012 1012 Member.create!(:user => @user, :project => @project, :role_ids => [1])
1013 1013 assert ! @user.notify_about?(@issue)
1014 1014 end
1015 1015 end
1016 1016
1017 1017 context "other events" do
1018 1018 should 'be added and tested'
1019 1019 end
1020 1020 end
1021 1021
1022 1022 def test_salt_unsalted_passwords
1023 1023 # Restore a user with an unsalted password
1024 1024 user = User.find(1)
1025 1025 user.salt = nil
1026 1026 user.hashed_password = User.hash_password("unsalted")
1027 1027 user.save!
1028 1028
1029 1029 User.salt_unsalted_passwords!
1030 1030
1031 1031 user.reload
1032 1032 # Salt added
1033 1033 assert !user.salt.blank?
1034 1034 # Password still valid
1035 1035 assert user.check_password?("unsalted")
1036 1036 assert_equal user, User.try_to_login(user.login, "unsalted")
1037 1037 end
1038 1038
1039 1039 if Object.const_defined?(:OpenID)
1040 1040
1041 1041 def test_setting_identity_url
1042 1042 normalized_open_id_url = 'http://example.com/'
1043 1043 u = User.new( :identity_url => 'http://example.com/' )
1044 1044 assert_equal normalized_open_id_url, u.identity_url
1045 1045 end
1046 1046
1047 1047 def test_setting_identity_url_without_trailing_slash
1048 1048 normalized_open_id_url = 'http://example.com/'
1049 1049 u = User.new( :identity_url => 'http://example.com' )
1050 1050 assert_equal normalized_open_id_url, u.identity_url
1051 1051 end
1052 1052
1053 1053 def test_setting_identity_url_without_protocol
1054 1054 normalized_open_id_url = 'http://example.com/'
1055 1055 u = User.new( :identity_url => 'example.com' )
1056 1056 assert_equal normalized_open_id_url, u.identity_url
1057 1057 end
1058 1058
1059 1059 def test_setting_blank_identity_url
1060 1060 u = User.new( :identity_url => 'example.com' )
1061 1061 u.identity_url = ''
1062 1062 assert u.identity_url.blank?
1063 1063 end
1064 1064
1065 1065 def test_setting_invalid_identity_url
1066 1066 u = User.new( :identity_url => 'this is not an openid url' )
1067 1067 assert u.identity_url.blank?
1068 1068 end
1069 1069
1070 1070 else
1071 1071 puts "Skipping openid tests."
1072 1072 end
1073 1073
1074 1074 end
General Comments 0
You need to be logged in to leave comments. Login now