##// END OF EJS Templates
Merged r15879 (#23969)....
Jean-Philippe Lang -
r15507:52b19aca3c4d
parent child
Show More

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

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