GitLab steht wegen Wartungsarbeiten am Montag, den 10. Mai, zwischen 17:00 und 19:00 Uhr nicht zur Verfügung.

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

18
require File.expand_path('../../test_helper', __FILE__)
jplang's avatar
jplang committed
19 20

class IssueNestedSetTest < ActiveSupport::TestCase
jplang's avatar
jplang committed
21
  fixtures :projects, :users, :roles,
jplang's avatar
jplang committed
22
           :trackers, :projects_trackers,
jplang's avatar
jplang committed
23
           :issue_statuses, :issue_categories, :issue_relations,
jplang's avatar
jplang committed
24
           :enumerations,
jplang's avatar
jplang committed
25
           :issues
jplang's avatar
jplang committed
26

27 28 29 30 31
  def test_new_record_is_leaf
    i = Issue.new
    assert i.leaf?
  end

jplang's avatar
jplang committed
32
  def test_create_root_issue
33
    lft1 = new_issue_lft
34
    issue1 = Issue.generate!
35
    lft2 = new_issue_lft
36
    issue2 = Issue.generate!
jplang's avatar
jplang committed
37 38
    issue1.reload
    issue2.reload
39 40
    assert_equal [issue1.id, nil, lft1, lft1 + 1], [issue1.root_id, issue1.parent_id, issue1.lft, issue1.rgt]
    assert_equal [issue2.id, nil, lft2, lft2 + 1], [issue2.root_id, issue2.parent_id, issue2.lft, issue2.rgt]
jplang's avatar
jplang committed
41
  end
42

jplang's avatar
jplang committed
43
  def test_create_child_issue
44
    lft = new_issue_lft
45
    parent = Issue.generate!
46
    child =  parent.generate_child!
jplang's avatar
jplang committed
47 48
    parent.reload
    child.reload
49 50
    assert_equal [parent.id, nil,       lft,     lft + 3], [parent.root_id, parent.parent_id, parent.lft, parent.rgt]
    assert_equal [parent.id, parent.id, lft + 1, lft + 2], [child.root_id, child.parent_id, child.lft, child.rgt]
jplang's avatar
jplang committed
51
  end
52

53
  def test_creating_a_child_in_a_subproject_should_validate
54
    issue = Issue.generate!
55 56 57 58 59 60 61
    child = Issue.new(:project_id => 3, :tracker_id => 2, :author_id => 1,
                      :subject => 'child', :parent_issue_id => issue.id)
    assert_save child
    assert_equal issue, child.reload.parent
  end

  def test_creating_a_child_in_an_invalid_project_should_not_validate
62
    issue = Issue.generate!
63 64
    child = Issue.new(:project_id => 2, :tracker_id => 1, :author_id => 1,
                      :subject => 'child', :parent_issue_id => issue.id)
jplang's avatar
jplang committed
65
    assert !child.save
66
    assert_not_equal [], child.errors[:parent_issue_id]
jplang's avatar
jplang committed
67
  end
68

jplang's avatar
jplang committed
69
  def test_move_a_root_to_child
70
    lft = new_issue_lft
71 72
    parent1 = Issue.generate!
    parent2 = Issue.generate!
73
    child = parent1.generate_child!
jplang's avatar
jplang committed
74 75 76 77 78
    parent2.parent_issue_id = parent1.id
    parent2.save!
    child.reload
    parent1.reload
    parent2.reload
79
    assert_equal [parent1.id, lft,     lft + 5], [parent1.root_id, parent1.lft, parent1.rgt]
80 81
    assert_equal [parent1.id, lft + 1, lft + 2], [parent2.root_id, parent2.lft, parent2.rgt]
    assert_equal [parent1.id, lft + 3, lft + 4], [child.root_id, child.lft, child.rgt]
jplang's avatar
jplang committed
82
  end
83

jplang's avatar
jplang committed
84
  def test_move_a_child_to_root
