To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.

Statistics Download as Zip
| Branch: | Tag: | Revision:

root / .svn / pristine / 72 / 727874d7bc35b744eb4b647417de27d95212b4b8.svn-base @ 1297:0a574315af3e

History | View | Annotate | Download (41.5 KB)

1
# Redmine - project management software
2
# Copyright (C) 2006-2011  Jean-Philippe Lang
3
#
4
# This program is free software; you can redistribute it and/or
5
# modify it under the terms of the GNU General Public License
6
# as published by the Free Software Foundation; either version 2
7
# of the License, or (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17

    
18
require File.expand_path('../../test_helper', __FILE__)
19

    
20
class IssueTest < ActiveSupport::TestCase
21
  fixtures :projects, :users, :members, :member_roles, :roles,
22
           :trackers, :projects_trackers,
23
           :enabled_modules,
24
           :versions,
25
           :issue_statuses, :issue_categories, :issue_relations, :workflows,
26
           :enumerations,
27
           :issues,
28
           :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values,
29
           :time_entries
30

    
31
  def test_create
32
    issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
33
                      :status_id => 1, :priority => IssuePriority.all.first,
34
                      :subject => 'test_create',
35
                      :description => 'IssueTest#test_create', :estimated_hours => '1:30')
36
    assert issue.save
37
    issue.reload
38
    assert_equal 1.5, issue.estimated_hours
39
  end
40

    
41
  def test_create_minimal
42
    issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
43
                      :status_id => 1, :priority => IssuePriority.all.first,
44
                      :subject => 'test_create')
45
    assert issue.save
46
    assert issue.description.nil?
47
  end
48

    
49
  def test_create_with_required_custom_field
50
    field = IssueCustomField.find_by_name('Database')
51
    field.update_attribute(:is_required, true)
52

    
53
    issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
54
                      :status_id => 1, :subject => 'test_create',
55
                      :description => 'IssueTest#test_create_with_required_custom_field')
56
    assert issue.available_custom_fields.include?(field)
57
    # No value for the custom field
58
    assert !issue.save
59
    assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
60
    # Blank value
61
    issue.custom_field_values = { field.id => '' }
62
    assert !issue.save
63
    assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
64
    # Invalid value
65
    issue.custom_field_values = { field.id => 'SQLServer' }
66
    assert !issue.save
67
    assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
68
    # Valid value
69
    issue.custom_field_values = { field.id => 'PostgreSQL' }
70
    assert issue.save
71
    issue.reload
72
    assert_equal 'PostgreSQL', issue.custom_value_for(field).value
73
  end
74

    
75
  def test_create_with_group_assignment
76
    with_settings :issue_group_assignment => '1' do
77
      assert Issue.new(:project_id => 2, :tracker_id => 1, :author_id => 1,
78
                       :subject => 'Group assignment',
79
                       :assigned_to_id => 11).save
80
      issue = Issue.first(:order => 'id DESC')
81
      assert_kind_of Group, issue.assigned_to
82
      assert_equal Group.find(11), issue.assigned_to
83
    end
84
  end
85

    
86
  def assert_visibility_match(user, issues)
87
    assert_equal issues.collect(&:id).sort, Issue.all.select {|issue| issue.visible?(user)}.collect(&:id).sort
88
  end
89

    
90
  def test_visible_scope_for_anonymous
91
    # Anonymous user should see issues of public projects only
92
    issues = Issue.visible(User.anonymous).all
93
    assert issues.any?
94
    assert_nil issues.detect {|issue| !issue.project.is_public?}
95
    assert_nil issues.detect {|issue| issue.is_private?}
96
    assert_visibility_match User.anonymous, issues
97
  end
98

    
99
  def test_visible_scope_for_anonymous_with_own_issues_visibility
100
    Role.anonymous.update_attribute :issues_visibility, 'own'
101
    Issue.create!(:project_id => 1, :tracker_id => 1,
102
                  :author_id => User.anonymous.id,
103
                  :subject => 'Issue by anonymous')
104

    
105
    issues = Issue.visible(User.anonymous).all
106
    assert issues.any?
107
    assert_nil issues.detect {|issue| issue.author != User.anonymous}
108
    assert_visibility_match User.anonymous, issues
109
  end
110

    
111
  def test_visible_scope_for_anonymous_without_view_issues_permissions
112
    # Anonymous user should not see issues without permission
113
    Role.anonymous.remove_permission!(:view_issues)
114
    issues = Issue.visible(User.anonymous).all
115
    assert issues.empty?
116
    assert_visibility_match User.anonymous, issues
117
  end
118

    
119
  def test_visible_scope_for_non_member
120
    user = User.find(9)
121
    assert user.projects.empty?
122
    # Non member user should see issues of public projects only
123
    issues = Issue.visible(user).all
124
    assert issues.any?
125
    assert_nil issues.detect {|issue| !issue.project.is_public?}
126
    assert_nil issues.detect {|issue| issue.is_private?}
127
    assert_visibility_match user, issues
128
  end
129

    
130
  def test_visible_scope_for_non_member_with_own_issues_visibility
131
    Role.non_member.update_attribute :issues_visibility, 'own'
132
    Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 9, :subject => 'Issue by non member')
133
    user = User.find(9)
134

    
135
    issues = Issue.visible(user).all
136
    assert issues.any?
137
    assert_nil issues.detect {|issue| issue.author != user}
138
    assert_visibility_match user, issues
139
  end
140

    
141
  def test_visible_scope_for_non_member_without_view_issues_permissions
142
    # Non member user should not see issues without permission
