##// END OF EJS Templates
Display issue form fields according to permissions....
Jean-Philippe Lang -
r8107:b6e6f557f01b
parent child
Show More
@@ -1,986 +1,987
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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
20
21 belongs_to :project
21 belongs_to :project
22 belongs_to :tracker
22 belongs_to :tracker
23 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
23 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
24 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
24 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
25 belongs_to :assigned_to, :class_name => 'Principal', :foreign_key => 'assigned_to_id'
25 belongs_to :assigned_to, :class_name => 'Principal', :foreign_key => 'assigned_to_id'
26 belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
26 belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
27 belongs_to :priority, :class_name => 'IssuePriority', :foreign_key => 'priority_id'
27 belongs_to :priority, :class_name => 'IssuePriority', :foreign_key => 'priority_id'
28 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
28 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
29
29
30 has_many :journals, :as => :journalized, :dependent => :destroy
30 has_many :journals, :as => :journalized, :dependent => :destroy
31 has_many :time_entries, :dependent => :delete_all
31 has_many :time_entries, :dependent => :delete_all
32 has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
32 has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
33
33
34 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
34 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
35 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
35 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
36
36
37 acts_as_nested_set :scope => 'root_id', :dependent => :destroy
37 acts_as_nested_set :scope => 'root_id', :dependent => :destroy
38 acts_as_attachable :after_add => :attachment_added, :after_remove => :attachment_removed
38 acts_as_attachable :after_add => :attachment_added, :after_remove => :attachment_removed
39 acts_as_customizable
39 acts_as_customizable
40 acts_as_watchable
40 acts_as_watchable
41 acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"],
41 acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"],
42 :include => [:project, :journals],
42 :include => [:project, :journals],
43 # sort by id so that limited eager loading doesn't break with postgresql
43 # sort by id so that limited eager loading doesn't break with postgresql
44 :order_column => "#{table_name}.id"
44 :order_column => "#{table_name}.id"
45 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"},
45 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"},
46 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}},
46 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}},
47 :type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') }
47 :type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') }
48
48
49 acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
49 acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
50 :author_key => :author_id
50 :author_key => :author_id
51
51
52 DONE_RATIO_OPTIONS = %w(issue_field issue_status)
52 DONE_RATIO_OPTIONS = %w(issue_field issue_status)
53
53
54 attr_reader :current_journal
54 attr_reader :current_journal
55
55
56 validates_presence_of :subject, :priority, :project, :tracker, :author, :status
56 validates_presence_of :subject, :priority, :project, :tracker, :author, :status
57
57
58 validates_length_of :subject, :maximum => 255
58 validates_length_of :subject, :maximum => 255
59 validates_inclusion_of :done_ratio, :in => 0..100
59 validates_inclusion_of :done_ratio, :in => 0..100
60 validates_numericality_of :estimated_hours, :allow_nil => true
60 validates_numericality_of :estimated_hours, :allow_nil => true
61 validate :validate_issue
61 validate :validate_issue
62
62
63 named_scope :visible, lambda {|*args| { :include => :project,
63 named_scope :visible, lambda {|*args| { :include => :project,
64 :conditions => Issue.visible_condition(args.shift || User.current, *args) } }
64 :conditions => Issue.visible_condition(args.shift || User.current, *args) } }
65
65
66 named_scope :open, :conditions => ["#{IssueStatus.table_name}.is_closed = ?", false], :include => :status
66 named_scope :open, :conditions => ["#{IssueStatus.table_name}.is_closed = ?", false], :include => :status
67
67
68 named_scope :recently_updated, :order => "#{Issue.table_name}.updated_on DESC"
68 named_scope :recently_updated, :order => "#{Issue.table_name}.updated_on DESC"
69 named_scope :with_limit, lambda { |limit| { :limit => limit} }
69 named_scope :with_limit, lambda { |limit| { :limit => limit} }
70 named_scope :on_active_project, :include => [:status, :project, :tracker],
70 named_scope :on_active_project, :include => [:status, :project, :tracker],
71 :conditions => ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"]
71 :conditions => ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"]
72
72
73 named_scope :without_version, lambda {
73 named_scope :without_version, lambda {
74 {
74 {
75 :conditions => { :fixed_version_id => nil}
75 :conditions => { :fixed_version_id => nil}
76 }
76 }
77 }
77 }
78
78
79 named_scope :with_query, lambda {|query|
79 named_scope :with_query, lambda {|query|
80 {
80 {
81 :conditions => Query.merge_conditions(query.statement)
81 :conditions => Query.merge_conditions(query.statement)
82 }
82 }
83 }
83 }
84
84
85 before_create :default_assign
85 before_create :default_assign
86 before_save :close_duplicates, :update_done_ratio_from_issue_status
86 before_save :close_duplicates, :update_done_ratio_from_issue_status
87 after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal
87 after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal
88 after_destroy :update_parent_attributes
88 after_destroy :update_parent_attributes
89
89
90 # Returns a SQL conditions string used to find all issues visible by the specified user
90 # Returns a SQL conditions string used to find all issues visible by the specified user
91 def self.visible_condition(user, options={})
91 def self.visible_condition(user, options={})
92 Project.allowed_to_condition(user, :view_issues, options) do |role, user|
92 Project.allowed_to_condition(user, :view_issues, options) do |role, user|
93 case role.issues_visibility
93 case role.issues_visibility
94 when 'all'
94 when 'all'
95 nil
95 nil
96 when 'default'
96 when 'default'
97 user_ids = [user.id] + user.groups.map(&:id)
97 user_ids = [user.id] + user.groups.map(&:id)
98 "(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
98 "(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
99 when 'own'
99 when 'own'
100 user_ids = [user.id] + user.groups.map(&:id)
100 user_ids = [user.id] + user.groups.map(&:id)
101 "(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
101 "(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
102 else
102 else
103 '1=0'
103 '1=0'
104 end
104 end
105 end
105 end
106 end
106 end
107
107
108 # Returns true if usr or current user is allowed to view the issue
108 # Returns true if usr or current user is allowed to view the issue
109 def visible?(usr=nil)
109 def visible?(usr=nil)
110 (usr || User.current).allowed_to?(:view_issues, self.project) do |role, user|
110 (usr || User.current).allowed_to?(:view_issues, self.project) do |role, user|
111 case role.issues_visibility
111 case role.issues_visibility
112 when 'all'
112 when 'all'
113 true
113 true
114 when 'default'
114 when 'default'
115 !self.is_private? || self.author == user || user.is_or_belongs_to?(assigned_to)
115 !self.is_private? || self.author == user || user.is_or_belongs_to?(assigned_to)
116 when 'own'
116 when 'own'
117 self.author == user || user.is_or_belongs_to?(assigned_to)
117 self.author == user || user.is_or_belongs_to?(assigned_to)
118 else
118 else
119 false
119 false
120 end
120 end
121 end
121 end
122 end
122 end
123
123
124 def after_initialize
124 def after_initialize
125 if new_record?
125 if new_record?
126 # set default values for new records only
126 # set default values for new records only
127 self.status ||= IssueStatus.default
127 self.status ||= IssueStatus.default
128 self.priority ||= IssuePriority.default
128 self.priority ||= IssuePriority.default
129 end
129 end
130 end
130 end
131
131
132 # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
132 # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
133 def available_custom_fields
133 def available_custom_fields
134 (project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields.all) : []
134 (project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields.all) : []
135 end
135 end
136
136
137 def copy_from(arg)
137 def copy_from(arg)
138 issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg)
138 issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg)
139 self.attributes = issue.attributes.dup.except("id", "root_id", "parent_id", "lft", "rgt", "created_on", "updated_on")
139 self.attributes = issue.attributes.dup.except("id", "root_id", "parent_id", "lft", "rgt", "created_on", "updated_on")
140 self.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
140 self.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
141 self.status = issue.status
141 self.status = issue.status
142 self
142 self
143 end
143 end
144
144
145 # Moves/copies an issue to a new project and tracker
145 # Moves/copies an issue to a new project and tracker
146 # Returns the moved/copied issue on success, false on failure
146 # Returns the moved/copied issue on success, false on failure
147 def move_to_project(*args)
147 def move_to_project(*args)
148 ret = Issue.transaction do
148 ret = Issue.transaction do
149 move_to_project_without_transaction(*args) || raise(ActiveRecord::Rollback)
149 move_to_project_without_transaction(*args) || raise(ActiveRecord::Rollback)
150 end || false
150 end || false
151 end
151 end
152
152
153 def move_to_project_without_transaction(new_project, new_tracker = nil, options = {})
153 def move_to_project_without_transaction(new_project, new_tracker = nil, options = {})
154 options ||= {}
154 options ||= {}
155
155
156 if options[:copy]
156 if options[:copy]
157 issue = self.class.new.copy_from(self)
157 issue = self.class.new.copy_from(self)
158 else
158 else
159 issue = self
159 issue = self
160 issue.init_journal(User.current, options[:notes])
160 issue.init_journal(User.current, options[:notes])
161 end
161 end
162
162
163 if new_project && issue.project_id != new_project.id
163 if new_project && issue.project_id != new_project.id
164 # delete issue relations
164 # delete issue relations
165 unless Setting.cross_project_issue_relations?
165 unless Setting.cross_project_issue_relations?
166 issue.relations_from.clear
166 issue.relations_from.clear
167 issue.relations_to.clear
167 issue.relations_to.clear
168 end
168 end
169 # issue is moved to another project
169 # issue is moved to another project
170 # reassign to the category with same name if any
170 # reassign to the category with same name if any
171 new_category = issue.category.nil? ? nil : new_project.issue_categories.find_by_name(issue.category.name)
171 new_category = issue.category.nil? ? nil : new_project.issue_categories.find_by_name(issue.category.name)
172 issue.category = new_category
172 issue.category = new_category
173 # Keep the fixed_version if it's still valid in the new_project
173 # Keep the fixed_version if it's still valid in the new_project
174 unless new_project.shared_versions.include?(issue.fixed_version)
174 unless new_project.shared_versions.include?(issue.fixed_version)
175 issue.fixed_version = nil
175 issue.fixed_version = nil
176 end
176 end
177 issue.project = new_project
177 issue.project = new_project
178 if issue.parent && issue.parent.project_id != issue.project_id
178 if issue.parent && issue.parent.project_id != issue.project_id
179 issue.parent_issue_id = nil
179 issue.parent_issue_id = nil
180 end
180 end
181 end
181 end
182 if new_tracker
182 if new_tracker
183 issue.tracker = new_tracker
183 issue.tracker = new_tracker
184 issue.reset_custom_values!
184 issue.reset_custom_values!
185 end
185 end
186 if options[:copy]
186 if options[:copy]
187 issue.author = User.current
187 issue.author = User.current
188 issue.custom_field_values = self.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
188 issue.custom_field_values = self.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
189 issue.status = if options[:attributes] && options[:attributes][:status_id]
189 issue.status = if options[:attributes] && options[:attributes][:status_id]
190 IssueStatus.find_by_id(options[:attributes][:status_id])
190 IssueStatus.find_by_id(options[:attributes][:status_id])
191 else
191 else
192 self.status
192 self.status
193 end
193 end
194 end
194 end
195 # Allow bulk setting of attributes on the issue
195 # Allow bulk setting of attributes on the issue
196 if options[:attributes]
196 if options[:attributes]
197 issue.attributes = options[:attributes]
197 issue.attributes = options[:attributes]
198 end
198 end
199 if options[:copy] && options[:notes].present?
199 if options[:copy] && options[:notes].present?
200 issue.init_journal(User.current, options[:notes])
200 issue.init_journal(User.current, options[:notes])
201 issue.current_journal.notify = false
201 issue.current_journal.notify = false
202 end
202 end
203 if issue.save
203 if issue.save
204 unless options[:copy]
204 unless options[:copy]
205 # Manually update project_id on related time entries
205 # Manually update project_id on related time entries
206 TimeEntry.update_all("project_id = #{new_project.id}", {:issue_id => id})
206 TimeEntry.update_all("project_id = #{new_project.id}", {:issue_id => id})
207
207
208 issue.children.each do |child|
208 issue.children.each do |child|
209 unless child.move_to_project_without_transaction(new_project)
209 unless child.move_to_project_without_transaction(new_project)
210 # Move failed and transaction was rollback'd
210 # Move failed and transaction was rollback'd
211 return false
211 return false
212 end
212 end
213 end
213 end
214 end
214 end
215 else
215 else
216 return false
216 return false
217 end
217 end
218 issue
218 issue
219 end
219 end
220
220
221 def status_id=(sid)
221 def status_id=(sid)
222 self.status = nil
222 self.status = nil
223 write_attribute(:status_id, sid)
223 write_attribute(:status_id, sid)
224 end
224 end
225
225
226 def priority_id=(pid)
226 def priority_id=(pid)
227 self.priority = nil
227 self.priority = nil
228 write_attribute(:priority_id, pid)
228 write_attribute(:priority_id, pid)
229 end
229 end
230
230
231 def tracker_id=(tid)
231 def tracker_id=(tid)
232 self.tracker = nil
232 self.tracker = nil
233 result = write_attribute(:tracker_id, tid)
233 result = write_attribute(:tracker_id, tid)
234 @custom_field_values = nil
234 @custom_field_values = nil
235 result
235 result
236 end
236 end
237
237
238 def description=(arg)
238 def description=(arg)
239 if arg.is_a?(String)
239 if arg.is_a?(String)
240 arg = arg.gsub(/(\r\n|\n|\r)/, "\r\n")
240 arg = arg.gsub(/(\r\n|\n|\r)/, "\r\n")
241 end
241 end
242 write_attribute(:description, arg)
242 write_attribute(:description, arg)
243 end
243 end
244
244
245 # Overrides attributes= so that project and tracker get assigned first
245 # Overrides attributes= so that project and tracker get assigned first
246 def attributes_with_project_and_tracker_first=(new_attributes, *args)
246 def attributes_with_project_and_tracker_first=(new_attributes, *args)
247 return if new_attributes.nil?
247 return if new_attributes.nil?
248 attrs = new_attributes.dup
248 attrs = new_attributes.dup
249 attrs.stringify_keys!
249 attrs.stringify_keys!
250
250
251 %w(project project_id tracker tracker_id).each do |attr|
251 %w(project project_id tracker tracker_id).each do |attr|
252 if attrs.has_key?(attr)
252 if attrs.has_key?(attr)
253 send "#{attr}=", attrs.delete(attr)
253 send "#{attr}=", attrs.delete(attr)
254 end
254 end
255 end
255 end
256 send :attributes_without_project_and_tracker_first=, attrs, *args
256 send :attributes_without_project_and_tracker_first=, attrs, *args
257 end
257 end
258 # Do not redefine alias chain on reload (see #4838)
258 # Do not redefine alias chain on reload (see #4838)
259 alias_method_chain(:attributes=, :project_and_tracker_first) unless method_defined?(:attributes_without_project_and_tracker_first=)
259 alias_method_chain(:attributes=, :project_and_tracker_first) unless method_defined?(:attributes_without_project_and_tracker_first=)
260
260
261 def estimated_hours=(h)
261 def estimated_hours=(h)
262 write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
262 write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
263 end
263 end
264
264
265 safe_attributes 'tracker_id',
265 safe_attributes 'tracker_id',
266 'status_id',
266 'status_id',
267 'category_id',
267 'category_id',
268 'assigned_to_id',
268 'assigned_to_id',
269 'priority_id',
269 'priority_id',
270 'fixed_version_id',
270 'fixed_version_id',
271 'subject',
271 'subject',
272 'description',
272 'description',
273 'start_date',
273 'start_date',
274 'due_date',
274 'due_date',
275 'done_ratio',
275 'done_ratio',
276 'estimated_hours',
276 'estimated_hours',
277 'custom_field_values',
277 'custom_field_values',
278 'custom_fields',
278 'custom_fields',
279 'lock_version',
279 'lock_version',
280 :if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) }
280 :if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) }
281
281
282 safe_attributes 'status_id',
282 safe_attributes 'status_id',
283 'assigned_to_id',
283 'assigned_to_id',
284 'fixed_version_id',
284 'fixed_version_id',
285 'done_ratio',
285 'done_ratio',
286 'lock_version',
286 :if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? }
287 :if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? }
287
288
288 safe_attributes 'watcher_user_ids',
289 safe_attributes 'watcher_user_ids',
289 :if => lambda {|issue, user| issue.new_record? && user.allowed_to?(:add_issue_watchers, issue.project)}
290 :if => lambda {|issue, user| issue.new_record? && user.allowed_to?(:add_issue_watchers, issue.project)}
290
291
291 safe_attributes 'is_private',
292 safe_attributes 'is_private',
292 :if => lambda {|issue, user|
293 :if => lambda {|issue, user|
293 user.allowed_to?(:set_issues_private, issue.project) ||
294 user.allowed_to?(:set_issues_private, issue.project) ||
294 (issue.author == user && user.allowed_to?(:set_own_issues_private, issue.project))
295 (issue.author == user && user.allowed_to?(:set_own_issues_private, issue.project))
295 }
296 }
296
297
297 safe_attributes 'parent_issue_id',
298 safe_attributes 'parent_issue_id',
298 :if => lambda {|issue, user| (issue.new_record? || user.allowed_to?(:edit_issues, issue.project)) &&
299 :if => lambda {|issue, user| (issue.new_record? || user.allowed_to?(:edit_issues, issue.project)) &&
299 user.allowed_to?(:manage_subtasks, issue.project)}
300 user.allowed_to?(:manage_subtasks, issue.project)}
300
301
301 # Safely sets attributes
302 # Safely sets attributes
302 # Should be called from controllers instead of #attributes=
303 # Should be called from controllers instead of #attributes=
303 # attr_accessible is too rough because we still want things like
304 # attr_accessible is too rough because we still want things like
304 # Issue.new(:project => foo) to work
305 # Issue.new(:project => foo) to work
305 # TODO: move workflow/permission checks from controllers to here
306 # TODO: move workflow/permission checks from controllers to here
306 def safe_attributes=(attrs, user=User.current)
307 def safe_attributes=(attrs, user=User.current)
307 return unless attrs.is_a?(Hash)
308 return unless attrs.is_a?(Hash)
308
309
309 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
310 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
310 attrs = delete_unsafe_attributes(attrs, user)
311 attrs = delete_unsafe_attributes(attrs, user)
311 return if attrs.empty?
312 return if attrs.empty?
312
313
313 # Tracker must be set before since new_statuses_allowed_to depends on it.
314 # Tracker must be set before since new_statuses_allowed_to depends on it.
314 if t = attrs.delete('tracker_id')
315 if t = attrs.delete('tracker_id')
315 self.tracker_id = t
316 self.tracker_id = t
316 end
317 end
317
318
318 if attrs['status_id']
319 if attrs['status_id']
319 unless new_statuses_allowed_to(user).collect(&:id).include?(attrs['status_id'].to_i)
320 unless new_statuses_allowed_to(user).collect(&:id).include?(attrs['status_id'].to_i)
320 attrs.delete('status_id')
321 attrs.delete('status_id')
321 end
322 end
322 end
323 end
323
324
324 unless leaf?
325 unless leaf?
325 attrs.reject! {|k,v| %w(priority_id done_ratio start_date due_date estimated_hours).include?(k)}
326 attrs.reject! {|k,v| %w(priority_id done_ratio start_date due_date estimated_hours).include?(k)}
326 end
327 end
327
328
328 if attrs['parent_issue_id'].present?
329 if attrs['parent_issue_id'].present?
329 attrs.delete('parent_issue_id') unless Issue.visible(user).exists?(attrs['parent_issue_id'].to_i)
330 attrs.delete('parent_issue_id') unless Issue.visible(user).exists?(attrs['parent_issue_id'].to_i)
330 end
331 end
331
332
332 # mass-assignment security bypass
333 # mass-assignment security bypass
333 self.send :attributes=, attrs, false
334 self.send :attributes=, attrs, false
334 end
335 end
335
336
336 def done_ratio
337 def done_ratio
337 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
338 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
338 status.default_done_ratio
339 status.default_done_ratio
339 else
340 else
340 read_attribute(:done_ratio)
341 read_attribute(:done_ratio)
341 end
342 end
342 end
343 end
343
344
344 def self.use_status_for_done_ratio?
345 def self.use_status_for_done_ratio?
345 Setting.issue_done_ratio == 'issue_status'
346 Setting.issue_done_ratio == 'issue_status'
346 end
347 end
347
348
348 def self.use_field_for_done_ratio?
349 def self.use_field_for_done_ratio?
349 Setting.issue_done_ratio == 'issue_field'
350 Setting.issue_done_ratio == 'issue_field'
350 end
351 end
351
352
352 def validate_issue
353 def validate_issue
353 if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
354 if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
354 errors.add :due_date, :not_a_date
355 errors.add :due_date, :not_a_date
355 end
356 end
356
357
357 if self.due_date and self.start_date and self.due_date < self.start_date
358 if self.due_date and self.start_date and self.due_date < self.start_date
358 errors.add :due_date, :greater_than_start_date
359 errors.add :due_date, :greater_than_start_date
359 end
360 end
360
361
361 if start_date && soonest_start && start_date < soonest_start
362 if start_date && soonest_start && start_date < soonest_start
362 errors.add :start_date, :invalid
363 errors.add :start_date, :invalid
363 end
364 end
364
365
365 if fixed_version
366 if fixed_version
366 if !assignable_versions.include?(fixed_version)
367 if !assignable_versions.include?(fixed_version)
367 errors.add :fixed_version_id, :inclusion
368 errors.add :fixed_version_id, :inclusion
368 elsif reopened? && fixed_version.closed?
369 elsif reopened? && fixed_version.closed?
369 errors.add :base, I18n.t(:error_can_not_reopen_issue_on_closed_version)
370 errors.add :base, I18n.t(:error_can_not_reopen_issue_on_closed_version)
370 end
371 end
371 end
372 end
372
373
373 # Checks that the issue can not be added/moved to a disabled tracker
374 # Checks that the issue can not be added/moved to a disabled tracker
374 if project && (tracker_id_changed? || project_id_changed?)
375 if project && (tracker_id_changed? || project_id_changed?)
375 unless project.trackers.include?(tracker)
376 unless project.trackers.include?(tracker)
376 errors.add :tracker_id, :inclusion
377 errors.add :tracker_id, :inclusion
377 end
378 end
378 end
379 end
379
380
380 # Checks parent issue assignment
381 # Checks parent issue assignment
381 if @parent_issue
382 if @parent_issue
382 if @parent_issue.project_id != project_id
383 if @parent_issue.project_id != project_id
383 errors.add :parent_issue_id, :not_same_project
384 errors.add :parent_issue_id, :not_same_project
384 elsif !new_record?
385 elsif !new_record?
385 # moving an existing issue
386 # moving an existing issue
386 if @parent_issue.root_id != root_id
387 if @parent_issue.root_id != root_id
387 # we can always move to another tree
388 # we can always move to another tree
388 elsif move_possible?(@parent_issue)
389 elsif move_possible?(@parent_issue)
389 # move accepted inside tree
390 # move accepted inside tree
390 else
391 else
391 errors.add :parent_issue_id, :not_a_valid_parent
392 errors.add :parent_issue_id, :not_a_valid_parent
392 end
393 end
393 end
394 end
394 end
395 end
395 end
396 end
396
397
397 # Set the done_ratio using the status if that setting is set. This will keep the done_ratios
398 # Set the done_ratio using the status if that setting is set. This will keep the done_ratios
398 # even if the user turns off the setting later
399 # even if the user turns off the setting later
399 def update_done_ratio_from_issue_status
400 def update_done_ratio_from_issue_status
400 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
401 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
401 self.done_ratio = status.default_done_ratio
402 self.done_ratio = status.default_done_ratio
402 end
403 end
403 end
404 end
404
405
405 def init_journal(user, notes = "")
406 def init_journal(user, notes = "")
406 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
407 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
407 @issue_before_change = self.clone
408 @issue_before_change = self.clone
408 @issue_before_change.status = self.status
409 @issue_before_change.status = self.status
409 @custom_values_before_change = {}
410 @custom_values_before_change = {}
410 self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
411 self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
411 # Make sure updated_on is updated when adding a note.
412 # Make sure updated_on is updated when adding a note.
412 updated_on_will_change!
413 updated_on_will_change!
413 @current_journal
414 @current_journal
414 end
415 end
415
416
416 # Return true if the issue is closed, otherwise false
417 # Return true if the issue is closed, otherwise false
417 def closed?
418 def closed?
418 self.status.is_closed?
419 self.status.is_closed?
419 end
420 end
420
421
421 # Return true if the issue is being reopened
422 # Return true if the issue is being reopened
422 def reopened?
423 def reopened?
423 if !new_record? && status_id_changed?
424 if !new_record? && status_id_changed?
424 status_was = IssueStatus.find_by_id(status_id_was)
425 status_was = IssueStatus.find_by_id(status_id_was)
425 status_new = IssueStatus.find_by_id(status_id)
426 status_new = IssueStatus.find_by_id(status_id)
426 if status_was && status_new && status_was.is_closed? && !status_new.is_closed?
427 if status_was && status_new && status_was.is_closed? && !status_new.is_closed?
427 return true
428 return true
428 end
429 end
429 end
430 end
430 false
431 false
431 end
432 end
432
433
433 # Return true if the issue is being closed
434 # Return true if the issue is being closed
434 def closing?
435 def closing?
435 if !new_record? && status_id_changed?
436 if !new_record? && status_id_changed?
436 status_was = IssueStatus.find_by_id(status_id_was)
437 status_was = IssueStatus.find_by_id(status_id_was)
437 status_new = IssueStatus.find_by_id(status_id)
438 status_new = IssueStatus.find_by_id(status_id)
438 if status_was && status_new && !status_was.is_closed? && status_new.is_closed?
439 if status_was && status_new && !status_was.is_closed? && status_new.is_closed?
439 return true
440 return true
440 end
441 end
441 end
442 end
442 false
443 false
443 end
444 end
444
445
445 # Returns true if the issue is overdue
446 # Returns true if the issue is overdue
446 def overdue?
447 def overdue?
447 !due_date.nil? && (due_date < Date.today) && !status.is_closed?
448 !due_date.nil? && (due_date < Date.today) && !status.is_closed?
448 end
449 end
449
450
450 # Is the amount of work done less than it should for the due date
451 # Is the amount of work done less than it should for the due date
451 def behind_schedule?
452 def behind_schedule?
452 return false if start_date.nil? || due_date.nil?
453 return false if start_date.nil? || due_date.nil?
453 done_date = start_date + ((due_date - start_date+1)* done_ratio/100).floor
454 done_date = start_date + ((due_date - start_date+1)* done_ratio/100).floor
454 return done_date <= Date.today
455 return done_date <= Date.today
455 end
456 end
456
457
457 # Does this issue have children?
458 # Does this issue have children?
458 def children?
459 def children?
459 !leaf?
460 !leaf?
460 end
461 end
461
462
462 # Users the issue can be assigned to
463 # Users the issue can be assigned to
463 def assignable_users
464 def assignable_users
464 users = project.assignable_users
465 users = project.assignable_users
465 users << author if author
466 users << author if author
466 users << assigned_to if assigned_to
467 users << assigned_to if assigned_to
467 users.uniq.sort
468 users.uniq.sort
468 end
469 end
469
470
470 # Versions that the issue can be assigned to
471 # Versions that the issue can be assigned to
471 def assignable_versions
472 def assignable_versions
472 @assignable_versions ||= (project.shared_versions.open + [Version.find_by_id(fixed_version_id_was)]).compact.uniq.sort
473 @assignable_versions ||= (project.shared_versions.open + [Version.find_by_id(fixed_version_id_was)]).compact.uniq.sort
473 end
474 end
474
475
475 # Returns true if this issue is blocked by another issue that is still open
476 # Returns true if this issue is blocked by another issue that is still open
476 def blocked?
477 def blocked?
477 !relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil?
478 !relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil?
478 end
479 end
479
480
480 # Returns an array of status that user is able to apply
481 # Returns an array of status that user is able to apply
481 def new_statuses_allowed_to(user, include_default=false)
482 def new_statuses_allowed_to(user, include_default=false)
482 statuses = status.find_new_statuses_allowed_to(
483 statuses = status.find_new_statuses_allowed_to(
483 user.roles_for_project(project),
484 user.roles_for_project(project),
484 tracker,
485 tracker,
485 author == user,
486 author == user,
486 assigned_to_id_changed? ? assigned_to_id_was == user.id : assigned_to_id == user.id
487 assigned_to_id_changed? ? assigned_to_id_was == user.id : assigned_to_id == user.id
487 )
488 )
488 statuses << status unless statuses.empty?
489 statuses << status unless statuses.empty?
489 statuses << IssueStatus.default if include_default
490 statuses << IssueStatus.default if include_default
490 statuses = statuses.uniq.sort
491 statuses = statuses.uniq.sort
491 blocked? ? statuses.reject {|s| s.is_closed?} : statuses
492 blocked? ? statuses.reject {|s| s.is_closed?} : statuses
492 end
493 end
493
494
494 # Returns the mail adresses of users that should be notified
495 # Returns the mail adresses of users that should be notified
495 def recipients
496 def recipients
496 notified = project.notified_users
497 notified = project.notified_users
497 # Author and assignee are always notified unless they have been
498 # Author and assignee are always notified unless they have been
498 # locked or don't want to be notified
499 # locked or don't want to be notified
499 notified << author if author && author.active? && author.notify_about?(self)
500 notified << author if author && author.active? && author.notify_about?(self)
500 if assigned_to
501 if assigned_to
501 if assigned_to.is_a?(Group)
502 if assigned_to.is_a?(Group)
502 notified += assigned_to.users.select {|u| u.active? && u.notify_about?(self)}
503 notified += assigned_to.users.select {|u| u.active? && u.notify_about?(self)}
503 else
504 else
504 notified << assigned_to if assigned_to.active? && assigned_to.notify_about?(self)
505 notified << assigned_to if assigned_to.active? && assigned_to.notify_about?(self)
505 end
506 end
506 end
507 end
507 notified.uniq!
508 notified.uniq!
508 # Remove users that can not view the issue
509 # Remove users that can not view the issue
509 notified.reject! {|user| !visible?(user)}
510 notified.reject! {|user| !visible?(user)}
510 notified.collect(&:mail)
511 notified.collect(&:mail)
511 end
512 end
512
513
513 # Returns the number of hours spent on this issue
514 # Returns the number of hours spent on this issue
514 def spent_hours
515 def spent_hours
515 @spent_hours ||= time_entries.sum(:hours) || 0
516 @spent_hours ||= time_entries.sum(:hours) || 0
516 end
517 end
517
518
518 # Returns the total number of hours spent on this issue and its descendants
519 # Returns the total number of hours spent on this issue and its descendants
519 #
520 #
520 # Example:
521 # Example:
521 # spent_hours => 0.0
522 # spent_hours => 0.0
522 # spent_hours => 50.2
523 # spent_hours => 50.2
523 def total_spent_hours
524 def total_spent_hours
524 @total_spent_hours ||= self_and_descendants.sum("#{TimeEntry.table_name}.hours", :include => :time_entries).to_f || 0.0
525 @total_spent_hours ||= self_and_descendants.sum("#{TimeEntry.table_name}.hours", :include => :time_entries).to_f || 0.0
525 end
526 end
526
527
527 def relations
528 def relations
528 @relations ||= (relations_from + relations_to).sort
529 @relations ||= (relations_from + relations_to).sort
529 end
530 end
530
531
531 # Preloads relations for a collection of issues
532 # Preloads relations for a collection of issues
532 def self.load_relations(issues)
533 def self.load_relations(issues)
533 if issues.any?
534 if issues.any?
534 relations = IssueRelation.all(:conditions => ["issue_from_id IN (:ids) OR issue_to_id IN (:ids)", {:ids => issues.map(&:id)}])
535 relations = IssueRelation.all(:conditions => ["issue_from_id IN (:ids) OR issue_to_id IN (:ids)", {:ids => issues.map(&:id)}])
535 issues.each do |issue|
536 issues.each do |issue|
536 issue.instance_variable_set "@relations", relations.select {|r| r.issue_from_id == issue.id || r.issue_to_id == issue.id}
537 issue.instance_variable_set "@relations", relations.select {|r| r.issue_from_id == issue.id || r.issue_to_id == issue.id}
537 end
538 end
538 end
539 end
539 end
540 end
540
541
541 # Preloads visible spent time for a collection of issues
542 # Preloads visible spent time for a collection of issues
542 def self.load_visible_spent_hours(issues, user=User.current)
543 def self.load_visible_spent_hours(issues, user=User.current)
543 if issues.any?
544 if issues.any?
544 hours_by_issue_id = TimeEntry.visible(user).sum(:hours, :group => :issue_id)
545 hours_by_issue_id = TimeEntry.visible(user).sum(:hours, :group => :issue_id)
545 issues.each do |issue|
546 issues.each do |issue|
546 issue.instance_variable_set "@spent_hours", (hours_by_issue_id[issue.id] || 0)
547 issue.instance_variable_set "@spent_hours", (hours_by_issue_id[issue.id] || 0)
547 end
548 end
548 end
549 end
549 end
550 end
550
551
551 # Finds an issue relation given its id.
552 # Finds an issue relation given its id.
552 def find_relation(relation_id)
553 def find_relation(relation_id)
553 IssueRelation.find(relation_id, :conditions => ["issue_to_id = ? OR issue_from_id = ?", id, id])
554 IssueRelation.find(relation_id, :conditions => ["issue_to_id = ? OR issue_from_id = ?", id, id])
554 end
555 end
555
556
556 def all_dependent_issues(except=[])
557 def all_dependent_issues(except=[])
557 except << self
558 except << self
558 dependencies = []
559 dependencies = []
559 relations_from.each do |relation|
560 relations_from.each do |relation|
560 if relation.issue_to && !except.include?(relation.issue_to)
561 if relation.issue_to && !except.include?(relation.issue_to)
561 dependencies << relation.issue_to
562 dependencies << relation.issue_to
562 dependencies += relation.issue_to.all_dependent_issues(except)
563 dependencies += relation.issue_to.all_dependent_issues(except)
563 end
564 end
564 end
565 end
565 dependencies
566 dependencies
566 end
567 end
567
568
568 # Returns an array of issues that duplicate this one
569 # Returns an array of issues that duplicate this one
569 def duplicates
570 def duplicates
570 relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from}
571 relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from}
571 end
572 end
572
573
573 # Returns the due date or the target due date if any
574 # Returns the due date or the target due date if any
574 # Used on gantt chart
575 # Used on gantt chart
575 def due_before
576 def due_before
576 due_date || (fixed_version ? fixed_version.effective_date : nil)
577 due_date || (fixed_version ? fixed_version.effective_date : nil)
577 end
578 end
578
579
579 # Returns the time scheduled for this issue.
580 # Returns the time scheduled for this issue.
580 #
581 #
581 # Example:
582 # Example:
582 # Start Date: 2/26/09, End Date: 3/04/09
583 # Start Date: 2/26/09, End Date: 3/04/09
583 # duration => 6
584 # duration => 6
584 def duration
585 def duration
585 (start_date && due_date) ? due_date - start_date : 0
586 (start_date && due_date) ? due_date - start_date : 0
586 end
587 end
587
588
588 def soonest_start
589 def soonest_start
589 @soonest_start ||= (
590 @soonest_start ||= (
590 relations_to.collect{|relation| relation.successor_soonest_start} +
591 relations_to.collect{|relation| relation.successor_soonest_start} +
591 ancestors.collect(&:soonest_start)
592 ancestors.collect(&:soonest_start)
592 ).compact.max
593 ).compact.max
593 end
594 end
594
595
595 def reschedule_after(date)
596 def reschedule_after(date)
596 return if date.nil?
597 return if date.nil?
597 if leaf?
598 if leaf?
598 if start_date.nil? || start_date < date
599 if start_date.nil? || start_date < date
599 self.start_date, self.due_date = date, date + duration
600 self.start_date, self.due_date = date, date + duration
600 save
601 save
601 end
602 end
602 else
603 else
603 leaves.each do |leaf|
604 leaves.each do |leaf|
604 leaf.reschedule_after(date)
605 leaf.reschedule_after(date)
605 end
606 end
606 end
607 end
607 end
608 end
608
609
609 def <=>(issue)
610 def <=>(issue)
610 if issue.nil?
611 if issue.nil?
611 -1
612 -1
612 elsif root_id != issue.root_id
613 elsif root_id != issue.root_id
613 (root_id || 0) <=> (issue.root_id || 0)
614 (root_id || 0) <=> (issue.root_id || 0)
614 else
615 else
615 (lft || 0) <=> (issue.lft || 0)
616 (lft || 0) <=> (issue.lft || 0)
616 end
617 end
617 end
618 end
618
619
619 def to_s
620 def to_s
620 "#{tracker} ##{id}: #{subject}"
621 "#{tracker} ##{id}: #{subject}"
621 end
622 end
622
623
623 # Returns a string of css classes that apply to the issue
624 # Returns a string of css classes that apply to the issue
624 def css_classes
625 def css_classes
625 s = "issue status-#{status.position} priority-#{priority.position}"
626 s = "issue status-#{status.position} priority-#{priority.position}"
626 s << ' closed' if closed?
627 s << ' closed' if closed?
627 s << ' overdue' if overdue?
628 s << ' overdue' if overdue?
628 s << ' child' if child?
629 s << ' child' if child?
629 s << ' parent' unless leaf?
630 s << ' parent' unless leaf?
630 s << ' private' if is_private?
631 s << ' private' if is_private?
631 s << ' created-by-me' if User.current.logged? && author_id == User.current.id
632 s << ' created-by-me' if User.current.logged? && author_id == User.current.id
632 s << ' assigned-to-me' if User.current.logged? && assigned_to_id == User.current.id
633 s << ' assigned-to-me' if User.current.logged? && assigned_to_id == User.current.id
633 s
634 s
634 end
635 end
635
636
636 # Saves an issue, time_entry, attachments, and a journal from the parameters
637 # Saves an issue, time_entry, attachments, and a journal from the parameters
637 # Returns false if save fails
638 # Returns false if save fails
638 def save_issue_with_child_records(params, existing_time_entry=nil)
639 def save_issue_with_child_records(params, existing_time_entry=nil)
639 Issue.transaction do
640 Issue.transaction do
640 if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, project)
641 if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, project)
641 @time_entry = existing_time_entry || TimeEntry.new
642 @time_entry = existing_time_entry || TimeEntry.new
642 @time_entry.project = project
643 @time_entry.project = project
643 @time_entry.issue = self
644 @time_entry.issue = self
644 @time_entry.user = User.current
645 @time_entry.user = User.current
645 @time_entry.spent_on = User.current.today
646 @time_entry.spent_on = User.current.today
646 @time_entry.attributes = params[:time_entry]
647 @time_entry.attributes = params[:time_entry]
647 self.time_entries << @time_entry
648 self.time_entries << @time_entry
648 end
649 end
649
650
650 if valid?
651 if valid?
651 attachments = Attachment.attach_files(self, params[:attachments])
652 attachments = Attachment.attach_files(self, params[:attachments])
652 # TODO: Rename hook
653 # TODO: Rename hook
653 Redmine::Hook.call_hook(:controller_issues_edit_before_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
654 Redmine::Hook.call_hook(:controller_issues_edit_before_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
654 begin
655 begin
655 if save
656 if save
656 # TODO: Rename hook
657 # TODO: Rename hook
657 Redmine::Hook.call_hook(:controller_issues_edit_after_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
658 Redmine::Hook.call_hook(:controller_issues_edit_after_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
658 else
659 else
659 raise ActiveRecord::Rollback
660 raise ActiveRecord::Rollback
660 end
661 end
661 rescue ActiveRecord::StaleObjectError
662 rescue ActiveRecord::StaleObjectError
662 attachments[:files].each(&:destroy)
663 attachments[:files].each(&:destroy)
663 errors.add :base, l(:notice_locking_conflict)
664 errors.add :base, l(:notice_locking_conflict)
664 raise ActiveRecord::Rollback
665 raise ActiveRecord::Rollback
665 end
666 end
666 end
667 end
667 end
668 end
668 end
669 end
669
670
670 # Unassigns issues from +version+ if it's no longer shared with issue's project
671 # Unassigns issues from +version+ if it's no longer shared with issue's project
671 def self.update_versions_from_sharing_change(version)
672 def self.update_versions_from_sharing_change(version)
672 # Update issues assigned to the version
673 # Update issues assigned to the version
673 update_versions(["#{Issue.table_name}.fixed_version_id = ?", version.id])
674 update_versions(["#{Issue.table_name}.fixed_version_id = ?", version.id])
674 end
675 end
675
676
676 # Unassigns issues from versions that are no longer shared
677 # Unassigns issues from versions that are no longer shared
677 # after +project+ was moved
678 # after +project+ was moved
678 def self.update_versions_from_hierarchy_change(project)
679 def self.update_versions_from_hierarchy_change(project)
679 moved_project_ids = project.self_and_descendants.reload.collect(&:id)
680 moved_project_ids = project.self_and_descendants.reload.collect(&:id)
680 # Update issues of the moved projects and issues assigned to a version of a moved project
681 # Update issues of the moved projects and issues assigned to a version of a moved project
681 Issue.update_versions(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids])
682 Issue.update_versions(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids])
682 end
683 end
683
684
684 def parent_issue_id=(arg)
685 def parent_issue_id=(arg)
685 parent_issue_id = arg.blank? ? nil : arg.to_i
686 parent_issue_id = arg.blank? ? nil : arg.to_i
686 if parent_issue_id && @parent_issue = Issue.find_by_id(parent_issue_id)
687 if parent_issue_id && @parent_issue = Issue.find_by_id(parent_issue_id)
687 @parent_issue.id
688 @parent_issue.id
688 else
689 else
689 @parent_issue = nil
690 @parent_issue = nil
690 nil
691 nil
691 end
692 end
692 end
693 end
693
694
694 def parent_issue_id
695 def parent_issue_id
695 if instance_variable_defined? :@parent_issue
696 if instance_variable_defined? :@parent_issue
696 @parent_issue.nil? ? nil : @parent_issue.id
697 @parent_issue.nil? ? nil : @parent_issue.id
697 else
698 else
698 parent_id
699 parent_id
699 end
700 end
700 end
701 end
701
702
702 # Extracted from the ReportsController.
703 # Extracted from the ReportsController.
703 def self.by_tracker(project)
704 def self.by_tracker(project)
704 count_and_group_by(:project => project,
705 count_and_group_by(:project => project,
705 :field => 'tracker_id',
706 :field => 'tracker_id',
706 :joins => Tracker.table_name)
707 :joins => Tracker.table_name)
707 end
708 end
708
709
709 def self.by_version(project)
710 def self.by_version(project)
710 count_and_group_by(:project => project,
711 count_and_group_by(:project => project,
711 :field => 'fixed_version_id',
712 :field => 'fixed_version_id',
712 :joins => Version.table_name)
713 :joins => Version.table_name)
713 end
714 end
714
715
715 def self.by_priority(project)
716 def self.by_priority(project)
716 count_and_group_by(:project => project,
717 count_and_group_by(:project => project,
717 :field => 'priority_id',
718 :field => 'priority_id',
718 :joins => IssuePriority.table_name)
719 :joins => IssuePriority.table_name)
719 end
720 end
720
721
721 def self.by_category(project)
722 def self.by_category(project)
722 count_and_group_by(:project => project,
723 count_and_group_by(:project => project,
723 :field => 'category_id',
724 :field => 'category_id',
724 :joins => IssueCategory.table_name)
725 :joins => IssueCategory.table_name)
725 end
726 end
726
727
727 def self.by_assigned_to(project)
728 def self.by_assigned_to(project)
728 count_and_group_by(:project => project,
729 count_and_group_by(:project => project,
729 :field => 'assigned_to_id',
730 :field => 'assigned_to_id',
730 :joins => User.table_name)
731 :joins => User.table_name)
731 end
732 end
732
733
733 def self.by_author(project)
734 def self.by_author(project)
734 count_and_group_by(:project => project,
735 count_and_group_by(:project => project,
735 :field => 'author_id',
736 :field => 'author_id',
736 :joins => User.table_name)
737 :joins => User.table_name)
737 end
738 end
738
739
739 def self.by_subproject(project)
740 def self.by_subproject(project)
740 ActiveRecord::Base.connection.select_all("select s.id as status_id,
741 ActiveRecord::Base.connection.select_all("select s.id as status_id,
741 s.is_closed as closed,
742 s.is_closed as closed,
742 #{Issue.table_name}.project_id as project_id,
743 #{Issue.table_name}.project_id as project_id,
743 count(#{Issue.table_name}.id) as total
744 count(#{Issue.table_name}.id) as total
744 from
745 from
745 #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s
746 #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s
746 where
747 where
747 #{Issue.table_name}.status_id=s.id
748 #{Issue.table_name}.status_id=s.id
748 and #{Issue.table_name}.project_id = #{Project.table_name}.id
749 and #{Issue.table_name}.project_id = #{Project.table_name}.id
749 and #{visible_condition(User.current, :project => project, :with_subprojects => true)}
750 and #{visible_condition(User.current, :project => project, :with_subprojects => true)}
750 and #{Issue.table_name}.project_id <> #{project.id}
751 and #{Issue.table_name}.project_id <> #{project.id}
751 group by s.id, s.is_closed, #{Issue.table_name}.project_id") if project.descendants.active.any?
752 group by s.id, s.is_closed, #{Issue.table_name}.project_id") if project.descendants.active.any?
752 end
753 end
753 # End ReportsController extraction
754 # End ReportsController extraction
754
755
755 # Returns an array of projects that current user can move issues to
756 # Returns an array of projects that current user can move issues to
756 def self.allowed_target_projects_on_move
757 def self.allowed_target_projects_on_move
757 projects = []
758 projects = []
758 if User.current.admin?
759 if User.current.admin?
759 # admin is allowed to move issues to any active (visible) project
760 # admin is allowed to move issues to any active (visible) project
760 projects = Project.visible.all
761 projects = Project.visible.all
761 elsif User.current.logged?
762 elsif User.current.logged?
762 if Role.non_member.allowed_to?(:move_issues)
763 if Role.non_member.allowed_to?(:move_issues)
763 projects = Project.visible.all
764 projects = Project.visible.all
764 else
765 else
765 User.current.memberships.each {|m| projects << m.project if m.roles.detect {|r| r.allowed_to?(:move_issues)}}
766 User.current.memberships.each {|m| projects << m.project if m.roles.detect {|r| r.allowed_to?(:move_issues)}}
766 end
767 end
767 end
768 end
768 projects
769 projects
769 end
770 end
770
771
771 private
772 private
772
773
773 def update_nested_set_attributes
774 def update_nested_set_attributes
774 if root_id.nil?
775 if root_id.nil?
775 # issue was just created
776 # issue was just created
776 self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id)
777 self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id)
777 set_default_left_and_right
778 set_default_left_and_right
778 Issue.update_all("root_id = #{root_id}, lft = #{lft}, rgt = #{rgt}", ["id = ?", id])
779 Issue.update_all("root_id = #{root_id}, lft = #{lft}, rgt = #{rgt}", ["id = ?", id])
779 if @parent_issue
780 if @parent_issue
780 move_to_child_of(@parent_issue)
781 move_to_child_of(@parent_issue)
781 end
782 end
782 reload
783 reload
783 elsif parent_issue_id != parent_id
784 elsif parent_issue_id != parent_id
784 former_parent_id = parent_id
785 former_parent_id = parent_id
785 # moving an existing issue
786 # moving an existing issue
786 if @parent_issue && @parent_issue.root_id == root_id
787 if @parent_issue && @parent_issue.root_id == root_id
787 # inside the same tree
788 # inside the same tree
788 move_to_child_of(@parent_issue)
789 move_to_child_of(@parent_issue)
789 else
790 else
790 # to another tree
791 # to another tree
791 unless root?
792 unless root?
792 move_to_right_of(root)
793 move_to_right_of(root)
793 reload
794 reload
794 end
795 end
795 old_root_id = root_id
796 old_root_id = root_id
796 self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id )
797 self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id )
797 target_maxright = nested_set_scope.maximum(right_column_name) || 0
798 target_maxright = nested_set_scope.maximum(right_column_name) || 0
798 offset = target_maxright + 1 - lft
799 offset = target_maxright + 1 - lft
799 Issue.update_all("root_id = #{root_id}, lft = lft + #{offset}, rgt = rgt + #{offset}",
800 Issue.update_all("root_id = #{root_id}, lft = lft + #{offset}, rgt = rgt + #{offset}",
800 ["root_id = ? AND lft >= ? AND rgt <= ? ", old_root_id, lft, rgt])
801 ["root_id = ? AND lft >= ? AND rgt <= ? ", old_root_id, lft, rgt])
801 self[left_column_name] = lft + offset
802 self[left_column_name] = lft + offset
802 self[right_column_name] = rgt + offset
803 self[right_column_name] = rgt + offset
803 if @parent_issue
804 if @parent_issue
804 move_to_child_of(@parent_issue)
805 move_to_child_of(@parent_issue)
805 end
806 end
806 end
807 end
807 reload
808 reload
808 # delete invalid relations of all descendants
809 # delete invalid relations of all descendants
809 self_and_descendants.each do |issue|
810 self_and_descendants.each do |issue|
810 issue.relations.each do |relation|
811 issue.relations.each do |relation|
811 relation.destroy unless relation.valid?
812 relation.destroy unless relation.valid?
812 end
813 end
813 end
814 end
814 # update former parent
815 # update former parent
815 recalculate_attributes_for(former_parent_id) if former_parent_id
816 recalculate_attributes_for(former_parent_id) if former_parent_id
816 end
817 end
817 remove_instance_variable(:@parent_issue) if instance_variable_defined?(:@parent_issue)
818 remove_instance_variable(:@parent_issue) if instance_variable_defined?(:@parent_issue)
818 end
819 end
819
820
820 def update_parent_attributes
821 def update_parent_attributes
821 recalculate_attributes_for(parent_id) if parent_id
822 recalculate_attributes_for(parent_id) if parent_id
822 end
823 end
823
824
824 def recalculate_attributes_for(issue_id)
825 def recalculate_attributes_for(issue_id)
825 if issue_id && p = Issue.find_by_id(issue_id)
826 if issue_id && p = Issue.find_by_id(issue_id)
826 # priority = highest priority of children
827 # priority = highest priority of children
827 if priority_position = p.children.maximum("#{IssuePriority.table_name}.position", :include => :priority)
828 if priority_position = p.children.maximum("#{IssuePriority.table_name}.position", :include => :priority)
828 p.priority = IssuePriority.find_by_position(priority_position)
829 p.priority = IssuePriority.find_by_position(priority_position)
829 end
830 end
830
831
831 # start/due dates = lowest/highest dates of children
832 # start/due dates = lowest/highest dates of children
832 p.start_date = p.children.minimum(:start_date)
833 p.start_date = p.children.minimum(:start_date)
833 p.due_date = p.children.maximum(:due_date)
834 p.due_date = p.children.maximum(:due_date)
834 if p.start_date && p.due_date && p.due_date < p.start_date
835 if p.start_date && p.due_date && p.due_date < p.start_date
835 p.start_date, p.due_date = p.due_date, p.start_date
836 p.start_date, p.due_date = p.due_date, p.start_date
836 end
837 end
837
838
838 # done ratio = weighted average ratio of leaves
839 # done ratio = weighted average ratio of leaves
839 unless Issue.use_status_for_done_ratio? && p.status && p.status.default_done_ratio
840 unless Issue.use_status_for_done_ratio? && p.status && p.status.default_done_ratio
840 leaves_count = p.leaves.count
841 leaves_count = p.leaves.count
841 if leaves_count > 0
842 if leaves_count > 0
842 average = p.leaves.average(:estimated_hours).to_f
843 average = p.leaves.average(:estimated_hours).to_f
843 if average == 0
844 if average == 0
844 average = 1
845 average = 1
845 end
846 end
846 done = p.leaves.sum("COALESCE(estimated_hours, #{average}) * (CASE WHEN is_closed = #{connection.quoted_true} THEN 100 ELSE COALESCE(done_ratio, 0) END)", :include => :status).to_f
847 done = p.leaves.sum("COALESCE(estimated_hours, #{average}) * (CASE WHEN is_closed = #{connection.quoted_true} THEN 100 ELSE COALESCE(done_ratio, 0) END)", :include => :status).to_f
847 progress = done / (average * leaves_count)
848 progress = done / (average * leaves_count)
848 p.done_ratio = progress.round
849 p.done_ratio = progress.round
849 end
850 end
850 end
851 end
851
852
852 # estimate = sum of leaves estimates
853 # estimate = sum of leaves estimates
853 p.estimated_hours = p.leaves.sum(:estimated_hours).to_f
854 p.estimated_hours = p.leaves.sum(:estimated_hours).to_f
854 p.estimated_hours = nil if p.estimated_hours == 0.0
855 p.estimated_hours = nil if p.estimated_hours == 0.0
855
856
856 # ancestors will be recursively updated
857 # ancestors will be recursively updated
857 p.save(false)
858 p.save(false)
858 end
859 end
859 end
860 end
860
861
861 # Update issues so their versions are not pointing to a
862 # Update issues so their versions are not pointing to a
862 # fixed_version that is not shared with the issue's project
863 # fixed_version that is not shared with the issue's project
863 def self.update_versions(conditions=nil)
864 def self.update_versions(conditions=nil)
864 # Only need to update issues with a fixed_version from
865 # Only need to update issues with a fixed_version from
865 # a different project and that is not systemwide shared
866 # a different project and that is not systemwide shared
866 Issue.all(:conditions => merge_conditions("#{Issue.table_name}.fixed_version_id IS NOT NULL" +
867 Issue.all(:conditions => merge_conditions("#{Issue.table_name}.fixed_version_id IS NOT NULL" +
867 " AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" +
868 " AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" +
868 " AND #{Version.table_name}.sharing <> 'system'",
869 " AND #{Version.table_name}.sharing <> 'system'",
869 conditions),
870 conditions),
870 :include => [:project, :fixed_version]
871 :include => [:project, :fixed_version]
871 ).each do |issue|
872 ).each do |issue|
872 next if issue.project.nil? || issue.fixed_version.nil?
873 next if issue.project.nil? || issue.fixed_version.nil?
873 unless issue.project.shared_versions.include?(issue.fixed_version)
874 unless issue.project.shared_versions.include?(issue.fixed_version)
874 issue.init_journal(User.current)
875 issue.init_journal(User.current)
875 issue.fixed_version = nil
876 issue.fixed_version = nil
876 issue.save
877 issue.save
877 end
878 end
878 end
879 end
879 end
880 end
880
881
881 # Callback on attachment deletion
882 # Callback on attachment deletion
882 def attachment_added(obj)
883 def attachment_added(obj)
883 if @current_journal && !obj.new_record?
884 if @current_journal && !obj.new_record?
884 @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => obj.id, :value => obj.filename)
885 @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => obj.id, :value => obj.filename)
885 end
886 end
886 end
887 end
887
888
888 # Callback on attachment deletion
889 # Callback on attachment deletion
889 def attachment_removed(obj)
890 def attachment_removed(obj)
890 journal = init_journal(User.current)
891 journal = init_journal(User.current)
891 journal.details << JournalDetail.new(:property => 'attachment',
892 journal.details << JournalDetail.new(:property => 'attachment',
892 :prop_key => obj.id,
893 :prop_key => obj.id,
893 :old_value => obj.filename)
894 :old_value => obj.filename)
894 journal.save
895 journal.save
895 end
896 end
896
897
897 # Default assignment based on category
898 # Default assignment based on category
898 def default_assign
899 def default_assign
899 if assigned_to.nil? && category && category.assigned_to
900 if assigned_to.nil? && category && category.assigned_to
900 self.assigned_to = category.assigned_to
901 self.assigned_to = category.assigned_to
901 end
902 end
902 end
903 end
903
904
904 # Updates start/due dates of following issues
905 # Updates start/due dates of following issues
905 def reschedule_following_issues
906 def reschedule_following_issues
906 if start_date_changed? || due_date_changed?
907 if start_date_changed? || due_date_changed?
907 relations_from.each do |relation|
908 relations_from.each do |relation|
908 relation.set_issue_to_dates
909 relation.set_issue_to_dates
909 end
910 end
910 end
911 end
911 end
912 end
912
913
913 # Closes duplicates if the issue is being closed
914 # Closes duplicates if the issue is being closed
914 def close_duplicates
915 def close_duplicates
915 if closing?
916 if closing?
916 duplicates.each do |duplicate|
917 duplicates.each do |duplicate|
917 # Reload is need in case the duplicate was updated by a previous duplicate
918 # Reload is need in case the duplicate was updated by a previous duplicate
918 duplicate.reload
919 duplicate.reload
919 # Don't re-close it if it's already closed
920 # Don't re-close it if it's already closed
920 next if duplicate.closed?
921 next if duplicate.closed?
921 # Same user and notes
922 # Same user and notes
922 if @current_journal
923 if @current_journal
923 duplicate.init_journal(@current_journal.user, @current_journal.notes)
924 duplicate.init_journal(@current_journal.user, @current_journal.notes)
924 end
925 end
925 duplicate.update_attribute :status, self.status
926 duplicate.update_attribute :status, self.status
926 end
927 end
927 end
928 end
928 end
929 end
929
930
930 # Saves the changes in a Journal
931 # Saves the changes in a Journal
931 # Called after_save
932 # Called after_save
932 def create_journal
933 def create_journal
933 if @current_journal
934 if @current_journal
934 # attributes changes
935 # attributes changes
935 (Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on)).each {|c|
936 (Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on)).each {|c|
936 before = @issue_before_change.send(c)
937 before = @issue_before_change.send(c)
937 after = send(c)
938 after = send(c)
938 next if before == after || (before.blank? && after.blank?)
939 next if before == after || (before.blank? && after.blank?)
939 @current_journal.details << JournalDetail.new(:property => 'attr',
940 @current_journal.details << JournalDetail.new(:property => 'attr',
940 :prop_key => c,
941 :prop_key => c,
941 :old_value => @issue_before_change.send(c),
942 :old_value => @issue_before_change.send(c),
942 :value => send(c))
943 :value => send(c))
943 }
944 }
944 # custom fields changes
945 # custom fields changes
945 custom_values.each {|c|
946 custom_values.each {|c|
946 next if (@custom_values_before_change[c.custom_field_id]==c.value ||
947 next if (@custom_values_before_change[c.custom_field_id]==c.value ||
947 (@custom_values_before_change[c.custom_field_id].blank? && c.value.blank?))
948 (@custom_values_before_change[c.custom_field_id].blank? && c.value.blank?))
948 @current_journal.details << JournalDetail.new(:property => 'cf',
949 @current_journal.details << JournalDetail.new(:property => 'cf',
949 :prop_key => c.custom_field_id,
950 :prop_key => c.custom_field_id,
950 :old_value => @custom_values_before_change[c.custom_field_id],
951 :old_value => @custom_values_before_change[c.custom_field_id],
951 :value => c.value)
952 :value => c.value)
952 }
953 }
953 @current_journal.save
954 @current_journal.save
954 # reset current journal
955 # reset current journal
955 init_journal @current_journal.user, @current_journal.notes
956 init_journal @current_journal.user, @current_journal.notes
956 end
957 end
957 end
958 end
958
959
959 # Query generator for selecting groups of issue counts for a project
960 # Query generator for selecting groups of issue counts for a project
960 # based on specific criteria
961 # based on specific criteria
961 #
962 #
962 # Options
963 # Options
963 # * project - Project to search in.
964 # * project - Project to search in.
964 # * field - String. Issue field to key off of in the grouping.
965 # * field - String. Issue field to key off of in the grouping.
965 # * joins - String. The table name to join against.
966 # * joins - String. The table name to join against.
966 def self.count_and_group_by(options)
967 def self.count_and_group_by(options)
967 project = options.delete(:project)
968 project = options.delete(:project)
968 select_field = options.delete(:field)
969 select_field = options.delete(:field)
969 joins = options.delete(:joins)
970 joins = options.delete(:joins)
970
971
971 where = "#{Issue.table_name}.#{select_field}=j.id"
972 where = "#{Issue.table_name}.#{select_field}=j.id"
972
973
973 ActiveRecord::Base.connection.select_all("select s.id as status_id,
974 ActiveRecord::Base.connection.select_all("select s.id as status_id,
974 s.is_closed as closed,
975 s.is_closed as closed,
975 j.id as #{select_field},
976 j.id as #{select_field},
976 count(#{Issue.table_name}.id) as total
977 count(#{Issue.table_name}.id) as total
977 from
978 from
978 #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s, #{joins} j
979 #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s, #{joins} j
979 where
980 where
980 #{Issue.table_name}.status_id=s.id
981 #{Issue.table_name}.status_id=s.id
981 and #{where}
982 and #{where}
982 and #{Issue.table_name}.project_id=#{Project.table_name}.id
983 and #{Issue.table_name}.project_id=#{Project.table_name}.id
983 and #{visible_condition(User.current, :project => project)}
984 and #{visible_condition(User.current, :project => project)}
984 group by s.id, s.is_closed, j.id")
985 group by s.id, s.is_closed, j.id")
985 end
986 end
986 end
987 end
@@ -1,50 +1,69
1 <% labelled_fields_for :issue, @issue do |f| %>
1 <% labelled_fields_for :issue, @issue do |f| %>
2
2
3 <div class="splitcontentleft">
3 <div class="splitcontentleft">
4 <% if @issue.new_record? || @allowed_statuses.any? %>
4 <% if @issue.safe_attribute? 'status_id' %>
5 <p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), :required => true %></p>
5 <p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), :required => true %></p>
6 <% else %>
6 <% else %>
7 <p><label><%= l(:field_status) %></label> <%= h(@issue.status.name) %></p>
7 <p><label><%= l(:field_status) %></label> <%= h(@issue.status.name) %></p>
8 <% end %>
8 <% end %>
9
9
10 <% if @issue.safe_attribute? 'priority_id' %>
10 <p><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), {:required => true}, :disabled => !@issue.leaf? %></p>
11 <p><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), {:required => true}, :disabled => !@issue.leaf? %></p>
12 <% end %>
13
14 <% if @issue.safe_attribute? 'assigned_to_id' %>
11 <p><%= f.select :assigned_to_id, principals_options_for_select(@issue.assignable_users, @issue.assigned_to), :include_blank => true %></p>
15 <p><%= f.select :assigned_to_id, principals_options_for_select(@issue.assignable_users, @issue.assigned_to), :include_blank => true %></p>
12 <% unless @project.issue_categories.empty? %>
16 <% end %>
17
18 <% if @issue.safe_attribute?('category_id') && @project.issue_categories.any? %>
13 <p><%= f.select :category_id, (@project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
19 <p><%= f.select :category_id, (@project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
14 <%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
20 <%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
15 l(:label_issue_category_new),
21 l(:label_issue_category_new),
16 'issue_category[name]',
22 'issue_category[name]',
17 {:controller => 'issue_categories', :action => 'create', :project_id => @project},
23 {:controller => 'issue_categories', :action => 'create', :project_id => @project},
18 :title => l(:label_issue_category_new),
24 :title => l(:label_issue_category_new),
19 :tabindex => 199) if authorize_for('issue_categories', 'new') %></p>
25 :tabindex => 199) if authorize_for('issue_categories', 'new') %></p>
20 <% end %>
26 <% end %>
21 <% unless @issue.assignable_versions.empty? %>
27
28 <% if @issue.safe_attribute?('fixed_version_id') && @issue.assignable_versions.any? %>
22 <p><%= f.select :fixed_version_id, version_options_for_select(@issue.assignable_versions, @issue.fixed_version), :include_blank => true %>
29 <p><%= f.select :fixed_version_id, version_options_for_select(@issue.assignable_versions, @issue.fixed_version), :include_blank => true %>
23 <%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
30 <%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
24 l(:label_version_new),
31 l(:label_version_new),
25 'version[name]',
32 'version[name]',
26 {:controller => 'versions', :action => 'create', :project_id => @project},
33 {:controller => 'versions', :action => 'create', :project_id => @project},
27 :title => l(:label_version_new),
34 :title => l(:label_version_new),
28 :tabindex => 200) if authorize_for('versions', 'new') %>
35 :tabindex => 200) if authorize_for('versions', 'new') %>
29 </p>
36 </p>
30 <% end %>
37 <% end %>
31 </div>
38 </div>
32
39
33 <div class="splitcontentright">
40 <div class="splitcontentright">
34 <% if @issue.safe_attribute? 'parent_issue_id' %>
41 <% if @issue.safe_attribute? 'parent_issue_id' %>
35 <p id="parent_issue"><%= f.text_field :parent_issue_id, :size => 10 %></p>
42 <p id="parent_issue"><%= f.text_field :parent_issue_id, :size => 10 %></p>
36 <div id="parent_issue_candidates" class="autocomplete"></div>
43 <div id="parent_issue_candidates" class="autocomplete"></div>
37 <%= javascript_tag "observeParentIssueField('#{auto_complete_issues_path(:id => @issue, :project_id => @project) }')" %>
44 <%= javascript_tag "observeParentIssueField('#{auto_complete_issues_path(:id => @issue, :project_id => @project) }')" %>
38 <% end %>
45 <% end %>
46
47 <% if @issue.safe_attribute? 'start_date' %>
39 <p><%= f.text_field :start_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_start_date') if @issue.leaf? %></p>
48 <p><%= f.text_field :start_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_start_date') if @issue.leaf? %></p>
49 <% end %>
50
51 <% if @issue.safe_attribute? 'due_date' %>
40 <p><%= f.text_field :due_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_due_date') if @issue.leaf? %></p>
52 <p><%= f.text_field :due_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_due_date') if @issue.leaf? %></p>
53 <% end %>
54
55 <% if @issue.safe_attribute? 'estimated_hours' %>
41 <p><%= f.text_field :estimated_hours, :size => 3, :disabled => !@issue.leaf? %> <%= l(:field_hours) %></p>
56 <p><%= f.text_field :estimated_hours, :size => 3, :disabled => !@issue.leaf? %> <%= l(:field_hours) %></p>
42 <% if @issue.leaf? && Issue.use_field_for_done_ratio? %>
57 <% end %>
58
59 <% if @issue.safe_attribute?('done_ratio') && @issue.leaf? && Issue.use_field_for_done_ratio? %>
43 <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
60 <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
44 <% end %>
61 <% end %>
45 </div>
62 </div>
46
63
47 <div style="clear:both;"> </div>
64 <div style="clear:both;"> </div>
65 <% if @issue.safe_attribute? 'custom_field_values' %>
48 <%= render :partial => 'issues/form_custom_fields' %>
66 <%= render :partial => 'issues/form_custom_fields' %>
67 <% end %>
49
68
50 <% end %>
69 <% end %>
@@ -1,46 +1,46
1 <% labelled_form_for @issue, :html => {:id => 'issue-form', :multipart => true} do |f| %>
1 <% labelled_form_for @issue, :html => {:id => 'issue-form', :multipart => true} do |f| %>
2 <%= error_messages_for 'issue', 'time_entry' %>
2 <%= error_messages_for 'issue', 'time_entry' %>
3 <div class="box">
3 <div class="box">
4 <% if @edit_allowed || !@allowed_statuses.empty? %>
4 <% if @edit_allowed || !@allowed_statuses.empty? %>
5 <fieldset class="tabular"><legend><%= l(:label_change_properties) %></legend>
5 <fieldset class="tabular"><legend><%= l(:label_change_properties) %></legend>
6 <%= render :partial => (@edit_allowed ? 'form' : 'form_update'), :locals => {:f => f} %>
6 <%= render :partial => 'form', :locals => {:f => f} %>
7 </fieldset>
7 </fieldset>
8 <% end %>
8 <% end %>
9 <% if User.current.allowed_to?(:log_time, @project) %>
9 <% if User.current.allowed_to?(:log_time, @project) %>
10 <fieldset class="tabular"><legend><%= l(:button_log_time) %></legend>
10 <fieldset class="tabular"><legend><%= l(:button_log_time) %></legend>
11 <% labelled_fields_for :time_entry, @time_entry do |time_entry| %>
11 <% labelled_fields_for :time_entry, @time_entry do |time_entry| %>
12 <div class="splitcontentleft">
12 <div class="splitcontentleft">
13 <p><%= time_entry.text_field :hours, :size => 6, :label => :label_spent_time %> <%= l(:field_hours) %></p>
13 <p><%= time_entry.text_field :hours, :size => 6, :label => :label_spent_time %> <%= l(:field_hours) %></p>
14 </div>
14 </div>
15 <div class="splitcontentright">
15 <div class="splitcontentright">
16 <p><%= time_entry.select :activity_id, activity_collection_for_select_options %></p>
16 <p><%= time_entry.select :activity_id, activity_collection_for_select_options %></p>
17 </div>
17 </div>
18 <p><%= time_entry.text_field :comments, :size => 60 %></p>
18 <p><%= time_entry.text_field :comments, :size => 60 %></p>
19 <% @time_entry.custom_field_values.each do |value| %>
19 <% @time_entry.custom_field_values.each do |value| %>
20 <p><%= custom_field_tag_with_label :time_entry, value %></p>
20 <p><%= custom_field_tag_with_label :time_entry, value %></p>
21 <% end %>
21 <% end %>
22 <% end %>
22 <% end %>
23 </fieldset>
23 </fieldset>
24 <% end %>
24 <% end %>
25
25
26 <fieldset><legend><%= l(:field_notes) %></legend>
26 <fieldset><legend><%= l(:field_notes) %></legend>
27 <%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %>
27 <%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %>
28 <%= wikitoolbar_for 'notes' %>
28 <%= wikitoolbar_for 'notes' %>
29 <%= call_hook(:view_issues_edit_notes_bottom, { :issue => @issue, :notes => @notes, :form => f }) %>
29 <%= call_hook(:view_issues_edit_notes_bottom, { :issue => @issue, :notes => @notes, :form => f }) %>
30
30
31 <p><%=l(:label_attachment_plural)%><br /><%= render :partial => 'attachments/form' %></p>
31 <p><%=l(:label_attachment_plural)%><br /><%= render :partial => 'attachments/form' %></p>
32 </fieldset>
32 </fieldset>
33 </div>
33 </div>
34
34
35 <%= f.hidden_field :lock_version %>
35 <%= f.hidden_field :lock_version %>
36 <%= submit_tag l(:button_submit) %>
36 <%= submit_tag l(:button_submit) %>
37 <%= link_to_remote l(:label_preview),
37 <%= link_to_remote l(:label_preview),
38 { :url => preview_issue_path(:project_id => @project, :id => @issue),
38 { :url => preview_issue_path(:project_id => @project, :id => @issue),
39 :method => 'post',
39 :method => 'post',
40 :update => 'preview',
40 :update => 'preview',
41 :with => 'Form.serialize("issue-form")',
41 :with => 'Form.serialize("issue-form")',
42 :complete => "Element.scrollTo('preview')"
42 :complete => "Element.scrollTo('preview')"
43 }, :accesskey => accesskey(:preview) %>
43 }, :accesskey => accesskey(:preview) %>
44 <% end %>
44 <% end %>
45
45
46 <div id="preview" class="wiki"></div>
46 <div id="preview" class="wiki"></div>
@@ -1,34 +1,41
1 <%= call_hook(:view_issues_form_details_top, { :issue => @issue, :form => f }) %>
1 <%= call_hook(:view_issues_form_details_top, { :issue => @issue, :form => f }) %>
2
2
3 <% if @issue.safe_attribute? 'is_private' %>
3 <% if @issue.safe_attribute? 'is_private' %>
4 <p style="float:right; margin-right:1em;">
4 <p style="float:right; margin-right:1em;">
5 <label class="inline" for="issue_is_private" id="issue_is_private_label"><%= f.check_box :is_private, :no_label => true %> <%= l(:field_is_private) %></label>
5 <label class="inline" for="issue_is_private" id="issue_is_private_label"><%= f.check_box :is_private, :no_label => true %> <%= l(:field_is_private) %></label>
6 </p>
6 </p>
7 <% end %>
7 <% end %>
8
9 <% if @issue.safe_attribute? 'tracker_id' %>
8 <p><%= f.select :tracker_id, @project.trackers.collect {|t| [t.name, t.id]}, :required => true %></p>
10 <p><%= f.select :tracker_id, @project.trackers.collect {|t| [t.name, t.id]}, :required => true %></p>
9 <%= observe_field :issue_tracker_id, :url => project_issue_form_path(@project, :id => @issue),
11 <%= observe_field :issue_tracker_id, :url => project_issue_form_path(@project, :id => @issue),
10 :update => :attributes,
12 :update => :attributes,
11 :with => "Form.serialize('issue-form')" %>
13 :with => "Form.serialize('issue-form')" %>
14 <% end %>
12
15
16 <% if @issue.safe_attribute? 'subject' %>
13 <p><%= f.text_field :subject, :size => 80, :required => true %></p>
17 <p><%= f.text_field :subject, :size => 80, :required => true %></p>
18 <% end %>
19
20 <% if @issue.safe_attribute? 'description' %>
14 <p>
21 <p>
15 <label><%= l(:field_description) %></label>
22 <label><%= l(:field_description) %></label>
16 <%= link_to_function image_tag('edit.png'),
23 <%= link_to_function image_tag('edit.png'),
17 'Element.hide(this); Effect.toggle("issue_description_and_toolbar", "appear", {duration:0.3})' unless @issue.new_record? %>
24 'Element.hide(this); Effect.toggle("issue_description_and_toolbar", "appear", {duration:0.3})' unless @issue.new_record? %>
18 <% content_tag 'span', :id => "issue_description_and_toolbar", :style => (@issue.new_record? ? nil : 'display:none') do %>
25 <% content_tag 'span', :id => "issue_description_and_toolbar", :style => (@issue.new_record? ? nil : 'display:none') do %>
19 <%= f.text_area :description,
26 <%= f.text_area :description,
20 :cols => 60,
27 :cols => 60,
21 :rows => (@issue.description.blank? ? 10 : [[10, @issue.description.length / 50].max, 100].min),
28 :rows => (@issue.description.blank? ? 10 : [[10, @issue.description.length / 50].max, 100].min),
22 :accesskey => accesskey(:edit),
29 :accesskey => accesskey(:edit),
23 :class => 'wiki-edit',
30 :class => 'wiki-edit',
24 :no_label => true %>
31 :no_label => true %>
25 <% end %>
32 <% end %>
26 </p>
33 </p>
34 <%= wikitoolbar_for 'issue_description' %>
35 <% end %>
27
36
28 <div id="attributes" class="attributes">
37 <div id="attributes" class="attributes">
29 <%= render :partial => 'issues/attributes' %>
38 <%= render :partial => 'issues/attributes' %>
30 </div>
39 </div>
31
40
32 <%= call_hook(:view_issues_form_details_bottom, { :issue => @issue, :form => f }) %>
41 <%= call_hook(:view_issues_form_details_bottom, { :issue => @issue, :form => f }) %>
33
34 <%= wikitoolbar_for 'issue_description' %>
@@ -1,2065 +1,2191
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 require 'issues_controller'
19 require 'issues_controller'
20
20
21 class IssuesControllerTest < ActionController::TestCase
21 class IssuesControllerTest < ActionController::TestCase
22 fixtures :projects,
22 fixtures :projects,
23 :users,
23 :users,
24 :roles,
24 :roles,
25 :members,
25 :members,
26 :member_roles,
26 :member_roles,
27 :issues,
27 :issues,
28 :issue_statuses,
28 :issue_statuses,
29 :versions,
29 :versions,
30 :trackers,
30 :trackers,
31 :projects_trackers,
31 :projects_trackers,
32 :issue_categories,
32 :issue_categories,
33 :enabled_modules,
33 :enabled_modules,
34 :enumerations,
34 :enumerations,
35 :attachments,
35 :attachments,
36 :workflows,
36 :workflows,
37 :custom_fields,
37 :custom_fields,
38 :custom_values,
38 :custom_values,
39 :custom_fields_projects,
39 :custom_fields_projects,
40 :custom_fields_trackers,
40 :custom_fields_trackers,
41 :time_entries,
41 :time_entries,
42 :journals,
42 :journals,
43 :journal_details,
43 :journal_details,
44 :queries
44 :queries
45
45
46 include Redmine::I18n
46 include Redmine::I18n
47
47
48 def setup
48 def setup
49 @controller = IssuesController.new
49 @controller = IssuesController.new
50 @request = ActionController::TestRequest.new
50 @request = ActionController::TestRequest.new
51 @response = ActionController::TestResponse.new
51 @response = ActionController::TestResponse.new
52 User.current = nil
52 User.current = nil
53 end
53 end
54
54
55 def test_index
55 def test_index
56 Setting.default_language = 'en'
56 Setting.default_language = 'en'
57
57
58 get :index
58 get :index
59 assert_response :success
59 assert_response :success
60 assert_template 'index'
60 assert_template 'index'
61 assert_not_nil assigns(:issues)
61 assert_not_nil assigns(:issues)
62 assert_nil assigns(:project)
62 assert_nil assigns(:project)
63 assert_tag :tag => 'a', :content => /Can't print recipes/
63 assert_tag :tag => 'a', :content => /Can't print recipes/
64 assert_tag :tag => 'a', :content => /Subproject issue/
64 assert_tag :tag => 'a', :content => /Subproject issue/
65 # private projects hidden
65 # private projects hidden
66 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
66 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
67 assert_no_tag :tag => 'a', :content => /Issue on project 2/
67 assert_no_tag :tag => 'a', :content => /Issue on project 2/
68 # project column
68 # project column
69 assert_tag :tag => 'th', :content => /Project/
69 assert_tag :tag => 'th', :content => /Project/
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 assert_no_tag :tag => 'a', :content => /Can't print recipes/
79 assert_no_tag :tag => 'a', :content => /Can't print recipes/
80 assert_tag :tag => 'a', :content => /Subproject issue/
80 assert_tag :tag => 'a', :content => /Subproject issue/
81 end
81 end
82
82
83 def test_index_should_list_visible_issues_only
83 def test_index_should_list_visible_issues_only
84 get :index, :per_page => 100
84 get :index, :per_page => 100
85 assert_response :success
85 assert_response :success
86 assert_not_nil assigns(:issues)
86 assert_not_nil assigns(:issues)
87 assert_nil assigns(:issues).detect {|issue| !issue.visible?}
87 assert_nil assigns(:issues).detect {|issue| !issue.visible?}
88 end
88 end
89
89
90 def test_index_with_project
90 def test_index_with_project
91 Setting.display_subprojects_issues = 0
91 Setting.display_subprojects_issues = 0
92 get :index, :project_id => 1
92 get :index, :project_id => 1
93 assert_response :success
93 assert_response :success
94 assert_template 'index'
94 assert_template 'index'
95 assert_not_nil assigns(:issues)
95 assert_not_nil assigns(:issues)
96 assert_tag :tag => 'a', :content => /Can't print recipes/
96 assert_tag :tag => 'a', :content => /Can't print recipes/
97 assert_no_tag :tag => 'a', :content => /Subproject issue/
97 assert_no_tag :tag => 'a', :content => /Subproject issue/
98 end
98 end
99
99
100 def test_index_with_project_and_subprojects
100 def test_index_with_project_and_subprojects
101 Setting.display_subprojects_issues = 1
101 Setting.display_subprojects_issues = 1
102 get :index, :project_id => 1
102 get :index, :project_id => 1
103 assert_response :success
103 assert_response :success
104 assert_template 'index'
104 assert_template 'index'
105 assert_not_nil assigns(:issues)
105 assert_not_nil assigns(:issues)
106 assert_tag :tag => 'a', :content => /Can't print recipes/
106 assert_tag :tag => 'a', :content => /Can't print recipes/
107 assert_tag :tag => 'a', :content => /Subproject issue/
107 assert_tag :tag => 'a', :content => /Subproject issue/
108 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
108 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
109 end
109 end
110
110
111 def test_index_with_project_and_subprojects_should_show_private_subprojects
111 def test_index_with_project_and_subprojects_should_show_private_subprojects
112 @request.session[:user_id] = 2
112 @request.session[:user_id] = 2
113 Setting.display_subprojects_issues = 1
113 Setting.display_subprojects_issues = 1
114 get :index, :project_id => 1
114 get :index, :project_id => 1
115 assert_response :success
115 assert_response :success
116 assert_template 'index'
116 assert_template 'index'
117 assert_not_nil assigns(:issues)
117 assert_not_nil assigns(:issues)
118 assert_tag :tag => 'a', :content => /Can't print recipes/
118 assert_tag :tag => 'a', :content => /Can't print recipes/
119 assert_tag :tag => 'a', :content => /Subproject issue/
119 assert_tag :tag => 'a', :content => /Subproject issue/
120 assert_tag :tag => 'a', :content => /Issue of a private subproject/
120 assert_tag :tag => 'a', :content => /Issue of a private subproject/
121 end
121 end
122
122
123 def test_index_with_project_and_default_filter
123 def test_index_with_project_and_default_filter
124 get :index, :project_id => 1, :set_filter => 1
124 get :index, :project_id => 1, :set_filter => 1
125 assert_response :success
125 assert_response :success
126 assert_template 'index'
126 assert_template 'index'
127 assert_not_nil assigns(:issues)
127 assert_not_nil assigns(:issues)
128
128
129 query = assigns(:query)
129 query = assigns(:query)
130 assert_not_nil query
130 assert_not_nil query
131 # default filter
131 # default filter
132 assert_equal({'status_id' => {:operator => 'o', :values => ['']}}, query.filters)
132 assert_equal({'status_id' => {:operator => 'o', :values => ['']}}, query.filters)
133 end
133 end
134
134
135 def test_index_with_project_and_filter
135 def test_index_with_project_and_filter
136 get :index, :project_id => 1, :set_filter => 1,
136 get :index, :project_id => 1, :set_filter => 1,
137 :f => ['tracker_id'],
137 :f => ['tracker_id'],
138 :op => {'tracker_id' => '='},
138 :op => {'tracker_id' => '='},
139 :v => {'tracker_id' => ['1']}
139 :v => {'tracker_id' => ['1']}
140 assert_response :success
140 assert_response :success
141 assert_template 'index'
141 assert_template 'index'
142 assert_not_nil assigns(:issues)
142 assert_not_nil assigns(:issues)
143
143
144 query = assigns(:query)
144 query = assigns(:query)
145 assert_not_nil query
145 assert_not_nil query
146 assert_equal({'tracker_id' => {:operator => '=', :values => ['1']}}, query.filters)
146 assert_equal({'tracker_id' => {:operator => '=', :values => ['1']}}, query.filters)
147 end
147 end
148
148
149 def test_index_with_short_filters
149 def test_index_with_short_filters
150
150
151 to_test = {
151 to_test = {
152 'status_id' => {
152 'status_id' => {
153 'o' => { :op => 'o', :values => [''] },
153 'o' => { :op => 'o', :values => [''] },
154 'c' => { :op => 'c', :values => [''] },
154 'c' => { :op => 'c', :values => [''] },
155 '7' => { :op => '=', :values => ['7'] },
155 '7' => { :op => '=', :values => ['7'] },
156 '7|3|4' => { :op => '=', :values => ['7', '3', '4'] },
156 '7|3|4' => { :op => '=', :values => ['7', '3', '4'] },
157 '=7' => { :op => '=', :values => ['7'] },
157 '=7' => { :op => '=', :values => ['7'] },
158 '!3' => { :op => '!', :values => ['3'] },
158 '!3' => { :op => '!', :values => ['3'] },
159 '!7|3|4' => { :op => '!', :values => ['7', '3', '4'] }},
159 '!7|3|4' => { :op => '!', :values => ['7', '3', '4'] }},
160 'subject' => {
160 'subject' => {
161 'This is a subject' => { :op => '=', :values => ['This is a subject'] },
161 'This is a subject' => { :op => '=', :values => ['This is a subject'] },
162 'o' => { :op => '=', :values => ['o'] },
162 'o' => { :op => '=', :values => ['o'] },
163 '~This is part of a subject' => { :op => '~', :values => ['This is part of a subject'] },
163 '~This is part of a subject' => { :op => '~', :values => ['This is part of a subject'] },
164 '!~This is part of a subject' => { :op => '!~', :values => ['This is part of a subject'] }},
164 '!~This is part of a subject' => { :op => '!~', :values => ['This is part of a subject'] }},
165 'tracker_id' => {
165 'tracker_id' => {
166 '3' => { :op => '=', :values => ['3'] },
166 '3' => { :op => '=', :values => ['3'] },
167 '=3' => { :op => '=', :values => ['3'] }},
167 '=3' => { :op => '=', :values => ['3'] }},
168 'start_date' => {
168 'start_date' => {
169 '2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
169 '2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
170 '=2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
170 '=2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
171 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
171 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
172 '<=2011-10-12' => { :op => '<=', :values => ['2011-10-12'] },
172 '<=2011-10-12' => { :op => '<=', :values => ['2011-10-12'] },
173 '><2011-10-01|2011-10-30' => { :op => '><', :values => ['2011-10-01', '2011-10-30'] },
173 '><2011-10-01|2011-10-30' => { :op => '><', :values => ['2011-10-01', '2011-10-30'] },
174 '<t+2' => { :op => '<t+', :values => ['2'] },
174 '<t+2' => { :op => '<t+', :values => ['2'] },
175 '>t+2' => { :op => '>t+', :values => ['2'] },
175 '>t+2' => { :op => '>t+', :values => ['2'] },
176 't+2' => { :op => 't+', :values => ['2'] },
176 't+2' => { :op => 't+', :values => ['2'] },
177 't' => { :op => 't', :values => [''] },
177 't' => { :op => 't', :values => [''] },
178 'w' => { :op => 'w', :values => [''] },
178 'w' => { :op => 'w', :values => [''] },
179 '>t-2' => { :op => '>t-', :values => ['2'] },
179 '>t-2' => { :op => '>t-', :values => ['2'] },
180 '<t-2' => { :op => '<t-', :values => ['2'] },
180 '<t-2' => { :op => '<t-', :values => ['2'] },
181 't-2' => { :op => 't-', :values => ['2'] }},
181 't-2' => { :op => 't-', :values => ['2'] }},
182 'created_on' => {
182 'created_on' => {
183 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
183 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
184 '<t+2' => { :op => '=', :values => ['<t+2'] },
184 '<t+2' => { :op => '=', :values => ['<t+2'] },
185 '>t+2' => { :op => '=', :values => ['>t+2'] },
185 '>t+2' => { :op => '=', :values => ['>t+2'] },
186 't+2' => { :op => 't', :values => ['+2'] }},
186 't+2' => { :op => 't', :values => ['+2'] }},
187 'cf_1' => {
187 'cf_1' => {
188 'c' => { :op => '=', :values => ['c'] },
188 'c' => { :op => '=', :values => ['c'] },
189 '!c' => { :op => '!', :values => ['c'] },
189 '!c' => { :op => '!', :values => ['c'] },
190 '!*' => { :op => '!*', :values => [''] },
190 '!*' => { :op => '!*', :values => [''] },
191 '*' => { :op => '*', :values => [''] }},
191 '*' => { :op => '*', :values => [''] }},
192 'estimated_hours' => {
192 'estimated_hours' => {
193 '=13.4' => { :op => '=', :values => ['13.4'] },
193 '=13.4' => { :op => '=', :values => ['13.4'] },
194 '>=45' => { :op => '>=', :values => ['45'] },
194 '>=45' => { :op => '>=', :values => ['45'] },
195 '<=125' => { :op => '<=', :values => ['125'] },
195 '<=125' => { :op => '<=', :values => ['125'] },
196 '><10.5|20.5' => { :op => '><', :values => ['10.5', '20.5'] },
196 '><10.5|20.5' => { :op => '><', :values => ['10.5', '20.5'] },
197 '!*' => { :op => '!*', :values => [''] },
197 '!*' => { :op => '!*', :values => [''] },
198 '*' => { :op => '*', :values => [''] }}
198 '*' => { :op => '*', :values => [''] }}
199 }
199 }
200
200
201 default_filter = { 'status_id' => {:operator => 'o', :values => [''] }}
201 default_filter = { 'status_id' => {:operator => 'o', :values => [''] }}
202
202
203 to_test.each do |field, expression_and_expected|
203 to_test.each do |field, expression_and_expected|
204 expression_and_expected.each do |filter_expression, expected|
204 expression_and_expected.each do |filter_expression, expected|
205
205
206 get :index, :set_filter => 1, field => filter_expression
206 get :index, :set_filter => 1, field => filter_expression
207
207
208 assert_response :success
208 assert_response :success
209 assert_template 'index'
209 assert_template 'index'
210 assert_not_nil assigns(:issues)
210 assert_not_nil assigns(:issues)
211
211
212 query = assigns(:query)
212 query = assigns(:query)
213 assert_not_nil query
213 assert_not_nil query
214 assert query.has_filter?(field)
214 assert query.has_filter?(field)
215 assert_equal(default_filter.merge({field => {:operator => expected[:op], :values => expected[:values]}}), query.filters)
215 assert_equal(default_filter.merge({field => {:operator => expected[:op], :values => expected[:values]}}), query.filters)
216 end
216 end
217 end
217 end
218
218
219 end
219 end
220
220
221 def test_index_with_project_and_empty_filters
221 def test_index_with_project_and_empty_filters
222 get :index, :project_id => 1, :set_filter => 1, :fields => ['']
222 get :index, :project_id => 1, :set_filter => 1, :fields => ['']
223 assert_response :success
223 assert_response :success
224 assert_template 'index'
224 assert_template 'index'
225 assert_not_nil assigns(:issues)
225 assert_not_nil assigns(:issues)
226
226
227 query = assigns(:query)
227 query = assigns(:query)
228 assert_not_nil query
228 assert_not_nil query
229 # no filter
229 # no filter
230 assert_equal({}, query.filters)
230 assert_equal({}, query.filters)
231 end
231 end
232
232
233 def test_index_with_query
233 def test_index_with_query
234 get :index, :project_id => 1, :query_id => 5
234 get :index, :project_id => 1, :query_id => 5
235 assert_response :success
235 assert_response :success
236 assert_template 'index'
236 assert_template 'index'
237 assert_not_nil assigns(:issues)
237 assert_not_nil assigns(:issues)
238 assert_nil assigns(:issue_count_by_group)
238 assert_nil assigns(:issue_count_by_group)
239 end
239 end
240
240
241 def test_index_with_query_grouped_by_tracker
241 def test_index_with_query_grouped_by_tracker
242 get :index, :project_id => 1, :query_id => 6
242 get :index, :project_id => 1, :query_id => 6
243 assert_response :success
243 assert_response :success
244 assert_template 'index'
244 assert_template 'index'
245 assert_not_nil assigns(:issues)
245 assert_not_nil assigns(:issues)
246 assert_not_nil assigns(:issue_count_by_group)
246 assert_not_nil assigns(:issue_count_by_group)
247 end
247 end
248
248
249 def test_index_with_query_grouped_by_list_custom_field
249 def test_index_with_query_grouped_by_list_custom_field
250 get :index, :project_id => 1, :query_id => 9
250 get :index, :project_id => 1, :query_id => 9
251 assert_response :success
251 assert_response :success
252 assert_template 'index'
252 assert_template 'index'
253 assert_not_nil assigns(:issues)
253 assert_not_nil assigns(:issues)
254 assert_not_nil assigns(:issue_count_by_group)
254 assert_not_nil assigns(:issue_count_by_group)
255 end
255 end
256
256
257 def test_index_with_query_id_and_project_id_should_set_session_query
257 def test_index_with_query_id_and_project_id_should_set_session_query
258 get :index, :project_id => 1, :query_id => 4
258 get :index, :project_id => 1, :query_id => 4
259 assert_response :success
259 assert_response :success
260 assert_kind_of Hash, session[:query]
260 assert_kind_of Hash, session[:query]
261 assert_equal 4, session[:query][:id]
261 assert_equal 4, session[:query][:id]
262 assert_equal 1, session[:query][:project_id]
262 assert_equal 1, session[:query][:project_id]
263 end
263 end
264
264
265 def test_index_with_cross_project_query_in_session_should_show_project_issues
265 def test_index_with_cross_project_query_in_session_should_show_project_issues
266 q = Query.create!(:name => "test", :user_id => 2, :is_public => false, :project => nil)
266 q = Query.create!(:name => "test", :user_id => 2, :is_public => false, :project => nil)
267 @request.session[:query] = {:id => q.id, :project_id => 1}
267 @request.session[:query] = {:id => q.id, :project_id => 1}
268
268
269 with_settings :display_subprojects_issues => '0' do
269 with_settings :display_subprojects_issues => '0' do
270 get :index, :project_id => 1
270 get :index, :project_id => 1
271 end
271 end
272 assert_response :success
272 assert_response :success
273 assert_not_nil assigns(:query)
273 assert_not_nil assigns(:query)
274 assert_equal q.id, assigns(:query).id
274 assert_equal q.id, assigns(:query).id
275 assert_equal 1, assigns(:query).project_id
275 assert_equal 1, assigns(:query).project_id
276 assert_equal [1], assigns(:issues).map(&:project_id).uniq
276 assert_equal [1], assigns(:issues).map(&:project_id).uniq
277 end
277 end
278
278
279 def test_private_query_should_not_be_available_to_other_users
279 def test_private_query_should_not_be_available_to_other_users
280 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
280 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
281 @request.session[:user_id] = 3
281 @request.session[:user_id] = 3
282
282
283 get :index, :query_id => q.id
283 get :index, :query_id => q.id
284 assert_response 403
284 assert_response 403
285 end
285 end
286
286
287 def test_private_query_should_be_available_to_its_user
287 def test_private_query_should_be_available_to_its_user
288 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
288 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
289 @request.session[:user_id] = 2
289 @request.session[:user_id] = 2
290
290
291 get :index, :query_id => q.id
291 get :index, :query_id => q.id
292 assert_response :success
292 assert_response :success
293 end
293 end
294
294
295 def test_public_query_should_be_available_to_other_users
295 def test_public_query_should_be_available_to_other_users
296 q = Query.create!(:name => "private", :user => User.find(2), :is_public => true, :project => nil)
296 q = Query.create!(:name => "private", :user => User.find(2), :is_public => true, :project => nil)
297 @request.session[:user_id] = 3
297 @request.session[:user_id] = 3
298
298
299 get :index, :query_id => q.id
299 get :index, :query_id => q.id
300 assert_response :success
300 assert_response :success
301 end
301 end
302
302
303 def test_index_csv
303 def test_index_csv
304 get :index, :format => 'csv'
304 get :index, :format => 'csv'
305 assert_response :success
305 assert_response :success
306 assert_not_nil assigns(:issues)
306 assert_not_nil assigns(:issues)
307 assert_equal 'text/csv', @response.content_type
307 assert_equal 'text/csv', @response.content_type
308 assert @response.body.starts_with?("#,")
308 assert @response.body.starts_with?("#,")
309 lines = @response.body.chomp.split("\n")
309 lines = @response.body.chomp.split("\n")
310 assert_equal assigns(:query).columns.size + 1, lines[0].split(',').size
310 assert_equal assigns(:query).columns.size + 1, lines[0].split(',').size
311 end
311 end
312
312
313 def test_index_csv_with_project
313 def test_index_csv_with_project
314 get :index, :project_id => 1, :format => 'csv'
314 get :index, :project_id => 1, :format => 'csv'
315 assert_response :success
315 assert_response :success
316 assert_not_nil assigns(:issues)
316 assert_not_nil assigns(:issues)
317 assert_equal 'text/csv', @response.content_type
317 assert_equal 'text/csv', @response.content_type
318 end
318 end
319
319
320 def test_index_csv_with_description
320 def test_index_csv_with_description
321 get :index, :format => 'csv', :description => '1'
321 get :index, :format => 'csv', :description => '1'
322 assert_response :success
322 assert_response :success
323 assert_not_nil assigns(:issues)
323 assert_not_nil assigns(:issues)
324 assert_equal 'text/csv', @response.content_type
324 assert_equal 'text/csv', @response.content_type
325 assert @response.body.starts_with?("#,")
325 assert @response.body.starts_with?("#,")
326 lines = @response.body.chomp.split("\n")
326 lines = @response.body.chomp.split("\n")
327 assert_equal assigns(:query).columns.size + 2, lines[0].split(',').size
327 assert_equal assigns(:query).columns.size + 2, lines[0].split(',').size
328 end
328 end
329
329
330 def test_index_csv_with_all_columns
330 def test_index_csv_with_all_columns
331 get :index, :format => 'csv', :columns => 'all'
331 get :index, :format => 'csv', :columns => 'all'
332 assert_response :success
332 assert_response :success
333 assert_not_nil assigns(:issues)
333 assert_not_nil assigns(:issues)
334 assert_equal 'text/csv', @response.content_type
334 assert_equal 'text/csv', @response.content_type
335 assert @response.body.starts_with?("#,")
335 assert @response.body.starts_with?("#,")
336 lines = @response.body.chomp.split("\n")
336 lines = @response.body.chomp.split("\n")
337 assert_equal assigns(:query).available_columns.size + 1, lines[0].split(',').size
337 assert_equal assigns(:query).available_columns.size + 1, lines[0].split(',').size
338 end
338 end
339
339
340 def test_index_csv_big_5
340 def test_index_csv_big_5
341 with_settings :default_language => "zh-TW" do
341 with_settings :default_language => "zh-TW" do
342 str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88"
342 str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88"
343 str_big5 = "\xa4@\xa4\xeb"
343 str_big5 = "\xa4@\xa4\xeb"
344 if str_utf8.respond_to?(:force_encoding)
344 if str_utf8.respond_to?(:force_encoding)
345 str_utf8.force_encoding('UTF-8')
345 str_utf8.force_encoding('UTF-8')
346 str_big5.force_encoding('Big5')
346 str_big5.force_encoding('Big5')
347 end
347 end
348 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
348 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
349 :status_id => 1, :priority => IssuePriority.all.first,
349 :status_id => 1, :priority => IssuePriority.all.first,
350 :subject => str_utf8)
350 :subject => str_utf8)
351 assert issue.save
351 assert issue.save
352
352
353 get :index, :project_id => 1,
353 get :index, :project_id => 1,
354 :f => ['subject'],
354 :f => ['subject'],
355 :op => '=', :values => [str_utf8],
355 :op => '=', :values => [str_utf8],
356 :format => 'csv'
356 :format => 'csv'
357 assert_equal 'text/csv', @response.content_type
357 assert_equal 'text/csv', @response.content_type
358 lines = @response.body.chomp.split("\n")
358 lines = @response.body.chomp.split("\n")
359 s1 = "\xaa\xac\xbaA"
359 s1 = "\xaa\xac\xbaA"
360 if str_utf8.respond_to?(:force_encoding)
360 if str_utf8.respond_to?(:force_encoding)
361 s1.force_encoding('Big5')
361 s1.force_encoding('Big5')
362 end
362 end
363 assert lines[0].include?(s1)
363 assert lines[0].include?(s1)
364 assert lines[1].include?(str_big5)
364 assert lines[1].include?(str_big5)
365 end
365 end
366 end
366 end
367
367
368 def test_index_csv_cannot_convert_should_be_replaced_big_5
368 def test_index_csv_cannot_convert_should_be_replaced_big_5
369 with_settings :default_language => "zh-TW" do
369 with_settings :default_language => "zh-TW" do
370 str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85"
370 str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85"
371 if str_utf8.respond_to?(:force_encoding)
371 if str_utf8.respond_to?(:force_encoding)
372 str_utf8.force_encoding('UTF-8')
372 str_utf8.force_encoding('UTF-8')
373 end
373 end
374 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
374 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
375 :status_id => 1, :priority => IssuePriority.all.first,
375 :status_id => 1, :priority => IssuePriority.all.first,
376 :subject => str_utf8)
376 :subject => str_utf8)
377 assert issue.save
377 assert issue.save
378
378
379 get :index, :project_id => 1,
379 get :index, :project_id => 1,
380 :f => ['subject'],
380 :f => ['subject'],
381 :op => '=', :values => [str_utf8],
381 :op => '=', :values => [str_utf8],
382 :c => ['status', 'subject'],
382 :c => ['status', 'subject'],
383 :format => 'csv',
383 :format => 'csv',
384 :set_filter => 1
384 :set_filter => 1
385 assert_equal 'text/csv', @response.content_type
385 assert_equal 'text/csv', @response.content_type
386 lines = @response.body.chomp.split("\n")
386 lines = @response.body.chomp.split("\n")
387 s1 = "\xaa\xac\xbaA" # status
387 s1 = "\xaa\xac\xbaA" # status
388 if str_utf8.respond_to?(:force_encoding)
388 if str_utf8.respond_to?(:force_encoding)
389 s1.force_encoding('Big5')
389 s1.force_encoding('Big5')
390 end
390 end
391 assert lines[0].include?(s1)
391 assert lines[0].include?(s1)
392 s2 = lines[1].split(",")[2]
392 s2 = lines[1].split(",")[2]
393 if s1.respond_to?(:force_encoding)
393 if s1.respond_to?(:force_encoding)
394 s3 = "\xa5H?" # subject
394 s3 = "\xa5H?" # subject
395 s3.force_encoding('Big5')
395 s3.force_encoding('Big5')
396 assert_equal s3, s2
396 assert_equal s3, s2
397 elsif RUBY_PLATFORM == 'java'
397 elsif RUBY_PLATFORM == 'java'
398 assert_equal "??", s2
398 assert_equal "??", s2
399 else
399 else
400 assert_equal "\xa5H???", s2
400 assert_equal "\xa5H???", s2
401 end
401 end
402 end
402 end
403 end
403 end
404
404
405 def test_index_csv_tw
405 def test_index_csv_tw
406 with_settings :default_language => "zh-TW" do
406 with_settings :default_language => "zh-TW" do
407 str1 = "test_index_csv_tw"
407 str1 = "test_index_csv_tw"
408 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
408 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
409 :status_id => 1, :priority => IssuePriority.all.first,
409 :status_id => 1, :priority => IssuePriority.all.first,
410 :subject => str1, :estimated_hours => '1234.5')
410 :subject => str1, :estimated_hours => '1234.5')
411 assert issue.save
411 assert issue.save
412 assert_equal 1234.5, issue.estimated_hours
412 assert_equal 1234.5, issue.estimated_hours
413
413
414 get :index, :project_id => 1,
414 get :index, :project_id => 1,
415 :f => ['subject'],
415 :f => ['subject'],
416 :op => '=', :values => [str1],
416 :op => '=', :values => [str1],
417 :c => ['estimated_hours', 'subject'],
417 :c => ['estimated_hours', 'subject'],
418 :format => 'csv',
418 :format => 'csv',
419 :set_filter => 1
419 :set_filter => 1
420 assert_equal 'text/csv', @response.content_type
420 assert_equal 'text/csv', @response.content_type
421 lines = @response.body.chomp.split("\n")
421 lines = @response.body.chomp.split("\n")
422 assert_equal "#{issue.id},1234.5,#{str1}", lines[1]
422 assert_equal "#{issue.id},1234.5,#{str1}", lines[1]
423
423
424 str_tw = "Traditional Chinese (\xe7\xb9\x81\xe9\xab\x94\xe4\xb8\xad\xe6\x96\x87)"
424 str_tw = "Traditional Chinese (\xe7\xb9\x81\xe9\xab\x94\xe4\xb8\xad\xe6\x96\x87)"
425 if str_tw.respond_to?(:force_encoding)
425 if str_tw.respond_to?(:force_encoding)
426 str_tw.force_encoding('UTF-8')
426 str_tw.force_encoding('UTF-8')
427 end
427 end
428 assert_equal str_tw, l(:general_lang_name)
428 assert_equal str_tw, l(:general_lang_name)
429 assert_equal ',', l(:general_csv_separator)
429 assert_equal ',', l(:general_csv_separator)
430 assert_equal '.', l(:general_csv_decimal_separator)
430 assert_equal '.', l(:general_csv_decimal_separator)
431 end
431 end
432 end
432 end
433
433
434 def test_index_csv_fr
434 def test_index_csv_fr
435 with_settings :default_language => "fr" do
435 with_settings :default_language => "fr" do
436 str1 = "test_index_csv_fr"
436 str1 = "test_index_csv_fr"
437 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
437 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
438 :status_id => 1, :priority => IssuePriority.all.first,
438 :status_id => 1, :priority => IssuePriority.all.first,
439 :subject => str1, :estimated_hours => '1234.5')
439 :subject => str1, :estimated_hours => '1234.5')
440 assert issue.save
440 assert issue.save
441 assert_equal 1234.5, issue.estimated_hours
441 assert_equal 1234.5, issue.estimated_hours
442
442
443 get :index, :project_id => 1,
443 get :index, :project_id => 1,
444 :f => ['subject'],
444 :f => ['subject'],
445 :op => '=', :values => [str1],
445 :op => '=', :values => [str1],
446 :c => ['estimated_hours', 'subject'],
446 :c => ['estimated_hours', 'subject'],
447 :format => 'csv',
447 :format => 'csv',
448 :set_filter => 1
448 :set_filter => 1
449 assert_equal 'text/csv', @response.content_type
449 assert_equal 'text/csv', @response.content_type
450 lines = @response.body.chomp.split("\n")
450 lines = @response.body.chomp.split("\n")
451 assert_equal "#{issue.id};1234,5;#{str1}", lines[1]
451 assert_equal "#{issue.id};1234,5;#{str1}", lines[1]
452
452
453 str_fr = "Fran\xc3\xa7ais"
453 str_fr = "Fran\xc3\xa7ais"
454 if str_fr.respond_to?(:force_encoding)
454 if str_fr.respond_to?(:force_encoding)
455 str_fr.force_encoding('UTF-8')
455 str_fr.force_encoding('UTF-8')
456 end
456 end
457 assert_equal str_fr, l(:general_lang_name)
457 assert_equal str_fr, l(:general_lang_name)
458 assert_equal ';', l(:general_csv_separator)
458 assert_equal ';', l(:general_csv_separator)
459 assert_equal ',', l(:general_csv_decimal_separator)
459 assert_equal ',', l(:general_csv_decimal_separator)
460 end
460 end
461 end
461 end
462
462
463 def test_index_pdf
463 def test_index_pdf
464 ["en", "zh", "zh-TW", "ja", "ko"].each do |lang|
464 ["en", "zh", "zh-TW", "ja", "ko"].each do |lang|
465 with_settings :default_language => lang do
465 with_settings :default_language => lang do
466
466
467 get :index
467 get :index
468 assert_response :success
468 assert_response :success
469 assert_template 'index'
469 assert_template 'index'
470
470
471 if lang == "ja"
471 if lang == "ja"
472 if RUBY_PLATFORM != 'java'
472 if RUBY_PLATFORM != 'java'
473 assert_equal "CP932", l(:general_pdf_encoding)
473 assert_equal "CP932", l(:general_pdf_encoding)
474 end
474 end
475 if RUBY_PLATFORM == 'java' && l(:general_pdf_encoding) == "CP932"
475 if RUBY_PLATFORM == 'java' && l(:general_pdf_encoding) == "CP932"
476 next
476 next
477 end
477 end
478 end
478 end
479
479
480 get :index, :format => 'pdf'
480 get :index, :format => 'pdf'
481 assert_response :success
481 assert_response :success
482 assert_not_nil assigns(:issues)
482 assert_not_nil assigns(:issues)
483 assert_equal 'application/pdf', @response.content_type
483 assert_equal 'application/pdf', @response.content_type
484
484
485 get :index, :project_id => 1, :format => 'pdf'
485 get :index, :project_id => 1, :format => 'pdf'
486 assert_response :success
486 assert_response :success
487 assert_not_nil assigns(:issues)
487 assert_not_nil assigns(:issues)
488 assert_equal 'application/pdf', @response.content_type
488 assert_equal 'application/pdf', @response.content_type
489
489
490 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
490 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
491 assert_response :success
491 assert_response :success
492 assert_not_nil assigns(:issues)
492 assert_not_nil assigns(:issues)
493 assert_equal 'application/pdf', @response.content_type
493 assert_equal 'application/pdf', @response.content_type
494 end
494 end
495 end
495 end
496 end
496 end
497
497
498 def test_index_pdf_with_query_grouped_by_list_custom_field
498 def test_index_pdf_with_query_grouped_by_list_custom_field
499 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
499 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
500 assert_response :success
500 assert_response :success
501 assert_not_nil assigns(:issues)
501 assert_not_nil assigns(:issues)
502 assert_not_nil assigns(:issue_count_by_group)
502 assert_not_nil assigns(:issue_count_by_group)
503 assert_equal 'application/pdf', @response.content_type
503 assert_equal 'application/pdf', @response.content_type
504 end
504 end
505
505
506 def test_index_sort
506 def test_index_sort
507 get :index, :sort => 'tracker,id:desc'
507 get :index, :sort => 'tracker,id:desc'
508 assert_response :success
508 assert_response :success
509
509
510 sort_params = @request.session['issues_index_sort']
510 sort_params = @request.session['issues_index_sort']
511 assert sort_params.is_a?(String)
511 assert sort_params.is_a?(String)
512 assert_equal 'tracker,id:desc', sort_params
512 assert_equal 'tracker,id:desc', sort_params
513
513
514 issues = assigns(:issues)
514 issues = assigns(:issues)
515 assert_not_nil issues
515 assert_not_nil issues
516 assert !issues.empty?
516 assert !issues.empty?
517 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
517 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
518 end
518 end
519
519
520 def test_index_sort_by_field_not_included_in_columns
520 def test_index_sort_by_field_not_included_in_columns
521 Setting.issue_list_default_columns = %w(subject author)
521 Setting.issue_list_default_columns = %w(subject author)
522 get :index, :sort => 'tracker'
522 get :index, :sort => 'tracker'
523 end
523 end
524
524
525 def test_index_sort_by_assigned_to
525 def test_index_sort_by_assigned_to
526 get :index, :sort => 'assigned_to'
526 get :index, :sort => 'assigned_to'
527 assert_response :success
527 assert_response :success
528 assignees = assigns(:issues).collect(&:assigned_to).compact
528 assignees = assigns(:issues).collect(&:assigned_to).compact
529 assert_equal assignees.sort, assignees
529 assert_equal assignees.sort, assignees
530 end
530 end
531
531
532 def test_index_sort_by_assigned_to_desc
532 def test_index_sort_by_assigned_to_desc
533 get :index, :sort => 'assigned_to:desc'
533 get :index, :sort => 'assigned_to:desc'
534 assert_response :success
534 assert_response :success
535 assignees = assigns(:issues).collect(&:assigned_to).compact
535 assignees = assigns(:issues).collect(&:assigned_to).compact
536 assert_equal assignees.sort.reverse, assignees
536 assert_equal assignees.sort.reverse, assignees
537 end
537 end
538
538
539 def test_index_group_by_assigned_to
539 def test_index_group_by_assigned_to
540 get :index, :group_by => 'assigned_to', :sort => 'priority'
540 get :index, :group_by => 'assigned_to', :sort => 'priority'
541 assert_response :success
541 assert_response :success
542 end
542 end
543
543
544 def test_index_sort_by_author
544 def test_index_sort_by_author
545 get :index, :sort => 'author'
545 get :index, :sort => 'author'
546 assert_response :success
546 assert_response :success
547 authors = assigns(:issues).collect(&:author)
547 authors = assigns(:issues).collect(&:author)
548 assert_equal authors.sort, authors
548 assert_equal authors.sort, authors
549 end
549 end
550
550
551 def test_index_sort_by_author_desc
551 def test_index_sort_by_author_desc
552 get :index, :sort => 'author:desc'
552 get :index, :sort => 'author:desc'
553 assert_response :success
553 assert_response :success
554 authors = assigns(:issues).collect(&:author)
554 authors = assigns(:issues).collect(&:author)
555 assert_equal authors.sort.reverse, authors
555 assert_equal authors.sort.reverse, authors
556 end
556 end
557
557
558 def test_index_group_by_author
558 def test_index_group_by_author
559 get :index, :group_by => 'author', :sort => 'priority'
559 get :index, :group_by => 'author', :sort => 'priority'
560 assert_response :success
560 assert_response :success
561 end
561 end
562
562
563 def test_index_sort_by_spent_hours
563 def test_index_sort_by_spent_hours
564 get :index, :sort => 'spent_hours:desc'
564 get :index, :sort => 'spent_hours:desc'
565 assert_response :success
565 assert_response :success
566 hours = assigns(:issues).collect(&:spent_hours)
566 hours = assigns(:issues).collect(&:spent_hours)
567 assert_equal hours.sort.reverse, hours
567 assert_equal hours.sort.reverse, hours
568 end
568 end
569
569
570 def test_index_with_columns
570 def test_index_with_columns
571 columns = ['tracker', 'subject', 'assigned_to']
571 columns = ['tracker', 'subject', 'assigned_to']
572 get :index, :set_filter => 1, :c => columns
572 get :index, :set_filter => 1, :c => columns
573 assert_response :success
573 assert_response :success
574
574
575 # query should use specified columns
575 # query should use specified columns
576 query = assigns(:query)
576 query = assigns(:query)
577 assert_kind_of Query, query
577 assert_kind_of Query, query
578 assert_equal columns, query.column_names.map(&:to_s)
578 assert_equal columns, query.column_names.map(&:to_s)
579
579
580 # columns should be stored in session
580 # columns should be stored in session
581 assert_kind_of Hash, session[:query]
581 assert_kind_of Hash, session[:query]
582 assert_kind_of Array, session[:query][:column_names]
582 assert_kind_of Array, session[:query][:column_names]
583 assert_equal columns, session[:query][:column_names].map(&:to_s)
583 assert_equal columns, session[:query][:column_names].map(&:to_s)
584
584
585 # ensure only these columns are kept in the selected columns list
585 # ensure only these columns are kept in the selected columns list
586 assert_tag :tag => 'select', :attributes => { :id => 'selected_columns' },
586 assert_tag :tag => 'select', :attributes => { :id => 'selected_columns' },
587 :children => { :count => 3 }
587 :children => { :count => 3 }
588 assert_no_tag :tag => 'option', :attributes => { :value => 'project' },
588 assert_no_tag :tag => 'option', :attributes => { :value => 'project' },
589 :parent => { :tag => 'select', :attributes => { :id => "selected_columns" } }
589 :parent => { :tag => 'select', :attributes => { :id => "selected_columns" } }
590 end
590 end
591
591
592 def test_index_without_project_should_implicitly_add_project_column_to_default_columns
592 def test_index_without_project_should_implicitly_add_project_column_to_default_columns
593 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
593 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
594 get :index, :set_filter => 1
594 get :index, :set_filter => 1
595
595
596 # query should use specified columns
596 # query should use specified columns
597 query = assigns(:query)
597 query = assigns(:query)
598 assert_kind_of Query, query
598 assert_kind_of Query, query
599 assert_equal [:project, :tracker, :subject, :assigned_to], query.columns.map(&:name)
599 assert_equal [:project, :tracker, :subject, :assigned_to], query.columns.map(&:name)
600 end
600 end
601
601
602 def test_index_without_project_and_explicit_default_columns_should_not_add_project_column
602 def test_index_without_project_and_explicit_default_columns_should_not_add_project_column
603 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
603 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
604 columns = ['tracker', 'subject', 'assigned_to']
604 columns = ['tracker', 'subject', 'assigned_to']
605 get :index, :set_filter => 1, :c => columns
605 get :index, :set_filter => 1, :c => columns
606
606
607 # query should use specified columns
607 # query should use specified columns
608 query = assigns(:query)
608 query = assigns(:query)
609 assert_kind_of Query, query
609 assert_kind_of Query, query
610 assert_equal columns.map(&:to_sym), query.columns.map(&:name)
610 assert_equal columns.map(&:to_sym), query.columns.map(&:name)
611 end
611 end
612
612
613 def test_index_with_custom_field_column
613 def test_index_with_custom_field_column
614 columns = %w(tracker subject cf_2)
614 columns = %w(tracker subject cf_2)
615 get :index, :set_filter => 1, :c => columns
615 get :index, :set_filter => 1, :c => columns
616 assert_response :success
616 assert_response :success
617
617
618 # query should use specified columns
618 # query should use specified columns
619 query = assigns(:query)
619 query = assigns(:query)
620 assert_kind_of Query, query
620 assert_kind_of Query, query
621 assert_equal columns, query.column_names.map(&:to_s)
621 assert_equal columns, query.column_names.map(&:to_s)
622
622
623 assert_tag :td,
623 assert_tag :td,
624 :attributes => {:class => 'cf_2 string'},
624 :attributes => {:class => 'cf_2 string'},
625 :ancestor => {:tag => 'table', :attributes => {:class => /issues/}}
625 :ancestor => {:tag => 'table', :attributes => {:class => /issues/}}
626 end
626 end
627
627
628 def test_index_with_date_column
628 def test_index_with_date_column
629 Issue.find(1).update_attribute :start_date, '1987-08-24'
629 Issue.find(1).update_attribute :start_date, '1987-08-24'
630
630
631 with_settings :date_format => '%d/%m/%Y' do
631 with_settings :date_format => '%d/%m/%Y' do
632 get :index, :set_filter => 1, :c => %w(start_date)
632 get :index, :set_filter => 1, :c => %w(start_date)
633 assert_tag 'td', :attributes => {:class => /start_date/}, :content => '24/08/1987'
633 assert_tag 'td', :attributes => {:class => /start_date/}, :content => '24/08/1987'
634 end
634 end
635 end
635 end
636
636
637 def test_index_with_done_ratio
637 def test_index_with_done_ratio
638 Issue.find(1).update_attribute :done_ratio, 40
638 Issue.find(1).update_attribute :done_ratio, 40
639
639
640 get :index, :set_filter => 1, :c => %w(done_ratio)
640 get :index, :set_filter => 1, :c => %w(done_ratio)
641 assert_tag 'td', :attributes => {:class => /done_ratio/},
641 assert_tag 'td', :attributes => {:class => /done_ratio/},
642 :child => {:tag => 'table', :attributes => {:class => 'progress'},
642 :child => {:tag => 'table', :attributes => {:class => 'progress'},
643 :descendant => {:tag => 'td', :attributes => {:class => 'closed', :style => 'width: 40%;'}}
643 :descendant => {:tag => 'td', :attributes => {:class => 'closed', :style => 'width: 40%;'}}
644 }
644 }
645 end
645 end
646
646
647 def test_index_with_spent_hours_column
647 def test_index_with_spent_hours_column
648 get :index, :set_filter => 1, :c => %w(subject spent_hours)
648 get :index, :set_filter => 1, :c => %w(subject spent_hours)
649
649
650 assert_tag 'tr', :attributes => {:id => 'issue-3'},
650 assert_tag 'tr', :attributes => {:id => 'issue-3'},
651 :child => {
651 :child => {
652 :tag => 'td', :attributes => {:class => /spent_hours/}, :content => '1.00'
652 :tag => 'td', :attributes => {:class => /spent_hours/}, :content => '1.00'
653 }
653 }
654 end
654 end
655
655
656 def test_index_should_not_show_spent_hours_column_without_permission
656 def test_index_should_not_show_spent_hours_column_without_permission
657 Role.anonymous.remove_permission! :view_time_entries
657 Role.anonymous.remove_permission! :view_time_entries
658 get :index, :set_filter => 1, :c => %w(subject spent_hours)
658 get :index, :set_filter => 1, :c => %w(subject spent_hours)
659
659
660 assert_no_tag 'td', :attributes => {:class => /spent_hours/}
660 assert_no_tag 'td', :attributes => {:class => /spent_hours/}
661 end
661 end
662
662
663 def test_index_with_fixed_version
663 def test_index_with_fixed_version
664 get :index, :set_filter => 1, :c => %w(fixed_version)
664 get :index, :set_filter => 1, :c => %w(fixed_version)
665 assert_tag 'td', :attributes => {:class => /fixed_version/},
665 assert_tag 'td', :attributes => {:class => /fixed_version/},
666 :child => {:tag => 'a', :content => '1.0', :attributes => {:href => '/versions/2'}}
666 :child => {:tag => 'a', :content => '1.0', :attributes => {:href => '/versions/2'}}
667 end
667 end
668
668
669 def test_index_send_html_if_query_is_invalid
669 def test_index_send_html_if_query_is_invalid
670 get :index, :f => ['start_date'], :op => {:start_date => '='}
670 get :index, :f => ['start_date'], :op => {:start_date => '='}
671 assert_equal 'text/html', @response.content_type
671 assert_equal 'text/html', @response.content_type
672 assert_template 'index'
672 assert_template 'index'
673 end
673 end
674
674
675 def test_index_send_nothing_if_query_is_invalid
675 def test_index_send_nothing_if_query_is_invalid
676 get :index, :f => ['start_date'], :op => {:start_date => '='}, :format => 'csv'
676 get :index, :f => ['start_date'], :op => {:start_date => '='}, :format => 'csv'
677 assert_equal 'text/csv', @response.content_type
677 assert_equal 'text/csv', @response.content_type
678 assert @response.body.blank?
678 assert @response.body.blank?
679 end
679 end
680
680
681 def test_show_by_anonymous
681 def test_show_by_anonymous
682 get :show, :id => 1
682 get :show, :id => 1
683 assert_response :success
683 assert_response :success
684 assert_template 'show'
684 assert_template 'show'
685 assert_not_nil assigns(:issue)
685 assert_not_nil assigns(:issue)
686 assert_equal Issue.find(1), assigns(:issue)
686 assert_equal Issue.find(1), assigns(:issue)
687
687
688 # anonymous role is allowed to add a note
688 # anonymous role is allowed to add a note
689 assert_tag :tag => 'form',
689 assert_tag :tag => 'form',
690 :descendant => { :tag => 'fieldset',
690 :descendant => { :tag => 'fieldset',
691 :child => { :tag => 'legend',
691 :child => { :tag => 'legend',
692 :content => /Notes/ } }
692 :content => /Notes/ } }
693 assert_tag :tag => 'title',
693 assert_tag :tag => 'title',
694 :content => "Bug #1: Can't print recipes - eCookbook - Redmine"
694 :content => "Bug #1: Can't print recipes - eCookbook - Redmine"
695 end
695 end
696
696
697 def test_show_by_manager
697 def test_show_by_manager
698 @request.session[:user_id] = 2
698 @request.session[:user_id] = 2
699 get :show, :id => 1
699 get :show, :id => 1
700 assert_response :success
700 assert_response :success
701
701
702 assert_tag :tag => 'a',
702 assert_tag :tag => 'a',
703 :content => /Quote/
703 :content => /Quote/
704
704
705 assert_tag :tag => 'form',
705 assert_tag :tag => 'form',
706 :descendant => { :tag => 'fieldset',
706 :descendant => { :tag => 'fieldset',
707 :child => { :tag => 'legend',
707 :child => { :tag => 'legend',
708 :content => /Change properties/ } },
708 :content => /Change properties/ } },
709 :descendant => { :tag => 'fieldset',
709 :descendant => { :tag => 'fieldset',
710 :child => { :tag => 'legend',
710 :child => { :tag => 'legend',
711 :content => /Log time/ } },
711 :content => /Log time/ } },
712 :descendant => { :tag => 'fieldset',
712 :descendant => { :tag => 'fieldset',
713 :child => { :tag => 'legend',
713 :child => { :tag => 'legend',
714 :content => /Notes/ } }
714 :content => /Notes/ } }
715 end
715 end
716
716
717 def test_show_should_display_update_form
718 @request.session[:user_id] = 2
719 get :show, :id => 1
720 assert_response :success
721
722 assert_tag 'form', :attributes => {:id => 'issue-form'}
723 assert_tag 'input', :attributes => {:name => 'issue[is_private]'}
724 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
725 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
726 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
727 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
728 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
729 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
730 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
731 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
732 assert_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
733 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
734 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
735 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
736 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
737 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
738 assert_tag 'textarea', :attributes => {:name => 'notes'}
739 end
740
741 def test_show_should_display_update_form_with_minimal_permissions
742 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
743 Workflow.delete_all :role_id => 1
744
745 @request.session[:user_id] = 2
746 get :show, :id => 1
747 assert_response :success
748
749 assert_tag 'form', :attributes => {:id => 'issue-form'}
750 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
751 assert_no_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
752 assert_no_tag 'input', :attributes => {:name => 'issue[subject]'}
753 assert_no_tag 'textarea', :attributes => {:name => 'issue[description]'}
754 assert_no_tag 'select', :attributes => {:name => 'issue[status_id]'}
755 assert_no_tag 'select', :attributes => {:name => 'issue[priority_id]'}
756 assert_no_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
757 assert_no_tag 'select', :attributes => {:name => 'issue[category_id]'}
758 assert_no_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
759 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
760 assert_no_tag 'input', :attributes => {:name => 'issue[start_date]'}
761 assert_no_tag 'input', :attributes => {:name => 'issue[due_date]'}
762 assert_no_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
763 assert_no_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
764 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
765 assert_tag 'textarea', :attributes => {:name => 'notes'}
766 end
767
768 def test_show_should_display_update_form_with_workflow_permissions
769 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
770
771 @request.session[:user_id] = 2
772 get :show, :id => 1
773 assert_response :success
774
775 assert_tag 'form', :attributes => {:id => 'issue-form'}
776 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
777 assert_no_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
778 assert_no_tag 'input', :attributes => {:name => 'issue[subject]'}
779 assert_no_tag 'textarea', :attributes => {:name => 'issue[description]'}
780 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
781 assert_no_tag 'select', :attributes => {:name => 'issue[priority_id]'}
782 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
783 assert_no_tag 'select', :attributes => {:name => 'issue[category_id]'}
784 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
785 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
786 assert_no_tag 'input', :attributes => {:name => 'issue[start_date]'}
787 assert_no_tag 'input', :attributes => {:name => 'issue[due_date]'}
788 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
789 assert_no_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
790 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
791 assert_tag 'textarea', :attributes => {:name => 'notes'}
792 end
793
794 def test_show_should_not_display_update_form_without_permissions
795 Role.find(1).update_attribute :permissions, [:view_issues]
796
797 @request.session[:user_id] = 2
798 get :show, :id => 1
799 assert_response :success
800
801 assert_no_tag 'form', :attributes => {:id => 'issue-form'}
802 end
803
717 def test_update_form_should_not_display_inactive_enumerations
804 def test_update_form_should_not_display_inactive_enumerations
718 @request.session[:user_id] = 2
805 @request.session[:user_id] = 2
719 get :show, :id => 1
806 get :show, :id => 1
720 assert_response :success
807 assert_response :success
721
808
722 assert ! IssuePriority.find(15).active?
809 assert ! IssuePriority.find(15).active?
723 assert_no_tag :option, :attributes => {:value => '15'},
810 assert_no_tag :option, :attributes => {:value => '15'},
724 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
811 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
725 end
812 end
726
813
727 def test_update_form_should_allow_attachment_upload
814 def test_update_form_should_allow_attachment_upload
728 @request.session[:user_id] = 2
815 @request.session[:user_id] = 2
729 get :show, :id => 1
816 get :show, :id => 1
730
817
731 assert_tag :tag => 'form',
818 assert_tag :tag => 'form',
732 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
819 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
733 :descendant => {
820 :descendant => {
734 :tag => 'input',
821 :tag => 'input',
735 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
822 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
736 }
823 }
737 end
824 end
738
825
739 def test_show_should_deny_anonymous_access_without_permission
826 def test_show_should_deny_anonymous_access_without_permission
740 Role.anonymous.remove_permission!(:view_issues)
827 Role.anonymous.remove_permission!(:view_issues)
741 get :show, :id => 1
828 get :show, :id => 1
742 assert_response :redirect
829 assert_response :redirect
743 end
830 end
744
831
745 def test_show_should_deny_anonymous_access_to_private_issue
832 def test_show_should_deny_anonymous_access_to_private_issue
746 Issue.update_all(["is_private = ?", true], "id = 1")
833 Issue.update_all(["is_private = ?", true], "id = 1")
747 get :show, :id => 1
834 get :show, :id => 1
748 assert_response :redirect
835 assert_response :redirect
749 end
836 end
750
837
751 def test_show_should_deny_non_member_access_without_permission
838 def test_show_should_deny_non_member_access_without_permission
752 Role.non_member.remove_permission!(:view_issues)
839 Role.non_member.remove_permission!(:view_issues)
753 @request.session[:user_id] = 9
840 @request.session[:user_id] = 9
754 get :show, :id => 1
841 get :show, :id => 1
755 assert_response 403
842 assert_response 403
756 end
843 end
757
844
758 def test_show_should_deny_non_member_access_to_private_issue
845 def test_show_should_deny_non_member_access_to_private_issue
759 Issue.update_all(["is_private = ?", true], "id = 1")
846 Issue.update_all(["is_private = ?", true], "id = 1")
760 @request.session[:user_id] = 9
847 @request.session[:user_id] = 9
761 get :show, :id => 1
848 get :show, :id => 1
762 assert_response 403
849 assert_response 403
763 end
850 end
764
851
765 def test_show_should_deny_member_access_without_permission
852 def test_show_should_deny_member_access_without_permission
766 Role.find(1).remove_permission!(:view_issues)
853 Role.find(1).remove_permission!(:view_issues)
767 @request.session[:user_id] = 2
854 @request.session[:user_id] = 2
768 get :show, :id => 1
855 get :show, :id => 1
769 assert_response 403
856 assert_response 403
770 end
857 end
771
858
772 def test_show_should_deny_member_access_to_private_issue_without_permission
859 def test_show_should_deny_member_access_to_private_issue_without_permission
773 Issue.update_all(["is_private = ?", true], "id = 1")
860 Issue.update_all(["is_private = ?", true], "id = 1")
774 @request.session[:user_id] = 3
861 @request.session[:user_id] = 3
775 get :show, :id => 1
862 get :show, :id => 1
776 assert_response 403
863 assert_response 403
777 end
864 end
778
865
779 def test_show_should_allow_author_access_to_private_issue
866 def test_show_should_allow_author_access_to_private_issue
780 Issue.update_all(["is_private = ?, author_id = 3", true], "id = 1")
867 Issue.update_all(["is_private = ?, author_id = 3", true], "id = 1")
781 @request.session[:user_id] = 3
868 @request.session[:user_id] = 3
782 get :show, :id => 1
869 get :show, :id => 1
783 assert_response :success
870 assert_response :success
784 end
871 end
785
872
786 def test_show_should_allow_assignee_access_to_private_issue
873 def test_show_should_allow_assignee_access_to_private_issue
787 Issue.update_all(["is_private = ?, assigned_to_id = 3", true], "id = 1")
874 Issue.update_all(["is_private = ?, assigned_to_id = 3", true], "id = 1")
788 @request.session[:user_id] = 3
875 @request.session[:user_id] = 3
789 get :show, :id => 1
876 get :show, :id => 1
790 assert_response :success
877 assert_response :success
791 end
878 end
792
879
793 def test_show_should_allow_member_access_to_private_issue_with_permission
880 def test_show_should_allow_member_access_to_private_issue_with_permission
794 Issue.update_all(["is_private = ?", true], "id = 1")
881 Issue.update_all(["is_private = ?", true], "id = 1")
795 User.find(3).roles_for_project(Project.find(1)).first.update_attribute :issues_visibility, 'all'
882 User.find(3).roles_for_project(Project.find(1)).first.update_attribute :issues_visibility, 'all'
796 @request.session[:user_id] = 3
883 @request.session[:user_id] = 3
797 get :show, :id => 1
884 get :show, :id => 1
798 assert_response :success
885 assert_response :success
799 end
886 end
800
887
801 def test_show_should_not_disclose_relations_to_invisible_issues
888 def test_show_should_not_disclose_relations_to_invisible_issues
802 Setting.cross_project_issue_relations = '1'
889 Setting.cross_project_issue_relations = '1'
803 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
890 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
804 # Relation to a private project issue
891 # Relation to a private project issue
805 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
892 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
806
893
807 get :show, :id => 1
894 get :show, :id => 1
808 assert_response :success
895 assert_response :success
809
896
810 assert_tag :div, :attributes => { :id => 'relations' },
897 assert_tag :div, :attributes => { :id => 'relations' },
811 :descendant => { :tag => 'a', :content => /#2$/ }
898 :descendant => { :tag => 'a', :content => /#2$/ }
812 assert_no_tag :div, :attributes => { :id => 'relations' },
899 assert_no_tag :div, :attributes => { :id => 'relations' },
813 :descendant => { :tag => 'a', :content => /#4$/ }
900 :descendant => { :tag => 'a', :content => /#4$/ }
814 end
901 end
815
902
816 def test_show_should_list_subtasks
903 def test_show_should_list_subtasks
817 Issue.generate!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
904 Issue.generate!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
818
905
819 get :show, :id => 1
906 get :show, :id => 1
820 assert_response :success
907 assert_response :success
821 assert_tag 'div', :attributes => {:id => 'issue_tree'},
908 assert_tag 'div', :attributes => {:id => 'issue_tree'},
822 :descendant => {:tag => 'td', :content => /Child Issue/, :attributes => {:class => /subject/}}
909 :descendant => {:tag => 'td', :content => /Child Issue/, :attributes => {:class => /subject/}}
823 end
910 end
824
911
825 def test_show_should_list_parents
912 def test_show_should_list_parents
826 issue = Issue.generate!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
913 issue = Issue.generate!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
827
914
828 get :show, :id => issue.id
915 get :show, :id => issue.id
829 assert_response :success
916 assert_response :success
830 assert_tag 'div', :attributes => {:class => 'subject'},
917 assert_tag 'div', :attributes => {:class => 'subject'},
831 :descendant => {:tag => 'h3', :content => 'Child Issue'}
918 :descendant => {:tag => 'h3', :content => 'Child Issue'}
832 assert_tag 'div', :attributes => {:class => 'subject'},
919 assert_tag 'div', :attributes => {:class => 'subject'},
833 :descendant => {:tag => 'a', :attributes => {:href => '/issues/1'}}
920 :descendant => {:tag => 'a', :attributes => {:href => '/issues/1'}}
834 end
921 end
835
922
836 def test_show_atom
923 def test_show_atom
837 get :show, :id => 2, :format => 'atom'
924 get :show, :id => 2, :format => 'atom'
838 assert_response :success
925 assert_response :success
839 assert_template 'journals/index'
926 assert_template 'journals/index'
840 # Inline image
927 # Inline image
841 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
928 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
842 end
929 end
843
930
844 def test_show_export_to_pdf
931 def test_show_export_to_pdf
845 get :show, :id => 3, :format => 'pdf'
932 get :show, :id => 3, :format => 'pdf'
846 assert_response :success
933 assert_response :success
847 assert_equal 'application/pdf', @response.content_type
934 assert_equal 'application/pdf', @response.content_type
848 assert @response.body.starts_with?('%PDF')
935 assert @response.body.starts_with?('%PDF')
849 assert_not_nil assigns(:issue)
936 assert_not_nil assigns(:issue)
850 end
937 end
851
938
852 def test_get_new
939 def test_get_new
853 @request.session[:user_id] = 2
940 @request.session[:user_id] = 2
854 get :new, :project_id => 1, :tracker_id => 1
941 get :new, :project_id => 1, :tracker_id => 1
855 assert_response :success
942 assert_response :success
856 assert_template 'new'
943 assert_template 'new'
857
944
858 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
945 assert_tag 'input', :attributes => {:name => 'issue[is_private]'}
859 :value => 'Default string' }
946 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
947 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
948 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
949 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
950 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
951 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
952 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
953 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
954 assert_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
955 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
956 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
957 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
958 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]', :value => 'Default string' }
959 assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
860
960
861 # Be sure we don't display inactive IssuePriorities
961 # Be sure we don't display inactive IssuePriorities
862 assert ! IssuePriority.find(15).active?
962 assert ! IssuePriority.find(15).active?
863 assert_no_tag :option, :attributes => {:value => '15'},
963 assert_no_tag :option, :attributes => {:value => '15'},
864 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
964 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
865 end
965 end
866
966
967 def test_get_new_with_minimal_permissions
968 Role.find(1).update_attribute :permissions, [:add_issues]
969 Workflow.delete_all :role_id => 1
970
971 @request.session[:user_id] = 2
972 get :new, :project_id => 1, :tracker_id => 1
973 assert_response :success
974 assert_template 'new'
975
976 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
977 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
978 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
979 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
980 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
981 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
982 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
983 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
984 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
985 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
986 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
987 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
988 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
989 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]', :value => 'Default string' }
990 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
991 end
992
867 def test_get_new_without_default_start_date_is_creation_date
993 def test_get_new_without_default_start_date_is_creation_date
868 Setting.default_issue_start_date_to_creation_date = 0
994 Setting.default_issue_start_date_to_creation_date = 0
869
995
870 @request.session[:user_id] = 2
996 @request.session[:user_id] = 2
871 get :new, :project_id => 1, :tracker_id => 1
997 get :new, :project_id => 1, :tracker_id => 1
872 assert_response :success
998 assert_response :success
873 assert_template 'new'
999 assert_template 'new'
874
1000
875 assert_tag :tag => 'input', :attributes => { :name => 'issue[start_date]',
1001 assert_tag :tag => 'input', :attributes => { :name => 'issue[start_date]',
876 :value => nil }
1002 :value => nil }
877 end
1003 end
878
1004
879 def test_get_new_with_default_start_date_is_creation_date
1005 def test_get_new_with_default_start_date_is_creation_date
880 Setting.default_issue_start_date_to_creation_date = 1
1006 Setting.default_issue_start_date_to_creation_date = 1
881
1007
882 @request.session[:user_id] = 2
1008 @request.session[:user_id] = 2
883 get :new, :project_id => 1, :tracker_id => 1
1009 get :new, :project_id => 1, :tracker_id => 1
884 assert_response :success
1010 assert_response :success
885 assert_template 'new'
1011 assert_template 'new'
886
1012
887 assert_tag :tag => 'input', :attributes => { :name => 'issue[start_date]',
1013 assert_tag :tag => 'input', :attributes => { :name => 'issue[start_date]',
888 :value => Date.today.to_s }
1014 :value => Date.today.to_s }
889 end
1015 end
890
1016
891 def test_get_new_form_should_allow_attachment_upload
1017 def test_get_new_form_should_allow_attachment_upload
892 @request.session[:user_id] = 2
1018 @request.session[:user_id] = 2
893 get :new, :project_id => 1, :tracker_id => 1
1019 get :new, :project_id => 1, :tracker_id => 1
894
1020
895 assert_tag :tag => 'form',
1021 assert_tag :tag => 'form',
896 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
1022 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
897 :descendant => {
1023 :descendant => {
898 :tag => 'input',
1024 :tag => 'input',
899 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
1025 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
900 }
1026 }
901 end
1027 end
902
1028
903 def test_get_new_without_tracker_id
1029 def test_get_new_without_tracker_id
904 @request.session[:user_id] = 2
1030 @request.session[:user_id] = 2
905 get :new, :project_id => 1
1031 get :new, :project_id => 1
906 assert_response :success
1032 assert_response :success
907 assert_template 'new'
1033 assert_template 'new'
908
1034
909 issue = assigns(:issue)
1035 issue = assigns(:issue)
910 assert_not_nil issue
1036 assert_not_nil issue
911 assert_equal Project.find(1).trackers.first, issue.tracker
1037 assert_equal Project.find(1).trackers.first, issue.tracker
912 end
1038 end
913
1039
914 def test_get_new_with_no_default_status_should_display_an_error
1040 def test_get_new_with_no_default_status_should_display_an_error
915 @request.session[:user_id] = 2
1041 @request.session[:user_id] = 2
916 IssueStatus.delete_all
1042 IssueStatus.delete_all
917
1043
918 get :new, :project_id => 1
1044 get :new, :project_id => 1
919 assert_response 500
1045 assert_response 500
920 assert_error_tag :content => /No default issue/
1046 assert_error_tag :content => /No default issue/
921 end
1047 end
922
1048
923 def test_get_new_with_no_tracker_should_display_an_error
1049 def test_get_new_with_no_tracker_should_display_an_error
924 @request.session[:user_id] = 2
1050 @request.session[:user_id] = 2
925 Tracker.delete_all
1051 Tracker.delete_all
926
1052
927 get :new, :project_id => 1
1053 get :new, :project_id => 1
928 assert_response 500
1054 assert_response 500
929 assert_error_tag :content => /No tracker/
1055 assert_error_tag :content => /No tracker/
930 end
1056 end
931
1057
932 def test_update_new_form
1058 def test_update_new_form
933 @request.session[:user_id] = 2
1059 @request.session[:user_id] = 2
934 xhr :post, :new, :project_id => 1,
1060 xhr :post, :new, :project_id => 1,
935 :issue => {:tracker_id => 2,
1061 :issue => {:tracker_id => 2,
936 :subject => 'This is the test_new issue',
1062 :subject => 'This is the test_new issue',
937 :description => 'This is the description',
1063 :description => 'This is the description',
938 :priority_id => 5}
1064 :priority_id => 5}
939 assert_response :success
1065 assert_response :success
940 assert_template 'attributes'
1066 assert_template 'attributes'
941
1067
942 issue = assigns(:issue)
1068 issue = assigns(:issue)
943 assert_kind_of Issue, issue
1069 assert_kind_of Issue, issue
944 assert_equal 1, issue.project_id
1070 assert_equal 1, issue.project_id
945 assert_equal 2, issue.tracker_id
1071 assert_equal 2, issue.tracker_id
946 assert_equal 'This is the test_new issue', issue.subject
1072 assert_equal 'This is the test_new issue', issue.subject
947 end
1073 end
948
1074
949 def test_post_create
1075 def test_post_create
950 @request.session[:user_id] = 2
1076 @request.session[:user_id] = 2
951 assert_difference 'Issue.count' do
1077 assert_difference 'Issue.count' do
952 post :create, :project_id => 1,
1078 post :create, :project_id => 1,
953 :issue => {:tracker_id => 3,
1079 :issue => {:tracker_id => 3,
954 :status_id => 2,
1080 :status_id => 2,
955 :subject => 'This is the test_new issue',
1081 :subject => 'This is the test_new issue',
956 :description => 'This is the description',
1082 :description => 'This is the description',
957 :priority_id => 5,
1083 :priority_id => 5,
958 :start_date => '2010-11-07',
1084 :start_date => '2010-11-07',
959 :estimated_hours => '',
1085 :estimated_hours => '',
960 :custom_field_values => {'2' => 'Value for field 2'}}
1086 :custom_field_values => {'2' => 'Value for field 2'}}
961 end
1087 end
962 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1088 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
963
1089
964 issue = Issue.find_by_subject('This is the test_new issue')
1090 issue = Issue.find_by_subject('This is the test_new issue')
965 assert_not_nil issue
1091 assert_not_nil issue
966 assert_equal 2, issue.author_id
1092 assert_equal 2, issue.author_id
967 assert_equal 3, issue.tracker_id
1093 assert_equal 3, issue.tracker_id
968 assert_equal 2, issue.status_id
1094 assert_equal 2, issue.status_id
969 assert_equal Date.parse('2010-11-07'), issue.start_date
1095 assert_equal Date.parse('2010-11-07'), issue.start_date
970 assert_nil issue.estimated_hours
1096 assert_nil issue.estimated_hours
971 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
1097 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
972 assert_not_nil v
1098 assert_not_nil v
973 assert_equal 'Value for field 2', v.value
1099 assert_equal 'Value for field 2', v.value
974 end
1100 end
975
1101
976 def test_post_new_with_group_assignment
1102 def test_post_new_with_group_assignment
977 group = Group.find(11)
1103 group = Group.find(11)
978 project = Project.find(1)
1104 project = Project.find(1)
979 project.members << Member.new(:principal => group, :roles => [Role.first])
1105 project.members << Member.new(:principal => group, :roles => [Role.first])
980
1106
981 with_settings :issue_group_assignment => '1' do
1107 with_settings :issue_group_assignment => '1' do
982 @request.session[:user_id] = 2
1108 @request.session[:user_id] = 2
983 assert_difference 'Issue.count' do
1109 assert_difference 'Issue.count' do
984 post :create, :project_id => project.id,
1110 post :create, :project_id => project.id,
985 :issue => {:tracker_id => 3,
1111 :issue => {:tracker_id => 3,
986 :status_id => 1,
1112 :status_id => 1,
987 :subject => 'This is the test_new_with_group_assignment issue',
1113 :subject => 'This is the test_new_with_group_assignment issue',
988 :assigned_to_id => group.id}
1114 :assigned_to_id => group.id}
989 end
1115 end
990 end
1116 end
991 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1117 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
992
1118
993 issue = Issue.find_by_subject('This is the test_new_with_group_assignment issue')
1119 issue = Issue.find_by_subject('This is the test_new_with_group_assignment issue')
994 assert_not_nil issue
1120 assert_not_nil issue
995 assert_equal group, issue.assigned_to
1121 assert_equal group, issue.assigned_to
996 end
1122 end
997
1123
998 def test_post_create_without_start_date_and_default_start_date_is_not_creation_date
1124 def test_post_create_without_start_date_and_default_start_date_is_not_creation_date
999 Setting.default_issue_start_date_to_creation_date = 0
1125 Setting.default_issue_start_date_to_creation_date = 0
1000
1126
1001 @request.session[:user_id] = 2
1127 @request.session[:user_id] = 2
1002 assert_difference 'Issue.count' do
1128 assert_difference 'Issue.count' do
1003 post :create, :project_id => 1,
1129 post :create, :project_id => 1,
1004 :issue => {:tracker_id => 3,
1130 :issue => {:tracker_id => 3,
1005 :status_id => 2,
1131 :status_id => 2,
1006 :subject => 'This is the test_new issue',
1132 :subject => 'This is the test_new issue',
1007 :description => 'This is the description',
1133 :description => 'This is the description',
1008 :priority_id => 5,
1134 :priority_id => 5,
1009 :estimated_hours => '',
1135 :estimated_hours => '',
1010 :custom_field_values => {'2' => 'Value for field 2'}}
1136 :custom_field_values => {'2' => 'Value for field 2'}}
1011 end
1137 end
1012 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1138 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1013
1139
1014 issue = Issue.find_by_subject('This is the test_new issue')
1140 issue = Issue.find_by_subject('This is the test_new issue')
1015 assert_not_nil issue
1141 assert_not_nil issue
1016 assert_nil issue.start_date
1142 assert_nil issue.start_date
1017 end
1143 end
1018
1144
1019 def test_post_create_without_start_date_and_default_start_date_is_creation_date
1145 def test_post_create_without_start_date_and_default_start_date_is_creation_date
1020 Setting.default_issue_start_date_to_creation_date = 1
1146 Setting.default_issue_start_date_to_creation_date = 1
1021
1147
1022 @request.session[:user_id] = 2
1148 @request.session[:user_id] = 2
1023 assert_difference 'Issue.count' do
1149 assert_difference 'Issue.count' do
1024 post :create, :project_id => 1,
1150 post :create, :project_id => 1,
1025 :issue => {:tracker_id => 3,
1151 :issue => {:tracker_id => 3,
1026 :status_id => 2,
1152 :status_id => 2,
1027 :subject => 'This is the test_new issue',
1153 :subject => 'This is the test_new issue',
1028 :description => 'This is the description',
1154 :description => 'This is the description',
1029 :priority_id => 5,
1155 :priority_id => 5,
1030 :estimated_hours => '',
1156 :estimated_hours => '',
1031 :custom_field_values => {'2' => 'Value for field 2'}}
1157 :custom_field_values => {'2' => 'Value for field 2'}}
1032 end
1158 end
1033 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1159 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1034
1160
1035 issue = Issue.find_by_subject('This is the test_new issue')
1161 issue = Issue.find_by_subject('This is the test_new issue')
1036 assert_not_nil issue
1162 assert_not_nil issue
1037 assert_equal Date.today, issue.start_date
1163 assert_equal Date.today, issue.start_date
1038 end
1164 end
1039
1165
1040 def test_post_create_and_continue
1166 def test_post_create_and_continue
1041 @request.session[:user_id] = 2
1167 @request.session[:user_id] = 2
1042 assert_difference 'Issue.count' do
1168 assert_difference 'Issue.count' do
1043 post :create, :project_id => 1,
1169 post :create, :project_id => 1,
1044 :issue => {:tracker_id => 3, :subject => 'This is first issue', :priority_id => 5},
1170 :issue => {:tracker_id => 3, :subject => 'This is first issue', :priority_id => 5},
1045 :continue => ''
1171 :continue => ''
1046 end
1172 end
1047
1173
1048 issue = Issue.first(:order => 'id DESC')
1174 issue = Issue.first(:order => 'id DESC')
1049 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook', :issue => {:tracker_id => 3}
1175 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook', :issue => {:tracker_id => 3}
1050 assert_not_nil flash[:notice], "flash was not set"
1176 assert_not_nil flash[:notice], "flash was not set"
1051 assert flash[:notice].include?("<a href='/issues/#{issue.id}'>##{issue.id}</a>"), "issue link not found in flash: #{flash[:notice]}"
1177 assert flash[:notice].include?("<a href='/issues/#{issue.id}'>##{issue.id}</a>"), "issue link not found in flash: #{flash[:notice]}"
1052 end
1178 end
1053
1179
1054 def test_post_create_without_custom_fields_param
1180 def test_post_create_without_custom_fields_param
1055 @request.session[:user_id] = 2
1181 @request.session[:user_id] = 2
1056 assert_difference 'Issue.count' do
1182 assert_difference 'Issue.count' do
1057 post :create, :project_id => 1,
1183 post :create, :project_id => 1,
1058 :issue => {:tracker_id => 1,
1184 :issue => {:tracker_id => 1,
1059 :subject => 'This is the test_new issue',
1185 :subject => 'This is the test_new issue',
1060 :description => 'This is the description',
1186 :description => 'This is the description',
1061 :priority_id => 5}
1187 :priority_id => 5}
1062 end
1188 end
1063 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1189 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1064 end
1190 end
1065
1191
1066 def test_post_create_with_required_custom_field_and_without_custom_fields_param
1192 def test_post_create_with_required_custom_field_and_without_custom_fields_param
1067 field = IssueCustomField.find_by_name('Database')
1193 field = IssueCustomField.find_by_name('Database')
1068 field.update_attribute(:is_required, true)
1194 field.update_attribute(:is_required, true)
1069
1195
1070 @request.session[:user_id] = 2
1196 @request.session[:user_id] = 2
1071 post :create, :project_id => 1,
1197 post :create, :project_id => 1,
1072 :issue => {:tracker_id => 1,
1198 :issue => {:tracker_id => 1,
1073 :subject => 'This is the test_new issue',
1199 :subject => 'This is the test_new issue',
1074 :description => 'This is the description',
1200 :description => 'This is the description',
1075 :priority_id => 5}
1201 :priority_id => 5}
1076 assert_response :success
1202 assert_response :success
1077 assert_template 'new'
1203 assert_template 'new'
1078 issue = assigns(:issue)
1204 issue = assigns(:issue)
1079 assert_not_nil issue
1205 assert_not_nil issue
1080 assert_equal I18n.translate('activerecord.errors.messages.invalid'),
1206 assert_equal I18n.translate('activerecord.errors.messages.invalid'),
1081 issue.errors[:custom_values].to_s
1207 issue.errors[:custom_values].to_s
1082 end
1208 end
1083
1209
1084 def test_post_create_with_watchers
1210 def test_post_create_with_watchers
1085 @request.session[:user_id] = 2
1211 @request.session[:user_id] = 2
1086 ActionMailer::Base.deliveries.clear
1212 ActionMailer::Base.deliveries.clear
1087
1213
1088 assert_difference 'Watcher.count', 2 do
1214 assert_difference 'Watcher.count', 2 do
1089 post :create, :project_id => 1,
1215 post :create, :project_id => 1,
1090 :issue => {:tracker_id => 1,
1216 :issue => {:tracker_id => 1,
1091 :subject => 'This is a new issue with watchers',
1217 :subject => 'This is a new issue with watchers',
1092 :description => 'This is the description',
1218 :description => 'This is the description',
1093 :priority_id => 5,
1219 :priority_id => 5,
1094 :watcher_user_ids => ['2', '3']}
1220 :watcher_user_ids => ['2', '3']}
1095 end
1221 end
1096 issue = Issue.find_by_subject('This is a new issue with watchers')
1222 issue = Issue.find_by_subject('This is a new issue with watchers')
1097 assert_not_nil issue
1223 assert_not_nil issue
1098 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
1224 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
1099
1225
1100 # Watchers added
1226 # Watchers added
1101 assert_equal [2, 3], issue.watcher_user_ids.sort
1227 assert_equal [2, 3], issue.watcher_user_ids.sort
1102 assert issue.watched_by?(User.find(3))
1228 assert issue.watched_by?(User.find(3))
1103 # Watchers notified
1229 # Watchers notified
1104 mail = ActionMailer::Base.deliveries.last
1230 mail = ActionMailer::Base.deliveries.last
1105 assert_kind_of TMail::Mail, mail
1231 assert_kind_of TMail::Mail, mail
1106 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
1232 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
1107 end
1233 end
1108
1234
1109 def test_post_create_subissue
1235 def test_post_create_subissue
1110 @request.session[:user_id] = 2
1236 @request.session[:user_id] = 2
1111
1237
1112 assert_difference 'Issue.count' do
1238 assert_difference 'Issue.count' do
1113 post :create, :project_id => 1,
1239 post :create, :project_id => 1,
1114 :issue => {:tracker_id => 1,
1240 :issue => {:tracker_id => 1,
1115 :subject => 'This is a child issue',
1241 :subject => 'This is a child issue',
1116 :parent_issue_id => 2}
1242 :parent_issue_id => 2}
1117 end
1243 end
1118 issue = Issue.find_by_subject('This is a child issue')
1244 issue = Issue.find_by_subject('This is a child issue')
1119 assert_not_nil issue
1245 assert_not_nil issue
1120 assert_equal Issue.find(2), issue.parent
1246 assert_equal Issue.find(2), issue.parent
1121 end
1247 end
1122
1248
1123 def test_post_create_subissue_with_non_numeric_parent_id
1249 def test_post_create_subissue_with_non_numeric_parent_id
1124 @request.session[:user_id] = 2
1250 @request.session[:user_id] = 2
1125
1251
1126 assert_difference 'Issue.count' do
1252 assert_difference 'Issue.count' do
1127 post :create, :project_id => 1,
1253 post :create, :project_id => 1,
1128 :issue => {:tracker_id => 1,
1254 :issue => {:tracker_id => 1,
1129 :subject => 'This is a child issue',
1255 :subject => 'This is a child issue',
1130 :parent_issue_id => 'ABC'}
1256 :parent_issue_id => 'ABC'}
1131 end
1257 end
1132 issue = Issue.find_by_subject('This is a child issue')
1258 issue = Issue.find_by_subject('This is a child issue')
1133 assert_not_nil issue
1259 assert_not_nil issue
1134 assert_nil issue.parent
1260 assert_nil issue.parent
1135 end
1261 end
1136
1262
1137 def test_post_create_private
1263 def test_post_create_private
1138 @request.session[:user_id] = 2
1264 @request.session[:user_id] = 2
1139
1265
1140 assert_difference 'Issue.count' do
1266 assert_difference 'Issue.count' do
1141 post :create, :project_id => 1,
1267 post :create, :project_id => 1,
1142 :issue => {:tracker_id => 1,
1268 :issue => {:tracker_id => 1,
1143 :subject => 'This is a private issue',
1269 :subject => 'This is a private issue',
1144 :is_private => '1'}
1270 :is_private => '1'}
1145 end
1271 end
1146 issue = Issue.first(:order => 'id DESC')
1272 issue = Issue.first(:order => 'id DESC')
1147 assert issue.is_private?
1273 assert issue.is_private?
1148 end
1274 end
1149
1275
1150 def test_post_create_private_with_set_own_issues_private_permission
1276 def test_post_create_private_with_set_own_issues_private_permission
1151 role = Role.find(1)
1277 role = Role.find(1)
1152 role.remove_permission! :set_issues_private
1278 role.remove_permission! :set_issues_private
1153 role.add_permission! :set_own_issues_private
1279 role.add_permission! :set_own_issues_private
1154
1280
1155 @request.session[:user_id] = 2
1281 @request.session[:user_id] = 2
1156
1282
1157 assert_difference 'Issue.count' do
1283 assert_difference 'Issue.count' do
1158 post :create, :project_id => 1,
1284 post :create, :project_id => 1,
1159 :issue => {:tracker_id => 1,
1285 :issue => {:tracker_id => 1,
1160 :subject => 'This is a private issue',
1286 :subject => 'This is a private issue',
1161 :is_private => '1'}
1287 :is_private => '1'}
1162 end
1288 end
1163 issue = Issue.first(:order => 'id DESC')
1289 issue = Issue.first(:order => 'id DESC')
1164 assert issue.is_private?
1290 assert issue.is_private?
1165 end
1291 end
1166
1292
1167 def test_post_create_should_send_a_notification
1293 def test_post_create_should_send_a_notification
1168 ActionMailer::Base.deliveries.clear
1294 ActionMailer::Base.deliveries.clear
1169 @request.session[:user_id] = 2
1295 @request.session[:user_id] = 2
1170 assert_difference 'Issue.count' do
1296 assert_difference 'Issue.count' do
1171 post :create, :project_id => 1,
1297 post :create, :project_id => 1,
1172 :issue => {:tracker_id => 3,
1298 :issue => {:tracker_id => 3,
1173 :subject => 'This is the test_new issue',
1299 :subject => 'This is the test_new issue',
1174 :description => 'This is the description',
1300 :description => 'This is the description',
1175 :priority_id => 5,
1301 :priority_id => 5,
1176 :estimated_hours => '',
1302 :estimated_hours => '',
1177 :custom_field_values => {'2' => 'Value for field 2'}}
1303 :custom_field_values => {'2' => 'Value for field 2'}}
1178 end
1304 end
1179 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1305 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1180
1306
1181 assert_equal 1, ActionMailer::Base.deliveries.size
1307 assert_equal 1, ActionMailer::Base.deliveries.size
1182 end
1308 end
1183
1309
1184 def test_post_create_should_preserve_fields_values_on_validation_failure
1310 def test_post_create_should_preserve_fields_values_on_validation_failure
1185 @request.session[:user_id] = 2
1311 @request.session[:user_id] = 2
1186 post :create, :project_id => 1,
1312 post :create, :project_id => 1,
1187 :issue => {:tracker_id => 1,
1313 :issue => {:tracker_id => 1,
1188 # empty subject
1314 # empty subject
1189 :subject => '',
1315 :subject => '',
1190 :description => 'This is a description',
1316 :description => 'This is a description',
1191 :priority_id => 6,
1317 :priority_id => 6,
1192 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
1318 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
1193 assert_response :success
1319 assert_response :success
1194 assert_template 'new'
1320 assert_template 'new'
1195
1321
1196 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
1322 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
1197 :content => 'This is a description'
1323 :content => 'This is a description'
1198 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
1324 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
1199 :child => { :tag => 'option', :attributes => { :selected => 'selected',
1325 :child => { :tag => 'option', :attributes => { :selected => 'selected',
1200 :value => '6' },
1326 :value => '6' },
1201 :content => 'High' }
1327 :content => 'High' }
1202 # Custom fields
1328 # Custom fields
1203 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
1329 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
1204 :child => { :tag => 'option', :attributes => { :selected => 'selected',
1330 :child => { :tag => 'option', :attributes => { :selected => 'selected',
1205 :value => 'Oracle' },
1331 :value => 'Oracle' },
1206 :content => 'Oracle' }
1332 :content => 'Oracle' }
1207 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
1333 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
1208 :value => 'Value for field 2'}
1334 :value => 'Value for field 2'}
1209 end
1335 end
1210
1336
1211 def test_post_create_should_ignore_non_safe_attributes
1337 def test_post_create_should_ignore_non_safe_attributes
1212 @request.session[:user_id] = 2
1338 @request.session[:user_id] = 2
1213 assert_nothing_raised do
1339 assert_nothing_raised do
1214 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
1340 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
1215 end
1341 end
1216 end
1342 end
1217
1343
1218 def test_post_create_with_attachment
1344 def test_post_create_with_attachment
1219 set_tmp_attachments_directory
1345 set_tmp_attachments_directory
1220 @request.session[:user_id] = 2
1346 @request.session[:user_id] = 2
1221
1347
1222 assert_difference 'Issue.count' do
1348 assert_difference 'Issue.count' do
1223 assert_difference 'Attachment.count' do
1349 assert_difference 'Attachment.count' do
1224 post :create, :project_id => 1,
1350 post :create, :project_id => 1,
1225 :issue => { :tracker_id => '1', :subject => 'With attachment' },
1351 :issue => { :tracker_id => '1', :subject => 'With attachment' },
1226 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
1352 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
1227 end
1353 end
1228 end
1354 end
1229
1355
1230 issue = Issue.first(:order => 'id DESC')
1356 issue = Issue.first(:order => 'id DESC')
1231 attachment = Attachment.first(:order => 'id DESC')
1357 attachment = Attachment.first(:order => 'id DESC')
1232
1358
1233 assert_equal issue, attachment.container
1359 assert_equal issue, attachment.container
1234 assert_equal 2, attachment.author_id
1360 assert_equal 2, attachment.author_id
1235 assert_equal 'testfile.txt', attachment.filename
1361 assert_equal 'testfile.txt', attachment.filename
1236 assert_equal 'text/plain', attachment.content_type
1362 assert_equal 'text/plain', attachment.content_type
1237 assert_equal 'test file', attachment.description
1363 assert_equal 'test file', attachment.description
1238 assert_equal 59, attachment.filesize
1364 assert_equal 59, attachment.filesize
1239 assert File.exists?(attachment.diskfile)
1365 assert File.exists?(attachment.diskfile)
1240 assert_equal 59, File.size(attachment.diskfile)
1366 assert_equal 59, File.size(attachment.diskfile)
1241 end
1367 end
1242
1368
1243 context "without workflow privilege" do
1369 context "without workflow privilege" do
1244 setup do
1370 setup do
1245 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
1371 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
1246 Role.anonymous.add_permission! :add_issues, :add_issue_notes
1372 Role.anonymous.add_permission! :add_issues, :add_issue_notes
1247 end
1373 end
1248
1374
1249 context "#new" do
1375 context "#new" do
1250 should "propose default status only" do
1376 should "propose default status only" do
1251 get :new, :project_id => 1
1377 get :new, :project_id => 1
1252 assert_response :success
1378 assert_response :success
1253 assert_template 'new'
1379 assert_template 'new'
1254 assert_tag :tag => 'select',
1380 assert_tag :tag => 'select',
1255 :attributes => {:name => 'issue[status_id]'},
1381 :attributes => {:name => 'issue[status_id]'},
1256 :children => {:count => 1},
1382 :children => {:count => 1},
1257 :child => {:tag => 'option', :attributes => {:value => IssueStatus.default.id.to_s}}
1383 :child => {:tag => 'option', :attributes => {:value => IssueStatus.default.id.to_s}}
1258 end
1384 end
1259
1385
1260 should "accept default status" do
1386 should "accept default status" do
1261 assert_difference 'Issue.count' do
1387 assert_difference 'Issue.count' do
1262 post :create, :project_id => 1,
1388 post :create, :project_id => 1,
1263 :issue => {:tracker_id => 1,
1389 :issue => {:tracker_id => 1,
1264 :subject => 'This is an issue',
1390 :subject => 'This is an issue',
1265 :status_id => 1}
1391 :status_id => 1}
1266 end
1392 end
1267 issue = Issue.last(:order => 'id')
1393 issue = Issue.last(:order => 'id')
1268 assert_equal IssueStatus.default, issue.status
1394 assert_equal IssueStatus.default, issue.status
1269 end
1395 end
1270
1396
1271 should "ignore unauthorized status" do
1397 should "ignore unauthorized status" do
1272 assert_difference 'Issue.count' do
1398 assert_difference 'Issue.count' do
1273 post :create, :project_id => 1,
1399 post :create, :project_id => 1,
1274 :issue => {:tracker_id => 1,
1400 :issue => {:tracker_id => 1,
1275 :subject => 'This is an issue',
1401 :subject => 'This is an issue',
1276 :status_id => 3}
1402 :status_id => 3}
1277 end
1403 end
1278 issue = Issue.last(:order => 'id')
1404 issue = Issue.last(:order => 'id')
1279 assert_equal IssueStatus.default, issue.status
1405 assert_equal IssueStatus.default, issue.status
1280 end
1406 end
1281 end
1407 end
1282
1408
1283 context "#update" do
1409 context "#update" do
1284 should "ignore status change" do
1410 should "ignore status change" do
1285 assert_difference 'Journal.count' do
1411 assert_difference 'Journal.count' do
1286 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1412 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1287 end
1413 end
1288 assert_equal 1, Issue.find(1).status_id
1414 assert_equal 1, Issue.find(1).status_id
1289 end
1415 end
1290
1416
1291 should "ignore attributes changes" do
1417 should "ignore attributes changes" do
1292 assert_difference 'Journal.count' do
1418 assert_difference 'Journal.count' do
1293 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
1419 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
1294 end
1420 end
1295 issue = Issue.find(1)
1421 issue = Issue.find(1)
1296 assert_equal "Can't print recipes", issue.subject
1422 assert_equal "Can't print recipes", issue.subject
1297 assert_nil issue.assigned_to
1423 assert_nil issue.assigned_to
1298 end
1424 end
1299 end
1425 end
1300 end
1426 end
1301
1427
1302 context "with workflow privilege" do
1428 context "with workflow privilege" do
1303 setup do
1429 setup do
1304 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
1430 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
1305 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
1431 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
1306 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
1432 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
1307 Role.anonymous.add_permission! :add_issues, :add_issue_notes
1433 Role.anonymous.add_permission! :add_issues, :add_issue_notes
1308 end
1434 end
1309
1435
1310 context "#update" do
1436 context "#update" do
1311 should "accept authorized status" do
1437 should "accept authorized status" do
1312 assert_difference 'Journal.count' do
1438 assert_difference 'Journal.count' do
1313 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1439 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1314 end
1440 end
1315 assert_equal 3, Issue.find(1).status_id
1441 assert_equal 3, Issue.find(1).status_id
1316 end
1442 end
1317
1443
1318 should "ignore unauthorized status" do
1444 should "ignore unauthorized status" do
1319 assert_difference 'Journal.count' do
1445 assert_difference 'Journal.count' do
1320 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
1446 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
1321 end
1447 end
1322 assert_equal 1, Issue.find(1).status_id
1448 assert_equal 1, Issue.find(1).status_id
1323 end
1449 end
1324
1450
1325 should "accept authorized attributes changes" do
1451 should "accept authorized attributes changes" do
1326 assert_difference 'Journal.count' do
1452 assert_difference 'Journal.count' do
1327 put :update, :id => 1, :notes => 'just trying', :issue => {:assigned_to_id => 2}
1453 put :update, :id => 1, :notes => 'just trying', :issue => {:assigned_to_id => 2}
1328 end
1454 end
1329 issue = Issue.find(1)
1455 issue = Issue.find(1)
1330 assert_equal 2, issue.assigned_to_id
1456 assert_equal 2, issue.assigned_to_id
1331 end
1457 end
1332
1458
1333 should "ignore unauthorized attributes changes" do
1459 should "ignore unauthorized attributes changes" do
1334 assert_difference 'Journal.count' do
1460 assert_difference 'Journal.count' do
1335 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed'}
1461 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed'}
1336 end
1462 end
1337 issue = Issue.find(1)
1463 issue = Issue.find(1)
1338 assert_equal "Can't print recipes", issue.subject
1464 assert_equal "Can't print recipes", issue.subject
1339 end
1465 end
1340 end
1466 end
1341
1467
1342 context "and :edit_issues permission" do
1468 context "and :edit_issues permission" do
1343 setup do
1469 setup do
1344 Role.anonymous.add_permission! :add_issues, :edit_issues
1470 Role.anonymous.add_permission! :add_issues, :edit_issues
1345 end
1471 end
1346
1472
1347 should "accept authorized status" do
1473 should "accept authorized status" do
1348 assert_difference 'Journal.count' do
1474 assert_difference 'Journal.count' do
1349 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1475 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1350 end
1476 end
1351 assert_equal 3, Issue.find(1).status_id
1477 assert_equal 3, Issue.find(1).status_id
1352 end
1478 end
1353
1479
1354 should "ignore unauthorized status" do
1480 should "ignore unauthorized status" do
1355 assert_difference 'Journal.count' do
1481 assert_difference 'Journal.count' do
1356 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
1482 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
1357 end
1483 end
1358 assert_equal 1, Issue.find(1).status_id
1484 assert_equal 1, Issue.find(1).status_id
1359 end
1485 end
1360
1486
1361 should "accept authorized attributes changes" do
1487 should "accept authorized attributes changes" do
1362 assert_difference 'Journal.count' do
1488 assert_difference 'Journal.count' do
1363 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
1489 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
1364 end
1490 end
1365 issue = Issue.find(1)
1491 issue = Issue.find(1)
1366 assert_equal "changed", issue.subject
1492 assert_equal "changed", issue.subject
1367 assert_equal 2, issue.assigned_to_id
1493 assert_equal 2, issue.assigned_to_id
1368 end
1494 end
1369 end
1495 end
1370 end
1496 end
1371
1497
1372 def test_copy_issue
1498 def test_copy_issue
1373 @request.session[:user_id] = 2
1499 @request.session[:user_id] = 2
1374 get :new, :project_id => 1, :copy_from => 1
1500 get :new, :project_id => 1, :copy_from => 1
1375 assert_template 'new'
1501 assert_template 'new'
1376 assert_not_nil assigns(:issue)
1502 assert_not_nil assigns(:issue)
1377 orig = Issue.find(1)
1503 orig = Issue.find(1)
1378 assert_equal orig.subject, assigns(:issue).subject
1504 assert_equal orig.subject, assigns(:issue).subject
1379 end
1505 end
1380
1506
1381 def test_get_edit
1507 def test_get_edit
1382 @request.session[:user_id] = 2
1508 @request.session[:user_id] = 2
1383 get :edit, :id => 1
1509 get :edit, :id => 1
1384 assert_response :success
1510 assert_response :success
1385 assert_template 'edit'
1511 assert_template 'edit'
1386 assert_not_nil assigns(:issue)
1512 assert_not_nil assigns(:issue)
1387 assert_equal Issue.find(1), assigns(:issue)
1513 assert_equal Issue.find(1), assigns(:issue)
1388
1514
1389 # Be sure we don't display inactive IssuePriorities
1515 # Be sure we don't display inactive IssuePriorities
1390 assert ! IssuePriority.find(15).active?
1516 assert ! IssuePriority.find(15).active?
1391 assert_no_tag :option, :attributes => {:value => '15'},
1517 assert_no_tag :option, :attributes => {:value => '15'},
1392 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1518 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1393 end
1519 end
1394
1520
1395 def test_get_edit_should_display_the_time_entry_form_with_log_time_permission
1521 def test_get_edit_should_display_the_time_entry_form_with_log_time_permission
1396 @request.session[:user_id] = 2
1522 @request.session[:user_id] = 2
1397 Role.find_by_name('Manager').update_attribute :permissions, [:view_issues, :edit_issues, :log_time]
1523 Role.find_by_name('Manager').update_attribute :permissions, [:view_issues, :edit_issues, :log_time]
1398
1524
1399 get :edit, :id => 1
1525 get :edit, :id => 1
1400 assert_tag 'input', :attributes => {:name => 'time_entry[hours]'}
1526 assert_tag 'input', :attributes => {:name => 'time_entry[hours]'}
1401 end
1527 end
1402
1528
1403 def test_get_edit_should_not_display_the_time_entry_form_without_log_time_permission
1529 def test_get_edit_should_not_display_the_time_entry_form_without_log_time_permission
1404 @request.session[:user_id] = 2
1530 @request.session[:user_id] = 2
1405 Role.find_by_name('Manager').remove_permission! :log_time
1531 Role.find_by_name('Manager').remove_permission! :log_time
1406
1532
1407 get :edit, :id => 1
1533 get :edit, :id => 1
1408 assert_no_tag 'input', :attributes => {:name => 'time_entry[hours]'}
1534 assert_no_tag 'input', :attributes => {:name => 'time_entry[hours]'}
1409 end
1535 end
1410
1536
1411 def test_get_edit_with_params
1537 def test_get_edit_with_params
1412 @request.session[:user_id] = 2
1538 @request.session[:user_id] = 2
1413 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 },
1539 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 },
1414 :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => TimeEntryActivity.first.id }
1540 :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => TimeEntryActivity.first.id }
1415 assert_response :success
1541 assert_response :success
1416 assert_template 'edit'
1542 assert_template 'edit'
1417
1543
1418 issue = assigns(:issue)
1544 issue = assigns(:issue)
1419 assert_not_nil issue
1545 assert_not_nil issue
1420
1546
1421 assert_equal 5, issue.status_id
1547 assert_equal 5, issue.status_id
1422 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
1548 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
1423 :child => { :tag => 'option',
1549 :child => { :tag => 'option',
1424 :content => 'Closed',
1550 :content => 'Closed',
1425 :attributes => { :selected => 'selected' } }
1551 :attributes => { :selected => 'selected' } }
1426
1552
1427 assert_equal 7, issue.priority_id
1553 assert_equal 7, issue.priority_id
1428 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
1554 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
1429 :child => { :tag => 'option',
1555 :child => { :tag => 'option',
1430 :content => 'Urgent',
1556 :content => 'Urgent',
1431 :attributes => { :selected => 'selected' } }
1557 :attributes => { :selected => 'selected' } }
1432
1558
1433 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => '2.5' }
1559 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => '2.5' }
1434 assert_tag :select, :attributes => { :name => 'time_entry[activity_id]' },
1560 assert_tag :select, :attributes => { :name => 'time_entry[activity_id]' },
1435 :child => { :tag => 'option',
1561 :child => { :tag => 'option',
1436 :attributes => { :selected => 'selected', :value => TimeEntryActivity.first.id } }
1562 :attributes => { :selected => 'selected', :value => TimeEntryActivity.first.id } }
1437 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => 'test_get_edit_with_params' }
1563 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => 'test_get_edit_with_params' }
1438 end
1564 end
1439
1565
1440 def test_update_edit_form
1566 def test_update_edit_form
1441 @request.session[:user_id] = 2
1567 @request.session[:user_id] = 2
1442 xhr :post, :new, :project_id => 1,
1568 xhr :post, :new, :project_id => 1,
1443 :id => 1,
1569 :id => 1,
1444 :issue => {:tracker_id => 2,
1570 :issue => {:tracker_id => 2,
1445 :subject => 'This is the test_new issue',
1571 :subject => 'This is the test_new issue',
1446 :description => 'This is the description',
1572 :description => 'This is the description',
1447 :priority_id => 5}
1573 :priority_id => 5}
1448 assert_response :success
1574 assert_response :success
1449 assert_template 'attributes'
1575 assert_template 'attributes'
1450
1576
1451 issue = assigns(:issue)
1577 issue = assigns(:issue)
1452 assert_kind_of Issue, issue
1578 assert_kind_of Issue, issue
1453 assert_equal 1, issue.id
1579 assert_equal 1, issue.id
1454 assert_equal 1, issue.project_id
1580 assert_equal 1, issue.project_id
1455 assert_equal 2, issue.tracker_id
1581 assert_equal 2, issue.tracker_id
1456 assert_equal 'This is the test_new issue', issue.subject
1582 assert_equal 'This is the test_new issue', issue.subject
1457 end
1583 end
1458
1584
1459 def test_update_using_invalid_http_verbs
1585 def test_update_using_invalid_http_verbs
1460 @request.session[:user_id] = 2
1586 @request.session[:user_id] = 2
1461 subject = 'Updated by an invalid http verb'
1587 subject = 'Updated by an invalid http verb'
1462
1588
1463 get :update, :id => 1, :issue => {:subject => subject}
1589 get :update, :id => 1, :issue => {:subject => subject}
1464 assert_not_equal subject, Issue.find(1).subject
1590 assert_not_equal subject, Issue.find(1).subject
1465
1591
1466 post :update, :id => 1, :issue => {:subject => subject}
1592 post :update, :id => 1, :issue => {:subject => subject}
1467 assert_not_equal subject, Issue.find(1).subject
1593 assert_not_equal subject, Issue.find(1).subject
1468
1594
1469 delete :update, :id => 1, :issue => {:subject => subject}
1595 delete :update, :id => 1, :issue => {:subject => subject}
1470 assert_not_equal subject, Issue.find(1).subject
1596 assert_not_equal subject, Issue.find(1).subject
1471 end
1597 end
1472
1598
1473 def test_put_update_without_custom_fields_param
1599 def test_put_update_without_custom_fields_param
1474 @request.session[:user_id] = 2
1600 @request.session[:user_id] = 2
1475 ActionMailer::Base.deliveries.clear
1601 ActionMailer::Base.deliveries.clear
1476
1602
1477 issue = Issue.find(1)
1603 issue = Issue.find(1)
1478 assert_equal '125', issue.custom_value_for(2).value
1604 assert_equal '125', issue.custom_value_for(2).value
1479 old_subject = issue.subject
1605 old_subject = issue.subject
1480 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
1606 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
1481
1607
1482 assert_difference('Journal.count') do
1608 assert_difference('Journal.count') do
1483 assert_difference('JournalDetail.count', 2) do
1609 assert_difference('JournalDetail.count', 2) do
1484 put :update, :id => 1, :issue => {:subject => new_subject,
1610 put :update, :id => 1, :issue => {:subject => new_subject,
1485 :priority_id => '6',
1611 :priority_id => '6',
1486 :category_id => '1' # no change
1612 :category_id => '1' # no change
1487 }
1613 }
1488 end
1614 end
1489 end
1615 end
1490 assert_redirected_to :action => 'show', :id => '1'
1616 assert_redirected_to :action => 'show', :id => '1'
1491 issue.reload
1617 issue.reload
1492 assert_equal new_subject, issue.subject
1618 assert_equal new_subject, issue.subject
1493 # Make sure custom fields were not cleared
1619 # Make sure custom fields were not cleared
1494 assert_equal '125', issue.custom_value_for(2).value
1620 assert_equal '125', issue.custom_value_for(2).value
1495
1621
1496 mail = ActionMailer::Base.deliveries.last
1622 mail = ActionMailer::Base.deliveries.last
1497 assert_kind_of TMail::Mail, mail
1623 assert_kind_of TMail::Mail, mail
1498 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
1624 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
1499 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
1625 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
1500 end
1626 end
1501
1627
1502 def test_put_update_with_custom_field_change
1628 def test_put_update_with_custom_field_change
1503 @request.session[:user_id] = 2
1629 @request.session[:user_id] = 2
1504 issue = Issue.find(1)
1630 issue = Issue.find(1)
1505 assert_equal '125', issue.custom_value_for(2).value
1631 assert_equal '125', issue.custom_value_for(2).value
1506
1632
1507 assert_difference('Journal.count') do
1633 assert_difference('Journal.count') do
1508 assert_difference('JournalDetail.count', 3) do
1634 assert_difference('JournalDetail.count', 3) do
1509 put :update, :id => 1, :issue => {:subject => 'Custom field change',
1635 put :update, :id => 1, :issue => {:subject => 'Custom field change',
1510 :priority_id => '6',
1636 :priority_id => '6',
1511 :category_id => '1', # no change
1637 :category_id => '1', # no change
1512 :custom_field_values => { '2' => 'New custom value' }
1638 :custom_field_values => { '2' => 'New custom value' }
1513 }
1639 }
1514 end
1640 end
1515 end
1641 end
1516 assert_redirected_to :action => 'show', :id => '1'
1642 assert_redirected_to :action => 'show', :id => '1'
1517 issue.reload
1643 issue.reload
1518 assert_equal 'New custom value', issue.custom_value_for(2).value
1644 assert_equal 'New custom value', issue.custom_value_for(2).value
1519
1645
1520 mail = ActionMailer::Base.deliveries.last
1646 mail = ActionMailer::Base.deliveries.last
1521 assert_kind_of TMail::Mail, mail
1647 assert_kind_of TMail::Mail, mail
1522 assert mail.body.include?("Searchable field changed from 125 to New custom value")
1648 assert mail.body.include?("Searchable field changed from 125 to New custom value")
1523 end
1649 end
1524
1650
1525 def test_put_update_with_status_and_assignee_change
1651 def test_put_update_with_status_and_assignee_change
1526 issue = Issue.find(1)
1652 issue = Issue.find(1)
1527 assert_equal 1, issue.status_id
1653 assert_equal 1, issue.status_id
1528 @request.session[:user_id] = 2
1654 @request.session[:user_id] = 2
1529 assert_difference('TimeEntry.count', 0) do
1655 assert_difference('TimeEntry.count', 0) do
1530 put :update,
1656 put :update,
1531 :id => 1,
1657 :id => 1,
1532 :issue => { :status_id => 2, :assigned_to_id => 3 },
1658 :issue => { :status_id => 2, :assigned_to_id => 3 },
1533 :notes => 'Assigned to dlopper',
1659 :notes => 'Assigned to dlopper',
1534 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
1660 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
1535 end
1661 end
1536 assert_redirected_to :action => 'show', :id => '1'
1662 assert_redirected_to :action => 'show', :id => '1'
1537 issue.reload
1663 issue.reload
1538 assert_equal 2, issue.status_id
1664 assert_equal 2, issue.status_id
1539 j = Journal.find(:first, :order => 'id DESC')
1665 j = Journal.find(:first, :order => 'id DESC')
1540 assert_equal 'Assigned to dlopper', j.notes
1666 assert_equal 'Assigned to dlopper', j.notes
1541 assert_equal 2, j.details.size
1667 assert_equal 2, j.details.size
1542
1668
1543 mail = ActionMailer::Base.deliveries.last
1669 mail = ActionMailer::Base.deliveries.last
1544 assert mail.body.include?("Status changed from New to Assigned")
1670 assert mail.body.include?("Status changed from New to Assigned")
1545 # subject should contain the new status
1671 # subject should contain the new status
1546 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
1672 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
1547 end
1673 end
1548
1674
1549 def test_put_update_with_note_only
1675 def test_put_update_with_note_only
1550 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
1676 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
1551 # anonymous user
1677 # anonymous user
1552 put :update,
1678 put :update,
1553 :id => 1,
1679 :id => 1,
1554 :notes => notes
1680 :notes => notes
1555 assert_redirected_to :action => 'show', :id => '1'
1681 assert_redirected_to :action => 'show', :id => '1'
1556 j = Journal.find(:first, :order => 'id DESC')
1682 j = Journal.find(:first, :order => 'id DESC')
1557 assert_equal notes, j.notes
1683 assert_equal notes, j.notes
1558 assert_equal 0, j.details.size
1684 assert_equal 0, j.details.size
1559 assert_equal User.anonymous, j.user
1685 assert_equal User.anonymous, j.user
1560
1686
1561 mail = ActionMailer::Base.deliveries.last
1687 mail = ActionMailer::Base.deliveries.last
1562 assert mail.body.include?(notes)
1688 assert mail.body.include?(notes)
1563 end
1689 end
1564
1690
1565 def test_put_update_with_note_and_spent_time
1691 def test_put_update_with_note_and_spent_time
1566 @request.session[:user_id] = 2
1692 @request.session[:user_id] = 2
1567 spent_hours_before = Issue.find(1).spent_hours
1693 spent_hours_before = Issue.find(1).spent_hours
1568 assert_difference('TimeEntry.count') do
1694 assert_difference('TimeEntry.count') do
1569 put :update,
1695 put :update,
1570 :id => 1,
1696 :id => 1,
1571 :notes => '2.5 hours added',
1697 :notes => '2.5 hours added',
1572 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
1698 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
1573 end
1699 end
1574 assert_redirected_to :action => 'show', :id => '1'
1700 assert_redirected_to :action => 'show', :id => '1'
1575
1701
1576 issue = Issue.find(1)
1702 issue = Issue.find(1)
1577
1703
1578 j = Journal.find(:first, :order => 'id DESC')
1704 j = Journal.find(:first, :order => 'id DESC')
1579 assert_equal '2.5 hours added', j.notes
1705 assert_equal '2.5 hours added', j.notes
1580 assert_equal 0, j.details.size
1706 assert_equal 0, j.details.size
1581
1707
1582 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
1708 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
1583 assert_not_nil t
1709 assert_not_nil t
1584 assert_equal 2.5, t.hours
1710 assert_equal 2.5, t.hours
1585 assert_equal spent_hours_before + 2.5, issue.spent_hours
1711 assert_equal spent_hours_before + 2.5, issue.spent_hours
1586 end
1712 end
1587
1713
1588 def test_put_update_with_attachment_only
1714 def test_put_update_with_attachment_only
1589 set_tmp_attachments_directory
1715 set_tmp_attachments_directory
1590
1716
1591 # Delete all fixtured journals, a race condition can occur causing the wrong
1717 # Delete all fixtured journals, a race condition can occur causing the wrong
1592 # journal to get fetched in the next find.
1718 # journal to get fetched in the next find.
1593 Journal.delete_all
1719 Journal.delete_all
1594
1720
1595 # anonymous user
1721 # anonymous user
1596 assert_difference 'Attachment.count' do
1722 assert_difference 'Attachment.count' do
1597 put :update, :id => 1,
1723 put :update, :id => 1,
1598 :notes => '',
1724 :notes => '',
1599 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
1725 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
1600 end
1726 end
1601
1727
1602 assert_redirected_to :action => 'show', :id => '1'
1728 assert_redirected_to :action => 'show', :id => '1'
1603 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
1729 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
1604 assert j.notes.blank?
1730 assert j.notes.blank?
1605 assert_equal 1, j.details.size
1731 assert_equal 1, j.details.size
1606 assert_equal 'testfile.txt', j.details.first.value
1732 assert_equal 'testfile.txt', j.details.first.value
1607 assert_equal User.anonymous, j.user
1733 assert_equal User.anonymous, j.user
1608
1734
1609 attachment = Attachment.first(:order => 'id DESC')
1735 attachment = Attachment.first(:order => 'id DESC')
1610 assert_equal Issue.find(1), attachment.container
1736 assert_equal Issue.find(1), attachment.container
1611 assert_equal User.anonymous, attachment.author
1737 assert_equal User.anonymous, attachment.author
1612 assert_equal 'testfile.txt', attachment.filename
1738 assert_equal 'testfile.txt', attachment.filename
1613 assert_equal 'text/plain', attachment.content_type
1739 assert_equal 'text/plain', attachment.content_type
1614 assert_equal 'test file', attachment.description
1740 assert_equal 'test file', attachment.description
1615 assert_equal 59, attachment.filesize
1741 assert_equal 59, attachment.filesize
1616 assert File.exists?(attachment.diskfile)
1742 assert File.exists?(attachment.diskfile)
1617 assert_equal 59, File.size(attachment.diskfile)
1743 assert_equal 59, File.size(attachment.diskfile)
1618
1744
1619 mail = ActionMailer::Base.deliveries.last
1745 mail = ActionMailer::Base.deliveries.last
1620 assert mail.body.include?('testfile.txt')
1746 assert mail.body.include?('testfile.txt')
1621 end
1747 end
1622
1748
1623 def test_put_update_with_attachment_that_fails_to_save
1749 def test_put_update_with_attachment_that_fails_to_save
1624 set_tmp_attachments_directory
1750 set_tmp_attachments_directory
1625
1751
1626 # Delete all fixtured journals, a race condition can occur causing the wrong
1752 # Delete all fixtured journals, a race condition can occur causing the wrong
1627 # journal to get fetched in the next find.
1753 # journal to get fetched in the next find.
1628 Journal.delete_all
1754 Journal.delete_all
1629
1755
1630 # Mock out the unsaved attachment
1756 # Mock out the unsaved attachment
1631 Attachment.any_instance.stubs(:create).returns(Attachment.new)
1757 Attachment.any_instance.stubs(:create).returns(Attachment.new)
1632
1758
1633 # anonymous user
1759 # anonymous user
1634 put :update,
1760 put :update,
1635 :id => 1,
1761 :id => 1,
1636 :notes => '',
1762 :notes => '',
1637 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
1763 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
1638 assert_redirected_to :action => 'show', :id => '1'
1764 assert_redirected_to :action => 'show', :id => '1'
1639 assert_equal '1 file(s) could not be saved.', flash[:warning]
1765 assert_equal '1 file(s) could not be saved.', flash[:warning]
1640
1766
1641 end if Object.const_defined?(:Mocha)
1767 end if Object.const_defined?(:Mocha)
1642
1768
1643 def test_put_update_with_no_change
1769 def test_put_update_with_no_change
1644 issue = Issue.find(1)
1770 issue = Issue.find(1)
1645 issue.journals.clear
1771 issue.journals.clear
1646 ActionMailer::Base.deliveries.clear
1772 ActionMailer::Base.deliveries.clear
1647
1773
1648 put :update,
1774 put :update,
1649 :id => 1,
1775 :id => 1,
1650 :notes => ''
1776 :notes => ''
1651 assert_redirected_to :action => 'show', :id => '1'
1777 assert_redirected_to :action => 'show', :id => '1'
1652
1778
1653 issue.reload
1779 issue.reload
1654 assert issue.journals.empty?
1780 assert issue.journals.empty?
1655 # No email should be sent
1781 # No email should be sent
1656 assert ActionMailer::Base.deliveries.empty?
1782 assert ActionMailer::Base.deliveries.empty?
1657 end
1783 end
1658
1784
1659 def test_put_update_should_send_a_notification
1785 def test_put_update_should_send_a_notification
1660 @request.session[:user_id] = 2
1786 @request.session[:user_id] = 2
1661 ActionMailer::Base.deliveries.clear
1787 ActionMailer::Base.deliveries.clear
1662 issue = Issue.find(1)
1788 issue = Issue.find(1)
1663 old_subject = issue.subject
1789 old_subject = issue.subject
1664 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
1790 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
1665
1791
1666 put :update, :id => 1, :issue => {:subject => new_subject,
1792 put :update, :id => 1, :issue => {:subject => new_subject,
1667 :priority_id => '6',
1793 :priority_id => '6',
1668 :category_id => '1' # no change
1794 :category_id => '1' # no change
1669 }
1795 }
1670 assert_equal 1, ActionMailer::Base.deliveries.size
1796 assert_equal 1, ActionMailer::Base.deliveries.size
1671 end
1797 end
1672
1798
1673 def test_put_update_with_invalid_spent_time_hours_only
1799 def test_put_update_with_invalid_spent_time_hours_only
1674 @request.session[:user_id] = 2
1800 @request.session[:user_id] = 2
1675 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
1801 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
1676
1802
1677 assert_no_difference('Journal.count') do
1803 assert_no_difference('Journal.count') do
1678 put :update,
1804 put :update,
1679 :id => 1,
1805 :id => 1,
1680 :notes => notes,
1806 :notes => notes,
1681 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
1807 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
1682 end
1808 end
1683 assert_response :success
1809 assert_response :success
1684 assert_template 'edit'
1810 assert_template 'edit'
1685
1811
1686 assert_error_tag :descendant => {:content => /Activity can't be blank/}
1812 assert_error_tag :descendant => {:content => /Activity can't be blank/}
1687 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
1813 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
1688 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
1814 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
1689 end
1815 end
1690
1816
1691 def test_put_update_with_invalid_spent_time_comments_only
1817 def test_put_update_with_invalid_spent_time_comments_only
1692 @request.session[:user_id] = 2
1818 @request.session[:user_id] = 2
1693 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
1819 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
1694
1820
1695 assert_no_difference('Journal.count') do
1821 assert_no_difference('Journal.count') do
1696 put :update,
1822 put :update,
1697 :id => 1,
1823 :id => 1,
1698 :notes => notes,
1824 :notes => notes,
1699 :time_entry => {"comments"=>"this is my comment", "activity_id"=>"", "hours"=>""}
1825 :time_entry => {"comments"=>"this is my comment", "activity_id"=>"", "hours"=>""}
1700 end
1826 end
1701 assert_response :success
1827 assert_response :success
1702 assert_template 'edit'
1828 assert_template 'edit'
1703
1829
1704 assert_error_tag :descendant => {:content => /Activity can't be blank/}
1830 assert_error_tag :descendant => {:content => /Activity can't be blank/}
1705 assert_error_tag :descendant => {:content => /Hours can't be blank/}
1831 assert_error_tag :descendant => {:content => /Hours can't be blank/}
1706 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
1832 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
1707 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => "this is my comment" }
1833 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => "this is my comment" }
1708 end
1834 end
1709
1835
1710 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
1836 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
1711 issue = Issue.find(2)
1837 issue = Issue.find(2)
1712 @request.session[:user_id] = 2
1838 @request.session[:user_id] = 2
1713
1839
1714 put :update,
1840 put :update,
1715 :id => issue.id,
1841 :id => issue.id,
1716 :issue => {
1842 :issue => {
1717 :fixed_version_id => 4
1843 :fixed_version_id => 4
1718 }
1844 }
1719
1845
1720 assert_response :redirect
1846 assert_response :redirect
1721 issue.reload
1847 issue.reload
1722 assert_equal 4, issue.fixed_version_id
1848 assert_equal 4, issue.fixed_version_id
1723 assert_not_equal issue.project_id, issue.fixed_version.project_id
1849 assert_not_equal issue.project_id, issue.fixed_version.project_id
1724 end
1850 end
1725
1851
1726 def test_put_update_should_redirect_back_using_the_back_url_parameter
1852 def test_put_update_should_redirect_back_using_the_back_url_parameter
1727 issue = Issue.find(2)
1853 issue = Issue.find(2)
1728 @request.session[:user_id] = 2
1854 @request.session[:user_id] = 2
1729
1855
1730 put :update,
1856 put :update,
1731 :id => issue.id,
1857 :id => issue.id,
1732 :issue => {
1858 :issue => {
1733 :fixed_version_id => 4
1859 :fixed_version_id => 4
1734 },
1860 },
1735 :back_url => '/issues'
1861 :back_url => '/issues'
1736
1862
1737 assert_response :redirect
1863 assert_response :redirect
1738 assert_redirected_to '/issues'
1864 assert_redirected_to '/issues'
1739 end
1865 end
1740
1866
1741 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
1867 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
1742 issue = Issue.find(2)
1868 issue = Issue.find(2)
1743 @request.session[:user_id] = 2
1869 @request.session[:user_id] = 2
1744
1870
1745 put :update,
1871 put :update,
1746 :id => issue.id,
1872 :id => issue.id,
1747 :issue => {
1873 :issue => {
1748 :fixed_version_id => 4
1874 :fixed_version_id => 4
1749 },
1875 },
1750 :back_url => 'http://google.com'
1876 :back_url => 'http://google.com'
1751
1877
1752 assert_response :redirect
1878 assert_response :redirect
1753 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
1879 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
1754 end
1880 end
1755
1881
1756 def test_get_bulk_edit
1882 def test_get_bulk_edit
1757 @request.session[:user_id] = 2
1883 @request.session[:user_id] = 2
1758 get :bulk_edit, :ids => [1, 2]
1884 get :bulk_edit, :ids => [1, 2]
1759 assert_response :success
1885 assert_response :success
1760 assert_template 'bulk_edit'
1886 assert_template 'bulk_edit'
1761
1887
1762 assert_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
1888 assert_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
1763
1889
1764 # Project specific custom field, date type
1890 # Project specific custom field, date type
1765 field = CustomField.find(9)
1891 field = CustomField.find(9)
1766 assert !field.is_for_all?
1892 assert !field.is_for_all?
1767 assert_equal 'date', field.field_format
1893 assert_equal 'date', field.field_format
1768 assert_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
1894 assert_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
1769
1895
1770 # System wide custom field
1896 # System wide custom field
1771 assert CustomField.find(1).is_for_all?
1897 assert CustomField.find(1).is_for_all?
1772 assert_tag :select, :attributes => {:name => 'issue[custom_field_values][1]'}
1898 assert_tag :select, :attributes => {:name => 'issue[custom_field_values][1]'}
1773
1899
1774 # Be sure we don't display inactive IssuePriorities
1900 # Be sure we don't display inactive IssuePriorities
1775 assert ! IssuePriority.find(15).active?
1901 assert ! IssuePriority.find(15).active?
1776 assert_no_tag :option, :attributes => {:value => '15'},
1902 assert_no_tag :option, :attributes => {:value => '15'},
1777 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1903 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1778 end
1904 end
1779
1905
1780 def test_get_bulk_edit_on_different_projects
1906 def test_get_bulk_edit_on_different_projects
1781 @request.session[:user_id] = 2
1907 @request.session[:user_id] = 2
1782 get :bulk_edit, :ids => [1, 2, 6]
1908 get :bulk_edit, :ids => [1, 2, 6]
1783 assert_response :success
1909 assert_response :success
1784 assert_template 'bulk_edit'
1910 assert_template 'bulk_edit'
1785
1911
1786 # Can not set issues from different projects as children of an issue
1912 # Can not set issues from different projects as children of an issue
1787 assert_no_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
1913 assert_no_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
1788
1914
1789 # Project specific custom field, date type
1915 # Project specific custom field, date type
1790 field = CustomField.find(9)
1916 field = CustomField.find(9)
1791 assert !field.is_for_all?
1917 assert !field.is_for_all?
1792 assert !field.project_ids.include?(Issue.find(6).project_id)
1918 assert !field.project_ids.include?(Issue.find(6).project_id)
1793 assert_no_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
1919 assert_no_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
1794 end
1920 end
1795
1921
1796 def test_get_bulk_edit_with_user_custom_field
1922 def test_get_bulk_edit_with_user_custom_field
1797 field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true)
1923 field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true)
1798
1924
1799 @request.session[:user_id] = 2
1925 @request.session[:user_id] = 2
1800 get :bulk_edit, :ids => [1, 2]
1926 get :bulk_edit, :ids => [1, 2]
1801 assert_response :success
1927 assert_response :success
1802 assert_template 'bulk_edit'
1928 assert_template 'bulk_edit'
1803
1929
1804 assert_tag :select,
1930 assert_tag :select,
1805 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
1931 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
1806 :children => {
1932 :children => {
1807 :only => {:tag => 'option'},
1933 :only => {:tag => 'option'},
1808 :count => Project.find(1).users.count + 1
1934 :count => Project.find(1).users.count + 1
1809 }
1935 }
1810 end
1936 end
1811
1937
1812 def test_get_bulk_edit_with_version_custom_field
1938 def test_get_bulk_edit_with_version_custom_field
1813 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true)
1939 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true)
1814
1940
1815 @request.session[:user_id] = 2
1941 @request.session[:user_id] = 2
1816 get :bulk_edit, :ids => [1, 2]
1942 get :bulk_edit, :ids => [1, 2]
1817 assert_response :success
1943 assert_response :success
1818 assert_template 'bulk_edit'
1944 assert_template 'bulk_edit'
1819
1945
1820 assert_tag :select,
1946 assert_tag :select,
1821 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
1947 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
1822 :children => {
1948 :children => {
1823 :only => {:tag => 'option'},
1949 :only => {:tag => 'option'},
1824 :count => Project.find(1).shared_versions.count + 1
1950 :count => Project.find(1).shared_versions.count + 1
1825 }
1951 }
1826 end
1952 end
1827
1953
1828 def test_bulk_update
1954 def test_bulk_update
1829 @request.session[:user_id] = 2
1955 @request.session[:user_id] = 2
1830 # update issues priority
1956 # update issues priority
1831 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
1957 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
1832 :issue => {:priority_id => 7,
1958 :issue => {:priority_id => 7,
1833 :assigned_to_id => '',
1959 :assigned_to_id => '',
1834 :custom_field_values => {'2' => ''}}
1960 :custom_field_values => {'2' => ''}}
1835
1961
1836 assert_response 302
1962 assert_response 302
1837 # check that the issues were updated
1963 # check that the issues were updated
1838 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
1964 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
1839
1965
1840 issue = Issue.find(1)
1966 issue = Issue.find(1)
1841 journal = issue.journals.find(:first, :order => 'created_on DESC')
1967 journal = issue.journals.find(:first, :order => 'created_on DESC')
1842 assert_equal '125', issue.custom_value_for(2).value
1968 assert_equal '125', issue.custom_value_for(2).value
1843 assert_equal 'Bulk editing', journal.notes
1969 assert_equal 'Bulk editing', journal.notes
1844 assert_equal 1, journal.details.size
1970 assert_equal 1, journal.details.size
1845 end
1971 end
1846
1972
1847 def test_bulk_update_with_group_assignee
1973 def test_bulk_update_with_group_assignee
1848 group = Group.find(11)
1974 group = Group.find(11)
1849 project = Project.find(1)
1975 project = Project.find(1)
1850 project.members << Member.new(:principal => group, :roles => [Role.first])
1976 project.members << Member.new(:principal => group, :roles => [Role.first])
1851
1977
1852 @request.session[:user_id] = 2
1978 @request.session[:user_id] = 2
1853 # update issues assignee
1979 # update issues assignee
1854 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
1980 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
1855 :issue => {:priority_id => '',
1981 :issue => {:priority_id => '',
1856 :assigned_to_id => group.id,
1982 :assigned_to_id => group.id,
1857 :custom_field_values => {'2' => ''}}
1983 :custom_field_values => {'2' => ''}}
1858
1984
1859 assert_response 302
1985 assert_response 302
1860 assert_equal [group, group], Issue.find_all_by_id([1, 2]).collect {|i| i.assigned_to}
1986 assert_equal [group, group], Issue.find_all_by_id([1, 2]).collect {|i| i.assigned_to}
1861 end
1987 end
1862
1988
1863 def test_bulk_update_on_different_projects
1989 def test_bulk_update_on_different_projects
1864 @request.session[:user_id] = 2
1990 @request.session[:user_id] = 2
1865 # update issues priority
1991 # update issues priority
1866 post :bulk_update, :ids => [1, 2, 6], :notes => 'Bulk editing',
1992 post :bulk_update, :ids => [1, 2, 6], :notes => 'Bulk editing',
1867 :issue => {:priority_id => 7,
1993 :issue => {:priority_id => 7,
1868 :assigned_to_id => '',
1994 :assigned_to_id => '',
1869 :custom_field_values => {'2' => ''}}
1995 :custom_field_values => {'2' => ''}}
1870
1996
1871 assert_response 302
1997 assert_response 302
1872 # check that the issues were updated
1998 # check that the issues were updated
1873 assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
1999 assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
1874
2000
1875 issue = Issue.find(1)
2001 issue = Issue.find(1)
1876 journal = issue.journals.find(:first, :order => 'created_on DESC')
2002 journal = issue.journals.find(:first, :order => 'created_on DESC')
1877 assert_equal '125', issue.custom_value_for(2).value
2003 assert_equal '125', issue.custom_value_for(2).value
1878 assert_equal 'Bulk editing', journal.notes
2004 assert_equal 'Bulk editing', journal.notes
1879 assert_equal 1, journal.details.size
2005 assert_equal 1, journal.details.size
1880 end
2006 end
1881
2007
1882 def test_bulk_update_on_different_projects_without_rights
2008 def test_bulk_update_on_different_projects_without_rights
1883 @request.session[:user_id] = 3
2009 @request.session[:user_id] = 3
1884 user = User.find(3)
2010 user = User.find(3)
1885 action = { :controller => "issues", :action => "bulk_update" }
2011 action = { :controller => "issues", :action => "bulk_update" }
1886 assert user.allowed_to?(action, Issue.find(1).project)
2012 assert user.allowed_to?(action, Issue.find(1).project)
1887 assert ! user.allowed_to?(action, Issue.find(6).project)
2013 assert ! user.allowed_to?(action, Issue.find(6).project)
1888 post :bulk_update, :ids => [1, 6], :notes => 'Bulk should fail',
2014 post :bulk_update, :ids => [1, 6], :notes => 'Bulk should fail',
1889 :issue => {:priority_id => 7,
2015 :issue => {:priority_id => 7,
1890 :assigned_to_id => '',
2016 :assigned_to_id => '',
1891 :custom_field_values => {'2' => ''}}
2017 :custom_field_values => {'2' => ''}}
1892 assert_response 403
2018 assert_response 403
1893 assert_not_equal "Bulk should fail", Journal.last.notes
2019 assert_not_equal "Bulk should fail", Journal.last.notes
1894 end
2020 end
1895
2021
1896 def test_bullk_update_should_send_a_notification
2022 def test_bullk_update_should_send_a_notification
1897 @request.session[:user_id] = 2
2023 @request.session[:user_id] = 2
1898 ActionMailer::Base.deliveries.clear
2024 ActionMailer::Base.deliveries.clear
1899 post(:bulk_update,
2025 post(:bulk_update,
1900 {
2026 {
1901 :ids => [1, 2],
2027 :ids => [1, 2],
1902 :notes => 'Bulk editing',
2028 :notes => 'Bulk editing',
1903 :issue => {
2029 :issue => {
1904 :priority_id => 7,
2030 :priority_id => 7,
1905 :assigned_to_id => '',
2031 :assigned_to_id => '',
1906 :custom_field_values => {'2' => ''}
2032 :custom_field_values => {'2' => ''}
1907 }
2033 }
1908 })
2034 })
1909
2035
1910 assert_response 302
2036 assert_response 302
1911 assert_equal 2, ActionMailer::Base.deliveries.size
2037 assert_equal 2, ActionMailer::Base.deliveries.size
1912 end
2038 end
1913
2039
1914 def test_bulk_update_status
2040 def test_bulk_update_status
1915 @request.session[:user_id] = 2
2041 @request.session[:user_id] = 2
1916 # update issues priority
2042 # update issues priority
1917 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
2043 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
1918 :issue => {:priority_id => '',
2044 :issue => {:priority_id => '',
1919 :assigned_to_id => '',
2045 :assigned_to_id => '',
1920 :status_id => '5'}
2046 :status_id => '5'}
1921
2047
1922 assert_response 302
2048 assert_response 302
1923 issue = Issue.find(1)
2049 issue = Issue.find(1)
1924 assert issue.closed?
2050 assert issue.closed?
1925 end
2051 end
1926
2052
1927 def test_bulk_update_parent_id
2053 def test_bulk_update_parent_id
1928 @request.session[:user_id] = 2
2054 @request.session[:user_id] = 2
1929 post :bulk_update, :ids => [1, 3],
2055 post :bulk_update, :ids => [1, 3],
1930 :notes => 'Bulk editing parent',
2056 :notes => 'Bulk editing parent',
1931 :issue => {:priority_id => '', :assigned_to_id => '', :status_id => '', :parent_issue_id => '2'}
2057 :issue => {:priority_id => '', :assigned_to_id => '', :status_id => '', :parent_issue_id => '2'}
1932
2058
1933 assert_response 302
2059 assert_response 302
1934 parent = Issue.find(2)
2060 parent = Issue.find(2)
1935 assert_equal parent.id, Issue.find(1).parent_id
2061 assert_equal parent.id, Issue.find(1).parent_id
1936 assert_equal parent.id, Issue.find(3).parent_id
2062 assert_equal parent.id, Issue.find(3).parent_id
1937 assert_equal [1, 3], parent.children.collect(&:id).sort
2063 assert_equal [1, 3], parent.children.collect(&:id).sort
1938 end
2064 end
1939
2065
1940 def test_bulk_update_custom_field
2066 def test_bulk_update_custom_field
1941 @request.session[:user_id] = 2
2067 @request.session[:user_id] = 2
1942 # update issues priority
2068 # update issues priority
1943 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
2069 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
1944 :issue => {:priority_id => '',
2070 :issue => {:priority_id => '',
1945 :assigned_to_id => '',
2071 :assigned_to_id => '',
1946 :custom_field_values => {'2' => '777'}}
2072 :custom_field_values => {'2' => '777'}}
1947
2073
1948 assert_response 302
2074 assert_response 302
1949
2075
1950 issue = Issue.find(1)
2076 issue = Issue.find(1)
1951 journal = issue.journals.find(:first, :order => 'created_on DESC')
2077 journal = issue.journals.find(:first, :order => 'created_on DESC')
1952 assert_equal '777', issue.custom_value_for(2).value
2078 assert_equal '777', issue.custom_value_for(2).value
1953 assert_equal 1, journal.details.size
2079 assert_equal 1, journal.details.size
1954 assert_equal '125', journal.details.first.old_value
2080 assert_equal '125', journal.details.first.old_value
1955 assert_equal '777', journal.details.first.value
2081 assert_equal '777', journal.details.first.value
1956 end
2082 end
1957
2083
1958 def test_bulk_update_unassign
2084 def test_bulk_update_unassign
1959 assert_not_nil Issue.find(2).assigned_to
2085 assert_not_nil Issue.find(2).assigned_to
1960 @request.session[:user_id] = 2
2086 @request.session[:user_id] = 2
1961 # unassign issues
2087 # unassign issues
1962 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
2088 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
1963 assert_response 302
2089 assert_response 302
1964 # check that the issues were updated
2090 # check that the issues were updated
1965 assert_nil Issue.find(2).assigned_to
2091 assert_nil Issue.find(2).assigned_to
1966 end
2092 end
1967
2093
1968 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
2094 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
1969 @request.session[:user_id] = 2
2095 @request.session[:user_id] = 2
1970
2096
1971 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
2097 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
1972
2098
1973 assert_response :redirect
2099 assert_response :redirect
1974 issues = Issue.find([1,2])
2100 issues = Issue.find([1,2])
1975 issues.each do |issue|
2101 issues.each do |issue|
1976 assert_equal 4, issue.fixed_version_id
2102 assert_equal 4, issue.fixed_version_id
1977 assert_not_equal issue.project_id, issue.fixed_version.project_id
2103 assert_not_equal issue.project_id, issue.fixed_version.project_id
1978 end
2104 end
1979 end
2105 end
1980
2106
1981 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
2107 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
1982 @request.session[:user_id] = 2
2108 @request.session[:user_id] = 2
1983 post :bulk_update, :ids => [1,2], :back_url => '/issues'
2109 post :bulk_update, :ids => [1,2], :back_url => '/issues'
1984
2110
1985 assert_response :redirect
2111 assert_response :redirect
1986 assert_redirected_to '/issues'
2112 assert_redirected_to '/issues'
1987 end
2113 end
1988
2114
1989 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
2115 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
1990 @request.session[:user_id] = 2
2116 @request.session[:user_id] = 2
1991 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
2117 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
1992
2118
1993 assert_response :redirect
2119 assert_response :redirect
1994 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
2120 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
1995 end
2121 end
1996
2122
1997 def test_destroy_issue_with_no_time_entries
2123 def test_destroy_issue_with_no_time_entries
1998 assert_nil TimeEntry.find_by_issue_id(2)
2124 assert_nil TimeEntry.find_by_issue_id(2)
1999 @request.session[:user_id] = 2
2125 @request.session[:user_id] = 2
2000 delete :destroy, :id => 2
2126 delete :destroy, :id => 2
2001 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
2127 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
2002 assert_nil Issue.find_by_id(2)
2128 assert_nil Issue.find_by_id(2)
2003 end
2129 end
2004
2130
2005 def test_destroy_issues_with_time_entries
2131 def test_destroy_issues_with_time_entries
2006 @request.session[:user_id] = 2
2132 @request.session[:user_id] = 2
2007 delete :destroy, :ids => [1, 3]
2133 delete :destroy, :ids => [1, 3]
2008 assert_response :success
2134 assert_response :success
2009 assert_template 'destroy'
2135 assert_template 'destroy'
2010 assert_not_nil assigns(:hours)
2136 assert_not_nil assigns(:hours)
2011 assert Issue.find_by_id(1) && Issue.find_by_id(3)
2137 assert Issue.find_by_id(1) && Issue.find_by_id(3)
2012 end
2138 end
2013
2139
2014 def test_destroy_issues_and_destroy_time_entries
2140 def test_destroy_issues_and_destroy_time_entries
2015 @request.session[:user_id] = 2
2141 @request.session[:user_id] = 2
2016 delete :destroy, :ids => [1, 3], :todo => 'destroy'
2142 delete :destroy, :ids => [1, 3], :todo => 'destroy'
2017 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
2143 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
2018 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
2144 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
2019 assert_nil TimeEntry.find_by_id([1, 2])
2145 assert_nil TimeEntry.find_by_id([1, 2])
2020 end
2146 end
2021
2147
2022 def test_destroy_issues_and_assign_time_entries_to_project
2148 def test_destroy_issues_and_assign_time_entries_to_project
2023 @request.session[:user_id] = 2
2149 @request.session[:user_id] = 2
2024 delete :destroy, :ids => [1, 3], :todo => 'nullify'
2150 delete :destroy, :ids => [1, 3], :todo => 'nullify'
2025 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
2151 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
2026 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
2152 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
2027 assert_nil TimeEntry.find(1).issue_id
2153 assert_nil TimeEntry.find(1).issue_id
2028 assert_nil TimeEntry.find(2).issue_id
2154 assert_nil TimeEntry.find(2).issue_id
2029 end
2155 end
2030
2156
2031 def test_destroy_issues_and_reassign_time_entries_to_another_issue
2157 def test_destroy_issues_and_reassign_time_entries_to_another_issue
2032 @request.session[:user_id] = 2
2158 @request.session[:user_id] = 2
2033 delete :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
2159 delete :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
2034 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
2160 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
2035 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
2161 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
2036 assert_equal 2, TimeEntry.find(1).issue_id
2162 assert_equal 2, TimeEntry.find(1).issue_id
2037 assert_equal 2, TimeEntry.find(2).issue_id
2163 assert_equal 2, TimeEntry.find(2).issue_id
2038 end
2164 end
2039
2165
2040 def test_destroy_issues_from_different_projects
2166 def test_destroy_issues_from_different_projects
2041 @request.session[:user_id] = 2
2167 @request.session[:user_id] = 2
2042 delete :destroy, :ids => [1, 2, 6], :todo => 'destroy'
2168 delete :destroy, :ids => [1, 2, 6], :todo => 'destroy'
2043 assert_redirected_to :controller => 'issues', :action => 'index'
2169 assert_redirected_to :controller => 'issues', :action => 'index'
2044 assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6))
2170 assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6))
2045 end
2171 end
2046
2172
2047 def test_destroy_parent_and_child_issues
2173 def test_destroy_parent_and_child_issues
2048 parent = Issue.generate!(:project_id => 1, :tracker_id => 1)
2174 parent = Issue.generate!(:project_id => 1, :tracker_id => 1)
2049 child = Issue.generate!(:project_id => 1, :tracker_id => 1, :parent_issue_id => parent.id)
2175 child = Issue.generate!(:project_id => 1, :tracker_id => 1, :parent_issue_id => parent.id)
2050 assert child.is_descendant_of?(parent.reload)
2176 assert child.is_descendant_of?(parent.reload)
2051
2177
2052 @request.session[:user_id] = 2
2178 @request.session[:user_id] = 2
2053 assert_difference 'Issue.count', -2 do
2179 assert_difference 'Issue.count', -2 do
2054 delete :destroy, :ids => [parent.id, child.id], :todo => 'destroy'
2180 delete :destroy, :ids => [parent.id, child.id], :todo => 'destroy'
2055 end
2181 end
2056 assert_response 302
2182 assert_response 302
2057 end
2183 end
2058
2184
2059 def test_default_search_scope
2185 def test_default_search_scope
2060 get :index
2186 get :index
2061 assert_tag :div, :attributes => {:id => 'quick-search'},
2187 assert_tag :div, :attributes => {:id => 'quick-search'},
2062 :child => {:tag => 'form',
2188 :child => {:tag => 'form',
2063 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
2189 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
2064 end
2190 end
2065 end
2191 end
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now