85
    lft1 = new_issue_lft
86
    parent1 = Issue.generate!
87
    lft2 = new_issue_lft
88
    parent2 = Issue.generate!
89
    lft3 = new_issue_lft
90
    child = parent1.generate_child!
jplang's avatar
jplang committed
91 92 93 94 95
    child.parent_issue_id = nil
    child.save!
    child.reload
    parent1.reload
    parent2.reload
96 97
    assert_equal [parent1.id, lft1, lft1 + 1], [parent1.root_id, parent1.lft, parent1.rgt]
    assert_equal [parent2.id, lft2, lft2 + 1], [parent2.root_id, parent2.lft, parent2.rgt]
98
    assert_equal [child.id,   lft3, lft3 + 1], [child.root_id, child.lft, child.rgt]
jplang's avatar
jplang committed
99
  end
100

jplang's avatar
jplang committed
101
  def test_move_a_child_to_another_issue
102
    lft1 = new_issue_lft
103
    parent1 = Issue.generate!
104
    lft2 = new_issue_lft
105
    parent2 = Issue.generate!
106
    child = parent1.generate_child!
jplang's avatar
jplang committed
107 108 109 110 111
    child.parent_issue_id = parent2.id
    child.save!
    child.reload
    parent1.reload
    parent2.reload
112 113 114
    assert_equal [parent1.id, lft1,     lft1 + 1], [parent1.root_id, parent1.lft, parent1.rgt]
    assert_equal [parent2.id, lft2,     lft2 + 3], [parent2.root_id, parent2.lft, parent2.rgt]
    assert_equal [parent2.id, lft2 + 1, lft2 + 2], [child.root_id,   child.lft,   child.rgt]
jplang's avatar
jplang committed
115
  end
116

jplang's avatar
jplang committed
117
  def test_move_a_child_with_descendants_to_another_issue
118
    lft1 = new_issue_lft
119
    parent1 = Issue.generate!
120
    lft2 = new_issue_lft
121
    parent2 = Issue.generate!
122 123
    child = parent1.generate_child!
    grandchild = child.generate_child!
jplang's avatar
jplang committed
124 125 126 127
    parent1.reload
    parent2.reload
    child.reload
    grandchild.reload
128 129 130 131
    assert_equal [parent1.id, lft1,     lft1 + 5], [parent1.root_id, parent1.lft, parent1.rgt]
    assert_equal [parent2.id, lft2,     lft2 + 1], [parent2.root_id, parent2.lft, parent2.rgt]
    assert_equal [parent1.id, lft1 + 1, lft1 + 4], [child.root_id, child.lft, child.rgt]
    assert_equal [parent1.id, lft1 + 2, lft1 + 3], [grandchild.root_id, grandchild.lft, grandchild.rgt]
jplang's avatar
jplang committed
132 133 134 135 136 137
    child.reload.parent_issue_id = parent2.id
    child.save!
    child.reload
    grandchild.reload
    parent1.reload
    parent2.reload
138 139 140 141
    assert_equal [parent1.id, lft1,     lft1 + 1], [parent1.root_id, parent1.lft, parent1.rgt]
    assert_equal [parent2.id, lft2,     lft2 + 5], [parent2.root_id, parent2.lft, parent2.rgt]
    assert_equal [parent2.id, lft2 + 1, lft2 + 4], [child.root_id, child.lft, child.rgt]
    assert_equal [parent2.id, lft2 + 2, lft2 + 3], [grandchild.root_id, grandchild.lft, grandchild.rgt]
jplang's avatar
jplang committed
142
  end
143

jplang's avatar
jplang committed
144
  def test_move_a_child_with_descendants_to_another_project
145
    lft1 = new_issue_lft
146
    parent1 = Issue.generate!
147 148
    child = parent1.generate_child!
    grandchild = child.generate_child!
149
    lft4 = new_issue_lft
