You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

issue_subtasking_test.rb 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. # frozen_string_literal: true
  2. # Redmine - project management software
  3. # Copyright (C) 2006- Jean-Philippe Lang
  4. #
  5. # This program is free software; you can redistribute it and/or
  6. # modify it under the terms of the GNU General Public License
  7. # as published by the Free Software Foundation; either version 2
  8. # of the License, or (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. require_relative '../test_helper'
  19. class IssueSubtaskingTest < ActiveSupport::TestCase
  20. fixtures :projects, :users, :roles, :members, :member_roles,
  21. :trackers, :projects_trackers,
  22. :issue_statuses, :issue_categories, :enumerations,
  23. :issues,
  24. :enabled_modules,
  25. :workflows
  26. def setup
  27. User.current = nil
  28. end
  29. def test_leaf_planning_fields_should_be_editable
  30. issue = Issue.generate!
  31. user = User.find(1)
  32. %w(priority_id done_ratio start_date due_date estimated_hours).each do |attribute|
  33. assert issue.safe_attribute?(attribute, user)
  34. end
  35. end
  36. def test_parent_dates_should_be_read_only_with_parent_issue_dates_set_to_derived
  37. with_settings :parent_issue_dates => 'derived' do
  38. issue = Issue.generate_with_child!
  39. user = User.find(1)
  40. %w(start_date due_date).each do |attribute|
  41. assert !issue.safe_attribute?(attribute, user)
  42. end
  43. end
  44. end
  45. def test_parent_dates_should_be_lowest_start_and_highest_due_dates_with_parent_issue_dates_set_to_derived
  46. with_settings :parent_issue_dates => 'derived' do
  47. parent = Issue.generate!
  48. parent.generate_child!(:start_date => '2010-01-25', :due_date => '2010-02-15')
  49. parent.generate_child!( :due_date => '2010-02-13')
  50. parent.generate_child!(:start_date => '2010-02-01', :due_date => '2010-02-22')
  51. parent.reload
  52. assert_equal Date.parse('2010-01-25'), parent.start_date
  53. assert_equal Date.parse('2010-02-22'), parent.due_date
  54. end
  55. end
  56. def test_reschuling_a_parent_should_reschedule_subtasks_with_parent_issue_dates_set_to_derived
  57. with_settings :parent_issue_dates => 'derived' do
  58. parent = Issue.generate!
  59. c1 = parent.generate_child!(:start_date => '2010-05-12', :due_date => '2010-05-18')
  60. c2 = parent.generate_child!(:start_date => '2010-06-03', :due_date => '2010-06-10')
  61. parent.reload.reschedule_on!(Date.parse('2010-06-02'))
  62. c1.reload
  63. assert_equal [Date.parse('2010-06-02'), Date.parse('2010-06-08')], [c1.start_date, c1.due_date]
  64. c2.reload
  65. assert_equal [Date.parse('2010-06-03'), Date.parse('2010-06-10')], [c2.start_date, c2.due_date] # no change
  66. parent.reload
  67. assert_equal [Date.parse('2010-06-02'), Date.parse('2010-06-10')], [parent.start_date, parent.due_date]
  68. end
  69. end
  70. def test_parent_priority_should_be_read_only_with_parent_issue_priority_set_to_derived
  71. with_settings :parent_issue_priority => 'derived' do
  72. issue = Issue.generate_with_child!
  73. user = User.find(1)
  74. assert !issue.safe_attribute?('priority_id', user)
  75. end
  76. end
  77. def test_parent_priority_should_be_the_highest_open_child_priority
  78. with_settings :parent_issue_priority => 'derived' do
  79. parent = Issue.generate!(:priority => IssuePriority.find_by_name('Normal'))
  80. # Create children
  81. child1 = parent.generate_child!(:priority => IssuePriority.find_by_name('High'))
  82. assert_equal 'High', parent.reload.priority.name
  83. child2 = child1.generate_child!(:priority => IssuePriority.find_by_name('Immediate'))
  84. assert_equal 'Immediate', child1.reload.priority.name
  85. assert_equal 'Immediate', parent.reload.priority.name
  86. child3 = parent.generate_child!(:priority => IssuePriority.find_by_name('Low'))
  87. child4 = parent.generate_child!(:priority => IssuePriority.find_by_name('Urgent'))
  88. assert_equal 'Immediate', parent.reload.priority.name
  89. # Destroy a child
  90. child1.destroy
  91. assert_equal 'Urgent', parent.reload.priority.name
  92. # Close a child
  93. child4.status = IssueStatus.where(:is_closed => true).first
  94. child4.save!
  95. assert_equal 'Low', parent.reload.priority.name
  96. # Update a child
  97. child3.reload.priority = IssuePriority.find_by_name('Normal')
  98. child3.save!
  99. assert_equal 'Normal', parent.reload.priority.name
  100. # Reopen a child
  101. child4.status = IssueStatus.where(:is_closed => false).first
  102. child4.save!
  103. assert_equal 'Urgent', parent.reload.priority.name
  104. end
  105. end
  106. def test_parent_priority_should_be_set_to_default_when_all_children_are_closed
  107. with_settings :parent_issue_priority => 'derived' do
  108. parent = Issue.generate!
  109. child = parent.generate_child!(:priority => IssuePriority.find_by_name('High'))
  110. assert_equal 'High', parent.reload.priority.name
  111. child.status = IssueStatus.where(:is_closed => true).first
  112. child.save!
  113. assert_equal 'Normal', parent.reload.priority.name
  114. end
  115. end
  116. def test_parent_priority_should_be_left_unchanged_when_all_children_are_closed_and_no_default_priority
  117. IssuePriority.update_all :is_default => false
  118. with_settings :parent_issue_priority => 'derived' do
  119. parent = Issue.generate!(:priority => IssuePriority.find_by_name('Normal'))
  120. child = parent.generate_child!(:priority => IssuePriority.find_by_name('High'))
  121. assert_equal 'High', parent.reload.priority.name
  122. child.status = IssueStatus.where(:is_closed => true).first
  123. child.save!
  124. assert_equal 'High', parent.reload.priority.name
  125. end
  126. end
  127. def test_parent_done_ratio_should_be_read_only_with_parent_issue_done_ratio_set_to_derived
  128. with_settings :parent_issue_done_ratio => 'derived' do
  129. issue = Issue.generate_with_child!
  130. user = User.find(1)
  131. assert !issue.safe_attribute?('done_ratio', user)
  132. end
  133. end
  134. def test_parent_done_ratio_should_be_average_done_ratio_of_leaves
  135. with_settings :parent_issue_done_ratio => 'derived' do
  136. parent = Issue.generate!
  137. parent.generate_child!(:done_ratio => 20)
  138. assert_equal 20, parent.reload.done_ratio
  139. parent.generate_child!(:done_ratio => 70)
  140. assert_equal 45, parent.reload.done_ratio
  141. child = parent.generate_child!(:done_ratio => 0)
  142. assert_equal 30, parent.reload.done_ratio
  143. child.generate_child!(:done_ratio => 30)
  144. assert_equal 30, child.reload.done_ratio
  145. assert_equal 40, parent.reload.done_ratio
  146. end
  147. end
  148. def test_parent_done_ratio_should_be_rounded_down_to_the_nearest_integer
  149. with_settings :parent_issue_done_ratio => 'derived' do
  150. parent = Issue.generate!
  151. parent.generate_child!(:done_ratio => 20)
  152. parent.generate_child!(:done_ratio => 20)
  153. parent.generate_child!(:done_ratio => 10)
  154. # (20 + 20 + 10) / 3 = 16.666...
  155. assert_equal 16, parent.reload.done_ratio
  156. end
  157. end
  158. def test_parent_done_ratio_should_be_weighted_by_estimated_times_if_any
  159. with_settings :parent_issue_done_ratio => 'derived' do
  160. parent = Issue.generate!
  161. parent.generate_child!(:estimated_hours => 10, :done_ratio => 20)
  162. assert_equal 20, parent.reload.done_ratio
  163. parent.generate_child!(:estimated_hours => 20, :done_ratio => 50)
  164. assert_equal (50 * 20 + 20 * 10) / 30, parent.reload.done_ratio
  165. end
  166. end
  167. def test_parent_done_ratio_should_be_weighted_by_estimated_times_if_any_with_grandchildren
  168. # parent
  169. # child 1 (2h estd, 0% done)
  170. # child 2 (no estd)
  171. # child a (2h estd, 50% done)
  172. # child b (2h estd, 50% done)
  173. #
  174. # => parent should have a calculated progress of 33%
  175. #
  176. with_settings :parent_issue_done_ratio => 'derived' do
  177. parent = Issue.generate!
  178. parent.generate_child!(:estimated_hours => 2, :done_ratio => 0)
  179. child = parent.generate_child!
  180. child.generate_child!(:estimated_hours => 2, :done_ratio => 50)
  181. child.generate_child!(:estimated_hours => 2, :done_ratio => 50)
  182. assert_equal 50, child.reload.done_ratio
  183. assert_equal 33, parent.reload.done_ratio
  184. end
  185. end
  186. def test_parent_done_ratio_with_child_estimate_to_0_should_reach_100
  187. with_settings :parent_issue_done_ratio => 'derived' do
  188. parent = Issue.generate!
  189. issue1 = parent.generate_child!
  190. issue2 = parent.generate_child!(:estimated_hours => 0)
  191. assert_equal 0, parent.reload.done_ratio
  192. issue1.reload.close!
  193. assert_equal 50, parent.reload.done_ratio
  194. issue2.reload.close!
  195. assert_equal 100, parent.reload.done_ratio
  196. end
  197. end
  198. def test_done_ratio_of_parent_with_a_child_without_estimated_time_should_not_exceed_100
  199. with_settings :parent_issue_done_ratio => 'derived' do
  200. parent = Issue.generate!
  201. parent.generate_child!(:estimated_hours => 40)
  202. parent.generate_child!(:estimated_hours => 40)
  203. parent.generate_child!(:estimated_hours => 20)
  204. parent.generate_child!
  205. parent.reload.children.each(&:close!)
  206. assert_equal 100, parent.reload.done_ratio
  207. end
  208. end
  209. def test_done_ratio_of_parent_with_a_child_with_estimated_time_at_0_should_not_exceed_100
  210. with_settings :parent_issue_done_ratio => 'derived' do
  211. parent = Issue.generate!
  212. parent.generate_child!(:estimated_hours => 40)
  213. parent.generate_child!(:estimated_hours => 40)
  214. parent.generate_child!(:estimated_hours => 20)
  215. parent.generate_child!(:estimated_hours => 0)
  216. parent.reload.children.each(&:close!)
  217. assert_equal 100, parent.reload.done_ratio
  218. end
  219. end
  220. def test_done_ratio_of_parent_with_completed_children_should_not_be_99
  221. with_settings :parent_issue_done_ratio => 'derived' do
  222. parent1 = Issue.generate!
  223. parent1.generate_child!(:estimated_hours => 8.0, :done_ratio => 100)
  224. parent1.generate_child!(:estimated_hours => 8.1, :done_ratio => 100)
  225. # (8.0 * 100 + 8.1 * 100) / (8.0 + 8.1) => 99.99999999999999
  226. assert_equal 100, parent1.reload.done_ratio
  227. parent2 = Issue.generate!
  228. parent2.generate_child!(:estimated_hours => 9.0, :done_ratio => 100)
  229. 10.times do
  230. parent2.generate_child!(:estimated_hours => 10.0, :done_ratio => 100)
  231. end
  232. assert_equal 100, parent2.reload.done_ratio
  233. end
  234. end
  235. def test_changing_parent_should_update_previous_parent_done_ratio
  236. with_settings :parent_issue_done_ratio => 'derived' do
  237. first_parent = Issue.generate!
  238. second_parent = Issue.generate!
  239. first_parent.generate_child!(:done_ratio => 40)
  240. child = first_parent.generate_child!(:done_ratio => 20)
  241. assert_equal 30, first_parent.reload.done_ratio
  242. assert_equal 0, second_parent.reload.done_ratio
  243. child.update(:parent_issue_id => second_parent.id)
  244. assert_equal 40, first_parent.reload.done_ratio
  245. assert_equal 20, second_parent.reload.done_ratio
  246. end
  247. end
  248. def test_done_ratio_of_parent_should_reflect_children
  249. root = Issue.generate!
  250. child1 = root.generate_child!
  251. child2 = child1.generate_child!
  252. assert_equal 0, root.done_ratio
  253. assert_equal 0, child1.done_ratio
  254. assert_equal 0, child2.done_ratio
  255. with_settings :issue_done_ratio => 'issue_status' do
  256. status = IssueStatus.find(4)
  257. status.update_attribute :default_done_ratio, 50
  258. child1.reload
  259. child1.update_attribute :status, status
  260. assert_equal 50, child1.done_ratio
  261. root.reload
  262. assert_equal 50, root.done_ratio
  263. end
  264. end
  265. def test_parent_dates_should_be_editable_with_parent_issue_dates_set_to_independent
  266. with_settings :parent_issue_dates => 'independent' do
  267. issue = Issue.generate_with_child!
  268. user = User.find(1)
  269. %w(start_date due_date).each do |attribute|
  270. assert issue.safe_attribute?(attribute, user)
  271. end
  272. end
  273. end
  274. def test_parent_dates_should_not_be_updated_with_parent_issue_dates_set_to_independent
  275. with_settings :parent_issue_dates => 'independent' do
  276. parent = Issue.generate!(:start_date => '2015-07-01', :due_date => '2015-08-01')
  277. parent.generate_child!(:start_date => '2015-06-01', :due_date => '2015-09-01')
  278. parent.reload
  279. assert_equal Date.parse('2015-07-01'), parent.start_date
  280. assert_equal Date.parse('2015-08-01'), parent.due_date
  281. end
  282. end
  283. def test_reschuling_a_parent_should_not_reschedule_subtasks_with_parent_issue_dates_set_to_independent
  284. with_settings :parent_issue_dates => 'independent' do
  285. parent = Issue.generate!(:start_date => '2010-05-01', :due_date => '2010-05-20')
  286. c1 = parent.generate_child!(:start_date => '2010-05-12', :due_date => '2010-05-18')
  287. parent.reload.reschedule_on!(Date.parse('2010-06-01'))
  288. assert_equal Date.parse('2010-06-01'), parent.reload.start_date
  289. c1.reload
  290. assert_equal [Date.parse('2010-05-12'), Date.parse('2010-05-18')], [c1.start_date, c1.due_date]
  291. end
  292. end
  293. def test_parent_priority_should_be_editable_with_parent_issue_priority_set_to_independent
  294. with_settings :parent_issue_priority => 'independent' do
  295. issue = Issue.generate_with_child!
  296. user = User.find(1)
  297. assert issue.safe_attribute?('priority_id', user)
  298. end
  299. end
  300. def test_parent_priority_should_not_be_updated_with_parent_issue_priority_set_to_independent
  301. with_settings :parent_issue_priority => 'independent' do
  302. parent = Issue.generate!(:priority => IssuePriority.find_by_name('Normal'))
  303. child1 = parent.generate_child!(:priority => IssuePriority.find_by_name('High'))
  304. assert_equal 'Normal', parent.reload.priority.name
  305. end
  306. end
  307. def test_parent_done_ratio_should_be_editable_with_parent_issue_done_ratio_set_to_independent
  308. with_settings :parent_issue_done_ratio => 'independent' do
  309. issue = Issue.generate_with_child!
  310. user = User.find(1)
  311. assert issue.safe_attribute?('done_ratio', user)
  312. end
  313. end
  314. def test_parent_done_ratio_should_not_be_updated_with_parent_issue_done_ratio_set_to_independent
  315. with_settings :parent_issue_done_ratio => 'independent' do
  316. parent = Issue.generate!(:done_ratio => 0)
  317. child1 = parent.generate_child!(:done_ratio => 10)
  318. assert_equal 0, parent.reload.done_ratio
  319. end
  320. end
  321. def test_parent_total_estimated_hours_should_be_sum_of_visible_descendants
  322. parent = Issue.generate!
  323. parent.generate_child!(:estimated_hours => nil)
  324. assert_equal 0, parent.reload.total_estimated_hours
  325. parent.generate_child!(:estimated_hours => 5)
  326. assert_equal 5, parent.reload.total_estimated_hours
  327. parent.generate_child!(:estimated_hours => 7)
  328. assert_equal 12, parent.reload.total_estimated_hours
  329. parent.generate_child!(:estimated_hours => 9, :is_private => true)
  330. assert_equal 12, parent.reload.total_estimated_hours
  331. end
  332. def test_open_issue_with_closed_parent_should_not_validate
  333. parent = Issue.generate!(:status_id => 5)
  334. child = Issue.generate!
  335. child.parent_issue_id = parent.id
  336. assert !child.save
  337. assert_include I18n.t("activerecord.errors.messages.open_issue_with_closed_parent"), child.errors.full_messages
  338. end
  339. end