143
    Role.non_member.remove_permission!(:view_issues)
144
    user = User.find(9)
145
    assert user.projects.empty?
146
    issues = Issue.visible(user).all
147
    assert issues.empty?
148
    assert_visibility_match user, issues
149
  end
150

    
151
  def test_visible_scope_for_member
152
    user = User.find(9)
153
    # User should see issues of projects for which he has view_issues permissions only
154
    Role.non_member.remove_permission!(:view_issues)
155
    Member.create!(:principal => user, :project_id => 3, :role_ids => [2])
156
    issues = Issue.visible(user).all
157
    assert issues.any?
158
    assert_nil issues.detect {|issue| issue.project_id != 3}
159
    assert_nil issues.detect {|issue| issue.is_private?}
160
    assert_visibility_match user, issues
161
  end
162

    
163
  def test_visible_scope_for_member_with_groups_should_return_assigned_issues
164
    user = User.find(8)
165
    assert user.groups.any?
166
    Member.create!(:principal => user.groups.first, :project_id => 1, :role_ids => [2])
167
    Role.non_member.remove_permission!(:view_issues)
168
    
169
    issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3,
170
      :status_id => 1, :priority => IssuePriority.all.first,
171
      :subject => 'Assignment test',
172
      :assigned_to => user.groups.first,
173
      :is_private => true)
174
    
175
    Role.find(2).update_attribute :issues_visibility, 'default'
176
    issues = Issue.visible(User.find(8)).all
177
    assert issues.any?
178
    assert issues.include?(issue)
179
    
180
    Role.find(2).update_attribute :issues_visibility, 'own'
181
    issues = Issue.visible(User.find(8)).all
182
    assert issues.any?
183
    assert issues.include?(issue)
184
  end
185

    
186
  def test_visible_scope_for_admin
187
    user = User.find(1)
188
    user.members.each(&:destroy)
189
    assert user.projects.empty?
190
    issues = Issue.visible(user).all
191
    assert issues.any?
192
    # Admin should see issues on private projects that he does not belong to
193
    assert issues.detect {|issue| !issue.project.is_public?}
194
    # Admin should see private issues of other users
195
    assert issues.detect {|issue| issue.is_private? && issue.author != user}
196
    assert_visibility_match user, issues
197
  end
198

    
199
  def test_visible_scope_with_project
200
    project = Project.find(1)
201
    issues = Issue.visible(User.find(2), :project => project).all
202
    projects = issues.collect(&:project).uniq
203
    assert_equal 1, projects.size
204
    assert_equal project, projects.first
205
  end
206

    
207
  def test_visible_scope_with_project_and_subprojects
208
    project = Project.find(1)
209
    issues = Issue.visible(User.find(2), :project => project, :with_subprojects => true).all
210
    projects = issues.collect(&:project).uniq
211
    assert projects.size > 1
212
    assert_equal [], projects.select {|p| !p.is_or_is_descendant_of?(project)}
213
  end
214

    
215
  def test_visible_and_nested_set_scopes
216
    assert_equal 0, Issue.find(1).descendants.visible.all.size
217
  end
218

    
219
  def test_errors_full_messages_should_include_custom_fields_errors
220
    field = IssueCustomField.find_by_name('Database')
221

    
222
    issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
223
                      :status_id => 1, :subject => 'test_create',
224
                      :description => 'IssueTest#test_create_with_required_custom_field')
225
    assert issue.available_custom_fields.include?(field)
226
    # Invalid value
227
    issue.custom_field_values = { field.id => 'SQLServer' }
228

    
229
    assert !issue.valid?
230
    assert_equal 1, issue.errors.full_messages.size
231
    assert_equal "Database #{I18n.translate('activerecord.errors.messages.inclusion')}",
232
                 issue.errors.full_messages.first
233
  end
234

    
235
  def test_update_issue_with_required_custom_field
236
    field = IssueCustomField.find_by_name('Database')
237
    field.update_attribute(:is_required, true)
238

    
239
    issue = Issue.find(1)
240
    assert_nil issue.custom_value_for(field)
241
    assert issue.available_custom_fields.include?(field)
242
    # No change to custom values, issue can be saved
243
    assert issue.save
244
    # Blank value
245
    issue.custom_field_values = { field.id => '' }
246
    assert !issue.save
247
    # Valid value
248
    issue.custom_field_values = { field.id => 'PostgreSQL' }
249
    assert issue.save
250
    issue.reload
251
    assert_equal 'PostgreSQL', issue.custom_value_for(field).value
252
  end
253

    
254
  def test_should_not_update_attributes_if_custom_fields_validation_fails
255
    issue = Issue.find(1)
256
    field = IssueCustomField.find_by_name('Database')
257
    assert issue.available_custom_fields.include?(field)
258

    
259
    issue.custom_field_values = { field.id => 'Invalid' }
260
    issue.subject = 'Should be not be saved'
261
    assert !issue.save
262

    
263
    issue.reload
264
    assert_equal "Can't print recipes", issue.subject
265
  end
266

    
267
  def test_should_not_recreate_custom_values_objects_on_update
268
    field = IssueCustomField.find_by_name('Database')
269

    
270
    issue = Issue.find(1)
271
    issue.custom_field_values = { field.id => 'PostgreSQL' }
272
    assert issue.save
273
    custom_value = issue.custom_value_for(field)
274
    issue.reload
275
    issue.custom_field_values = { field.id => 'MySQL' }
276
    assert issue.save
277
    issue.reload
278
    assert_equal custom_value.id, issue.custom_value_for(field).id
279
  end