jplang's avatar
jplang committed
150 151 152
    child.reload
    child.project = Project.find(2)
    assert child.save
jplang's avatar
jplang committed
153 154 155
    child.reload
    grandchild.reload
    parent1.reload
156
    assert_equal [1, parent1.id, lft1, lft1 + 1], [parent1.project_id, parent1.root_id, parent1.lft, parent1.rgt]
157 158 159 160
    assert_equal [2, child.id, lft4, lft4 + 3],
                 [child.project_id, child.root_id, child.lft, child.rgt]
    assert_equal [2, child.id, lft4 + 1, lft4 + 2],
                 [grandchild.project_id, grandchild.root_id, grandchild.lft, grandchild.rgt]
jplang's avatar
jplang committed
161
  end
162

jplang's avatar
jplang committed
163
  def test_moving_an_issue_to_a_descendant_should_not_validate
164 165
    parent1 = Issue.generate!
    parent2 = Issue.generate!
166 167
    child = parent1.generate_child!
    grandchild = child.generate_child!
168

jplang's avatar
jplang committed
169 170 171
    child.reload
    child.parent_issue_id = grandchild.id
    assert !child.save
172
    assert_not_equal [], child.errors[:parent_issue_id]
jplang's avatar
jplang committed
173
  end
174

175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
  def test_updating_a_root_issue_should_not_trigger_update_nested_set_attributes_on_parent_change
    issue = Issue.find(Issue.generate!.id)
    issue.parent_issue_id = ""
    issue.expects(:update_nested_set_attributes_on_parent_change).never
    issue.save!
  end

  def test_updating_a_child_issue_should_not_trigger_update_nested_set_attributes_on_parent_change
    issue = Issue.find(Issue.generate!(:parent_issue_id => 1).id)
    issue.parent_issue_id = "1"
    issue.expects(:update_nested_set_attributes_on_parent_change).never
    issue.save!
  end

  def test_moving_a_root_issue_should_trigger_update_nested_set_attributes_on_parent_change
    issue = Issue.find(Issue.generate!.id)
    issue.parent_issue_id = "1"
    issue.expects(:update_nested_set_attributes_on_parent_change).once
    issue.save!
  end

  def test_moving_a_child_issue_to_another_parent_should_trigger_update_nested_set_attributes_on_parent_change
    issue = Issue.find(Issue.generate!(:parent_issue_id => 1).id)
    issue.parent_issue_id = "2"
    issue.expects(:update_nested_set_attributes_on_parent_change).once
    issue.save!
  end

  def test_moving_a_child_issue_to_root_should_trigger_update_nested_set_attributes_on_parent_change
    issue = Issue.find(Issue.generate!(:parent_issue_id => 1).id)
    issue.parent_issue_id = ""
    issue.expects(:update_nested_set_attributes_on_parent_change).once
    issue.save!
  end

jplang's avatar
jplang committed
210
  def test_destroy_should_destroy_children
211
    lft1 = new_issue_lft
212 213
    issue1 = Issue.generate!
    issue2 = Issue.generate!
214 215
    issue3 = issue2.generate_child!
    issue4 = issue1.generate_child!
216 217 218 219 220 221 222 223 224 225
    issue3.init_journal(User.find(2))
    issue3.subject = 'child with journal'
    issue3.save!
    assert_difference 'Issue.count', -2 do
      assert_difference 'Journal.count', -1 do
        assert_difference 'JournalDetail.count', -1 do
          Issue.find(issue2.id).destroy
        end
      end
    end
jplang's avatar
jplang committed
226 227 228 229
    issue1.reload
    issue4.reload
    assert !Issue.exists?(issue2.id)
    assert !Issue.exists?(issue3.id)
230 231
    assert_equal [issue1.id, lft1,     lft1 + 3], [issue1.root_id, issue1.lft, issue1.rgt]
    assert_equal [issue1.id, lft1 + 1, lft1 + 2], [issue4.root_id, issue4.lft, issue4.rgt]
