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

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

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