##// END OF EJS Templates
Fixed that some error messages were not displayed (#2866)....
Jean-Philippe Lang -
r2474:38db62f1e711
parent child
Show More
@@ -1,498 +1,496
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
20
21 before_filter :find_issue, :only => [:show, :edit, :reply]
21 before_filter :find_issue, :only => [:show, :edit, :reply]
22 before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
22 before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
23 before_filter :find_project, :only => [:new, :update_form, :preview]
23 before_filter :find_project, :only => [:new, :update_form, :preview]
24 before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu]
24 before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu]
25 before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar]
25 before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar]
26 accept_key_auth :index, :changes
26 accept_key_auth :index, :changes
27
27
28 helper :journals
28 helper :journals
29 helper :projects
29 helper :projects
30 include ProjectsHelper
30 include ProjectsHelper
31 helper :custom_fields
31 helper :custom_fields
32 include CustomFieldsHelper
32 include CustomFieldsHelper
33 helper :issue_relations
33 helper :issue_relations
34 include IssueRelationsHelper
34 include IssueRelationsHelper
35 helper :watchers
35 helper :watchers
36 include WatchersHelper
36 include WatchersHelper
37 helper :attachments
37 helper :attachments
38 include AttachmentsHelper
38 include AttachmentsHelper
39 helper :queries
39 helper :queries
40 helper :sort
40 helper :sort
41 include SortHelper
41 include SortHelper
42 include IssuesHelper
42 include IssuesHelper
43 helper :timelog
43 helper :timelog
44 include Redmine::Export::PDF
44 include Redmine::Export::PDF
45
45
46 def index
46 def index
47 retrieve_query
47 retrieve_query
48 sort_init 'id', 'desc'
48 sort_init 'id', 'desc'
49 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
49 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
50
50
51 if @query.valid?
51 if @query.valid?
52 limit = per_page_option
52 limit = per_page_option
53 respond_to do |format|
53 respond_to do |format|
54 format.html { }
54 format.html { }
55 format.atom { }
55 format.atom { }
56 format.csv { limit = Setting.issues_export_limit.to_i }
56 format.csv { limit = Setting.issues_export_limit.to_i }
57 format.pdf { limit = Setting.issues_export_limit.to_i }
57 format.pdf { limit = Setting.issues_export_limit.to_i }
58 end
58 end
59 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
59 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
60 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
60 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
61 @issues = Issue.find :all, :order => sort_clause,
61 @issues = Issue.find :all, :order => sort_clause,
62 :include => [ :assigned_to, :status, :tracker, :project, :priority, :category, :fixed_version ],
62 :include => [ :assigned_to, :status, :tracker, :project, :priority, :category, :fixed_version ],
63 :conditions => @query.statement,
63 :conditions => @query.statement,
64 :limit => limit,
64 :limit => limit,
65 :offset => @issue_pages.current.offset
65 :offset => @issue_pages.current.offset
66 respond_to do |format|
66 respond_to do |format|
67 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
67 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
68 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
68 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
69 format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
69 format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
70 format.pdf { send_data(issues_to_pdf(@issues, @project), :type => 'application/pdf', :filename => 'export.pdf') }
70 format.pdf { send_data(issues_to_pdf(@issues, @project), :type => 'application/pdf', :filename => 'export.pdf') }
71 end
71 end
72 else
72 else
73 # Send html if the query is not valid
73 # Send html if the query is not valid
74 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
74 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
75 end
75 end
76 rescue ActiveRecord::RecordNotFound
76 rescue ActiveRecord::RecordNotFound
77 render_404
77 render_404
78 end
78 end
79
79
80 def changes
80 def changes
81 retrieve_query
81 retrieve_query
82 sort_init 'id', 'desc'
82 sort_init 'id', 'desc'
83 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
83 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
84
84
85 if @query.valid?
85 if @query.valid?
86 @journals = Journal.find :all, :include => [ :details, :user, {:issue => [:project, :author, :tracker, :status]} ],
86 @journals = Journal.find :all, :include => [ :details, :user, {:issue => [:project, :author, :tracker, :status]} ],
87 :conditions => @query.statement,
87 :conditions => @query.statement,
88 :limit => 25,
88 :limit => 25,
89 :order => "#{Journal.table_name}.created_on DESC"
89 :order => "#{Journal.table_name}.created_on DESC"
90 end
90 end
91 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
91 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
92 render :layout => false, :content_type => 'application/atom+xml'
92 render :layout => false, :content_type => 'application/atom+xml'
93 rescue ActiveRecord::RecordNotFound
93 rescue ActiveRecord::RecordNotFound
94 render_404
94 render_404
95 end
95 end
96
96
97 def show
97 def show
98 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
98 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
99 @journals.each_with_index {|j,i| j.indice = i+1}
99 @journals.each_with_index {|j,i| j.indice = i+1}
100 @journals.reverse! if User.current.wants_comments_in_reverse_order?
100 @journals.reverse! if User.current.wants_comments_in_reverse_order?
101 @changesets = @issue.changesets
101 @changesets = @issue.changesets
102 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
102 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
103 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
103 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
104 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
104 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
105 @priorities = Enumeration.priorities
105 @priorities = Enumeration.priorities
106 @time_entry = TimeEntry.new
106 @time_entry = TimeEntry.new
107 respond_to do |format|
107 respond_to do |format|
108 format.html { render :template => 'issues/show.rhtml' }
108 format.html { render :template => 'issues/show.rhtml' }
109 format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
109 format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
110 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
110 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
111 end
111 end
112 end
112 end
113
113
114 # Add a new issue
114 # Add a new issue
115 # The new issue will be created from an existing one if copy_from parameter is given
115 # The new issue will be created from an existing one if copy_from parameter is given
116 def new
116 def new
117 @issue = Issue.new
117 @issue = Issue.new
118 @issue.copy_from(params[:copy_from]) if params[:copy_from]
118 @issue.copy_from(params[:copy_from]) if params[:copy_from]
119 @issue.project = @project
119 @issue.project = @project
120 # Tracker must be set before custom field values
120 # Tracker must be set before custom field values
121 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
121 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
122 if @issue.tracker.nil?
122 if @issue.tracker.nil?
123 flash.now[:error] = 'No tracker is associated to this project. Please check the Project settings.'
123 render_error 'No tracker is associated to this project. Please check the Project settings.'
124 render :nothing => true, :layout => true
125 return
124 return
126 end
125 end
127 if params[:issue].is_a?(Hash)
126 if params[:issue].is_a?(Hash)
128 @issue.attributes = params[:issue]
127 @issue.attributes = params[:issue]
129 @issue.watcher_user_ids = params[:issue]['watcher_user_ids'] if User.current.allowed_to?(:add_issue_watchers, @project)
128 @issue.watcher_user_ids = params[:issue]['watcher_user_ids'] if User.current.allowed_to?(:add_issue_watchers, @project)
130 end
129 end
131 @issue.author = User.current
130 @issue.author = User.current
132
131
133 default_status = IssueStatus.default
132 default_status = IssueStatus.default
134 unless default_status
133 unless default_status
135 flash.now[:error] = 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
134 render_error 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
136 render :nothing => true, :layout => true
137 return
135 return
138 end
136 end
139 @issue.status = default_status
137 @issue.status = default_status
140 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)).uniq
138 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)).uniq
141
139
142 if request.get? || request.xhr?
140 if request.get? || request.xhr?
143 @issue.start_date ||= Date.today
141 @issue.start_date ||= Date.today
144 else
142 else
145 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
143 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
146 # Check that the user is allowed to apply the requested status
144 # Check that the user is allowed to apply the requested status
147 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
145 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
148 if @issue.save
146 if @issue.save
149 attach_files(@issue, params[:attachments])
147 attach_files(@issue, params[:attachments])
150 flash[:notice] = l(:notice_successful_create)
148 flash[:notice] = l(:notice_successful_create)
151 Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
149 Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
152 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
150 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
153 redirect_to(params[:continue] ? { :action => 'new', :tracker_id => @issue.tracker } :
151 redirect_to(params[:continue] ? { :action => 'new', :tracker_id => @issue.tracker } :
154 { :action => 'show', :id => @issue })
152 { :action => 'show', :id => @issue })
155 return
153 return
156 end
154 end
157 end
155 end
158 @priorities = Enumeration.priorities
156 @priorities = Enumeration.priorities
159 render :layout => !request.xhr?
157 render :layout => !request.xhr?
160 end
158 end
161
159
162 # Attributes that can be updated on workflow transition (without :edit permission)
160 # Attributes that can be updated on workflow transition (without :edit permission)
163 # TODO: make it configurable (at least per role)
161 # TODO: make it configurable (at least per role)
164 UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
162 UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
165
163
166 def edit
164 def edit
167 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
165 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
168 @priorities = Enumeration.priorities
166 @priorities = Enumeration.priorities
169 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
167 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
170 @time_entry = TimeEntry.new
168 @time_entry = TimeEntry.new
171
169
172 @notes = params[:notes]
170 @notes = params[:notes]
173 journal = @issue.init_journal(User.current, @notes)
171 journal = @issue.init_journal(User.current, @notes)
174 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
172 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
175 if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
173 if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
176 attrs = params[:issue].dup
174 attrs = params[:issue].dup
177 attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
175 attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
178 attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
176 attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
179 @issue.attributes = attrs
177 @issue.attributes = attrs
180 end
178 end
181
179
182 if request.post?
180 if request.post?
183 @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
181 @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
184 @time_entry.attributes = params[:time_entry]
182 @time_entry.attributes = params[:time_entry]
185 attachments = attach_files(@issue, params[:attachments])
183 attachments = attach_files(@issue, params[:attachments])
186 attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
184 attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
187
185
188 call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
186 call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
189
187
190 if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.save
188 if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.save
191 # Log spend time
189 # Log spend time
192 if User.current.allowed_to?(:log_time, @project)
190 if User.current.allowed_to?(:log_time, @project)
193 @time_entry.save
191 @time_entry.save
194 end
192 end
195 if !journal.new_record?
193 if !journal.new_record?
196 # Only send notification if something was actually changed
194 # Only send notification if something was actually changed
197 flash[:notice] = l(:notice_successful_update)
195 flash[:notice] = l(:notice_successful_update)
198 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
196 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
199 end
197 end
200 call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
198 call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
201 redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
199 redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
202 end
200 end
203 end
201 end
204 rescue ActiveRecord::StaleObjectError
202 rescue ActiveRecord::StaleObjectError
205 # Optimistic locking exception
203 # Optimistic locking exception
206 flash.now[:error] = l(:notice_locking_conflict)
204 flash.now[:error] = l(:notice_locking_conflict)
207 end
205 end
208
206
209 def reply
207 def reply
210 journal = Journal.find(params[:journal_id]) if params[:journal_id]
208 journal = Journal.find(params[:journal_id]) if params[:journal_id]
211 if journal
209 if journal
212 user = journal.user
210 user = journal.user
213 text = journal.notes
211 text = journal.notes
214 else
212 else
215 user = @issue.author
213 user = @issue.author
216 text = @issue.description
214 text = @issue.description
217 end
215 end
218 content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> "
216 content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> "
219 content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
217 content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
220 render(:update) { |page|
218 render(:update) { |page|
221 page.<< "$('notes').value = \"#{content}\";"
219 page.<< "$('notes').value = \"#{content}\";"
222 page.show 'update'
220 page.show 'update'
223 page << "Form.Element.focus('notes');"
221 page << "Form.Element.focus('notes');"
224 page << "Element.scrollTo('update');"
222 page << "Element.scrollTo('update');"
225 page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
223 page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
226 }
224 }
227 end
225 end
228
226
229 # Bulk edit a set of issues
227 # Bulk edit a set of issues
230 def bulk_edit
228 def bulk_edit
231 if request.post?
229 if request.post?
232 status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
230 status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
233 priority = params[:priority_id].blank? ? nil : Enumeration.find_by_id(params[:priority_id])
231 priority = params[:priority_id].blank? ? nil : Enumeration.find_by_id(params[:priority_id])
234 assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id])
232 assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id])
235 category = (params[:category_id].blank? || params[:category_id] == 'none') ? nil : @project.issue_categories.find_by_id(params[:category_id])
233 category = (params[:category_id].blank? || params[:category_id] == 'none') ? nil : @project.issue_categories.find_by_id(params[:category_id])
236 fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.versions.find_by_id(params[:fixed_version_id])
234 fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.versions.find_by_id(params[:fixed_version_id])
237 custom_field_values = params[:custom_field_values] ? params[:custom_field_values].reject {|k,v| v.blank?} : nil
235 custom_field_values = params[:custom_field_values] ? params[:custom_field_values].reject {|k,v| v.blank?} : nil
238
236
239 unsaved_issue_ids = []
237 unsaved_issue_ids = []
240 @issues.each do |issue|
238 @issues.each do |issue|
241 journal = issue.init_journal(User.current, params[:notes])
239 journal = issue.init_journal(User.current, params[:notes])
242 issue.priority = priority if priority
240 issue.priority = priority if priority
243 issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
241 issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
244 issue.category = category if category || params[:category_id] == 'none'
242 issue.category = category if category || params[:category_id] == 'none'
245 issue.fixed_version = fixed_version if fixed_version || params[:fixed_version_id] == 'none'
243 issue.fixed_version = fixed_version if fixed_version || params[:fixed_version_id] == 'none'
246 issue.start_date = params[:start_date] unless params[:start_date].blank?
244 issue.start_date = params[:start_date] unless params[:start_date].blank?
247 issue.due_date = params[:due_date] unless params[:due_date].blank?
245 issue.due_date = params[:due_date] unless params[:due_date].blank?
248 issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
246 issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
249 issue.custom_field_values = custom_field_values if custom_field_values && !custom_field_values.empty?
247 issue.custom_field_values = custom_field_values if custom_field_values && !custom_field_values.empty?
250 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
248 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
251 # Don't save any change to the issue if the user is not authorized to apply the requested status
249 # Don't save any change to the issue if the user is not authorized to apply the requested status
252 if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
250 if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
253 # Send notification for each issue (if changed)
251 # Send notification for each issue (if changed)
254 Mailer.deliver_issue_edit(journal) if journal.details.any? && Setting.notified_events.include?('issue_updated')
252 Mailer.deliver_issue_edit(journal) if journal.details.any? && Setting.notified_events.include?('issue_updated')
255 else
253 else
256 # Keep unsaved issue ids to display them in flash error
254 # Keep unsaved issue ids to display them in flash error
257 unsaved_issue_ids << issue.id
255 unsaved_issue_ids << issue.id
258 end
256 end
259 end
257 end
260 if unsaved_issue_ids.empty?
258 if unsaved_issue_ids.empty?
261 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
259 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
262 else
260 else
263 flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
261 flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
264 :total => @issues.size,
262 :total => @issues.size,
265 :ids => '#' + unsaved_issue_ids.join(', #'))
263 :ids => '#' + unsaved_issue_ids.join(', #'))
266 end
264 end
267 redirect_to(params[:back_to] || {:controller => 'issues', :action => 'index', :project_id => @project})
265 redirect_to(params[:back_to] || {:controller => 'issues', :action => 'index', :project_id => @project})
268 return
266 return
269 end
267 end
270 # Find potential statuses the user could be allowed to switch issues to
268 # Find potential statuses the user could be allowed to switch issues to
271 @available_statuses = Workflow.find(:all, :include => :new_status,
269 @available_statuses = Workflow.find(:all, :include => :new_status,
272 :conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq.sort
270 :conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq.sort
273 @custom_fields = @project.issue_custom_fields.select {|f| f.field_format == 'list'}
271 @custom_fields = @project.issue_custom_fields.select {|f| f.field_format == 'list'}
274 end
272 end
275
273
276 def move
274 def move
277 @allowed_projects = []
275 @allowed_projects = []
278 # find projects to which the user is allowed to move the issue
276 # find projects to which the user is allowed to move the issue
279 if User.current.admin?
277 if User.current.admin?
280 # admin is allowed to move issues to any active (visible) project
278 # admin is allowed to move issues to any active (visible) project
281 @allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current))
279 @allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current))
282 else
280 else
283 User.current.memberships.each {|m| @allowed_projects << m.project if m.role.allowed_to?(:move_issues)}
281 User.current.memberships.each {|m| @allowed_projects << m.project if m.role.allowed_to?(:move_issues)}
284 end
282 end
285 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
283 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
286 @target_project ||= @project
284 @target_project ||= @project
287 @trackers = @target_project.trackers
285 @trackers = @target_project.trackers
288 if request.post?
286 if request.post?
289 new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
287 new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
290 unsaved_issue_ids = []
288 unsaved_issue_ids = []
291 @issues.each do |issue|
289 @issues.each do |issue|
292 issue.init_journal(User.current)
290 issue.init_journal(User.current)
293 unsaved_issue_ids << issue.id unless issue.move_to(@target_project, new_tracker, params[:copy_options])
291 unsaved_issue_ids << issue.id unless issue.move_to(@target_project, new_tracker, params[:copy_options])
294 end
292 end
295 if unsaved_issue_ids.empty?
293 if unsaved_issue_ids.empty?
296 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
294 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
297 else
295 else
298 flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
296 flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
299 :total => @issues.size,
297 :total => @issues.size,
300 :ids => '#' + unsaved_issue_ids.join(', #'))
298 :ids => '#' + unsaved_issue_ids.join(', #'))
301 end
299 end
302 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
300 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
303 return
301 return
304 end
302 end
305 render :layout => false if request.xhr?
303 render :layout => false if request.xhr?
306 end
304 end
307
305
308 def destroy
306 def destroy
309 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
307 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
310 if @hours > 0
308 if @hours > 0
311 case params[:todo]
309 case params[:todo]
312 when 'destroy'
310 when 'destroy'
313 # nothing to do
311 # nothing to do
314 when 'nullify'
312 when 'nullify'
315 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
313 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
316 when 'reassign'
314 when 'reassign'
317 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
315 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
318 if reassign_to.nil?
316 if reassign_to.nil?
319 flash.now[:error] = l(:error_issue_not_found_in_project)
317 flash.now[:error] = l(:error_issue_not_found_in_project)
320 return
318 return
321 else
319 else
322 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
320 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
323 end
321 end
324 else
322 else
325 # display the destroy form
323 # display the destroy form
326 return
324 return
327 end
325 end
328 end
326 end
329 @issues.each(&:destroy)
327 @issues.each(&:destroy)
330 redirect_to :action => 'index', :project_id => @project
328 redirect_to :action => 'index', :project_id => @project
331 end
329 end
332
330
333 def gantt
331 def gantt
334 @gantt = Redmine::Helpers::Gantt.new(params)
332 @gantt = Redmine::Helpers::Gantt.new(params)
335 retrieve_query
333 retrieve_query
336 if @query.valid?
334 if @query.valid?
337 events = []
335 events = []
338 # Issues that have start and due dates
336 # Issues that have start and due dates
339 events += Issue.find(:all,
337 events += Issue.find(:all,
340 :order => "start_date, due_date",
338 :order => "start_date, due_date",
341 :include => [:tracker, :status, :assigned_to, :priority, :project],
339 :include => [:tracker, :status, :assigned_to, :priority, :project],
342 :conditions => ["(#{@query.statement}) AND (((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]
340 :conditions => ["(#{@query.statement}) AND (((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]
343 )
341 )
344 # Issues that don't have a due date but that are assigned to a version with a date
342 # Issues that don't have a due date but that are assigned to a version with a date
345 events += Issue.find(:all,
343 events += Issue.find(:all,
346 :order => "start_date, effective_date",
344 :order => "start_date, effective_date",
347 :include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version],
345 :include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version],
348 :conditions => ["(#{@query.statement}) AND (((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]
346 :conditions => ["(#{@query.statement}) AND (((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]
349 )
347 )
350 # Versions
348 # Versions
351 events += Version.find(:all, :include => :project,
349 events += Version.find(:all, :include => :project,
352 :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to])
350 :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to])
353
351
354 @gantt.events = events
352 @gantt.events = events
355 end
353 end
356
354
357 respond_to do |format|
355 respond_to do |format|
358 format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? }
356 format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? }
359 format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.nil? ? '' : "#{@project.identifier}-" }gantt.png") } if @gantt.respond_to?('to_image')
357 format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.nil? ? '' : "#{@project.identifier}-" }gantt.png") } if @gantt.respond_to?('to_image')
360 format.pdf { send_data(gantt_to_pdf(@gantt, @project), :type => 'application/pdf', :filename => "#{@project.nil? ? '' : "#{@project.identifier}-" }gantt.pdf") }
358 format.pdf { send_data(gantt_to_pdf(@gantt, @project), :type => 'application/pdf', :filename => "#{@project.nil? ? '' : "#{@project.identifier}-" }gantt.pdf") }
361 end
359 end
362 end
360 end
363
361
364 def calendar
362 def calendar
365 if params[:year] and params[:year].to_i > 1900
363 if params[:year] and params[:year].to_i > 1900
366 @year = params[:year].to_i
364 @year = params[:year].to_i
367 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
365 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
368 @month = params[:month].to_i
366 @month = params[:month].to_i
369 end
367 end
370 end
368 end
371 @year ||= Date.today.year
369 @year ||= Date.today.year
372 @month ||= Date.today.month
370 @month ||= Date.today.month
373
371
374 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
372 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
375 retrieve_query
373 retrieve_query
376 if @query.valid?
374 if @query.valid?
377 events = []
375 events = []
378 events += Issue.find(:all,
376 events += Issue.find(:all,
379 :include => [:tracker, :status, :assigned_to, :priority, :project],
377 :include => [:tracker, :status, :assigned_to, :priority, :project],
380 :conditions => ["(#{@query.statement}) AND ((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
378 :conditions => ["(#{@query.statement}) AND ((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
381 )
379 )
382 events += Version.find(:all, :include => :project,
380 events += Version.find(:all, :include => :project,
383 :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
381 :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
384
382
385 @calendar.events = events
383 @calendar.events = events
386 end
384 end
387
385
388 render :layout => false if request.xhr?
386 render :layout => false if request.xhr?
389 end
387 end
390
388
391 def context_menu
389 def context_menu
392 @issues = Issue.find_all_by_id(params[:ids], :include => :project)
390 @issues = Issue.find_all_by_id(params[:ids], :include => :project)
393 if (@issues.size == 1)
391 if (@issues.size == 1)
394 @issue = @issues.first
392 @issue = @issues.first
395 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
393 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
396 end
394 end
397 projects = @issues.collect(&:project).compact.uniq
395 projects = @issues.collect(&:project).compact.uniq
398 @project = projects.first if projects.size == 1
396 @project = projects.first if projects.size == 1
399
397
400 @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
398 @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
401 :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
399 :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
402 :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))),
400 :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))),
403 :move => (@project && User.current.allowed_to?(:move_issues, @project)),
401 :move => (@project && User.current.allowed_to?(:move_issues, @project)),
404 :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
402 :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
405 :delete => (@project && User.current.allowed_to?(:delete_issues, @project))
403 :delete => (@project && User.current.allowed_to?(:delete_issues, @project))
406 }
404 }
407 if @project
405 if @project
408 @assignables = @project.assignable_users
406 @assignables = @project.assignable_users
409 @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
407 @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
410 end
408 end
411
409
412 @priorities = Enumeration.priorities.reverse
410 @priorities = Enumeration.priorities.reverse
413 @statuses = IssueStatus.find(:all, :order => 'position')
411 @statuses = IssueStatus.find(:all, :order => 'position')
414 @back = request.env['HTTP_REFERER']
412 @back = request.env['HTTP_REFERER']
415
413
416 render :layout => false
414 render :layout => false
417 end
415 end
418
416
419 def update_form
417 def update_form
420 @issue = Issue.new(params[:issue])
418 @issue = Issue.new(params[:issue])
421 render :action => :new, :layout => false
419 render :action => :new, :layout => false
422 end
420 end
423
421
424 def preview
422 def preview
425 @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
423 @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
426 @attachements = @issue.attachments if @issue
424 @attachements = @issue.attachments if @issue
427 @text = params[:notes] || (params[:issue] ? params[:issue][:description] : nil)
425 @text = params[:notes] || (params[:issue] ? params[:issue][:description] : nil)
428 render :partial => 'common/preview'
426 render :partial => 'common/preview'
429 end
427 end
430
428
431 private
429 private
432 def find_issue
430 def find_issue
433 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
431 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
434 @project = @issue.project
432 @project = @issue.project
435 rescue ActiveRecord::RecordNotFound
433 rescue ActiveRecord::RecordNotFound
436 render_404
434 render_404
437 end
435 end
438
436
439 # Filter for bulk operations
437 # Filter for bulk operations
440 def find_issues
438 def find_issues
441 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
439 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
442 raise ActiveRecord::RecordNotFound if @issues.empty?
440 raise ActiveRecord::RecordNotFound if @issues.empty?
443 projects = @issues.collect(&:project).compact.uniq
441 projects = @issues.collect(&:project).compact.uniq
444 if projects.size == 1
442 if projects.size == 1
445 @project = projects.first
443 @project = projects.first
446 else
444 else
447 # TODO: let users bulk edit/move/destroy issues from different projects
445 # TODO: let users bulk edit/move/destroy issues from different projects
448 render_error 'Can not bulk edit/move/destroy issues from different projects' and return false
446 render_error 'Can not bulk edit/move/destroy issues from different projects' and return false
449 end
447 end
450 rescue ActiveRecord::RecordNotFound
448 rescue ActiveRecord::RecordNotFound
451 render_404
449 render_404
452 end
450 end
453
451
454 def find_project
452 def find_project
455 @project = Project.find(params[:project_id])
453 @project = Project.find(params[:project_id])
456 rescue ActiveRecord::RecordNotFound
454 rescue ActiveRecord::RecordNotFound
457 render_404
455 render_404
458 end
456 end
459
457
460 def find_optional_project
458 def find_optional_project
461 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
459 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
462 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
460 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
463 allowed ? true : deny_access
461 allowed ? true : deny_access
464 rescue ActiveRecord::RecordNotFound
462 rescue ActiveRecord::RecordNotFound
465 render_404
463 render_404
466 end
464 end
467
465
468 # Retrieve query from session or build a new query
466 # Retrieve query from session or build a new query
469 def retrieve_query
467 def retrieve_query
470 if !params[:query_id].blank?
468 if !params[:query_id].blank?
471 cond = "project_id IS NULL"
469 cond = "project_id IS NULL"
472 cond << " OR project_id = #{@project.id}" if @project
470 cond << " OR project_id = #{@project.id}" if @project
473 @query = Query.find(params[:query_id], :conditions => cond)
471 @query = Query.find(params[:query_id], :conditions => cond)
474 @query.project = @project
472 @query.project = @project
475 session[:query] = {:id => @query.id, :project_id => @query.project_id}
473 session[:query] = {:id => @query.id, :project_id => @query.project_id}
476 else
474 else
477 if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
475 if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
478 # Give it a name, required to be valid
476 # Give it a name, required to be valid
479 @query = Query.new(:name => "_")
477 @query = Query.new(:name => "_")
480 @query.project = @project
478 @query.project = @project
481 if params[:fields] and params[:fields].is_a? Array
479 if params[:fields] and params[:fields].is_a? Array
482 params[:fields].each do |field|
480 params[:fields].each do |field|
483 @query.add_filter(field, params[:operators][field], params[:values][field])
481 @query.add_filter(field, params[:operators][field], params[:values][field])
484 end
482 end
485 else
483 else
486 @query.available_filters.keys.each do |field|
484 @query.available_filters.keys.each do |field|
487 @query.add_short_filter(field, params[field]) if params[field]
485 @query.add_short_filter(field, params[field]) if params[field]
488 end
486 end
489 end
487 end
490 session[:query] = {:project_id => @query.project_id, :filters => @query.filters}
488 session[:query] = {:project_id => @query.project_id, :filters => @query.filters}
491 else
489 else
492 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
490 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
493 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters])
491 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters])
494 @query.project = @project
492 @query.project = @project
495 end
493 end
496 end
494 end
497 end
495 end
498 end
496 end
@@ -1,963 +1,985
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 < Test::Unit::TestCase
24 class IssuesControllerTest < Test::Unit::TestCase
25 fixtures :projects,
25 fixtures :projects,
26 :users,
26 :users,
27 :roles,
27 :roles,
28 :members,
28 :members,
29 :issues,
29 :issues,
30 :issue_statuses,
30 :issue_statuses,
31 :versions,
31 :versions,
32 :trackers,
32 :trackers,
33 :projects_trackers,
33 :projects_trackers,
34 :issue_categories,
34 :issue_categories,
35 :enabled_modules,
35 :enabled_modules,
36 :enumerations,
36 :enumerations,
37 :attachments,
37 :attachments,
38 :workflows,
38 :workflows,
39 :custom_fields,
39 :custom_fields,
40 :custom_values,
40 :custom_values,
41 :custom_fields_trackers,
41 :custom_fields_trackers,
42 :time_entries,
42 :time_entries,
43 :journals,
43 :journals,
44 :journal_details
44 :journal_details
45
45
46 def setup
46 def setup
47 @controller = IssuesController.new
47 @controller = IssuesController.new
48 @request = ActionController::TestRequest.new
48 @request = ActionController::TestRequest.new
49 @response = ActionController::TestResponse.new
49 @response = ActionController::TestResponse.new
50 User.current = nil
50 User.current = nil
51 end
51 end
52
52
53 def test_index_routing
53 def test_index_routing
54 assert_routing(
54 assert_routing(
55 {:method => :get, :path => '/issues'},
55 {:method => :get, :path => '/issues'},
56 :controller => 'issues', :action => 'index'
56 :controller => 'issues', :action => 'index'
57 )
57 )
58 end
58 end
59
59
60 def test_index
60 def test_index
61 get :index
61 get :index
62 assert_response :success
62 assert_response :success
63 assert_template 'index.rhtml'
63 assert_template 'index.rhtml'
64 assert_not_nil assigns(:issues)
64 assert_not_nil assigns(:issues)
65 assert_nil assigns(:project)
65 assert_nil assigns(:project)
66 assert_tag :tag => 'a', :content => /Can't print recipes/
66 assert_tag :tag => 'a', :content => /Can't print recipes/
67 assert_tag :tag => 'a', :content => /Subproject issue/
67 assert_tag :tag => 'a', :content => /Subproject issue/
68 # private projects hidden
68 # private projects hidden
69 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
69 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
70 assert_no_tag :tag => 'a', :content => /Issue on project 2/
70 assert_no_tag :tag => 'a', :content => /Issue on project 2/
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_with_project_routing
84 def test_index_with_project_routing
85 assert_routing(
85 assert_routing(
86 {:method => :get, :path => '/projects/23/issues'},
86 {:method => :get, :path => '/projects/23/issues'},
87 :controller => 'issues', :action => 'index', :project_id => '23'
87 :controller => 'issues', :action => 'index', :project_id => '23'
88 )
88 )
89 end
89 end
90
90
91 def test_index_should_not_list_issues_when_module_disabled
91 def test_index_should_not_list_issues_when_module_disabled
92 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
92 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
93 get :index
93 get :index
94 assert_response :success
94 assert_response :success
95 assert_template 'index.rhtml'
95 assert_template 'index.rhtml'
96 assert_not_nil assigns(:issues)
96 assert_not_nil assigns(:issues)
97 assert_nil assigns(:project)
97 assert_nil assigns(:project)
98 assert_no_tag :tag => 'a', :content => /Can't print recipes/
98 assert_no_tag :tag => 'a', :content => /Can't print recipes/
99 assert_tag :tag => 'a', :content => /Subproject issue/
99 assert_tag :tag => 'a', :content => /Subproject issue/
100 end
100 end
101
101
102 def test_index_with_project_routing
102 def test_index_with_project_routing
103 assert_routing(
103 assert_routing(
104 {:method => :get, :path => 'projects/23/issues'},
104 {:method => :get, :path => 'projects/23/issues'},
105 :controller => 'issues', :action => 'index', :project_id => '23'
105 :controller => 'issues', :action => 'index', :project_id => '23'
106 )
106 )
107 end
107 end
108
108
109 def test_index_with_project
109 def test_index_with_project
110 Setting.display_subprojects_issues = 0
110 Setting.display_subprojects_issues = 0
111 get :index, :project_id => 1
111 get :index, :project_id => 1
112 assert_response :success
112 assert_response :success
113 assert_template 'index.rhtml'
113 assert_template 'index.rhtml'
114 assert_not_nil assigns(:issues)
114 assert_not_nil assigns(:issues)
115 assert_tag :tag => 'a', :content => /Can't print recipes/
115 assert_tag :tag => 'a', :content => /Can't print recipes/
116 assert_no_tag :tag => 'a', :content => /Subproject issue/
116 assert_no_tag :tag => 'a', :content => /Subproject issue/
117 end
117 end
118
118
119 def test_index_with_project_and_subprojects
119 def test_index_with_project_and_subprojects
120 Setting.display_subprojects_issues = 1
120 Setting.display_subprojects_issues = 1
121 get :index, :project_id => 1
121 get :index, :project_id => 1
122 assert_response :success
122 assert_response :success
123 assert_template 'index.rhtml'
123 assert_template 'index.rhtml'
124 assert_not_nil assigns(:issues)
124 assert_not_nil assigns(:issues)
125 assert_tag :tag => 'a', :content => /Can't print recipes/
125 assert_tag :tag => 'a', :content => /Can't print recipes/
126 assert_tag :tag => 'a', :content => /Subproject issue/
126 assert_tag :tag => 'a', :content => /Subproject issue/
127 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
127 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
128 end
128 end
129
129
130 def test_index_with_project_and_subprojects_should_show_private_subprojects
130 def test_index_with_project_and_subprojects_should_show_private_subprojects
131 @request.session[:user_id] = 2
131 @request.session[:user_id] = 2
132 Setting.display_subprojects_issues = 1
132 Setting.display_subprojects_issues = 1
133 get :index, :project_id => 1
133 get :index, :project_id => 1
134 assert_response :success
134 assert_response :success
135 assert_template 'index.rhtml'
135 assert_template 'index.rhtml'
136 assert_not_nil assigns(:issues)
136 assert_not_nil assigns(:issues)
137 assert_tag :tag => 'a', :content => /Can't print recipes/
137 assert_tag :tag => 'a', :content => /Can't print recipes/
138 assert_tag :tag => 'a', :content => /Subproject issue/
138 assert_tag :tag => 'a', :content => /Subproject issue/
139 assert_tag :tag => 'a', :content => /Issue of a private subproject/
139 assert_tag :tag => 'a', :content => /Issue of a private subproject/
140 end
140 end
141
141
142 def test_index_with_project_routing_formatted
142 def test_index_with_project_routing_formatted
143 assert_routing(
143 assert_routing(
144 {:method => :get, :path => 'projects/23/issues.pdf'},
144 {:method => :get, :path => 'projects/23/issues.pdf'},
145 :controller => 'issues', :action => 'index', :project_id => '23', :format => 'pdf'
145 :controller => 'issues', :action => 'index', :project_id => '23', :format => 'pdf'
146 )
146 )
147 assert_routing(
147 assert_routing(
148 {:method => :get, :path => 'projects/23/issues.atom'},
148 {:method => :get, :path => 'projects/23/issues.atom'},
149 :controller => 'issues', :action => 'index', :project_id => '23', :format => 'atom'
149 :controller => 'issues', :action => 'index', :project_id => '23', :format => 'atom'
150 )
150 )
151 end
151 end
152
152
153 def test_index_with_project_and_filter
153 def test_index_with_project_and_filter
154 get :index, :project_id => 1, :set_filter => 1
154 get :index, :project_id => 1, :set_filter => 1
155 assert_response :success
155 assert_response :success
156 assert_template 'index.rhtml'
156 assert_template 'index.rhtml'
157 assert_not_nil assigns(:issues)
157 assert_not_nil assigns(:issues)
158 end
158 end
159
159
160 def test_index_csv_with_project
160 def test_index_csv_with_project
161 get :index, :format => 'csv'
161 get :index, :format => 'csv'
162 assert_response :success
162 assert_response :success
163 assert_not_nil assigns(:issues)
163 assert_not_nil assigns(:issues)
164 assert_equal 'text/csv', @response.content_type
164 assert_equal 'text/csv', @response.content_type
165
165
166 get :index, :project_id => 1, :format => 'csv'
166 get :index, :project_id => 1, :format => 'csv'
167 assert_response :success
167 assert_response :success
168 assert_not_nil assigns(:issues)
168 assert_not_nil assigns(:issues)
169 assert_equal 'text/csv', @response.content_type
169 assert_equal 'text/csv', @response.content_type
170 end
170 end
171
171
172 def test_index_formatted
172 def test_index_formatted
173 assert_routing(
173 assert_routing(
174 {:method => :get, :path => 'issues.pdf'},
174 {:method => :get, :path => 'issues.pdf'},
175 :controller => 'issues', :action => 'index', :format => 'pdf'
175 :controller => 'issues', :action => 'index', :format => 'pdf'
176 )
176 )
177 assert_routing(
177 assert_routing(
178 {:method => :get, :path => 'issues.atom'},
178 {:method => :get, :path => 'issues.atom'},
179 :controller => 'issues', :action => 'index', :format => 'atom'
179 :controller => 'issues', :action => 'index', :format => 'atom'
180 )
180 )
181 end
181 end
182
182
183 def test_index_pdf
183 def test_index_pdf
184 get :index, :format => 'pdf'
184 get :index, :format => 'pdf'
185 assert_response :success
185 assert_response :success
186 assert_not_nil assigns(:issues)
186 assert_not_nil assigns(:issues)
187 assert_equal 'application/pdf', @response.content_type
187 assert_equal 'application/pdf', @response.content_type
188
188
189 get :index, :project_id => 1, :format => 'pdf'
189 get :index, :project_id => 1, :format => 'pdf'
190 assert_response :success
190 assert_response :success
191 assert_not_nil assigns(:issues)
191 assert_not_nil assigns(:issues)
192 assert_equal 'application/pdf', @response.content_type
192 assert_equal 'application/pdf', @response.content_type
193 end
193 end
194
194
195 def test_index_sort
195 def test_index_sort
196 get :index, :sort_key => 'tracker'
196 get :index, :sort_key => 'tracker'
197 assert_response :success
197 assert_response :success
198
198
199 sort_params = @request.session['issuesindex_sort']
199 sort_params = @request.session['issuesindex_sort']
200 assert sort_params.is_a?(Hash)
200 assert sort_params.is_a?(Hash)
201 assert_equal 'tracker', sort_params[:key]
201 assert_equal 'tracker', sort_params[:key]
202 assert_equal 'ASC', sort_params[:order]
202 assert_equal 'ASC', sort_params[:order]
203 end
203 end
204
204
205 def test_gantt
205 def test_gantt
206 get :gantt, :project_id => 1
206 get :gantt, :project_id => 1
207 assert_response :success
207 assert_response :success
208 assert_template 'gantt.rhtml'
208 assert_template 'gantt.rhtml'
209 assert_not_nil assigns(:gantt)
209 assert_not_nil assigns(:gantt)
210 events = assigns(:gantt).events
210 events = assigns(:gantt).events
211 assert_not_nil events
211 assert_not_nil events
212 # Issue with start and due dates
212 # Issue with start and due dates
213 i = Issue.find(1)
213 i = Issue.find(1)
214 assert_not_nil i.due_date
214 assert_not_nil i.due_date
215 assert events.include?(Issue.find(1))
215 assert events.include?(Issue.find(1))
216 # Issue with without due date but targeted to a version with date
216 # Issue with without due date but targeted to a version with date
217 i = Issue.find(2)
217 i = Issue.find(2)
218 assert_nil i.due_date
218 assert_nil i.due_date
219 assert events.include?(i)
219 assert events.include?(i)
220 end
220 end
221
221
222 def test_cross_project_gantt
222 def test_cross_project_gantt
223 get :gantt
223 get :gantt
224 assert_response :success
224 assert_response :success
225 assert_template 'gantt.rhtml'
225 assert_template 'gantt.rhtml'
226 assert_not_nil assigns(:gantt)
226 assert_not_nil assigns(:gantt)
227 events = assigns(:gantt).events
227 events = assigns(:gantt).events
228 assert_not_nil events
228 assert_not_nil events
229 end
229 end
230
230
231 def test_gantt_export_to_pdf
231 def test_gantt_export_to_pdf
232 get :gantt, :project_id => 1, :format => 'pdf'
232 get :gantt, :project_id => 1, :format => 'pdf'
233 assert_response :success
233 assert_response :success
234 assert_equal 'application/pdf', @response.content_type
234 assert_equal 'application/pdf', @response.content_type
235 assert @response.body.starts_with?('%PDF')
235 assert @response.body.starts_with?('%PDF')
236 assert_not_nil assigns(:gantt)
236 assert_not_nil assigns(:gantt)
237 end
237 end
238
238
239 def test_cross_project_gantt_export_to_pdf
239 def test_cross_project_gantt_export_to_pdf
240 get :gantt, :format => 'pdf'
240 get :gantt, :format => 'pdf'
241 assert_response :success
241 assert_response :success
242 assert_equal 'application/pdf', @response.content_type
242 assert_equal 'application/pdf', @response.content_type
243 assert @response.body.starts_with?('%PDF')
243 assert @response.body.starts_with?('%PDF')
244 assert_not_nil assigns(:gantt)
244 assert_not_nil assigns(:gantt)
245 end
245 end
246
246
247 if Object.const_defined?(:Magick)
247 if Object.const_defined?(:Magick)
248 def test_gantt_image
248 def test_gantt_image
249 get :gantt, :project_id => 1, :format => 'png'
249 get :gantt, :project_id => 1, :format => 'png'
250 assert_response :success
250 assert_response :success
251 assert_equal 'image/png', @response.content_type
251 assert_equal 'image/png', @response.content_type
252 end
252 end
253 else
253 else
254 puts "RMagick not installed. Skipping tests !!!"
254 puts "RMagick not installed. Skipping tests !!!"
255 end
255 end
256
256
257 def test_calendar
257 def test_calendar
258 get :calendar, :project_id => 1
258 get :calendar, :project_id => 1
259 assert_response :success
259 assert_response :success
260 assert_template 'calendar'
260 assert_template 'calendar'
261 assert_not_nil assigns(:calendar)
261 assert_not_nil assigns(:calendar)
262 end
262 end
263
263
264 def test_cross_project_calendar
264 def test_cross_project_calendar
265 get :calendar
265 get :calendar
266 assert_response :success
266 assert_response :success
267 assert_template 'calendar'
267 assert_template 'calendar'
268 assert_not_nil assigns(:calendar)
268 assert_not_nil assigns(:calendar)
269 end
269 end
270
270
271 def test_changes
271 def test_changes
272 get :changes, :project_id => 1
272 get :changes, :project_id => 1
273 assert_response :success
273 assert_response :success
274 assert_not_nil assigns(:journals)
274 assert_not_nil assigns(:journals)
275 assert_equal 'application/atom+xml', @response.content_type
275 assert_equal 'application/atom+xml', @response.content_type
276 end
276 end
277
277
278 def test_show_routing
278 def test_show_routing
279 assert_routing(
279 assert_routing(
280 {:method => :get, :path => '/issues/64'},
280 {:method => :get, :path => '/issues/64'},
281 :controller => 'issues', :action => 'show', :id => '64'
281 :controller => 'issues', :action => 'show', :id => '64'
282 )
282 )
283 end
283 end
284
284
285 def test_show_routing_formatted
285 def test_show_routing_formatted
286 assert_routing(
286 assert_routing(
287 {:method => :get, :path => '/issues/2332.pdf'},
287 {:method => :get, :path => '/issues/2332.pdf'},
288 :controller => 'issues', :action => 'show', :id => '2332', :format => 'pdf'
288 :controller => 'issues', :action => 'show', :id => '2332', :format => 'pdf'
289 )
289 )
290 assert_routing(
290 assert_routing(
291 {:method => :get, :path => '/issues/23123.atom'},
291 {:method => :get, :path => '/issues/23123.atom'},
292 :controller => 'issues', :action => 'show', :id => '23123', :format => 'atom'
292 :controller => 'issues', :action => 'show', :id => '23123', :format => 'atom'
293 )
293 )
294 end
294 end
295
295
296 def test_show_by_anonymous
296 def test_show_by_anonymous
297 get :show, :id => 1
297 get :show, :id => 1
298 assert_response :success
298 assert_response :success
299 assert_template 'show.rhtml'
299 assert_template 'show.rhtml'
300 assert_not_nil assigns(:issue)
300 assert_not_nil assigns(:issue)
301 assert_equal Issue.find(1), assigns(:issue)
301 assert_equal Issue.find(1), assigns(:issue)
302
302
303 # anonymous role is allowed to add a note
303 # anonymous role is allowed to add a note
304 assert_tag :tag => 'form',
304 assert_tag :tag => 'form',
305 :descendant => { :tag => 'fieldset',
305 :descendant => { :tag => 'fieldset',
306 :child => { :tag => 'legend',
306 :child => { :tag => 'legend',
307 :content => /Notes/ } }
307 :content => /Notes/ } }
308 end
308 end
309
309
310 def test_show_by_manager
310 def test_show_by_manager
311 @request.session[:user_id] = 2
311 @request.session[:user_id] = 2
312 get :show, :id => 1
312 get :show, :id => 1
313 assert_response :success
313 assert_response :success
314
314
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 => /Change properties/ } },
318 :content => /Change properties/ } },
319 :descendant => { :tag => 'fieldset',
319 :descendant => { :tag => 'fieldset',
320 :child => { :tag => 'legend',
320 :child => { :tag => 'legend',
321 :content => /Log time/ } },
321 :content => /Log time/ } },
322 :descendant => { :tag => 'fieldset',
322 :descendant => { :tag => 'fieldset',
323 :child => { :tag => 'legend',
323 :child => { :tag => 'legend',
324 :content => /Notes/ } }
324 :content => /Notes/ } }
325 end
325 end
326
326
327 def test_show_should_not_disclose_relations_to_invisible_issues
327 def test_show_should_not_disclose_relations_to_invisible_issues
328 Setting.cross_project_issue_relations = '1'
328 Setting.cross_project_issue_relations = '1'
329 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
329 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
330 # Relation to a private project issue
330 # Relation to a private project issue
331 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
331 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
332
332
333 get :show, :id => 1
333 get :show, :id => 1
334 assert_response :success
334 assert_response :success
335
335
336 assert_tag :div, :attributes => { :id => 'relations' },
336 assert_tag :div, :attributes => { :id => 'relations' },
337 :descendant => { :tag => 'a', :content => /#2$/ }
337 :descendant => { :tag => 'a', :content => /#2$/ }
338 assert_no_tag :div, :attributes => { :id => 'relations' },
338 assert_no_tag :div, :attributes => { :id => 'relations' },
339 :descendant => { :tag => 'a', :content => /#4$/ }
339 :descendant => { :tag => 'a', :content => /#4$/ }
340 end
340 end
341
341
342 def test_new_routing
342 def test_new_routing
343 assert_routing(
343 assert_routing(
344 {:method => :get, :path => '/projects/1/issues/new'},
344 {:method => :get, :path => '/projects/1/issues/new'},
345 :controller => 'issues', :action => 'new', :project_id => '1'
345 :controller => 'issues', :action => 'new', :project_id => '1'
346 )
346 )
347 assert_recognizes(
347 assert_recognizes(
348 {:controller => 'issues', :action => 'new', :project_id => '1'},
348 {:controller => 'issues', :action => 'new', :project_id => '1'},
349 {:method => :post, :path => '/projects/1/issues'}
349 {:method => :post, :path => '/projects/1/issues'}
350 )
350 )
351 end
351 end
352
352
353 def test_show_export_to_pdf
353 def test_show_export_to_pdf
354 get :show, :id => 3, :format => 'pdf'
354 get :show, :id => 3, :format => 'pdf'
355 assert_response :success
355 assert_response :success
356 assert_equal 'application/pdf', @response.content_type
356 assert_equal 'application/pdf', @response.content_type
357 assert @response.body.starts_with?('%PDF')
357 assert @response.body.starts_with?('%PDF')
358 assert_not_nil assigns(:issue)
358 assert_not_nil assigns(:issue)
359 end
359 end
360
360
361 def test_get_new
361 def test_get_new
362 @request.session[:user_id] = 2
362 @request.session[:user_id] = 2
363 get :new, :project_id => 1, :tracker_id => 1
363 get :new, :project_id => 1, :tracker_id => 1
364 assert_response :success
364 assert_response :success
365 assert_template 'new'
365 assert_template 'new'
366
366
367 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
367 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
368 :value => 'Default string' }
368 :value => 'Default string' }
369 end
369 end
370
370
371 def test_get_new_without_tracker_id
371 def test_get_new_without_tracker_id
372 @request.session[:user_id] = 2
372 @request.session[:user_id] = 2
373 get :new, :project_id => 1
373 get :new, :project_id => 1
374 assert_response :success
374 assert_response :success
375 assert_template 'new'
375 assert_template 'new'
376
376
377 issue = assigns(:issue)
377 issue = assigns(:issue)
378 assert_not_nil issue
378 assert_not_nil issue
379 assert_equal Project.find(1).trackers.first, issue.tracker
379 assert_equal Project.find(1).trackers.first, issue.tracker
380 end
380 end
381
381
382 def test_get_new_with_no_default_status_should_display_an_error
383 @request.session[:user_id] = 2
384 IssueStatus.delete_all
385
386 get :new, :project_id => 1
387 assert_response 500
388 assert_not_nil flash[:error]
389 assert_tag :tag => 'div', :attributes => { :class => /error/ },
390 :content => /No default issue/
391 end
392
393 def test_get_new_with_no_tracker_should_display_an_error
394 @request.session[:user_id] = 2
395 Tracker.delete_all
396
397 get :new, :project_id => 1
398 assert_response 500
399 assert_not_nil flash[:error]
400 assert_tag :tag => 'div', :attributes => { :class => /error/ },
401 :content => /No tracker/
402 end
403
382 def test_update_new_form
404 def test_update_new_form
383 @request.session[:user_id] = 2
405 @request.session[:user_id] = 2
384 xhr :post, :new, :project_id => 1,
406 xhr :post, :new, :project_id => 1,
385 :issue => {:tracker_id => 2,
407 :issue => {:tracker_id => 2,
386 :subject => 'This is the test_new issue',
408 :subject => 'This is the test_new issue',
387 :description => 'This is the description',
409 :description => 'This is the description',
388 :priority_id => 5}
410 :priority_id => 5}
389 assert_response :success
411 assert_response :success
390 assert_template 'new'
412 assert_template 'new'
391 end
413 end
392
414
393 def test_post_new
415 def test_post_new
394 @request.session[:user_id] = 2
416 @request.session[:user_id] = 2
395 post :new, :project_id => 1,
417 post :new, :project_id => 1,
396 :issue => {:tracker_id => 3,
418 :issue => {:tracker_id => 3,
397 :subject => 'This is the test_new issue',
419 :subject => 'This is the test_new issue',
398 :description => 'This is the description',
420 :description => 'This is the description',
399 :priority_id => 5,
421 :priority_id => 5,
400 :estimated_hours => '',
422 :estimated_hours => '',
401 :custom_field_values => {'2' => 'Value for field 2'}}
423 :custom_field_values => {'2' => 'Value for field 2'}}
402 assert_redirected_to :action => 'show'
424 assert_redirected_to :action => 'show'
403
425
404 issue = Issue.find_by_subject('This is the test_new issue')
426 issue = Issue.find_by_subject('This is the test_new issue')
405 assert_not_nil issue
427 assert_not_nil issue
406 assert_equal 2, issue.author_id
428 assert_equal 2, issue.author_id
407 assert_equal 3, issue.tracker_id
429 assert_equal 3, issue.tracker_id
408 assert_nil issue.estimated_hours
430 assert_nil issue.estimated_hours
409 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
431 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
410 assert_not_nil v
432 assert_not_nil v
411 assert_equal 'Value for field 2', v.value
433 assert_equal 'Value for field 2', v.value
412 end
434 end
413
435
414 def test_post_new_and_continue
436 def test_post_new_and_continue
415 @request.session[:user_id] = 2
437 @request.session[:user_id] = 2
416 post :new, :project_id => 1,
438 post :new, :project_id => 1,
417 :issue => {:tracker_id => 3,
439 :issue => {:tracker_id => 3,
418 :subject => 'This is first issue',
440 :subject => 'This is first issue',
419 :priority_id => 5},
441 :priority_id => 5},
420 :continue => ''
442 :continue => ''
421 assert_redirected_to :controller => 'issues', :action => 'new', :tracker_id => 3
443 assert_redirected_to :controller => 'issues', :action => 'new', :tracker_id => 3
422 end
444 end
423
445
424 def test_post_new_without_custom_fields_param
446 def test_post_new_without_custom_fields_param
425 @request.session[:user_id] = 2
447 @request.session[:user_id] = 2
426 post :new, :project_id => 1,
448 post :new, :project_id => 1,
427 :issue => {:tracker_id => 1,
449 :issue => {:tracker_id => 1,
428 :subject => 'This is the test_new issue',
450 :subject => 'This is the test_new issue',
429 :description => 'This is the description',
451 :description => 'This is the description',
430 :priority_id => 5}
452 :priority_id => 5}
431 assert_redirected_to :action => 'show'
453 assert_redirected_to :action => 'show'
432 end
454 end
433
455
434 def test_post_new_with_required_custom_field_and_without_custom_fields_param
456 def test_post_new_with_required_custom_field_and_without_custom_fields_param
435 field = IssueCustomField.find_by_name('Database')
457 field = IssueCustomField.find_by_name('Database')
436 field.update_attribute(:is_required, true)
458 field.update_attribute(:is_required, true)
437
459
438 @request.session[:user_id] = 2
460 @request.session[:user_id] = 2
439 post :new, :project_id => 1,
461 post :new, :project_id => 1,
440 :issue => {:tracker_id => 1,
462 :issue => {:tracker_id => 1,
441 :subject => 'This is the test_new issue',
463 :subject => 'This is the test_new issue',
442 :description => 'This is the description',
464 :description => 'This is the description',
443 :priority_id => 5}
465 :priority_id => 5}
444 assert_response :success
466 assert_response :success
445 assert_template 'new'
467 assert_template 'new'
446 issue = assigns(:issue)
468 issue = assigns(:issue)
447 assert_not_nil issue
469 assert_not_nil issue
448 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
470 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
449 end
471 end
450
472
451 def test_post_new_with_watchers
473 def test_post_new_with_watchers
452 @request.session[:user_id] = 2
474 @request.session[:user_id] = 2
453 ActionMailer::Base.deliveries.clear
475 ActionMailer::Base.deliveries.clear
454
476
455 assert_difference 'Watcher.count', 2 do
477 assert_difference 'Watcher.count', 2 do
456 post :new, :project_id => 1,
478 post :new, :project_id => 1,
457 :issue => {:tracker_id => 1,
479 :issue => {:tracker_id => 1,
458 :subject => 'This is a new issue with watchers',
480 :subject => 'This is a new issue with watchers',
459 :description => 'This is the description',
481 :description => 'This is the description',
460 :priority_id => 5,
482 :priority_id => 5,
461 :watcher_user_ids => ['2', '3']}
483 :watcher_user_ids => ['2', '3']}
462 end
484 end
463 issue = Issue.find_by_subject('This is a new issue with watchers')
485 issue = Issue.find_by_subject('This is a new issue with watchers')
464 assert_not_nil issue
486 assert_not_nil issue
465 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
487 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
466
488
467 # Watchers added
489 # Watchers added
468 assert_equal [2, 3], issue.watcher_user_ids.sort
490 assert_equal [2, 3], issue.watcher_user_ids.sort
469 assert issue.watched_by?(User.find(3))
491 assert issue.watched_by?(User.find(3))
470 # Watchers notified
492 # Watchers notified
471 mail = ActionMailer::Base.deliveries.last
493 mail = ActionMailer::Base.deliveries.last
472 assert_kind_of TMail::Mail, mail
494 assert_kind_of TMail::Mail, mail
473 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
495 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
474 end
496 end
475
497
476 def test_post_should_preserve_fields_values_on_validation_failure
498 def test_post_should_preserve_fields_values_on_validation_failure
477 @request.session[:user_id] = 2
499 @request.session[:user_id] = 2
478 post :new, :project_id => 1,
500 post :new, :project_id => 1,
479 :issue => {:tracker_id => 1,
501 :issue => {:tracker_id => 1,
480 # empty subject
502 # empty subject
481 :subject => '',
503 :subject => '',
482 :description => 'This is a description',
504 :description => 'This is a description',
483 :priority_id => 6,
505 :priority_id => 6,
484 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
506 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
485 assert_response :success
507 assert_response :success
486 assert_template 'new'
508 assert_template 'new'
487
509
488 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
510 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
489 :content => 'This is a description'
511 :content => 'This is a description'
490 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
512 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
491 :child => { :tag => 'option', :attributes => { :selected => 'selected',
513 :child => { :tag => 'option', :attributes => { :selected => 'selected',
492 :value => '6' },
514 :value => '6' },
493 :content => 'High' }
515 :content => 'High' }
494 # Custom fields
516 # Custom fields
495 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
517 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
496 :child => { :tag => 'option', :attributes => { :selected => 'selected',
518 :child => { :tag => 'option', :attributes => { :selected => 'selected',
497 :value => 'Oracle' },
519 :value => 'Oracle' },
498 :content => 'Oracle' }
520 :content => 'Oracle' }
499 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
521 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
500 :value => 'Value for field 2'}
522 :value => 'Value for field 2'}
501 end
523 end
502
524
503 def test_copy_routing
525 def test_copy_routing
504 assert_routing(
526 assert_routing(
505 {:method => :get, :path => '/projects/world_domination/issues/567/copy'},
527 {:method => :get, :path => '/projects/world_domination/issues/567/copy'},
506 :controller => 'issues', :action => 'new', :project_id => 'world_domination', :copy_from => '567'
528 :controller => 'issues', :action => 'new', :project_id => 'world_domination', :copy_from => '567'
507 )
529 )
508 end
530 end
509
531
510 def test_copy_issue
532 def test_copy_issue
511 @request.session[:user_id] = 2
533 @request.session[:user_id] = 2
512 get :new, :project_id => 1, :copy_from => 1
534 get :new, :project_id => 1, :copy_from => 1
513 assert_template 'new'
535 assert_template 'new'
514 assert_not_nil assigns(:issue)
536 assert_not_nil assigns(:issue)
515 orig = Issue.find(1)
537 orig = Issue.find(1)
516 assert_equal orig.subject, assigns(:issue).subject
538 assert_equal orig.subject, assigns(:issue).subject
517 end
539 end
518
540
519 def test_edit_routing
541 def test_edit_routing
520 assert_routing(
542 assert_routing(
521 {:method => :get, :path => '/issues/1/edit'},
543 {:method => :get, :path => '/issues/1/edit'},
522 :controller => 'issues', :action => 'edit', :id => '1'
544 :controller => 'issues', :action => 'edit', :id => '1'
523 )
545 )
524 assert_recognizes( #TODO: use a PUT on the issue URI isntead, need to adjust form
546 assert_recognizes( #TODO: use a PUT on the issue URI isntead, need to adjust form
525 {:controller => 'issues', :action => 'edit', :id => '1'},
547 {:controller => 'issues', :action => 'edit', :id => '1'},
526 {:method => :post, :path => '/issues/1/edit'}
548 {:method => :post, :path => '/issues/1/edit'}
527 )
549 )
528 end
550 end
529
551
530 def test_get_edit
552 def test_get_edit
531 @request.session[:user_id] = 2
553 @request.session[:user_id] = 2
532 get :edit, :id => 1
554 get :edit, :id => 1
533 assert_response :success
555 assert_response :success
534 assert_template 'edit'
556 assert_template 'edit'
535 assert_not_nil assigns(:issue)
557 assert_not_nil assigns(:issue)
536 assert_equal Issue.find(1), assigns(:issue)
558 assert_equal Issue.find(1), assigns(:issue)
537 end
559 end
538
560
539 def test_get_edit_with_params
561 def test_get_edit_with_params
540 @request.session[:user_id] = 2
562 @request.session[:user_id] = 2
541 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
563 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
542 assert_response :success
564 assert_response :success
543 assert_template 'edit'
565 assert_template 'edit'
544
566
545 issue = assigns(:issue)
567 issue = assigns(:issue)
546 assert_not_nil issue
568 assert_not_nil issue
547
569
548 assert_equal 5, issue.status_id
570 assert_equal 5, issue.status_id
549 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
571 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
550 :child => { :tag => 'option',
572 :child => { :tag => 'option',
551 :content => 'Closed',
573 :content => 'Closed',
552 :attributes => { :selected => 'selected' } }
574 :attributes => { :selected => 'selected' } }
553
575
554 assert_equal 7, issue.priority_id
576 assert_equal 7, issue.priority_id
555 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
577 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
556 :child => { :tag => 'option',
578 :child => { :tag => 'option',
557 :content => 'Urgent',
579 :content => 'Urgent',
558 :attributes => { :selected => 'selected' } }
580 :attributes => { :selected => 'selected' } }
559 end
581 end
560
582
561 def test_reply_routing
583 def test_reply_routing
562 assert_routing(
584 assert_routing(
563 {:method => :post, :path => '/issues/1/quoted'},
585 {:method => :post, :path => '/issues/1/quoted'},
564 :controller => 'issues', :action => 'reply', :id => '1'
586 :controller => 'issues', :action => 'reply', :id => '1'
565 )
587 )
566 end
588 end
567
589
568 def test_reply_to_issue
590 def test_reply_to_issue
569 @request.session[:user_id] = 2
591 @request.session[:user_id] = 2
570 get :reply, :id => 1
592 get :reply, :id => 1
571 assert_response :success
593 assert_response :success
572 assert_select_rjs :show, "update"
594 assert_select_rjs :show, "update"
573 end
595 end
574
596
575 def test_reply_to_note
597 def test_reply_to_note
576 @request.session[:user_id] = 2
598 @request.session[:user_id] = 2
577 get :reply, :id => 1, :journal_id => 2
599 get :reply, :id => 1, :journal_id => 2
578 assert_response :success
600 assert_response :success
579 assert_select_rjs :show, "update"
601 assert_select_rjs :show, "update"
580 end
602 end
581
603
582 def test_post_edit_without_custom_fields_param
604 def test_post_edit_without_custom_fields_param
583 @request.session[:user_id] = 2
605 @request.session[:user_id] = 2
584 ActionMailer::Base.deliveries.clear
606 ActionMailer::Base.deliveries.clear
585
607
586 issue = Issue.find(1)
608 issue = Issue.find(1)
587 assert_equal '125', issue.custom_value_for(2).value
609 assert_equal '125', issue.custom_value_for(2).value
588 old_subject = issue.subject
610 old_subject = issue.subject
589 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
611 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
590
612
591 assert_difference('Journal.count') do
613 assert_difference('Journal.count') do
592 assert_difference('JournalDetail.count', 2) do
614 assert_difference('JournalDetail.count', 2) do
593 post :edit, :id => 1, :issue => {:subject => new_subject,
615 post :edit, :id => 1, :issue => {:subject => new_subject,
594 :priority_id => '6',
616 :priority_id => '6',
595 :category_id => '1' # no change
617 :category_id => '1' # no change
596 }
618 }
597 end
619 end
598 end
620 end
599 assert_redirected_to :action => 'show', :id => '1'
621 assert_redirected_to :action => 'show', :id => '1'
600 issue.reload
622 issue.reload
601 assert_equal new_subject, issue.subject
623 assert_equal new_subject, issue.subject
602 # Make sure custom fields were not cleared
624 # Make sure custom fields were not cleared
603 assert_equal '125', issue.custom_value_for(2).value
625 assert_equal '125', issue.custom_value_for(2).value
604
626
605 mail = ActionMailer::Base.deliveries.last
627 mail = ActionMailer::Base.deliveries.last
606 assert_kind_of TMail::Mail, mail
628 assert_kind_of TMail::Mail, mail
607 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
629 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
608 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
630 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
609 end
631 end
610
632
611 def test_post_edit_with_custom_field_change
633 def test_post_edit_with_custom_field_change
612 @request.session[:user_id] = 2
634 @request.session[:user_id] = 2
613 issue = Issue.find(1)
635 issue = Issue.find(1)
614 assert_equal '125', issue.custom_value_for(2).value
636 assert_equal '125', issue.custom_value_for(2).value
615
637
616 assert_difference('Journal.count') do
638 assert_difference('Journal.count') do
617 assert_difference('JournalDetail.count', 3) do
639 assert_difference('JournalDetail.count', 3) do
618 post :edit, :id => 1, :issue => {:subject => 'Custom field change',
640 post :edit, :id => 1, :issue => {:subject => 'Custom field change',
619 :priority_id => '6',
641 :priority_id => '6',
620 :category_id => '1', # no change
642 :category_id => '1', # no change
621 :custom_field_values => { '2' => 'New custom value' }
643 :custom_field_values => { '2' => 'New custom value' }
622 }
644 }
623 end
645 end
624 end
646 end
625 assert_redirected_to :action => 'show', :id => '1'
647 assert_redirected_to :action => 'show', :id => '1'
626 issue.reload
648 issue.reload
627 assert_equal 'New custom value', issue.custom_value_for(2).value
649 assert_equal 'New custom value', issue.custom_value_for(2).value
628
650
629 mail = ActionMailer::Base.deliveries.last
651 mail = ActionMailer::Base.deliveries.last
630 assert_kind_of TMail::Mail, mail
652 assert_kind_of TMail::Mail, mail
631 assert mail.body.include?("Searchable field changed from 125 to New custom value")
653 assert mail.body.include?("Searchable field changed from 125 to New custom value")
632 end
654 end
633
655
634 def test_post_edit_with_status_and_assignee_change
656 def test_post_edit_with_status_and_assignee_change
635 issue = Issue.find(1)
657 issue = Issue.find(1)
636 assert_equal 1, issue.status_id
658 assert_equal 1, issue.status_id
637 @request.session[:user_id] = 2
659 @request.session[:user_id] = 2
638 assert_difference('TimeEntry.count', 0) do
660 assert_difference('TimeEntry.count', 0) do
639 post :edit,
661 post :edit,
640 :id => 1,
662 :id => 1,
641 :issue => { :status_id => 2, :assigned_to_id => 3 },
663 :issue => { :status_id => 2, :assigned_to_id => 3 },
642 :notes => 'Assigned to dlopper',
664 :notes => 'Assigned to dlopper',
643 :time_entry => { :hours => '', :comments => '', :activity_id => Enumeration.activities.first }
665 :time_entry => { :hours => '', :comments => '', :activity_id => Enumeration.activities.first }
644 end
666 end
645 assert_redirected_to :action => 'show', :id => '1'
667 assert_redirected_to :action => 'show', :id => '1'
646 issue.reload
668 issue.reload
647 assert_equal 2, issue.status_id
669 assert_equal 2, issue.status_id
648 j = issue.journals.find(:first, :order => 'id DESC')
670 j = issue.journals.find(:first, :order => 'id DESC')
649 assert_equal 'Assigned to dlopper', j.notes
671 assert_equal 'Assigned to dlopper', j.notes
650 assert_equal 2, j.details.size
672 assert_equal 2, j.details.size
651
673
652 mail = ActionMailer::Base.deliveries.last
674 mail = ActionMailer::Base.deliveries.last
653 assert mail.body.include?("Status changed from New to Assigned")
675 assert mail.body.include?("Status changed from New to Assigned")
654 end
676 end
655
677
656 def test_post_edit_with_note_only
678 def test_post_edit_with_note_only
657 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
679 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
658 # anonymous user
680 # anonymous user
659 post :edit,
681 post :edit,
660 :id => 1,
682 :id => 1,
661 :notes => notes
683 :notes => notes
662 assert_redirected_to :action => 'show', :id => '1'
684 assert_redirected_to :action => 'show', :id => '1'
663 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
685 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
664 assert_equal notes, j.notes
686 assert_equal notes, j.notes
665 assert_equal 0, j.details.size
687 assert_equal 0, j.details.size
666 assert_equal User.anonymous, j.user
688 assert_equal User.anonymous, j.user
667
689
668 mail = ActionMailer::Base.deliveries.last
690 mail = ActionMailer::Base.deliveries.last
669 assert mail.body.include?(notes)
691 assert mail.body.include?(notes)
670 end
692 end
671
693
672 def test_post_edit_with_note_and_spent_time
694 def test_post_edit_with_note_and_spent_time
673 @request.session[:user_id] = 2
695 @request.session[:user_id] = 2
674 spent_hours_before = Issue.find(1).spent_hours
696 spent_hours_before = Issue.find(1).spent_hours
675 assert_difference('TimeEntry.count') do
697 assert_difference('TimeEntry.count') do
676 post :edit,
698 post :edit,
677 :id => 1,
699 :id => 1,
678 :notes => '2.5 hours added',
700 :notes => '2.5 hours added',
679 :time_entry => { :hours => '2.5', :comments => '', :activity_id => Enumeration.activities.first }
701 :time_entry => { :hours => '2.5', :comments => '', :activity_id => Enumeration.activities.first }
680 end
702 end
681 assert_redirected_to :action => 'show', :id => '1'
703 assert_redirected_to :action => 'show', :id => '1'
682
704
683 issue = Issue.find(1)
705 issue = Issue.find(1)
684
706
685 j = issue.journals.find(:first, :order => 'id DESC')
707 j = issue.journals.find(:first, :order => 'id DESC')
686 assert_equal '2.5 hours added', j.notes
708 assert_equal '2.5 hours added', j.notes
687 assert_equal 0, j.details.size
709 assert_equal 0, j.details.size
688
710
689 t = issue.time_entries.find(:first, :order => 'id DESC')
711 t = issue.time_entries.find(:first, :order => 'id DESC')
690 assert_not_nil t
712 assert_not_nil t
691 assert_equal 2.5, t.hours
713 assert_equal 2.5, t.hours
692 assert_equal spent_hours_before + 2.5, issue.spent_hours
714 assert_equal spent_hours_before + 2.5, issue.spent_hours
693 end
715 end
694
716
695 def test_post_edit_with_attachment_only
717 def test_post_edit_with_attachment_only
696 set_tmp_attachments_directory
718 set_tmp_attachments_directory
697
719
698 # Delete all fixtured journals, a race condition can occur causing the wrong
720 # Delete all fixtured journals, a race condition can occur causing the wrong
699 # journal to get fetched in the next find.
721 # journal to get fetched in the next find.
700 Journal.delete_all
722 Journal.delete_all
701
723
702 # anonymous user
724 # anonymous user
703 post :edit,
725 post :edit,
704 :id => 1,
726 :id => 1,
705 :notes => '',
727 :notes => '',
706 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
728 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
707 assert_redirected_to :action => 'show', :id => '1'
729 assert_redirected_to :action => 'show', :id => '1'
708 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
730 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
709 assert j.notes.blank?
731 assert j.notes.blank?
710 assert_equal 1, j.details.size
732 assert_equal 1, j.details.size
711 assert_equal 'testfile.txt', j.details.first.value
733 assert_equal 'testfile.txt', j.details.first.value
712 assert_equal User.anonymous, j.user
734 assert_equal User.anonymous, j.user
713
735
714 mail = ActionMailer::Base.deliveries.last
736 mail = ActionMailer::Base.deliveries.last
715 assert mail.body.include?('testfile.txt')
737 assert mail.body.include?('testfile.txt')
716 end
738 end
717
739
718 def test_post_edit_with_no_change
740 def test_post_edit_with_no_change
719 issue = Issue.find(1)
741 issue = Issue.find(1)
720 issue.journals.clear
742 issue.journals.clear
721 ActionMailer::Base.deliveries.clear
743 ActionMailer::Base.deliveries.clear
722
744
723 post :edit,
745 post :edit,
724 :id => 1,
746 :id => 1,
725 :notes => ''
747 :notes => ''
726 assert_redirected_to :action => 'show', :id => '1'
748 assert_redirected_to :action => 'show', :id => '1'
727
749
728 issue.reload
750 issue.reload
729 assert issue.journals.empty?
751 assert issue.journals.empty?
730 # No email should be sent
752 # No email should be sent
731 assert ActionMailer::Base.deliveries.empty?
753 assert ActionMailer::Base.deliveries.empty?
732 end
754 end
733
755
734 def test_post_edit_with_invalid_spent_time
756 def test_post_edit_with_invalid_spent_time
735 @request.session[:user_id] = 2
757 @request.session[:user_id] = 2
736 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
758 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
737
759
738 assert_no_difference('Journal.count') do
760 assert_no_difference('Journal.count') do
739 post :edit,
761 post :edit,
740 :id => 1,
762 :id => 1,
741 :notes => notes,
763 :notes => notes,
742 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
764 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
743 end
765 end
744 assert_response :success
766 assert_response :success
745 assert_template 'edit'
767 assert_template 'edit'
746
768
747 assert_tag :textarea, :attributes => { :name => 'notes' },
769 assert_tag :textarea, :attributes => { :name => 'notes' },
748 :content => notes
770 :content => notes
749 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
771 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
750 end
772 end
751
773
752 def test_bulk_edit
774 def test_bulk_edit
753 @request.session[:user_id] = 2
775 @request.session[:user_id] = 2
754 # update issues priority
776 # update issues priority
755 post :bulk_edit, :ids => [1, 2], :priority_id => 7,
777 post :bulk_edit, :ids => [1, 2], :priority_id => 7,
756 :assigned_to_id => '',
778 :assigned_to_id => '',
757 :custom_field_values => {'2' => ''},
779 :custom_field_values => {'2' => ''},
758 :notes => 'Bulk editing'
780 :notes => 'Bulk editing'
759 assert_response 302
781 assert_response 302
760 # check that the issues were updated
782 # check that the issues were updated
761 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
783 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
762
784
763 issue = Issue.find(1)
785 issue = Issue.find(1)
764 journal = issue.journals.find(:first, :order => 'created_on DESC')
786 journal = issue.journals.find(:first, :order => 'created_on DESC')
765 assert_equal '125', issue.custom_value_for(2).value
787 assert_equal '125', issue.custom_value_for(2).value
766 assert_equal 'Bulk editing', journal.notes
788 assert_equal 'Bulk editing', journal.notes
767 assert_equal 1, journal.details.size
789 assert_equal 1, journal.details.size
768 end
790 end
769
791
770 def test_bulk_edit_custom_field
792 def test_bulk_edit_custom_field
771 @request.session[:user_id] = 2
793 @request.session[:user_id] = 2
772 # update issues priority
794 # update issues priority
773 post :bulk_edit, :ids => [1, 2], :priority_id => '',
795 post :bulk_edit, :ids => [1, 2], :priority_id => '',
774 :assigned_to_id => '',
796 :assigned_to_id => '',
775 :custom_field_values => {'2' => '777'},
797 :custom_field_values => {'2' => '777'},
776 :notes => 'Bulk editing custom field'
798 :notes => 'Bulk editing custom field'
777 assert_response 302
799 assert_response 302
778
800
779 issue = Issue.find(1)
801 issue = Issue.find(1)
780 journal = issue.journals.find(:first, :order => 'created_on DESC')
802 journal = issue.journals.find(:first, :order => 'created_on DESC')
781 assert_equal '777', issue.custom_value_for(2).value
803 assert_equal '777', issue.custom_value_for(2).value
782 assert_equal 1, journal.details.size
804 assert_equal 1, journal.details.size
783 assert_equal '125', journal.details.first.old_value
805 assert_equal '125', journal.details.first.old_value
784 assert_equal '777', journal.details.first.value
806 assert_equal '777', journal.details.first.value
785 end
807 end
786
808
787 def test_bulk_unassign
809 def test_bulk_unassign
788 assert_not_nil Issue.find(2).assigned_to
810 assert_not_nil Issue.find(2).assigned_to
789 @request.session[:user_id] = 2
811 @request.session[:user_id] = 2
790 # unassign issues
812 # unassign issues
791 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :assigned_to_id => 'none'
813 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :assigned_to_id => 'none'
792 assert_response 302
814 assert_response 302
793 # check that the issues were updated
815 # check that the issues were updated
794 assert_nil Issue.find(2).assigned_to
816 assert_nil Issue.find(2).assigned_to
795 end
817 end
796
818
797 def test_move_routing
819 def test_move_routing
798 assert_routing(
820 assert_routing(
799 {:method => :get, :path => '/issues/1/move'},
821 {:method => :get, :path => '/issues/1/move'},
800 :controller => 'issues', :action => 'move', :id => '1'
822 :controller => 'issues', :action => 'move', :id => '1'
801 )
823 )
802 assert_recognizes(
824 assert_recognizes(
803 {:controller => 'issues', :action => 'move', :id => '1'},
825 {:controller => 'issues', :action => 'move', :id => '1'},
804 {:method => :post, :path => '/issues/1/move'}
826 {:method => :post, :path => '/issues/1/move'}
805 )
827 )
806 end
828 end
807
829
808 def test_move_one_issue_to_another_project
830 def test_move_one_issue_to_another_project
809 @request.session[:user_id] = 1
831 @request.session[:user_id] = 1
810 post :move, :id => 1, :new_project_id => 2
832 post :move, :id => 1, :new_project_id => 2
811 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
833 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
812 assert_equal 2, Issue.find(1).project_id
834 assert_equal 2, Issue.find(1).project_id
813 end
835 end
814
836
815 def test_bulk_move_to_another_project
837 def test_bulk_move_to_another_project
816 @request.session[:user_id] = 1
838 @request.session[:user_id] = 1
817 post :move, :ids => [1, 2], :new_project_id => 2
839 post :move, :ids => [1, 2], :new_project_id => 2
818 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
840 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
819 # Issues moved to project 2
841 # Issues moved to project 2
820 assert_equal 2, Issue.find(1).project_id
842 assert_equal 2, Issue.find(1).project_id
821 assert_equal 2, Issue.find(2).project_id
843 assert_equal 2, Issue.find(2).project_id
822 # No tracker change
844 # No tracker change
823 assert_equal 1, Issue.find(1).tracker_id
845 assert_equal 1, Issue.find(1).tracker_id
824 assert_equal 2, Issue.find(2).tracker_id
846 assert_equal 2, Issue.find(2).tracker_id
825 end
847 end
826
848
827 def test_bulk_move_to_another_tracker
849 def test_bulk_move_to_another_tracker
828 @request.session[:user_id] = 1
850 @request.session[:user_id] = 1
829 post :move, :ids => [1, 2], :new_tracker_id => 2
851 post :move, :ids => [1, 2], :new_tracker_id => 2
830 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
852 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
831 assert_equal 2, Issue.find(1).tracker_id
853 assert_equal 2, Issue.find(1).tracker_id
832 assert_equal 2, Issue.find(2).tracker_id
854 assert_equal 2, Issue.find(2).tracker_id
833 end
855 end
834
856
835 def test_bulk_copy_to_another_project
857 def test_bulk_copy_to_another_project
836 @request.session[:user_id] = 1
858 @request.session[:user_id] = 1
837 assert_difference 'Issue.count', 2 do
859 assert_difference 'Issue.count', 2 do
838 assert_no_difference 'Project.find(1).issues.count' do
860 assert_no_difference 'Project.find(1).issues.count' do
839 post :move, :ids => [1, 2], :new_project_id => 2, :copy_options => {:copy => '1'}
861 post :move, :ids => [1, 2], :new_project_id => 2, :copy_options => {:copy => '1'}
840 end
862 end
841 end
863 end
842 assert_redirected_to 'projects/ecookbook/issues'
864 assert_redirected_to 'projects/ecookbook/issues'
843 end
865 end
844
866
845 def test_context_menu_one_issue
867 def test_context_menu_one_issue
846 @request.session[:user_id] = 2
868 @request.session[:user_id] = 2
847 get :context_menu, :ids => [1]
869 get :context_menu, :ids => [1]
848 assert_response :success
870 assert_response :success
849 assert_template 'context_menu'
871 assert_template 'context_menu'
850 assert_tag :tag => 'a', :content => 'Edit',
872 assert_tag :tag => 'a', :content => 'Edit',
851 :attributes => { :href => '/issues/1/edit',
873 :attributes => { :href => '/issues/1/edit',
852 :class => 'icon-edit' }
874 :class => 'icon-edit' }
853 assert_tag :tag => 'a', :content => 'Closed',
875 assert_tag :tag => 'a', :content => 'Closed',
854 :attributes => { :href => '/issues/1/edit?issue%5Bstatus_id%5D=5',
876 :attributes => { :href => '/issues/1/edit?issue%5Bstatus_id%5D=5',
855 :class => '' }
877 :class => '' }
856 assert_tag :tag => 'a', :content => 'Immediate',
878 assert_tag :tag => 'a', :content => 'Immediate',
857 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;priority_id=8',
879 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;priority_id=8',
858 :class => '' }
880 :class => '' }
859 assert_tag :tag => 'a', :content => 'Dave Lopper',
881 assert_tag :tag => 'a', :content => 'Dave Lopper',
860 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1',
882 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1',
861 :class => '' }
883 :class => '' }
862 assert_tag :tag => 'a', :content => 'Copy',
884 assert_tag :tag => 'a', :content => 'Copy',
863 :attributes => { :href => '/projects/ecookbook/issues/1/copy',
885 :attributes => { :href => '/projects/ecookbook/issues/1/copy',
864 :class => 'icon-copy' }
886 :class => 'icon-copy' }
865 assert_tag :tag => 'a', :content => 'Move',
887 assert_tag :tag => 'a', :content => 'Move',
866 :attributes => { :href => '/issues/move?ids%5B%5D=1',
888 :attributes => { :href => '/issues/move?ids%5B%5D=1',
867 :class => 'icon-move' }
889 :class => 'icon-move' }
868 assert_tag :tag => 'a', :content => 'Delete',
890 assert_tag :tag => 'a', :content => 'Delete',
869 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
891 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
870 :class => 'icon-del' }
892 :class => 'icon-del' }
871 end
893 end
872
894
873 def test_context_menu_one_issue_by_anonymous
895 def test_context_menu_one_issue_by_anonymous
874 get :context_menu, :ids => [1]
896 get :context_menu, :ids => [1]
875 assert_response :success
897 assert_response :success
876 assert_template 'context_menu'
898 assert_template 'context_menu'
877 assert_tag :tag => 'a', :content => 'Delete',
899 assert_tag :tag => 'a', :content => 'Delete',
878 :attributes => { :href => '#',
900 :attributes => { :href => '#',
879 :class => 'icon-del disabled' }
901 :class => 'icon-del disabled' }
880 end
902 end
881
903
882 def test_context_menu_multiple_issues_of_same_project
904 def test_context_menu_multiple_issues_of_same_project
883 @request.session[:user_id] = 2
905 @request.session[:user_id] = 2
884 get :context_menu, :ids => [1, 2]
906 get :context_menu, :ids => [1, 2]
885 assert_response :success
907 assert_response :success
886 assert_template 'context_menu'
908 assert_template 'context_menu'
887 assert_tag :tag => 'a', :content => 'Edit',
909 assert_tag :tag => 'a', :content => 'Edit',
888 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
910 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
889 :class => 'icon-edit' }
911 :class => 'icon-edit' }
890 assert_tag :tag => 'a', :content => 'Immediate',
912 assert_tag :tag => 'a', :content => 'Immediate',
891 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;priority_id=8',
913 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;priority_id=8',
892 :class => '' }
914 :class => '' }
893 assert_tag :tag => 'a', :content => 'Dave Lopper',
915 assert_tag :tag => 'a', :content => 'Dave Lopper',
894 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
916 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
895 :class => '' }
917 :class => '' }
896 assert_tag :tag => 'a', :content => 'Move',
918 assert_tag :tag => 'a', :content => 'Move',
897 :attributes => { :href => '/issues/move?ids%5B%5D=1&amp;ids%5B%5D=2',
919 :attributes => { :href => '/issues/move?ids%5B%5D=1&amp;ids%5B%5D=2',
898 :class => 'icon-move' }
920 :class => 'icon-move' }
899 assert_tag :tag => 'a', :content => 'Delete',
921 assert_tag :tag => 'a', :content => 'Delete',
900 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
922 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
901 :class => 'icon-del' }
923 :class => 'icon-del' }
902 end
924 end
903
925
904 def test_context_menu_multiple_issues_of_different_project
926 def test_context_menu_multiple_issues_of_different_project
905 @request.session[:user_id] = 2
927 @request.session[:user_id] = 2
906 get :context_menu, :ids => [1, 2, 4]
928 get :context_menu, :ids => [1, 2, 4]
907 assert_response :success
929 assert_response :success
908 assert_template 'context_menu'
930 assert_template 'context_menu'
909 assert_tag :tag => 'a', :content => 'Delete',
931 assert_tag :tag => 'a', :content => 'Delete',
910 :attributes => { :href => '#',
932 :attributes => { :href => '#',
911 :class => 'icon-del disabled' }
933 :class => 'icon-del disabled' }
912 end
934 end
913
935
914 def test_destroy_routing
936 def test_destroy_routing
915 assert_recognizes( #TODO: use DELETE on issue URI (need to change forms)
937 assert_recognizes( #TODO: use DELETE on issue URI (need to change forms)
916 {:controller => 'issues', :action => 'destroy', :id => '1'},
938 {:controller => 'issues', :action => 'destroy', :id => '1'},
917 {:method => :post, :path => '/issues/1/destroy'}
939 {:method => :post, :path => '/issues/1/destroy'}
918 )
940 )
919 end
941 end
920
942
921 def test_destroy_issue_with_no_time_entries
943 def test_destroy_issue_with_no_time_entries
922 assert_nil TimeEntry.find_by_issue_id(2)
944 assert_nil TimeEntry.find_by_issue_id(2)
923 @request.session[:user_id] = 2
945 @request.session[:user_id] = 2
924 post :destroy, :id => 2
946 post :destroy, :id => 2
925 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
947 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
926 assert_nil Issue.find_by_id(2)
948 assert_nil Issue.find_by_id(2)
927 end
949 end
928
950
929 def test_destroy_issues_with_time_entries
951 def test_destroy_issues_with_time_entries
930 @request.session[:user_id] = 2
952 @request.session[:user_id] = 2
931 post :destroy, :ids => [1, 3]
953 post :destroy, :ids => [1, 3]
932 assert_response :success
954 assert_response :success
933 assert_template 'destroy'
955 assert_template 'destroy'
934 assert_not_nil assigns(:hours)
956 assert_not_nil assigns(:hours)
935 assert Issue.find_by_id(1) && Issue.find_by_id(3)
957 assert Issue.find_by_id(1) && Issue.find_by_id(3)
936 end
958 end
937
959
938 def test_destroy_issues_and_destroy_time_entries
960 def test_destroy_issues_and_destroy_time_entries
939 @request.session[:user_id] = 2
961 @request.session[:user_id] = 2
940 post :destroy, :ids => [1, 3], :todo => 'destroy'
962 post :destroy, :ids => [1, 3], :todo => 'destroy'
941 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
963 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
942 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
964 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
943 assert_nil TimeEntry.find_by_id([1, 2])
965 assert_nil TimeEntry.find_by_id([1, 2])
944 end
966 end
945
967
946 def test_destroy_issues_and_assign_time_entries_to_project
968 def test_destroy_issues_and_assign_time_entries_to_project
947 @request.session[:user_id] = 2
969 @request.session[:user_id] = 2
948 post :destroy, :ids => [1, 3], :todo => 'nullify'
970 post :destroy, :ids => [1, 3], :todo => 'nullify'
949 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
971 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
950 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
972 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
951 assert_nil TimeEntry.find(1).issue_id
973 assert_nil TimeEntry.find(1).issue_id
952 assert_nil TimeEntry.find(2).issue_id
974 assert_nil TimeEntry.find(2).issue_id
953 end
975 end
954
976
955 def test_destroy_issues_and_reassign_time_entries_to_another_issue
977 def test_destroy_issues_and_reassign_time_entries_to_another_issue
956 @request.session[:user_id] = 2
978 @request.session[:user_id] = 2
957 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
979 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
958 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
980 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
959 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
981 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
960 assert_equal 2, TimeEntry.find(1).issue_id
982 assert_equal 2, TimeEntry.find(1).issue_id
961 assert_equal 2, TimeEntry.find(2).issue_id
983 assert_equal 2, TimeEntry.find(2).issue_id
962 end
984 end
963 end
985 end
General Comments 0
You need to be logged in to leave comments. Login now