jplang's avatar
jplang committed
232
  end
233

234
  def test_destroy_child_should_update_parent
235
    lft1 = new_issue_lft
236
    issue = Issue.generate!
237 238
    child1 = issue.generate_child!
    child2 = issue.generate_child!
239
    issue.reload
240
    assert_equal [issue.id, lft1, lft1 + 5], [issue.root_id, issue.lft, issue.rgt]
241 242
    child2.reload.destroy
    issue.reload
243
    assert_equal [issue.id, lft1, lft1 + 3], [issue.root_id, issue.lft, issue.rgt]
244
  end
245

246
  def test_destroy_parent_issue_updated_during_children_destroy
247
    parent = Issue.generate!
248 249
    parent.generate_child!(:start_date => Date.today)
    parent.generate_child!(:start_date => 2.days.from_now)
250

251 252 253 254
    assert_difference 'Issue.count', -3 do
      Issue.find(parent.id).destroy
    end
  end
255

256
  def test_destroy_child_issue_with_children
257 258 259
    root = Issue.generate!
    child = root.generate_child!
    leaf = child.generate_child!
260 261 262
    leaf.init_journal(User.find(2))
    leaf.subject = 'leaf with journal'
    leaf.save!
263

264 265 266 267 268 269 270
    assert_difference 'Issue.count', -2 do
      assert_difference 'Journal.count', -1 do
        assert_difference 'JournalDetail.count', -1 do
          Issue.find(child.id).destroy
        end
      end
    end
271

272 273 274
    root = Issue.find(root.id)
    assert root.leaf?, "Root issue is not a leaf (lft: #{root.lft}, rgt: #{root.rgt})"
  end
275

276
  def test_destroy_issue_with_grand_child
277
    lft1 = new_issue_lft
278
    parent = Issue.generate!
279 280 281 282
    issue = parent.generate_child!
    child = issue.generate_child!
    grandchild1 = child.generate_child!
    grandchild2 = child.generate_child!
283 284 285
    assert_difference 'Issue.count', -4 do
      Issue.find(issue.id).destroy
      parent.reload
286
      assert_equal [lft1, lft1 + 1], [parent.lft, parent.rgt]
287 288
    end
  end
289

jplang's avatar
jplang committed
290
  def test_parent_done_ratio_should_be_average_done_ratio_of_leaves
291
    parent = Issue.generate!
292
    parent.generate_child!(:done_ratio => 20)
jplang's avatar
jplang committed
293
    assert_equal 20, parent.reload.done_ratio
294
    parent.generate_child!(:done_ratio => 70)
jplang's avatar
jplang committed
295
    assert_equal 45, parent.reload.done_ratio
296

297
    child = parent.generate_child!(:done_ratio => 0)
jplang's avatar
jplang committed
298
    assert_equal 30, parent.reload.done_ratio
299

300
    child.generate_child!(:done_ratio => 30)
jplang's avatar
jplang committed
301 302 303
    assert_equal 30, child.reload.done_ratio
    assert_equal 40, parent.reload.done_ratio
  end
304

jplang's avatar
jplang committed
305
  def test_parent_done_ratio_should_be_weighted_by_estimated_times_if_any
306
    parent = Issue.generate!
307
    parent.generate_child!(:estimated_hours => 10, :done_ratio => 20)
jplang's avatar
jplang committed
308
    assert_equal 20, parent.reload.done_ratio
309
    parent.generate_child!(:estimated_hours => 20, :done_ratio => 50)
jplang's avatar
jplang committed
310 311
    assert_equal (50 * 20 + 20 * 10) / 30, parent.reload.done_ratio
  end
312

jplang's avatar
jplang committed
313 314
  def test_parent_done_ratio_with_child_estimate_to_0_should_reach_100
    parent = Issue.generate!
