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