280

    
281
  def test_should_not_update_custom_fields_on_changing_tracker_with_different_custom_fields
282
    issue = Issue.new(:project_id => 1)
283
    issue.attributes = {:tracker_id => 1, :author_id => 1, :status_id => 1, :subject => 'Test', :custom_field_values => {'2' => 'Test'}}
284
    issue.save!
285

    
286
    assert !Tracker.find(2).custom_field_ids.include?(2)
287

    
288
    issue = Issue.find(issue.id)
289
    issue.attributes = {:tracker_id => 2, :custom_field_values => {'1' => ''}}
290

    
291
    issue = Issue.find(issue.id)
292
    custom_value = issue.custom_value_for(2)
293
    assert_not_nil custom_value
294
    assert_equal 'Test', custom_value.value
295
  end
296

    
297
  def test_assigning_tracker_id_should_reload_custom_fields_values
298
    issue = Issue.new(:project => Project.find(1))
299
    assert issue.custom_field_values.empty?
300
    issue.tracker_id = 1
301
    assert issue.custom_field_values.any?
302
  end
303

    
304
  def test_assigning_attributes_should_assign_tracker_id_first
305
    attributes = ActiveSupport::OrderedHash.new
306
    attributes['custom_field_values'] = { '1' => 'MySQL' }
307
    attributes['tracker_id'] = '1'
308
    issue = Issue.new(:project => Project.find(1))
309
    issue.attributes = attributes
310
    assert_not_nil issue.custom_value_for(1)
311
    assert_equal 'MySQL', issue.custom_value_for(1).value
312
  end
313

    
314
  def test_should_update_issue_with_disabled_tracker
315
    p = Project.find(1)
316
    issue = Issue.find(1)
317

    
318
    p.trackers.delete(issue.tracker)
319
    assert !p.trackers.include?(issue.tracker)
320

    
321
    issue.reload
322
    issue.subject = 'New subject'
323
    assert issue.save
324
  end
325

    
326
  def test_should_not_set_a_disabled_tracker
327
    p = Project.find(1)
328
    p.trackers.delete(Tracker.find(2))
329

    
330
    issue = Issue.find(1)
331
    issue.tracker_id = 2
332
    issue.subject = 'New subject'
333
    assert !issue.save
334
    assert_not_nil issue.errors[:tracker_id]
335
  end
336

    
337
  def test_category_based_assignment
338
    issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3,
339
                         :status_id => 1, :priority => IssuePriority.all.first,
340
                         :subject => 'Assignment test',
341
                         :description => 'Assignment test', :category_id => 1)
342
    assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
343
  end
344

    
345
  def test_new_statuses_allowed_to
346
    Workflow.delete_all
347

    
348
    Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2, :author => false, :assignee => false)
349
    Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3, :author => true, :assignee => false)
350
    Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4, :author => false, :assignee => true)
351
    Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5, :author => true, :assignee => true)
352
    status = IssueStatus.find(1)
353
    role = Role.find(1)
354
    tracker = Tracker.find(1)
355
    user = User.find(2)
356

    
357
    issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1)
358
    assert_equal [1, 2], issue.new_statuses_allowed_to(user).map(&:id)
359

    
360
    issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :author => user)
361
    assert_equal [1, 2, 3, 5], issue.new_statuses_allowed_to(user).map(&:id)
362

    
363
    issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :assigned_to => user)
364
    assert_equal [1, 2, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
365

    
366
    issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :author => user, :assigned_to => user)
367
    assert_equal [1, 2, 3, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
368
  end
369

    
370
  def test_copy
371
    issue = Issue.new.copy_from(1)
372
    assert issue.save
373
    issue.reload
374
    orig = Issue.find(1)
375
    assert_equal orig.subject, issue.subject
376
    assert_equal orig.tracker, issue.tracker
377
    assert_equal "125", issue.custom_value_for(2).value
378
  end
379

    
380
  def test_copy_should_copy_status
381
    orig = Issue.find(8)
382
    assert orig.status != IssueStatus.default
383

    
384
    issue = Issue.new.copy_from(orig)
385
    assert issue.save
386
    issue.reload
387
    assert_equal orig.status, issue.status
388
  end
389

    
390
  def test_should_close_duplicates
391
    # Create 3 issues
392
    issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
393
                       :status_id => 1, :priority => IssuePriority.all.first,
394
                       :subject => 'Duplicates test', :description => 'Duplicates test')
395
    assert issue1.save
396
    issue2 = issue1.clone
397
    assert issue2.save
398
    issue3 = issue1.clone
399
    assert issue3.save
400

    
401
    # 2 is a dupe of 1
402
    IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
403
    # And 3 is a dupe of 2
404
    IssueRelation.create(:issue_from => issue3, :issue_to => issue2, :relation_type => IssueRelation::TYPE_DUPLICATES)
405
    # And 3 is a dupe of 1 (circular duplicates)
406
    IssueRelation.create(:issue_from => issue3, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
407

    
408
    assert issue1.reload.duplicates.include?(issue2)
409

    
410
    # Closing issue 1
411
    issue1.init_journal(User.find(:first), "Closing issue1")
412
    issue1.status = IssueStatus.find :first, :conditions => {:is_closed => true}
413
    assert issue1.save
414
    # 2 and 3 should be also closed
415
    assert issue2.reload.closed?
416
    assert issue3.reload.closed?
417
  end
418

    
419
  def test_should_not_close_duplicated_issue
420
    # Create 3 issues
421
    issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
422
                       :status_id => 1, :priority => IssuePriority.all.first,
423
                       :subject => 'Duplicates test', :description => 'Duplicates test')