315 316
    issue1 = parent.generate_child!
    issue2 = parent.generate_child!(:estimated_hours => 0)
jplang's avatar
jplang committed
317
    assert_equal 0, parent.reload.done_ratio
318
    issue1.reload.close!
jplang's avatar
jplang committed
319
    assert_equal 50, parent.reload.done_ratio
320
    issue2.reload.close!
jplang's avatar
jplang committed
321 322 323
    assert_equal 100, parent.reload.done_ratio
  end

jplang's avatar
jplang committed
324
  def test_parent_estimate_should_be_sum_of_leaves
325
    parent = Issue.generate!
326
    parent.generate_child!(:estimated_hours => nil)
jplang's avatar
jplang committed
327
    assert_equal nil, parent.reload.estimated_hours
328
    parent.generate_child!(:estimated_hours => 5)
jplang's avatar
jplang committed
329
    assert_equal 5, parent.reload.estimated_hours
330
    parent.generate_child!(:estimated_hours => 7)
jplang's avatar
jplang committed
331 332
    assert_equal 12, parent.reload.estimated_hours
  end
333

334 335
  def test_done_ratio_of_parent_with_a_child_without_estimated_time_should_not_exceed_100
    parent = Issue.generate!
336 337 338 339 340
    parent.generate_child!(:estimated_hours => 40)
    parent.generate_child!(:estimated_hours => 40)
    parent.generate_child!(:estimated_hours => 20)
    parent.generate_child!
    parent.reload.children.each(&:close!)
341 342 343
    assert_equal 100, parent.reload.done_ratio
  end

344 345
  def test_done_ratio_of_parent_with_a_child_with_estimated_time_at_0_should_not_exceed_100
    parent = Issue.generate!
346 347 348 349 350
    parent.generate_child!(:estimated_hours => 40)
    parent.generate_child!(:estimated_hours => 40)
    parent.generate_child!(:estimated_hours => 20)
    parent.generate_child!(:estimated_hours => 0)
    parent.reload.children.each(&:close!)
351 352 353
    assert_equal 100, parent.reload.done_ratio
  end

354
  def test_move_parent_updates_old_parent_attributes
355 356
    first_parent = Issue.generate!
    second_parent = Issue.generate!
357
    child = first_parent.generate_child!(:estimated_hours => 5)
358 359 360 361 362
    assert_equal 5, first_parent.reload.estimated_hours
    child.update_attributes(:estimated_hours => 7, :parent_issue_id => second_parent.id)
    assert_equal 7, second_parent.reload.estimated_hours
    assert_nil first_parent.reload.estimated_hours
  end
363

jplang's avatar
jplang committed
364 365
  def test_project_copy_should_copy_issue_tree
    p = Project.create!(:name => 'Tree copy', :identifier => 'tree-copy', :tracker_ids => [1, 2])
366
    i1 = Issue.generate!(:project => p, :subject => 'i1')
367 368 369
    i2 = i1.generate_child!(:project => p, :subject => 'i2')
    i3 = i1.generate_child!(:project => p, :subject => 'i3')
    i4 = i2.generate_child!(:project => p, :subject => 'i4')
370
    i5 = Issue.generate!(:project => p, :subject => 'i5')
jplang's avatar
jplang committed
371 372 373
    c = Project.new(:name => 'Copy', :identifier => 'copy', :tracker_ids => [1, 2])
    c.copy(p, :only => 'issues')
    c.reload
374

jplang's avatar
jplang committed
375
    assert_equal 5, c.issues.count
jplang's avatar
jplang committed
376
    ic1, ic2, ic3, ic4, ic5 = c.issues.order('subject').to_a
jplang's avatar
jplang committed
377 378 379 380 381 382 383
    assert ic1.root?
    assert_equal ic1, ic2.parent
    assert_equal ic1, ic3.parent
    assert_equal ic2, ic4.parent
    assert ic5.root?
  end
end