##// END OF EJS Templates
Fix nil method error when no issue params are submitted. #5123...
Eric Davis -
r3682:e7790bb6b5a9
parent child
Show More
@@ -1,541 +1,541
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2008 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
20 20 default_search_scope :issues
21 21
22 22 before_filter :find_issue, :only => [:show, :edit, :reply]
23 23 before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
24 24 before_filter :find_project, :only => [:new, :update_form, :preview]
25 25 before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :context_menu]
26 26 before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar]
27 27 accept_key_auth :index, :show, :changes
28 28
29 29 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
30 30
31 31 helper :journals
32 32 helper :projects
33 33 include ProjectsHelper
34 34 helper :custom_fields
35 35 include CustomFieldsHelper
36 36 helper :issue_relations
37 37 include IssueRelationsHelper
38 38 helper :watchers
39 39 include WatchersHelper
40 40 helper :attachments
41 41 include AttachmentsHelper
42 42 helper :queries
43 43 include QueriesHelper
44 44 helper :sort
45 45 include SortHelper
46 46 include IssuesHelper
47 47 helper :timelog
48 48 include Redmine::Export::PDF
49 49
50 50 verify :method => :post,
51 51 :only => :destroy,
52 52 :render => { :nothing => true, :status => :method_not_allowed }
53 53
54 54 def index
55 55 retrieve_query
56 56 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
57 57 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.available_columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
58 58
59 59 if @query.valid?
60 60 limit = case params[:format]
61 61 when 'csv', 'pdf'
62 62 Setting.issues_export_limit.to_i
63 63 when 'atom'
64 64 Setting.feeds_limit.to_i
65 65 else
66 66 per_page_option
67 67 end
68 68
69 69 @issue_count = @query.issue_count
70 70 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
71 71 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
72 72 :order => sort_clause,
73 73 :offset => @issue_pages.current.offset,
74 74 :limit => limit)
75 75 @issue_count_by_group = @query.issue_count_by_group
76 76
77 77 respond_to do |format|
78 78 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
79 79 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
80 80 format.csv { send_data(issues_to_csv(@issues, @project), :type => 'text/csv; header=present', :filename => 'export.csv') }
81 81 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
82 82 end
83 83 else
84 84 # Send html if the query is not valid
85 85 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
86 86 end
87 87 rescue ActiveRecord::RecordNotFound
88 88 render_404
89 89 end
90 90
91 91 def changes
92 92 retrieve_query
93 93 sort_init 'id', 'desc'
94 94 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.available_columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
95 95
96 96 if @query.valid?
97 97 @journals = @query.journals(:order => "#{Journal.table_name}.created_on DESC",
98 98 :limit => 25)
99 99 end
100 100 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
101 101 render :layout => false, :content_type => 'application/atom+xml'
102 102 rescue ActiveRecord::RecordNotFound
103 103 render_404
104 104 end
105 105
106 106 def show
107 107 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
108 108 @journals.each_with_index {|j,i| j.indice = i+1}
109 109 @journals.reverse! if User.current.wants_comments_in_reverse_order?
110 110 @changesets = @issue.changesets.visible.all
111 111 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
112 112 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
113 113 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
114 114 @priorities = IssuePriority.all
115 115 @time_entry = TimeEntry.new
116 116 respond_to do |format|
117 117 format.html { render :template => 'issues/show.rhtml' }
118 118 format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
119 119 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
120 120 end
121 121 end
122 122
123 123 # Add a new issue
124 124 # The new issue will be created from an existing one if copy_from parameter is given
125 125 def new
126 126 @issue = Issue.new
127 127 @issue.copy_from(params[:copy_from]) if params[:copy_from]
128 128 @issue.project = @project
129 129 # Tracker must be set before custom field values
130 130 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
131 131 if @issue.tracker.nil?
132 132 render_error l(:error_no_tracker_in_project)
133 133 return
134 134 end
135 135 if params[:issue].is_a?(Hash)
136 136 @issue.attributes = params[:issue]
137 137 @issue.watcher_user_ids = params[:issue]['watcher_user_ids'] if User.current.allowed_to?(:add_issue_watchers, @project)
138 138 end
139 139 @issue.author = User.current
140 140
141 141 default_status = IssueStatus.default
142 142 unless default_status
143 143 render_error l(:error_no_default_issue_status)
144 144 return
145 145 end
146 146 @issue.status = default_status
147 147 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.roles_for_project(@project), @issue.tracker)).uniq
148 148
149 149 if request.get? || request.xhr?
150 150 @issue.start_date ||= Date.today
151 151 else
152 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
152 requested_status = IssueStatus.find_by_id(params[:issue][:status_id]) if params[:issue]
153 153 # Check that the user is allowed to apply the requested status
154 154 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
155 155 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
156 156 if @issue.save
157 157 attach_files(@issue, params[:attachments])
158 158 flash[:notice] = l(:notice_successful_create)
159 159 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
160 160 redirect_to(params[:continue] ? { :action => 'new', :tracker_id => @issue.tracker } :
161 161 { :action => 'show', :id => @issue })
162 162 return
163 163 end
164 164 end
165 165 @priorities = IssuePriority.all
166 166 render :layout => !request.xhr?
167 167 end
168 168
169 169 # Attributes that can be updated on workflow transition (without :edit permission)
170 170 # TODO: make it configurable (at least per role)
171 171 UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
172 172
173 173 def edit
174 174 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
175 175 @priorities = IssuePriority.all
176 176 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
177 177 @time_entry = TimeEntry.new
178 178
179 179 @notes = params[:notes]
180 180 journal = @issue.init_journal(User.current, @notes)
181 181 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
182 182 if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
183 183 attrs = params[:issue].dup
184 184 attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
185 185 attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
186 186 @issue.attributes = attrs
187 187 end
188 188
189 189 if request.post?
190 190 @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
191 191 @time_entry.attributes = params[:time_entry]
192 192 if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.valid?
193 193 attachments = attach_files(@issue, params[:attachments])
194 194 attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
195 195 call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
196 196 if @issue.save
197 197 # Log spend time
198 198 if User.current.allowed_to?(:log_time, @project)
199 199 @time_entry.save
200 200 end
201 201 if !journal.new_record?
202 202 # Only send notification if something was actually changed
203 203 flash[:notice] = l(:notice_successful_update)
204 204 end
205 205 call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
206 206 redirect_back_or_default({:action => 'show', :id => @issue})
207 207 end
208 208 end
209 209 end
210 210 rescue ActiveRecord::StaleObjectError
211 211 # Optimistic locking exception
212 212 flash.now[:error] = l(:notice_locking_conflict)
213 213 # Remove the previously added attachments if issue was not updated
214 214 attachments.each(&:destroy)
215 215 end
216 216
217 217 def reply
218 218 journal = Journal.find(params[:journal_id]) if params[:journal_id]
219 219 if journal
220 220 user = journal.user
221 221 text = journal.notes
222 222 else
223 223 user = @issue.author
224 224 text = @issue.description
225 225 end
226 226 # Replaces pre blocks with [...]
227 227 text = text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]')
228 228 content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> "
229 229 content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
230 230
231 231 render(:update) { |page|
232 232 page.<< "$('notes').value = \"#{escape_javascript content}\";"
233 233 page.show 'update'
234 234 page << "Form.Element.focus('notes');"
235 235 page << "Element.scrollTo('update');"
236 236 page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
237 237 }
238 238 end
239 239
240 240 # Bulk edit a set of issues
241 241 def bulk_edit
242 242 if request.post?
243 243 tracker = params[:tracker_id].blank? ? nil : @project.trackers.find_by_id(params[:tracker_id])
244 244 status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
245 245 priority = params[:priority_id].blank? ? nil : IssuePriority.find_by_id(params[:priority_id])
246 246 assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id])
247 247 category = (params[:category_id].blank? || params[:category_id] == 'none') ? nil : @project.issue_categories.find_by_id(params[:category_id])
248 248 fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.shared_versions.find_by_id(params[:fixed_version_id])
249 249 custom_field_values = params[:custom_field_values] ? params[:custom_field_values].reject {|k,v| v.blank?} : nil
250 250
251 251 unsaved_issue_ids = []
252 252 @issues.each do |issue|
253 253 journal = issue.init_journal(User.current, params[:notes])
254 254 issue.tracker = tracker if tracker
255 255 issue.priority = priority if priority
256 256 issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
257 257 issue.category = category if category || params[:category_id] == 'none'
258 258 issue.fixed_version = fixed_version if fixed_version || params[:fixed_version_id] == 'none'
259 259 issue.start_date = params[:start_date] unless params[:start_date].blank?
260 260 issue.due_date = params[:due_date] unless params[:due_date].blank?
261 261 issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
262 262 issue.custom_field_values = custom_field_values if custom_field_values && !custom_field_values.empty?
263 263 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
264 264 # Don't save any change to the issue if the user is not authorized to apply the requested status
265 265 unless (status.nil? || (issue.new_statuses_allowed_to(User.current).include?(status) && issue.status = status)) && issue.save
266 266 # Keep unsaved issue ids to display them in flash error
267 267 unsaved_issue_ids << issue.id
268 268 end
269 269 end
270 270 if unsaved_issue_ids.empty?
271 271 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
272 272 else
273 273 flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
274 274 :total => @issues.size,
275 275 :ids => '#' + unsaved_issue_ids.join(', #'))
276 276 end
277 277 redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
278 278 return
279 279 end
280 280 @available_statuses = Workflow.available_statuses(@project)
281 281 @custom_fields = @project.all_issue_custom_fields
282 282 end
283 283
284 284 def move
285 285 @copy = params[:copy_options] && params[:copy_options][:copy]
286 286 @allowed_projects = Issue.allowed_target_projects_on_move
287 287 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
288 288 @target_project ||= @project
289 289 @trackers = @target_project.trackers
290 290 @available_statuses = Workflow.available_statuses(@project)
291 291 if request.post?
292 292 new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
293 293 unsaved_issue_ids = []
294 294 moved_issues = []
295 295 @issues.each do |issue|
296 296 changed_attributes = {}
297 297 [:assigned_to_id, :status_id, :start_date, :due_date].each do |valid_attribute|
298 298 unless params[valid_attribute].blank?
299 299 changed_attributes[valid_attribute] = (params[valid_attribute] == 'none' ? nil : params[valid_attribute])
300 300 end
301 301 end
302 302 issue.init_journal(User.current)
303 303 if r = issue.move_to(@target_project, new_tracker, {:copy => @copy, :attributes => changed_attributes})
304 304 moved_issues << r
305 305 else
306 306 unsaved_issue_ids << issue.id
307 307 end
308 308 end
309 309 if unsaved_issue_ids.empty?
310 310 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
311 311 else
312 312 flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
313 313 :total => @issues.size,
314 314 :ids => '#' + unsaved_issue_ids.join(', #'))
315 315 end
316 316 if params[:follow]
317 317 if @issues.size == 1 && moved_issues.size == 1
318 318 redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first
319 319 else
320 320 redirect_to :controller => 'issues', :action => 'index', :project_id => (@target_project || @project)
321 321 end
322 322 else
323 323 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
324 324 end
325 325 return
326 326 end
327 327 render :layout => false if request.xhr?
328 328 end
329 329
330 330 def destroy
331 331 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
332 332 if @hours > 0
333 333 case params[:todo]
334 334 when 'destroy'
335 335 # nothing to do
336 336 when 'nullify'
337 337 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
338 338 when 'reassign'
339 339 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
340 340 if reassign_to.nil?
341 341 flash.now[:error] = l(:error_issue_not_found_in_project)
342 342 return
343 343 else
344 344 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
345 345 end
346 346 else
347 347 # display the destroy form
348 348 return
349 349 end
350 350 end
351 351 @issues.each(&:destroy)
352 352 redirect_to :action => 'index', :project_id => @project
353 353 end
354 354
355 355 def gantt
356 356 @gantt = Redmine::Helpers::Gantt.new(params)
357 357 retrieve_query
358 358 @query.group_by = nil
359 359 if @query.valid?
360 360 events = []
361 361 # Issues that have start and due dates
362 362 events += @query.issues(:include => [:tracker, :assigned_to, :priority],
363 363 :order => "start_date, due_date",
364 364 :conditions => ["(((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
365 365 )
366 366 # Issues that don't have a due date but that are assigned to a version with a date
367 367 events += @query.issues(:include => [:tracker, :assigned_to, :priority, :fixed_version],
368 368 :order => "start_date, effective_date",
369 369 :conditions => ["(((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date<? and effective_date>?)) and start_date is not null and due_date is null and effective_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
370 370 )
371 371 # Versions
372 372 events += @query.versions(:conditions => ["effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to])
373 373
374 374 @gantt.events = events
375 375 end
376 376
377 377 basename = (@project ? "#{@project.identifier}-" : '') + 'gantt'
378 378
379 379 respond_to do |format|
380 380 format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? }
381 381 format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{basename}.png") } if @gantt.respond_to?('to_image')
382 382 format.pdf { send_data(gantt_to_pdf(@gantt, @project), :type => 'application/pdf', :filename => "#{basename}.pdf") }
383 383 end
384 384 end
385 385
386 386 def calendar
387 387 if params[:year] and params[:year].to_i > 1900
388 388 @year = params[:year].to_i
389 389 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
390 390 @month = params[:month].to_i
391 391 end
392 392 end
393 393 @year ||= Date.today.year
394 394 @month ||= Date.today.month
395 395
396 396 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
397 397 retrieve_query
398 398 @query.group_by = nil
399 399 if @query.valid?
400 400 events = []
401 401 events += @query.issues(:include => [:tracker, :assigned_to, :priority],
402 402 :conditions => ["((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
403 403 )
404 404 events += @query.versions(:conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
405 405
406 406 @calendar.events = events
407 407 end
408 408
409 409 render :layout => false if request.xhr?
410 410 end
411 411
412 412 def context_menu
413 413 @issues = Issue.find_all_by_id(params[:ids], :include => :project)
414 414 if (@issues.size == 1)
415 415 @issue = @issues.first
416 416 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
417 417 end
418 418 projects = @issues.collect(&:project).compact.uniq
419 419 @project = projects.first if projects.size == 1
420 420
421 421 @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
422 422 :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
423 423 :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))),
424 424 :move => (@project && User.current.allowed_to?(:move_issues, @project)),
425 425 :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
426 426 :delete => (@project && User.current.allowed_to?(:delete_issues, @project))
427 427 }
428 428 if @project
429 429 @assignables = @project.assignable_users
430 430 @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
431 431 @trackers = @project.trackers
432 432 end
433 433
434 434 @priorities = IssuePriority.all.reverse
435 435 @statuses = IssueStatus.find(:all, :order => 'position')
436 436 @back = params[:back_url] || request.env['HTTP_REFERER']
437 437
438 438 render :layout => false
439 439 end
440 440
441 441 def update_form
442 442 if params[:id].blank?
443 443 @issue = Issue.new
444 444 @issue.project = @project
445 445 else
446 446 @issue = @project.issues.visible.find(params[:id])
447 447 end
448 448 @issue.attributes = params[:issue]
449 449 @allowed_statuses = ([@issue.status] + @issue.status.find_new_statuses_allowed_to(User.current.roles_for_project(@project), @issue.tracker)).uniq
450 450 @priorities = IssuePriority.all
451 451
452 452 render :partial => 'attributes'
453 453 end
454 454
455 455 def preview
456 456 @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
457 457 @attachements = @issue.attachments if @issue
458 458 @text = params[:notes] || (params[:issue] ? params[:issue][:description] : nil)
459 459 render :partial => 'common/preview'
460 460 end
461 461
462 462 private
463 463 def find_issue
464 464 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
465 465 @project = @issue.project
466 466 rescue ActiveRecord::RecordNotFound
467 467 render_404
468 468 end
469 469
470 470 # Filter for bulk operations
471 471 def find_issues
472 472 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
473 473 raise ActiveRecord::RecordNotFound if @issues.empty?
474 474 projects = @issues.collect(&:project).compact.uniq
475 475 if projects.size == 1
476 476 @project = projects.first
477 477 else
478 478 # TODO: let users bulk edit/move/destroy issues from different projects
479 479 render_error 'Can not bulk edit/move/destroy issues from different projects'
480 480 return false
481 481 end
482 482 rescue ActiveRecord::RecordNotFound
483 483 render_404
484 484 end
485 485
486 486 def find_project
487 487 @project = Project.find(params[:project_id])
488 488 rescue ActiveRecord::RecordNotFound
489 489 render_404
490 490 end
491 491
492 492 def find_optional_project
493 493 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
494 494 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
495 495 allowed ? true : deny_access
496 496 rescue ActiveRecord::RecordNotFound
497 497 render_404
498 498 end
499 499
500 500 # Retrieve query from session or build a new query
501 501 def retrieve_query
502 502 if !params[:query_id].blank?
503 503 cond = "project_id IS NULL"
504 504 cond << " OR project_id = #{@project.id}" if @project
505 505 @query = Query.find(params[:query_id], :conditions => cond)
506 506 @query.project = @project
507 507 session[:query] = {:id => @query.id, :project_id => @query.project_id}
508 508 sort_clear
509 509 else
510 510 if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
511 511 # Give it a name, required to be valid
512 512 @query = Query.new(:name => "_")
513 513 @query.project = @project
514 514 if params[:fields] and params[:fields].is_a? Array
515 515 params[:fields].each do |field|
516 516 @query.add_filter(field, params[:operators][field], params[:values][field])
517 517 end
518 518 else
519 519 @query.available_filters.keys.each do |field|
520 520 @query.add_short_filter(field, params[field]) if params[field]
521 521 end
522 522 end
523 523 @query.group_by = params[:group_by]
524 524 @query.column_names = params[:query] && params[:query][:column_names]
525 525 session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
526 526 else
527 527 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
528 528 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
529 529 @query.project = @project
530 530 end
531 531 end
532 532 end
533 533
534 534 # Rescues an invalid query statement. Just in case...
535 535 def query_statement_invalid(exception)
536 536 logger.error "Query::StatementInvalid: #{exception.message}" if logger
537 537 session.delete(:query)
538 538 sort_clear
539 539 render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
540 540 end
541 541 end
@@ -1,1338 +1,1344
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2008 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.dirname(__FILE__) + '/../test_helper'
19 19 require 'issues_controller'
20 20
21 21 # Re-raise errors caught by the controller.
22 22 class IssuesController; def rescue_action(e) raise e end; end
23 23
24 24 class IssuesControllerTest < ActionController::TestCase
25 25 fixtures :projects,
26 26 :users,
27 27 :roles,
28 28 :members,
29 29 :member_roles,
30 30 :issues,
31 31 :issue_statuses,
32 32 :versions,
33 33 :trackers,
34 34 :projects_trackers,
35 35 :issue_categories,
36 36 :enabled_modules,
37 37 :enumerations,
38 38 :attachments,
39 39 :workflows,
40 40 :custom_fields,
41 41 :custom_values,
42 42 :custom_fields_projects,
43 43 :custom_fields_trackers,
44 44 :time_entries,
45 45 :journals,
46 46 :journal_details,
47 47 :queries
48 48
49 49 def setup
50 50 @controller = IssuesController.new
51 51 @request = ActionController::TestRequest.new
52 52 @response = ActionController::TestResponse.new
53 53 User.current = nil
54 54 end
55 55
56 56 def test_index_routing
57 57 assert_routing(
58 58 {:method => :get, :path => '/issues'},
59 59 :controller => 'issues', :action => 'index'
60 60 )
61 61 end
62 62
63 63 def test_index
64 64 Setting.default_language = 'en'
65 65
66 66 get :index
67 67 assert_response :success
68 68 assert_template 'index.rhtml'
69 69 assert_not_nil assigns(:issues)
70 70 assert_nil assigns(:project)
71 71 assert_tag :tag => 'a', :content => /Can't print recipes/
72 72 assert_tag :tag => 'a', :content => /Subproject issue/
73 73 # private projects hidden
74 74 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
75 75 assert_no_tag :tag => 'a', :content => /Issue on project 2/
76 76 # project column
77 77 assert_tag :tag => 'th', :content => /Project/
78 78 end
79 79
80 80 def test_index_should_not_list_issues_when_module_disabled
81 81 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
82 82 get :index
83 83 assert_response :success
84 84 assert_template 'index.rhtml'
85 85 assert_not_nil assigns(:issues)
86 86 assert_nil assigns(:project)
87 87 assert_no_tag :tag => 'a', :content => /Can't print recipes/
88 88 assert_tag :tag => 'a', :content => /Subproject issue/
89 89 end
90 90
91 91 def test_index_with_project_routing
92 92 assert_routing(
93 93 {:method => :get, :path => '/projects/23/issues'},
94 94 :controller => 'issues', :action => 'index', :project_id => '23'
95 95 )
96 96 end
97 97
98 98 def test_index_should_not_list_issues_when_module_disabled
99 99 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
100 100 get :index
101 101 assert_response :success
102 102 assert_template 'index.rhtml'
103 103 assert_not_nil assigns(:issues)
104 104 assert_nil assigns(:project)
105 105 assert_no_tag :tag => 'a', :content => /Can't print recipes/
106 106 assert_tag :tag => 'a', :content => /Subproject issue/
107 107 end
108 108
109 109 def test_index_with_project_routing
110 110 assert_routing(
111 111 {:method => :get, :path => 'projects/23/issues'},
112 112 :controller => 'issues', :action => 'index', :project_id => '23'
113 113 )
114 114 end
115 115
116 116 def test_index_with_project
117 117 Setting.display_subprojects_issues = 0
118 118 get :index, :project_id => 1
119 119 assert_response :success
120 120 assert_template 'index.rhtml'
121 121 assert_not_nil assigns(:issues)
122 122 assert_tag :tag => 'a', :content => /Can't print recipes/
123 123 assert_no_tag :tag => 'a', :content => /Subproject issue/
124 124 end
125 125
126 126 def test_index_with_project_and_subprojects
127 127 Setting.display_subprojects_issues = 1
128 128 get :index, :project_id => 1
129 129 assert_response :success
130 130 assert_template 'index.rhtml'
131 131 assert_not_nil assigns(:issues)
132 132 assert_tag :tag => 'a', :content => /Can't print recipes/
133 133 assert_tag :tag => 'a', :content => /Subproject issue/
134 134 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
135 135 end
136 136
137 137 def test_index_with_project_and_subprojects_should_show_private_subprojects
138 138 @request.session[:user_id] = 2
139 139 Setting.display_subprojects_issues = 1
140 140 get :index, :project_id => 1
141 141 assert_response :success
142 142 assert_template 'index.rhtml'
143 143 assert_not_nil assigns(:issues)
144 144 assert_tag :tag => 'a', :content => /Can't print recipes/
145 145 assert_tag :tag => 'a', :content => /Subproject issue/
146 146 assert_tag :tag => 'a', :content => /Issue of a private subproject/
147 147 end
148 148
149 149 def test_index_with_project_routing_formatted
150 150 assert_routing(
151 151 {:method => :get, :path => 'projects/23/issues.pdf'},
152 152 :controller => 'issues', :action => 'index', :project_id => '23', :format => 'pdf'
153 153 )
154 154 assert_routing(
155 155 {:method => :get, :path => 'projects/23/issues.atom'},
156 156 :controller => 'issues', :action => 'index', :project_id => '23', :format => 'atom'
157 157 )
158 158 end
159 159
160 160 def test_index_with_project_and_filter
161 161 get :index, :project_id => 1, :set_filter => 1
162 162 assert_response :success
163 163 assert_template 'index.rhtml'
164 164 assert_not_nil assigns(:issues)
165 165 end
166 166
167 167 def test_index_with_query
168 168 get :index, :project_id => 1, :query_id => 5
169 169 assert_response :success
170 170 assert_template 'index.rhtml'
171 171 assert_not_nil assigns(:issues)
172 172 assert_nil assigns(:issue_count_by_group)
173 173 end
174 174
175 175 def test_index_with_query_grouped_by_tracker
176 176 get :index, :project_id => 1, :query_id => 6
177 177 assert_response :success
178 178 assert_template 'index.rhtml'
179 179 assert_not_nil assigns(:issues)
180 180 assert_not_nil assigns(:issue_count_by_group)
181 181 end
182 182
183 183 def test_index_with_query_grouped_by_list_custom_field
184 184 get :index, :project_id => 1, :query_id => 9
185 185 assert_response :success
186 186 assert_template 'index.rhtml'
187 187 assert_not_nil assigns(:issues)
188 188 assert_not_nil assigns(:issue_count_by_group)
189 189 end
190 190
191 191 def test_index_sort_by_field_not_included_in_columns
192 192 Setting.issue_list_default_columns = %w(subject author)
193 193 get :index, :sort => 'tracker'
194 194 end
195 195
196 196 def test_index_csv_with_project
197 197 Setting.default_language = 'en'
198 198
199 199 get :index, :format => 'csv'
200 200 assert_response :success
201 201 assert_not_nil assigns(:issues)
202 202 assert_equal 'text/csv', @response.content_type
203 203 assert @response.body.starts_with?("#,")
204 204
205 205 get :index, :project_id => 1, :format => 'csv'
206 206 assert_response :success
207 207 assert_not_nil assigns(:issues)
208 208 assert_equal 'text/csv', @response.content_type
209 209 end
210 210
211 211 def test_index_formatted
212 212 assert_routing(
213 213 {:method => :get, :path => 'issues.pdf'},
214 214 :controller => 'issues', :action => 'index', :format => 'pdf'
215 215 )
216 216 assert_routing(
217 217 {:method => :get, :path => 'issues.atom'},
218 218 :controller => 'issues', :action => 'index', :format => 'atom'
219 219 )
220 220 end
221 221
222 222 def test_index_pdf
223 223 get :index, :format => 'pdf'
224 224 assert_response :success
225 225 assert_not_nil assigns(:issues)
226 226 assert_equal 'application/pdf', @response.content_type
227 227
228 228 get :index, :project_id => 1, :format => 'pdf'
229 229 assert_response :success
230 230 assert_not_nil assigns(:issues)
231 231 assert_equal 'application/pdf', @response.content_type
232 232
233 233 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
234 234 assert_response :success
235 235 assert_not_nil assigns(:issues)
236 236 assert_equal 'application/pdf', @response.content_type
237 237 end
238 238
239 239 def test_index_pdf_with_query_grouped_by_list_custom_field
240 240 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
241 241 assert_response :success
242 242 assert_not_nil assigns(:issues)
243 243 assert_not_nil assigns(:issue_count_by_group)
244 244 assert_equal 'application/pdf', @response.content_type
245 245 end
246 246
247 247 def test_index_sort
248 248 get :index, :sort => 'tracker,id:desc'
249 249 assert_response :success
250 250
251 251 sort_params = @request.session['issues_index_sort']
252 252 assert sort_params.is_a?(String)
253 253 assert_equal 'tracker,id:desc', sort_params
254 254
255 255 issues = assigns(:issues)
256 256 assert_not_nil issues
257 257 assert !issues.empty?
258 258 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
259 259 end
260 260
261 261 def test_index_with_columns
262 262 columns = ['tracker', 'subject', 'assigned_to']
263 263 get :index, :set_filter => 1, :query => { 'column_names' => columns}
264 264 assert_response :success
265 265
266 266 # query should use specified columns
267 267 query = assigns(:query)
268 268 assert_kind_of Query, query
269 269 assert_equal columns, query.column_names.map(&:to_s)
270 270
271 271 # columns should be stored in session
272 272 assert_kind_of Hash, session[:query]
273 273 assert_kind_of Array, session[:query][:column_names]
274 274 assert_equal columns, session[:query][:column_names].map(&:to_s)
275 275 end
276 276
277 277 def test_gantt
278 278 get :gantt, :project_id => 1
279 279 assert_response :success
280 280 assert_template 'gantt.rhtml'
281 281 assert_not_nil assigns(:gantt)
282 282 events = assigns(:gantt).events
283 283 assert_not_nil events
284 284 # Issue with start and due dates
285 285 i = Issue.find(1)
286 286 assert_not_nil i.due_date
287 287 assert events.include?(Issue.find(1))
288 288 # Issue with without due date but targeted to a version with date
289 289 i = Issue.find(2)
290 290 assert_nil i.due_date
291 291 assert events.include?(i)
292 292 end
293 293
294 294 def test_cross_project_gantt
295 295 get :gantt
296 296 assert_response :success
297 297 assert_template 'gantt.rhtml'
298 298 assert_not_nil assigns(:gantt)
299 299 events = assigns(:gantt).events
300 300 assert_not_nil events
301 301 end
302 302
303 303 def test_gantt_export_to_pdf
304 304 get :gantt, :project_id => 1, :format => 'pdf'
305 305 assert_response :success
306 306 assert_equal 'application/pdf', @response.content_type
307 307 assert @response.body.starts_with?('%PDF')
308 308 assert_not_nil assigns(:gantt)
309 309 end
310 310
311 311 def test_cross_project_gantt_export_to_pdf
312 312 get :gantt, :format => 'pdf'
313 313 assert_response :success
314 314 assert_equal 'application/pdf', @response.content_type
315 315 assert @response.body.starts_with?('%PDF')
316 316 assert_not_nil assigns(:gantt)
317 317 end
318 318
319 319 if Object.const_defined?(:Magick)
320 320 def test_gantt_image
321 321 get :gantt, :project_id => 1, :format => 'png'
322 322 assert_response :success
323 323 assert_equal 'image/png', @response.content_type
324 324 end
325 325 else
326 326 puts "RMagick not installed. Skipping tests !!!"
327 327 end
328 328
329 329 def test_calendar
330 330 get :calendar, :project_id => 1
331 331 assert_response :success
332 332 assert_template 'calendar'
333 333 assert_not_nil assigns(:calendar)
334 334 end
335 335
336 336 def test_cross_project_calendar
337 337 get :calendar
338 338 assert_response :success
339 339 assert_template 'calendar'
340 340 assert_not_nil assigns(:calendar)
341 341 end
342 342
343 343 def test_changes
344 344 get :changes, :project_id => 1
345 345 assert_response :success
346 346 assert_not_nil assigns(:journals)
347 347 assert_equal 'application/atom+xml', @response.content_type
348 348 end
349 349
350 350 def test_show_routing
351 351 assert_routing(
352 352 {:method => :get, :path => '/issues/64'},
353 353 :controller => 'issues', :action => 'show', :id => '64'
354 354 )
355 355 end
356 356
357 357 def test_show_routing_formatted
358 358 assert_routing(
359 359 {:method => :get, :path => '/issues/2332.pdf'},
360 360 :controller => 'issues', :action => 'show', :id => '2332', :format => 'pdf'
361 361 )
362 362 assert_routing(
363 363 {:method => :get, :path => '/issues/23123.atom'},
364 364 :controller => 'issues', :action => 'show', :id => '23123', :format => 'atom'
365 365 )
366 366 end
367 367
368 368 def test_show_by_anonymous
369 369 get :show, :id => 1
370 370 assert_response :success
371 371 assert_template 'show.rhtml'
372 372 assert_not_nil assigns(:issue)
373 373 assert_equal Issue.find(1), assigns(:issue)
374 374
375 375 # anonymous role is allowed to add a note
376 376 assert_tag :tag => 'form',
377 377 :descendant => { :tag => 'fieldset',
378 378 :child => { :tag => 'legend',
379 379 :content => /Notes/ } }
380 380 end
381 381
382 382 def test_show_by_manager
383 383 @request.session[:user_id] = 2
384 384 get :show, :id => 1
385 385 assert_response :success
386 386
387 387 assert_tag :tag => 'form',
388 388 :descendant => { :tag => 'fieldset',
389 389 :child => { :tag => 'legend',
390 390 :content => /Change properties/ } },
391 391 :descendant => { :tag => 'fieldset',
392 392 :child => { :tag => 'legend',
393 393 :content => /Log time/ } },
394 394 :descendant => { :tag => 'fieldset',
395 395 :child => { :tag => 'legend',
396 396 :content => /Notes/ } }
397 397 end
398 398
399 399 def test_show_should_deny_anonymous_access_without_permission
400 400 Role.anonymous.remove_permission!(:view_issues)
401 401 get :show, :id => 1
402 402 assert_response :redirect
403 403 end
404 404
405 405 def test_show_should_deny_non_member_access_without_permission
406 406 Role.non_member.remove_permission!(:view_issues)
407 407 @request.session[:user_id] = 9
408 408 get :show, :id => 1
409 409 assert_response 403
410 410 end
411 411
412 412 def test_show_should_deny_member_access_without_permission
413 413 Role.find(1).remove_permission!(:view_issues)
414 414 @request.session[:user_id] = 2
415 415 get :show, :id => 1
416 416 assert_response 403
417 417 end
418 418
419 419 def test_show_should_not_disclose_relations_to_invisible_issues
420 420 Setting.cross_project_issue_relations = '1'
421 421 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
422 422 # Relation to a private project issue
423 423 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
424 424
425 425 get :show, :id => 1
426 426 assert_response :success
427 427
428 428 assert_tag :div, :attributes => { :id => 'relations' },
429 429 :descendant => { :tag => 'a', :content => /#2$/ }
430 430 assert_no_tag :div, :attributes => { :id => 'relations' },
431 431 :descendant => { :tag => 'a', :content => /#4$/ }
432 432 end
433 433
434 434 def test_show_atom
435 435 get :show, :id => 2, :format => 'atom'
436 436 assert_response :success
437 437 assert_template 'changes.rxml'
438 438 # Inline image
439 439 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
440 440 end
441 441
442 442 def test_new_routing
443 443 assert_routing(
444 444 {:method => :get, :path => '/projects/1/issues/new'},
445 445 :controller => 'issues', :action => 'new', :project_id => '1'
446 446 )
447 447 assert_recognizes(
448 448 {:controller => 'issues', :action => 'new', :project_id => '1'},
449 449 {:method => :post, :path => '/projects/1/issues'}
450 450 )
451 451 end
452 452
453 453 def test_show_export_to_pdf
454 454 get :show, :id => 3, :format => 'pdf'
455 455 assert_response :success
456 456 assert_equal 'application/pdf', @response.content_type
457 457 assert @response.body.starts_with?('%PDF')
458 458 assert_not_nil assigns(:issue)
459 459 end
460 460
461 461 def test_get_new
462 462 @request.session[:user_id] = 2
463 463 get :new, :project_id => 1, :tracker_id => 1
464 464 assert_response :success
465 465 assert_template 'new'
466 466
467 467 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
468 468 :value => 'Default string' }
469 469 end
470 470
471 471 def test_get_new_without_tracker_id
472 472 @request.session[:user_id] = 2
473 473 get :new, :project_id => 1
474 474 assert_response :success
475 475 assert_template 'new'
476 476
477 477 issue = assigns(:issue)
478 478 assert_not_nil issue
479 479 assert_equal Project.find(1).trackers.first, issue.tracker
480 480 end
481 481
482 482 def test_get_new_with_no_default_status_should_display_an_error
483 483 @request.session[:user_id] = 2
484 484 IssueStatus.delete_all
485 485
486 486 get :new, :project_id => 1
487 487 assert_response 500
488 488 assert_not_nil flash[:error]
489 489 assert_tag :tag => 'div', :attributes => { :class => /error/ },
490 490 :content => /No default issue/
491 491 end
492 492
493 493 def test_get_new_with_no_tracker_should_display_an_error
494 494 @request.session[:user_id] = 2
495 495 Tracker.delete_all
496 496
497 497 get :new, :project_id => 1
498 498 assert_response 500
499 499 assert_not_nil flash[:error]
500 500 assert_tag :tag => 'div', :attributes => { :class => /error/ },
501 501 :content => /No tracker/
502 502 end
503 503
504 504 def test_update_new_form
505 505 @request.session[:user_id] = 2
506 506 xhr :post, :update_form, :project_id => 1,
507 507 :issue => {:tracker_id => 2,
508 508 :subject => 'This is the test_new issue',
509 509 :description => 'This is the description',
510 510 :priority_id => 5}
511 511 assert_response :success
512 512 assert_template 'attributes'
513 513
514 514 issue = assigns(:issue)
515 515 assert_kind_of Issue, issue
516 516 assert_equal 1, issue.project_id
517 517 assert_equal 2, issue.tracker_id
518 518 assert_equal 'This is the test_new issue', issue.subject
519 519 end
520 520
521 521 def test_post_new
522 522 @request.session[:user_id] = 2
523 523 assert_difference 'Issue.count' do
524 524 post :new, :project_id => 1,
525 525 :issue => {:tracker_id => 3,
526 526 :subject => 'This is the test_new issue',
527 527 :description => 'This is the description',
528 528 :priority_id => 5,
529 529 :estimated_hours => '',
530 530 :custom_field_values => {'2' => 'Value for field 2'}}
531 531 end
532 532 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
533 533
534 534 issue = Issue.find_by_subject('This is the test_new issue')
535 535 assert_not_nil issue
536 536 assert_equal 2, issue.author_id
537 537 assert_equal 3, issue.tracker_id
538 538 assert_nil issue.estimated_hours
539 539 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
540 540 assert_not_nil v
541 541 assert_equal 'Value for field 2', v.value
542 542 end
543 543
544 544 def test_post_new_and_continue
545 545 @request.session[:user_id] = 2
546 546 post :new, :project_id => 1,
547 547 :issue => {:tracker_id => 3,
548 548 :subject => 'This is first issue',
549 549 :priority_id => 5},
550 550 :continue => ''
551 551 assert_redirected_to :controller => 'issues', :action => 'new', :tracker_id => 3
552 552 end
553 553
554 554 def test_post_new_without_custom_fields_param
555 555 @request.session[:user_id] = 2
556 556 assert_difference 'Issue.count' do
557 557 post :new, :project_id => 1,
558 558 :issue => {:tracker_id => 1,
559 559 :subject => 'This is the test_new issue',
560 560 :description => 'This is the description',
561 561 :priority_id => 5}
562 562 end
563 563 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
564 564 end
565 565
566 566 def test_post_new_with_required_custom_field_and_without_custom_fields_param
567 567 field = IssueCustomField.find_by_name('Database')
568 568 field.update_attribute(:is_required, true)
569 569
570 570 @request.session[:user_id] = 2
571 571 post :new, :project_id => 1,
572 572 :issue => {:tracker_id => 1,
573 573 :subject => 'This is the test_new issue',
574 574 :description => 'This is the description',
575 575 :priority_id => 5}
576 576 assert_response :success
577 577 assert_template 'new'
578 578 issue = assigns(:issue)
579 579 assert_not_nil issue
580 580 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
581 581 end
582 582
583 583 def test_post_new_with_watchers
584 584 @request.session[:user_id] = 2
585 585 ActionMailer::Base.deliveries.clear
586 586
587 587 assert_difference 'Watcher.count', 2 do
588 588 post :new, :project_id => 1,
589 589 :issue => {:tracker_id => 1,
590 590 :subject => 'This is a new issue with watchers',
591 591 :description => 'This is the description',
592 592 :priority_id => 5,
593 593 :watcher_user_ids => ['2', '3']}
594 594 end
595 595 issue = Issue.find_by_subject('This is a new issue with watchers')
596 596 assert_not_nil issue
597 597 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
598 598
599 599 # Watchers added
600 600 assert_equal [2, 3], issue.watcher_user_ids.sort
601 601 assert issue.watched_by?(User.find(3))
602 602 # Watchers notified
603 603 mail = ActionMailer::Base.deliveries.last
604 604 assert_kind_of TMail::Mail, mail
605 605 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
606 606 end
607 607
608 608 def test_post_new_should_send_a_notification
609 609 ActionMailer::Base.deliveries.clear
610 610 @request.session[:user_id] = 2
611 611 assert_difference 'Issue.count' do
612 612 post :new, :project_id => 1,
613 613 :issue => {:tracker_id => 3,
614 614 :subject => 'This is the test_new issue',
615 615 :description => 'This is the description',
616 616 :priority_id => 5,
617 617 :estimated_hours => '',
618 618 :custom_field_values => {'2' => 'Value for field 2'}}
619 619 end
620 620 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
621 621
622 622 assert_equal 1, ActionMailer::Base.deliveries.size
623 623 end
624 624
625 625 def test_post_should_preserve_fields_values_on_validation_failure
626 626 @request.session[:user_id] = 2
627 627 post :new, :project_id => 1,
628 628 :issue => {:tracker_id => 1,
629 629 # empty subject
630 630 :subject => '',
631 631 :description => 'This is a description',
632 632 :priority_id => 6,
633 633 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
634 634 assert_response :success
635 635 assert_template 'new'
636 636
637 637 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
638 638 :content => 'This is a description'
639 639 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
640 640 :child => { :tag => 'option', :attributes => { :selected => 'selected',
641 641 :value => '6' },
642 642 :content => 'High' }
643 643 # Custom fields
644 644 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
645 645 :child => { :tag => 'option', :attributes => { :selected => 'selected',
646 646 :value => 'Oracle' },
647 647 :content => 'Oracle' }
648 648 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
649 649 :value => 'Value for field 2'}
650 650 end
651 651
652 test "POST new with no issue params" do
653 @request.session[:user_id] = 2
654 post :new, :project_id => 1
655 assert_response :success
656 end
657
652 658 def test_copy_routing
653 659 assert_routing(
654 660 {:method => :get, :path => '/projects/world_domination/issues/567/copy'},
655 661 :controller => 'issues', :action => 'new', :project_id => 'world_domination', :copy_from => '567'
656 662 )
657 663 end
658 664
659 665 def test_copy_issue
660 666 @request.session[:user_id] = 2
661 667 get :new, :project_id => 1, :copy_from => 1
662 668 assert_template 'new'
663 669 assert_not_nil assigns(:issue)
664 670 orig = Issue.find(1)
665 671 assert_equal orig.subject, assigns(:issue).subject
666 672 end
667 673
668 674 def test_edit_routing
669 675 assert_routing(
670 676 {:method => :get, :path => '/issues/1/edit'},
671 677 :controller => 'issues', :action => 'edit', :id => '1'
672 678 )
673 679 assert_recognizes( #TODO: use a PUT on the issue URI isntead, need to adjust form
674 680 {:controller => 'issues', :action => 'edit', :id => '1'},
675 681 {:method => :post, :path => '/issues/1/edit'}
676 682 )
677 683 end
678 684
679 685 def test_get_edit
680 686 @request.session[:user_id] = 2
681 687 get :edit, :id => 1
682 688 assert_response :success
683 689 assert_template 'edit'
684 690 assert_not_nil assigns(:issue)
685 691 assert_equal Issue.find(1), assigns(:issue)
686 692 end
687 693
688 694 def test_get_edit_with_params
689 695 @request.session[:user_id] = 2
690 696 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
691 697 assert_response :success
692 698 assert_template 'edit'
693 699
694 700 issue = assigns(:issue)
695 701 assert_not_nil issue
696 702
697 703 assert_equal 5, issue.status_id
698 704 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
699 705 :child => { :tag => 'option',
700 706 :content => 'Closed',
701 707 :attributes => { :selected => 'selected' } }
702 708
703 709 assert_equal 7, issue.priority_id
704 710 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
705 711 :child => { :tag => 'option',
706 712 :content => 'Urgent',
707 713 :attributes => { :selected => 'selected' } }
708 714 end
709 715
710 716 def test_update_edit_form
711 717 @request.session[:user_id] = 2
712 718 xhr :post, :update_form, :project_id => 1,
713 719 :id => 1,
714 720 :issue => {:tracker_id => 2,
715 721 :subject => 'This is the test_new issue',
716 722 :description => 'This is the description',
717 723 :priority_id => 5}
718 724 assert_response :success
719 725 assert_template 'attributes'
720 726
721 727 issue = assigns(:issue)
722 728 assert_kind_of Issue, issue
723 729 assert_equal 1, issue.id
724 730 assert_equal 1, issue.project_id
725 731 assert_equal 2, issue.tracker_id
726 732 assert_equal 'This is the test_new issue', issue.subject
727 733 end
728 734
729 735 def test_reply_routing
730 736 assert_routing(
731 737 {:method => :post, :path => '/issues/1/quoted'},
732 738 :controller => 'issues', :action => 'reply', :id => '1'
733 739 )
734 740 end
735 741
736 742 def test_reply_to_issue
737 743 @request.session[:user_id] = 2
738 744 get :reply, :id => 1
739 745 assert_response :success
740 746 assert_select_rjs :show, "update"
741 747 end
742 748
743 749 def test_reply_to_note
744 750 @request.session[:user_id] = 2
745 751 get :reply, :id => 1, :journal_id => 2
746 752 assert_response :success
747 753 assert_select_rjs :show, "update"
748 754 end
749 755
750 756 def test_post_edit_without_custom_fields_param
751 757 @request.session[:user_id] = 2
752 758 ActionMailer::Base.deliveries.clear
753 759
754 760 issue = Issue.find(1)
755 761 assert_equal '125', issue.custom_value_for(2).value
756 762 old_subject = issue.subject
757 763 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
758 764
759 765 assert_difference('Journal.count') do
760 766 assert_difference('JournalDetail.count', 2) do
761 767 post :edit, :id => 1, :issue => {:subject => new_subject,
762 768 :priority_id => '6',
763 769 :category_id => '1' # no change
764 770 }
765 771 end
766 772 end
767 773 assert_redirected_to :action => 'show', :id => '1'
768 774 issue.reload
769 775 assert_equal new_subject, issue.subject
770 776 # Make sure custom fields were not cleared
771 777 assert_equal '125', issue.custom_value_for(2).value
772 778
773 779 mail = ActionMailer::Base.deliveries.last
774 780 assert_kind_of TMail::Mail, mail
775 781 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
776 782 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
777 783 end
778 784
779 785 def test_post_edit_with_custom_field_change
780 786 @request.session[:user_id] = 2
781 787 issue = Issue.find(1)
782 788 assert_equal '125', issue.custom_value_for(2).value
783 789
784 790 assert_difference('Journal.count') do
785 791 assert_difference('JournalDetail.count', 3) do
786 792 post :edit, :id => 1, :issue => {:subject => 'Custom field change',
787 793 :priority_id => '6',
788 794 :category_id => '1', # no change
789 795 :custom_field_values => { '2' => 'New custom value' }
790 796 }
791 797 end
792 798 end
793 799 assert_redirected_to :action => 'show', :id => '1'
794 800 issue.reload
795 801 assert_equal 'New custom value', issue.custom_value_for(2).value
796 802
797 803 mail = ActionMailer::Base.deliveries.last
798 804 assert_kind_of TMail::Mail, mail
799 805 assert mail.body.include?("Searchable field changed from 125 to New custom value")
800 806 end
801 807
802 808 def test_post_edit_with_status_and_assignee_change
803 809 issue = Issue.find(1)
804 810 assert_equal 1, issue.status_id
805 811 @request.session[:user_id] = 2
806 812 assert_difference('TimeEntry.count', 0) do
807 813 post :edit,
808 814 :id => 1,
809 815 :issue => { :status_id => 2, :assigned_to_id => 3 },
810 816 :notes => 'Assigned to dlopper',
811 817 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
812 818 end
813 819 assert_redirected_to :action => 'show', :id => '1'
814 820 issue.reload
815 821 assert_equal 2, issue.status_id
816 822 j = Journal.find(:first, :order => 'id DESC')
817 823 assert_equal 'Assigned to dlopper', j.notes
818 824 assert_equal 2, j.details.size
819 825
820 826 mail = ActionMailer::Base.deliveries.last
821 827 assert mail.body.include?("Status changed from New to Assigned")
822 828 # subject should contain the new status
823 829 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
824 830 end
825 831
826 832 def test_post_edit_with_note_only
827 833 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
828 834 # anonymous user
829 835 post :edit,
830 836 :id => 1,
831 837 :notes => notes
832 838 assert_redirected_to :action => 'show', :id => '1'
833 839 j = Journal.find(:first, :order => 'id DESC')
834 840 assert_equal notes, j.notes
835 841 assert_equal 0, j.details.size
836 842 assert_equal User.anonymous, j.user
837 843
838 844 mail = ActionMailer::Base.deliveries.last
839 845 assert mail.body.include?(notes)
840 846 end
841 847
842 848 def test_post_edit_with_note_and_spent_time
843 849 @request.session[:user_id] = 2
844 850 spent_hours_before = Issue.find(1).spent_hours
845 851 assert_difference('TimeEntry.count') do
846 852 post :edit,
847 853 :id => 1,
848 854 :notes => '2.5 hours added',
849 855 :time_entry => { :hours => '2.5', :comments => '', :activity_id => TimeEntryActivity.first }
850 856 end
851 857 assert_redirected_to :action => 'show', :id => '1'
852 858
853 859 issue = Issue.find(1)
854 860
855 861 j = Journal.find(:first, :order => 'id DESC')
856 862 assert_equal '2.5 hours added', j.notes
857 863 assert_equal 0, j.details.size
858 864
859 865 t = issue.time_entries.find(:first, :order => 'id DESC')
860 866 assert_not_nil t
861 867 assert_equal 2.5, t.hours
862 868 assert_equal spent_hours_before + 2.5, issue.spent_hours
863 869 end
864 870
865 871 def test_post_edit_with_attachment_only
866 872 set_tmp_attachments_directory
867 873
868 874 # Delete all fixtured journals, a race condition can occur causing the wrong
869 875 # journal to get fetched in the next find.
870 876 Journal.delete_all
871 877
872 878 # anonymous user
873 879 post :edit,
874 880 :id => 1,
875 881 :notes => '',
876 882 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
877 883 assert_redirected_to :action => 'show', :id => '1'
878 884 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
879 885 assert j.notes.blank?
880 886 assert_equal 1, j.details.size
881 887 assert_equal 'testfile.txt', j.details.first.value
882 888 assert_equal User.anonymous, j.user
883 889
884 890 mail = ActionMailer::Base.deliveries.last
885 891 assert mail.body.include?('testfile.txt')
886 892 end
887 893
888 894 def test_post_edit_with_no_change
889 895 issue = Issue.find(1)
890 896 issue.journals.clear
891 897 ActionMailer::Base.deliveries.clear
892 898
893 899 post :edit,
894 900 :id => 1,
895 901 :notes => ''
896 902 assert_redirected_to :action => 'show', :id => '1'
897 903
898 904 issue.reload
899 905 assert issue.journals.empty?
900 906 # No email should be sent
901 907 assert ActionMailer::Base.deliveries.empty?
902 908 end
903 909
904 910 def test_post_edit_should_send_a_notification
905 911 @request.session[:user_id] = 2
906 912 ActionMailer::Base.deliveries.clear
907 913 issue = Issue.find(1)
908 914 old_subject = issue.subject
909 915 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
910 916
911 917 post :edit, :id => 1, :issue => {:subject => new_subject,
912 918 :priority_id => '6',
913 919 :category_id => '1' # no change
914 920 }
915 921 assert_equal 1, ActionMailer::Base.deliveries.size
916 922 end
917 923
918 924 def test_post_edit_with_invalid_spent_time
919 925 @request.session[:user_id] = 2
920 926 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
921 927
922 928 assert_no_difference('Journal.count') do
923 929 post :edit,
924 930 :id => 1,
925 931 :notes => notes,
926 932 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
927 933 end
928 934 assert_response :success
929 935 assert_template 'edit'
930 936
931 937 assert_tag :textarea, :attributes => { :name => 'notes' },
932 938 :content => notes
933 939 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
934 940 end
935 941
936 942 def test_post_edit_should_allow_fixed_version_to_be_set_to_a_subproject
937 943 issue = Issue.find(2)
938 944 @request.session[:user_id] = 2
939 945
940 946 post :edit,
941 947 :id => issue.id,
942 948 :issue => {
943 949 :fixed_version_id => 4
944 950 }
945 951
946 952 assert_response :redirect
947 953 issue.reload
948 954 assert_equal 4, issue.fixed_version_id
949 955 assert_not_equal issue.project_id, issue.fixed_version.project_id
950 956 end
951 957
952 958 def test_post_edit_should_redirect_back_using_the_back_url_parameter
953 959 issue = Issue.find(2)
954 960 @request.session[:user_id] = 2
955 961
956 962 post :edit,
957 963 :id => issue.id,
958 964 :issue => {
959 965 :fixed_version_id => 4
960 966 },
961 967 :back_url => '/issues'
962 968
963 969 assert_response :redirect
964 970 assert_redirected_to '/issues'
965 971 end
966 972
967 973 def test_post_edit_should_not_redirect_back_using_the_back_url_parameter_off_the_host
968 974 issue = Issue.find(2)
969 975 @request.session[:user_id] = 2
970 976
971 977 post :edit,
972 978 :id => issue.id,
973 979 :issue => {
974 980 :fixed_version_id => 4
975 981 },
976 982 :back_url => 'http://google.com'
977 983
978 984 assert_response :redirect
979 985 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
980 986 end
981 987
982 988 def test_get_bulk_edit
983 989 @request.session[:user_id] = 2
984 990 get :bulk_edit, :ids => [1, 2]
985 991 assert_response :success
986 992 assert_template 'bulk_edit'
987 993
988 994 # Project specific custom field, date type
989 995 field = CustomField.find(9)
990 996 assert !field.is_for_all?
991 997 assert_equal 'date', field.field_format
992 998 assert_tag :input, :attributes => {:name => 'custom_field_values[9]'}
993 999
994 1000 # System wide custom field
995 1001 assert CustomField.find(1).is_for_all?
996 1002 assert_tag :select, :attributes => {:name => 'custom_field_values[1]'}
997 1003 end
998 1004
999 1005 def test_bulk_edit
1000 1006 @request.session[:user_id] = 2
1001 1007 # update issues priority
1002 1008 post :bulk_edit, :ids => [1, 2], :priority_id => 7,
1003 1009 :assigned_to_id => '',
1004 1010 :custom_field_values => {'2' => ''},
1005 1011 :notes => 'Bulk editing'
1006 1012 assert_response 302
1007 1013 # check that the issues were updated
1008 1014 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
1009 1015
1010 1016 issue = Issue.find(1)
1011 1017 journal = issue.journals.find(:first, :order => 'created_on DESC')
1012 1018 assert_equal '125', issue.custom_value_for(2).value
1013 1019 assert_equal 'Bulk editing', journal.notes
1014 1020 assert_equal 1, journal.details.size
1015 1021 end
1016 1022
1017 1023 def test_bullk_edit_should_send_a_notification
1018 1024 @request.session[:user_id] = 2
1019 1025 ActionMailer::Base.deliveries.clear
1020 1026 post(:bulk_edit,
1021 1027 {
1022 1028 :ids => [1, 2],
1023 1029 :priority_id => 7,
1024 1030 :assigned_to_id => '',
1025 1031 :custom_field_values => {'2' => ''},
1026 1032 :notes => 'Bulk editing'
1027 1033 })
1028 1034
1029 1035 assert_response 302
1030 1036 assert_equal 2, ActionMailer::Base.deliveries.size
1031 1037 end
1032 1038
1033 1039 def test_bulk_edit_status
1034 1040 @request.session[:user_id] = 2
1035 1041 # update issues priority
1036 1042 post :bulk_edit, :ids => [1, 2], :priority_id => '',
1037 1043 :assigned_to_id => '',
1038 1044 :status_id => '5',
1039 1045 :notes => 'Bulk editing status'
1040 1046 assert_response 302
1041 1047 issue = Issue.find(1)
1042 1048 assert issue.closed?
1043 1049 end
1044 1050
1045 1051 def test_bulk_edit_custom_field
1046 1052 @request.session[:user_id] = 2
1047 1053 # update issues priority
1048 1054 post :bulk_edit, :ids => [1, 2], :priority_id => '',
1049 1055 :assigned_to_id => '',
1050 1056 :custom_field_values => {'2' => '777'},
1051 1057 :notes => 'Bulk editing custom field'
1052 1058 assert_response 302
1053 1059
1054 1060 issue = Issue.find(1)
1055 1061 journal = issue.journals.find(:first, :order => 'created_on DESC')
1056 1062 assert_equal '777', issue.custom_value_for(2).value
1057 1063 assert_equal 1, journal.details.size
1058 1064 assert_equal '125', journal.details.first.old_value
1059 1065 assert_equal '777', journal.details.first.value
1060 1066 end
1061 1067
1062 1068 def test_bulk_unassign
1063 1069 assert_not_nil Issue.find(2).assigned_to
1064 1070 @request.session[:user_id] = 2
1065 1071 # unassign issues
1066 1072 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :assigned_to_id => 'none'
1067 1073 assert_response 302
1068 1074 # check that the issues were updated
1069 1075 assert_nil Issue.find(2).assigned_to
1070 1076 end
1071 1077
1072 1078 def test_post_bulk_edit_should_allow_fixed_version_to_be_set_to_a_subproject
1073 1079 @request.session[:user_id] = 2
1074 1080
1075 1081 post :bulk_edit,
1076 1082 :ids => [1,2],
1077 1083 :fixed_version_id => 4
1078 1084
1079 1085 assert_response :redirect
1080 1086 issues = Issue.find([1,2])
1081 1087 issues.each do |issue|
1082 1088 assert_equal 4, issue.fixed_version_id
1083 1089 assert_not_equal issue.project_id, issue.fixed_version.project_id
1084 1090 end
1085 1091 end
1086 1092
1087 1093 def test_post_bulk_edit_should_redirect_back_using_the_back_url_parameter
1088 1094 @request.session[:user_id] = 2
1089 1095 post :bulk_edit, :ids => [1,2], :back_url => '/issues'
1090 1096
1091 1097 assert_response :redirect
1092 1098 assert_redirected_to '/issues'
1093 1099 end
1094 1100
1095 1101 def test_post_bulk_edit_should_not_redirect_back_using_the_back_url_parameter_off_the_host
1096 1102 @request.session[:user_id] = 2
1097 1103 post :bulk_edit, :ids => [1,2], :back_url => 'http://google.com'
1098 1104
1099 1105 assert_response :redirect
1100 1106 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
1101 1107 end
1102 1108
1103 1109 def test_move_routing
1104 1110 assert_routing(
1105 1111 {:method => :get, :path => '/issues/1/move'},
1106 1112 :controller => 'issues', :action => 'move', :id => '1'
1107 1113 )
1108 1114 assert_recognizes(
1109 1115 {:controller => 'issues', :action => 'move', :id => '1'},
1110 1116 {:method => :post, :path => '/issues/1/move'}
1111 1117 )
1112 1118 end
1113 1119
1114 1120 def test_move_one_issue_to_another_project
1115 1121 @request.session[:user_id] = 2
1116 1122 post :move, :id => 1, :new_project_id => 2, :tracker_id => '', :assigned_to_id => '', :status_id => '', :start_date => '', :due_date => ''
1117 1123 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1118 1124 assert_equal 2, Issue.find(1).project_id
1119 1125 end
1120 1126
1121 1127 def test_move_one_issue_to_another_project_should_follow_when_needed
1122 1128 @request.session[:user_id] = 2
1123 1129 post :move, :id => 1, :new_project_id => 2, :follow => '1'
1124 1130 assert_redirected_to '/issues/1'
1125 1131 end
1126 1132
1127 1133 def test_bulk_move_to_another_project
1128 1134 @request.session[:user_id] = 2
1129 1135 post :move, :ids => [1, 2], :new_project_id => 2
1130 1136 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1131 1137 # Issues moved to project 2
1132 1138 assert_equal 2, Issue.find(1).project_id
1133 1139 assert_equal 2, Issue.find(2).project_id
1134 1140 # No tracker change
1135 1141 assert_equal 1, Issue.find(1).tracker_id
1136 1142 assert_equal 2, Issue.find(2).tracker_id
1137 1143 end
1138 1144
1139 1145 def test_bulk_move_to_another_tracker
1140 1146 @request.session[:user_id] = 2
1141 1147 post :move, :ids => [1, 2], :new_tracker_id => 2
1142 1148 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1143 1149 assert_equal 2, Issue.find(1).tracker_id
1144 1150 assert_equal 2, Issue.find(2).tracker_id
1145 1151 end
1146 1152
1147 1153 def test_bulk_copy_to_another_project
1148 1154 @request.session[:user_id] = 2
1149 1155 assert_difference 'Issue.count', 2 do
1150 1156 assert_no_difference 'Project.find(1).issues.count' do
1151 1157 post :move, :ids => [1, 2], :new_project_id => 2, :copy_options => {:copy => '1'}
1152 1158 end
1153 1159 end
1154 1160 assert_redirected_to 'projects/ecookbook/issues'
1155 1161 end
1156 1162
1157 1163 context "#move via bulk copy" do
1158 1164 should "allow not changing the issue's attributes" do
1159 1165 @request.session[:user_id] = 2
1160 1166 issue_before_move = Issue.find(1)
1161 1167 assert_difference 'Issue.count', 1 do
1162 1168 assert_no_difference 'Project.find(1).issues.count' do
1163 1169 post :move, :ids => [1], :new_project_id => 2, :copy_options => {:copy => '1'}, :new_tracker_id => '', :assigned_to_id => '', :status_id => '', :start_date => '', :due_date => ''
1164 1170 end
1165 1171 end
1166 1172 issue_after_move = Issue.first(:order => 'id desc', :conditions => {:project_id => 2})
1167 1173 assert_equal issue_before_move.tracker_id, issue_after_move.tracker_id
1168 1174 assert_equal issue_before_move.status_id, issue_after_move.status_id
1169 1175 assert_equal issue_before_move.assigned_to_id, issue_after_move.assigned_to_id
1170 1176 end
1171 1177
1172 1178 should "allow changing the issue's attributes" do
1173 1179 @request.session[:user_id] = 2
1174 1180 assert_difference 'Issue.count', 2 do
1175 1181 assert_no_difference 'Project.find(1).issues.count' do
1176 1182 post :move, :ids => [1, 2], :new_project_id => 2, :copy_options => {:copy => '1'}, :new_tracker_id => '', :assigned_to_id => 4, :status_id => 3, :start_date => '2009-12-01', :due_date => '2009-12-31'
1177 1183 end
1178 1184 end
1179 1185
1180 1186 copied_issues = Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2})
1181 1187 assert_equal 2, copied_issues.size
1182 1188 copied_issues.each do |issue|
1183 1189 assert_equal 2, issue.project_id, "Project is incorrect"
1184 1190 assert_equal 4, issue.assigned_to_id, "Assigned to is incorrect"
1185 1191 assert_equal 3, issue.status_id, "Status is incorrect"
1186 1192 assert_equal '2009-12-01', issue.start_date.to_s, "Start date is incorrect"
1187 1193 assert_equal '2009-12-31', issue.due_date.to_s, "Due date is incorrect"
1188 1194 end
1189 1195 end
1190 1196 end
1191 1197
1192 1198 def test_copy_to_another_project_should_follow_when_needed
1193 1199 @request.session[:user_id] = 2
1194 1200 post :move, :ids => [1], :new_project_id => 2, :copy_options => {:copy => '1'}, :follow => '1'
1195 1201 issue = Issue.first(:order => 'id DESC')
1196 1202 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
1197 1203 end
1198 1204
1199 1205 def test_context_menu_one_issue
1200 1206 @request.session[:user_id] = 2
1201 1207 get :context_menu, :ids => [1]
1202 1208 assert_response :success
1203 1209 assert_template 'context_menu'
1204 1210 assert_tag :tag => 'a', :content => 'Edit',
1205 1211 :attributes => { :href => '/issues/1/edit',
1206 1212 :class => 'icon-edit' }
1207 1213 assert_tag :tag => 'a', :content => 'Closed',
1208 1214 :attributes => { :href => '/issues/1/edit?issue%5Bstatus_id%5D=5',
1209 1215 :class => '' }
1210 1216 assert_tag :tag => 'a', :content => 'Immediate',
1211 1217 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;priority_id=8',
1212 1218 :class => '' }
1213 1219 # Versions
1214 1220 assert_tag :tag => 'a', :content => '2.0',
1215 1221 :attributes => { :href => '/issues/bulk_edit?fixed_version_id=3&amp;ids%5B%5D=1',
1216 1222 :class => '' }
1217 1223 assert_tag :tag => 'a', :content => 'eCookbook Subproject 1 - 2.0',
1218 1224 :attributes => { :href => '/issues/bulk_edit?fixed_version_id=4&amp;ids%5B%5D=1',
1219 1225 :class => '' }
1220 1226
1221 1227 assert_tag :tag => 'a', :content => 'Dave Lopper',
1222 1228 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1',
1223 1229 :class => '' }
1224 1230 assert_tag :tag => 'a', :content => 'Duplicate',
1225 1231 :attributes => { :href => '/projects/ecookbook/issues/1/copy',
1226 1232 :class => 'icon-duplicate' }
1227 1233 assert_tag :tag => 'a', :content => 'Copy',
1228 1234 :attributes => { :href => '/issues/move?copy_options%5Bcopy%5D=t&amp;ids%5B%5D=1',
1229 1235 :class => 'icon-copy' }
1230 1236 assert_tag :tag => 'a', :content => 'Move',
1231 1237 :attributes => { :href => '/issues/move?ids%5B%5D=1',
1232 1238 :class => 'icon-move' }
1233 1239 assert_tag :tag => 'a', :content => 'Delete',
1234 1240 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
1235 1241 :class => 'icon-del' }
1236 1242 end
1237 1243
1238 1244 def test_context_menu_one_issue_by_anonymous
1239 1245 get :context_menu, :ids => [1]
1240 1246 assert_response :success
1241 1247 assert_template 'context_menu'
1242 1248 assert_tag :tag => 'a', :content => 'Delete',
1243 1249 :attributes => { :href => '#',
1244 1250 :class => 'icon-del disabled' }
1245 1251 end
1246 1252
1247 1253 def test_context_menu_multiple_issues_of_same_project
1248 1254 @request.session[:user_id] = 2
1249 1255 get :context_menu, :ids => [1, 2]
1250 1256 assert_response :success
1251 1257 assert_template 'context_menu'
1252 1258 assert_tag :tag => 'a', :content => 'Edit',
1253 1259 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
1254 1260 :class => 'icon-edit' }
1255 1261 assert_tag :tag => 'a', :content => 'Immediate',
1256 1262 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;priority_id=8',
1257 1263 :class => '' }
1258 1264 assert_tag :tag => 'a', :content => 'Dave Lopper',
1259 1265 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
1260 1266 :class => '' }
1261 1267 assert_tag :tag => 'a', :content => 'Copy',
1262 1268 :attributes => { :href => '/issues/move?copy_options%5Bcopy%5D=t&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
1263 1269 :class => 'icon-copy' }
1264 1270 assert_tag :tag => 'a', :content => 'Move',
1265 1271 :attributes => { :href => '/issues/move?ids%5B%5D=1&amp;ids%5B%5D=2',
1266 1272 :class => 'icon-move' }
1267 1273 assert_tag :tag => 'a', :content => 'Delete',
1268 1274 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
1269 1275 :class => 'icon-del' }
1270 1276 end
1271 1277
1272 1278 def test_context_menu_multiple_issues_of_different_project
1273 1279 @request.session[:user_id] = 2
1274 1280 get :context_menu, :ids => [1, 2, 4]
1275 1281 assert_response :success
1276 1282 assert_template 'context_menu'
1277 1283 assert_tag :tag => 'a', :content => 'Delete',
1278 1284 :attributes => { :href => '#',
1279 1285 :class => 'icon-del disabled' }
1280 1286 end
1281 1287
1282 1288 def test_destroy_routing
1283 1289 assert_recognizes( #TODO: use DELETE on issue URI (need to change forms)
1284 1290 {:controller => 'issues', :action => 'destroy', :id => '1'},
1285 1291 {:method => :post, :path => '/issues/1/destroy'}
1286 1292 )
1287 1293 end
1288 1294
1289 1295 def test_destroy_issue_with_no_time_entries
1290 1296 assert_nil TimeEntry.find_by_issue_id(2)
1291 1297 @request.session[:user_id] = 2
1292 1298 post :destroy, :id => 2
1293 1299 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1294 1300 assert_nil Issue.find_by_id(2)
1295 1301 end
1296 1302
1297 1303 def test_destroy_issues_with_time_entries
1298 1304 @request.session[:user_id] = 2
1299 1305 post :destroy, :ids => [1, 3]
1300 1306 assert_response :success
1301 1307 assert_template 'destroy'
1302 1308 assert_not_nil assigns(:hours)
1303 1309 assert Issue.find_by_id(1) && Issue.find_by_id(3)
1304 1310 end
1305 1311
1306 1312 def test_destroy_issues_and_destroy_time_entries
1307 1313 @request.session[:user_id] = 2
1308 1314 post :destroy, :ids => [1, 3], :todo => 'destroy'
1309 1315 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1310 1316 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1311 1317 assert_nil TimeEntry.find_by_id([1, 2])
1312 1318 end
1313 1319
1314 1320 def test_destroy_issues_and_assign_time_entries_to_project
1315 1321 @request.session[:user_id] = 2
1316 1322 post :destroy, :ids => [1, 3], :todo => 'nullify'
1317 1323 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1318 1324 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1319 1325 assert_nil TimeEntry.find(1).issue_id
1320 1326 assert_nil TimeEntry.find(2).issue_id
1321 1327 end
1322 1328
1323 1329 def test_destroy_issues_and_reassign_time_entries_to_another_issue
1324 1330 @request.session[:user_id] = 2
1325 1331 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
1326 1332 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1327 1333 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1328 1334 assert_equal 2, TimeEntry.find(1).issue_id
1329 1335 assert_equal 2, TimeEntry.find(2).issue_id
1330 1336 end
1331 1337
1332 1338 def test_default_search_scope
1333 1339 get :index
1334 1340 assert_tag :div, :attributes => {:id => 'quick-search'},
1335 1341 :child => {:tag => 'form',
1336 1342 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
1337 1343 end
1338 1344 end
General Comments 0
You need to be logged in to leave comments. Login now