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