424
    assert issue1.save
425
    issue2 = issue1.clone
426
    assert issue2.save
427

    
428
    # 2 is a dupe of 1
429
    IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
430
    # 2 is a dup of 1 but 1 is not a duplicate of 2
431
    assert !issue2.reload.duplicates.include?(issue1)
432

    
433
    # Closing issue 2
434
    issue2.init_journal(User.find(:first), "Closing issue2")
435
    issue2.status = IssueStatus.find :first, :conditions => {:is_closed => true}
436
    assert issue2.save
437
    # 1 should not be also closed
438
    assert !issue1.reload.closed?
439
  end
440

    
441
  def test_assignable_versions
442
    issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue')
443
    assert_equal ['open'], issue.assignable_versions.collect(&:status).uniq
444
  end
445

    
446
  def test_should_not_be_able_to_assign_a_new_issue_to_a_closed_version
447
    issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue')
448
    assert !issue.save
449
    assert_not_nil issue.errors[:fixed_version_id]
450
  end
451

    
452
  def test_should_not_be_able_to_assign_a_new_issue_to_a_locked_version
453
    issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 2, :subject => 'New issue')
454
    assert !issue.save
455
    assert_not_nil issue.errors[:fixed_version_id]
456
  end
457

    
458
  def test_should_be_able_to_assign_a_new_issue_to_an_open_version
459
    issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 3, :subject => 'New issue')
460
    assert issue.save
461
  end
462

    
463
  def test_should_be_able_to_update_an_issue_assigned_to_a_closed_version
464
    issue = Issue.find(11)
465
    assert_equal 'closed', issue.fixed_version.status
466
    issue.subject = 'Subject changed'
467
    assert issue.save
468
  end
469

    
470
  def test_should_not_be_able_to_reopen_an_issue_assigned_to_a_closed_version
471
    issue = Issue.find(11)
472
    issue.status_id = 1
473
    assert !issue.save
474
    assert_not_nil issue.errors[:base]
475
  end
476

    
477
  def test_should_be_able_to_reopen_and_reassign_an_issue_assigned_to_a_closed_version
478
    issue = Issue.find(11)
479
    issue.status_id = 1
480
    issue.fixed_version_id = 3
481
    assert issue.save
482
  end
483

    
484
  def test_should_be_able_to_reopen_an_issue_assigned_to_a_locked_version
485
    issue = Issue.find(12)
486
    assert_equal 'locked', issue.fixed_version.status
487
    issue.status_id = 1
488
    assert issue.save
489
  end
490

    
491
  def test_move_to_another_project_with_same_category
492
    issue = Issue.find(1)
493
    assert issue.move_to_project(Project.find(2))
494
    issue.reload
495
    assert_equal 2, issue.project_id
496
    # Category changes
497
    assert_equal 4, issue.category_id
498
    # Make sure time entries were move to the target project
499
    assert_equal 2, issue.time_entries.first.project_id
500
  end
501

    
502
  def test_move_to_another_project_without_same_category
503
    issue = Issue.find(2)
504
    assert issue.move_to_project(Project.find(2))
505
    issue.reload
506
    assert_equal 2, issue.project_id
507
    # Category cleared
508
    assert_nil issue.category_id
509
  end
510

    
511
  def test_move_to_another_project_should_clear_fixed_version_when_not_shared
512
    issue = Issue.find(1)
513
    issue.update_attribute(:fixed_version_id, 1)
514
    assert issue.move_to_project(Project.find(2))
515
    issue.reload
516
    assert_equal 2, issue.project_id
517
    # Cleared fixed_version
518
    assert_equal nil, issue.fixed_version
519
  end
520

    
521
  def test_move_to_another_project_should_keep_fixed_version_when_shared_with_the_target_project
522
    issue = Issue.find(1)
523
    issue.update_attribute(:fixed_version_id, 4)
524
    assert issue.move_to_project(Project.find(5))
525
    issue.reload
526
    assert_equal 5, issue.project_id
527
    # Keep fixed_version
528
    assert_equal 4, issue.fixed_version_id
529
  end
530

    
531
  def test_move_to_another_project_should_clear_fixed_version_when_not_shared_with_the_target_project
532
    issue = Issue.find(1)
533
    issue.update_attribute(:fixed_version_id, 1)
534
    assert issue.move_to_project(Project.find(5))
535
    issue.reload
536
    assert_equal 5, issue.project_id
537
    # Cleared fixed_version
538
    assert_equal nil, issue.fixed_version
539
  end
540

    
541
  def test_move_to_another_project_should_keep_fixed_version_when_shared_systemwide
542
    issue = Issue.find(1)
543
    issue.update_attribute(:fixed_version_id, 7)
544
    assert issue.move_to_project(Project.find(2))
545
    issue.reload
546
    assert_equal 2, issue.project_id
547
    # Keep fixed_version
548
    assert_equal 7, issue.fixed_version_id
549
  end
550

    
551
  def test_move_to_another_project_with_disabled_tracker
552
    issue = Issue.find(1)
553
    target = Project.find(2)
554
    target.tracker_ids = [3]
555
    target.save
556
    assert_equal false, issue.move_to_project(target)
557
    issue.reload
558
    assert_equal 1, issue.project_id
559
  end
560

    
561
  def test_copy_to_the_same_project
562
    issue = Issue.find(1)
563
    copy = nil
564
    assert_difference 'Issue.count' do
565
      copy = issue.move_to_project(issue.project, nil, :copy => true)
566
    end
567
    assert_kind_of Issue, copy
568
    assert_equal issue.project, copy.project
