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