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