569
    assert_equal "125", copy.custom_value_for(2).value
570
  end
571

    
572
  def test_copy_to_another_project_and_tracker
573
    issue = Issue.find(1)
574
    copy = nil
575
    assert_difference 'Issue.count' do
576
      copy = issue.move_to_project(Project.find(3), Tracker.find(2), :copy => true)
577
    end
578
    copy.reload
579
    assert_kind_of Issue, copy
580
    assert_equal Project.find(3), copy.project
581
    assert_equal Tracker.find(2), copy.tracker
582
    # Custom field #2 is not associated with target tracker
583
    assert_nil copy.custom_value_for(2)
584
  end
585

    
586
  context "#move_to_project" do
587
    context "as a copy" do
588
      setup do
589
        @issue = Issue.find(1)
590
        @copy = nil
591
      end
592

    
593
      should "not create a journal" do
594
        @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:assigned_to_id => 3}})
595
        assert_equal 0, @copy.reload.journals.size
596
      end
597

    
598
      should "allow assigned_to changes" do
599
        @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:assigned_to_id => 3}})
600
        assert_equal 3, @copy.assigned_to_id
601
      end
602

    
603
      should "allow status changes" do
604
        @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:status_id => 2}})
605
        assert_equal 2, @copy.status_id
606
      end
607

    
608
      should "allow start date changes" do
609
        date = Date.today
610
        @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:start_date => date}})
611
        assert_equal date, @copy.start_date
612
      end
613

    
614
      should "allow due date changes" do
615
        date = Date.today
616
        @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:due_date => date}})
617

    
618
        assert_equal date, @copy.due_date
619
      end
620

    
621
      should "set current user as author" do
622
        User.current = User.find(9)
623
        @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {}})
624

    
625
        assert_equal User.current, @copy.author
626
      end
627

    
628
      should "keep journal notes" do
629
        date = Date.today
630
        notes = "Notes added when copying"
631
        User.current = User.find(9)
632
        @issue.init_journal(User.current, notes)
633
        @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:start_date => date}})
634

    
635
        assert_equal 1, @copy.journals.size
636
        journal = @copy.journals.first
637
        assert_equal 0, journal.details.size
638
        assert_equal notes, journal.notes
639
      end
640
    end
641
  end
642

    
643
  def test_recipients_should_not_include_users_that_cannot_view_the_issue
644
    issue = Issue.find(12)
645
    assert issue.recipients.include?(issue.author.mail)
646
    # move the issue to a private project
647
    copy  = issue.move_to_project(Project.find(5), Tracker.find(2), :copy => true)
648
    # author is not a member of project anymore
649
    assert !copy.recipients.include?(copy.author.mail)
650
  end
651

    
652
  def test_recipients_should_include_the_assigned_group_members
653
    group_member = User.generate_with_protected!
654
    group = Group.generate!
655
    group.users << group_member
656

    
657
    issue = Issue.find(12)
658
    issue.assigned_to = group
659
    assert issue.recipients.include?(group_member.mail)
660
  end
661

    
662
  def test_watcher_recipients_should_not_include_users_that_cannot_view_the_issue
663
    user = User.find(3)
664
    issue = Issue.find(9)
665
    Watcher.create!(:user => user, :watchable => issue)
666
    assert issue.watched_by?(user)
667
    assert !issue.watcher_recipients.include?(user.mail)
668
  end
669

    
670
  def test_issue_destroy
671
    Issue.find(1).destroy
672
    assert_nil Issue.find_by_id(1)
673
    assert_nil TimeEntry.find_by_issue_id(1)
674
  end
675

    
676
  def test_blocked
677
    blocked_issue = Issue.find(9)
678
    blocking_issue = Issue.find(10)
679

    
680
    assert blocked_issue.blocked?
681
    assert !blocking_issue.blocked?
682
  end
683

    
684
  def test_blocked_issues_dont_allow_closed_statuses
685
    blocked_issue = Issue.find(9)
686

    
687
    allowed_statuses = blocked_issue.new_statuses_allowed_to(users(:users_002))
688
    assert !allowed_statuses.empty?
689
    closed_statuses = allowed_statuses.select {|st| st.is_closed?}
690
    assert closed_statuses.empty?
691
  end
692

    
693
  def test_unblocked_issues_allow_closed_statuses
694
    blocking_issue = Issue.find(10)
695

    
696
    allowed_statuses = blocking_issue.new_statuses_allowed_to(users(:users_002))
697
    assert !allowed_statuses.empty?
698
    closed_statuses = allowed_statuses.select {|st| st.is_closed?}
699
    assert !closed_statuses.empty?
700
  end
701

    
702
  def test_rescheduling_an_issue_should_reschedule_following_issue
703
    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)
704
    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)
705
    IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
706
    assert_equal issue1.due_date + 1, issue2.reload.start_date
707

    
708
    issue1.due_date = Date.today + 5
709
    issue1.save!
710
    assert_equal issue1.due_date + 1, issue2.reload.start_date
711
  end
712

    
713
  def test_overdue
714
    assert Issue.new(:due_date => 1.day.ago.to_date).overdue?
715
    assert !Issue.new(:due_date => Date.today).overdue?
716
    assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue?
717
    assert !Issue.new(:due_date => nil).overdue?
718
    assert !Issue.new(:due_date => 1.day.ago.to_date, :status => IssueStatus.find(:first, :conditions => {:is_closed => true})).overdue?
719
  end
720

    
721
  context "#behind_schedule?" do
722
    should "be false if the issue has no start_date" do
