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