##// END OF EJS Templates
"Parent task is invalid" while editing child issues with restricted Issues Visibility (#12851)....
Jean-Philippe Lang -
r10998:f99535bba284
parent child
Show More
@@ -1,1399 +1,1399
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
2 # Copyright (C) 2006-2013 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
21
22 belongs_to :project
22 belongs_to :project
23 belongs_to :tracker
23 belongs_to :tracker
24 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
24 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
25 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
25 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
26 belongs_to :assigned_to, :class_name => 'Principal', :foreign_key => 'assigned_to_id'
26 belongs_to :assigned_to, :class_name => 'Principal', :foreign_key => 'assigned_to_id'
27 belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
27 belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
28 belongs_to :priority, :class_name => 'IssuePriority', :foreign_key => 'priority_id'
28 belongs_to :priority, :class_name => 'IssuePriority', :foreign_key => 'priority_id'
29 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
29 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
30
30
31 has_many :journals, :as => :journalized, :dependent => :destroy
31 has_many :journals, :as => :journalized, :dependent => :destroy
32 has_many :visible_journals,
32 has_many :visible_journals,
33 :class_name => 'Journal',
33 :class_name => 'Journal',
34 :as => :journalized,
34 :as => :journalized,
35 :conditions => Proc.new {
35 :conditions => Proc.new {
36 ["(#{Journal.table_name}.private_notes = ? OR (#{Project.allowed_to_condition(User.current, :view_private_notes)}))", false]
36 ["(#{Journal.table_name}.private_notes = ? OR (#{Project.allowed_to_condition(User.current, :view_private_notes)}))", false]
37 },
37 },
38 :readonly => true
38 :readonly => true
39
39
40 has_many :time_entries, :dependent => :delete_all
40 has_many :time_entries, :dependent => :delete_all
41 has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
41 has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
42
42
43 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
43 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
44 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
44 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
45
45
46 acts_as_nested_set :scope => 'root_id', :dependent => :destroy
46 acts_as_nested_set :scope => 'root_id', :dependent => :destroy
47 acts_as_attachable :after_add => :attachment_added, :after_remove => :attachment_removed
47 acts_as_attachable :after_add => :attachment_added, :after_remove => :attachment_removed
48 acts_as_customizable
48 acts_as_customizable
49 acts_as_watchable
49 acts_as_watchable
50 acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"],
50 acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"],
51 :include => [:project, :visible_journals],
51 :include => [:project, :visible_journals],
52 # sort by id so that limited eager loading doesn't break with postgresql
52 # sort by id so that limited eager loading doesn't break with postgresql
53 :order_column => "#{table_name}.id"
53 :order_column => "#{table_name}.id"
54 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"},
54 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"},
55 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}},
55 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}},
56 :type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') }
56 :type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') }
57
57
58 acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
58 acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
59 :author_key => :author_id
59 :author_key => :author_id
60
60
61 DONE_RATIO_OPTIONS = %w(issue_field issue_status)
61 DONE_RATIO_OPTIONS = %w(issue_field issue_status)
62
62
63 attr_reader :current_journal
63 attr_reader :current_journal
64 delegate :notes, :notes=, :private_notes, :private_notes=, :to => :current_journal, :allow_nil => true
64 delegate :notes, :notes=, :private_notes, :private_notes=, :to => :current_journal, :allow_nil => true
65
65
66 validates_presence_of :subject, :priority, :project, :tracker, :author, :status
66 validates_presence_of :subject, :priority, :project, :tracker, :author, :status
67
67
68 validates_length_of :subject, :maximum => 255
68 validates_length_of :subject, :maximum => 255
69 validates_inclusion_of :done_ratio, :in => 0..100
69 validates_inclusion_of :done_ratio, :in => 0..100
70 validates :estimated_hours, :numericality => {:greater_than_or_equal_to => 0, :allow_nil => true, :message => :invalid}
70 validates :estimated_hours, :numericality => {:greater_than_or_equal_to => 0, :allow_nil => true, :message => :invalid}
71 validates :start_date, :date => true
71 validates :start_date, :date => true
72 validates :due_date, :date => true
72 validates :due_date, :date => true
73 validate :validate_issue, :validate_required_fields
73 validate :validate_issue, :validate_required_fields
74
74
75 scope :visible, lambda {|*args|
75 scope :visible, lambda {|*args|
76 includes(:project).where(Issue.visible_condition(args.shift || User.current, *args))
76 includes(:project).where(Issue.visible_condition(args.shift || User.current, *args))
77 }
77 }
78
78
79 scope :open, lambda {|*args|
79 scope :open, lambda {|*args|
80 is_closed = args.size > 0 ? !args.first : false
80 is_closed = args.size > 0 ? !args.first : false
81 includes(:status).where("#{IssueStatus.table_name}.is_closed = ?", is_closed)
81 includes(:status).where("#{IssueStatus.table_name}.is_closed = ?", is_closed)
82 }
82 }
83
83
84 scope :recently_updated, lambda { order("#{Issue.table_name}.updated_on DESC") }
84 scope :recently_updated, lambda { order("#{Issue.table_name}.updated_on DESC") }
85 scope :on_active_project, lambda {
85 scope :on_active_project, lambda {
86 includes(:status, :project, :tracker).where("#{Project.table_name}.status = ?", Project::STATUS_ACTIVE)
86 includes(:status, :project, :tracker).where("#{Project.table_name}.status = ?", Project::STATUS_ACTIVE)
87 }
87 }
88 scope :fixed_version, lambda {|versions|
88 scope :fixed_version, lambda {|versions|
89 ids = [versions].flatten.compact.map {|v| v.is_a?(Version) ? v.id : v}
89 ids = [versions].flatten.compact.map {|v| v.is_a?(Version) ? v.id : v}
90 ids.any? ? where(:fixed_version_id => ids) : where('1=0')
90 ids.any? ? where(:fixed_version_id => ids) : where('1=0')
91 }
91 }
92
92
93 before_create :default_assign
93 before_create :default_assign
94 before_save :close_duplicates, :update_done_ratio_from_issue_status, :force_updated_on_change
94 before_save :close_duplicates, :update_done_ratio_from_issue_status, :force_updated_on_change
95 after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?}
95 after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?}
96 after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal
96 after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal
97 # Should be after_create but would be called before previous after_save callbacks
97 # Should be after_create but would be called before previous after_save callbacks
98 after_save :after_create_from_copy
98 after_save :after_create_from_copy
99 after_destroy :update_parent_attributes
99 after_destroy :update_parent_attributes
100
100
101 # Returns a SQL conditions string used to find all issues visible by the specified user
101 # Returns a SQL conditions string used to find all issues visible by the specified user
102 def self.visible_condition(user, options={})
102 def self.visible_condition(user, options={})
103 Project.allowed_to_condition(user, :view_issues, options) do |role, user|
103 Project.allowed_to_condition(user, :view_issues, options) do |role, user|
104 if user.logged?
104 if user.logged?
105 case role.issues_visibility
105 case role.issues_visibility
106 when 'all'
106 when 'all'
107 nil
107 nil
108 when 'default'
108 when 'default'
109 user_ids = [user.id] + user.groups.map(&:id)
109 user_ids = [user.id] + user.groups.map(&:id)
110 "(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
110 "(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
111 when 'own'
111 when 'own'
112 user_ids = [user.id] + user.groups.map(&:id)
112 user_ids = [user.id] + user.groups.map(&:id)
113 "(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
113 "(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
114 else
114 else
115 '1=0'
115 '1=0'
116 end
116 end
117 else
117 else
118 "(#{table_name}.is_private = #{connection.quoted_false})"
118 "(#{table_name}.is_private = #{connection.quoted_false})"
119 end
119 end
120 end
120 end
121 end
121 end
122
122
123 # Returns true if usr or current user is allowed to view the issue
123 # Returns true if usr or current user is allowed to view the issue
124 def visible?(usr=nil)
124 def visible?(usr=nil)
125 (usr || User.current).allowed_to?(:view_issues, self.project) do |role, user|
125 (usr || User.current).allowed_to?(:view_issues, self.project) do |role, user|
126 if user.logged?
126 if user.logged?
127 case role.issues_visibility
127 case role.issues_visibility
128 when 'all'
128 when 'all'
129 true
129 true
130 when 'default'
130 when 'default'
131 !self.is_private? || (self.author == user || user.is_or_belongs_to?(assigned_to))
131 !self.is_private? || (self.author == user || user.is_or_belongs_to?(assigned_to))
132 when 'own'
132 when 'own'
133 self.author == user || user.is_or_belongs_to?(assigned_to)
133 self.author == user || user.is_or_belongs_to?(assigned_to)
134 else
134 else
135 false
135 false
136 end
136 end
137 else
137 else
138 !self.is_private?
138 !self.is_private?
139 end
139 end
140 end
140 end
141 end
141 end
142
142
143 # Returns true if user or current user is allowed to edit or add a note to the issue
143 # Returns true if user or current user is allowed to edit or add a note to the issue
144 def editable?(user=User.current)
144 def editable?(user=User.current)
145 user.allowed_to?(:edit_issues, project) || user.allowed_to?(:add_issue_notes, project)
145 user.allowed_to?(:edit_issues, project) || user.allowed_to?(:add_issue_notes, project)
146 end
146 end
147
147
148 def initialize(attributes=nil, *args)
148 def initialize(attributes=nil, *args)
149 super
149 super
150 if new_record?
150 if new_record?
151 # set default values for new records only
151 # set default values for new records only
152 self.status ||= IssueStatus.default
152 self.status ||= IssueStatus.default
153 self.priority ||= IssuePriority.default
153 self.priority ||= IssuePriority.default
154 self.watcher_user_ids = []
154 self.watcher_user_ids = []
155 end
155 end
156 end
156 end
157
157
158 # AR#Persistence#destroy would raise and RecordNotFound exception
158 # AR#Persistence#destroy would raise and RecordNotFound exception
159 # if the issue was already deleted or updated (non matching lock_version).
159 # if the issue was already deleted or updated (non matching lock_version).
160 # This is a problem when bulk deleting issues or deleting a project
160 # This is a problem when bulk deleting issues or deleting a project
161 # (because an issue may already be deleted if its parent was deleted
161 # (because an issue may already be deleted if its parent was deleted
162 # first).
162 # first).
163 # The issue is reloaded by the nested_set before being deleted so
163 # The issue is reloaded by the nested_set before being deleted so
164 # the lock_version condition should not be an issue but we handle it.
164 # the lock_version condition should not be an issue but we handle it.
165 def destroy
165 def destroy
166 super
166 super
167 rescue ActiveRecord::RecordNotFound
167 rescue ActiveRecord::RecordNotFound
168 # Stale or already deleted
168 # Stale or already deleted
169 begin
169 begin
170 reload
170 reload
171 rescue ActiveRecord::RecordNotFound
171 rescue ActiveRecord::RecordNotFound
172 # The issue was actually already deleted
172 # The issue was actually already deleted
173 @destroyed = true
173 @destroyed = true
174 return freeze
174 return freeze
175 end
175 end
176 # The issue was stale, retry to destroy
176 # The issue was stale, retry to destroy
177 super
177 super
178 end
178 end
179
179
180 def reload(*args)
180 def reload(*args)
181 @workflow_rule_by_attribute = nil
181 @workflow_rule_by_attribute = nil
182 @assignable_versions = nil
182 @assignable_versions = nil
183 super
183 super
184 end
184 end
185
185
186 # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
186 # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
187 def available_custom_fields
187 def available_custom_fields
188 (project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields.all) : []
188 (project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields.all) : []
189 end
189 end
190
190
191 # Copies attributes from another issue, arg can be an id or an Issue
191 # Copies attributes from another issue, arg can be an id or an Issue
192 def copy_from(arg, options={})
192 def copy_from(arg, options={})
193 issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg)
193 issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg)
194 self.attributes = issue.attributes.dup.except("id", "root_id", "parent_id", "lft", "rgt", "created_on", "updated_on")
194 self.attributes = issue.attributes.dup.except("id", "root_id", "parent_id", "lft", "rgt", "created_on", "updated_on")
195 self.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
195 self.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
196 self.status = issue.status
196 self.status = issue.status
197 self.author = User.current
197 self.author = User.current
198 unless options[:attachments] == false
198 unless options[:attachments] == false
199 self.attachments = issue.attachments.map do |attachement|
199 self.attachments = issue.attachments.map do |attachement|
200 attachement.copy(:container => self)
200 attachement.copy(:container => self)
201 end
201 end
202 end
202 end
203 @copied_from = issue
203 @copied_from = issue
204 @copy_options = options
204 @copy_options = options
205 self
205 self
206 end
206 end
207
207
208 # Returns an unsaved copy of the issue
208 # Returns an unsaved copy of the issue
209 def copy(attributes=nil, copy_options={})
209 def copy(attributes=nil, copy_options={})
210 copy = self.class.new.copy_from(self, copy_options)
210 copy = self.class.new.copy_from(self, copy_options)
211 copy.attributes = attributes if attributes
211 copy.attributes = attributes if attributes
212 copy
212 copy
213 end
213 end
214
214
215 # Returns true if the issue is a copy
215 # Returns true if the issue is a copy
216 def copy?
216 def copy?
217 @copied_from.present?
217 @copied_from.present?
218 end
218 end
219
219
220 # Moves/copies an issue to a new project and tracker
220 # Moves/copies an issue to a new project and tracker
221 # Returns the moved/copied issue on success, false on failure
221 # Returns the moved/copied issue on success, false on failure
222 def move_to_project(new_project, new_tracker=nil, options={})
222 def move_to_project(new_project, new_tracker=nil, options={})
223 ActiveSupport::Deprecation.warn "Issue#move_to_project is deprecated, use #project= instead."
223 ActiveSupport::Deprecation.warn "Issue#move_to_project is deprecated, use #project= instead."
224
224
225 if options[:copy]
225 if options[:copy]
226 issue = self.copy
226 issue = self.copy
227 else
227 else
228 issue = self
228 issue = self
229 end
229 end
230
230
231 issue.init_journal(User.current, options[:notes])
231 issue.init_journal(User.current, options[:notes])
232
232
233 # Preserve previous behaviour
233 # Preserve previous behaviour
234 # #move_to_project doesn't change tracker automatically
234 # #move_to_project doesn't change tracker automatically
235 issue.send :project=, new_project, true
235 issue.send :project=, new_project, true
236 if new_tracker
236 if new_tracker
237 issue.tracker = new_tracker
237 issue.tracker = new_tracker
238 end
238 end
239 # Allow bulk setting of attributes on the issue
239 # Allow bulk setting of attributes on the issue
240 if options[:attributes]
240 if options[:attributes]
241 issue.attributes = options[:attributes]
241 issue.attributes = options[:attributes]
242 end
242 end
243
243
244 issue.save ? issue : false
244 issue.save ? issue : false
245 end
245 end
246
246
247 def status_id=(sid)
247 def status_id=(sid)
248 self.status = nil
248 self.status = nil
249 result = write_attribute(:status_id, sid)
249 result = write_attribute(:status_id, sid)
250 @workflow_rule_by_attribute = nil
250 @workflow_rule_by_attribute = nil
251 result
251 result
252 end
252 end
253
253
254 def priority_id=(pid)
254 def priority_id=(pid)
255 self.priority = nil
255 self.priority = nil
256 write_attribute(:priority_id, pid)
256 write_attribute(:priority_id, pid)
257 end
257 end
258
258
259 def category_id=(cid)
259 def category_id=(cid)
260 self.category = nil
260 self.category = nil
261 write_attribute(:category_id, cid)
261 write_attribute(:category_id, cid)
262 end
262 end
263
263
264 def fixed_version_id=(vid)
264 def fixed_version_id=(vid)
265 self.fixed_version = nil
265 self.fixed_version = nil
266 write_attribute(:fixed_version_id, vid)
266 write_attribute(:fixed_version_id, vid)
267 end
267 end
268
268
269 def tracker_id=(tid)
269 def tracker_id=(tid)
270 self.tracker = nil
270 self.tracker = nil
271 result = write_attribute(:tracker_id, tid)
271 result = write_attribute(:tracker_id, tid)
272 @custom_field_values = nil
272 @custom_field_values = nil
273 @workflow_rule_by_attribute = nil
273 @workflow_rule_by_attribute = nil
274 result
274 result
275 end
275 end
276
276
277 def project_id=(project_id)
277 def project_id=(project_id)
278 if project_id.to_s != self.project_id.to_s
278 if project_id.to_s != self.project_id.to_s
279 self.project = (project_id.present? ? Project.find_by_id(project_id) : nil)
279 self.project = (project_id.present? ? Project.find_by_id(project_id) : nil)
280 end
280 end
281 end
281 end
282
282
283 def project=(project, keep_tracker=false)
283 def project=(project, keep_tracker=false)
284 project_was = self.project
284 project_was = self.project
285 write_attribute(:project_id, project ? project.id : nil)
285 write_attribute(:project_id, project ? project.id : nil)
286 association_instance_set('project', project)
286 association_instance_set('project', project)
287 if project_was && project && project_was != project
287 if project_was && project && project_was != project
288 @assignable_versions = nil
288 @assignable_versions = nil
289
289
290 unless keep_tracker || project.trackers.include?(tracker)
290 unless keep_tracker || project.trackers.include?(tracker)
291 self.tracker = project.trackers.first
291 self.tracker = project.trackers.first
292 end
292 end
293 # Reassign to the category with same name if any
293 # Reassign to the category with same name if any
294 if category
294 if category
295 self.category = project.issue_categories.find_by_name(category.name)
295 self.category = project.issue_categories.find_by_name(category.name)
296 end
296 end
297 # Keep the fixed_version if it's still valid in the new_project
297 # Keep the fixed_version if it's still valid in the new_project
298 if fixed_version && fixed_version.project != project && !project.shared_versions.include?(fixed_version)
298 if fixed_version && fixed_version.project != project && !project.shared_versions.include?(fixed_version)
299 self.fixed_version = nil
299 self.fixed_version = nil
300 end
300 end
301 # Clear the parent task if it's no longer valid
301 # Clear the parent task if it's no longer valid
302 unless valid_parent_project?
302 unless valid_parent_project?
303 self.parent_issue_id = nil
303 self.parent_issue_id = nil
304 end
304 end
305 @custom_field_values = nil
305 @custom_field_values = nil
306 end
306 end
307 end
307 end
308
308
309 def description=(arg)
309 def description=(arg)
310 if arg.is_a?(String)
310 if arg.is_a?(String)
311 arg = arg.gsub(/(\r\n|\n|\r)/, "\r\n")
311 arg = arg.gsub(/(\r\n|\n|\r)/, "\r\n")
312 end
312 end
313 write_attribute(:description, arg)
313 write_attribute(:description, arg)
314 end
314 end
315
315
316 # Overrides assign_attributes so that project and tracker get assigned first
316 # Overrides assign_attributes so that project and tracker get assigned first
317 def assign_attributes_with_project_and_tracker_first(new_attributes, *args)
317 def assign_attributes_with_project_and_tracker_first(new_attributes, *args)
318 return if new_attributes.nil?
318 return if new_attributes.nil?
319 attrs = new_attributes.dup
319 attrs = new_attributes.dup
320 attrs.stringify_keys!
320 attrs.stringify_keys!
321
321
322 %w(project project_id tracker tracker_id).each do |attr|
322 %w(project project_id tracker tracker_id).each do |attr|
323 if attrs.has_key?(attr)
323 if attrs.has_key?(attr)
324 send "#{attr}=", attrs.delete(attr)
324 send "#{attr}=", attrs.delete(attr)
325 end
325 end
326 end
326 end
327 send :assign_attributes_without_project_and_tracker_first, attrs, *args
327 send :assign_attributes_without_project_and_tracker_first, attrs, *args
328 end
328 end
329 # Do not redefine alias chain on reload (see #4838)
329 # Do not redefine alias chain on reload (see #4838)
330 alias_method_chain(:assign_attributes, :project_and_tracker_first) unless method_defined?(:assign_attributes_without_project_and_tracker_first)
330 alias_method_chain(:assign_attributes, :project_and_tracker_first) unless method_defined?(:assign_attributes_without_project_and_tracker_first)
331
331
332 def estimated_hours=(h)
332 def estimated_hours=(h)
333 write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
333 write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
334 end
334 end
335
335
336 safe_attributes 'project_id',
336 safe_attributes 'project_id',
337 :if => lambda {|issue, user|
337 :if => lambda {|issue, user|
338 if issue.new_record?
338 if issue.new_record?
339 issue.copy?
339 issue.copy?
340 elsif user.allowed_to?(:move_issues, issue.project)
340 elsif user.allowed_to?(:move_issues, issue.project)
341 projects = Issue.allowed_target_projects_on_move(user)
341 projects = Issue.allowed_target_projects_on_move(user)
342 projects.include?(issue.project) && projects.size > 1
342 projects.include?(issue.project) && projects.size > 1
343 end
343 end
344 }
344 }
345
345
346 safe_attributes 'tracker_id',
346 safe_attributes 'tracker_id',
347 'status_id',
347 'status_id',
348 'category_id',
348 'category_id',
349 'assigned_to_id',
349 'assigned_to_id',
350 'priority_id',
350 'priority_id',
351 'fixed_version_id',
351 'fixed_version_id',
352 'subject',
352 'subject',
353 'description',
353 'description',
354 'start_date',
354 'start_date',
355 'due_date',
355 'due_date',
356 'done_ratio',
356 'done_ratio',
357 'estimated_hours',
357 'estimated_hours',
358 'custom_field_values',
358 'custom_field_values',
359 'custom_fields',
359 'custom_fields',
360 'lock_version',
360 'lock_version',
361 'notes',
361 'notes',
362 :if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) }
362 :if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) }
363
363
364 safe_attributes 'status_id',
364 safe_attributes 'status_id',
365 'assigned_to_id',
365 'assigned_to_id',
366 'fixed_version_id',
366 'fixed_version_id',
367 'done_ratio',
367 'done_ratio',
368 'lock_version',
368 'lock_version',
369 'notes',
369 'notes',
370 :if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? }
370 :if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? }
371
371
372 safe_attributes 'notes',
372 safe_attributes 'notes',
373 :if => lambda {|issue, user| user.allowed_to?(:add_issue_notes, issue.project)}
373 :if => lambda {|issue, user| user.allowed_to?(:add_issue_notes, issue.project)}
374
374
375 safe_attributes 'private_notes',
375 safe_attributes 'private_notes',
376 :if => lambda {|issue, user| !issue.new_record? && user.allowed_to?(:set_notes_private, issue.project)}
376 :if => lambda {|issue, user| !issue.new_record? && user.allowed_to?(:set_notes_private, issue.project)}
377
377
378 safe_attributes 'watcher_user_ids',
378 safe_attributes 'watcher_user_ids',
379 :if => lambda {|issue, user| issue.new_record? && user.allowed_to?(:add_issue_watchers, issue.project)}
379 :if => lambda {|issue, user| issue.new_record? && user.allowed_to?(:add_issue_watchers, issue.project)}
380
380
381 safe_attributes 'is_private',
381 safe_attributes 'is_private',
382 :if => lambda {|issue, user|
382 :if => lambda {|issue, user|
383 user.allowed_to?(:set_issues_private, issue.project) ||
383 user.allowed_to?(:set_issues_private, issue.project) ||
384 (issue.author == user && user.allowed_to?(:set_own_issues_private, issue.project))
384 (issue.author == user && user.allowed_to?(:set_own_issues_private, issue.project))
385 }
385 }
386
386
387 safe_attributes 'parent_issue_id',
387 safe_attributes 'parent_issue_id',
388 :if => lambda {|issue, user| (issue.new_record? || user.allowed_to?(:edit_issues, issue.project)) &&
388 :if => lambda {|issue, user| (issue.new_record? || user.allowed_to?(:edit_issues, issue.project)) &&
389 user.allowed_to?(:manage_subtasks, issue.project)}
389 user.allowed_to?(:manage_subtasks, issue.project)}
390
390
391 def safe_attribute_names(user=nil)
391 def safe_attribute_names(user=nil)
392 names = super
392 names = super
393 names -= disabled_core_fields
393 names -= disabled_core_fields
394 names -= read_only_attribute_names(user)
394 names -= read_only_attribute_names(user)
395 names
395 names
396 end
396 end
397
397
398 # Safely sets attributes
398 # Safely sets attributes
399 # Should be called from controllers instead of #attributes=
399 # Should be called from controllers instead of #attributes=
400 # attr_accessible is too rough because we still want things like
400 # attr_accessible is too rough because we still want things like
401 # Issue.new(:project => foo) to work
401 # Issue.new(:project => foo) to work
402 def safe_attributes=(attrs, user=User.current)
402 def safe_attributes=(attrs, user=User.current)
403 return unless attrs.is_a?(Hash)
403 return unless attrs.is_a?(Hash)
404
404
405 attrs = attrs.dup
405 attrs = attrs.dup
406
406
407 # Project and Tracker must be set before since new_statuses_allowed_to depends on it.
407 # Project and Tracker must be set before since new_statuses_allowed_to depends on it.
408 if (p = attrs.delete('project_id')) && safe_attribute?('project_id')
408 if (p = attrs.delete('project_id')) && safe_attribute?('project_id')
409 if allowed_target_projects(user).collect(&:id).include?(p.to_i)
409 if allowed_target_projects(user).collect(&:id).include?(p.to_i)
410 self.project_id = p
410 self.project_id = p
411 end
411 end
412 end
412 end
413
413
414 if (t = attrs.delete('tracker_id')) && safe_attribute?('tracker_id')
414 if (t = attrs.delete('tracker_id')) && safe_attribute?('tracker_id')
415 self.tracker_id = t
415 self.tracker_id = t
416 end
416 end
417
417
418 if (s = attrs.delete('status_id')) && safe_attribute?('status_id')
418 if (s = attrs.delete('status_id')) && safe_attribute?('status_id')
419 if new_statuses_allowed_to(user).collect(&:id).include?(s.to_i)
419 if new_statuses_allowed_to(user).collect(&:id).include?(s.to_i)
420 self.status_id = s
420 self.status_id = s
421 end
421 end
422 end
422 end
423
423
424 attrs = delete_unsafe_attributes(attrs, user)
424 attrs = delete_unsafe_attributes(attrs, user)
425 return if attrs.empty?
425 return if attrs.empty?
426
426
427 unless leaf?
427 unless leaf?
428 attrs.reject! {|k,v| %w(priority_id done_ratio start_date due_date estimated_hours).include?(k)}
428 attrs.reject! {|k,v| %w(priority_id done_ratio start_date due_date estimated_hours).include?(k)}
429 end
429 end
430
430
431 if attrs['parent_issue_id'].present?
431 if attrs['parent_issue_id'].present?
432 s = attrs['parent_issue_id'].to_s
432 s = attrs['parent_issue_id'].to_s
433 unless (m = s.match(%r{\A#?(\d+)\z})) && Issue.visible(user).exists?(m[1])
433 unless (m = s.match(%r{\A#?(\d+)\z})) && (m[1] == parent_id.to_s || Issue.visible(user).exists?(m[1]))
434 @invalid_parent_issue_id = attrs.delete('parent_issue_id')
434 @invalid_parent_issue_id = attrs.delete('parent_issue_id')
435 end
435 end
436 end
436 end
437
437
438 if attrs['custom_field_values'].present?
438 if attrs['custom_field_values'].present?
439 attrs['custom_field_values'] = attrs['custom_field_values'].reject {|k, v| read_only_attribute_names(user).include? k.to_s}
439 attrs['custom_field_values'] = attrs['custom_field_values'].reject {|k, v| read_only_attribute_names(user).include? k.to_s}
440 end
440 end
441
441
442 if attrs['custom_fields'].present?
442 if attrs['custom_fields'].present?
443 attrs['custom_fields'] = attrs['custom_fields'].reject {|c| read_only_attribute_names(user).include? c['id'].to_s}
443 attrs['custom_fields'] = attrs['custom_fields'].reject {|c| read_only_attribute_names(user).include? c['id'].to_s}
444 end
444 end
445
445
446 # mass-assignment security bypass
446 # mass-assignment security bypass
447 assign_attributes attrs, :without_protection => true
447 assign_attributes attrs, :without_protection => true
448 end
448 end
449
449
450 def disabled_core_fields
450 def disabled_core_fields
451 tracker ? tracker.disabled_core_fields : []
451 tracker ? tracker.disabled_core_fields : []
452 end
452 end
453
453
454 # Returns the custom_field_values that can be edited by the given user
454 # Returns the custom_field_values that can be edited by the given user
455 def editable_custom_field_values(user=nil)
455 def editable_custom_field_values(user=nil)
456 custom_field_values.reject do |value|
456 custom_field_values.reject do |value|
457 read_only_attribute_names(user).include?(value.custom_field_id.to_s)
457 read_only_attribute_names(user).include?(value.custom_field_id.to_s)
458 end
458 end
459 end
459 end
460
460
461 # Returns the names of attributes that are read-only for user or the current user
461 # Returns the names of attributes that are read-only for user or the current user
462 # For users with multiple roles, the read-only fields are the intersection of
462 # For users with multiple roles, the read-only fields are the intersection of
463 # read-only fields of each role
463 # read-only fields of each role
464 # The result is an array of strings where sustom fields are represented with their ids
464 # The result is an array of strings where sustom fields are represented with their ids
465 #
465 #
466 # Examples:
466 # Examples:
467 # issue.read_only_attribute_names # => ['due_date', '2']
467 # issue.read_only_attribute_names # => ['due_date', '2']
468 # issue.read_only_attribute_names(user) # => []
468 # issue.read_only_attribute_names(user) # => []
469 def read_only_attribute_names(user=nil)
469 def read_only_attribute_names(user=nil)
470 workflow_rule_by_attribute(user).reject {|attr, rule| rule != 'readonly'}.keys
470 workflow_rule_by_attribute(user).reject {|attr, rule| rule != 'readonly'}.keys
471 end
471 end
472
472
473 # Returns the names of required attributes for user or the current user
473 # Returns the names of required attributes for user or the current user
474 # For users with multiple roles, the required fields are the intersection of
474 # For users with multiple roles, the required fields are the intersection of
475 # required fields of each role
475 # required fields of each role
476 # The result is an array of strings where sustom fields are represented with their ids
476 # The result is an array of strings where sustom fields are represented with their ids
477 #
477 #
478 # Examples:
478 # Examples:
479 # issue.required_attribute_names # => ['due_date', '2']
479 # issue.required_attribute_names # => ['due_date', '2']
480 # issue.required_attribute_names(user) # => []
480 # issue.required_attribute_names(user) # => []
481 def required_attribute_names(user=nil)
481 def required_attribute_names(user=nil)
482 workflow_rule_by_attribute(user).reject {|attr, rule| rule != 'required'}.keys
482 workflow_rule_by_attribute(user).reject {|attr, rule| rule != 'required'}.keys
483 end
483 end
484
484
485 # Returns true if the attribute is required for user
485 # Returns true if the attribute is required for user
486 def required_attribute?(name, user=nil)
486 def required_attribute?(name, user=nil)
487 required_attribute_names(user).include?(name.to_s)
487 required_attribute_names(user).include?(name.to_s)
488 end
488 end
489
489
490 # Returns a hash of the workflow rule by attribute for the given user
490 # Returns a hash of the workflow rule by attribute for the given user
491 #
491 #
492 # Examples:
492 # Examples:
493 # issue.workflow_rule_by_attribute # => {'due_date' => 'required', 'start_date' => 'readonly'}
493 # issue.workflow_rule_by_attribute # => {'due_date' => 'required', 'start_date' => 'readonly'}
494 def workflow_rule_by_attribute(user=nil)
494 def workflow_rule_by_attribute(user=nil)
495 return @workflow_rule_by_attribute if @workflow_rule_by_attribute && user.nil?
495 return @workflow_rule_by_attribute if @workflow_rule_by_attribute && user.nil?
496
496
497 user_real = user || User.current
497 user_real = user || User.current
498 roles = user_real.admin ? Role.all : user_real.roles_for_project(project)
498 roles = user_real.admin ? Role.all : user_real.roles_for_project(project)
499 return {} if roles.empty?
499 return {} if roles.empty?
500
500
501 result = {}
501 result = {}
502 workflow_permissions = WorkflowPermission.where(:tracker_id => tracker_id, :old_status_id => status_id, :role_id => roles.map(&:id)).all
502 workflow_permissions = WorkflowPermission.where(:tracker_id => tracker_id, :old_status_id => status_id, :role_id => roles.map(&:id)).all
503 if workflow_permissions.any?
503 if workflow_permissions.any?
504 workflow_rules = workflow_permissions.inject({}) do |h, wp|
504 workflow_rules = workflow_permissions.inject({}) do |h, wp|
505 h[wp.field_name] ||= []
505 h[wp.field_name] ||= []
506 h[wp.field_name] << wp.rule
506 h[wp.field_name] << wp.rule
507 h
507 h
508 end
508 end
509 workflow_rules.each do |attr, rules|
509 workflow_rules.each do |attr, rules|
510 next if rules.size < roles.size
510 next if rules.size < roles.size
511 uniq_rules = rules.uniq
511 uniq_rules = rules.uniq
512 if uniq_rules.size == 1
512 if uniq_rules.size == 1
513 result[attr] = uniq_rules.first
513 result[attr] = uniq_rules.first
514 else
514 else
515 result[attr] = 'required'
515 result[attr] = 'required'
516 end
516 end
517 end
517 end
518 end
518 end
519 @workflow_rule_by_attribute = result if user.nil?
519 @workflow_rule_by_attribute = result if user.nil?
520 result
520 result
521 end
521 end
522 private :workflow_rule_by_attribute
522 private :workflow_rule_by_attribute
523
523
524 def done_ratio
524 def done_ratio
525 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
525 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
526 status.default_done_ratio
526 status.default_done_ratio
527 else
527 else
528 read_attribute(:done_ratio)
528 read_attribute(:done_ratio)
529 end
529 end
530 end
530 end
531
531
532 def self.use_status_for_done_ratio?
532 def self.use_status_for_done_ratio?
533 Setting.issue_done_ratio == 'issue_status'
533 Setting.issue_done_ratio == 'issue_status'
534 end
534 end
535
535
536 def self.use_field_for_done_ratio?
536 def self.use_field_for_done_ratio?
537 Setting.issue_done_ratio == 'issue_field'
537 Setting.issue_done_ratio == 'issue_field'
538 end
538 end
539
539
540 def validate_issue
540 def validate_issue
541 if due_date && start_date && due_date < start_date
541 if due_date && start_date && due_date < start_date
542 errors.add :due_date, :greater_than_start_date
542 errors.add :due_date, :greater_than_start_date
543 end
543 end
544
544
545 if start_date && soonest_start && start_date < soonest_start
545 if start_date && soonest_start && start_date < soonest_start
546 errors.add :start_date, :invalid
546 errors.add :start_date, :invalid
547 end
547 end
548
548
549 if fixed_version
549 if fixed_version
550 if !assignable_versions.include?(fixed_version)
550 if !assignable_versions.include?(fixed_version)
551 errors.add :fixed_version_id, :inclusion
551 errors.add :fixed_version_id, :inclusion
552 elsif reopened? && fixed_version.closed?
552 elsif reopened? && fixed_version.closed?
553 errors.add :base, I18n.t(:error_can_not_reopen_issue_on_closed_version)
553 errors.add :base, I18n.t(:error_can_not_reopen_issue_on_closed_version)
554 end
554 end
555 end
555 end
556
556
557 # Checks that the issue can not be added/moved to a disabled tracker
557 # Checks that the issue can not be added/moved to a disabled tracker
558 if project && (tracker_id_changed? || project_id_changed?)
558 if project && (tracker_id_changed? || project_id_changed?)
559 unless project.trackers.include?(tracker)
559 unless project.trackers.include?(tracker)
560 errors.add :tracker_id, :inclusion
560 errors.add :tracker_id, :inclusion
561 end
561 end
562 end
562 end
563
563
564 # Checks parent issue assignment
564 # Checks parent issue assignment
565 if @invalid_parent_issue_id.present?
565 if @invalid_parent_issue_id.present?
566 errors.add :parent_issue_id, :invalid
566 errors.add :parent_issue_id, :invalid
567 elsif @parent_issue
567 elsif @parent_issue
568 if !valid_parent_project?(@parent_issue)
568 if !valid_parent_project?(@parent_issue)
569 errors.add :parent_issue_id, :invalid
569 errors.add :parent_issue_id, :invalid
570 elsif !new_record?
570 elsif !new_record?
571 # moving an existing issue
571 # moving an existing issue
572 if @parent_issue.root_id != root_id
572 if @parent_issue.root_id != root_id
573 # we can always move to another tree
573 # we can always move to another tree
574 elsif move_possible?(@parent_issue)
574 elsif move_possible?(@parent_issue)
575 # move accepted inside tree
575 # move accepted inside tree
576 else
576 else
577 errors.add :parent_issue_id, :invalid
577 errors.add :parent_issue_id, :invalid
578 end
578 end
579 end
579 end
580 end
580 end
581 end
581 end
582
582
583 # Validates the issue against additional workflow requirements
583 # Validates the issue against additional workflow requirements
584 def validate_required_fields
584 def validate_required_fields
585 user = new_record? ? author : current_journal.try(:user)
585 user = new_record? ? author : current_journal.try(:user)
586
586
587 required_attribute_names(user).each do |attribute|
587 required_attribute_names(user).each do |attribute|
588 if attribute =~ /^\d+$/
588 if attribute =~ /^\d+$/
589 attribute = attribute.to_i
589 attribute = attribute.to_i
590 v = custom_field_values.detect {|v| v.custom_field_id == attribute }
590 v = custom_field_values.detect {|v| v.custom_field_id == attribute }
591 if v && v.value.blank?
591 if v && v.value.blank?
592 errors.add :base, v.custom_field.name + ' ' + l('activerecord.errors.messages.blank')
592 errors.add :base, v.custom_field.name + ' ' + l('activerecord.errors.messages.blank')
593 end
593 end
594 else
594 else
595 if respond_to?(attribute) && send(attribute).blank?
595 if respond_to?(attribute) && send(attribute).blank?
596 errors.add attribute, :blank
596 errors.add attribute, :blank
597 end
597 end
598 end
598 end
599 end
599 end
600 end
600 end
601
601
602 # Set the done_ratio using the status if that setting is set. This will keep the done_ratios
602 # Set the done_ratio using the status if that setting is set. This will keep the done_ratios
603 # even if the user turns off the setting later
603 # even if the user turns off the setting later
604 def update_done_ratio_from_issue_status
604 def update_done_ratio_from_issue_status
605 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
605 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
606 self.done_ratio = status.default_done_ratio
606 self.done_ratio = status.default_done_ratio
607 end
607 end
608 end
608 end
609
609
610 def init_journal(user, notes = "")
610 def init_journal(user, notes = "")
611 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
611 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
612 if new_record?
612 if new_record?
613 @current_journal.notify = false
613 @current_journal.notify = false
614 else
614 else
615 @attributes_before_change = attributes.dup
615 @attributes_before_change = attributes.dup
616 @custom_values_before_change = {}
616 @custom_values_before_change = {}
617 self.custom_field_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
617 self.custom_field_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
618 end
618 end
619 @current_journal
619 @current_journal
620 end
620 end
621
621
622 # Returns the id of the last journal or nil
622 # Returns the id of the last journal or nil
623 def last_journal_id
623 def last_journal_id
624 if new_record?
624 if new_record?
625 nil
625 nil
626 else
626 else
627 journals.maximum(:id)
627 journals.maximum(:id)
628 end
628 end
629 end
629 end
630
630
631 # Returns a scope for journals that have an id greater than journal_id
631 # Returns a scope for journals that have an id greater than journal_id
632 def journals_after(journal_id)
632 def journals_after(journal_id)
633 scope = journals.reorder("#{Journal.table_name}.id ASC")
633 scope = journals.reorder("#{Journal.table_name}.id ASC")
634 if journal_id.present?
634 if journal_id.present?
635 scope = scope.where("#{Journal.table_name}.id > ?", journal_id.to_i)
635 scope = scope.where("#{Journal.table_name}.id > ?", journal_id.to_i)
636 end
636 end
637 scope
637 scope
638 end
638 end
639
639
640 # Return true if the issue is closed, otherwise false
640 # Return true if the issue is closed, otherwise false
641 def closed?
641 def closed?
642 self.status.is_closed?
642 self.status.is_closed?
643 end
643 end
644
644
645 # Return true if the issue is being reopened
645 # Return true if the issue is being reopened
646 def reopened?
646 def reopened?
647 if !new_record? && status_id_changed?
647 if !new_record? && status_id_changed?
648 status_was = IssueStatus.find_by_id(status_id_was)
648 status_was = IssueStatus.find_by_id(status_id_was)
649 status_new = IssueStatus.find_by_id(status_id)
649 status_new = IssueStatus.find_by_id(status_id)
650 if status_was && status_new && status_was.is_closed? && !status_new.is_closed?
650 if status_was && status_new && status_was.is_closed? && !status_new.is_closed?
651 return true
651 return true
652 end
652 end
653 end
653 end
654 false
654 false
655 end
655 end
656
656
657 # Return true if the issue is being closed
657 # Return true if the issue is being closed
658 def closing?
658 def closing?
659 if !new_record? && status_id_changed?
659 if !new_record? && status_id_changed?
660 status_was = IssueStatus.find_by_id(status_id_was)
660 status_was = IssueStatus.find_by_id(status_id_was)
661 status_new = IssueStatus.find_by_id(status_id)
661 status_new = IssueStatus.find_by_id(status_id)
662 if status_was && status_new && !status_was.is_closed? && status_new.is_closed?
662 if status_was && status_new && !status_was.is_closed? && status_new.is_closed?
663 return true
663 return true
664 end
664 end
665 end
665 end
666 false
666 false
667 end
667 end
668
668
669 # Returns true if the issue is overdue
669 # Returns true if the issue is overdue
670 def overdue?
670 def overdue?
671 !due_date.nil? && (due_date < Date.today) && !status.is_closed?
671 !due_date.nil? && (due_date < Date.today) && !status.is_closed?
672 end
672 end
673
673
674 # Is the amount of work done less than it should for the due date
674 # Is the amount of work done less than it should for the due date
675 def behind_schedule?
675 def behind_schedule?
676 return false if start_date.nil? || due_date.nil?
676 return false if start_date.nil? || due_date.nil?
677 done_date = start_date + ((due_date - start_date+1)* done_ratio/100).floor
677 done_date = start_date + ((due_date - start_date+1)* done_ratio/100).floor
678 return done_date <= Date.today
678 return done_date <= Date.today
679 end
679 end
680
680
681 # Does this issue have children?
681 # Does this issue have children?
682 def children?
682 def children?
683 !leaf?
683 !leaf?
684 end
684 end
685
685
686 # Users the issue can be assigned to
686 # Users the issue can be assigned to
687 def assignable_users
687 def assignable_users
688 users = project.assignable_users
688 users = project.assignable_users
689 users << author if author
689 users << author if author
690 users << assigned_to if assigned_to
690 users << assigned_to if assigned_to
691 users.uniq.sort
691 users.uniq.sort
692 end
692 end
693
693
694 # Versions that the issue can be assigned to
694 # Versions that the issue can be assigned to
695 def assignable_versions
695 def assignable_versions
696 return @assignable_versions if @assignable_versions
696 return @assignable_versions if @assignable_versions
697
697
698 versions = project.shared_versions.open.all
698 versions = project.shared_versions.open.all
699 if fixed_version
699 if fixed_version
700 if fixed_version_id_changed?
700 if fixed_version_id_changed?
701 # nothing to do
701 # nothing to do
702 elsif project_id_changed?
702 elsif project_id_changed?
703 if project.shared_versions.include?(fixed_version)
703 if project.shared_versions.include?(fixed_version)
704 versions << fixed_version
704 versions << fixed_version
705 end
705 end
706 else
706 else
707 versions << fixed_version
707 versions << fixed_version
708 end
708 end
709 end
709 end
710 @assignable_versions = versions.uniq.sort
710 @assignable_versions = versions.uniq.sort
711 end
711 end
712
712
713 # Returns true if this issue is blocked by another issue that is still open
713 # Returns true if this issue is blocked by another issue that is still open
714 def blocked?
714 def blocked?
715 !relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil?
715 !relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil?
716 end
716 end
717
717
718 # Returns an array of statuses that user is able to apply
718 # Returns an array of statuses that user is able to apply
719 def new_statuses_allowed_to(user=User.current, include_default=false)
719 def new_statuses_allowed_to(user=User.current, include_default=false)
720 if new_record? && @copied_from
720 if new_record? && @copied_from
721 [IssueStatus.default, @copied_from.status].compact.uniq.sort
721 [IssueStatus.default, @copied_from.status].compact.uniq.sort
722 else
722 else
723 initial_status = nil
723 initial_status = nil
724 if new_record?
724 if new_record?
725 initial_status = IssueStatus.default
725 initial_status = IssueStatus.default
726 elsif status_id_was
726 elsif status_id_was
727 initial_status = IssueStatus.find_by_id(status_id_was)
727 initial_status = IssueStatus.find_by_id(status_id_was)
728 end
728 end
729 initial_status ||= status
729 initial_status ||= status
730
730
731 statuses = initial_status.find_new_statuses_allowed_to(
731 statuses = initial_status.find_new_statuses_allowed_to(
732 user.admin ? Role.all : user.roles_for_project(project),
732 user.admin ? Role.all : user.roles_for_project(project),
733 tracker,
733 tracker,
734 author == user,
734 author == user,
735 assigned_to_id_changed? ? assigned_to_id_was == user.id : assigned_to_id == user.id
735 assigned_to_id_changed? ? assigned_to_id_was == user.id : assigned_to_id == user.id
736 )
736 )
737 statuses << initial_status unless statuses.empty?
737 statuses << initial_status unless statuses.empty?
738 statuses << IssueStatus.default if include_default
738 statuses << IssueStatus.default if include_default
739 statuses = statuses.compact.uniq.sort
739 statuses = statuses.compact.uniq.sort
740 blocked? ? statuses.reject {|s| s.is_closed?} : statuses
740 blocked? ? statuses.reject {|s| s.is_closed?} : statuses
741 end
741 end
742 end
742 end
743
743
744 def assigned_to_was
744 def assigned_to_was
745 if assigned_to_id_changed? && assigned_to_id_was.present?
745 if assigned_to_id_changed? && assigned_to_id_was.present?
746 @assigned_to_was ||= User.find_by_id(assigned_to_id_was)
746 @assigned_to_was ||= User.find_by_id(assigned_to_id_was)
747 end
747 end
748 end
748 end
749
749
750 # Returns the users that should be notified
750 # Returns the users that should be notified
751 def notified_users
751 def notified_users
752 notified = []
752 notified = []
753 # Author and assignee are always notified unless they have been
753 # Author and assignee are always notified unless they have been
754 # locked or don't want to be notified
754 # locked or don't want to be notified
755 notified << author if author
755 notified << author if author
756 if assigned_to
756 if assigned_to
757 notified += (assigned_to.is_a?(Group) ? assigned_to.users : [assigned_to])
757 notified += (assigned_to.is_a?(Group) ? assigned_to.users : [assigned_to])
758 end
758 end
759 if assigned_to_was
759 if assigned_to_was
760 notified += (assigned_to_was.is_a?(Group) ? assigned_to_was.users : [assigned_to_was])
760 notified += (assigned_to_was.is_a?(Group) ? assigned_to_was.users : [assigned_to_was])
761 end
761 end
762 notified = notified.select {|u| u.active? && u.notify_about?(self)}
762 notified = notified.select {|u| u.active? && u.notify_about?(self)}
763
763
764 notified += project.notified_users
764 notified += project.notified_users
765 notified.uniq!
765 notified.uniq!
766 # Remove users that can not view the issue
766 # Remove users that can not view the issue
767 notified.reject! {|user| !visible?(user)}
767 notified.reject! {|user| !visible?(user)}
768 notified
768 notified
769 end
769 end
770
770
771 # Returns the email addresses that should be notified
771 # Returns the email addresses that should be notified
772 def recipients
772 def recipients
773 notified_users.collect(&:mail)
773 notified_users.collect(&:mail)
774 end
774 end
775
775
776 # Returns the number of hours spent on this issue
776 # Returns the number of hours spent on this issue
777 def spent_hours
777 def spent_hours
778 @spent_hours ||= time_entries.sum(:hours) || 0
778 @spent_hours ||= time_entries.sum(:hours) || 0
779 end
779 end
780
780
781 # Returns the total number of hours spent on this issue and its descendants
781 # Returns the total number of hours spent on this issue and its descendants
782 #
782 #
783 # Example:
783 # Example:
784 # spent_hours => 0.0
784 # spent_hours => 0.0
785 # spent_hours => 50.2
785 # spent_hours => 50.2
786 def total_spent_hours
786 def total_spent_hours
787 @total_spent_hours ||= self_and_descendants.sum("#{TimeEntry.table_name}.hours",
787 @total_spent_hours ||= self_and_descendants.sum("#{TimeEntry.table_name}.hours",
788 :joins => "LEFT JOIN #{TimeEntry.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id").to_f || 0.0
788 :joins => "LEFT JOIN #{TimeEntry.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id").to_f || 0.0
789 end
789 end
790
790
791 def relations
791 def relations
792 @relations ||= IssueRelation::Relations.new(self, (relations_from + relations_to).sort)
792 @relations ||= IssueRelation::Relations.new(self, (relations_from + relations_to).sort)
793 end
793 end
794
794
795 # Preloads relations for a collection of issues
795 # Preloads relations for a collection of issues
796 def self.load_relations(issues)
796 def self.load_relations(issues)
797 if issues.any?
797 if issues.any?
798 relations = IssueRelation.all(:conditions => ["issue_from_id IN (:ids) OR issue_to_id IN (:ids)", {:ids => issues.map(&:id)}])
798 relations = IssueRelation.all(:conditions => ["issue_from_id IN (:ids) OR issue_to_id IN (:ids)", {:ids => issues.map(&:id)}])
799 issues.each do |issue|
799 issues.each do |issue|
800 issue.instance_variable_set "@relations", relations.select {|r| r.issue_from_id == issue.id || r.issue_to_id == issue.id}
800 issue.instance_variable_set "@relations", relations.select {|r| r.issue_from_id == issue.id || r.issue_to_id == issue.id}
801 end
801 end
802 end
802 end
803 end
803 end
804
804
805 # Preloads visible spent time for a collection of issues
805 # Preloads visible spent time for a collection of issues
806 def self.load_visible_spent_hours(issues, user=User.current)
806 def self.load_visible_spent_hours(issues, user=User.current)
807 if issues.any?
807 if issues.any?
808 hours_by_issue_id = TimeEntry.visible(user).sum(:hours, :group => :issue_id)
808 hours_by_issue_id = TimeEntry.visible(user).sum(:hours, :group => :issue_id)
809 issues.each do |issue|
809 issues.each do |issue|
810 issue.instance_variable_set "@spent_hours", (hours_by_issue_id[issue.id] || 0)
810 issue.instance_variable_set "@spent_hours", (hours_by_issue_id[issue.id] || 0)
811 end
811 end
812 end
812 end
813 end
813 end
814
814
815 # Preloads visible relations for a collection of issues
815 # Preloads visible relations for a collection of issues
816 def self.load_visible_relations(issues, user=User.current)
816 def self.load_visible_relations(issues, user=User.current)
817 if issues.any?
817 if issues.any?
818 issue_ids = issues.map(&:id)
818 issue_ids = issues.map(&:id)
819 # Relations with issue_from in given issues and visible issue_to
819 # Relations with issue_from in given issues and visible issue_to
820 relations_from = IssueRelation.includes(:issue_to => [:status, :project]).where(visible_condition(user)).where(:issue_from_id => issue_ids).all
820 relations_from = IssueRelation.includes(:issue_to => [:status, :project]).where(visible_condition(user)).where(:issue_from_id => issue_ids).all
821 # Relations with issue_to in given issues and visible issue_from
821 # Relations with issue_to in given issues and visible issue_from
822 relations_to = IssueRelation.includes(:issue_from => [:status, :project]).where(visible_condition(user)).where(:issue_to_id => issue_ids).all
822 relations_to = IssueRelation.includes(:issue_from => [:status, :project]).where(visible_condition(user)).where(:issue_to_id => issue_ids).all
823
823
824 issues.each do |issue|
824 issues.each do |issue|
825 relations =
825 relations =
826 relations_from.select {|relation| relation.issue_from_id == issue.id} +
826 relations_from.select {|relation| relation.issue_from_id == issue.id} +
827 relations_to.select {|relation| relation.issue_to_id == issue.id}
827 relations_to.select {|relation| relation.issue_to_id == issue.id}
828
828
829 issue.instance_variable_set "@relations", IssueRelation::Relations.new(issue, relations.sort)
829 issue.instance_variable_set "@relations", IssueRelation::Relations.new(issue, relations.sort)
830 end
830 end
831 end
831 end
832 end
832 end
833
833
834 # Finds an issue relation given its id.
834 # Finds an issue relation given its id.
835 def find_relation(relation_id)
835 def find_relation(relation_id)
836 IssueRelation.find(relation_id, :conditions => ["issue_to_id = ? OR issue_from_id = ?", id, id])
836 IssueRelation.find(relation_id, :conditions => ["issue_to_id = ? OR issue_from_id = ?", id, id])
837 end
837 end
838
838
839 def all_dependent_issues(except=[])
839 def all_dependent_issues(except=[])
840 except << self
840 except << self
841 dependencies = []
841 dependencies = []
842 relations_from.each do |relation|
842 relations_from.each do |relation|
843 if relation.issue_to && !except.include?(relation.issue_to)
843 if relation.issue_to && !except.include?(relation.issue_to)
844 dependencies << relation.issue_to
844 dependencies << relation.issue_to
845 dependencies += relation.issue_to.all_dependent_issues(except)
845 dependencies += relation.issue_to.all_dependent_issues(except)
846 end
846 end
847 end
847 end
848 dependencies
848 dependencies
849 end
849 end
850
850
851 # Returns an array of issues that duplicate this one
851 # Returns an array of issues that duplicate this one
852 def duplicates
852 def duplicates
853 relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from}
853 relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from}
854 end
854 end
855
855
856 # Returns the due date or the target due date if any
856 # Returns the due date or the target due date if any
857 # Used on gantt chart
857 # Used on gantt chart
858 def due_before
858 def due_before
859 due_date || (fixed_version ? fixed_version.effective_date : nil)
859 due_date || (fixed_version ? fixed_version.effective_date : nil)
860 end
860 end
861
861
862 # Returns the time scheduled for this issue.
862 # Returns the time scheduled for this issue.
863 #
863 #
864 # Example:
864 # Example:
865 # Start Date: 2/26/09, End Date: 3/04/09
865 # Start Date: 2/26/09, End Date: 3/04/09
866 # duration => 6
866 # duration => 6
867 def duration
867 def duration
868 (start_date && due_date) ? due_date - start_date : 0
868 (start_date && due_date) ? due_date - start_date : 0
869 end
869 end
870
870
871 # Returns the duration in working days
871 # Returns the duration in working days
872 def working_duration
872 def working_duration
873 (start_date && due_date) ? working_days(start_date, due_date) : 0
873 (start_date && due_date) ? working_days(start_date, due_date) : 0
874 end
874 end
875
875
876 def soonest_start(reload=false)
876 def soonest_start(reload=false)
877 @soonest_start = nil if reload
877 @soonest_start = nil if reload
878 @soonest_start ||= (
878 @soonest_start ||= (
879 relations_to(reload).collect{|relation| relation.successor_soonest_start} +
879 relations_to(reload).collect{|relation| relation.successor_soonest_start} +
880 ancestors.collect(&:soonest_start)
880 ancestors.collect(&:soonest_start)
881 ).compact.max
881 ).compact.max
882 end
882 end
883
883
884 # Sets start_date on the given date or the next working day
884 # Sets start_date on the given date or the next working day
885 # and changes due_date to keep the same working duration.
885 # and changes due_date to keep the same working duration.
886 def reschedule_on(date)
886 def reschedule_on(date)
887 wd = working_duration
887 wd = working_duration
888 date = next_working_date(date)
888 date = next_working_date(date)
889 self.start_date = date
889 self.start_date = date
890 self.due_date = add_working_days(date, wd)
890 self.due_date = add_working_days(date, wd)
891 end
891 end
892
892
893 # Reschedules the issue on the given date or the next working day and saves the record.
893 # Reschedules the issue on the given date or the next working day and saves the record.
894 # If the issue is a parent task, this is done by rescheduling its subtasks.
894 # If the issue is a parent task, this is done by rescheduling its subtasks.
895 def reschedule_on!(date)
895 def reschedule_on!(date)
896 return if date.nil?
896 return if date.nil?
897 if leaf?
897 if leaf?
898 if start_date.nil? || start_date != date
898 if start_date.nil? || start_date != date
899 if start_date && start_date > date
899 if start_date && start_date > date
900 # Issue can not be moved earlier than its soonest start date
900 # Issue can not be moved earlier than its soonest start date
901 date = [soonest_start(true), date].compact.max
901 date = [soonest_start(true), date].compact.max
902 end
902 end
903 reschedule_on(date)
903 reschedule_on(date)
904 begin
904 begin
905 save
905 save
906 rescue ActiveRecord::StaleObjectError
906 rescue ActiveRecord::StaleObjectError
907 reload
907 reload
908 reschedule_on(date)
908 reschedule_on(date)
909 save
909 save
910 end
910 end
911 end
911 end
912 else
912 else
913 leaves.each do |leaf|
913 leaves.each do |leaf|
914 if leaf.start_date
914 if leaf.start_date
915 # Only move subtask if it starts at the same date as the parent
915 # Only move subtask if it starts at the same date as the parent
916 # or if it starts before the given date
916 # or if it starts before the given date
917 if start_date == leaf.start_date || date > leaf.start_date
917 if start_date == leaf.start_date || date > leaf.start_date
918 leaf.reschedule_on!(date)
918 leaf.reschedule_on!(date)
919 end
919 end
920 else
920 else
921 leaf.reschedule_on!(date)
921 leaf.reschedule_on!(date)
922 end
922 end
923 end
923 end
924 end
924 end
925 end
925 end
926
926
927 def <=>(issue)
927 def <=>(issue)
928 if issue.nil?
928 if issue.nil?
929 -1
929 -1
930 elsif root_id != issue.root_id
930 elsif root_id != issue.root_id
931 (root_id || 0) <=> (issue.root_id || 0)
931 (root_id || 0) <=> (issue.root_id || 0)
932 else
932 else
933 (lft || 0) <=> (issue.lft || 0)
933 (lft || 0) <=> (issue.lft || 0)
934 end
934 end
935 end
935 end
936
936
937 def to_s
937 def to_s
938 "#{tracker} ##{id}: #{subject}"
938 "#{tracker} ##{id}: #{subject}"
939 end
939 end
940
940
941 # Returns a string of css classes that apply to the issue
941 # Returns a string of css classes that apply to the issue
942 def css_classes
942 def css_classes
943 s = "issue status-#{status_id} #{priority.try(:css_classes)}"
943 s = "issue status-#{status_id} #{priority.try(:css_classes)}"
944 s << ' closed' if closed?
944 s << ' closed' if closed?
945 s << ' overdue' if overdue?
945 s << ' overdue' if overdue?
946 s << ' child' if child?
946 s << ' child' if child?
947 s << ' parent' unless leaf?
947 s << ' parent' unless leaf?
948 s << ' private' if is_private?
948 s << ' private' if is_private?
949 s << ' created-by-me' if User.current.logged? && author_id == User.current.id
949 s << ' created-by-me' if User.current.logged? && author_id == User.current.id
950 s << ' assigned-to-me' if User.current.logged? && assigned_to_id == User.current.id
950 s << ' assigned-to-me' if User.current.logged? && assigned_to_id == User.current.id
951 s
951 s
952 end
952 end
953
953
954 # Saves an issue and a time_entry from the parameters
954 # Saves an issue and a time_entry from the parameters
955 def save_issue_with_child_records(params, existing_time_entry=nil)
955 def save_issue_with_child_records(params, existing_time_entry=nil)
956 Issue.transaction do
956 Issue.transaction do
957 if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, project)
957 if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, project)
958 @time_entry = existing_time_entry || TimeEntry.new
958 @time_entry = existing_time_entry || TimeEntry.new
959 @time_entry.project = project
959 @time_entry.project = project
960 @time_entry.issue = self
960 @time_entry.issue = self
961 @time_entry.user = User.current
961 @time_entry.user = User.current
962 @time_entry.spent_on = User.current.today
962 @time_entry.spent_on = User.current.today
963 @time_entry.attributes = params[:time_entry]
963 @time_entry.attributes = params[:time_entry]
964 self.time_entries << @time_entry
964 self.time_entries << @time_entry
965 end
965 end
966
966
967 # TODO: Rename hook
967 # TODO: Rename hook
968 Redmine::Hook.call_hook(:controller_issues_edit_before_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
968 Redmine::Hook.call_hook(:controller_issues_edit_before_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
969 if save
969 if save
970 # TODO: Rename hook
970 # TODO: Rename hook
971 Redmine::Hook.call_hook(:controller_issues_edit_after_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
971 Redmine::Hook.call_hook(:controller_issues_edit_after_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
972 else
972 else
973 raise ActiveRecord::Rollback
973 raise ActiveRecord::Rollback
974 end
974 end
975 end
975 end
976 end
976 end
977
977
978 # Unassigns issues from +version+ if it's no longer shared with issue's project
978 # Unassigns issues from +version+ if it's no longer shared with issue's project
979 def self.update_versions_from_sharing_change(version)
979 def self.update_versions_from_sharing_change(version)
980 # Update issues assigned to the version
980 # Update issues assigned to the version
981 update_versions(["#{Issue.table_name}.fixed_version_id = ?", version.id])
981 update_versions(["#{Issue.table_name}.fixed_version_id = ?", version.id])
982 end
982 end
983
983
984 # Unassigns issues from versions that are no longer shared
984 # Unassigns issues from versions that are no longer shared
985 # after +project+ was moved
985 # after +project+ was moved
986 def self.update_versions_from_hierarchy_change(project)
986 def self.update_versions_from_hierarchy_change(project)
987 moved_project_ids = project.self_and_descendants.reload.collect(&:id)
987 moved_project_ids = project.self_and_descendants.reload.collect(&:id)
988 # Update issues of the moved projects and issues assigned to a version of a moved project
988 # Update issues of the moved projects and issues assigned to a version of a moved project
989 Issue.update_versions(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids])
989 Issue.update_versions(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids])
990 end
990 end
991
991
992 def parent_issue_id=(arg)
992 def parent_issue_id=(arg)
993 s = arg.to_s.strip.presence
993 s = arg.to_s.strip.presence
994 if s && (m = s.match(%r{\A#?(\d+)\z})) && (@parent_issue = Issue.find_by_id(m[1]))
994 if s && (m = s.match(%r{\A#?(\d+)\z})) && (@parent_issue = Issue.find_by_id(m[1]))
995 @parent_issue.id
995 @parent_issue.id
996 else
996 else
997 @parent_issue = nil
997 @parent_issue = nil
998 @invalid_parent_issue_id = arg
998 @invalid_parent_issue_id = arg
999 end
999 end
1000 end
1000 end
1001
1001
1002 def parent_issue_id
1002 def parent_issue_id
1003 if @invalid_parent_issue_id
1003 if @invalid_parent_issue_id
1004 @invalid_parent_issue_id
1004 @invalid_parent_issue_id
1005 elsif instance_variable_defined? :@parent_issue
1005 elsif instance_variable_defined? :@parent_issue
1006 @parent_issue.nil? ? nil : @parent_issue.id
1006 @parent_issue.nil? ? nil : @parent_issue.id
1007 else
1007 else
1008 parent_id
1008 parent_id
1009 end
1009 end
1010 end
1010 end
1011
1011
1012 # Returns true if issue's project is a valid
1012 # Returns true if issue's project is a valid
1013 # parent issue project
1013 # parent issue project
1014 def valid_parent_project?(issue=parent)
1014 def valid_parent_project?(issue=parent)
1015 return true if issue.nil? || issue.project_id == project_id
1015 return true if issue.nil? || issue.project_id == project_id
1016
1016
1017 case Setting.cross_project_subtasks
1017 case Setting.cross_project_subtasks
1018 when 'system'
1018 when 'system'
1019 true
1019 true
1020 when 'tree'
1020 when 'tree'
1021 issue.project.root == project.root
1021 issue.project.root == project.root
1022 when 'hierarchy'
1022 when 'hierarchy'
1023 issue.project.is_or_is_ancestor_of?(project) || issue.project.is_descendant_of?(project)
1023 issue.project.is_or_is_ancestor_of?(project) || issue.project.is_descendant_of?(project)
1024 when 'descendants'
1024 when 'descendants'
1025 issue.project.is_or_is_ancestor_of?(project)
1025 issue.project.is_or_is_ancestor_of?(project)
1026 else
1026 else
1027 false
1027 false
1028 end
1028 end
1029 end
1029 end
1030
1030
1031 # Extracted from the ReportsController.
1031 # Extracted from the ReportsController.
1032 def self.by_tracker(project)
1032 def self.by_tracker(project)
1033 count_and_group_by(:project => project,
1033 count_and_group_by(:project => project,
1034 :field => 'tracker_id',
1034 :field => 'tracker_id',
1035 :joins => Tracker.table_name)
1035 :joins => Tracker.table_name)
1036 end
1036 end
1037
1037
1038 def self.by_version(project)
1038 def self.by_version(project)
1039 count_and_group_by(:project => project,
1039 count_and_group_by(:project => project,
1040 :field => 'fixed_version_id',
1040 :field => 'fixed_version_id',
1041 :joins => Version.table_name)
1041 :joins => Version.table_name)
1042 end
1042 end
1043
1043
1044 def self.by_priority(project)
1044 def self.by_priority(project)
1045 count_and_group_by(:project => project,
1045 count_and_group_by(:project => project,
1046 :field => 'priority_id',
1046 :field => 'priority_id',
1047 :joins => IssuePriority.table_name)
1047 :joins => IssuePriority.table_name)
1048 end
1048 end
1049
1049
1050 def self.by_category(project)
1050 def self.by_category(project)
1051 count_and_group_by(:project => project,
1051 count_and_group_by(:project => project,
1052 :field => 'category_id',
1052 :field => 'category_id',
1053 :joins => IssueCategory.table_name)
1053 :joins => IssueCategory.table_name)
1054 end
1054 end
1055
1055
1056 def self.by_assigned_to(project)
1056 def self.by_assigned_to(project)
1057 count_and_group_by(:project => project,
1057 count_and_group_by(:project => project,
1058 :field => 'assigned_to_id',
1058 :field => 'assigned_to_id',
1059 :joins => User.table_name)
1059 :joins => User.table_name)
1060 end
1060 end
1061
1061
1062 def self.by_author(project)
1062 def self.by_author(project)
1063 count_and_group_by(:project => project,
1063 count_and_group_by(:project => project,
1064 :field => 'author_id',
1064 :field => 'author_id',
1065 :joins => User.table_name)
1065 :joins => User.table_name)
1066 end
1066 end
1067
1067
1068 def self.by_subproject(project)
1068 def self.by_subproject(project)
1069 ActiveRecord::Base.connection.select_all("select s.id as status_id,
1069 ActiveRecord::Base.connection.select_all("select s.id as status_id,
1070 s.is_closed as closed,
1070 s.is_closed as closed,
1071 #{Issue.table_name}.project_id as project_id,
1071 #{Issue.table_name}.project_id as project_id,
1072 count(#{Issue.table_name}.id) as total
1072 count(#{Issue.table_name}.id) as total
1073 from
1073 from
1074 #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s
1074 #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s
1075 where
1075 where
1076 #{Issue.table_name}.status_id=s.id
1076 #{Issue.table_name}.status_id=s.id
1077 and #{Issue.table_name}.project_id = #{Project.table_name}.id
1077 and #{Issue.table_name}.project_id = #{Project.table_name}.id
1078 and #{visible_condition(User.current, :project => project, :with_subprojects => true)}
1078 and #{visible_condition(User.current, :project => project, :with_subprojects => true)}
1079 and #{Issue.table_name}.project_id <> #{project.id}
1079 and #{Issue.table_name}.project_id <> #{project.id}
1080 group by s.id, s.is_closed, #{Issue.table_name}.project_id") if project.descendants.active.any?
1080 group by s.id, s.is_closed, #{Issue.table_name}.project_id") if project.descendants.active.any?
1081 end
1081 end
1082 # End ReportsController extraction
1082 # End ReportsController extraction
1083
1083
1084 # Returns an array of projects that user can assign the issue to
1084 # Returns an array of projects that user can assign the issue to
1085 def allowed_target_projects(user=User.current)
1085 def allowed_target_projects(user=User.current)
1086 if new_record?
1086 if new_record?
1087 Project.all(:conditions => Project.allowed_to_condition(user, :add_issues))
1087 Project.all(:conditions => Project.allowed_to_condition(user, :add_issues))
1088 else
1088 else
1089 self.class.allowed_target_projects_on_move(user)
1089 self.class.allowed_target_projects_on_move(user)
1090 end
1090 end
1091 end
1091 end
1092
1092
1093 # Returns an array of projects that user can move issues to
1093 # Returns an array of projects that user can move issues to
1094 def self.allowed_target_projects_on_move(user=User.current)
1094 def self.allowed_target_projects_on_move(user=User.current)
1095 Project.all(:conditions => Project.allowed_to_condition(user, :move_issues))
1095 Project.all(:conditions => Project.allowed_to_condition(user, :move_issues))
1096 end
1096 end
1097
1097
1098 private
1098 private
1099
1099
1100 def after_project_change
1100 def after_project_change
1101 # Update project_id on related time entries
1101 # Update project_id on related time entries
1102 TimeEntry.update_all(["project_id = ?", project_id], {:issue_id => id})
1102 TimeEntry.update_all(["project_id = ?", project_id], {:issue_id => id})
1103
1103
1104 # Delete issue relations
1104 # Delete issue relations
1105 unless Setting.cross_project_issue_relations?
1105 unless Setting.cross_project_issue_relations?
1106 relations_from.clear
1106 relations_from.clear
1107 relations_to.clear
1107 relations_to.clear
1108 end
1108 end
1109
1109
1110 # Move subtasks that were in the same project
1110 # Move subtasks that were in the same project
1111 children.each do |child|
1111 children.each do |child|
1112 next unless child.project_id == project_id_was
1112 next unless child.project_id == project_id_was
1113 # Change project and keep project
1113 # Change project and keep project
1114 child.send :project=, project, true
1114 child.send :project=, project, true
1115 unless child.save
1115 unless child.save
1116 raise ActiveRecord::Rollback
1116 raise ActiveRecord::Rollback
1117 end
1117 end
1118 end
1118 end
1119 end
1119 end
1120
1120
1121 # Callback for after the creation of an issue by copy
1121 # Callback for after the creation of an issue by copy
1122 # * adds a "copied to" relation with the copied issue
1122 # * adds a "copied to" relation with the copied issue
1123 # * copies subtasks from the copied issue
1123 # * copies subtasks from the copied issue
1124 def after_create_from_copy
1124 def after_create_from_copy
1125 return unless copy? && !@after_create_from_copy_handled
1125 return unless copy? && !@after_create_from_copy_handled
1126
1126
1127 if (@copied_from.project_id == project_id || Setting.cross_project_issue_relations?) && @copy_options[:link] != false
1127 if (@copied_from.project_id == project_id || Setting.cross_project_issue_relations?) && @copy_options[:link] != false
1128 relation = IssueRelation.new(:issue_from => @copied_from, :issue_to => self, :relation_type => IssueRelation::TYPE_COPIED_TO)
1128 relation = IssueRelation.new(:issue_from => @copied_from, :issue_to => self, :relation_type => IssueRelation::TYPE_COPIED_TO)
1129 unless relation.save
1129 unless relation.save
1130 logger.error "Could not create relation while copying ##{@copied_from.id} to ##{id} due to validation errors: #{relation.errors.full_messages.join(', ')}" if logger
1130 logger.error "Could not create relation while copying ##{@copied_from.id} to ##{id} due to validation errors: #{relation.errors.full_messages.join(', ')}" if logger
1131 end
1131 end
1132 end
1132 end
1133
1133
1134 unless @copied_from.leaf? || @copy_options[:subtasks] == false
1134 unless @copied_from.leaf? || @copy_options[:subtasks] == false
1135 @copied_from.children.each do |child|
1135 @copied_from.children.each do |child|
1136 unless child.visible?
1136 unless child.visible?
1137 # Do not copy subtasks that are not visible to avoid potential disclosure of private data
1137 # Do not copy subtasks that are not visible to avoid potential disclosure of private data
1138 logger.error "Subtask ##{child.id} was not copied during ##{@copied_from.id} copy because it is not visible to the current user" if logger
1138 logger.error "Subtask ##{child.id} was not copied during ##{@copied_from.id} copy because it is not visible to the current user" if logger
1139 next
1139 next
1140 end
1140 end
1141 copy = Issue.new.copy_from(child, @copy_options)
1141 copy = Issue.new.copy_from(child, @copy_options)
1142 copy.author = author
1142 copy.author = author
1143 copy.project = project
1143 copy.project = project
1144 copy.parent_issue_id = id
1144 copy.parent_issue_id = id
1145 # Children subtasks are copied recursively
1145 # Children subtasks are copied recursively
1146 unless copy.save
1146 unless copy.save
1147 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
1147 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
1148 end
1148 end
1149 end
1149 end
1150 end
1150 end
1151 @after_create_from_copy_handled = true
1151 @after_create_from_copy_handled = true
1152 end
1152 end
1153
1153
1154 def update_nested_set_attributes
1154 def update_nested_set_attributes
1155 if root_id.nil?
1155 if root_id.nil?
1156 # issue was just created
1156 # issue was just created
1157 self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id)
1157 self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id)
1158 set_default_left_and_right
1158 set_default_left_and_right
1159 Issue.update_all("root_id = #{root_id}, lft = #{lft}, rgt = #{rgt}", ["id = ?", id])
1159 Issue.update_all("root_id = #{root_id}, lft = #{lft}, rgt = #{rgt}", ["id = ?", id])
1160 if @parent_issue
1160 if @parent_issue
1161 move_to_child_of(@parent_issue)
1161 move_to_child_of(@parent_issue)
1162 end
1162 end
1163 reload
1163 reload
1164 elsif parent_issue_id != parent_id
1164 elsif parent_issue_id != parent_id
1165 former_parent_id = parent_id
1165 former_parent_id = parent_id
1166 # moving an existing issue
1166 # moving an existing issue
1167 if @parent_issue && @parent_issue.root_id == root_id
1167 if @parent_issue && @parent_issue.root_id == root_id
1168 # inside the same tree
1168 # inside the same tree
1169 move_to_child_of(@parent_issue)
1169 move_to_child_of(@parent_issue)
1170 else
1170 else
1171 # to another tree
1171 # to another tree
1172 unless root?
1172 unless root?
1173 move_to_right_of(root)
1173 move_to_right_of(root)
1174 reload
1174 reload
1175 end
1175 end
1176 old_root_id = root_id
1176 old_root_id = root_id
1177 self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id )
1177 self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id )
1178 target_maxright = nested_set_scope.maximum(right_column_name) || 0
1178 target_maxright = nested_set_scope.maximum(right_column_name) || 0
1179 offset = target_maxright + 1 - lft
1179 offset = target_maxright + 1 - lft
1180 Issue.update_all("root_id = #{root_id}, lft = lft + #{offset}, rgt = rgt + #{offset}",
1180 Issue.update_all("root_id = #{root_id}, lft = lft + #{offset}, rgt = rgt + #{offset}",
1181 ["root_id = ? AND lft >= ? AND rgt <= ? ", old_root_id, lft, rgt])
1181 ["root_id = ? AND lft >= ? AND rgt <= ? ", old_root_id, lft, rgt])
1182 self[left_column_name] = lft + offset
1182 self[left_column_name] = lft + offset
1183 self[right_column_name] = rgt + offset
1183 self[right_column_name] = rgt + offset
1184 if @parent_issue
1184 if @parent_issue
1185 move_to_child_of(@parent_issue)
1185 move_to_child_of(@parent_issue)
1186 end
1186 end
1187 end
1187 end
1188 reload
1188 reload
1189 # delete invalid relations of all descendants
1189 # delete invalid relations of all descendants
1190 self_and_descendants.each do |issue|
1190 self_and_descendants.each do |issue|
1191 issue.relations.each do |relation|
1191 issue.relations.each do |relation|
1192 relation.destroy unless relation.valid?
1192 relation.destroy unless relation.valid?
1193 end
1193 end
1194 end
1194 end
1195 # update former parent
1195 # update former parent
1196 recalculate_attributes_for(former_parent_id) if former_parent_id
1196 recalculate_attributes_for(former_parent_id) if former_parent_id
1197 end
1197 end
1198 remove_instance_variable(:@parent_issue) if instance_variable_defined?(:@parent_issue)
1198 remove_instance_variable(:@parent_issue) if instance_variable_defined?(:@parent_issue)
1199 end
1199 end
1200
1200
1201 def update_parent_attributes
1201 def update_parent_attributes
1202 recalculate_attributes_for(parent_id) if parent_id
1202 recalculate_attributes_for(parent_id) if parent_id
1203 end
1203 end
1204
1204
1205 def recalculate_attributes_for(issue_id)
1205 def recalculate_attributes_for(issue_id)
1206 if issue_id && p = Issue.find_by_id(issue_id)
1206 if issue_id && p = Issue.find_by_id(issue_id)
1207 # priority = highest priority of children
1207 # priority = highest priority of children
1208 if priority_position = p.children.maximum("#{IssuePriority.table_name}.position", :joins => :priority)
1208 if priority_position = p.children.maximum("#{IssuePriority.table_name}.position", :joins => :priority)
1209 p.priority = IssuePriority.find_by_position(priority_position)
1209 p.priority = IssuePriority.find_by_position(priority_position)
1210 end
1210 end
1211
1211
1212 # start/due dates = lowest/highest dates of children
1212 # start/due dates = lowest/highest dates of children
1213 p.start_date = p.children.minimum(:start_date)
1213 p.start_date = p.children.minimum(:start_date)
1214 p.due_date = p.children.maximum(:due_date)
1214 p.due_date = p.children.maximum(:due_date)
1215 if p.start_date && p.due_date && p.due_date < p.start_date
1215 if p.start_date && p.due_date && p.due_date < p.start_date
1216 p.start_date, p.due_date = p.due_date, p.start_date
1216 p.start_date, p.due_date = p.due_date, p.start_date
1217 end
1217 end
1218
1218
1219 # done ratio = weighted average ratio of leaves
1219 # done ratio = weighted average ratio of leaves
1220 unless Issue.use_status_for_done_ratio? && p.status && p.status.default_done_ratio
1220 unless Issue.use_status_for_done_ratio? && p.status && p.status.default_done_ratio
1221 leaves_count = p.leaves.count
1221 leaves_count = p.leaves.count
1222 if leaves_count > 0
1222 if leaves_count > 0
1223 average = p.leaves.average(:estimated_hours).to_f
1223 average = p.leaves.average(:estimated_hours).to_f
1224 if average == 0
1224 if average == 0
1225 average = 1
1225 average = 1
1226 end
1226 end
1227 done = p.leaves.sum("COALESCE(estimated_hours, #{average}) * (CASE WHEN is_closed = #{connection.quoted_true} THEN 100 ELSE COALESCE(done_ratio, 0) END)", :joins => :status).to_f
1227 done = p.leaves.sum("COALESCE(estimated_hours, #{average}) * (CASE WHEN is_closed = #{connection.quoted_true} THEN 100 ELSE COALESCE(done_ratio, 0) END)", :joins => :status).to_f
1228 progress = done / (average * leaves_count)
1228 progress = done / (average * leaves_count)
1229 p.done_ratio = progress.round
1229 p.done_ratio = progress.round
1230 end
1230 end
1231 end
1231 end
1232
1232
1233 # estimate = sum of leaves estimates
1233 # estimate = sum of leaves estimates
1234 p.estimated_hours = p.leaves.sum(:estimated_hours).to_f
1234 p.estimated_hours = p.leaves.sum(:estimated_hours).to_f
1235 p.estimated_hours = nil if p.estimated_hours == 0.0
1235 p.estimated_hours = nil if p.estimated_hours == 0.0
1236
1236
1237 # ancestors will be recursively updated
1237 # ancestors will be recursively updated
1238 p.save(:validate => false)
1238 p.save(:validate => false)
1239 end
1239 end
1240 end
1240 end
1241
1241
1242 # Update issues so their versions are not pointing to a
1242 # Update issues so their versions are not pointing to a
1243 # fixed_version that is not shared with the issue's project
1243 # fixed_version that is not shared with the issue's project
1244 def self.update_versions(conditions=nil)
1244 def self.update_versions(conditions=nil)
1245 # Only need to update issues with a fixed_version from
1245 # Only need to update issues with a fixed_version from
1246 # a different project and that is not systemwide shared
1246 # a different project and that is not systemwide shared
1247 Issue.scoped(:conditions => conditions).all(
1247 Issue.scoped(:conditions => conditions).all(
1248 :conditions => "#{Issue.table_name}.fixed_version_id IS NOT NULL" +
1248 :conditions => "#{Issue.table_name}.fixed_version_id IS NOT NULL" +
1249 " AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" +
1249 " AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" +
1250 " AND #{Version.table_name}.sharing <> 'system'",
1250 " AND #{Version.table_name}.sharing <> 'system'",
1251 :include => [:project, :fixed_version]
1251 :include => [:project, :fixed_version]
1252 ).each do |issue|
1252 ).each do |issue|
1253 next if issue.project.nil? || issue.fixed_version.nil?
1253 next if issue.project.nil? || issue.fixed_version.nil?
1254 unless issue.project.shared_versions.include?(issue.fixed_version)
1254 unless issue.project.shared_versions.include?(issue.fixed_version)
1255 issue.init_journal(User.current)
1255 issue.init_journal(User.current)
1256 issue.fixed_version = nil
1256 issue.fixed_version = nil
1257 issue.save
1257 issue.save
1258 end
1258 end
1259 end
1259 end
1260 end
1260 end
1261
1261
1262 # Callback on file attachment
1262 # Callback on file attachment
1263 def attachment_added(obj)
1263 def attachment_added(obj)
1264 if @current_journal && !obj.new_record?
1264 if @current_journal && !obj.new_record?
1265 @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => obj.id, :value => obj.filename)
1265 @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => obj.id, :value => obj.filename)
1266 end
1266 end
1267 end
1267 end
1268
1268
1269 # Callback on attachment deletion
1269 # Callback on attachment deletion
1270 def attachment_removed(obj)
1270 def attachment_removed(obj)
1271 if @current_journal && !obj.new_record?
1271 if @current_journal && !obj.new_record?
1272 @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => obj.id, :old_value => obj.filename)
1272 @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => obj.id, :old_value => obj.filename)
1273 @current_journal.save
1273 @current_journal.save
1274 end
1274 end
1275 end
1275 end
1276
1276
1277 # Default assignment based on category
1277 # Default assignment based on category
1278 def default_assign
1278 def default_assign
1279 if assigned_to.nil? && category && category.assigned_to
1279 if assigned_to.nil? && category && category.assigned_to
1280 self.assigned_to = category.assigned_to
1280 self.assigned_to = category.assigned_to
1281 end
1281 end
1282 end
1282 end
1283
1283
1284 # Updates start/due dates of following issues
1284 # Updates start/due dates of following issues
1285 def reschedule_following_issues
1285 def reschedule_following_issues
1286 if start_date_changed? || due_date_changed?
1286 if start_date_changed? || due_date_changed?
1287 relations_from.each do |relation|
1287 relations_from.each do |relation|
1288 relation.set_issue_to_dates
1288 relation.set_issue_to_dates
1289 end
1289 end
1290 end
1290 end
1291 end
1291 end
1292
1292
1293 # Closes duplicates if the issue is being closed
1293 # Closes duplicates if the issue is being closed
1294 def close_duplicates
1294 def close_duplicates
1295 if closing?
1295 if closing?
1296 duplicates.each do |duplicate|
1296 duplicates.each do |duplicate|
1297 # Reload is need in case the duplicate was updated by a previous duplicate
1297 # Reload is need in case the duplicate was updated by a previous duplicate
1298 duplicate.reload
1298 duplicate.reload
1299 # Don't re-close it if it's already closed
1299 # Don't re-close it if it's already closed
1300 next if duplicate.closed?
1300 next if duplicate.closed?
1301 # Same user and notes
1301 # Same user and notes
1302 if @current_journal
1302 if @current_journal
1303 duplicate.init_journal(@current_journal.user, @current_journal.notes)
1303 duplicate.init_journal(@current_journal.user, @current_journal.notes)
1304 end
1304 end
1305 duplicate.update_attribute :status, self.status
1305 duplicate.update_attribute :status, self.status
1306 end
1306 end
1307 end
1307 end
1308 end
1308 end
1309
1309
1310 # Make sure updated_on is updated when adding a note
1310 # Make sure updated_on is updated when adding a note
1311 def force_updated_on_change
1311 def force_updated_on_change
1312 if @current_journal
1312 if @current_journal
1313 self.updated_on = current_time_from_proper_timezone
1313 self.updated_on = current_time_from_proper_timezone
1314 end
1314 end
1315 end
1315 end
1316
1316
1317 # Saves the changes in a Journal
1317 # Saves the changes in a Journal
1318 # Called after_save
1318 # Called after_save
1319 def create_journal
1319 def create_journal
1320 if @current_journal
1320 if @current_journal
1321 # attributes changes
1321 # attributes changes
1322 if @attributes_before_change
1322 if @attributes_before_change
1323 (Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on)).each {|c|
1323 (Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on)).each {|c|
1324 before = @attributes_before_change[c]
1324 before = @attributes_before_change[c]
1325 after = send(c)
1325 after = send(c)
1326 next if before == after || (before.blank? && after.blank?)
1326 next if before == after || (before.blank? && after.blank?)
1327 @current_journal.details << JournalDetail.new(:property => 'attr',
1327 @current_journal.details << JournalDetail.new(:property => 'attr',
1328 :prop_key => c,
1328 :prop_key => c,
1329 :old_value => before,
1329 :old_value => before,
1330 :value => after)
1330 :value => after)
1331 }
1331 }
1332 end
1332 end
1333 if @custom_values_before_change
1333 if @custom_values_before_change
1334 # custom fields changes
1334 # custom fields changes
1335 custom_field_values.each {|c|
1335 custom_field_values.each {|c|
1336 before = @custom_values_before_change[c.custom_field_id]
1336 before = @custom_values_before_change[c.custom_field_id]
1337 after = c.value
1337 after = c.value
1338 next if before == after || (before.blank? && after.blank?)
1338 next if before == after || (before.blank? && after.blank?)
1339
1339
1340 if before.is_a?(Array) || after.is_a?(Array)
1340 if before.is_a?(Array) || after.is_a?(Array)
1341 before = [before] unless before.is_a?(Array)
1341 before = [before] unless before.is_a?(Array)
1342 after = [after] unless after.is_a?(Array)
1342 after = [after] unless after.is_a?(Array)
1343
1343
1344 # values removed
1344 # values removed
1345 (before - after).reject(&:blank?).each do |value|
1345 (before - after).reject(&:blank?).each do |value|
1346 @current_journal.details << JournalDetail.new(:property => 'cf',
1346 @current_journal.details << JournalDetail.new(:property => 'cf',
1347 :prop_key => c.custom_field_id,
1347 :prop_key => c.custom_field_id,
1348 :old_value => value,
1348 :old_value => value,
1349 :value => nil)
1349 :value => nil)
1350 end
1350 end
1351 # values added
1351 # values added
1352 (after - before).reject(&:blank?).each do |value|
1352 (after - before).reject(&:blank?).each do |value|
1353 @current_journal.details << JournalDetail.new(:property => 'cf',
1353 @current_journal.details << JournalDetail.new(:property => 'cf',
1354 :prop_key => c.custom_field_id,
1354 :prop_key => c.custom_field_id,
1355 :old_value => nil,
1355 :old_value => nil,
1356 :value => value)
1356 :value => value)
1357 end
1357 end
1358 else
1358 else
1359 @current_journal.details << JournalDetail.new(:property => 'cf',
1359 @current_journal.details << JournalDetail.new(:property => 'cf',
1360 :prop_key => c.custom_field_id,
1360 :prop_key => c.custom_field_id,
1361 :old_value => before,
1361 :old_value => before,
1362 :value => after)
1362 :value => after)
1363 end
1363 end
1364 }
1364 }
1365 end
1365 end
1366 @current_journal.save
1366 @current_journal.save
1367 # reset current journal
1367 # reset current journal
1368 init_journal @current_journal.user, @current_journal.notes
1368 init_journal @current_journal.user, @current_journal.notes
1369 end
1369 end
1370 end
1370 end
1371
1371
1372 # Query generator for selecting groups of issue counts for a project
1372 # Query generator for selecting groups of issue counts for a project
1373 # based on specific criteria
1373 # based on specific criteria
1374 #
1374 #
1375 # Options
1375 # Options
1376 # * project - Project to search in.
1376 # * project - Project to search in.
1377 # * field - String. Issue field to key off of in the grouping.
1377 # * field - String. Issue field to key off of in the grouping.
1378 # * joins - String. The table name to join against.
1378 # * joins - String. The table name to join against.
1379 def self.count_and_group_by(options)
1379 def self.count_and_group_by(options)
1380 project = options.delete(:project)
1380 project = options.delete(:project)
1381 select_field = options.delete(:field)
1381 select_field = options.delete(:field)
1382 joins = options.delete(:joins)
1382 joins = options.delete(:joins)
1383
1383
1384 where = "#{Issue.table_name}.#{select_field}=j.id"
1384 where = "#{Issue.table_name}.#{select_field}=j.id"
1385
1385
1386 ActiveRecord::Base.connection.select_all("select s.id as status_id,
1386 ActiveRecord::Base.connection.select_all("select s.id as status_id,
1387 s.is_closed as closed,
1387 s.is_closed as closed,
1388 j.id as #{select_field},
1388 j.id as #{select_field},
1389 count(#{Issue.table_name}.id) as total
1389 count(#{Issue.table_name}.id) as total
1390 from
1390 from
1391 #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s, #{joins} j
1391 #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s, #{joins} j
1392 where
1392 where
1393 #{Issue.table_name}.status_id=s.id
1393 #{Issue.table_name}.status_id=s.id
1394 and #{where}
1394 and #{where}
1395 and #{Issue.table_name}.project_id=#{Project.table_name}.id
1395 and #{Issue.table_name}.project_id=#{Project.table_name}.id
1396 and #{visible_condition(User.current, :project => project)}
1396 and #{visible_condition(User.current, :project => project)}
1397 group by s.id, s.is_closed, j.id")
1397 group by s.id, s.is_closed, j.id")
1398 end
1398 end
1399 end
1399 end
@@ -1,3858 +1,3872
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
2 # Copyright (C) 2006-2013 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 IssuesControllerTest < ActionController::TestCase
20 class IssuesControllerTest < ActionController::TestCase
21 fixtures :projects,
21 fixtures :projects,
22 :users,
22 :users,
23 :roles,
23 :roles,
24 :members,
24 :members,
25 :member_roles,
25 :member_roles,
26 :issues,
26 :issues,
27 :issue_statuses,
27 :issue_statuses,
28 :versions,
28 :versions,
29 :trackers,
29 :trackers,
30 :projects_trackers,
30 :projects_trackers,
31 :issue_categories,
31 :issue_categories,
32 :enabled_modules,
32 :enabled_modules,
33 :enumerations,
33 :enumerations,
34 :attachments,
34 :attachments,
35 :workflows,
35 :workflows,
36 :custom_fields,
36 :custom_fields,
37 :custom_values,
37 :custom_values,
38 :custom_fields_projects,
38 :custom_fields_projects,
39 :custom_fields_trackers,
39 :custom_fields_trackers,
40 :time_entries,
40 :time_entries,
41 :journals,
41 :journals,
42 :journal_details,
42 :journal_details,
43 :queries,
43 :queries,
44 :repositories,
44 :repositories,
45 :changesets
45 :changesets
46
46
47 include Redmine::I18n
47 include Redmine::I18n
48
48
49 def setup
49 def setup
50 User.current = nil
50 User.current = nil
51 end
51 end
52
52
53 def test_index
53 def test_index
54 with_settings :default_language => "en" do
54 with_settings :default_language => "en" do
55 get :index
55 get :index
56 assert_response :success
56 assert_response :success
57 assert_template 'index'
57 assert_template 'index'
58 assert_not_nil assigns(:issues)
58 assert_not_nil assigns(:issues)
59 assert_nil assigns(:project)
59 assert_nil assigns(:project)
60
60
61 # links to visible issues
61 # links to visible issues
62 assert_select 'a[href=/issues/1]', :text => /Can&#x27;t print recipes/
62 assert_select 'a[href=/issues/1]', :text => /Can&#x27;t print recipes/
63 assert_select 'a[href=/issues/5]', :text => /Subproject issue/
63 assert_select 'a[href=/issues/5]', :text => /Subproject issue/
64 # private projects hidden
64 # private projects hidden
65 assert_select 'a[href=/issues/6]', 0
65 assert_select 'a[href=/issues/6]', 0
66 assert_select 'a[href=/issues/4]', 0
66 assert_select 'a[href=/issues/4]', 0
67 # project column
67 # project column
68 assert_select 'th', :text => /Project/
68 assert_select 'th', :text => /Project/
69 end
69 end
70 end
70 end
71
71
72 def test_index_should_not_list_issues_when_module_disabled
72 def test_index_should_not_list_issues_when_module_disabled
73 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
73 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
74 get :index
74 get :index
75 assert_response :success
75 assert_response :success
76 assert_template 'index'
76 assert_template 'index'
77 assert_not_nil assigns(:issues)
77 assert_not_nil assigns(:issues)
78 assert_nil assigns(:project)
78 assert_nil assigns(:project)
79
79
80 assert_select 'a[href=/issues/1]', 0
80 assert_select 'a[href=/issues/1]', 0
81 assert_select 'a[href=/issues/5]', :text => /Subproject issue/
81 assert_select 'a[href=/issues/5]', :text => /Subproject issue/
82 end
82 end
83
83
84 def test_index_should_list_visible_issues_only
84 def test_index_should_list_visible_issues_only
85 get :index, :per_page => 100
85 get :index, :per_page => 100
86 assert_response :success
86 assert_response :success
87 assert_not_nil assigns(:issues)
87 assert_not_nil assigns(:issues)
88 assert_nil assigns(:issues).detect {|issue| !issue.visible?}
88 assert_nil assigns(:issues).detect {|issue| !issue.visible?}
89 end
89 end
90
90
91 def test_index_with_project
91 def test_index_with_project
92 Setting.display_subprojects_issues = 0
92 Setting.display_subprojects_issues = 0
93 get :index, :project_id => 1
93 get :index, :project_id => 1
94 assert_response :success
94 assert_response :success
95 assert_template 'index'
95 assert_template 'index'
96 assert_not_nil assigns(:issues)
96 assert_not_nil assigns(:issues)
97
97
98 assert_select 'a[href=/issues/1]', :text => /Can&#x27;t print recipes/
98 assert_select 'a[href=/issues/1]', :text => /Can&#x27;t print recipes/
99 assert_select 'a[href=/issues/5]', 0
99 assert_select 'a[href=/issues/5]', 0
100 end
100 end
101
101
102 def test_index_with_project_and_subprojects
102 def test_index_with_project_and_subprojects
103 Setting.display_subprojects_issues = 1
103 Setting.display_subprojects_issues = 1
104 get :index, :project_id => 1
104 get :index, :project_id => 1
105 assert_response :success
105 assert_response :success
106 assert_template 'index'
106 assert_template 'index'
107 assert_not_nil assigns(:issues)
107 assert_not_nil assigns(:issues)
108
108
109 assert_select 'a[href=/issues/1]', :text => /Can&#x27;t print recipes/
109 assert_select 'a[href=/issues/1]', :text => /Can&#x27;t print recipes/
110 assert_select 'a[href=/issues/5]', :text => /Subproject issue/
110 assert_select 'a[href=/issues/5]', :text => /Subproject issue/
111 assert_select 'a[href=/issues/6]', 0
111 assert_select 'a[href=/issues/6]', 0
112 end
112 end
113
113
114 def test_index_with_project_and_subprojects_should_show_private_subprojects_with_permission
114 def test_index_with_project_and_subprojects_should_show_private_subprojects_with_permission
115 @request.session[:user_id] = 2
115 @request.session[:user_id] = 2
116 Setting.display_subprojects_issues = 1
116 Setting.display_subprojects_issues = 1
117 get :index, :project_id => 1
117 get :index, :project_id => 1
118 assert_response :success
118 assert_response :success
119 assert_template 'index'
119 assert_template 'index'
120 assert_not_nil assigns(:issues)
120 assert_not_nil assigns(:issues)
121
121
122 assert_select 'a[href=/issues/1]', :text => /Can&#x27;t print recipes/
122 assert_select 'a[href=/issues/1]', :text => /Can&#x27;t print recipes/
123 assert_select 'a[href=/issues/5]', :text => /Subproject issue/
123 assert_select 'a[href=/issues/5]', :text => /Subproject issue/
124 assert_select 'a[href=/issues/6]', :text => /Issue of a private subproject/
124 assert_select 'a[href=/issues/6]', :text => /Issue of a private subproject/
125 end
125 end
126
126
127 def test_index_with_project_and_default_filter
127 def test_index_with_project_and_default_filter
128 get :index, :project_id => 1, :set_filter => 1
128 get :index, :project_id => 1, :set_filter => 1
129 assert_response :success
129 assert_response :success
130 assert_template 'index'
130 assert_template 'index'
131 assert_not_nil assigns(:issues)
131 assert_not_nil assigns(:issues)
132
132
133 query = assigns(:query)
133 query = assigns(:query)
134 assert_not_nil query
134 assert_not_nil query
135 # default filter
135 # default filter
136 assert_equal({'status_id' => {:operator => 'o', :values => ['']}}, query.filters)
136 assert_equal({'status_id' => {:operator => 'o', :values => ['']}}, query.filters)
137 end
137 end
138
138
139 def test_index_with_project_and_filter
139 def test_index_with_project_and_filter
140 get :index, :project_id => 1, :set_filter => 1,
140 get :index, :project_id => 1, :set_filter => 1,
141 :f => ['tracker_id'],
141 :f => ['tracker_id'],
142 :op => {'tracker_id' => '='},
142 :op => {'tracker_id' => '='},
143 :v => {'tracker_id' => ['1']}
143 :v => {'tracker_id' => ['1']}
144 assert_response :success
144 assert_response :success
145 assert_template 'index'
145 assert_template 'index'
146 assert_not_nil assigns(:issues)
146 assert_not_nil assigns(:issues)
147
147
148 query = assigns(:query)
148 query = assigns(:query)
149 assert_not_nil query
149 assert_not_nil query
150 assert_equal({'tracker_id' => {:operator => '=', :values => ['1']}}, query.filters)
150 assert_equal({'tracker_id' => {:operator => '=', :values => ['1']}}, query.filters)
151 end
151 end
152
152
153 def test_index_with_short_filters
153 def test_index_with_short_filters
154 to_test = {
154 to_test = {
155 'status_id' => {
155 'status_id' => {
156 'o' => { :op => 'o', :values => [''] },
156 'o' => { :op => 'o', :values => [''] },
157 'c' => { :op => 'c', :values => [''] },
157 'c' => { :op => 'c', :values => [''] },
158 '7' => { :op => '=', :values => ['7'] },
158 '7' => { :op => '=', :values => ['7'] },
159 '7|3|4' => { :op => '=', :values => ['7', '3', '4'] },
159 '7|3|4' => { :op => '=', :values => ['7', '3', '4'] },
160 '=7' => { :op => '=', :values => ['7'] },
160 '=7' => { :op => '=', :values => ['7'] },
161 '!3' => { :op => '!', :values => ['3'] },
161 '!3' => { :op => '!', :values => ['3'] },
162 '!7|3|4' => { :op => '!', :values => ['7', '3', '4'] }},
162 '!7|3|4' => { :op => '!', :values => ['7', '3', '4'] }},
163 'subject' => {
163 'subject' => {
164 'This is a subject' => { :op => '=', :values => ['This is a subject'] },
164 'This is a subject' => { :op => '=', :values => ['This is a subject'] },
165 'o' => { :op => '=', :values => ['o'] },
165 'o' => { :op => '=', :values => ['o'] },
166 '~This is part of a subject' => { :op => '~', :values => ['This is part of a subject'] },
166 '~This is part of a subject' => { :op => '~', :values => ['This is part of a subject'] },
167 '!~This is part of a subject' => { :op => '!~', :values => ['This is part of a subject'] }},
167 '!~This is part of a subject' => { :op => '!~', :values => ['This is part of a subject'] }},
168 'tracker_id' => {
168 'tracker_id' => {
169 '3' => { :op => '=', :values => ['3'] },
169 '3' => { :op => '=', :values => ['3'] },
170 '=3' => { :op => '=', :values => ['3'] }},
170 '=3' => { :op => '=', :values => ['3'] }},
171 'start_date' => {
171 'start_date' => {
172 '2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
172 '2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
173 '=2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
173 '=2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
174 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
174 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
175 '<=2011-10-12' => { :op => '<=', :values => ['2011-10-12'] },
175 '<=2011-10-12' => { :op => '<=', :values => ['2011-10-12'] },
176 '><2011-10-01|2011-10-30' => { :op => '><', :values => ['2011-10-01', '2011-10-30'] },
176 '><2011-10-01|2011-10-30' => { :op => '><', :values => ['2011-10-01', '2011-10-30'] },
177 '<t+2' => { :op => '<t+', :values => ['2'] },
177 '<t+2' => { :op => '<t+', :values => ['2'] },
178 '>t+2' => { :op => '>t+', :values => ['2'] },
178 '>t+2' => { :op => '>t+', :values => ['2'] },
179 't+2' => { :op => 't+', :values => ['2'] },
179 't+2' => { :op => 't+', :values => ['2'] },
180 't' => { :op => 't', :values => [''] },
180 't' => { :op => 't', :values => [''] },
181 'w' => { :op => 'w', :values => [''] },
181 'w' => { :op => 'w', :values => [''] },
182 '>t-2' => { :op => '>t-', :values => ['2'] },
182 '>t-2' => { :op => '>t-', :values => ['2'] },
183 '<t-2' => { :op => '<t-', :values => ['2'] },
183 '<t-2' => { :op => '<t-', :values => ['2'] },
184 't-2' => { :op => 't-', :values => ['2'] }},
184 't-2' => { :op => 't-', :values => ['2'] }},
185 'created_on' => {
185 'created_on' => {
186 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
186 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
187 '<t-2' => { :op => '<t-', :values => ['2'] },
187 '<t-2' => { :op => '<t-', :values => ['2'] },
188 '>t-2' => { :op => '>t-', :values => ['2'] },
188 '>t-2' => { :op => '>t-', :values => ['2'] },
189 't-2' => { :op => 't-', :values => ['2'] }},
189 't-2' => { :op => 't-', :values => ['2'] }},
190 'cf_1' => {
190 'cf_1' => {
191 'c' => { :op => '=', :values => ['c'] },
191 'c' => { :op => '=', :values => ['c'] },
192 '!c' => { :op => '!', :values => ['c'] },
192 '!c' => { :op => '!', :values => ['c'] },
193 '!*' => { :op => '!*', :values => [''] },
193 '!*' => { :op => '!*', :values => [''] },
194 '*' => { :op => '*', :values => [''] }},
194 '*' => { :op => '*', :values => [''] }},
195 'estimated_hours' => {
195 'estimated_hours' => {
196 '=13.4' => { :op => '=', :values => ['13.4'] },
196 '=13.4' => { :op => '=', :values => ['13.4'] },
197 '>=45' => { :op => '>=', :values => ['45'] },
197 '>=45' => { :op => '>=', :values => ['45'] },
198 '<=125' => { :op => '<=', :values => ['125'] },
198 '<=125' => { :op => '<=', :values => ['125'] },
199 '><10.5|20.5' => { :op => '><', :values => ['10.5', '20.5'] },
199 '><10.5|20.5' => { :op => '><', :values => ['10.5', '20.5'] },
200 '!*' => { :op => '!*', :values => [''] },
200 '!*' => { :op => '!*', :values => [''] },
201 '*' => { :op => '*', :values => [''] }}
201 '*' => { :op => '*', :values => [''] }}
202 }
202 }
203
203
204 default_filter = { 'status_id' => {:operator => 'o', :values => [''] }}
204 default_filter = { 'status_id' => {:operator => 'o', :values => [''] }}
205
205
206 to_test.each do |field, expression_and_expected|
206 to_test.each do |field, expression_and_expected|
207 expression_and_expected.each do |filter_expression, expected|
207 expression_and_expected.each do |filter_expression, expected|
208
208
209 get :index, :set_filter => 1, field => filter_expression
209 get :index, :set_filter => 1, field => filter_expression
210
210
211 assert_response :success
211 assert_response :success
212 assert_template 'index'
212 assert_template 'index'
213 assert_not_nil assigns(:issues)
213 assert_not_nil assigns(:issues)
214
214
215 query = assigns(:query)
215 query = assigns(:query)
216 assert_not_nil query
216 assert_not_nil query
217 assert query.has_filter?(field)
217 assert query.has_filter?(field)
218 assert_equal(default_filter.merge({field => {:operator => expected[:op], :values => expected[:values]}}), query.filters)
218 assert_equal(default_filter.merge({field => {:operator => expected[:op], :values => expected[:values]}}), query.filters)
219 end
219 end
220 end
220 end
221 end
221 end
222
222
223 def test_index_with_project_and_empty_filters
223 def test_index_with_project_and_empty_filters
224 get :index, :project_id => 1, :set_filter => 1, :fields => ['']
224 get :index, :project_id => 1, :set_filter => 1, :fields => ['']
225 assert_response :success
225 assert_response :success
226 assert_template 'index'
226 assert_template 'index'
227 assert_not_nil assigns(:issues)
227 assert_not_nil assigns(:issues)
228
228
229 query = assigns(:query)
229 query = assigns(:query)
230 assert_not_nil query
230 assert_not_nil query
231 # no filter
231 # no filter
232 assert_equal({}, query.filters)
232 assert_equal({}, query.filters)
233 end
233 end
234
234
235 def test_index_with_project_custom_field_filter
235 def test_index_with_project_custom_field_filter
236 field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
236 field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
237 CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo')
237 CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo')
238 CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo')
238 CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo')
239 filter_name = "project.cf_#{field.id}"
239 filter_name = "project.cf_#{field.id}"
240 @request.session[:user_id] = 1
240 @request.session[:user_id] = 1
241
241
242 get :index, :set_filter => 1,
242 get :index, :set_filter => 1,
243 :f => [filter_name],
243 :f => [filter_name],
244 :op => {filter_name => '='},
244 :op => {filter_name => '='},
245 :v => {filter_name => ['Foo']}
245 :v => {filter_name => ['Foo']}
246 assert_response :success
246 assert_response :success
247 assert_template 'index'
247 assert_template 'index'
248 assert_equal [3, 5], assigns(:issues).map(&:project_id).uniq.sort
248 assert_equal [3, 5], assigns(:issues).map(&:project_id).uniq.sort
249 end
249 end
250
250
251 def test_index_with_query
251 def test_index_with_query
252 get :index, :project_id => 1, :query_id => 5
252 get :index, :project_id => 1, :query_id => 5
253 assert_response :success
253 assert_response :success
254 assert_template 'index'
254 assert_template 'index'
255 assert_not_nil assigns(:issues)
255 assert_not_nil assigns(:issues)
256 assert_nil assigns(:issue_count_by_group)
256 assert_nil assigns(:issue_count_by_group)
257 end
257 end
258
258
259 def test_index_with_query_grouped_by_tracker
259 def test_index_with_query_grouped_by_tracker
260 get :index, :project_id => 1, :query_id => 6
260 get :index, :project_id => 1, :query_id => 6
261 assert_response :success
261 assert_response :success
262 assert_template 'index'
262 assert_template 'index'
263 assert_not_nil assigns(:issues)
263 assert_not_nil assigns(:issues)
264 assert_not_nil assigns(:issue_count_by_group)
264 assert_not_nil assigns(:issue_count_by_group)
265 end
265 end
266
266
267 def test_index_with_query_grouped_by_list_custom_field
267 def test_index_with_query_grouped_by_list_custom_field
268 get :index, :project_id => 1, :query_id => 9
268 get :index, :project_id => 1, :query_id => 9
269 assert_response :success
269 assert_response :success
270 assert_template 'index'
270 assert_template 'index'
271 assert_not_nil assigns(:issues)
271 assert_not_nil assigns(:issues)
272 assert_not_nil assigns(:issue_count_by_group)
272 assert_not_nil assigns(:issue_count_by_group)
273 end
273 end
274
274
275 def test_index_with_query_grouped_by_user_custom_field
275 def test_index_with_query_grouped_by_user_custom_field
276 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
276 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
277 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
277 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
278 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
278 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
279 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
279 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
280 CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')
280 CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')
281
281
282 get :index, :project_id => 1, :set_filter => 1, :group_by => "cf_#{cf.id}"
282 get :index, :project_id => 1, :set_filter => 1, :group_by => "cf_#{cf.id}"
283 assert_response :success
283 assert_response :success
284
284
285 assert_select 'tr.group', 3
285 assert_select 'tr.group', 3
286 assert_select 'tr.group' do
286 assert_select 'tr.group' do
287 assert_select 'a', :text => 'John Smith'
287 assert_select 'a', :text => 'John Smith'
288 assert_select 'span.count', :text => '1'
288 assert_select 'span.count', :text => '1'
289 end
289 end
290 assert_select 'tr.group' do
290 assert_select 'tr.group' do
291 assert_select 'a', :text => 'Dave Lopper'
291 assert_select 'a', :text => 'Dave Lopper'
292 assert_select 'span.count', :text => '2'
292 assert_select 'span.count', :text => '2'
293 end
293 end
294 end
294 end
295
295
296 def test_index_with_query_grouped_by_tracker
296 def test_index_with_query_grouped_by_tracker
297 3.times {|i| Issue.generate!(:tracker_id => (i + 1))}
297 3.times {|i| Issue.generate!(:tracker_id => (i + 1))}
298
298
299 get :index, :set_filter => 1, :group_by => 'tracker', :sort => 'id:desc'
299 get :index, :set_filter => 1, :group_by => 'tracker', :sort => 'id:desc'
300 assert_response :success
300 assert_response :success
301
301
302 trackers = assigns(:issues).map(&:tracker).uniq
302 trackers = assigns(:issues).map(&:tracker).uniq
303 assert_equal [1, 2, 3], trackers.map(&:id)
303 assert_equal [1, 2, 3], trackers.map(&:id)
304 end
304 end
305
305
306 def test_index_with_query_grouped_by_tracker_in_reverse_order
306 def test_index_with_query_grouped_by_tracker_in_reverse_order
307 3.times {|i| Issue.generate!(:tracker_id => (i + 1))}
307 3.times {|i| Issue.generate!(:tracker_id => (i + 1))}
308
308
309 get :index, :set_filter => 1, :group_by => 'tracker', :sort => 'id:desc,tracker:desc'
309 get :index, :set_filter => 1, :group_by => 'tracker', :sort => 'id:desc,tracker:desc'
310 assert_response :success
310 assert_response :success
311
311
312 trackers = assigns(:issues).map(&:tracker).uniq
312 trackers = assigns(:issues).map(&:tracker).uniq
313 assert_equal [3, 2, 1], trackers.map(&:id)
313 assert_equal [3, 2, 1], trackers.map(&:id)
314 end
314 end
315
315
316 def test_index_with_query_id_and_project_id_should_set_session_query
316 def test_index_with_query_id_and_project_id_should_set_session_query
317 get :index, :project_id => 1, :query_id => 4
317 get :index, :project_id => 1, :query_id => 4
318 assert_response :success
318 assert_response :success
319 assert_kind_of Hash, session[:query]
319 assert_kind_of Hash, session[:query]
320 assert_equal 4, session[:query][:id]
320 assert_equal 4, session[:query][:id]
321 assert_equal 1, session[:query][:project_id]
321 assert_equal 1, session[:query][:project_id]
322 end
322 end
323
323
324 def test_index_with_invalid_query_id_should_respond_404
324 def test_index_with_invalid_query_id_should_respond_404
325 get :index, :project_id => 1, :query_id => 999
325 get :index, :project_id => 1, :query_id => 999
326 assert_response 404
326 assert_response 404
327 end
327 end
328
328
329 def test_index_with_cross_project_query_in_session_should_show_project_issues
329 def test_index_with_cross_project_query_in_session_should_show_project_issues
330 q = IssueQuery.create!(:name => "test", :user_id => 2, :is_public => false, :project => nil)
330 q = IssueQuery.create!(:name => "test", :user_id => 2, :is_public => false, :project => nil)
331 @request.session[:query] = {:id => q.id, :project_id => 1}
331 @request.session[:query] = {:id => q.id, :project_id => 1}
332
332
333 with_settings :display_subprojects_issues => '0' do
333 with_settings :display_subprojects_issues => '0' do
334 get :index, :project_id => 1
334 get :index, :project_id => 1
335 end
335 end
336 assert_response :success
336 assert_response :success
337 assert_not_nil assigns(:query)
337 assert_not_nil assigns(:query)
338 assert_equal q.id, assigns(:query).id
338 assert_equal q.id, assigns(:query).id
339 assert_equal 1, assigns(:query).project_id
339 assert_equal 1, assigns(:query).project_id
340 assert_equal [1], assigns(:issues).map(&:project_id).uniq
340 assert_equal [1], assigns(:issues).map(&:project_id).uniq
341 end
341 end
342
342
343 def test_private_query_should_not_be_available_to_other_users
343 def test_private_query_should_not_be_available_to_other_users
344 q = IssueQuery.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
344 q = IssueQuery.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
345 @request.session[:user_id] = 3
345 @request.session[:user_id] = 3
346
346
347 get :index, :query_id => q.id
347 get :index, :query_id => q.id
348 assert_response 403
348 assert_response 403
349 end
349 end
350
350
351 def test_private_query_should_be_available_to_its_user
351 def test_private_query_should_be_available_to_its_user
352 q = IssueQuery.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
352 q = IssueQuery.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
353 @request.session[:user_id] = 2
353 @request.session[:user_id] = 2
354
354
355 get :index, :query_id => q.id
355 get :index, :query_id => q.id
356 assert_response :success
356 assert_response :success
357 end
357 end
358
358
359 def test_public_query_should_be_available_to_other_users
359 def test_public_query_should_be_available_to_other_users
360 q = IssueQuery.create!(:name => "private", :user => User.find(2), :is_public => true, :project => nil)
360 q = IssueQuery.create!(:name => "private", :user => User.find(2), :is_public => true, :project => nil)
361 @request.session[:user_id] = 3
361 @request.session[:user_id] = 3
362
362
363 get :index, :query_id => q.id
363 get :index, :query_id => q.id
364 assert_response :success
364 assert_response :success
365 end
365 end
366
366
367 def test_index_should_omit_page_param_in_export_links
367 def test_index_should_omit_page_param_in_export_links
368 get :index, :page => 2
368 get :index, :page => 2
369 assert_response :success
369 assert_response :success
370 assert_select 'a.atom[href=/issues.atom]'
370 assert_select 'a.atom[href=/issues.atom]'
371 assert_select 'a.csv[href=/issues.csv]'
371 assert_select 'a.csv[href=/issues.csv]'
372 assert_select 'a.pdf[href=/issues.pdf]'
372 assert_select 'a.pdf[href=/issues.pdf]'
373 assert_select 'form#csv-export-form[action=/issues.csv]'
373 assert_select 'form#csv-export-form[action=/issues.csv]'
374 end
374 end
375
375
376 def test_index_csv
376 def test_index_csv
377 get :index, :format => 'csv'
377 get :index, :format => 'csv'
378 assert_response :success
378 assert_response :success
379 assert_not_nil assigns(:issues)
379 assert_not_nil assigns(:issues)
380 assert_equal 'text/csv; header=present', @response.content_type
380 assert_equal 'text/csv; header=present', @response.content_type
381 assert @response.body.starts_with?("#,")
381 assert @response.body.starts_with?("#,")
382 lines = @response.body.chomp.split("\n")
382 lines = @response.body.chomp.split("\n")
383 assert_equal assigns(:query).columns.size + 1, lines[0].split(',').size
383 assert_equal assigns(:query).columns.size + 1, lines[0].split(',').size
384 end
384 end
385
385
386 def test_index_csv_with_project
386 def test_index_csv_with_project
387 get :index, :project_id => 1, :format => 'csv'
387 get :index, :project_id => 1, :format => 'csv'
388 assert_response :success
388 assert_response :success
389 assert_not_nil assigns(:issues)
389 assert_not_nil assigns(:issues)
390 assert_equal 'text/csv; header=present', @response.content_type
390 assert_equal 'text/csv; header=present', @response.content_type
391 end
391 end
392
392
393 def test_index_csv_with_description
393 def test_index_csv_with_description
394 get :index, :format => 'csv', :description => '1'
394 get :index, :format => 'csv', :description => '1'
395 assert_response :success
395 assert_response :success
396 assert_not_nil assigns(:issues)
396 assert_not_nil assigns(:issues)
397 assert_equal 'text/csv; header=present', @response.content_type
397 assert_equal 'text/csv; header=present', @response.content_type
398 assert @response.body.starts_with?("#,")
398 assert @response.body.starts_with?("#,")
399 lines = @response.body.chomp.split("\n")
399 lines = @response.body.chomp.split("\n")
400 assert_equal assigns(:query).columns.size + 2, lines[0].split(',').size
400 assert_equal assigns(:query).columns.size + 2, lines[0].split(',').size
401 end
401 end
402
402
403 def test_index_csv_with_spent_time_column
403 def test_index_csv_with_spent_time_column
404 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :subject => 'test_index_csv_with_spent_time_column', :author_id => 2)
404 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :subject => 'test_index_csv_with_spent_time_column', :author_id => 2)
405 TimeEntry.create!(:project => issue.project, :issue => issue, :hours => 7.33, :user => User.find(2), :spent_on => Date.today)
405 TimeEntry.create!(:project => issue.project, :issue => issue, :hours => 7.33, :user => User.find(2), :spent_on => Date.today)
406
406
407 get :index, :format => 'csv', :set_filter => '1', :c => %w(subject spent_hours)
407 get :index, :format => 'csv', :set_filter => '1', :c => %w(subject spent_hours)
408 assert_response :success
408 assert_response :success
409 assert_equal 'text/csv; header=present', @response.content_type
409 assert_equal 'text/csv; header=present', @response.content_type
410 lines = @response.body.chomp.split("\n")
410 lines = @response.body.chomp.split("\n")
411 assert_include "#{issue.id},#{issue.subject},7.33", lines
411 assert_include "#{issue.id},#{issue.subject},7.33", lines
412 end
412 end
413
413
414 def test_index_csv_with_all_columns
414 def test_index_csv_with_all_columns
415 get :index, :format => 'csv', :columns => 'all'
415 get :index, :format => 'csv', :columns => 'all'
416 assert_response :success
416 assert_response :success
417 assert_not_nil assigns(:issues)
417 assert_not_nil assigns(:issues)
418 assert_equal 'text/csv; header=present', @response.content_type
418 assert_equal 'text/csv; header=present', @response.content_type
419 assert @response.body.starts_with?("#,")
419 assert @response.body.starts_with?("#,")
420 lines = @response.body.chomp.split("\n")
420 lines = @response.body.chomp.split("\n")
421 assert_equal assigns(:query).available_inline_columns.size + 1, lines[0].split(',').size
421 assert_equal assigns(:query).available_inline_columns.size + 1, lines[0].split(',').size
422 end
422 end
423
423
424 def test_index_csv_with_multi_column_field
424 def test_index_csv_with_multi_column_field
425 CustomField.find(1).update_attribute :multiple, true
425 CustomField.find(1).update_attribute :multiple, true
426 issue = Issue.find(1)
426 issue = Issue.find(1)
427 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
427 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
428 issue.save!
428 issue.save!
429
429
430 get :index, :format => 'csv', :columns => 'all'
430 get :index, :format => 'csv', :columns => 'all'
431 assert_response :success
431 assert_response :success
432 lines = @response.body.chomp.split("\n")
432 lines = @response.body.chomp.split("\n")
433 assert lines.detect {|line| line.include?('"MySQL, Oracle"')}
433 assert lines.detect {|line| line.include?('"MySQL, Oracle"')}
434 end
434 end
435
435
436 def test_index_csv_big_5
436 def test_index_csv_big_5
437 with_settings :default_language => "zh-TW" do
437 with_settings :default_language => "zh-TW" do
438 str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88"
438 str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88"
439 str_big5 = "\xa4@\xa4\xeb"
439 str_big5 = "\xa4@\xa4\xeb"
440 if str_utf8.respond_to?(:force_encoding)
440 if str_utf8.respond_to?(:force_encoding)
441 str_utf8.force_encoding('UTF-8')
441 str_utf8.force_encoding('UTF-8')
442 str_big5.force_encoding('Big5')
442 str_big5.force_encoding('Big5')
443 end
443 end
444 issue = Issue.generate!(:subject => str_utf8)
444 issue = Issue.generate!(:subject => str_utf8)
445
445
446 get :index, :project_id => 1,
446 get :index, :project_id => 1,
447 :f => ['subject'],
447 :f => ['subject'],
448 :op => '=', :values => [str_utf8],
448 :op => '=', :values => [str_utf8],
449 :format => 'csv'
449 :format => 'csv'
450 assert_equal 'text/csv; header=present', @response.content_type
450 assert_equal 'text/csv; header=present', @response.content_type
451 lines = @response.body.chomp.split("\n")
451 lines = @response.body.chomp.split("\n")
452 s1 = "\xaa\xac\xbaA"
452 s1 = "\xaa\xac\xbaA"
453 if str_utf8.respond_to?(:force_encoding)
453 if str_utf8.respond_to?(:force_encoding)
454 s1.force_encoding('Big5')
454 s1.force_encoding('Big5')
455 end
455 end
456 assert lines[0].include?(s1)
456 assert lines[0].include?(s1)
457 assert lines[1].include?(str_big5)
457 assert lines[1].include?(str_big5)
458 end
458 end
459 end
459 end
460
460
461 def test_index_csv_cannot_convert_should_be_replaced_big_5
461 def test_index_csv_cannot_convert_should_be_replaced_big_5
462 with_settings :default_language => "zh-TW" do
462 with_settings :default_language => "zh-TW" do
463 str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85"
463 str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85"
464 if str_utf8.respond_to?(:force_encoding)
464 if str_utf8.respond_to?(:force_encoding)
465 str_utf8.force_encoding('UTF-8')
465 str_utf8.force_encoding('UTF-8')
466 end
466 end
467 issue = Issue.generate!(:subject => str_utf8)
467 issue = Issue.generate!(:subject => str_utf8)
468
468
469 get :index, :project_id => 1,
469 get :index, :project_id => 1,
470 :f => ['subject'],
470 :f => ['subject'],
471 :op => '=', :values => [str_utf8],
471 :op => '=', :values => [str_utf8],
472 :c => ['status', 'subject'],
472 :c => ['status', 'subject'],
473 :format => 'csv',
473 :format => 'csv',
474 :set_filter => 1
474 :set_filter => 1
475 assert_equal 'text/csv; header=present', @response.content_type
475 assert_equal 'text/csv; header=present', @response.content_type
476 lines = @response.body.chomp.split("\n")
476 lines = @response.body.chomp.split("\n")
477 s1 = "\xaa\xac\xbaA" # status
477 s1 = "\xaa\xac\xbaA" # status
478 if str_utf8.respond_to?(:force_encoding)
478 if str_utf8.respond_to?(:force_encoding)
479 s1.force_encoding('Big5')
479 s1.force_encoding('Big5')
480 end
480 end
481 assert lines[0].include?(s1)
481 assert lines[0].include?(s1)
482 s2 = lines[1].split(",")[2]
482 s2 = lines[1].split(",")[2]
483 if s1.respond_to?(:force_encoding)
483 if s1.respond_to?(:force_encoding)
484 s3 = "\xa5H?" # subject
484 s3 = "\xa5H?" # subject
485 s3.force_encoding('Big5')
485 s3.force_encoding('Big5')
486 assert_equal s3, s2
486 assert_equal s3, s2
487 elsif RUBY_PLATFORM == 'java'
487 elsif RUBY_PLATFORM == 'java'
488 assert_equal "??", s2
488 assert_equal "??", s2
489 else
489 else
490 assert_equal "\xa5H???", s2
490 assert_equal "\xa5H???", s2
491 end
491 end
492 end
492 end
493 end
493 end
494
494
495 def test_index_csv_tw
495 def test_index_csv_tw
496 with_settings :default_language => "zh-TW" do
496 with_settings :default_language => "zh-TW" do
497 str1 = "test_index_csv_tw"
497 str1 = "test_index_csv_tw"
498 issue = Issue.generate!(:subject => str1, :estimated_hours => '1234.5')
498 issue = Issue.generate!(:subject => str1, :estimated_hours => '1234.5')
499
499
500 get :index, :project_id => 1,
500 get :index, :project_id => 1,
501 :f => ['subject'],
501 :f => ['subject'],
502 :op => '=', :values => [str1],
502 :op => '=', :values => [str1],
503 :c => ['estimated_hours', 'subject'],
503 :c => ['estimated_hours', 'subject'],
504 :format => 'csv',
504 :format => 'csv',
505 :set_filter => 1
505 :set_filter => 1
506 assert_equal 'text/csv; header=present', @response.content_type
506 assert_equal 'text/csv; header=present', @response.content_type
507 lines = @response.body.chomp.split("\n")
507 lines = @response.body.chomp.split("\n")
508 assert_equal "#{issue.id},1234.50,#{str1}", lines[1]
508 assert_equal "#{issue.id},1234.50,#{str1}", lines[1]
509 end
509 end
510 end
510 end
511
511
512 def test_index_csv_fr
512 def test_index_csv_fr
513 with_settings :default_language => "fr" do
513 with_settings :default_language => "fr" do
514 str1 = "test_index_csv_fr"
514 str1 = "test_index_csv_fr"
515 issue = Issue.generate!(:subject => str1, :estimated_hours => '1234.5')
515 issue = Issue.generate!(:subject => str1, :estimated_hours => '1234.5')
516
516
517 get :index, :project_id => 1,
517 get :index, :project_id => 1,
518 :f => ['subject'],
518 :f => ['subject'],
519 :op => '=', :values => [str1],
519 :op => '=', :values => [str1],
520 :c => ['estimated_hours', 'subject'],
520 :c => ['estimated_hours', 'subject'],
521 :format => 'csv',
521 :format => 'csv',
522 :set_filter => 1
522 :set_filter => 1
523 assert_equal 'text/csv; header=present', @response.content_type
523 assert_equal 'text/csv; header=present', @response.content_type
524 lines = @response.body.chomp.split("\n")
524 lines = @response.body.chomp.split("\n")
525 assert_equal "#{issue.id};1234,50;#{str1}", lines[1]
525 assert_equal "#{issue.id};1234,50;#{str1}", lines[1]
526 end
526 end
527 end
527 end
528
528
529 def test_index_pdf
529 def test_index_pdf
530 ["en", "zh", "zh-TW", "ja", "ko"].each do |lang|
530 ["en", "zh", "zh-TW", "ja", "ko"].each do |lang|
531 with_settings :default_language => lang do
531 with_settings :default_language => lang do
532
532
533 get :index
533 get :index
534 assert_response :success
534 assert_response :success
535 assert_template 'index'
535 assert_template 'index'
536
536
537 if lang == "ja"
537 if lang == "ja"
538 if RUBY_PLATFORM != 'java'
538 if RUBY_PLATFORM != 'java'
539 assert_equal "CP932", l(:general_pdf_encoding)
539 assert_equal "CP932", l(:general_pdf_encoding)
540 end
540 end
541 if RUBY_PLATFORM == 'java' && l(:general_pdf_encoding) == "CP932"
541 if RUBY_PLATFORM == 'java' && l(:general_pdf_encoding) == "CP932"
542 next
542 next
543 end
543 end
544 end
544 end
545
545
546 get :index, :format => 'pdf'
546 get :index, :format => 'pdf'
547 assert_response :success
547 assert_response :success
548 assert_not_nil assigns(:issues)
548 assert_not_nil assigns(:issues)
549 assert_equal 'application/pdf', @response.content_type
549 assert_equal 'application/pdf', @response.content_type
550
550
551 get :index, :project_id => 1, :format => 'pdf'
551 get :index, :project_id => 1, :format => 'pdf'
552 assert_response :success
552 assert_response :success
553 assert_not_nil assigns(:issues)
553 assert_not_nil assigns(:issues)
554 assert_equal 'application/pdf', @response.content_type
554 assert_equal 'application/pdf', @response.content_type
555
555
556 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
556 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
557 assert_response :success
557 assert_response :success
558 assert_not_nil assigns(:issues)
558 assert_not_nil assigns(:issues)
559 assert_equal 'application/pdf', @response.content_type
559 assert_equal 'application/pdf', @response.content_type
560 end
560 end
561 end
561 end
562 end
562 end
563
563
564 def test_index_pdf_with_query_grouped_by_list_custom_field
564 def test_index_pdf_with_query_grouped_by_list_custom_field
565 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
565 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
566 assert_response :success
566 assert_response :success
567 assert_not_nil assigns(:issues)
567 assert_not_nil assigns(:issues)
568 assert_not_nil assigns(:issue_count_by_group)
568 assert_not_nil assigns(:issue_count_by_group)
569 assert_equal 'application/pdf', @response.content_type
569 assert_equal 'application/pdf', @response.content_type
570 end
570 end
571
571
572 def test_index_atom
572 def test_index_atom
573 get :index, :project_id => 'ecookbook', :format => 'atom'
573 get :index, :project_id => 'ecookbook', :format => 'atom'
574 assert_response :success
574 assert_response :success
575 assert_template 'common/feed'
575 assert_template 'common/feed'
576 assert_equal 'application/atom+xml', response.content_type
576 assert_equal 'application/atom+xml', response.content_type
577
577
578 assert_select 'feed' do
578 assert_select 'feed' do
579 assert_select 'link[rel=self][href=?]', 'http://test.host/projects/ecookbook/issues.atom'
579 assert_select 'link[rel=self][href=?]', 'http://test.host/projects/ecookbook/issues.atom'
580 assert_select 'link[rel=alternate][href=?]', 'http://test.host/projects/ecookbook/issues'
580 assert_select 'link[rel=alternate][href=?]', 'http://test.host/projects/ecookbook/issues'
581 assert_select 'entry link[href=?]', 'http://test.host/issues/1'
581 assert_select 'entry link[href=?]', 'http://test.host/issues/1'
582 end
582 end
583 end
583 end
584
584
585 def test_index_sort
585 def test_index_sort
586 get :index, :sort => 'tracker,id:desc'
586 get :index, :sort => 'tracker,id:desc'
587 assert_response :success
587 assert_response :success
588
588
589 sort_params = @request.session['issues_index_sort']
589 sort_params = @request.session['issues_index_sort']
590 assert sort_params.is_a?(String)
590 assert sort_params.is_a?(String)
591 assert_equal 'tracker,id:desc', sort_params
591 assert_equal 'tracker,id:desc', sort_params
592
592
593 issues = assigns(:issues)
593 issues = assigns(:issues)
594 assert_not_nil issues
594 assert_not_nil issues
595 assert !issues.empty?
595 assert !issues.empty?
596 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
596 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
597 end
597 end
598
598
599 def test_index_sort_by_field_not_included_in_columns
599 def test_index_sort_by_field_not_included_in_columns
600 Setting.issue_list_default_columns = %w(subject author)
600 Setting.issue_list_default_columns = %w(subject author)
601 get :index, :sort => 'tracker'
601 get :index, :sort => 'tracker'
602 end
602 end
603
603
604 def test_index_sort_by_assigned_to
604 def test_index_sort_by_assigned_to
605 get :index, :sort => 'assigned_to'
605 get :index, :sort => 'assigned_to'
606 assert_response :success
606 assert_response :success
607 assignees = assigns(:issues).collect(&:assigned_to).compact
607 assignees = assigns(:issues).collect(&:assigned_to).compact
608 assert_equal assignees.sort, assignees
608 assert_equal assignees.sort, assignees
609 end
609 end
610
610
611 def test_index_sort_by_assigned_to_desc
611 def test_index_sort_by_assigned_to_desc
612 get :index, :sort => 'assigned_to:desc'
612 get :index, :sort => 'assigned_to:desc'
613 assert_response :success
613 assert_response :success
614 assignees = assigns(:issues).collect(&:assigned_to).compact
614 assignees = assigns(:issues).collect(&:assigned_to).compact
615 assert_equal assignees.sort.reverse, assignees
615 assert_equal assignees.sort.reverse, assignees
616 end
616 end
617
617
618 def test_index_group_by_assigned_to
618 def test_index_group_by_assigned_to
619 get :index, :group_by => 'assigned_to', :sort => 'priority'
619 get :index, :group_by => 'assigned_to', :sort => 'priority'
620 assert_response :success
620 assert_response :success
621 end
621 end
622
622
623 def test_index_sort_by_author
623 def test_index_sort_by_author
624 get :index, :sort => 'author'
624 get :index, :sort => 'author'
625 assert_response :success
625 assert_response :success
626 authors = assigns(:issues).collect(&:author)
626 authors = assigns(:issues).collect(&:author)
627 assert_equal authors.sort, authors
627 assert_equal authors.sort, authors
628 end
628 end
629
629
630 def test_index_sort_by_author_desc
630 def test_index_sort_by_author_desc
631 get :index, :sort => 'author:desc'
631 get :index, :sort => 'author:desc'
632 assert_response :success
632 assert_response :success
633 authors = assigns(:issues).collect(&:author)
633 authors = assigns(:issues).collect(&:author)
634 assert_equal authors.sort.reverse, authors
634 assert_equal authors.sort.reverse, authors
635 end
635 end
636
636
637 def test_index_group_by_author
637 def test_index_group_by_author
638 get :index, :group_by => 'author', :sort => 'priority'
638 get :index, :group_by => 'author', :sort => 'priority'
639 assert_response :success
639 assert_response :success
640 end
640 end
641
641
642 def test_index_sort_by_spent_hours
642 def test_index_sort_by_spent_hours
643 get :index, :sort => 'spent_hours:desc'
643 get :index, :sort => 'spent_hours:desc'
644 assert_response :success
644 assert_response :success
645 hours = assigns(:issues).collect(&:spent_hours)
645 hours = assigns(:issues).collect(&:spent_hours)
646 assert_equal hours.sort.reverse, hours
646 assert_equal hours.sort.reverse, hours
647 end
647 end
648
648
649 def test_index_sort_by_user_custom_field
649 def test_index_sort_by_user_custom_field
650 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
650 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
651 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
651 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
652 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
652 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
653 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
653 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
654 CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')
654 CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')
655
655
656 get :index, :project_id => 1, :set_filter => 1, :sort => "cf_#{cf.id},id"
656 get :index, :project_id => 1, :set_filter => 1, :sort => "cf_#{cf.id},id"
657 assert_response :success
657 assert_response :success
658
658
659 assert_equal [2, 3, 1], assigns(:issues).select {|issue| issue.custom_field_value(cf).present?}.map(&:id)
659 assert_equal [2, 3, 1], assigns(:issues).select {|issue| issue.custom_field_value(cf).present?}.map(&:id)
660 end
660 end
661
661
662 def test_index_with_columns
662 def test_index_with_columns
663 columns = ['tracker', 'subject', 'assigned_to']
663 columns = ['tracker', 'subject', 'assigned_to']
664 get :index, :set_filter => 1, :c => columns
664 get :index, :set_filter => 1, :c => columns
665 assert_response :success
665 assert_response :success
666
666
667 # query should use specified columns
667 # query should use specified columns
668 query = assigns(:query)
668 query = assigns(:query)
669 assert_kind_of IssueQuery, query
669 assert_kind_of IssueQuery, query
670 assert_equal columns, query.column_names.map(&:to_s)
670 assert_equal columns, query.column_names.map(&:to_s)
671
671
672 # columns should be stored in session
672 # columns should be stored in session
673 assert_kind_of Hash, session[:query]
673 assert_kind_of Hash, session[:query]
674 assert_kind_of Array, session[:query][:column_names]
674 assert_kind_of Array, session[:query][:column_names]
675 assert_equal columns, session[:query][:column_names].map(&:to_s)
675 assert_equal columns, session[:query][:column_names].map(&:to_s)
676
676
677 # ensure only these columns are kept in the selected columns list
677 # ensure only these columns are kept in the selected columns list
678 assert_select 'select#selected_columns option' do
678 assert_select 'select#selected_columns option' do
679 assert_select 'option', 3
679 assert_select 'option', 3
680 assert_select 'option[value=tracker]'
680 assert_select 'option[value=tracker]'
681 assert_select 'option[value=project]', 0
681 assert_select 'option[value=project]', 0
682 end
682 end
683 end
683 end
684
684
685 def test_index_without_project_should_implicitly_add_project_column_to_default_columns
685 def test_index_without_project_should_implicitly_add_project_column_to_default_columns
686 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
686 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
687 get :index, :set_filter => 1
687 get :index, :set_filter => 1
688
688
689 # query should use specified columns
689 # query should use specified columns
690 query = assigns(:query)
690 query = assigns(:query)
691 assert_kind_of IssueQuery, query
691 assert_kind_of IssueQuery, query
692 assert_equal [:project, :tracker, :subject, :assigned_to], query.columns.map(&:name)
692 assert_equal [:project, :tracker, :subject, :assigned_to], query.columns.map(&:name)
693 end
693 end
694
694
695 def test_index_without_project_and_explicit_default_columns_should_not_add_project_column
695 def test_index_without_project_and_explicit_default_columns_should_not_add_project_column
696 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
696 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
697 columns = ['tracker', 'subject', 'assigned_to']
697 columns = ['tracker', 'subject', 'assigned_to']
698 get :index, :set_filter => 1, :c => columns
698 get :index, :set_filter => 1, :c => columns
699
699
700 # query should use specified columns
700 # query should use specified columns
701 query = assigns(:query)
701 query = assigns(:query)
702 assert_kind_of IssueQuery, query
702 assert_kind_of IssueQuery, query
703 assert_equal columns.map(&:to_sym), query.columns.map(&:name)
703 assert_equal columns.map(&:to_sym), query.columns.map(&:name)
704 end
704 end
705
705
706 def test_index_with_custom_field_column
706 def test_index_with_custom_field_column
707 columns = %w(tracker subject cf_2)
707 columns = %w(tracker subject cf_2)
708 get :index, :set_filter => 1, :c => columns
708 get :index, :set_filter => 1, :c => columns
709 assert_response :success
709 assert_response :success
710
710
711 # query should use specified columns
711 # query should use specified columns
712 query = assigns(:query)
712 query = assigns(:query)
713 assert_kind_of IssueQuery, query
713 assert_kind_of IssueQuery, query
714 assert_equal columns, query.column_names.map(&:to_s)
714 assert_equal columns, query.column_names.map(&:to_s)
715
715
716 assert_select 'table.issues td.cf_2.string'
716 assert_select 'table.issues td.cf_2.string'
717 end
717 end
718
718
719 def test_index_with_multi_custom_field_column
719 def test_index_with_multi_custom_field_column
720 field = CustomField.find(1)
720 field = CustomField.find(1)
721 field.update_attribute :multiple, true
721 field.update_attribute :multiple, true
722 issue = Issue.find(1)
722 issue = Issue.find(1)
723 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
723 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
724 issue.save!
724 issue.save!
725
725
726 get :index, :set_filter => 1, :c => %w(tracker subject cf_1)
726 get :index, :set_filter => 1, :c => %w(tracker subject cf_1)
727 assert_response :success
727 assert_response :success
728
728
729 assert_select 'table.issues td.cf_1', :text => 'MySQL, Oracle'
729 assert_select 'table.issues td.cf_1', :text => 'MySQL, Oracle'
730 end
730 end
731
731
732 def test_index_with_multi_user_custom_field_column
732 def test_index_with_multi_user_custom_field_column
733 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
733 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
734 :tracker_ids => [1], :is_for_all => true)
734 :tracker_ids => [1], :is_for_all => true)
735 issue = Issue.find(1)
735 issue = Issue.find(1)
736 issue.custom_field_values = {field.id => ['2', '3']}
736 issue.custom_field_values = {field.id => ['2', '3']}
737 issue.save!
737 issue.save!
738
738
739 get :index, :set_filter => 1, :c => ['tracker', 'subject', "cf_#{field.id}"]
739 get :index, :set_filter => 1, :c => ['tracker', 'subject', "cf_#{field.id}"]
740 assert_response :success
740 assert_response :success
741
741
742 assert_select "table.issues td.cf_#{field.id}" do
742 assert_select "table.issues td.cf_#{field.id}" do
743 assert_select 'a', 2
743 assert_select 'a', 2
744 assert_select 'a[href=?]', '/users/2', :text => 'John Smith'
744 assert_select 'a[href=?]', '/users/2', :text => 'John Smith'
745 assert_select 'a[href=?]', '/users/3', :text => 'Dave Lopper'
745 assert_select 'a[href=?]', '/users/3', :text => 'Dave Lopper'
746 end
746 end
747 end
747 end
748
748
749 def test_index_with_date_column
749 def test_index_with_date_column
750 with_settings :date_format => '%d/%m/%Y' do
750 with_settings :date_format => '%d/%m/%Y' do
751 Issue.find(1).update_attribute :start_date, '1987-08-24'
751 Issue.find(1).update_attribute :start_date, '1987-08-24'
752
752
753 get :index, :set_filter => 1, :c => %w(start_date)
753 get :index, :set_filter => 1, :c => %w(start_date)
754
754
755 assert_select "table.issues td.start_date", :text => '24/08/1987'
755 assert_select "table.issues td.start_date", :text => '24/08/1987'
756 end
756 end
757 end
757 end
758
758
759 def test_index_with_done_ratio_column
759 def test_index_with_done_ratio_column
760 Issue.find(1).update_attribute :done_ratio, 40
760 Issue.find(1).update_attribute :done_ratio, 40
761
761
762 get :index, :set_filter => 1, :c => %w(done_ratio)
762 get :index, :set_filter => 1, :c => %w(done_ratio)
763
763
764 assert_select 'table.issues td.done_ratio' do
764 assert_select 'table.issues td.done_ratio' do
765 assert_select 'table.progress' do
765 assert_select 'table.progress' do
766 assert_select 'td.closed[style=?]', 'width: 40%;'
766 assert_select 'td.closed[style=?]', 'width: 40%;'
767 end
767 end
768 end
768 end
769 end
769 end
770
770
771 def test_index_with_spent_hours_column
771 def test_index_with_spent_hours_column
772 get :index, :set_filter => 1, :c => %w(subject spent_hours)
772 get :index, :set_filter => 1, :c => %w(subject spent_hours)
773
773
774 assert_select 'table.issues tr#issue-3 td.spent_hours', :text => '1.00'
774 assert_select 'table.issues tr#issue-3 td.spent_hours', :text => '1.00'
775 end
775 end
776
776
777 def test_index_should_not_show_spent_hours_column_without_permission
777 def test_index_should_not_show_spent_hours_column_without_permission
778 Role.anonymous.remove_permission! :view_time_entries
778 Role.anonymous.remove_permission! :view_time_entries
779 get :index, :set_filter => 1, :c => %w(subject spent_hours)
779 get :index, :set_filter => 1, :c => %w(subject spent_hours)
780
780
781 assert_select 'td.spent_hours', 0
781 assert_select 'td.spent_hours', 0
782 end
782 end
783
783
784 def test_index_with_fixed_version_column
784 def test_index_with_fixed_version_column
785 get :index, :set_filter => 1, :c => %w(fixed_version)
785 get :index, :set_filter => 1, :c => %w(fixed_version)
786
786
787 assert_select 'table.issues td.fixed_version' do
787 assert_select 'table.issues td.fixed_version' do
788 assert_select 'a[href=?]', '/versions/2', :text => '1.0'
788 assert_select 'a[href=?]', '/versions/2', :text => '1.0'
789 end
789 end
790 end
790 end
791
791
792 def test_index_with_relations_column
792 def test_index_with_relations_column
793 IssueRelation.delete_all
793 IssueRelation.delete_all
794 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(7))
794 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(7))
795 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(8), :issue_to => Issue.find(1))
795 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(8), :issue_to => Issue.find(1))
796 IssueRelation.create!(:relation_type => "blocks", :issue_from => Issue.find(1), :issue_to => Issue.find(11))
796 IssueRelation.create!(:relation_type => "blocks", :issue_from => Issue.find(1), :issue_to => Issue.find(11))
797 IssueRelation.create!(:relation_type => "blocks", :issue_from => Issue.find(12), :issue_to => Issue.find(2))
797 IssueRelation.create!(:relation_type => "blocks", :issue_from => Issue.find(12), :issue_to => Issue.find(2))
798
798
799 get :index, :set_filter => 1, :c => %w(subject relations)
799 get :index, :set_filter => 1, :c => %w(subject relations)
800 assert_response :success
800 assert_response :success
801 assert_select "tr#issue-1 td.relations" do
801 assert_select "tr#issue-1 td.relations" do
802 assert_select "span", 3
802 assert_select "span", 3
803 assert_select "span", :text => "Related to #7"
803 assert_select "span", :text => "Related to #7"
804 assert_select "span", :text => "Related to #8"
804 assert_select "span", :text => "Related to #8"
805 assert_select "span", :text => "Blocks #11"
805 assert_select "span", :text => "Blocks #11"
806 end
806 end
807 assert_select "tr#issue-2 td.relations" do
807 assert_select "tr#issue-2 td.relations" do
808 assert_select "span", 1
808 assert_select "span", 1
809 assert_select "span", :text => "Blocked by #12"
809 assert_select "span", :text => "Blocked by #12"
810 end
810 end
811 assert_select "tr#issue-3 td.relations" do
811 assert_select "tr#issue-3 td.relations" do
812 assert_select "span", 0
812 assert_select "span", 0
813 end
813 end
814
814
815 get :index, :set_filter => 1, :c => %w(relations), :format => 'csv'
815 get :index, :set_filter => 1, :c => %w(relations), :format => 'csv'
816 assert_response :success
816 assert_response :success
817 assert_equal 'text/csv; header=present', response.content_type
817 assert_equal 'text/csv; header=present', response.content_type
818 lines = response.body.chomp.split("\n")
818 lines = response.body.chomp.split("\n")
819 assert_include '1,"Related to #7, Related to #8, Blocks #11"', lines
819 assert_include '1,"Related to #7, Related to #8, Blocks #11"', lines
820 assert_include '2,Blocked by #12', lines
820 assert_include '2,Blocked by #12', lines
821 assert_include '3,""', lines
821 assert_include '3,""', lines
822
822
823 get :index, :set_filter => 1, :c => %w(subject relations), :format => 'pdf'
823 get :index, :set_filter => 1, :c => %w(subject relations), :format => 'pdf'
824 assert_response :success
824 assert_response :success
825 assert_equal 'application/pdf', response.content_type
825 assert_equal 'application/pdf', response.content_type
826 end
826 end
827
827
828 def test_index_with_description_column
828 def test_index_with_description_column
829 get :index, :set_filter => 1, :c => %w(subject description)
829 get :index, :set_filter => 1, :c => %w(subject description)
830
830
831 assert_select 'table.issues thead th', 3 # columns: chekbox + id + subject
831 assert_select 'table.issues thead th', 3 # columns: chekbox + id + subject
832 assert_select 'td.description[colspan=3]', :text => 'Unable to print recipes'
832 assert_select 'td.description[colspan=3]', :text => 'Unable to print recipes'
833
833
834 get :index, :set_filter => 1, :c => %w(subject description), :format => 'pdf'
834 get :index, :set_filter => 1, :c => %w(subject description), :format => 'pdf'
835 assert_response :success
835 assert_response :success
836 assert_equal 'application/pdf', response.content_type
836 assert_equal 'application/pdf', response.content_type
837 end
837 end
838
838
839 def test_index_send_html_if_query_is_invalid
839 def test_index_send_html_if_query_is_invalid
840 get :index, :f => ['start_date'], :op => {:start_date => '='}
840 get :index, :f => ['start_date'], :op => {:start_date => '='}
841 assert_equal 'text/html', @response.content_type
841 assert_equal 'text/html', @response.content_type
842 assert_template 'index'
842 assert_template 'index'
843 end
843 end
844
844
845 def test_index_send_nothing_if_query_is_invalid
845 def test_index_send_nothing_if_query_is_invalid
846 get :index, :f => ['start_date'], :op => {:start_date => '='}, :format => 'csv'
846 get :index, :f => ['start_date'], :op => {:start_date => '='}, :format => 'csv'
847 assert_equal 'text/csv', @response.content_type
847 assert_equal 'text/csv', @response.content_type
848 assert @response.body.blank?
848 assert @response.body.blank?
849 end
849 end
850
850
851 def test_show_by_anonymous
851 def test_show_by_anonymous
852 get :show, :id => 1
852 get :show, :id => 1
853 assert_response :success
853 assert_response :success
854 assert_template 'show'
854 assert_template 'show'
855 assert_equal Issue.find(1), assigns(:issue)
855 assert_equal Issue.find(1), assigns(:issue)
856
856
857 assert_select 'div.issue div.description', :text => /Unable to print recipes/
857 assert_select 'div.issue div.description', :text => /Unable to print recipes/
858
858
859 # anonymous role is allowed to add a note
859 # anonymous role is allowed to add a note
860 assert_select 'form#issue-form' do
860 assert_select 'form#issue-form' do
861 assert_select 'fieldset' do
861 assert_select 'fieldset' do
862 assert_select 'legend', :text => 'Notes'
862 assert_select 'legend', :text => 'Notes'
863 assert_select 'textarea[name=?]', 'issue[notes]'
863 assert_select 'textarea[name=?]', 'issue[notes]'
864 end
864 end
865 end
865 end
866
866
867 assert_select 'title', :text => "Bug #1: Can&#x27;t print recipes - eCookbook - Redmine"
867 assert_select 'title', :text => "Bug #1: Can&#x27;t print recipes - eCookbook - Redmine"
868 end
868 end
869
869
870 def test_show_by_manager
870 def test_show_by_manager
871 @request.session[:user_id] = 2
871 @request.session[:user_id] = 2
872 get :show, :id => 1
872 get :show, :id => 1
873 assert_response :success
873 assert_response :success
874
874
875 assert_select 'a', :text => /Quote/
875 assert_select 'a', :text => /Quote/
876
876
877 assert_select 'form#issue-form' do
877 assert_select 'form#issue-form' do
878 assert_select 'fieldset' do
878 assert_select 'fieldset' do
879 assert_select 'legend', :text => 'Change properties'
879 assert_select 'legend', :text => 'Change properties'
880 assert_select 'input[name=?]', 'issue[subject]'
880 assert_select 'input[name=?]', 'issue[subject]'
881 end
881 end
882 assert_select 'fieldset' do
882 assert_select 'fieldset' do
883 assert_select 'legend', :text => 'Log time'
883 assert_select 'legend', :text => 'Log time'
884 assert_select 'input[name=?]', 'time_entry[hours]'
884 assert_select 'input[name=?]', 'time_entry[hours]'
885 end
885 end
886 assert_select 'fieldset' do
886 assert_select 'fieldset' do
887 assert_select 'legend', :text => 'Notes'
887 assert_select 'legend', :text => 'Notes'
888 assert_select 'textarea[name=?]', 'issue[notes]'
888 assert_select 'textarea[name=?]', 'issue[notes]'
889 end
889 end
890 end
890 end
891 end
891 end
892
892
893 def test_show_should_display_update_form
893 def test_show_should_display_update_form
894 @request.session[:user_id] = 2
894 @request.session[:user_id] = 2
895 get :show, :id => 1
895 get :show, :id => 1
896 assert_response :success
896 assert_response :success
897
897
898 assert_select 'form#issue-form' do
898 assert_select 'form#issue-form' do
899 assert_select 'input[name=?]', 'issue[is_private]'
899 assert_select 'input[name=?]', 'issue[is_private]'
900 assert_select 'select[name=?]', 'issue[project_id]'
900 assert_select 'select[name=?]', 'issue[project_id]'
901 assert_select 'select[name=?]', 'issue[tracker_id]'
901 assert_select 'select[name=?]', 'issue[tracker_id]'
902 assert_select 'input[name=?]', 'issue[subject]'
902 assert_select 'input[name=?]', 'issue[subject]'
903 assert_select 'textarea[name=?]', 'issue[description]'
903 assert_select 'textarea[name=?]', 'issue[description]'
904 assert_select 'select[name=?]', 'issue[status_id]'
904 assert_select 'select[name=?]', 'issue[status_id]'
905 assert_select 'select[name=?]', 'issue[priority_id]'
905 assert_select 'select[name=?]', 'issue[priority_id]'
906 assert_select 'select[name=?]', 'issue[assigned_to_id]'
906 assert_select 'select[name=?]', 'issue[assigned_to_id]'
907 assert_select 'select[name=?]', 'issue[category_id]'
907 assert_select 'select[name=?]', 'issue[category_id]'
908 assert_select 'select[name=?]', 'issue[fixed_version_id]'
908 assert_select 'select[name=?]', 'issue[fixed_version_id]'
909 assert_select 'input[name=?]', 'issue[parent_issue_id]'
909 assert_select 'input[name=?]', 'issue[parent_issue_id]'
910 assert_select 'input[name=?]', 'issue[start_date]'
910 assert_select 'input[name=?]', 'issue[start_date]'
911 assert_select 'input[name=?]', 'issue[due_date]'
911 assert_select 'input[name=?]', 'issue[due_date]'
912 assert_select 'select[name=?]', 'issue[done_ratio]'
912 assert_select 'select[name=?]', 'issue[done_ratio]'
913 assert_select 'input[name=?]', 'issue[custom_field_values][2]'
913 assert_select 'input[name=?]', 'issue[custom_field_values][2]'
914 assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0
914 assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0
915 assert_select 'textarea[name=?]', 'issue[notes]'
915 assert_select 'textarea[name=?]', 'issue[notes]'
916 end
916 end
917 end
917 end
918
918
919 def test_show_should_display_update_form_with_minimal_permissions
919 def test_show_should_display_update_form_with_minimal_permissions
920 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
920 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
921 WorkflowTransition.delete_all :role_id => 1
921 WorkflowTransition.delete_all :role_id => 1
922
922
923 @request.session[:user_id] = 2
923 @request.session[:user_id] = 2
924 get :show, :id => 1
924 get :show, :id => 1
925 assert_response :success
925 assert_response :success
926
926
927 assert_select 'form#issue-form' do
927 assert_select 'form#issue-form' do
928 assert_select 'input[name=?]', 'issue[is_private]', 0
928 assert_select 'input[name=?]', 'issue[is_private]', 0
929 assert_select 'select[name=?]', 'issue[project_id]', 0
929 assert_select 'select[name=?]', 'issue[project_id]', 0
930 assert_select 'select[name=?]', 'issue[tracker_id]', 0
930 assert_select 'select[name=?]', 'issue[tracker_id]', 0
931 assert_select 'input[name=?]', 'issue[subject]', 0
931 assert_select 'input[name=?]', 'issue[subject]', 0
932 assert_select 'textarea[name=?]', 'issue[description]', 0
932 assert_select 'textarea[name=?]', 'issue[description]', 0
933 assert_select 'select[name=?]', 'issue[status_id]', 0
933 assert_select 'select[name=?]', 'issue[status_id]', 0
934 assert_select 'select[name=?]', 'issue[priority_id]', 0
934 assert_select 'select[name=?]', 'issue[priority_id]', 0
935 assert_select 'select[name=?]', 'issue[assigned_to_id]', 0
935 assert_select 'select[name=?]', 'issue[assigned_to_id]', 0
936 assert_select 'select[name=?]', 'issue[category_id]', 0
936 assert_select 'select[name=?]', 'issue[category_id]', 0
937 assert_select 'select[name=?]', 'issue[fixed_version_id]', 0
937 assert_select 'select[name=?]', 'issue[fixed_version_id]', 0
938 assert_select 'input[name=?]', 'issue[parent_issue_id]', 0
938 assert_select 'input[name=?]', 'issue[parent_issue_id]', 0
939 assert_select 'input[name=?]', 'issue[start_date]', 0
939 assert_select 'input[name=?]', 'issue[start_date]', 0
940 assert_select 'input[name=?]', 'issue[due_date]', 0
940 assert_select 'input[name=?]', 'issue[due_date]', 0
941 assert_select 'select[name=?]', 'issue[done_ratio]', 0
941 assert_select 'select[name=?]', 'issue[done_ratio]', 0
942 assert_select 'input[name=?]', 'issue[custom_field_values][2]', 0
942 assert_select 'input[name=?]', 'issue[custom_field_values][2]', 0
943 assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0
943 assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0
944 assert_select 'textarea[name=?]', 'issue[notes]'
944 assert_select 'textarea[name=?]', 'issue[notes]'
945 end
945 end
946 end
946 end
947
947
948 def test_show_should_display_update_form_with_workflow_permissions
948 def test_show_should_display_update_form_with_workflow_permissions
949 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
949 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
950
950
951 @request.session[:user_id] = 2
951 @request.session[:user_id] = 2
952 get :show, :id => 1
952 get :show, :id => 1
953 assert_response :success
953 assert_response :success
954
954
955 assert_select 'form#issue-form' do
955 assert_select 'form#issue-form' do
956 assert_select 'input[name=?]', 'issue[is_private]', 0
956 assert_select 'input[name=?]', 'issue[is_private]', 0
957 assert_select 'select[name=?]', 'issue[project_id]', 0
957 assert_select 'select[name=?]', 'issue[project_id]', 0
958 assert_select 'select[name=?]', 'issue[tracker_id]', 0
958 assert_select 'select[name=?]', 'issue[tracker_id]', 0
959 assert_select 'input[name=?]', 'issue[subject]', 0
959 assert_select 'input[name=?]', 'issue[subject]', 0
960 assert_select 'textarea[name=?]', 'issue[description]', 0
960 assert_select 'textarea[name=?]', 'issue[description]', 0
961 assert_select 'select[name=?]', 'issue[status_id]'
961 assert_select 'select[name=?]', 'issue[status_id]'
962 assert_select 'select[name=?]', 'issue[priority_id]', 0
962 assert_select 'select[name=?]', 'issue[priority_id]', 0
963 assert_select 'select[name=?]', 'issue[assigned_to_id]'
963 assert_select 'select[name=?]', 'issue[assigned_to_id]'
964 assert_select 'select[name=?]', 'issue[category_id]', 0
964 assert_select 'select[name=?]', 'issue[category_id]', 0
965 assert_select 'select[name=?]', 'issue[fixed_version_id]'
965 assert_select 'select[name=?]', 'issue[fixed_version_id]'
966 assert_select 'input[name=?]', 'issue[parent_issue_id]', 0
966 assert_select 'input[name=?]', 'issue[parent_issue_id]', 0
967 assert_select 'input[name=?]', 'issue[start_date]', 0
967 assert_select 'input[name=?]', 'issue[start_date]', 0
968 assert_select 'input[name=?]', 'issue[due_date]', 0
968 assert_select 'input[name=?]', 'issue[due_date]', 0
969 assert_select 'select[name=?]', 'issue[done_ratio]'
969 assert_select 'select[name=?]', 'issue[done_ratio]'
970 assert_select 'input[name=?]', 'issue[custom_field_values][2]', 0
970 assert_select 'input[name=?]', 'issue[custom_field_values][2]', 0
971 assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0
971 assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0
972 assert_select 'textarea[name=?]', 'issue[notes]'
972 assert_select 'textarea[name=?]', 'issue[notes]'
973 end
973 end
974 end
974 end
975
975
976 def test_show_should_not_display_update_form_without_permissions
976 def test_show_should_not_display_update_form_without_permissions
977 Role.find(1).update_attribute :permissions, [:view_issues]
977 Role.find(1).update_attribute :permissions, [:view_issues]
978
978
979 @request.session[:user_id] = 2
979 @request.session[:user_id] = 2
980 get :show, :id => 1
980 get :show, :id => 1
981 assert_response :success
981 assert_response :success
982
982
983 assert_select 'form#issue-form', 0
983 assert_select 'form#issue-form', 0
984 end
984 end
985
985
986 def test_update_form_should_not_display_inactive_enumerations
986 def test_update_form_should_not_display_inactive_enumerations
987 assert !IssuePriority.find(15).active?
987 assert !IssuePriority.find(15).active?
988
988
989 @request.session[:user_id] = 2
989 @request.session[:user_id] = 2
990 get :show, :id => 1
990 get :show, :id => 1
991 assert_response :success
991 assert_response :success
992
992
993 assert_select 'form#issue-form' do
993 assert_select 'form#issue-form' do
994 assert_select 'select[name=?]', 'issue[priority_id]' do
994 assert_select 'select[name=?]', 'issue[priority_id]' do
995 assert_select 'option[value=4]'
995 assert_select 'option[value=4]'
996 assert_select 'option[value=15]', 0
996 assert_select 'option[value=15]', 0
997 end
997 end
998 end
998 end
999 end
999 end
1000
1000
1001 def test_update_form_should_allow_attachment_upload
1001 def test_update_form_should_allow_attachment_upload
1002 @request.session[:user_id] = 2
1002 @request.session[:user_id] = 2
1003 get :show, :id => 1
1003 get :show, :id => 1
1004
1004
1005 assert_select 'form#issue-form[method=post][enctype=multipart/form-data]' do
1005 assert_select 'form#issue-form[method=post][enctype=multipart/form-data]' do
1006 assert_select 'input[type=file][name=?]', 'attachments[dummy][file]'
1006 assert_select 'input[type=file][name=?]', 'attachments[dummy][file]'
1007 end
1007 end
1008 end
1008 end
1009
1009
1010 def test_show_should_deny_anonymous_access_without_permission
1010 def test_show_should_deny_anonymous_access_without_permission
1011 Role.anonymous.remove_permission!(:view_issues)
1011 Role.anonymous.remove_permission!(:view_issues)
1012 get :show, :id => 1
1012 get :show, :id => 1
1013 assert_response :redirect
1013 assert_response :redirect
1014 end
1014 end
1015
1015
1016 def test_show_should_deny_anonymous_access_to_private_issue
1016 def test_show_should_deny_anonymous_access_to_private_issue
1017 Issue.update_all(["is_private = ?", true], "id = 1")
1017 Issue.update_all(["is_private = ?", true], "id = 1")
1018 get :show, :id => 1
1018 get :show, :id => 1
1019 assert_response :redirect
1019 assert_response :redirect
1020 end
1020 end
1021
1021
1022 def test_show_should_deny_non_member_access_without_permission
1022 def test_show_should_deny_non_member_access_without_permission
1023 Role.non_member.remove_permission!(:view_issues)
1023 Role.non_member.remove_permission!(:view_issues)
1024 @request.session[:user_id] = 9
1024 @request.session[:user_id] = 9
1025 get :show, :id => 1
1025 get :show, :id => 1
1026 assert_response 403
1026 assert_response 403
1027 end
1027 end
1028
1028
1029 def test_show_should_deny_non_member_access_to_private_issue
1029 def test_show_should_deny_non_member_access_to_private_issue
1030 Issue.update_all(["is_private = ?", true], "id = 1")
1030 Issue.update_all(["is_private = ?", true], "id = 1")
1031 @request.session[:user_id] = 9
1031 @request.session[:user_id] = 9
1032 get :show, :id => 1
1032 get :show, :id => 1
1033 assert_response 403
1033 assert_response 403
1034 end
1034 end
1035
1035
1036 def test_show_should_deny_member_access_without_permission
1036 def test_show_should_deny_member_access_without_permission
1037 Role.find(1).remove_permission!(:view_issues)
1037 Role.find(1).remove_permission!(:view_issues)
1038 @request.session[:user_id] = 2
1038 @request.session[:user_id] = 2
1039 get :show, :id => 1
1039 get :show, :id => 1
1040 assert_response 403
1040 assert_response 403
1041 end
1041 end
1042
1042
1043 def test_show_should_deny_member_access_to_private_issue_without_permission
1043 def test_show_should_deny_member_access_to_private_issue_without_permission
1044 Issue.update_all(["is_private = ?", true], "id = 1")
1044 Issue.update_all(["is_private = ?", true], "id = 1")
1045 @request.session[:user_id] = 3
1045 @request.session[:user_id] = 3
1046 get :show, :id => 1
1046 get :show, :id => 1
1047 assert_response 403
1047 assert_response 403
1048 end
1048 end
1049
1049
1050 def test_show_should_allow_author_access_to_private_issue
1050 def test_show_should_allow_author_access_to_private_issue
1051 Issue.update_all(["is_private = ?, author_id = 3", true], "id = 1")
1051 Issue.update_all(["is_private = ?, author_id = 3", true], "id = 1")
1052 @request.session[:user_id] = 3
1052 @request.session[:user_id] = 3
1053 get :show, :id => 1
1053 get :show, :id => 1
1054 assert_response :success
1054 assert_response :success
1055 end
1055 end
1056
1056
1057 def test_show_should_allow_assignee_access_to_private_issue
1057 def test_show_should_allow_assignee_access_to_private_issue
1058 Issue.update_all(["is_private = ?, assigned_to_id = 3", true], "id = 1")
1058 Issue.update_all(["is_private = ?, assigned_to_id = 3", true], "id = 1")
1059 @request.session[:user_id] = 3
1059 @request.session[:user_id] = 3
1060 get :show, :id => 1
1060 get :show, :id => 1
1061 assert_response :success
1061 assert_response :success
1062 end
1062 end
1063
1063
1064 def test_show_should_allow_member_access_to_private_issue_with_permission
1064 def test_show_should_allow_member_access_to_private_issue_with_permission
1065 Issue.update_all(["is_private = ?", true], "id = 1")
1065 Issue.update_all(["is_private = ?", true], "id = 1")
1066 User.find(3).roles_for_project(Project.find(1)).first.update_attribute :issues_visibility, 'all'
1066 User.find(3).roles_for_project(Project.find(1)).first.update_attribute :issues_visibility, 'all'
1067 @request.session[:user_id] = 3
1067 @request.session[:user_id] = 3
1068 get :show, :id => 1
1068 get :show, :id => 1
1069 assert_response :success
1069 assert_response :success
1070 end
1070 end
1071
1071
1072 def test_show_should_not_disclose_relations_to_invisible_issues
1072 def test_show_should_not_disclose_relations_to_invisible_issues
1073 Setting.cross_project_issue_relations = '1'
1073 Setting.cross_project_issue_relations = '1'
1074 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
1074 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
1075 # Relation to a private project issue
1075 # Relation to a private project issue
1076 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
1076 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
1077
1077
1078 get :show, :id => 1
1078 get :show, :id => 1
1079 assert_response :success
1079 assert_response :success
1080
1080
1081 assert_select 'div#relations' do
1081 assert_select 'div#relations' do
1082 assert_select 'a', :text => /#2$/
1082 assert_select 'a', :text => /#2$/
1083 assert_select 'a', :text => /#4$/, :count => 0
1083 assert_select 'a', :text => /#4$/, :count => 0
1084 end
1084 end
1085 end
1085 end
1086
1086
1087 def test_show_should_list_subtasks
1087 def test_show_should_list_subtasks
1088 Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
1088 Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
1089
1089
1090 get :show, :id => 1
1090 get :show, :id => 1
1091 assert_response :success
1091 assert_response :success
1092
1092
1093 assert_select 'div#issue_tree' do
1093 assert_select 'div#issue_tree' do
1094 assert_select 'td.subject', :text => /Child Issue/
1094 assert_select 'td.subject', :text => /Child Issue/
1095 end
1095 end
1096 end
1096 end
1097
1097
1098 def test_show_should_list_parents
1098 def test_show_should_list_parents
1099 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
1099 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
1100
1100
1101 get :show, :id => issue.id
1101 get :show, :id => issue.id
1102 assert_response :success
1102 assert_response :success
1103
1103
1104 assert_select 'div.subject' do
1104 assert_select 'div.subject' do
1105 assert_select 'h3', 'Child Issue'
1105 assert_select 'h3', 'Child Issue'
1106 assert_select 'a[href=/issues/1]'
1106 assert_select 'a[href=/issues/1]'
1107 end
1107 end
1108 end
1108 end
1109
1109
1110 def test_show_should_not_display_prev_next_links_without_query_in_session
1110 def test_show_should_not_display_prev_next_links_without_query_in_session
1111 get :show, :id => 1
1111 get :show, :id => 1
1112 assert_response :success
1112 assert_response :success
1113 assert_nil assigns(:prev_issue_id)
1113 assert_nil assigns(:prev_issue_id)
1114 assert_nil assigns(:next_issue_id)
1114 assert_nil assigns(:next_issue_id)
1115
1115
1116 assert_select 'div.next-prev-links', 0
1116 assert_select 'div.next-prev-links', 0
1117 end
1117 end
1118
1118
1119 def test_show_should_display_prev_next_links_with_query_in_session
1119 def test_show_should_display_prev_next_links_with_query_in_session
1120 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
1120 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
1121 @request.session['issues_index_sort'] = 'id'
1121 @request.session['issues_index_sort'] = 'id'
1122
1122
1123 with_settings :display_subprojects_issues => '0' do
1123 with_settings :display_subprojects_issues => '0' do
1124 get :show, :id => 3
1124 get :show, :id => 3
1125 end
1125 end
1126
1126
1127 assert_response :success
1127 assert_response :success
1128 # Previous and next issues for all projects
1128 # Previous and next issues for all projects
1129 assert_equal 2, assigns(:prev_issue_id)
1129 assert_equal 2, assigns(:prev_issue_id)
1130 assert_equal 5, assigns(:next_issue_id)
1130 assert_equal 5, assigns(:next_issue_id)
1131
1131
1132 count = Issue.open.visible.count
1132 count = Issue.open.visible.count
1133
1133
1134 assert_select 'div.next-prev-links' do
1134 assert_select 'div.next-prev-links' do
1135 assert_select 'a[href=/issues/2]', :text => /Previous/
1135 assert_select 'a[href=/issues/2]', :text => /Previous/
1136 assert_select 'a[href=/issues/5]', :text => /Next/
1136 assert_select 'a[href=/issues/5]', :text => /Next/
1137 assert_select 'span.position', :text => "3 of #{count}"
1137 assert_select 'span.position', :text => "3 of #{count}"
1138 end
1138 end
1139 end
1139 end
1140
1140
1141 def test_show_should_display_prev_next_links_with_saved_query_in_session
1141 def test_show_should_display_prev_next_links_with_saved_query_in_session
1142 query = IssueQuery.create!(:name => 'test', :is_public => true, :user_id => 1,
1142 query = IssueQuery.create!(:name => 'test', :is_public => true, :user_id => 1,
1143 :filters => {'status_id' => {:values => ['5'], :operator => '='}},
1143 :filters => {'status_id' => {:values => ['5'], :operator => '='}},
1144 :sort_criteria => [['id', 'asc']])
1144 :sort_criteria => [['id', 'asc']])
1145 @request.session[:query] = {:id => query.id, :project_id => nil}
1145 @request.session[:query] = {:id => query.id, :project_id => nil}
1146
1146
1147 get :show, :id => 11
1147 get :show, :id => 11
1148
1148
1149 assert_response :success
1149 assert_response :success
1150 assert_equal query, assigns(:query)
1150 assert_equal query, assigns(:query)
1151 # Previous and next issues for all projects
1151 # Previous and next issues for all projects
1152 assert_equal 8, assigns(:prev_issue_id)
1152 assert_equal 8, assigns(:prev_issue_id)
1153 assert_equal 12, assigns(:next_issue_id)
1153 assert_equal 12, assigns(:next_issue_id)
1154
1154
1155 assert_select 'div.next-prev-links' do
1155 assert_select 'div.next-prev-links' do
1156 assert_select 'a[href=/issues/8]', :text => /Previous/
1156 assert_select 'a[href=/issues/8]', :text => /Previous/
1157 assert_select 'a[href=/issues/12]', :text => /Next/
1157 assert_select 'a[href=/issues/12]', :text => /Next/
1158 end
1158 end
1159 end
1159 end
1160
1160
1161 def test_show_should_display_prev_next_links_with_query_and_sort_on_association
1161 def test_show_should_display_prev_next_links_with_query_and_sort_on_association
1162 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
1162 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
1163
1163
1164 %w(project tracker status priority author assigned_to category fixed_version).each do |assoc_sort|
1164 %w(project tracker status priority author assigned_to category fixed_version).each do |assoc_sort|
1165 @request.session['issues_index_sort'] = assoc_sort
1165 @request.session['issues_index_sort'] = assoc_sort
1166
1166
1167 get :show, :id => 3
1167 get :show, :id => 3
1168 assert_response :success, "Wrong response status for #{assoc_sort} sort"
1168 assert_response :success, "Wrong response status for #{assoc_sort} sort"
1169
1169
1170 assert_select 'div.next-prev-links' do
1170 assert_select 'div.next-prev-links' do
1171 assert_select 'a', :text => /(Previous|Next)/
1171 assert_select 'a', :text => /(Previous|Next)/
1172 end
1172 end
1173 end
1173 end
1174 end
1174 end
1175
1175
1176 def test_show_should_display_prev_next_links_with_project_query_in_session
1176 def test_show_should_display_prev_next_links_with_project_query_in_session
1177 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
1177 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
1178 @request.session['issues_index_sort'] = 'id'
1178 @request.session['issues_index_sort'] = 'id'
1179
1179
1180 with_settings :display_subprojects_issues => '0' do
1180 with_settings :display_subprojects_issues => '0' do
1181 get :show, :id => 3
1181 get :show, :id => 3
1182 end
1182 end
1183
1183
1184 assert_response :success
1184 assert_response :success
1185 # Previous and next issues inside project
1185 # Previous and next issues inside project
1186 assert_equal 2, assigns(:prev_issue_id)
1186 assert_equal 2, assigns(:prev_issue_id)
1187 assert_equal 7, assigns(:next_issue_id)
1187 assert_equal 7, assigns(:next_issue_id)
1188
1188
1189 assert_select 'div.next-prev-links' do
1189 assert_select 'div.next-prev-links' do
1190 assert_select 'a[href=/issues/2]', :text => /Previous/
1190 assert_select 'a[href=/issues/2]', :text => /Previous/
1191 assert_select 'a[href=/issues/7]', :text => /Next/
1191 assert_select 'a[href=/issues/7]', :text => /Next/
1192 end
1192 end
1193 end
1193 end
1194
1194
1195 def test_show_should_not_display_prev_link_for_first_issue
1195 def test_show_should_not_display_prev_link_for_first_issue
1196 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
1196 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
1197 @request.session['issues_index_sort'] = 'id'
1197 @request.session['issues_index_sort'] = 'id'
1198
1198
1199 with_settings :display_subprojects_issues => '0' do
1199 with_settings :display_subprojects_issues => '0' do
1200 get :show, :id => 1
1200 get :show, :id => 1
1201 end
1201 end
1202
1202
1203 assert_response :success
1203 assert_response :success
1204 assert_nil assigns(:prev_issue_id)
1204 assert_nil assigns(:prev_issue_id)
1205 assert_equal 2, assigns(:next_issue_id)
1205 assert_equal 2, assigns(:next_issue_id)
1206
1206
1207 assert_select 'div.next-prev-links' do
1207 assert_select 'div.next-prev-links' do
1208 assert_select 'a', :text => /Previous/, :count => 0
1208 assert_select 'a', :text => /Previous/, :count => 0
1209 assert_select 'a[href=/issues/2]', :text => /Next/
1209 assert_select 'a[href=/issues/2]', :text => /Next/
1210 end
1210 end
1211 end
1211 end
1212
1212
1213 def test_show_should_not_display_prev_next_links_for_issue_not_in_query_results
1213 def test_show_should_not_display_prev_next_links_for_issue_not_in_query_results
1214 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'c'}}, :project_id => 1}
1214 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'c'}}, :project_id => 1}
1215 @request.session['issues_index_sort'] = 'id'
1215 @request.session['issues_index_sort'] = 'id'
1216
1216
1217 get :show, :id => 1
1217 get :show, :id => 1
1218
1218
1219 assert_response :success
1219 assert_response :success
1220 assert_nil assigns(:prev_issue_id)
1220 assert_nil assigns(:prev_issue_id)
1221 assert_nil assigns(:next_issue_id)
1221 assert_nil assigns(:next_issue_id)
1222
1222
1223 assert_select 'a', :text => /Previous/, :count => 0
1223 assert_select 'a', :text => /Previous/, :count => 0
1224 assert_select 'a', :text => /Next/, :count => 0
1224 assert_select 'a', :text => /Next/, :count => 0
1225 end
1225 end
1226
1226
1227 def test_show_show_should_display_prev_next_links_with_query_sort_by_user_custom_field
1227 def test_show_show_should_display_prev_next_links_with_query_sort_by_user_custom_field
1228 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
1228 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
1229 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
1229 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
1230 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
1230 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
1231 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
1231 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
1232 CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')
1232 CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')
1233
1233
1234 query = IssueQuery.create!(:name => 'test', :is_public => true, :user_id => 1, :filters => {},
1234 query = IssueQuery.create!(:name => 'test', :is_public => true, :user_id => 1, :filters => {},
1235 :sort_criteria => [["cf_#{cf.id}", 'asc'], ['id', 'asc']])
1235 :sort_criteria => [["cf_#{cf.id}", 'asc'], ['id', 'asc']])
1236 @request.session[:query] = {:id => query.id, :project_id => nil}
1236 @request.session[:query] = {:id => query.id, :project_id => nil}
1237
1237
1238 get :show, :id => 3
1238 get :show, :id => 3
1239 assert_response :success
1239 assert_response :success
1240
1240
1241 assert_equal 2, assigns(:prev_issue_id)
1241 assert_equal 2, assigns(:prev_issue_id)
1242 assert_equal 1, assigns(:next_issue_id)
1242 assert_equal 1, assigns(:next_issue_id)
1243
1243
1244 assert_select 'div.next-prev-links' do
1244 assert_select 'div.next-prev-links' do
1245 assert_select 'a[href=/issues/2]', :text => /Previous/
1245 assert_select 'a[href=/issues/2]', :text => /Previous/
1246 assert_select 'a[href=/issues/1]', :text => /Next/
1246 assert_select 'a[href=/issues/1]', :text => /Next/
1247 end
1247 end
1248 end
1248 end
1249
1249
1250 def test_show_should_display_link_to_the_assignee
1250 def test_show_should_display_link_to_the_assignee
1251 get :show, :id => 2
1251 get :show, :id => 2
1252 assert_response :success
1252 assert_response :success
1253 assert_select '.assigned-to' do
1253 assert_select '.assigned-to' do
1254 assert_select 'a[href=/users/3]'
1254 assert_select 'a[href=/users/3]'
1255 end
1255 end
1256 end
1256 end
1257
1257
1258 def test_show_should_display_visible_changesets_from_other_projects
1258 def test_show_should_display_visible_changesets_from_other_projects
1259 project = Project.find(2)
1259 project = Project.find(2)
1260 issue = project.issues.first
1260 issue = project.issues.first
1261 issue.changeset_ids = [102]
1261 issue.changeset_ids = [102]
1262 issue.save!
1262 issue.save!
1263 # changesets from other projects should be displayed even if repository
1263 # changesets from other projects should be displayed even if repository
1264 # is disabled on issue's project
1264 # is disabled on issue's project
1265 project.disable_module! :repository
1265 project.disable_module! :repository
1266
1266
1267 @request.session[:user_id] = 2
1267 @request.session[:user_id] = 2
1268 get :show, :id => issue.id
1268 get :show, :id => issue.id
1269
1269
1270 assert_select 'a[href=?]', '/projects/ecookbook/repository/revisions/3'
1270 assert_select 'a[href=?]', '/projects/ecookbook/repository/revisions/3'
1271 end
1271 end
1272
1272
1273 def test_show_should_display_watchers
1273 def test_show_should_display_watchers
1274 @request.session[:user_id] = 2
1274 @request.session[:user_id] = 2
1275 Issue.find(1).add_watcher User.find(2)
1275 Issue.find(1).add_watcher User.find(2)
1276
1276
1277 get :show, :id => 1
1277 get :show, :id => 1
1278 assert_select 'div#watchers ul' do
1278 assert_select 'div#watchers ul' do
1279 assert_select 'li' do
1279 assert_select 'li' do
1280 assert_select 'a[href=/users/2]'
1280 assert_select 'a[href=/users/2]'
1281 assert_select 'a img[alt=Delete]'
1281 assert_select 'a img[alt=Delete]'
1282 end
1282 end
1283 end
1283 end
1284 end
1284 end
1285
1285
1286 def test_show_should_display_watchers_with_gravatars
1286 def test_show_should_display_watchers_with_gravatars
1287 @request.session[:user_id] = 2
1287 @request.session[:user_id] = 2
1288 Issue.find(1).add_watcher User.find(2)
1288 Issue.find(1).add_watcher User.find(2)
1289
1289
1290 with_settings :gravatar_enabled => '1' do
1290 with_settings :gravatar_enabled => '1' do
1291 get :show, :id => 1
1291 get :show, :id => 1
1292 end
1292 end
1293
1293
1294 assert_select 'div#watchers ul' do
1294 assert_select 'div#watchers ul' do
1295 assert_select 'li' do
1295 assert_select 'li' do
1296 assert_select 'img.gravatar'
1296 assert_select 'img.gravatar'
1297 assert_select 'a[href=/users/2]'
1297 assert_select 'a[href=/users/2]'
1298 assert_select 'a img[alt=Delete]'
1298 assert_select 'a img[alt=Delete]'
1299 end
1299 end
1300 end
1300 end
1301 end
1301 end
1302
1302
1303 def test_show_with_thumbnails_enabled_should_display_thumbnails
1303 def test_show_with_thumbnails_enabled_should_display_thumbnails
1304 @request.session[:user_id] = 2
1304 @request.session[:user_id] = 2
1305
1305
1306 with_settings :thumbnails_enabled => '1' do
1306 with_settings :thumbnails_enabled => '1' do
1307 get :show, :id => 14
1307 get :show, :id => 14
1308 assert_response :success
1308 assert_response :success
1309 end
1309 end
1310
1310
1311 assert_select 'div.thumbnails' do
1311 assert_select 'div.thumbnails' do
1312 assert_select 'a[href=/attachments/16/testfile.png]' do
1312 assert_select 'a[href=/attachments/16/testfile.png]' do
1313 assert_select 'img[src=/attachments/thumbnail/16]'
1313 assert_select 'img[src=/attachments/thumbnail/16]'
1314 end
1314 end
1315 end
1315 end
1316 end
1316 end
1317
1317
1318 def test_show_with_thumbnails_disabled_should_not_display_thumbnails
1318 def test_show_with_thumbnails_disabled_should_not_display_thumbnails
1319 @request.session[:user_id] = 2
1319 @request.session[:user_id] = 2
1320
1320
1321 with_settings :thumbnails_enabled => '0' do
1321 with_settings :thumbnails_enabled => '0' do
1322 get :show, :id => 14
1322 get :show, :id => 14
1323 assert_response :success
1323 assert_response :success
1324 end
1324 end
1325
1325
1326 assert_select 'div.thumbnails', 0
1326 assert_select 'div.thumbnails', 0
1327 end
1327 end
1328
1328
1329 def test_show_with_multi_custom_field
1329 def test_show_with_multi_custom_field
1330 field = CustomField.find(1)
1330 field = CustomField.find(1)
1331 field.update_attribute :multiple, true
1331 field.update_attribute :multiple, true
1332 issue = Issue.find(1)
1332 issue = Issue.find(1)
1333 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
1333 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
1334 issue.save!
1334 issue.save!
1335
1335
1336 get :show, :id => 1
1336 get :show, :id => 1
1337 assert_response :success
1337 assert_response :success
1338
1338
1339 assert_select 'td', :text => 'MySQL, Oracle'
1339 assert_select 'td', :text => 'MySQL, Oracle'
1340 end
1340 end
1341
1341
1342 def test_show_with_multi_user_custom_field
1342 def test_show_with_multi_user_custom_field
1343 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1343 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1344 :tracker_ids => [1], :is_for_all => true)
1344 :tracker_ids => [1], :is_for_all => true)
1345 issue = Issue.find(1)
1345 issue = Issue.find(1)
1346 issue.custom_field_values = {field.id => ['2', '3']}
1346 issue.custom_field_values = {field.id => ['2', '3']}
1347 issue.save!
1347 issue.save!
1348
1348
1349 get :show, :id => 1
1349 get :show, :id => 1
1350 assert_response :success
1350 assert_response :success
1351
1351
1352 # TODO: should display links
1352 # TODO: should display links
1353 assert_select 'td', :text => 'Dave Lopper, John Smith'
1353 assert_select 'td', :text => 'Dave Lopper, John Smith'
1354 end
1354 end
1355
1355
1356 def test_show_should_display_private_notes_with_permission_only
1356 def test_show_should_display_private_notes_with_permission_only
1357 journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true, :user_id => 1)
1357 journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true, :user_id => 1)
1358 @request.session[:user_id] = 2
1358 @request.session[:user_id] = 2
1359
1359
1360 get :show, :id => 2
1360 get :show, :id => 2
1361 assert_response :success
1361 assert_response :success
1362 assert_include journal, assigns(:journals)
1362 assert_include journal, assigns(:journals)
1363
1363
1364 Role.find(1).remove_permission! :view_private_notes
1364 Role.find(1).remove_permission! :view_private_notes
1365 get :show, :id => 2
1365 get :show, :id => 2
1366 assert_response :success
1366 assert_response :success
1367 assert_not_include journal, assigns(:journals)
1367 assert_not_include journal, assigns(:journals)
1368 end
1368 end
1369
1369
1370 def test_show_atom
1370 def test_show_atom
1371 get :show, :id => 2, :format => 'atom'
1371 get :show, :id => 2, :format => 'atom'
1372 assert_response :success
1372 assert_response :success
1373 assert_template 'journals/index'
1373 assert_template 'journals/index'
1374 # Inline image
1374 # Inline image
1375 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
1375 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
1376 end
1376 end
1377
1377
1378 def test_show_export_to_pdf
1378 def test_show_export_to_pdf
1379 get :show, :id => 3, :format => 'pdf'
1379 get :show, :id => 3, :format => 'pdf'
1380 assert_response :success
1380 assert_response :success
1381 assert_equal 'application/pdf', @response.content_type
1381 assert_equal 'application/pdf', @response.content_type
1382 assert @response.body.starts_with?('%PDF')
1382 assert @response.body.starts_with?('%PDF')
1383 assert_not_nil assigns(:issue)
1383 assert_not_nil assigns(:issue)
1384 end
1384 end
1385
1385
1386 def test_show_export_to_pdf_with_ancestors
1386 def test_show_export_to_pdf_with_ancestors
1387 issue = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => 1)
1387 issue = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => 1)
1388
1388
1389 get :show, :id => issue.id, :format => 'pdf'
1389 get :show, :id => issue.id, :format => 'pdf'
1390 assert_response :success
1390 assert_response :success
1391 assert_equal 'application/pdf', @response.content_type
1391 assert_equal 'application/pdf', @response.content_type
1392 assert @response.body.starts_with?('%PDF')
1392 assert @response.body.starts_with?('%PDF')
1393 end
1393 end
1394
1394
1395 def test_show_export_to_pdf_with_descendants
1395 def test_show_export_to_pdf_with_descendants
1396 c1 = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => 1)
1396 c1 = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => 1)
1397 c2 = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => 1)
1397 c2 = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => 1)
1398 c3 = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => c1.id)
1398 c3 = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => c1.id)
1399
1399
1400 get :show, :id => 1, :format => 'pdf'
1400 get :show, :id => 1, :format => 'pdf'
1401 assert_response :success
1401 assert_response :success
1402 assert_equal 'application/pdf', @response.content_type
1402 assert_equal 'application/pdf', @response.content_type
1403 assert @response.body.starts_with?('%PDF')
1403 assert @response.body.starts_with?('%PDF')
1404 end
1404 end
1405
1405
1406 def test_show_export_to_pdf_with_journals
1406 def test_show_export_to_pdf_with_journals
1407 get :show, :id => 1, :format => 'pdf'
1407 get :show, :id => 1, :format => 'pdf'
1408 assert_response :success
1408 assert_response :success
1409 assert_equal 'application/pdf', @response.content_type
1409 assert_equal 'application/pdf', @response.content_type
1410 assert @response.body.starts_with?('%PDF')
1410 assert @response.body.starts_with?('%PDF')
1411 end
1411 end
1412
1412
1413 def test_show_export_to_pdf_with_changesets
1413 def test_show_export_to_pdf_with_changesets
1414 Issue.find(3).changesets = Changeset.find_all_by_id(100, 101, 102)
1414 Issue.find(3).changesets = Changeset.find_all_by_id(100, 101, 102)
1415
1415
1416 get :show, :id => 3, :format => 'pdf'
1416 get :show, :id => 3, :format => 'pdf'
1417 assert_response :success
1417 assert_response :success
1418 assert_equal 'application/pdf', @response.content_type
1418 assert_equal 'application/pdf', @response.content_type
1419 assert @response.body.starts_with?('%PDF')
1419 assert @response.body.starts_with?('%PDF')
1420 end
1420 end
1421
1421
1422 def test_show_invalid_should_respond_with_404
1422 def test_show_invalid_should_respond_with_404
1423 get :show, :id => 999
1423 get :show, :id => 999
1424 assert_response 404
1424 assert_response 404
1425 end
1425 end
1426
1426
1427 def test_get_new
1427 def test_get_new
1428 @request.session[:user_id] = 2
1428 @request.session[:user_id] = 2
1429 get :new, :project_id => 1, :tracker_id => 1
1429 get :new, :project_id => 1, :tracker_id => 1
1430 assert_response :success
1430 assert_response :success
1431 assert_template 'new'
1431 assert_template 'new'
1432
1432
1433 assert_select 'form#issue-form' do
1433 assert_select 'form#issue-form' do
1434 assert_select 'input[name=?]', 'issue[is_private]'
1434 assert_select 'input[name=?]', 'issue[is_private]'
1435 assert_select 'select[name=?]', 'issue[project_id]', 0
1435 assert_select 'select[name=?]', 'issue[project_id]', 0
1436 assert_select 'select[name=?]', 'issue[tracker_id]'
1436 assert_select 'select[name=?]', 'issue[tracker_id]'
1437 assert_select 'input[name=?]', 'issue[subject]'
1437 assert_select 'input[name=?]', 'issue[subject]'
1438 assert_select 'textarea[name=?]', 'issue[description]'
1438 assert_select 'textarea[name=?]', 'issue[description]'
1439 assert_select 'select[name=?]', 'issue[status_id]'
1439 assert_select 'select[name=?]', 'issue[status_id]'
1440 assert_select 'select[name=?]', 'issue[priority_id]'
1440 assert_select 'select[name=?]', 'issue[priority_id]'
1441 assert_select 'select[name=?]', 'issue[assigned_to_id]'
1441 assert_select 'select[name=?]', 'issue[assigned_to_id]'
1442 assert_select 'select[name=?]', 'issue[category_id]'
1442 assert_select 'select[name=?]', 'issue[category_id]'
1443 assert_select 'select[name=?]', 'issue[fixed_version_id]'
1443 assert_select 'select[name=?]', 'issue[fixed_version_id]'
1444 assert_select 'input[name=?]', 'issue[parent_issue_id]'
1444 assert_select 'input[name=?]', 'issue[parent_issue_id]'
1445 assert_select 'input[name=?]', 'issue[start_date]'
1445 assert_select 'input[name=?]', 'issue[start_date]'
1446 assert_select 'input[name=?]', 'issue[due_date]'
1446 assert_select 'input[name=?]', 'issue[due_date]'
1447 assert_select 'select[name=?]', 'issue[done_ratio]'
1447 assert_select 'select[name=?]', 'issue[done_ratio]'
1448 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Default string'
1448 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Default string'
1449 assert_select 'input[name=?]', 'issue[watcher_user_ids][]'
1449 assert_select 'input[name=?]', 'issue[watcher_user_ids][]'
1450 end
1450 end
1451
1451
1452 # Be sure we don't display inactive IssuePriorities
1452 # Be sure we don't display inactive IssuePriorities
1453 assert ! IssuePriority.find(15).active?
1453 assert ! IssuePriority.find(15).active?
1454 assert_select 'select[name=?]', 'issue[priority_id]' do
1454 assert_select 'select[name=?]', 'issue[priority_id]' do
1455 assert_select 'option[value=15]', 0
1455 assert_select 'option[value=15]', 0
1456 end
1456 end
1457 end
1457 end
1458
1458
1459 def test_get_new_with_minimal_permissions
1459 def test_get_new_with_minimal_permissions
1460 Role.find(1).update_attribute :permissions, [:add_issues]
1460 Role.find(1).update_attribute :permissions, [:add_issues]
1461 WorkflowTransition.delete_all :role_id => 1
1461 WorkflowTransition.delete_all :role_id => 1
1462
1462
1463 @request.session[:user_id] = 2
1463 @request.session[:user_id] = 2
1464 get :new, :project_id => 1, :tracker_id => 1
1464 get :new, :project_id => 1, :tracker_id => 1
1465 assert_response :success
1465 assert_response :success
1466 assert_template 'new'
1466 assert_template 'new'
1467
1467
1468 assert_select 'form#issue-form' do
1468 assert_select 'form#issue-form' do
1469 assert_select 'input[name=?]', 'issue[is_private]', 0
1469 assert_select 'input[name=?]', 'issue[is_private]', 0
1470 assert_select 'select[name=?]', 'issue[project_id]', 0
1470 assert_select 'select[name=?]', 'issue[project_id]', 0
1471 assert_select 'select[name=?]', 'issue[tracker_id]'
1471 assert_select 'select[name=?]', 'issue[tracker_id]'
1472 assert_select 'input[name=?]', 'issue[subject]'
1472 assert_select 'input[name=?]', 'issue[subject]'
1473 assert_select 'textarea[name=?]', 'issue[description]'
1473 assert_select 'textarea[name=?]', 'issue[description]'
1474 assert_select 'select[name=?]', 'issue[status_id]'
1474 assert_select 'select[name=?]', 'issue[status_id]'
1475 assert_select 'select[name=?]', 'issue[priority_id]'
1475 assert_select 'select[name=?]', 'issue[priority_id]'
1476 assert_select 'select[name=?]', 'issue[assigned_to_id]'
1476 assert_select 'select[name=?]', 'issue[assigned_to_id]'
1477 assert_select 'select[name=?]', 'issue[category_id]'
1477 assert_select 'select[name=?]', 'issue[category_id]'
1478 assert_select 'select[name=?]', 'issue[fixed_version_id]'
1478 assert_select 'select[name=?]', 'issue[fixed_version_id]'
1479 assert_select 'input[name=?]', 'issue[parent_issue_id]', 0
1479 assert_select 'input[name=?]', 'issue[parent_issue_id]', 0
1480 assert_select 'input[name=?]', 'issue[start_date]'
1480 assert_select 'input[name=?]', 'issue[start_date]'
1481 assert_select 'input[name=?]', 'issue[due_date]'
1481 assert_select 'input[name=?]', 'issue[due_date]'
1482 assert_select 'select[name=?]', 'issue[done_ratio]'
1482 assert_select 'select[name=?]', 'issue[done_ratio]'
1483 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Default string'
1483 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Default string'
1484 assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0
1484 assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0
1485 end
1485 end
1486 end
1486 end
1487
1487
1488 def test_get_new_with_list_custom_field
1488 def test_get_new_with_list_custom_field
1489 @request.session[:user_id] = 2
1489 @request.session[:user_id] = 2
1490 get :new, :project_id => 1, :tracker_id => 1
1490 get :new, :project_id => 1, :tracker_id => 1
1491 assert_response :success
1491 assert_response :success
1492 assert_template 'new'
1492 assert_template 'new'
1493
1493
1494 assert_select 'select.list_cf[name=?]', 'issue[custom_field_values][1]' do
1494 assert_select 'select.list_cf[name=?]', 'issue[custom_field_values][1]' do
1495 assert_select 'option', 4
1495 assert_select 'option', 4
1496 assert_select 'option[value=MySQL]', :text => 'MySQL'
1496 assert_select 'option[value=MySQL]', :text => 'MySQL'
1497 end
1497 end
1498 end
1498 end
1499
1499
1500 def test_get_new_with_multi_custom_field
1500 def test_get_new_with_multi_custom_field
1501 field = IssueCustomField.find(1)
1501 field = IssueCustomField.find(1)
1502 field.update_attribute :multiple, true
1502 field.update_attribute :multiple, true
1503
1503
1504 @request.session[:user_id] = 2
1504 @request.session[:user_id] = 2
1505 get :new, :project_id => 1, :tracker_id => 1
1505 get :new, :project_id => 1, :tracker_id => 1
1506 assert_response :success
1506 assert_response :success
1507 assert_template 'new'
1507 assert_template 'new'
1508
1508
1509 assert_select 'select[name=?][multiple=multiple]', 'issue[custom_field_values][1][]' do
1509 assert_select 'select[name=?][multiple=multiple]', 'issue[custom_field_values][1][]' do
1510 assert_select 'option', 3
1510 assert_select 'option', 3
1511 assert_select 'option[value=MySQL]', :text => 'MySQL'
1511 assert_select 'option[value=MySQL]', :text => 'MySQL'
1512 end
1512 end
1513 assert_select 'input[name=?][type=hidden][value=?]', 'issue[custom_field_values][1][]', ''
1513 assert_select 'input[name=?][type=hidden][value=?]', 'issue[custom_field_values][1][]', ''
1514 end
1514 end
1515
1515
1516 def test_get_new_with_multi_user_custom_field
1516 def test_get_new_with_multi_user_custom_field
1517 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1517 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1518 :tracker_ids => [1], :is_for_all => true)
1518 :tracker_ids => [1], :is_for_all => true)
1519
1519
1520 @request.session[:user_id] = 2
1520 @request.session[:user_id] = 2
1521 get :new, :project_id => 1, :tracker_id => 1
1521 get :new, :project_id => 1, :tracker_id => 1
1522 assert_response :success
1522 assert_response :success
1523 assert_template 'new'
1523 assert_template 'new'
1524
1524
1525 assert_select 'select[name=?][multiple=multiple]', "issue[custom_field_values][#{field.id}][]" do
1525 assert_select 'select[name=?][multiple=multiple]', "issue[custom_field_values][#{field.id}][]" do
1526 assert_select 'option', Project.find(1).users.count
1526 assert_select 'option', Project.find(1).users.count
1527 assert_select 'option[value=2]', :text => 'John Smith'
1527 assert_select 'option[value=2]', :text => 'John Smith'
1528 end
1528 end
1529 assert_select 'input[name=?][type=hidden][value=?]', "issue[custom_field_values][#{field.id}][]", ''
1529 assert_select 'input[name=?][type=hidden][value=?]', "issue[custom_field_values][#{field.id}][]", ''
1530 end
1530 end
1531
1531
1532 def test_get_new_with_date_custom_field
1532 def test_get_new_with_date_custom_field
1533 field = IssueCustomField.create!(:name => 'Date', :field_format => 'date', :tracker_ids => [1], :is_for_all => true)
1533 field = IssueCustomField.create!(:name => 'Date', :field_format => 'date', :tracker_ids => [1], :is_for_all => true)
1534
1534
1535 @request.session[:user_id] = 2
1535 @request.session[:user_id] = 2
1536 get :new, :project_id => 1, :tracker_id => 1
1536 get :new, :project_id => 1, :tracker_id => 1
1537 assert_response :success
1537 assert_response :success
1538
1538
1539 assert_select 'input[name=?]', "issue[custom_field_values][#{field.id}]"
1539 assert_select 'input[name=?]', "issue[custom_field_values][#{field.id}]"
1540 end
1540 end
1541
1541
1542 def test_get_new_with_text_custom_field
1542 def test_get_new_with_text_custom_field
1543 field = IssueCustomField.create!(:name => 'Text', :field_format => 'text', :tracker_ids => [1], :is_for_all => true)
1543 field = IssueCustomField.create!(:name => 'Text', :field_format => 'text', :tracker_ids => [1], :is_for_all => true)
1544
1544
1545 @request.session[:user_id] = 2
1545 @request.session[:user_id] = 2
1546 get :new, :project_id => 1, :tracker_id => 1
1546 get :new, :project_id => 1, :tracker_id => 1
1547 assert_response :success
1547 assert_response :success
1548
1548
1549 assert_select 'textarea[name=?]', "issue[custom_field_values][#{field.id}]"
1549 assert_select 'textarea[name=?]', "issue[custom_field_values][#{field.id}]"
1550 end
1550 end
1551
1551
1552 def test_get_new_without_default_start_date_is_creation_date
1552 def test_get_new_without_default_start_date_is_creation_date
1553 Setting.default_issue_start_date_to_creation_date = 0
1553 Setting.default_issue_start_date_to_creation_date = 0
1554
1554
1555 @request.session[:user_id] = 2
1555 @request.session[:user_id] = 2
1556 get :new, :project_id => 1, :tracker_id => 1
1556 get :new, :project_id => 1, :tracker_id => 1
1557 assert_response :success
1557 assert_response :success
1558 assert_template 'new'
1558 assert_template 'new'
1559
1559
1560 assert_select 'input[name=?]', 'issue[start_date]'
1560 assert_select 'input[name=?]', 'issue[start_date]'
1561 assert_select 'input[name=?][value]', 'issue[start_date]', 0
1561 assert_select 'input[name=?][value]', 'issue[start_date]', 0
1562 end
1562 end
1563
1563
1564 def test_get_new_with_default_start_date_is_creation_date
1564 def test_get_new_with_default_start_date_is_creation_date
1565 Setting.default_issue_start_date_to_creation_date = 1
1565 Setting.default_issue_start_date_to_creation_date = 1
1566
1566
1567 @request.session[:user_id] = 2
1567 @request.session[:user_id] = 2
1568 get :new, :project_id => 1, :tracker_id => 1
1568 get :new, :project_id => 1, :tracker_id => 1
1569 assert_response :success
1569 assert_response :success
1570 assert_template 'new'
1570 assert_template 'new'
1571
1571
1572 assert_select 'input[name=?][value=?]', 'issue[start_date]', Date.today.to_s
1572 assert_select 'input[name=?][value=?]', 'issue[start_date]', Date.today.to_s
1573 end
1573 end
1574
1574
1575 def test_get_new_form_should_allow_attachment_upload
1575 def test_get_new_form_should_allow_attachment_upload
1576 @request.session[:user_id] = 2
1576 @request.session[:user_id] = 2
1577 get :new, :project_id => 1, :tracker_id => 1
1577 get :new, :project_id => 1, :tracker_id => 1
1578
1578
1579 assert_select 'form[id=issue-form][method=post][enctype=multipart/form-data]' do
1579 assert_select 'form[id=issue-form][method=post][enctype=multipart/form-data]' do
1580 assert_select 'input[name=?][type=file]', 'attachments[dummy][file]'
1580 assert_select 'input[name=?][type=file]', 'attachments[dummy][file]'
1581 end
1581 end
1582 end
1582 end
1583
1583
1584 def test_get_new_should_prefill_the_form_from_params
1584 def test_get_new_should_prefill_the_form_from_params
1585 @request.session[:user_id] = 2
1585 @request.session[:user_id] = 2
1586 get :new, :project_id => 1,
1586 get :new, :project_id => 1,
1587 :issue => {:tracker_id => 3, :description => 'Prefilled', :custom_field_values => {'2' => 'Custom field value'}}
1587 :issue => {:tracker_id => 3, :description => 'Prefilled', :custom_field_values => {'2' => 'Custom field value'}}
1588
1588
1589 issue = assigns(:issue)
1589 issue = assigns(:issue)
1590 assert_equal 3, issue.tracker_id
1590 assert_equal 3, issue.tracker_id
1591 assert_equal 'Prefilled', issue.description
1591 assert_equal 'Prefilled', issue.description
1592 assert_equal 'Custom field value', issue.custom_field_value(2)
1592 assert_equal 'Custom field value', issue.custom_field_value(2)
1593
1593
1594 assert_select 'select[name=?]', 'issue[tracker_id]' do
1594 assert_select 'select[name=?]', 'issue[tracker_id]' do
1595 assert_select 'option[value=3][selected=selected]'
1595 assert_select 'option[value=3][selected=selected]'
1596 end
1596 end
1597 assert_select 'textarea[name=?]', 'issue[description]', :text => /Prefilled/
1597 assert_select 'textarea[name=?]', 'issue[description]', :text => /Prefilled/
1598 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Custom field value'
1598 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Custom field value'
1599 end
1599 end
1600
1600
1601 def test_get_new_should_mark_required_fields
1601 def test_get_new_should_mark_required_fields
1602 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1602 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1603 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1603 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1604 WorkflowPermission.delete_all
1604 WorkflowPermission.delete_all
1605 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'required')
1605 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'required')
1606 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'required')
1606 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'required')
1607 @request.session[:user_id] = 2
1607 @request.session[:user_id] = 2
1608
1608
1609 get :new, :project_id => 1
1609 get :new, :project_id => 1
1610 assert_response :success
1610 assert_response :success
1611 assert_template 'new'
1611 assert_template 'new'
1612
1612
1613 assert_select 'label[for=issue_start_date]' do
1613 assert_select 'label[for=issue_start_date]' do
1614 assert_select 'span[class=required]', 0
1614 assert_select 'span[class=required]', 0
1615 end
1615 end
1616 assert_select 'label[for=issue_due_date]' do
1616 assert_select 'label[for=issue_due_date]' do
1617 assert_select 'span[class=required]'
1617 assert_select 'span[class=required]'
1618 end
1618 end
1619 assert_select 'label[for=?]', "issue_custom_field_values_#{cf1.id}" do
1619 assert_select 'label[for=?]', "issue_custom_field_values_#{cf1.id}" do
1620 assert_select 'span[class=required]', 0
1620 assert_select 'span[class=required]', 0
1621 end
1621 end
1622 assert_select 'label[for=?]', "issue_custom_field_values_#{cf2.id}" do
1622 assert_select 'label[for=?]', "issue_custom_field_values_#{cf2.id}" do
1623 assert_select 'span[class=required]'
1623 assert_select 'span[class=required]'
1624 end
1624 end
1625 end
1625 end
1626
1626
1627 def test_get_new_should_not_display_readonly_fields
1627 def test_get_new_should_not_display_readonly_fields
1628 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1628 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1629 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1629 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1630 WorkflowPermission.delete_all
1630 WorkflowPermission.delete_all
1631 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'readonly')
1631 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'readonly')
1632 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'readonly')
1632 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'readonly')
1633 @request.session[:user_id] = 2
1633 @request.session[:user_id] = 2
1634
1634
1635 get :new, :project_id => 1
1635 get :new, :project_id => 1
1636 assert_response :success
1636 assert_response :success
1637 assert_template 'new'
1637 assert_template 'new'
1638
1638
1639 assert_select 'input[name=?]', 'issue[start_date]'
1639 assert_select 'input[name=?]', 'issue[start_date]'
1640 assert_select 'input[name=?]', 'issue[due_date]', 0
1640 assert_select 'input[name=?]', 'issue[due_date]', 0
1641 assert_select 'input[name=?]', "issue[custom_field_values][#{cf1.id}]"
1641 assert_select 'input[name=?]', "issue[custom_field_values][#{cf1.id}]"
1642 assert_select 'input[name=?]', "issue[custom_field_values][#{cf2.id}]", 0
1642 assert_select 'input[name=?]', "issue[custom_field_values][#{cf2.id}]", 0
1643 end
1643 end
1644
1644
1645 def test_get_new_without_tracker_id
1645 def test_get_new_without_tracker_id
1646 @request.session[:user_id] = 2
1646 @request.session[:user_id] = 2
1647 get :new, :project_id => 1
1647 get :new, :project_id => 1
1648 assert_response :success
1648 assert_response :success
1649 assert_template 'new'
1649 assert_template 'new'
1650
1650
1651 issue = assigns(:issue)
1651 issue = assigns(:issue)
1652 assert_not_nil issue
1652 assert_not_nil issue
1653 assert_equal Project.find(1).trackers.first, issue.tracker
1653 assert_equal Project.find(1).trackers.first, issue.tracker
1654 end
1654 end
1655
1655
1656 def test_get_new_with_no_default_status_should_display_an_error
1656 def test_get_new_with_no_default_status_should_display_an_error
1657 @request.session[:user_id] = 2
1657 @request.session[:user_id] = 2
1658 IssueStatus.delete_all
1658 IssueStatus.delete_all
1659
1659
1660 get :new, :project_id => 1
1660 get :new, :project_id => 1
1661 assert_response 500
1661 assert_response 500
1662 assert_error_tag :content => /No default issue/
1662 assert_error_tag :content => /No default issue/
1663 end
1663 end
1664
1664
1665 def test_get_new_with_no_tracker_should_display_an_error
1665 def test_get_new_with_no_tracker_should_display_an_error
1666 @request.session[:user_id] = 2
1666 @request.session[:user_id] = 2
1667 Tracker.delete_all
1667 Tracker.delete_all
1668
1668
1669 get :new, :project_id => 1
1669 get :new, :project_id => 1
1670 assert_response 500
1670 assert_response 500
1671 assert_error_tag :content => /No tracker/
1671 assert_error_tag :content => /No tracker/
1672 end
1672 end
1673
1673
1674 def test_update_new_form
1674 def test_update_new_form
1675 @request.session[:user_id] = 2
1675 @request.session[:user_id] = 2
1676 xhr :post, :new, :project_id => 1,
1676 xhr :post, :new, :project_id => 1,
1677 :issue => {:tracker_id => 2,
1677 :issue => {:tracker_id => 2,
1678 :subject => 'This is the test_new issue',
1678 :subject => 'This is the test_new issue',
1679 :description => 'This is the description',
1679 :description => 'This is the description',
1680 :priority_id => 5}
1680 :priority_id => 5}
1681 assert_response :success
1681 assert_response :success
1682 assert_template 'update_form'
1682 assert_template 'update_form'
1683 assert_template 'form'
1683 assert_template 'form'
1684 assert_equal 'text/javascript', response.content_type
1684 assert_equal 'text/javascript', response.content_type
1685
1685
1686 issue = assigns(:issue)
1686 issue = assigns(:issue)
1687 assert_kind_of Issue, issue
1687 assert_kind_of Issue, issue
1688 assert_equal 1, issue.project_id
1688 assert_equal 1, issue.project_id
1689 assert_equal 2, issue.tracker_id
1689 assert_equal 2, issue.tracker_id
1690 assert_equal 'This is the test_new issue', issue.subject
1690 assert_equal 'This is the test_new issue', issue.subject
1691 end
1691 end
1692
1692
1693 def test_update_new_form_should_propose_transitions_based_on_initial_status
1693 def test_update_new_form_should_propose_transitions_based_on_initial_status
1694 @request.session[:user_id] = 2
1694 @request.session[:user_id] = 2
1695 WorkflowTransition.delete_all
1695 WorkflowTransition.delete_all
1696 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2)
1696 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2)
1697 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5)
1697 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5)
1698 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4)
1698 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4)
1699
1699
1700 xhr :post, :new, :project_id => 1,
1700 xhr :post, :new, :project_id => 1,
1701 :issue => {:tracker_id => 1,
1701 :issue => {:tracker_id => 1,
1702 :status_id => 5,
1702 :status_id => 5,
1703 :subject => 'This is an issue'}
1703 :subject => 'This is an issue'}
1704
1704
1705 assert_equal 5, assigns(:issue).status_id
1705 assert_equal 5, assigns(:issue).status_id
1706 assert_equal [1,2,5], assigns(:allowed_statuses).map(&:id).sort
1706 assert_equal [1,2,5], assigns(:allowed_statuses).map(&:id).sort
1707 end
1707 end
1708
1708
1709 def test_post_create
1709 def test_post_create
1710 @request.session[:user_id] = 2
1710 @request.session[:user_id] = 2
1711 assert_difference 'Issue.count' do
1711 assert_difference 'Issue.count' do
1712 post :create, :project_id => 1,
1712 post :create, :project_id => 1,
1713 :issue => {:tracker_id => 3,
1713 :issue => {:tracker_id => 3,
1714 :status_id => 2,
1714 :status_id => 2,
1715 :subject => 'This is the test_new issue',
1715 :subject => 'This is the test_new issue',
1716 :description => 'This is the description',
1716 :description => 'This is the description',
1717 :priority_id => 5,
1717 :priority_id => 5,
1718 :start_date => '2010-11-07',
1718 :start_date => '2010-11-07',
1719 :estimated_hours => '',
1719 :estimated_hours => '',
1720 :custom_field_values => {'2' => 'Value for field 2'}}
1720 :custom_field_values => {'2' => 'Value for field 2'}}
1721 end
1721 end
1722 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1722 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1723
1723
1724 issue = Issue.find_by_subject('This is the test_new issue')
1724 issue = Issue.find_by_subject('This is the test_new issue')
1725 assert_not_nil issue
1725 assert_not_nil issue
1726 assert_equal 2, issue.author_id
1726 assert_equal 2, issue.author_id
1727 assert_equal 3, issue.tracker_id
1727 assert_equal 3, issue.tracker_id
1728 assert_equal 2, issue.status_id
1728 assert_equal 2, issue.status_id
1729 assert_equal Date.parse('2010-11-07'), issue.start_date
1729 assert_equal Date.parse('2010-11-07'), issue.start_date
1730 assert_nil issue.estimated_hours
1730 assert_nil issue.estimated_hours
1731 v = issue.custom_values.where(:custom_field_id => 2).first
1731 v = issue.custom_values.where(:custom_field_id => 2).first
1732 assert_not_nil v
1732 assert_not_nil v
1733 assert_equal 'Value for field 2', v.value
1733 assert_equal 'Value for field 2', v.value
1734 end
1734 end
1735
1735
1736 def test_post_new_with_group_assignment
1736 def test_post_new_with_group_assignment
1737 group = Group.find(11)
1737 group = Group.find(11)
1738 project = Project.find(1)
1738 project = Project.find(1)
1739 project.members << Member.new(:principal => group, :roles => [Role.givable.first])
1739 project.members << Member.new(:principal => group, :roles => [Role.givable.first])
1740
1740
1741 with_settings :issue_group_assignment => '1' do
1741 with_settings :issue_group_assignment => '1' do
1742 @request.session[:user_id] = 2
1742 @request.session[:user_id] = 2
1743 assert_difference 'Issue.count' do
1743 assert_difference 'Issue.count' do
1744 post :create, :project_id => project.id,
1744 post :create, :project_id => project.id,
1745 :issue => {:tracker_id => 3,
1745 :issue => {:tracker_id => 3,
1746 :status_id => 1,
1746 :status_id => 1,
1747 :subject => 'This is the test_new_with_group_assignment issue',
1747 :subject => 'This is the test_new_with_group_assignment issue',
1748 :assigned_to_id => group.id}
1748 :assigned_to_id => group.id}
1749 end
1749 end
1750 end
1750 end
1751 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1751 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1752
1752
1753 issue = Issue.find_by_subject('This is the test_new_with_group_assignment issue')
1753 issue = Issue.find_by_subject('This is the test_new_with_group_assignment issue')
1754 assert_not_nil issue
1754 assert_not_nil issue
1755 assert_equal group, issue.assigned_to
1755 assert_equal group, issue.assigned_to
1756 end
1756 end
1757
1757
1758 def test_post_create_without_start_date_and_default_start_date_is_not_creation_date
1758 def test_post_create_without_start_date_and_default_start_date_is_not_creation_date
1759 Setting.default_issue_start_date_to_creation_date = 0
1759 Setting.default_issue_start_date_to_creation_date = 0
1760
1760
1761 @request.session[:user_id] = 2
1761 @request.session[:user_id] = 2
1762 assert_difference 'Issue.count' do
1762 assert_difference 'Issue.count' do
1763 post :create, :project_id => 1,
1763 post :create, :project_id => 1,
1764 :issue => {:tracker_id => 3,
1764 :issue => {:tracker_id => 3,
1765 :status_id => 2,
1765 :status_id => 2,
1766 :subject => 'This is the test_new issue',
1766 :subject => 'This is the test_new issue',
1767 :description => 'This is the description',
1767 :description => 'This is the description',
1768 :priority_id => 5,
1768 :priority_id => 5,
1769 :estimated_hours => '',
1769 :estimated_hours => '',
1770 :custom_field_values => {'2' => 'Value for field 2'}}
1770 :custom_field_values => {'2' => 'Value for field 2'}}
1771 end
1771 end
1772 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1772 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1773
1773
1774 issue = Issue.find_by_subject('This is the test_new issue')
1774 issue = Issue.find_by_subject('This is the test_new issue')
1775 assert_not_nil issue
1775 assert_not_nil issue
1776 assert_nil issue.start_date
1776 assert_nil issue.start_date
1777 end
1777 end
1778
1778
1779 def test_post_create_without_start_date_and_default_start_date_is_creation_date
1779 def test_post_create_without_start_date_and_default_start_date_is_creation_date
1780 Setting.default_issue_start_date_to_creation_date = 1
1780 Setting.default_issue_start_date_to_creation_date = 1
1781
1781
1782 @request.session[:user_id] = 2
1782 @request.session[:user_id] = 2
1783 assert_difference 'Issue.count' do
1783 assert_difference 'Issue.count' do
1784 post :create, :project_id => 1,
1784 post :create, :project_id => 1,
1785 :issue => {:tracker_id => 3,
1785 :issue => {:tracker_id => 3,
1786 :status_id => 2,
1786 :status_id => 2,
1787 :subject => 'This is the test_new issue',
1787 :subject => 'This is the test_new issue',
1788 :description => 'This is the description',
1788 :description => 'This is the description',
1789 :priority_id => 5,
1789 :priority_id => 5,
1790 :estimated_hours => '',
1790 :estimated_hours => '',
1791 :custom_field_values => {'2' => 'Value for field 2'}}
1791 :custom_field_values => {'2' => 'Value for field 2'}}
1792 end
1792 end
1793 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1793 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1794
1794
1795 issue = Issue.find_by_subject('This is the test_new issue')
1795 issue = Issue.find_by_subject('This is the test_new issue')
1796 assert_not_nil issue
1796 assert_not_nil issue
1797 assert_equal Date.today, issue.start_date
1797 assert_equal Date.today, issue.start_date
1798 end
1798 end
1799
1799
1800 def test_post_create_and_continue
1800 def test_post_create_and_continue
1801 @request.session[:user_id] = 2
1801 @request.session[:user_id] = 2
1802 assert_difference 'Issue.count' do
1802 assert_difference 'Issue.count' do
1803 post :create, :project_id => 1,
1803 post :create, :project_id => 1,
1804 :issue => {:tracker_id => 3, :subject => 'This is first issue', :priority_id => 5},
1804 :issue => {:tracker_id => 3, :subject => 'This is first issue', :priority_id => 5},
1805 :continue => ''
1805 :continue => ''
1806 end
1806 end
1807
1807
1808 issue = Issue.first(:order => 'id DESC')
1808 issue = Issue.first(:order => 'id DESC')
1809 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook', :issue => {:tracker_id => 3}
1809 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook', :issue => {:tracker_id => 3}
1810 assert_not_nil flash[:notice], "flash was not set"
1810 assert_not_nil flash[:notice], "flash was not set"
1811 assert_include %|<a href="/issues/#{issue.id}" title="This is first issue">##{issue.id}</a>|, flash[:notice], "issue link not found in the flash message"
1811 assert_include %|<a href="/issues/#{issue.id}" title="This is first issue">##{issue.id}</a>|, flash[:notice], "issue link not found in the flash message"
1812 end
1812 end
1813
1813
1814 def test_post_create_without_custom_fields_param
1814 def test_post_create_without_custom_fields_param
1815 @request.session[:user_id] = 2
1815 @request.session[:user_id] = 2
1816 assert_difference 'Issue.count' do
1816 assert_difference 'Issue.count' do
1817 post :create, :project_id => 1,
1817 post :create, :project_id => 1,
1818 :issue => {:tracker_id => 1,
1818 :issue => {:tracker_id => 1,
1819 :subject => 'This is the test_new issue',
1819 :subject => 'This is the test_new issue',
1820 :description => 'This is the description',
1820 :description => 'This is the description',
1821 :priority_id => 5}
1821 :priority_id => 5}
1822 end
1822 end
1823 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1823 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1824 end
1824 end
1825
1825
1826 def test_post_create_with_multi_custom_field
1826 def test_post_create_with_multi_custom_field
1827 field = IssueCustomField.find_by_name('Database')
1827 field = IssueCustomField.find_by_name('Database')
1828 field.update_attribute(:multiple, true)
1828 field.update_attribute(:multiple, true)
1829
1829
1830 @request.session[:user_id] = 2
1830 @request.session[:user_id] = 2
1831 assert_difference 'Issue.count' do
1831 assert_difference 'Issue.count' do
1832 post :create, :project_id => 1,
1832 post :create, :project_id => 1,
1833 :issue => {:tracker_id => 1,
1833 :issue => {:tracker_id => 1,
1834 :subject => 'This is the test_new issue',
1834 :subject => 'This is the test_new issue',
1835 :description => 'This is the description',
1835 :description => 'This is the description',
1836 :priority_id => 5,
1836 :priority_id => 5,
1837 :custom_field_values => {'1' => ['', 'MySQL', 'Oracle']}}
1837 :custom_field_values => {'1' => ['', 'MySQL', 'Oracle']}}
1838 end
1838 end
1839 assert_response 302
1839 assert_response 302
1840 issue = Issue.first(:order => 'id DESC')
1840 issue = Issue.first(:order => 'id DESC')
1841 assert_equal ['MySQL', 'Oracle'], issue.custom_field_value(1).sort
1841 assert_equal ['MySQL', 'Oracle'], issue.custom_field_value(1).sort
1842 end
1842 end
1843
1843
1844 def test_post_create_with_empty_multi_custom_field
1844 def test_post_create_with_empty_multi_custom_field
1845 field = IssueCustomField.find_by_name('Database')
1845 field = IssueCustomField.find_by_name('Database')
1846 field.update_attribute(:multiple, true)
1846 field.update_attribute(:multiple, true)
1847
1847
1848 @request.session[:user_id] = 2
1848 @request.session[:user_id] = 2
1849 assert_difference 'Issue.count' do
1849 assert_difference 'Issue.count' do
1850 post :create, :project_id => 1,
1850 post :create, :project_id => 1,
1851 :issue => {:tracker_id => 1,
1851 :issue => {:tracker_id => 1,
1852 :subject => 'This is the test_new issue',
1852 :subject => 'This is the test_new issue',
1853 :description => 'This is the description',
1853 :description => 'This is the description',
1854 :priority_id => 5,
1854 :priority_id => 5,
1855 :custom_field_values => {'1' => ['']}}
1855 :custom_field_values => {'1' => ['']}}
1856 end
1856 end
1857 assert_response 302
1857 assert_response 302
1858 issue = Issue.first(:order => 'id DESC')
1858 issue = Issue.first(:order => 'id DESC')
1859 assert_equal [''], issue.custom_field_value(1).sort
1859 assert_equal [''], issue.custom_field_value(1).sort
1860 end
1860 end
1861
1861
1862 def test_post_create_with_multi_user_custom_field
1862 def test_post_create_with_multi_user_custom_field
1863 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1863 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1864 :tracker_ids => [1], :is_for_all => true)
1864 :tracker_ids => [1], :is_for_all => true)
1865
1865
1866 @request.session[:user_id] = 2
1866 @request.session[:user_id] = 2
1867 assert_difference 'Issue.count' do
1867 assert_difference 'Issue.count' do
1868 post :create, :project_id => 1,
1868 post :create, :project_id => 1,
1869 :issue => {:tracker_id => 1,
1869 :issue => {:tracker_id => 1,
1870 :subject => 'This is the test_new issue',
1870 :subject => 'This is the test_new issue',
1871 :description => 'This is the description',
1871 :description => 'This is the description',
1872 :priority_id => 5,
1872 :priority_id => 5,
1873 :custom_field_values => {field.id.to_s => ['', '2', '3']}}
1873 :custom_field_values => {field.id.to_s => ['', '2', '3']}}
1874 end
1874 end
1875 assert_response 302
1875 assert_response 302
1876 issue = Issue.first(:order => 'id DESC')
1876 issue = Issue.first(:order => 'id DESC')
1877 assert_equal ['2', '3'], issue.custom_field_value(field).sort
1877 assert_equal ['2', '3'], issue.custom_field_value(field).sort
1878 end
1878 end
1879
1879
1880 def test_post_create_with_required_custom_field_and_without_custom_fields_param
1880 def test_post_create_with_required_custom_field_and_without_custom_fields_param
1881 field = IssueCustomField.find_by_name('Database')
1881 field = IssueCustomField.find_by_name('Database')
1882 field.update_attribute(:is_required, true)
1882 field.update_attribute(:is_required, true)
1883
1883
1884 @request.session[:user_id] = 2
1884 @request.session[:user_id] = 2
1885 assert_no_difference 'Issue.count' do
1885 assert_no_difference 'Issue.count' do
1886 post :create, :project_id => 1,
1886 post :create, :project_id => 1,
1887 :issue => {:tracker_id => 1,
1887 :issue => {:tracker_id => 1,
1888 :subject => 'This is the test_new issue',
1888 :subject => 'This is the test_new issue',
1889 :description => 'This is the description',
1889 :description => 'This is the description',
1890 :priority_id => 5}
1890 :priority_id => 5}
1891 end
1891 end
1892 assert_response :success
1892 assert_response :success
1893 assert_template 'new'
1893 assert_template 'new'
1894 issue = assigns(:issue)
1894 issue = assigns(:issue)
1895 assert_not_nil issue
1895 assert_not_nil issue
1896 assert_error_tag :content => /Database can&#x27;t be blank/
1896 assert_error_tag :content => /Database can&#x27;t be blank/
1897 end
1897 end
1898
1898
1899 def test_create_should_validate_required_fields
1899 def test_create_should_validate_required_fields
1900 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1900 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1901 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1901 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1902 WorkflowPermission.delete_all
1902 WorkflowPermission.delete_all
1903 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => 'due_date', :rule => 'required')
1903 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => 'due_date', :rule => 'required')
1904 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'required')
1904 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'required')
1905 @request.session[:user_id] = 2
1905 @request.session[:user_id] = 2
1906
1906
1907 assert_no_difference 'Issue.count' do
1907 assert_no_difference 'Issue.count' do
1908 post :create, :project_id => 1, :issue => {
1908 post :create, :project_id => 1, :issue => {
1909 :tracker_id => 2,
1909 :tracker_id => 2,
1910 :status_id => 1,
1910 :status_id => 1,
1911 :subject => 'Test',
1911 :subject => 'Test',
1912 :start_date => '',
1912 :start_date => '',
1913 :due_date => '',
1913 :due_date => '',
1914 :custom_field_values => {cf1.id.to_s => '', cf2.id.to_s => ''}
1914 :custom_field_values => {cf1.id.to_s => '', cf2.id.to_s => ''}
1915 }
1915 }
1916 assert_response :success
1916 assert_response :success
1917 assert_template 'new'
1917 assert_template 'new'
1918 end
1918 end
1919
1919
1920 assert_error_tag :content => /Due date can&#x27;t be blank/i
1920 assert_error_tag :content => /Due date can&#x27;t be blank/i
1921 assert_error_tag :content => /Bar can&#x27;t be blank/i
1921 assert_error_tag :content => /Bar can&#x27;t be blank/i
1922 end
1922 end
1923
1923
1924 def test_create_should_ignore_readonly_fields
1924 def test_create_should_ignore_readonly_fields
1925 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1925 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1926 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1926 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1927 WorkflowPermission.delete_all
1927 WorkflowPermission.delete_all
1928 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => 'due_date', :rule => 'readonly')
1928 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => 'due_date', :rule => 'readonly')
1929 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'readonly')
1929 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'readonly')
1930 @request.session[:user_id] = 2
1930 @request.session[:user_id] = 2
1931
1931
1932 assert_difference 'Issue.count' do
1932 assert_difference 'Issue.count' do
1933 post :create, :project_id => 1, :issue => {
1933 post :create, :project_id => 1, :issue => {
1934 :tracker_id => 2,
1934 :tracker_id => 2,
1935 :status_id => 1,
1935 :status_id => 1,
1936 :subject => 'Test',
1936 :subject => 'Test',
1937 :start_date => '2012-07-14',
1937 :start_date => '2012-07-14',
1938 :due_date => '2012-07-16',
1938 :due_date => '2012-07-16',
1939 :custom_field_values => {cf1.id.to_s => 'value1', cf2.id.to_s => 'value2'}
1939 :custom_field_values => {cf1.id.to_s => 'value1', cf2.id.to_s => 'value2'}
1940 }
1940 }
1941 assert_response 302
1941 assert_response 302
1942 end
1942 end
1943
1943
1944 issue = Issue.first(:order => 'id DESC')
1944 issue = Issue.first(:order => 'id DESC')
1945 assert_equal Date.parse('2012-07-14'), issue.start_date
1945 assert_equal Date.parse('2012-07-14'), issue.start_date
1946 assert_nil issue.due_date
1946 assert_nil issue.due_date
1947 assert_equal 'value1', issue.custom_field_value(cf1)
1947 assert_equal 'value1', issue.custom_field_value(cf1)
1948 assert_nil issue.custom_field_value(cf2)
1948 assert_nil issue.custom_field_value(cf2)
1949 end
1949 end
1950
1950
1951 def test_post_create_with_watchers
1951 def test_post_create_with_watchers
1952 @request.session[:user_id] = 2
1952 @request.session[:user_id] = 2
1953 ActionMailer::Base.deliveries.clear
1953 ActionMailer::Base.deliveries.clear
1954
1954
1955 assert_difference 'Watcher.count', 2 do
1955 assert_difference 'Watcher.count', 2 do
1956 post :create, :project_id => 1,
1956 post :create, :project_id => 1,
1957 :issue => {:tracker_id => 1,
1957 :issue => {:tracker_id => 1,
1958 :subject => 'This is a new issue with watchers',
1958 :subject => 'This is a new issue with watchers',
1959 :description => 'This is the description',
1959 :description => 'This is the description',
1960 :priority_id => 5,
1960 :priority_id => 5,
1961 :watcher_user_ids => ['2', '3']}
1961 :watcher_user_ids => ['2', '3']}
1962 end
1962 end
1963 issue = Issue.find_by_subject('This is a new issue with watchers')
1963 issue = Issue.find_by_subject('This is a new issue with watchers')
1964 assert_not_nil issue
1964 assert_not_nil issue
1965 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
1965 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
1966
1966
1967 # Watchers added
1967 # Watchers added
1968 assert_equal [2, 3], issue.watcher_user_ids.sort
1968 assert_equal [2, 3], issue.watcher_user_ids.sort
1969 assert issue.watched_by?(User.find(3))
1969 assert issue.watched_by?(User.find(3))
1970 # Watchers notified
1970 # Watchers notified
1971 mail = ActionMailer::Base.deliveries.last
1971 mail = ActionMailer::Base.deliveries.last
1972 assert_not_nil mail
1972 assert_not_nil mail
1973 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
1973 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
1974 end
1974 end
1975
1975
1976 def test_post_create_subissue
1976 def test_post_create_subissue
1977 @request.session[:user_id] = 2
1977 @request.session[:user_id] = 2
1978
1978
1979 assert_difference 'Issue.count' do
1979 assert_difference 'Issue.count' do
1980 post :create, :project_id => 1,
1980 post :create, :project_id => 1,
1981 :issue => {:tracker_id => 1,
1981 :issue => {:tracker_id => 1,
1982 :subject => 'This is a child issue',
1982 :subject => 'This is a child issue',
1983 :parent_issue_id => '2'}
1983 :parent_issue_id => '2'}
1984 assert_response 302
1984 assert_response 302
1985 end
1985 end
1986 issue = Issue.order('id DESC').first
1986 issue = Issue.order('id DESC').first
1987 assert_equal Issue.find(2), issue.parent
1987 assert_equal Issue.find(2), issue.parent
1988 end
1988 end
1989
1989
1990 def test_post_create_subissue_with_sharp_parent_id
1990 def test_post_create_subissue_with_sharp_parent_id
1991 @request.session[:user_id] = 2
1991 @request.session[:user_id] = 2
1992
1992
1993 assert_difference 'Issue.count' do
1993 assert_difference 'Issue.count' do
1994 post :create, :project_id => 1,
1994 post :create, :project_id => 1,
1995 :issue => {:tracker_id => 1,
1995 :issue => {:tracker_id => 1,
1996 :subject => 'This is a child issue',
1996 :subject => 'This is a child issue',
1997 :parent_issue_id => '#2'}
1997 :parent_issue_id => '#2'}
1998 assert_response 302
1998 assert_response 302
1999 end
1999 end
2000 issue = Issue.order('id DESC').first
2000 issue = Issue.order('id DESC').first
2001 assert_equal Issue.find(2), issue.parent
2001 assert_equal Issue.find(2), issue.parent
2002 end
2002 end
2003
2003
2004 def test_post_create_subissue_with_non_visible_parent_id_should_not_validate
2004 def test_post_create_subissue_with_non_visible_parent_id_should_not_validate
2005 @request.session[:user_id] = 2
2005 @request.session[:user_id] = 2
2006
2006
2007 assert_no_difference 'Issue.count' do
2007 assert_no_difference 'Issue.count' do
2008 post :create, :project_id => 1,
2008 post :create, :project_id => 1,
2009 :issue => {:tracker_id => 1,
2009 :issue => {:tracker_id => 1,
2010 :subject => 'This is a child issue',
2010 :subject => 'This is a child issue',
2011 :parent_issue_id => '4'}
2011 :parent_issue_id => '4'}
2012
2012
2013 assert_response :success
2013 assert_response :success
2014 assert_select 'input[name=?][value=?]', 'issue[parent_issue_id]', '4'
2014 assert_select 'input[name=?][value=?]', 'issue[parent_issue_id]', '4'
2015 assert_error_tag :content => /Parent task is invalid/i
2015 assert_error_tag :content => /Parent task is invalid/i
2016 end
2016 end
2017 end
2017 end
2018
2018
2019 def test_post_create_subissue_with_non_numeric_parent_id_should_not_validate
2019 def test_post_create_subissue_with_non_numeric_parent_id_should_not_validate
2020 @request.session[:user_id] = 2
2020 @request.session[:user_id] = 2
2021
2021
2022 assert_no_difference 'Issue.count' do
2022 assert_no_difference 'Issue.count' do
2023 post :create, :project_id => 1,
2023 post :create, :project_id => 1,
2024 :issue => {:tracker_id => 1,
2024 :issue => {:tracker_id => 1,
2025 :subject => 'This is a child issue',
2025 :subject => 'This is a child issue',
2026 :parent_issue_id => '01ABC'}
2026 :parent_issue_id => '01ABC'}
2027
2027
2028 assert_response :success
2028 assert_response :success
2029 assert_select 'input[name=?][value=?]', 'issue[parent_issue_id]', '01ABC'
2029 assert_select 'input[name=?][value=?]', 'issue[parent_issue_id]', '01ABC'
2030 assert_error_tag :content => /Parent task is invalid/i
2030 assert_error_tag :content => /Parent task is invalid/i
2031 end
2031 end
2032 end
2032 end
2033
2033
2034 def test_post_create_private
2034 def test_post_create_private
2035 @request.session[:user_id] = 2
2035 @request.session[:user_id] = 2
2036
2036
2037 assert_difference 'Issue.count' do
2037 assert_difference 'Issue.count' do
2038 post :create, :project_id => 1,
2038 post :create, :project_id => 1,
2039 :issue => {:tracker_id => 1,
2039 :issue => {:tracker_id => 1,
2040 :subject => 'This is a private issue',
2040 :subject => 'This is a private issue',
2041 :is_private => '1'}
2041 :is_private => '1'}
2042 end
2042 end
2043 issue = Issue.first(:order => 'id DESC')
2043 issue = Issue.first(:order => 'id DESC')
2044 assert issue.is_private?
2044 assert issue.is_private?
2045 end
2045 end
2046
2046
2047 def test_post_create_private_with_set_own_issues_private_permission
2047 def test_post_create_private_with_set_own_issues_private_permission
2048 role = Role.find(1)
2048 role = Role.find(1)
2049 role.remove_permission! :set_issues_private
2049 role.remove_permission! :set_issues_private
2050 role.add_permission! :set_own_issues_private
2050 role.add_permission! :set_own_issues_private
2051
2051
2052 @request.session[:user_id] = 2
2052 @request.session[:user_id] = 2
2053
2053
2054 assert_difference 'Issue.count' do
2054 assert_difference 'Issue.count' do
2055 post :create, :project_id => 1,
2055 post :create, :project_id => 1,
2056 :issue => {:tracker_id => 1,
2056 :issue => {:tracker_id => 1,
2057 :subject => 'This is a private issue',
2057 :subject => 'This is a private issue',
2058 :is_private => '1'}
2058 :is_private => '1'}
2059 end
2059 end
2060 issue = Issue.first(:order => 'id DESC')
2060 issue = Issue.first(:order => 'id DESC')
2061 assert issue.is_private?
2061 assert issue.is_private?
2062 end
2062 end
2063
2063
2064 def test_post_create_should_send_a_notification
2064 def test_post_create_should_send_a_notification
2065 ActionMailer::Base.deliveries.clear
2065 ActionMailer::Base.deliveries.clear
2066 @request.session[:user_id] = 2
2066 @request.session[:user_id] = 2
2067 assert_difference 'Issue.count' do
2067 assert_difference 'Issue.count' do
2068 post :create, :project_id => 1,
2068 post :create, :project_id => 1,
2069 :issue => {:tracker_id => 3,
2069 :issue => {:tracker_id => 3,
2070 :subject => 'This is the test_new issue',
2070 :subject => 'This is the test_new issue',
2071 :description => 'This is the description',
2071 :description => 'This is the description',
2072 :priority_id => 5,
2072 :priority_id => 5,
2073 :estimated_hours => '',
2073 :estimated_hours => '',
2074 :custom_field_values => {'2' => 'Value for field 2'}}
2074 :custom_field_values => {'2' => 'Value for field 2'}}
2075 end
2075 end
2076 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
2076 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
2077
2077
2078 assert_equal 1, ActionMailer::Base.deliveries.size
2078 assert_equal 1, ActionMailer::Base.deliveries.size
2079 end
2079 end
2080
2080
2081 def test_post_create_should_preserve_fields_values_on_validation_failure
2081 def test_post_create_should_preserve_fields_values_on_validation_failure
2082 @request.session[:user_id] = 2
2082 @request.session[:user_id] = 2
2083 post :create, :project_id => 1,
2083 post :create, :project_id => 1,
2084 :issue => {:tracker_id => 1,
2084 :issue => {:tracker_id => 1,
2085 # empty subject
2085 # empty subject
2086 :subject => '',
2086 :subject => '',
2087 :description => 'This is a description',
2087 :description => 'This is a description',
2088 :priority_id => 6,
2088 :priority_id => 6,
2089 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
2089 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
2090 assert_response :success
2090 assert_response :success
2091 assert_template 'new'
2091 assert_template 'new'
2092
2092
2093 assert_select 'textarea[name=?]', 'issue[description]', :text => 'This is a description'
2093 assert_select 'textarea[name=?]', 'issue[description]', :text => 'This is a description'
2094 assert_select 'select[name=?]', 'issue[priority_id]' do
2094 assert_select 'select[name=?]', 'issue[priority_id]' do
2095 assert_select 'option[value=6][selected=selected]', :text => 'High'
2095 assert_select 'option[value=6][selected=selected]', :text => 'High'
2096 end
2096 end
2097 # Custom fields
2097 # Custom fields
2098 assert_select 'select[name=?]', 'issue[custom_field_values][1]' do
2098 assert_select 'select[name=?]', 'issue[custom_field_values][1]' do
2099 assert_select 'option[value=Oracle][selected=selected]', :text => 'Oracle'
2099 assert_select 'option[value=Oracle][selected=selected]', :text => 'Oracle'
2100 end
2100 end
2101 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Value for field 2'
2101 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Value for field 2'
2102 end
2102 end
2103
2103
2104 def test_post_create_with_failure_should_preserve_watchers
2104 def test_post_create_with_failure_should_preserve_watchers
2105 assert !User.find(8).member_of?(Project.find(1))
2105 assert !User.find(8).member_of?(Project.find(1))
2106
2106
2107 @request.session[:user_id] = 2
2107 @request.session[:user_id] = 2
2108 post :create, :project_id => 1,
2108 post :create, :project_id => 1,
2109 :issue => {:tracker_id => 1,
2109 :issue => {:tracker_id => 1,
2110 :watcher_user_ids => ['3', '8']}
2110 :watcher_user_ids => ['3', '8']}
2111 assert_response :success
2111 assert_response :success
2112 assert_template 'new'
2112 assert_template 'new'
2113
2113
2114 assert_select 'input[name=?][value=2]:not(checked)', 'issue[watcher_user_ids][]'
2114 assert_select 'input[name=?][value=2]:not(checked)', 'issue[watcher_user_ids][]'
2115 assert_select 'input[name=?][value=3][checked=checked]', 'issue[watcher_user_ids][]'
2115 assert_select 'input[name=?][value=3][checked=checked]', 'issue[watcher_user_ids][]'
2116 assert_select 'input[name=?][value=8][checked=checked]', 'issue[watcher_user_ids][]'
2116 assert_select 'input[name=?][value=8][checked=checked]', 'issue[watcher_user_ids][]'
2117 end
2117 end
2118
2118
2119 def test_post_create_should_ignore_non_safe_attributes
2119 def test_post_create_should_ignore_non_safe_attributes
2120 @request.session[:user_id] = 2
2120 @request.session[:user_id] = 2
2121 assert_nothing_raised do
2121 assert_nothing_raised do
2122 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
2122 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
2123 end
2123 end
2124 end
2124 end
2125
2125
2126 def test_post_create_with_attachment
2126 def test_post_create_with_attachment
2127 set_tmp_attachments_directory
2127 set_tmp_attachments_directory
2128 @request.session[:user_id] = 2
2128 @request.session[:user_id] = 2
2129
2129
2130 assert_difference 'Issue.count' do
2130 assert_difference 'Issue.count' do
2131 assert_difference 'Attachment.count' do
2131 assert_difference 'Attachment.count' do
2132 post :create, :project_id => 1,
2132 post :create, :project_id => 1,
2133 :issue => { :tracker_id => '1', :subject => 'With attachment' },
2133 :issue => { :tracker_id => '1', :subject => 'With attachment' },
2134 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2134 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2135 end
2135 end
2136 end
2136 end
2137
2137
2138 issue = Issue.first(:order => 'id DESC')
2138 issue = Issue.first(:order => 'id DESC')
2139 attachment = Attachment.first(:order => 'id DESC')
2139 attachment = Attachment.first(:order => 'id DESC')
2140
2140
2141 assert_equal issue, attachment.container
2141 assert_equal issue, attachment.container
2142 assert_equal 2, attachment.author_id
2142 assert_equal 2, attachment.author_id
2143 assert_equal 'testfile.txt', attachment.filename
2143 assert_equal 'testfile.txt', attachment.filename
2144 assert_equal 'text/plain', attachment.content_type
2144 assert_equal 'text/plain', attachment.content_type
2145 assert_equal 'test file', attachment.description
2145 assert_equal 'test file', attachment.description
2146 assert_equal 59, attachment.filesize
2146 assert_equal 59, attachment.filesize
2147 assert File.exists?(attachment.diskfile)
2147 assert File.exists?(attachment.diskfile)
2148 assert_equal 59, File.size(attachment.diskfile)
2148 assert_equal 59, File.size(attachment.diskfile)
2149 end
2149 end
2150
2150
2151 def test_post_create_with_failure_should_save_attachments
2151 def test_post_create_with_failure_should_save_attachments
2152 set_tmp_attachments_directory
2152 set_tmp_attachments_directory
2153 @request.session[:user_id] = 2
2153 @request.session[:user_id] = 2
2154
2154
2155 assert_no_difference 'Issue.count' do
2155 assert_no_difference 'Issue.count' do
2156 assert_difference 'Attachment.count' do
2156 assert_difference 'Attachment.count' do
2157 post :create, :project_id => 1,
2157 post :create, :project_id => 1,
2158 :issue => { :tracker_id => '1', :subject => '' },
2158 :issue => { :tracker_id => '1', :subject => '' },
2159 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2159 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2160 assert_response :success
2160 assert_response :success
2161 assert_template 'new'
2161 assert_template 'new'
2162 end
2162 end
2163 end
2163 end
2164
2164
2165 attachment = Attachment.first(:order => 'id DESC')
2165 attachment = Attachment.first(:order => 'id DESC')
2166 assert_equal 'testfile.txt', attachment.filename
2166 assert_equal 'testfile.txt', attachment.filename
2167 assert File.exists?(attachment.diskfile)
2167 assert File.exists?(attachment.diskfile)
2168 assert_nil attachment.container
2168 assert_nil attachment.container
2169
2169
2170 assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token
2170 assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token
2171 assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt'
2171 assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt'
2172 end
2172 end
2173
2173
2174 def test_post_create_with_failure_should_keep_saved_attachments
2174 def test_post_create_with_failure_should_keep_saved_attachments
2175 set_tmp_attachments_directory
2175 set_tmp_attachments_directory
2176 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2176 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2177 @request.session[:user_id] = 2
2177 @request.session[:user_id] = 2
2178
2178
2179 assert_no_difference 'Issue.count' do
2179 assert_no_difference 'Issue.count' do
2180 assert_no_difference 'Attachment.count' do
2180 assert_no_difference 'Attachment.count' do
2181 post :create, :project_id => 1,
2181 post :create, :project_id => 1,
2182 :issue => { :tracker_id => '1', :subject => '' },
2182 :issue => { :tracker_id => '1', :subject => '' },
2183 :attachments => {'p0' => {'token' => attachment.token}}
2183 :attachments => {'p0' => {'token' => attachment.token}}
2184 assert_response :success
2184 assert_response :success
2185 assert_template 'new'
2185 assert_template 'new'
2186 end
2186 end
2187 end
2187 end
2188
2188
2189 assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token
2189 assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token
2190 assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt'
2190 assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt'
2191 end
2191 end
2192
2192
2193 def test_post_create_should_attach_saved_attachments
2193 def test_post_create_should_attach_saved_attachments
2194 set_tmp_attachments_directory
2194 set_tmp_attachments_directory
2195 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2195 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2196 @request.session[:user_id] = 2
2196 @request.session[:user_id] = 2
2197
2197
2198 assert_difference 'Issue.count' do
2198 assert_difference 'Issue.count' do
2199 assert_no_difference 'Attachment.count' do
2199 assert_no_difference 'Attachment.count' do
2200 post :create, :project_id => 1,
2200 post :create, :project_id => 1,
2201 :issue => { :tracker_id => '1', :subject => 'Saved attachments' },
2201 :issue => { :tracker_id => '1', :subject => 'Saved attachments' },
2202 :attachments => {'p0' => {'token' => attachment.token}}
2202 :attachments => {'p0' => {'token' => attachment.token}}
2203 assert_response 302
2203 assert_response 302
2204 end
2204 end
2205 end
2205 end
2206
2206
2207 issue = Issue.first(:order => 'id DESC')
2207 issue = Issue.first(:order => 'id DESC')
2208 assert_equal 1, issue.attachments.count
2208 assert_equal 1, issue.attachments.count
2209
2209
2210 attachment.reload
2210 attachment.reload
2211 assert_equal issue, attachment.container
2211 assert_equal issue, attachment.container
2212 end
2212 end
2213
2213
2214 context "without workflow privilege" do
2214 context "without workflow privilege" do
2215 setup do
2215 setup do
2216 WorkflowTransition.delete_all(["role_id = ?", Role.anonymous.id])
2216 WorkflowTransition.delete_all(["role_id = ?", Role.anonymous.id])
2217 Role.anonymous.add_permission! :add_issues, :add_issue_notes
2217 Role.anonymous.add_permission! :add_issues, :add_issue_notes
2218 end
2218 end
2219
2219
2220 context "#new" do
2220 context "#new" do
2221 should "propose default status only" do
2221 should "propose default status only" do
2222 get :new, :project_id => 1
2222 get :new, :project_id => 1
2223 assert_response :success
2223 assert_response :success
2224 assert_template 'new'
2224 assert_template 'new'
2225 assert_select 'select[name=?]', 'issue[status_id]' do
2225 assert_select 'select[name=?]', 'issue[status_id]' do
2226 assert_select 'option', 1
2226 assert_select 'option', 1
2227 assert_select 'option[value=?]', IssueStatus.default.id.to_s
2227 assert_select 'option[value=?]', IssueStatus.default.id.to_s
2228 end
2228 end
2229 end
2229 end
2230
2230
2231 should "accept default status" do
2231 should "accept default status" do
2232 assert_difference 'Issue.count' do
2232 assert_difference 'Issue.count' do
2233 post :create, :project_id => 1,
2233 post :create, :project_id => 1,
2234 :issue => {:tracker_id => 1,
2234 :issue => {:tracker_id => 1,
2235 :subject => 'This is an issue',
2235 :subject => 'This is an issue',
2236 :status_id => 1}
2236 :status_id => 1}
2237 end
2237 end
2238 issue = Issue.last(:order => 'id')
2238 issue = Issue.last(:order => 'id')
2239 assert_equal IssueStatus.default, issue.status
2239 assert_equal IssueStatus.default, issue.status
2240 end
2240 end
2241
2241
2242 should "ignore unauthorized status" do
2242 should "ignore unauthorized status" do
2243 assert_difference 'Issue.count' do
2243 assert_difference 'Issue.count' do
2244 post :create, :project_id => 1,
2244 post :create, :project_id => 1,
2245 :issue => {:tracker_id => 1,
2245 :issue => {:tracker_id => 1,
2246 :subject => 'This is an issue',
2246 :subject => 'This is an issue',
2247 :status_id => 3}
2247 :status_id => 3}
2248 end
2248 end
2249 issue = Issue.last(:order => 'id')
2249 issue = Issue.last(:order => 'id')
2250 assert_equal IssueStatus.default, issue.status
2250 assert_equal IssueStatus.default, issue.status
2251 end
2251 end
2252 end
2252 end
2253
2253
2254 context "#update" do
2254 context "#update" do
2255 should "ignore status change" do
2255 should "ignore status change" do
2256 assert_difference 'Journal.count' do
2256 assert_difference 'Journal.count' do
2257 put :update, :id => 1, :issue => {:status_id => 3, :notes => 'just trying'}
2257 put :update, :id => 1, :issue => {:status_id => 3, :notes => 'just trying'}
2258 end
2258 end
2259 assert_equal 1, Issue.find(1).status_id
2259 assert_equal 1, Issue.find(1).status_id
2260 end
2260 end
2261
2261
2262 should "ignore attributes changes" do
2262 should "ignore attributes changes" do
2263 assert_difference 'Journal.count' do
2263 assert_difference 'Journal.count' do
2264 put :update, :id => 1, :issue => {:subject => 'changed', :assigned_to_id => 2, :notes => 'just trying'}
2264 put :update, :id => 1, :issue => {:subject => 'changed', :assigned_to_id => 2, :notes => 'just trying'}
2265 end
2265 end
2266 issue = Issue.find(1)
2266 issue = Issue.find(1)
2267 assert_equal "Can't print recipes", issue.subject
2267 assert_equal "Can't print recipes", issue.subject
2268 assert_nil issue.assigned_to
2268 assert_nil issue.assigned_to
2269 end
2269 end
2270 end
2270 end
2271 end
2271 end
2272
2272
2273 context "with workflow privilege" do
2273 context "with workflow privilege" do
2274 setup do
2274 setup do
2275 WorkflowTransition.delete_all(["role_id = ?", Role.anonymous.id])
2275 WorkflowTransition.delete_all(["role_id = ?", Role.anonymous.id])
2276 WorkflowTransition.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
2276 WorkflowTransition.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
2277 WorkflowTransition.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
2277 WorkflowTransition.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
2278 Role.anonymous.add_permission! :add_issues, :add_issue_notes
2278 Role.anonymous.add_permission! :add_issues, :add_issue_notes
2279 end
2279 end
2280
2280
2281 context "#update" do
2281 context "#update" do
2282 should "accept authorized status" do
2282 should "accept authorized status" do
2283 assert_difference 'Journal.count' do
2283 assert_difference 'Journal.count' do
2284 put :update, :id => 1, :issue => {:status_id => 3, :notes => 'just trying'}
2284 put :update, :id => 1, :issue => {:status_id => 3, :notes => 'just trying'}
2285 end
2285 end
2286 assert_equal 3, Issue.find(1).status_id
2286 assert_equal 3, Issue.find(1).status_id
2287 end
2287 end
2288
2288
2289 should "ignore unauthorized status" do
2289 should "ignore unauthorized status" do
2290 assert_difference 'Journal.count' do
2290 assert_difference 'Journal.count' do
2291 put :update, :id => 1, :issue => {:status_id => 2, :notes => 'just trying'}
2291 put :update, :id => 1, :issue => {:status_id => 2, :notes => 'just trying'}
2292 end
2292 end
2293 assert_equal 1, Issue.find(1).status_id
2293 assert_equal 1, Issue.find(1).status_id
2294 end
2294 end
2295
2295
2296 should "accept authorized attributes changes" do
2296 should "accept authorized attributes changes" do
2297 assert_difference 'Journal.count' do
2297 assert_difference 'Journal.count' do
2298 put :update, :id => 1, :issue => {:assigned_to_id => 2, :notes => 'just trying'}
2298 put :update, :id => 1, :issue => {:assigned_to_id => 2, :notes => 'just trying'}
2299 end
2299 end
2300 issue = Issue.find(1)
2300 issue = Issue.find(1)
2301 assert_equal 2, issue.assigned_to_id
2301 assert_equal 2, issue.assigned_to_id
2302 end
2302 end
2303
2303
2304 should "ignore unauthorized attributes changes" do
2304 should "ignore unauthorized attributes changes" do
2305 assert_difference 'Journal.count' do
2305 assert_difference 'Journal.count' do
2306 put :update, :id => 1, :issue => {:subject => 'changed', :notes => 'just trying'}
2306 put :update, :id => 1, :issue => {:subject => 'changed', :notes => 'just trying'}
2307 end
2307 end
2308 issue = Issue.find(1)
2308 issue = Issue.find(1)
2309 assert_equal "Can't print recipes", issue.subject
2309 assert_equal "Can't print recipes", issue.subject
2310 end
2310 end
2311 end
2311 end
2312
2312
2313 context "and :edit_issues permission" do
2313 context "and :edit_issues permission" do
2314 setup do
2314 setup do
2315 Role.anonymous.add_permission! :add_issues, :edit_issues
2315 Role.anonymous.add_permission! :add_issues, :edit_issues
2316 end
2316 end
2317
2317
2318 should "accept authorized status" do
2318 should "accept authorized status" do
2319 assert_difference 'Journal.count' do
2319 assert_difference 'Journal.count' do
2320 put :update, :id => 1, :issue => {:status_id => 3, :notes => 'just trying'}
2320 put :update, :id => 1, :issue => {:status_id => 3, :notes => 'just trying'}
2321 end
2321 end
2322 assert_equal 3, Issue.find(1).status_id
2322 assert_equal 3, Issue.find(1).status_id
2323 end
2323 end
2324
2324
2325 should "ignore unauthorized status" do
2325 should "ignore unauthorized status" do
2326 assert_difference 'Journal.count' do
2326 assert_difference 'Journal.count' do
2327 put :update, :id => 1, :issue => {:status_id => 2, :notes => 'just trying'}
2327 put :update, :id => 1, :issue => {:status_id => 2, :notes => 'just trying'}
2328 end
2328 end
2329 assert_equal 1, Issue.find(1).status_id
2329 assert_equal 1, Issue.find(1).status_id
2330 end
2330 end
2331
2331
2332 should "accept authorized attributes changes" do
2332 should "accept authorized attributes changes" do
2333 assert_difference 'Journal.count' do
2333 assert_difference 'Journal.count' do
2334 put :update, :id => 1, :issue => {:subject => 'changed', :assigned_to_id => 2, :notes => 'just trying'}
2334 put :update, :id => 1, :issue => {:subject => 'changed', :assigned_to_id => 2, :notes => 'just trying'}
2335 end
2335 end
2336 issue = Issue.find(1)
2336 issue = Issue.find(1)
2337 assert_equal "changed", issue.subject
2337 assert_equal "changed", issue.subject
2338 assert_equal 2, issue.assigned_to_id
2338 assert_equal 2, issue.assigned_to_id
2339 end
2339 end
2340 end
2340 end
2341 end
2341 end
2342
2342
2343 def test_new_as_copy
2343 def test_new_as_copy
2344 @request.session[:user_id] = 2
2344 @request.session[:user_id] = 2
2345 get :new, :project_id => 1, :copy_from => 1
2345 get :new, :project_id => 1, :copy_from => 1
2346
2346
2347 assert_response :success
2347 assert_response :success
2348 assert_template 'new'
2348 assert_template 'new'
2349
2349
2350 assert_not_nil assigns(:issue)
2350 assert_not_nil assigns(:issue)
2351 orig = Issue.find(1)
2351 orig = Issue.find(1)
2352 assert_equal 1, assigns(:issue).project_id
2352 assert_equal 1, assigns(:issue).project_id
2353 assert_equal orig.subject, assigns(:issue).subject
2353 assert_equal orig.subject, assigns(:issue).subject
2354 assert assigns(:issue).copy?
2354 assert assigns(:issue).copy?
2355
2355
2356 assert_select 'form[id=issue-form][action=/projects/ecookbook/issues]' do
2356 assert_select 'form[id=issue-form][action=/projects/ecookbook/issues]' do
2357 assert_select 'select[name=?]', 'issue[project_id]' do
2357 assert_select 'select[name=?]', 'issue[project_id]' do
2358 assert_select 'option[value=1][selected=selected]', :text => 'eCookbook'
2358 assert_select 'option[value=1][selected=selected]', :text => 'eCookbook'
2359 assert_select 'option[value=2]:not([selected])', :text => 'OnlineStore'
2359 assert_select 'option[value=2]:not([selected])', :text => 'OnlineStore'
2360 end
2360 end
2361 assert_select 'input[name=copy_from][value=1]'
2361 assert_select 'input[name=copy_from][value=1]'
2362 end
2362 end
2363
2363
2364 # "New issue" menu item should not link to copy
2364 # "New issue" menu item should not link to copy
2365 assert_select '#main-menu a.new-issue[href=/projects/ecookbook/issues/new]'
2365 assert_select '#main-menu a.new-issue[href=/projects/ecookbook/issues/new]'
2366 end
2366 end
2367
2367
2368 def test_new_as_copy_with_attachments_should_show_copy_attachments_checkbox
2368 def test_new_as_copy_with_attachments_should_show_copy_attachments_checkbox
2369 @request.session[:user_id] = 2
2369 @request.session[:user_id] = 2
2370 issue = Issue.find(3)
2370 issue = Issue.find(3)
2371 assert issue.attachments.count > 0
2371 assert issue.attachments.count > 0
2372 get :new, :project_id => 1, :copy_from => 3
2372 get :new, :project_id => 1, :copy_from => 3
2373
2373
2374 assert_select 'input[name=copy_attachments][type=checkbox][checked=checked][value=1]'
2374 assert_select 'input[name=copy_attachments][type=checkbox][checked=checked][value=1]'
2375 end
2375 end
2376
2376
2377 def test_new_as_copy_without_attachments_should_not_show_copy_attachments_checkbox
2377 def test_new_as_copy_without_attachments_should_not_show_copy_attachments_checkbox
2378 @request.session[:user_id] = 2
2378 @request.session[:user_id] = 2
2379 issue = Issue.find(3)
2379 issue = Issue.find(3)
2380 issue.attachments.delete_all
2380 issue.attachments.delete_all
2381 get :new, :project_id => 1, :copy_from => 3
2381 get :new, :project_id => 1, :copy_from => 3
2382
2382
2383 assert_select 'input[name=copy_attachments]', 0
2383 assert_select 'input[name=copy_attachments]', 0
2384 end
2384 end
2385
2385
2386 def test_new_as_copy_with_subtasks_should_show_copy_subtasks_checkbox
2386 def test_new_as_copy_with_subtasks_should_show_copy_subtasks_checkbox
2387 @request.session[:user_id] = 2
2387 @request.session[:user_id] = 2
2388 issue = Issue.generate_with_descendants!
2388 issue = Issue.generate_with_descendants!
2389 get :new, :project_id => 1, :copy_from => issue.id
2389 get :new, :project_id => 1, :copy_from => issue.id
2390
2390
2391 assert_select 'input[type=checkbox][name=copy_subtasks][checked=checked][value=1]'
2391 assert_select 'input[type=checkbox][name=copy_subtasks][checked=checked][value=1]'
2392 end
2392 end
2393
2393
2394 def test_new_as_copy_with_invalid_issue_should_respond_with_404
2394 def test_new_as_copy_with_invalid_issue_should_respond_with_404
2395 @request.session[:user_id] = 2
2395 @request.session[:user_id] = 2
2396 get :new, :project_id => 1, :copy_from => 99999
2396 get :new, :project_id => 1, :copy_from => 99999
2397 assert_response 404
2397 assert_response 404
2398 end
2398 end
2399
2399
2400 def test_create_as_copy_on_different_project
2400 def test_create_as_copy_on_different_project
2401 @request.session[:user_id] = 2
2401 @request.session[:user_id] = 2
2402 assert_difference 'Issue.count' do
2402 assert_difference 'Issue.count' do
2403 post :create, :project_id => 1, :copy_from => 1,
2403 post :create, :project_id => 1, :copy_from => 1,
2404 :issue => {:project_id => '2', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
2404 :issue => {:project_id => '2', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
2405
2405
2406 assert_not_nil assigns(:issue)
2406 assert_not_nil assigns(:issue)
2407 assert assigns(:issue).copy?
2407 assert assigns(:issue).copy?
2408 end
2408 end
2409 issue = Issue.first(:order => 'id DESC')
2409 issue = Issue.first(:order => 'id DESC')
2410 assert_redirected_to "/issues/#{issue.id}"
2410 assert_redirected_to "/issues/#{issue.id}"
2411
2411
2412 assert_equal 2, issue.project_id
2412 assert_equal 2, issue.project_id
2413 assert_equal 3, issue.tracker_id
2413 assert_equal 3, issue.tracker_id
2414 assert_equal 'Copy', issue.subject
2414 assert_equal 'Copy', issue.subject
2415 end
2415 end
2416
2416
2417 def test_create_as_copy_should_copy_attachments
2417 def test_create_as_copy_should_copy_attachments
2418 @request.session[:user_id] = 2
2418 @request.session[:user_id] = 2
2419 issue = Issue.find(3)
2419 issue = Issue.find(3)
2420 count = issue.attachments.count
2420 count = issue.attachments.count
2421 assert count > 0
2421 assert count > 0
2422
2422
2423 assert_difference 'Issue.count' do
2423 assert_difference 'Issue.count' do
2424 assert_difference 'Attachment.count', count do
2424 assert_difference 'Attachment.count', count do
2425 assert_no_difference 'Journal.count' do
2425 assert_no_difference 'Journal.count' do
2426 post :create, :project_id => 1, :copy_from => 3,
2426 post :create, :project_id => 1, :copy_from => 3,
2427 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'},
2427 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'},
2428 :copy_attachments => '1'
2428 :copy_attachments => '1'
2429 end
2429 end
2430 end
2430 end
2431 end
2431 end
2432 copy = Issue.first(:order => 'id DESC')
2432 copy = Issue.first(:order => 'id DESC')
2433 assert_equal count, copy.attachments.count
2433 assert_equal count, copy.attachments.count
2434 assert_equal issue.attachments.map(&:filename).sort, copy.attachments.map(&:filename).sort
2434 assert_equal issue.attachments.map(&:filename).sort, copy.attachments.map(&:filename).sort
2435 end
2435 end
2436
2436
2437 def test_create_as_copy_without_copy_attachments_option_should_not_copy_attachments
2437 def test_create_as_copy_without_copy_attachments_option_should_not_copy_attachments
2438 @request.session[:user_id] = 2
2438 @request.session[:user_id] = 2
2439 issue = Issue.find(3)
2439 issue = Issue.find(3)
2440 count = issue.attachments.count
2440 count = issue.attachments.count
2441 assert count > 0
2441 assert count > 0
2442
2442
2443 assert_difference 'Issue.count' do
2443 assert_difference 'Issue.count' do
2444 assert_no_difference 'Attachment.count' do
2444 assert_no_difference 'Attachment.count' do
2445 assert_no_difference 'Journal.count' do
2445 assert_no_difference 'Journal.count' do
2446 post :create, :project_id => 1, :copy_from => 3,
2446 post :create, :project_id => 1, :copy_from => 3,
2447 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'}
2447 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'}
2448 end
2448 end
2449 end
2449 end
2450 end
2450 end
2451 copy = Issue.first(:order => 'id DESC')
2451 copy = Issue.first(:order => 'id DESC')
2452 assert_equal 0, copy.attachments.count
2452 assert_equal 0, copy.attachments.count
2453 end
2453 end
2454
2454
2455 def test_create_as_copy_with_attachments_should_add_new_files
2455 def test_create_as_copy_with_attachments_should_add_new_files
2456 @request.session[:user_id] = 2
2456 @request.session[:user_id] = 2
2457 issue = Issue.find(3)
2457 issue = Issue.find(3)
2458 count = issue.attachments.count
2458 count = issue.attachments.count
2459 assert count > 0
2459 assert count > 0
2460
2460
2461 assert_difference 'Issue.count' do
2461 assert_difference 'Issue.count' do
2462 assert_difference 'Attachment.count', count + 1 do
2462 assert_difference 'Attachment.count', count + 1 do
2463 assert_no_difference 'Journal.count' do
2463 assert_no_difference 'Journal.count' do
2464 post :create, :project_id => 1, :copy_from => 3,
2464 post :create, :project_id => 1, :copy_from => 3,
2465 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'},
2465 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'},
2466 :copy_attachments => '1',
2466 :copy_attachments => '1',
2467 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2467 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2468 end
2468 end
2469 end
2469 end
2470 end
2470 end
2471 copy = Issue.first(:order => 'id DESC')
2471 copy = Issue.first(:order => 'id DESC')
2472 assert_equal count + 1, copy.attachments.count
2472 assert_equal count + 1, copy.attachments.count
2473 end
2473 end
2474
2474
2475 def test_create_as_copy_should_add_relation_with_copied_issue
2475 def test_create_as_copy_should_add_relation_with_copied_issue
2476 @request.session[:user_id] = 2
2476 @request.session[:user_id] = 2
2477
2477
2478 assert_difference 'Issue.count' do
2478 assert_difference 'Issue.count' do
2479 assert_difference 'IssueRelation.count' do
2479 assert_difference 'IssueRelation.count' do
2480 post :create, :project_id => 1, :copy_from => 1,
2480 post :create, :project_id => 1, :copy_from => 1,
2481 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
2481 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
2482 end
2482 end
2483 end
2483 end
2484 copy = Issue.first(:order => 'id DESC')
2484 copy = Issue.first(:order => 'id DESC')
2485 assert_equal 1, copy.relations.size
2485 assert_equal 1, copy.relations.size
2486 end
2486 end
2487
2487
2488 def test_create_as_copy_should_copy_subtasks
2488 def test_create_as_copy_should_copy_subtasks
2489 @request.session[:user_id] = 2
2489 @request.session[:user_id] = 2
2490 issue = Issue.generate_with_descendants!
2490 issue = Issue.generate_with_descendants!
2491 count = issue.descendants.count
2491 count = issue.descendants.count
2492
2492
2493 assert_difference 'Issue.count', count+1 do
2493 assert_difference 'Issue.count', count+1 do
2494 assert_no_difference 'Journal.count' do
2494 assert_no_difference 'Journal.count' do
2495 post :create, :project_id => 1, :copy_from => issue.id,
2495 post :create, :project_id => 1, :copy_from => issue.id,
2496 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with subtasks'},
2496 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with subtasks'},
2497 :copy_subtasks => '1'
2497 :copy_subtasks => '1'
2498 end
2498 end
2499 end
2499 end
2500 copy = Issue.where(:parent_id => nil).first(:order => 'id DESC')
2500 copy = Issue.where(:parent_id => nil).first(:order => 'id DESC')
2501 assert_equal count, copy.descendants.count
2501 assert_equal count, copy.descendants.count
2502 assert_equal issue.descendants.map(&:subject).sort, copy.descendants.map(&:subject).sort
2502 assert_equal issue.descendants.map(&:subject).sort, copy.descendants.map(&:subject).sort
2503 end
2503 end
2504
2504
2505 def test_create_as_copy_without_copy_subtasks_option_should_not_copy_subtasks
2505 def test_create_as_copy_without_copy_subtasks_option_should_not_copy_subtasks
2506 @request.session[:user_id] = 2
2506 @request.session[:user_id] = 2
2507 issue = Issue.generate_with_descendants!
2507 issue = Issue.generate_with_descendants!
2508
2508
2509 assert_difference 'Issue.count', 1 do
2509 assert_difference 'Issue.count', 1 do
2510 assert_no_difference 'Journal.count' do
2510 assert_no_difference 'Journal.count' do
2511 post :create, :project_id => 1, :copy_from => 3,
2511 post :create, :project_id => 1, :copy_from => 3,
2512 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with subtasks'}
2512 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with subtasks'}
2513 end
2513 end
2514 end
2514 end
2515 copy = Issue.where(:parent_id => nil).first(:order => 'id DESC')
2515 copy = Issue.where(:parent_id => nil).first(:order => 'id DESC')
2516 assert_equal 0, copy.descendants.count
2516 assert_equal 0, copy.descendants.count
2517 end
2517 end
2518
2518
2519 def test_create_as_copy_with_failure
2519 def test_create_as_copy_with_failure
2520 @request.session[:user_id] = 2
2520 @request.session[:user_id] = 2
2521 post :create, :project_id => 1, :copy_from => 1,
2521 post :create, :project_id => 1, :copy_from => 1,
2522 :issue => {:project_id => '2', :tracker_id => '3', :status_id => '1', :subject => ''}
2522 :issue => {:project_id => '2', :tracker_id => '3', :status_id => '1', :subject => ''}
2523
2523
2524 assert_response :success
2524 assert_response :success
2525 assert_template 'new'
2525 assert_template 'new'
2526
2526
2527 assert_not_nil assigns(:issue)
2527 assert_not_nil assigns(:issue)
2528 assert assigns(:issue).copy?
2528 assert assigns(:issue).copy?
2529
2529
2530 assert_select 'form#issue-form[action=/projects/ecookbook/issues]' do
2530 assert_select 'form#issue-form[action=/projects/ecookbook/issues]' do
2531 assert_select 'select[name=?]', 'issue[project_id]' do
2531 assert_select 'select[name=?]', 'issue[project_id]' do
2532 assert_select 'option[value=1]:not([selected])', :text => 'eCookbook'
2532 assert_select 'option[value=1]:not([selected])', :text => 'eCookbook'
2533 assert_select 'option[value=2][selected=selected]', :text => 'OnlineStore'
2533 assert_select 'option[value=2][selected=selected]', :text => 'OnlineStore'
2534 end
2534 end
2535 assert_select 'input[name=copy_from][value=1]'
2535 assert_select 'input[name=copy_from][value=1]'
2536 end
2536 end
2537 end
2537 end
2538
2538
2539 def test_create_as_copy_on_project_without_permission_should_ignore_target_project
2539 def test_create_as_copy_on_project_without_permission_should_ignore_target_project
2540 @request.session[:user_id] = 2
2540 @request.session[:user_id] = 2
2541 assert !User.find(2).member_of?(Project.find(4))
2541 assert !User.find(2).member_of?(Project.find(4))
2542
2542
2543 assert_difference 'Issue.count' do
2543 assert_difference 'Issue.count' do
2544 post :create, :project_id => 1, :copy_from => 1,
2544 post :create, :project_id => 1, :copy_from => 1,
2545 :issue => {:project_id => '4', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
2545 :issue => {:project_id => '4', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
2546 end
2546 end
2547 issue = Issue.first(:order => 'id DESC')
2547 issue = Issue.first(:order => 'id DESC')
2548 assert_equal 1, issue.project_id
2548 assert_equal 1, issue.project_id
2549 end
2549 end
2550
2550
2551 def test_get_edit
2551 def test_get_edit
2552 @request.session[:user_id] = 2
2552 @request.session[:user_id] = 2
2553 get :edit, :id => 1
2553 get :edit, :id => 1
2554 assert_response :success
2554 assert_response :success
2555 assert_template 'edit'
2555 assert_template 'edit'
2556 assert_not_nil assigns(:issue)
2556 assert_not_nil assigns(:issue)
2557 assert_equal Issue.find(1), assigns(:issue)
2557 assert_equal Issue.find(1), assigns(:issue)
2558
2558
2559 # Be sure we don't display inactive IssuePriorities
2559 # Be sure we don't display inactive IssuePriorities
2560 assert ! IssuePriority.find(15).active?
2560 assert ! IssuePriority.find(15).active?
2561 assert_select 'select[name=?]', 'issue[priority_id]' do
2561 assert_select 'select[name=?]', 'issue[priority_id]' do
2562 assert_select 'option[value=15]', 0
2562 assert_select 'option[value=15]', 0
2563 end
2563 end
2564 end
2564 end
2565
2565
2566 def test_get_edit_should_display_the_time_entry_form_with_log_time_permission
2566 def test_get_edit_should_display_the_time_entry_form_with_log_time_permission
2567 @request.session[:user_id] = 2
2567 @request.session[:user_id] = 2
2568 Role.find_by_name('Manager').update_attribute :permissions, [:view_issues, :edit_issues, :log_time]
2568 Role.find_by_name('Manager').update_attribute :permissions, [:view_issues, :edit_issues, :log_time]
2569
2569
2570 get :edit, :id => 1
2570 get :edit, :id => 1
2571 assert_select 'input[name=?]', 'time_entry[hours]'
2571 assert_select 'input[name=?]', 'time_entry[hours]'
2572 end
2572 end
2573
2573
2574 def test_get_edit_should_not_display_the_time_entry_form_without_log_time_permission
2574 def test_get_edit_should_not_display_the_time_entry_form_without_log_time_permission
2575 @request.session[:user_id] = 2
2575 @request.session[:user_id] = 2
2576 Role.find_by_name('Manager').remove_permission! :log_time
2576 Role.find_by_name('Manager').remove_permission! :log_time
2577
2577
2578 get :edit, :id => 1
2578 get :edit, :id => 1
2579 assert_select 'input[name=?]', 'time_entry[hours]', 0
2579 assert_select 'input[name=?]', 'time_entry[hours]', 0
2580 end
2580 end
2581
2581
2582 def test_get_edit_with_params
2582 def test_get_edit_with_params
2583 @request.session[:user_id] = 2
2583 @request.session[:user_id] = 2
2584 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 },
2584 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 },
2585 :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => 10 }
2585 :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => 10 }
2586 assert_response :success
2586 assert_response :success
2587 assert_template 'edit'
2587 assert_template 'edit'
2588
2588
2589 issue = assigns(:issue)
2589 issue = assigns(:issue)
2590 assert_not_nil issue
2590 assert_not_nil issue
2591
2591
2592 assert_equal 5, issue.status_id
2592 assert_equal 5, issue.status_id
2593 assert_select 'select[name=?]', 'issue[status_id]' do
2593 assert_select 'select[name=?]', 'issue[status_id]' do
2594 assert_select 'option[value=5][selected=selected]', :text => 'Closed'
2594 assert_select 'option[value=5][selected=selected]', :text => 'Closed'
2595 end
2595 end
2596
2596
2597 assert_equal 7, issue.priority_id
2597 assert_equal 7, issue.priority_id
2598 assert_select 'select[name=?]', 'issue[priority_id]' do
2598 assert_select 'select[name=?]', 'issue[priority_id]' do
2599 assert_select 'option[value=7][selected=selected]', :text => 'Urgent'
2599 assert_select 'option[value=7][selected=selected]', :text => 'Urgent'
2600 end
2600 end
2601
2601
2602 assert_select 'input[name=?][value=2.5]', 'time_entry[hours]'
2602 assert_select 'input[name=?][value=2.5]', 'time_entry[hours]'
2603 assert_select 'select[name=?]', 'time_entry[activity_id]' do
2603 assert_select 'select[name=?]', 'time_entry[activity_id]' do
2604 assert_select 'option[value=10][selected=selected]', :text => 'Development'
2604 assert_select 'option[value=10][selected=selected]', :text => 'Development'
2605 end
2605 end
2606 assert_select 'input[name=?][value=test_get_edit_with_params]', 'time_entry[comments]'
2606 assert_select 'input[name=?][value=test_get_edit_with_params]', 'time_entry[comments]'
2607 end
2607 end
2608
2608
2609 def test_get_edit_with_multi_custom_field
2609 def test_get_edit_with_multi_custom_field
2610 field = CustomField.find(1)
2610 field = CustomField.find(1)
2611 field.update_attribute :multiple, true
2611 field.update_attribute :multiple, true
2612 issue = Issue.find(1)
2612 issue = Issue.find(1)
2613 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
2613 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
2614 issue.save!
2614 issue.save!
2615
2615
2616 @request.session[:user_id] = 2
2616 @request.session[:user_id] = 2
2617 get :edit, :id => 1
2617 get :edit, :id => 1
2618 assert_response :success
2618 assert_response :success
2619 assert_template 'edit'
2619 assert_template 'edit'
2620
2620
2621 assert_select 'select[name=?][multiple=multiple]', 'issue[custom_field_values][1][]' do
2621 assert_select 'select[name=?][multiple=multiple]', 'issue[custom_field_values][1][]' do
2622 assert_select 'option', 3
2622 assert_select 'option', 3
2623 assert_select 'option[value=MySQL][selected=selected]'
2623 assert_select 'option[value=MySQL][selected=selected]'
2624 assert_select 'option[value=Oracle][selected=selected]'
2624 assert_select 'option[value=Oracle][selected=selected]'
2625 assert_select 'option[value=PostgreSQL]:not([selected])'
2625 assert_select 'option[value=PostgreSQL]:not([selected])'
2626 end
2626 end
2627 end
2627 end
2628
2628
2629 def test_update_edit_form
2629 def test_update_edit_form
2630 @request.session[:user_id] = 2
2630 @request.session[:user_id] = 2
2631 xhr :put, :new, :project_id => 1,
2631 xhr :put, :new, :project_id => 1,
2632 :id => 1,
2632 :id => 1,
2633 :issue => {:tracker_id => 2,
2633 :issue => {:tracker_id => 2,
2634 :subject => 'This is the test_new issue',
2634 :subject => 'This is the test_new issue',
2635 :description => 'This is the description',
2635 :description => 'This is the description',
2636 :priority_id => 5}
2636 :priority_id => 5}
2637 assert_response :success
2637 assert_response :success
2638 assert_equal 'text/javascript', response.content_type
2638 assert_equal 'text/javascript', response.content_type
2639 assert_template 'update_form'
2639 assert_template 'update_form'
2640 assert_template 'form'
2640 assert_template 'form'
2641
2641
2642 issue = assigns(:issue)
2642 issue = assigns(:issue)
2643 assert_kind_of Issue, issue
2643 assert_kind_of Issue, issue
2644 assert_equal 1, issue.id
2644 assert_equal 1, issue.id
2645 assert_equal 1, issue.project_id
2645 assert_equal 1, issue.project_id
2646 assert_equal 2, issue.tracker_id
2646 assert_equal 2, issue.tracker_id
2647 assert_equal 'This is the test_new issue', issue.subject
2647 assert_equal 'This is the test_new issue', issue.subject
2648 end
2648 end
2649
2649
2650 def test_update_edit_form_should_keep_issue_author
2650 def test_update_edit_form_should_keep_issue_author
2651 @request.session[:user_id] = 3
2651 @request.session[:user_id] = 3
2652 xhr :put, :new, :project_id => 1, :id => 1, :issue => {:subject => 'Changed'}
2652 xhr :put, :new, :project_id => 1, :id => 1, :issue => {:subject => 'Changed'}
2653 assert_response :success
2653 assert_response :success
2654 assert_equal 'text/javascript', response.content_type
2654 assert_equal 'text/javascript', response.content_type
2655
2655
2656 issue = assigns(:issue)
2656 issue = assigns(:issue)
2657 assert_equal User.find(2), issue.author
2657 assert_equal User.find(2), issue.author
2658 assert_equal 2, issue.author_id
2658 assert_equal 2, issue.author_id
2659 assert_not_equal User.current, issue.author
2659 assert_not_equal User.current, issue.author
2660 end
2660 end
2661
2661
2662 def test_update_edit_form_should_propose_transitions_based_on_initial_status
2662 def test_update_edit_form_should_propose_transitions_based_on_initial_status
2663 @request.session[:user_id] = 2
2663 @request.session[:user_id] = 2
2664 WorkflowTransition.delete_all
2664 WorkflowTransition.delete_all
2665 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1)
2665 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1)
2666 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 5)
2666 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 5)
2667 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 5, :new_status_id => 4)
2667 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 5, :new_status_id => 4)
2668
2668
2669 xhr :put, :new, :project_id => 1,
2669 xhr :put, :new, :project_id => 1,
2670 :id => 2,
2670 :id => 2,
2671 :issue => {:tracker_id => 2,
2671 :issue => {:tracker_id => 2,
2672 :status_id => 5,
2672 :status_id => 5,
2673 :subject => 'This is an issue'}
2673 :subject => 'This is an issue'}
2674
2674
2675 assert_equal 5, assigns(:issue).status_id
2675 assert_equal 5, assigns(:issue).status_id
2676 assert_equal [1,2,5], assigns(:allowed_statuses).map(&:id).sort
2676 assert_equal [1,2,5], assigns(:allowed_statuses).map(&:id).sort
2677 end
2677 end
2678
2678
2679 def test_update_edit_form_with_project_change
2679 def test_update_edit_form_with_project_change
2680 @request.session[:user_id] = 2
2680 @request.session[:user_id] = 2
2681 xhr :put, :new, :project_id => 1,
2681 xhr :put, :new, :project_id => 1,
2682 :id => 1,
2682 :id => 1,
2683 :issue => {:project_id => 2,
2683 :issue => {:project_id => 2,
2684 :tracker_id => 2,
2684 :tracker_id => 2,
2685 :subject => 'This is the test_new issue',
2685 :subject => 'This is the test_new issue',
2686 :description => 'This is the description',
2686 :description => 'This is the description',
2687 :priority_id => 5}
2687 :priority_id => 5}
2688 assert_response :success
2688 assert_response :success
2689 assert_template 'form'
2689 assert_template 'form'
2690
2690
2691 issue = assigns(:issue)
2691 issue = assigns(:issue)
2692 assert_kind_of Issue, issue
2692 assert_kind_of Issue, issue
2693 assert_equal 1, issue.id
2693 assert_equal 1, issue.id
2694 assert_equal 2, issue.project_id
2694 assert_equal 2, issue.project_id
2695 assert_equal 2, issue.tracker_id
2695 assert_equal 2, issue.tracker_id
2696 assert_equal 'This is the test_new issue', issue.subject
2696 assert_equal 'This is the test_new issue', issue.subject
2697 end
2697 end
2698
2698
2699 def test_put_update_without_custom_fields_param
2699 def test_put_update_without_custom_fields_param
2700 @request.session[:user_id] = 2
2700 @request.session[:user_id] = 2
2701 ActionMailer::Base.deliveries.clear
2701 ActionMailer::Base.deliveries.clear
2702
2702
2703 issue = Issue.find(1)
2703 issue = Issue.find(1)
2704 assert_equal '125', issue.custom_value_for(2).value
2704 assert_equal '125', issue.custom_value_for(2).value
2705 old_subject = issue.subject
2705 old_subject = issue.subject
2706 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
2706 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
2707
2707
2708 assert_difference('Journal.count') do
2708 assert_difference('Journal.count') do
2709 assert_difference('JournalDetail.count', 2) do
2709 assert_difference('JournalDetail.count', 2) do
2710 put :update, :id => 1, :issue => {:subject => new_subject,
2710 put :update, :id => 1, :issue => {:subject => new_subject,
2711 :priority_id => '6',
2711 :priority_id => '6',
2712 :category_id => '1' # no change
2712 :category_id => '1' # no change
2713 }
2713 }
2714 end
2714 end
2715 end
2715 end
2716 assert_redirected_to :action => 'show', :id => '1'
2716 assert_redirected_to :action => 'show', :id => '1'
2717 issue.reload
2717 issue.reload
2718 assert_equal new_subject, issue.subject
2718 assert_equal new_subject, issue.subject
2719 # Make sure custom fields were not cleared
2719 # Make sure custom fields were not cleared
2720 assert_equal '125', issue.custom_value_for(2).value
2720 assert_equal '125', issue.custom_value_for(2).value
2721
2721
2722 mail = ActionMailer::Base.deliveries.last
2722 mail = ActionMailer::Base.deliveries.last
2723 assert_not_nil mail
2723 assert_not_nil mail
2724 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2724 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2725 assert_mail_body_match "Subject changed from #{old_subject} to #{new_subject}", mail
2725 assert_mail_body_match "Subject changed from #{old_subject} to #{new_subject}", mail
2726 end
2726 end
2727
2727
2728 def test_put_update_with_project_change
2728 def test_put_update_with_project_change
2729 @request.session[:user_id] = 2
2729 @request.session[:user_id] = 2
2730 ActionMailer::Base.deliveries.clear
2730 ActionMailer::Base.deliveries.clear
2731
2731
2732 assert_difference('Journal.count') do
2732 assert_difference('Journal.count') do
2733 assert_difference('JournalDetail.count', 3) do
2733 assert_difference('JournalDetail.count', 3) do
2734 put :update, :id => 1, :issue => {:project_id => '2',
2734 put :update, :id => 1, :issue => {:project_id => '2',
2735 :tracker_id => '1', # no change
2735 :tracker_id => '1', # no change
2736 :priority_id => '6',
2736 :priority_id => '6',
2737 :category_id => '3'
2737 :category_id => '3'
2738 }
2738 }
2739 end
2739 end
2740 end
2740 end
2741 assert_redirected_to :action => 'show', :id => '1'
2741 assert_redirected_to :action => 'show', :id => '1'
2742 issue = Issue.find(1)
2742 issue = Issue.find(1)
2743 assert_equal 2, issue.project_id
2743 assert_equal 2, issue.project_id
2744 assert_equal 1, issue.tracker_id
2744 assert_equal 1, issue.tracker_id
2745 assert_equal 6, issue.priority_id
2745 assert_equal 6, issue.priority_id
2746 assert_equal 3, issue.category_id
2746 assert_equal 3, issue.category_id
2747
2747
2748 mail = ActionMailer::Base.deliveries.last
2748 mail = ActionMailer::Base.deliveries.last
2749 assert_not_nil mail
2749 assert_not_nil mail
2750 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2750 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2751 assert_mail_body_match "Project changed from eCookbook to OnlineStore", mail
2751 assert_mail_body_match "Project changed from eCookbook to OnlineStore", mail
2752 end
2752 end
2753
2753
2754 def test_put_update_with_tracker_change
2754 def test_put_update_with_tracker_change
2755 @request.session[:user_id] = 2
2755 @request.session[:user_id] = 2
2756 ActionMailer::Base.deliveries.clear
2756 ActionMailer::Base.deliveries.clear
2757
2757
2758 assert_difference('Journal.count') do
2758 assert_difference('Journal.count') do
2759 assert_difference('JournalDetail.count', 2) do
2759 assert_difference('JournalDetail.count', 2) do
2760 put :update, :id => 1, :issue => {:project_id => '1',
2760 put :update, :id => 1, :issue => {:project_id => '1',
2761 :tracker_id => '2',
2761 :tracker_id => '2',
2762 :priority_id => '6'
2762 :priority_id => '6'
2763 }
2763 }
2764 end
2764 end
2765 end
2765 end
2766 assert_redirected_to :action => 'show', :id => '1'
2766 assert_redirected_to :action => 'show', :id => '1'
2767 issue = Issue.find(1)
2767 issue = Issue.find(1)
2768 assert_equal 1, issue.project_id
2768 assert_equal 1, issue.project_id
2769 assert_equal 2, issue.tracker_id
2769 assert_equal 2, issue.tracker_id
2770 assert_equal 6, issue.priority_id
2770 assert_equal 6, issue.priority_id
2771 assert_equal 1, issue.category_id
2771 assert_equal 1, issue.category_id
2772
2772
2773 mail = ActionMailer::Base.deliveries.last
2773 mail = ActionMailer::Base.deliveries.last
2774 assert_not_nil mail
2774 assert_not_nil mail
2775 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2775 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2776 assert_mail_body_match "Tracker changed from Bug to Feature request", mail
2776 assert_mail_body_match "Tracker changed from Bug to Feature request", mail
2777 end
2777 end
2778
2778
2779 def test_put_update_with_custom_field_change
2779 def test_put_update_with_custom_field_change
2780 @request.session[:user_id] = 2
2780 @request.session[:user_id] = 2
2781 issue = Issue.find(1)
2781 issue = Issue.find(1)
2782 assert_equal '125', issue.custom_value_for(2).value
2782 assert_equal '125', issue.custom_value_for(2).value
2783
2783
2784 assert_difference('Journal.count') do
2784 assert_difference('Journal.count') do
2785 assert_difference('JournalDetail.count', 3) do
2785 assert_difference('JournalDetail.count', 3) do
2786 put :update, :id => 1, :issue => {:subject => 'Custom field change',
2786 put :update, :id => 1, :issue => {:subject => 'Custom field change',
2787 :priority_id => '6',
2787 :priority_id => '6',
2788 :category_id => '1', # no change
2788 :category_id => '1', # no change
2789 :custom_field_values => { '2' => 'New custom value' }
2789 :custom_field_values => { '2' => 'New custom value' }
2790 }
2790 }
2791 end
2791 end
2792 end
2792 end
2793 assert_redirected_to :action => 'show', :id => '1'
2793 assert_redirected_to :action => 'show', :id => '1'
2794 issue.reload
2794 issue.reload
2795 assert_equal 'New custom value', issue.custom_value_for(2).value
2795 assert_equal 'New custom value', issue.custom_value_for(2).value
2796
2796
2797 mail = ActionMailer::Base.deliveries.last
2797 mail = ActionMailer::Base.deliveries.last
2798 assert_not_nil mail
2798 assert_not_nil mail
2799 assert_mail_body_match "Searchable field changed from 125 to New custom value", mail
2799 assert_mail_body_match "Searchable field changed from 125 to New custom value", mail
2800 end
2800 end
2801
2801
2802 def test_put_update_with_multi_custom_field_change
2802 def test_put_update_with_multi_custom_field_change
2803 field = CustomField.find(1)
2803 field = CustomField.find(1)
2804 field.update_attribute :multiple, true
2804 field.update_attribute :multiple, true
2805 issue = Issue.find(1)
2805 issue = Issue.find(1)
2806 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
2806 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
2807 issue.save!
2807 issue.save!
2808
2808
2809 @request.session[:user_id] = 2
2809 @request.session[:user_id] = 2
2810 assert_difference('Journal.count') do
2810 assert_difference('Journal.count') do
2811 assert_difference('JournalDetail.count', 3) do
2811 assert_difference('JournalDetail.count', 3) do
2812 put :update, :id => 1,
2812 put :update, :id => 1,
2813 :issue => {
2813 :issue => {
2814 :subject => 'Custom field change',
2814 :subject => 'Custom field change',
2815 :custom_field_values => { '1' => ['', 'Oracle', 'PostgreSQL'] }
2815 :custom_field_values => { '1' => ['', 'Oracle', 'PostgreSQL'] }
2816 }
2816 }
2817 end
2817 end
2818 end
2818 end
2819 assert_redirected_to :action => 'show', :id => '1'
2819 assert_redirected_to :action => 'show', :id => '1'
2820 assert_equal ['Oracle', 'PostgreSQL'], Issue.find(1).custom_field_value(1).sort
2820 assert_equal ['Oracle', 'PostgreSQL'], Issue.find(1).custom_field_value(1).sort
2821 end
2821 end
2822
2822
2823 def test_put_update_with_status_and_assignee_change
2823 def test_put_update_with_status_and_assignee_change
2824 issue = Issue.find(1)
2824 issue = Issue.find(1)
2825 assert_equal 1, issue.status_id
2825 assert_equal 1, issue.status_id
2826 @request.session[:user_id] = 2
2826 @request.session[:user_id] = 2
2827 assert_difference('TimeEntry.count', 0) do
2827 assert_difference('TimeEntry.count', 0) do
2828 put :update,
2828 put :update,
2829 :id => 1,
2829 :id => 1,
2830 :issue => { :status_id => 2, :assigned_to_id => 3, :notes => 'Assigned to dlopper' },
2830 :issue => { :status_id => 2, :assigned_to_id => 3, :notes => 'Assigned to dlopper' },
2831 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
2831 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
2832 end
2832 end
2833 assert_redirected_to :action => 'show', :id => '1'
2833 assert_redirected_to :action => 'show', :id => '1'
2834 issue.reload
2834 issue.reload
2835 assert_equal 2, issue.status_id
2835 assert_equal 2, issue.status_id
2836 j = Journal.order('id DESC').first
2836 j = Journal.order('id DESC').first
2837 assert_equal 'Assigned to dlopper', j.notes
2837 assert_equal 'Assigned to dlopper', j.notes
2838 assert_equal 2, j.details.size
2838 assert_equal 2, j.details.size
2839
2839
2840 mail = ActionMailer::Base.deliveries.last
2840 mail = ActionMailer::Base.deliveries.last
2841 assert_mail_body_match "Status changed from New to Assigned", mail
2841 assert_mail_body_match "Status changed from New to Assigned", mail
2842 # subject should contain the new status
2842 # subject should contain the new status
2843 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
2843 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
2844 end
2844 end
2845
2845
2846 def test_put_update_with_note_only
2846 def test_put_update_with_note_only
2847 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
2847 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
2848 # anonymous user
2848 # anonymous user
2849 put :update,
2849 put :update,
2850 :id => 1,
2850 :id => 1,
2851 :issue => { :notes => notes }
2851 :issue => { :notes => notes }
2852 assert_redirected_to :action => 'show', :id => '1'
2852 assert_redirected_to :action => 'show', :id => '1'
2853 j = Journal.order('id DESC').first
2853 j = Journal.order('id DESC').first
2854 assert_equal notes, j.notes
2854 assert_equal notes, j.notes
2855 assert_equal 0, j.details.size
2855 assert_equal 0, j.details.size
2856 assert_equal User.anonymous, j.user
2856 assert_equal User.anonymous, j.user
2857
2857
2858 mail = ActionMailer::Base.deliveries.last
2858 mail = ActionMailer::Base.deliveries.last
2859 assert_mail_body_match notes, mail
2859 assert_mail_body_match notes, mail
2860 end
2860 end
2861
2861
2862 def test_put_update_with_private_note_only
2862 def test_put_update_with_private_note_only
2863 notes = 'Private note'
2863 notes = 'Private note'
2864 @request.session[:user_id] = 2
2864 @request.session[:user_id] = 2
2865
2865
2866 assert_difference 'Journal.count' do
2866 assert_difference 'Journal.count' do
2867 put :update, :id => 1, :issue => {:notes => notes, :private_notes => '1'}
2867 put :update, :id => 1, :issue => {:notes => notes, :private_notes => '1'}
2868 assert_redirected_to :action => 'show', :id => '1'
2868 assert_redirected_to :action => 'show', :id => '1'
2869 end
2869 end
2870
2870
2871 j = Journal.order('id DESC').first
2871 j = Journal.order('id DESC').first
2872 assert_equal notes, j.notes
2872 assert_equal notes, j.notes
2873 assert_equal true, j.private_notes
2873 assert_equal true, j.private_notes
2874 end
2874 end
2875
2875
2876 def test_put_update_with_private_note_and_changes
2876 def test_put_update_with_private_note_and_changes
2877 notes = 'Private note'
2877 notes = 'Private note'
2878 @request.session[:user_id] = 2
2878 @request.session[:user_id] = 2
2879
2879
2880 assert_difference 'Journal.count', 2 do
2880 assert_difference 'Journal.count', 2 do
2881 put :update, :id => 1, :issue => {:subject => 'New subject', :notes => notes, :private_notes => '1'}
2881 put :update, :id => 1, :issue => {:subject => 'New subject', :notes => notes, :private_notes => '1'}
2882 assert_redirected_to :action => 'show', :id => '1'
2882 assert_redirected_to :action => 'show', :id => '1'
2883 end
2883 end
2884
2884
2885 j = Journal.order('id DESC').first
2885 j = Journal.order('id DESC').first
2886 assert_equal notes, j.notes
2886 assert_equal notes, j.notes
2887 assert_equal true, j.private_notes
2887 assert_equal true, j.private_notes
2888 assert_equal 0, j.details.count
2888 assert_equal 0, j.details.count
2889
2889
2890 j = Journal.order('id DESC').offset(1).first
2890 j = Journal.order('id DESC').offset(1).first
2891 assert_nil j.notes
2891 assert_nil j.notes
2892 assert_equal false, j.private_notes
2892 assert_equal false, j.private_notes
2893 assert_equal 1, j.details.count
2893 assert_equal 1, j.details.count
2894 end
2894 end
2895
2895
2896 def test_put_update_with_note_and_spent_time
2896 def test_put_update_with_note_and_spent_time
2897 @request.session[:user_id] = 2
2897 @request.session[:user_id] = 2
2898 spent_hours_before = Issue.find(1).spent_hours
2898 spent_hours_before = Issue.find(1).spent_hours
2899 assert_difference('TimeEntry.count') do
2899 assert_difference('TimeEntry.count') do
2900 put :update,
2900 put :update,
2901 :id => 1,
2901 :id => 1,
2902 :issue => { :notes => '2.5 hours added' },
2902 :issue => { :notes => '2.5 hours added' },
2903 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
2903 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
2904 end
2904 end
2905 assert_redirected_to :action => 'show', :id => '1'
2905 assert_redirected_to :action => 'show', :id => '1'
2906
2906
2907 issue = Issue.find(1)
2907 issue = Issue.find(1)
2908
2908
2909 j = Journal.order('id DESC').first
2909 j = Journal.order('id DESC').first
2910 assert_equal '2.5 hours added', j.notes
2910 assert_equal '2.5 hours added', j.notes
2911 assert_equal 0, j.details.size
2911 assert_equal 0, j.details.size
2912
2912
2913 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
2913 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
2914 assert_not_nil t
2914 assert_not_nil t
2915 assert_equal 2.5, t.hours
2915 assert_equal 2.5, t.hours
2916 assert_equal spent_hours_before + 2.5, issue.spent_hours
2916 assert_equal spent_hours_before + 2.5, issue.spent_hours
2917 end
2917 end
2918
2918
2919 def test_put_update_should_preserve_parent_issue_even_if_not_visible
2920 parent = Issue.generate!(:project_id => 1, :is_private => true)
2921 issue = Issue.generate!(:parent_issue_id => parent.id)
2922 assert !parent.visible?(User.find(3))
2923 @request.session[:user_id] = 3
2924
2925 get :edit, :id => issue.id
2926 assert_select 'input[name=?][value=?]', 'issue[parent_issue_id]', parent.id.to_s
2927
2928 put :update, :id => issue.id, :issue => {:subject => 'New subject', :parent_issue_id => parent.id.to_s}
2929 assert_response 302
2930 assert_equal parent, issue.parent
2931 end
2932
2919 def test_put_update_with_attachment_only
2933 def test_put_update_with_attachment_only
2920 set_tmp_attachments_directory
2934 set_tmp_attachments_directory
2921
2935
2922 # Delete all fixtured journals, a race condition can occur causing the wrong
2936 # Delete all fixtured journals, a race condition can occur causing the wrong
2923 # journal to get fetched in the next find.
2937 # journal to get fetched in the next find.
2924 Journal.delete_all
2938 Journal.delete_all
2925
2939
2926 # anonymous user
2940 # anonymous user
2927 assert_difference 'Attachment.count' do
2941 assert_difference 'Attachment.count' do
2928 put :update, :id => 1,
2942 put :update, :id => 1,
2929 :issue => {:notes => ''},
2943 :issue => {:notes => ''},
2930 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2944 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2931 end
2945 end
2932
2946
2933 assert_redirected_to :action => 'show', :id => '1'
2947 assert_redirected_to :action => 'show', :id => '1'
2934 j = Issue.find(1).journals.reorder('id DESC').first
2948 j = Issue.find(1).journals.reorder('id DESC').first
2935 assert j.notes.blank?
2949 assert j.notes.blank?
2936 assert_equal 1, j.details.size
2950 assert_equal 1, j.details.size
2937 assert_equal 'testfile.txt', j.details.first.value
2951 assert_equal 'testfile.txt', j.details.first.value
2938 assert_equal User.anonymous, j.user
2952 assert_equal User.anonymous, j.user
2939
2953
2940 attachment = Attachment.first(:order => 'id DESC')
2954 attachment = Attachment.first(:order => 'id DESC')
2941 assert_equal Issue.find(1), attachment.container
2955 assert_equal Issue.find(1), attachment.container
2942 assert_equal User.anonymous, attachment.author
2956 assert_equal User.anonymous, attachment.author
2943 assert_equal 'testfile.txt', attachment.filename
2957 assert_equal 'testfile.txt', attachment.filename
2944 assert_equal 'text/plain', attachment.content_type
2958 assert_equal 'text/plain', attachment.content_type
2945 assert_equal 'test file', attachment.description
2959 assert_equal 'test file', attachment.description
2946 assert_equal 59, attachment.filesize
2960 assert_equal 59, attachment.filesize
2947 assert File.exists?(attachment.diskfile)
2961 assert File.exists?(attachment.diskfile)
2948 assert_equal 59, File.size(attachment.diskfile)
2962 assert_equal 59, File.size(attachment.diskfile)
2949
2963
2950 mail = ActionMailer::Base.deliveries.last
2964 mail = ActionMailer::Base.deliveries.last
2951 assert_mail_body_match 'testfile.txt', mail
2965 assert_mail_body_match 'testfile.txt', mail
2952 end
2966 end
2953
2967
2954 def test_put_update_with_failure_should_save_attachments
2968 def test_put_update_with_failure_should_save_attachments
2955 set_tmp_attachments_directory
2969 set_tmp_attachments_directory
2956 @request.session[:user_id] = 2
2970 @request.session[:user_id] = 2
2957
2971
2958 assert_no_difference 'Journal.count' do
2972 assert_no_difference 'Journal.count' do
2959 assert_difference 'Attachment.count' do
2973 assert_difference 'Attachment.count' do
2960 put :update, :id => 1,
2974 put :update, :id => 1,
2961 :issue => { :subject => '' },
2975 :issue => { :subject => '' },
2962 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2976 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2963 assert_response :success
2977 assert_response :success
2964 assert_template 'edit'
2978 assert_template 'edit'
2965 end
2979 end
2966 end
2980 end
2967
2981
2968 attachment = Attachment.first(:order => 'id DESC')
2982 attachment = Attachment.first(:order => 'id DESC')
2969 assert_equal 'testfile.txt', attachment.filename
2983 assert_equal 'testfile.txt', attachment.filename
2970 assert File.exists?(attachment.diskfile)
2984 assert File.exists?(attachment.diskfile)
2971 assert_nil attachment.container
2985 assert_nil attachment.container
2972
2986
2973 assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token
2987 assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token
2974 assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt'
2988 assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt'
2975 end
2989 end
2976
2990
2977 def test_put_update_with_failure_should_keep_saved_attachments
2991 def test_put_update_with_failure_should_keep_saved_attachments
2978 set_tmp_attachments_directory
2992 set_tmp_attachments_directory
2979 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2993 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2980 @request.session[:user_id] = 2
2994 @request.session[:user_id] = 2
2981
2995
2982 assert_no_difference 'Journal.count' do
2996 assert_no_difference 'Journal.count' do
2983 assert_no_difference 'Attachment.count' do
2997 assert_no_difference 'Attachment.count' do
2984 put :update, :id => 1,
2998 put :update, :id => 1,
2985 :issue => { :subject => '' },
2999 :issue => { :subject => '' },
2986 :attachments => {'p0' => {'token' => attachment.token}}
3000 :attachments => {'p0' => {'token' => attachment.token}}
2987 assert_response :success
3001 assert_response :success
2988 assert_template 'edit'
3002 assert_template 'edit'
2989 end
3003 end
2990 end
3004 end
2991
3005
2992 assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token
3006 assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token
2993 assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt'
3007 assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt'
2994 end
3008 end
2995
3009
2996 def test_put_update_should_attach_saved_attachments
3010 def test_put_update_should_attach_saved_attachments
2997 set_tmp_attachments_directory
3011 set_tmp_attachments_directory
2998 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
3012 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2999 @request.session[:user_id] = 2
3013 @request.session[:user_id] = 2
3000
3014
3001 assert_difference 'Journal.count' do
3015 assert_difference 'Journal.count' do
3002 assert_difference 'JournalDetail.count' do
3016 assert_difference 'JournalDetail.count' do
3003 assert_no_difference 'Attachment.count' do
3017 assert_no_difference 'Attachment.count' do
3004 put :update, :id => 1,
3018 put :update, :id => 1,
3005 :issue => {:notes => 'Attachment added'},
3019 :issue => {:notes => 'Attachment added'},
3006 :attachments => {'p0' => {'token' => attachment.token}}
3020 :attachments => {'p0' => {'token' => attachment.token}}
3007 assert_redirected_to '/issues/1'
3021 assert_redirected_to '/issues/1'
3008 end
3022 end
3009 end
3023 end
3010 end
3024 end
3011
3025
3012 attachment.reload
3026 attachment.reload
3013 assert_equal Issue.find(1), attachment.container
3027 assert_equal Issue.find(1), attachment.container
3014
3028
3015 journal = Journal.first(:order => 'id DESC')
3029 journal = Journal.first(:order => 'id DESC')
3016 assert_equal 1, journal.details.size
3030 assert_equal 1, journal.details.size
3017 assert_equal 'testfile.txt', journal.details.first.value
3031 assert_equal 'testfile.txt', journal.details.first.value
3018 end
3032 end
3019
3033
3020 def test_put_update_with_attachment_that_fails_to_save
3034 def test_put_update_with_attachment_that_fails_to_save
3021 set_tmp_attachments_directory
3035 set_tmp_attachments_directory
3022
3036
3023 # Delete all fixtured journals, a race condition can occur causing the wrong
3037 # Delete all fixtured journals, a race condition can occur causing the wrong
3024 # journal to get fetched in the next find.
3038 # journal to get fetched in the next find.
3025 Journal.delete_all
3039 Journal.delete_all
3026
3040
3027 # Mock out the unsaved attachment
3041 # Mock out the unsaved attachment
3028 Attachment.any_instance.stubs(:create).returns(Attachment.new)
3042 Attachment.any_instance.stubs(:create).returns(Attachment.new)
3029
3043
3030 # anonymous user
3044 # anonymous user
3031 put :update,
3045 put :update,
3032 :id => 1,
3046 :id => 1,
3033 :issue => {:notes => ''},
3047 :issue => {:notes => ''},
3034 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
3048 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
3035 assert_redirected_to :action => 'show', :id => '1'
3049 assert_redirected_to :action => 'show', :id => '1'
3036 assert_equal '1 file(s) could not be saved.', flash[:warning]
3050 assert_equal '1 file(s) could not be saved.', flash[:warning]
3037 end
3051 end
3038
3052
3039 def test_put_update_with_no_change
3053 def test_put_update_with_no_change
3040 issue = Issue.find(1)
3054 issue = Issue.find(1)
3041 issue.journals.clear
3055 issue.journals.clear
3042 ActionMailer::Base.deliveries.clear
3056 ActionMailer::Base.deliveries.clear
3043
3057
3044 put :update,
3058 put :update,
3045 :id => 1,
3059 :id => 1,
3046 :issue => {:notes => ''}
3060 :issue => {:notes => ''}
3047 assert_redirected_to :action => 'show', :id => '1'
3061 assert_redirected_to :action => 'show', :id => '1'
3048
3062
3049 issue.reload
3063 issue.reload
3050 assert issue.journals.empty?
3064 assert issue.journals.empty?
3051 # No email should be sent
3065 # No email should be sent
3052 assert ActionMailer::Base.deliveries.empty?
3066 assert ActionMailer::Base.deliveries.empty?
3053 end
3067 end
3054
3068
3055 def test_put_update_should_send_a_notification
3069 def test_put_update_should_send_a_notification
3056 @request.session[:user_id] = 2
3070 @request.session[:user_id] = 2
3057 ActionMailer::Base.deliveries.clear
3071 ActionMailer::Base.deliveries.clear
3058 issue = Issue.find(1)
3072 issue = Issue.find(1)
3059 old_subject = issue.subject
3073 old_subject = issue.subject
3060 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
3074 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
3061
3075
3062 put :update, :id => 1, :issue => {:subject => new_subject,
3076 put :update, :id => 1, :issue => {:subject => new_subject,
3063 :priority_id => '6',
3077 :priority_id => '6',
3064 :category_id => '1' # no change
3078 :category_id => '1' # no change
3065 }
3079 }
3066 assert_equal 1, ActionMailer::Base.deliveries.size
3080 assert_equal 1, ActionMailer::Base.deliveries.size
3067 end
3081 end
3068
3082
3069 def test_put_update_with_invalid_spent_time_hours_only
3083 def test_put_update_with_invalid_spent_time_hours_only
3070 @request.session[:user_id] = 2
3084 @request.session[:user_id] = 2
3071 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
3085 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
3072
3086
3073 assert_no_difference('Journal.count') do
3087 assert_no_difference('Journal.count') do
3074 put :update,
3088 put :update,
3075 :id => 1,
3089 :id => 1,
3076 :issue => {:notes => notes},
3090 :issue => {:notes => notes},
3077 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
3091 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
3078 end
3092 end
3079 assert_response :success
3093 assert_response :success
3080 assert_template 'edit'
3094 assert_template 'edit'
3081
3095
3082 assert_error_tag :descendant => {:content => /Activity can&#x27;t be blank/}
3096 assert_error_tag :descendant => {:content => /Activity can&#x27;t be blank/}
3083 assert_select 'textarea[name=?]', 'issue[notes]', :text => notes
3097 assert_select 'textarea[name=?]', 'issue[notes]', :text => notes
3084 assert_select 'input[name=?][value=?]', 'time_entry[hours]', '2z'
3098 assert_select 'input[name=?][value=?]', 'time_entry[hours]', '2z'
3085 end
3099 end
3086
3100
3087 def test_put_update_with_invalid_spent_time_comments_only
3101 def test_put_update_with_invalid_spent_time_comments_only
3088 @request.session[:user_id] = 2
3102 @request.session[:user_id] = 2
3089 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
3103 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
3090
3104
3091 assert_no_difference('Journal.count') do
3105 assert_no_difference('Journal.count') do
3092 put :update,
3106 put :update,
3093 :id => 1,
3107 :id => 1,
3094 :issue => {:notes => notes},
3108 :issue => {:notes => notes},
3095 :time_entry => {"comments"=>"this is my comment", "activity_id"=>"", "hours"=>""}
3109 :time_entry => {"comments"=>"this is my comment", "activity_id"=>"", "hours"=>""}
3096 end
3110 end
3097 assert_response :success
3111 assert_response :success
3098 assert_template 'edit'
3112 assert_template 'edit'
3099
3113
3100 assert_error_tag :descendant => {:content => /Activity can&#x27;t be blank/}
3114 assert_error_tag :descendant => {:content => /Activity can&#x27;t be blank/}
3101 assert_error_tag :descendant => {:content => /Hours can&#x27;t be blank/}
3115 assert_error_tag :descendant => {:content => /Hours can&#x27;t be blank/}
3102 assert_select 'textarea[name=?]', 'issue[notes]', :text => notes
3116 assert_select 'textarea[name=?]', 'issue[notes]', :text => notes
3103 assert_select 'input[name=?][value=?]', 'time_entry[comments]', 'this is my comment'
3117 assert_select 'input[name=?][value=?]', 'time_entry[comments]', 'this is my comment'
3104 end
3118 end
3105
3119
3106 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
3120 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
3107 issue = Issue.find(2)
3121 issue = Issue.find(2)
3108 @request.session[:user_id] = 2
3122 @request.session[:user_id] = 2
3109
3123
3110 put :update,
3124 put :update,
3111 :id => issue.id,
3125 :id => issue.id,
3112 :issue => {
3126 :issue => {
3113 :fixed_version_id => 4
3127 :fixed_version_id => 4
3114 }
3128 }
3115
3129
3116 assert_response :redirect
3130 assert_response :redirect
3117 issue.reload
3131 issue.reload
3118 assert_equal 4, issue.fixed_version_id
3132 assert_equal 4, issue.fixed_version_id
3119 assert_not_equal issue.project_id, issue.fixed_version.project_id
3133 assert_not_equal issue.project_id, issue.fixed_version.project_id
3120 end
3134 end
3121
3135
3122 def test_put_update_should_redirect_back_using_the_back_url_parameter
3136 def test_put_update_should_redirect_back_using_the_back_url_parameter
3123 issue = Issue.find(2)
3137 issue = Issue.find(2)
3124 @request.session[:user_id] = 2
3138 @request.session[:user_id] = 2
3125
3139
3126 put :update,
3140 put :update,
3127 :id => issue.id,
3141 :id => issue.id,
3128 :issue => {
3142 :issue => {
3129 :fixed_version_id => 4
3143 :fixed_version_id => 4
3130 },
3144 },
3131 :back_url => '/issues'
3145 :back_url => '/issues'
3132
3146
3133 assert_response :redirect
3147 assert_response :redirect
3134 assert_redirected_to '/issues'
3148 assert_redirected_to '/issues'
3135 end
3149 end
3136
3150
3137 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
3151 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
3138 issue = Issue.find(2)
3152 issue = Issue.find(2)
3139 @request.session[:user_id] = 2
3153 @request.session[:user_id] = 2
3140
3154
3141 put :update,
3155 put :update,
3142 :id => issue.id,
3156 :id => issue.id,
3143 :issue => {
3157 :issue => {
3144 :fixed_version_id => 4
3158 :fixed_version_id => 4
3145 },
3159 },
3146 :back_url => 'http://google.com'
3160 :back_url => 'http://google.com'
3147
3161
3148 assert_response :redirect
3162 assert_response :redirect
3149 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
3163 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
3150 end
3164 end
3151
3165
3152 def test_get_bulk_edit
3166 def test_get_bulk_edit
3153 @request.session[:user_id] = 2
3167 @request.session[:user_id] = 2
3154 get :bulk_edit, :ids => [1, 2]
3168 get :bulk_edit, :ids => [1, 2]
3155 assert_response :success
3169 assert_response :success
3156 assert_template 'bulk_edit'
3170 assert_template 'bulk_edit'
3157
3171
3158 assert_select 'ul#bulk-selection' do
3172 assert_select 'ul#bulk-selection' do
3159 assert_select 'li', 2
3173 assert_select 'li', 2
3160 assert_select 'li a', :text => 'Bug #1'
3174 assert_select 'li a', :text => 'Bug #1'
3161 end
3175 end
3162
3176
3163 assert_select 'form#bulk_edit_form[action=?]', '/issues/bulk_update' do
3177 assert_select 'form#bulk_edit_form[action=?]', '/issues/bulk_update' do
3164 assert_select 'input[name=?]', 'ids[]', 2
3178 assert_select 'input[name=?]', 'ids[]', 2
3165 assert_select 'input[name=?][value=1][type=hidden]', 'ids[]'
3179 assert_select 'input[name=?][value=1][type=hidden]', 'ids[]'
3166
3180
3167 assert_select 'select[name=?]', 'issue[project_id]'
3181 assert_select 'select[name=?]', 'issue[project_id]'
3168 assert_select 'input[name=?]', 'issue[parent_issue_id]'
3182 assert_select 'input[name=?]', 'issue[parent_issue_id]'
3169
3183
3170 # Project specific custom field, date type
3184 # Project specific custom field, date type
3171 field = CustomField.find(9)
3185 field = CustomField.find(9)
3172 assert !field.is_for_all?
3186 assert !field.is_for_all?
3173 assert_equal 'date', field.field_format
3187 assert_equal 'date', field.field_format
3174 assert_select 'input[name=?]', 'issue[custom_field_values][9]'
3188 assert_select 'input[name=?]', 'issue[custom_field_values][9]'
3175
3189
3176 # System wide custom field
3190 # System wide custom field
3177 assert CustomField.find(1).is_for_all?
3191 assert CustomField.find(1).is_for_all?
3178 assert_select 'select[name=?]', 'issue[custom_field_values][1]'
3192 assert_select 'select[name=?]', 'issue[custom_field_values][1]'
3179
3193
3180 # Be sure we don't display inactive IssuePriorities
3194 # Be sure we don't display inactive IssuePriorities
3181 assert ! IssuePriority.find(15).active?
3195 assert ! IssuePriority.find(15).active?
3182 assert_select 'select[name=?]', 'issue[priority_id]' do
3196 assert_select 'select[name=?]', 'issue[priority_id]' do
3183 assert_select 'option[value=15]', 0
3197 assert_select 'option[value=15]', 0
3184 end
3198 end
3185 end
3199 end
3186 end
3200 end
3187
3201
3188 def test_get_bulk_edit_on_different_projects
3202 def test_get_bulk_edit_on_different_projects
3189 @request.session[:user_id] = 2
3203 @request.session[:user_id] = 2
3190 get :bulk_edit, :ids => [1, 2, 6]
3204 get :bulk_edit, :ids => [1, 2, 6]
3191 assert_response :success
3205 assert_response :success
3192 assert_template 'bulk_edit'
3206 assert_template 'bulk_edit'
3193
3207
3194 # Can not set issues from different projects as children of an issue
3208 # Can not set issues from different projects as children of an issue
3195 assert_select 'input[name=?]', 'issue[parent_issue_id]', 0
3209 assert_select 'input[name=?]', 'issue[parent_issue_id]', 0
3196
3210
3197 # Project specific custom field, date type
3211 # Project specific custom field, date type
3198 field = CustomField.find(9)
3212 field = CustomField.find(9)
3199 assert !field.is_for_all?
3213 assert !field.is_for_all?
3200 assert !field.project_ids.include?(Issue.find(6).project_id)
3214 assert !field.project_ids.include?(Issue.find(6).project_id)
3201 assert_select 'input[name=?]', 'issue[custom_field_values][9]', 0
3215 assert_select 'input[name=?]', 'issue[custom_field_values][9]', 0
3202 end
3216 end
3203
3217
3204 def test_get_bulk_edit_with_user_custom_field
3218 def test_get_bulk_edit_with_user_custom_field
3205 field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true)
3219 field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true)
3206
3220
3207 @request.session[:user_id] = 2
3221 @request.session[:user_id] = 2
3208 get :bulk_edit, :ids => [1, 2]
3222 get :bulk_edit, :ids => [1, 2]
3209 assert_response :success
3223 assert_response :success
3210 assert_template 'bulk_edit'
3224 assert_template 'bulk_edit'
3211
3225
3212 assert_select 'select.user_cf[name=?]', "issue[custom_field_values][#{field.id}]" do
3226 assert_select 'select.user_cf[name=?]', "issue[custom_field_values][#{field.id}]" do
3213 assert_select 'option', Project.find(1).users.count + 2 # "no change" + "none" options
3227 assert_select 'option', Project.find(1).users.count + 2 # "no change" + "none" options
3214 end
3228 end
3215 end
3229 end
3216
3230
3217 def test_get_bulk_edit_with_version_custom_field
3231 def test_get_bulk_edit_with_version_custom_field
3218 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true)
3232 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true)
3219
3233
3220 @request.session[:user_id] = 2
3234 @request.session[:user_id] = 2
3221 get :bulk_edit, :ids => [1, 2]
3235 get :bulk_edit, :ids => [1, 2]
3222 assert_response :success
3236 assert_response :success
3223 assert_template 'bulk_edit'
3237 assert_template 'bulk_edit'
3224
3238
3225 assert_select 'select.version_cf[name=?]', "issue[custom_field_values][#{field.id}]" do
3239 assert_select 'select.version_cf[name=?]', "issue[custom_field_values][#{field.id}]" do
3226 assert_select 'option', Project.find(1).shared_versions.count + 2 # "no change" + "none" options
3240 assert_select 'option', Project.find(1).shared_versions.count + 2 # "no change" + "none" options
3227 end
3241 end
3228 end
3242 end
3229
3243
3230 def test_get_bulk_edit_with_multi_custom_field
3244 def test_get_bulk_edit_with_multi_custom_field
3231 field = CustomField.find(1)
3245 field = CustomField.find(1)
3232 field.update_attribute :multiple, true
3246 field.update_attribute :multiple, true
3233
3247
3234 @request.session[:user_id] = 2
3248 @request.session[:user_id] = 2
3235 get :bulk_edit, :ids => [1, 2]
3249 get :bulk_edit, :ids => [1, 2]
3236 assert_response :success
3250 assert_response :success
3237 assert_template 'bulk_edit'
3251 assert_template 'bulk_edit'
3238
3252
3239 assert_select 'select[name=?]', 'issue[custom_field_values][1][]' do
3253 assert_select 'select[name=?]', 'issue[custom_field_values][1][]' do
3240 assert_select 'option', field.possible_values.size + 1 # "none" options
3254 assert_select 'option', field.possible_values.size + 1 # "none" options
3241 end
3255 end
3242 end
3256 end
3243
3257
3244 def test_bulk_edit_should_only_propose_statuses_allowed_for_all_issues
3258 def test_bulk_edit_should_only_propose_statuses_allowed_for_all_issues
3245 WorkflowTransition.delete_all
3259 WorkflowTransition.delete_all
3246 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 1)
3260 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 1)
3247 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
3261 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
3248 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
3262 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
3249 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1)
3263 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1)
3250 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 3)
3264 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 3)
3251 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 5)
3265 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 5)
3252 @request.session[:user_id] = 2
3266 @request.session[:user_id] = 2
3253 get :bulk_edit, :ids => [1, 2]
3267 get :bulk_edit, :ids => [1, 2]
3254
3268
3255 assert_response :success
3269 assert_response :success
3256 statuses = assigns(:available_statuses)
3270 statuses = assigns(:available_statuses)
3257 assert_not_nil statuses
3271 assert_not_nil statuses
3258 assert_equal [1, 3], statuses.map(&:id).sort
3272 assert_equal [1, 3], statuses.map(&:id).sort
3259
3273
3260 assert_select 'select[name=?]', 'issue[status_id]' do
3274 assert_select 'select[name=?]', 'issue[status_id]' do
3261 assert_select 'option', 3 # 2 statuses + "no change" option
3275 assert_select 'option', 3 # 2 statuses + "no change" option
3262 end
3276 end
3263 end
3277 end
3264
3278
3265 def test_bulk_edit_should_propose_target_project_open_shared_versions
3279 def test_bulk_edit_should_propose_target_project_open_shared_versions
3266 @request.session[:user_id] = 2
3280 @request.session[:user_id] = 2
3267 post :bulk_edit, :ids => [1, 2, 6], :issue => {:project_id => 1}
3281 post :bulk_edit, :ids => [1, 2, 6], :issue => {:project_id => 1}
3268 assert_response :success
3282 assert_response :success
3269 assert_template 'bulk_edit'
3283 assert_template 'bulk_edit'
3270 assert_equal Project.find(1).shared_versions.open.all.sort, assigns(:versions).sort
3284 assert_equal Project.find(1).shared_versions.open.all.sort, assigns(:versions).sort
3271
3285
3272 assert_select 'select[name=?]', 'issue[fixed_version_id]' do
3286 assert_select 'select[name=?]', 'issue[fixed_version_id]' do
3273 assert_select 'option', :text => '2.0'
3287 assert_select 'option', :text => '2.0'
3274 end
3288 end
3275 end
3289 end
3276
3290
3277 def test_bulk_edit_should_propose_target_project_categories
3291 def test_bulk_edit_should_propose_target_project_categories
3278 @request.session[:user_id] = 2
3292 @request.session[:user_id] = 2
3279 post :bulk_edit, :ids => [1, 2, 6], :issue => {:project_id => 1}
3293 post :bulk_edit, :ids => [1, 2, 6], :issue => {:project_id => 1}
3280 assert_response :success
3294 assert_response :success
3281 assert_template 'bulk_edit'
3295 assert_template 'bulk_edit'
3282 assert_equal Project.find(1).issue_categories.sort, assigns(:categories).sort
3296 assert_equal Project.find(1).issue_categories.sort, assigns(:categories).sort
3283
3297
3284 assert_select 'select[name=?]', 'issue[category_id]' do
3298 assert_select 'select[name=?]', 'issue[category_id]' do
3285 assert_select 'option', :text => 'Recipes'
3299 assert_select 'option', :text => 'Recipes'
3286 end
3300 end
3287 end
3301 end
3288
3302
3289 def test_bulk_update
3303 def test_bulk_update
3290 @request.session[:user_id] = 2
3304 @request.session[:user_id] = 2
3291 # update issues priority
3305 # update issues priority
3292 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
3306 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
3293 :issue => {:priority_id => 7,
3307 :issue => {:priority_id => 7,
3294 :assigned_to_id => '',
3308 :assigned_to_id => '',
3295 :custom_field_values => {'2' => ''}}
3309 :custom_field_values => {'2' => ''}}
3296
3310
3297 assert_response 302
3311 assert_response 302
3298 # check that the issues were updated
3312 # check that the issues were updated
3299 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
3313 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
3300
3314
3301 issue = Issue.find(1)
3315 issue = Issue.find(1)
3302 journal = issue.journals.reorder('created_on DESC').first
3316 journal = issue.journals.reorder('created_on DESC').first
3303 assert_equal '125', issue.custom_value_for(2).value
3317 assert_equal '125', issue.custom_value_for(2).value
3304 assert_equal 'Bulk editing', journal.notes
3318 assert_equal 'Bulk editing', journal.notes
3305 assert_equal 1, journal.details.size
3319 assert_equal 1, journal.details.size
3306 end
3320 end
3307
3321
3308 def test_bulk_update_with_group_assignee
3322 def test_bulk_update_with_group_assignee
3309 group = Group.find(11)
3323 group = Group.find(11)
3310 project = Project.find(1)
3324 project = Project.find(1)
3311 project.members << Member.new(:principal => group, :roles => [Role.givable.first])
3325 project.members << Member.new(:principal => group, :roles => [Role.givable.first])
3312
3326
3313 @request.session[:user_id] = 2
3327 @request.session[:user_id] = 2
3314 # update issues assignee
3328 # update issues assignee
3315 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
3329 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
3316 :issue => {:priority_id => '',
3330 :issue => {:priority_id => '',
3317 :assigned_to_id => group.id,
3331 :assigned_to_id => group.id,
3318 :custom_field_values => {'2' => ''}}
3332 :custom_field_values => {'2' => ''}}
3319
3333
3320 assert_response 302
3334 assert_response 302
3321 assert_equal [group, group], Issue.find_all_by_id([1, 2]).collect {|i| i.assigned_to}
3335 assert_equal [group, group], Issue.find_all_by_id([1, 2]).collect {|i| i.assigned_to}
3322 end
3336 end
3323
3337
3324 def test_bulk_update_on_different_projects
3338 def test_bulk_update_on_different_projects
3325 @request.session[:user_id] = 2
3339 @request.session[:user_id] = 2
3326 # update issues priority
3340 # update issues priority
3327 post :bulk_update, :ids => [1, 2, 6], :notes => 'Bulk editing',
3341 post :bulk_update, :ids => [1, 2, 6], :notes => 'Bulk editing',
3328 :issue => {:priority_id => 7,
3342 :issue => {:priority_id => 7,
3329 :assigned_to_id => '',
3343 :assigned_to_id => '',
3330 :custom_field_values => {'2' => ''}}
3344 :custom_field_values => {'2' => ''}}
3331
3345
3332 assert_response 302
3346 assert_response 302
3333 # check that the issues were updated
3347 # check that the issues were updated
3334 assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
3348 assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
3335
3349
3336 issue = Issue.find(1)
3350 issue = Issue.find(1)
3337 journal = issue.journals.reorder('created_on DESC').first
3351 journal = issue.journals.reorder('created_on DESC').first
3338 assert_equal '125', issue.custom_value_for(2).value
3352 assert_equal '125', issue.custom_value_for(2).value
3339 assert_equal 'Bulk editing', journal.notes
3353 assert_equal 'Bulk editing', journal.notes
3340 assert_equal 1, journal.details.size
3354 assert_equal 1, journal.details.size
3341 end
3355 end
3342
3356
3343 def test_bulk_update_on_different_projects_without_rights
3357 def test_bulk_update_on_different_projects_without_rights
3344 @request.session[:user_id] = 3
3358 @request.session[:user_id] = 3
3345 user = User.find(3)
3359 user = User.find(3)
3346 action = { :controller => "issues", :action => "bulk_update" }
3360 action = { :controller => "issues", :action => "bulk_update" }
3347 assert user.allowed_to?(action, Issue.find(1).project)
3361 assert user.allowed_to?(action, Issue.find(1).project)
3348 assert ! user.allowed_to?(action, Issue.find(6).project)
3362 assert ! user.allowed_to?(action, Issue.find(6).project)
3349 post :bulk_update, :ids => [1, 6], :notes => 'Bulk should fail',
3363 post :bulk_update, :ids => [1, 6], :notes => 'Bulk should fail',
3350 :issue => {:priority_id => 7,
3364 :issue => {:priority_id => 7,
3351 :assigned_to_id => '',
3365 :assigned_to_id => '',
3352 :custom_field_values => {'2' => ''}}
3366 :custom_field_values => {'2' => ''}}
3353 assert_response 403
3367 assert_response 403
3354 assert_not_equal "Bulk should fail", Journal.last.notes
3368 assert_not_equal "Bulk should fail", Journal.last.notes
3355 end
3369 end
3356
3370
3357 def test_bullk_update_should_send_a_notification
3371 def test_bullk_update_should_send_a_notification
3358 @request.session[:user_id] = 2
3372 @request.session[:user_id] = 2
3359 ActionMailer::Base.deliveries.clear
3373 ActionMailer::Base.deliveries.clear
3360 post(:bulk_update,
3374 post(:bulk_update,
3361 {
3375 {
3362 :ids => [1, 2],
3376 :ids => [1, 2],
3363 :notes => 'Bulk editing',
3377 :notes => 'Bulk editing',
3364 :issue => {
3378 :issue => {
3365 :priority_id => 7,
3379 :priority_id => 7,
3366 :assigned_to_id => '',
3380 :assigned_to_id => '',
3367 :custom_field_values => {'2' => ''}
3381 :custom_field_values => {'2' => ''}
3368 }
3382 }
3369 })
3383 })
3370
3384
3371 assert_response 302
3385 assert_response 302
3372 assert_equal 2, ActionMailer::Base.deliveries.size
3386 assert_equal 2, ActionMailer::Base.deliveries.size
3373 end
3387 end
3374
3388
3375 def test_bulk_update_project
3389 def test_bulk_update_project
3376 @request.session[:user_id] = 2
3390 @request.session[:user_id] = 2
3377 post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'}
3391 post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'}
3378 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3392 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3379 # Issues moved to project 2
3393 # Issues moved to project 2
3380 assert_equal 2, Issue.find(1).project_id
3394 assert_equal 2, Issue.find(1).project_id
3381 assert_equal 2, Issue.find(2).project_id
3395 assert_equal 2, Issue.find(2).project_id
3382 # No tracker change
3396 # No tracker change
3383 assert_equal 1, Issue.find(1).tracker_id
3397 assert_equal 1, Issue.find(1).tracker_id
3384 assert_equal 2, Issue.find(2).tracker_id
3398 assert_equal 2, Issue.find(2).tracker_id
3385 end
3399 end
3386
3400
3387 def test_bulk_update_project_on_single_issue_should_follow_when_needed
3401 def test_bulk_update_project_on_single_issue_should_follow_when_needed
3388 @request.session[:user_id] = 2
3402 @request.session[:user_id] = 2
3389 post :bulk_update, :id => 1, :issue => {:project_id => '2'}, :follow => '1'
3403 post :bulk_update, :id => 1, :issue => {:project_id => '2'}, :follow => '1'
3390 assert_redirected_to '/issues/1'
3404 assert_redirected_to '/issues/1'
3391 end
3405 end
3392
3406
3393 def test_bulk_update_project_on_multiple_issues_should_follow_when_needed
3407 def test_bulk_update_project_on_multiple_issues_should_follow_when_needed
3394 @request.session[:user_id] = 2
3408 @request.session[:user_id] = 2
3395 post :bulk_update, :id => [1, 2], :issue => {:project_id => '2'}, :follow => '1'
3409 post :bulk_update, :id => [1, 2], :issue => {:project_id => '2'}, :follow => '1'
3396 assert_redirected_to '/projects/onlinestore/issues'
3410 assert_redirected_to '/projects/onlinestore/issues'
3397 end
3411 end
3398
3412
3399 def test_bulk_update_tracker
3413 def test_bulk_update_tracker
3400 @request.session[:user_id] = 2
3414 @request.session[:user_id] = 2
3401 post :bulk_update, :ids => [1, 2], :issue => {:tracker_id => '2'}
3415 post :bulk_update, :ids => [1, 2], :issue => {:tracker_id => '2'}
3402 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3416 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3403 assert_equal 2, Issue.find(1).tracker_id
3417 assert_equal 2, Issue.find(1).tracker_id
3404 assert_equal 2, Issue.find(2).tracker_id
3418 assert_equal 2, Issue.find(2).tracker_id
3405 end
3419 end
3406
3420
3407 def test_bulk_update_status
3421 def test_bulk_update_status
3408 @request.session[:user_id] = 2
3422 @request.session[:user_id] = 2
3409 # update issues priority
3423 # update issues priority
3410 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
3424 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
3411 :issue => {:priority_id => '',
3425 :issue => {:priority_id => '',
3412 :assigned_to_id => '',
3426 :assigned_to_id => '',
3413 :status_id => '5'}
3427 :status_id => '5'}
3414
3428
3415 assert_response 302
3429 assert_response 302
3416 issue = Issue.find(1)
3430 issue = Issue.find(1)
3417 assert issue.closed?
3431 assert issue.closed?
3418 end
3432 end
3419
3433
3420 def test_bulk_update_priority
3434 def test_bulk_update_priority
3421 @request.session[:user_id] = 2
3435 @request.session[:user_id] = 2
3422 post :bulk_update, :ids => [1, 2], :issue => {:priority_id => 6}
3436 post :bulk_update, :ids => [1, 2], :issue => {:priority_id => 6}
3423
3437
3424 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3438 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3425 assert_equal 6, Issue.find(1).priority_id
3439 assert_equal 6, Issue.find(1).priority_id
3426 assert_equal 6, Issue.find(2).priority_id
3440 assert_equal 6, Issue.find(2).priority_id
3427 end
3441 end
3428
3442
3429 def test_bulk_update_with_notes
3443 def test_bulk_update_with_notes
3430 @request.session[:user_id] = 2
3444 @request.session[:user_id] = 2
3431 post :bulk_update, :ids => [1, 2], :notes => 'Moving two issues'
3445 post :bulk_update, :ids => [1, 2], :notes => 'Moving two issues'
3432
3446
3433 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3447 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3434 assert_equal 'Moving two issues', Issue.find(1).journals.sort_by(&:id).last.notes
3448 assert_equal 'Moving two issues', Issue.find(1).journals.sort_by(&:id).last.notes
3435 assert_equal 'Moving two issues', Issue.find(2).journals.sort_by(&:id).last.notes
3449 assert_equal 'Moving two issues', Issue.find(2).journals.sort_by(&:id).last.notes
3436 end
3450 end
3437
3451
3438 def test_bulk_update_parent_id
3452 def test_bulk_update_parent_id
3439 @request.session[:user_id] = 2
3453 @request.session[:user_id] = 2
3440 post :bulk_update, :ids => [1, 3],
3454 post :bulk_update, :ids => [1, 3],
3441 :notes => 'Bulk editing parent',
3455 :notes => 'Bulk editing parent',
3442 :issue => {:priority_id => '', :assigned_to_id => '', :status_id => '', :parent_issue_id => '2'}
3456 :issue => {:priority_id => '', :assigned_to_id => '', :status_id => '', :parent_issue_id => '2'}
3443
3457
3444 assert_response 302
3458 assert_response 302
3445 parent = Issue.find(2)
3459 parent = Issue.find(2)
3446 assert_equal parent.id, Issue.find(1).parent_id
3460 assert_equal parent.id, Issue.find(1).parent_id
3447 assert_equal parent.id, Issue.find(3).parent_id
3461 assert_equal parent.id, Issue.find(3).parent_id
3448 assert_equal [1, 3], parent.children.collect(&:id).sort
3462 assert_equal [1, 3], parent.children.collect(&:id).sort
3449 end
3463 end
3450
3464
3451 def test_bulk_update_custom_field
3465 def test_bulk_update_custom_field
3452 @request.session[:user_id] = 2
3466 @request.session[:user_id] = 2
3453 # update issues priority
3467 # update issues priority
3454 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
3468 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
3455 :issue => {:priority_id => '',
3469 :issue => {:priority_id => '',
3456 :assigned_to_id => '',
3470 :assigned_to_id => '',
3457 :custom_field_values => {'2' => '777'}}
3471 :custom_field_values => {'2' => '777'}}
3458
3472
3459 assert_response 302
3473 assert_response 302
3460
3474
3461 issue = Issue.find(1)
3475 issue = Issue.find(1)
3462 journal = issue.journals.reorder('created_on DESC').first
3476 journal = issue.journals.reorder('created_on DESC').first
3463 assert_equal '777', issue.custom_value_for(2).value
3477 assert_equal '777', issue.custom_value_for(2).value
3464 assert_equal 1, journal.details.size
3478 assert_equal 1, journal.details.size
3465 assert_equal '125', journal.details.first.old_value
3479 assert_equal '125', journal.details.first.old_value
3466 assert_equal '777', journal.details.first.value
3480 assert_equal '777', journal.details.first.value
3467 end
3481 end
3468
3482
3469 def test_bulk_update_custom_field_to_blank
3483 def test_bulk_update_custom_field_to_blank
3470 @request.session[:user_id] = 2
3484 @request.session[:user_id] = 2
3471 post :bulk_update, :ids => [1, 3], :notes => 'Bulk editing custom field',
3485 post :bulk_update, :ids => [1, 3], :notes => 'Bulk editing custom field',
3472 :issue => {:priority_id => '',
3486 :issue => {:priority_id => '',
3473 :assigned_to_id => '',
3487 :assigned_to_id => '',
3474 :custom_field_values => {'1' => '__none__'}}
3488 :custom_field_values => {'1' => '__none__'}}
3475 assert_response 302
3489 assert_response 302
3476 assert_equal '', Issue.find(1).custom_field_value(1)
3490 assert_equal '', Issue.find(1).custom_field_value(1)
3477 assert_equal '', Issue.find(3).custom_field_value(1)
3491 assert_equal '', Issue.find(3).custom_field_value(1)
3478 end
3492 end
3479
3493
3480 def test_bulk_update_multi_custom_field
3494 def test_bulk_update_multi_custom_field
3481 field = CustomField.find(1)
3495 field = CustomField.find(1)
3482 field.update_attribute :multiple, true
3496 field.update_attribute :multiple, true
3483
3497
3484 @request.session[:user_id] = 2
3498 @request.session[:user_id] = 2
3485 post :bulk_update, :ids => [1, 2, 3], :notes => 'Bulk editing multi custom field',
3499 post :bulk_update, :ids => [1, 2, 3], :notes => 'Bulk editing multi custom field',
3486 :issue => {:priority_id => '',
3500 :issue => {:priority_id => '',
3487 :assigned_to_id => '',
3501 :assigned_to_id => '',
3488 :custom_field_values => {'1' => ['MySQL', 'Oracle']}}
3502 :custom_field_values => {'1' => ['MySQL', 'Oracle']}}
3489
3503
3490 assert_response 302
3504 assert_response 302
3491
3505
3492 assert_equal ['MySQL', 'Oracle'], Issue.find(1).custom_field_value(1).sort
3506 assert_equal ['MySQL', 'Oracle'], Issue.find(1).custom_field_value(1).sort
3493 assert_equal ['MySQL', 'Oracle'], Issue.find(3).custom_field_value(1).sort
3507 assert_equal ['MySQL', 'Oracle'], Issue.find(3).custom_field_value(1).sort
3494 # the custom field is not associated with the issue tracker
3508 # the custom field is not associated with the issue tracker
3495 assert_nil Issue.find(2).custom_field_value(1)
3509 assert_nil Issue.find(2).custom_field_value(1)
3496 end
3510 end
3497
3511
3498 def test_bulk_update_multi_custom_field_to_blank
3512 def test_bulk_update_multi_custom_field_to_blank
3499 field = CustomField.find(1)
3513 field = CustomField.find(1)
3500 field.update_attribute :multiple, true
3514 field.update_attribute :multiple, true
3501
3515
3502 @request.session[:user_id] = 2
3516 @request.session[:user_id] = 2
3503 post :bulk_update, :ids => [1, 3], :notes => 'Bulk editing multi custom field',
3517 post :bulk_update, :ids => [1, 3], :notes => 'Bulk editing multi custom field',
3504 :issue => {:priority_id => '',
3518 :issue => {:priority_id => '',
3505 :assigned_to_id => '',
3519 :assigned_to_id => '',
3506 :custom_field_values => {'1' => ['__none__']}}
3520 :custom_field_values => {'1' => ['__none__']}}
3507 assert_response 302
3521 assert_response 302
3508 assert_equal [''], Issue.find(1).custom_field_value(1)
3522 assert_equal [''], Issue.find(1).custom_field_value(1)
3509 assert_equal [''], Issue.find(3).custom_field_value(1)
3523 assert_equal [''], Issue.find(3).custom_field_value(1)
3510 end
3524 end
3511
3525
3512 def test_bulk_update_unassign
3526 def test_bulk_update_unassign
3513 assert_not_nil Issue.find(2).assigned_to
3527 assert_not_nil Issue.find(2).assigned_to
3514 @request.session[:user_id] = 2
3528 @request.session[:user_id] = 2
3515 # unassign issues
3529 # unassign issues
3516 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
3530 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
3517 assert_response 302
3531 assert_response 302
3518 # check that the issues were updated
3532 # check that the issues were updated
3519 assert_nil Issue.find(2).assigned_to
3533 assert_nil Issue.find(2).assigned_to
3520 end
3534 end
3521
3535
3522 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
3536 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
3523 @request.session[:user_id] = 2
3537 @request.session[:user_id] = 2
3524
3538
3525 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
3539 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
3526
3540
3527 assert_response :redirect
3541 assert_response :redirect
3528 issues = Issue.find([1,2])
3542 issues = Issue.find([1,2])
3529 issues.each do |issue|
3543 issues.each do |issue|
3530 assert_equal 4, issue.fixed_version_id
3544 assert_equal 4, issue.fixed_version_id
3531 assert_not_equal issue.project_id, issue.fixed_version.project_id
3545 assert_not_equal issue.project_id, issue.fixed_version.project_id
3532 end
3546 end
3533 end
3547 end
3534
3548
3535 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
3549 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
3536 @request.session[:user_id] = 2
3550 @request.session[:user_id] = 2
3537 post :bulk_update, :ids => [1,2], :back_url => '/issues'
3551 post :bulk_update, :ids => [1,2], :back_url => '/issues'
3538
3552
3539 assert_response :redirect
3553 assert_response :redirect
3540 assert_redirected_to '/issues'
3554 assert_redirected_to '/issues'
3541 end
3555 end
3542
3556
3543 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
3557 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
3544 @request.session[:user_id] = 2
3558 @request.session[:user_id] = 2
3545 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
3559 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
3546
3560
3547 assert_response :redirect
3561 assert_response :redirect
3548 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
3562 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
3549 end
3563 end
3550
3564
3551 def test_bulk_update_with_failure_should_set_flash
3565 def test_bulk_update_with_failure_should_set_flash
3552 @request.session[:user_id] = 2
3566 @request.session[:user_id] = 2
3553 Issue.update_all("subject = ''", "id = 2") # Make it invalid
3567 Issue.update_all("subject = ''", "id = 2") # Make it invalid
3554 post :bulk_update, :ids => [1, 2], :issue => {:priority_id => 6}
3568 post :bulk_update, :ids => [1, 2], :issue => {:priority_id => 6}
3555
3569
3556 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3570 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3557 assert_equal 'Failed to save 1 issue(s) on 2 selected: #2.', flash[:error]
3571 assert_equal 'Failed to save 1 issue(s) on 2 selected: #2.', flash[:error]
3558 end
3572 end
3559
3573
3560 def test_get_bulk_copy
3574 def test_get_bulk_copy
3561 @request.session[:user_id] = 2
3575 @request.session[:user_id] = 2
3562 get :bulk_edit, :ids => [1, 2, 3], :copy => '1'
3576 get :bulk_edit, :ids => [1, 2, 3], :copy => '1'
3563 assert_response :success
3577 assert_response :success
3564 assert_template 'bulk_edit'
3578 assert_template 'bulk_edit'
3565
3579
3566 issues = assigns(:issues)
3580 issues = assigns(:issues)
3567 assert_not_nil issues
3581 assert_not_nil issues
3568 assert_equal [1, 2, 3], issues.map(&:id).sort
3582 assert_equal [1, 2, 3], issues.map(&:id).sort
3569
3583
3570 assert_select 'input[name=copy_attachments]'
3584 assert_select 'input[name=copy_attachments]'
3571 end
3585 end
3572
3586
3573 def test_bulk_copy_to_another_project
3587 def test_bulk_copy_to_another_project
3574 @request.session[:user_id] = 2
3588 @request.session[:user_id] = 2
3575 assert_difference 'Issue.count', 2 do
3589 assert_difference 'Issue.count', 2 do
3576 assert_no_difference 'Project.find(1).issues.count' do
3590 assert_no_difference 'Project.find(1).issues.count' do
3577 post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'}, :copy => '1'
3591 post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'}, :copy => '1'
3578 end
3592 end
3579 end
3593 end
3580 assert_redirected_to '/projects/ecookbook/issues'
3594 assert_redirected_to '/projects/ecookbook/issues'
3581
3595
3582 copies = Issue.all(:order => 'id DESC', :limit => issues.size)
3596 copies = Issue.all(:order => 'id DESC', :limit => issues.size)
3583 copies.each do |copy|
3597 copies.each do |copy|
3584 assert_equal 2, copy.project_id
3598 assert_equal 2, copy.project_id
3585 end
3599 end
3586 end
3600 end
3587
3601
3588 def test_bulk_copy_should_allow_not_changing_the_issue_attributes
3602 def test_bulk_copy_should_allow_not_changing_the_issue_attributes
3589 @request.session[:user_id] = 2
3603 @request.session[:user_id] = 2
3590 issues = [
3604 issues = [
3591 Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 1, :priority_id => 2, :subject => 'issue 1', :author_id => 1, :assigned_to_id => nil),
3605 Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 1, :priority_id => 2, :subject => 'issue 1', :author_id => 1, :assigned_to_id => nil),
3592 Issue.create!(:project_id => 2, :tracker_id => 3, :status_id => 2, :priority_id => 1, :subject => 'issue 2', :author_id => 2, :assigned_to_id => 3)
3606 Issue.create!(:project_id => 2, :tracker_id => 3, :status_id => 2, :priority_id => 1, :subject => 'issue 2', :author_id => 2, :assigned_to_id => 3)
3593 ]
3607 ]
3594
3608
3595 assert_difference 'Issue.count', issues.size do
3609 assert_difference 'Issue.count', issues.size do
3596 post :bulk_update, :ids => issues.map(&:id), :copy => '1',
3610 post :bulk_update, :ids => issues.map(&:id), :copy => '1',
3597 :issue => {
3611 :issue => {
3598 :project_id => '', :tracker_id => '', :assigned_to_id => '',
3612 :project_id => '', :tracker_id => '', :assigned_to_id => '',
3599 :status_id => '', :start_date => '', :due_date => ''
3613 :status_id => '', :start_date => '', :due_date => ''
3600 }
3614 }
3601 end
3615 end
3602
3616
3603 copies = Issue.all(:order => 'id DESC', :limit => issues.size)
3617 copies = Issue.all(:order => 'id DESC', :limit => issues.size)
3604 issues.each do |orig|
3618 issues.each do |orig|
3605 copy = copies.detect {|c| c.subject == orig.subject}
3619 copy = copies.detect {|c| c.subject == orig.subject}
3606 assert_not_nil copy
3620 assert_not_nil copy
3607 assert_equal orig.project_id, copy.project_id
3621 assert_equal orig.project_id, copy.project_id
3608 assert_equal orig.tracker_id, copy.tracker_id
3622 assert_equal orig.tracker_id, copy.tracker_id
3609 assert_equal orig.status_id, copy.status_id
3623 assert_equal orig.status_id, copy.status_id
3610 assert_equal orig.assigned_to_id, copy.assigned_to_id
3624 assert_equal orig.assigned_to_id, copy.assigned_to_id
3611 assert_equal orig.priority_id, copy.priority_id
3625 assert_equal orig.priority_id, copy.priority_id
3612 end
3626 end
3613 end
3627 end
3614
3628
3615 def test_bulk_copy_should_allow_changing_the_issue_attributes
3629 def test_bulk_copy_should_allow_changing_the_issue_attributes
3616 # Fixes random test failure with Mysql
3630 # Fixes random test failure with Mysql
3617 # where Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2})
3631 # where Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2})
3618 # doesn't return the expected results
3632 # doesn't return the expected results
3619 Issue.delete_all("project_id=2")
3633 Issue.delete_all("project_id=2")
3620
3634
3621 @request.session[:user_id] = 2
3635 @request.session[:user_id] = 2
3622 assert_difference 'Issue.count', 2 do
3636 assert_difference 'Issue.count', 2 do
3623 assert_no_difference 'Project.find(1).issues.count' do
3637 assert_no_difference 'Project.find(1).issues.count' do
3624 post :bulk_update, :ids => [1, 2], :copy => '1',
3638 post :bulk_update, :ids => [1, 2], :copy => '1',
3625 :issue => {
3639 :issue => {
3626 :project_id => '2', :tracker_id => '', :assigned_to_id => '4',
3640 :project_id => '2', :tracker_id => '', :assigned_to_id => '4',
3627 :status_id => '1', :start_date => '2009-12-01', :due_date => '2009-12-31'
3641 :status_id => '1', :start_date => '2009-12-01', :due_date => '2009-12-31'
3628 }
3642 }
3629 end
3643 end
3630 end
3644 end
3631
3645
3632 copied_issues = Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2})
3646 copied_issues = Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2})
3633 assert_equal 2, copied_issues.size
3647 assert_equal 2, copied_issues.size
3634 copied_issues.each do |issue|
3648 copied_issues.each do |issue|
3635 assert_equal 2, issue.project_id, "Project is incorrect"
3649 assert_equal 2, issue.project_id, "Project is incorrect"
3636 assert_equal 4, issue.assigned_to_id, "Assigned to is incorrect"
3650 assert_equal 4, issue.assigned_to_id, "Assigned to is incorrect"
3637 assert_equal 1, issue.status_id, "Status is incorrect"
3651 assert_equal 1, issue.status_id, "Status is incorrect"
3638 assert_equal '2009-12-01', issue.start_date.to_s, "Start date is incorrect"
3652 assert_equal '2009-12-01', issue.start_date.to_s, "Start date is incorrect"
3639 assert_equal '2009-12-31', issue.due_date.to_s, "Due date is incorrect"
3653 assert_equal '2009-12-31', issue.due_date.to_s, "Due date is incorrect"
3640 end
3654 end
3641 end
3655 end
3642
3656
3643 def test_bulk_copy_should_allow_adding_a_note
3657 def test_bulk_copy_should_allow_adding_a_note
3644 @request.session[:user_id] = 2
3658 @request.session[:user_id] = 2
3645 assert_difference 'Issue.count', 1 do
3659 assert_difference 'Issue.count', 1 do
3646 post :bulk_update, :ids => [1], :copy => '1',
3660 post :bulk_update, :ids => [1], :copy => '1',
3647 :notes => 'Copying one issue',
3661 :notes => 'Copying one issue',
3648 :issue => {
3662 :issue => {
3649 :project_id => '', :tracker_id => '', :assigned_to_id => '4',
3663 :project_id => '', :tracker_id => '', :assigned_to_id => '4',
3650 :status_id => '3', :start_date => '2009-12-01', :due_date => '2009-12-31'
3664 :status_id => '3', :start_date => '2009-12-01', :due_date => '2009-12-31'
3651 }
3665 }
3652 end
3666 end
3653
3667
3654 issue = Issue.first(:order => 'id DESC')
3668 issue = Issue.first(:order => 'id DESC')
3655 assert_equal 1, issue.journals.size
3669 assert_equal 1, issue.journals.size
3656 journal = issue.journals.first
3670 journal = issue.journals.first
3657 assert_equal 0, journal.details.size
3671 assert_equal 0, journal.details.size
3658 assert_equal 'Copying one issue', journal.notes
3672 assert_equal 'Copying one issue', journal.notes
3659 end
3673 end
3660
3674
3661 def test_bulk_copy_should_allow_not_copying_the_attachments
3675 def test_bulk_copy_should_allow_not_copying_the_attachments
3662 attachment_count = Issue.find(3).attachments.size
3676 attachment_count = Issue.find(3).attachments.size
3663 assert attachment_count > 0
3677 assert attachment_count > 0
3664 @request.session[:user_id] = 2
3678 @request.session[:user_id] = 2
3665
3679
3666 assert_difference 'Issue.count', 1 do
3680 assert_difference 'Issue.count', 1 do
3667 assert_no_difference 'Attachment.count' do
3681 assert_no_difference 'Attachment.count' do
3668 post :bulk_update, :ids => [3], :copy => '1',
3682 post :bulk_update, :ids => [3], :copy => '1',
3669 :issue => {
3683 :issue => {
3670 :project_id => ''
3684 :project_id => ''
3671 }
3685 }
3672 end
3686 end
3673 end
3687 end
3674 end
3688 end
3675
3689
3676 def test_bulk_copy_should_allow_copying_the_attachments
3690 def test_bulk_copy_should_allow_copying_the_attachments
3677 attachment_count = Issue.find(3).attachments.size
3691 attachment_count = Issue.find(3).attachments.size
3678 assert attachment_count > 0
3692 assert attachment_count > 0
3679 @request.session[:user_id] = 2
3693 @request.session[:user_id] = 2
3680
3694
3681 assert_difference 'Issue.count', 1 do
3695 assert_difference 'Issue.count', 1 do
3682 assert_difference 'Attachment.count', attachment_count do
3696 assert_difference 'Attachment.count', attachment_count do
3683 post :bulk_update, :ids => [3], :copy => '1', :copy_attachments => '1',
3697 post :bulk_update, :ids => [3], :copy => '1', :copy_attachments => '1',
3684 :issue => {
3698 :issue => {
3685 :project_id => ''
3699 :project_id => ''
3686 }
3700 }
3687 end
3701 end
3688 end
3702 end
3689 end
3703 end
3690
3704
3691 def test_bulk_copy_should_add_relations_with_copied_issues
3705 def test_bulk_copy_should_add_relations_with_copied_issues
3692 @request.session[:user_id] = 2
3706 @request.session[:user_id] = 2
3693
3707
3694 assert_difference 'Issue.count', 2 do
3708 assert_difference 'Issue.count', 2 do
3695 assert_difference 'IssueRelation.count', 2 do
3709 assert_difference 'IssueRelation.count', 2 do
3696 post :bulk_update, :ids => [1, 3], :copy => '1',
3710 post :bulk_update, :ids => [1, 3], :copy => '1',
3697 :issue => {
3711 :issue => {
3698 :project_id => '1'
3712 :project_id => '1'
3699 }
3713 }
3700 end
3714 end
3701 end
3715 end
3702 end
3716 end
3703
3717
3704 def test_bulk_copy_should_allow_not_copying_the_subtasks
3718 def test_bulk_copy_should_allow_not_copying_the_subtasks
3705 issue = Issue.generate_with_descendants!
3719 issue = Issue.generate_with_descendants!
3706 @request.session[:user_id] = 2
3720 @request.session[:user_id] = 2
3707
3721
3708 assert_difference 'Issue.count', 1 do
3722 assert_difference 'Issue.count', 1 do
3709 post :bulk_update, :ids => [issue.id], :copy => '1',
3723 post :bulk_update, :ids => [issue.id], :copy => '1',
3710 :issue => {
3724 :issue => {
3711 :project_id => ''
3725 :project_id => ''
3712 }
3726 }
3713 end
3727 end
3714 end
3728 end
3715
3729
3716 def test_bulk_copy_should_allow_copying_the_subtasks
3730 def test_bulk_copy_should_allow_copying_the_subtasks
3717 issue = Issue.generate_with_descendants!
3731 issue = Issue.generate_with_descendants!
3718 count = issue.descendants.count
3732 count = issue.descendants.count
3719 @request.session[:user_id] = 2
3733 @request.session[:user_id] = 2
3720
3734
3721 assert_difference 'Issue.count', count+1 do
3735 assert_difference 'Issue.count', count+1 do
3722 post :bulk_update, :ids => [issue.id], :copy => '1', :copy_subtasks => '1',
3736 post :bulk_update, :ids => [issue.id], :copy => '1', :copy_subtasks => '1',
3723 :issue => {
3737 :issue => {
3724 :project_id => ''
3738 :project_id => ''
3725 }
3739 }
3726 end
3740 end
3727 copy = Issue.where(:parent_id => nil).order("id DESC").first
3741 copy = Issue.where(:parent_id => nil).order("id DESC").first
3728 assert_equal count, copy.descendants.count
3742 assert_equal count, copy.descendants.count
3729 end
3743 end
3730
3744
3731 def test_bulk_copy_should_not_copy_selected_subtasks_twice
3745 def test_bulk_copy_should_not_copy_selected_subtasks_twice
3732 issue = Issue.generate_with_descendants!
3746 issue = Issue.generate_with_descendants!
3733 count = issue.descendants.count
3747 count = issue.descendants.count
3734 @request.session[:user_id] = 2
3748 @request.session[:user_id] = 2
3735
3749
3736 assert_difference 'Issue.count', count+1 do
3750 assert_difference 'Issue.count', count+1 do
3737 post :bulk_update, :ids => issue.self_and_descendants.map(&:id), :copy => '1', :copy_subtasks => '1',
3751 post :bulk_update, :ids => issue.self_and_descendants.map(&:id), :copy => '1', :copy_subtasks => '1',
3738 :issue => {
3752 :issue => {
3739 :project_id => ''
3753 :project_id => ''
3740 }
3754 }
3741 end
3755 end
3742 copy = Issue.where(:parent_id => nil).order("id DESC").first
3756 copy = Issue.where(:parent_id => nil).order("id DESC").first
3743 assert_equal count, copy.descendants.count
3757 assert_equal count, copy.descendants.count
3744 end
3758 end
3745
3759
3746 def test_bulk_copy_to_another_project_should_follow_when_needed
3760 def test_bulk_copy_to_another_project_should_follow_when_needed
3747 @request.session[:user_id] = 2
3761 @request.session[:user_id] = 2
3748 post :bulk_update, :ids => [1], :copy => '1', :issue => {:project_id => 2}, :follow => '1'
3762 post :bulk_update, :ids => [1], :copy => '1', :issue => {:project_id => 2}, :follow => '1'
3749 issue = Issue.first(:order => 'id DESC')
3763 issue = Issue.first(:order => 'id DESC')
3750 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
3764 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
3751 end
3765 end
3752
3766
3753 def test_destroy_issue_with_no_time_entries
3767 def test_destroy_issue_with_no_time_entries
3754 assert_nil TimeEntry.find_by_issue_id(2)
3768 assert_nil TimeEntry.find_by_issue_id(2)
3755 @request.session[:user_id] = 2
3769 @request.session[:user_id] = 2
3756
3770
3757 assert_difference 'Issue.count', -1 do
3771 assert_difference 'Issue.count', -1 do
3758 delete :destroy, :id => 2
3772 delete :destroy, :id => 2
3759 end
3773 end
3760 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3774 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3761 assert_nil Issue.find_by_id(2)
3775 assert_nil Issue.find_by_id(2)
3762 end
3776 end
3763
3777
3764 def test_destroy_issues_with_time_entries
3778 def test_destroy_issues_with_time_entries
3765 @request.session[:user_id] = 2
3779 @request.session[:user_id] = 2
3766
3780
3767 assert_no_difference 'Issue.count' do
3781 assert_no_difference 'Issue.count' do
3768 delete :destroy, :ids => [1, 3]
3782 delete :destroy, :ids => [1, 3]
3769 end
3783 end
3770 assert_response :success
3784 assert_response :success
3771 assert_template 'destroy'
3785 assert_template 'destroy'
3772 assert_not_nil assigns(:hours)
3786 assert_not_nil assigns(:hours)
3773 assert Issue.find_by_id(1) && Issue.find_by_id(3)
3787 assert Issue.find_by_id(1) && Issue.find_by_id(3)
3774
3788
3775 assert_select 'form' do
3789 assert_select 'form' do
3776 assert_select 'input[name=_method][value=delete]'
3790 assert_select 'input[name=_method][value=delete]'
3777 end
3791 end
3778 end
3792 end
3779
3793
3780 def test_destroy_issues_and_destroy_time_entries
3794 def test_destroy_issues_and_destroy_time_entries
3781 @request.session[:user_id] = 2
3795 @request.session[:user_id] = 2
3782
3796
3783 assert_difference 'Issue.count', -2 do
3797 assert_difference 'Issue.count', -2 do
3784 assert_difference 'TimeEntry.count', -3 do
3798 assert_difference 'TimeEntry.count', -3 do
3785 delete :destroy, :ids => [1, 3], :todo => 'destroy'
3799 delete :destroy, :ids => [1, 3], :todo => 'destroy'
3786 end
3800 end
3787 end
3801 end
3788 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3802 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3789 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3803 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3790 assert_nil TimeEntry.find_by_id([1, 2])
3804 assert_nil TimeEntry.find_by_id([1, 2])
3791 end
3805 end
3792
3806
3793 def test_destroy_issues_and_assign_time_entries_to_project
3807 def test_destroy_issues_and_assign_time_entries_to_project
3794 @request.session[:user_id] = 2
3808 @request.session[:user_id] = 2
3795
3809
3796 assert_difference 'Issue.count', -2 do
3810 assert_difference 'Issue.count', -2 do
3797 assert_no_difference 'TimeEntry.count' do
3811 assert_no_difference 'TimeEntry.count' do
3798 delete :destroy, :ids => [1, 3], :todo => 'nullify'
3812 delete :destroy, :ids => [1, 3], :todo => 'nullify'
3799 end
3813 end
3800 end
3814 end
3801 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3815 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3802 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3816 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3803 assert_nil TimeEntry.find(1).issue_id
3817 assert_nil TimeEntry.find(1).issue_id
3804 assert_nil TimeEntry.find(2).issue_id
3818 assert_nil TimeEntry.find(2).issue_id
3805 end
3819 end
3806
3820
3807 def test_destroy_issues_and_reassign_time_entries_to_another_issue
3821 def test_destroy_issues_and_reassign_time_entries_to_another_issue
3808 @request.session[:user_id] = 2
3822 @request.session[:user_id] = 2
3809
3823
3810 assert_difference 'Issue.count', -2 do
3824 assert_difference 'Issue.count', -2 do
3811 assert_no_difference 'TimeEntry.count' do
3825 assert_no_difference 'TimeEntry.count' do
3812 delete :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
3826 delete :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
3813 end
3827 end
3814 end
3828 end
3815 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3829 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3816 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3830 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3817 assert_equal 2, TimeEntry.find(1).issue_id
3831 assert_equal 2, TimeEntry.find(1).issue_id
3818 assert_equal 2, TimeEntry.find(2).issue_id
3832 assert_equal 2, TimeEntry.find(2).issue_id
3819 end
3833 end
3820
3834
3821 def test_destroy_issues_from_different_projects
3835 def test_destroy_issues_from_different_projects
3822 @request.session[:user_id] = 2
3836 @request.session[:user_id] = 2
3823
3837
3824 assert_difference 'Issue.count', -3 do
3838 assert_difference 'Issue.count', -3 do
3825 delete :destroy, :ids => [1, 2, 6], :todo => 'destroy'
3839 delete :destroy, :ids => [1, 2, 6], :todo => 'destroy'
3826 end
3840 end
3827 assert_redirected_to :controller => 'issues', :action => 'index'
3841 assert_redirected_to :controller => 'issues', :action => 'index'
3828 assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6))
3842 assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6))
3829 end
3843 end
3830
3844
3831 def test_destroy_parent_and_child_issues
3845 def test_destroy_parent_and_child_issues
3832 parent = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'Parent Issue')
3846 parent = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'Parent Issue')
3833 child = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'Child Issue', :parent_issue_id => parent.id)
3847 child = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'Child Issue', :parent_issue_id => parent.id)
3834 assert child.is_descendant_of?(parent.reload)
3848 assert child.is_descendant_of?(parent.reload)
3835
3849
3836 @request.session[:user_id] = 2
3850 @request.session[:user_id] = 2
3837 assert_difference 'Issue.count', -2 do
3851 assert_difference 'Issue.count', -2 do
3838 delete :destroy, :ids => [parent.id, child.id], :todo => 'destroy'
3852 delete :destroy, :ids => [parent.id, child.id], :todo => 'destroy'
3839 end
3853 end
3840 assert_response 302
3854 assert_response 302
3841 end
3855 end
3842
3856
3843 def test_destroy_invalid_should_respond_with_404
3857 def test_destroy_invalid_should_respond_with_404
3844 @request.session[:user_id] = 2
3858 @request.session[:user_id] = 2
3845 assert_no_difference 'Issue.count' do
3859 assert_no_difference 'Issue.count' do
3846 delete :destroy, :id => 999
3860 delete :destroy, :id => 999
3847 end
3861 end
3848 assert_response 404
3862 assert_response 404
3849 end
3863 end
3850
3864
3851 def test_default_search_scope
3865 def test_default_search_scope
3852 get :index
3866 get :index
3853
3867
3854 assert_select 'div#quick-search form' do
3868 assert_select 'div#quick-search form' do
3855 assert_select 'input[name=issues][value=1][type=hidden]'
3869 assert_select 'input[name=issues][value=1][type=hidden]'
3856 end
3870 end
3857 end
3871 end
3858 end
3872 end
General Comments 0
You need to be logged in to leave comments. Login now