723
      assert !Issue.new(:start_date => nil, :due_date => 1.day.from_now.to_date, :done_ratio => 0).behind_schedule?
724
    end
725

    
726
    should "be false if the issue has no end_date" do
727
      assert !Issue.new(:start_date => 1.day.from_now.to_date, :due_date => nil, :done_ratio => 0).behind_schedule?
728
    end
729

    
730
    should "be false if the issue has more done than it's calendar time" do
731
      assert !Issue.new(:start_date => 50.days.ago.to_date, :due_date => 50.days.from_now.to_date, :done_ratio => 90).behind_schedule?
732
    end
733

    
734
    should "be true if the issue hasn't been started at all" do
735
      assert Issue.new(:start_date => 1.day.ago.to_date, :due_date => 1.day.from_now.to_date, :done_ratio => 0).behind_schedule?
736
    end
737

    
738
    should "be true if the issue has used more calendar time than it's done ratio" do
739
      assert Issue.new(:start_date => 100.days.ago.to_date, :due_date => Date.today, :done_ratio => 90).behind_schedule?
740
    end
741
  end
742

    
743
  context "#assignable_users" do
744
    should "be Users" do
745
      assert_kind_of User, Issue.find(1).assignable_users.first
746
    end
747

    
748
    should "include the issue author" do
749
      project = Project.find(1)
750
      non_project_member = User.generate!
751
      issue = Issue.generate_for_project!(project, :author => non_project_member)
752

    
753
      assert issue.assignable_users.include?(non_project_member)
754
    end
755

    
756
    should "include the current assignee" do
757
      project = Project.find(1)
758
      user = User.generate!
759
      issue = Issue.generate_for_project!(project, :assigned_to => user)
760
      user.lock!
761

    
762
      assert Issue.find(issue.id).assignable_users.include?(user)
763
    end
764

    
765
    should "not show the issue author twice" do
766
      assignable_user_ids = Issue.find(1).assignable_users.collect(&:id)
767
      assert_equal 2, assignable_user_ids.length
768

    
769
      assignable_user_ids.each do |user_id|
770
        assert_equal 1, assignable_user_ids.select {|i| i == user_id}.length, "User #{user_id} appears more or less than once"
771
      end
772
    end
773

    
774
    context "with issue_group_assignment" do
775
      should "include groups" do
776
        issue = Issue.new(:project => Project.find(2))
777

    
778
        with_settings :issue_group_assignment => '1' do
