##// END OF EJS Templates
Merged r15223 and r15225 (#22127)....
Jean-Philippe Lang -
r14849:fdc0782be1d6
parent child
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,1651 +1,1662
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Issue < ActiveRecord::Base
18 class Issue < ActiveRecord::Base
19 include Redmine::SafeAttributes
19 include Redmine::SafeAttributes
20 include Redmine::Utils::DateCalculation
20 include Redmine::Utils::DateCalculation
21 include Redmine::I18n
21 include Redmine::I18n
22 before_save :set_parent_id
22 before_save :set_parent_id
23 include Redmine::NestedSet::IssueNestedSet
23 include Redmine::NestedSet::IssueNestedSet
24
24
25 belongs_to :project
25 belongs_to :project
26 belongs_to :tracker
26 belongs_to :tracker
27 belongs_to :status, :class_name => 'IssueStatus'
27 belongs_to :status, :class_name => 'IssueStatus'
28 belongs_to :author, :class_name => 'User'
28 belongs_to :author, :class_name => 'User'
29 belongs_to :assigned_to, :class_name => 'Principal'
29 belongs_to :assigned_to, :class_name => 'Principal'
30 belongs_to :fixed_version, :class_name => 'Version'
30 belongs_to :fixed_version, :class_name => 'Version'
31 belongs_to :priority, :class_name => 'IssuePriority'
31 belongs_to :priority, :class_name => 'IssuePriority'
32 belongs_to :category, :class_name => 'IssueCategory'
32 belongs_to :category, :class_name => 'IssueCategory'
33
33
34 has_many :journals, :as => :journalized, :dependent => :destroy, :inverse_of => :journalized
34 has_many :journals, :as => :journalized, :dependent => :destroy, :inverse_of => :journalized
35 has_many :visible_journals,
35 has_many :visible_journals,
36 lambda {where(["(#{Journal.table_name}.private_notes = ? OR (#{Project.allowed_to_condition(User.current, :view_private_notes)}))", false])},
36 lambda {where(["(#{Journal.table_name}.private_notes = ? OR (#{Project.allowed_to_condition(User.current, :view_private_notes)}))", false])},
37 :class_name => 'Journal',
37 :class_name => 'Journal',
38 :as => :journalized
38 :as => :journalized
39
39
40 has_many :time_entries, :dependent => :destroy
40 has_many :time_entries, :dependent => :destroy
41 has_and_belongs_to_many :changesets, lambda {order("#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC")}
41 has_and_belongs_to_many :changesets, lambda {order("#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC")}
42
42
43 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
43 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
44 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
44 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
45
45
46 acts_as_attachable :after_add => :attachment_added, :after_remove => :attachment_removed
46 acts_as_attachable :after_add => :attachment_added, :after_remove => :attachment_removed
47 acts_as_customizable
47 acts_as_customizable
48 acts_as_watchable
48 acts_as_watchable
49 acts_as_searchable :columns => ['subject', "#{table_name}.description"],
49 acts_as_searchable :columns => ['subject', "#{table_name}.description"],
50 :preload => [:project, :status, :tracker],
50 :preload => [:project, :status, :tracker],
51 :scope => lambda {|options| options[:open_issues] ? self.open : self.all}
51 :scope => lambda {|options| options[:open_issues] ? self.open : self.all}
52
52
53 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"},
53 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"},
54 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}},
54 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}},
55 :type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') }
55 :type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') }
56
56
57 acts_as_activity_provider :scope => preload(:project, :author, :tracker),
57 acts_as_activity_provider :scope => preload(:project, :author, :tracker),
58 :author_key => :author_id
58 :author_key => :author_id
59
59
60 DONE_RATIO_OPTIONS = %w(issue_field issue_status)
60 DONE_RATIO_OPTIONS = %w(issue_field issue_status)
61
61
62 attr_reader :current_journal
62 attr_reader :current_journal
63 delegate :notes, :notes=, :private_notes, :private_notes=, :to => :current_journal, :allow_nil => true
63 delegate :notes, :notes=, :private_notes, :private_notes=, :to => :current_journal, :allow_nil => true
64
64
65 validates_presence_of :subject, :project, :tracker
65 validates_presence_of :subject, :project, :tracker
66 validates_presence_of :priority, :if => Proc.new {|issue| issue.new_record? || issue.priority_id_changed?}
66 validates_presence_of :priority, :if => Proc.new {|issue| issue.new_record? || issue.priority_id_changed?}
67 validates_presence_of :status, :if => Proc.new {|issue| issue.new_record? || issue.status_id_changed?}
67 validates_presence_of :status, :if => Proc.new {|issue| issue.new_record? || issue.status_id_changed?}
68 validates_presence_of :author, :if => Proc.new {|issue| issue.new_record? || issue.author_id_changed?}
68 validates_presence_of :author, :if => Proc.new {|issue| issue.new_record? || issue.author_id_changed?}
69
69
70 validates_length_of :subject, :maximum => 255
70 validates_length_of :subject, :maximum => 255
71 validates_inclusion_of :done_ratio, :in => 0..100
71 validates_inclusion_of :done_ratio, :in => 0..100
72 validates :estimated_hours, :numericality => {:greater_than_or_equal_to => 0, :allow_nil => true, :message => :invalid}
72 validates :estimated_hours, :numericality => {:greater_than_or_equal_to => 0, :allow_nil => true, :message => :invalid}
73 validates :start_date, :date => true
73 validates :start_date, :date => true
74 validates :due_date, :date => true
74 validates :due_date, :date => true
75 validate :validate_issue, :validate_required_fields
75 validate :validate_issue, :validate_required_fields
76 attr_protected :id
76 attr_protected :id
77
77
78 scope :visible, lambda {|*args|
78 scope :visible, lambda {|*args|
79 joins(:project).
79 joins(:project).
80 where(Issue.visible_condition(args.shift || User.current, *args))
80 where(Issue.visible_condition(args.shift || User.current, *args))
81 }
81 }
82
82
83 scope :open, lambda {|*args|
83 scope :open, lambda {|*args|
84 is_closed = args.size > 0 ? !args.first : false
84 is_closed = args.size > 0 ? !args.first : false
85 joins(:status).
85 joins(:status).
86 where("#{IssueStatus.table_name}.is_closed = ?", is_closed)
86 where("#{IssueStatus.table_name}.is_closed = ?", is_closed)
87 }
87 }
88
88
89 scope :recently_updated, lambda { order("#{Issue.table_name}.updated_on DESC") }
89 scope :recently_updated, lambda { order("#{Issue.table_name}.updated_on DESC") }
90 scope :on_active_project, lambda {
90 scope :on_active_project, lambda {
91 joins(:project).
91 joins(:project).
92 where("#{Project.table_name}.status = ?", Project::STATUS_ACTIVE)
92 where("#{Project.table_name}.status = ?", Project::STATUS_ACTIVE)
93 }
93 }
94 scope :fixed_version, lambda {|versions|
94 scope :fixed_version, lambda {|versions|
95 ids = [versions].flatten.compact.map {|v| v.is_a?(Version) ? v.id : v}
95 ids = [versions].flatten.compact.map {|v| v.is_a?(Version) ? v.id : v}
96 ids.any? ? where(:fixed_version_id => ids) : where('1=0')
96 ids.any? ? where(:fixed_version_id => ids) : where('1=0')
97 }
97 }
98
98
99 before_validation :clear_disabled_fields
99 before_validation :clear_disabled_fields
100 before_create :default_assign
100 before_create :default_assign
101 before_save :close_duplicates, :update_done_ratio_from_issue_status,
101 before_save :close_duplicates, :update_done_ratio_from_issue_status,
102 :force_updated_on_change, :update_closed_on, :set_assigned_to_was
102 :force_updated_on_change, :update_closed_on, :set_assigned_to_was
103 after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?}
103 after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?}
104 after_save :reschedule_following_issues, :update_nested_set_attributes,
104 after_save :reschedule_following_issues, :update_nested_set_attributes,
105 :update_parent_attributes, :create_journal
105 :update_parent_attributes, :create_journal
106 # Should be after_create but would be called before previous after_save callbacks
106 # Should be after_create but would be called before previous after_save callbacks
107 after_save :after_create_from_copy
107 after_save :after_create_from_copy
108 after_destroy :update_parent_attributes
108 after_destroy :update_parent_attributes
109 after_create :send_notification
109 after_create :send_notification
110 # Keep it at the end of after_save callbacks
110 # Keep it at the end of after_save callbacks
111 after_save :clear_assigned_to_was
111 after_save :clear_assigned_to_was
112
112
113 # Returns a SQL conditions string used to find all issues visible by the specified user
113 # Returns a SQL conditions string used to find all issues visible by the specified user
114 def self.visible_condition(user, options={})
114 def self.visible_condition(user, options={})
115 Project.allowed_to_condition(user, :view_issues, options) do |role, user|
115 Project.allowed_to_condition(user, :view_issues, options) do |role, user|
116 if user.id && user.logged?
116 if user.id && user.logged?
117 case role.issues_visibility
117 case role.issues_visibility
118 when 'all'
118 when 'all'
119 nil
119 nil
120 when 'default'
120 when 'default'
121 user_ids = [user.id] + user.groups.map(&:id).compact
121 user_ids = [user.id] + user.groups.map(&:id).compact
122 "(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
122 "(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
123 when 'own'
123 when 'own'
124 user_ids = [user.id] + user.groups.map(&:id).compact
124 user_ids = [user.id] + user.groups.map(&:id).compact
125 "(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
125 "(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
126 else
126 else
127 '1=0'
127 '1=0'
128 end
128 end
129 else
129 else
130 "(#{table_name}.is_private = #{connection.quoted_false})"
130 "(#{table_name}.is_private = #{connection.quoted_false})"
131 end
131 end
132 end
132 end
133 end
133 end
134
134
135 # Returns true if usr or current user is allowed to view the issue
135 # Returns true if usr or current user is allowed to view the issue
136 def visible?(usr=nil)
136 def visible?(usr=nil)
137 (usr || User.current).allowed_to?(:view_issues, self.project) do |role, user|
137 (usr || User.current).allowed_to?(:view_issues, self.project) do |role, user|
138 if user.logged?
138 if user.logged?
139 case role.issues_visibility
139 case role.issues_visibility
140 when 'all'
140 when 'all'
141 true
141 true
142 when 'default'
142 when 'default'
143 !self.is_private? || (self.author == user || user.is_or_belongs_to?(assigned_to))
143 !self.is_private? || (self.author == user || user.is_or_belongs_to?(assigned_to))
144 when 'own'
144 when 'own'
145 self.author == user || user.is_or_belongs_to?(assigned_to)
145 self.author == user || user.is_or_belongs_to?(assigned_to)
146 else
146 else
147 false
147 false
148 end
148 end
149 else
149 else
150 !self.is_private?
150 !self.is_private?
151 end
151 end
152 end
152 end
153 end
153 end
154
154
155 # Returns true if user or current user is allowed to edit or add a note to the issue
155 # Returns true if user or current user is allowed to edit or add a note to the issue
156 def editable?(user=User.current)
156 def editable?(user=User.current)
157 attributes_editable?(user) || user.allowed_to?(:add_issue_notes, project)
157 attributes_editable?(user) || user.allowed_to?(:add_issue_notes, project)
158 end
158 end
159
159
160 # Returns true if user or current user is allowed to edit the issue
160 # Returns true if user or current user is allowed to edit the issue
161 def attributes_editable?(user=User.current)
161 def attributes_editable?(user=User.current)
162 user.allowed_to?(:edit_issues, project)
162 user.allowed_to?(:edit_issues, project)
163 end
163 end
164
164
165 def initialize(attributes=nil, *args)
165 def initialize(attributes=nil, *args)
166 super
166 super
167 if new_record?
167 if new_record?
168 # set default values for new records only
168 # set default values for new records only
169 self.priority ||= IssuePriority.default
169 self.priority ||= IssuePriority.default
170 self.watcher_user_ids = []
170 self.watcher_user_ids = []
171 end
171 end
172 end
172 end
173
173
174 def create_or_update
174 def create_or_update
175 super
175 super
176 ensure
176 ensure
177 @status_was = nil
177 @status_was = nil
178 end
178 end
179 private :create_or_update
179 private :create_or_update
180
180
181 # AR#Persistence#destroy would raise and RecordNotFound exception
181 # AR#Persistence#destroy would raise and RecordNotFound exception
182 # if the issue was already deleted or updated (non matching lock_version).
182 # if the issue was already deleted or updated (non matching lock_version).
183 # This is a problem when bulk deleting issues or deleting a project
183 # This is a problem when bulk deleting issues or deleting a project
184 # (because an issue may already be deleted if its parent was deleted
184 # (because an issue may already be deleted if its parent was deleted
185 # first).
185 # first).
186 # The issue is reloaded by the nested_set before being deleted so
186 # The issue is reloaded by the nested_set before being deleted so
187 # the lock_version condition should not be an issue but we handle it.
187 # the lock_version condition should not be an issue but we handle it.
188 def destroy
188 def destroy
189 super
189 super
190 rescue ActiveRecord::StaleObjectError, ActiveRecord::RecordNotFound
190 rescue ActiveRecord::StaleObjectError, ActiveRecord::RecordNotFound
191 # Stale or already deleted
191 # Stale or already deleted
192 begin
192 begin
193 reload
193 reload
194 rescue ActiveRecord::RecordNotFound
194 rescue ActiveRecord::RecordNotFound
195 # The issue was actually already deleted
195 # The issue was actually already deleted
196 @destroyed = true
196 @destroyed = true
197 return freeze
197 return freeze
198 end
198 end
199 # The issue was stale, retry to destroy
199 # The issue was stale, retry to destroy
200 super
200 super
201 end
201 end
202
202
203 alias :base_reload :reload
203 alias :base_reload :reload
204 def reload(*args)
204 def reload(*args)
205 @workflow_rule_by_attribute = nil
205 @workflow_rule_by_attribute = nil
206 @assignable_versions = nil
206 @assignable_versions = nil
207 @relations = nil
207 @relations = nil
208 @spent_hours = nil
208 @spent_hours = nil
209 @total_spent_hours = nil
209 @total_spent_hours = nil
210 @total_estimated_hours = nil
210 @total_estimated_hours = nil
211 base_reload(*args)
211 base_reload(*args)
212 end
212 end
213
213
214 # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
214 # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
215 def available_custom_fields
215 def available_custom_fields
216 (project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields) : []
216 (project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields) : []
217 end
217 end
218
218
219 def visible_custom_field_values(user=nil)
219 def visible_custom_field_values(user=nil)
220 user_real = user || User.current
220 user_real = user || User.current
221 custom_field_values.select do |value|
221 custom_field_values.select do |value|
222 value.custom_field.visible_by?(project, user_real)
222 value.custom_field.visible_by?(project, user_real)
223 end
223 end
224 end
224 end
225
225
226 # Copies attributes from another issue, arg can be an id or an Issue
226 # Copies attributes from another issue, arg can be an id or an Issue
227 def copy_from(arg, options={})
227 def copy_from(arg, options={})
228 issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg)
228 issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg)
229 self.attributes = issue.attributes.dup.except("id", "root_id", "parent_id", "lft", "rgt", "created_on", "updated_on")
229 self.attributes = issue.attributes.dup.except("id", "root_id", "parent_id", "lft", "rgt", "created_on", "updated_on")
230 self.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
230 self.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
231 self.status = issue.status
231 self.status = issue.status
232 self.author = User.current
232 self.author = User.current
233 unless options[:attachments] == false
233 unless options[:attachments] == false
234 self.attachments = issue.attachments.map do |attachement|
234 self.attachments = issue.attachments.map do |attachement|
235 attachement.copy(:container => self)
235 attachement.copy(:container => self)
236 end
236 end
237 end
237 end
238 @copied_from = issue
238 @copied_from = issue
239 @copy_options = options
239 @copy_options = options
240 self
240 self
241 end
241 end
242
242
243 # Returns an unsaved copy of the issue
243 # Returns an unsaved copy of the issue
244 def copy(attributes=nil, copy_options={})
244 def copy(attributes=nil, copy_options={})
245 copy = self.class.new.copy_from(self, copy_options)
245 copy = self.class.new.copy_from(self, copy_options)
246 copy.attributes = attributes if attributes
246 copy.attributes = attributes if attributes
247 copy
247 copy
248 end
248 end
249
249
250 # Returns true if the issue is a copy
250 # Returns true if the issue is a copy
251 def copy?
251 def copy?
252 @copied_from.present?
252 @copied_from.present?
253 end
253 end
254
254
255 def status_id=(status_id)
255 def status_id=(status_id)
256 if status_id.to_s != self.status_id.to_s
256 if status_id.to_s != self.status_id.to_s
257 self.status = (status_id.present? ? IssueStatus.find_by_id(status_id) : nil)
257 self.status = (status_id.present? ? IssueStatus.find_by_id(status_id) : nil)
258 end
258 end
259 self.status_id
259 self.status_id
260 end
260 end
261
261
262 # Sets the status.
262 # Sets the status.
263 def status=(status)
263 def status=(status)
264 if status != self.status
264 if status != self.status
265 @workflow_rule_by_attribute = nil
265 @workflow_rule_by_attribute = nil
266 end
266 end
267 association(:status).writer(status)
267 association(:status).writer(status)
268 end
268 end
269
269
270 def priority_id=(pid)
270 def priority_id=(pid)
271 self.priority = nil
271 self.priority = nil
272 write_attribute(:priority_id, pid)
272 write_attribute(:priority_id, pid)
273 end
273 end
274
274
275 def category_id=(cid)
275 def category_id=(cid)
276 self.category = nil
276 self.category = nil
277 write_attribute(:category_id, cid)
277 write_attribute(:category_id, cid)
278 end
278 end
279
279
280 def fixed_version_id=(vid)
280 def fixed_version_id=(vid)
281 self.fixed_version = nil
281 self.fixed_version = nil
282 write_attribute(:fixed_version_id, vid)
282 write_attribute(:fixed_version_id, vid)
283 end
283 end
284
284
285 def tracker_id=(tracker_id)
285 def tracker_id=(tracker_id)
286 if tracker_id.to_s != self.tracker_id.to_s
286 if tracker_id.to_s != self.tracker_id.to_s
287 self.tracker = (tracker_id.present? ? Tracker.find_by_id(tracker_id) : nil)
287 self.tracker = (tracker_id.present? ? Tracker.find_by_id(tracker_id) : nil)
288 end
288 end
289 self.tracker_id
289 self.tracker_id
290 end
290 end
291
291
292 # Sets the tracker.
292 # Sets the tracker.
293 # This will set the status to the default status of the new tracker if:
293 # This will set the status to the default status of the new tracker if:
294 # * the status was the default for the previous tracker
294 # * the status was the default for the previous tracker
295 # * or if the status was not part of the new tracker statuses
295 # * or if the status was not part of the new tracker statuses
296 # * or the status was nil
296 # * or the status was nil
297 def tracker=(tracker)
297 def tracker=(tracker)
298 if tracker != self.tracker
298 if tracker != self.tracker
299 if status == default_status
299 if status == default_status
300 self.status = nil
300 self.status = nil
301 elsif status && tracker && !tracker.issue_status_ids.include?(status.id)
301 elsif status && tracker && !tracker.issue_status_ids.include?(status.id)
302 self.status = nil
302 self.status = nil
303 end
303 end
304 @custom_field_values = nil
304 @custom_field_values = nil
305 @workflow_rule_by_attribute = nil
305 @workflow_rule_by_attribute = nil
306 end
306 end
307 association(:tracker).writer(tracker)
307 association(:tracker).writer(tracker)
308 self.status ||= default_status
308 self.status ||= default_status
309 self.tracker
309 self.tracker
310 end
310 end
311
311
312 def project_id=(project_id)
312 def project_id=(project_id)
313 if project_id.to_s != self.project_id.to_s
313 if project_id.to_s != self.project_id.to_s
314 self.project = (project_id.present? ? Project.find_by_id(project_id) : nil)
314 self.project = (project_id.present? ? Project.find_by_id(project_id) : nil)
315 end
315 end
316 self.project_id
316 self.project_id
317 end
317 end
318
318
319 # Sets the project.
319 # Sets the project.
320 # Unless keep_tracker argument is set to true, this will change the tracker
320 # Unless keep_tracker argument is set to true, this will change the tracker
321 # to the first tracker of the new project if the previous tracker is not part
321 # to the first tracker of the new project if the previous tracker is not part
322 # of the new project trackers.
322 # of the new project trackers.
323 # This will clear the fixed_version is it's no longer valid for the new project.
323 # This will clear the fixed_version is it's no longer valid for the new project.
324 # This will clear the parent issue if it's no longer valid for the new project.
324 # This will clear the parent issue if it's no longer valid for the new project.
325 # This will set the category to the category with the same name in the new
325 # This will set the category to the category with the same name in the new
326 # project if it exists, or clear it if it doesn't.
326 # project if it exists, or clear it if it doesn't.
327 def project=(project, keep_tracker=false)
327 def project=(project, keep_tracker=false)
328 project_was = self.project
328 project_was = self.project
329 association(:project).writer(project)
329 association(:project).writer(project)
330 if project_was && project && project_was != project
330 if project_was && project && project_was != project
331 @assignable_versions = nil
331 @assignable_versions = nil
332
332
333 unless keep_tracker || project.trackers.include?(tracker)
333 unless keep_tracker || project.trackers.include?(tracker)
334 self.tracker = project.trackers.first
334 self.tracker = project.trackers.first
335 end
335 end
336 # Reassign to the category with same name if any
336 # Reassign to the category with same name if any
337 if category
337 if category
338 self.category = project.issue_categories.find_by_name(category.name)
338 self.category = project.issue_categories.find_by_name(category.name)
339 end
339 end
340 # Keep the fixed_version if it's still valid in the new_project
340 # Keep the fixed_version if it's still valid in the new_project
341 if fixed_version && fixed_version.project != project && !project.shared_versions.include?(fixed_version)
341 if fixed_version && fixed_version.project != project && !project.shared_versions.include?(fixed_version)
342 self.fixed_version = nil
342 self.fixed_version = nil
343 end
343 end
344 # Clear the parent task if it's no longer valid
344 # Clear the parent task if it's no longer valid
345 unless valid_parent_project?
345 unless valid_parent_project?
346 self.parent_issue_id = nil
346 self.parent_issue_id = nil
347 end
347 end
348 @custom_field_values = nil
348 @custom_field_values = nil
349 @workflow_rule_by_attribute = nil
349 @workflow_rule_by_attribute = nil
350 end
350 end
351 self.project
351 self.project
352 end
352 end
353
353
354 def description=(arg)
354 def description=(arg)
355 if arg.is_a?(String)
355 if arg.is_a?(String)
356 arg = arg.gsub(/(\r\n|\n|\r)/, "\r\n")
356 arg = arg.gsub(/(\r\n|\n|\r)/, "\r\n")
357 end
357 end
358 write_attribute(:description, arg)
358 write_attribute(:description, arg)
359 end
359 end
360
360
361 # Overrides assign_attributes so that project and tracker get assigned first
361 # Overrides assign_attributes so that project and tracker get assigned first
362 def assign_attributes_with_project_and_tracker_first(new_attributes, *args)
362 def assign_attributes_with_project_and_tracker_first(new_attributes, *args)
363 return if new_attributes.nil?
363 return if new_attributes.nil?
364 attrs = new_attributes.dup
364 attrs = new_attributes.dup
365 attrs.stringify_keys!
365 attrs.stringify_keys!
366
366
367 %w(project project_id tracker tracker_id).each do |attr|
367 %w(project project_id tracker tracker_id).each do |attr|
368 if attrs.has_key?(attr)
368 if attrs.has_key?(attr)
369 send "#{attr}=", attrs.delete(attr)
369 send "#{attr}=", attrs.delete(attr)
370 end
370 end
371 end
371 end
372 send :assign_attributes_without_project_and_tracker_first, attrs, *args
372 send :assign_attributes_without_project_and_tracker_first, attrs, *args
373 end
373 end
374 # Do not redefine alias chain on reload (see #4838)
374 # Do not redefine alias chain on reload (see #4838)
375 alias_method_chain(:assign_attributes, :project_and_tracker_first) unless method_defined?(:assign_attributes_without_project_and_tracker_first)
375 alias_method_chain(:assign_attributes, :project_and_tracker_first) unless method_defined?(:assign_attributes_without_project_and_tracker_first)
376
376
377 def attributes=(new_attributes)
377 def attributes=(new_attributes)
378 assign_attributes new_attributes
378 assign_attributes new_attributes
379 end
379 end
380
380
381 def estimated_hours=(h)
381 def estimated_hours=(h)
382 write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
382 write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
383 end
383 end
384
384
385 safe_attributes 'project_id',
385 safe_attributes 'project_id',
386 'tracker_id',
386 'tracker_id',
387 'status_id',
387 'status_id',
388 'category_id',
388 'category_id',
389 'assigned_to_id',
389 'assigned_to_id',
390 'priority_id',
390 'priority_id',
391 'fixed_version_id',
391 'fixed_version_id',
392 'subject',
392 'subject',
393 'description',
393 'description',
394 'start_date',
394 'start_date',
395 'due_date',
395 'due_date',
396 'done_ratio',
396 'done_ratio',
397 'estimated_hours',
397 'estimated_hours',
398 'custom_field_values',
398 'custom_field_values',
399 'custom_fields',
399 'custom_fields',
400 'lock_version',
400 'lock_version',
401 'notes',
401 'notes',
402 :if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) }
402 :if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) }
403
403
404 safe_attributes 'notes',
404 safe_attributes 'notes',
405 :if => lambda {|issue, user| user.allowed_to?(:add_issue_notes, issue.project)}
405 :if => lambda {|issue, user| user.allowed_to?(:add_issue_notes, issue.project)}
406
406
407 safe_attributes 'private_notes',
407 safe_attributes 'private_notes',
408 :if => lambda {|issue, user| !issue.new_record? && user.allowed_to?(:set_notes_private, issue.project)}
408 :if => lambda {|issue, user| !issue.new_record? && user.allowed_to?(:set_notes_private, issue.project)}
409
409
410 safe_attributes 'watcher_user_ids',
410 safe_attributes 'watcher_user_ids',
411 :if => lambda {|issue, user| issue.new_record? && user.allowed_to?(:add_issue_watchers, issue.project)}
411 :if => lambda {|issue, user| issue.new_record? && user.allowed_to?(:add_issue_watchers, issue.project)}
412
412
413 safe_attributes 'is_private',
413 safe_attributes 'is_private',
414 :if => lambda {|issue, user|
414 :if => lambda {|issue, user|
415 user.allowed_to?(:set_issues_private, issue.project) ||
415 user.allowed_to?(:set_issues_private, issue.project) ||
416 (issue.author_id == user.id && user.allowed_to?(:set_own_issues_private, issue.project))
416 (issue.author_id == user.id && user.allowed_to?(:set_own_issues_private, issue.project))
417 }
417 }
418
418
419 safe_attributes 'parent_issue_id',
419 safe_attributes 'parent_issue_id',
420 :if => lambda {|issue, user| (issue.new_record? || user.allowed_to?(:edit_issues, issue.project)) &&
420 :if => lambda {|issue, user| (issue.new_record? || user.allowed_to?(:edit_issues, issue.project)) &&
421 user.allowed_to?(:manage_subtasks, issue.project)}
421 user.allowed_to?(:manage_subtasks, issue.project)}
422
422
423 def safe_attribute_names(user=nil)
423 def safe_attribute_names(user=nil)
424 names = super
424 names = super
425 names -= disabled_core_fields
425 names -= disabled_core_fields
426 names -= read_only_attribute_names(user)
426 names -= read_only_attribute_names(user)
427 if new_record?
427 if new_record?
428 # Make sure that project_id can always be set for new issues
428 # Make sure that project_id can always be set for new issues
429 names |= %w(project_id)
429 names |= %w(project_id)
430 end
430 end
431 if dates_derived?
431 if dates_derived?
432 names -= %w(start_date due_date)
432 names -= %w(start_date due_date)
433 end
433 end
434 if priority_derived?
434 if priority_derived?
435 names -= %w(priority_id)
435 names -= %w(priority_id)
436 end
436 end
437 if done_ratio_derived?
437 if done_ratio_derived?
438 names -= %w(done_ratio)
438 names -= %w(done_ratio)
439 end
439 end
440 names
440 names
441 end
441 end
442
442
443 # Safely sets attributes
443 # Safely sets attributes
444 # Should be called from controllers instead of #attributes=
444 # Should be called from controllers instead of #attributes=
445 # attr_accessible is too rough because we still want things like
445 # attr_accessible is too rough because we still want things like
446 # Issue.new(:project => foo) to work
446 # Issue.new(:project => foo) to work
447 def safe_attributes=(attrs, user=User.current)
447 def safe_attributes=(attrs, user=User.current)
448 return unless attrs.is_a?(Hash)
448 return unless attrs.is_a?(Hash)
449
449
450 attrs = attrs.deep_dup
450 attrs = attrs.deep_dup
451
451
452 # Project and Tracker must be set before since new_statuses_allowed_to depends on it.
452 # Project and Tracker must be set before since new_statuses_allowed_to depends on it.
453 if (p = attrs.delete('project_id')) && safe_attribute?('project_id')
453 if (p = attrs.delete('project_id')) && safe_attribute?('project_id')
454 if allowed_target_projects(user).where(:id => p.to_i).exists?
454 if allowed_target_projects(user).where(:id => p.to_i).exists?
455 self.project_id = p
455 self.project_id = p
456 end
456 end
457 end
457 end
458
458
459 if (t = attrs.delete('tracker_id')) && safe_attribute?('tracker_id')
459 if (t = attrs.delete('tracker_id')) && safe_attribute?('tracker_id')
460 self.tracker_id = t
460 self.tracker_id = t
461 end
461 end
462 if project
462 if project
463 # Set the default tracker to accept custom field values
463 # Set the default tracker to accept custom field values
464 # even if tracker is not specified
464 # even if tracker is not specified
465 self.tracker ||= project.trackers.first
465 self.tracker ||= project.trackers.first
466 end
466 end
467
467
468 if (s = attrs.delete('status_id')) && safe_attribute?('status_id')
468 if (s = attrs.delete('status_id')) && safe_attribute?('status_id')
469 if new_statuses_allowed_to(user).collect(&:id).include?(s.to_i)
469 if new_statuses_allowed_to(user).collect(&:id).include?(s.to_i)
470 self.status_id = s
470 self.status_id = s
471 end
471 end
472 end
472 end
473 if (u = attrs.delete('assigned_to_id')) && safe_attribute?('assigned_to_id')
474 if u.blank?
475 self.assigned_to_id = nil
476 else
477 u = u.to_i
478 if assignable_users.any?{|assignable_user| assignable_user.id == u}
479 self.assigned_to_id = u
480 end
481 end
482 end
483
473
484
474 attrs = delete_unsafe_attributes(attrs, user)
485 attrs = delete_unsafe_attributes(attrs, user)
475 return if attrs.empty?
486 return if attrs.empty?
476
487
477 if attrs['parent_issue_id'].present?
488 if attrs['parent_issue_id'].present?
478 s = attrs['parent_issue_id'].to_s
489 s = attrs['parent_issue_id'].to_s
479 unless (m = s.match(%r{\A#?(\d+)\z})) && (m[1] == parent_id.to_s || Issue.visible(user).exists?(m[1]))
490 unless (m = s.match(%r{\A#?(\d+)\z})) && (m[1] == parent_id.to_s || Issue.visible(user).exists?(m[1]))
480 @invalid_parent_issue_id = attrs.delete('parent_issue_id')
491 @invalid_parent_issue_id = attrs.delete('parent_issue_id')
481 end
492 end
482 end
493 end
483
494
484 if attrs['custom_field_values'].present?
495 if attrs['custom_field_values'].present?
485 editable_custom_field_ids = editable_custom_field_values(user).map {|v| v.custom_field_id.to_s}
496 editable_custom_field_ids = editable_custom_field_values(user).map {|v| v.custom_field_id.to_s}
486 attrs['custom_field_values'].select! {|k, v| editable_custom_field_ids.include?(k.to_s)}
497 attrs['custom_field_values'].select! {|k, v| editable_custom_field_ids.include?(k.to_s)}
487 end
498 end
488
499
489 if attrs['custom_fields'].present?
500 if attrs['custom_fields'].present?
490 editable_custom_field_ids = editable_custom_field_values(user).map {|v| v.custom_field_id.to_s}
501 editable_custom_field_ids = editable_custom_field_values(user).map {|v| v.custom_field_id.to_s}
491 attrs['custom_fields'].select! {|c| editable_custom_field_ids.include?(c['id'].to_s)}
502 attrs['custom_fields'].select! {|c| editable_custom_field_ids.include?(c['id'].to_s)}
492 end
503 end
493
504
494 # mass-assignment security bypass
505 # mass-assignment security bypass
495 assign_attributes attrs, :without_protection => true
506 assign_attributes attrs, :without_protection => true
496 end
507 end
497
508
498 def disabled_core_fields
509 def disabled_core_fields
499 tracker ? tracker.disabled_core_fields : []
510 tracker ? tracker.disabled_core_fields : []
500 end
511 end
501
512
502 # Returns the custom_field_values that can be edited by the given user
513 # Returns the custom_field_values that can be edited by the given user
503 def editable_custom_field_values(user=nil)
514 def editable_custom_field_values(user=nil)
504 visible_custom_field_values(user).reject do |value|
515 visible_custom_field_values(user).reject do |value|
505 read_only_attribute_names(user).include?(value.custom_field_id.to_s)
516 read_only_attribute_names(user).include?(value.custom_field_id.to_s)
506 end
517 end
507 end
518 end
508
519
509 # Returns the custom fields that can be edited by the given user
520 # Returns the custom fields that can be edited by the given user
510 def editable_custom_fields(user=nil)
521 def editable_custom_fields(user=nil)
511 editable_custom_field_values(user).map(&:custom_field).uniq
522 editable_custom_field_values(user).map(&:custom_field).uniq
512 end
523 end
513
524
514 # Returns the names of attributes that are read-only for user or the current user
525 # Returns the names of attributes that are read-only for user or the current user
515 # For users with multiple roles, the read-only fields are the intersection of
526 # For users with multiple roles, the read-only fields are the intersection of
516 # read-only fields of each role
527 # read-only fields of each role
517 # The result is an array of strings where sustom fields are represented with their ids
528 # The result is an array of strings where sustom fields are represented with their ids
518 #
529 #
519 # Examples:
530 # Examples:
520 # issue.read_only_attribute_names # => ['due_date', '2']
531 # issue.read_only_attribute_names # => ['due_date', '2']
521 # issue.read_only_attribute_names(user) # => []
532 # issue.read_only_attribute_names(user) # => []
522 def read_only_attribute_names(user=nil)
533 def read_only_attribute_names(user=nil)
523 workflow_rule_by_attribute(user).reject {|attr, rule| rule != 'readonly'}.keys
534 workflow_rule_by_attribute(user).reject {|attr, rule| rule != 'readonly'}.keys
524 end
535 end
525
536
526 # Returns the names of required attributes for user or the current user
537 # Returns the names of required attributes for user or the current user
527 # For users with multiple roles, the required fields are the intersection of
538 # For users with multiple roles, the required fields are the intersection of
528 # required fields of each role
539 # required fields of each role
529 # The result is an array of strings where sustom fields are represented with their ids
540 # The result is an array of strings where sustom fields are represented with their ids
530 #
541 #
531 # Examples:
542 # Examples:
532 # issue.required_attribute_names # => ['due_date', '2']
543 # issue.required_attribute_names # => ['due_date', '2']
533 # issue.required_attribute_names(user) # => []
544 # issue.required_attribute_names(user) # => []
534 def required_attribute_names(user=nil)
545 def required_attribute_names(user=nil)
535 workflow_rule_by_attribute(user).reject {|attr, rule| rule != 'required'}.keys
546 workflow_rule_by_attribute(user).reject {|attr, rule| rule != 'required'}.keys
536 end
547 end
537
548
538 # Returns true if the attribute is required for user
549 # Returns true if the attribute is required for user
539 def required_attribute?(name, user=nil)
550 def required_attribute?(name, user=nil)
540 required_attribute_names(user).include?(name.to_s)
551 required_attribute_names(user).include?(name.to_s)
541 end
552 end
542
553
543 # Returns a hash of the workflow rule by attribute for the given user
554 # Returns a hash of the workflow rule by attribute for the given user
544 #
555 #
545 # Examples:
556 # Examples:
546 # issue.workflow_rule_by_attribute # => {'due_date' => 'required', 'start_date' => 'readonly'}
557 # issue.workflow_rule_by_attribute # => {'due_date' => 'required', 'start_date' => 'readonly'}
547 def workflow_rule_by_attribute(user=nil)
558 def workflow_rule_by_attribute(user=nil)
548 return @workflow_rule_by_attribute if @workflow_rule_by_attribute && user.nil?
559 return @workflow_rule_by_attribute if @workflow_rule_by_attribute && user.nil?
549
560
550 user_real = user || User.current
561 user_real = user || User.current
551 roles = user_real.admin ? Role.all.to_a : user_real.roles_for_project(project)
562 roles = user_real.admin ? Role.all.to_a : user_real.roles_for_project(project)
552 roles = roles.select(&:consider_workflow?)
563 roles = roles.select(&:consider_workflow?)
553 return {} if roles.empty?
564 return {} if roles.empty?
554
565
555 result = {}
566 result = {}
556 workflow_permissions = WorkflowPermission.where(:tracker_id => tracker_id, :old_status_id => status_id, :role_id => roles.map(&:id)).to_a
567 workflow_permissions = WorkflowPermission.where(:tracker_id => tracker_id, :old_status_id => status_id, :role_id => roles.map(&:id)).to_a
557 if workflow_permissions.any?
568 if workflow_permissions.any?
558 workflow_rules = workflow_permissions.inject({}) do |h, wp|
569 workflow_rules = workflow_permissions.inject({}) do |h, wp|
559 h[wp.field_name] ||= {}
570 h[wp.field_name] ||= {}
560 h[wp.field_name][wp.role_id] = wp.rule
571 h[wp.field_name][wp.role_id] = wp.rule
561 h
572 h
562 end
573 end
563 fields_with_roles = {}
574 fields_with_roles = {}
564 IssueCustomField.where(:visible => false).joins(:roles).pluck(:id, "role_id").each do |field_id, role_id|
575 IssueCustomField.where(:visible => false).joins(:roles).pluck(:id, "role_id").each do |field_id, role_id|
565 fields_with_roles[field_id] ||= []
576 fields_with_roles[field_id] ||= []
566 fields_with_roles[field_id] << role_id
577 fields_with_roles[field_id] << role_id
567 end
578 end
568 roles.each do |role|
579 roles.each do |role|
569 fields_with_roles.each do |field_id, role_ids|
580 fields_with_roles.each do |field_id, role_ids|
570 unless role_ids.include?(role.id)
581 unless role_ids.include?(role.id)
571 field_name = field_id.to_s
582 field_name = field_id.to_s
572 workflow_rules[field_name] ||= {}
583 workflow_rules[field_name] ||= {}
573 workflow_rules[field_name][role.id] = 'readonly'
584 workflow_rules[field_name][role.id] = 'readonly'
574 end
585 end
575 end
586 end
576 end
587 end
577 workflow_rules.each do |attr, rules|
588 workflow_rules.each do |attr, rules|
578 next if rules.size < roles.size
589 next if rules.size < roles.size
579 uniq_rules = rules.values.uniq
590 uniq_rules = rules.values.uniq
580 if uniq_rules.size == 1
591 if uniq_rules.size == 1
581 result[attr] = uniq_rules.first
592 result[attr] = uniq_rules.first
582 else
593 else
583 result[attr] = 'required'
594 result[attr] = 'required'
584 end
595 end
585 end
596 end
586 end
597 end
587 @workflow_rule_by_attribute = result if user.nil?
598 @workflow_rule_by_attribute = result if user.nil?
588 result
599 result
589 end
600 end
590 private :workflow_rule_by_attribute
601 private :workflow_rule_by_attribute
591
602
592 def done_ratio
603 def done_ratio
593 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
604 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
594 status.default_done_ratio
605 status.default_done_ratio
595 else
606 else
596 read_attribute(:done_ratio)
607 read_attribute(:done_ratio)
597 end
608 end
598 end
609 end
599
610
600 def self.use_status_for_done_ratio?
611 def self.use_status_for_done_ratio?
601 Setting.issue_done_ratio == 'issue_status'
612 Setting.issue_done_ratio == 'issue_status'
602 end
613 end
603
614
604 def self.use_field_for_done_ratio?
615 def self.use_field_for_done_ratio?
605 Setting.issue_done_ratio == 'issue_field'
616 Setting.issue_done_ratio == 'issue_field'
606 end
617 end
607
618
608 def validate_issue
619 def validate_issue
609 if due_date && start_date && (start_date_changed? || due_date_changed?) && due_date < start_date
620 if due_date && start_date && (start_date_changed? || due_date_changed?) && due_date < start_date
610 errors.add :due_date, :greater_than_start_date
621 errors.add :due_date, :greater_than_start_date
611 end
622 end
612
623
613 if start_date && start_date_changed? && soonest_start && start_date < soonest_start
624 if start_date && start_date_changed? && soonest_start && start_date < soonest_start
614 errors.add :start_date, :earlier_than_minimum_start_date, :date => format_date(soonest_start)
625 errors.add :start_date, :earlier_than_minimum_start_date, :date => format_date(soonest_start)
615 end
626 end
616
627
617 if fixed_version
628 if fixed_version
618 if !assignable_versions.include?(fixed_version)
629 if !assignable_versions.include?(fixed_version)
619 errors.add :fixed_version_id, :inclusion
630 errors.add :fixed_version_id, :inclusion
620 elsif reopening? && fixed_version.closed?
631 elsif reopening? && fixed_version.closed?
621 errors.add :base, I18n.t(:error_can_not_reopen_issue_on_closed_version)
632 errors.add :base, I18n.t(:error_can_not_reopen_issue_on_closed_version)
622 end
633 end
623 end
634 end
624
635
625 # Checks that the issue can not be added/moved to a disabled tracker
636 # Checks that the issue can not be added/moved to a disabled tracker
626 if project && (tracker_id_changed? || project_id_changed?)
637 if project && (tracker_id_changed? || project_id_changed?)
627 unless project.trackers.include?(tracker)
638 unless project.trackers.include?(tracker)
628 errors.add :tracker_id, :inclusion
639 errors.add :tracker_id, :inclusion
629 end
640 end
630 end
641 end
631
642
632 # Checks parent issue assignment
643 # Checks parent issue assignment
633 if @invalid_parent_issue_id.present?
644 if @invalid_parent_issue_id.present?
634 errors.add :parent_issue_id, :invalid
645 errors.add :parent_issue_id, :invalid
635 elsif @parent_issue
646 elsif @parent_issue
636 if !valid_parent_project?(@parent_issue)
647 if !valid_parent_project?(@parent_issue)
637 errors.add :parent_issue_id, :invalid
648 errors.add :parent_issue_id, :invalid
638 elsif (@parent_issue != parent) && (all_dependent_issues.include?(@parent_issue) || @parent_issue.all_dependent_issues.include?(self))
649 elsif (@parent_issue != parent) && (all_dependent_issues.include?(@parent_issue) || @parent_issue.all_dependent_issues.include?(self))
639 errors.add :parent_issue_id, :invalid
650 errors.add :parent_issue_id, :invalid
640 elsif !new_record?
651 elsif !new_record?
641 # moving an existing issue
652 # moving an existing issue
642 if move_possible?(@parent_issue)
653 if move_possible?(@parent_issue)
643 # move accepted
654 # move accepted
644 else
655 else
645 errors.add :parent_issue_id, :invalid
656 errors.add :parent_issue_id, :invalid
646 end
657 end
647 end
658 end
648 end
659 end
649 end
660 end
650
661
651 # Validates the issue against additional workflow requirements
662 # Validates the issue against additional workflow requirements
652 def validate_required_fields
663 def validate_required_fields
653 user = new_record? ? author : current_journal.try(:user)
664 user = new_record? ? author : current_journal.try(:user)
654
665
655 required_attribute_names(user).each do |attribute|
666 required_attribute_names(user).each do |attribute|
656 if attribute =~ /^\d+$/
667 if attribute =~ /^\d+$/
657 attribute = attribute.to_i
668 attribute = attribute.to_i
658 v = custom_field_values.detect {|v| v.custom_field_id == attribute }
669 v = custom_field_values.detect {|v| v.custom_field_id == attribute }
659 if v && Array(v.value).detect(&:present?).nil?
670 if v && Array(v.value).detect(&:present?).nil?
660 errors.add :base, v.custom_field.name + ' ' + l('activerecord.errors.messages.blank')
671 errors.add :base, v.custom_field.name + ' ' + l('activerecord.errors.messages.blank')
661 end
672 end
662 else
673 else
663 if respond_to?(attribute) && send(attribute).blank? && !disabled_core_fields.include?(attribute)
674 if respond_to?(attribute) && send(attribute).blank? && !disabled_core_fields.include?(attribute)
664 errors.add attribute, :blank
675 errors.add attribute, :blank
665 end
676 end
666 end
677 end
667 end
678 end
668 end
679 end
669
680
670 # Overrides Redmine::Acts::Customizable::InstanceMethods#validate_custom_field_values
681 # Overrides Redmine::Acts::Customizable::InstanceMethods#validate_custom_field_values
671 # so that custom values that are not editable are not validated (eg. a custom field that
682 # so that custom values that are not editable are not validated (eg. a custom field that
672 # is marked as required should not trigger a validation error if the user is not allowed
683 # is marked as required should not trigger a validation error if the user is not allowed
673 # to edit this field).
684 # to edit this field).
674 def validate_custom_field_values
685 def validate_custom_field_values
675 user = new_record? ? author : current_journal.try(:user)
686 user = new_record? ? author : current_journal.try(:user)
676 if new_record? || custom_field_values_changed?
687 if new_record? || custom_field_values_changed?
677 editable_custom_field_values(user).each(&:validate_value)
688 editable_custom_field_values(user).each(&:validate_value)
678 end
689 end
679 end
690 end
680
691
681 # Set the done_ratio using the status if that setting is set. This will keep the done_ratios
692 # Set the done_ratio using the status if that setting is set. This will keep the done_ratios
682 # even if the user turns off the setting later
693 # even if the user turns off the setting later
683 def update_done_ratio_from_issue_status
694 def update_done_ratio_from_issue_status
684 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
695 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
685 self.done_ratio = status.default_done_ratio
696 self.done_ratio = status.default_done_ratio
686 end
697 end
687 end
698 end
688
699
689 def init_journal(user, notes = "")
700 def init_journal(user, notes = "")
690 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
701 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
691 end
702 end
692
703
693 # Returns the current journal or nil if it's not initialized
704 # Returns the current journal or nil if it's not initialized
694 def current_journal
705 def current_journal
695 @current_journal
706 @current_journal
696 end
707 end
697
708
698 # Returns the names of attributes that are journalized when updating the issue
709 # Returns the names of attributes that are journalized when updating the issue
699 def journalized_attribute_names
710 def journalized_attribute_names
700 names = Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on closed_on)
711 names = Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on closed_on)
701 if tracker
712 if tracker
702 names -= tracker.disabled_core_fields
713 names -= tracker.disabled_core_fields
703 end
714 end
704 names
715 names
705 end
716 end
706
717
707 # Returns the id of the last journal or nil
718 # Returns the id of the last journal or nil
708 def last_journal_id
719 def last_journal_id
709 if new_record?
720 if new_record?
710 nil
721 nil
711 else
722 else
712 journals.maximum(:id)
723 journals.maximum(:id)
713 end
724 end
714 end
725 end
715
726
716 # Returns a scope for journals that have an id greater than journal_id
727 # Returns a scope for journals that have an id greater than journal_id
717 def journals_after(journal_id)
728 def journals_after(journal_id)
718 scope = journals.reorder("#{Journal.table_name}.id ASC")
729 scope = journals.reorder("#{Journal.table_name}.id ASC")
719 if journal_id.present?
730 if journal_id.present?
720 scope = scope.where("#{Journal.table_name}.id > ?", journal_id.to_i)
731 scope = scope.where("#{Journal.table_name}.id > ?", journal_id.to_i)
721 end
732 end
722 scope
733 scope
723 end
734 end
724
735
725 # Returns the initial status of the issue
736 # Returns the initial status of the issue
726 # Returns nil for a new issue
737 # Returns nil for a new issue
727 def status_was
738 def status_was
728 if status_id_changed?
739 if status_id_changed?
729 if status_id_was.to_i > 0
740 if status_id_was.to_i > 0
730 @status_was ||= IssueStatus.find_by_id(status_id_was)
741 @status_was ||= IssueStatus.find_by_id(status_id_was)
731 end
742 end
732 else
743 else
733 @status_was ||= status
744 @status_was ||= status
734 end
745 end
735 end
746 end
736
747
737 # Return true if the issue is closed, otherwise false
748 # Return true if the issue is closed, otherwise false
738 def closed?
749 def closed?
739 status.present? && status.is_closed?
750 status.present? && status.is_closed?
740 end
751 end
741
752
742 # Returns true if the issue was closed when loaded
753 # Returns true if the issue was closed when loaded
743 def was_closed?
754 def was_closed?
744 status_was.present? && status_was.is_closed?
755 status_was.present? && status_was.is_closed?
745 end
756 end
746
757
747 # Return true if the issue is being reopened
758 # Return true if the issue is being reopened
748 def reopening?
759 def reopening?
749 if new_record?
760 if new_record?
750 false
761 false
751 else
762 else
752 status_id_changed? && !closed? && was_closed?
763 status_id_changed? && !closed? && was_closed?
753 end
764 end
754 end
765 end
755 alias :reopened? :reopening?
766 alias :reopened? :reopening?
756
767
757 # Return true if the issue is being closed
768 # Return true if the issue is being closed
758 def closing?
769 def closing?
759 if new_record?
770 if new_record?
760 closed?
771 closed?
761 else
772 else
762 status_id_changed? && closed? && !was_closed?
773 status_id_changed? && closed? && !was_closed?
763 end
774 end
764 end
775 end
765
776
766 # Returns true if the issue is overdue
777 # Returns true if the issue is overdue
767 def overdue?
778 def overdue?
768 due_date.present? && (due_date < Date.today) && !closed?
779 due_date.present? && (due_date < Date.today) && !closed?
769 end
780 end
770
781
771 # Is the amount of work done less than it should for the due date
782 # Is the amount of work done less than it should for the due date
772 def behind_schedule?
783 def behind_schedule?
773 return false if start_date.nil? || due_date.nil?
784 return false if start_date.nil? || due_date.nil?
774 done_date = start_date + ((due_date - start_date + 1) * done_ratio / 100).floor
785 done_date = start_date + ((due_date - start_date + 1) * done_ratio / 100).floor
775 return done_date <= Date.today
786 return done_date <= Date.today
776 end
787 end
777
788
778 # Does this issue have children?
789 # Does this issue have children?
779 def children?
790 def children?
780 !leaf?
791 !leaf?
781 end
792 end
782
793
783 # Users the issue can be assigned to
794 # Users the issue can be assigned to
784 def assignable_users
795 def assignable_users
785 users = project.assignable_users.to_a
796 users = project.assignable_users.to_a
786 users << author if author && author.active?
797 users << author if author && author.active?
787 users << assigned_to if assigned_to
798 users << assigned_to if assigned_to
788 users.uniq.sort
799 users.uniq.sort
789 end
800 end
790
801
791 # Versions that the issue can be assigned to
802 # Versions that the issue can be assigned to
792 def assignable_versions
803 def assignable_versions
793 return @assignable_versions if @assignable_versions
804 return @assignable_versions if @assignable_versions
794
805
795 versions = project.shared_versions.open.to_a
806 versions = project.shared_versions.open.to_a
796 if fixed_version
807 if fixed_version
797 if fixed_version_id_changed?
808 if fixed_version_id_changed?
798 # nothing to do
809 # nothing to do
799 elsif project_id_changed?
810 elsif project_id_changed?
800 if project.shared_versions.include?(fixed_version)
811 if project.shared_versions.include?(fixed_version)
801 versions << fixed_version
812 versions << fixed_version
802 end
813 end
803 else
814 else
804 versions << fixed_version
815 versions << fixed_version
805 end
816 end
806 end
817 end
807 @assignable_versions = versions.uniq.sort
818 @assignable_versions = versions.uniq.sort
808 end
819 end
809
820
810 # Returns true if this issue is blocked by another issue that is still open
821 # Returns true if this issue is blocked by another issue that is still open
811 def blocked?
822 def blocked?
812 !relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil?
823 !relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil?
813 end
824 end
814
825
815 # Returns the default status of the issue based on its tracker
826 # Returns the default status of the issue based on its tracker
816 # Returns nil if tracker is nil
827 # Returns nil if tracker is nil
817 def default_status
828 def default_status
818 tracker.try(:default_status)
829 tracker.try(:default_status)
819 end
830 end
820
831
821 # Returns an array of statuses that user is able to apply
832 # Returns an array of statuses that user is able to apply
822 def new_statuses_allowed_to(user=User.current, include_default=false)
833 def new_statuses_allowed_to(user=User.current, include_default=false)
823 if new_record? && @copied_from
834 if new_record? && @copied_from
824 [default_status, @copied_from.status].compact.uniq.sort
835 [default_status, @copied_from.status].compact.uniq.sort
825 else
836 else
826 initial_status = nil
837 initial_status = nil
827 if new_record?
838 if new_record?
828 initial_status = default_status
839 initial_status = default_status
829 elsif tracker_id_changed?
840 elsif tracker_id_changed?
830 if Tracker.where(:id => tracker_id_was, :default_status_id => status_id_was).any?
841 if Tracker.where(:id => tracker_id_was, :default_status_id => status_id_was).any?
831 initial_status = default_status
842 initial_status = default_status
832 elsif tracker.issue_status_ids.include?(status_id_was)
843 elsif tracker.issue_status_ids.include?(status_id_was)
833 initial_status = IssueStatus.find_by_id(status_id_was)
844 initial_status = IssueStatus.find_by_id(status_id_was)
834 else
845 else
835 initial_status = default_status
846 initial_status = default_status
836 end
847 end
837 else
848 else
838 initial_status = status_was
849 initial_status = status_was
839 end
850 end
840
851
841 initial_assigned_to_id = assigned_to_id_changed? ? assigned_to_id_was : assigned_to_id
852 initial_assigned_to_id = assigned_to_id_changed? ? assigned_to_id_was : assigned_to_id
842 assignee_transitions_allowed = initial_assigned_to_id.present? &&
853 assignee_transitions_allowed = initial_assigned_to_id.present? &&
843 (user.id == initial_assigned_to_id || user.group_ids.include?(initial_assigned_to_id))
854 (user.id == initial_assigned_to_id || user.group_ids.include?(initial_assigned_to_id))
844
855
845 statuses = []
856 statuses = []
846 if initial_status
857 if initial_status
847 statuses += initial_status.find_new_statuses_allowed_to(
858 statuses += initial_status.find_new_statuses_allowed_to(
848 user.admin ? Role.all.to_a : user.roles_for_project(project),
859 user.admin ? Role.all.to_a : user.roles_for_project(project),
849 tracker,
860 tracker,
850 author == user,
861 author == user,
851 assignee_transitions_allowed
862 assignee_transitions_allowed
852 )
863 )
853 end
864 end
854 statuses << initial_status unless statuses.empty?
865 statuses << initial_status unless statuses.empty?
855 statuses << default_status if include_default
866 statuses << default_status if include_default
856 statuses = statuses.compact.uniq.sort
867 statuses = statuses.compact.uniq.sort
857 if blocked?
868 if blocked?
858 statuses.reject!(&:is_closed?)
869 statuses.reject!(&:is_closed?)
859 end
870 end
860 statuses
871 statuses
861 end
872 end
862 end
873 end
863
874
864 # Returns the previous assignee (user or group) if changed
875 # Returns the previous assignee (user or group) if changed
865 def assigned_to_was
876 def assigned_to_was
866 # assigned_to_id_was is reset before after_save callbacks
877 # assigned_to_id_was is reset before after_save callbacks
867 user_id = @previous_assigned_to_id || assigned_to_id_was
878 user_id = @previous_assigned_to_id || assigned_to_id_was
868 if user_id && user_id != assigned_to_id
879 if user_id && user_id != assigned_to_id
869 @assigned_to_was ||= Principal.find_by_id(user_id)
880 @assigned_to_was ||= Principal.find_by_id(user_id)
870 end
881 end
871 end
882 end
872
883
873 # Returns the users that should be notified
884 # Returns the users that should be notified
874 def notified_users
885 def notified_users
875 notified = []
886 notified = []
876 # Author and assignee are always notified unless they have been
887 # Author and assignee are always notified unless they have been
877 # locked or don't want to be notified
888 # locked or don't want to be notified
878 notified << author if author
889 notified << author if author
879 if assigned_to
890 if assigned_to
880 notified += (assigned_to.is_a?(Group) ? assigned_to.users : [assigned_to])
891 notified += (assigned_to.is_a?(Group) ? assigned_to.users : [assigned_to])
881 end
892 end
882 if assigned_to_was
893 if assigned_to_was
883 notified += (assigned_to_was.is_a?(Group) ? assigned_to_was.users : [assigned_to_was])
894 notified += (assigned_to_was.is_a?(Group) ? assigned_to_was.users : [assigned_to_was])
884 end
895 end
885 notified = notified.select {|u| u.active? && u.notify_about?(self)}
896 notified = notified.select {|u| u.active? && u.notify_about?(self)}
886
897
887 notified += project.notified_users
898 notified += project.notified_users
888 notified.uniq!
899 notified.uniq!
889 # Remove users that can not view the issue
900 # Remove users that can not view the issue
890 notified.reject! {|user| !visible?(user)}
901 notified.reject! {|user| !visible?(user)}
891 notified
902 notified
892 end
903 end
893
904
894 # Returns the email addresses that should be notified
905 # Returns the email addresses that should be notified
895 def recipients
906 def recipients
896 notified_users.collect(&:mail)
907 notified_users.collect(&:mail)
897 end
908 end
898
909
899 def each_notification(users, &block)
910 def each_notification(users, &block)
900 if users.any?
911 if users.any?
901 if custom_field_values.detect {|value| !value.custom_field.visible?}
912 if custom_field_values.detect {|value| !value.custom_field.visible?}
902 users_by_custom_field_visibility = users.group_by do |user|
913 users_by_custom_field_visibility = users.group_by do |user|
903 visible_custom_field_values(user).map(&:custom_field_id).sort
914 visible_custom_field_values(user).map(&:custom_field_id).sort
904 end
915 end
905 users_by_custom_field_visibility.values.each do |users|
916 users_by_custom_field_visibility.values.each do |users|
906 yield(users)
917 yield(users)
907 end
918 end
908 else
919 else
909 yield(users)
920 yield(users)
910 end
921 end
911 end
922 end
912 end
923 end
913
924
914 # Returns the number of hours spent on this issue
925 # Returns the number of hours spent on this issue
915 def spent_hours
926 def spent_hours
916 @spent_hours ||= time_entries.sum(:hours) || 0
927 @spent_hours ||= time_entries.sum(:hours) || 0
917 end
928 end
918
929
919 # Returns the total number of hours spent on this issue and its descendants
930 # Returns the total number of hours spent on this issue and its descendants
920 def total_spent_hours
931 def total_spent_hours
921 @total_spent_hours ||= if leaf?
932 @total_spent_hours ||= if leaf?
922 spent_hours
933 spent_hours
923 else
934 else
924 self_and_descendants.joins(:time_entries).sum("#{TimeEntry.table_name}.hours").to_f || 0.0
935 self_and_descendants.joins(:time_entries).sum("#{TimeEntry.table_name}.hours").to_f || 0.0
925 end
936 end
926 end
937 end
927
938
928 def total_estimated_hours
939 def total_estimated_hours
929 if leaf?
940 if leaf?
930 estimated_hours
941 estimated_hours
931 else
942 else
932 @total_estimated_hours ||= self_and_descendants.sum(:estimated_hours)
943 @total_estimated_hours ||= self_and_descendants.sum(:estimated_hours)
933 end
944 end
934 end
945 end
935
946
936 def relations
947 def relations
937 @relations ||= IssueRelation::Relations.new(self, (relations_from + relations_to).sort)
948 @relations ||= IssueRelation::Relations.new(self, (relations_from + relations_to).sort)
938 end
949 end
939
950
940 # Preloads relations for a collection of issues
951 # Preloads relations for a collection of issues
941 def self.load_relations(issues)
952 def self.load_relations(issues)
942 if issues.any?
953 if issues.any?
943 relations = IssueRelation.where("issue_from_id IN (:ids) OR issue_to_id IN (:ids)", :ids => issues.map(&:id)).all
954 relations = IssueRelation.where("issue_from_id IN (:ids) OR issue_to_id IN (:ids)", :ids => issues.map(&:id)).all
944 issues.each do |issue|
955 issues.each do |issue|
945 issue.instance_variable_set "@relations", relations.select {|r| r.issue_from_id == issue.id || r.issue_to_id == issue.id}
956 issue.instance_variable_set "@relations", relations.select {|r| r.issue_from_id == issue.id || r.issue_to_id == issue.id}
946 end
957 end
947 end
958 end
948 end
959 end
949
960
950 # Preloads visible spent time for a collection of issues
961 # Preloads visible spent time for a collection of issues
951 def self.load_visible_spent_hours(issues, user=User.current)
962 def self.load_visible_spent_hours(issues, user=User.current)
952 if issues.any?
963 if issues.any?
953 hours_by_issue_id = TimeEntry.visible(user).where(:issue_id => issues.map(&:id)).group(:issue_id).sum(:hours)
964 hours_by_issue_id = TimeEntry.visible(user).where(:issue_id => issues.map(&:id)).group(:issue_id).sum(:hours)
954 issues.each do |issue|
965 issues.each do |issue|
955 issue.instance_variable_set "@spent_hours", (hours_by_issue_id[issue.id] || 0)
966 issue.instance_variable_set "@spent_hours", (hours_by_issue_id[issue.id] || 0)
956 end
967 end
957 end
968 end
958 end
969 end
959
970
960 # Preloads visible total spent time for a collection of issues
971 # Preloads visible total spent time for a collection of issues
961 def self.load_visible_total_spent_hours(issues, user=User.current)
972 def self.load_visible_total_spent_hours(issues, user=User.current)
962 if issues.any?
973 if issues.any?
963 hours_by_issue_id = TimeEntry.visible(user).joins(:issue).
974 hours_by_issue_id = TimeEntry.visible(user).joins(:issue).
964 joins("JOIN #{Issue.table_name} parent ON parent.root_id = #{Issue.table_name}.root_id" +
975 joins("JOIN #{Issue.table_name} parent ON parent.root_id = #{Issue.table_name}.root_id" +
965 " AND parent.lft <= #{Issue.table_name}.lft AND parent.rgt >= #{Issue.table_name}.rgt").
976 " AND parent.lft <= #{Issue.table_name}.lft AND parent.rgt >= #{Issue.table_name}.rgt").
966 where("parent.id IN (?)", issues.map(&:id)).group("parent.id").sum(:hours)
977 where("parent.id IN (?)", issues.map(&:id)).group("parent.id").sum(:hours)
967 issues.each do |issue|
978 issues.each do |issue|
968 issue.instance_variable_set "@total_spent_hours", (hours_by_issue_id[issue.id] || 0)
979 issue.instance_variable_set "@total_spent_hours", (hours_by_issue_id[issue.id] || 0)
969 end
980 end
970 end
981 end
971 end
982 end
972
983
973 # Preloads visible relations for a collection of issues
984 # Preloads visible relations for a collection of issues
974 def self.load_visible_relations(issues, user=User.current)
985 def self.load_visible_relations(issues, user=User.current)
975 if issues.any?
986 if issues.any?
976 issue_ids = issues.map(&:id)
987 issue_ids = issues.map(&:id)
977 # Relations with issue_from in given issues and visible issue_to
988 # Relations with issue_from in given issues and visible issue_to
978 relations_from = IssueRelation.joins(:issue_to => :project).
989 relations_from = IssueRelation.joins(:issue_to => :project).
979 where(visible_condition(user)).where(:issue_from_id => issue_ids).to_a
990 where(visible_condition(user)).where(:issue_from_id => issue_ids).to_a
980 # Relations with issue_to in given issues and visible issue_from
991 # Relations with issue_to in given issues and visible issue_from
981 relations_to = IssueRelation.joins(:issue_from => :project).
992 relations_to = IssueRelation.joins(:issue_from => :project).
982 where(visible_condition(user)).
993 where(visible_condition(user)).
983 where(:issue_to_id => issue_ids).to_a
994 where(:issue_to_id => issue_ids).to_a
984 issues.each do |issue|
995 issues.each do |issue|
985 relations =
996 relations =
986 relations_from.select {|relation| relation.issue_from_id == issue.id} +
997 relations_from.select {|relation| relation.issue_from_id == issue.id} +
987 relations_to.select {|relation| relation.issue_to_id == issue.id}
998 relations_to.select {|relation| relation.issue_to_id == issue.id}
988
999
989 issue.instance_variable_set "@relations", IssueRelation::Relations.new(issue, relations.sort)
1000 issue.instance_variable_set "@relations", IssueRelation::Relations.new(issue, relations.sort)
990 end
1001 end
991 end
1002 end
992 end
1003 end
993
1004
994 # Finds an issue relation given its id.
1005 # Finds an issue relation given its id.
995 def find_relation(relation_id)
1006 def find_relation(relation_id)
996 IssueRelation.where("issue_to_id = ? OR issue_from_id = ?", id, id).find(relation_id)
1007 IssueRelation.where("issue_to_id = ? OR issue_from_id = ?", id, id).find(relation_id)
997 end
1008 end
998
1009
999 # Returns all the other issues that depend on the issue
1010 # Returns all the other issues that depend on the issue
1000 # The algorithm is a modified breadth first search (bfs)
1011 # The algorithm is a modified breadth first search (bfs)
1001 def all_dependent_issues(except=[])
1012 def all_dependent_issues(except=[])
1002 # The found dependencies
1013 # The found dependencies
1003 dependencies = []
1014 dependencies = []
1004
1015
1005 # The visited flag for every node (issue) used by the breadth first search
1016 # The visited flag for every node (issue) used by the breadth first search
1006 eNOT_DISCOVERED = 0 # The issue is "new" to the algorithm, it has not seen it before.
1017 eNOT_DISCOVERED = 0 # The issue is "new" to the algorithm, it has not seen it before.
1007
1018
1008 ePROCESS_ALL = 1 # The issue is added to the queue. Process both children and relations of
1019 ePROCESS_ALL = 1 # The issue is added to the queue. Process both children and relations of
1009 # the issue when it is processed.
1020 # the issue when it is processed.
1010
1021
1011 ePROCESS_RELATIONS_ONLY = 2 # The issue was added to the queue and will be output as dependent issue,
1022 ePROCESS_RELATIONS_ONLY = 2 # The issue was added to the queue and will be output as dependent issue,
1012 # but its children will not be added to the queue when it is processed.
1023 # but its children will not be added to the queue when it is processed.
1013
1024
1014 eRELATIONS_PROCESSED = 3 # The related issues, the parent issue and the issue itself have been added to
1025 eRELATIONS_PROCESSED = 3 # The related issues, the parent issue and the issue itself have been added to
1015 # the queue, but its children have not been added.
1026 # the queue, but its children have not been added.
1016
1027
1017 ePROCESS_CHILDREN_ONLY = 4 # The relations and the parent of the issue have been added to the queue, but
1028 ePROCESS_CHILDREN_ONLY = 4 # The relations and the parent of the issue have been added to the queue, but
1018 # the children still need to be processed.
1029 # the children still need to be processed.
1019
1030
1020 eALL_PROCESSED = 5 # The issue and all its children, its parent and its related issues have been
1031 eALL_PROCESSED = 5 # The issue and all its children, its parent and its related issues have been
1021 # added as dependent issues. It needs no further processing.
1032 # added as dependent issues. It needs no further processing.
1022
1033
1023 issue_status = Hash.new(eNOT_DISCOVERED)
1034 issue_status = Hash.new(eNOT_DISCOVERED)
1024
1035
1025 # The queue
1036 # The queue
1026 queue = []
1037 queue = []
1027
1038
1028 # Initialize the bfs, add start node (self) to the queue
1039 # Initialize the bfs, add start node (self) to the queue
1029 queue << self
1040 queue << self
1030 issue_status[self] = ePROCESS_ALL
1041 issue_status[self] = ePROCESS_ALL
1031
1042
1032 while (!queue.empty?) do
1043 while (!queue.empty?) do
1033 current_issue = queue.shift
1044 current_issue = queue.shift
1034 current_issue_status = issue_status[current_issue]
1045 current_issue_status = issue_status[current_issue]
1035 dependencies << current_issue
1046 dependencies << current_issue
1036
1047
1037 # Add parent to queue, if not already in it.
1048 # Add parent to queue, if not already in it.
1038 parent = current_issue.parent
1049 parent = current_issue.parent
1039 parent_status = issue_status[parent]
1050 parent_status = issue_status[parent]
1040
1051
1041 if parent && (parent_status == eNOT_DISCOVERED) && !except.include?(parent)
1052 if parent && (parent_status == eNOT_DISCOVERED) && !except.include?(parent)
1042 queue << parent
1053 queue << parent
1043 issue_status[parent] = ePROCESS_RELATIONS_ONLY
1054 issue_status[parent] = ePROCESS_RELATIONS_ONLY
1044 end
1055 end
1045
1056
1046 # Add children to queue, but only if they are not already in it and
1057 # Add children to queue, but only if they are not already in it and
1047 # the children of the current node need to be processed.
1058 # the children of the current node need to be processed.
1048 if (current_issue_status == ePROCESS_CHILDREN_ONLY || current_issue_status == ePROCESS_ALL)
1059 if (current_issue_status == ePROCESS_CHILDREN_ONLY || current_issue_status == ePROCESS_ALL)
1049 current_issue.children.each do |child|
1060 current_issue.children.each do |child|
1050 next if except.include?(child)
1061 next if except.include?(child)
1051
1062
1052 if (issue_status[child] == eNOT_DISCOVERED)
1063 if (issue_status[child] == eNOT_DISCOVERED)
1053 queue << child
1064 queue << child
1054 issue_status[child] = ePROCESS_ALL
1065 issue_status[child] = ePROCESS_ALL
1055 elsif (issue_status[child] == eRELATIONS_PROCESSED)
1066 elsif (issue_status[child] == eRELATIONS_PROCESSED)
1056 queue << child
1067 queue << child
1057 issue_status[child] = ePROCESS_CHILDREN_ONLY
1068 issue_status[child] = ePROCESS_CHILDREN_ONLY
1058 elsif (issue_status[child] == ePROCESS_RELATIONS_ONLY)
1069 elsif (issue_status[child] == ePROCESS_RELATIONS_ONLY)
1059 queue << child
1070 queue << child
1060 issue_status[child] = ePROCESS_ALL
1071 issue_status[child] = ePROCESS_ALL
1061 end
1072 end
1062 end
1073 end
1063 end
1074 end
1064
1075
1065 # Add related issues to the queue, if they are not already in it.
1076 # Add related issues to the queue, if they are not already in it.
1066 current_issue.relations_from.map(&:issue_to).each do |related_issue|
1077 current_issue.relations_from.map(&:issue_to).each do |related_issue|
1067 next if except.include?(related_issue)
1078 next if except.include?(related_issue)
1068
1079
1069 if (issue_status[related_issue] == eNOT_DISCOVERED)
1080 if (issue_status[related_issue] == eNOT_DISCOVERED)
1070 queue << related_issue
1081 queue << related_issue
1071 issue_status[related_issue] = ePROCESS_ALL
1082 issue_status[related_issue] = ePROCESS_ALL
1072 elsif (issue_status[related_issue] == eRELATIONS_PROCESSED)
1083 elsif (issue_status[related_issue] == eRELATIONS_PROCESSED)
1073 queue << related_issue
1084 queue << related_issue
1074 issue_status[related_issue] = ePROCESS_CHILDREN_ONLY
1085 issue_status[related_issue] = ePROCESS_CHILDREN_ONLY
1075 elsif (issue_status[related_issue] == ePROCESS_RELATIONS_ONLY)
1086 elsif (issue_status[related_issue] == ePROCESS_RELATIONS_ONLY)
1076 queue << related_issue
1087 queue << related_issue
1077 issue_status[related_issue] = ePROCESS_ALL
1088 issue_status[related_issue] = ePROCESS_ALL
1078 end
1089 end
1079 end
1090 end
1080
1091
1081 # Set new status for current issue
1092 # Set new status for current issue
1082 if (current_issue_status == ePROCESS_ALL) || (current_issue_status == ePROCESS_CHILDREN_ONLY)
1093 if (current_issue_status == ePROCESS_ALL) || (current_issue_status == ePROCESS_CHILDREN_ONLY)
1083 issue_status[current_issue] = eALL_PROCESSED
1094 issue_status[current_issue] = eALL_PROCESSED
1084 elsif (current_issue_status == ePROCESS_RELATIONS_ONLY)
1095 elsif (current_issue_status == ePROCESS_RELATIONS_ONLY)
1085 issue_status[current_issue] = eRELATIONS_PROCESSED
1096 issue_status[current_issue] = eRELATIONS_PROCESSED
1086 end
1097 end
1087 end # while
1098 end # while
1088
1099
1089 # Remove the issues from the "except" parameter from the result array
1100 # Remove the issues from the "except" parameter from the result array
1090 dependencies -= except
1101 dependencies -= except
1091 dependencies.delete(self)
1102 dependencies.delete(self)
1092
1103
1093 dependencies
1104 dependencies
1094 end
1105 end
1095
1106
1096 # Returns an array of issues that duplicate this one
1107 # Returns an array of issues that duplicate this one
1097 def duplicates
1108 def duplicates
1098 relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from}
1109 relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from}
1099 end
1110 end
1100
1111
1101 # Returns the due date or the target due date if any
1112 # Returns the due date or the target due date if any
1102 # Used on gantt chart
1113 # Used on gantt chart
1103 def due_before
1114 def due_before
1104 due_date || (fixed_version ? fixed_version.effective_date : nil)
1115 due_date || (fixed_version ? fixed_version.effective_date : nil)
1105 end
1116 end
1106
1117
1107 # Returns the time scheduled for this issue.
1118 # Returns the time scheduled for this issue.
1108 #
1119 #
1109 # Example:
1120 # Example:
1110 # Start Date: 2/26/09, End Date: 3/04/09
1121 # Start Date: 2/26/09, End Date: 3/04/09
1111 # duration => 6
1122 # duration => 6
1112 def duration
1123 def duration
1113 (start_date && due_date) ? due_date - start_date : 0
1124 (start_date && due_date) ? due_date - start_date : 0
1114 end
1125 end
1115
1126
1116 # Returns the duration in working days
1127 # Returns the duration in working days
1117 def working_duration
1128 def working_duration
1118 (start_date && due_date) ? working_days(start_date, due_date) : 0
1129 (start_date && due_date) ? working_days(start_date, due_date) : 0
1119 end
1130 end
1120
1131
1121 def soonest_start(reload=false)
1132 def soonest_start(reload=false)
1122 if @soonest_start.nil? || reload
1133 if @soonest_start.nil? || reload
1123 dates = relations_to(reload).collect{|relation| relation.successor_soonest_start}
1134 dates = relations_to(reload).collect{|relation| relation.successor_soonest_start}
1124 p = @parent_issue || parent
1135 p = @parent_issue || parent
1125 if p && Setting.parent_issue_dates == 'derived'
1136 if p && Setting.parent_issue_dates == 'derived'
1126 dates << p.soonest_start
1137 dates << p.soonest_start
1127 end
1138 end
1128 @soonest_start = dates.compact.max
1139 @soonest_start = dates.compact.max
1129 end
1140 end
1130 @soonest_start
1141 @soonest_start
1131 end
1142 end
1132
1143
1133 # Sets start_date on the given date or the next working day
1144 # Sets start_date on the given date or the next working day
1134 # and changes due_date to keep the same working duration.
1145 # and changes due_date to keep the same working duration.
1135 def reschedule_on(date)
1146 def reschedule_on(date)
1136 wd = working_duration
1147 wd = working_duration
1137 date = next_working_date(date)
1148 date = next_working_date(date)
1138 self.start_date = date
1149 self.start_date = date
1139 self.due_date = add_working_days(date, wd)
1150 self.due_date = add_working_days(date, wd)
1140 end
1151 end
1141
1152
1142 # Reschedules the issue on the given date or the next working day and saves the record.
1153 # Reschedules the issue on the given date or the next working day and saves the record.
1143 # If the issue is a parent task, this is done by rescheduling its subtasks.
1154 # If the issue is a parent task, this is done by rescheduling its subtasks.
1144 def reschedule_on!(date)
1155 def reschedule_on!(date)
1145 return if date.nil?
1156 return if date.nil?
1146 if leaf? || !dates_derived?
1157 if leaf? || !dates_derived?
1147 if start_date.nil? || start_date != date
1158 if start_date.nil? || start_date != date
1148 if start_date && start_date > date
1159 if start_date && start_date > date
1149 # Issue can not be moved earlier than its soonest start date
1160 # Issue can not be moved earlier than its soonest start date
1150 date = [soonest_start(true), date].compact.max
1161 date = [soonest_start(true), date].compact.max
1151 end
1162 end
1152 reschedule_on(date)
1163 reschedule_on(date)
1153 begin
1164 begin
1154 save
1165 save
1155 rescue ActiveRecord::StaleObjectError
1166 rescue ActiveRecord::StaleObjectError
1156 reload
1167 reload
1157 reschedule_on(date)
1168 reschedule_on(date)
1158 save
1169 save
1159 end
1170 end
1160 end
1171 end
1161 else
1172 else
1162 leaves.each do |leaf|
1173 leaves.each do |leaf|
1163 if leaf.start_date
1174 if leaf.start_date
1164 # Only move subtask if it starts at the same date as the parent
1175 # Only move subtask if it starts at the same date as the parent
1165 # or if it starts before the given date
1176 # or if it starts before the given date
1166 if start_date == leaf.start_date || date > leaf.start_date
1177 if start_date == leaf.start_date || date > leaf.start_date
1167 leaf.reschedule_on!(date)
1178 leaf.reschedule_on!(date)
1168 end
1179 end
1169 else
1180 else
1170 leaf.reschedule_on!(date)
1181 leaf.reschedule_on!(date)
1171 end
1182 end
1172 end
1183 end
1173 end
1184 end
1174 end
1185 end
1175
1186
1176 def dates_derived?
1187 def dates_derived?
1177 !leaf? && Setting.parent_issue_dates == 'derived'
1188 !leaf? && Setting.parent_issue_dates == 'derived'
1178 end
1189 end
1179
1190
1180 def priority_derived?
1191 def priority_derived?
1181 !leaf? && Setting.parent_issue_priority == 'derived'
1192 !leaf? && Setting.parent_issue_priority == 'derived'
1182 end
1193 end
1183
1194
1184 def done_ratio_derived?
1195 def done_ratio_derived?
1185 !leaf? && Setting.parent_issue_done_ratio == 'derived'
1196 !leaf? && Setting.parent_issue_done_ratio == 'derived'
1186 end
1197 end
1187
1198
1188 def <=>(issue)
1199 def <=>(issue)
1189 if issue.nil?
1200 if issue.nil?
1190 -1
1201 -1
1191 elsif root_id != issue.root_id
1202 elsif root_id != issue.root_id
1192 (root_id || 0) <=> (issue.root_id || 0)
1203 (root_id || 0) <=> (issue.root_id || 0)
1193 else
1204 else
1194 (lft || 0) <=> (issue.lft || 0)
1205 (lft || 0) <=> (issue.lft || 0)
1195 end
1206 end
1196 end
1207 end
1197
1208
1198 def to_s
1209 def to_s
1199 "#{tracker} ##{id}: #{subject}"
1210 "#{tracker} ##{id}: #{subject}"
1200 end
1211 end
1201
1212
1202 # Returns a string of css classes that apply to the issue
1213 # Returns a string of css classes that apply to the issue
1203 def css_classes(user=User.current)
1214 def css_classes(user=User.current)
1204 s = "issue tracker-#{tracker_id} status-#{status_id} #{priority.try(:css_classes)}"
1215 s = "issue tracker-#{tracker_id} status-#{status_id} #{priority.try(:css_classes)}"
1205 s << ' closed' if closed?
1216 s << ' closed' if closed?
1206 s << ' overdue' if overdue?
1217 s << ' overdue' if overdue?
1207 s << ' child' if child?
1218 s << ' child' if child?
1208 s << ' parent' unless leaf?
1219 s << ' parent' unless leaf?
1209 s << ' private' if is_private?
1220 s << ' private' if is_private?
1210 if user.logged?
1221 if user.logged?
1211 s << ' created-by-me' if author_id == user.id
1222 s << ' created-by-me' if author_id == user.id
1212 s << ' assigned-to-me' if assigned_to_id == user.id
1223 s << ' assigned-to-me' if assigned_to_id == user.id
1213 s << ' assigned-to-my-group' if user.groups.any? {|g| g.id == assigned_to_id}
1224 s << ' assigned-to-my-group' if user.groups.any? {|g| g.id == assigned_to_id}
1214 end
1225 end
1215 s
1226 s
1216 end
1227 end
1217
1228
1218 # Unassigns issues from +version+ if it's no longer shared with issue's project
1229 # Unassigns issues from +version+ if it's no longer shared with issue's project
1219 def self.update_versions_from_sharing_change(version)
1230 def self.update_versions_from_sharing_change(version)
1220 # Update issues assigned to the version
1231 # Update issues assigned to the version
1221 update_versions(["#{Issue.table_name}.fixed_version_id = ?", version.id])
1232 update_versions(["#{Issue.table_name}.fixed_version_id = ?", version.id])
1222 end
1233 end
1223
1234
1224 # Unassigns issues from versions that are no longer shared
1235 # Unassigns issues from versions that are no longer shared
1225 # after +project+ was moved
1236 # after +project+ was moved
1226 def self.update_versions_from_hierarchy_change(project)
1237 def self.update_versions_from_hierarchy_change(project)
1227 moved_project_ids = project.self_and_descendants.reload.collect(&:id)
1238 moved_project_ids = project.self_and_descendants.reload.collect(&:id)
1228 # Update issues of the moved projects and issues assigned to a version of a moved project
1239 # Update issues of the moved projects and issues assigned to a version of a moved project
1229 Issue.update_versions(
1240 Issue.update_versions(
1230 ["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)",
1241 ["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)",
1231 moved_project_ids, moved_project_ids]
1242 moved_project_ids, moved_project_ids]
1232 )
1243 )
1233 end
1244 end
1234
1245
1235 def parent_issue_id=(arg)
1246 def parent_issue_id=(arg)
1236 s = arg.to_s.strip.presence
1247 s = arg.to_s.strip.presence
1237 if s && (m = s.match(%r{\A#?(\d+)\z})) && (@parent_issue = Issue.find_by_id(m[1]))
1248 if s && (m = s.match(%r{\A#?(\d+)\z})) && (@parent_issue = Issue.find_by_id(m[1]))
1238 @invalid_parent_issue_id = nil
1249 @invalid_parent_issue_id = nil
1239 elsif s.blank?
1250 elsif s.blank?
1240 @parent_issue = nil
1251 @parent_issue = nil
1241 @invalid_parent_issue_id = nil
1252 @invalid_parent_issue_id = nil
1242 else
1253 else
1243 @parent_issue = nil
1254 @parent_issue = nil
1244 @invalid_parent_issue_id = arg
1255 @invalid_parent_issue_id = arg
1245 end
1256 end
1246 end
1257 end
1247
1258
1248 def parent_issue_id
1259 def parent_issue_id
1249 if @invalid_parent_issue_id
1260 if @invalid_parent_issue_id
1250 @invalid_parent_issue_id
1261 @invalid_parent_issue_id
1251 elsif instance_variable_defined? :@parent_issue
1262 elsif instance_variable_defined? :@parent_issue
1252 @parent_issue.nil? ? nil : @parent_issue.id
1263 @parent_issue.nil? ? nil : @parent_issue.id
1253 else
1264 else
1254 parent_id
1265 parent_id
1255 end
1266 end
1256 end
1267 end
1257
1268
1258 def set_parent_id
1269 def set_parent_id
1259 self.parent_id = parent_issue_id
1270 self.parent_id = parent_issue_id
1260 end
1271 end
1261
1272
1262 # Returns true if issue's project is a valid
1273 # Returns true if issue's project is a valid
1263 # parent issue project
1274 # parent issue project
1264 def valid_parent_project?(issue=parent)
1275 def valid_parent_project?(issue=parent)
1265 return true if issue.nil? || issue.project_id == project_id
1276 return true if issue.nil? || issue.project_id == project_id
1266
1277
1267 case Setting.cross_project_subtasks
1278 case Setting.cross_project_subtasks
1268 when 'system'
1279 when 'system'
1269 true
1280 true
1270 when 'tree'
1281 when 'tree'
1271 issue.project.root == project.root
1282 issue.project.root == project.root
1272 when 'hierarchy'
1283 when 'hierarchy'
1273 issue.project.is_or_is_ancestor_of?(project) || issue.project.is_descendant_of?(project)
1284 issue.project.is_or_is_ancestor_of?(project) || issue.project.is_descendant_of?(project)
1274 when 'descendants'
1285 when 'descendants'
1275 issue.project.is_or_is_ancestor_of?(project)
1286 issue.project.is_or_is_ancestor_of?(project)
1276 else
1287 else
1277 false
1288 false
1278 end
1289 end
1279 end
1290 end
1280
1291
1281 # Returns an issue scope based on project and scope
1292 # Returns an issue scope based on project and scope
1282 def self.cross_project_scope(project, scope=nil)
1293 def self.cross_project_scope(project, scope=nil)
1283 if project.nil?
1294 if project.nil?
1284 return Issue
1295 return Issue
1285 end
1296 end
1286 case scope
1297 case scope
1287 when 'all', 'system'
1298 when 'all', 'system'
1288 Issue
1299 Issue
1289 when 'tree'
1300 when 'tree'
1290 Issue.joins(:project).where("(#{Project.table_name}.lft >= :lft AND #{Project.table_name}.rgt <= :rgt)",
1301 Issue.joins(:project).where("(#{Project.table_name}.lft >= :lft AND #{Project.table_name}.rgt <= :rgt)",
1291 :lft => project.root.lft, :rgt => project.root.rgt)
1302 :lft => project.root.lft, :rgt => project.root.rgt)
1292 when 'hierarchy'
1303 when 'hierarchy'
1293 Issue.joins(:project).where("(#{Project.table_name}.lft >= :lft AND #{Project.table_name}.rgt <= :rgt) OR (#{Project.table_name}.lft < :lft AND #{Project.table_name}.rgt > :rgt)",
1304 Issue.joins(:project).where("(#{Project.table_name}.lft >= :lft AND #{Project.table_name}.rgt <= :rgt) OR (#{Project.table_name}.lft < :lft AND #{Project.table_name}.rgt > :rgt)",
1294 :lft => project.lft, :rgt => project.rgt)
1305 :lft => project.lft, :rgt => project.rgt)
1295 when 'descendants'
1306 when 'descendants'
1296 Issue.joins(:project).where("(#{Project.table_name}.lft >= :lft AND #{Project.table_name}.rgt <= :rgt)",
1307 Issue.joins(:project).where("(#{Project.table_name}.lft >= :lft AND #{Project.table_name}.rgt <= :rgt)",
1297 :lft => project.lft, :rgt => project.rgt)
1308 :lft => project.lft, :rgt => project.rgt)
1298 else
1309 else
1299 Issue.where(:project_id => project.id)
1310 Issue.where(:project_id => project.id)
1300 end
1311 end
1301 end
1312 end
1302
1313
1303 def self.by_tracker(project)
1314 def self.by_tracker(project)
1304 count_and_group_by(:project => project, :association => :tracker)
1315 count_and_group_by(:project => project, :association => :tracker)
1305 end
1316 end
1306
1317
1307 def self.by_version(project)
1318 def self.by_version(project)
1308 count_and_group_by(:project => project, :association => :fixed_version)
1319 count_and_group_by(:project => project, :association => :fixed_version)
1309 end
1320 end
1310
1321
1311 def self.by_priority(project)
1322 def self.by_priority(project)
1312 count_and_group_by(:project => project, :association => :priority)
1323 count_and_group_by(:project => project, :association => :priority)
1313 end
1324 end
1314
1325
1315 def self.by_category(project)
1326 def self.by_category(project)
1316 count_and_group_by(:project => project, :association => :category)
1327 count_and_group_by(:project => project, :association => :category)
1317 end
1328 end
1318
1329
1319 def self.by_assigned_to(project)
1330 def self.by_assigned_to(project)
1320 count_and_group_by(:project => project, :association => :assigned_to)
1331 count_and_group_by(:project => project, :association => :assigned_to)
1321 end
1332 end
1322
1333
1323 def self.by_author(project)
1334 def self.by_author(project)
1324 count_and_group_by(:project => project, :association => :author)
1335 count_and_group_by(:project => project, :association => :author)
1325 end
1336 end
1326
1337
1327 def self.by_subproject(project)
1338 def self.by_subproject(project)
1328 r = count_and_group_by(:project => project, :with_subprojects => true, :association => :project)
1339 r = count_and_group_by(:project => project, :with_subprojects => true, :association => :project)
1329 r.reject {|r| r["project_id"] == project.id.to_s}
1340 r.reject {|r| r["project_id"] == project.id.to_s}
1330 end
1341 end
1331
1342
1332 # Query generator for selecting groups of issue counts for a project
1343 # Query generator for selecting groups of issue counts for a project
1333 # based on specific criteria
1344 # based on specific criteria
1334 #
1345 #
1335 # Options
1346 # Options
1336 # * project - Project to search in.
1347 # * project - Project to search in.
1337 # * with_subprojects - Includes subprojects issues if set to true.
1348 # * with_subprojects - Includes subprojects issues if set to true.
1338 # * association - Symbol. Association for grouping.
1349 # * association - Symbol. Association for grouping.
1339 def self.count_and_group_by(options)
1350 def self.count_and_group_by(options)
1340 assoc = reflect_on_association(options[:association])
1351 assoc = reflect_on_association(options[:association])
1341 select_field = assoc.foreign_key
1352 select_field = assoc.foreign_key
1342
1353
1343 Issue.
1354 Issue.
1344 visible(User.current, :project => options[:project], :with_subprojects => options[:with_subprojects]).
1355 visible(User.current, :project => options[:project], :with_subprojects => options[:with_subprojects]).
1345 joins(:status, assoc.name).
1356 joins(:status, assoc.name).
1346 group(:status_id, :is_closed, select_field).
1357 group(:status_id, :is_closed, select_field).
1347 count.
1358 count.
1348 map do |columns, total|
1359 map do |columns, total|
1349 status_id, is_closed, field_value = columns
1360 status_id, is_closed, field_value = columns
1350 is_closed = ['t', 'true', '1'].include?(is_closed.to_s)
1361 is_closed = ['t', 'true', '1'].include?(is_closed.to_s)
1351 {
1362 {
1352 "status_id" => status_id.to_s,
1363 "status_id" => status_id.to_s,
1353 "closed" => is_closed,
1364 "closed" => is_closed,
1354 select_field => field_value.to_s,
1365 select_field => field_value.to_s,
1355 "total" => total.to_s
1366 "total" => total.to_s
1356 }
1367 }
1357 end
1368 end
1358 end
1369 end
1359
1370
1360 # Returns a scope of projects that user can assign the issue to
1371 # Returns a scope of projects that user can assign the issue to
1361 def allowed_target_projects(user=User.current)
1372 def allowed_target_projects(user=User.current)
1362 current_project = new_record? ? nil : project
1373 current_project = new_record? ? nil : project
1363 self.class.allowed_target_projects(user, current_project)
1374 self.class.allowed_target_projects(user, current_project)
1364 end
1375 end
1365
1376
1366 # Returns a scope of projects that user can assign issues to
1377 # Returns a scope of projects that user can assign issues to
1367 # If current_project is given, it will be included in the scope
1378 # If current_project is given, it will be included in the scope
1368 def self.allowed_target_projects(user=User.current, current_project=nil)
1379 def self.allowed_target_projects(user=User.current, current_project=nil)
1369 condition = Project.allowed_to_condition(user, :add_issues)
1380 condition = Project.allowed_to_condition(user, :add_issues)
1370 if current_project
1381 if current_project
1371 condition = ["(#{condition}) OR #{Project.table_name}.id = ?", current_project.id]
1382 condition = ["(#{condition}) OR #{Project.table_name}.id = ?", current_project.id]
1372 end
1383 end
1373 Project.where(condition)
1384 Project.where(condition)
1374 end
1385 end
1375
1386
1376 private
1387 private
1377
1388
1378 def after_project_change
1389 def after_project_change
1379 # Update project_id on related time entries
1390 # Update project_id on related time entries
1380 TimeEntry.where({:issue_id => id}).update_all(["project_id = ?", project_id])
1391 TimeEntry.where({:issue_id => id}).update_all(["project_id = ?", project_id])
1381
1392
1382 # Delete issue relations
1393 # Delete issue relations
1383 unless Setting.cross_project_issue_relations?
1394 unless Setting.cross_project_issue_relations?
1384 relations_from.clear
1395 relations_from.clear
1385 relations_to.clear
1396 relations_to.clear
1386 end
1397 end
1387
1398
1388 # Move subtasks that were in the same project
1399 # Move subtasks that were in the same project
1389 children.each do |child|
1400 children.each do |child|
1390 next unless child.project_id == project_id_was
1401 next unless child.project_id == project_id_was
1391 # Change project and keep project
1402 # Change project and keep project
1392 child.send :project=, project, true
1403 child.send :project=, project, true
1393 unless child.save
1404 unless child.save
1394 raise ActiveRecord::Rollback
1405 raise ActiveRecord::Rollback
1395 end
1406 end
1396 end
1407 end
1397 end
1408 end
1398
1409
1399 # Callback for after the creation of an issue by copy
1410 # Callback for after the creation of an issue by copy
1400 # * adds a "copied to" relation with the copied issue
1411 # * adds a "copied to" relation with the copied issue
1401 # * copies subtasks from the copied issue
1412 # * copies subtasks from the copied issue
1402 def after_create_from_copy
1413 def after_create_from_copy
1403 return unless copy? && !@after_create_from_copy_handled
1414 return unless copy? && !@after_create_from_copy_handled
1404
1415
1405 if (@copied_from.project_id == project_id || Setting.cross_project_issue_relations?) && @copy_options[:link] != false
1416 if (@copied_from.project_id == project_id || Setting.cross_project_issue_relations?) && @copy_options[:link] != false
1406 if @current_journal
1417 if @current_journal
1407 @copied_from.init_journal(@current_journal.user)
1418 @copied_from.init_journal(@current_journal.user)
1408 end
1419 end
1409 relation = IssueRelation.new(:issue_from => @copied_from, :issue_to => self, :relation_type => IssueRelation::TYPE_COPIED_TO)
1420 relation = IssueRelation.new(:issue_from => @copied_from, :issue_to => self, :relation_type => IssueRelation::TYPE_COPIED_TO)
1410 unless relation.save
1421 unless relation.save
1411 logger.error "Could not create relation while copying ##{@copied_from.id} to ##{id} due to validation errors: #{relation.errors.full_messages.join(', ')}" if logger
1422 logger.error "Could not create relation while copying ##{@copied_from.id} to ##{id} due to validation errors: #{relation.errors.full_messages.join(', ')}" if logger
1412 end
1423 end
1413 end
1424 end
1414
1425
1415 unless @copied_from.leaf? || @copy_options[:subtasks] == false
1426 unless @copied_from.leaf? || @copy_options[:subtasks] == false
1416 copy_options = (@copy_options || {}).merge(:subtasks => false)
1427 copy_options = (@copy_options || {}).merge(:subtasks => false)
1417 copied_issue_ids = {@copied_from.id => self.id}
1428 copied_issue_ids = {@copied_from.id => self.id}
1418 @copied_from.reload.descendants.reorder("#{Issue.table_name}.lft").each do |child|
1429 @copied_from.reload.descendants.reorder("#{Issue.table_name}.lft").each do |child|
1419 # Do not copy self when copying an issue as a descendant of the copied issue
1430 # Do not copy self when copying an issue as a descendant of the copied issue
1420 next if child == self
1431 next if child == self
1421 # Do not copy subtasks of issues that were not copied
1432 # Do not copy subtasks of issues that were not copied
1422 next unless copied_issue_ids[child.parent_id]
1433 next unless copied_issue_ids[child.parent_id]
1423 # Do not copy subtasks that are not visible to avoid potential disclosure of private data
1434 # Do not copy subtasks that are not visible to avoid potential disclosure of private data
1424 unless child.visible?
1435 unless child.visible?
1425 logger.error "Subtask ##{child.id} was not copied during ##{@copied_from.id} copy because it is not visible to the current user" if logger
1436 logger.error "Subtask ##{child.id} was not copied during ##{@copied_from.id} copy because it is not visible to the current user" if logger
1426 next
1437 next
1427 end
1438 end
1428 copy = Issue.new.copy_from(child, copy_options)
1439 copy = Issue.new.copy_from(child, copy_options)
1429 if @current_journal
1440 if @current_journal
1430 copy.init_journal(@current_journal.user)
1441 copy.init_journal(@current_journal.user)
1431 end
1442 end
1432 copy.author = author
1443 copy.author = author
1433 copy.project = project
1444 copy.project = project
1434 copy.parent_issue_id = copied_issue_ids[child.parent_id]
1445 copy.parent_issue_id = copied_issue_ids[child.parent_id]
1435 unless copy.save
1446 unless copy.save
1436 logger.error "Could not copy subtask ##{child.id} while copying ##{@copied_from.id} to ##{id} due to validation errors: #{copy.errors.full_messages.join(', ')}" if logger
1447 logger.error "Could not copy subtask ##{child.id} while copying ##{@copied_from.id} to ##{id} due to validation errors: #{copy.errors.full_messages.join(', ')}" if logger
1437 next
1448 next
1438 end
1449 end
1439 copied_issue_ids[child.id] = copy.id
1450 copied_issue_ids[child.id] = copy.id
1440 end
1451 end
1441 end
1452 end
1442 @after_create_from_copy_handled = true
1453 @after_create_from_copy_handled = true
1443 end
1454 end
1444
1455
1445 def update_nested_set_attributes
1456 def update_nested_set_attributes
1446 if parent_id_changed?
1457 if parent_id_changed?
1447 update_nested_set_attributes_on_parent_change
1458 update_nested_set_attributes_on_parent_change
1448 end
1459 end
1449 remove_instance_variable(:@parent_issue) if instance_variable_defined?(:@parent_issue)
1460 remove_instance_variable(:@parent_issue) if instance_variable_defined?(:@parent_issue)
1450 end
1461 end
1451
1462
1452 # Updates the nested set for when an existing issue is moved
1463 # Updates the nested set for when an existing issue is moved
1453 def update_nested_set_attributes_on_parent_change
1464 def update_nested_set_attributes_on_parent_change
1454 former_parent_id = parent_id_was
1465 former_parent_id = parent_id_was
1455 # delete invalid relations of all descendants
1466 # delete invalid relations of all descendants
1456 self_and_descendants.each do |issue|
1467 self_and_descendants.each do |issue|
1457 issue.relations.each do |relation|
1468 issue.relations.each do |relation|
1458 relation.destroy unless relation.valid?
1469 relation.destroy unless relation.valid?
1459 end
1470 end
1460 end
1471 end
1461 # update former parent
1472 # update former parent
1462 recalculate_attributes_for(former_parent_id) if former_parent_id
1473 recalculate_attributes_for(former_parent_id) if former_parent_id
1463 end
1474 end
1464
1475
1465 def update_parent_attributes
1476 def update_parent_attributes
1466 if parent_id
1477 if parent_id
1467 recalculate_attributes_for(parent_id)
1478 recalculate_attributes_for(parent_id)
1468 association(:parent).reset
1479 association(:parent).reset
1469 end
1480 end
1470 end
1481 end
1471
1482
1472 def recalculate_attributes_for(issue_id)
1483 def recalculate_attributes_for(issue_id)
1473 if issue_id && p = Issue.find_by_id(issue_id)
1484 if issue_id && p = Issue.find_by_id(issue_id)
1474 if p.priority_derived?
1485 if p.priority_derived?
1475 # priority = highest priority of children
1486 # priority = highest priority of children
1476 if priority_position = p.children.joins(:priority).maximum("#{IssuePriority.table_name}.position")
1487 if priority_position = p.children.joins(:priority).maximum("#{IssuePriority.table_name}.position")
1477 p.priority = IssuePriority.find_by_position(priority_position)
1488 p.priority = IssuePriority.find_by_position(priority_position)
1478 end
1489 end
1479 end
1490 end
1480
1491
1481 if p.dates_derived?
1492 if p.dates_derived?
1482 # start/due dates = lowest/highest dates of children
1493 # start/due dates = lowest/highest dates of children
1483 p.start_date = p.children.minimum(:start_date)
1494 p.start_date = p.children.minimum(:start_date)
1484 p.due_date = p.children.maximum(:due_date)
1495 p.due_date = p.children.maximum(:due_date)
1485 if p.start_date && p.due_date && p.due_date < p.start_date
1496 if p.start_date && p.due_date && p.due_date < p.start_date
1486 p.start_date, p.due_date = p.due_date, p.start_date
1497 p.start_date, p.due_date = p.due_date, p.start_date
1487 end
1498 end
1488 end
1499 end
1489
1500
1490 if p.done_ratio_derived?
1501 if p.done_ratio_derived?
1491 # done ratio = weighted average ratio of leaves
1502 # done ratio = weighted average ratio of leaves
1492 unless Issue.use_status_for_done_ratio? && p.status && p.status.default_done_ratio
1503 unless Issue.use_status_for_done_ratio? && p.status && p.status.default_done_ratio
1493 leaves_count = p.leaves.count
1504 leaves_count = p.leaves.count
1494 if leaves_count > 0
1505 if leaves_count > 0
1495 average = p.leaves.where("estimated_hours > 0").average(:estimated_hours).to_f
1506 average = p.leaves.where("estimated_hours > 0").average(:estimated_hours).to_f
1496 if average == 0
1507 if average == 0
1497 average = 1
1508 average = 1
1498 end
1509 end
1499 done = p.leaves.joins(:status).
1510 done = p.leaves.joins(:status).
1500 sum("COALESCE(CASE WHEN estimated_hours > 0 THEN estimated_hours ELSE NULL END, #{average}) " +
1511 sum("COALESCE(CASE WHEN estimated_hours > 0 THEN estimated_hours ELSE NULL END, #{average}) " +
1501 "* (CASE WHEN is_closed = #{self.class.connection.quoted_true} THEN 100 ELSE COALESCE(done_ratio, 0) END)").to_f
1512 "* (CASE WHEN is_closed = #{self.class.connection.quoted_true} THEN 100 ELSE COALESCE(done_ratio, 0) END)").to_f
1502 progress = done / (average * leaves_count)
1513 progress = done / (average * leaves_count)
1503 p.done_ratio = progress.round
1514 p.done_ratio = progress.round
1504 end
1515 end
1505 end
1516 end
1506 end
1517 end
1507
1518
1508 # ancestors will be recursively updated
1519 # ancestors will be recursively updated
1509 p.save(:validate => false)
1520 p.save(:validate => false)
1510 end
1521 end
1511 end
1522 end
1512
1523
1513 # Update issues so their versions are not pointing to a
1524 # Update issues so their versions are not pointing to a
1514 # fixed_version that is not shared with the issue's project
1525 # fixed_version that is not shared with the issue's project
1515 def self.update_versions(conditions=nil)
1526 def self.update_versions(conditions=nil)
1516 # Only need to update issues with a fixed_version from
1527 # Only need to update issues with a fixed_version from
1517 # a different project and that is not systemwide shared
1528 # a different project and that is not systemwide shared
1518 Issue.joins(:project, :fixed_version).
1529 Issue.joins(:project, :fixed_version).
1519 where("#{Issue.table_name}.fixed_version_id IS NOT NULL" +
1530 where("#{Issue.table_name}.fixed_version_id IS NOT NULL" +
1520 " AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" +
1531 " AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" +
1521 " AND #{Version.table_name}.sharing <> 'system'").
1532 " AND #{Version.table_name}.sharing <> 'system'").
1522 where(conditions).each do |issue|
1533 where(conditions).each do |issue|
1523 next if issue.project.nil? || issue.fixed_version.nil?
1534 next if issue.project.nil? || issue.fixed_version.nil?
1524 unless issue.project.shared_versions.include?(issue.fixed_version)
1535 unless issue.project.shared_versions.include?(issue.fixed_version)
1525 issue.init_journal(User.current)
1536 issue.init_journal(User.current)
1526 issue.fixed_version = nil
1537 issue.fixed_version = nil
1527 issue.save
1538 issue.save
1528 end
1539 end
1529 end
1540 end
1530 end
1541 end
1531
1542
1532 # Callback on file attachment
1543 # Callback on file attachment
1533 def attachment_added(attachment)
1544 def attachment_added(attachment)
1534 if current_journal && !attachment.new_record?
1545 if current_journal && !attachment.new_record?
1535 current_journal.journalize_attachment(attachment, :added)
1546 current_journal.journalize_attachment(attachment, :added)
1536 end
1547 end
1537 end
1548 end
1538
1549
1539 # Callback on attachment deletion
1550 # Callback on attachment deletion
1540 def attachment_removed(attachment)
1551 def attachment_removed(attachment)
1541 if current_journal && !attachment.new_record?
1552 if current_journal && !attachment.new_record?
1542 current_journal.journalize_attachment(attachment, :removed)
1553 current_journal.journalize_attachment(attachment, :removed)
1543 current_journal.save
1554 current_journal.save
1544 end
1555 end
1545 end
1556 end
1546
1557
1547 # Called after a relation is added
1558 # Called after a relation is added
1548 def relation_added(relation)
1559 def relation_added(relation)
1549 if current_journal
1560 if current_journal
1550 current_journal.journalize_relation(relation, :added)
1561 current_journal.journalize_relation(relation, :added)
1551 current_journal.save
1562 current_journal.save
1552 end
1563 end
1553 end
1564 end
1554
1565
1555 # Called after a relation is removed
1566 # Called after a relation is removed
1556 def relation_removed(relation)
1567 def relation_removed(relation)
1557 if current_journal
1568 if current_journal
1558 current_journal.journalize_relation(relation, :removed)
1569 current_journal.journalize_relation(relation, :removed)
1559 current_journal.save
1570 current_journal.save
1560 end
1571 end
1561 end
1572 end
1562
1573
1563 # Default assignment based on category
1574 # Default assignment based on category
1564 def default_assign
1575 def default_assign
1565 if assigned_to.nil? && category && category.assigned_to
1576 if assigned_to.nil? && category && category.assigned_to
1566 self.assigned_to = category.assigned_to
1577 self.assigned_to = category.assigned_to
1567 end
1578 end
1568 end
1579 end
1569
1580
1570 # Updates start/due dates of following issues
1581 # Updates start/due dates of following issues
1571 def reschedule_following_issues
1582 def reschedule_following_issues
1572 if start_date_changed? || due_date_changed?
1583 if start_date_changed? || due_date_changed?
1573 relations_from.each do |relation|
1584 relations_from.each do |relation|
1574 relation.set_issue_to_dates
1585 relation.set_issue_to_dates
1575 end
1586 end
1576 end
1587 end
1577 end
1588 end
1578
1589
1579 # Closes duplicates if the issue is being closed
1590 # Closes duplicates if the issue is being closed
1580 def close_duplicates
1591 def close_duplicates
1581 if closing?
1592 if closing?
1582 duplicates.each do |duplicate|
1593 duplicates.each do |duplicate|
1583 # Reload is needed in case the duplicate was updated by a previous duplicate
1594 # Reload is needed in case the duplicate was updated by a previous duplicate
1584 duplicate.reload
1595 duplicate.reload
1585 # Don't re-close it if it's already closed
1596 # Don't re-close it if it's already closed
1586 next if duplicate.closed?
1597 next if duplicate.closed?
1587 # Same user and notes
1598 # Same user and notes
1588 if @current_journal
1599 if @current_journal
1589 duplicate.init_journal(@current_journal.user, @current_journal.notes)
1600 duplicate.init_journal(@current_journal.user, @current_journal.notes)
1590 duplicate.private_notes = @current_journal.private_notes
1601 duplicate.private_notes = @current_journal.private_notes
1591 end
1602 end
1592 duplicate.update_attribute :status, self.status
1603 duplicate.update_attribute :status, self.status
1593 end
1604 end
1594 end
1605 end
1595 end
1606 end
1596
1607
1597 # Make sure updated_on is updated when adding a note and set updated_on now
1608 # Make sure updated_on is updated when adding a note and set updated_on now
1598 # so we can set closed_on with the same value on closing
1609 # so we can set closed_on with the same value on closing
1599 def force_updated_on_change
1610 def force_updated_on_change
1600 if @current_journal || changed?
1611 if @current_journal || changed?
1601 self.updated_on = current_time_from_proper_timezone
1612 self.updated_on = current_time_from_proper_timezone
1602 if new_record?
1613 if new_record?
1603 self.created_on = updated_on
1614 self.created_on = updated_on
1604 end
1615 end
1605 end
1616 end
1606 end
1617 end
1607
1618
1608 # Callback for setting closed_on when the issue is closed.
1619 # Callback for setting closed_on when the issue is closed.
1609 # The closed_on attribute stores the time of the last closing
1620 # The closed_on attribute stores the time of the last closing
1610 # and is preserved when the issue is reopened.
1621 # and is preserved when the issue is reopened.
1611 def update_closed_on
1622 def update_closed_on
1612 if closing?
1623 if closing?
1613 self.closed_on = updated_on
1624 self.closed_on = updated_on
1614 end
1625 end
1615 end
1626 end
1616
1627
1617 # Saves the changes in a Journal
1628 # Saves the changes in a Journal
1618 # Called after_save
1629 # Called after_save
1619 def create_journal
1630 def create_journal
1620 if current_journal
1631 if current_journal
1621 current_journal.save
1632 current_journal.save
1622 end
1633 end
1623 end
1634 end
1624
1635
1625 def send_notification
1636 def send_notification
1626 if Setting.notified_events.include?('issue_added')
1637 if Setting.notified_events.include?('issue_added')
1627 Mailer.deliver_issue_add(self)
1638 Mailer.deliver_issue_add(self)
1628 end
1639 end
1629 end
1640 end
1630
1641
1631 # Stores the previous assignee so we can still have access
1642 # Stores the previous assignee so we can still have access
1632 # to it during after_save callbacks (assigned_to_id_was is reset)
1643 # to it during after_save callbacks (assigned_to_id_was is reset)
1633 def set_assigned_to_was
1644 def set_assigned_to_was
1634 @previous_assigned_to_id = assigned_to_id_was
1645 @previous_assigned_to_id = assigned_to_id_was
1635 end
1646 end
1636
1647
1637 # Clears the previous assignee at the end of after_save callbacks
1648 # Clears the previous assignee at the end of after_save callbacks
1638 def clear_assigned_to_was
1649 def clear_assigned_to_was
1639 @assigned_to_was = nil
1650 @assigned_to_was = nil
1640 @previous_assigned_to_id = nil
1651 @previous_assigned_to_id = nil
1641 end
1652 end
1642
1653
1643 def clear_disabled_fields
1654 def clear_disabled_fields
1644 if tracker
1655 if tracker
1645 tracker.disabled_core_fields.each do |attribute|
1656 tracker.disabled_core_fields.each do |attribute|
1646 send "#{attribute}=", nil
1657 send "#{attribute}=", nil
1647 end
1658 end
1648 self.done_ratio ||= 0
1659 self.done_ratio ||= 0
1649 end
1660 end
1650 end
1661 end
1651 end
1662 end
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,2745 +1,2779
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class IssueTest < ActiveSupport::TestCase
20 class IssueTest < ActiveSupport::TestCase
21 fixtures :projects, :users, :email_addresses, :members, :member_roles, :roles,
21 fixtures :projects, :users, :email_addresses, :members, :member_roles, :roles,
22 :groups_users,
22 :groups_users,
23 :trackers, :projects_trackers,
23 :trackers, :projects_trackers,
24 :enabled_modules,
24 :enabled_modules,
25 :versions,
25 :versions,
26 :issue_statuses, :issue_categories, :issue_relations, :workflows,
26 :issue_statuses, :issue_categories, :issue_relations, :workflows,
27 :enumerations,
27 :enumerations,
28 :issues, :journals, :journal_details,
28 :issues, :journals, :journal_details,
29 :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values,
29 :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values,
30 :time_entries
30 :time_entries
31
31
32 include Redmine::I18n
32 include Redmine::I18n
33
33
34 def setup
34 def setup
35 set_language_if_valid 'en'
35 set_language_if_valid 'en'
36 end
36 end
37
37
38 def teardown
38 def teardown
39 User.current = nil
39 User.current = nil
40 end
40 end
41
41
42 def test_initialize
42 def test_initialize
43 issue = Issue.new
43 issue = Issue.new
44
44
45 assert_nil issue.project_id
45 assert_nil issue.project_id
46 assert_nil issue.tracker_id
46 assert_nil issue.tracker_id
47 assert_nil issue.status_id
47 assert_nil issue.status_id
48 assert_nil issue.author_id
48 assert_nil issue.author_id
49 assert_nil issue.assigned_to_id
49 assert_nil issue.assigned_to_id
50 assert_nil issue.category_id
50 assert_nil issue.category_id
51
51
52 assert_equal IssuePriority.default, issue.priority
52 assert_equal IssuePriority.default, issue.priority
53 end
53 end
54
54
55 def test_create
55 def test_create
56 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
56 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
57 :status_id => 1, :priority => IssuePriority.all.first,
57 :status_id => 1, :priority => IssuePriority.all.first,
58 :subject => 'test_create',
58 :subject => 'test_create',
59 :description => 'IssueTest#test_create', :estimated_hours => '1:30')
59 :description => 'IssueTest#test_create', :estimated_hours => '1:30')
60 assert issue.save
60 assert issue.save
61 issue.reload
61 issue.reload
62 assert_equal 1.5, issue.estimated_hours
62 assert_equal 1.5, issue.estimated_hours
63 end
63 end
64
64
65 def test_create_minimal
65 def test_create_minimal
66 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :subject => 'test_create')
66 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :subject => 'test_create')
67 assert issue.save
67 assert issue.save
68 assert_equal issue.tracker.default_status, issue.status
68 assert_equal issue.tracker.default_status, issue.status
69 assert issue.description.nil?
69 assert issue.description.nil?
70 assert_nil issue.estimated_hours
70 assert_nil issue.estimated_hours
71 end
71 end
72
72
73 def test_create_with_all_fields_disabled
73 def test_create_with_all_fields_disabled
74 tracker = Tracker.find(1)
74 tracker = Tracker.find(1)
75 tracker.core_fields = []
75 tracker.core_fields = []
76 tracker.save!
76 tracker.save!
77
77
78 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :subject => 'test_create_with_all_fields_disabled')
78 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :subject => 'test_create_with_all_fields_disabled')
79 assert_save issue
79 assert_save issue
80 end
80 end
81
81
82 def test_start_date_format_should_be_validated
82 def test_start_date_format_should_be_validated
83 set_language_if_valid 'en'
83 set_language_if_valid 'en'
84 ['2012', 'ABC', '2012-15-20'].each do |invalid_date|
84 ['2012', 'ABC', '2012-15-20'].each do |invalid_date|
85 issue = Issue.new(:start_date => invalid_date)
85 issue = Issue.new(:start_date => invalid_date)
86 assert !issue.valid?
86 assert !issue.valid?
87 assert_include 'Start date is not a valid date', issue.errors.full_messages, "No error found for invalid date #{invalid_date}"
87 assert_include 'Start date is not a valid date', issue.errors.full_messages, "No error found for invalid date #{invalid_date}"
88 end
88 end
89 end
89 end
90
90
91 def test_due_date_format_should_be_validated
91 def test_due_date_format_should_be_validated
92 set_language_if_valid 'en'
92 set_language_if_valid 'en'
93 ['2012', 'ABC', '2012-15-20'].each do |invalid_date|
93 ['2012', 'ABC', '2012-15-20'].each do |invalid_date|
94 issue = Issue.new(:due_date => invalid_date)
94 issue = Issue.new(:due_date => invalid_date)
95 assert !issue.valid?
95 assert !issue.valid?
96 assert_include 'Due date is not a valid date', issue.errors.full_messages, "No error found for invalid date #{invalid_date}"
96 assert_include 'Due date is not a valid date', issue.errors.full_messages, "No error found for invalid date #{invalid_date}"
97 end
97 end
98 end
98 end
99
99
100 def test_due_date_lesser_than_start_date_should_not_validate
100 def test_due_date_lesser_than_start_date_should_not_validate
101 set_language_if_valid 'en'
101 set_language_if_valid 'en'
102 issue = Issue.new(:start_date => '2012-10-06', :due_date => '2012-10-02')
102 issue = Issue.new(:start_date => '2012-10-06', :due_date => '2012-10-02')
103 assert !issue.valid?
103 assert !issue.valid?
104 assert_include 'Due date must be greater than start date', issue.errors.full_messages
104 assert_include 'Due date must be greater than start date', issue.errors.full_messages
105 end
105 end
106
106
107 def test_start_date_lesser_than_soonest_start_should_not_validate_on_create
107 def test_start_date_lesser_than_soonest_start_should_not_validate_on_create
108 issue = Issue.generate(:start_date => '2013-06-04')
108 issue = Issue.generate(:start_date => '2013-06-04')
109 issue.stubs(:soonest_start).returns(Date.parse('2013-06-10'))
109 issue.stubs(:soonest_start).returns(Date.parse('2013-06-10'))
110 assert !issue.valid?
110 assert !issue.valid?
111 assert_include "Start date cannot be earlier than 06/10/2013 because of preceding issues", issue.errors.full_messages
111 assert_include "Start date cannot be earlier than 06/10/2013 because of preceding issues", issue.errors.full_messages
112 end
112 end
113
113
114 def test_start_date_lesser_than_soonest_start_should_not_validate_on_update_if_changed
114 def test_start_date_lesser_than_soonest_start_should_not_validate_on_update_if_changed
115 issue = Issue.generate!(:start_date => '2013-06-04')
115 issue = Issue.generate!(:start_date => '2013-06-04')
116 issue.stubs(:soonest_start).returns(Date.parse('2013-06-10'))
116 issue.stubs(:soonest_start).returns(Date.parse('2013-06-10'))
117 issue.start_date = '2013-06-07'
117 issue.start_date = '2013-06-07'
118 assert !issue.valid?
118 assert !issue.valid?
119 assert_include "Start date cannot be earlier than 06/10/2013 because of preceding issues", issue.errors.full_messages
119 assert_include "Start date cannot be earlier than 06/10/2013 because of preceding issues", issue.errors.full_messages
120 end
120 end
121
121
122 def test_start_date_lesser_than_soonest_start_should_validate_on_update_if_unchanged
122 def test_start_date_lesser_than_soonest_start_should_validate_on_update_if_unchanged
123 issue = Issue.generate!(:start_date => '2013-06-04')
123 issue = Issue.generate!(:start_date => '2013-06-04')
124 issue.stubs(:soonest_start).returns(Date.parse('2013-06-10'))
124 issue.stubs(:soonest_start).returns(Date.parse('2013-06-10'))
125 assert issue.valid?
125 assert issue.valid?
126 end
126 end
127
127
128 def test_estimated_hours_should_be_validated
128 def test_estimated_hours_should_be_validated
129 set_language_if_valid 'en'
129 set_language_if_valid 'en'
130 ['-2'].each do |invalid|
130 ['-2'].each do |invalid|
131 issue = Issue.new(:estimated_hours => invalid)
131 issue = Issue.new(:estimated_hours => invalid)
132 assert !issue.valid?
132 assert !issue.valid?
133 assert_include 'Estimated time is invalid', issue.errors.full_messages
133 assert_include 'Estimated time is invalid', issue.errors.full_messages
134 end
134 end
135 end
135 end
136
136
137 def test_create_with_required_custom_field
137 def test_create_with_required_custom_field
138 set_language_if_valid 'en'
138 set_language_if_valid 'en'
139 field = IssueCustomField.find_by_name('Database')
139 field = IssueCustomField.find_by_name('Database')
140 field.update_attribute(:is_required, true)
140 field.update_attribute(:is_required, true)
141
141
142 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
142 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
143 :status_id => 1, :subject => 'test_create',
143 :status_id => 1, :subject => 'test_create',
144 :description => 'IssueTest#test_create_with_required_custom_field')
144 :description => 'IssueTest#test_create_with_required_custom_field')
145 assert issue.available_custom_fields.include?(field)
145 assert issue.available_custom_fields.include?(field)
146 # No value for the custom field
146 # No value for the custom field
147 assert !issue.save
147 assert !issue.save
148 assert_equal ["Database cannot be blank"], issue.errors.full_messages
148 assert_equal ["Database cannot be blank"], issue.errors.full_messages
149 # Blank value
149 # Blank value
150 issue.custom_field_values = { field.id => '' }
150 issue.custom_field_values = { field.id => '' }
151 assert !issue.save
151 assert !issue.save
152 assert_equal ["Database cannot be blank"], issue.errors.full_messages
152 assert_equal ["Database cannot be blank"], issue.errors.full_messages
153 # Invalid value
153 # Invalid value
154 issue.custom_field_values = { field.id => 'SQLServer' }
154 issue.custom_field_values = { field.id => 'SQLServer' }
155 assert !issue.save
155 assert !issue.save
156 assert_equal ["Database is not included in the list"], issue.errors.full_messages
156 assert_equal ["Database is not included in the list"], issue.errors.full_messages
157 # Valid value
157 # Valid value
158 issue.custom_field_values = { field.id => 'PostgreSQL' }
158 issue.custom_field_values = { field.id => 'PostgreSQL' }
159 assert issue.save
159 assert issue.save
160 issue.reload
160 issue.reload
161 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
161 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
162 end
162 end
163
163
164 def test_create_with_group_assignment
164 def test_create_with_group_assignment
165 with_settings :issue_group_assignment => '1' do
165 with_settings :issue_group_assignment => '1' do
166 assert Issue.new(:project_id => 2, :tracker_id => 1, :author_id => 1,
166 assert Issue.new(:project_id => 2, :tracker_id => 1, :author_id => 1,
167 :subject => 'Group assignment',
167 :subject => 'Group assignment',
168 :assigned_to_id => 11).save
168 :assigned_to_id => 11).save
169 issue = Issue.order('id DESC').first
169 issue = Issue.order('id DESC').first
170 assert_kind_of Group, issue.assigned_to
170 assert_kind_of Group, issue.assigned_to
171 assert_equal Group.find(11), issue.assigned_to
171 assert_equal Group.find(11), issue.assigned_to
172 end
172 end
173 end
173 end
174
174
175 def test_create_with_parent_issue_id
175 def test_create_with_parent_issue_id
176 issue = Issue.new(:project_id => 1, :tracker_id => 1,
176 issue = Issue.new(:project_id => 1, :tracker_id => 1,
177 :author_id => 1, :subject => 'Group assignment',
177 :author_id => 1, :subject => 'Group assignment',
178 :parent_issue_id => 1)
178 :parent_issue_id => 1)
179 assert_save issue
179 assert_save issue
180 assert_equal 1, issue.parent_issue_id
180 assert_equal 1, issue.parent_issue_id
181 assert_equal Issue.find(1), issue.parent
181 assert_equal Issue.find(1), issue.parent
182 end
182 end
183
183
184 def test_create_with_sharp_parent_issue_id
184 def test_create_with_sharp_parent_issue_id
185 issue = Issue.new(:project_id => 1, :tracker_id => 1,
185 issue = Issue.new(:project_id => 1, :tracker_id => 1,
186 :author_id => 1, :subject => 'Group assignment',
186 :author_id => 1, :subject => 'Group assignment',
187 :parent_issue_id => "#1")
187 :parent_issue_id => "#1")
188 assert_save issue
188 assert_save issue
189 assert_equal 1, issue.parent_issue_id
189 assert_equal 1, issue.parent_issue_id
190 assert_equal Issue.find(1), issue.parent
190 assert_equal Issue.find(1), issue.parent
191 end
191 end
192
192
193 def test_create_with_invalid_parent_issue_id
193 def test_create_with_invalid_parent_issue_id
194 set_language_if_valid 'en'
194 set_language_if_valid 'en'
195 issue = Issue.new(:project_id => 1, :tracker_id => 1,
195 issue = Issue.new(:project_id => 1, :tracker_id => 1,
196 :author_id => 1, :subject => 'Group assignment',
196 :author_id => 1, :subject => 'Group assignment',
197 :parent_issue_id => '01ABC')
197 :parent_issue_id => '01ABC')
198 assert !issue.save
198 assert !issue.save
199 assert_equal '01ABC', issue.parent_issue_id
199 assert_equal '01ABC', issue.parent_issue_id
200 assert_include 'Parent task is invalid', issue.errors.full_messages
200 assert_include 'Parent task is invalid', issue.errors.full_messages
201 end
201 end
202
202
203 def test_create_with_invalid_sharp_parent_issue_id
203 def test_create_with_invalid_sharp_parent_issue_id
204 set_language_if_valid 'en'
204 set_language_if_valid 'en'
205 issue = Issue.new(:project_id => 1, :tracker_id => 1,
205 issue = Issue.new(:project_id => 1, :tracker_id => 1,
206 :author_id => 1, :subject => 'Group assignment',
206 :author_id => 1, :subject => 'Group assignment',
207 :parent_issue_id => '#01ABC')
207 :parent_issue_id => '#01ABC')
208 assert !issue.save
208 assert !issue.save
209 assert_equal '#01ABC', issue.parent_issue_id
209 assert_equal '#01ABC', issue.parent_issue_id
210 assert_include 'Parent task is invalid', issue.errors.full_messages
210 assert_include 'Parent task is invalid', issue.errors.full_messages
211 end
211 end
212
212
213 def assert_visibility_match(user, issues)
213 def assert_visibility_match(user, issues)
214 assert_equal issues.collect(&:id).sort, Issue.all.select {|issue| issue.visible?(user)}.collect(&:id).sort
214 assert_equal issues.collect(&:id).sort, Issue.all.select {|issue| issue.visible?(user)}.collect(&:id).sort
215 end
215 end
216
216
217 def test_visible_scope_for_anonymous
217 def test_visible_scope_for_anonymous
218 # Anonymous user should see issues of public projects only
218 # Anonymous user should see issues of public projects only
219 issues = Issue.visible(User.anonymous).to_a
219 issues = Issue.visible(User.anonymous).to_a
220 assert issues.any?
220 assert issues.any?
221 assert_nil issues.detect {|issue| !issue.project.is_public?}
221 assert_nil issues.detect {|issue| !issue.project.is_public?}
222 assert_nil issues.detect {|issue| issue.is_private?}
222 assert_nil issues.detect {|issue| issue.is_private?}
223 assert_visibility_match User.anonymous, issues
223 assert_visibility_match User.anonymous, issues
224 end
224 end
225
225
226 def test_visible_scope_for_anonymous_without_view_issues_permissions
226 def test_visible_scope_for_anonymous_without_view_issues_permissions
227 # Anonymous user should not see issues without permission
227 # Anonymous user should not see issues without permission
228 Role.anonymous.remove_permission!(:view_issues)
228 Role.anonymous.remove_permission!(:view_issues)
229 issues = Issue.visible(User.anonymous).to_a
229 issues = Issue.visible(User.anonymous).to_a
230 assert issues.empty?
230 assert issues.empty?
231 assert_visibility_match User.anonymous, issues
231 assert_visibility_match User.anonymous, issues
232 end
232 end
233
233
234 def test_visible_scope_for_anonymous_without_view_issues_permissions_and_membership
234 def test_visible_scope_for_anonymous_without_view_issues_permissions_and_membership
235 Role.anonymous.remove_permission!(:view_issues)
235 Role.anonymous.remove_permission!(:view_issues)
236 Member.create!(:project_id => 1, :principal => Group.anonymous, :role_ids => [2])
236 Member.create!(:project_id => 1, :principal => Group.anonymous, :role_ids => [2])
237
237
238 issues = Issue.visible(User.anonymous).all
238 issues = Issue.visible(User.anonymous).all
239 assert issues.any?
239 assert issues.any?
240 assert_equal [1], issues.map(&:project_id).uniq.sort
240 assert_equal [1], issues.map(&:project_id).uniq.sort
241 assert_visibility_match User.anonymous, issues
241 assert_visibility_match User.anonymous, issues
242 end
242 end
243
243
244 def test_anonymous_should_not_see_private_issues_with_issues_visibility_set_to_default
244 def test_anonymous_should_not_see_private_issues_with_issues_visibility_set_to_default
245 assert Role.anonymous.update_attribute(:issues_visibility, 'default')
245 assert Role.anonymous.update_attribute(:issues_visibility, 'default')
246 issue = Issue.generate!(:author => User.anonymous, :assigned_to => User.anonymous, :is_private => true)
246 issue = Issue.generate!(:author => User.anonymous, :assigned_to => User.anonymous, :is_private => true)
247 assert_nil Issue.where(:id => issue.id).visible(User.anonymous).first
247 assert_nil Issue.where(:id => issue.id).visible(User.anonymous).first
248 assert !issue.visible?(User.anonymous)
248 assert !issue.visible?(User.anonymous)
249 end
249 end
250
250
251 def test_anonymous_should_not_see_private_issues_with_issues_visibility_set_to_own
251 def test_anonymous_should_not_see_private_issues_with_issues_visibility_set_to_own
252 assert Role.anonymous.update_attribute(:issues_visibility, 'own')
252 assert Role.anonymous.update_attribute(:issues_visibility, 'own')
253 issue = Issue.generate!(:author => User.anonymous, :assigned_to => User.anonymous, :is_private => true)
253 issue = Issue.generate!(:author => User.anonymous, :assigned_to => User.anonymous, :is_private => true)
254 assert_nil Issue.where(:id => issue.id).visible(User.anonymous).first
254 assert_nil Issue.where(:id => issue.id).visible(User.anonymous).first
255 assert !issue.visible?(User.anonymous)
255 assert !issue.visible?(User.anonymous)
256 end
256 end
257
257
258 def test_visible_scope_for_non_member
258 def test_visible_scope_for_non_member
259 user = User.find(9)
259 user = User.find(9)
260 assert user.projects.empty?
260 assert user.projects.empty?
261 # Non member user should see issues of public projects only
261 # Non member user should see issues of public projects only
262 issues = Issue.visible(user).to_a
262 issues = Issue.visible(user).to_a
263 assert issues.any?
263 assert issues.any?
264 assert_nil issues.detect {|issue| !issue.project.is_public?}
264 assert_nil issues.detect {|issue| !issue.project.is_public?}
265 assert_nil issues.detect {|issue| issue.is_private?}
265 assert_nil issues.detect {|issue| issue.is_private?}
266 assert_visibility_match user, issues
266 assert_visibility_match user, issues
267 end
267 end
268
268
269 def test_visible_scope_for_non_member_with_own_issues_visibility
269 def test_visible_scope_for_non_member_with_own_issues_visibility
270 Role.non_member.update_attribute :issues_visibility, 'own'
270 Role.non_member.update_attribute :issues_visibility, 'own'
271 Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 9, :subject => 'Issue by non member')
271 Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 9, :subject => 'Issue by non member')
272 user = User.find(9)
272 user = User.find(9)
273
273
274 issues = Issue.visible(user).to_a
274 issues = Issue.visible(user).to_a
275 assert issues.any?
275 assert issues.any?
276 assert_nil issues.detect {|issue| issue.author != user}
276 assert_nil issues.detect {|issue| issue.author != user}
277 assert_visibility_match user, issues
277 assert_visibility_match user, issues
278 end
278 end
279
279
280 def test_visible_scope_for_non_member_without_view_issues_permissions
280 def test_visible_scope_for_non_member_without_view_issues_permissions
281 # Non member user should not see issues without permission
281 # Non member user should not see issues without permission
282 Role.non_member.remove_permission!(:view_issues)
282 Role.non_member.remove_permission!(:view_issues)
283 user = User.find(9)
283 user = User.find(9)
284 assert user.projects.empty?
284 assert user.projects.empty?
285 issues = Issue.visible(user).to_a
285 issues = Issue.visible(user).to_a
286 assert issues.empty?
286 assert issues.empty?
287 assert_visibility_match user, issues
287 assert_visibility_match user, issues
288 end
288 end
289
289
290 def test_visible_scope_for_non_member_without_view_issues_permissions_and_membership
290 def test_visible_scope_for_non_member_without_view_issues_permissions_and_membership
291 Role.non_member.remove_permission!(:view_issues)
291 Role.non_member.remove_permission!(:view_issues)
292 Member.create!(:project_id => 1, :principal => Group.non_member, :role_ids => [2])
292 Member.create!(:project_id => 1, :principal => Group.non_member, :role_ids => [2])
293 user = User.find(9)
293 user = User.find(9)
294
294
295 issues = Issue.visible(user).all
295 issues = Issue.visible(user).all
296 assert issues.any?
296 assert issues.any?
297 assert_equal [1], issues.map(&:project_id).uniq.sort
297 assert_equal [1], issues.map(&:project_id).uniq.sort
298 assert_visibility_match user, issues
298 assert_visibility_match user, issues
299 end
299 end
300
300
301 def test_visible_scope_for_member
301 def test_visible_scope_for_member
302 user = User.find(9)
302 user = User.find(9)
303 # User should see issues of projects for which user has view_issues permissions only
303 # User should see issues of projects for which user has view_issues permissions only
304 Role.non_member.remove_permission!(:view_issues)
304 Role.non_member.remove_permission!(:view_issues)
305 Member.create!(:principal => user, :project_id => 3, :role_ids => [2])
305 Member.create!(:principal => user, :project_id => 3, :role_ids => [2])
306 issues = Issue.visible(user).to_a
306 issues = Issue.visible(user).to_a
307 assert issues.any?
307 assert issues.any?
308 assert_nil issues.detect {|issue| issue.project_id != 3}
308 assert_nil issues.detect {|issue| issue.project_id != 3}
309 assert_nil issues.detect {|issue| issue.is_private?}
309 assert_nil issues.detect {|issue| issue.is_private?}
310 assert_visibility_match user, issues
310 assert_visibility_match user, issues
311 end
311 end
312
312
313 def test_visible_scope_for_member_without_view_issues_permission_and_non_member_role_having_the_permission
313 def test_visible_scope_for_member_without_view_issues_permission_and_non_member_role_having_the_permission
314 Role.non_member.add_permission!(:view_issues)
314 Role.non_member.add_permission!(:view_issues)
315 Role.find(1).remove_permission!(:view_issues)
315 Role.find(1).remove_permission!(:view_issues)
316 user = User.find(2)
316 user = User.find(2)
317
317
318 assert_equal 0, Issue.where(:project_id => 1).visible(user).count
318 assert_equal 0, Issue.where(:project_id => 1).visible(user).count
319 assert_equal false, Issue.where(:project_id => 1).first.visible?(user)
319 assert_equal false, Issue.where(:project_id => 1).first.visible?(user)
320 end
320 end
321
321
322 def test_visible_scope_for_member_with_groups_should_return_assigned_issues
322 def test_visible_scope_for_member_with_groups_should_return_assigned_issues
323 user = User.find(8)
323 user = User.find(8)
324 assert user.groups.any?
324 assert user.groups.any?
325 Member.create!(:principal => user.groups.first, :project_id => 1, :role_ids => [2])
325 Member.create!(:principal => user.groups.first, :project_id => 1, :role_ids => [2])
326 Role.non_member.remove_permission!(:view_issues)
326 Role.non_member.remove_permission!(:view_issues)
327
327
328 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 3,
328 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 3,
329 :status_id => 1, :priority => IssuePriority.all.first,
329 :status_id => 1, :priority => IssuePriority.all.first,
330 :subject => 'Assignment test',
330 :subject => 'Assignment test',
331 :assigned_to => user.groups.first,
331 :assigned_to => user.groups.first,
332 :is_private => true)
332 :is_private => true)
333
333
334 Role.find(2).update_attribute :issues_visibility, 'default'
334 Role.find(2).update_attribute :issues_visibility, 'default'
335 issues = Issue.visible(User.find(8)).to_a
335 issues = Issue.visible(User.find(8)).to_a
336 assert issues.any?
336 assert issues.any?
337 assert issues.include?(issue)
337 assert issues.include?(issue)
338
338
339 Role.find(2).update_attribute :issues_visibility, 'own'
339 Role.find(2).update_attribute :issues_visibility, 'own'
340 issues = Issue.visible(User.find(8)).to_a
340 issues = Issue.visible(User.find(8)).to_a
341 assert issues.any?
341 assert issues.any?
342 assert_include issue, issues
342 assert_include issue, issues
343 end
343 end
344
344
345 def test_visible_scope_for_admin
345 def test_visible_scope_for_admin
346 user = User.find(1)
346 user = User.find(1)
347 user.members.each(&:destroy)
347 user.members.each(&:destroy)
348 assert user.projects.empty?
348 assert user.projects.empty?
349 issues = Issue.visible(user).to_a
349 issues = Issue.visible(user).to_a
350 assert issues.any?
350 assert issues.any?
351 # Admin should see issues on private projects that admin does not belong to
351 # Admin should see issues on private projects that admin does not belong to
352 assert issues.detect {|issue| !issue.project.is_public?}
352 assert issues.detect {|issue| !issue.project.is_public?}
353 # Admin should see private issues of other users
353 # Admin should see private issues of other users
354 assert issues.detect {|issue| issue.is_private? && issue.author != user}
354 assert issues.detect {|issue| issue.is_private? && issue.author != user}
355 assert_visibility_match user, issues
355 assert_visibility_match user, issues
356 end
356 end
357
357
358 def test_visible_scope_with_project
358 def test_visible_scope_with_project
359 project = Project.find(1)
359 project = Project.find(1)
360 issues = Issue.visible(User.find(2), :project => project).to_a
360 issues = Issue.visible(User.find(2), :project => project).to_a
361 projects = issues.collect(&:project).uniq
361 projects = issues.collect(&:project).uniq
362 assert_equal 1, projects.size
362 assert_equal 1, projects.size
363 assert_equal project, projects.first
363 assert_equal project, projects.first
364 end
364 end
365
365
366 def test_visible_scope_with_project_and_subprojects
366 def test_visible_scope_with_project_and_subprojects
367 project = Project.find(1)
367 project = Project.find(1)
368 issues = Issue.visible(User.find(2), :project => project, :with_subprojects => true).to_a
368 issues = Issue.visible(User.find(2), :project => project, :with_subprojects => true).to_a
369 projects = issues.collect(&:project).uniq
369 projects = issues.collect(&:project).uniq
370 assert projects.size > 1
370 assert projects.size > 1
371 assert_equal [], projects.select {|p| !p.is_or_is_descendant_of?(project)}
371 assert_equal [], projects.select {|p| !p.is_or_is_descendant_of?(project)}
372 end
372 end
373
373
374 def test_visible_and_nested_set_scopes
374 def test_visible_and_nested_set_scopes
375 user = User.generate!
375 user = User.generate!
376 parent = Issue.generate!(:assigned_to => user)
376 parent = Issue.generate!(:assigned_to => user)
377 assert parent.visible?(user)
377 assert parent.visible?(user)
378 child1 = Issue.generate!(:parent_issue_id => parent.id, :assigned_to => user)
378 child1 = Issue.generate!(:parent_issue_id => parent.id, :assigned_to => user)
379 child2 = Issue.generate!(:parent_issue_id => parent.id, :assigned_to => user)
379 child2 = Issue.generate!(:parent_issue_id => parent.id, :assigned_to => user)
380 parent.reload
380 parent.reload
381 child1.reload
381 child1.reload
382 child2.reload
382 child2.reload
383 assert child1.visible?(user)
383 assert child1.visible?(user)
384 assert child2.visible?(user)
384 assert child2.visible?(user)
385 assert_equal 2, parent.descendants.count
385 assert_equal 2, parent.descendants.count
386 assert_equal 2, parent.descendants.visible(user).count
386 assert_equal 2, parent.descendants.visible(user).count
387 # awesome_nested_set 2-1-stable branch has regression.
387 # awesome_nested_set 2-1-stable branch has regression.
388 # https://github.com/collectiveidea/awesome_nested_set/commit/3d5ac746542b564f6586c2316180254b088bebb6
388 # https://github.com/collectiveidea/awesome_nested_set/commit/3d5ac746542b564f6586c2316180254b088bebb6
389 # ActiveRecord::StatementInvalid: SQLite3::SQLException: ambiguous column name: lft:
389 # ActiveRecord::StatementInvalid: SQLite3::SQLException: ambiguous column name: lft:
390 assert_equal 2, parent.descendants.collect{|i| i}.size
390 assert_equal 2, parent.descendants.collect{|i| i}.size
391 assert_equal 2, parent.descendants.visible(user).collect{|i| i}.size
391 assert_equal 2, parent.descendants.visible(user).collect{|i| i}.size
392 end
392 end
393
393
394 def test_visible_scope_with_unsaved_user_should_not_raise_an_error
394 def test_visible_scope_with_unsaved_user_should_not_raise_an_error
395 user = User.new
395 user = User.new
396 assert_nothing_raised do
396 assert_nothing_raised do
397 Issue.visible(user).to_a
397 Issue.visible(user).to_a
398 end
398 end
399 end
399 end
400
400
401 def test_open_scope
401 def test_open_scope
402 issues = Issue.open.to_a
402 issues = Issue.open.to_a
403 assert_nil issues.detect(&:closed?)
403 assert_nil issues.detect(&:closed?)
404 end
404 end
405
405
406 def test_open_scope_with_arg
406 def test_open_scope_with_arg
407 issues = Issue.open(false).to_a
407 issues = Issue.open(false).to_a
408 assert_equal issues, issues.select(&:closed?)
408 assert_equal issues, issues.select(&:closed?)
409 end
409 end
410
410
411 def test_fixed_version_scope_with_a_version_should_return_its_fixed_issues
411 def test_fixed_version_scope_with_a_version_should_return_its_fixed_issues
412 version = Version.find(2)
412 version = Version.find(2)
413 assert version.fixed_issues.any?
413 assert version.fixed_issues.any?
414 assert_equal version.fixed_issues.to_a.sort, Issue.fixed_version(version).to_a.sort
414 assert_equal version.fixed_issues.to_a.sort, Issue.fixed_version(version).to_a.sort
415 end
415 end
416
416
417 def test_fixed_version_scope_with_empty_array_should_return_no_result
417 def test_fixed_version_scope_with_empty_array_should_return_no_result
418 assert_equal 0, Issue.fixed_version([]).count
418 assert_equal 0, Issue.fixed_version([]).count
419 end
419 end
420
420
421 def test_errors_full_messages_should_include_custom_fields_errors
421 def test_errors_full_messages_should_include_custom_fields_errors
422 field = IssueCustomField.find_by_name('Database')
422 field = IssueCustomField.find_by_name('Database')
423
423
424 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
424 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
425 :status_id => 1, :subject => 'test_create',
425 :status_id => 1, :subject => 'test_create',
426 :description => 'IssueTest#test_create_with_required_custom_field')
426 :description => 'IssueTest#test_create_with_required_custom_field')
427 assert issue.available_custom_fields.include?(field)
427 assert issue.available_custom_fields.include?(field)
428 # Invalid value
428 # Invalid value
429 issue.custom_field_values = { field.id => 'SQLServer' }
429 issue.custom_field_values = { field.id => 'SQLServer' }
430
430
431 assert !issue.valid?
431 assert !issue.valid?
432 assert_equal 1, issue.errors.full_messages.size
432 assert_equal 1, issue.errors.full_messages.size
433 assert_equal "Database #{I18n.translate('activerecord.errors.messages.inclusion')}",
433 assert_equal "Database #{I18n.translate('activerecord.errors.messages.inclusion')}",
434 issue.errors.full_messages.first
434 issue.errors.full_messages.first
435 end
435 end
436
436
437 def test_update_issue_with_required_custom_field
437 def test_update_issue_with_required_custom_field
438 field = IssueCustomField.find_by_name('Database')
438 field = IssueCustomField.find_by_name('Database')
439 field.update_attribute(:is_required, true)
439 field.update_attribute(:is_required, true)
440
440
441 issue = Issue.find(1)
441 issue = Issue.find(1)
442 assert_nil issue.custom_value_for(field)
442 assert_nil issue.custom_value_for(field)
443 assert issue.available_custom_fields.include?(field)
443 assert issue.available_custom_fields.include?(field)
444 # No change to custom values, issue can be saved
444 # No change to custom values, issue can be saved
445 assert issue.save
445 assert issue.save
446 # Blank value
446 # Blank value
447 issue.custom_field_values = { field.id => '' }
447 issue.custom_field_values = { field.id => '' }
448 assert !issue.save
448 assert !issue.save
449 # Valid value
449 # Valid value
450 issue.custom_field_values = { field.id => 'PostgreSQL' }
450 issue.custom_field_values = { field.id => 'PostgreSQL' }
451 assert issue.save
451 assert issue.save
452 issue.reload
452 issue.reload
453 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
453 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
454 end
454 end
455
455
456 def test_should_not_update_attributes_if_custom_fields_validation_fails
456 def test_should_not_update_attributes_if_custom_fields_validation_fails
457 issue = Issue.find(1)
457 issue = Issue.find(1)
458 field = IssueCustomField.find_by_name('Database')
458 field = IssueCustomField.find_by_name('Database')
459 assert issue.available_custom_fields.include?(field)
459 assert issue.available_custom_fields.include?(field)
460
460
461 issue.custom_field_values = { field.id => 'Invalid' }
461 issue.custom_field_values = { field.id => 'Invalid' }
462 issue.subject = 'Should be not be saved'
462 issue.subject = 'Should be not be saved'
463 assert !issue.save
463 assert !issue.save
464
464
465 issue.reload
465 issue.reload
466 assert_equal "Cannot print recipes", issue.subject
466 assert_equal "Cannot print recipes", issue.subject
467 end
467 end
468
468
469 def test_should_not_recreate_custom_values_objects_on_update
469 def test_should_not_recreate_custom_values_objects_on_update
470 field = IssueCustomField.find_by_name('Database')
470 field = IssueCustomField.find_by_name('Database')
471
471
472 issue = Issue.find(1)
472 issue = Issue.find(1)
473 issue.custom_field_values = { field.id => 'PostgreSQL' }
473 issue.custom_field_values = { field.id => 'PostgreSQL' }
474 assert issue.save
474 assert issue.save
475 custom_value = issue.custom_value_for(field)
475 custom_value = issue.custom_value_for(field)
476 issue.reload
476 issue.reload
477 issue.custom_field_values = { field.id => 'MySQL' }
477 issue.custom_field_values = { field.id => 'MySQL' }
478 assert issue.save
478 assert issue.save
479 issue.reload
479 issue.reload
480 assert_equal custom_value.id, issue.custom_value_for(field).id
480 assert_equal custom_value.id, issue.custom_value_for(field).id
481 end
481 end
482
482
483 def test_should_not_update_custom_fields_on_changing_tracker_with_different_custom_fields
483 def test_should_not_update_custom_fields_on_changing_tracker_with_different_custom_fields
484 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1,
484 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1,
485 :status_id => 1, :subject => 'Test',
485 :status_id => 1, :subject => 'Test',
486 :custom_field_values => {'2' => 'Test'})
486 :custom_field_values => {'2' => 'Test'})
487 assert !Tracker.find(2).custom_field_ids.include?(2)
487 assert !Tracker.find(2).custom_field_ids.include?(2)
488
488
489 issue = Issue.find(issue.id)
489 issue = Issue.find(issue.id)
490 issue.attributes = {:tracker_id => 2, :custom_field_values => {'1' => ''}}
490 issue.attributes = {:tracker_id => 2, :custom_field_values => {'1' => ''}}
491
491
492 issue = Issue.find(issue.id)
492 issue = Issue.find(issue.id)
493 custom_value = issue.custom_value_for(2)
493 custom_value = issue.custom_value_for(2)
494 assert_not_nil custom_value
494 assert_not_nil custom_value
495 assert_equal 'Test', custom_value.value
495 assert_equal 'Test', custom_value.value
496 end
496 end
497
497
498 def test_assigning_tracker_id_should_reload_custom_fields_values
498 def test_assigning_tracker_id_should_reload_custom_fields_values
499 issue = Issue.new(:project => Project.find(1))
499 issue = Issue.new(:project => Project.find(1))
500 assert issue.custom_field_values.empty?
500 assert issue.custom_field_values.empty?
501 issue.tracker_id = 1
501 issue.tracker_id = 1
502 assert issue.custom_field_values.any?
502 assert issue.custom_field_values.any?
503 end
503 end
504
504
505 def test_assigning_attributes_should_assign_project_and_tracker_first
505 def test_assigning_attributes_should_assign_project_and_tracker_first
506 seq = sequence('seq')
506 seq = sequence('seq')
507 issue = Issue.new
507 issue = Issue.new
508 issue.expects(:project_id=).in_sequence(seq)
508 issue.expects(:project_id=).in_sequence(seq)
509 issue.expects(:tracker_id=).in_sequence(seq)
509 issue.expects(:tracker_id=).in_sequence(seq)
510 issue.expects(:subject=).in_sequence(seq)
510 issue.expects(:subject=).in_sequence(seq)
511 issue.attributes = {:tracker_id => 2, :project_id => 1, :subject => 'Test'}
511 issue.attributes = {:tracker_id => 2, :project_id => 1, :subject => 'Test'}
512 end
512 end
513
513
514 def test_assigning_tracker_and_custom_fields_should_assign_custom_fields
514 def test_assigning_tracker_and_custom_fields_should_assign_custom_fields
515 attributes = ActiveSupport::OrderedHash.new
515 attributes = ActiveSupport::OrderedHash.new
516 attributes['custom_field_values'] = { '1' => 'MySQL' }
516 attributes['custom_field_values'] = { '1' => 'MySQL' }
517 attributes['tracker_id'] = '1'
517 attributes['tracker_id'] = '1'
518 issue = Issue.new(:project => Project.find(1))
518 issue = Issue.new(:project => Project.find(1))
519 issue.attributes = attributes
519 issue.attributes = attributes
520 assert_equal 'MySQL', issue.custom_field_value(1)
520 assert_equal 'MySQL', issue.custom_field_value(1)
521 end
521 end
522
522
523 def test_changing_tracker_should_clear_disabled_core_fields
523 def test_changing_tracker_should_clear_disabled_core_fields
524 tracker = Tracker.find(2)
524 tracker = Tracker.find(2)
525 tracker.core_fields = tracker.core_fields - %w(due_date)
525 tracker.core_fields = tracker.core_fields - %w(due_date)
526 tracker.save!
526 tracker.save!
527
527
528 issue = Issue.generate!(:tracker_id => 1, :start_date => Date.today, :due_date => Date.today)
528 issue = Issue.generate!(:tracker_id => 1, :start_date => Date.today, :due_date => Date.today)
529 issue.save!
529 issue.save!
530
530
531 issue.tracker_id = 2
531 issue.tracker_id = 2
532 issue.save!
532 issue.save!
533 assert_not_nil issue.start_date
533 assert_not_nil issue.start_date
534 assert_nil issue.due_date
534 assert_nil issue.due_date
535 end
535 end
536
536
537 def test_changing_tracker_should_not_add_cleared_fields_to_journal
537 def test_changing_tracker_should_not_add_cleared_fields_to_journal
538 tracker = Tracker.find(2)
538 tracker = Tracker.find(2)
539 tracker.core_fields = tracker.core_fields - %w(due_date)
539 tracker.core_fields = tracker.core_fields - %w(due_date)
540 tracker.save!
540 tracker.save!
541
541
542 issue = Issue.generate!(:tracker_id => 1, :due_date => Date.today)
542 issue = Issue.generate!(:tracker_id => 1, :due_date => Date.today)
543 issue.save!
543 issue.save!
544
544
545 assert_difference 'Journal.count' do
545 assert_difference 'Journal.count' do
546 issue.init_journal User.find(1)
546 issue.init_journal User.find(1)
547 issue.tracker_id = 2
547 issue.tracker_id = 2
548 issue.save!
548 issue.save!
549 assert_nil issue.due_date
549 assert_nil issue.due_date
550 end
550 end
551 journal = Journal.order('id DESC').first
551 journal = Journal.order('id DESC').first
552 assert_equal 1, journal.details.count
552 assert_equal 1, journal.details.count
553 end
553 end
554
554
555 def test_reload_should_reload_custom_field_values
555 def test_reload_should_reload_custom_field_values
556 issue = Issue.generate!
556 issue = Issue.generate!
557 issue.custom_field_values = {'2' => 'Foo'}
557 issue.custom_field_values = {'2' => 'Foo'}
558 issue.save!
558 issue.save!
559
559
560 issue = Issue.order('id desc').first
560 issue = Issue.order('id desc').first
561 assert_equal 'Foo', issue.custom_field_value(2)
561 assert_equal 'Foo', issue.custom_field_value(2)
562
562
563 issue.custom_field_values = {'2' => 'Bar'}
563 issue.custom_field_values = {'2' => 'Bar'}
564 assert_equal 'Bar', issue.custom_field_value(2)
564 assert_equal 'Bar', issue.custom_field_value(2)
565
565
566 issue.reload
566 issue.reload
567 assert_equal 'Foo', issue.custom_field_value(2)
567 assert_equal 'Foo', issue.custom_field_value(2)
568 end
568 end
569
569
570 def test_should_update_issue_with_disabled_tracker
570 def test_should_update_issue_with_disabled_tracker
571 p = Project.find(1)
571 p = Project.find(1)
572 issue = Issue.find(1)
572 issue = Issue.find(1)
573
573
574 p.trackers.delete(issue.tracker)
574 p.trackers.delete(issue.tracker)
575 assert !p.trackers.include?(issue.tracker)
575 assert !p.trackers.include?(issue.tracker)
576
576
577 issue.reload
577 issue.reload
578 issue.subject = 'New subject'
578 issue.subject = 'New subject'
579 assert issue.save
579 assert issue.save
580 end
580 end
581
581
582 def test_should_not_set_a_disabled_tracker
582 def test_should_not_set_a_disabled_tracker
583 p = Project.find(1)
583 p = Project.find(1)
584 p.trackers.delete(Tracker.find(2))
584 p.trackers.delete(Tracker.find(2))
585
585
586 issue = Issue.find(1)
586 issue = Issue.find(1)
587 issue.tracker_id = 2
587 issue.tracker_id = 2
588 issue.subject = 'New subject'
588 issue.subject = 'New subject'
589 assert !issue.save
589 assert !issue.save
590 assert_not_equal [], issue.errors[:tracker_id]
590 assert_not_equal [], issue.errors[:tracker_id]
591 end
591 end
592
592
593 def test_category_based_assignment
593 def test_category_based_assignment
594 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3,
594 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3,
595 :status_id => 1, :priority => IssuePriority.all.first,
595 :status_id => 1, :priority => IssuePriority.all.first,
596 :subject => 'Assignment test',
596 :subject => 'Assignment test',
597 :description => 'Assignment test', :category_id => 1)
597 :description => 'Assignment test', :category_id => 1)
598 assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
598 assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
599 end
599 end
600
600
601 def test_new_statuses_allowed_to
601 def test_new_statuses_allowed_to
602 WorkflowTransition.delete_all
602 WorkflowTransition.delete_all
603 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
603 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
604 :old_status_id => 1, :new_status_id => 2,
604 :old_status_id => 1, :new_status_id => 2,
605 :author => false, :assignee => false)
605 :author => false, :assignee => false)
606 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
606 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
607 :old_status_id => 1, :new_status_id => 3,
607 :old_status_id => 1, :new_status_id => 3,
608 :author => true, :assignee => false)
608 :author => true, :assignee => false)
609 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
609 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
610 :old_status_id => 1, :new_status_id => 4,
610 :old_status_id => 1, :new_status_id => 4,
611 :author => false, :assignee => true)
611 :author => false, :assignee => true)
612 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
612 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
613 :old_status_id => 1, :new_status_id => 5,
613 :old_status_id => 1, :new_status_id => 5,
614 :author => true, :assignee => true)
614 :author => true, :assignee => true)
615 status = IssueStatus.find(1)
615 status = IssueStatus.find(1)
616 role = Role.find(1)
616 role = Role.find(1)
617 tracker = Tracker.find(1)
617 tracker = Tracker.find(1)
618 user = User.find(2)
618 user = User.find(2)
619
619
620 issue = Issue.generate!(:tracker => tracker, :status => status,
620 issue = Issue.generate!(:tracker => tracker, :status => status,
621 :project_id => 1, :author_id => 1)
621 :project_id => 1, :author_id => 1)
622 assert_equal [1, 2], issue.new_statuses_allowed_to(user).map(&:id)
622 assert_equal [1, 2], issue.new_statuses_allowed_to(user).map(&:id)
623
623
624 issue = Issue.generate!(:tracker => tracker, :status => status,
624 issue = Issue.generate!(:tracker => tracker, :status => status,
625 :project_id => 1, :author => user)
625 :project_id => 1, :author => user)
626 assert_equal [1, 2, 3, 5], issue.new_statuses_allowed_to(user).map(&:id)
626 assert_equal [1, 2, 3, 5], issue.new_statuses_allowed_to(user).map(&:id)
627
627
628 issue = Issue.generate!(:tracker => tracker, :status => status,
628 issue = Issue.generate!(:tracker => tracker, :status => status,
629 :project_id => 1, :author_id => 1,
629 :project_id => 1, :author_id => 1,
630 :assigned_to => user)
630 :assigned_to => user)
631 assert_equal [1, 2, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
631 assert_equal [1, 2, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
632
632
633 issue = Issue.generate!(:tracker => tracker, :status => status,
633 issue = Issue.generate!(:tracker => tracker, :status => status,
634 :project_id => 1, :author => user,
634 :project_id => 1, :author => user,
635 :assigned_to => user)
635 :assigned_to => user)
636 assert_equal [1, 2, 3, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
636 assert_equal [1, 2, 3, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
637
637
638 group = Group.generate!
638 group = Group.generate!
639 group.users << user
639 group.users << user
640 issue = Issue.generate!(:tracker => tracker, :status => status,
640 issue = Issue.generate!(:tracker => tracker, :status => status,
641 :project_id => 1, :author => user,
641 :project_id => 1, :author => user,
642 :assigned_to => group)
642 :assigned_to => group)
643 assert_equal [1, 2, 3, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
643 assert_equal [1, 2, 3, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
644 end
644 end
645
645
646 def test_new_statuses_allowed_to_should_consider_group_assignment
646 def test_new_statuses_allowed_to_should_consider_group_assignment
647 WorkflowTransition.delete_all
647 WorkflowTransition.delete_all
648 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
648 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
649 :old_status_id => 1, :new_status_id => 4,
649 :old_status_id => 1, :new_status_id => 4,
650 :author => false, :assignee => true)
650 :author => false, :assignee => true)
651 user = User.find(2)
651 user = User.find(2)
652 group = Group.generate!
652 group = Group.generate!
653 group.users << user
653 group.users << user
654
654
655 issue = Issue.generate!(:author_id => 1, :assigned_to => group)
655 issue = Issue.generate!(:author_id => 1, :assigned_to => group)
656 assert_include 4, issue.new_statuses_allowed_to(user).map(&:id)
656 assert_include 4, issue.new_statuses_allowed_to(user).map(&:id)
657 end
657 end
658
658
659 def test_new_statuses_allowed_to_should_return_all_transitions_for_admin
659 def test_new_statuses_allowed_to_should_return_all_transitions_for_admin
660 admin = User.find(1)
660 admin = User.find(1)
661 issue = Issue.find(1)
661 issue = Issue.find(1)
662 assert !admin.member_of?(issue.project)
662 assert !admin.member_of?(issue.project)
663 expected_statuses = [issue.status] +
663 expected_statuses = [issue.status] +
664 WorkflowTransition.where(:old_status_id => issue.status_id).
664 WorkflowTransition.where(:old_status_id => issue.status_id).
665 map(&:new_status).uniq.sort
665 map(&:new_status).uniq.sort
666 assert_equal expected_statuses, issue.new_statuses_allowed_to(admin)
666 assert_equal expected_statuses, issue.new_statuses_allowed_to(admin)
667 end
667 end
668
668
669 def test_new_statuses_allowed_to_should_return_default_and_current_status_when_copying
669 def test_new_statuses_allowed_to_should_return_default_and_current_status_when_copying
670 issue = Issue.find(1).copy
670 issue = Issue.find(1).copy
671 assert_equal [1], issue.new_statuses_allowed_to(User.find(2)).map(&:id)
671 assert_equal [1], issue.new_statuses_allowed_to(User.find(2)).map(&:id)
672
672
673 issue = Issue.find(2).copy
673 issue = Issue.find(2).copy
674 assert_equal [1, 2], issue.new_statuses_allowed_to(User.find(2)).map(&:id)
674 assert_equal [1, 2], issue.new_statuses_allowed_to(User.find(2)).map(&:id)
675 end
675 end
676
676
677 def test_safe_attributes_names_should_not_include_disabled_field
677 def test_safe_attributes_names_should_not_include_disabled_field
678 tracker = Tracker.new(:core_fields => %w(assigned_to_id fixed_version_id))
678 tracker = Tracker.new(:core_fields => %w(assigned_to_id fixed_version_id))
679
679
680 issue = Issue.new(:tracker => tracker)
680 issue = Issue.new(:tracker => tracker)
681 assert_include 'tracker_id', issue.safe_attribute_names
681 assert_include 'tracker_id', issue.safe_attribute_names
682 assert_include 'status_id', issue.safe_attribute_names
682 assert_include 'status_id', issue.safe_attribute_names
683 assert_include 'subject', issue.safe_attribute_names
683 assert_include 'subject', issue.safe_attribute_names
684 assert_include 'description', issue.safe_attribute_names
684 assert_include 'description', issue.safe_attribute_names
685 assert_include 'custom_field_values', issue.safe_attribute_names
685 assert_include 'custom_field_values', issue.safe_attribute_names
686 assert_include 'custom_fields', issue.safe_attribute_names
686 assert_include 'custom_fields', issue.safe_attribute_names
687 assert_include 'lock_version', issue.safe_attribute_names
687 assert_include 'lock_version', issue.safe_attribute_names
688
688
689 tracker.core_fields.each do |field|
689 tracker.core_fields.each do |field|
690 assert_include field, issue.safe_attribute_names
690 assert_include field, issue.safe_attribute_names
691 end
691 end
692
692
693 tracker.disabled_core_fields.each do |field|
693 tracker.disabled_core_fields.each do |field|
694 assert_not_include field, issue.safe_attribute_names
694 assert_not_include field, issue.safe_attribute_names
695 end
695 end
696 end
696 end
697
697
698 def test_safe_attributes_should_ignore_disabled_fields
698 def test_safe_attributes_should_ignore_disabled_fields
699 tracker = Tracker.find(1)
699 tracker = Tracker.find(1)
700 tracker.core_fields = %w(assigned_to_id due_date)
700 tracker.core_fields = %w(assigned_to_id due_date)
701 tracker.save!
701 tracker.save!
702
702
703 issue = Issue.new(:tracker => tracker)
703 issue = Issue.new(:tracker => tracker)
704 issue.safe_attributes = {'start_date' => '2012-07-14', 'due_date' => '2012-07-14'}
704 issue.safe_attributes = {'start_date' => '2012-07-14', 'due_date' => '2012-07-14'}
705 assert_nil issue.start_date
705 assert_nil issue.start_date
706 assert_equal Date.parse('2012-07-14'), issue.due_date
706 assert_equal Date.parse('2012-07-14'), issue.due_date
707 end
707 end
708
708
709 def test_safe_attributes_should_accept_target_tracker_enabled_fields
709 def test_safe_attributes_should_accept_target_tracker_enabled_fields
710 source = Tracker.find(1)
710 source = Tracker.find(1)
711 source.core_fields = []
711 source.core_fields = []
712 source.save!
712 source.save!
713 target = Tracker.find(2)
713 target = Tracker.find(2)
714 target.core_fields = %w(assigned_to_id due_date)
714 target.core_fields = %w(assigned_to_id due_date)
715 target.save!
715 target.save!
716
716
717 issue = Issue.new(:tracker => source)
717 issue = Issue.new(:tracker => source)
718 issue.safe_attributes = {'tracker_id' => 2, 'due_date' => '2012-07-14'}
718 issue.safe_attributes = {'tracker_id' => 2, 'due_date' => '2012-07-14'}
719 assert_equal target, issue.tracker
719 assert_equal target, issue.tracker
720 assert_equal Date.parse('2012-07-14'), issue.due_date
720 assert_equal Date.parse('2012-07-14'), issue.due_date
721 end
721 end
722
722
723 def test_safe_attributes_should_not_include_readonly_fields
723 def test_safe_attributes_should_not_include_readonly_fields
724 WorkflowPermission.delete_all
724 WorkflowPermission.delete_all
725 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
725 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
726 :role_id => 1, :field_name => 'due_date',
726 :role_id => 1, :field_name => 'due_date',
727 :rule => 'readonly')
727 :rule => 'readonly')
728 user = User.find(2)
728 user = User.find(2)
729
729
730 issue = Issue.new(:project_id => 1, :tracker_id => 1)
730 issue = Issue.new(:project_id => 1, :tracker_id => 1)
731 assert_equal %w(due_date), issue.read_only_attribute_names(user)
731 assert_equal %w(due_date), issue.read_only_attribute_names(user)
732 assert_not_include 'due_date', issue.safe_attribute_names(user)
732 assert_not_include 'due_date', issue.safe_attribute_names(user)
733
733
734 issue.send :safe_attributes=, {'start_date' => '2012-07-14', 'due_date' => '2012-07-14'}, user
734 issue.send :safe_attributes=, {'start_date' => '2012-07-14', 'due_date' => '2012-07-14'}, user
735 assert_equal Date.parse('2012-07-14'), issue.start_date
735 assert_equal Date.parse('2012-07-14'), issue.start_date
736 assert_nil issue.due_date
736 assert_nil issue.due_date
737 end
737 end
738
738
739 def test_safe_attributes_should_not_include_readonly_custom_fields
739 def test_safe_attributes_should_not_include_readonly_custom_fields
740 cf1 = IssueCustomField.create!(:name => 'Writable field',
740 cf1 = IssueCustomField.create!(:name => 'Writable field',
741 :field_format => 'string',
741 :field_format => 'string',
742 :is_for_all => true, :tracker_ids => [1])
742 :is_for_all => true, :tracker_ids => [1])
743 cf2 = IssueCustomField.create!(:name => 'Readonly field',
743 cf2 = IssueCustomField.create!(:name => 'Readonly field',
744 :field_format => 'string',
744 :field_format => 'string',
745 :is_for_all => true, :tracker_ids => [1])
745 :is_for_all => true, :tracker_ids => [1])
746 WorkflowPermission.delete_all
746 WorkflowPermission.delete_all
747 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
747 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
748 :role_id => 1, :field_name => cf2.id.to_s,
748 :role_id => 1, :field_name => cf2.id.to_s,
749 :rule => 'readonly')
749 :rule => 'readonly')
750 user = User.find(2)
750 user = User.find(2)
751 issue = Issue.new(:project_id => 1, :tracker_id => 1)
751 issue = Issue.new(:project_id => 1, :tracker_id => 1)
752 assert_equal [cf2.id.to_s], issue.read_only_attribute_names(user)
752 assert_equal [cf2.id.to_s], issue.read_only_attribute_names(user)
753 assert_not_include cf2.id.to_s, issue.safe_attribute_names(user)
753 assert_not_include cf2.id.to_s, issue.safe_attribute_names(user)
754
754
755 issue.send :safe_attributes=, {'custom_field_values' => {
755 issue.send :safe_attributes=, {'custom_field_values' => {
756 cf1.id.to_s => 'value1', cf2.id.to_s => 'value2'
756 cf1.id.to_s => 'value1', cf2.id.to_s => 'value2'
757 }}, user
757 }}, user
758 assert_equal 'value1', issue.custom_field_value(cf1)
758 assert_equal 'value1', issue.custom_field_value(cf1)
759 assert_nil issue.custom_field_value(cf2)
759 assert_nil issue.custom_field_value(cf2)
760
760
761 issue.send :safe_attributes=, {'custom_fields' => [
761 issue.send :safe_attributes=, {'custom_fields' => [
762 {'id' => cf1.id.to_s, 'value' => 'valuea'},
762 {'id' => cf1.id.to_s, 'value' => 'valuea'},
763 {'id' => cf2.id.to_s, 'value' => 'valueb'}
763 {'id' => cf2.id.to_s, 'value' => 'valueb'}
764 ]}, user
764 ]}, user
765 assert_equal 'valuea', issue.custom_field_value(cf1)
765 assert_equal 'valuea', issue.custom_field_value(cf1)
766 assert_nil issue.custom_field_value(cf2)
766 assert_nil issue.custom_field_value(cf2)
767 end
767 end
768
768
769 def test_safe_attributes_should_ignore_unassignable_assignee
770 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
771 :status_id => 1, :priority => IssuePriority.all.first,
772 :subject => 'test_create')
773 assert issue.valid?
774
775 # locked user, not allowed
776 issue.safe_attributes=({'assigned_to_id' => '5'})
777 assert_nil issue.assigned_to_id
778 # no member
779 issue.safe_attributes=({'assigned_to_id' => '1'})
780 assert_nil issue.assigned_to_id
781 # user 2 is ok
782 issue.safe_attributes=({'assigned_to_id' => '2'})
783 assert_equal 2, issue.assigned_to_id
784 assert issue.save
785
786 issue.reload
787 assert_equal 2, issue.assigned_to_id
788 issue.safe_attributes=({'assigned_to_id' => '5'})
789 assert_equal 2, issue.assigned_to_id
790 issue.safe_attributes=({'assigned_to_id' => '1'})
791 assert_equal 2, issue.assigned_to_id
792 # user 3 is also ok
793 issue.safe_attributes=({'assigned_to_id' => '3'})
794 assert_equal 3, issue.assigned_to_id
795 assert issue.save
796
797 # removal of assignee
798 issue.safe_attributes=({'assigned_to_id' => ''})
799 assert_nil issue.assigned_to_id
800 assert issue.save
801 end
802
769 def test_editable_custom_field_values_should_return_non_readonly_custom_values
803 def test_editable_custom_field_values_should_return_non_readonly_custom_values
770 cf1 = IssueCustomField.create!(:name => 'Writable field', :field_format => 'string',
804 cf1 = IssueCustomField.create!(:name => 'Writable field', :field_format => 'string',
771 :is_for_all => true, :tracker_ids => [1, 2])
805 :is_for_all => true, :tracker_ids => [1, 2])
772 cf2 = IssueCustomField.create!(:name => 'Readonly field', :field_format => 'string',
806 cf2 = IssueCustomField.create!(:name => 'Readonly field', :field_format => 'string',
773 :is_for_all => true, :tracker_ids => [1, 2])
807 :is_for_all => true, :tracker_ids => [1, 2])
774 WorkflowPermission.delete_all
808 WorkflowPermission.delete_all
775 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1,
809 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1,
776 :field_name => cf2.id.to_s, :rule => 'readonly')
810 :field_name => cf2.id.to_s, :rule => 'readonly')
777 user = User.find(2)
811 user = User.find(2)
778
812
779 issue = Issue.new(:project_id => 1, :tracker_id => 1)
813 issue = Issue.new(:project_id => 1, :tracker_id => 1)
780 values = issue.editable_custom_field_values(user)
814 values = issue.editable_custom_field_values(user)
781 assert values.detect {|value| value.custom_field == cf1}
815 assert values.detect {|value| value.custom_field == cf1}
782 assert_nil values.detect {|value| value.custom_field == cf2}
816 assert_nil values.detect {|value| value.custom_field == cf2}
783
817
784 issue.tracker_id = 2
818 issue.tracker_id = 2
785 values = issue.editable_custom_field_values(user)
819 values = issue.editable_custom_field_values(user)
786 assert values.detect {|value| value.custom_field == cf1}
820 assert values.detect {|value| value.custom_field == cf1}
787 assert values.detect {|value| value.custom_field == cf2}
821 assert values.detect {|value| value.custom_field == cf2}
788 end
822 end
789
823
790 def test_editable_custom_fields_should_return_custom_field_that_is_enabled_for_the_role_only
824 def test_editable_custom_fields_should_return_custom_field_that_is_enabled_for_the_role_only
791 enabled_cf = IssueCustomField.generate!(:is_for_all => true, :tracker_ids => [1], :visible => false, :role_ids => [1,2])
825 enabled_cf = IssueCustomField.generate!(:is_for_all => true, :tracker_ids => [1], :visible => false, :role_ids => [1,2])
792 disabled_cf = IssueCustomField.generate!(:is_for_all => true, :tracker_ids => [1], :visible => false, :role_ids => [2])
826 disabled_cf = IssueCustomField.generate!(:is_for_all => true, :tracker_ids => [1], :visible => false, :role_ids => [2])
793 user = User.find(2)
827 user = User.find(2)
794 issue = Issue.new(:project_id => 1, :tracker_id => 1)
828 issue = Issue.new(:project_id => 1, :tracker_id => 1)
795
829
796 assert_include enabled_cf, issue.editable_custom_fields(user)
830 assert_include enabled_cf, issue.editable_custom_fields(user)
797 assert_not_include disabled_cf, issue.editable_custom_fields(user)
831 assert_not_include disabled_cf, issue.editable_custom_fields(user)
798 end
832 end
799
833
800 def test_safe_attributes_should_accept_target_tracker_writable_fields
834 def test_safe_attributes_should_accept_target_tracker_writable_fields
801 WorkflowPermission.delete_all
835 WorkflowPermission.delete_all
802 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
836 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
803 :role_id => 1, :field_name => 'due_date',
837 :role_id => 1, :field_name => 'due_date',
804 :rule => 'readonly')
838 :rule => 'readonly')
805 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2,
839 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2,
806 :role_id => 1, :field_name => 'start_date',
840 :role_id => 1, :field_name => 'start_date',
807 :rule => 'readonly')
841 :rule => 'readonly')
808 user = User.find(2)
842 user = User.find(2)
809
843
810 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
844 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
811
845
812 issue.send :safe_attributes=, {'start_date' => '2012-07-12',
846 issue.send :safe_attributes=, {'start_date' => '2012-07-12',
813 'due_date' => '2012-07-14'}, user
847 'due_date' => '2012-07-14'}, user
814 assert_equal Date.parse('2012-07-12'), issue.start_date
848 assert_equal Date.parse('2012-07-12'), issue.start_date
815 assert_nil issue.due_date
849 assert_nil issue.due_date
816
850
817 issue.send :safe_attributes=, {'start_date' => '2012-07-15',
851 issue.send :safe_attributes=, {'start_date' => '2012-07-15',
818 'due_date' => '2012-07-16',
852 'due_date' => '2012-07-16',
819 'tracker_id' => 2}, user
853 'tracker_id' => 2}, user
820 assert_equal Date.parse('2012-07-12'), issue.start_date
854 assert_equal Date.parse('2012-07-12'), issue.start_date
821 assert_equal Date.parse('2012-07-16'), issue.due_date
855 assert_equal Date.parse('2012-07-16'), issue.due_date
822 end
856 end
823
857
824 def test_safe_attributes_should_accept_target_status_writable_fields
858 def test_safe_attributes_should_accept_target_status_writable_fields
825 WorkflowPermission.delete_all
859 WorkflowPermission.delete_all
826 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
860 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
827 :role_id => 1, :field_name => 'due_date',
861 :role_id => 1, :field_name => 'due_date',
828 :rule => 'readonly')
862 :rule => 'readonly')
829 WorkflowPermission.create!(:old_status_id => 2, :tracker_id => 1,
863 WorkflowPermission.create!(:old_status_id => 2, :tracker_id => 1,
830 :role_id => 1, :field_name => 'start_date',
864 :role_id => 1, :field_name => 'start_date',
831 :rule => 'readonly')
865 :rule => 'readonly')
832 user = User.find(2)
866 user = User.find(2)
833
867
834 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
868 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
835
869
836 issue.send :safe_attributes=, {'start_date' => '2012-07-12',
870 issue.send :safe_attributes=, {'start_date' => '2012-07-12',
837 'due_date' => '2012-07-14'},
871 'due_date' => '2012-07-14'},
838 user
872 user
839 assert_equal Date.parse('2012-07-12'), issue.start_date
873 assert_equal Date.parse('2012-07-12'), issue.start_date
840 assert_nil issue.due_date
874 assert_nil issue.due_date
841
875
842 issue.send :safe_attributes=, {'start_date' => '2012-07-15',
876 issue.send :safe_attributes=, {'start_date' => '2012-07-15',
843 'due_date' => '2012-07-16',
877 'due_date' => '2012-07-16',
844 'status_id' => 2},
878 'status_id' => 2},
845 user
879 user
846 assert_equal Date.parse('2012-07-12'), issue.start_date
880 assert_equal Date.parse('2012-07-12'), issue.start_date
847 assert_equal Date.parse('2012-07-16'), issue.due_date
881 assert_equal Date.parse('2012-07-16'), issue.due_date
848 end
882 end
849
883
850 def test_required_attributes_should_be_validated
884 def test_required_attributes_should_be_validated
851 cf = IssueCustomField.create!(:name => 'Foo', :field_format => 'string',
885 cf = IssueCustomField.create!(:name => 'Foo', :field_format => 'string',
852 :is_for_all => true, :tracker_ids => [1, 2])
886 :is_for_all => true, :tracker_ids => [1, 2])
853
887
854 WorkflowPermission.delete_all
888 WorkflowPermission.delete_all
855 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
889 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
856 :role_id => 1, :field_name => 'due_date',
890 :role_id => 1, :field_name => 'due_date',
857 :rule => 'required')
891 :rule => 'required')
858 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
892 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
859 :role_id => 1, :field_name => 'category_id',
893 :role_id => 1, :field_name => 'category_id',
860 :rule => 'required')
894 :rule => 'required')
861 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
895 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
862 :role_id => 1, :field_name => cf.id.to_s,
896 :role_id => 1, :field_name => cf.id.to_s,
863 :rule => 'required')
897 :rule => 'required')
864
898
865 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2,
899 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2,
866 :role_id => 1, :field_name => 'start_date',
900 :role_id => 1, :field_name => 'start_date',
867 :rule => 'required')
901 :rule => 'required')
868 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2,
902 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2,
869 :role_id => 1, :field_name => cf.id.to_s,
903 :role_id => 1, :field_name => cf.id.to_s,
870 :rule => 'required')
904 :rule => 'required')
871 user = User.find(2)
905 user = User.find(2)
872
906
873 issue = Issue.new(:project_id => 1, :tracker_id => 1,
907 issue = Issue.new(:project_id => 1, :tracker_id => 1,
874 :status_id => 1, :subject => 'Required fields',
908 :status_id => 1, :subject => 'Required fields',
875 :author => user)
909 :author => user)
876 assert_equal [cf.id.to_s, "category_id", "due_date"],
910 assert_equal [cf.id.to_s, "category_id", "due_date"],
877 issue.required_attribute_names(user).sort
911 issue.required_attribute_names(user).sort
878 assert !issue.save, "Issue was saved"
912 assert !issue.save, "Issue was saved"
879 assert_equal ["Category cannot be blank", "Due date cannot be blank", "Foo cannot be blank"],
913 assert_equal ["Category cannot be blank", "Due date cannot be blank", "Foo cannot be blank"],
880 issue.errors.full_messages.sort
914 issue.errors.full_messages.sort
881
915
882 issue.tracker_id = 2
916 issue.tracker_id = 2
883 assert_equal [cf.id.to_s, "start_date"], issue.required_attribute_names(user).sort
917 assert_equal [cf.id.to_s, "start_date"], issue.required_attribute_names(user).sort
884 assert !issue.save, "Issue was saved"
918 assert !issue.save, "Issue was saved"
885 assert_equal ["Foo cannot be blank", "Start date cannot be blank"],
919 assert_equal ["Foo cannot be blank", "Start date cannot be blank"],
886 issue.errors.full_messages.sort
920 issue.errors.full_messages.sort
887
921
888 issue.start_date = Date.today
922 issue.start_date = Date.today
889 issue.custom_field_values = {cf.id.to_s => 'bar'}
923 issue.custom_field_values = {cf.id.to_s => 'bar'}
890 assert issue.save
924 assert issue.save
891 end
925 end
892
926
893 def test_required_attribute_that_is_disabled_for_the_tracker_should_not_be_required
927 def test_required_attribute_that_is_disabled_for_the_tracker_should_not_be_required
894 WorkflowPermission.delete_all
928 WorkflowPermission.delete_all
895 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
929 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
896 :role_id => 1, :field_name => 'start_date',
930 :role_id => 1, :field_name => 'start_date',
897 :rule => 'required')
931 :rule => 'required')
898 user = User.find(2)
932 user = User.find(2)
899
933
900 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
934 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
901 :subject => 'Required fields', :author => user)
935 :subject => 'Required fields', :author => user)
902 assert !issue.save
936 assert !issue.save
903 assert_include "Start date cannot be blank", issue.errors.full_messages
937 assert_include "Start date cannot be blank", issue.errors.full_messages
904
938
905 tracker = Tracker.find(1)
939 tracker = Tracker.find(1)
906 tracker.core_fields -= %w(start_date)
940 tracker.core_fields -= %w(start_date)
907 tracker.save!
941 tracker.save!
908 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
942 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
909 :subject => 'Required fields', :author => user)
943 :subject => 'Required fields', :author => user)
910 assert issue.save
944 assert issue.save
911 end
945 end
912
946
913 def test_required_custom_field_that_is_not_visible_for_the_user_should_not_be_required
947 def test_required_custom_field_that_is_not_visible_for_the_user_should_not_be_required
914 CustomField.delete_all
948 CustomField.delete_all
915 field = IssueCustomField.generate!(:is_required => true, :visible => false, :role_ids => [1], :trackers => Tracker.all, :is_for_all => true)
949 field = IssueCustomField.generate!(:is_required => true, :visible => false, :role_ids => [1], :trackers => Tracker.all, :is_for_all => true)
916 user = User.generate!
950 user = User.generate!
917 User.add_to_project(user, Project.find(1), Role.find(2))
951 User.add_to_project(user, Project.find(1), Role.find(2))
918
952
919 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
953 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
920 :subject => 'Required fields', :author => user)
954 :subject => 'Required fields', :author => user)
921 assert_save issue
955 assert_save issue
922 end
956 end
923
957
924 def test_required_custom_field_that_is_visible_for_the_user_should_be_required
958 def test_required_custom_field_that_is_visible_for_the_user_should_be_required
925 CustomField.delete_all
959 CustomField.delete_all
926 field = IssueCustomField.generate!(:is_required => true, :visible => false, :role_ids => [1], :trackers => Tracker.all, :is_for_all => true)
960 field = IssueCustomField.generate!(:is_required => true, :visible => false, :role_ids => [1], :trackers => Tracker.all, :is_for_all => true)
927 user = User.generate!
961 user = User.generate!
928 User.add_to_project(user, Project.find(1), Role.find(1))
962 User.add_to_project(user, Project.find(1), Role.find(1))
929
963
930 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
964 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
931 :subject => 'Required fields', :author => user)
965 :subject => 'Required fields', :author => user)
932 assert !issue.save
966 assert !issue.save
933 assert_include "#{field.name} cannot be blank", issue.errors.full_messages
967 assert_include "#{field.name} cannot be blank", issue.errors.full_messages
934 end
968 end
935
969
936 def test_required_attribute_names_for_multiple_roles_should_intersect_rules
970 def test_required_attribute_names_for_multiple_roles_should_intersect_rules
937 WorkflowPermission.delete_all
971 WorkflowPermission.delete_all
938 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
972 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
939 :role_id => 1, :field_name => 'due_date',
973 :role_id => 1, :field_name => 'due_date',
940 :rule => 'required')
974 :rule => 'required')
941 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
975 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
942 :role_id => 1, :field_name => 'start_date',
976 :role_id => 1, :field_name => 'start_date',
943 :rule => 'required')
977 :rule => 'required')
944 user = User.find(2)
978 user = User.find(2)
945 member = Member.find(1)
979 member = Member.find(1)
946 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
980 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
947
981
948 assert_equal %w(due_date start_date), issue.required_attribute_names(user).sort
982 assert_equal %w(due_date start_date), issue.required_attribute_names(user).sort
949
983
950 member.role_ids = [1, 2]
984 member.role_ids = [1, 2]
951 member.save!
985 member.save!
952 assert_equal [], issue.required_attribute_names(user.reload)
986 assert_equal [], issue.required_attribute_names(user.reload)
953
987
954 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
988 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
955 :role_id => 2, :field_name => 'due_date',
989 :role_id => 2, :field_name => 'due_date',
956 :rule => 'required')
990 :rule => 'required')
957 assert_equal %w(due_date), issue.required_attribute_names(user)
991 assert_equal %w(due_date), issue.required_attribute_names(user)
958
992
959 member.role_ids = [1, 2, 3]
993 member.role_ids = [1, 2, 3]
960 member.save!
994 member.save!
961 assert_equal [], issue.required_attribute_names(user.reload)
995 assert_equal [], issue.required_attribute_names(user.reload)
962
996
963 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
997 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
964 :role_id => 3, :field_name => 'due_date',
998 :role_id => 3, :field_name => 'due_date',
965 :rule => 'readonly')
999 :rule => 'readonly')
966 # required + readonly => required
1000 # required + readonly => required
967 assert_equal %w(due_date), issue.required_attribute_names(user)
1001 assert_equal %w(due_date), issue.required_attribute_names(user)
968 end
1002 end
969
1003
970 def test_read_only_attribute_names_for_multiple_roles_should_intersect_rules
1004 def test_read_only_attribute_names_for_multiple_roles_should_intersect_rules
971 WorkflowPermission.delete_all
1005 WorkflowPermission.delete_all
972 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1006 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
973 :role_id => 1, :field_name => 'due_date',
1007 :role_id => 1, :field_name => 'due_date',
974 :rule => 'readonly')
1008 :rule => 'readonly')
975 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1009 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
976 :role_id => 1, :field_name => 'start_date',
1010 :role_id => 1, :field_name => 'start_date',
977 :rule => 'readonly')
1011 :rule => 'readonly')
978 user = User.find(2)
1012 user = User.find(2)
979 member = Member.find(1)
1013 member = Member.find(1)
980 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
1014 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
981
1015
982 assert_equal %w(due_date start_date), issue.read_only_attribute_names(user).sort
1016 assert_equal %w(due_date start_date), issue.read_only_attribute_names(user).sort
983
1017
984 member.role_ids = [1, 2]
1018 member.role_ids = [1, 2]
985 member.save!
1019 member.save!
986 assert_equal [], issue.read_only_attribute_names(user.reload)
1020 assert_equal [], issue.read_only_attribute_names(user.reload)
987
1021
988 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1022 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
989 :role_id => 2, :field_name => 'due_date',
1023 :role_id => 2, :field_name => 'due_date',
990 :rule => 'readonly')
1024 :rule => 'readonly')
991 assert_equal %w(due_date), issue.read_only_attribute_names(user)
1025 assert_equal %w(due_date), issue.read_only_attribute_names(user)
992 end
1026 end
993
1027
994 # A field that is not visible by role 2 and readonly by role 1 should be readonly for user with role 1 and 2
1028 # A field that is not visible by role 2 and readonly by role 1 should be readonly for user with role 1 and 2
995 def test_read_only_attribute_names_should_include_custom_fields_that_combine_readonly_and_not_visible_for_roles
1029 def test_read_only_attribute_names_should_include_custom_fields_that_combine_readonly_and_not_visible_for_roles
996 field = IssueCustomField.generate!(
1030 field = IssueCustomField.generate!(
997 :is_for_all => true, :trackers => Tracker.all, :visible => false, :role_ids => [1]
1031 :is_for_all => true, :trackers => Tracker.all, :visible => false, :role_ids => [1]
998 )
1032 )
999 WorkflowPermission.delete_all
1033 WorkflowPermission.delete_all
1000 WorkflowPermission.create!(
1034 WorkflowPermission.create!(
1001 :old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => field.id, :rule => 'readonly'
1035 :old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => field.id, :rule => 'readonly'
1002 )
1036 )
1003 user = User.generate!
1037 user = User.generate!
1004 project = Project.find(1)
1038 project = Project.find(1)
1005 User.add_to_project(user, project, Role.where(:id => [1, 2]))
1039 User.add_to_project(user, project, Role.where(:id => [1, 2]))
1006
1040
1007 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
1041 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
1008 assert_equal [field.id.to_s], issue.read_only_attribute_names(user)
1042 assert_equal [field.id.to_s], issue.read_only_attribute_names(user)
1009 end
1043 end
1010
1044
1011 def test_workflow_rules_should_ignore_roles_without_issue_permissions
1045 def test_workflow_rules_should_ignore_roles_without_issue_permissions
1012 role = Role.generate! :permissions => [:view_issues, :edit_issues]
1046 role = Role.generate! :permissions => [:view_issues, :edit_issues]
1013 ignored_role = Role.generate! :permissions => [:view_issues]
1047 ignored_role = Role.generate! :permissions => [:view_issues]
1014
1048
1015 WorkflowPermission.delete_all
1049 WorkflowPermission.delete_all
1016 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1050 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1017 :role => role, :field_name => 'due_date',
1051 :role => role, :field_name => 'due_date',
1018 :rule => 'required')
1052 :rule => 'required')
1019 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1053 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1020 :role => role, :field_name => 'start_date',
1054 :role => role, :field_name => 'start_date',
1021 :rule => 'readonly')
1055 :rule => 'readonly')
1022 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1056 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1023 :role => role, :field_name => 'done_ratio',
1057 :role => role, :field_name => 'done_ratio',
1024 :rule => 'readonly')
1058 :rule => 'readonly')
1025 user = User.generate!
1059 user = User.generate!
1026 User.add_to_project user, Project.find(1), [role, ignored_role]
1060 User.add_to_project user, Project.find(1), [role, ignored_role]
1027
1061
1028 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
1062 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
1029
1063
1030 assert_equal %w(due_date), issue.required_attribute_names(user)
1064 assert_equal %w(due_date), issue.required_attribute_names(user)
1031 assert_equal %w(done_ratio start_date), issue.read_only_attribute_names(user).sort
1065 assert_equal %w(done_ratio start_date), issue.read_only_attribute_names(user).sort
1032 end
1066 end
1033
1067
1034 def test_workflow_rules_should_work_for_member_with_duplicate_role
1068 def test_workflow_rules_should_work_for_member_with_duplicate_role
1035 WorkflowPermission.delete_all
1069 WorkflowPermission.delete_all
1036 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1070 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1037 :role_id => 1, :field_name => 'due_date',
1071 :role_id => 1, :field_name => 'due_date',
1038 :rule => 'required')
1072 :rule => 'required')
1039 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1073 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1040 :role_id => 1, :field_name => 'start_date',
1074 :role_id => 1, :field_name => 'start_date',
1041 :rule => 'readonly')
1075 :rule => 'readonly')
1042
1076
1043 user = User.generate!
1077 user = User.generate!
1044 m = Member.new(:user_id => user.id, :project_id => 1)
1078 m = Member.new(:user_id => user.id, :project_id => 1)
1045 m.member_roles.build(:role_id => 1)
1079 m.member_roles.build(:role_id => 1)
1046 m.member_roles.build(:role_id => 1)
1080 m.member_roles.build(:role_id => 1)
1047 m.save!
1081 m.save!
1048
1082
1049 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
1083 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
1050
1084
1051 assert_equal %w(due_date), issue.required_attribute_names(user)
1085 assert_equal %w(due_date), issue.required_attribute_names(user)
1052 assert_equal %w(start_date), issue.read_only_attribute_names(user)
1086 assert_equal %w(start_date), issue.read_only_attribute_names(user)
1053 end
1087 end
1054
1088
1055 def test_copy
1089 def test_copy
1056 issue = Issue.new.copy_from(1)
1090 issue = Issue.new.copy_from(1)
1057 assert issue.copy?
1091 assert issue.copy?
1058 assert issue.save
1092 assert issue.save
1059 issue.reload
1093 issue.reload
1060 orig = Issue.find(1)
1094 orig = Issue.find(1)
1061 assert_equal orig.subject, issue.subject
1095 assert_equal orig.subject, issue.subject
1062 assert_equal orig.tracker, issue.tracker
1096 assert_equal orig.tracker, issue.tracker
1063 assert_equal "125", issue.custom_value_for(2).value
1097 assert_equal "125", issue.custom_value_for(2).value
1064 end
1098 end
1065
1099
1066 def test_copy_should_copy_status
1100 def test_copy_should_copy_status
1067 orig = Issue.find(8)
1101 orig = Issue.find(8)
1068 assert orig.status != orig.default_status
1102 assert orig.status != orig.default_status
1069
1103
1070 issue = Issue.new.copy_from(orig)
1104 issue = Issue.new.copy_from(orig)
1071 assert issue.save
1105 assert issue.save
1072 issue.reload
1106 issue.reload
1073 assert_equal orig.status, issue.status
1107 assert_equal orig.status, issue.status
1074 end
1108 end
1075
1109
1076 def test_copy_should_add_relation_with_copied_issue
1110 def test_copy_should_add_relation_with_copied_issue
1077 copied = Issue.find(1)
1111 copied = Issue.find(1)
1078 issue = Issue.new.copy_from(copied)
1112 issue = Issue.new.copy_from(copied)
1079 assert issue.save
1113 assert issue.save
1080 issue.reload
1114 issue.reload
1081
1115
1082 assert_equal 1, issue.relations.size
1116 assert_equal 1, issue.relations.size
1083 relation = issue.relations.first
1117 relation = issue.relations.first
1084 assert_equal 'copied_to', relation.relation_type
1118 assert_equal 'copied_to', relation.relation_type
1085 assert_equal copied, relation.issue_from
1119 assert_equal copied, relation.issue_from
1086 assert_equal issue, relation.issue_to
1120 assert_equal issue, relation.issue_to
1087 end
1121 end
1088
1122
1089 def test_copy_should_copy_subtasks
1123 def test_copy_should_copy_subtasks
1090 issue = Issue.generate_with_descendants!
1124 issue = Issue.generate_with_descendants!
1091
1125
1092 copy = issue.reload.copy
1126 copy = issue.reload.copy
1093 copy.author = User.find(7)
1127 copy.author = User.find(7)
1094 assert_difference 'Issue.count', 1+issue.descendants.count do
1128 assert_difference 'Issue.count', 1+issue.descendants.count do
1095 assert copy.save
1129 assert copy.save
1096 end
1130 end
1097 copy.reload
1131 copy.reload
1098 assert_equal %w(Child1 Child2), copy.children.map(&:subject).sort
1132 assert_equal %w(Child1 Child2), copy.children.map(&:subject).sort
1099 child_copy = copy.children.detect {|c| c.subject == 'Child1'}
1133 child_copy = copy.children.detect {|c| c.subject == 'Child1'}
1100 assert_equal %w(Child11), child_copy.children.map(&:subject).sort
1134 assert_equal %w(Child11), child_copy.children.map(&:subject).sort
1101 assert_equal copy.author, child_copy.author
1135 assert_equal copy.author, child_copy.author
1102 end
1136 end
1103
1137
1104 def test_copy_as_a_child_of_copied_issue_should_not_copy_itself
1138 def test_copy_as_a_child_of_copied_issue_should_not_copy_itself
1105 parent = Issue.generate!
1139 parent = Issue.generate!
1106 child1 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 1')
1140 child1 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 1')
1107 child2 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 2')
1141 child2 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 2')
1108
1142
1109 copy = parent.reload.copy
1143 copy = parent.reload.copy
1110 copy.parent_issue_id = parent.id
1144 copy.parent_issue_id = parent.id
1111 copy.author = User.find(7)
1145 copy.author = User.find(7)
1112 assert_difference 'Issue.count', 3 do
1146 assert_difference 'Issue.count', 3 do
1113 assert copy.save
1147 assert copy.save
1114 end
1148 end
1115 parent.reload
1149 parent.reload
1116 copy.reload
1150 copy.reload
1117 assert_equal parent, copy.parent
1151 assert_equal parent, copy.parent
1118 assert_equal 3, parent.children.count
1152 assert_equal 3, parent.children.count
1119 assert_equal 5, parent.descendants.count
1153 assert_equal 5, parent.descendants.count
1120 assert_equal 2, copy.children.count
1154 assert_equal 2, copy.children.count
1121 assert_equal 2, copy.descendants.count
1155 assert_equal 2, copy.descendants.count
1122 end
1156 end
1123
1157
1124 def test_copy_as_a_descendant_of_copied_issue_should_not_copy_itself
1158 def test_copy_as_a_descendant_of_copied_issue_should_not_copy_itself
1125 parent = Issue.generate!
1159 parent = Issue.generate!
1126 child1 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 1')
1160 child1 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 1')
1127 child2 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 2')
1161 child2 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 2')
1128
1162
1129 copy = parent.reload.copy
1163 copy = parent.reload.copy
1130 copy.parent_issue_id = child1.id
1164 copy.parent_issue_id = child1.id
1131 copy.author = User.find(7)
1165 copy.author = User.find(7)
1132 assert_difference 'Issue.count', 3 do
1166 assert_difference 'Issue.count', 3 do
1133 assert copy.save
1167 assert copy.save
1134 end
1168 end
1135 parent.reload
1169 parent.reload
1136 child1.reload
1170 child1.reload
1137 copy.reload
1171 copy.reload
1138 assert_equal child1, copy.parent
1172 assert_equal child1, copy.parent
1139 assert_equal 2, parent.children.count
1173 assert_equal 2, parent.children.count
1140 assert_equal 5, parent.descendants.count
1174 assert_equal 5, parent.descendants.count
1141 assert_equal 1, child1.children.count
1175 assert_equal 1, child1.children.count
1142 assert_equal 3, child1.descendants.count
1176 assert_equal 3, child1.descendants.count
1143 assert_equal 2, copy.children.count
1177 assert_equal 2, copy.children.count
1144 assert_equal 2, copy.descendants.count
1178 assert_equal 2, copy.descendants.count
1145 end
1179 end
1146
1180
1147 def test_copy_should_copy_subtasks_to_target_project
1181 def test_copy_should_copy_subtasks_to_target_project
1148 issue = Issue.generate_with_descendants!
1182 issue = Issue.generate_with_descendants!
1149
1183
1150 copy = issue.copy(:project_id => 3)
1184 copy = issue.copy(:project_id => 3)
1151 assert_difference 'Issue.count', 1+issue.descendants.count do
1185 assert_difference 'Issue.count', 1+issue.descendants.count do
1152 assert copy.save
1186 assert copy.save
1153 end
1187 end
1154 assert_equal [3], copy.reload.descendants.map(&:project_id).uniq
1188 assert_equal [3], copy.reload.descendants.map(&:project_id).uniq
1155 end
1189 end
1156
1190
1157 def test_copy_should_not_copy_subtasks_twice_when_saving_twice
1191 def test_copy_should_not_copy_subtasks_twice_when_saving_twice
1158 issue = Issue.generate_with_descendants!
1192 issue = Issue.generate_with_descendants!
1159
1193
1160 copy = issue.reload.copy
1194 copy = issue.reload.copy
1161 assert_difference 'Issue.count', 1+issue.descendants.count do
1195 assert_difference 'Issue.count', 1+issue.descendants.count do
1162 assert copy.save
1196 assert copy.save
1163 assert copy.save
1197 assert copy.save
1164 end
1198 end
1165 end
1199 end
1166
1200
1167 def test_should_not_call_after_project_change_on_creation
1201 def test_should_not_call_after_project_change_on_creation
1168 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
1202 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
1169 :subject => 'Test', :author_id => 1)
1203 :subject => 'Test', :author_id => 1)
1170 issue.expects(:after_project_change).never
1204 issue.expects(:after_project_change).never
1171 issue.save!
1205 issue.save!
1172 end
1206 end
1173
1207
1174 def test_should_not_call_after_project_change_on_update
1208 def test_should_not_call_after_project_change_on_update
1175 issue = Issue.find(1)
1209 issue = Issue.find(1)
1176 issue.project = Project.find(1)
1210 issue.project = Project.find(1)
1177 issue.subject = 'No project change'
1211 issue.subject = 'No project change'
1178 issue.expects(:after_project_change).never
1212 issue.expects(:after_project_change).never
1179 issue.save!
1213 issue.save!
1180 end
1214 end
1181
1215
1182 def test_should_call_after_project_change_on_project_change
1216 def test_should_call_after_project_change_on_project_change
1183 issue = Issue.find(1)
1217 issue = Issue.find(1)
1184 issue.project = Project.find(2)
1218 issue.project = Project.find(2)
1185 issue.expects(:after_project_change).once
1219 issue.expects(:after_project_change).once
1186 issue.save!
1220 issue.save!
1187 end
1221 end
1188
1222
1189 def test_adding_journal_should_update_timestamp
1223 def test_adding_journal_should_update_timestamp
1190 issue = Issue.find(1)
1224 issue = Issue.find(1)
1191 updated_on_was = issue.updated_on
1225 updated_on_was = issue.updated_on
1192
1226
1193 issue.init_journal(User.first, "Adding notes")
1227 issue.init_journal(User.first, "Adding notes")
1194 assert_difference 'Journal.count' do
1228 assert_difference 'Journal.count' do
1195 assert issue.save
1229 assert issue.save
1196 end
1230 end
1197 issue.reload
1231 issue.reload
1198
1232
1199 assert_not_equal updated_on_was, issue.updated_on
1233 assert_not_equal updated_on_was, issue.updated_on
1200 end
1234 end
1201
1235
1202 def test_should_close_duplicates
1236 def test_should_close_duplicates
1203 # Create 3 issues
1237 # Create 3 issues
1204 issue1 = Issue.generate!
1238 issue1 = Issue.generate!
1205 issue2 = Issue.generate!
1239 issue2 = Issue.generate!
1206 issue3 = Issue.generate!
1240 issue3 = Issue.generate!
1207
1241
1208 # 2 is a dupe of 1
1242 # 2 is a dupe of 1
1209 IssueRelation.create!(:issue_from => issue2, :issue_to => issue1,
1243 IssueRelation.create!(:issue_from => issue2, :issue_to => issue1,
1210 :relation_type => IssueRelation::TYPE_DUPLICATES)
1244 :relation_type => IssueRelation::TYPE_DUPLICATES)
1211 # And 3 is a dupe of 2
1245 # And 3 is a dupe of 2
1212 # IssueRelation.create!(:issue_from => issue3, :issue_to => issue2,
1246 # IssueRelation.create!(:issue_from => issue3, :issue_to => issue2,
1213 # :relation_type => IssueRelation::TYPE_DUPLICATES)
1247 # :relation_type => IssueRelation::TYPE_DUPLICATES)
1214 # And 3 is a dupe of 1 (circular duplicates)
1248 # And 3 is a dupe of 1 (circular duplicates)
1215 IssueRelation.create!(:issue_from => issue3, :issue_to => issue1,
1249 IssueRelation.create!(:issue_from => issue3, :issue_to => issue1,
1216 :relation_type => IssueRelation::TYPE_DUPLICATES)
1250 :relation_type => IssueRelation::TYPE_DUPLICATES)
1217
1251
1218 assert issue1.reload.duplicates.include?(issue2)
1252 assert issue1.reload.duplicates.include?(issue2)
1219
1253
1220 # Closing issue 1
1254 # Closing issue 1
1221 issue1.init_journal(User.first, "Closing issue1")
1255 issue1.init_journal(User.first, "Closing issue1")
1222 issue1.status = IssueStatus.where(:is_closed => true).first
1256 issue1.status = IssueStatus.where(:is_closed => true).first
1223 assert issue1.save
1257 assert issue1.save
1224 # 2 and 3 should be also closed
1258 # 2 and 3 should be also closed
1225 assert issue2.reload.closed?
1259 assert issue2.reload.closed?
1226 assert issue3.reload.closed?
1260 assert issue3.reload.closed?
1227 end
1261 end
1228
1262
1229 def test_should_close_duplicates_with_private_notes
1263 def test_should_close_duplicates_with_private_notes
1230 issue = Issue.generate!
1264 issue = Issue.generate!
1231 duplicate = Issue.generate!
1265 duplicate = Issue.generate!
1232 IssueRelation.create!(:issue_from => duplicate, :issue_to => issue,
1266 IssueRelation.create!(:issue_from => duplicate, :issue_to => issue,
1233 :relation_type => IssueRelation::TYPE_DUPLICATES)
1267 :relation_type => IssueRelation::TYPE_DUPLICATES)
1234 assert issue.reload.duplicates.include?(duplicate)
1268 assert issue.reload.duplicates.include?(duplicate)
1235
1269
1236 # Closing issue with private notes
1270 # Closing issue with private notes
1237 issue.init_journal(User.first, "Private notes")
1271 issue.init_journal(User.first, "Private notes")
1238 issue.private_notes = true
1272 issue.private_notes = true
1239 issue.status = IssueStatus.where(:is_closed => true).first
1273 issue.status = IssueStatus.where(:is_closed => true).first
1240 assert_save issue
1274 assert_save issue
1241
1275
1242 duplicate.reload
1276 duplicate.reload
1243 assert journal = duplicate.journals.detect {|journal| journal.notes == "Private notes"}
1277 assert journal = duplicate.journals.detect {|journal| journal.notes == "Private notes"}
1244 assert_equal true, journal.private_notes
1278 assert_equal true, journal.private_notes
1245 end
1279 end
1246
1280
1247 def test_should_not_close_duplicated_issue
1281 def test_should_not_close_duplicated_issue
1248 issue1 = Issue.generate!
1282 issue1 = Issue.generate!
1249 issue2 = Issue.generate!
1283 issue2 = Issue.generate!
1250
1284
1251 # 2 is a dupe of 1
1285 # 2 is a dupe of 1
1252 IssueRelation.create(:issue_from => issue2, :issue_to => issue1,
1286 IssueRelation.create(:issue_from => issue2, :issue_to => issue1,
1253 :relation_type => IssueRelation::TYPE_DUPLICATES)
1287 :relation_type => IssueRelation::TYPE_DUPLICATES)
1254 # 2 is a dup of 1 but 1 is not a duplicate of 2
1288 # 2 is a dup of 1 but 1 is not a duplicate of 2
1255 assert !issue2.reload.duplicates.include?(issue1)
1289 assert !issue2.reload.duplicates.include?(issue1)
1256
1290
1257 # Closing issue 2
1291 # Closing issue 2
1258 issue2.init_journal(User.first, "Closing issue2")
1292 issue2.init_journal(User.first, "Closing issue2")
1259 issue2.status = IssueStatus.where(:is_closed => true).first
1293 issue2.status = IssueStatus.where(:is_closed => true).first
1260 assert issue2.save
1294 assert issue2.save
1261 # 1 should not be also closed
1295 # 1 should not be also closed
1262 assert !issue1.reload.closed?
1296 assert !issue1.reload.closed?
1263 end
1297 end
1264
1298
1265 def test_assignable_versions
1299 def test_assignable_versions
1266 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1300 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1267 :status_id => 1, :fixed_version_id => 1,
1301 :status_id => 1, :fixed_version_id => 1,
1268 :subject => 'New issue')
1302 :subject => 'New issue')
1269 assert_equal ['open'], issue.assignable_versions.collect(&:status).uniq
1303 assert_equal ['open'], issue.assignable_versions.collect(&:status).uniq
1270 end
1304 end
1271
1305
1272 def test_should_not_be_able_to_assign_a_new_issue_to_a_closed_version
1306 def test_should_not_be_able_to_assign_a_new_issue_to_a_closed_version
1273 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1307 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1274 :status_id => 1, :fixed_version_id => 1,
1308 :status_id => 1, :fixed_version_id => 1,
1275 :subject => 'New issue')
1309 :subject => 'New issue')
1276 assert !issue.save
1310 assert !issue.save
1277 assert_not_equal [], issue.errors[:fixed_version_id]
1311 assert_not_equal [], issue.errors[:fixed_version_id]
1278 end
1312 end
1279
1313
1280 def test_should_not_be_able_to_assign_a_new_issue_to_a_locked_version
1314 def test_should_not_be_able_to_assign_a_new_issue_to_a_locked_version
1281 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1315 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1282 :status_id => 1, :fixed_version_id => 2,
1316 :status_id => 1, :fixed_version_id => 2,
1283 :subject => 'New issue')
1317 :subject => 'New issue')
1284 assert !issue.save
1318 assert !issue.save
1285 assert_not_equal [], issue.errors[:fixed_version_id]
1319 assert_not_equal [], issue.errors[:fixed_version_id]
1286 end
1320 end
1287
1321
1288 def test_should_be_able_to_assign_a_new_issue_to_an_open_version
1322 def test_should_be_able_to_assign_a_new_issue_to_an_open_version
1289 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1323 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1290 :status_id => 1, :fixed_version_id => 3,
1324 :status_id => 1, :fixed_version_id => 3,
1291 :subject => 'New issue')
1325 :subject => 'New issue')
1292 assert issue.save
1326 assert issue.save
1293 end
1327 end
1294
1328
1295 def test_should_be_able_to_update_an_issue_assigned_to_a_closed_version
1329 def test_should_be_able_to_update_an_issue_assigned_to_a_closed_version
1296 issue = Issue.find(11)
1330 issue = Issue.find(11)
1297 assert_equal 'closed', issue.fixed_version.status
1331 assert_equal 'closed', issue.fixed_version.status
1298 issue.subject = 'Subject changed'
1332 issue.subject = 'Subject changed'
1299 assert issue.save
1333 assert issue.save
1300 end
1334 end
1301
1335
1302 def test_should_not_be_able_to_reopen_an_issue_assigned_to_a_closed_version
1336 def test_should_not_be_able_to_reopen_an_issue_assigned_to_a_closed_version
1303 issue = Issue.find(11)
1337 issue = Issue.find(11)
1304 issue.status_id = 1
1338 issue.status_id = 1
1305 assert !issue.save
1339 assert !issue.save
1306 assert_not_equal [], issue.errors[:base]
1340 assert_not_equal [], issue.errors[:base]
1307 end
1341 end
1308
1342
1309 def test_should_be_able_to_reopen_and_reassign_an_issue_assigned_to_a_closed_version
1343 def test_should_be_able_to_reopen_and_reassign_an_issue_assigned_to_a_closed_version
1310 issue = Issue.find(11)
1344 issue = Issue.find(11)
1311 issue.status_id = 1
1345 issue.status_id = 1
1312 issue.fixed_version_id = 3
1346 issue.fixed_version_id = 3
1313 assert issue.save
1347 assert issue.save
1314 end
1348 end
1315
1349
1316 def test_should_be_able_to_reopen_an_issue_assigned_to_a_locked_version
1350 def test_should_be_able_to_reopen_an_issue_assigned_to_a_locked_version
1317 issue = Issue.find(12)
1351 issue = Issue.find(12)
1318 assert_equal 'locked', issue.fixed_version.status
1352 assert_equal 'locked', issue.fixed_version.status
1319 issue.status_id = 1
1353 issue.status_id = 1
1320 assert issue.save
1354 assert issue.save
1321 end
1355 end
1322
1356
1323 def test_should_not_be_able_to_keep_unshared_version_when_changing_project
1357 def test_should_not_be_able_to_keep_unshared_version_when_changing_project
1324 issue = Issue.find(2)
1358 issue = Issue.find(2)
1325 assert_equal 2, issue.fixed_version_id
1359 assert_equal 2, issue.fixed_version_id
1326 issue.project_id = 3
1360 issue.project_id = 3
1327 assert_nil issue.fixed_version_id
1361 assert_nil issue.fixed_version_id
1328 issue.fixed_version_id = 2
1362 issue.fixed_version_id = 2
1329 assert !issue.save
1363 assert !issue.save
1330 assert_include 'Target version is not included in the list', issue.errors.full_messages
1364 assert_include 'Target version is not included in the list', issue.errors.full_messages
1331 end
1365 end
1332
1366
1333 def test_should_keep_shared_version_when_changing_project
1367 def test_should_keep_shared_version_when_changing_project
1334 Version.find(2).update_attribute :sharing, 'tree'
1368 Version.find(2).update_attribute :sharing, 'tree'
1335
1369
1336 issue = Issue.find(2)
1370 issue = Issue.find(2)
1337 assert_equal 2, issue.fixed_version_id
1371 assert_equal 2, issue.fixed_version_id
1338 issue.project_id = 3
1372 issue.project_id = 3
1339 assert_equal 2, issue.fixed_version_id
1373 assert_equal 2, issue.fixed_version_id
1340 assert issue.save
1374 assert issue.save
1341 end
1375 end
1342
1376
1343 def test_allowed_target_projects_should_include_projects_with_issue_tracking_enabled
1377 def test_allowed_target_projects_should_include_projects_with_issue_tracking_enabled
1344 assert_include Project.find(2), Issue.allowed_target_projects(User.find(2))
1378 assert_include Project.find(2), Issue.allowed_target_projects(User.find(2))
1345 end
1379 end
1346
1380
1347 def test_allowed_target_projects_should_not_include_projects_with_issue_tracking_disabled
1381 def test_allowed_target_projects_should_not_include_projects_with_issue_tracking_disabled
1348 Project.find(2).disable_module! :issue_tracking
1382 Project.find(2).disable_module! :issue_tracking
1349 assert_not_include Project.find(2), Issue.allowed_target_projects(User.find(2))
1383 assert_not_include Project.find(2), Issue.allowed_target_projects(User.find(2))
1350 end
1384 end
1351
1385
1352 def test_move_to_another_project_with_same_category
1386 def test_move_to_another_project_with_same_category
1353 issue = Issue.find(1)
1387 issue = Issue.find(1)
1354 issue.project = Project.find(2)
1388 issue.project = Project.find(2)
1355 assert issue.save
1389 assert issue.save
1356 issue.reload
1390 issue.reload
1357 assert_equal 2, issue.project_id
1391 assert_equal 2, issue.project_id
1358 # Category changes
1392 # Category changes
1359 assert_equal 4, issue.category_id
1393 assert_equal 4, issue.category_id
1360 # Make sure time entries were move to the target project
1394 # Make sure time entries were move to the target project
1361 assert_equal 2, issue.time_entries.first.project_id
1395 assert_equal 2, issue.time_entries.first.project_id
1362 end
1396 end
1363
1397
1364 def test_move_to_another_project_without_same_category
1398 def test_move_to_another_project_without_same_category
1365 issue = Issue.find(2)
1399 issue = Issue.find(2)
1366 issue.project = Project.find(2)
1400 issue.project = Project.find(2)
1367 assert issue.save
1401 assert issue.save
1368 issue.reload
1402 issue.reload
1369 assert_equal 2, issue.project_id
1403 assert_equal 2, issue.project_id
1370 # Category cleared
1404 # Category cleared
1371 assert_nil issue.category_id
1405 assert_nil issue.category_id
1372 end
1406 end
1373
1407
1374 def test_move_to_another_project_should_clear_fixed_version_when_not_shared
1408 def test_move_to_another_project_should_clear_fixed_version_when_not_shared
1375 issue = Issue.find(1)
1409 issue = Issue.find(1)
1376 issue.update_attribute(:fixed_version_id, 1)
1410 issue.update_attribute(:fixed_version_id, 1)
1377 issue.project = Project.find(2)
1411 issue.project = Project.find(2)
1378 assert issue.save
1412 assert issue.save
1379 issue.reload
1413 issue.reload
1380 assert_equal 2, issue.project_id
1414 assert_equal 2, issue.project_id
1381 # Cleared fixed_version
1415 # Cleared fixed_version
1382 assert_equal nil, issue.fixed_version
1416 assert_equal nil, issue.fixed_version
1383 end
1417 end
1384
1418
1385 def test_move_to_another_project_should_keep_fixed_version_when_shared_with_the_target_project
1419 def test_move_to_another_project_should_keep_fixed_version_when_shared_with_the_target_project
1386 issue = Issue.find(1)
1420 issue = Issue.find(1)
1387 issue.update_attribute(:fixed_version_id, 4)
1421 issue.update_attribute(:fixed_version_id, 4)
1388 issue.project = Project.find(5)
1422 issue.project = Project.find(5)
1389 assert issue.save
1423 assert issue.save
1390 issue.reload
1424 issue.reload
1391 assert_equal 5, issue.project_id
1425 assert_equal 5, issue.project_id
1392 # Keep fixed_version
1426 # Keep fixed_version
1393 assert_equal 4, issue.fixed_version_id
1427 assert_equal 4, issue.fixed_version_id
1394 end
1428 end
1395
1429
1396 def test_move_to_another_project_should_clear_fixed_version_when_not_shared_with_the_target_project
1430 def test_move_to_another_project_should_clear_fixed_version_when_not_shared_with_the_target_project
1397 issue = Issue.find(1)
1431 issue = Issue.find(1)
1398 issue.update_attribute(:fixed_version_id, 1)
1432 issue.update_attribute(:fixed_version_id, 1)
1399 issue.project = Project.find(5)
1433 issue.project = Project.find(5)
1400 assert issue.save
1434 assert issue.save
1401 issue.reload
1435 issue.reload
1402 assert_equal 5, issue.project_id
1436 assert_equal 5, issue.project_id
1403 # Cleared fixed_version
1437 # Cleared fixed_version
1404 assert_equal nil, issue.fixed_version
1438 assert_equal nil, issue.fixed_version
1405 end
1439 end
1406
1440
1407 def test_move_to_another_project_should_keep_fixed_version_when_shared_systemwide
1441 def test_move_to_another_project_should_keep_fixed_version_when_shared_systemwide
1408 issue = Issue.find(1)
1442 issue = Issue.find(1)
1409 issue.update_attribute(:fixed_version_id, 7)
1443 issue.update_attribute(:fixed_version_id, 7)
1410 issue.project = Project.find(2)
1444 issue.project = Project.find(2)
1411 assert issue.save
1445 assert issue.save
1412 issue.reload
1446 issue.reload
1413 assert_equal 2, issue.project_id
1447 assert_equal 2, issue.project_id
1414 # Keep fixed_version
1448 # Keep fixed_version
1415 assert_equal 7, issue.fixed_version_id
1449 assert_equal 7, issue.fixed_version_id
1416 end
1450 end
1417
1451
1418 def test_move_to_another_project_should_keep_parent_if_valid
1452 def test_move_to_another_project_should_keep_parent_if_valid
1419 issue = Issue.find(1)
1453 issue = Issue.find(1)
1420 issue.update_attribute(:parent_issue_id, 2)
1454 issue.update_attribute(:parent_issue_id, 2)
1421 issue.project = Project.find(3)
1455 issue.project = Project.find(3)
1422 assert issue.save
1456 assert issue.save
1423 issue.reload
1457 issue.reload
1424 assert_equal 2, issue.parent_id
1458 assert_equal 2, issue.parent_id
1425 end
1459 end
1426
1460
1427 def test_move_to_another_project_should_clear_parent_if_not_valid
1461 def test_move_to_another_project_should_clear_parent_if_not_valid
1428 issue = Issue.find(1)
1462 issue = Issue.find(1)
1429 issue.update_attribute(:parent_issue_id, 2)
1463 issue.update_attribute(:parent_issue_id, 2)
1430 issue.project = Project.find(2)
1464 issue.project = Project.find(2)
1431 assert issue.save
1465 assert issue.save
1432 issue.reload
1466 issue.reload
1433 assert_nil issue.parent_id
1467 assert_nil issue.parent_id
1434 end
1468 end
1435
1469
1436 def test_move_to_another_project_with_disabled_tracker
1470 def test_move_to_another_project_with_disabled_tracker
1437 issue = Issue.find(1)
1471 issue = Issue.find(1)
1438 target = Project.find(2)
1472 target = Project.find(2)
1439 target.tracker_ids = [3]
1473 target.tracker_ids = [3]
1440 target.save
1474 target.save
1441 issue.project = target
1475 issue.project = target
1442 assert issue.save
1476 assert issue.save
1443 issue.reload
1477 issue.reload
1444 assert_equal 2, issue.project_id
1478 assert_equal 2, issue.project_id
1445 assert_equal 3, issue.tracker_id
1479 assert_equal 3, issue.tracker_id
1446 end
1480 end
1447
1481
1448 def test_copy_to_the_same_project
1482 def test_copy_to_the_same_project
1449 issue = Issue.find(1)
1483 issue = Issue.find(1)
1450 copy = issue.copy
1484 copy = issue.copy
1451 assert_difference 'Issue.count' do
1485 assert_difference 'Issue.count' do
1452 copy.save!
1486 copy.save!
1453 end
1487 end
1454 assert_kind_of Issue, copy
1488 assert_kind_of Issue, copy
1455 assert_equal issue.project, copy.project
1489 assert_equal issue.project, copy.project
1456 assert_equal "125", copy.custom_value_for(2).value
1490 assert_equal "125", copy.custom_value_for(2).value
1457 end
1491 end
1458
1492
1459 def test_copy_to_another_project_and_tracker
1493 def test_copy_to_another_project_and_tracker
1460 issue = Issue.find(1)
1494 issue = Issue.find(1)
1461 copy = issue.copy(:project_id => 3, :tracker_id => 2)
1495 copy = issue.copy(:project_id => 3, :tracker_id => 2)
1462 assert_difference 'Issue.count' do
1496 assert_difference 'Issue.count' do
1463 copy.save!
1497 copy.save!
1464 end
1498 end
1465 copy.reload
1499 copy.reload
1466 assert_kind_of Issue, copy
1500 assert_kind_of Issue, copy
1467 assert_equal Project.find(3), copy.project
1501 assert_equal Project.find(3), copy.project
1468 assert_equal Tracker.find(2), copy.tracker
1502 assert_equal Tracker.find(2), copy.tracker
1469 # Custom field #2 is not associated with target tracker
1503 # Custom field #2 is not associated with target tracker
1470 assert_nil copy.custom_value_for(2)
1504 assert_nil copy.custom_value_for(2)
1471 end
1505 end
1472
1506
1473 test "#copy should not create a journal" do
1507 test "#copy should not create a journal" do
1474 copy = Issue.find(1).copy({:project_id => 3, :tracker_id => 2, :assigned_to_id => 3}, :link => false)
1508 copy = Issue.find(1).copy({:project_id => 3, :tracker_id => 2, :assigned_to_id => 3}, :link => false)
1475 copy.save!
1509 copy.save!
1476 assert_equal 0, copy.reload.journals.size
1510 assert_equal 0, copy.reload.journals.size
1477 end
1511 end
1478
1512
1479 test "#copy should allow assigned_to changes" do
1513 test "#copy should allow assigned_to changes" do
1480 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3)
1514 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3)
1481 assert_equal 3, copy.assigned_to_id
1515 assert_equal 3, copy.assigned_to_id
1482 end
1516 end
1483
1517
1484 test "#copy should allow status changes" do
1518 test "#copy should allow status changes" do
1485 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :status_id => 2)
1519 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :status_id => 2)
1486 assert_equal 2, copy.status_id
1520 assert_equal 2, copy.status_id
1487 end
1521 end
1488
1522
1489 test "#copy should allow start date changes" do
1523 test "#copy should allow start date changes" do
1490 date = Date.today
1524 date = Date.today
1491 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :start_date => date)
1525 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :start_date => date)
1492 assert_equal date, copy.start_date
1526 assert_equal date, copy.start_date
1493 end
1527 end
1494
1528
1495 test "#copy should allow due date changes" do
1529 test "#copy should allow due date changes" do
1496 date = Date.today
1530 date = Date.today
1497 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :due_date => date)
1531 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :due_date => date)
1498 assert_equal date, copy.due_date
1532 assert_equal date, copy.due_date
1499 end
1533 end
1500
1534
1501 test "#copy should set current user as author" do
1535 test "#copy should set current user as author" do
1502 User.current = User.find(9)
1536 User.current = User.find(9)
1503 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2)
1537 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2)
1504 assert_equal User.current, copy.author
1538 assert_equal User.current, copy.author
1505 end
1539 end
1506
1540
1507 test "#copy should create a journal with notes" do
1541 test "#copy should create a journal with notes" do
1508 date = Date.today
1542 date = Date.today
1509 notes = "Notes added when copying"
1543 notes = "Notes added when copying"
1510 copy = Issue.find(1).copy({:project_id => 3, :tracker_id => 2, :start_date => date}, :link => false)
1544 copy = Issue.find(1).copy({:project_id => 3, :tracker_id => 2, :start_date => date}, :link => false)
1511 copy.init_journal(User.current, notes)
1545 copy.init_journal(User.current, notes)
1512 copy.save!
1546 copy.save!
1513
1547
1514 assert_equal 1, copy.journals.size
1548 assert_equal 1, copy.journals.size
1515 journal = copy.journals.first
1549 journal = copy.journals.first
1516 assert_equal 0, journal.details.size
1550 assert_equal 0, journal.details.size
1517 assert_equal notes, journal.notes
1551 assert_equal notes, journal.notes
1518 end
1552 end
1519
1553
1520 def test_valid_parent_project
1554 def test_valid_parent_project
1521 issue = Issue.find(1)
1555 issue = Issue.find(1)
1522 issue_in_same_project = Issue.find(2)
1556 issue_in_same_project = Issue.find(2)
1523 issue_in_child_project = Issue.find(5)
1557 issue_in_child_project = Issue.find(5)
1524 issue_in_grandchild_project = Issue.generate!(:project_id => 6, :tracker_id => 1)
1558 issue_in_grandchild_project = Issue.generate!(:project_id => 6, :tracker_id => 1)
1525 issue_in_other_child_project = Issue.find(6)
1559 issue_in_other_child_project = Issue.find(6)
1526 issue_in_different_tree = Issue.find(4)
1560 issue_in_different_tree = Issue.find(4)
1527
1561
1528 with_settings :cross_project_subtasks => '' do
1562 with_settings :cross_project_subtasks => '' do
1529 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1563 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1530 assert_equal false, issue.valid_parent_project?(issue_in_child_project)
1564 assert_equal false, issue.valid_parent_project?(issue_in_child_project)
1531 assert_equal false, issue.valid_parent_project?(issue_in_grandchild_project)
1565 assert_equal false, issue.valid_parent_project?(issue_in_grandchild_project)
1532 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1566 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1533 end
1567 end
1534
1568
1535 with_settings :cross_project_subtasks => 'system' do
1569 with_settings :cross_project_subtasks => 'system' do
1536 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1570 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1537 assert_equal true, issue.valid_parent_project?(issue_in_child_project)
1571 assert_equal true, issue.valid_parent_project?(issue_in_child_project)
1538 assert_equal true, issue.valid_parent_project?(issue_in_different_tree)
1572 assert_equal true, issue.valid_parent_project?(issue_in_different_tree)
1539 end
1573 end
1540
1574
1541 with_settings :cross_project_subtasks => 'tree' do
1575 with_settings :cross_project_subtasks => 'tree' do
1542 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1576 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1543 assert_equal true, issue.valid_parent_project?(issue_in_child_project)
1577 assert_equal true, issue.valid_parent_project?(issue_in_child_project)
1544 assert_equal true, issue.valid_parent_project?(issue_in_grandchild_project)
1578 assert_equal true, issue.valid_parent_project?(issue_in_grandchild_project)
1545 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1579 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1546
1580
1547 assert_equal true, issue_in_child_project.valid_parent_project?(issue_in_same_project)
1581 assert_equal true, issue_in_child_project.valid_parent_project?(issue_in_same_project)
1548 assert_equal true, issue_in_child_project.valid_parent_project?(issue_in_other_child_project)
1582 assert_equal true, issue_in_child_project.valid_parent_project?(issue_in_other_child_project)
1549 end
1583 end
1550
1584
1551 with_settings :cross_project_subtasks => 'descendants' do
1585 with_settings :cross_project_subtasks => 'descendants' do
1552 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1586 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1553 assert_equal false, issue.valid_parent_project?(issue_in_child_project)
1587 assert_equal false, issue.valid_parent_project?(issue_in_child_project)
1554 assert_equal false, issue.valid_parent_project?(issue_in_grandchild_project)
1588 assert_equal false, issue.valid_parent_project?(issue_in_grandchild_project)
1555 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1589 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1556
1590
1557 assert_equal true, issue_in_child_project.valid_parent_project?(issue)
1591 assert_equal true, issue_in_child_project.valid_parent_project?(issue)
1558 assert_equal false, issue_in_child_project.valid_parent_project?(issue_in_other_child_project)
1592 assert_equal false, issue_in_child_project.valid_parent_project?(issue_in_other_child_project)
1559 end
1593 end
1560 end
1594 end
1561
1595
1562 def test_recipients_should_include_previous_assignee
1596 def test_recipients_should_include_previous_assignee
1563 user = User.find(3)
1597 user = User.find(3)
1564 user.members.update_all ["mail_notification = ?", false]
1598 user.members.update_all ["mail_notification = ?", false]
1565 user.update_attribute :mail_notification, 'only_assigned'
1599 user.update_attribute :mail_notification, 'only_assigned'
1566
1600
1567 issue = Issue.find(2)
1601 issue = Issue.find(2)
1568 issue.assigned_to = nil
1602 issue.assigned_to = nil
1569 assert_include user.mail, issue.recipients
1603 assert_include user.mail, issue.recipients
1570 issue.save!
1604 issue.save!
1571 assert !issue.recipients.include?(user.mail)
1605 assert !issue.recipients.include?(user.mail)
1572 end
1606 end
1573
1607
1574 def test_recipients_should_not_include_users_that_cannot_view_the_issue
1608 def test_recipients_should_not_include_users_that_cannot_view_the_issue
1575 issue = Issue.find(12)
1609 issue = Issue.find(12)
1576 assert issue.recipients.include?(issue.author.mail)
1610 assert issue.recipients.include?(issue.author.mail)
1577 # copy the issue to a private project
1611 # copy the issue to a private project
1578 copy = issue.copy(:project_id => 5, :tracker_id => 2)
1612 copy = issue.copy(:project_id => 5, :tracker_id => 2)
1579 # author is not a member of project anymore
1613 # author is not a member of project anymore
1580 assert !copy.recipients.include?(copy.author.mail)
1614 assert !copy.recipients.include?(copy.author.mail)
1581 end
1615 end
1582
1616
1583 def test_recipients_should_include_the_assigned_group_members
1617 def test_recipients_should_include_the_assigned_group_members
1584 group_member = User.generate!
1618 group_member = User.generate!
1585 group = Group.generate!
1619 group = Group.generate!
1586 group.users << group_member
1620 group.users << group_member
1587
1621
1588 issue = Issue.find(12)
1622 issue = Issue.find(12)
1589 issue.assigned_to = group
1623 issue.assigned_to = group
1590 assert issue.recipients.include?(group_member.mail)
1624 assert issue.recipients.include?(group_member.mail)
1591 end
1625 end
1592
1626
1593 def test_watcher_recipients_should_not_include_users_that_cannot_view_the_issue
1627 def test_watcher_recipients_should_not_include_users_that_cannot_view_the_issue
1594 user = User.find(3)
1628 user = User.find(3)
1595 issue = Issue.find(9)
1629 issue = Issue.find(9)
1596 Watcher.create!(:user => user, :watchable => issue)
1630 Watcher.create!(:user => user, :watchable => issue)
1597 assert issue.watched_by?(user)
1631 assert issue.watched_by?(user)
1598 assert !issue.watcher_recipients.include?(user.mail)
1632 assert !issue.watcher_recipients.include?(user.mail)
1599 end
1633 end
1600
1634
1601 def test_issue_destroy
1635 def test_issue_destroy
1602 Issue.find(1).destroy
1636 Issue.find(1).destroy
1603 assert_nil Issue.find_by_id(1)
1637 assert_nil Issue.find_by_id(1)
1604 assert_nil TimeEntry.find_by_issue_id(1)
1638 assert_nil TimeEntry.find_by_issue_id(1)
1605 end
1639 end
1606
1640
1607 def test_destroy_should_delete_time_entries_custom_values
1641 def test_destroy_should_delete_time_entries_custom_values
1608 issue = Issue.generate!
1642 issue = Issue.generate!
1609 time_entry = TimeEntry.generate!(:issue => issue, :custom_field_values => {10 => '1'})
1643 time_entry = TimeEntry.generate!(:issue => issue, :custom_field_values => {10 => '1'})
1610
1644
1611 assert_difference 'CustomValue.where(:customized_type => "TimeEntry").count', -1 do
1645 assert_difference 'CustomValue.where(:customized_type => "TimeEntry").count', -1 do
1612 assert issue.destroy
1646 assert issue.destroy
1613 end
1647 end
1614 end
1648 end
1615
1649
1616 def test_destroying_a_deleted_issue_should_not_raise_an_error
1650 def test_destroying_a_deleted_issue_should_not_raise_an_error
1617 issue = Issue.find(1)
1651 issue = Issue.find(1)
1618 Issue.find(1).destroy
1652 Issue.find(1).destroy
1619
1653
1620 assert_nothing_raised do
1654 assert_nothing_raised do
1621 assert_no_difference 'Issue.count' do
1655 assert_no_difference 'Issue.count' do
1622 issue.destroy
1656 issue.destroy
1623 end
1657 end
1624 assert issue.destroyed?
1658 assert issue.destroyed?
1625 end
1659 end
1626 end
1660 end
1627
1661
1628 def test_destroying_a_stale_issue_should_not_raise_an_error
1662 def test_destroying_a_stale_issue_should_not_raise_an_error
1629 issue = Issue.find(1)
1663 issue = Issue.find(1)
1630 Issue.find(1).update_attribute :subject, "Updated"
1664 Issue.find(1).update_attribute :subject, "Updated"
1631
1665
1632 assert_nothing_raised do
1666 assert_nothing_raised do
1633 assert_difference 'Issue.count', -1 do
1667 assert_difference 'Issue.count', -1 do
1634 issue.destroy
1668 issue.destroy
1635 end
1669 end
1636 assert issue.destroyed?
1670 assert issue.destroyed?
1637 end
1671 end
1638 end
1672 end
1639
1673
1640 def test_blocked
1674 def test_blocked
1641 blocked_issue = Issue.find(9)
1675 blocked_issue = Issue.find(9)
1642 blocking_issue = Issue.find(10)
1676 blocking_issue = Issue.find(10)
1643
1677
1644 assert blocked_issue.blocked?
1678 assert blocked_issue.blocked?
1645 assert !blocking_issue.blocked?
1679 assert !blocking_issue.blocked?
1646 end
1680 end
1647
1681
1648 def test_blocked_issues_dont_allow_closed_statuses
1682 def test_blocked_issues_dont_allow_closed_statuses
1649 blocked_issue = Issue.find(9)
1683 blocked_issue = Issue.find(9)
1650
1684
1651 allowed_statuses = blocked_issue.new_statuses_allowed_to(users(:users_002))
1685 allowed_statuses = blocked_issue.new_statuses_allowed_to(users(:users_002))
1652 assert !allowed_statuses.empty?
1686 assert !allowed_statuses.empty?
1653 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
1687 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
1654 assert closed_statuses.empty?
1688 assert closed_statuses.empty?
1655 end
1689 end
1656
1690
1657 def test_unblocked_issues_allow_closed_statuses
1691 def test_unblocked_issues_allow_closed_statuses
1658 blocking_issue = Issue.find(10)
1692 blocking_issue = Issue.find(10)
1659
1693
1660 allowed_statuses = blocking_issue.new_statuses_allowed_to(users(:users_002))
1694 allowed_statuses = blocking_issue.new_statuses_allowed_to(users(:users_002))
1661 assert !allowed_statuses.empty?
1695 assert !allowed_statuses.empty?
1662 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
1696 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
1663 assert !closed_statuses.empty?
1697 assert !closed_statuses.empty?
1664 end
1698 end
1665
1699
1666 def test_reschedule_an_issue_without_dates
1700 def test_reschedule_an_issue_without_dates
1667 with_settings :non_working_week_days => [] do
1701 with_settings :non_working_week_days => [] do
1668 issue = Issue.new(:start_date => nil, :due_date => nil)
1702 issue = Issue.new(:start_date => nil, :due_date => nil)
1669 issue.reschedule_on '2012-10-09'.to_date
1703 issue.reschedule_on '2012-10-09'.to_date
1670 assert_equal '2012-10-09'.to_date, issue.start_date
1704 assert_equal '2012-10-09'.to_date, issue.start_date
1671 assert_equal '2012-10-09'.to_date, issue.due_date
1705 assert_equal '2012-10-09'.to_date, issue.due_date
1672 end
1706 end
1673
1707
1674 with_settings :non_working_week_days => %w(6 7) do
1708 with_settings :non_working_week_days => %w(6 7) do
1675 issue = Issue.new(:start_date => nil, :due_date => nil)
1709 issue = Issue.new(:start_date => nil, :due_date => nil)
1676 issue.reschedule_on '2012-10-09'.to_date
1710 issue.reschedule_on '2012-10-09'.to_date
1677 assert_equal '2012-10-09'.to_date, issue.start_date
1711 assert_equal '2012-10-09'.to_date, issue.start_date
1678 assert_equal '2012-10-09'.to_date, issue.due_date
1712 assert_equal '2012-10-09'.to_date, issue.due_date
1679
1713
1680 issue = Issue.new(:start_date => nil, :due_date => nil)
1714 issue = Issue.new(:start_date => nil, :due_date => nil)
1681 issue.reschedule_on '2012-10-13'.to_date
1715 issue.reschedule_on '2012-10-13'.to_date
1682 assert_equal '2012-10-15'.to_date, issue.start_date
1716 assert_equal '2012-10-15'.to_date, issue.start_date
1683 assert_equal '2012-10-15'.to_date, issue.due_date
1717 assert_equal '2012-10-15'.to_date, issue.due_date
1684 end
1718 end
1685 end
1719 end
1686
1720
1687 def test_reschedule_an_issue_with_start_date
1721 def test_reschedule_an_issue_with_start_date
1688 with_settings :non_working_week_days => [] do
1722 with_settings :non_working_week_days => [] do
1689 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1723 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1690 issue.reschedule_on '2012-10-13'.to_date
1724 issue.reschedule_on '2012-10-13'.to_date
1691 assert_equal '2012-10-13'.to_date, issue.start_date
1725 assert_equal '2012-10-13'.to_date, issue.start_date
1692 assert_equal '2012-10-13'.to_date, issue.due_date
1726 assert_equal '2012-10-13'.to_date, issue.due_date
1693 end
1727 end
1694
1728
1695 with_settings :non_working_week_days => %w(6 7) do
1729 with_settings :non_working_week_days => %w(6 7) do
1696 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1730 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1697 issue.reschedule_on '2012-10-11'.to_date
1731 issue.reschedule_on '2012-10-11'.to_date
1698 assert_equal '2012-10-11'.to_date, issue.start_date
1732 assert_equal '2012-10-11'.to_date, issue.start_date
1699 assert_equal '2012-10-11'.to_date, issue.due_date
1733 assert_equal '2012-10-11'.to_date, issue.due_date
1700
1734
1701 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1735 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1702 issue.reschedule_on '2012-10-13'.to_date
1736 issue.reschedule_on '2012-10-13'.to_date
1703 assert_equal '2012-10-15'.to_date, issue.start_date
1737 assert_equal '2012-10-15'.to_date, issue.start_date
1704 assert_equal '2012-10-15'.to_date, issue.due_date
1738 assert_equal '2012-10-15'.to_date, issue.due_date
1705 end
1739 end
1706 end
1740 end
1707
1741
1708 def test_reschedule_an_issue_with_start_and_due_dates
1742 def test_reschedule_an_issue_with_start_and_due_dates
1709 with_settings :non_working_week_days => [] do
1743 with_settings :non_working_week_days => [] do
1710 issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-15')
1744 issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-15')
1711 issue.reschedule_on '2012-10-13'.to_date
1745 issue.reschedule_on '2012-10-13'.to_date
1712 assert_equal '2012-10-13'.to_date, issue.start_date
1746 assert_equal '2012-10-13'.to_date, issue.start_date
1713 assert_equal '2012-10-19'.to_date, issue.due_date
1747 assert_equal '2012-10-19'.to_date, issue.due_date
1714 end
1748 end
1715
1749
1716 with_settings :non_working_week_days => %w(6 7) do
1750 with_settings :non_working_week_days => %w(6 7) do
1717 issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-19') # 8 working days
1751 issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-19') # 8 working days
1718 issue.reschedule_on '2012-10-11'.to_date
1752 issue.reschedule_on '2012-10-11'.to_date
1719 assert_equal '2012-10-11'.to_date, issue.start_date
1753 assert_equal '2012-10-11'.to_date, issue.start_date
1720 assert_equal '2012-10-23'.to_date, issue.due_date
1754 assert_equal '2012-10-23'.to_date, issue.due_date
1721
1755
1722 issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-19')
1756 issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-19')
1723 issue.reschedule_on '2012-10-13'.to_date
1757 issue.reschedule_on '2012-10-13'.to_date
1724 assert_equal '2012-10-15'.to_date, issue.start_date
1758 assert_equal '2012-10-15'.to_date, issue.start_date
1725 assert_equal '2012-10-25'.to_date, issue.due_date
1759 assert_equal '2012-10-25'.to_date, issue.due_date
1726 end
1760 end
1727 end
1761 end
1728
1762
1729 def test_rescheduling_an_issue_to_a_later_due_date_should_reschedule_following_issue
1763 def test_rescheduling_an_issue_to_a_later_due_date_should_reschedule_following_issue
1730 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1764 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1731 issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1765 issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1732 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1766 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1733 :relation_type => IssueRelation::TYPE_PRECEDES)
1767 :relation_type => IssueRelation::TYPE_PRECEDES)
1734 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
1768 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
1735
1769
1736 issue1.reload
1770 issue1.reload
1737 issue1.due_date = '2012-10-23'
1771 issue1.due_date = '2012-10-23'
1738 issue1.save!
1772 issue1.save!
1739 issue2.reload
1773 issue2.reload
1740 assert_equal Date.parse('2012-10-24'), issue2.start_date
1774 assert_equal Date.parse('2012-10-24'), issue2.start_date
1741 assert_equal Date.parse('2012-10-26'), issue2.due_date
1775 assert_equal Date.parse('2012-10-26'), issue2.due_date
1742 end
1776 end
1743
1777
1744 def test_rescheduling_an_issue_to_an_earlier_due_date_should_reschedule_following_issue
1778 def test_rescheduling_an_issue_to_an_earlier_due_date_should_reschedule_following_issue
1745 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1779 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1746 issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1780 issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1747 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1781 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1748 :relation_type => IssueRelation::TYPE_PRECEDES)
1782 :relation_type => IssueRelation::TYPE_PRECEDES)
1749 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
1783 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
1750
1784
1751 issue1.reload
1785 issue1.reload
1752 issue1.start_date = '2012-09-17'
1786 issue1.start_date = '2012-09-17'
1753 issue1.due_date = '2012-09-18'
1787 issue1.due_date = '2012-09-18'
1754 issue1.save!
1788 issue1.save!
1755 issue2.reload
1789 issue2.reload
1756 assert_equal Date.parse('2012-09-19'), issue2.start_date
1790 assert_equal Date.parse('2012-09-19'), issue2.start_date
1757 assert_equal Date.parse('2012-09-21'), issue2.due_date
1791 assert_equal Date.parse('2012-09-21'), issue2.due_date
1758 end
1792 end
1759
1793
1760 def test_rescheduling_reschedule_following_issue_earlier_should_consider_other_preceding_issues
1794 def test_rescheduling_reschedule_following_issue_earlier_should_consider_other_preceding_issues
1761 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1795 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1762 issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1796 issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1763 issue3 = Issue.generate!(:start_date => '2012-10-01', :due_date => '2012-10-02')
1797 issue3 = Issue.generate!(:start_date => '2012-10-01', :due_date => '2012-10-02')
1764 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1798 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1765 :relation_type => IssueRelation::TYPE_PRECEDES)
1799 :relation_type => IssueRelation::TYPE_PRECEDES)
1766 IssueRelation.create!(:issue_from => issue3, :issue_to => issue2,
1800 IssueRelation.create!(:issue_from => issue3, :issue_to => issue2,
1767 :relation_type => IssueRelation::TYPE_PRECEDES)
1801 :relation_type => IssueRelation::TYPE_PRECEDES)
1768 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
1802 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
1769
1803
1770 issue1.reload
1804 issue1.reload
1771 issue1.start_date = '2012-09-17'
1805 issue1.start_date = '2012-09-17'
1772 issue1.due_date = '2012-09-18'
1806 issue1.due_date = '2012-09-18'
1773 issue1.save!
1807 issue1.save!
1774 issue2.reload
1808 issue2.reload
1775 # Issue 2 must start after Issue 3
1809 # Issue 2 must start after Issue 3
1776 assert_equal Date.parse('2012-10-03'), issue2.start_date
1810 assert_equal Date.parse('2012-10-03'), issue2.start_date
1777 assert_equal Date.parse('2012-10-05'), issue2.due_date
1811 assert_equal Date.parse('2012-10-05'), issue2.due_date
1778 end
1812 end
1779
1813
1780 def test_rescheduling_a_stale_issue_should_not_raise_an_error
1814 def test_rescheduling_a_stale_issue_should_not_raise_an_error
1781 with_settings :non_working_week_days => [] do
1815 with_settings :non_working_week_days => [] do
1782 stale = Issue.find(1)
1816 stale = Issue.find(1)
1783 issue = Issue.find(1)
1817 issue = Issue.find(1)
1784 issue.subject = "Updated"
1818 issue.subject = "Updated"
1785 issue.save!
1819 issue.save!
1786 date = 10.days.from_now.to_date
1820 date = 10.days.from_now.to_date
1787 assert_nothing_raised do
1821 assert_nothing_raised do
1788 stale.reschedule_on!(date)
1822 stale.reschedule_on!(date)
1789 end
1823 end
1790 assert_equal date, stale.reload.start_date
1824 assert_equal date, stale.reload.start_date
1791 end
1825 end
1792 end
1826 end
1793
1827
1794 def test_child_issue_should_consider_parent_soonest_start_on_create
1828 def test_child_issue_should_consider_parent_soonest_start_on_create
1795 set_language_if_valid 'en'
1829 set_language_if_valid 'en'
1796 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1830 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1797 issue2 = Issue.generate!(:start_date => '2012-10-18', :due_date => '2012-10-20')
1831 issue2 = Issue.generate!(:start_date => '2012-10-18', :due_date => '2012-10-20')
1798 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1832 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1799 :relation_type => IssueRelation::TYPE_PRECEDES)
1833 :relation_type => IssueRelation::TYPE_PRECEDES)
1800 issue1.reload
1834 issue1.reload
1801 issue2.reload
1835 issue2.reload
1802 assert_equal Date.parse('2012-10-18'), issue2.start_date
1836 assert_equal Date.parse('2012-10-18'), issue2.start_date
1803
1837
1804 with_settings :date_format => '%m/%d/%Y' do
1838 with_settings :date_format => '%m/%d/%Y' do
1805 child = Issue.new(:parent_issue_id => issue2.id, :start_date => '2012-10-16',
1839 child = Issue.new(:parent_issue_id => issue2.id, :start_date => '2012-10-16',
1806 :project_id => 1, :tracker_id => 1, :status_id => 1, :subject => 'Child', :author_id => 1)
1840 :project_id => 1, :tracker_id => 1, :status_id => 1, :subject => 'Child', :author_id => 1)
1807 assert !child.valid?
1841 assert !child.valid?
1808 assert_include 'Start date cannot be earlier than 10/18/2012 because of preceding issues', child.errors.full_messages
1842 assert_include 'Start date cannot be earlier than 10/18/2012 because of preceding issues', child.errors.full_messages
1809 assert_equal Date.parse('2012-10-18'), child.soonest_start
1843 assert_equal Date.parse('2012-10-18'), child.soonest_start
1810 child.start_date = '2012-10-18'
1844 child.start_date = '2012-10-18'
1811 assert child.save
1845 assert child.save
1812 end
1846 end
1813 end
1847 end
1814
1848
1815 def test_setting_parent_to_a_dependent_issue_should_not_validate
1849 def test_setting_parent_to_a_dependent_issue_should_not_validate
1816 set_language_if_valid 'en'
1850 set_language_if_valid 'en'
1817 issue1 = Issue.generate!
1851 issue1 = Issue.generate!
1818 issue2 = Issue.generate!
1852 issue2 = Issue.generate!
1819 issue3 = Issue.generate!
1853 issue3 = Issue.generate!
1820 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
1854 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
1821 IssueRelation.create!(:issue_from => issue3, :issue_to => issue1, :relation_type => IssueRelation::TYPE_PRECEDES)
1855 IssueRelation.create!(:issue_from => issue3, :issue_to => issue1, :relation_type => IssueRelation::TYPE_PRECEDES)
1822 issue3.reload
1856 issue3.reload
1823 issue3.parent_issue_id = issue2.id
1857 issue3.parent_issue_id = issue2.id
1824 assert !issue3.valid?
1858 assert !issue3.valid?
1825 assert_include 'Parent task is invalid', issue3.errors.full_messages
1859 assert_include 'Parent task is invalid', issue3.errors.full_messages
1826 end
1860 end
1827
1861
1828 def test_setting_parent_should_not_allow_circular_dependency
1862 def test_setting_parent_should_not_allow_circular_dependency
1829 set_language_if_valid 'en'
1863 set_language_if_valid 'en'
1830 issue1 = Issue.generate!
1864 issue1 = Issue.generate!
1831 issue2 = Issue.generate!
1865 issue2 = Issue.generate!
1832 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
1866 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
1833 issue3 = Issue.generate!
1867 issue3 = Issue.generate!
1834 issue2.reload
1868 issue2.reload
1835 issue2.parent_issue_id = issue3.id
1869 issue2.parent_issue_id = issue3.id
1836 issue2.save!
1870 issue2.save!
1837 issue4 = Issue.generate!
1871 issue4 = Issue.generate!
1838 IssueRelation.create!(:issue_from => issue3, :issue_to => issue4, :relation_type => IssueRelation::TYPE_PRECEDES)
1872 IssueRelation.create!(:issue_from => issue3, :issue_to => issue4, :relation_type => IssueRelation::TYPE_PRECEDES)
1839 issue4.reload
1873 issue4.reload
1840 issue4.parent_issue_id = issue1.id
1874 issue4.parent_issue_id = issue1.id
1841 assert !issue4.valid?
1875 assert !issue4.valid?
1842 assert_include 'Parent task is invalid', issue4.errors.full_messages
1876 assert_include 'Parent task is invalid', issue4.errors.full_messages
1843 end
1877 end
1844
1878
1845 def test_overdue
1879 def test_overdue
1846 assert Issue.new(:due_date => 1.day.ago.to_date).overdue?
1880 assert Issue.new(:due_date => 1.day.ago.to_date).overdue?
1847 assert !Issue.new(:due_date => Date.today).overdue?
1881 assert !Issue.new(:due_date => Date.today).overdue?
1848 assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue?
1882 assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue?
1849 assert !Issue.new(:due_date => nil).overdue?
1883 assert !Issue.new(:due_date => nil).overdue?
1850 assert !Issue.new(:due_date => 1.day.ago.to_date,
1884 assert !Issue.new(:due_date => 1.day.ago.to_date,
1851 :status => IssueStatus.where(:is_closed => true).first
1885 :status => IssueStatus.where(:is_closed => true).first
1852 ).overdue?
1886 ).overdue?
1853 end
1887 end
1854
1888
1855 test "#behind_schedule? should be false if the issue has no start_date" do
1889 test "#behind_schedule? should be false if the issue has no start_date" do
1856 assert !Issue.new(:start_date => nil,
1890 assert !Issue.new(:start_date => nil,
1857 :due_date => 1.day.from_now.to_date,
1891 :due_date => 1.day.from_now.to_date,
1858 :done_ratio => 0).behind_schedule?
1892 :done_ratio => 0).behind_schedule?
1859 end
1893 end
1860
1894
1861 test "#behind_schedule? should be false if the issue has no end_date" do
1895 test "#behind_schedule? should be false if the issue has no end_date" do
1862 assert !Issue.new(:start_date => 1.day.from_now.to_date,
1896 assert !Issue.new(:start_date => 1.day.from_now.to_date,
1863 :due_date => nil,
1897 :due_date => nil,
1864 :done_ratio => 0).behind_schedule?
1898 :done_ratio => 0).behind_schedule?
1865 end
1899 end
1866
1900
1867 test "#behind_schedule? should be false if the issue has more done than it's calendar time" do
1901 test "#behind_schedule? should be false if the issue has more done than it's calendar time" do
1868 assert !Issue.new(:start_date => 50.days.ago.to_date,
1902 assert !Issue.new(:start_date => 50.days.ago.to_date,
1869 :due_date => 50.days.from_now.to_date,
1903 :due_date => 50.days.from_now.to_date,
1870 :done_ratio => 90).behind_schedule?
1904 :done_ratio => 90).behind_schedule?
1871 end
1905 end
1872
1906
1873 test "#behind_schedule? should be true if the issue hasn't been started at all" do
1907 test "#behind_schedule? should be true if the issue hasn't been started at all" do
1874 assert Issue.new(:start_date => 1.day.ago.to_date,
1908 assert Issue.new(:start_date => 1.day.ago.to_date,
1875 :due_date => 1.day.from_now.to_date,
1909 :due_date => 1.day.from_now.to_date,
1876 :done_ratio => 0).behind_schedule?
1910 :done_ratio => 0).behind_schedule?
1877 end
1911 end
1878
1912
1879 test "#behind_schedule? should be true if the issue has used more calendar time than it's done ratio" do
1913 test "#behind_schedule? should be true if the issue has used more calendar time than it's done ratio" do
1880 assert Issue.new(:start_date => 100.days.ago.to_date,
1914 assert Issue.new(:start_date => 100.days.ago.to_date,
1881 :due_date => Date.today,
1915 :due_date => Date.today,
1882 :done_ratio => 90).behind_schedule?
1916 :done_ratio => 90).behind_schedule?
1883 end
1917 end
1884
1918
1885 test "#assignable_users should be Users" do
1919 test "#assignable_users should be Users" do
1886 assert_kind_of User, Issue.find(1).assignable_users.first
1920 assert_kind_of User, Issue.find(1).assignable_users.first
1887 end
1921 end
1888
1922
1889 test "#assignable_users should include the issue author" do
1923 test "#assignable_users should include the issue author" do
1890 non_project_member = User.generate!
1924 non_project_member = User.generate!
1891 issue = Issue.generate!(:author => non_project_member)
1925 issue = Issue.generate!(:author => non_project_member)
1892
1926
1893 assert issue.assignable_users.include?(non_project_member)
1927 assert issue.assignable_users.include?(non_project_member)
1894 end
1928 end
1895
1929
1896 def test_assignable_users_should_not_include_anonymous_user
1930 def test_assignable_users_should_not_include_anonymous_user
1897 issue = Issue.generate!(:author => User.anonymous)
1931 issue = Issue.generate!(:author => User.anonymous)
1898
1932
1899 assert !issue.assignable_users.include?(User.anonymous)
1933 assert !issue.assignable_users.include?(User.anonymous)
1900 end
1934 end
1901
1935
1902 def test_assignable_users_should_not_include_locked_user
1936 def test_assignable_users_should_not_include_locked_user
1903 user = User.generate!
1937 user = User.generate!
1904 issue = Issue.generate!(:author => user)
1938 issue = Issue.generate!(:author => user)
1905 user.lock!
1939 user.lock!
1906
1940
1907 assert !issue.assignable_users.include?(user)
1941 assert !issue.assignable_users.include?(user)
1908 end
1942 end
1909
1943
1910 test "#assignable_users should include the current assignee" do
1944 test "#assignable_users should include the current assignee" do
1911 user = User.generate!
1945 user = User.generate!
1912 issue = Issue.generate!(:assigned_to => user)
1946 issue = Issue.generate!(:assigned_to => user)
1913 user.lock!
1947 user.lock!
1914
1948
1915 assert Issue.find(issue.id).assignable_users.include?(user)
1949 assert Issue.find(issue.id).assignable_users.include?(user)
1916 end
1950 end
1917
1951
1918 test "#assignable_users should not show the issue author twice" do
1952 test "#assignable_users should not show the issue author twice" do
1919 assignable_user_ids = Issue.find(1).assignable_users.collect(&:id)
1953 assignable_user_ids = Issue.find(1).assignable_users.collect(&:id)
1920 assert_equal 2, assignable_user_ids.length
1954 assert_equal 2, assignable_user_ids.length
1921
1955
1922 assignable_user_ids.each do |user_id|
1956 assignable_user_ids.each do |user_id|
1923 assert_equal 1, assignable_user_ids.select {|i| i == user_id}.length,
1957 assert_equal 1, assignable_user_ids.select {|i| i == user_id}.length,
1924 "User #{user_id} appears more or less than once"
1958 "User #{user_id} appears more or less than once"
1925 end
1959 end
1926 end
1960 end
1927
1961
1928 test "#assignable_users with issue_group_assignment should include groups" do
1962 test "#assignable_users with issue_group_assignment should include groups" do
1929 issue = Issue.new(:project => Project.find(2))
1963 issue = Issue.new(:project => Project.find(2))
1930
1964
1931 with_settings :issue_group_assignment => '1' do
1965 with_settings :issue_group_assignment => '1' do
1932 assert_equal %w(Group User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
1966 assert_equal %w(Group User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
1933 assert issue.assignable_users.include?(Group.find(11))
1967 assert issue.assignable_users.include?(Group.find(11))
1934 end
1968 end
1935 end
1969 end
1936
1970
1937 test "#assignable_users without issue_group_assignment should not include groups" do
1971 test "#assignable_users without issue_group_assignment should not include groups" do
1938 issue = Issue.new(:project => Project.find(2))
1972 issue = Issue.new(:project => Project.find(2))
1939
1973
1940 with_settings :issue_group_assignment => '0' do
1974 with_settings :issue_group_assignment => '0' do
1941 assert_equal %w(User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
1975 assert_equal %w(User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
1942 assert !issue.assignable_users.include?(Group.find(11))
1976 assert !issue.assignable_users.include?(Group.find(11))
1943 end
1977 end
1944 end
1978 end
1945
1979
1946 def test_assignable_users_should_not_include_builtin_groups
1980 def test_assignable_users_should_not_include_builtin_groups
1947 Member.create!(:project_id => 1, :principal => Group.non_member, :role_ids => [1])
1981 Member.create!(:project_id => 1, :principal => Group.non_member, :role_ids => [1])
1948 Member.create!(:project_id => 1, :principal => Group.anonymous, :role_ids => [1])
1982 Member.create!(:project_id => 1, :principal => Group.anonymous, :role_ids => [1])
1949 issue = Issue.new(:project => Project.find(1))
1983 issue = Issue.new(:project => Project.find(1))
1950
1984
1951 with_settings :issue_group_assignment => '1' do
1985 with_settings :issue_group_assignment => '1' do
1952 assert_nil issue.assignable_users.detect {|u| u.is_a?(GroupBuiltin)}
1986 assert_nil issue.assignable_users.detect {|u| u.is_a?(GroupBuiltin)}
1953 end
1987 end
1954 end
1988 end
1955
1989
1956 def test_create_should_send_email_notification
1990 def test_create_should_send_email_notification
1957 ActionMailer::Base.deliveries.clear
1991 ActionMailer::Base.deliveries.clear
1958 issue = Issue.new(:project_id => 1, :tracker_id => 1,
1992 issue = Issue.new(:project_id => 1, :tracker_id => 1,
1959 :author_id => 3, :status_id => 1,
1993 :author_id => 3, :status_id => 1,
1960 :priority => IssuePriority.all.first,
1994 :priority => IssuePriority.all.first,
1961 :subject => 'test_create', :estimated_hours => '1:30')
1995 :subject => 'test_create', :estimated_hours => '1:30')
1962 with_settings :notified_events => %w(issue_added) do
1996 with_settings :notified_events => %w(issue_added) do
1963 assert issue.save
1997 assert issue.save
1964 assert_equal 1, ActionMailer::Base.deliveries.size
1998 assert_equal 1, ActionMailer::Base.deliveries.size
1965 end
1999 end
1966 end
2000 end
1967
2001
1968 def test_create_should_send_one_email_notification_with_both_settings
2002 def test_create_should_send_one_email_notification_with_both_settings
1969 ActionMailer::Base.deliveries.clear
2003 ActionMailer::Base.deliveries.clear
1970 issue = Issue.new(:project_id => 1, :tracker_id => 1,
2004 issue = Issue.new(:project_id => 1, :tracker_id => 1,
1971 :author_id => 3, :status_id => 1,
2005 :author_id => 3, :status_id => 1,
1972 :priority => IssuePriority.all.first,
2006 :priority => IssuePriority.all.first,
1973 :subject => 'test_create', :estimated_hours => '1:30')
2007 :subject => 'test_create', :estimated_hours => '1:30')
1974 with_settings :notified_events => %w(issue_added issue_updated) do
2008 with_settings :notified_events => %w(issue_added issue_updated) do
1975 assert issue.save
2009 assert issue.save
1976 assert_equal 1, ActionMailer::Base.deliveries.size
2010 assert_equal 1, ActionMailer::Base.deliveries.size
1977 end
2011 end
1978 end
2012 end
1979
2013
1980 def test_create_should_not_send_email_notification_with_no_setting
2014 def test_create_should_not_send_email_notification_with_no_setting
1981 ActionMailer::Base.deliveries.clear
2015 ActionMailer::Base.deliveries.clear
1982 issue = Issue.new(:project_id => 1, :tracker_id => 1,
2016 issue = Issue.new(:project_id => 1, :tracker_id => 1,
1983 :author_id => 3, :status_id => 1,
2017 :author_id => 3, :status_id => 1,
1984 :priority => IssuePriority.all.first,
2018 :priority => IssuePriority.all.first,
1985 :subject => 'test_create', :estimated_hours => '1:30')
2019 :subject => 'test_create', :estimated_hours => '1:30')
1986 with_settings :notified_events => [] do
2020 with_settings :notified_events => [] do
1987 assert issue.save
2021 assert issue.save
1988 assert_equal 0, ActionMailer::Base.deliveries.size
2022 assert_equal 0, ActionMailer::Base.deliveries.size
1989 end
2023 end
1990 end
2024 end
1991
2025
1992 def test_update_should_notify_previous_assignee
2026 def test_update_should_notify_previous_assignee
1993 ActionMailer::Base.deliveries.clear
2027 ActionMailer::Base.deliveries.clear
1994 user = User.find(3)
2028 user = User.find(3)
1995 user.members.update_all ["mail_notification = ?", false]
2029 user.members.update_all ["mail_notification = ?", false]
1996 user.update_attribute :mail_notification, 'only_assigned'
2030 user.update_attribute :mail_notification, 'only_assigned'
1997
2031
1998 with_settings :notified_events => %w(issue_updated) do
2032 with_settings :notified_events => %w(issue_updated) do
1999 issue = Issue.find(2)
2033 issue = Issue.find(2)
2000 issue.init_journal User.find(1)
2034 issue.init_journal User.find(1)
2001 issue.assigned_to = nil
2035 issue.assigned_to = nil
2002 issue.save!
2036 issue.save!
2003 assert_include user.mail, ActionMailer::Base.deliveries.last.bcc
2037 assert_include user.mail, ActionMailer::Base.deliveries.last.bcc
2004 end
2038 end
2005 end
2039 end
2006
2040
2007 def test_stale_issue_should_not_send_email_notification
2041 def test_stale_issue_should_not_send_email_notification
2008 ActionMailer::Base.deliveries.clear
2042 ActionMailer::Base.deliveries.clear
2009 issue = Issue.find(1)
2043 issue = Issue.find(1)
2010 stale = Issue.find(1)
2044 stale = Issue.find(1)
2011
2045
2012 issue.init_journal(User.find(1))
2046 issue.init_journal(User.find(1))
2013 issue.subject = 'Subjet update'
2047 issue.subject = 'Subjet update'
2014 with_settings :notified_events => %w(issue_updated) do
2048 with_settings :notified_events => %w(issue_updated) do
2015 assert issue.save
2049 assert issue.save
2016 assert_equal 1, ActionMailer::Base.deliveries.size
2050 assert_equal 1, ActionMailer::Base.deliveries.size
2017 ActionMailer::Base.deliveries.clear
2051 ActionMailer::Base.deliveries.clear
2018
2052
2019 stale.init_journal(User.find(1))
2053 stale.init_journal(User.find(1))
2020 stale.subject = 'Another subjet update'
2054 stale.subject = 'Another subjet update'
2021 assert_raise ActiveRecord::StaleObjectError do
2055 assert_raise ActiveRecord::StaleObjectError do
2022 stale.save
2056 stale.save
2023 end
2057 end
2024 assert ActionMailer::Base.deliveries.empty?
2058 assert ActionMailer::Base.deliveries.empty?
2025 end
2059 end
2026 end
2060 end
2027
2061
2028 def test_journalized_description
2062 def test_journalized_description
2029 IssueCustomField.delete_all
2063 IssueCustomField.delete_all
2030
2064
2031 i = Issue.first
2065 i = Issue.first
2032 old_description = i.description
2066 old_description = i.description
2033 new_description = "This is the new description"
2067 new_description = "This is the new description"
2034
2068
2035 i.init_journal(User.find(2))
2069 i.init_journal(User.find(2))
2036 i.description = new_description
2070 i.description = new_description
2037 assert_difference 'Journal.count', 1 do
2071 assert_difference 'Journal.count', 1 do
2038 assert_difference 'JournalDetail.count', 1 do
2072 assert_difference 'JournalDetail.count', 1 do
2039 i.save!
2073 i.save!
2040 end
2074 end
2041 end
2075 end
2042
2076
2043 detail = JournalDetail.order('id DESC').first
2077 detail = JournalDetail.order('id DESC').first
2044 assert_equal i, detail.journal.journalized
2078 assert_equal i, detail.journal.journalized
2045 assert_equal 'attr', detail.property
2079 assert_equal 'attr', detail.property
2046 assert_equal 'description', detail.prop_key
2080 assert_equal 'description', detail.prop_key
2047 assert_equal old_description, detail.old_value
2081 assert_equal old_description, detail.old_value
2048 assert_equal new_description, detail.value
2082 assert_equal new_description, detail.value
2049 end
2083 end
2050
2084
2051 def test_blank_descriptions_should_not_be_journalized
2085 def test_blank_descriptions_should_not_be_journalized
2052 IssueCustomField.delete_all
2086 IssueCustomField.delete_all
2053 Issue.where(:id => 1).update_all("description = NULL")
2087 Issue.where(:id => 1).update_all("description = NULL")
2054
2088
2055 i = Issue.find(1)
2089 i = Issue.find(1)
2056 i.init_journal(User.find(2))
2090 i.init_journal(User.find(2))
2057 i.subject = "blank description"
2091 i.subject = "blank description"
2058 i.description = "\r\n"
2092 i.description = "\r\n"
2059
2093
2060 assert_difference 'Journal.count', 1 do
2094 assert_difference 'Journal.count', 1 do
2061 assert_difference 'JournalDetail.count', 1 do
2095 assert_difference 'JournalDetail.count', 1 do
2062 i.save!
2096 i.save!
2063 end
2097 end
2064 end
2098 end
2065 end
2099 end
2066
2100
2067 def test_journalized_multi_custom_field
2101 def test_journalized_multi_custom_field
2068 field = IssueCustomField.create!(:name => 'filter', :field_format => 'list',
2102 field = IssueCustomField.create!(:name => 'filter', :field_format => 'list',
2069 :is_filter => true, :is_for_all => true,
2103 :is_filter => true, :is_for_all => true,
2070 :tracker_ids => [1],
2104 :tracker_ids => [1],
2071 :possible_values => ['value1', 'value2', 'value3'],
2105 :possible_values => ['value1', 'value2', 'value3'],
2072 :multiple => true)
2106 :multiple => true)
2073
2107
2074 issue = Issue.create!(:project_id => 1, :tracker_id => 1,
2108 issue = Issue.create!(:project_id => 1, :tracker_id => 1,
2075 :subject => 'Test', :author_id => 1)
2109 :subject => 'Test', :author_id => 1)
2076
2110
2077 assert_difference 'Journal.count' do
2111 assert_difference 'Journal.count' do
2078 assert_difference 'JournalDetail.count' do
2112 assert_difference 'JournalDetail.count' do
2079 issue.init_journal(User.first)
2113 issue.init_journal(User.first)
2080 issue.custom_field_values = {field.id => ['value1']}
2114 issue.custom_field_values = {field.id => ['value1']}
2081 issue.save!
2115 issue.save!
2082 end
2116 end
2083 assert_difference 'JournalDetail.count' do
2117 assert_difference 'JournalDetail.count' do
2084 issue.init_journal(User.first)
2118 issue.init_journal(User.first)
2085 issue.custom_field_values = {field.id => ['value1', 'value2']}
2119 issue.custom_field_values = {field.id => ['value1', 'value2']}
2086 issue.save!
2120 issue.save!
2087 end
2121 end
2088 assert_difference 'JournalDetail.count', 2 do
2122 assert_difference 'JournalDetail.count', 2 do
2089 issue.init_journal(User.first)
2123 issue.init_journal(User.first)
2090 issue.custom_field_values = {field.id => ['value3', 'value2']}
2124 issue.custom_field_values = {field.id => ['value3', 'value2']}
2091 issue.save!
2125 issue.save!
2092 end
2126 end
2093 assert_difference 'JournalDetail.count', 2 do
2127 assert_difference 'JournalDetail.count', 2 do
2094 issue.init_journal(User.first)
2128 issue.init_journal(User.first)
2095 issue.custom_field_values = {field.id => nil}
2129 issue.custom_field_values = {field.id => nil}
2096 issue.save!
2130 issue.save!
2097 end
2131 end
2098 end
2132 end
2099 end
2133 end
2100
2134
2101 def test_description_eol_should_be_normalized
2135 def test_description_eol_should_be_normalized
2102 i = Issue.new(:description => "CR \r LF \n CRLF \r\n")
2136 i = Issue.new(:description => "CR \r LF \n CRLF \r\n")
2103 assert_equal "CR \r\n LF \r\n CRLF \r\n", i.description
2137 assert_equal "CR \r\n LF \r\n CRLF \r\n", i.description
2104 end
2138 end
2105
2139
2106 def test_saving_twice_should_not_duplicate_journal_details
2140 def test_saving_twice_should_not_duplicate_journal_details
2107 i = Issue.first
2141 i = Issue.first
2108 i.init_journal(User.find(2), 'Some notes')
2142 i.init_journal(User.find(2), 'Some notes')
2109 # initial changes
2143 # initial changes
2110 i.subject = 'New subject'
2144 i.subject = 'New subject'
2111 i.done_ratio = i.done_ratio + 10
2145 i.done_ratio = i.done_ratio + 10
2112 assert_difference 'Journal.count' do
2146 assert_difference 'Journal.count' do
2113 assert i.save
2147 assert i.save
2114 end
2148 end
2115 # 1 more change
2149 # 1 more change
2116 i.priority = IssuePriority.where("id <> ?", i.priority_id).first
2150 i.priority = IssuePriority.where("id <> ?", i.priority_id).first
2117 assert_no_difference 'Journal.count' do
2151 assert_no_difference 'Journal.count' do
2118 assert_difference 'JournalDetail.count', 1 do
2152 assert_difference 'JournalDetail.count', 1 do
2119 i.save
2153 i.save
2120 end
2154 end
2121 end
2155 end
2122 # no more change
2156 # no more change
2123 assert_no_difference 'Journal.count' do
2157 assert_no_difference 'Journal.count' do
2124 assert_no_difference 'JournalDetail.count' do
2158 assert_no_difference 'JournalDetail.count' do
2125 i.save
2159 i.save
2126 end
2160 end
2127 end
2161 end
2128 end
2162 end
2129
2163
2130 def test_all_dependent_issues
2164 def test_all_dependent_issues
2131 IssueRelation.delete_all
2165 IssueRelation.delete_all
2132 assert IssueRelation.create!(:issue_from => Issue.find(1),
2166 assert IssueRelation.create!(:issue_from => Issue.find(1),
2133 :issue_to => Issue.find(2),
2167 :issue_to => Issue.find(2),
2134 :relation_type => IssueRelation::TYPE_PRECEDES)
2168 :relation_type => IssueRelation::TYPE_PRECEDES)
2135 assert IssueRelation.create!(:issue_from => Issue.find(2),
2169 assert IssueRelation.create!(:issue_from => Issue.find(2),
2136 :issue_to => Issue.find(3),
2170 :issue_to => Issue.find(3),
2137 :relation_type => IssueRelation::TYPE_PRECEDES)
2171 :relation_type => IssueRelation::TYPE_PRECEDES)
2138 assert IssueRelation.create!(:issue_from => Issue.find(3),
2172 assert IssueRelation.create!(:issue_from => Issue.find(3),
2139 :issue_to => Issue.find(8),
2173 :issue_to => Issue.find(8),
2140 :relation_type => IssueRelation::TYPE_PRECEDES)
2174 :relation_type => IssueRelation::TYPE_PRECEDES)
2141
2175
2142 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
2176 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
2143 end
2177 end
2144
2178
2145 def test_all_dependent_issues_with_subtask
2179 def test_all_dependent_issues_with_subtask
2146 IssueRelation.delete_all
2180 IssueRelation.delete_all
2147
2181
2148 project = Project.generate!(:name => "testproject")
2182 project = Project.generate!(:name => "testproject")
2149
2183
2150 parentIssue = Issue.generate!(:project => project)
2184 parentIssue = Issue.generate!(:project => project)
2151 childIssue1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2185 childIssue1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2152 childIssue2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2186 childIssue2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2153
2187
2154 assert_equal [childIssue1.id, childIssue2.id].sort, parentIssue.all_dependent_issues.collect(&:id).uniq.sort
2188 assert_equal [childIssue1.id, childIssue2.id].sort, parentIssue.all_dependent_issues.collect(&:id).uniq.sort
2155 end
2189 end
2156
2190
2157 def test_all_dependent_issues_does_not_include_self
2191 def test_all_dependent_issues_does_not_include_self
2158 IssueRelation.delete_all
2192 IssueRelation.delete_all
2159
2193
2160 project = Project.generate!(:name => "testproject")
2194 project = Project.generate!(:name => "testproject")
2161
2195
2162 parentIssue = Issue.generate!(:project => project)
2196 parentIssue = Issue.generate!(:project => project)
2163 childIssue = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2197 childIssue = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2164
2198
2165 assert_equal [childIssue.id], parentIssue.all_dependent_issues.collect(&:id)
2199 assert_equal [childIssue.id], parentIssue.all_dependent_issues.collect(&:id)
2166 end
2200 end
2167
2201
2168 def test_all_dependent_issues_with_parenttask_and_sibling
2202 def test_all_dependent_issues_with_parenttask_and_sibling
2169 IssueRelation.delete_all
2203 IssueRelation.delete_all
2170
2204
2171 project = Project.generate!(:name => "testproject")
2205 project = Project.generate!(:name => "testproject")
2172
2206
2173 parentIssue = Issue.generate!(:project => project)
2207 parentIssue = Issue.generate!(:project => project)
2174 childIssue1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2208 childIssue1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2175 childIssue2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2209 childIssue2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2176
2210
2177 assert_equal [parentIssue.id].sort, childIssue1.all_dependent_issues.collect(&:id)
2211 assert_equal [parentIssue.id].sort, childIssue1.all_dependent_issues.collect(&:id)
2178 end
2212 end
2179
2213
2180 def test_all_dependent_issues_with_relation_to_leaf_in_other_tree
2214 def test_all_dependent_issues_with_relation_to_leaf_in_other_tree
2181 IssueRelation.delete_all
2215 IssueRelation.delete_all
2182
2216
2183 project = Project.generate!(:name => "testproject")
2217 project = Project.generate!(:name => "testproject")
2184
2218
2185 parentIssue1 = Issue.generate!(:project => project)
2219 parentIssue1 = Issue.generate!(:project => project)
2186 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2220 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2187 childIssue1_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2221 childIssue1_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2188
2222
2189 parentIssue2 = Issue.generate!(:project => project)
2223 parentIssue2 = Issue.generate!(:project => project)
2190 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2224 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2191 childIssue2_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2225 childIssue2_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2192
2226
2193
2227
2194 assert IssueRelation.create(:issue_from => parentIssue1,
2228 assert IssueRelation.create(:issue_from => parentIssue1,
2195 :issue_to => childIssue2_2,
2229 :issue_to => childIssue2_2,
2196 :relation_type => IssueRelation::TYPE_BLOCKS)
2230 :relation_type => IssueRelation::TYPE_BLOCKS)
2197
2231
2198 assert_equal [childIssue1_1.id, childIssue1_2.id, parentIssue2.id, childIssue2_2.id].sort,
2232 assert_equal [childIssue1_1.id, childIssue1_2.id, parentIssue2.id, childIssue2_2.id].sort,
2199 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
2233 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
2200 end
2234 end
2201
2235
2202 def test_all_dependent_issues_with_relation_to_parent_in_other_tree
2236 def test_all_dependent_issues_with_relation_to_parent_in_other_tree
2203 IssueRelation.delete_all
2237 IssueRelation.delete_all
2204
2238
2205 project = Project.generate!(:name => "testproject")
2239 project = Project.generate!(:name => "testproject")
2206
2240
2207 parentIssue1 = Issue.generate!(:project => project)
2241 parentIssue1 = Issue.generate!(:project => project)
2208 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2242 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2209 childIssue1_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2243 childIssue1_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2210
2244
2211 parentIssue2 = Issue.generate!(:project => project)
2245 parentIssue2 = Issue.generate!(:project => project)
2212 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2246 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2213 childIssue2_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2247 childIssue2_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2214
2248
2215
2249
2216 assert IssueRelation.create(:issue_from => parentIssue1,
2250 assert IssueRelation.create(:issue_from => parentIssue1,
2217 :issue_to => parentIssue2,
2251 :issue_to => parentIssue2,
2218 :relation_type => IssueRelation::TYPE_BLOCKS)
2252 :relation_type => IssueRelation::TYPE_BLOCKS)
2219
2253
2220 assert_equal [childIssue1_1.id, childIssue1_2.id, parentIssue2.id, childIssue2_1.id, childIssue2_2.id].sort,
2254 assert_equal [childIssue1_1.id, childIssue1_2.id, parentIssue2.id, childIssue2_1.id, childIssue2_2.id].sort,
2221 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
2255 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
2222 end
2256 end
2223
2257
2224 def test_all_dependent_issues_with_transitive_relation
2258 def test_all_dependent_issues_with_transitive_relation
2225 IssueRelation.delete_all
2259 IssueRelation.delete_all
2226
2260
2227 project = Project.generate!(:name => "testproject")
2261 project = Project.generate!(:name => "testproject")
2228
2262
2229 parentIssue1 = Issue.generate!(:project => project)
2263 parentIssue1 = Issue.generate!(:project => project)
2230 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2264 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2231
2265
2232 parentIssue2 = Issue.generate!(:project => project)
2266 parentIssue2 = Issue.generate!(:project => project)
2233 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2267 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2234
2268
2235 independentIssue = Issue.generate!(:project => project)
2269 independentIssue = Issue.generate!(:project => project)
2236
2270
2237 assert IssueRelation.create(:issue_from => parentIssue1,
2271 assert IssueRelation.create(:issue_from => parentIssue1,
2238 :issue_to => childIssue2_1,
2272 :issue_to => childIssue2_1,
2239 :relation_type => IssueRelation::TYPE_RELATES)
2273 :relation_type => IssueRelation::TYPE_RELATES)
2240
2274
2241 assert IssueRelation.create(:issue_from => childIssue2_1,
2275 assert IssueRelation.create(:issue_from => childIssue2_1,
2242 :issue_to => independentIssue,
2276 :issue_to => independentIssue,
2243 :relation_type => IssueRelation::TYPE_RELATES)
2277 :relation_type => IssueRelation::TYPE_RELATES)
2244
2278
2245 assert_equal [childIssue1_1.id, parentIssue2.id, childIssue2_1.id, independentIssue.id].sort,
2279 assert_equal [childIssue1_1.id, parentIssue2.id, childIssue2_1.id, independentIssue.id].sort,
2246 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
2280 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
2247 end
2281 end
2248
2282
2249 def test_all_dependent_issues_with_transitive_relation2
2283 def test_all_dependent_issues_with_transitive_relation2
2250 IssueRelation.delete_all
2284 IssueRelation.delete_all
2251
2285
2252 project = Project.generate!(:name => "testproject")
2286 project = Project.generate!(:name => "testproject")
2253
2287
2254 parentIssue1 = Issue.generate!(:project => project)
2288 parentIssue1 = Issue.generate!(:project => project)
2255 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2289 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2256
2290
2257 parentIssue2 = Issue.generate!(:project => project)
2291 parentIssue2 = Issue.generate!(:project => project)
2258 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2292 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2259
2293
2260 independentIssue = Issue.generate!(:project => project)
2294 independentIssue = Issue.generate!(:project => project)
2261
2295
2262 assert IssueRelation.create(:issue_from => parentIssue1,
2296 assert IssueRelation.create(:issue_from => parentIssue1,
2263 :issue_to => independentIssue,
2297 :issue_to => independentIssue,
2264 :relation_type => IssueRelation::TYPE_RELATES)
2298 :relation_type => IssueRelation::TYPE_RELATES)
2265
2299
2266 assert IssueRelation.create(:issue_from => independentIssue,
2300 assert IssueRelation.create(:issue_from => independentIssue,
2267 :issue_to => childIssue2_1,
2301 :issue_to => childIssue2_1,
2268 :relation_type => IssueRelation::TYPE_RELATES)
2302 :relation_type => IssueRelation::TYPE_RELATES)
2269
2303
2270 assert_equal [childIssue1_1.id, parentIssue2.id, childIssue2_1.id, independentIssue.id].sort,
2304 assert_equal [childIssue1_1.id, parentIssue2.id, childIssue2_1.id, independentIssue.id].sort,
2271 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
2305 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
2272
2306
2273 end
2307 end
2274
2308
2275 def test_all_dependent_issues_with_persistent_circular_dependency
2309 def test_all_dependent_issues_with_persistent_circular_dependency
2276 IssueRelation.delete_all
2310 IssueRelation.delete_all
2277 assert IssueRelation.create!(:issue_from => Issue.find(1),
2311 assert IssueRelation.create!(:issue_from => Issue.find(1),
2278 :issue_to => Issue.find(2),
2312 :issue_to => Issue.find(2),
2279 :relation_type => IssueRelation::TYPE_PRECEDES)
2313 :relation_type => IssueRelation::TYPE_PRECEDES)
2280 assert IssueRelation.create!(:issue_from => Issue.find(2),
2314 assert IssueRelation.create!(:issue_from => Issue.find(2),
2281 :issue_to => Issue.find(3),
2315 :issue_to => Issue.find(3),
2282 :relation_type => IssueRelation::TYPE_PRECEDES)
2316 :relation_type => IssueRelation::TYPE_PRECEDES)
2283
2317
2284 r = IssueRelation.create!(:issue_from => Issue.find(3),
2318 r = IssueRelation.create!(:issue_from => Issue.find(3),
2285 :issue_to => Issue.find(7),
2319 :issue_to => Issue.find(7),
2286 :relation_type => IssueRelation::TYPE_PRECEDES)
2320 :relation_type => IssueRelation::TYPE_PRECEDES)
2287 IssueRelation.where(["id = ?", r.id]).update_all("issue_to_id = 1")
2321 IssueRelation.where(["id = ?", r.id]).update_all("issue_to_id = 1")
2288
2322
2289 assert_equal [2, 3], Issue.find(1).all_dependent_issues.collect(&:id).sort
2323 assert_equal [2, 3], Issue.find(1).all_dependent_issues.collect(&:id).sort
2290 end
2324 end
2291
2325
2292 def test_all_dependent_issues_with_persistent_multiple_circular_dependencies
2326 def test_all_dependent_issues_with_persistent_multiple_circular_dependencies
2293 IssueRelation.delete_all
2327 IssueRelation.delete_all
2294 assert IssueRelation.create!(:issue_from => Issue.find(1),
2328 assert IssueRelation.create!(:issue_from => Issue.find(1),
2295 :issue_to => Issue.find(2),
2329 :issue_to => Issue.find(2),
2296 :relation_type => IssueRelation::TYPE_RELATES)
2330 :relation_type => IssueRelation::TYPE_RELATES)
2297 assert IssueRelation.create!(:issue_from => Issue.find(2),
2331 assert IssueRelation.create!(:issue_from => Issue.find(2),
2298 :issue_to => Issue.find(3),
2332 :issue_to => Issue.find(3),
2299 :relation_type => IssueRelation::TYPE_RELATES)
2333 :relation_type => IssueRelation::TYPE_RELATES)
2300 assert IssueRelation.create!(:issue_from => Issue.find(3),
2334 assert IssueRelation.create!(:issue_from => Issue.find(3),
2301 :issue_to => Issue.find(8),
2335 :issue_to => Issue.find(8),
2302 :relation_type => IssueRelation::TYPE_RELATES)
2336 :relation_type => IssueRelation::TYPE_RELATES)
2303
2337
2304 r = IssueRelation.create!(:issue_from => Issue.find(8),
2338 r = IssueRelation.create!(:issue_from => Issue.find(8),
2305 :issue_to => Issue.find(7),
2339 :issue_to => Issue.find(7),
2306 :relation_type => IssueRelation::TYPE_RELATES)
2340 :relation_type => IssueRelation::TYPE_RELATES)
2307 IssueRelation.where(["id = ?", r.id]).update_all("issue_to_id = 2")
2341 IssueRelation.where(["id = ?", r.id]).update_all("issue_to_id = 2")
2308
2342
2309 r = IssueRelation.create!(:issue_from => Issue.find(3),
2343 r = IssueRelation.create!(:issue_from => Issue.find(3),
2310 :issue_to => Issue.find(7),
2344 :issue_to => Issue.find(7),
2311 :relation_type => IssueRelation::TYPE_RELATES)
2345 :relation_type => IssueRelation::TYPE_RELATES)
2312 IssueRelation.where(["id = ?", r.id]).update_all("issue_to_id = 1")
2346 IssueRelation.where(["id = ?", r.id]).update_all("issue_to_id = 1")
2313
2347
2314 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
2348 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
2315 end
2349 end
2316
2350
2317 test "#done_ratio should use the issue_status according to Setting.issue_done_ratio" do
2351 test "#done_ratio should use the issue_status according to Setting.issue_done_ratio" do
2318 @issue = Issue.find(1)
2352 @issue = Issue.find(1)
2319 @issue_status = IssueStatus.find(1)
2353 @issue_status = IssueStatus.find(1)
2320 @issue_status.update_attribute(:default_done_ratio, 50)
2354 @issue_status.update_attribute(:default_done_ratio, 50)
2321 @issue2 = Issue.find(2)
2355 @issue2 = Issue.find(2)
2322 @issue_status2 = IssueStatus.find(2)
2356 @issue_status2 = IssueStatus.find(2)
2323 @issue_status2.update_attribute(:default_done_ratio, 0)
2357 @issue_status2.update_attribute(:default_done_ratio, 0)
2324
2358
2325 with_settings :issue_done_ratio => 'issue_field' do
2359 with_settings :issue_done_ratio => 'issue_field' do
2326 assert_equal 0, @issue.done_ratio
2360 assert_equal 0, @issue.done_ratio
2327 assert_equal 30, @issue2.done_ratio
2361 assert_equal 30, @issue2.done_ratio
2328 end
2362 end
2329
2363
2330 with_settings :issue_done_ratio => 'issue_status' do
2364 with_settings :issue_done_ratio => 'issue_status' do
2331 assert_equal 50, @issue.done_ratio
2365 assert_equal 50, @issue.done_ratio
2332 assert_equal 0, @issue2.done_ratio
2366 assert_equal 0, @issue2.done_ratio
2333 end
2367 end
2334 end
2368 end
2335
2369
2336 test "#update_done_ratio_from_issue_status should update done_ratio according to Setting.issue_done_ratio" do
2370 test "#update_done_ratio_from_issue_status should update done_ratio according to Setting.issue_done_ratio" do
2337 @issue = Issue.find(1)
2371 @issue = Issue.find(1)
2338 @issue_status = IssueStatus.find(1)
2372 @issue_status = IssueStatus.find(1)
2339 @issue_status.update_attribute(:default_done_ratio, 50)
2373 @issue_status.update_attribute(:default_done_ratio, 50)
2340 @issue2 = Issue.find(2)
2374 @issue2 = Issue.find(2)
2341 @issue_status2 = IssueStatus.find(2)
2375 @issue_status2 = IssueStatus.find(2)
2342 @issue_status2.update_attribute(:default_done_ratio, 0)
2376 @issue_status2.update_attribute(:default_done_ratio, 0)
2343
2377
2344 with_settings :issue_done_ratio => 'issue_field' do
2378 with_settings :issue_done_ratio => 'issue_field' do
2345 @issue.update_done_ratio_from_issue_status
2379 @issue.update_done_ratio_from_issue_status
2346 @issue2.update_done_ratio_from_issue_status
2380 @issue2.update_done_ratio_from_issue_status
2347
2381
2348 assert_equal 0, @issue.read_attribute(:done_ratio)
2382 assert_equal 0, @issue.read_attribute(:done_ratio)
2349 assert_equal 30, @issue2.read_attribute(:done_ratio)
2383 assert_equal 30, @issue2.read_attribute(:done_ratio)
2350 end
2384 end
2351
2385
2352 with_settings :issue_done_ratio => 'issue_status' do
2386 with_settings :issue_done_ratio => 'issue_status' do
2353 @issue.update_done_ratio_from_issue_status
2387 @issue.update_done_ratio_from_issue_status
2354 @issue2.update_done_ratio_from_issue_status
2388 @issue2.update_done_ratio_from_issue_status
2355
2389
2356 assert_equal 50, @issue.read_attribute(:done_ratio)
2390 assert_equal 50, @issue.read_attribute(:done_ratio)
2357 assert_equal 0, @issue2.read_attribute(:done_ratio)
2391 assert_equal 0, @issue2.read_attribute(:done_ratio)
2358 end
2392 end
2359 end
2393 end
2360
2394
2361 test "#by_tracker" do
2395 test "#by_tracker" do
2362 User.current = User.anonymous
2396 User.current = User.anonymous
2363 groups = Issue.by_tracker(Project.find(1))
2397 groups = Issue.by_tracker(Project.find(1))
2364 assert_equal 3, groups.count
2398 assert_equal 3, groups.count
2365 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2399 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2366 end
2400 end
2367
2401
2368 test "#by_version" do
2402 test "#by_version" do
2369 User.current = User.anonymous
2403 User.current = User.anonymous
2370 groups = Issue.by_version(Project.find(1))
2404 groups = Issue.by_version(Project.find(1))
2371 assert_equal 3, groups.count
2405 assert_equal 3, groups.count
2372 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2406 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2373 end
2407 end
2374
2408
2375 test "#by_priority" do
2409 test "#by_priority" do
2376 User.current = User.anonymous
2410 User.current = User.anonymous
2377 groups = Issue.by_priority(Project.find(1))
2411 groups = Issue.by_priority(Project.find(1))
2378 assert_equal 4, groups.count
2412 assert_equal 4, groups.count
2379 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2413 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2380 end
2414 end
2381
2415
2382 test "#by_category" do
2416 test "#by_category" do
2383 User.current = User.anonymous
2417 User.current = User.anonymous
2384 groups = Issue.by_category(Project.find(1))
2418 groups = Issue.by_category(Project.find(1))
2385 assert_equal 2, groups.count
2419 assert_equal 2, groups.count
2386 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2420 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2387 end
2421 end
2388
2422
2389 test "#by_assigned_to" do
2423 test "#by_assigned_to" do
2390 User.current = User.anonymous
2424 User.current = User.anonymous
2391 groups = Issue.by_assigned_to(Project.find(1))
2425 groups = Issue.by_assigned_to(Project.find(1))
2392 assert_equal 2, groups.count
2426 assert_equal 2, groups.count
2393 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2427 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2394 end
2428 end
2395
2429
2396 test "#by_author" do
2430 test "#by_author" do
2397 User.current = User.anonymous
2431 User.current = User.anonymous
2398 groups = Issue.by_author(Project.find(1))
2432 groups = Issue.by_author(Project.find(1))
2399 assert_equal 4, groups.count
2433 assert_equal 4, groups.count
2400 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2434 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2401 end
2435 end
2402
2436
2403 test "#by_subproject" do
2437 test "#by_subproject" do
2404 User.current = User.anonymous
2438 User.current = User.anonymous
2405 groups = Issue.by_subproject(Project.find(1))
2439 groups = Issue.by_subproject(Project.find(1))
2406 # Private descendant not visible
2440 # Private descendant not visible
2407 assert_equal 1, groups.count
2441 assert_equal 1, groups.count
2408 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2442 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2409 end
2443 end
2410
2444
2411 def test_recently_updated_scope
2445 def test_recently_updated_scope
2412 #should return the last updated issue
2446 #should return the last updated issue
2413 assert_equal Issue.reorder("updated_on DESC").first, Issue.recently_updated.limit(1).first
2447 assert_equal Issue.reorder("updated_on DESC").first, Issue.recently_updated.limit(1).first
2414 end
2448 end
2415
2449
2416 def test_on_active_projects_scope
2450 def test_on_active_projects_scope
2417 assert Project.find(2).archive
2451 assert Project.find(2).archive
2418
2452
2419 before = Issue.on_active_project.length
2453 before = Issue.on_active_project.length
2420 # test inclusion to results
2454 # test inclusion to results
2421 issue = Issue.generate!(:tracker => Project.find(2).trackers.first)
2455 issue = Issue.generate!(:tracker => Project.find(2).trackers.first)
2422 assert_equal before + 1, Issue.on_active_project.length
2456 assert_equal before + 1, Issue.on_active_project.length
2423
2457
2424 # Move to an archived project
2458 # Move to an archived project
2425 issue.project = Project.find(2)
2459 issue.project = Project.find(2)
2426 assert issue.save
2460 assert issue.save
2427 assert_equal before, Issue.on_active_project.length
2461 assert_equal before, Issue.on_active_project.length
2428 end
2462 end
2429
2463
2430 test "Issue#recipients should include project recipients" do
2464 test "Issue#recipients should include project recipients" do
2431 issue = Issue.generate!
2465 issue = Issue.generate!
2432 assert issue.project.recipients.present?
2466 assert issue.project.recipients.present?
2433 issue.project.recipients.each do |project_recipient|
2467 issue.project.recipients.each do |project_recipient|
2434 assert issue.recipients.include?(project_recipient)
2468 assert issue.recipients.include?(project_recipient)
2435 end
2469 end
2436 end
2470 end
2437
2471
2438 test "Issue#recipients should include the author if the author is active" do
2472 test "Issue#recipients should include the author if the author is active" do
2439 issue = Issue.generate!(:author => User.generate!)
2473 issue = Issue.generate!(:author => User.generate!)
2440 assert issue.author, "No author set for Issue"
2474 assert issue.author, "No author set for Issue"
2441 assert issue.recipients.include?(issue.author.mail)
2475 assert issue.recipients.include?(issue.author.mail)
2442 end
2476 end
2443
2477
2444 test "Issue#recipients should include the assigned to user if the assigned to user is active" do
2478 test "Issue#recipients should include the assigned to user if the assigned to user is active" do
2445 issue = Issue.generate!(:assigned_to => User.generate!)
2479 issue = Issue.generate!(:assigned_to => User.generate!)
2446 assert issue.assigned_to, "No assigned_to set for Issue"
2480 assert issue.assigned_to, "No assigned_to set for Issue"
2447 assert issue.recipients.include?(issue.assigned_to.mail)
2481 assert issue.recipients.include?(issue.assigned_to.mail)
2448 end
2482 end
2449
2483
2450 test "Issue#recipients should not include users who opt out of all email" do
2484 test "Issue#recipients should not include users who opt out of all email" do
2451 issue = Issue.generate!(:author => User.generate!)
2485 issue = Issue.generate!(:author => User.generate!)
2452 issue.author.update_attribute(:mail_notification, :none)
2486 issue.author.update_attribute(:mail_notification, :none)
2453 assert !issue.recipients.include?(issue.author.mail)
2487 assert !issue.recipients.include?(issue.author.mail)
2454 end
2488 end
2455
2489
2456 test "Issue#recipients should not include the issue author if they are only notified of assigned issues" do
2490 test "Issue#recipients should not include the issue author if they are only notified of assigned issues" do
2457 issue = Issue.generate!(:author => User.generate!)
2491 issue = Issue.generate!(:author => User.generate!)
2458 issue.author.update_attribute(:mail_notification, :only_assigned)
2492 issue.author.update_attribute(:mail_notification, :only_assigned)
2459 assert !issue.recipients.include?(issue.author.mail)
2493 assert !issue.recipients.include?(issue.author.mail)
2460 end
2494 end
2461
2495
2462 test "Issue#recipients should not include the assigned user if they are only notified of owned issues" do
2496 test "Issue#recipients should not include the assigned user if they are only notified of owned issues" do
2463 issue = Issue.generate!(:assigned_to => User.generate!)
2497 issue = Issue.generate!(:assigned_to => User.generate!)
2464 issue.assigned_to.update_attribute(:mail_notification, :only_owner)
2498 issue.assigned_to.update_attribute(:mail_notification, :only_owner)
2465 assert !issue.recipients.include?(issue.assigned_to.mail)
2499 assert !issue.recipients.include?(issue.assigned_to.mail)
2466 end
2500 end
2467
2501
2468 def test_last_journal_id_with_journals_should_return_the_journal_id
2502 def test_last_journal_id_with_journals_should_return_the_journal_id
2469 assert_equal 2, Issue.find(1).last_journal_id
2503 assert_equal 2, Issue.find(1).last_journal_id
2470 end
2504 end
2471
2505
2472 def test_last_journal_id_without_journals_should_return_nil
2506 def test_last_journal_id_without_journals_should_return_nil
2473 assert_nil Issue.find(3).last_journal_id
2507 assert_nil Issue.find(3).last_journal_id
2474 end
2508 end
2475
2509
2476 def test_journals_after_should_return_journals_with_greater_id
2510 def test_journals_after_should_return_journals_with_greater_id
2477 assert_equal [Journal.find(2)], Issue.find(1).journals_after('1')
2511 assert_equal [Journal.find(2)], Issue.find(1).journals_after('1')
2478 assert_equal [], Issue.find(1).journals_after('2')
2512 assert_equal [], Issue.find(1).journals_after('2')
2479 end
2513 end
2480
2514
2481 def test_journals_after_with_blank_arg_should_return_all_journals
2515 def test_journals_after_with_blank_arg_should_return_all_journals
2482 assert_equal [Journal.find(1), Journal.find(2)], Issue.find(1).journals_after('')
2516 assert_equal [Journal.find(1), Journal.find(2)], Issue.find(1).journals_after('')
2483 end
2517 end
2484
2518
2485 def test_css_classes_should_include_tracker
2519 def test_css_classes_should_include_tracker
2486 issue = Issue.new(:tracker => Tracker.find(2))
2520 issue = Issue.new(:tracker => Tracker.find(2))
2487 classes = issue.css_classes.split(' ')
2521 classes = issue.css_classes.split(' ')
2488 assert_include 'tracker-2', classes
2522 assert_include 'tracker-2', classes
2489 end
2523 end
2490
2524
2491 def test_css_classes_should_include_priority
2525 def test_css_classes_should_include_priority
2492 issue = Issue.new(:priority => IssuePriority.find(8))
2526 issue = Issue.new(:priority => IssuePriority.find(8))
2493 classes = issue.css_classes.split(' ')
2527 classes = issue.css_classes.split(' ')
2494 assert_include 'priority-8', classes
2528 assert_include 'priority-8', classes
2495 assert_include 'priority-highest', classes
2529 assert_include 'priority-highest', classes
2496 end
2530 end
2497
2531
2498 def test_css_classes_should_include_user_and_group_assignment
2532 def test_css_classes_should_include_user_and_group_assignment
2499 project = Project.first
2533 project = Project.first
2500 user = User.generate!
2534 user = User.generate!
2501 group = Group.generate!
2535 group = Group.generate!
2502 Member.create!(:principal => group, :project => project, :role_ids => [1, 2])
2536 Member.create!(:principal => group, :project => project, :role_ids => [1, 2])
2503 group.users << user
2537 group.users << user
2504 assert user.member_of?(project)
2538 assert user.member_of?(project)
2505 issue1 = Issue.generate(:assigned_to_id => group.id)
2539 issue1 = Issue.generate(:assigned_to_id => group.id)
2506 assert_include 'assigned-to-my-group', issue1.css_classes(user)
2540 assert_include 'assigned-to-my-group', issue1.css_classes(user)
2507 assert_not_include 'assigned-to-me', issue1.css_classes(user)
2541 assert_not_include 'assigned-to-me', issue1.css_classes(user)
2508 issue2 = Issue.generate(:assigned_to_id => user.id)
2542 issue2 = Issue.generate(:assigned_to_id => user.id)
2509 assert_not_include 'assigned-to-my-group', issue2.css_classes(user)
2543 assert_not_include 'assigned-to-my-group', issue2.css_classes(user)
2510 assert_include 'assigned-to-me', issue2.css_classes(user)
2544 assert_include 'assigned-to-me', issue2.css_classes(user)
2511 end
2545 end
2512
2546
2513 def test_save_attachments_with_hash_should_save_attachments_in_keys_order
2547 def test_save_attachments_with_hash_should_save_attachments_in_keys_order
2514 set_tmp_attachments_directory
2548 set_tmp_attachments_directory
2515 issue = Issue.generate!
2549 issue = Issue.generate!
2516 issue.save_attachments({
2550 issue.save_attachments({
2517 'p0' => {'file' => mock_file_with_options(:original_filename => 'upload')},
2551 'p0' => {'file' => mock_file_with_options(:original_filename => 'upload')},
2518 '3' => {'file' => mock_file_with_options(:original_filename => 'bar')},
2552 '3' => {'file' => mock_file_with_options(:original_filename => 'bar')},
2519 '1' => {'file' => mock_file_with_options(:original_filename => 'foo')}
2553 '1' => {'file' => mock_file_with_options(:original_filename => 'foo')}
2520 })
2554 })
2521 issue.attach_saved_attachments
2555 issue.attach_saved_attachments
2522
2556
2523 assert_equal 3, issue.reload.attachments.count
2557 assert_equal 3, issue.reload.attachments.count
2524 assert_equal %w(upload foo bar), issue.attachments.map(&:filename)
2558 assert_equal %w(upload foo bar), issue.attachments.map(&:filename)
2525 end
2559 end
2526
2560
2527 def test_closed_on_should_be_nil_when_creating_an_open_issue
2561 def test_closed_on_should_be_nil_when_creating_an_open_issue
2528 issue = Issue.generate!(:status_id => 1).reload
2562 issue = Issue.generate!(:status_id => 1).reload
2529 assert !issue.closed?
2563 assert !issue.closed?
2530 assert_nil issue.closed_on
2564 assert_nil issue.closed_on
2531 end
2565 end
2532
2566
2533 def test_closed_on_should_be_set_when_creating_a_closed_issue
2567 def test_closed_on_should_be_set_when_creating_a_closed_issue
2534 issue = Issue.generate!(:status_id => 5).reload
2568 issue = Issue.generate!(:status_id => 5).reload
2535 assert issue.closed?
2569 assert issue.closed?
2536 assert_not_nil issue.closed_on
2570 assert_not_nil issue.closed_on
2537 assert_equal issue.updated_on, issue.closed_on
2571 assert_equal issue.updated_on, issue.closed_on
2538 assert_equal issue.created_on, issue.closed_on
2572 assert_equal issue.created_on, issue.closed_on
2539 end
2573 end
2540
2574
2541 def test_closed_on_should_be_nil_when_updating_an_open_issue
2575 def test_closed_on_should_be_nil_when_updating_an_open_issue
2542 issue = Issue.find(1)
2576 issue = Issue.find(1)
2543 issue.subject = 'Not closed yet'
2577 issue.subject = 'Not closed yet'
2544 issue.save!
2578 issue.save!
2545 issue.reload
2579 issue.reload
2546 assert_nil issue.closed_on
2580 assert_nil issue.closed_on
2547 end
2581 end
2548
2582
2549 def test_closed_on_should_be_set_when_closing_an_open_issue
2583 def test_closed_on_should_be_set_when_closing_an_open_issue
2550 issue = Issue.find(1)
2584 issue = Issue.find(1)
2551 issue.subject = 'Now closed'
2585 issue.subject = 'Now closed'
2552 issue.status_id = 5
2586 issue.status_id = 5
2553 issue.save!
2587 issue.save!
2554 issue.reload
2588 issue.reload
2555 assert_not_nil issue.closed_on
2589 assert_not_nil issue.closed_on
2556 assert_equal issue.updated_on, issue.closed_on
2590 assert_equal issue.updated_on, issue.closed_on
2557 end
2591 end
2558
2592
2559 def test_closed_on_should_not_be_updated_when_updating_a_closed_issue
2593 def test_closed_on_should_not_be_updated_when_updating_a_closed_issue
2560 issue = Issue.open(false).first
2594 issue = Issue.open(false).first
2561 was_closed_on = issue.closed_on
2595 was_closed_on = issue.closed_on
2562 assert_not_nil was_closed_on
2596 assert_not_nil was_closed_on
2563 issue.subject = 'Updating a closed issue'
2597 issue.subject = 'Updating a closed issue'
2564 issue.save!
2598 issue.save!
2565 issue.reload
2599 issue.reload
2566 assert_equal was_closed_on, issue.closed_on
2600 assert_equal was_closed_on, issue.closed_on
2567 end
2601 end
2568
2602
2569 def test_closed_on_should_be_preserved_when_reopening_a_closed_issue
2603 def test_closed_on_should_be_preserved_when_reopening_a_closed_issue
2570 issue = Issue.open(false).first
2604 issue = Issue.open(false).first
2571 was_closed_on = issue.closed_on
2605 was_closed_on = issue.closed_on
2572 assert_not_nil was_closed_on
2606 assert_not_nil was_closed_on
2573 issue.subject = 'Reopening a closed issue'
2607 issue.subject = 'Reopening a closed issue'
2574 issue.status_id = 1
2608 issue.status_id = 1
2575 issue.save!
2609 issue.save!
2576 issue.reload
2610 issue.reload
2577 assert !issue.closed?
2611 assert !issue.closed?
2578 assert_equal was_closed_on, issue.closed_on
2612 assert_equal was_closed_on, issue.closed_on
2579 end
2613 end
2580
2614
2581 def test_status_was_should_return_nil_for_new_issue
2615 def test_status_was_should_return_nil_for_new_issue
2582 issue = Issue.new
2616 issue = Issue.new
2583 assert_nil issue.status_was
2617 assert_nil issue.status_was
2584 end
2618 end
2585
2619
2586 def test_status_was_should_return_status_before_change
2620 def test_status_was_should_return_status_before_change
2587 issue = Issue.find(1)
2621 issue = Issue.find(1)
2588 issue.status = IssueStatus.find(2)
2622 issue.status = IssueStatus.find(2)
2589 assert_equal IssueStatus.find(1), issue.status_was
2623 assert_equal IssueStatus.find(1), issue.status_was
2590 end
2624 end
2591
2625
2592 def test_status_was_should_return_status_before_change_with_status_id
2626 def test_status_was_should_return_status_before_change_with_status_id
2593 issue = Issue.find(1)
2627 issue = Issue.find(1)
2594 assert_equal IssueStatus.find(1), issue.status
2628 assert_equal IssueStatus.find(1), issue.status
2595 issue.status_id = 2
2629 issue.status_id = 2
2596 assert_equal IssueStatus.find(1), issue.status_was
2630 assert_equal IssueStatus.find(1), issue.status_was
2597 end
2631 end
2598
2632
2599 def test_status_was_should_be_reset_on_save
2633 def test_status_was_should_be_reset_on_save
2600 issue = Issue.find(1)
2634 issue = Issue.find(1)
2601 issue.status = IssueStatus.find(2)
2635 issue.status = IssueStatus.find(2)
2602 assert_equal IssueStatus.find(1), issue.status_was
2636 assert_equal IssueStatus.find(1), issue.status_was
2603 assert issue.save!
2637 assert issue.save!
2604 assert_equal IssueStatus.find(2), issue.status_was
2638 assert_equal IssueStatus.find(2), issue.status_was
2605 end
2639 end
2606
2640
2607 def test_closing_should_return_true_when_closing_an_issue
2641 def test_closing_should_return_true_when_closing_an_issue
2608 issue = Issue.find(1)
2642 issue = Issue.find(1)
2609 issue.status = IssueStatus.find(2)
2643 issue.status = IssueStatus.find(2)
2610 assert_equal false, issue.closing?
2644 assert_equal false, issue.closing?
2611 issue.status = IssueStatus.find(5)
2645 issue.status = IssueStatus.find(5)
2612 assert_equal true, issue.closing?
2646 assert_equal true, issue.closing?
2613 end
2647 end
2614
2648
2615 def test_closing_should_return_true_when_closing_an_issue_with_status_id
2649 def test_closing_should_return_true_when_closing_an_issue_with_status_id
2616 issue = Issue.find(1)
2650 issue = Issue.find(1)
2617 issue.status_id = 2
2651 issue.status_id = 2
2618 assert_equal false, issue.closing?
2652 assert_equal false, issue.closing?
2619 issue.status_id = 5
2653 issue.status_id = 5
2620 assert_equal true, issue.closing?
2654 assert_equal true, issue.closing?
2621 end
2655 end
2622
2656
2623 def test_closing_should_return_true_for_new_closed_issue
2657 def test_closing_should_return_true_for_new_closed_issue
2624 issue = Issue.new
2658 issue = Issue.new
2625 assert_equal false, issue.closing?
2659 assert_equal false, issue.closing?
2626 issue.status = IssueStatus.find(5)
2660 issue.status = IssueStatus.find(5)
2627 assert_equal true, issue.closing?
2661 assert_equal true, issue.closing?
2628 end
2662 end
2629
2663
2630 def test_closing_should_return_true_for_new_closed_issue_with_status_id
2664 def test_closing_should_return_true_for_new_closed_issue_with_status_id
2631 issue = Issue.new
2665 issue = Issue.new
2632 assert_equal false, issue.closing?
2666 assert_equal false, issue.closing?
2633 issue.status_id = 5
2667 issue.status_id = 5
2634 assert_equal true, issue.closing?
2668 assert_equal true, issue.closing?
2635 end
2669 end
2636
2670
2637 def test_closing_should_be_reset_after_save
2671 def test_closing_should_be_reset_after_save
2638 issue = Issue.find(1)
2672 issue = Issue.find(1)
2639 issue.status_id = 5
2673 issue.status_id = 5
2640 assert_equal true, issue.closing?
2674 assert_equal true, issue.closing?
2641 issue.save!
2675 issue.save!
2642 assert_equal false, issue.closing?
2676 assert_equal false, issue.closing?
2643 end
2677 end
2644
2678
2645 def test_reopening_should_return_true_when_reopening_an_issue
2679 def test_reopening_should_return_true_when_reopening_an_issue
2646 issue = Issue.find(8)
2680 issue = Issue.find(8)
2647 issue.status = IssueStatus.find(6)
2681 issue.status = IssueStatus.find(6)
2648 assert_equal false, issue.reopening?
2682 assert_equal false, issue.reopening?
2649 issue.status = IssueStatus.find(2)
2683 issue.status = IssueStatus.find(2)
2650 assert_equal true, issue.reopening?
2684 assert_equal true, issue.reopening?
2651 end
2685 end
2652
2686
2653 def test_reopening_should_return_true_when_reopening_an_issue_with_status_id
2687 def test_reopening_should_return_true_when_reopening_an_issue_with_status_id
2654 issue = Issue.find(8)
2688 issue = Issue.find(8)
2655 issue.status_id = 6
2689 issue.status_id = 6
2656 assert_equal false, issue.reopening?
2690 assert_equal false, issue.reopening?
2657 issue.status_id = 2
2691 issue.status_id = 2
2658 assert_equal true, issue.reopening?
2692 assert_equal true, issue.reopening?
2659 end
2693 end
2660
2694
2661 def test_reopening_should_return_false_for_new_open_issue
2695 def test_reopening_should_return_false_for_new_open_issue
2662 issue = Issue.new
2696 issue = Issue.new
2663 issue.status = IssueStatus.find(1)
2697 issue.status = IssueStatus.find(1)
2664 assert_equal false, issue.reopening?
2698 assert_equal false, issue.reopening?
2665 end
2699 end
2666
2700
2667 def test_reopening_should_be_reset_after_save
2701 def test_reopening_should_be_reset_after_save
2668 issue = Issue.find(8)
2702 issue = Issue.find(8)
2669 issue.status_id = 2
2703 issue.status_id = 2
2670 assert_equal true, issue.reopening?
2704 assert_equal true, issue.reopening?
2671 issue.save!
2705 issue.save!
2672 assert_equal false, issue.reopening?
2706 assert_equal false, issue.reopening?
2673 end
2707 end
2674
2708
2675 def test_default_status_without_tracker_should_be_nil
2709 def test_default_status_without_tracker_should_be_nil
2676 issue = Issue.new
2710 issue = Issue.new
2677 assert_nil issue.tracker
2711 assert_nil issue.tracker
2678 assert_nil issue.default_status
2712 assert_nil issue.default_status
2679 end
2713 end
2680
2714
2681 def test_default_status_should_be_tracker_default_status
2715 def test_default_status_should_be_tracker_default_status
2682 issue = Issue.new(:tracker_id => 1)
2716 issue = Issue.new(:tracker_id => 1)
2683 assert_not_nil issue.status
2717 assert_not_nil issue.status
2684 assert_equal issue.tracker.default_status, issue.default_status
2718 assert_equal issue.tracker.default_status, issue.default_status
2685 end
2719 end
2686
2720
2687 def test_initializing_with_tracker_should_set_default_status
2721 def test_initializing_with_tracker_should_set_default_status
2688 issue = Issue.new(:tracker => Tracker.find(1))
2722 issue = Issue.new(:tracker => Tracker.find(1))
2689 assert_not_nil issue.status
2723 assert_not_nil issue.status
2690 assert_equal issue.default_status, issue.status
2724 assert_equal issue.default_status, issue.status
2691 end
2725 end
2692
2726
2693 def test_initializing_with_tracker_id_should_set_default_status
2727 def test_initializing_with_tracker_id_should_set_default_status
2694 issue = Issue.new(:tracker_id => 1)
2728 issue = Issue.new(:tracker_id => 1)
2695 assert_not_nil issue.status
2729 assert_not_nil issue.status
2696 assert_equal issue.default_status, issue.status
2730 assert_equal issue.default_status, issue.status
2697 end
2731 end
2698
2732
2699 def test_setting_tracker_should_set_default_status
2733 def test_setting_tracker_should_set_default_status
2700 issue = Issue.new
2734 issue = Issue.new
2701 issue.tracker = Tracker.find(1)
2735 issue.tracker = Tracker.find(1)
2702 assert_not_nil issue.status
2736 assert_not_nil issue.status
2703 assert_equal issue.default_status, issue.status
2737 assert_equal issue.default_status, issue.status
2704 end
2738 end
2705
2739
2706 def test_changing_tracker_should_set_default_status_if_status_was_default
2740 def test_changing_tracker_should_set_default_status_if_status_was_default
2707 WorkflowTransition.delete_all
2741 WorkflowTransition.delete_all
2708 WorkflowTransition.create! :role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1
2742 WorkflowTransition.create! :role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1
2709 Tracker.find(2).update! :default_status_id => 2
2743 Tracker.find(2).update! :default_status_id => 2
2710
2744
2711 issue = Issue.new(:tracker_id => 1, :status_id => 1)
2745 issue = Issue.new(:tracker_id => 1, :status_id => 1)
2712 assert_equal IssueStatus.find(1), issue.status
2746 assert_equal IssueStatus.find(1), issue.status
2713 issue.tracker = Tracker.find(2)
2747 issue.tracker = Tracker.find(2)
2714 assert_equal IssueStatus.find(2), issue.status
2748 assert_equal IssueStatus.find(2), issue.status
2715 end
2749 end
2716
2750
2717 def test_changing_tracker_should_set_default_status_if_status_is_not_used_by_tracker
2751 def test_changing_tracker_should_set_default_status_if_status_is_not_used_by_tracker
2718 WorkflowTransition.delete_all
2752 WorkflowTransition.delete_all
2719 Tracker.find(2).update! :default_status_id => 2
2753 Tracker.find(2).update! :default_status_id => 2
2720
2754
2721 issue = Issue.new(:tracker_id => 1, :status_id => 3)
2755 issue = Issue.new(:tracker_id => 1, :status_id => 3)
2722 assert_equal IssueStatus.find(3), issue.status
2756 assert_equal IssueStatus.find(3), issue.status
2723 issue.tracker = Tracker.find(2)
2757 issue.tracker = Tracker.find(2)
2724 assert_equal IssueStatus.find(2), issue.status
2758 assert_equal IssueStatus.find(2), issue.status
2725 end
2759 end
2726
2760
2727 def test_changing_tracker_should_keep_status_if_status_was_not_default_and_is_used_by_tracker
2761 def test_changing_tracker_should_keep_status_if_status_was_not_default_and_is_used_by_tracker
2728 WorkflowTransition.delete_all
2762 WorkflowTransition.delete_all
2729 WorkflowTransition.create! :role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 3
2763 WorkflowTransition.create! :role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 3
2730 Tracker.find(2).update! :default_status_id => 2
2764 Tracker.find(2).update! :default_status_id => 2
2731
2765
2732 issue = Issue.new(:tracker_id => 1, :status_id => 3)
2766 issue = Issue.new(:tracker_id => 1, :status_id => 3)
2733 assert_equal IssueStatus.find(3), issue.status
2767 assert_equal IssueStatus.find(3), issue.status
2734 issue.tracker = Tracker.find(2)
2768 issue.tracker = Tracker.find(2)
2735 assert_equal IssueStatus.find(3), issue.status
2769 assert_equal IssueStatus.find(3), issue.status
2736 end
2770 end
2737
2771
2738 def test_assigned_to_was_with_a_group
2772 def test_assigned_to_was_with_a_group
2739 group = Group.find(10)
2773 group = Group.find(10)
2740
2774
2741 issue = Issue.generate!(:assigned_to => group)
2775 issue = Issue.generate!(:assigned_to => group)
2742 issue.reload.assigned_to = nil
2776 issue.reload.assigned_to = nil
2743 assert_equal group, issue.assigned_to_was
2777 assert_equal group, issue.assigned_to_was
2744 end
2778 end
2745 end
2779 end
General Comments 0
You need to be logged in to leave comments. Login now