779
          assert_equal %w(Group User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
780
          assert issue.assignable_users.include?(Group.find(11))
781
        end
782
      end
783
    end
784

    
785
    context "without issue_group_assignment" do
786
      should "not include groups" do
787
        issue = Issue.new(:project => Project.find(2))
788

    
789
        with_settings :issue_group_assignment => '0' do
790
          assert_equal %w(User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
791
          assert !issue.assignable_users.include?(Group.find(11))
792
        end
793
      end
794
    end
795
  end
796

    
797
  def test_create_should_send_email_notification
798
    ActionMailer::Base.deliveries.clear
799
    issue = Issue.new(:project_id => 1, :tracker_id => 1,
800
                      :author_id => 3, :status_id => 1,
801
                      :priority => IssuePriority.all.first,
802
                      :subject => 'test_create', :estimated_hours => '1:30')
803

    
804
    assert issue.save
805
    assert_equal 1, ActionMailer::Base.deliveries.size
806
  end
807

    
808
  def test_stale_issue_should_not_send_email_notification
809
    ActionMailer::Base.deliveries.clear
810
    issue = Issue.find(1)
811
    stale = Issue.find(1)
812

    
813
    issue.init_journal(User.find(1))
814
    issue.subject = 'Subjet update'
815
    assert issue.save
816
    assert_equal 1, ActionMailer::Base.deliveries.size
817
    ActionMailer::Base.deliveries.clear
818

    
819
    stale.init_journal(User.find(1))
820
    stale.subject = 'Another subjet update'
821
    assert_raise ActiveRecord::StaleObjectError do
822
      stale.save
823
    end
824
    assert ActionMailer::Base.deliveries.empty?
825
  end
826

    
827
  def test_journalized_description
828
    IssueCustomField.delete_all
829

    
830
    i = Issue.first
831
    old_description = i.description
832
    new_description = "This is the new description"
833

    
834
    i.init_journal(User.find(2))
835
    i.description = new_description
836
    assert_difference 'Journal.count', 1 do
837
      assert_difference 'JournalDetail.count', 1 do
838
        i.save!
839
      end
840
    end
841

    
842
    detail = JournalDetail.first(:order => 'id DESC')
843
    assert_equal i, detail.journal.journalized
844
    assert_equal 'attr', detail.property
845
    assert_equal 'description', detail.prop_key
846
    assert_equal old_description, detail.old_value
847
    assert_equal new_description, detail.value
848
  end
849

    
850
  def test_blank_descriptions_should_not_be_journalized
851
    IssueCustomField.delete_all
852
    Issue.update_all("description = NULL", "id=1")
853

    
854
    i = Issue.find(1)
855
    i.init_journal(User.find(2))
856
    i.subject = "blank description"
857
    i.description = "\r\n"
858

    
859
    assert_difference 'Journal.count', 1 do
860
      assert_difference 'JournalDetail.count', 1 do
861
        i.save!
862
      end
863
    end
864
  end
865

    
866
  def test_description_eol_should_be_normalized
867
    i = Issue.new(:description => "CR \r LF \n CRLF \r\n")
868
    assert_equal "CR \r\n LF \r\n CRLF \r\n", i.description
869
  end
870

    
871
  def test_saving_twice_should_not_duplicate_journal_details
872
    i = Issue.find(:first)
873
    i.init_journal(User.find(2), 'Some notes')
874
    # initial changes
875
    i.subject = 'New subject'
876
    i.done_ratio = i.done_ratio + 10
877
    assert_difference 'Journal.count' do
878
      assert i.save
879
    end
880
    # 1 more change
881
    i.priority = IssuePriority.find(:first, :conditions => ["id <> ?", i.priority_id])
882
    assert_no_difference 'Journal.count' do
883
      assert_difference 'JournalDetail.count', 1 do
884
        i.save
885
      end
886
    end
887
    # no more change
888
    assert_no_difference 'Journal.count' do
889
      assert_no_difference 'JournalDetail.count' do
890
        i.save
891
      end
892
    end
893
  end
894

    
895
  def test_all_dependent_issues
896
    IssueRelation.delete_all
897
    assert IssueRelation.create!(:issue_from => Issue.find(1),
898
                                 :issue_to   => Issue.find(2),
899
                                 :relation_type => IssueRelation::TYPE_PRECEDES)
900
    assert IssueRelation.create!(:issue_from => Issue.find(2),
901
                                 :issue_to   => Issue.find(3),
902
                                 :relation_type => IssueRelation::TYPE_PRECEDES)
903
    assert IssueRelation.create!(:issue_from => Issue.find(3),
904
                                 :issue_to   => Issue.find(8),
905
                                 :relation_type => IssueRelation::TYPE_PRECEDES)
906

    
907
    assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
908
  end
909

    
910
  def test_all_dependent_issues_with_persistent_circular_dependency
911
    IssueRelation.delete_all
912
    assert IssueRelation.create!(:issue_from => Issue.find(1),
913
                                 :issue_to   => Issue.find(2),
914
                                 :relation_type => IssueRelation::TYPE_PRECEDES)
915
    assert IssueRelation.create!(:issue_from => Issue.find(2),
916
                                 :issue_to   => Issue.find(3),
917
                                 :relation_type => IssueRelation::TYPE_PRECEDES)
918
    # Validation skipping
919
    assert IssueRelation.new(:issue_from => Issue.find(3),
920
                             :issue_to   => Issue.find(1),
921
                             :relation_type => IssueRelation::TYPE_PRECEDES).save(false)
922

    
923
    assert_equal [2, 3], Issue.find(1).all_dependent_issues.collect(&:id).sort
924
  end
925

    
926
  def test_all_dependent_issues_with_persistent_multiple_circular_dependencies
927
    IssueRelation.delete_all
928
    assert IssueRelation.create!(:issue_from => Issue.find(1),
929
                                 :issue_to   => Issue.find(2),
930
                                 :relation_type => IssueRelation::TYPE_RELATES)
931
    assert IssueRelation.create!(:issue_from => Issue.find(2),
932
                                 :issue_to   => Issue.find(3),
933
                                 :relation_type => IssueRelation::TYPE_RELATES)
934
    assert IssueRelation.create!(:issue_from => Issue.find(3),
935
                                 :issue_to   => Issue.find(8),
936
                                 :relation_type => IssueRelation::TYPE_RELATES)
937
    # Validation skipping
938
    assert IssueRelation.new(:issue_from => Issue.find(8),
939
                             :issue_to   => Issue.find(2),
940
                             :relation_type => IssueRelation::TYPE_RELATES).save(false)
941
    assert IssueRelation.new(:issue_from => Issue.find(3),
942
                             :issue_to   => Issue.find(1),
943
                             :relation_type => IssueRelation::TYPE_RELATES).save(false)
944

    
945
    assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
946
  end
947

    
948
  context "#done_ratio" do
949
    setup do
950
      @issue = Issue.find(1)
951
      @issue_status = IssueStatus.find(1)
952
      @issue_status.update_attribute(:default_done_ratio, 50)
953
      @issue2 = Issue.find(2)
954
      @issue_status2 = IssueStatus.find(2)
955
      @issue_status2.update_attribute(:default_done_ratio, 0)
956
    end
957

    
958
    context "with Setting.issue_done_ratio using the issue_field" do
959
      setup do
960
        Setting.issue_done_ratio = 'issue_field'
961
      end
962

    
963
      should "read the issue's field" do
964
        assert_equal 0, @issue.done_ratio
965
        assert_equal 30, @issue2.done_ratio
966
      end
967
    end
968

    
969
    context "with Setting.issue_done_ratio using the issue_status" do
970
      setup do
971
        Setting.issue_done_ratio = 'issue_status'
972
      end
973

    
974
      should "read the Issue Status's default done ratio" do
975
        assert_equal 50, @issue.done_ratio
976
        assert_equal 0, @issue2.done_ratio
977
      end
978
    end
979
  end
980

    
981
  context "#update_done_ratio_from_issue_status" do
982
    setup do
983
      @issue = Issue.find(1)
984
      @issue_status = IssueStatus.find(1)
985
      @issue_status.update_attribute(:default_done_ratio, 50)
986
      @issue2 = Issue.find(2)
987
      @issue_status2 = IssueStatus.find(2)
988
      @issue_status2.update_attribute(:default_done_ratio, 0)
989
    end
990

    
991
    context "with Setting.issue_done_ratio using the issue_field" do
992
      setup do
993
        Setting.issue_done_ratio = 'issue_field'
994
      end
995

    
996
      should "not change the issue" do
997
        @issue.update_done_ratio_from_issue_status
998
        @issue2.update_done_ratio_from_issue_status
999

    
1000
        assert_equal 0, @issue.read_attribute(:done_ratio)
1001
        assert_equal 30, @issue2.read_attribute(:done_ratio)
1002
      end
1003
    end
1004

    
1005
    context "with Setting.issue_done_ratio using the issue_status" do
1006
      setup do
1007
        Setting.issue_done_ratio = 'issue_status'
1008
      end
1009

    
1010
      should "change the issue's done ratio" do
1011
        @issue.update_done_ratio_from_issue_status
1012
        @issue2.update_done_ratio_from_issue_status
1013

    
1014
        assert_equal 50, @issue.read_attribute(:done_ratio)
1015
        assert_equal 0, @issue2.read_attribute(:done_ratio)
1016
      end
1017
    end
1018
  end
1019

    
1020
  test "#by_tracker" do
1021
    User.current = User.anonymous
1022
    groups = Issue.by_tracker(Project.find(1))
1023
    assert_equal 3, groups.size
1024
    assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1025
  end
1026

    
1027
  test "#by_version" do
1028
    User.current = User.anonymous
1029
    groups = Issue.by_version(Project.find(1))
1030
    assert_equal 3, groups.size
1031
    assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1032
  end
1033

    
1034
  test "#by_priority" do
1035
    User.current = User.anonymous
1036
    groups = Issue.by_priority(Project.find(1))
1037
    assert_equal 4, groups.size
1038
    assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1039
  end
1040

    
1041
  test "#by_category" do
1042
    User.current = User.anonymous
1043
    groups = Issue.by_category(Project.find(1))
1044
    assert_equal 2, groups.size
1045
    assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1046
  end
1047

    
1048
  test "#by_assigned_to" do
1049
    User.current = User.anonymous
1050
    groups = Issue.by_assigned_to(Project.find(1))
1051
    assert_equal 2, groups.size
1052
    assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1053
  end
1054

    
1055
  test "#by_author" do
1056
    User.current = User.anonymous
1057
    groups = Issue.by_author(Project.find(1))
1058
    assert_equal 4, groups.size
1059
    assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1060
  end
1061

    
1062
  test "#by_subproject" do
1063
    User.current = User.anonymous
1064
    groups = Issue.by_subproject(Project.find(1))
1065
    # Private descendant not visible
1066
    assert_equal 1, groups.size
1067
    assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1068
  end
1069

    
1070
  context ".allowed_target_projects_on_move" do
1071
    should "return all active projects for admin users" do
1072
      User.current = User.find(1)
1073
      assert_equal Project.active.count, Issue.allowed_target_projects_on_move.size
1074
    end
1075

    
1076
    should "return allowed projects for non admin users" do
1077
      User.current = User.find(2)
1078
      Role.non_member.remove_permission! :move_issues
1079
      assert_equal 3, Issue.allowed_target_projects_on_move.size
1080

    
1081
      Role.non_member.add_permission! :move_issues
1082
      assert_equal Project.active.count, Issue.allowed_target_projects_on_move.size
1083
    end
1084
  end
1085

    
1086
  def test_recently_updated_with_limit_scopes
1087
    #should return the last updated issue
1088
    assert_equal 1, Issue.recently_updated.with_limit(1).length
1089
    assert_equal Issue.find(:first, :order => "updated_on DESC"), Issue.recently_updated.with_limit(1).first
1090
  end
1091

    
1092
  def test_on_active_projects_scope
1093
    assert Project.find(2).archive
1094

    
1095
    before = Issue.on_active_project.length
1096
    # test inclusion to results
1097
    issue = Issue.generate_for_project!(Project.find(1), :tracker => Project.find(2).trackers.first)
1098
    assert_equal before + 1, Issue.on_active_project.length
1099

    
1100
    # Move to an archived project
1101
    issue.project = Project.find(2)
1102
    assert issue.save
1103
    assert_equal before, Issue.on_active_project.length
1104
  end
1105

    
1106
  context "Issue#recipients" do
1107
    setup do
1108
      @project = Project.find(1)
1109
      @author = User.generate_with_protected!
1110
      @assignee = User.generate_with_protected!
1111
      @issue = Issue.generate_for_project!(@project, :assigned_to => @assignee, :author => @author)
1112
    end
1113

    
1114
    should "include project recipients" do
1115
      assert @project.recipients.present?
1116
      @project.recipients.each do |project_recipient|
1117
        assert @issue.recipients.include?(project_recipient)
1118
      end
1119
    end
1120

    
1121
    should "include the author if the author is active" do
1122
      assert @issue.author, "No author set for Issue"
1123
      assert @issue.recipients.include?(@issue.author.mail)
1124
    end
1125

    
1126
    should "include the assigned to user if the assigned to user is active" do
1127
      assert @issue.assigned_to, "No assigned_to set for Issue"
1128
      assert @issue.recipients.include?(@issue.assigned_to.mail)
1129
    end
1130

    
1131
    should "not include users who opt out of all email" do
1132
      @author.update_attribute(:mail_notification, :none)
1133

    
1134
      assert !@issue.recipients.include?(@issue.author.mail)
1135
    end
1136

    
1137
    should "not include the issue author if they are only notified of assigned issues" do
1138
      @author.update_attribute(:mail_notification, :only_assigned)
1139

    
1140
      assert !@issue.recipients.include?(@issue.author.mail)
1141
    end
1142

    
1143
    should "not include the assigned user if they are only notified of owned issues" do
1144
      @assignee.update_attribute(:mail_notification, :only_owner)
1145

    
1146
      assert !@issue.recipients.include?(@issue.assigned_to.mail)
1147
    end
1148

    
1149
  end
1150
end