##// END OF EJS Templates
When copying issues, let the status be changed to default or left unchanged....
Jean-Philippe Lang -
r9270:09375960d69d
parent child
Show More
@@ -1,435 +1,439
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class IssuesController < ApplicationController
18 class IssuesController < ApplicationController
19 menu_item :new_issue, :only => [:new, :create]
19 menu_item :new_issue, :only => [:new, :create]
20 default_search_scope :issues
20 default_search_scope :issues
21
21
22 before_filter :find_issue, :only => [:show, :edit, :update]
22 before_filter :find_issue, :only => [:show, :edit, :update]
23 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy]
23 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy]
24 before_filter :find_project, :only => [:new, :create]
24 before_filter :find_project, :only => [:new, :create]
25 before_filter :authorize, :except => [:index]
25 before_filter :authorize, :except => [:index]
26 before_filter :find_optional_project, :only => [:index]
26 before_filter :find_optional_project, :only => [:index]
27 before_filter :check_for_default_issue_status, :only => [:new, :create]
27 before_filter :check_for_default_issue_status, :only => [:new, :create]
28 before_filter :build_new_issue_from_params, :only => [:new, :create]
28 before_filter :build_new_issue_from_params, :only => [:new, :create]
29 accept_rss_auth :index, :show
29 accept_rss_auth :index, :show
30 accept_api_auth :index, :show, :create, :update, :destroy
30 accept_api_auth :index, :show, :create, :update, :destroy
31
31
32 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
32 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
33
33
34 helper :journals
34 helper :journals
35 helper :projects
35 helper :projects
36 include ProjectsHelper
36 include ProjectsHelper
37 helper :custom_fields
37 helper :custom_fields
38 include CustomFieldsHelper
38 include CustomFieldsHelper
39 helper :issue_relations
39 helper :issue_relations
40 include IssueRelationsHelper
40 include IssueRelationsHelper
41 helper :watchers
41 helper :watchers
42 include WatchersHelper
42 include WatchersHelper
43 helper :attachments
43 helper :attachments
44 include AttachmentsHelper
44 include AttachmentsHelper
45 helper :queries
45 helper :queries
46 include QueriesHelper
46 include QueriesHelper
47 helper :repositories
47 helper :repositories
48 include RepositoriesHelper
48 include RepositoriesHelper
49 helper :sort
49 helper :sort
50 include SortHelper
50 include SortHelper
51 include IssuesHelper
51 include IssuesHelper
52 helper :timelog
52 helper :timelog
53 helper :gantt
53 helper :gantt
54 include Redmine::Export::PDF
54 include Redmine::Export::PDF
55
55
56 def index
56 def index
57 retrieve_query
57 retrieve_query
58 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
58 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
59 sort_update(@query.sortable_columns)
59 sort_update(@query.sortable_columns)
60
60
61 if @query.valid?
61 if @query.valid?
62 case params[:format]
62 case params[:format]
63 when 'csv', 'pdf'
63 when 'csv', 'pdf'
64 @limit = Setting.issues_export_limit.to_i
64 @limit = Setting.issues_export_limit.to_i
65 when 'atom'
65 when 'atom'
66 @limit = Setting.feeds_limit.to_i
66 @limit = Setting.feeds_limit.to_i
67 when 'xml', 'json'
67 when 'xml', 'json'
68 @offset, @limit = api_offset_and_limit
68 @offset, @limit = api_offset_and_limit
69 else
69 else
70 @limit = per_page_option
70 @limit = per_page_option
71 end
71 end
72
72
73 @issue_count = @query.issue_count
73 @issue_count = @query.issue_count
74 @issue_pages = Paginator.new self, @issue_count, @limit, params['page']
74 @issue_pages = Paginator.new self, @issue_count, @limit, params['page']
75 @offset ||= @issue_pages.current.offset
75 @offset ||= @issue_pages.current.offset
76 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
76 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
77 :order => sort_clause,
77 :order => sort_clause,
78 :offset => @offset,
78 :offset => @offset,
79 :limit => @limit)
79 :limit => @limit)
80 @issue_count_by_group = @query.issue_count_by_group
80 @issue_count_by_group = @query.issue_count_by_group
81
81
82 respond_to do |format|
82 respond_to do |format|
83 format.html { render :template => 'issues/index', :layout => !request.xhr? }
83 format.html { render :template => 'issues/index', :layout => !request.xhr? }
84 format.api {
84 format.api {
85 Issue.load_relations(@issues) if include_in_api_response?('relations')
85 Issue.load_relations(@issues) if include_in_api_response?('relations')
86 }
86 }
87 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
87 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
88 format.csv { send_data(issues_to_csv(@issues, @project, @query, params), :type => 'text/csv; header=present', :filename => 'export.csv') }
88 format.csv { send_data(issues_to_csv(@issues, @project, @query, params), :type => 'text/csv; header=present', :filename => 'export.csv') }
89 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
89 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
90 end
90 end
91 else
91 else
92 respond_to do |format|
92 respond_to do |format|
93 format.html { render(:template => 'issues/index', :layout => !request.xhr?) }
93 format.html { render(:template => 'issues/index', :layout => !request.xhr?) }
94 format.any(:atom, :csv, :pdf) { render(:nothing => true) }
94 format.any(:atom, :csv, :pdf) { render(:nothing => true) }
95 format.api { render_validation_errors(@query) }
95 format.api { render_validation_errors(@query) }
96 end
96 end
97 end
97 end
98 rescue ActiveRecord::RecordNotFound
98 rescue ActiveRecord::RecordNotFound
99 render_404
99 render_404
100 end
100 end
101
101
102 def show
102 def show
103 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
103 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
104 @journals.each_with_index {|j,i| j.indice = i+1}
104 @journals.each_with_index {|j,i| j.indice = i+1}
105 @journals.reverse! if User.current.wants_comments_in_reverse_order?
105 @journals.reverse! if User.current.wants_comments_in_reverse_order?
106
106
107 @changesets = @issue.changesets.visible.all
107 @changesets = @issue.changesets.visible.all
108 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
108 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
109
109
110 @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
110 @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
111 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
111 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
112 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
112 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
113 @priorities = IssuePriority.active
113 @priorities = IssuePriority.active
114 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
114 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
115 respond_to do |format|
115 respond_to do |format|
116 format.html {
116 format.html {
117 retrieve_previous_and_next_issue_ids
117 retrieve_previous_and_next_issue_ids
118 render :template => 'issues/show'
118 render :template => 'issues/show'
119 }
119 }
120 format.api
120 format.api
121 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
121 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
122 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
122 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
123 end
123 end
124 end
124 end
125
125
126 # Add a new issue
126 # Add a new issue
127 # The new issue will be created from an existing one if copy_from parameter is given
127 # The new issue will be created from an existing one if copy_from parameter is given
128 def new
128 def new
129 respond_to do |format|
129 respond_to do |format|
130 format.html { render :action => 'new', :layout => !request.xhr? }
130 format.html { render :action => 'new', :layout => !request.xhr? }
131 format.js {
131 format.js {
132 render(:update) { |page|
132 render(:update) { |page|
133 if params[:project_change]
133 if params[:project_change]
134 page.replace_html 'all_attributes', :partial => 'form'
134 page.replace_html 'all_attributes', :partial => 'form'
135 else
135 else
136 page.replace_html 'attributes', :partial => 'attributes'
136 page.replace_html 'attributes', :partial => 'attributes'
137 end
137 end
138 m = User.current.allowed_to?(:log_time, @issue.project) ? 'show' : 'hide'
138 m = User.current.allowed_to?(:log_time, @issue.project) ? 'show' : 'hide'
139 page << "if ($('log_time')) {Element.#{m}('log_time');}"
139 page << "if ($('log_time')) {Element.#{m}('log_time');}"
140 }
140 }
141 }
141 }
142 end
142 end
143 end
143 end
144
144
145 def create
145 def create
146 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
146 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
147 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
147 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
148 if @issue.save
148 if @issue.save
149 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
149 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
150 respond_to do |format|
150 respond_to do |format|
151 format.html {
151 format.html {
152 render_attachment_warning_if_needed(@issue)
152 render_attachment_warning_if_needed(@issue)
153 flash[:notice] = l(:notice_issue_successful_create, :id => "<a href='#{issue_path(@issue)}'>##{@issue.id}</a>")
153 flash[:notice] = l(:notice_issue_successful_create, :id => "<a href='#{issue_path(@issue)}'>##{@issue.id}</a>")
154 redirect_to(params[:continue] ? { :action => 'new', :project_id => @issue.project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } :
154 redirect_to(params[:continue] ? { :action => 'new', :project_id => @issue.project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } :
155 { :action => 'show', :id => @issue })
155 { :action => 'show', :id => @issue })
156 }
156 }
157 format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
157 format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
158 end
158 end
159 return
159 return
160 else
160 else
161 respond_to do |format|
161 respond_to do |format|
162 format.html { render :action => 'new' }
162 format.html { render :action => 'new' }
163 format.api { render_validation_errors(@issue) }
163 format.api { render_validation_errors(@issue) }
164 end
164 end
165 end
165 end
166 end
166 end
167
167
168 def edit
168 def edit
169 return unless update_issue_from_params
169 return unless update_issue_from_params
170
170
171 respond_to do |format|
171 respond_to do |format|
172 format.html { }
172 format.html { }
173 format.xml { }
173 format.xml { }
174 end
174 end
175 end
175 end
176
176
177 def update
177 def update
178 return unless update_issue_from_params
178 return unless update_issue_from_params
179 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
179 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
180 saved = false
180 saved = false
181 begin
181 begin
182 saved = @issue.save_issue_with_child_records(params, @time_entry)
182 saved = @issue.save_issue_with_child_records(params, @time_entry)
183 rescue ActiveRecord::StaleObjectError
183 rescue ActiveRecord::StaleObjectError
184 @conflict = true
184 @conflict = true
185 if params[:last_journal_id]
185 if params[:last_journal_id]
186 if params[:last_journal_id].present?
186 if params[:last_journal_id].present?
187 last_journal_id = params[:last_journal_id].to_i
187 last_journal_id = params[:last_journal_id].to_i
188 @conflict_journals = @issue.journals.all(:conditions => ["#{Journal.table_name}.id > ?", last_journal_id])
188 @conflict_journals = @issue.journals.all(:conditions => ["#{Journal.table_name}.id > ?", last_journal_id])
189 else
189 else
190 @conflict_journals = @issue.journals.all
190 @conflict_journals = @issue.journals.all
191 end
191 end
192 end
192 end
193 end
193 end
194
194
195 if saved
195 if saved
196 render_attachment_warning_if_needed(@issue)
196 render_attachment_warning_if_needed(@issue)
197 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
197 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
198
198
199 respond_to do |format|
199 respond_to do |format|
200 format.html { redirect_back_or_default({:action => 'show', :id => @issue}) }
200 format.html { redirect_back_or_default({:action => 'show', :id => @issue}) }
201 format.api { head :ok }
201 format.api { head :ok }
202 end
202 end
203 else
203 else
204 respond_to do |format|
204 respond_to do |format|
205 format.html { render :action => 'edit' }
205 format.html { render :action => 'edit' }
206 format.api { render_validation_errors(@issue) }
206 format.api { render_validation_errors(@issue) }
207 end
207 end
208 end
208 end
209 end
209 end
210
210
211 # Bulk edit/copy a set of issues
211 # Bulk edit/copy a set of issues
212 def bulk_edit
212 def bulk_edit
213 @issues.sort!
213 @issues.sort!
214 @copy = params[:copy].present?
214 @copy = params[:copy].present?
215 @notes = params[:notes]
215 @notes = params[:notes]
216
216
217 if User.current.allowed_to?(:move_issues, @projects)
217 if User.current.allowed_to?(:move_issues, @projects)
218 @allowed_projects = Issue.allowed_target_projects_on_move
218 @allowed_projects = Issue.allowed_target_projects_on_move
219 if params[:issue]
219 if params[:issue]
220 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:issue][:project_id].to_s}
220 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:issue][:project_id].to_s}
221 if @target_project
221 if @target_project
222 target_projects = [@target_project]
222 target_projects = [@target_project]
223 end
223 end
224 end
224 end
225 end
225 end
226 target_projects ||= @projects
226 target_projects ||= @projects
227
227
228 @available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
228 if @copy
229 @available_statuses = [IssueStatus.default]
230 else
231 @available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
232 end
229 @custom_fields = target_projects.map{|p|p.all_issue_custom_fields}.reduce(:&)
233 @custom_fields = target_projects.map{|p|p.all_issue_custom_fields}.reduce(:&)
230 @assignables = target_projects.map(&:assignable_users).reduce(:&)
234 @assignables = target_projects.map(&:assignable_users).reduce(:&)
231 @trackers = target_projects.map(&:trackers).reduce(:&)
235 @trackers = target_projects.map(&:trackers).reduce(:&)
232 @versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
236 @versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
233 @categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
237 @categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
234
238
235 @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
239 @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
236 render :layout => false if request.xhr?
240 render :layout => false if request.xhr?
237 end
241 end
238
242
239 def bulk_update
243 def bulk_update
240 @issues.sort!
244 @issues.sort!
241 @copy = params[:copy].present?
245 @copy = params[:copy].present?
242 attributes = parse_params_for_bulk_issue_attributes(params)
246 attributes = parse_params_for_bulk_issue_attributes(params)
243
247
244 unsaved_issue_ids = []
248 unsaved_issue_ids = []
245 moved_issues = []
249 moved_issues = []
246 @issues.each do |issue|
250 @issues.each do |issue|
247 issue.reload
251 issue.reload
248 if @copy
252 if @copy
249 issue = issue.copy
253 issue = issue.copy
250 end
254 end
251 journal = issue.init_journal(User.current, params[:notes])
255 journal = issue.init_journal(User.current, params[:notes])
252 issue.safe_attributes = attributes
256 issue.safe_attributes = attributes
253 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
257 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
254 if issue.save
258 if issue.save
255 moved_issues << issue
259 moved_issues << issue
256 else
260 else
257 # Keep unsaved issue ids to display them in flash error
261 # Keep unsaved issue ids to display them in flash error
258 unsaved_issue_ids << issue.id
262 unsaved_issue_ids << issue.id
259 end
263 end
260 end
264 end
261 set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
265 set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
262
266
263 if params[:follow]
267 if params[:follow]
264 if @issues.size == 1 && moved_issues.size == 1
268 if @issues.size == 1 && moved_issues.size == 1
265 redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first
269 redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first
266 elsif moved_issues.map(&:project).uniq.size == 1
270 elsif moved_issues.map(&:project).uniq.size == 1
267 redirect_to :controller => 'issues', :action => 'index', :project_id => moved_issues.map(&:project).first
271 redirect_to :controller => 'issues', :action => 'index', :project_id => moved_issues.map(&:project).first
268 end
272 end
269 else
273 else
270 redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
274 redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
271 end
275 end
272 end
276 end
273
277
274 def destroy
278 def destroy
275 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
279 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
276 if @hours > 0
280 if @hours > 0
277 case params[:todo]
281 case params[:todo]
278 when 'destroy'
282 when 'destroy'
279 # nothing to do
283 # nothing to do
280 when 'nullify'
284 when 'nullify'
281 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
285 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
282 when 'reassign'
286 when 'reassign'
283 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
287 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
284 if reassign_to.nil?
288 if reassign_to.nil?
285 flash.now[:error] = l(:error_issue_not_found_in_project)
289 flash.now[:error] = l(:error_issue_not_found_in_project)
286 return
290 return
287 else
291 else
288 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
292 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
289 end
293 end
290 else
294 else
291 # display the destroy form if it's a user request
295 # display the destroy form if it's a user request
292 return unless api_request?
296 return unless api_request?
293 end
297 end
294 end
298 end
295 @issues.each do |issue|
299 @issues.each do |issue|
296 begin
300 begin
297 issue.reload.destroy
301 issue.reload.destroy
298 rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists
302 rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists
299 # nothing to do, issue was already deleted (eg. by a parent)
303 # nothing to do, issue was already deleted (eg. by a parent)
300 end
304 end
301 end
305 end
302 respond_to do |format|
306 respond_to do |format|
303 format.html { redirect_back_or_default(:action => 'index', :project_id => @project) }
307 format.html { redirect_back_or_default(:action => 'index', :project_id => @project) }
304 format.api { head :ok }
308 format.api { head :ok }
305 end
309 end
306 end
310 end
307
311
308 private
312 private
309 def find_issue
313 def find_issue
310 # Issue.visible.find(...) can not be used to redirect user to the login form
314 # Issue.visible.find(...) can not be used to redirect user to the login form
311 # if the issue actually exists but requires authentication
315 # if the issue actually exists but requires authentication
312 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
316 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
313 unless @issue.visible?
317 unless @issue.visible?
314 deny_access
318 deny_access
315 return
319 return
316 end
320 end
317 @project = @issue.project
321 @project = @issue.project
318 rescue ActiveRecord::RecordNotFound
322 rescue ActiveRecord::RecordNotFound
319 render_404
323 render_404
320 end
324 end
321
325
322 def find_project
326 def find_project
323 project_id = params[:project_id] || (params[:issue] && params[:issue][:project_id])
327 project_id = params[:project_id] || (params[:issue] && params[:issue][:project_id])
324 @project = Project.find(project_id)
328 @project = Project.find(project_id)
325 rescue ActiveRecord::RecordNotFound
329 rescue ActiveRecord::RecordNotFound
326 render_404
330 render_404
327 end
331 end
328
332
329 def retrieve_previous_and_next_issue_ids
333 def retrieve_previous_and_next_issue_ids
330 retrieve_query_from_session
334 retrieve_query_from_session
331 if @query
335 if @query
332 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
336 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
333 sort_update(@query.sortable_columns, 'issues_index_sort')
337 sort_update(@query.sortable_columns, 'issues_index_sort')
334 limit = 500
338 limit = 500
335 issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1), :include => [:assigned_to, :tracker, :priority, :category, :fixed_version])
339 issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1), :include => [:assigned_to, :tracker, :priority, :category, :fixed_version])
336 if (idx = issue_ids.index(@issue.id)) && idx < limit
340 if (idx = issue_ids.index(@issue.id)) && idx < limit
337 if issue_ids.size < 500
341 if issue_ids.size < 500
338 @issue_position = idx + 1
342 @issue_position = idx + 1
339 @issue_count = issue_ids.size
343 @issue_count = issue_ids.size
340 end
344 end
341 @prev_issue_id = issue_ids[idx - 1] if idx > 0
345 @prev_issue_id = issue_ids[idx - 1] if idx > 0
342 @next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
346 @next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
343 end
347 end
344 end
348 end
345 end
349 end
346
350
347 # Used by #edit and #update to set some common instance variables
351 # Used by #edit and #update to set some common instance variables
348 # from the params
352 # from the params
349 # TODO: Refactor, not everything in here is needed by #edit
353 # TODO: Refactor, not everything in here is needed by #edit
350 def update_issue_from_params
354 def update_issue_from_params
351 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
355 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
352 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
356 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
353 @time_entry.attributes = params[:time_entry]
357 @time_entry.attributes = params[:time_entry]
354
358
355 @notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
359 @notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
356 @issue.init_journal(User.current, @notes)
360 @issue.init_journal(User.current, @notes)
357
361
358 issue_attributes = params[:issue]
362 issue_attributes = params[:issue]
359 if issue_attributes && params[:conflict_resolution]
363 if issue_attributes && params[:conflict_resolution]
360 case params[:conflict_resolution]
364 case params[:conflict_resolution]
361 when 'overwrite'
365 when 'overwrite'
362 issue_attributes = issue_attributes.dup
366 issue_attributes = issue_attributes.dup
363 issue_attributes.delete(:lock_version)
367 issue_attributes.delete(:lock_version)
364 when 'add_notes'
368 when 'add_notes'
365 issue_attributes = {}
369 issue_attributes = {}
366 when 'cancel'
370 when 'cancel'
367 redirect_to issue_path(@issue)
371 redirect_to issue_path(@issue)
368 return false
372 return false
369 end
373 end
370 end
374 end
371 @issue.safe_attributes = issue_attributes
375 @issue.safe_attributes = issue_attributes
372 @priorities = IssuePriority.active
376 @priorities = IssuePriority.active
373 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
377 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
374 true
378 true
375 end
379 end
376
380
377 # TODO: Refactor, lots of extra code in here
381 # TODO: Refactor, lots of extra code in here
378 # TODO: Changing tracker on an existing issue should not trigger this
382 # TODO: Changing tracker on an existing issue should not trigger this
379 def build_new_issue_from_params
383 def build_new_issue_from_params
380 if params[:id].blank?
384 if params[:id].blank?
381 @issue = Issue.new
385 @issue = Issue.new
382 if params[:copy_from]
386 if params[:copy_from]
383 begin
387 begin
384 @copy_from = Issue.visible.find(params[:copy_from])
388 @copy_from = Issue.visible.find(params[:copy_from])
385 @copy_attachments = params[:copy_attachments].present? || request.get?
389 @copy_attachments = params[:copy_attachments].present? || request.get?
386 @issue.copy_from(@copy_from, :attachments => @copy_attachments)
390 @issue.copy_from(@copy_from, :attachments => @copy_attachments)
387 rescue ActiveRecord::RecordNotFound
391 rescue ActiveRecord::RecordNotFound
388 render_404
392 render_404
389 return
393 return
390 end
394 end
391 end
395 end
392 @issue.project = @project
396 @issue.project = @project
393 else
397 else
394 @issue = @project.issues.visible.find(params[:id])
398 @issue = @project.issues.visible.find(params[:id])
395 end
399 end
396
400
397 @issue.project = @project
401 @issue.project = @project
398 @issue.author = User.current
402 @issue.author = User.current
399 # Tracker must be set before custom field values
403 # Tracker must be set before custom field values
400 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
404 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
401 if @issue.tracker.nil?
405 if @issue.tracker.nil?
402 render_error l(:error_no_tracker_in_project)
406 render_error l(:error_no_tracker_in_project)
403 return false
407 return false
404 end
408 end
405 @issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
409 @issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
406 @issue.safe_attributes = params[:issue]
410 @issue.safe_attributes = params[:issue]
407
411
408 @priorities = IssuePriority.active
412 @priorities = IssuePriority.active
409 @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
413 @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
410 @available_watchers = (@issue.project.users.sort + @issue.watcher_users).uniq
414 @available_watchers = (@issue.project.users.sort + @issue.watcher_users).uniq
411 end
415 end
412
416
413 def check_for_default_issue_status
417 def check_for_default_issue_status
414 if IssueStatus.default.nil?
418 if IssueStatus.default.nil?
415 render_error l(:error_no_default_issue_status)
419 render_error l(:error_no_default_issue_status)
416 return false
420 return false
417 end
421 end
418 end
422 end
419
423
420 def parse_params_for_bulk_issue_attributes(params)
424 def parse_params_for_bulk_issue_attributes(params)
421 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
425 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
422 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
426 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
423 if custom = attributes[:custom_field_values]
427 if custom = attributes[:custom_field_values]
424 custom.reject! {|k,v| v.blank?}
428 custom.reject! {|k,v| v.blank?}
425 custom.keys.each do |k|
429 custom.keys.each do |k|
426 if custom[k].is_a?(Array)
430 if custom[k].is_a?(Array)
427 custom[k] << '' if custom[k].delete('__none__')
431 custom[k] << '' if custom[k].delete('__none__')
428 else
432 else
429 custom[k] = '' if custom[k] == '__none__'
433 custom[k] = '' if custom[k] == '__none__'
430 end
434 end
431 end
435 end
432 end
436 end
433 attributes
437 attributes
434 end
438 end
435 end
439 end
@@ -1,1074 +1,1078
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Issue < ActiveRecord::Base
18 class Issue < ActiveRecord::Base
19 include Redmine::SafeAttributes
19 include Redmine::SafeAttributes
20
20
21 belongs_to :project
21 belongs_to :project
22 belongs_to :tracker
22 belongs_to :tracker
23 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
23 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
24 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
24 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
25 belongs_to :assigned_to, :class_name => 'Principal', :foreign_key => 'assigned_to_id'
25 belongs_to :assigned_to, :class_name => 'Principal', :foreign_key => 'assigned_to_id'
26 belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
26 belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
27 belongs_to :priority, :class_name => 'IssuePriority', :foreign_key => 'priority_id'
27 belongs_to :priority, :class_name => 'IssuePriority', :foreign_key => 'priority_id'
28 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
28 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
29
29
30 has_many :journals, :as => :journalized, :dependent => :destroy
30 has_many :journals, :as => :journalized, :dependent => :destroy
31 has_many :time_entries, :dependent => :delete_all
31 has_many :time_entries, :dependent => :delete_all
32 has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
32 has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
33
33
34 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
34 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
35 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
35 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
36
36
37 acts_as_nested_set :scope => 'root_id', :dependent => :destroy
37 acts_as_nested_set :scope => 'root_id', :dependent => :destroy
38 acts_as_attachable :after_add => :attachment_added, :after_remove => :attachment_removed
38 acts_as_attachable :after_add => :attachment_added, :after_remove => :attachment_removed
39 acts_as_customizable
39 acts_as_customizable
40 acts_as_watchable
40 acts_as_watchable
41 acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"],
41 acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"],
42 :include => [:project, :journals],
42 :include => [:project, :journals],
43 # sort by id so that limited eager loading doesn't break with postgresql
43 # sort by id so that limited eager loading doesn't break with postgresql
44 :order_column => "#{table_name}.id"
44 :order_column => "#{table_name}.id"
45 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"},
45 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"},
46 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}},
46 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}},
47 :type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') }
47 :type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') }
48
48
49 acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
49 acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
50 :author_key => :author_id
50 :author_key => :author_id
51
51
52 DONE_RATIO_OPTIONS = %w(issue_field issue_status)
52 DONE_RATIO_OPTIONS = %w(issue_field issue_status)
53
53
54 attr_reader :current_journal
54 attr_reader :current_journal
55
55
56 validates_presence_of :subject, :priority, :project, :tracker, :author, :status
56 validates_presence_of :subject, :priority, :project, :tracker, :author, :status
57
57
58 validates_length_of :subject, :maximum => 255
58 validates_length_of :subject, :maximum => 255
59 validates_inclusion_of :done_ratio, :in => 0..100
59 validates_inclusion_of :done_ratio, :in => 0..100
60 validates_numericality_of :estimated_hours, :allow_nil => true
60 validates_numericality_of :estimated_hours, :allow_nil => true
61 validate :validate_issue
61 validate :validate_issue
62
62
63 named_scope :visible, lambda {|*args| { :include => :project,
63 named_scope :visible, lambda {|*args| { :include => :project,
64 :conditions => Issue.visible_condition(args.shift || User.current, *args) } }
64 :conditions => Issue.visible_condition(args.shift || User.current, *args) } }
65
65
66 named_scope :open, lambda {|*args|
66 named_scope :open, lambda {|*args|
67 is_closed = args.size > 0 ? !args.first : false
67 is_closed = args.size > 0 ? !args.first : false
68 {:conditions => ["#{IssueStatus.table_name}.is_closed = ?", is_closed], :include => :status}
68 {:conditions => ["#{IssueStatus.table_name}.is_closed = ?", is_closed], :include => :status}
69 }
69 }
70
70
71 named_scope :recently_updated, :order => "#{Issue.table_name}.updated_on DESC"
71 named_scope :recently_updated, :order => "#{Issue.table_name}.updated_on DESC"
72 named_scope :with_limit, lambda { |limit| { :limit => limit} }
72 named_scope :with_limit, lambda { |limit| { :limit => limit} }
73 named_scope :on_active_project, :include => [:status, :project, :tracker],
73 named_scope :on_active_project, :include => [:status, :project, :tracker],
74 :conditions => ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"]
74 :conditions => ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"]
75
75
76 before_create :default_assign
76 before_create :default_assign
77 before_save :close_duplicates, :update_done_ratio_from_issue_status
77 before_save :close_duplicates, :update_done_ratio_from_issue_status
78 after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?}
78 after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?}
79 after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal
79 after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal
80 after_destroy :update_parent_attributes
80 after_destroy :update_parent_attributes
81
81
82 # Returns a SQL conditions string used to find all issues visible by the specified user
82 # Returns a SQL conditions string used to find all issues visible by the specified user
83 def self.visible_condition(user, options={})
83 def self.visible_condition(user, options={})
84 Project.allowed_to_condition(user, :view_issues, options) do |role, user|
84 Project.allowed_to_condition(user, :view_issues, options) do |role, user|
85 case role.issues_visibility
85 case role.issues_visibility
86 when 'all'
86 when 'all'
87 nil
87 nil
88 when 'default'
88 when 'default'
89 user_ids = [user.id] + user.groups.map(&:id)
89 user_ids = [user.id] + user.groups.map(&:id)
90 "(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
90 "(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
91 when 'own'
91 when 'own'
92 user_ids = [user.id] + user.groups.map(&:id)
92 user_ids = [user.id] + user.groups.map(&:id)
93 "(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
93 "(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
94 else
94 else
95 '1=0'
95 '1=0'
96 end
96 end
97 end
97 end
98 end
98 end
99
99
100 # Returns true if usr or current user is allowed to view the issue
100 # Returns true if usr or current user is allowed to view the issue
101 def visible?(usr=nil)
101 def visible?(usr=nil)
102 (usr || User.current).allowed_to?(:view_issues, self.project) do |role, user|
102 (usr || User.current).allowed_to?(:view_issues, self.project) do |role, user|
103 case role.issues_visibility
103 case role.issues_visibility
104 when 'all'
104 when 'all'
105 true
105 true
106 when 'default'
106 when 'default'
107 !self.is_private? || self.author == user || user.is_or_belongs_to?(assigned_to)
107 !self.is_private? || self.author == user || user.is_or_belongs_to?(assigned_to)
108 when 'own'
108 when 'own'
109 self.author == user || user.is_or_belongs_to?(assigned_to)
109 self.author == user || user.is_or_belongs_to?(assigned_to)
110 else
110 else
111 false
111 false
112 end
112 end
113 end
113 end
114 end
114 end
115
115
116 def initialize(attributes=nil, *args)
116 def initialize(attributes=nil, *args)
117 super
117 super
118 if new_record?
118 if new_record?
119 # set default values for new records only
119 # set default values for new records only
120 self.status ||= IssueStatus.default
120 self.status ||= IssueStatus.default
121 self.priority ||= IssuePriority.default
121 self.priority ||= IssuePriority.default
122 self.watcher_user_ids = []
122 self.watcher_user_ids = []
123 end
123 end
124 end
124 end
125
125
126 # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
126 # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
127 def available_custom_fields
127 def available_custom_fields
128 (project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields.all) : []
128 (project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields.all) : []
129 end
129 end
130
130
131 # Copies attributes from another issue, arg can be an id or an Issue
131 # Copies attributes from another issue, arg can be an id or an Issue
132 def copy_from(arg, options={})
132 def copy_from(arg, options={})
133 issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg)
133 issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg)
134 self.attributes = issue.attributes.dup.except("id", "root_id", "parent_id", "lft", "rgt", "created_on", "updated_on")
134 self.attributes = issue.attributes.dup.except("id", "root_id", "parent_id", "lft", "rgt", "created_on", "updated_on")
135 self.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
135 self.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
136 self.status = issue.status
136 self.status = issue.status
137 self.author = User.current
137 self.author = User.current
138 unless options[:attachments] == false
138 unless options[:attachments] == false
139 self.attachments = issue.attachments.map do |attachement|
139 self.attachments = issue.attachments.map do |attachement|
140 attachement.copy(:container => self)
140 attachement.copy(:container => self)
141 end
141 end
142 end
142 end
143 @copied_from = issue
143 @copied_from = issue
144 self
144 self
145 end
145 end
146
146
147 # Returns an unsaved copy of the issue
147 # Returns an unsaved copy of the issue
148 def copy(attributes=nil)
148 def copy(attributes=nil)
149 copy = self.class.new.copy_from(self)
149 copy = self.class.new.copy_from(self)
150 copy.attributes = attributes if attributes
150 copy.attributes = attributes if attributes
151 copy
151 copy
152 end
152 end
153
153
154 # Returns true if the issue is a copy
154 # Returns true if the issue is a copy
155 def copy?
155 def copy?
156 @copied_from.present?
156 @copied_from.present?
157 end
157 end
158
158
159 # Moves/copies an issue to a new project and tracker
159 # Moves/copies an issue to a new project and tracker
160 # Returns the moved/copied issue on success, false on failure
160 # Returns the moved/copied issue on success, false on failure
161 def move_to_project(new_project, new_tracker=nil, options={})
161 def move_to_project(new_project, new_tracker=nil, options={})
162 ActiveSupport::Deprecation.warn "Issue#move_to_project is deprecated, use #project= instead."
162 ActiveSupport::Deprecation.warn "Issue#move_to_project is deprecated, use #project= instead."
163
163
164 if options[:copy]
164 if options[:copy]
165 issue = self.copy
165 issue = self.copy
166 else
166 else
167 issue = self
167 issue = self
168 end
168 end
169
169
170 issue.init_journal(User.current, options[:notes])
170 issue.init_journal(User.current, options[:notes])
171
171
172 # Preserve previous behaviour
172 # Preserve previous behaviour
173 # #move_to_project doesn't change tracker automatically
173 # #move_to_project doesn't change tracker automatically
174 issue.send :project=, new_project, true
174 issue.send :project=, new_project, true
175 if new_tracker
175 if new_tracker
176 issue.tracker = new_tracker
176 issue.tracker = new_tracker
177 end
177 end
178 # Allow bulk setting of attributes on the issue
178 # Allow bulk setting of attributes on the issue
179 if options[:attributes]
179 if options[:attributes]
180 issue.attributes = options[:attributes]
180 issue.attributes = options[:attributes]
181 end
181 end
182
182
183 issue.save ? issue : false
183 issue.save ? issue : false
184 end
184 end
185
185
186 def status_id=(sid)
186 def status_id=(sid)
187 self.status = nil
187 self.status = nil
188 write_attribute(:status_id, sid)
188 write_attribute(:status_id, sid)
189 end
189 end
190
190
191 def priority_id=(pid)
191 def priority_id=(pid)
192 self.priority = nil
192 self.priority = nil
193 write_attribute(:priority_id, pid)
193 write_attribute(:priority_id, pid)
194 end
194 end
195
195
196 def category_id=(cid)
196 def category_id=(cid)
197 self.category = nil
197 self.category = nil
198 write_attribute(:category_id, cid)
198 write_attribute(:category_id, cid)
199 end
199 end
200
200
201 def fixed_version_id=(vid)
201 def fixed_version_id=(vid)
202 self.fixed_version = nil
202 self.fixed_version = nil
203 write_attribute(:fixed_version_id, vid)
203 write_attribute(:fixed_version_id, vid)
204 end
204 end
205
205
206 def tracker_id=(tid)
206 def tracker_id=(tid)
207 self.tracker = nil
207 self.tracker = nil
208 result = write_attribute(:tracker_id, tid)
208 result = write_attribute(:tracker_id, tid)
209 @custom_field_values = nil
209 @custom_field_values = nil
210 result
210 result
211 end
211 end
212
212
213 def project_id=(project_id)
213 def project_id=(project_id)
214 if project_id.to_s != self.project_id.to_s
214 if project_id.to_s != self.project_id.to_s
215 self.project = (project_id.present? ? Project.find_by_id(project_id) : nil)
215 self.project = (project_id.present? ? Project.find_by_id(project_id) : nil)
216 end
216 end
217 end
217 end
218
218
219 def project=(project, keep_tracker=false)
219 def project=(project, keep_tracker=false)
220 project_was = self.project
220 project_was = self.project
221 write_attribute(:project_id, project ? project.id : nil)
221 write_attribute(:project_id, project ? project.id : nil)
222 association_instance_set('project', project)
222 association_instance_set('project', project)
223 if project_was && project && project_was != project
223 if project_was && project && project_was != project
224 unless keep_tracker || project.trackers.include?(tracker)
224 unless keep_tracker || project.trackers.include?(tracker)
225 self.tracker = project.trackers.first
225 self.tracker = project.trackers.first
226 end
226 end
227 # Reassign to the category with same name if any
227 # Reassign to the category with same name if any
228 if category
228 if category
229 self.category = project.issue_categories.find_by_name(category.name)
229 self.category = project.issue_categories.find_by_name(category.name)
230 end
230 end
231 # Keep the fixed_version if it's still valid in the new_project
231 # Keep the fixed_version if it's still valid in the new_project
232 if fixed_version && fixed_version.project != project && !project.shared_versions.include?(fixed_version)
232 if fixed_version && fixed_version.project != project && !project.shared_versions.include?(fixed_version)
233 self.fixed_version = nil
233 self.fixed_version = nil
234 end
234 end
235 if parent && parent.project_id != project_id
235 if parent && parent.project_id != project_id
236 self.parent_issue_id = nil
236 self.parent_issue_id = nil
237 end
237 end
238 @custom_field_values = nil
238 @custom_field_values = nil
239 end
239 end
240 end
240 end
241
241
242 def description=(arg)
242 def description=(arg)
243 if arg.is_a?(String)
243 if arg.is_a?(String)
244 arg = arg.gsub(/(\r\n|\n|\r)/, "\r\n")
244 arg = arg.gsub(/(\r\n|\n|\r)/, "\r\n")
245 end
245 end
246 write_attribute(:description, arg)
246 write_attribute(:description, arg)
247 end
247 end
248
248
249 # Overrides attributes= so that project and tracker get assigned first
249 # Overrides attributes= so that project and tracker get assigned first
250 def attributes_with_project_and_tracker_first=(new_attributes, *args)
250 def attributes_with_project_and_tracker_first=(new_attributes, *args)
251 return if new_attributes.nil?
251 return if new_attributes.nil?
252 attrs = new_attributes.dup
252 attrs = new_attributes.dup
253 attrs.stringify_keys!
253 attrs.stringify_keys!
254
254
255 %w(project project_id tracker tracker_id).each do |attr|
255 %w(project project_id tracker tracker_id).each do |attr|
256 if attrs.has_key?(attr)
256 if attrs.has_key?(attr)
257 send "#{attr}=", attrs.delete(attr)
257 send "#{attr}=", attrs.delete(attr)
258 end
258 end
259 end
259 end
260 send :attributes_without_project_and_tracker_first=, attrs, *args
260 send :attributes_without_project_and_tracker_first=, attrs, *args
261 end
261 end
262 # Do not redefine alias chain on reload (see #4838)
262 # Do not redefine alias chain on reload (see #4838)
263 alias_method_chain(:attributes=, :project_and_tracker_first) unless method_defined?(:attributes_without_project_and_tracker_first=)
263 alias_method_chain(:attributes=, :project_and_tracker_first) unless method_defined?(:attributes_without_project_and_tracker_first=)
264
264
265 def estimated_hours=(h)
265 def estimated_hours=(h)
266 write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
266 write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
267 end
267 end
268
268
269 safe_attributes 'project_id',
269 safe_attributes 'project_id',
270 :if => lambda {|issue, user|
270 :if => lambda {|issue, user|
271 if issue.new_record?
271 if issue.new_record?
272 issue.copy?
272 issue.copy?
273 elsif user.allowed_to?(:move_issues, issue.project)
273 elsif user.allowed_to?(:move_issues, issue.project)
274 projects = Issue.allowed_target_projects_on_move(user)
274 projects = Issue.allowed_target_projects_on_move(user)
275 projects.include?(issue.project) && projects.size > 1
275 projects.include?(issue.project) && projects.size > 1
276 end
276 end
277 }
277 }
278
278
279 safe_attributes 'tracker_id',
279 safe_attributes 'tracker_id',
280 'status_id',
280 'status_id',
281 'category_id',
281 'category_id',
282 'assigned_to_id',
282 'assigned_to_id',
283 'priority_id',
283 'priority_id',
284 'fixed_version_id',
284 'fixed_version_id',
285 'subject',
285 'subject',
286 'description',
286 'description',
287 'start_date',
287 'start_date',
288 'due_date',
288 'due_date',
289 'done_ratio',
289 'done_ratio',
290 'estimated_hours',
290 'estimated_hours',
291 'custom_field_values',
291 'custom_field_values',
292 'custom_fields',
292 'custom_fields',
293 'lock_version',
293 'lock_version',
294 :if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) }
294 :if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) }
295
295
296 safe_attributes 'status_id',
296 safe_attributes 'status_id',
297 'assigned_to_id',
297 'assigned_to_id',
298 'fixed_version_id',
298 'fixed_version_id',
299 'done_ratio',
299 'done_ratio',
300 'lock_version',
300 'lock_version',
301 :if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? }
301 :if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? }
302
302
303 safe_attributes 'watcher_user_ids',
303 safe_attributes 'watcher_user_ids',
304 :if => lambda {|issue, user| issue.new_record? && user.allowed_to?(:add_issue_watchers, issue.project)}
304 :if => lambda {|issue, user| issue.new_record? && user.allowed_to?(:add_issue_watchers, issue.project)}
305
305
306 safe_attributes 'is_private',
306 safe_attributes 'is_private',
307 :if => lambda {|issue, user|
307 :if => lambda {|issue, user|
308 user.allowed_to?(:set_issues_private, issue.project) ||
308 user.allowed_to?(:set_issues_private, issue.project) ||
309 (issue.author == user && user.allowed_to?(:set_own_issues_private, issue.project))
309 (issue.author == user && user.allowed_to?(:set_own_issues_private, issue.project))
310 }
310 }
311
311
312 safe_attributes 'parent_issue_id',
312 safe_attributes 'parent_issue_id',
313 :if => lambda {|issue, user| (issue.new_record? || user.allowed_to?(:edit_issues, issue.project)) &&
313 :if => lambda {|issue, user| (issue.new_record? || user.allowed_to?(:edit_issues, issue.project)) &&
314 user.allowed_to?(:manage_subtasks, issue.project)}
314 user.allowed_to?(:manage_subtasks, issue.project)}
315
315
316 # Safely sets attributes
316 # Safely sets attributes
317 # Should be called from controllers instead of #attributes=
317 # Should be called from controllers instead of #attributes=
318 # attr_accessible is too rough because we still want things like
318 # attr_accessible is too rough because we still want things like
319 # Issue.new(:project => foo) to work
319 # Issue.new(:project => foo) to work
320 def safe_attributes=(attrs, user=User.current)
320 def safe_attributes=(attrs, user=User.current)
321 return unless attrs.is_a?(Hash)
321 return unless attrs.is_a?(Hash)
322
322
323 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
323 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
324 attrs = delete_unsafe_attributes(attrs, user)
324 attrs = delete_unsafe_attributes(attrs, user)
325 return if attrs.empty?
325 return if attrs.empty?
326
326
327 # Project and Tracker must be set before since new_statuses_allowed_to depends on it.
327 # Project and Tracker must be set before since new_statuses_allowed_to depends on it.
328 if p = attrs.delete('project_id')
328 if p = attrs.delete('project_id')
329 if allowed_target_projects(user).collect(&:id).include?(p.to_i)
329 if allowed_target_projects(user).collect(&:id).include?(p.to_i)
330 self.project_id = p
330 self.project_id = p
331 end
331 end
332 end
332 end
333
333
334 if t = attrs.delete('tracker_id')
334 if t = attrs.delete('tracker_id')
335 self.tracker_id = t
335 self.tracker_id = t
336 end
336 end
337
337
338 if attrs['status_id']
338 if attrs['status_id']
339 unless new_statuses_allowed_to(user).collect(&:id).include?(attrs['status_id'].to_i)
339 unless new_statuses_allowed_to(user).collect(&:id).include?(attrs['status_id'].to_i)
340 attrs.delete('status_id')
340 attrs.delete('status_id')
341 end
341 end
342 end
342 end
343
343
344 unless leaf?
344 unless leaf?
345 attrs.reject! {|k,v| %w(priority_id done_ratio start_date due_date estimated_hours).include?(k)}
345 attrs.reject! {|k,v| %w(priority_id done_ratio start_date due_date estimated_hours).include?(k)}
346 end
346 end
347
347
348 if attrs['parent_issue_id'].present?
348 if attrs['parent_issue_id'].present?
349 attrs.delete('parent_issue_id') unless Issue.visible(user).exists?(attrs['parent_issue_id'].to_i)
349 attrs.delete('parent_issue_id') unless Issue.visible(user).exists?(attrs['parent_issue_id'].to_i)
350 end
350 end
351
351
352 # mass-assignment security bypass
352 # mass-assignment security bypass
353 self.send :attributes=, attrs, false
353 self.send :attributes=, attrs, false
354 end
354 end
355
355
356 def done_ratio
356 def done_ratio
357 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
357 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
358 status.default_done_ratio
358 status.default_done_ratio
359 else
359 else
360 read_attribute(:done_ratio)
360 read_attribute(:done_ratio)
361 end
361 end
362 end
362 end
363
363
364 def self.use_status_for_done_ratio?
364 def self.use_status_for_done_ratio?
365 Setting.issue_done_ratio == 'issue_status'
365 Setting.issue_done_ratio == 'issue_status'
366 end
366 end
367
367
368 def self.use_field_for_done_ratio?
368 def self.use_field_for_done_ratio?
369 Setting.issue_done_ratio == 'issue_field'
369 Setting.issue_done_ratio == 'issue_field'
370 end
370 end
371
371
372 def validate_issue
372 def validate_issue
373 if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
373 if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
374 errors.add :due_date, :not_a_date
374 errors.add :due_date, :not_a_date
375 end
375 end
376
376
377 if self.due_date and self.start_date and self.due_date < self.start_date
377 if self.due_date and self.start_date and self.due_date < self.start_date
378 errors.add :due_date, :greater_than_start_date
378 errors.add :due_date, :greater_than_start_date
379 end
379 end
380
380
381 if start_date && soonest_start && start_date < soonest_start
381 if start_date && soonest_start && start_date < soonest_start
382 errors.add :start_date, :invalid
382 errors.add :start_date, :invalid
383 end
383 end
384
384
385 if fixed_version
385 if fixed_version
386 if !assignable_versions.include?(fixed_version)
386 if !assignable_versions.include?(fixed_version)
387 errors.add :fixed_version_id, :inclusion
387 errors.add :fixed_version_id, :inclusion
388 elsif reopened? && fixed_version.closed?
388 elsif reopened? && fixed_version.closed?
389 errors.add :base, I18n.t(:error_can_not_reopen_issue_on_closed_version)
389 errors.add :base, I18n.t(:error_can_not_reopen_issue_on_closed_version)
390 end
390 end
391 end
391 end
392
392
393 # Checks that the issue can not be added/moved to a disabled tracker
393 # Checks that the issue can not be added/moved to a disabled tracker
394 if project && (tracker_id_changed? || project_id_changed?)
394 if project && (tracker_id_changed? || project_id_changed?)
395 unless project.trackers.include?(tracker)
395 unless project.trackers.include?(tracker)
396 errors.add :tracker_id, :inclusion
396 errors.add :tracker_id, :inclusion
397 end
397 end
398 end
398 end
399
399
400 # Checks parent issue assignment
400 # Checks parent issue assignment
401 if @parent_issue
401 if @parent_issue
402 if @parent_issue.project_id != project_id
402 if @parent_issue.project_id != project_id
403 errors.add :parent_issue_id, :not_same_project
403 errors.add :parent_issue_id, :not_same_project
404 elsif !new_record?
404 elsif !new_record?
405 # moving an existing issue
405 # moving an existing issue
406 if @parent_issue.root_id != root_id
406 if @parent_issue.root_id != root_id
407 # we can always move to another tree
407 # we can always move to another tree
408 elsif move_possible?(@parent_issue)
408 elsif move_possible?(@parent_issue)
409 # move accepted inside tree
409 # move accepted inside tree
410 else
410 else
411 errors.add :parent_issue_id, :not_a_valid_parent
411 errors.add :parent_issue_id, :not_a_valid_parent
412 end
412 end
413 end
413 end
414 end
414 end
415 end
415 end
416
416
417 # Set the done_ratio using the status if that setting is set. This will keep the done_ratios
417 # Set the done_ratio using the status if that setting is set. This will keep the done_ratios
418 # even if the user turns off the setting later
418 # even if the user turns off the setting later
419 def update_done_ratio_from_issue_status
419 def update_done_ratio_from_issue_status
420 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
420 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
421 self.done_ratio = status.default_done_ratio
421 self.done_ratio = status.default_done_ratio
422 end
422 end
423 end
423 end
424
424
425 def init_journal(user, notes = "")
425 def init_journal(user, notes = "")
426 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
426 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
427 if new_record?
427 if new_record?
428 @current_journal.notify = false
428 @current_journal.notify = false
429 else
429 else
430 @attributes_before_change = attributes.dup
430 @attributes_before_change = attributes.dup
431 @custom_values_before_change = {}
431 @custom_values_before_change = {}
432 self.custom_field_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
432 self.custom_field_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
433 end
433 end
434 # Make sure updated_on is updated when adding a note.
434 # Make sure updated_on is updated when adding a note.
435 updated_on_will_change!
435 updated_on_will_change!
436 @current_journal
436 @current_journal
437 end
437 end
438
438
439 # Returns the id of the last journal or nil
439 # Returns the id of the last journal or nil
440 def last_journal_id
440 def last_journal_id
441 if new_record?
441 if new_record?
442 nil
442 nil
443 else
443 else
444 journals.first(:order => "#{Journal.table_name}.id DESC").try(:id)
444 journals.first(:order => "#{Journal.table_name}.id DESC").try(:id)
445 end
445 end
446 end
446 end
447
447
448 # Return true if the issue is closed, otherwise false
448 # Return true if the issue is closed, otherwise false
449 def closed?
449 def closed?
450 self.status.is_closed?
450 self.status.is_closed?
451 end
451 end
452
452
453 # Return true if the issue is being reopened
453 # Return true if the issue is being reopened
454 def reopened?
454 def reopened?
455 if !new_record? && status_id_changed?
455 if !new_record? && status_id_changed?
456 status_was = IssueStatus.find_by_id(status_id_was)
456 status_was = IssueStatus.find_by_id(status_id_was)
457 status_new = IssueStatus.find_by_id(status_id)
457 status_new = IssueStatus.find_by_id(status_id)
458 if status_was && status_new && status_was.is_closed? && !status_new.is_closed?
458 if status_was && status_new && status_was.is_closed? && !status_new.is_closed?
459 return true
459 return true
460 end
460 end
461 end
461 end
462 false
462 false
463 end
463 end
464
464
465 # Return true if the issue is being closed
465 # Return true if the issue is being closed
466 def closing?
466 def closing?
467 if !new_record? && status_id_changed?
467 if !new_record? && status_id_changed?
468 status_was = IssueStatus.find_by_id(status_id_was)
468 status_was = IssueStatus.find_by_id(status_id_was)
469 status_new = IssueStatus.find_by_id(status_id)
469 status_new = IssueStatus.find_by_id(status_id)
470 if status_was && status_new && !status_was.is_closed? && status_new.is_closed?
470 if status_was && status_new && !status_was.is_closed? && status_new.is_closed?
471 return true
471 return true
472 end
472 end
473 end
473 end
474 false
474 false
475 end
475 end
476
476
477 # Returns true if the issue is overdue
477 # Returns true if the issue is overdue
478 def overdue?
478 def overdue?
479 !due_date.nil? && (due_date < Date.today) && !status.is_closed?
479 !due_date.nil? && (due_date < Date.today) && !status.is_closed?
480 end
480 end
481
481
482 # Is the amount of work done less than it should for the due date
482 # Is the amount of work done less than it should for the due date
483 def behind_schedule?
483 def behind_schedule?
484 return false if start_date.nil? || due_date.nil?
484 return false if start_date.nil? || due_date.nil?
485 done_date = start_date + ((due_date - start_date+1)* done_ratio/100).floor
485 done_date = start_date + ((due_date - start_date+1)* done_ratio/100).floor
486 return done_date <= Date.today
486 return done_date <= Date.today
487 end
487 end
488
488
489 # Does this issue have children?
489 # Does this issue have children?
490 def children?
490 def children?
491 !leaf?
491 !leaf?
492 end
492 end
493
493
494 # Users the issue can be assigned to
494 # Users the issue can be assigned to
495 def assignable_users
495 def assignable_users
496 users = project.assignable_users
496 users = project.assignable_users
497 users << author if author
497 users << author if author
498 users << assigned_to if assigned_to
498 users << assigned_to if assigned_to
499 users.uniq.sort
499 users.uniq.sort
500 end
500 end
501
501
502 # Versions that the issue can be assigned to
502 # Versions that the issue can be assigned to
503 def assignable_versions
503 def assignable_versions
504 @assignable_versions ||= (project.shared_versions.open + [Version.find_by_id(fixed_version_id_was)]).compact.uniq.sort
504 @assignable_versions ||= (project.shared_versions.open + [Version.find_by_id(fixed_version_id_was)]).compact.uniq.sort
505 end
505 end
506
506
507 # Returns true if this issue is blocked by another issue that is still open
507 # Returns true if this issue is blocked by another issue that is still open
508 def blocked?
508 def blocked?
509 !relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil?
509 !relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil?
510 end
510 end
511
511
512 # Returns an array of statuses that user is able to apply
512 # Returns an array of statuses that user is able to apply
513 def new_statuses_allowed_to(user=User.current, include_default=false)
513 def new_statuses_allowed_to(user=User.current, include_default=false)
514 initial_status = nil
514 if new_record? && @copied_from
515 if new_record?
515 [IssueStatus.default, @copied_from.status].compact.uniq.sort
516 initial_status = IssueStatus.default
516 else
517 elsif status_id_was
517 initial_status = nil
518 initial_status = IssueStatus.find_by_id(status_id_was)
518 if new_record?
519 initial_status = IssueStatus.default
520 elsif status_id_was
521 initial_status = IssueStatus.find_by_id(status_id_was)
522 end
523 initial_status ||= status
524
525 statuses = initial_status.find_new_statuses_allowed_to(
526 user.admin ? Role.all : user.roles_for_project(project),
527 tracker,
528 author == user,
529 assigned_to_id_changed? ? assigned_to_id_was == user.id : assigned_to_id == user.id
530 )
531 statuses << initial_status unless statuses.empty?
532 statuses << IssueStatus.default if include_default
533 statuses = statuses.compact.uniq.sort
534 blocked? ? statuses.reject {|s| s.is_closed?} : statuses
519 end
535 end
520 initial_status ||= status
521
522 statuses = initial_status.find_new_statuses_allowed_to(
523 user.admin ? Role.all : user.roles_for_project(project),
524 tracker,
525 author == user,
526 assigned_to_id_changed? ? assigned_to_id_was == user.id : assigned_to_id == user.id
527 )
528 statuses << initial_status unless statuses.empty?
529 statuses << IssueStatus.default if include_default
530 statuses = statuses.compact.uniq.sort
531 blocked? ? statuses.reject {|s| s.is_closed?} : statuses
532 end
536 end
533
537
534 def assigned_to_was
538 def assigned_to_was
535 if assigned_to_id_changed? && assigned_to_id_was.present?
539 if assigned_to_id_changed? && assigned_to_id_was.present?
536 @assigned_to_was ||= User.find_by_id(assigned_to_id_was)
540 @assigned_to_was ||= User.find_by_id(assigned_to_id_was)
537 end
541 end
538 end
542 end
539
543
540 # Returns the mail adresses of users that should be notified
544 # Returns the mail adresses of users that should be notified
541 def recipients
545 def recipients
542 notified = []
546 notified = []
543 # Author and assignee are always notified unless they have been
547 # Author and assignee are always notified unless they have been
544 # locked or don't want to be notified
548 # locked or don't want to be notified
545 notified << author if author
549 notified << author if author
546 if assigned_to
550 if assigned_to
547 notified += (assigned_to.is_a?(Group) ? assigned_to.users : [assigned_to])
551 notified += (assigned_to.is_a?(Group) ? assigned_to.users : [assigned_to])
548 end
552 end
549 if assigned_to_was
553 if assigned_to_was
550 notified += (assigned_to_was.is_a?(Group) ? assigned_to_was.users : [assigned_to_was])
554 notified += (assigned_to_was.is_a?(Group) ? assigned_to_was.users : [assigned_to_was])
551 end
555 end
552 notified = notified.select {|u| u.active? && u.notify_about?(self)}
556 notified = notified.select {|u| u.active? && u.notify_about?(self)}
553
557
554 notified += project.notified_users
558 notified += project.notified_users
555 notified.uniq!
559 notified.uniq!
556 # Remove users that can not view the issue
560 # Remove users that can not view the issue
557 notified.reject! {|user| !visible?(user)}
561 notified.reject! {|user| !visible?(user)}
558 notified.collect(&:mail)
562 notified.collect(&:mail)
559 end
563 end
560
564
561 # Returns the number of hours spent on this issue
565 # Returns the number of hours spent on this issue
562 def spent_hours
566 def spent_hours
563 @spent_hours ||= time_entries.sum(:hours) || 0
567 @spent_hours ||= time_entries.sum(:hours) || 0
564 end
568 end
565
569
566 # Returns the total number of hours spent on this issue and its descendants
570 # Returns the total number of hours spent on this issue and its descendants
567 #
571 #
568 # Example:
572 # Example:
569 # spent_hours => 0.0
573 # spent_hours => 0.0
570 # spent_hours => 50.2
574 # spent_hours => 50.2
571 def total_spent_hours
575 def total_spent_hours
572 @total_spent_hours ||= self_and_descendants.sum("#{TimeEntry.table_name}.hours",
576 @total_spent_hours ||= self_and_descendants.sum("#{TimeEntry.table_name}.hours",
573 :joins => "LEFT JOIN #{TimeEntry.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id").to_f || 0.0
577 :joins => "LEFT JOIN #{TimeEntry.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id").to_f || 0.0
574 end
578 end
575
579
576 def relations
580 def relations
577 @relations ||= (relations_from + relations_to).sort
581 @relations ||= (relations_from + relations_to).sort
578 end
582 end
579
583
580 # Preloads relations for a collection of issues
584 # Preloads relations for a collection of issues
581 def self.load_relations(issues)
585 def self.load_relations(issues)
582 if issues.any?
586 if issues.any?
583 relations = IssueRelation.all(:conditions => ["issue_from_id IN (:ids) OR issue_to_id IN (:ids)", {:ids => issues.map(&:id)}])
587 relations = IssueRelation.all(:conditions => ["issue_from_id IN (:ids) OR issue_to_id IN (:ids)", {:ids => issues.map(&:id)}])
584 issues.each do |issue|
588 issues.each do |issue|
585 issue.instance_variable_set "@relations", relations.select {|r| r.issue_from_id == issue.id || r.issue_to_id == issue.id}
589 issue.instance_variable_set "@relations", relations.select {|r| r.issue_from_id == issue.id || r.issue_to_id == issue.id}
586 end
590 end
587 end
591 end
588 end
592 end
589
593
590 # Preloads visible spent time for a collection of issues
594 # Preloads visible spent time for a collection of issues
591 def self.load_visible_spent_hours(issues, user=User.current)
595 def self.load_visible_spent_hours(issues, user=User.current)
592 if issues.any?
596 if issues.any?
593 hours_by_issue_id = TimeEntry.visible(user).sum(:hours, :group => :issue_id)
597 hours_by_issue_id = TimeEntry.visible(user).sum(:hours, :group => :issue_id)
594 issues.each do |issue|
598 issues.each do |issue|
595 issue.instance_variable_set "@spent_hours", (hours_by_issue_id[issue.id] || 0)
599 issue.instance_variable_set "@spent_hours", (hours_by_issue_id[issue.id] || 0)
596 end
600 end
597 end
601 end
598 end
602 end
599
603
600 # Finds an issue relation given its id.
604 # Finds an issue relation given its id.
601 def find_relation(relation_id)
605 def find_relation(relation_id)
602 IssueRelation.find(relation_id, :conditions => ["issue_to_id = ? OR issue_from_id = ?", id, id])
606 IssueRelation.find(relation_id, :conditions => ["issue_to_id = ? OR issue_from_id = ?", id, id])
603 end
607 end
604
608
605 def all_dependent_issues(except=[])
609 def all_dependent_issues(except=[])
606 except << self
610 except << self
607 dependencies = []
611 dependencies = []
608 relations_from.each do |relation|
612 relations_from.each do |relation|
609 if relation.issue_to && !except.include?(relation.issue_to)
613 if relation.issue_to && !except.include?(relation.issue_to)
610 dependencies << relation.issue_to
614 dependencies << relation.issue_to
611 dependencies += relation.issue_to.all_dependent_issues(except)
615 dependencies += relation.issue_to.all_dependent_issues(except)
612 end
616 end
613 end
617 end
614 dependencies
618 dependencies
615 end
619 end
616
620
617 # Returns an array of issues that duplicate this one
621 # Returns an array of issues that duplicate this one
618 def duplicates
622 def duplicates
619 relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from}
623 relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from}
620 end
624 end
621
625
622 # Returns the due date or the target due date if any
626 # Returns the due date or the target due date if any
623 # Used on gantt chart
627 # Used on gantt chart
624 def due_before
628 def due_before
625 due_date || (fixed_version ? fixed_version.effective_date : nil)
629 due_date || (fixed_version ? fixed_version.effective_date : nil)
626 end
630 end
627
631
628 # Returns the time scheduled for this issue.
632 # Returns the time scheduled for this issue.
629 #
633 #
630 # Example:
634 # Example:
631 # Start Date: 2/26/09, End Date: 3/04/09
635 # Start Date: 2/26/09, End Date: 3/04/09
632 # duration => 6
636 # duration => 6
633 def duration
637 def duration
634 (start_date && due_date) ? due_date - start_date : 0
638 (start_date && due_date) ? due_date - start_date : 0
635 end
639 end
636
640
637 def soonest_start
641 def soonest_start
638 @soonest_start ||= (
642 @soonest_start ||= (
639 relations_to.collect{|relation| relation.successor_soonest_start} +
643 relations_to.collect{|relation| relation.successor_soonest_start} +
640 ancestors.collect(&:soonest_start)
644 ancestors.collect(&:soonest_start)
641 ).compact.max
645 ).compact.max
642 end
646 end
643
647
644 def reschedule_after(date)
648 def reschedule_after(date)
645 return if date.nil?
649 return if date.nil?
646 if leaf?
650 if leaf?
647 if start_date.nil? || start_date < date
651 if start_date.nil? || start_date < date
648 self.start_date, self.due_date = date, date + duration
652 self.start_date, self.due_date = date, date + duration
649 begin
653 begin
650 save
654 save
651 rescue ActiveRecord::StaleObjectError
655 rescue ActiveRecord::StaleObjectError
652 reload
656 reload
653 self.start_date, self.due_date = date, date + duration
657 self.start_date, self.due_date = date, date + duration
654 save
658 save
655 end
659 end
656 end
660 end
657 else
661 else
658 leaves.each do |leaf|
662 leaves.each do |leaf|
659 leaf.reschedule_after(date)
663 leaf.reschedule_after(date)
660 end
664 end
661 end
665 end
662 end
666 end
663
667
664 def <=>(issue)
668 def <=>(issue)
665 if issue.nil?
669 if issue.nil?
666 -1
670 -1
667 elsif root_id != issue.root_id
671 elsif root_id != issue.root_id
668 (root_id || 0) <=> (issue.root_id || 0)
672 (root_id || 0) <=> (issue.root_id || 0)
669 else
673 else
670 (lft || 0) <=> (issue.lft || 0)
674 (lft || 0) <=> (issue.lft || 0)
671 end
675 end
672 end
676 end
673
677
674 def to_s
678 def to_s
675 "#{tracker} ##{id}: #{subject}"
679 "#{tracker} ##{id}: #{subject}"
676 end
680 end
677
681
678 # Returns a string of css classes that apply to the issue
682 # Returns a string of css classes that apply to the issue
679 def css_classes
683 def css_classes
680 s = "issue status-#{status.position} priority-#{priority.position}"
684 s = "issue status-#{status.position} priority-#{priority.position}"
681 s << ' closed' if closed?
685 s << ' closed' if closed?
682 s << ' overdue' if overdue?
686 s << ' overdue' if overdue?
683 s << ' child' if child?
687 s << ' child' if child?
684 s << ' parent' unless leaf?
688 s << ' parent' unless leaf?
685 s << ' private' if is_private?
689 s << ' private' if is_private?
686 s << ' created-by-me' if User.current.logged? && author_id == User.current.id
690 s << ' created-by-me' if User.current.logged? && author_id == User.current.id
687 s << ' assigned-to-me' if User.current.logged? && assigned_to_id == User.current.id
691 s << ' assigned-to-me' if User.current.logged? && assigned_to_id == User.current.id
688 s
692 s
689 end
693 end
690
694
691 # Saves an issue and a time_entry from the parameters
695 # Saves an issue and a time_entry from the parameters
692 def save_issue_with_child_records(params, existing_time_entry=nil)
696 def save_issue_with_child_records(params, existing_time_entry=nil)
693 Issue.transaction do
697 Issue.transaction do
694 if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, project)
698 if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, project)
695 @time_entry = existing_time_entry || TimeEntry.new
699 @time_entry = existing_time_entry || TimeEntry.new
696 @time_entry.project = project
700 @time_entry.project = project
697 @time_entry.issue = self
701 @time_entry.issue = self
698 @time_entry.user = User.current
702 @time_entry.user = User.current
699 @time_entry.spent_on = User.current.today
703 @time_entry.spent_on = User.current.today
700 @time_entry.attributes = params[:time_entry]
704 @time_entry.attributes = params[:time_entry]
701 self.time_entries << @time_entry
705 self.time_entries << @time_entry
702 end
706 end
703
707
704 # TODO: Rename hook
708 # TODO: Rename hook
705 Redmine::Hook.call_hook(:controller_issues_edit_before_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
709 Redmine::Hook.call_hook(:controller_issues_edit_before_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
706 if save
710 if save
707 # TODO: Rename hook
711 # TODO: Rename hook
708 Redmine::Hook.call_hook(:controller_issues_edit_after_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
712 Redmine::Hook.call_hook(:controller_issues_edit_after_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
709 else
713 else
710 raise ActiveRecord::Rollback
714 raise ActiveRecord::Rollback
711 end
715 end
712 end
716 end
713 end
717 end
714
718
715 # Unassigns issues from +version+ if it's no longer shared with issue's project
719 # Unassigns issues from +version+ if it's no longer shared with issue's project
716 def self.update_versions_from_sharing_change(version)
720 def self.update_versions_from_sharing_change(version)
717 # Update issues assigned to the version
721 # Update issues assigned to the version
718 update_versions(["#{Issue.table_name}.fixed_version_id = ?", version.id])
722 update_versions(["#{Issue.table_name}.fixed_version_id = ?", version.id])
719 end
723 end
720
724
721 # Unassigns issues from versions that are no longer shared
725 # Unassigns issues from versions that are no longer shared
722 # after +project+ was moved
726 # after +project+ was moved
723 def self.update_versions_from_hierarchy_change(project)
727 def self.update_versions_from_hierarchy_change(project)
724 moved_project_ids = project.self_and_descendants.reload.collect(&:id)
728 moved_project_ids = project.self_and_descendants.reload.collect(&:id)
725 # Update issues of the moved projects and issues assigned to a version of a moved project
729 # Update issues of the moved projects and issues assigned to a version of a moved project
726 Issue.update_versions(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids])
730 Issue.update_versions(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids])
727 end
731 end
728
732
729 def parent_issue_id=(arg)
733 def parent_issue_id=(arg)
730 parent_issue_id = arg.blank? ? nil : arg.to_i
734 parent_issue_id = arg.blank? ? nil : arg.to_i
731 if parent_issue_id && @parent_issue = Issue.find_by_id(parent_issue_id)
735 if parent_issue_id && @parent_issue = Issue.find_by_id(parent_issue_id)
732 @parent_issue.id
736 @parent_issue.id
733 else
737 else
734 @parent_issue = nil
738 @parent_issue = nil
735 nil
739 nil
736 end
740 end
737 end
741 end
738
742
739 def parent_issue_id
743 def parent_issue_id
740 if instance_variable_defined? :@parent_issue
744 if instance_variable_defined? :@parent_issue
741 @parent_issue.nil? ? nil : @parent_issue.id
745 @parent_issue.nil? ? nil : @parent_issue.id
742 else
746 else
743 parent_id
747 parent_id
744 end
748 end
745 end
749 end
746
750
747 # Extracted from the ReportsController.
751 # Extracted from the ReportsController.
748 def self.by_tracker(project)
752 def self.by_tracker(project)
749 count_and_group_by(:project => project,
753 count_and_group_by(:project => project,
750 :field => 'tracker_id',
754 :field => 'tracker_id',
751 :joins => Tracker.table_name)
755 :joins => Tracker.table_name)
752 end
756 end
753
757
754 def self.by_version(project)
758 def self.by_version(project)
755 count_and_group_by(:project => project,
759 count_and_group_by(:project => project,
756 :field => 'fixed_version_id',
760 :field => 'fixed_version_id',
757 :joins => Version.table_name)
761 :joins => Version.table_name)
758 end
762 end
759
763
760 def self.by_priority(project)
764 def self.by_priority(project)
761 count_and_group_by(:project => project,
765 count_and_group_by(:project => project,
762 :field => 'priority_id',
766 :field => 'priority_id',
763 :joins => IssuePriority.table_name)
767 :joins => IssuePriority.table_name)
764 end
768 end
765
769
766 def self.by_category(project)
770 def self.by_category(project)
767 count_and_group_by(:project => project,
771 count_and_group_by(:project => project,
768 :field => 'category_id',
772 :field => 'category_id',
769 :joins => IssueCategory.table_name)
773 :joins => IssueCategory.table_name)
770 end
774 end
771
775
772 def self.by_assigned_to(project)
776 def self.by_assigned_to(project)
773 count_and_group_by(:project => project,
777 count_and_group_by(:project => project,
774 :field => 'assigned_to_id',
778 :field => 'assigned_to_id',
775 :joins => User.table_name)
779 :joins => User.table_name)
776 end
780 end
777
781
778 def self.by_author(project)
782 def self.by_author(project)
779 count_and_group_by(:project => project,
783 count_and_group_by(:project => project,
780 :field => 'author_id',
784 :field => 'author_id',
781 :joins => User.table_name)
785 :joins => User.table_name)
782 end
786 end
783
787
784 def self.by_subproject(project)
788 def self.by_subproject(project)
785 ActiveRecord::Base.connection.select_all("select s.id as status_id,
789 ActiveRecord::Base.connection.select_all("select s.id as status_id,
786 s.is_closed as closed,
790 s.is_closed as closed,
787 #{Issue.table_name}.project_id as project_id,
791 #{Issue.table_name}.project_id as project_id,
788 count(#{Issue.table_name}.id) as total
792 count(#{Issue.table_name}.id) as total
789 from
793 from
790 #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s
794 #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s
791 where
795 where
792 #{Issue.table_name}.status_id=s.id
796 #{Issue.table_name}.status_id=s.id
793 and #{Issue.table_name}.project_id = #{Project.table_name}.id
797 and #{Issue.table_name}.project_id = #{Project.table_name}.id
794 and #{visible_condition(User.current, :project => project, :with_subprojects => true)}
798 and #{visible_condition(User.current, :project => project, :with_subprojects => true)}
795 and #{Issue.table_name}.project_id <> #{project.id}
799 and #{Issue.table_name}.project_id <> #{project.id}
796 group by s.id, s.is_closed, #{Issue.table_name}.project_id") if project.descendants.active.any?
800 group by s.id, s.is_closed, #{Issue.table_name}.project_id") if project.descendants.active.any?
797 end
801 end
798 # End ReportsController extraction
802 # End ReportsController extraction
799
803
800 # Returns an array of projects that user can assign the issue to
804 # Returns an array of projects that user can assign the issue to
801 def allowed_target_projects(user=User.current)
805 def allowed_target_projects(user=User.current)
802 if new_record?
806 if new_record?
803 Project.all(:conditions => Project.allowed_to_condition(user, :add_issues))
807 Project.all(:conditions => Project.allowed_to_condition(user, :add_issues))
804 else
808 else
805 self.class.allowed_target_projects_on_move(user)
809 self.class.allowed_target_projects_on_move(user)
806 end
810 end
807 end
811 end
808
812
809 # Returns an array of projects that user can move issues to
813 # Returns an array of projects that user can move issues to
810 def self.allowed_target_projects_on_move(user=User.current)
814 def self.allowed_target_projects_on_move(user=User.current)
811 Project.all(:conditions => Project.allowed_to_condition(user, :move_issues))
815 Project.all(:conditions => Project.allowed_to_condition(user, :move_issues))
812 end
816 end
813
817
814 private
818 private
815
819
816 def after_project_change
820 def after_project_change
817 # Update project_id on related time entries
821 # Update project_id on related time entries
818 TimeEntry.update_all(["project_id = ?", project_id], {:issue_id => id})
822 TimeEntry.update_all(["project_id = ?", project_id], {:issue_id => id})
819
823
820 # Delete issue relations
824 # Delete issue relations
821 unless Setting.cross_project_issue_relations?
825 unless Setting.cross_project_issue_relations?
822 relations_from.clear
826 relations_from.clear
823 relations_to.clear
827 relations_to.clear
824 end
828 end
825
829
826 # Move subtasks
830 # Move subtasks
827 children.each do |child|
831 children.each do |child|
828 # Change project and keep project
832 # Change project and keep project
829 child.send :project=, project, true
833 child.send :project=, project, true
830 unless child.save
834 unless child.save
831 raise ActiveRecord::Rollback
835 raise ActiveRecord::Rollback
832 end
836 end
833 end
837 end
834 end
838 end
835
839
836 def update_nested_set_attributes
840 def update_nested_set_attributes
837 if root_id.nil?
841 if root_id.nil?
838 # issue was just created
842 # issue was just created
839 self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id)
843 self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id)
840 set_default_left_and_right
844 set_default_left_and_right
841 Issue.update_all("root_id = #{root_id}, lft = #{lft}, rgt = #{rgt}", ["id = ?", id])
845 Issue.update_all("root_id = #{root_id}, lft = #{lft}, rgt = #{rgt}", ["id = ?", id])
842 if @parent_issue
846 if @parent_issue
843 move_to_child_of(@parent_issue)
847 move_to_child_of(@parent_issue)
844 end
848 end
845 reload
849 reload
846 elsif parent_issue_id != parent_id
850 elsif parent_issue_id != parent_id
847 former_parent_id = parent_id
851 former_parent_id = parent_id
848 # moving an existing issue
852 # moving an existing issue
849 if @parent_issue && @parent_issue.root_id == root_id
853 if @parent_issue && @parent_issue.root_id == root_id
850 # inside the same tree
854 # inside the same tree
851 move_to_child_of(@parent_issue)
855 move_to_child_of(@parent_issue)
852 else
856 else
853 # to another tree
857 # to another tree
854 unless root?
858 unless root?
855 move_to_right_of(root)
859 move_to_right_of(root)
856 reload
860 reload
857 end
861 end
858 old_root_id = root_id
862 old_root_id = root_id
859 self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id )
863 self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id )
860 target_maxright = nested_set_scope.maximum(right_column_name) || 0
864 target_maxright = nested_set_scope.maximum(right_column_name) || 0
861 offset = target_maxright + 1 - lft
865 offset = target_maxright + 1 - lft
862 Issue.update_all("root_id = #{root_id}, lft = lft + #{offset}, rgt = rgt + #{offset}",
866 Issue.update_all("root_id = #{root_id}, lft = lft + #{offset}, rgt = rgt + #{offset}",
863 ["root_id = ? AND lft >= ? AND rgt <= ? ", old_root_id, lft, rgt])
867 ["root_id = ? AND lft >= ? AND rgt <= ? ", old_root_id, lft, rgt])
864 self[left_column_name] = lft + offset
868 self[left_column_name] = lft + offset
865 self[right_column_name] = rgt + offset
869 self[right_column_name] = rgt + offset
866 if @parent_issue
870 if @parent_issue
867 move_to_child_of(@parent_issue)
871 move_to_child_of(@parent_issue)
868 end
872 end
869 end
873 end
870 reload
874 reload
871 # delete invalid relations of all descendants
875 # delete invalid relations of all descendants
872 self_and_descendants.each do |issue|
876 self_and_descendants.each do |issue|
873 issue.relations.each do |relation|
877 issue.relations.each do |relation|
874 relation.destroy unless relation.valid?
878 relation.destroy unless relation.valid?
875 end
879 end
876 end
880 end
877 # update former parent
881 # update former parent
878 recalculate_attributes_for(former_parent_id) if former_parent_id
882 recalculate_attributes_for(former_parent_id) if former_parent_id
879 end
883 end
880 remove_instance_variable(:@parent_issue) if instance_variable_defined?(:@parent_issue)
884 remove_instance_variable(:@parent_issue) if instance_variable_defined?(:@parent_issue)
881 end
885 end
882
886
883 def update_parent_attributes
887 def update_parent_attributes
884 recalculate_attributes_for(parent_id) if parent_id
888 recalculate_attributes_for(parent_id) if parent_id
885 end
889 end
886
890
887 def recalculate_attributes_for(issue_id)
891 def recalculate_attributes_for(issue_id)
888 if issue_id && p = Issue.find_by_id(issue_id)
892 if issue_id && p = Issue.find_by_id(issue_id)
889 # priority = highest priority of children
893 # priority = highest priority of children
890 if priority_position = p.children.maximum("#{IssuePriority.table_name}.position", :joins => :priority)
894 if priority_position = p.children.maximum("#{IssuePriority.table_name}.position", :joins => :priority)
891 p.priority = IssuePriority.find_by_position(priority_position)
895 p.priority = IssuePriority.find_by_position(priority_position)
892 end
896 end
893
897
894 # start/due dates = lowest/highest dates of children
898 # start/due dates = lowest/highest dates of children
895 p.start_date = p.children.minimum(:start_date)
899 p.start_date = p.children.minimum(:start_date)
896 p.due_date = p.children.maximum(:due_date)
900 p.due_date = p.children.maximum(:due_date)
897 if p.start_date && p.due_date && p.due_date < p.start_date
901 if p.start_date && p.due_date && p.due_date < p.start_date
898 p.start_date, p.due_date = p.due_date, p.start_date
902 p.start_date, p.due_date = p.due_date, p.start_date
899 end
903 end
900
904
901 # done ratio = weighted average ratio of leaves
905 # done ratio = weighted average ratio of leaves
902 unless Issue.use_status_for_done_ratio? && p.status && p.status.default_done_ratio
906 unless Issue.use_status_for_done_ratio? && p.status && p.status.default_done_ratio
903 leaves_count = p.leaves.count
907 leaves_count = p.leaves.count
904 if leaves_count > 0
908 if leaves_count > 0
905 average = p.leaves.average(:estimated_hours).to_f
909 average = p.leaves.average(:estimated_hours).to_f
906 if average == 0
910 if average == 0
907 average = 1
911 average = 1
908 end
912 end
909 done = p.leaves.sum("COALESCE(estimated_hours, #{average}) * (CASE WHEN is_closed = #{connection.quoted_true} THEN 100 ELSE COALESCE(done_ratio, 0) END)", :joins => :status).to_f
913 done = p.leaves.sum("COALESCE(estimated_hours, #{average}) * (CASE WHEN is_closed = #{connection.quoted_true} THEN 100 ELSE COALESCE(done_ratio, 0) END)", :joins => :status).to_f
910 progress = done / (average * leaves_count)
914 progress = done / (average * leaves_count)
911 p.done_ratio = progress.round
915 p.done_ratio = progress.round
912 end
916 end
913 end
917 end
914
918
915 # estimate = sum of leaves estimates
919 # estimate = sum of leaves estimates
916 p.estimated_hours = p.leaves.sum(:estimated_hours).to_f
920 p.estimated_hours = p.leaves.sum(:estimated_hours).to_f
917 p.estimated_hours = nil if p.estimated_hours == 0.0
921 p.estimated_hours = nil if p.estimated_hours == 0.0
918
922
919 # ancestors will be recursively updated
923 # ancestors will be recursively updated
920 p.save(false)
924 p.save(false)
921 end
925 end
922 end
926 end
923
927
924 # Update issues so their versions are not pointing to a
928 # Update issues so their versions are not pointing to a
925 # fixed_version that is not shared with the issue's project
929 # fixed_version that is not shared with the issue's project
926 def self.update_versions(conditions=nil)
930 def self.update_versions(conditions=nil)
927 # Only need to update issues with a fixed_version from
931 # Only need to update issues with a fixed_version from
928 # a different project and that is not systemwide shared
932 # a different project and that is not systemwide shared
929 Issue.scoped(:conditions => conditions).all(
933 Issue.scoped(:conditions => conditions).all(
930 :conditions => "#{Issue.table_name}.fixed_version_id IS NOT NULL" +
934 :conditions => "#{Issue.table_name}.fixed_version_id IS NOT NULL" +
931 " AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" +
935 " AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" +
932 " AND #{Version.table_name}.sharing <> 'system'",
936 " AND #{Version.table_name}.sharing <> 'system'",
933 :include => [:project, :fixed_version]
937 :include => [:project, :fixed_version]
934 ).each do |issue|
938 ).each do |issue|
935 next if issue.project.nil? || issue.fixed_version.nil?
939 next if issue.project.nil? || issue.fixed_version.nil?
936 unless issue.project.shared_versions.include?(issue.fixed_version)
940 unless issue.project.shared_versions.include?(issue.fixed_version)
937 issue.init_journal(User.current)
941 issue.init_journal(User.current)
938 issue.fixed_version = nil
942 issue.fixed_version = nil
939 issue.save
943 issue.save
940 end
944 end
941 end
945 end
942 end
946 end
943
947
944 # Callback on attachment deletion
948 # Callback on attachment deletion
945 def attachment_added(obj)
949 def attachment_added(obj)
946 if @current_journal && !obj.new_record?
950 if @current_journal && !obj.new_record?
947 @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => obj.id, :value => obj.filename)
951 @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => obj.id, :value => obj.filename)
948 end
952 end
949 end
953 end
950
954
951 # Callback on attachment deletion
955 # Callback on attachment deletion
952 def attachment_removed(obj)
956 def attachment_removed(obj)
953 if @current_journal && !obj.new_record?
957 if @current_journal && !obj.new_record?
954 @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => obj.id, :old_value => obj.filename)
958 @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => obj.id, :old_value => obj.filename)
955 @current_journal.save
959 @current_journal.save
956 end
960 end
957 end
961 end
958
962
959 # Default assignment based on category
963 # Default assignment based on category
960 def default_assign
964 def default_assign
961 if assigned_to.nil? && category && category.assigned_to
965 if assigned_to.nil? && category && category.assigned_to
962 self.assigned_to = category.assigned_to
966 self.assigned_to = category.assigned_to
963 end
967 end
964 end
968 end
965
969
966 # Updates start/due dates of following issues
970 # Updates start/due dates of following issues
967 def reschedule_following_issues
971 def reschedule_following_issues
968 if start_date_changed? || due_date_changed?
972 if start_date_changed? || due_date_changed?
969 relations_from.each do |relation|
973 relations_from.each do |relation|
970 relation.set_issue_to_dates
974 relation.set_issue_to_dates
971 end
975 end
972 end
976 end
973 end
977 end
974
978
975 # Closes duplicates if the issue is being closed
979 # Closes duplicates if the issue is being closed
976 def close_duplicates
980 def close_duplicates
977 if closing?
981 if closing?
978 duplicates.each do |duplicate|
982 duplicates.each do |duplicate|
979 # Reload is need in case the duplicate was updated by a previous duplicate
983 # Reload is need in case the duplicate was updated by a previous duplicate
980 duplicate.reload
984 duplicate.reload
981 # Don't re-close it if it's already closed
985 # Don't re-close it if it's already closed
982 next if duplicate.closed?
986 next if duplicate.closed?
983 # Same user and notes
987 # Same user and notes
984 if @current_journal
988 if @current_journal
985 duplicate.init_journal(@current_journal.user, @current_journal.notes)
989 duplicate.init_journal(@current_journal.user, @current_journal.notes)
986 end
990 end
987 duplicate.update_attribute :status, self.status
991 duplicate.update_attribute :status, self.status
988 end
992 end
989 end
993 end
990 end
994 end
991
995
992 # Saves the changes in a Journal
996 # Saves the changes in a Journal
993 # Called after_save
997 # Called after_save
994 def create_journal
998 def create_journal
995 if @current_journal
999 if @current_journal
996 # attributes changes
1000 # attributes changes
997 if @attributes_before_change
1001 if @attributes_before_change
998 (Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on)).each {|c|
1002 (Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on)).each {|c|
999 before = @attributes_before_change[c]
1003 before = @attributes_before_change[c]
1000 after = send(c)
1004 after = send(c)
1001 next if before == after || (before.blank? && after.blank?)
1005 next if before == after || (before.blank? && after.blank?)
1002 @current_journal.details << JournalDetail.new(:property => 'attr',
1006 @current_journal.details << JournalDetail.new(:property => 'attr',
1003 :prop_key => c,
1007 :prop_key => c,
1004 :old_value => before,
1008 :old_value => before,
1005 :value => after)
1009 :value => after)
1006 }
1010 }
1007 end
1011 end
1008 if @custom_values_before_change
1012 if @custom_values_before_change
1009 # custom fields changes
1013 # custom fields changes
1010 custom_field_values.each {|c|
1014 custom_field_values.each {|c|
1011 before = @custom_values_before_change[c.custom_field_id]
1015 before = @custom_values_before_change[c.custom_field_id]
1012 after = c.value
1016 after = c.value
1013 next if before == after || (before.blank? && after.blank?)
1017 next if before == after || (before.blank? && after.blank?)
1014
1018
1015 if before.is_a?(Array) || after.is_a?(Array)
1019 if before.is_a?(Array) || after.is_a?(Array)
1016 before = [before] unless before.is_a?(Array)
1020 before = [before] unless before.is_a?(Array)
1017 after = [after] unless after.is_a?(Array)
1021 after = [after] unless after.is_a?(Array)
1018
1022
1019 # values removed
1023 # values removed
1020 (before - after).reject(&:blank?).each do |value|
1024 (before - after).reject(&:blank?).each do |value|
1021 @current_journal.details << JournalDetail.new(:property => 'cf',
1025 @current_journal.details << JournalDetail.new(:property => 'cf',
1022 :prop_key => c.custom_field_id,
1026 :prop_key => c.custom_field_id,
1023 :old_value => value,
1027 :old_value => value,
1024 :value => nil)
1028 :value => nil)
1025 end
1029 end
1026 # values added
1030 # values added
1027 (after - before).reject(&:blank?).each do |value|
1031 (after - before).reject(&:blank?).each do |value|
1028 @current_journal.details << JournalDetail.new(:property => 'cf',
1032 @current_journal.details << JournalDetail.new(:property => 'cf',
1029 :prop_key => c.custom_field_id,
1033 :prop_key => c.custom_field_id,
1030 :old_value => nil,
1034 :old_value => nil,
1031 :value => value)
1035 :value => value)
1032 end
1036 end
1033 else
1037 else
1034 @current_journal.details << JournalDetail.new(:property => 'cf',
1038 @current_journal.details << JournalDetail.new(:property => 'cf',
1035 :prop_key => c.custom_field_id,
1039 :prop_key => c.custom_field_id,
1036 :old_value => before,
1040 :old_value => before,
1037 :value => after)
1041 :value => after)
1038 end
1042 end
1039 }
1043 }
1040 end
1044 end
1041 @current_journal.save
1045 @current_journal.save
1042 # reset current journal
1046 # reset current journal
1043 init_journal @current_journal.user, @current_journal.notes
1047 init_journal @current_journal.user, @current_journal.notes
1044 end
1048 end
1045 end
1049 end
1046
1050
1047 # Query generator for selecting groups of issue counts for a project
1051 # Query generator for selecting groups of issue counts for a project
1048 # based on specific criteria
1052 # based on specific criteria
1049 #
1053 #
1050 # Options
1054 # Options
1051 # * project - Project to search in.
1055 # * project - Project to search in.
1052 # * field - String. Issue field to key off of in the grouping.
1056 # * field - String. Issue field to key off of in the grouping.
1053 # * joins - String. The table name to join against.
1057 # * joins - String. The table name to join against.
1054 def self.count_and_group_by(options)
1058 def self.count_and_group_by(options)
1055 project = options.delete(:project)
1059 project = options.delete(:project)
1056 select_field = options.delete(:field)
1060 select_field = options.delete(:field)
1057 joins = options.delete(:joins)
1061 joins = options.delete(:joins)
1058
1062
1059 where = "#{Issue.table_name}.#{select_field}=j.id"
1063 where = "#{Issue.table_name}.#{select_field}=j.id"
1060
1064
1061 ActiveRecord::Base.connection.select_all("select s.id as status_id,
1065 ActiveRecord::Base.connection.select_all("select s.id as status_id,
1062 s.is_closed as closed,
1066 s.is_closed as closed,
1063 j.id as #{select_field},
1067 j.id as #{select_field},
1064 count(#{Issue.table_name}.id) as total
1068 count(#{Issue.table_name}.id) as total
1065 from
1069 from
1066 #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s, #{joins} j
1070 #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s, #{joins} j
1067 where
1071 where
1068 #{Issue.table_name}.status_id=s.id
1072 #{Issue.table_name}.status_id=s.id
1069 and #{where}
1073 and #{where}
1070 and #{Issue.table_name}.project_id=#{Project.table_name}.id
1074 and #{Issue.table_name}.project_id=#{Project.table_name}.id
1071 and #{visible_condition(User.current, :project => project)}
1075 and #{visible_condition(User.current, :project => project)}
1072 group by s.id, s.is_closed, j.id")
1076 group by s.id, s.is_closed, j.id")
1073 end
1077 end
1074 end
1078 end
@@ -1,3235 +1,3249
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19 require 'issues_controller'
19 require 'issues_controller'
20
20
21 class IssuesControllerTest < ActionController::TestCase
21 class IssuesControllerTest < ActionController::TestCase
22 fixtures :projects,
22 fixtures :projects,
23 :users,
23 :users,
24 :roles,
24 :roles,
25 :members,
25 :members,
26 :member_roles,
26 :member_roles,
27 :issues,
27 :issues,
28 :issue_statuses,
28 :issue_statuses,
29 :versions,
29 :versions,
30 :trackers,
30 :trackers,
31 :projects_trackers,
31 :projects_trackers,
32 :issue_categories,
32 :issue_categories,
33 :enabled_modules,
33 :enabled_modules,
34 :enumerations,
34 :enumerations,
35 :attachments,
35 :attachments,
36 :workflows,
36 :workflows,
37 :custom_fields,
37 :custom_fields,
38 :custom_values,
38 :custom_values,
39 :custom_fields_projects,
39 :custom_fields_projects,
40 :custom_fields_trackers,
40 :custom_fields_trackers,
41 :time_entries,
41 :time_entries,
42 :journals,
42 :journals,
43 :journal_details,
43 :journal_details,
44 :queries,
44 :queries,
45 :repositories,
45 :repositories,
46 :changesets
46 :changesets
47
47
48 include Redmine::I18n
48 include Redmine::I18n
49
49
50 def setup
50 def setup
51 @controller = IssuesController.new
51 @controller = IssuesController.new
52 @request = ActionController::TestRequest.new
52 @request = ActionController::TestRequest.new
53 @response = ActionController::TestResponse.new
53 @response = ActionController::TestResponse.new
54 User.current = nil
54 User.current = nil
55 end
55 end
56
56
57 def test_index
57 def test_index
58 with_settings :default_language => "en" do
58 with_settings :default_language => "en" do
59 get :index
59 get :index
60 assert_response :success
60 assert_response :success
61 assert_template 'index'
61 assert_template 'index'
62 assert_not_nil assigns(:issues)
62 assert_not_nil assigns(:issues)
63 assert_nil assigns(:project)
63 assert_nil assigns(:project)
64 assert_tag :tag => 'a', :content => /Can't print recipes/
64 assert_tag :tag => 'a', :content => /Can't print recipes/
65 assert_tag :tag => 'a', :content => /Subproject issue/
65 assert_tag :tag => 'a', :content => /Subproject issue/
66 # private projects hidden
66 # private projects hidden
67 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
67 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
68 assert_no_tag :tag => 'a', :content => /Issue on project 2/
68 assert_no_tag :tag => 'a', :content => /Issue on project 2/
69 # project column
69 # project column
70 assert_tag :tag => 'th', :content => /Project/
70 assert_tag :tag => 'th', :content => /Project/
71 end
71 end
72 end
72 end
73
73
74 def test_index_should_not_list_issues_when_module_disabled
74 def test_index_should_not_list_issues_when_module_disabled
75 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
75 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
76 get :index
76 get :index
77 assert_response :success
77 assert_response :success
78 assert_template 'index'
78 assert_template 'index'
79 assert_not_nil assigns(:issues)
79 assert_not_nil assigns(:issues)
80 assert_nil assigns(:project)
80 assert_nil assigns(:project)
81 assert_no_tag :tag => 'a', :content => /Can't print recipes/
81 assert_no_tag :tag => 'a', :content => /Can't print recipes/
82 assert_tag :tag => 'a', :content => /Subproject issue/
82 assert_tag :tag => 'a', :content => /Subproject issue/
83 end
83 end
84
84
85 def test_index_should_list_visible_issues_only
85 def test_index_should_list_visible_issues_only
86 get :index, :per_page => 100
86 get :index, :per_page => 100
87 assert_response :success
87 assert_response :success
88 assert_not_nil assigns(:issues)
88 assert_not_nil assigns(:issues)
89 assert_nil assigns(:issues).detect {|issue| !issue.visible?}
89 assert_nil assigns(:issues).detect {|issue| !issue.visible?}
90 end
90 end
91
91
92 def test_index_with_project
92 def test_index_with_project
93 Setting.display_subprojects_issues = 0
93 Setting.display_subprojects_issues = 0
94 get :index, :project_id => 1
94 get :index, :project_id => 1
95 assert_response :success
95 assert_response :success
96 assert_template 'index'
96 assert_template 'index'
97 assert_not_nil assigns(:issues)
97 assert_not_nil assigns(:issues)
98 assert_tag :tag => 'a', :content => /Can't print recipes/
98 assert_tag :tag => 'a', :content => /Can't print recipes/
99 assert_no_tag :tag => 'a', :content => /Subproject issue/
99 assert_no_tag :tag => 'a', :content => /Subproject issue/
100 end
100 end
101
101
102 def test_index_with_project_and_subprojects
102 def test_index_with_project_and_subprojects
103 Setting.display_subprojects_issues = 1
103 Setting.display_subprojects_issues = 1
104 get :index, :project_id => 1
104 get :index, :project_id => 1
105 assert_response :success
105 assert_response :success
106 assert_template 'index'
106 assert_template 'index'
107 assert_not_nil assigns(:issues)
107 assert_not_nil assigns(:issues)
108 assert_tag :tag => 'a', :content => /Can't print recipes/
108 assert_tag :tag => 'a', :content => /Can't print recipes/
109 assert_tag :tag => 'a', :content => /Subproject issue/
109 assert_tag :tag => 'a', :content => /Subproject issue/
110 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
110 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
111 end
111 end
112
112
113 def test_index_with_project_and_subprojects_should_show_private_subprojects
113 def test_index_with_project_and_subprojects_should_show_private_subprojects
114 @request.session[:user_id] = 2
114 @request.session[:user_id] = 2
115 Setting.display_subprojects_issues = 1
115 Setting.display_subprojects_issues = 1
116 get :index, :project_id => 1
116 get :index, :project_id => 1
117 assert_response :success
117 assert_response :success
118 assert_template 'index'
118 assert_template 'index'
119 assert_not_nil assigns(:issues)
119 assert_not_nil assigns(:issues)
120 assert_tag :tag => 'a', :content => /Can't print recipes/
120 assert_tag :tag => 'a', :content => /Can't print recipes/
121 assert_tag :tag => 'a', :content => /Subproject issue/
121 assert_tag :tag => 'a', :content => /Subproject issue/
122 assert_tag :tag => 'a', :content => /Issue of a private subproject/
122 assert_tag :tag => 'a', :content => /Issue of a private subproject/
123 end
123 end
124
124
125 def test_index_with_project_and_default_filter
125 def test_index_with_project_and_default_filter
126 get :index, :project_id => 1, :set_filter => 1
126 get :index, :project_id => 1, :set_filter => 1
127 assert_response :success
127 assert_response :success
128 assert_template 'index'
128 assert_template 'index'
129 assert_not_nil assigns(:issues)
129 assert_not_nil assigns(:issues)
130
130
131 query = assigns(:query)
131 query = assigns(:query)
132 assert_not_nil query
132 assert_not_nil query
133 # default filter
133 # default filter
134 assert_equal({'status_id' => {:operator => 'o', :values => ['']}}, query.filters)
134 assert_equal({'status_id' => {:operator => 'o', :values => ['']}}, query.filters)
135 end
135 end
136
136
137 def test_index_with_project_and_filter
137 def test_index_with_project_and_filter
138 get :index, :project_id => 1, :set_filter => 1,
138 get :index, :project_id => 1, :set_filter => 1,
139 :f => ['tracker_id'],
139 :f => ['tracker_id'],
140 :op => {'tracker_id' => '='},
140 :op => {'tracker_id' => '='},
141 :v => {'tracker_id' => ['1']}
141 :v => {'tracker_id' => ['1']}
142 assert_response :success
142 assert_response :success
143 assert_template 'index'
143 assert_template 'index'
144 assert_not_nil assigns(:issues)
144 assert_not_nil assigns(:issues)
145
145
146 query = assigns(:query)
146 query = assigns(:query)
147 assert_not_nil query
147 assert_not_nil query
148 assert_equal({'tracker_id' => {:operator => '=', :values => ['1']}}, query.filters)
148 assert_equal({'tracker_id' => {:operator => '=', :values => ['1']}}, query.filters)
149 end
149 end
150
150
151 def test_index_with_short_filters
151 def test_index_with_short_filters
152 to_test = {
152 to_test = {
153 'status_id' => {
153 'status_id' => {
154 'o' => { :op => 'o', :values => [''] },
154 'o' => { :op => 'o', :values => [''] },
155 'c' => { :op => 'c', :values => [''] },
155 'c' => { :op => 'c', :values => [''] },
156 '7' => { :op => '=', :values => ['7'] },
156 '7' => { :op => '=', :values => ['7'] },
157 '7|3|4' => { :op => '=', :values => ['7', '3', '4'] },
157 '7|3|4' => { :op => '=', :values => ['7', '3', '4'] },
158 '=7' => { :op => '=', :values => ['7'] },
158 '=7' => { :op => '=', :values => ['7'] },
159 '!3' => { :op => '!', :values => ['3'] },
159 '!3' => { :op => '!', :values => ['3'] },
160 '!7|3|4' => { :op => '!', :values => ['7', '3', '4'] }},
160 '!7|3|4' => { :op => '!', :values => ['7', '3', '4'] }},
161 'subject' => {
161 'subject' => {
162 'This is a subject' => { :op => '=', :values => ['This is a subject'] },
162 'This is a subject' => { :op => '=', :values => ['This is a subject'] },
163 'o' => { :op => '=', :values => ['o'] },
163 'o' => { :op => '=', :values => ['o'] },
164 '~This is part of a subject' => { :op => '~', :values => ['This is part of a subject'] },
164 '~This is part of a subject' => { :op => '~', :values => ['This is part of a subject'] },
165 '!~This is part of a subject' => { :op => '!~', :values => ['This is part of a subject'] }},
165 '!~This is part of a subject' => { :op => '!~', :values => ['This is part of a subject'] }},
166 'tracker_id' => {
166 'tracker_id' => {
167 '3' => { :op => '=', :values => ['3'] },
167 '3' => { :op => '=', :values => ['3'] },
168 '=3' => { :op => '=', :values => ['3'] }},
168 '=3' => { :op => '=', :values => ['3'] }},
169 'start_date' => {
169 'start_date' => {
170 '2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
170 '2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
171 '=2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
171 '=2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
172 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
172 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
173 '<=2011-10-12' => { :op => '<=', :values => ['2011-10-12'] },
173 '<=2011-10-12' => { :op => '<=', :values => ['2011-10-12'] },
174 '><2011-10-01|2011-10-30' => { :op => '><', :values => ['2011-10-01', '2011-10-30'] },
174 '><2011-10-01|2011-10-30' => { :op => '><', :values => ['2011-10-01', '2011-10-30'] },
175 '<t+2' => { :op => '<t+', :values => ['2'] },
175 '<t+2' => { :op => '<t+', :values => ['2'] },
176 '>t+2' => { :op => '>t+', :values => ['2'] },
176 '>t+2' => { :op => '>t+', :values => ['2'] },
177 't+2' => { :op => 't+', :values => ['2'] },
177 't+2' => { :op => 't+', :values => ['2'] },
178 't' => { :op => 't', :values => [''] },
178 't' => { :op => 't', :values => [''] },
179 'w' => { :op => 'w', :values => [''] },
179 'w' => { :op => 'w', :values => [''] },
180 '>t-2' => { :op => '>t-', :values => ['2'] },
180 '>t-2' => { :op => '>t-', :values => ['2'] },
181 '<t-2' => { :op => '<t-', :values => ['2'] },
181 '<t-2' => { :op => '<t-', :values => ['2'] },
182 't-2' => { :op => 't-', :values => ['2'] }},
182 't-2' => { :op => 't-', :values => ['2'] }},
183 'created_on' => {
183 'created_on' => {
184 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
184 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
185 '<t-2' => { :op => '<t-', :values => ['2'] },
185 '<t-2' => { :op => '<t-', :values => ['2'] },
186 '>t-2' => { :op => '>t-', :values => ['2'] },
186 '>t-2' => { :op => '>t-', :values => ['2'] },
187 't-2' => { :op => 't-', :values => ['2'] }},
187 't-2' => { :op => 't-', :values => ['2'] }},
188 'cf_1' => {
188 'cf_1' => {
189 'c' => { :op => '=', :values => ['c'] },
189 'c' => { :op => '=', :values => ['c'] },
190 '!c' => { :op => '!', :values => ['c'] },
190 '!c' => { :op => '!', :values => ['c'] },
191 '!*' => { :op => '!*', :values => [''] },
191 '!*' => { :op => '!*', :values => [''] },
192 '*' => { :op => '*', :values => [''] }},
192 '*' => { :op => '*', :values => [''] }},
193 'estimated_hours' => {
193 'estimated_hours' => {
194 '=13.4' => { :op => '=', :values => ['13.4'] },
194 '=13.4' => { :op => '=', :values => ['13.4'] },
195 '>=45' => { :op => '>=', :values => ['45'] },
195 '>=45' => { :op => '>=', :values => ['45'] },
196 '<=125' => { :op => '<=', :values => ['125'] },
196 '<=125' => { :op => '<=', :values => ['125'] },
197 '><10.5|20.5' => { :op => '><', :values => ['10.5', '20.5'] },
197 '><10.5|20.5' => { :op => '><', :values => ['10.5', '20.5'] },
198 '!*' => { :op => '!*', :values => [''] },
198 '!*' => { :op => '!*', :values => [''] },
199 '*' => { :op => '*', :values => [''] }}
199 '*' => { :op => '*', :values => [''] }}
200 }
200 }
201
201
202 default_filter = { 'status_id' => {:operator => 'o', :values => [''] }}
202 default_filter = { 'status_id' => {:operator => 'o', :values => [''] }}
203
203
204 to_test.each do |field, expression_and_expected|
204 to_test.each do |field, expression_and_expected|
205 expression_and_expected.each do |filter_expression, expected|
205 expression_and_expected.each do |filter_expression, expected|
206
206
207 get :index, :set_filter => 1, field => filter_expression
207 get :index, :set_filter => 1, field => filter_expression
208
208
209 assert_response :success
209 assert_response :success
210 assert_template 'index'
210 assert_template 'index'
211 assert_not_nil assigns(:issues)
211 assert_not_nil assigns(:issues)
212
212
213 query = assigns(:query)
213 query = assigns(:query)
214 assert_not_nil query
214 assert_not_nil query
215 assert query.has_filter?(field)
215 assert query.has_filter?(field)
216 assert_equal(default_filter.merge({field => {:operator => expected[:op], :values => expected[:values]}}), query.filters)
216 assert_equal(default_filter.merge({field => {:operator => expected[:op], :values => expected[:values]}}), query.filters)
217 end
217 end
218 end
218 end
219 end
219 end
220
220
221 def test_index_with_project_and_empty_filters
221 def test_index_with_project_and_empty_filters
222 get :index, :project_id => 1, :set_filter => 1, :fields => ['']
222 get :index, :project_id => 1, :set_filter => 1, :fields => ['']
223 assert_response :success
223 assert_response :success
224 assert_template 'index'
224 assert_template 'index'
225 assert_not_nil assigns(:issues)
225 assert_not_nil assigns(:issues)
226
226
227 query = assigns(:query)
227 query = assigns(:query)
228 assert_not_nil query
228 assert_not_nil query
229 # no filter
229 # no filter
230 assert_equal({}, query.filters)
230 assert_equal({}, query.filters)
231 end
231 end
232
232
233 def test_index_with_query
233 def test_index_with_query
234 get :index, :project_id => 1, :query_id => 5
234 get :index, :project_id => 1, :query_id => 5
235 assert_response :success
235 assert_response :success
236 assert_template 'index'
236 assert_template 'index'
237 assert_not_nil assigns(:issues)
237 assert_not_nil assigns(:issues)
238 assert_nil assigns(:issue_count_by_group)
238 assert_nil assigns(:issue_count_by_group)
239 end
239 end
240
240
241 def test_index_with_query_grouped_by_tracker
241 def test_index_with_query_grouped_by_tracker
242 get :index, :project_id => 1, :query_id => 6
242 get :index, :project_id => 1, :query_id => 6
243 assert_response :success
243 assert_response :success
244 assert_template 'index'
244 assert_template 'index'
245 assert_not_nil assigns(:issues)
245 assert_not_nil assigns(:issues)
246 assert_not_nil assigns(:issue_count_by_group)
246 assert_not_nil assigns(:issue_count_by_group)
247 end
247 end
248
248
249 def test_index_with_query_grouped_by_list_custom_field
249 def test_index_with_query_grouped_by_list_custom_field
250 get :index, :project_id => 1, :query_id => 9
250 get :index, :project_id => 1, :query_id => 9
251 assert_response :success
251 assert_response :success
252 assert_template 'index'
252 assert_template 'index'
253 assert_not_nil assigns(:issues)
253 assert_not_nil assigns(:issues)
254 assert_not_nil assigns(:issue_count_by_group)
254 assert_not_nil assigns(:issue_count_by_group)
255 end
255 end
256
256
257 def test_index_with_query_id_and_project_id_should_set_session_query
257 def test_index_with_query_id_and_project_id_should_set_session_query
258 get :index, :project_id => 1, :query_id => 4
258 get :index, :project_id => 1, :query_id => 4
259 assert_response :success
259 assert_response :success
260 assert_kind_of Hash, session[:query]
260 assert_kind_of Hash, session[:query]
261 assert_equal 4, session[:query][:id]
261 assert_equal 4, session[:query][:id]
262 assert_equal 1, session[:query][:project_id]
262 assert_equal 1, session[:query][:project_id]
263 end
263 end
264
264
265 def test_index_with_invalid_query_id_should_respond_404
265 def test_index_with_invalid_query_id_should_respond_404
266 get :index, :project_id => 1, :query_id => 999
266 get :index, :project_id => 1, :query_id => 999
267 assert_response 404
267 assert_response 404
268 end
268 end
269
269
270 def test_index_with_cross_project_query_in_session_should_show_project_issues
270 def test_index_with_cross_project_query_in_session_should_show_project_issues
271 q = Query.create!(:name => "test", :user_id => 2, :is_public => false, :project => nil)
271 q = Query.create!(:name => "test", :user_id => 2, :is_public => false, :project => nil)
272 @request.session[:query] = {:id => q.id, :project_id => 1}
272 @request.session[:query] = {:id => q.id, :project_id => 1}
273
273
274 with_settings :display_subprojects_issues => '0' do
274 with_settings :display_subprojects_issues => '0' do
275 get :index, :project_id => 1
275 get :index, :project_id => 1
276 end
276 end
277 assert_response :success
277 assert_response :success
278 assert_not_nil assigns(:query)
278 assert_not_nil assigns(:query)
279 assert_equal q.id, assigns(:query).id
279 assert_equal q.id, assigns(:query).id
280 assert_equal 1, assigns(:query).project_id
280 assert_equal 1, assigns(:query).project_id
281 assert_equal [1], assigns(:issues).map(&:project_id).uniq
281 assert_equal [1], assigns(:issues).map(&:project_id).uniq
282 end
282 end
283
283
284 def test_private_query_should_not_be_available_to_other_users
284 def test_private_query_should_not_be_available_to_other_users
285 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
285 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
286 @request.session[:user_id] = 3
286 @request.session[:user_id] = 3
287
287
288 get :index, :query_id => q.id
288 get :index, :query_id => q.id
289 assert_response 403
289 assert_response 403
290 end
290 end
291
291
292 def test_private_query_should_be_available_to_its_user
292 def test_private_query_should_be_available_to_its_user
293 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
293 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
294 @request.session[:user_id] = 2
294 @request.session[:user_id] = 2
295
295
296 get :index, :query_id => q.id
296 get :index, :query_id => q.id
297 assert_response :success
297 assert_response :success
298 end
298 end
299
299
300 def test_public_query_should_be_available_to_other_users
300 def test_public_query_should_be_available_to_other_users
301 q = Query.create!(:name => "private", :user => User.find(2), :is_public => true, :project => nil)
301 q = Query.create!(:name => "private", :user => User.find(2), :is_public => true, :project => nil)
302 @request.session[:user_id] = 3
302 @request.session[:user_id] = 3
303
303
304 get :index, :query_id => q.id
304 get :index, :query_id => q.id
305 assert_response :success
305 assert_response :success
306 end
306 end
307
307
308 def test_index_should_omit_page_param_in_export_links
308 def test_index_should_omit_page_param_in_export_links
309 get :index, :page => 2
309 get :index, :page => 2
310 assert_response :success
310 assert_response :success
311 assert_select 'a.atom[href=/issues.atom]'
311 assert_select 'a.atom[href=/issues.atom]'
312 assert_select 'a.csv[href=/issues.csv]'
312 assert_select 'a.csv[href=/issues.csv]'
313 assert_select 'a.pdf[href=/issues.pdf]'
313 assert_select 'a.pdf[href=/issues.pdf]'
314 assert_select 'form#csv-export-form[action=/issues.csv]'
314 assert_select 'form#csv-export-form[action=/issues.csv]'
315 end
315 end
316
316
317 def test_index_csv
317 def test_index_csv
318 get :index, :format => 'csv'
318 get :index, :format => 'csv'
319 assert_response :success
319 assert_response :success
320 assert_not_nil assigns(:issues)
320 assert_not_nil assigns(:issues)
321 assert_equal 'text/csv', @response.content_type
321 assert_equal 'text/csv', @response.content_type
322 assert @response.body.starts_with?("#,")
322 assert @response.body.starts_with?("#,")
323 lines = @response.body.chomp.split("\n")
323 lines = @response.body.chomp.split("\n")
324 assert_equal assigns(:query).columns.size + 1, lines[0].split(',').size
324 assert_equal assigns(:query).columns.size + 1, lines[0].split(',').size
325 end
325 end
326
326
327 def test_index_csv_with_project
327 def test_index_csv_with_project
328 get :index, :project_id => 1, :format => 'csv'
328 get :index, :project_id => 1, :format => 'csv'
329 assert_response :success
329 assert_response :success
330 assert_not_nil assigns(:issues)
330 assert_not_nil assigns(:issues)
331 assert_equal 'text/csv', @response.content_type
331 assert_equal 'text/csv', @response.content_type
332 end
332 end
333
333
334 def test_index_csv_with_description
334 def test_index_csv_with_description
335 get :index, :format => 'csv', :description => '1'
335 get :index, :format => 'csv', :description => '1'
336 assert_response :success
336 assert_response :success
337 assert_not_nil assigns(:issues)
337 assert_not_nil assigns(:issues)
338 assert_equal 'text/csv', @response.content_type
338 assert_equal 'text/csv', @response.content_type
339 assert @response.body.starts_with?("#,")
339 assert @response.body.starts_with?("#,")
340 lines = @response.body.chomp.split("\n")
340 lines = @response.body.chomp.split("\n")
341 assert_equal assigns(:query).columns.size + 2, lines[0].split(',').size
341 assert_equal assigns(:query).columns.size + 2, lines[0].split(',').size
342 end
342 end
343
343
344 def test_index_csv_with_spent_time_column
344 def test_index_csv_with_spent_time_column
345 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :subject => 'test_index_csv_with_spent_time_column', :author_id => 2)
345 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :subject => 'test_index_csv_with_spent_time_column', :author_id => 2)
346 TimeEntry.create!(:project => issue.project, :issue => issue, :hours => 7.33, :user => User.find(2), :spent_on => Date.today)
346 TimeEntry.create!(:project => issue.project, :issue => issue, :hours => 7.33, :user => User.find(2), :spent_on => Date.today)
347
347
348 get :index, :format => 'csv', :set_filter => '1', :c => %w(subject spent_hours)
348 get :index, :format => 'csv', :set_filter => '1', :c => %w(subject spent_hours)
349 assert_response :success
349 assert_response :success
350 assert_equal 'text/csv', @response.content_type
350 assert_equal 'text/csv', @response.content_type
351 lines = @response.body.chomp.split("\n")
351 lines = @response.body.chomp.split("\n")
352 assert_include "#{issue.id},#{issue.subject},7.33", lines
352 assert_include "#{issue.id},#{issue.subject},7.33", lines
353 end
353 end
354
354
355 def test_index_csv_with_all_columns
355 def test_index_csv_with_all_columns
356 get :index, :format => 'csv', :columns => 'all'
356 get :index, :format => 'csv', :columns => 'all'
357 assert_response :success
357 assert_response :success
358 assert_not_nil assigns(:issues)
358 assert_not_nil assigns(:issues)
359 assert_equal 'text/csv', @response.content_type
359 assert_equal 'text/csv', @response.content_type
360 assert @response.body.starts_with?("#,")
360 assert @response.body.starts_with?("#,")
361 lines = @response.body.chomp.split("\n")
361 lines = @response.body.chomp.split("\n")
362 assert_equal assigns(:query).available_columns.size + 1, lines[0].split(',').size
362 assert_equal assigns(:query).available_columns.size + 1, lines[0].split(',').size
363 end
363 end
364
364
365 def test_index_csv_with_multi_column_field
365 def test_index_csv_with_multi_column_field
366 CustomField.find(1).update_attribute :multiple, true
366 CustomField.find(1).update_attribute :multiple, true
367 issue = Issue.find(1)
367 issue = Issue.find(1)
368 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
368 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
369 issue.save!
369 issue.save!
370
370
371 get :index, :format => 'csv', :columns => 'all'
371 get :index, :format => 'csv', :columns => 'all'
372 assert_response :success
372 assert_response :success
373 lines = @response.body.chomp.split("\n")
373 lines = @response.body.chomp.split("\n")
374 assert lines.detect {|line| line.include?('"MySQL, Oracle"')}
374 assert lines.detect {|line| line.include?('"MySQL, Oracle"')}
375 end
375 end
376
376
377 def test_index_csv_big_5
377 def test_index_csv_big_5
378 with_settings :default_language => "zh-TW" do
378 with_settings :default_language => "zh-TW" do
379 str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88"
379 str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88"
380 str_big5 = "\xa4@\xa4\xeb"
380 str_big5 = "\xa4@\xa4\xeb"
381 if str_utf8.respond_to?(:force_encoding)
381 if str_utf8.respond_to?(:force_encoding)
382 str_utf8.force_encoding('UTF-8')
382 str_utf8.force_encoding('UTF-8')
383 str_big5.force_encoding('Big5')
383 str_big5.force_encoding('Big5')
384 end
384 end
385 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
385 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
386 :status_id => 1, :priority => IssuePriority.all.first,
386 :status_id => 1, :priority => IssuePriority.all.first,
387 :subject => str_utf8)
387 :subject => str_utf8)
388 assert issue.save
388 assert issue.save
389
389
390 get :index, :project_id => 1,
390 get :index, :project_id => 1,
391 :f => ['subject'],
391 :f => ['subject'],
392 :op => '=', :values => [str_utf8],
392 :op => '=', :values => [str_utf8],
393 :format => 'csv'
393 :format => 'csv'
394 assert_equal 'text/csv', @response.content_type
394 assert_equal 'text/csv', @response.content_type
395 lines = @response.body.chomp.split("\n")
395 lines = @response.body.chomp.split("\n")
396 s1 = "\xaa\xac\xbaA"
396 s1 = "\xaa\xac\xbaA"
397 if str_utf8.respond_to?(:force_encoding)
397 if str_utf8.respond_to?(:force_encoding)
398 s1.force_encoding('Big5')
398 s1.force_encoding('Big5')
399 end
399 end
400 assert lines[0].include?(s1)
400 assert lines[0].include?(s1)
401 assert lines[1].include?(str_big5)
401 assert lines[1].include?(str_big5)
402 end
402 end
403 end
403 end
404
404
405 def test_index_csv_cannot_convert_should_be_replaced_big_5
405 def test_index_csv_cannot_convert_should_be_replaced_big_5
406 with_settings :default_language => "zh-TW" do
406 with_settings :default_language => "zh-TW" do
407 str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85"
407 str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85"
408 if str_utf8.respond_to?(:force_encoding)
408 if str_utf8.respond_to?(:force_encoding)
409 str_utf8.force_encoding('UTF-8')
409 str_utf8.force_encoding('UTF-8')
410 end
410 end
411 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
411 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
412 :status_id => 1, :priority => IssuePriority.all.first,
412 :status_id => 1, :priority => IssuePriority.all.first,
413 :subject => str_utf8)
413 :subject => str_utf8)
414 assert issue.save
414 assert issue.save
415
415
416 get :index, :project_id => 1,
416 get :index, :project_id => 1,
417 :f => ['subject'],
417 :f => ['subject'],
418 :op => '=', :values => [str_utf8],
418 :op => '=', :values => [str_utf8],
419 :c => ['status', 'subject'],
419 :c => ['status', 'subject'],
420 :format => 'csv',
420 :format => 'csv',
421 :set_filter => 1
421 :set_filter => 1
422 assert_equal 'text/csv', @response.content_type
422 assert_equal 'text/csv', @response.content_type
423 lines = @response.body.chomp.split("\n")
423 lines = @response.body.chomp.split("\n")
424 s1 = "\xaa\xac\xbaA" # status
424 s1 = "\xaa\xac\xbaA" # status
425 if str_utf8.respond_to?(:force_encoding)
425 if str_utf8.respond_to?(:force_encoding)
426 s1.force_encoding('Big5')
426 s1.force_encoding('Big5')
427 end
427 end
428 assert lines[0].include?(s1)
428 assert lines[0].include?(s1)
429 s2 = lines[1].split(",")[2]
429 s2 = lines[1].split(",")[2]
430 if s1.respond_to?(:force_encoding)
430 if s1.respond_to?(:force_encoding)
431 s3 = "\xa5H?" # subject
431 s3 = "\xa5H?" # subject
432 s3.force_encoding('Big5')
432 s3.force_encoding('Big5')
433 assert_equal s3, s2
433 assert_equal s3, s2
434 elsif RUBY_PLATFORM == 'java'
434 elsif RUBY_PLATFORM == 'java'
435 assert_equal "??", s2
435 assert_equal "??", s2
436 else
436 else
437 assert_equal "\xa5H???", s2
437 assert_equal "\xa5H???", s2
438 end
438 end
439 end
439 end
440 end
440 end
441
441
442 def test_index_csv_tw
442 def test_index_csv_tw
443 with_settings :default_language => "zh-TW" do
443 with_settings :default_language => "zh-TW" do
444 str1 = "test_index_csv_tw"
444 str1 = "test_index_csv_tw"
445 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
445 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
446 :status_id => 1, :priority => IssuePriority.all.first,
446 :status_id => 1, :priority => IssuePriority.all.first,
447 :subject => str1, :estimated_hours => '1234.5')
447 :subject => str1, :estimated_hours => '1234.5')
448 assert issue.save
448 assert issue.save
449 assert_equal 1234.5, issue.estimated_hours
449 assert_equal 1234.5, issue.estimated_hours
450
450
451 get :index, :project_id => 1,
451 get :index, :project_id => 1,
452 :f => ['subject'],
452 :f => ['subject'],
453 :op => '=', :values => [str1],
453 :op => '=', :values => [str1],
454 :c => ['estimated_hours', 'subject'],
454 :c => ['estimated_hours', 'subject'],
455 :format => 'csv',
455 :format => 'csv',
456 :set_filter => 1
456 :set_filter => 1
457 assert_equal 'text/csv', @response.content_type
457 assert_equal 'text/csv', @response.content_type
458 lines = @response.body.chomp.split("\n")
458 lines = @response.body.chomp.split("\n")
459 assert_equal "#{issue.id},1234.50,#{str1}", lines[1]
459 assert_equal "#{issue.id},1234.50,#{str1}", lines[1]
460
460
461 str_tw = "Traditional Chinese (\xe7\xb9\x81\xe9\xab\x94\xe4\xb8\xad\xe6\x96\x87)"
461 str_tw = "Traditional Chinese (\xe7\xb9\x81\xe9\xab\x94\xe4\xb8\xad\xe6\x96\x87)"
462 if str_tw.respond_to?(:force_encoding)
462 if str_tw.respond_to?(:force_encoding)
463 str_tw.force_encoding('UTF-8')
463 str_tw.force_encoding('UTF-8')
464 end
464 end
465 assert_equal str_tw, l(:general_lang_name)
465 assert_equal str_tw, l(:general_lang_name)
466 assert_equal ',', l(:general_csv_separator)
466 assert_equal ',', l(:general_csv_separator)
467 assert_equal '.', l(:general_csv_decimal_separator)
467 assert_equal '.', l(:general_csv_decimal_separator)
468 end
468 end
469 end
469 end
470
470
471 def test_index_csv_fr
471 def test_index_csv_fr
472 with_settings :default_language => "fr" do
472 with_settings :default_language => "fr" do
473 str1 = "test_index_csv_fr"
473 str1 = "test_index_csv_fr"
474 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
474 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
475 :status_id => 1, :priority => IssuePriority.all.first,
475 :status_id => 1, :priority => IssuePriority.all.first,
476 :subject => str1, :estimated_hours => '1234.5')
476 :subject => str1, :estimated_hours => '1234.5')
477 assert issue.save
477 assert issue.save
478 assert_equal 1234.5, issue.estimated_hours
478 assert_equal 1234.5, issue.estimated_hours
479
479
480 get :index, :project_id => 1,
480 get :index, :project_id => 1,
481 :f => ['subject'],
481 :f => ['subject'],
482 :op => '=', :values => [str1],
482 :op => '=', :values => [str1],
483 :c => ['estimated_hours', 'subject'],
483 :c => ['estimated_hours', 'subject'],
484 :format => 'csv',
484 :format => 'csv',
485 :set_filter => 1
485 :set_filter => 1
486 assert_equal 'text/csv', @response.content_type
486 assert_equal 'text/csv', @response.content_type
487 lines = @response.body.chomp.split("\n")
487 lines = @response.body.chomp.split("\n")
488 assert_equal "#{issue.id};1234,50;#{str1}", lines[1]
488 assert_equal "#{issue.id};1234,50;#{str1}", lines[1]
489
489
490 str_fr = "Fran\xc3\xa7ais"
490 str_fr = "Fran\xc3\xa7ais"
491 if str_fr.respond_to?(:force_encoding)
491 if str_fr.respond_to?(:force_encoding)
492 str_fr.force_encoding('UTF-8')
492 str_fr.force_encoding('UTF-8')
493 end
493 end
494 assert_equal str_fr, l(:general_lang_name)
494 assert_equal str_fr, l(:general_lang_name)
495 assert_equal ';', l(:general_csv_separator)
495 assert_equal ';', l(:general_csv_separator)
496 assert_equal ',', l(:general_csv_decimal_separator)
496 assert_equal ',', l(:general_csv_decimal_separator)
497 end
497 end
498 end
498 end
499
499
500 def test_index_pdf
500 def test_index_pdf
501 ["en", "zh", "zh-TW", "ja", "ko"].each do |lang|
501 ["en", "zh", "zh-TW", "ja", "ko"].each do |lang|
502 with_settings :default_language => lang do
502 with_settings :default_language => lang do
503
503
504 get :index
504 get :index
505 assert_response :success
505 assert_response :success
506 assert_template 'index'
506 assert_template 'index'
507
507
508 if lang == "ja"
508 if lang == "ja"
509 if RUBY_PLATFORM != 'java'
509 if RUBY_PLATFORM != 'java'
510 assert_equal "CP932", l(:general_pdf_encoding)
510 assert_equal "CP932", l(:general_pdf_encoding)
511 end
511 end
512 if RUBY_PLATFORM == 'java' && l(:general_pdf_encoding) == "CP932"
512 if RUBY_PLATFORM == 'java' && l(:general_pdf_encoding) == "CP932"
513 next
513 next
514 end
514 end
515 end
515 end
516
516
517 get :index, :format => 'pdf'
517 get :index, :format => 'pdf'
518 assert_response :success
518 assert_response :success
519 assert_not_nil assigns(:issues)
519 assert_not_nil assigns(:issues)
520 assert_equal 'application/pdf', @response.content_type
520 assert_equal 'application/pdf', @response.content_type
521
521
522 get :index, :project_id => 1, :format => 'pdf'
522 get :index, :project_id => 1, :format => 'pdf'
523 assert_response :success
523 assert_response :success
524 assert_not_nil assigns(:issues)
524 assert_not_nil assigns(:issues)
525 assert_equal 'application/pdf', @response.content_type
525 assert_equal 'application/pdf', @response.content_type
526
526
527 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
527 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
528 assert_response :success
528 assert_response :success
529 assert_not_nil assigns(:issues)
529 assert_not_nil assigns(:issues)
530 assert_equal 'application/pdf', @response.content_type
530 assert_equal 'application/pdf', @response.content_type
531 end
531 end
532 end
532 end
533 end
533 end
534
534
535 def test_index_pdf_with_query_grouped_by_list_custom_field
535 def test_index_pdf_with_query_grouped_by_list_custom_field
536 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
536 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
537 assert_response :success
537 assert_response :success
538 assert_not_nil assigns(:issues)
538 assert_not_nil assigns(:issues)
539 assert_not_nil assigns(:issue_count_by_group)
539 assert_not_nil assigns(:issue_count_by_group)
540 assert_equal 'application/pdf', @response.content_type
540 assert_equal 'application/pdf', @response.content_type
541 end
541 end
542
542
543 def test_index_atom
543 def test_index_atom
544 get :index, :project_id => 'ecookbook', :format => 'atom'
544 get :index, :project_id => 'ecookbook', :format => 'atom'
545 assert_response :success
545 assert_response :success
546 assert_template 'common/feed'
546 assert_template 'common/feed'
547
547
548 assert_tag :tag => 'link', :parent => {:tag => 'feed', :parent => nil },
548 assert_tag :tag => 'link', :parent => {:tag => 'feed', :parent => nil },
549 :attributes => {:rel => 'self', :href => 'http://test.host/projects/ecookbook/issues.atom'}
549 :attributes => {:rel => 'self', :href => 'http://test.host/projects/ecookbook/issues.atom'}
550 assert_tag :tag => 'link', :parent => {:tag => 'feed', :parent => nil },
550 assert_tag :tag => 'link', :parent => {:tag => 'feed', :parent => nil },
551 :attributes => {:rel => 'alternate', :href => 'http://test.host/projects/ecookbook/issues'}
551 :attributes => {:rel => 'alternate', :href => 'http://test.host/projects/ecookbook/issues'}
552
552
553 assert_tag :tag => 'entry', :child => {
553 assert_tag :tag => 'entry', :child => {
554 :tag => 'link',
554 :tag => 'link',
555 :attributes => {:href => 'http://test.host/issues/1'}}
555 :attributes => {:href => 'http://test.host/issues/1'}}
556 end
556 end
557
557
558 def test_index_sort
558 def test_index_sort
559 get :index, :sort => 'tracker,id:desc'
559 get :index, :sort => 'tracker,id:desc'
560 assert_response :success
560 assert_response :success
561
561
562 sort_params = @request.session['issues_index_sort']
562 sort_params = @request.session['issues_index_sort']
563 assert sort_params.is_a?(String)
563 assert sort_params.is_a?(String)
564 assert_equal 'tracker,id:desc', sort_params
564 assert_equal 'tracker,id:desc', sort_params
565
565
566 issues = assigns(:issues)
566 issues = assigns(:issues)
567 assert_not_nil issues
567 assert_not_nil issues
568 assert !issues.empty?
568 assert !issues.empty?
569 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
569 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
570 end
570 end
571
571
572 def test_index_sort_by_field_not_included_in_columns
572 def test_index_sort_by_field_not_included_in_columns
573 Setting.issue_list_default_columns = %w(subject author)
573 Setting.issue_list_default_columns = %w(subject author)
574 get :index, :sort => 'tracker'
574 get :index, :sort => 'tracker'
575 end
575 end
576
576
577 def test_index_sort_by_assigned_to
577 def test_index_sort_by_assigned_to
578 get :index, :sort => 'assigned_to'
578 get :index, :sort => 'assigned_to'
579 assert_response :success
579 assert_response :success
580 assignees = assigns(:issues).collect(&:assigned_to).compact
580 assignees = assigns(:issues).collect(&:assigned_to).compact
581 assert_equal assignees.sort, assignees
581 assert_equal assignees.sort, assignees
582 end
582 end
583
583
584 def test_index_sort_by_assigned_to_desc
584 def test_index_sort_by_assigned_to_desc
585 get :index, :sort => 'assigned_to:desc'
585 get :index, :sort => 'assigned_to:desc'
586 assert_response :success
586 assert_response :success
587 assignees = assigns(:issues).collect(&:assigned_to).compact
587 assignees = assigns(:issues).collect(&:assigned_to).compact
588 assert_equal assignees.sort.reverse, assignees
588 assert_equal assignees.sort.reverse, assignees
589 end
589 end
590
590
591 def test_index_group_by_assigned_to
591 def test_index_group_by_assigned_to
592 get :index, :group_by => 'assigned_to', :sort => 'priority'
592 get :index, :group_by => 'assigned_to', :sort => 'priority'
593 assert_response :success
593 assert_response :success
594 end
594 end
595
595
596 def test_index_sort_by_author
596 def test_index_sort_by_author
597 get :index, :sort => 'author'
597 get :index, :sort => 'author'
598 assert_response :success
598 assert_response :success
599 authors = assigns(:issues).collect(&:author)
599 authors = assigns(:issues).collect(&:author)
600 assert_equal authors.sort, authors
600 assert_equal authors.sort, authors
601 end
601 end
602
602
603 def test_index_sort_by_author_desc
603 def test_index_sort_by_author_desc
604 get :index, :sort => 'author:desc'
604 get :index, :sort => 'author:desc'
605 assert_response :success
605 assert_response :success
606 authors = assigns(:issues).collect(&:author)
606 authors = assigns(:issues).collect(&:author)
607 assert_equal authors.sort.reverse, authors
607 assert_equal authors.sort.reverse, authors
608 end
608 end
609
609
610 def test_index_group_by_author
610 def test_index_group_by_author
611 get :index, :group_by => 'author', :sort => 'priority'
611 get :index, :group_by => 'author', :sort => 'priority'
612 assert_response :success
612 assert_response :success
613 end
613 end
614
614
615 def test_index_sort_by_spent_hours
615 def test_index_sort_by_spent_hours
616 get :index, :sort => 'spent_hours:desc'
616 get :index, :sort => 'spent_hours:desc'
617 assert_response :success
617 assert_response :success
618 hours = assigns(:issues).collect(&:spent_hours)
618 hours = assigns(:issues).collect(&:spent_hours)
619 assert_equal hours.sort.reverse, hours
619 assert_equal hours.sort.reverse, hours
620 end
620 end
621
621
622 def test_index_with_columns
622 def test_index_with_columns
623 columns = ['tracker', 'subject', 'assigned_to']
623 columns = ['tracker', 'subject', 'assigned_to']
624 get :index, :set_filter => 1, :c => columns
624 get :index, :set_filter => 1, :c => columns
625 assert_response :success
625 assert_response :success
626
626
627 # query should use specified columns
627 # query should use specified columns
628 query = assigns(:query)
628 query = assigns(:query)
629 assert_kind_of Query, query
629 assert_kind_of Query, query
630 assert_equal columns, query.column_names.map(&:to_s)
630 assert_equal columns, query.column_names.map(&:to_s)
631
631
632 # columns should be stored in session
632 # columns should be stored in session
633 assert_kind_of Hash, session[:query]
633 assert_kind_of Hash, session[:query]
634 assert_kind_of Array, session[:query][:column_names]
634 assert_kind_of Array, session[:query][:column_names]
635 assert_equal columns, session[:query][:column_names].map(&:to_s)
635 assert_equal columns, session[:query][:column_names].map(&:to_s)
636
636
637 # ensure only these columns are kept in the selected columns list
637 # ensure only these columns are kept in the selected columns list
638 assert_tag :tag => 'select', :attributes => { :id => 'selected_columns' },
638 assert_tag :tag => 'select', :attributes => { :id => 'selected_columns' },
639 :children => { :count => 3 }
639 :children => { :count => 3 }
640 assert_no_tag :tag => 'option', :attributes => { :value => 'project' },
640 assert_no_tag :tag => 'option', :attributes => { :value => 'project' },
641 :parent => { :tag => 'select', :attributes => { :id => "selected_columns" } }
641 :parent => { :tag => 'select', :attributes => { :id => "selected_columns" } }
642 end
642 end
643
643
644 def test_index_without_project_should_implicitly_add_project_column_to_default_columns
644 def test_index_without_project_should_implicitly_add_project_column_to_default_columns
645 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
645 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
646 get :index, :set_filter => 1
646 get :index, :set_filter => 1
647
647
648 # query should use specified columns
648 # query should use specified columns
649 query = assigns(:query)
649 query = assigns(:query)
650 assert_kind_of Query, query
650 assert_kind_of Query, query
651 assert_equal [:project, :tracker, :subject, :assigned_to], query.columns.map(&:name)
651 assert_equal [:project, :tracker, :subject, :assigned_to], query.columns.map(&:name)
652 end
652 end
653
653
654 def test_index_without_project_and_explicit_default_columns_should_not_add_project_column
654 def test_index_without_project_and_explicit_default_columns_should_not_add_project_column
655 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
655 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
656 columns = ['tracker', 'subject', 'assigned_to']
656 columns = ['tracker', 'subject', 'assigned_to']
657 get :index, :set_filter => 1, :c => columns
657 get :index, :set_filter => 1, :c => columns
658
658
659 # query should use specified columns
659 # query should use specified columns
660 query = assigns(:query)
660 query = assigns(:query)
661 assert_kind_of Query, query
661 assert_kind_of Query, query
662 assert_equal columns.map(&:to_sym), query.columns.map(&:name)
662 assert_equal columns.map(&:to_sym), query.columns.map(&:name)
663 end
663 end
664
664
665 def test_index_with_custom_field_column
665 def test_index_with_custom_field_column
666 columns = %w(tracker subject cf_2)
666 columns = %w(tracker subject cf_2)
667 get :index, :set_filter => 1, :c => columns
667 get :index, :set_filter => 1, :c => columns
668 assert_response :success
668 assert_response :success
669
669
670 # query should use specified columns
670 # query should use specified columns
671 query = assigns(:query)
671 query = assigns(:query)
672 assert_kind_of Query, query
672 assert_kind_of Query, query
673 assert_equal columns, query.column_names.map(&:to_s)
673 assert_equal columns, query.column_names.map(&:to_s)
674
674
675 assert_tag :td,
675 assert_tag :td,
676 :attributes => {:class => 'cf_2 string'},
676 :attributes => {:class => 'cf_2 string'},
677 :ancestor => {:tag => 'table', :attributes => {:class => /issues/}}
677 :ancestor => {:tag => 'table', :attributes => {:class => /issues/}}
678 end
678 end
679
679
680 def test_index_with_multi_custom_field_column
680 def test_index_with_multi_custom_field_column
681 field = CustomField.find(1)
681 field = CustomField.find(1)
682 field.update_attribute :multiple, true
682 field.update_attribute :multiple, true
683 issue = Issue.find(1)
683 issue = Issue.find(1)
684 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
684 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
685 issue.save!
685 issue.save!
686
686
687 get :index, :set_filter => 1, :c => %w(tracker subject cf_1)
687 get :index, :set_filter => 1, :c => %w(tracker subject cf_1)
688 assert_response :success
688 assert_response :success
689
689
690 assert_tag :td,
690 assert_tag :td,
691 :attributes => {:class => /cf_1/},
691 :attributes => {:class => /cf_1/},
692 :content => 'MySQL, Oracle'
692 :content => 'MySQL, Oracle'
693 end
693 end
694
694
695 def test_index_with_multi_user_custom_field_column
695 def test_index_with_multi_user_custom_field_column
696 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
696 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
697 :tracker_ids => [1], :is_for_all => true)
697 :tracker_ids => [1], :is_for_all => true)
698 issue = Issue.find(1)
698 issue = Issue.find(1)
699 issue.custom_field_values = {field.id => ['2', '3']}
699 issue.custom_field_values = {field.id => ['2', '3']}
700 issue.save!
700 issue.save!
701
701
702 get :index, :set_filter => 1, :c => ['tracker', 'subject', "cf_#{field.id}"]
702 get :index, :set_filter => 1, :c => ['tracker', 'subject', "cf_#{field.id}"]
703 assert_response :success
703 assert_response :success
704
704
705 assert_tag :td,
705 assert_tag :td,
706 :attributes => {:class => /cf_#{field.id}/},
706 :attributes => {:class => /cf_#{field.id}/},
707 :child => {:tag => 'a', :content => 'John Smith'}
707 :child => {:tag => 'a', :content => 'John Smith'}
708 end
708 end
709
709
710 def test_index_with_date_column
710 def test_index_with_date_column
711 Issue.find(1).update_attribute :start_date, '1987-08-24'
711 Issue.find(1).update_attribute :start_date, '1987-08-24'
712
712
713 with_settings :date_format => '%d/%m/%Y' do
713 with_settings :date_format => '%d/%m/%Y' do
714 get :index, :set_filter => 1, :c => %w(start_date)
714 get :index, :set_filter => 1, :c => %w(start_date)
715 assert_tag 'td', :attributes => {:class => /start_date/}, :content => '24/08/1987'
715 assert_tag 'td', :attributes => {:class => /start_date/}, :content => '24/08/1987'
716 end
716 end
717 end
717 end
718
718
719 def test_index_with_done_ratio
719 def test_index_with_done_ratio
720 Issue.find(1).update_attribute :done_ratio, 40
720 Issue.find(1).update_attribute :done_ratio, 40
721
721
722 get :index, :set_filter => 1, :c => %w(done_ratio)
722 get :index, :set_filter => 1, :c => %w(done_ratio)
723 assert_tag 'td', :attributes => {:class => /done_ratio/},
723 assert_tag 'td', :attributes => {:class => /done_ratio/},
724 :child => {:tag => 'table', :attributes => {:class => 'progress'},
724 :child => {:tag => 'table', :attributes => {:class => 'progress'},
725 :descendant => {:tag => 'td', :attributes => {:class => 'closed', :style => 'width: 40%;'}}
725 :descendant => {:tag => 'td', :attributes => {:class => 'closed', :style => 'width: 40%;'}}
726 }
726 }
727 end
727 end
728
728
729 def test_index_with_spent_hours_column
729 def test_index_with_spent_hours_column
730 get :index, :set_filter => 1, :c => %w(subject spent_hours)
730 get :index, :set_filter => 1, :c => %w(subject spent_hours)
731
731
732 assert_tag 'tr', :attributes => {:id => 'issue-3'},
732 assert_tag 'tr', :attributes => {:id => 'issue-3'},
733 :child => {
733 :child => {
734 :tag => 'td', :attributes => {:class => /spent_hours/}, :content => '1.00'
734 :tag => 'td', :attributes => {:class => /spent_hours/}, :content => '1.00'
735 }
735 }
736 end
736 end
737
737
738 def test_index_should_not_show_spent_hours_column_without_permission
738 def test_index_should_not_show_spent_hours_column_without_permission
739 Role.anonymous.remove_permission! :view_time_entries
739 Role.anonymous.remove_permission! :view_time_entries
740 get :index, :set_filter => 1, :c => %w(subject spent_hours)
740 get :index, :set_filter => 1, :c => %w(subject spent_hours)
741
741
742 assert_no_tag 'td', :attributes => {:class => /spent_hours/}
742 assert_no_tag 'td', :attributes => {:class => /spent_hours/}
743 end
743 end
744
744
745 def test_index_with_fixed_version
745 def test_index_with_fixed_version
746 get :index, :set_filter => 1, :c => %w(fixed_version)
746 get :index, :set_filter => 1, :c => %w(fixed_version)
747 assert_tag 'td', :attributes => {:class => /fixed_version/},
747 assert_tag 'td', :attributes => {:class => /fixed_version/},
748 :child => {:tag => 'a', :content => '1.0', :attributes => {:href => '/versions/2'}}
748 :child => {:tag => 'a', :content => '1.0', :attributes => {:href => '/versions/2'}}
749 end
749 end
750
750
751 def test_index_send_html_if_query_is_invalid
751 def test_index_send_html_if_query_is_invalid
752 get :index, :f => ['start_date'], :op => {:start_date => '='}
752 get :index, :f => ['start_date'], :op => {:start_date => '='}
753 assert_equal 'text/html', @response.content_type
753 assert_equal 'text/html', @response.content_type
754 assert_template 'index'
754 assert_template 'index'
755 end
755 end
756
756
757 def test_index_send_nothing_if_query_is_invalid
757 def test_index_send_nothing_if_query_is_invalid
758 get :index, :f => ['start_date'], :op => {:start_date => '='}, :format => 'csv'
758 get :index, :f => ['start_date'], :op => {:start_date => '='}, :format => 'csv'
759 assert_equal 'text/csv', @response.content_type
759 assert_equal 'text/csv', @response.content_type
760 assert @response.body.blank?
760 assert @response.body.blank?
761 end
761 end
762
762
763 def test_show_by_anonymous
763 def test_show_by_anonymous
764 get :show, :id => 1
764 get :show, :id => 1
765 assert_response :success
765 assert_response :success
766 assert_template 'show'
766 assert_template 'show'
767 assert_not_nil assigns(:issue)
767 assert_not_nil assigns(:issue)
768 assert_equal Issue.find(1), assigns(:issue)
768 assert_equal Issue.find(1), assigns(:issue)
769
769
770 # anonymous role is allowed to add a note
770 # anonymous role is allowed to add a note
771 assert_tag :tag => 'form',
771 assert_tag :tag => 'form',
772 :descendant => { :tag => 'fieldset',
772 :descendant => { :tag => 'fieldset',
773 :child => { :tag => 'legend',
773 :child => { :tag => 'legend',
774 :content => /Notes/ } }
774 :content => /Notes/ } }
775 assert_tag :tag => 'title',
775 assert_tag :tag => 'title',
776 :content => "Bug #1: Can't print recipes - eCookbook - Redmine"
776 :content => "Bug #1: Can't print recipes - eCookbook - Redmine"
777 end
777 end
778
778
779 def test_show_by_manager
779 def test_show_by_manager
780 @request.session[:user_id] = 2
780 @request.session[:user_id] = 2
781 get :show, :id => 1
781 get :show, :id => 1
782 assert_response :success
782 assert_response :success
783
783
784 assert_tag :tag => 'a',
784 assert_tag :tag => 'a',
785 :content => /Quote/
785 :content => /Quote/
786
786
787 assert_tag :tag => 'form',
787 assert_tag :tag => 'form',
788 :descendant => { :tag => 'fieldset',
788 :descendant => { :tag => 'fieldset',
789 :child => { :tag => 'legend',
789 :child => { :tag => 'legend',
790 :content => /Change properties/ } },
790 :content => /Change properties/ } },
791 :descendant => { :tag => 'fieldset',
791 :descendant => { :tag => 'fieldset',
792 :child => { :tag => 'legend',
792 :child => { :tag => 'legend',
793 :content => /Log time/ } },
793 :content => /Log time/ } },
794 :descendant => { :tag => 'fieldset',
794 :descendant => { :tag => 'fieldset',
795 :child => { :tag => 'legend',
795 :child => { :tag => 'legend',
796 :content => /Notes/ } }
796 :content => /Notes/ } }
797 end
797 end
798
798
799 def test_show_should_display_update_form
799 def test_show_should_display_update_form
800 @request.session[:user_id] = 2
800 @request.session[:user_id] = 2
801 get :show, :id => 1
801 get :show, :id => 1
802 assert_response :success
802 assert_response :success
803
803
804 assert_tag 'form', :attributes => {:id => 'issue-form'}
804 assert_tag 'form', :attributes => {:id => 'issue-form'}
805 assert_tag 'input', :attributes => {:name => 'issue[is_private]'}
805 assert_tag 'input', :attributes => {:name => 'issue[is_private]'}
806 assert_tag 'select', :attributes => {:name => 'issue[project_id]'}
806 assert_tag 'select', :attributes => {:name => 'issue[project_id]'}
807 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
807 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
808 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
808 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
809 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
809 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
810 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
810 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
811 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
811 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
812 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
812 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
813 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
813 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
814 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
814 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
815 assert_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
815 assert_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
816 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
816 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
817 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
817 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
818 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
818 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
819 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
819 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
820 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
820 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
821 assert_tag 'textarea', :attributes => {:name => 'notes'}
821 assert_tag 'textarea', :attributes => {:name => 'notes'}
822 end
822 end
823
823
824 def test_show_should_display_update_form_with_minimal_permissions
824 def test_show_should_display_update_form_with_minimal_permissions
825 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
825 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
826 Workflow.delete_all :role_id => 1
826 Workflow.delete_all :role_id => 1
827
827
828 @request.session[:user_id] = 2
828 @request.session[:user_id] = 2
829 get :show, :id => 1
829 get :show, :id => 1
830 assert_response :success
830 assert_response :success
831
831
832 assert_tag 'form', :attributes => {:id => 'issue-form'}
832 assert_tag 'form', :attributes => {:id => 'issue-form'}
833 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
833 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
834 assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
834 assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
835 assert_no_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
835 assert_no_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
836 assert_no_tag 'input', :attributes => {:name => 'issue[subject]'}
836 assert_no_tag 'input', :attributes => {:name => 'issue[subject]'}
837 assert_no_tag 'textarea', :attributes => {:name => 'issue[description]'}
837 assert_no_tag 'textarea', :attributes => {:name => 'issue[description]'}
838 assert_no_tag 'select', :attributes => {:name => 'issue[status_id]'}
838 assert_no_tag 'select', :attributes => {:name => 'issue[status_id]'}
839 assert_no_tag 'select', :attributes => {:name => 'issue[priority_id]'}
839 assert_no_tag 'select', :attributes => {:name => 'issue[priority_id]'}
840 assert_no_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
840 assert_no_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
841 assert_no_tag 'select', :attributes => {:name => 'issue[category_id]'}
841 assert_no_tag 'select', :attributes => {:name => 'issue[category_id]'}
842 assert_no_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
842 assert_no_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
843 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
843 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
844 assert_no_tag 'input', :attributes => {:name => 'issue[start_date]'}
844 assert_no_tag 'input', :attributes => {:name => 'issue[start_date]'}
845 assert_no_tag 'input', :attributes => {:name => 'issue[due_date]'}
845 assert_no_tag 'input', :attributes => {:name => 'issue[due_date]'}
846 assert_no_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
846 assert_no_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
847 assert_no_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
847 assert_no_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
848 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
848 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
849 assert_tag 'textarea', :attributes => {:name => 'notes'}
849 assert_tag 'textarea', :attributes => {:name => 'notes'}
850 end
850 end
851
851
852 def test_show_should_display_update_form_with_workflow_permissions
852 def test_show_should_display_update_form_with_workflow_permissions
853 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
853 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
854
854
855 @request.session[:user_id] = 2
855 @request.session[:user_id] = 2
856 get :show, :id => 1
856 get :show, :id => 1
857 assert_response :success
857 assert_response :success
858
858
859 assert_tag 'form', :attributes => {:id => 'issue-form'}
859 assert_tag 'form', :attributes => {:id => 'issue-form'}
860 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
860 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
861 assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
861 assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
862 assert_no_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
862 assert_no_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
863 assert_no_tag 'input', :attributes => {:name => 'issue[subject]'}
863 assert_no_tag 'input', :attributes => {:name => 'issue[subject]'}
864 assert_no_tag 'textarea', :attributes => {:name => 'issue[description]'}
864 assert_no_tag 'textarea', :attributes => {:name => 'issue[description]'}
865 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
865 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
866 assert_no_tag 'select', :attributes => {:name => 'issue[priority_id]'}
866 assert_no_tag 'select', :attributes => {:name => 'issue[priority_id]'}
867 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
867 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
868 assert_no_tag 'select', :attributes => {:name => 'issue[category_id]'}
868 assert_no_tag 'select', :attributes => {:name => 'issue[category_id]'}
869 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
869 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
870 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
870 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
871 assert_no_tag 'input', :attributes => {:name => 'issue[start_date]'}
871 assert_no_tag 'input', :attributes => {:name => 'issue[start_date]'}
872 assert_no_tag 'input', :attributes => {:name => 'issue[due_date]'}
872 assert_no_tag 'input', :attributes => {:name => 'issue[due_date]'}
873 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
873 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
874 assert_no_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
874 assert_no_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
875 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
875 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
876 assert_tag 'textarea', :attributes => {:name => 'notes'}
876 assert_tag 'textarea', :attributes => {:name => 'notes'}
877 end
877 end
878
878
879 def test_show_should_not_display_update_form_without_permissions
879 def test_show_should_not_display_update_form_without_permissions
880 Role.find(1).update_attribute :permissions, [:view_issues]
880 Role.find(1).update_attribute :permissions, [:view_issues]
881
881
882 @request.session[:user_id] = 2
882 @request.session[:user_id] = 2
883 get :show, :id => 1
883 get :show, :id => 1
884 assert_response :success
884 assert_response :success
885
885
886 assert_no_tag 'form', :attributes => {:id => 'issue-form'}
886 assert_no_tag 'form', :attributes => {:id => 'issue-form'}
887 end
887 end
888
888
889 def test_update_form_should_not_display_inactive_enumerations
889 def test_update_form_should_not_display_inactive_enumerations
890 @request.session[:user_id] = 2
890 @request.session[:user_id] = 2
891 get :show, :id => 1
891 get :show, :id => 1
892 assert_response :success
892 assert_response :success
893
893
894 assert ! IssuePriority.find(15).active?
894 assert ! IssuePriority.find(15).active?
895 assert_no_tag :option, :attributes => {:value => '15'},
895 assert_no_tag :option, :attributes => {:value => '15'},
896 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
896 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
897 end
897 end
898
898
899 def test_update_form_should_allow_attachment_upload
899 def test_update_form_should_allow_attachment_upload
900 @request.session[:user_id] = 2
900 @request.session[:user_id] = 2
901 get :show, :id => 1
901 get :show, :id => 1
902
902
903 assert_tag :tag => 'form',
903 assert_tag :tag => 'form',
904 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
904 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
905 :descendant => {
905 :descendant => {
906 :tag => 'input',
906 :tag => 'input',
907 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
907 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
908 }
908 }
909 end
909 end
910
910
911 def test_show_should_deny_anonymous_access_without_permission
911 def test_show_should_deny_anonymous_access_without_permission
912 Role.anonymous.remove_permission!(:view_issues)
912 Role.anonymous.remove_permission!(:view_issues)
913 get :show, :id => 1
913 get :show, :id => 1
914 assert_response :redirect
914 assert_response :redirect
915 end
915 end
916
916
917 def test_show_should_deny_anonymous_access_to_private_issue
917 def test_show_should_deny_anonymous_access_to_private_issue
918 Issue.update_all(["is_private = ?", true], "id = 1")
918 Issue.update_all(["is_private = ?", true], "id = 1")
919 get :show, :id => 1
919 get :show, :id => 1
920 assert_response :redirect
920 assert_response :redirect
921 end
921 end
922
922
923 def test_show_should_deny_non_member_access_without_permission
923 def test_show_should_deny_non_member_access_without_permission
924 Role.non_member.remove_permission!(:view_issues)
924 Role.non_member.remove_permission!(:view_issues)
925 @request.session[:user_id] = 9
925 @request.session[:user_id] = 9
926 get :show, :id => 1
926 get :show, :id => 1
927 assert_response 403
927 assert_response 403
928 end
928 end
929
929
930 def test_show_should_deny_non_member_access_to_private_issue
930 def test_show_should_deny_non_member_access_to_private_issue
931 Issue.update_all(["is_private = ?", true], "id = 1")
931 Issue.update_all(["is_private = ?", true], "id = 1")
932 @request.session[:user_id] = 9
932 @request.session[:user_id] = 9
933 get :show, :id => 1
933 get :show, :id => 1
934 assert_response 403
934 assert_response 403
935 end
935 end
936
936
937 def test_show_should_deny_member_access_without_permission
937 def test_show_should_deny_member_access_without_permission
938 Role.find(1).remove_permission!(:view_issues)
938 Role.find(1).remove_permission!(:view_issues)
939 @request.session[:user_id] = 2
939 @request.session[:user_id] = 2
940 get :show, :id => 1
940 get :show, :id => 1
941 assert_response 403
941 assert_response 403
942 end
942 end
943
943
944 def test_show_should_deny_member_access_to_private_issue_without_permission
944 def test_show_should_deny_member_access_to_private_issue_without_permission
945 Issue.update_all(["is_private = ?", true], "id = 1")
945 Issue.update_all(["is_private = ?", true], "id = 1")
946 @request.session[:user_id] = 3
946 @request.session[:user_id] = 3
947 get :show, :id => 1
947 get :show, :id => 1
948 assert_response 403
948 assert_response 403
949 end
949 end
950
950
951 def test_show_should_allow_author_access_to_private_issue
951 def test_show_should_allow_author_access_to_private_issue
952 Issue.update_all(["is_private = ?, author_id = 3", true], "id = 1")
952 Issue.update_all(["is_private = ?, author_id = 3", true], "id = 1")
953 @request.session[:user_id] = 3
953 @request.session[:user_id] = 3
954 get :show, :id => 1
954 get :show, :id => 1
955 assert_response :success
955 assert_response :success
956 end
956 end
957
957
958 def test_show_should_allow_assignee_access_to_private_issue
958 def test_show_should_allow_assignee_access_to_private_issue
959 Issue.update_all(["is_private = ?, assigned_to_id = 3", true], "id = 1")
959 Issue.update_all(["is_private = ?, assigned_to_id = 3", true], "id = 1")
960 @request.session[:user_id] = 3
960 @request.session[:user_id] = 3
961 get :show, :id => 1
961 get :show, :id => 1
962 assert_response :success
962 assert_response :success
963 end
963 end
964
964
965 def test_show_should_allow_member_access_to_private_issue_with_permission
965 def test_show_should_allow_member_access_to_private_issue_with_permission
966 Issue.update_all(["is_private = ?", true], "id = 1")
966 Issue.update_all(["is_private = ?", true], "id = 1")
967 User.find(3).roles_for_project(Project.find(1)).first.update_attribute :issues_visibility, 'all'
967 User.find(3).roles_for_project(Project.find(1)).first.update_attribute :issues_visibility, 'all'
968 @request.session[:user_id] = 3
968 @request.session[:user_id] = 3
969 get :show, :id => 1
969 get :show, :id => 1
970 assert_response :success
970 assert_response :success
971 end
971 end
972
972
973 def test_show_should_not_disclose_relations_to_invisible_issues
973 def test_show_should_not_disclose_relations_to_invisible_issues
974 Setting.cross_project_issue_relations = '1'
974 Setting.cross_project_issue_relations = '1'
975 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
975 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
976 # Relation to a private project issue
976 # Relation to a private project issue
977 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
977 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
978
978
979 get :show, :id => 1
979 get :show, :id => 1
980 assert_response :success
980 assert_response :success
981
981
982 assert_tag :div, :attributes => { :id => 'relations' },
982 assert_tag :div, :attributes => { :id => 'relations' },
983 :descendant => { :tag => 'a', :content => /#2$/ }
983 :descendant => { :tag => 'a', :content => /#2$/ }
984 assert_no_tag :div, :attributes => { :id => 'relations' },
984 assert_no_tag :div, :attributes => { :id => 'relations' },
985 :descendant => { :tag => 'a', :content => /#4$/ }
985 :descendant => { :tag => 'a', :content => /#4$/ }
986 end
986 end
987
987
988 def test_show_should_list_subtasks
988 def test_show_should_list_subtasks
989 Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
989 Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
990
990
991 get :show, :id => 1
991 get :show, :id => 1
992 assert_response :success
992 assert_response :success
993 assert_tag 'div', :attributes => {:id => 'issue_tree'},
993 assert_tag 'div', :attributes => {:id => 'issue_tree'},
994 :descendant => {:tag => 'td', :content => /Child Issue/, :attributes => {:class => /subject/}}
994 :descendant => {:tag => 'td', :content => /Child Issue/, :attributes => {:class => /subject/}}
995 end
995 end
996
996
997 def test_show_should_list_parents
997 def test_show_should_list_parents
998 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
998 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
999
999
1000 get :show, :id => issue.id
1000 get :show, :id => issue.id
1001 assert_response :success
1001 assert_response :success
1002 assert_tag 'div', :attributes => {:class => 'subject'},
1002 assert_tag 'div', :attributes => {:class => 'subject'},
1003 :descendant => {:tag => 'h3', :content => 'Child Issue'}
1003 :descendant => {:tag => 'h3', :content => 'Child Issue'}
1004 assert_tag 'div', :attributes => {:class => 'subject'},
1004 assert_tag 'div', :attributes => {:class => 'subject'},
1005 :descendant => {:tag => 'a', :attributes => {:href => '/issues/1'}}
1005 :descendant => {:tag => 'a', :attributes => {:href => '/issues/1'}}
1006 end
1006 end
1007
1007
1008 def test_show_should_not_display_prev_next_links_without_query_in_session
1008 def test_show_should_not_display_prev_next_links_without_query_in_session
1009 get :show, :id => 1
1009 get :show, :id => 1
1010 assert_response :success
1010 assert_response :success
1011 assert_nil assigns(:prev_issue_id)
1011 assert_nil assigns(:prev_issue_id)
1012 assert_nil assigns(:next_issue_id)
1012 assert_nil assigns(:next_issue_id)
1013
1013
1014 assert_no_tag 'div', :attributes => {:class => /next-prev-links/}
1014 assert_no_tag 'div', :attributes => {:class => /next-prev-links/}
1015 end
1015 end
1016
1016
1017 def test_show_should_display_prev_next_links_with_query_in_session
1017 def test_show_should_display_prev_next_links_with_query_in_session
1018 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
1018 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
1019 @request.session['issues_index_sort'] = 'id'
1019 @request.session['issues_index_sort'] = 'id'
1020
1020
1021 with_settings :display_subprojects_issues => '0' do
1021 with_settings :display_subprojects_issues => '0' do
1022 get :show, :id => 3
1022 get :show, :id => 3
1023 end
1023 end
1024
1024
1025 assert_response :success
1025 assert_response :success
1026 # Previous and next issues for all projects
1026 # Previous and next issues for all projects
1027 assert_equal 2, assigns(:prev_issue_id)
1027 assert_equal 2, assigns(:prev_issue_id)
1028 assert_equal 5, assigns(:next_issue_id)
1028 assert_equal 5, assigns(:next_issue_id)
1029
1029
1030 assert_tag 'div', :attributes => {:class => /next-prev-links/}
1030 assert_tag 'div', :attributes => {:class => /next-prev-links/}
1031 assert_tag 'a', :attributes => {:href => '/issues/2'}, :content => /Previous/
1031 assert_tag 'a', :attributes => {:href => '/issues/2'}, :content => /Previous/
1032 assert_tag 'a', :attributes => {:href => '/issues/5'}, :content => /Next/
1032 assert_tag 'a', :attributes => {:href => '/issues/5'}, :content => /Next/
1033
1033
1034 count = Issue.open.visible.count
1034 count = Issue.open.visible.count
1035 assert_tag 'span', :attributes => {:class => 'position'}, :content => "3 of #{count}"
1035 assert_tag 'span', :attributes => {:class => 'position'}, :content => "3 of #{count}"
1036 end
1036 end
1037
1037
1038 def test_show_should_display_prev_next_links_with_saved_query_in_session
1038 def test_show_should_display_prev_next_links_with_saved_query_in_session
1039 query = Query.create!(:name => 'test', :is_public => true, :user_id => 1,
1039 query = Query.create!(:name => 'test', :is_public => true, :user_id => 1,
1040 :filters => {'status_id' => {:values => ['5'], :operator => '='}},
1040 :filters => {'status_id' => {:values => ['5'], :operator => '='}},
1041 :sort_criteria => [['id', 'asc']])
1041 :sort_criteria => [['id', 'asc']])
1042 @request.session[:query] = {:id => query.id, :project_id => nil}
1042 @request.session[:query] = {:id => query.id, :project_id => nil}
1043
1043
1044 get :show, :id => 11
1044 get :show, :id => 11
1045
1045
1046 assert_response :success
1046 assert_response :success
1047 assert_equal query, assigns(:query)
1047 assert_equal query, assigns(:query)
1048 # Previous and next issues for all projects
1048 # Previous and next issues for all projects
1049 assert_equal 8, assigns(:prev_issue_id)
1049 assert_equal 8, assigns(:prev_issue_id)
1050 assert_equal 12, assigns(:next_issue_id)
1050 assert_equal 12, assigns(:next_issue_id)
1051
1051
1052 assert_tag 'a', :attributes => {:href => '/issues/8'}, :content => /Previous/
1052 assert_tag 'a', :attributes => {:href => '/issues/8'}, :content => /Previous/
1053 assert_tag 'a', :attributes => {:href => '/issues/12'}, :content => /Next/
1053 assert_tag 'a', :attributes => {:href => '/issues/12'}, :content => /Next/
1054 end
1054 end
1055
1055
1056 def test_show_should_display_prev_next_links_with_query_and_sort_on_association
1056 def test_show_should_display_prev_next_links_with_query_and_sort_on_association
1057 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
1057 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
1058
1058
1059 %w(project tracker status priority author assigned_to category fixed_version).each do |assoc_sort|
1059 %w(project tracker status priority author assigned_to category fixed_version).each do |assoc_sort|
1060 @request.session['issues_index_sort'] = assoc_sort
1060 @request.session['issues_index_sort'] = assoc_sort
1061
1061
1062 get :show, :id => 3
1062 get :show, :id => 3
1063 assert_response :success, "Wrong response status for #{assoc_sort} sort"
1063 assert_response :success, "Wrong response status for #{assoc_sort} sort"
1064
1064
1065 assert_tag 'div', :attributes => {:class => /next-prev-links/}, :content => /Previous/
1065 assert_tag 'div', :attributes => {:class => /next-prev-links/}, :content => /Previous/
1066 assert_tag 'div', :attributes => {:class => /next-prev-links/}, :content => /Next/
1066 assert_tag 'div', :attributes => {:class => /next-prev-links/}, :content => /Next/
1067 end
1067 end
1068 end
1068 end
1069
1069
1070 def test_show_should_display_prev_next_links_with_project_query_in_session
1070 def test_show_should_display_prev_next_links_with_project_query_in_session
1071 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
1071 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
1072 @request.session['issues_index_sort'] = 'id'
1072 @request.session['issues_index_sort'] = 'id'
1073
1073
1074 with_settings :display_subprojects_issues => '0' do
1074 with_settings :display_subprojects_issues => '0' do
1075 get :show, :id => 3
1075 get :show, :id => 3
1076 end
1076 end
1077
1077
1078 assert_response :success
1078 assert_response :success
1079 # Previous and next issues inside project
1079 # Previous and next issues inside project
1080 assert_equal 2, assigns(:prev_issue_id)
1080 assert_equal 2, assigns(:prev_issue_id)
1081 assert_equal 7, assigns(:next_issue_id)
1081 assert_equal 7, assigns(:next_issue_id)
1082
1082
1083 assert_tag 'a', :attributes => {:href => '/issues/2'}, :content => /Previous/
1083 assert_tag 'a', :attributes => {:href => '/issues/2'}, :content => /Previous/
1084 assert_tag 'a', :attributes => {:href => '/issues/7'}, :content => /Next/
1084 assert_tag 'a', :attributes => {:href => '/issues/7'}, :content => /Next/
1085 end
1085 end
1086
1086
1087 def test_show_should_not_display_prev_link_for_first_issue
1087 def test_show_should_not_display_prev_link_for_first_issue
1088 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
1088 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
1089 @request.session['issues_index_sort'] = 'id'
1089 @request.session['issues_index_sort'] = 'id'
1090
1090
1091 with_settings :display_subprojects_issues => '0' do
1091 with_settings :display_subprojects_issues => '0' do
1092 get :show, :id => 1
1092 get :show, :id => 1
1093 end
1093 end
1094
1094
1095 assert_response :success
1095 assert_response :success
1096 assert_nil assigns(:prev_issue_id)
1096 assert_nil assigns(:prev_issue_id)
1097 assert_equal 2, assigns(:next_issue_id)
1097 assert_equal 2, assigns(:next_issue_id)
1098
1098
1099 assert_no_tag 'a', :content => /Previous/
1099 assert_no_tag 'a', :content => /Previous/
1100 assert_tag 'a', :attributes => {:href => '/issues/2'}, :content => /Next/
1100 assert_tag 'a', :attributes => {:href => '/issues/2'}, :content => /Next/
1101 end
1101 end
1102
1102
1103 def test_show_should_not_display_prev_next_links_for_issue_not_in_query_results
1103 def test_show_should_not_display_prev_next_links_for_issue_not_in_query_results
1104 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'c'}}, :project_id => 1}
1104 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'c'}}, :project_id => 1}
1105 @request.session['issues_index_sort'] = 'id'
1105 @request.session['issues_index_sort'] = 'id'
1106
1106
1107 get :show, :id => 1
1107 get :show, :id => 1
1108
1108
1109 assert_response :success
1109 assert_response :success
1110 assert_nil assigns(:prev_issue_id)
1110 assert_nil assigns(:prev_issue_id)
1111 assert_nil assigns(:next_issue_id)
1111 assert_nil assigns(:next_issue_id)
1112
1112
1113 assert_no_tag 'a', :content => /Previous/
1113 assert_no_tag 'a', :content => /Previous/
1114 assert_no_tag 'a', :content => /Next/
1114 assert_no_tag 'a', :content => /Next/
1115 end
1115 end
1116
1116
1117 def test_show_should_display_visible_changesets_from_other_projects
1117 def test_show_should_display_visible_changesets_from_other_projects
1118 project = Project.find(2)
1118 project = Project.find(2)
1119 issue = project.issues.first
1119 issue = project.issues.first
1120 issue.changeset_ids = [102]
1120 issue.changeset_ids = [102]
1121 issue.save!
1121 issue.save!
1122 project.disable_module! :repository
1122 project.disable_module! :repository
1123
1123
1124 @request.session[:user_id] = 2
1124 @request.session[:user_id] = 2
1125 get :show, :id => issue.id
1125 get :show, :id => issue.id
1126 assert_tag 'a', :attributes => {:href => "/projects/ecookbook/repository/revisions/3"}
1126 assert_tag 'a', :attributes => {:href => "/projects/ecookbook/repository/revisions/3"}
1127 end
1127 end
1128
1128
1129 def test_show_with_multi_custom_field
1129 def test_show_with_multi_custom_field
1130 field = CustomField.find(1)
1130 field = CustomField.find(1)
1131 field.update_attribute :multiple, true
1131 field.update_attribute :multiple, true
1132 issue = Issue.find(1)
1132 issue = Issue.find(1)
1133 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
1133 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
1134 issue.save!
1134 issue.save!
1135
1135
1136 get :show, :id => 1
1136 get :show, :id => 1
1137 assert_response :success
1137 assert_response :success
1138
1138
1139 assert_tag :td, :content => 'MySQL, Oracle'
1139 assert_tag :td, :content => 'MySQL, Oracle'
1140 end
1140 end
1141
1141
1142 def test_show_with_multi_user_custom_field
1142 def test_show_with_multi_user_custom_field
1143 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1143 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1144 :tracker_ids => [1], :is_for_all => true)
1144 :tracker_ids => [1], :is_for_all => true)
1145 issue = Issue.find(1)
1145 issue = Issue.find(1)
1146 issue.custom_field_values = {field.id => ['2', '3']}
1146 issue.custom_field_values = {field.id => ['2', '3']}
1147 issue.save!
1147 issue.save!
1148
1148
1149 get :show, :id => 1
1149 get :show, :id => 1
1150 assert_response :success
1150 assert_response :success
1151
1151
1152 # TODO: should display links
1152 # TODO: should display links
1153 assert_tag :td, :content => 'Dave Lopper, John Smith'
1153 assert_tag :td, :content => 'Dave Lopper, John Smith'
1154 end
1154 end
1155
1155
1156 def test_show_atom
1156 def test_show_atom
1157 get :show, :id => 2, :format => 'atom'
1157 get :show, :id => 2, :format => 'atom'
1158 assert_response :success
1158 assert_response :success
1159 assert_template 'journals/index'
1159 assert_template 'journals/index'
1160 # Inline image
1160 # Inline image
1161 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
1161 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
1162 end
1162 end
1163
1163
1164 def test_show_export_to_pdf
1164 def test_show_export_to_pdf
1165 get :show, :id => 3, :format => 'pdf'
1165 get :show, :id => 3, :format => 'pdf'
1166 assert_response :success
1166 assert_response :success
1167 assert_equal 'application/pdf', @response.content_type
1167 assert_equal 'application/pdf', @response.content_type
1168 assert @response.body.starts_with?('%PDF')
1168 assert @response.body.starts_with?('%PDF')
1169 assert_not_nil assigns(:issue)
1169 assert_not_nil assigns(:issue)
1170 end
1170 end
1171
1171
1172 def test_get_new
1172 def test_get_new
1173 @request.session[:user_id] = 2
1173 @request.session[:user_id] = 2
1174 get :new, :project_id => 1, :tracker_id => 1
1174 get :new, :project_id => 1, :tracker_id => 1
1175 assert_response :success
1175 assert_response :success
1176 assert_template 'new'
1176 assert_template 'new'
1177
1177
1178 assert_tag 'input', :attributes => {:name => 'issue[is_private]'}
1178 assert_tag 'input', :attributes => {:name => 'issue[is_private]'}
1179 assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
1179 assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
1180 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
1180 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
1181 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
1181 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
1182 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
1182 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
1183 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
1183 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
1184 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
1184 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
1185 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
1185 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
1186 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
1186 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
1187 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
1187 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
1188 assert_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
1188 assert_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
1189 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
1189 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
1190 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
1190 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
1191 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
1191 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
1192 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]', :value => 'Default string' }
1192 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]', :value => 'Default string' }
1193 assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
1193 assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
1194
1194
1195 # Be sure we don't display inactive IssuePriorities
1195 # Be sure we don't display inactive IssuePriorities
1196 assert ! IssuePriority.find(15).active?
1196 assert ! IssuePriority.find(15).active?
1197 assert_no_tag :option, :attributes => {:value => '15'},
1197 assert_no_tag :option, :attributes => {:value => '15'},
1198 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1198 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1199 end
1199 end
1200
1200
1201 def test_get_new_with_minimal_permissions
1201 def test_get_new_with_minimal_permissions
1202 Role.find(1).update_attribute :permissions, [:add_issues]
1202 Role.find(1).update_attribute :permissions, [:add_issues]
1203 Workflow.delete_all :role_id => 1
1203 Workflow.delete_all :role_id => 1
1204
1204
1205 @request.session[:user_id] = 2
1205 @request.session[:user_id] = 2
1206 get :new, :project_id => 1, :tracker_id => 1
1206 get :new, :project_id => 1, :tracker_id => 1
1207 assert_response :success
1207 assert_response :success
1208 assert_template 'new'
1208 assert_template 'new'
1209
1209
1210 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
1210 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
1211 assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
1211 assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
1212 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
1212 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
1213 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
1213 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
1214 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
1214 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
1215 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
1215 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
1216 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
1216 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
1217 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
1217 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
1218 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
1218 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
1219 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
1219 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
1220 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
1220 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
1221 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
1221 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
1222 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
1222 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
1223 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
1223 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
1224 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]', :value => 'Default string' }
1224 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]', :value => 'Default string' }
1225 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
1225 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
1226 end
1226 end
1227
1227
1228 def test_get_new_with_list_custom_field
1228 def test_get_new_with_list_custom_field
1229 @request.session[:user_id] = 2
1229 @request.session[:user_id] = 2
1230 get :new, :project_id => 1, :tracker_id => 1
1230 get :new, :project_id => 1, :tracker_id => 1
1231 assert_response :success
1231 assert_response :success
1232 assert_template 'new'
1232 assert_template 'new'
1233
1233
1234 assert_tag 'select',
1234 assert_tag 'select',
1235 :attributes => {:name => 'issue[custom_field_values][1]'},
1235 :attributes => {:name => 'issue[custom_field_values][1]'},
1236 :children => {:count => 4},
1236 :children => {:count => 4},
1237 :child => {:tag => 'option', :attributes => {:value => 'MySQL'}, :content => 'MySQL'}
1237 :child => {:tag => 'option', :attributes => {:value => 'MySQL'}, :content => 'MySQL'}
1238 end
1238 end
1239
1239
1240 def test_get_new_with_multi_custom_field
1240 def test_get_new_with_multi_custom_field
1241 field = IssueCustomField.find(1)
1241 field = IssueCustomField.find(1)
1242 field.update_attribute :multiple, true
1242 field.update_attribute :multiple, true
1243
1243
1244 @request.session[:user_id] = 2
1244 @request.session[:user_id] = 2
1245 get :new, :project_id => 1, :tracker_id => 1
1245 get :new, :project_id => 1, :tracker_id => 1
1246 assert_response :success
1246 assert_response :success
1247 assert_template 'new'
1247 assert_template 'new'
1248
1248
1249 assert_tag 'select',
1249 assert_tag 'select',
1250 :attributes => {:name => 'issue[custom_field_values][1][]', :multiple => 'multiple'},
1250 :attributes => {:name => 'issue[custom_field_values][1][]', :multiple => 'multiple'},
1251 :children => {:count => 3},
1251 :children => {:count => 3},
1252 :child => {:tag => 'option', :attributes => {:value => 'MySQL'}, :content => 'MySQL'}
1252 :child => {:tag => 'option', :attributes => {:value => 'MySQL'}, :content => 'MySQL'}
1253 assert_tag 'input',
1253 assert_tag 'input',
1254 :attributes => {:name => 'issue[custom_field_values][1][]', :value => ''}
1254 :attributes => {:name => 'issue[custom_field_values][1][]', :value => ''}
1255 end
1255 end
1256
1256
1257 def test_get_new_with_multi_user_custom_field
1257 def test_get_new_with_multi_user_custom_field
1258 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1258 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1259 :tracker_ids => [1], :is_for_all => true)
1259 :tracker_ids => [1], :is_for_all => true)
1260
1260
1261 @request.session[:user_id] = 2
1261 @request.session[:user_id] = 2
1262 get :new, :project_id => 1, :tracker_id => 1
1262 get :new, :project_id => 1, :tracker_id => 1
1263 assert_response :success
1263 assert_response :success
1264 assert_template 'new'
1264 assert_template 'new'
1265
1265
1266 assert_tag 'select',
1266 assert_tag 'select',
1267 :attributes => {:name => "issue[custom_field_values][#{field.id}][]", :multiple => 'multiple'},
1267 :attributes => {:name => "issue[custom_field_values][#{field.id}][]", :multiple => 'multiple'},
1268 :children => {:count => Project.find(1).users.count},
1268 :children => {:count => Project.find(1).users.count},
1269 :child => {:tag => 'option', :attributes => {:value => '2'}, :content => 'John Smith'}
1269 :child => {:tag => 'option', :attributes => {:value => '2'}, :content => 'John Smith'}
1270 assert_tag 'input',
1270 assert_tag 'input',
1271 :attributes => {:name => "issue[custom_field_values][#{field.id}][]", :value => ''}
1271 :attributes => {:name => "issue[custom_field_values][#{field.id}][]", :value => ''}
1272 end
1272 end
1273
1273
1274 def test_get_new_without_default_start_date_is_creation_date
1274 def test_get_new_without_default_start_date_is_creation_date
1275 Setting.default_issue_start_date_to_creation_date = 0
1275 Setting.default_issue_start_date_to_creation_date = 0
1276
1276
1277 @request.session[:user_id] = 2
1277 @request.session[:user_id] = 2
1278 get :new, :project_id => 1, :tracker_id => 1
1278 get :new, :project_id => 1, :tracker_id => 1
1279 assert_response :success
1279 assert_response :success
1280 assert_template 'new'
1280 assert_template 'new'
1281
1281
1282 assert_tag :tag => 'input', :attributes => { :name => 'issue[start_date]',
1282 assert_tag :tag => 'input', :attributes => { :name => 'issue[start_date]',
1283 :value => nil }
1283 :value => nil }
1284 end
1284 end
1285
1285
1286 def test_get_new_with_default_start_date_is_creation_date
1286 def test_get_new_with_default_start_date_is_creation_date
1287 Setting.default_issue_start_date_to_creation_date = 1
1287 Setting.default_issue_start_date_to_creation_date = 1
1288
1288
1289 @request.session[:user_id] = 2
1289 @request.session[:user_id] = 2
1290 get :new, :project_id => 1, :tracker_id => 1
1290 get :new, :project_id => 1, :tracker_id => 1
1291 assert_response :success
1291 assert_response :success
1292 assert_template 'new'
1292 assert_template 'new'
1293
1293
1294 assert_tag :tag => 'input', :attributes => { :name => 'issue[start_date]',
1294 assert_tag :tag => 'input', :attributes => { :name => 'issue[start_date]',
1295 :value => Date.today.to_s }
1295 :value => Date.today.to_s }
1296 end
1296 end
1297
1297
1298 def test_get_new_form_should_allow_attachment_upload
1298 def test_get_new_form_should_allow_attachment_upload
1299 @request.session[:user_id] = 2
1299 @request.session[:user_id] = 2
1300 get :new, :project_id => 1, :tracker_id => 1
1300 get :new, :project_id => 1, :tracker_id => 1
1301
1301
1302 assert_tag :tag => 'form',
1302 assert_tag :tag => 'form',
1303 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
1303 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
1304 :descendant => {
1304 :descendant => {
1305 :tag => 'input',
1305 :tag => 'input',
1306 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
1306 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
1307 }
1307 }
1308 end
1308 end
1309
1309
1310 def test_get_new_should_prefill_the_form_from_params
1310 def test_get_new_should_prefill_the_form_from_params
1311 @request.session[:user_id] = 2
1311 @request.session[:user_id] = 2
1312 get :new, :project_id => 1,
1312 get :new, :project_id => 1,
1313 :issue => {:tracker_id => 3, :description => 'Prefilled', :custom_field_values => {'2' => 'Custom field value'}}
1313 :issue => {:tracker_id => 3, :description => 'Prefilled', :custom_field_values => {'2' => 'Custom field value'}}
1314
1314
1315 issue = assigns(:issue)
1315 issue = assigns(:issue)
1316 assert_equal 3, issue.tracker_id
1316 assert_equal 3, issue.tracker_id
1317 assert_equal 'Prefilled', issue.description
1317 assert_equal 'Prefilled', issue.description
1318 assert_equal 'Custom field value', issue.custom_field_value(2)
1318 assert_equal 'Custom field value', issue.custom_field_value(2)
1319
1319
1320 assert_tag 'select',
1320 assert_tag 'select',
1321 :attributes => {:name => 'issue[tracker_id]'},
1321 :attributes => {:name => 'issue[tracker_id]'},
1322 :child => {:tag => 'option', :attributes => {:value => '3', :selected => 'selected'}}
1322 :child => {:tag => 'option', :attributes => {:value => '3', :selected => 'selected'}}
1323 assert_tag 'textarea',
1323 assert_tag 'textarea',
1324 :attributes => {:name => 'issue[description]'}, :content => 'Prefilled'
1324 :attributes => {:name => 'issue[description]'}, :content => 'Prefilled'
1325 assert_tag 'input',
1325 assert_tag 'input',
1326 :attributes => {:name => 'issue[custom_field_values][2]', :value => 'Custom field value'}
1326 :attributes => {:name => 'issue[custom_field_values][2]', :value => 'Custom field value'}
1327 end
1327 end
1328
1328
1329 def test_get_new_without_tracker_id
1329 def test_get_new_without_tracker_id
1330 @request.session[:user_id] = 2
1330 @request.session[:user_id] = 2
1331 get :new, :project_id => 1
1331 get :new, :project_id => 1
1332 assert_response :success
1332 assert_response :success
1333 assert_template 'new'
1333 assert_template 'new'
1334
1334
1335 issue = assigns(:issue)
1335 issue = assigns(:issue)
1336 assert_not_nil issue
1336 assert_not_nil issue
1337 assert_equal Project.find(1).trackers.first, issue.tracker
1337 assert_equal Project.find(1).trackers.first, issue.tracker
1338 end
1338 end
1339
1339
1340 def test_get_new_with_no_default_status_should_display_an_error
1340 def test_get_new_with_no_default_status_should_display_an_error
1341 @request.session[:user_id] = 2
1341 @request.session[:user_id] = 2
1342 IssueStatus.delete_all
1342 IssueStatus.delete_all
1343
1343
1344 get :new, :project_id => 1
1344 get :new, :project_id => 1
1345 assert_response 500
1345 assert_response 500
1346 assert_error_tag :content => /No default issue/
1346 assert_error_tag :content => /No default issue/
1347 end
1347 end
1348
1348
1349 def test_get_new_with_no_tracker_should_display_an_error
1349 def test_get_new_with_no_tracker_should_display_an_error
1350 @request.session[:user_id] = 2
1350 @request.session[:user_id] = 2
1351 Tracker.delete_all
1351 Tracker.delete_all
1352
1352
1353 get :new, :project_id => 1
1353 get :new, :project_id => 1
1354 assert_response 500
1354 assert_response 500
1355 assert_error_tag :content => /No tracker/
1355 assert_error_tag :content => /No tracker/
1356 end
1356 end
1357
1357
1358 def test_update_new_form
1358 def test_update_new_form
1359 @request.session[:user_id] = 2
1359 @request.session[:user_id] = 2
1360 xhr :post, :new, :project_id => 1,
1360 xhr :post, :new, :project_id => 1,
1361 :issue => {:tracker_id => 2,
1361 :issue => {:tracker_id => 2,
1362 :subject => 'This is the test_new issue',
1362 :subject => 'This is the test_new issue',
1363 :description => 'This is the description',
1363 :description => 'This is the description',
1364 :priority_id => 5}
1364 :priority_id => 5}
1365 assert_response :success
1365 assert_response :success
1366 assert_template 'attributes'
1366 assert_template 'attributes'
1367
1367
1368 issue = assigns(:issue)
1368 issue = assigns(:issue)
1369 assert_kind_of Issue, issue
1369 assert_kind_of Issue, issue
1370 assert_equal 1, issue.project_id
1370 assert_equal 1, issue.project_id
1371 assert_equal 2, issue.tracker_id
1371 assert_equal 2, issue.tracker_id
1372 assert_equal 'This is the test_new issue', issue.subject
1372 assert_equal 'This is the test_new issue', issue.subject
1373 end
1373 end
1374
1374
1375 def test_update_new_form_should_propose_transitions_based_on_initial_status
1375 def test_update_new_form_should_propose_transitions_based_on_initial_status
1376 @request.session[:user_id] = 2
1376 @request.session[:user_id] = 2
1377 Workflow.delete_all
1377 Workflow.delete_all
1378 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2)
1378 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2)
1379 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5)
1379 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5)
1380 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4)
1380 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4)
1381
1381
1382 xhr :post, :new, :project_id => 1,
1382 xhr :post, :new, :project_id => 1,
1383 :issue => {:tracker_id => 1,
1383 :issue => {:tracker_id => 1,
1384 :status_id => 5,
1384 :status_id => 5,
1385 :subject => 'This is an issue'}
1385 :subject => 'This is an issue'}
1386
1386
1387 assert_equal 5, assigns(:issue).status_id
1387 assert_equal 5, assigns(:issue).status_id
1388 assert_equal [1,2,5], assigns(:allowed_statuses).map(&:id).sort
1388 assert_equal [1,2,5], assigns(:allowed_statuses).map(&:id).sort
1389 end
1389 end
1390
1390
1391 def test_post_create
1391 def test_post_create
1392 @request.session[:user_id] = 2
1392 @request.session[:user_id] = 2
1393 assert_difference 'Issue.count' do
1393 assert_difference 'Issue.count' do
1394 post :create, :project_id => 1,
1394 post :create, :project_id => 1,
1395 :issue => {:tracker_id => 3,
1395 :issue => {:tracker_id => 3,
1396 :status_id => 2,
1396 :status_id => 2,
1397 :subject => 'This is the test_new issue',
1397 :subject => 'This is the test_new issue',
1398 :description => 'This is the description',
1398 :description => 'This is the description',
1399 :priority_id => 5,
1399 :priority_id => 5,
1400 :start_date => '2010-11-07',
1400 :start_date => '2010-11-07',
1401 :estimated_hours => '',
1401 :estimated_hours => '',
1402 :custom_field_values => {'2' => 'Value for field 2'}}
1402 :custom_field_values => {'2' => 'Value for field 2'}}
1403 end
1403 end
1404 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1404 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1405
1405
1406 issue = Issue.find_by_subject('This is the test_new issue')
1406 issue = Issue.find_by_subject('This is the test_new issue')
1407 assert_not_nil issue
1407 assert_not_nil issue
1408 assert_equal 2, issue.author_id
1408 assert_equal 2, issue.author_id
1409 assert_equal 3, issue.tracker_id
1409 assert_equal 3, issue.tracker_id
1410 assert_equal 2, issue.status_id
1410 assert_equal 2, issue.status_id
1411 assert_equal Date.parse('2010-11-07'), issue.start_date
1411 assert_equal Date.parse('2010-11-07'), issue.start_date
1412 assert_nil issue.estimated_hours
1412 assert_nil issue.estimated_hours
1413 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
1413 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
1414 assert_not_nil v
1414 assert_not_nil v
1415 assert_equal 'Value for field 2', v.value
1415 assert_equal 'Value for field 2', v.value
1416 end
1416 end
1417
1417
1418 def test_post_new_with_group_assignment
1418 def test_post_new_with_group_assignment
1419 group = Group.find(11)
1419 group = Group.find(11)
1420 project = Project.find(1)
1420 project = Project.find(1)
1421 project.members << Member.new(:principal => group, :roles => [Role.givable.first])
1421 project.members << Member.new(:principal => group, :roles => [Role.givable.first])
1422
1422
1423 with_settings :issue_group_assignment => '1' do
1423 with_settings :issue_group_assignment => '1' do
1424 @request.session[:user_id] = 2
1424 @request.session[:user_id] = 2
1425 assert_difference 'Issue.count' do
1425 assert_difference 'Issue.count' do
1426 post :create, :project_id => project.id,
1426 post :create, :project_id => project.id,
1427 :issue => {:tracker_id => 3,
1427 :issue => {:tracker_id => 3,
1428 :status_id => 1,
1428 :status_id => 1,
1429 :subject => 'This is the test_new_with_group_assignment issue',
1429 :subject => 'This is the test_new_with_group_assignment issue',
1430 :assigned_to_id => group.id}
1430 :assigned_to_id => group.id}
1431 end
1431 end
1432 end
1432 end
1433 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1433 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1434
1434
1435 issue = Issue.find_by_subject('This is the test_new_with_group_assignment issue')
1435 issue = Issue.find_by_subject('This is the test_new_with_group_assignment issue')
1436 assert_not_nil issue
1436 assert_not_nil issue
1437 assert_equal group, issue.assigned_to
1437 assert_equal group, issue.assigned_to
1438 end
1438 end
1439
1439
1440 def test_post_create_without_start_date_and_default_start_date_is_not_creation_date
1440 def test_post_create_without_start_date_and_default_start_date_is_not_creation_date
1441 Setting.default_issue_start_date_to_creation_date = 0
1441 Setting.default_issue_start_date_to_creation_date = 0
1442
1442
1443 @request.session[:user_id] = 2
1443 @request.session[:user_id] = 2
1444 assert_difference 'Issue.count' do
1444 assert_difference 'Issue.count' do
1445 post :create, :project_id => 1,
1445 post :create, :project_id => 1,
1446 :issue => {:tracker_id => 3,
1446 :issue => {:tracker_id => 3,
1447 :status_id => 2,
1447 :status_id => 2,
1448 :subject => 'This is the test_new issue',
1448 :subject => 'This is the test_new issue',
1449 :description => 'This is the description',
1449 :description => 'This is the description',
1450 :priority_id => 5,
1450 :priority_id => 5,
1451 :estimated_hours => '',
1451 :estimated_hours => '',
1452 :custom_field_values => {'2' => 'Value for field 2'}}
1452 :custom_field_values => {'2' => 'Value for field 2'}}
1453 end
1453 end
1454 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1454 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1455
1455
1456 issue = Issue.find_by_subject('This is the test_new issue')
1456 issue = Issue.find_by_subject('This is the test_new issue')
1457 assert_not_nil issue
1457 assert_not_nil issue
1458 assert_nil issue.start_date
1458 assert_nil issue.start_date
1459 end
1459 end
1460
1460
1461 def test_post_create_without_start_date_and_default_start_date_is_creation_date
1461 def test_post_create_without_start_date_and_default_start_date_is_creation_date
1462 Setting.default_issue_start_date_to_creation_date = 1
1462 Setting.default_issue_start_date_to_creation_date = 1
1463
1463
1464 @request.session[:user_id] = 2
1464 @request.session[:user_id] = 2
1465 assert_difference 'Issue.count' do
1465 assert_difference 'Issue.count' do
1466 post :create, :project_id => 1,
1466 post :create, :project_id => 1,
1467 :issue => {:tracker_id => 3,
1467 :issue => {:tracker_id => 3,
1468 :status_id => 2,
1468 :status_id => 2,
1469 :subject => 'This is the test_new issue',
1469 :subject => 'This is the test_new issue',
1470 :description => 'This is the description',
1470 :description => 'This is the description',
1471 :priority_id => 5,
1471 :priority_id => 5,
1472 :estimated_hours => '',
1472 :estimated_hours => '',
1473 :custom_field_values => {'2' => 'Value for field 2'}}
1473 :custom_field_values => {'2' => 'Value for field 2'}}
1474 end
1474 end
1475 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1475 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1476
1476
1477 issue = Issue.find_by_subject('This is the test_new issue')
1477 issue = Issue.find_by_subject('This is the test_new issue')
1478 assert_not_nil issue
1478 assert_not_nil issue
1479 assert_equal Date.today, issue.start_date
1479 assert_equal Date.today, issue.start_date
1480 end
1480 end
1481
1481
1482 def test_post_create_and_continue
1482 def test_post_create_and_continue
1483 @request.session[:user_id] = 2
1483 @request.session[:user_id] = 2
1484 assert_difference 'Issue.count' do
1484 assert_difference 'Issue.count' do
1485 post :create, :project_id => 1,
1485 post :create, :project_id => 1,
1486 :issue => {:tracker_id => 3, :subject => 'This is first issue', :priority_id => 5},
1486 :issue => {:tracker_id => 3, :subject => 'This is first issue', :priority_id => 5},
1487 :continue => ''
1487 :continue => ''
1488 end
1488 end
1489
1489
1490 issue = Issue.first(:order => 'id DESC')
1490 issue = Issue.first(:order => 'id DESC')
1491 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook', :issue => {:tracker_id => 3}
1491 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook', :issue => {:tracker_id => 3}
1492 assert_not_nil flash[:notice], "flash was not set"
1492 assert_not_nil flash[:notice], "flash was not set"
1493 assert flash[:notice].include?("<a href='/issues/#{issue.id}'>##{issue.id}</a>"), "issue link not found in flash: #{flash[:notice]}"
1493 assert flash[:notice].include?("<a href='/issues/#{issue.id}'>##{issue.id}</a>"), "issue link not found in flash: #{flash[:notice]}"
1494 end
1494 end
1495
1495
1496 def test_post_create_without_custom_fields_param
1496 def test_post_create_without_custom_fields_param
1497 @request.session[:user_id] = 2
1497 @request.session[:user_id] = 2
1498 assert_difference 'Issue.count' do
1498 assert_difference 'Issue.count' do
1499 post :create, :project_id => 1,
1499 post :create, :project_id => 1,
1500 :issue => {:tracker_id => 1,
1500 :issue => {:tracker_id => 1,
1501 :subject => 'This is the test_new issue',
1501 :subject => 'This is the test_new issue',
1502 :description => 'This is the description',
1502 :description => 'This is the description',
1503 :priority_id => 5}
1503 :priority_id => 5}
1504 end
1504 end
1505 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1505 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1506 end
1506 end
1507
1507
1508 def test_post_create_with_multi_custom_field
1508 def test_post_create_with_multi_custom_field
1509 field = IssueCustomField.find_by_name('Database')
1509 field = IssueCustomField.find_by_name('Database')
1510 field.update_attribute(:multiple, true)
1510 field.update_attribute(:multiple, true)
1511
1511
1512 @request.session[:user_id] = 2
1512 @request.session[:user_id] = 2
1513 assert_difference 'Issue.count' do
1513 assert_difference 'Issue.count' do
1514 post :create, :project_id => 1,
1514 post :create, :project_id => 1,
1515 :issue => {:tracker_id => 1,
1515 :issue => {:tracker_id => 1,
1516 :subject => 'This is the test_new issue',
1516 :subject => 'This is the test_new issue',
1517 :description => 'This is the description',
1517 :description => 'This is the description',
1518 :priority_id => 5,
1518 :priority_id => 5,
1519 :custom_field_values => {'1' => ['', 'MySQL', 'Oracle']}}
1519 :custom_field_values => {'1' => ['', 'MySQL', 'Oracle']}}
1520 end
1520 end
1521 assert_response 302
1521 assert_response 302
1522 issue = Issue.first(:order => 'id DESC')
1522 issue = Issue.first(:order => 'id DESC')
1523 assert_equal ['MySQL', 'Oracle'], issue.custom_field_value(1).sort
1523 assert_equal ['MySQL', 'Oracle'], issue.custom_field_value(1).sort
1524 end
1524 end
1525
1525
1526 def test_post_create_with_empty_multi_custom_field
1526 def test_post_create_with_empty_multi_custom_field
1527 field = IssueCustomField.find_by_name('Database')
1527 field = IssueCustomField.find_by_name('Database')
1528 field.update_attribute(:multiple, true)
1528 field.update_attribute(:multiple, true)
1529
1529
1530 @request.session[:user_id] = 2
1530 @request.session[:user_id] = 2
1531 assert_difference 'Issue.count' do
1531 assert_difference 'Issue.count' do
1532 post :create, :project_id => 1,
1532 post :create, :project_id => 1,
1533 :issue => {:tracker_id => 1,
1533 :issue => {:tracker_id => 1,
1534 :subject => 'This is the test_new issue',
1534 :subject => 'This is the test_new issue',
1535 :description => 'This is the description',
1535 :description => 'This is the description',
1536 :priority_id => 5,
1536 :priority_id => 5,
1537 :custom_field_values => {'1' => ['']}}
1537 :custom_field_values => {'1' => ['']}}
1538 end
1538 end
1539 assert_response 302
1539 assert_response 302
1540 issue = Issue.first(:order => 'id DESC')
1540 issue = Issue.first(:order => 'id DESC')
1541 assert_equal [''], issue.custom_field_value(1).sort
1541 assert_equal [''], issue.custom_field_value(1).sort
1542 end
1542 end
1543
1543
1544 def test_post_create_with_multi_user_custom_field
1544 def test_post_create_with_multi_user_custom_field
1545 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1545 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1546 :tracker_ids => [1], :is_for_all => true)
1546 :tracker_ids => [1], :is_for_all => true)
1547
1547
1548 @request.session[:user_id] = 2
1548 @request.session[:user_id] = 2
1549 assert_difference 'Issue.count' do
1549 assert_difference 'Issue.count' do
1550 post :create, :project_id => 1,
1550 post :create, :project_id => 1,
1551 :issue => {:tracker_id => 1,
1551 :issue => {:tracker_id => 1,
1552 :subject => 'This is the test_new issue',
1552 :subject => 'This is the test_new issue',
1553 :description => 'This is the description',
1553 :description => 'This is the description',
1554 :priority_id => 5,
1554 :priority_id => 5,
1555 :custom_field_values => {field.id.to_s => ['', '2', '3']}}
1555 :custom_field_values => {field.id.to_s => ['', '2', '3']}}
1556 end
1556 end
1557 assert_response 302
1557 assert_response 302
1558 issue = Issue.first(:order => 'id DESC')
1558 issue = Issue.first(:order => 'id DESC')
1559 assert_equal ['2', '3'], issue.custom_field_value(field).sort
1559 assert_equal ['2', '3'], issue.custom_field_value(field).sort
1560 end
1560 end
1561
1561
1562 def test_post_create_with_required_custom_field_and_without_custom_fields_param
1562 def test_post_create_with_required_custom_field_and_without_custom_fields_param
1563 field = IssueCustomField.find_by_name('Database')
1563 field = IssueCustomField.find_by_name('Database')
1564 field.update_attribute(:is_required, true)
1564 field.update_attribute(:is_required, true)
1565
1565
1566 @request.session[:user_id] = 2
1566 @request.session[:user_id] = 2
1567 assert_no_difference 'Issue.count' do
1567 assert_no_difference 'Issue.count' do
1568 post :create, :project_id => 1,
1568 post :create, :project_id => 1,
1569 :issue => {:tracker_id => 1,
1569 :issue => {:tracker_id => 1,
1570 :subject => 'This is the test_new issue',
1570 :subject => 'This is the test_new issue',
1571 :description => 'This is the description',
1571 :description => 'This is the description',
1572 :priority_id => 5}
1572 :priority_id => 5}
1573 end
1573 end
1574 assert_response :success
1574 assert_response :success
1575 assert_template 'new'
1575 assert_template 'new'
1576 issue = assigns(:issue)
1576 issue = assigns(:issue)
1577 assert_not_nil issue
1577 assert_not_nil issue
1578 assert_error_tag :content => /Database can't be blank/
1578 assert_error_tag :content => /Database can't be blank/
1579 end
1579 end
1580
1580
1581 def test_post_create_with_watchers
1581 def test_post_create_with_watchers
1582 @request.session[:user_id] = 2
1582 @request.session[:user_id] = 2
1583 ActionMailer::Base.deliveries.clear
1583 ActionMailer::Base.deliveries.clear
1584
1584
1585 assert_difference 'Watcher.count', 2 do
1585 assert_difference 'Watcher.count', 2 do
1586 post :create, :project_id => 1,
1586 post :create, :project_id => 1,
1587 :issue => {:tracker_id => 1,
1587 :issue => {:tracker_id => 1,
1588 :subject => 'This is a new issue with watchers',
1588 :subject => 'This is a new issue with watchers',
1589 :description => 'This is the description',
1589 :description => 'This is the description',
1590 :priority_id => 5,
1590 :priority_id => 5,
1591 :watcher_user_ids => ['2', '3']}
1591 :watcher_user_ids => ['2', '3']}
1592 end
1592 end
1593 issue = Issue.find_by_subject('This is a new issue with watchers')
1593 issue = Issue.find_by_subject('This is a new issue with watchers')
1594 assert_not_nil issue
1594 assert_not_nil issue
1595 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
1595 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
1596
1596
1597 # Watchers added
1597 # Watchers added
1598 assert_equal [2, 3], issue.watcher_user_ids.sort
1598 assert_equal [2, 3], issue.watcher_user_ids.sort
1599 assert issue.watched_by?(User.find(3))
1599 assert issue.watched_by?(User.find(3))
1600 # Watchers notified
1600 # Watchers notified
1601 mail = ActionMailer::Base.deliveries.last
1601 mail = ActionMailer::Base.deliveries.last
1602 assert_not_nil mail
1602 assert_not_nil mail
1603 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
1603 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
1604 end
1604 end
1605
1605
1606 def test_post_create_subissue
1606 def test_post_create_subissue
1607 @request.session[:user_id] = 2
1607 @request.session[:user_id] = 2
1608
1608
1609 assert_difference 'Issue.count' do
1609 assert_difference 'Issue.count' do
1610 post :create, :project_id => 1,
1610 post :create, :project_id => 1,
1611 :issue => {:tracker_id => 1,
1611 :issue => {:tracker_id => 1,
1612 :subject => 'This is a child issue',
1612 :subject => 'This is a child issue',
1613 :parent_issue_id => 2}
1613 :parent_issue_id => 2}
1614 end
1614 end
1615 issue = Issue.find_by_subject('This is a child issue')
1615 issue = Issue.find_by_subject('This is a child issue')
1616 assert_not_nil issue
1616 assert_not_nil issue
1617 assert_equal Issue.find(2), issue.parent
1617 assert_equal Issue.find(2), issue.parent
1618 end
1618 end
1619
1619
1620 def test_post_create_subissue_with_non_numeric_parent_id
1620 def test_post_create_subissue_with_non_numeric_parent_id
1621 @request.session[:user_id] = 2
1621 @request.session[:user_id] = 2
1622
1622
1623 assert_difference 'Issue.count' do
1623 assert_difference 'Issue.count' do
1624 post :create, :project_id => 1,
1624 post :create, :project_id => 1,
1625 :issue => {:tracker_id => 1,
1625 :issue => {:tracker_id => 1,
1626 :subject => 'This is a child issue',
1626 :subject => 'This is a child issue',
1627 :parent_issue_id => 'ABC'}
1627 :parent_issue_id => 'ABC'}
1628 end
1628 end
1629 issue = Issue.find_by_subject('This is a child issue')
1629 issue = Issue.find_by_subject('This is a child issue')
1630 assert_not_nil issue
1630 assert_not_nil issue
1631 assert_nil issue.parent
1631 assert_nil issue.parent
1632 end
1632 end
1633
1633
1634 def test_post_create_private
1634 def test_post_create_private
1635 @request.session[:user_id] = 2
1635 @request.session[:user_id] = 2
1636
1636
1637 assert_difference 'Issue.count' do
1637 assert_difference 'Issue.count' do
1638 post :create, :project_id => 1,
1638 post :create, :project_id => 1,
1639 :issue => {:tracker_id => 1,
1639 :issue => {:tracker_id => 1,
1640 :subject => 'This is a private issue',
1640 :subject => 'This is a private issue',
1641 :is_private => '1'}
1641 :is_private => '1'}
1642 end
1642 end
1643 issue = Issue.first(:order => 'id DESC')
1643 issue = Issue.first(:order => 'id DESC')
1644 assert issue.is_private?
1644 assert issue.is_private?
1645 end
1645 end
1646
1646
1647 def test_post_create_private_with_set_own_issues_private_permission
1647 def test_post_create_private_with_set_own_issues_private_permission
1648 role = Role.find(1)
1648 role = Role.find(1)
1649 role.remove_permission! :set_issues_private
1649 role.remove_permission! :set_issues_private
1650 role.add_permission! :set_own_issues_private
1650 role.add_permission! :set_own_issues_private
1651
1651
1652 @request.session[:user_id] = 2
1652 @request.session[:user_id] = 2
1653
1653
1654 assert_difference 'Issue.count' do
1654 assert_difference 'Issue.count' do
1655 post :create, :project_id => 1,
1655 post :create, :project_id => 1,
1656 :issue => {:tracker_id => 1,
1656 :issue => {:tracker_id => 1,
1657 :subject => 'This is a private issue',
1657 :subject => 'This is a private issue',
1658 :is_private => '1'}
1658 :is_private => '1'}
1659 end
1659 end
1660 issue = Issue.first(:order => 'id DESC')
1660 issue = Issue.first(:order => 'id DESC')
1661 assert issue.is_private?
1661 assert issue.is_private?
1662 end
1662 end
1663
1663
1664 def test_post_create_should_send_a_notification
1664 def test_post_create_should_send_a_notification
1665 ActionMailer::Base.deliveries.clear
1665 ActionMailer::Base.deliveries.clear
1666 @request.session[:user_id] = 2
1666 @request.session[:user_id] = 2
1667 assert_difference 'Issue.count' do
1667 assert_difference 'Issue.count' do
1668 post :create, :project_id => 1,
1668 post :create, :project_id => 1,
1669 :issue => {:tracker_id => 3,
1669 :issue => {:tracker_id => 3,
1670 :subject => 'This is the test_new issue',
1670 :subject => 'This is the test_new issue',
1671 :description => 'This is the description',
1671 :description => 'This is the description',
1672 :priority_id => 5,
1672 :priority_id => 5,
1673 :estimated_hours => '',
1673 :estimated_hours => '',
1674 :custom_field_values => {'2' => 'Value for field 2'}}
1674 :custom_field_values => {'2' => 'Value for field 2'}}
1675 end
1675 end
1676 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1676 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1677
1677
1678 assert_equal 1, ActionMailer::Base.deliveries.size
1678 assert_equal 1, ActionMailer::Base.deliveries.size
1679 end
1679 end
1680
1680
1681 def test_post_create_should_preserve_fields_values_on_validation_failure
1681 def test_post_create_should_preserve_fields_values_on_validation_failure
1682 @request.session[:user_id] = 2
1682 @request.session[:user_id] = 2
1683 post :create, :project_id => 1,
1683 post :create, :project_id => 1,
1684 :issue => {:tracker_id => 1,
1684 :issue => {:tracker_id => 1,
1685 # empty subject
1685 # empty subject
1686 :subject => '',
1686 :subject => '',
1687 :description => 'This is a description',
1687 :description => 'This is a description',
1688 :priority_id => 6,
1688 :priority_id => 6,
1689 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
1689 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
1690 assert_response :success
1690 assert_response :success
1691 assert_template 'new'
1691 assert_template 'new'
1692
1692
1693 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
1693 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
1694 :content => 'This is a description'
1694 :content => 'This is a description'
1695 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
1695 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
1696 :child => { :tag => 'option', :attributes => { :selected => 'selected',
1696 :child => { :tag => 'option', :attributes => { :selected => 'selected',
1697 :value => '6' },
1697 :value => '6' },
1698 :content => 'High' }
1698 :content => 'High' }
1699 # Custom fields
1699 # Custom fields
1700 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
1700 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
1701 :child => { :tag => 'option', :attributes => { :selected => 'selected',
1701 :child => { :tag => 'option', :attributes => { :selected => 'selected',
1702 :value => 'Oracle' },
1702 :value => 'Oracle' },
1703 :content => 'Oracle' }
1703 :content => 'Oracle' }
1704 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
1704 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
1705 :value => 'Value for field 2'}
1705 :value => 'Value for field 2'}
1706 end
1706 end
1707
1707
1708 def test_post_create_with_failure_should_preserve_watchers
1708 def test_post_create_with_failure_should_preserve_watchers
1709 assert !User.find(8).member_of?(Project.find(1))
1709 assert !User.find(8).member_of?(Project.find(1))
1710
1710
1711 @request.session[:user_id] = 2
1711 @request.session[:user_id] = 2
1712 post :create, :project_id => 1,
1712 post :create, :project_id => 1,
1713 :issue => {:tracker_id => 1,
1713 :issue => {:tracker_id => 1,
1714 :watcher_user_ids => ['3', '8']}
1714 :watcher_user_ids => ['3', '8']}
1715 assert_response :success
1715 assert_response :success
1716 assert_template 'new'
1716 assert_template 'new'
1717
1717
1718 assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]', :value => '2', :checked => nil}
1718 assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]', :value => '2', :checked => nil}
1719 assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]', :value => '3', :checked => 'checked'}
1719 assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]', :value => '3', :checked => 'checked'}
1720 assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]', :value => '8', :checked => 'checked'}
1720 assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]', :value => '8', :checked => 'checked'}
1721 end
1721 end
1722
1722
1723 def test_post_create_should_ignore_non_safe_attributes
1723 def test_post_create_should_ignore_non_safe_attributes
1724 @request.session[:user_id] = 2
1724 @request.session[:user_id] = 2
1725 assert_nothing_raised do
1725 assert_nothing_raised do
1726 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
1726 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
1727 end
1727 end
1728 end
1728 end
1729
1729
1730 def test_post_create_with_attachment
1730 def test_post_create_with_attachment
1731 set_tmp_attachments_directory
1731 set_tmp_attachments_directory
1732 @request.session[:user_id] = 2
1732 @request.session[:user_id] = 2
1733
1733
1734 assert_difference 'Issue.count' do
1734 assert_difference 'Issue.count' do
1735 assert_difference 'Attachment.count' do
1735 assert_difference 'Attachment.count' do
1736 post :create, :project_id => 1,
1736 post :create, :project_id => 1,
1737 :issue => { :tracker_id => '1', :subject => 'With attachment' },
1737 :issue => { :tracker_id => '1', :subject => 'With attachment' },
1738 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
1738 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
1739 end
1739 end
1740 end
1740 end
1741
1741
1742 issue = Issue.first(:order => 'id DESC')
1742 issue = Issue.first(:order => 'id DESC')
1743 attachment = Attachment.first(:order => 'id DESC')
1743 attachment = Attachment.first(:order => 'id DESC')
1744
1744
1745 assert_equal issue, attachment.container
1745 assert_equal issue, attachment.container
1746 assert_equal 2, attachment.author_id
1746 assert_equal 2, attachment.author_id
1747 assert_equal 'testfile.txt', attachment.filename
1747 assert_equal 'testfile.txt', attachment.filename
1748 assert_equal 'text/plain', attachment.content_type
1748 assert_equal 'text/plain', attachment.content_type
1749 assert_equal 'test file', attachment.description
1749 assert_equal 'test file', attachment.description
1750 assert_equal 59, attachment.filesize
1750 assert_equal 59, attachment.filesize
1751 assert File.exists?(attachment.diskfile)
1751 assert File.exists?(attachment.diskfile)
1752 assert_equal 59, File.size(attachment.diskfile)
1752 assert_equal 59, File.size(attachment.diskfile)
1753 end
1753 end
1754
1754
1755 def test_post_create_with_failure_should_save_attachments
1755 def test_post_create_with_failure_should_save_attachments
1756 set_tmp_attachments_directory
1756 set_tmp_attachments_directory
1757 @request.session[:user_id] = 2
1757 @request.session[:user_id] = 2
1758
1758
1759 assert_no_difference 'Issue.count' do
1759 assert_no_difference 'Issue.count' do
1760 assert_difference 'Attachment.count' do
1760 assert_difference 'Attachment.count' do
1761 post :create, :project_id => 1,
1761 post :create, :project_id => 1,
1762 :issue => { :tracker_id => '1', :subject => '' },
1762 :issue => { :tracker_id => '1', :subject => '' },
1763 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
1763 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
1764 assert_response :success
1764 assert_response :success
1765 assert_template 'new'
1765 assert_template 'new'
1766 end
1766 end
1767 end
1767 end
1768
1768
1769 attachment = Attachment.first(:order => 'id DESC')
1769 attachment = Attachment.first(:order => 'id DESC')
1770 assert_equal 'testfile.txt', attachment.filename
1770 assert_equal 'testfile.txt', attachment.filename
1771 assert File.exists?(attachment.diskfile)
1771 assert File.exists?(attachment.diskfile)
1772 assert_nil attachment.container
1772 assert_nil attachment.container
1773
1773
1774 assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token}
1774 assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token}
1775 assert_tag 'span', :content => /testfile.txt/
1775 assert_tag 'span', :content => /testfile.txt/
1776 end
1776 end
1777
1777
1778 def test_post_create_with_failure_should_keep_saved_attachments
1778 def test_post_create_with_failure_should_keep_saved_attachments
1779 set_tmp_attachments_directory
1779 set_tmp_attachments_directory
1780 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
1780 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
1781 @request.session[:user_id] = 2
1781 @request.session[:user_id] = 2
1782
1782
1783 assert_no_difference 'Issue.count' do
1783 assert_no_difference 'Issue.count' do
1784 assert_no_difference 'Attachment.count' do
1784 assert_no_difference 'Attachment.count' do
1785 post :create, :project_id => 1,
1785 post :create, :project_id => 1,
1786 :issue => { :tracker_id => '1', :subject => '' },
1786 :issue => { :tracker_id => '1', :subject => '' },
1787 :attachments => {'p0' => {'token' => attachment.token}}
1787 :attachments => {'p0' => {'token' => attachment.token}}
1788 assert_response :success
1788 assert_response :success
1789 assert_template 'new'
1789 assert_template 'new'
1790 end
1790 end
1791 end
1791 end
1792
1792
1793 assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token}
1793 assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token}
1794 assert_tag 'span', :content => /testfile.txt/
1794 assert_tag 'span', :content => /testfile.txt/
1795 end
1795 end
1796
1796
1797 def test_post_create_should_attach_saved_attachments
1797 def test_post_create_should_attach_saved_attachments
1798 set_tmp_attachments_directory
1798 set_tmp_attachments_directory
1799 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
1799 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
1800 @request.session[:user_id] = 2
1800 @request.session[:user_id] = 2
1801
1801
1802 assert_difference 'Issue.count' do
1802 assert_difference 'Issue.count' do
1803 assert_no_difference 'Attachment.count' do
1803 assert_no_difference 'Attachment.count' do
1804 post :create, :project_id => 1,
1804 post :create, :project_id => 1,
1805 :issue => { :tracker_id => '1', :subject => 'Saved attachments' },
1805 :issue => { :tracker_id => '1', :subject => 'Saved attachments' },
1806 :attachments => {'p0' => {'token' => attachment.token}}
1806 :attachments => {'p0' => {'token' => attachment.token}}
1807 assert_response 302
1807 assert_response 302
1808 end
1808 end
1809 end
1809 end
1810
1810
1811 issue = Issue.first(:order => 'id DESC')
1811 issue = Issue.first(:order => 'id DESC')
1812 assert_equal 1, issue.attachments.count
1812 assert_equal 1, issue.attachments.count
1813
1813
1814 attachment.reload
1814 attachment.reload
1815 assert_equal issue, attachment.container
1815 assert_equal issue, attachment.container
1816 end
1816 end
1817
1817
1818 context "without workflow privilege" do
1818 context "without workflow privilege" do
1819 setup do
1819 setup do
1820 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
1820 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
1821 Role.anonymous.add_permission! :add_issues, :add_issue_notes
1821 Role.anonymous.add_permission! :add_issues, :add_issue_notes
1822 end
1822 end
1823
1823
1824 context "#new" do
1824 context "#new" do
1825 should "propose default status only" do
1825 should "propose default status only" do
1826 get :new, :project_id => 1
1826 get :new, :project_id => 1
1827 assert_response :success
1827 assert_response :success
1828 assert_template 'new'
1828 assert_template 'new'
1829 assert_tag :tag => 'select',
1829 assert_tag :tag => 'select',
1830 :attributes => {:name => 'issue[status_id]'},
1830 :attributes => {:name => 'issue[status_id]'},
1831 :children => {:count => 1},
1831 :children => {:count => 1},
1832 :child => {:tag => 'option', :attributes => {:value => IssueStatus.default.id.to_s}}
1832 :child => {:tag => 'option', :attributes => {:value => IssueStatus.default.id.to_s}}
1833 end
1833 end
1834
1834
1835 should "accept default status" do
1835 should "accept default status" do
1836 assert_difference 'Issue.count' do
1836 assert_difference 'Issue.count' do
1837 post :create, :project_id => 1,
1837 post :create, :project_id => 1,
1838 :issue => {:tracker_id => 1,
1838 :issue => {:tracker_id => 1,
1839 :subject => 'This is an issue',
1839 :subject => 'This is an issue',
1840 :status_id => 1}
1840 :status_id => 1}
1841 end
1841 end
1842 issue = Issue.last(:order => 'id')
1842 issue = Issue.last(:order => 'id')
1843 assert_equal IssueStatus.default, issue.status
1843 assert_equal IssueStatus.default, issue.status
1844 end
1844 end
1845
1845
1846 should "ignore unauthorized status" do
1846 should "ignore unauthorized status" do
1847 assert_difference 'Issue.count' do
1847 assert_difference 'Issue.count' do
1848 post :create, :project_id => 1,
1848 post :create, :project_id => 1,
1849 :issue => {:tracker_id => 1,
1849 :issue => {:tracker_id => 1,
1850 :subject => 'This is an issue',
1850 :subject => 'This is an issue',
1851 :status_id => 3}
1851 :status_id => 3}
1852 end
1852 end
1853 issue = Issue.last(:order => 'id')
1853 issue = Issue.last(:order => 'id')
1854 assert_equal IssueStatus.default, issue.status
1854 assert_equal IssueStatus.default, issue.status
1855 end
1855 end
1856 end
1856 end
1857
1857
1858 context "#update" do
1858 context "#update" do
1859 should "ignore status change" do
1859 should "ignore status change" do
1860 assert_difference 'Journal.count' do
1860 assert_difference 'Journal.count' do
1861 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1861 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1862 end
1862 end
1863 assert_equal 1, Issue.find(1).status_id
1863 assert_equal 1, Issue.find(1).status_id
1864 end
1864 end
1865
1865
1866 should "ignore attributes changes" do
1866 should "ignore attributes changes" do
1867 assert_difference 'Journal.count' do
1867 assert_difference 'Journal.count' do
1868 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
1868 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
1869 end
1869 end
1870 issue = Issue.find(1)
1870 issue = Issue.find(1)
1871 assert_equal "Can't print recipes", issue.subject
1871 assert_equal "Can't print recipes", issue.subject
1872 assert_nil issue.assigned_to
1872 assert_nil issue.assigned_to
1873 end
1873 end
1874 end
1874 end
1875 end
1875 end
1876
1876
1877 context "with workflow privilege" do
1877 context "with workflow privilege" do
1878 setup do
1878 setup do
1879 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
1879 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
1880 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
1880 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
1881 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
1881 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
1882 Role.anonymous.add_permission! :add_issues, :add_issue_notes
1882 Role.anonymous.add_permission! :add_issues, :add_issue_notes
1883 end
1883 end
1884
1884
1885 context "#update" do
1885 context "#update" do
1886 should "accept authorized status" do
1886 should "accept authorized status" do
1887 assert_difference 'Journal.count' do
1887 assert_difference 'Journal.count' do
1888 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1888 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1889 end
1889 end
1890 assert_equal 3, Issue.find(1).status_id
1890 assert_equal 3, Issue.find(1).status_id
1891 end
1891 end
1892
1892
1893 should "ignore unauthorized status" do
1893 should "ignore unauthorized status" do
1894 assert_difference 'Journal.count' do
1894 assert_difference 'Journal.count' do
1895 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
1895 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
1896 end
1896 end
1897 assert_equal 1, Issue.find(1).status_id
1897 assert_equal 1, Issue.find(1).status_id
1898 end
1898 end
1899
1899
1900 should "accept authorized attributes changes" do
1900 should "accept authorized attributes changes" do
1901 assert_difference 'Journal.count' do
1901 assert_difference 'Journal.count' do
1902 put :update, :id => 1, :notes => 'just trying', :issue => {:assigned_to_id => 2}
1902 put :update, :id => 1, :notes => 'just trying', :issue => {:assigned_to_id => 2}
1903 end
1903 end
1904 issue = Issue.find(1)
1904 issue = Issue.find(1)
1905 assert_equal 2, issue.assigned_to_id
1905 assert_equal 2, issue.assigned_to_id
1906 end
1906 end
1907
1907
1908 should "ignore unauthorized attributes changes" do
1908 should "ignore unauthorized attributes changes" do
1909 assert_difference 'Journal.count' do
1909 assert_difference 'Journal.count' do
1910 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed'}
1910 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed'}
1911 end
1911 end
1912 issue = Issue.find(1)
1912 issue = Issue.find(1)
1913 assert_equal "Can't print recipes", issue.subject
1913 assert_equal "Can't print recipes", issue.subject
1914 end
1914 end
1915 end
1915 end
1916
1916
1917 context "and :edit_issues permission" do
1917 context "and :edit_issues permission" do
1918 setup do
1918 setup do
1919 Role.anonymous.add_permission! :add_issues, :edit_issues
1919 Role.anonymous.add_permission! :add_issues, :edit_issues
1920 end
1920 end
1921
1921
1922 should "accept authorized status" do
1922 should "accept authorized status" do
1923 assert_difference 'Journal.count' do
1923 assert_difference 'Journal.count' do
1924 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1924 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1925 end
1925 end
1926 assert_equal 3, Issue.find(1).status_id
1926 assert_equal 3, Issue.find(1).status_id
1927 end
1927 end
1928
1928
1929 should "ignore unauthorized status" do
1929 should "ignore unauthorized status" do
1930 assert_difference 'Journal.count' do
1930 assert_difference 'Journal.count' do
1931 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
1931 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
1932 end
1932 end
1933 assert_equal 1, Issue.find(1).status_id
1933 assert_equal 1, Issue.find(1).status_id
1934 end
1934 end
1935
1935
1936 should "accept authorized attributes changes" do
1936 should "accept authorized attributes changes" do
1937 assert_difference 'Journal.count' do
1937 assert_difference 'Journal.count' do
1938 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
1938 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
1939 end
1939 end
1940 issue = Issue.find(1)
1940 issue = Issue.find(1)
1941 assert_equal "changed", issue.subject
1941 assert_equal "changed", issue.subject
1942 assert_equal 2, issue.assigned_to_id
1942 assert_equal 2, issue.assigned_to_id
1943 end
1943 end
1944 end
1944 end
1945 end
1945 end
1946
1946
1947 def test_new_as_copy
1947 def test_new_as_copy
1948 @request.session[:user_id] = 2
1948 @request.session[:user_id] = 2
1949 get :new, :project_id => 1, :copy_from => 1
1949 get :new, :project_id => 1, :copy_from => 1
1950
1950
1951 assert_response :success
1951 assert_response :success
1952 assert_template 'new'
1952 assert_template 'new'
1953
1953
1954 assert_not_nil assigns(:issue)
1954 assert_not_nil assigns(:issue)
1955 orig = Issue.find(1)
1955 orig = Issue.find(1)
1956 assert_equal 1, assigns(:issue).project_id
1956 assert_equal 1, assigns(:issue).project_id
1957 assert_equal orig.subject, assigns(:issue).subject
1957 assert_equal orig.subject, assigns(:issue).subject
1958 assert assigns(:issue).copy?
1958 assert assigns(:issue).copy?
1959
1959
1960 assert_tag 'form', :attributes => {:id => 'issue-form', :action => '/projects/ecookbook/issues'}
1960 assert_tag 'form', :attributes => {:id => 'issue-form', :action => '/projects/ecookbook/issues'}
1961 assert_tag 'select', :attributes => {:name => 'issue[project_id]'}
1961 assert_tag 'select', :attributes => {:name => 'issue[project_id]'}
1962 assert_tag 'select', :attributes => {:name => 'issue[project_id]'},
1962 assert_tag 'select', :attributes => {:name => 'issue[project_id]'},
1963 :child => {:tag => 'option', :attributes => {:value => '1', :selected => 'selected'}, :content => 'eCookbook'}
1963 :child => {:tag => 'option', :attributes => {:value => '1', :selected => 'selected'}, :content => 'eCookbook'}
1964 assert_tag 'select', :attributes => {:name => 'issue[project_id]'},
1964 assert_tag 'select', :attributes => {:name => 'issue[project_id]'},
1965 :child => {:tag => 'option', :attributes => {:value => '2', :selected => nil}, :content => 'OnlineStore'}
1965 :child => {:tag => 'option', :attributes => {:value => '2', :selected => nil}, :content => 'OnlineStore'}
1966 assert_tag 'input', :attributes => {:name => 'copy_from', :value => '1'}
1966 assert_tag 'input', :attributes => {:name => 'copy_from', :value => '1'}
1967 end
1967 end
1968
1968
1969 def test_new_as_copy_with_attachments_should_show_copy_attachments_checkbox
1969 def test_new_as_copy_with_attachments_should_show_copy_attachments_checkbox
1970 @request.session[:user_id] = 2
1970 @request.session[:user_id] = 2
1971 issue = Issue.find(3)
1971 issue = Issue.find(3)
1972 assert issue.attachments.count > 0
1972 assert issue.attachments.count > 0
1973 get :new, :project_id => 1, :copy_from => 3
1973 get :new, :project_id => 1, :copy_from => 3
1974
1974
1975 assert_tag 'input', :attributes => {:name => 'copy_attachments', :type => 'checkbox', :checked => 'checked', :value => '1'}
1975 assert_tag 'input', :attributes => {:name => 'copy_attachments', :type => 'checkbox', :checked => 'checked', :value => '1'}
1976 end
1976 end
1977
1977
1978 def test_new_as_copy_without_attachments_should_not_show_copy_attachments_checkbox
1978 def test_new_as_copy_without_attachments_should_not_show_copy_attachments_checkbox
1979 @request.session[:user_id] = 2
1979 @request.session[:user_id] = 2
1980 issue = Issue.find(3)
1980 issue = Issue.find(3)
1981 issue.attachments.delete_all
1981 issue.attachments.delete_all
1982 get :new, :project_id => 1, :copy_from => 3
1982 get :new, :project_id => 1, :copy_from => 3
1983
1983
1984 assert_no_tag 'input', :attributes => {:name => 'copy_attachments', :type => 'checkbox', :checked => 'checked', :value => '1'}
1984 assert_no_tag 'input', :attributes => {:name => 'copy_attachments', :type => 'checkbox', :checked => 'checked', :value => '1'}
1985 end
1985 end
1986
1986
1987 def test_new_as_copy_with_invalid_issue_should_respond_with_404
1987 def test_new_as_copy_with_invalid_issue_should_respond_with_404
1988 @request.session[:user_id] = 2
1988 @request.session[:user_id] = 2
1989 get :new, :project_id => 1, :copy_from => 99999
1989 get :new, :project_id => 1, :copy_from => 99999
1990 assert_response 404
1990 assert_response 404
1991 end
1991 end
1992
1992
1993 def test_create_as_copy_on_different_project
1993 def test_create_as_copy_on_different_project
1994 @request.session[:user_id] = 2
1994 @request.session[:user_id] = 2
1995 assert_difference 'Issue.count' do
1995 assert_difference 'Issue.count' do
1996 post :create, :project_id => 1, :copy_from => 1,
1996 post :create, :project_id => 1, :copy_from => 1,
1997 :issue => {:project_id => '2', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
1997 :issue => {:project_id => '2', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
1998
1998
1999 assert_not_nil assigns(:issue)
1999 assert_not_nil assigns(:issue)
2000 assert assigns(:issue).copy?
2000 assert assigns(:issue).copy?
2001 end
2001 end
2002 issue = Issue.first(:order => 'id DESC')
2002 issue = Issue.first(:order => 'id DESC')
2003 assert_redirected_to "/issues/#{issue.id}"
2003 assert_redirected_to "/issues/#{issue.id}"
2004
2004
2005 assert_equal 2, issue.project_id
2005 assert_equal 2, issue.project_id
2006 assert_equal 3, issue.tracker_id
2006 assert_equal 3, issue.tracker_id
2007 assert_equal 'Copy', issue.subject
2007 assert_equal 'Copy', issue.subject
2008 end
2008 end
2009
2009
2010 def test_create_as_copy_should_copy_attachments
2010 def test_create_as_copy_should_copy_attachments
2011 @request.session[:user_id] = 2
2011 @request.session[:user_id] = 2
2012 issue = Issue.find(3)
2012 issue = Issue.find(3)
2013 count = issue.attachments.count
2013 count = issue.attachments.count
2014 assert count > 0
2014 assert count > 0
2015
2015
2016 assert_difference 'Issue.count' do
2016 assert_difference 'Issue.count' do
2017 assert_difference 'Attachment.count', count do
2017 assert_difference 'Attachment.count', count do
2018 assert_no_difference 'Journal.count' do
2018 assert_no_difference 'Journal.count' do
2019 post :create, :project_id => 1, :copy_from => 3,
2019 post :create, :project_id => 1, :copy_from => 3,
2020 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'},
2020 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'},
2021 :copy_attachments => '1'
2021 :copy_attachments => '1'
2022 end
2022 end
2023 end
2023 end
2024 end
2024 end
2025 copy = Issue.first(:order => 'id DESC')
2025 copy = Issue.first(:order => 'id DESC')
2026 assert_equal count, copy.attachments.count
2026 assert_equal count, copy.attachments.count
2027 assert_equal issue.attachments.map(&:filename).sort, copy.attachments.map(&:filename).sort
2027 assert_equal issue.attachments.map(&:filename).sort, copy.attachments.map(&:filename).sort
2028 end
2028 end
2029
2029
2030 def test_create_as_copy_without_copy_attachments_option_should_not_copy_attachments
2030 def test_create_as_copy_without_copy_attachments_option_should_not_copy_attachments
2031 @request.session[:user_id] = 2
2031 @request.session[:user_id] = 2
2032 issue = Issue.find(3)
2032 issue = Issue.find(3)
2033 count = issue.attachments.count
2033 count = issue.attachments.count
2034 assert count > 0
2034 assert count > 0
2035
2035
2036 assert_difference 'Issue.count' do
2036 assert_difference 'Issue.count' do
2037 assert_no_difference 'Attachment.count' do
2037 assert_no_difference 'Attachment.count' do
2038 assert_no_difference 'Journal.count' do
2038 assert_no_difference 'Journal.count' do
2039 post :create, :project_id => 1, :copy_from => 3,
2039 post :create, :project_id => 1, :copy_from => 3,
2040 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'}
2040 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'}
2041 end
2041 end
2042 end
2042 end
2043 end
2043 end
2044 copy = Issue.first(:order => 'id DESC')
2044 copy = Issue.first(:order => 'id DESC')
2045 assert_equal 0, copy.attachments.count
2045 assert_equal 0, copy.attachments.count
2046 end
2046 end
2047
2047
2048 def test_create_as_copy_with_attachments_should_add_new_files
2048 def test_create_as_copy_with_attachments_should_add_new_files
2049 @request.session[:user_id] = 2
2049 @request.session[:user_id] = 2
2050 issue = Issue.find(3)
2050 issue = Issue.find(3)
2051 count = issue.attachments.count
2051 count = issue.attachments.count
2052 assert count > 0
2052 assert count > 0
2053
2053
2054 assert_difference 'Issue.count' do
2054 assert_difference 'Issue.count' do
2055 assert_difference 'Attachment.count', count + 1 do
2055 assert_difference 'Attachment.count', count + 1 do
2056 assert_no_difference 'Journal.count' do
2056 assert_no_difference 'Journal.count' do
2057 post :create, :project_id => 1, :copy_from => 3,
2057 post :create, :project_id => 1, :copy_from => 3,
2058 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'},
2058 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'},
2059 :copy_attachments => '1',
2059 :copy_attachments => '1',
2060 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2060 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2061 end
2061 end
2062 end
2062 end
2063 end
2063 end
2064 copy = Issue.first(:order => 'id DESC')
2064 copy = Issue.first(:order => 'id DESC')
2065 assert_equal count + 1, copy.attachments.count
2065 assert_equal count + 1, copy.attachments.count
2066 end
2066 end
2067
2067
2068 def test_create_as_copy_with_failure
2068 def test_create_as_copy_with_failure
2069 @request.session[:user_id] = 2
2069 @request.session[:user_id] = 2
2070 post :create, :project_id => 1, :copy_from => 1,
2070 post :create, :project_id => 1, :copy_from => 1,
2071 :issue => {:project_id => '2', :tracker_id => '3', :status_id => '1', :subject => ''}
2071 :issue => {:project_id => '2', :tracker_id => '3', :status_id => '1', :subject => ''}
2072
2072
2073 assert_response :success
2073 assert_response :success
2074 assert_template 'new'
2074 assert_template 'new'
2075
2075
2076 assert_not_nil assigns(:issue)
2076 assert_not_nil assigns(:issue)
2077 assert assigns(:issue).copy?
2077 assert assigns(:issue).copy?
2078
2078
2079 assert_tag 'form', :attributes => {:id => 'issue-form', :action => '/projects/ecookbook/issues'}
2079 assert_tag 'form', :attributes => {:id => 'issue-form', :action => '/projects/ecookbook/issues'}
2080 assert_tag 'select', :attributes => {:name => 'issue[project_id]'}
2080 assert_tag 'select', :attributes => {:name => 'issue[project_id]'}
2081 assert_tag 'select', :attributes => {:name => 'issue[project_id]'},
2081 assert_tag 'select', :attributes => {:name => 'issue[project_id]'},
2082 :child => {:tag => 'option', :attributes => {:value => '1', :selected => nil}, :content => 'eCookbook'}
2082 :child => {:tag => 'option', :attributes => {:value => '1', :selected => nil}, :content => 'eCookbook'}
2083 assert_tag 'select', :attributes => {:name => 'issue[project_id]'},
2083 assert_tag 'select', :attributes => {:name => 'issue[project_id]'},
2084 :child => {:tag => 'option', :attributes => {:value => '2', :selected => 'selected'}, :content => 'OnlineStore'}
2084 :child => {:tag => 'option', :attributes => {:value => '2', :selected => 'selected'}, :content => 'OnlineStore'}
2085 assert_tag 'input', :attributes => {:name => 'copy_from', :value => '1'}
2085 assert_tag 'input', :attributes => {:name => 'copy_from', :value => '1'}
2086 end
2086 end
2087
2087
2088 def test_create_as_copy_on_project_without_permission_should_ignore_target_project
2088 def test_create_as_copy_on_project_without_permission_should_ignore_target_project
2089 @request.session[:user_id] = 2
2089 @request.session[:user_id] = 2
2090 assert !User.find(2).member_of?(Project.find(4))
2090 assert !User.find(2).member_of?(Project.find(4))
2091
2091
2092 assert_difference 'Issue.count' do
2092 assert_difference 'Issue.count' do
2093 post :create, :project_id => 1, :copy_from => 1,
2093 post :create, :project_id => 1, :copy_from => 1,
2094 :issue => {:project_id => '4', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
2094 :issue => {:project_id => '4', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
2095 end
2095 end
2096 issue = Issue.first(:order => 'id DESC')
2096 issue = Issue.first(:order => 'id DESC')
2097 assert_equal 1, issue.project_id
2097 assert_equal 1, issue.project_id
2098 end
2098 end
2099
2099
2100 def test_get_edit
2100 def test_get_edit
2101 @request.session[:user_id] = 2
2101 @request.session[:user_id] = 2
2102 get :edit, :id => 1
2102 get :edit, :id => 1
2103 assert_response :success
2103 assert_response :success
2104 assert_template 'edit'
2104 assert_template 'edit'
2105 assert_not_nil assigns(:issue)
2105 assert_not_nil assigns(:issue)
2106 assert_equal Issue.find(1), assigns(:issue)
2106 assert_equal Issue.find(1), assigns(:issue)
2107
2107
2108 # Be sure we don't display inactive IssuePriorities
2108 # Be sure we don't display inactive IssuePriorities
2109 assert ! IssuePriority.find(15).active?
2109 assert ! IssuePriority.find(15).active?
2110 assert_no_tag :option, :attributes => {:value => '15'},
2110 assert_no_tag :option, :attributes => {:value => '15'},
2111 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
2111 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
2112 end
2112 end
2113
2113
2114 def test_get_edit_should_display_the_time_entry_form_with_log_time_permission
2114 def test_get_edit_should_display_the_time_entry_form_with_log_time_permission
2115 @request.session[:user_id] = 2
2115 @request.session[:user_id] = 2
2116 Role.find_by_name('Manager').update_attribute :permissions, [:view_issues, :edit_issues, :log_time]
2116 Role.find_by_name('Manager').update_attribute :permissions, [:view_issues, :edit_issues, :log_time]
2117
2117
2118 get :edit, :id => 1
2118 get :edit, :id => 1
2119 assert_tag 'input', :attributes => {:name => 'time_entry[hours]'}
2119 assert_tag 'input', :attributes => {:name => 'time_entry[hours]'}
2120 end
2120 end
2121
2121
2122 def test_get_edit_should_not_display_the_time_entry_form_without_log_time_permission
2122 def test_get_edit_should_not_display_the_time_entry_form_without_log_time_permission
2123 @request.session[:user_id] = 2
2123 @request.session[:user_id] = 2
2124 Role.find_by_name('Manager').remove_permission! :log_time
2124 Role.find_by_name('Manager').remove_permission! :log_time
2125
2125
2126 get :edit, :id => 1
2126 get :edit, :id => 1
2127 assert_no_tag 'input', :attributes => {:name => 'time_entry[hours]'}
2127 assert_no_tag 'input', :attributes => {:name => 'time_entry[hours]'}
2128 end
2128 end
2129
2129
2130 def test_get_edit_with_params
2130 def test_get_edit_with_params
2131 @request.session[:user_id] = 2
2131 @request.session[:user_id] = 2
2132 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 },
2132 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 },
2133 :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => TimeEntryActivity.first.id }
2133 :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => TimeEntryActivity.first.id }
2134 assert_response :success
2134 assert_response :success
2135 assert_template 'edit'
2135 assert_template 'edit'
2136
2136
2137 issue = assigns(:issue)
2137 issue = assigns(:issue)
2138 assert_not_nil issue
2138 assert_not_nil issue
2139
2139
2140 assert_equal 5, issue.status_id
2140 assert_equal 5, issue.status_id
2141 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
2141 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
2142 :child => { :tag => 'option',
2142 :child => { :tag => 'option',
2143 :content => 'Closed',
2143 :content => 'Closed',
2144 :attributes => { :selected => 'selected' } }
2144 :attributes => { :selected => 'selected' } }
2145
2145
2146 assert_equal 7, issue.priority_id
2146 assert_equal 7, issue.priority_id
2147 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
2147 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
2148 :child => { :tag => 'option',
2148 :child => { :tag => 'option',
2149 :content => 'Urgent',
2149 :content => 'Urgent',
2150 :attributes => { :selected => 'selected' } }
2150 :attributes => { :selected => 'selected' } }
2151
2151
2152 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => '2.5' }
2152 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => '2.5' }
2153 assert_tag :select, :attributes => { :name => 'time_entry[activity_id]' },
2153 assert_tag :select, :attributes => { :name => 'time_entry[activity_id]' },
2154 :child => { :tag => 'option',
2154 :child => { :tag => 'option',
2155 :attributes => { :selected => 'selected', :value => TimeEntryActivity.first.id } }
2155 :attributes => { :selected => 'selected', :value => TimeEntryActivity.first.id } }
2156 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => 'test_get_edit_with_params' }
2156 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => 'test_get_edit_with_params' }
2157 end
2157 end
2158
2158
2159 def test_get_edit_with_multi_custom_field
2159 def test_get_edit_with_multi_custom_field
2160 field = CustomField.find(1)
2160 field = CustomField.find(1)
2161 field.update_attribute :multiple, true
2161 field.update_attribute :multiple, true
2162 issue = Issue.find(1)
2162 issue = Issue.find(1)
2163 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
2163 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
2164 issue.save!
2164 issue.save!
2165
2165
2166 @request.session[:user_id] = 2
2166 @request.session[:user_id] = 2
2167 get :edit, :id => 1
2167 get :edit, :id => 1
2168 assert_response :success
2168 assert_response :success
2169 assert_template 'edit'
2169 assert_template 'edit'
2170
2170
2171 assert_tag 'select', :attributes => {:name => 'issue[custom_field_values][1][]', :multiple => 'multiple'}
2171 assert_tag 'select', :attributes => {:name => 'issue[custom_field_values][1][]', :multiple => 'multiple'}
2172 assert_tag 'select', :attributes => {:name => 'issue[custom_field_values][1][]'},
2172 assert_tag 'select', :attributes => {:name => 'issue[custom_field_values][1][]'},
2173 :child => {:tag => 'option', :attributes => {:value => 'MySQL', :selected => 'selected'}}
2173 :child => {:tag => 'option', :attributes => {:value => 'MySQL', :selected => 'selected'}}
2174 assert_tag 'select', :attributes => {:name => 'issue[custom_field_values][1][]'},
2174 assert_tag 'select', :attributes => {:name => 'issue[custom_field_values][1][]'},
2175 :child => {:tag => 'option', :attributes => {:value => 'PostgreSQL', :selected => nil}}
2175 :child => {:tag => 'option', :attributes => {:value => 'PostgreSQL', :selected => nil}}
2176 assert_tag 'select', :attributes => {:name => 'issue[custom_field_values][1][]'},
2176 assert_tag 'select', :attributes => {:name => 'issue[custom_field_values][1][]'},
2177 :child => {:tag => 'option', :attributes => {:value => 'Oracle', :selected => 'selected'}}
2177 :child => {:tag => 'option', :attributes => {:value => 'Oracle', :selected => 'selected'}}
2178 end
2178 end
2179
2179
2180 def test_update_edit_form
2180 def test_update_edit_form
2181 @request.session[:user_id] = 2
2181 @request.session[:user_id] = 2
2182 xhr :put, :new, :project_id => 1,
2182 xhr :put, :new, :project_id => 1,
2183 :id => 1,
2183 :id => 1,
2184 :issue => {:tracker_id => 2,
2184 :issue => {:tracker_id => 2,
2185 :subject => 'This is the test_new issue',
2185 :subject => 'This is the test_new issue',
2186 :description => 'This is the description',
2186 :description => 'This is the description',
2187 :priority_id => 5}
2187 :priority_id => 5}
2188 assert_response :success
2188 assert_response :success
2189 assert_template 'attributes'
2189 assert_template 'attributes'
2190
2190
2191 issue = assigns(:issue)
2191 issue = assigns(:issue)
2192 assert_kind_of Issue, issue
2192 assert_kind_of Issue, issue
2193 assert_equal 1, issue.id
2193 assert_equal 1, issue.id
2194 assert_equal 1, issue.project_id
2194 assert_equal 1, issue.project_id
2195 assert_equal 2, issue.tracker_id
2195 assert_equal 2, issue.tracker_id
2196 assert_equal 'This is the test_new issue', issue.subject
2196 assert_equal 'This is the test_new issue', issue.subject
2197 end
2197 end
2198
2198
2199 def test_update_edit_form_should_propose_transitions_based_on_initial_status
2199 def test_update_edit_form_should_propose_transitions_based_on_initial_status
2200 @request.session[:user_id] = 2
2200 @request.session[:user_id] = 2
2201 Workflow.delete_all
2201 Workflow.delete_all
2202 Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1)
2202 Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1)
2203 Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 5)
2203 Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 5)
2204 Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 5, :new_status_id => 4)
2204 Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 5, :new_status_id => 4)
2205
2205
2206 xhr :put, :new, :project_id => 1,
2206 xhr :put, :new, :project_id => 1,
2207 :id => 2,
2207 :id => 2,
2208 :issue => {:tracker_id => 2,
2208 :issue => {:tracker_id => 2,
2209 :status_id => 5,
2209 :status_id => 5,
2210 :subject => 'This is an issue'}
2210 :subject => 'This is an issue'}
2211
2211
2212 assert_equal 5, assigns(:issue).status_id
2212 assert_equal 5, assigns(:issue).status_id
2213 assert_equal [1,2,5], assigns(:allowed_statuses).map(&:id).sort
2213 assert_equal [1,2,5], assigns(:allowed_statuses).map(&:id).sort
2214 end
2214 end
2215
2215
2216 def test_update_edit_form_with_project_change
2216 def test_update_edit_form_with_project_change
2217 @request.session[:user_id] = 2
2217 @request.session[:user_id] = 2
2218 xhr :put, :new, :project_id => 1,
2218 xhr :put, :new, :project_id => 1,
2219 :id => 1,
2219 :id => 1,
2220 :project_change => '1',
2220 :project_change => '1',
2221 :issue => {:project_id => 2,
2221 :issue => {:project_id => 2,
2222 :tracker_id => 2,
2222 :tracker_id => 2,
2223 :subject => 'This is the test_new issue',
2223 :subject => 'This is the test_new issue',
2224 :description => 'This is the description',
2224 :description => 'This is the description',
2225 :priority_id => 5}
2225 :priority_id => 5}
2226 assert_response :success
2226 assert_response :success
2227 assert_template 'form'
2227 assert_template 'form'
2228
2228
2229 issue = assigns(:issue)
2229 issue = assigns(:issue)
2230 assert_kind_of Issue, issue
2230 assert_kind_of Issue, issue
2231 assert_equal 1, issue.id
2231 assert_equal 1, issue.id
2232 assert_equal 2, issue.project_id
2232 assert_equal 2, issue.project_id
2233 assert_equal 2, issue.tracker_id
2233 assert_equal 2, issue.tracker_id
2234 assert_equal 'This is the test_new issue', issue.subject
2234 assert_equal 'This is the test_new issue', issue.subject
2235 end
2235 end
2236
2236
2237 def test_put_update_without_custom_fields_param
2237 def test_put_update_without_custom_fields_param
2238 @request.session[:user_id] = 2
2238 @request.session[:user_id] = 2
2239 ActionMailer::Base.deliveries.clear
2239 ActionMailer::Base.deliveries.clear
2240
2240
2241 issue = Issue.find(1)
2241 issue = Issue.find(1)
2242 assert_equal '125', issue.custom_value_for(2).value
2242 assert_equal '125', issue.custom_value_for(2).value
2243 old_subject = issue.subject
2243 old_subject = issue.subject
2244 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
2244 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
2245
2245
2246 assert_difference('Journal.count') do
2246 assert_difference('Journal.count') do
2247 assert_difference('JournalDetail.count', 2) do
2247 assert_difference('JournalDetail.count', 2) do
2248 put :update, :id => 1, :issue => {:subject => new_subject,
2248 put :update, :id => 1, :issue => {:subject => new_subject,
2249 :priority_id => '6',
2249 :priority_id => '6',
2250 :category_id => '1' # no change
2250 :category_id => '1' # no change
2251 }
2251 }
2252 end
2252 end
2253 end
2253 end
2254 assert_redirected_to :action => 'show', :id => '1'
2254 assert_redirected_to :action => 'show', :id => '1'
2255 issue.reload
2255 issue.reload
2256 assert_equal new_subject, issue.subject
2256 assert_equal new_subject, issue.subject
2257 # Make sure custom fields were not cleared
2257 # Make sure custom fields were not cleared
2258 assert_equal '125', issue.custom_value_for(2).value
2258 assert_equal '125', issue.custom_value_for(2).value
2259
2259
2260 mail = ActionMailer::Base.deliveries.last
2260 mail = ActionMailer::Base.deliveries.last
2261 assert_not_nil mail
2261 assert_not_nil mail
2262 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2262 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2263 assert_mail_body_match "Subject changed from #{old_subject} to #{new_subject}", mail
2263 assert_mail_body_match "Subject changed from #{old_subject} to #{new_subject}", mail
2264 end
2264 end
2265
2265
2266 def test_put_update_with_project_change
2266 def test_put_update_with_project_change
2267 @request.session[:user_id] = 2
2267 @request.session[:user_id] = 2
2268 ActionMailer::Base.deliveries.clear
2268 ActionMailer::Base.deliveries.clear
2269
2269
2270 assert_difference('Journal.count') do
2270 assert_difference('Journal.count') do
2271 assert_difference('JournalDetail.count', 3) do
2271 assert_difference('JournalDetail.count', 3) do
2272 put :update, :id => 1, :issue => {:project_id => '2',
2272 put :update, :id => 1, :issue => {:project_id => '2',
2273 :tracker_id => '1', # no change
2273 :tracker_id => '1', # no change
2274 :priority_id => '6',
2274 :priority_id => '6',
2275 :category_id => '3'
2275 :category_id => '3'
2276 }
2276 }
2277 end
2277 end
2278 end
2278 end
2279 assert_redirected_to :action => 'show', :id => '1'
2279 assert_redirected_to :action => 'show', :id => '1'
2280 issue = Issue.find(1)
2280 issue = Issue.find(1)
2281 assert_equal 2, issue.project_id
2281 assert_equal 2, issue.project_id
2282 assert_equal 1, issue.tracker_id
2282 assert_equal 1, issue.tracker_id
2283 assert_equal 6, issue.priority_id
2283 assert_equal 6, issue.priority_id
2284 assert_equal 3, issue.category_id
2284 assert_equal 3, issue.category_id
2285
2285
2286 mail = ActionMailer::Base.deliveries.last
2286 mail = ActionMailer::Base.deliveries.last
2287 assert_not_nil mail
2287 assert_not_nil mail
2288 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2288 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2289 assert_mail_body_match "Project changed from eCookbook to OnlineStore", mail
2289 assert_mail_body_match "Project changed from eCookbook to OnlineStore", mail
2290 end
2290 end
2291
2291
2292 def test_put_update_with_tracker_change
2292 def test_put_update_with_tracker_change
2293 @request.session[:user_id] = 2
2293 @request.session[:user_id] = 2
2294 ActionMailer::Base.deliveries.clear
2294 ActionMailer::Base.deliveries.clear
2295
2295
2296 assert_difference('Journal.count') do
2296 assert_difference('Journal.count') do
2297 assert_difference('JournalDetail.count', 2) do
2297 assert_difference('JournalDetail.count', 2) do
2298 put :update, :id => 1, :issue => {:project_id => '1',
2298 put :update, :id => 1, :issue => {:project_id => '1',
2299 :tracker_id => '2',
2299 :tracker_id => '2',
2300 :priority_id => '6'
2300 :priority_id => '6'
2301 }
2301 }
2302 end
2302 end
2303 end
2303 end
2304 assert_redirected_to :action => 'show', :id => '1'
2304 assert_redirected_to :action => 'show', :id => '1'
2305 issue = Issue.find(1)
2305 issue = Issue.find(1)
2306 assert_equal 1, issue.project_id
2306 assert_equal 1, issue.project_id
2307 assert_equal 2, issue.tracker_id
2307 assert_equal 2, issue.tracker_id
2308 assert_equal 6, issue.priority_id
2308 assert_equal 6, issue.priority_id
2309 assert_equal 1, issue.category_id
2309 assert_equal 1, issue.category_id
2310
2310
2311 mail = ActionMailer::Base.deliveries.last
2311 mail = ActionMailer::Base.deliveries.last
2312 assert_not_nil mail
2312 assert_not_nil mail
2313 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2313 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2314 assert_mail_body_match "Tracker changed from Bug to Feature request", mail
2314 assert_mail_body_match "Tracker changed from Bug to Feature request", mail
2315 end
2315 end
2316
2316
2317 def test_put_update_with_custom_field_change
2317 def test_put_update_with_custom_field_change
2318 @request.session[:user_id] = 2
2318 @request.session[:user_id] = 2
2319 issue = Issue.find(1)
2319 issue = Issue.find(1)
2320 assert_equal '125', issue.custom_value_for(2).value
2320 assert_equal '125', issue.custom_value_for(2).value
2321
2321
2322 assert_difference('Journal.count') do
2322 assert_difference('Journal.count') do
2323 assert_difference('JournalDetail.count', 3) do
2323 assert_difference('JournalDetail.count', 3) do
2324 put :update, :id => 1, :issue => {:subject => 'Custom field change',
2324 put :update, :id => 1, :issue => {:subject => 'Custom field change',
2325 :priority_id => '6',
2325 :priority_id => '6',
2326 :category_id => '1', # no change
2326 :category_id => '1', # no change
2327 :custom_field_values => { '2' => 'New custom value' }
2327 :custom_field_values => { '2' => 'New custom value' }
2328 }
2328 }
2329 end
2329 end
2330 end
2330 end
2331 assert_redirected_to :action => 'show', :id => '1'
2331 assert_redirected_to :action => 'show', :id => '1'
2332 issue.reload
2332 issue.reload
2333 assert_equal 'New custom value', issue.custom_value_for(2).value
2333 assert_equal 'New custom value', issue.custom_value_for(2).value
2334
2334
2335 mail = ActionMailer::Base.deliveries.last
2335 mail = ActionMailer::Base.deliveries.last
2336 assert_not_nil mail
2336 assert_not_nil mail
2337 assert_mail_body_match "Searchable field changed from 125 to New custom value", mail
2337 assert_mail_body_match "Searchable field changed from 125 to New custom value", mail
2338 end
2338 end
2339
2339
2340 def test_put_update_with_multi_custom_field_change
2340 def test_put_update_with_multi_custom_field_change
2341 field = CustomField.find(1)
2341 field = CustomField.find(1)
2342 field.update_attribute :multiple, true
2342 field.update_attribute :multiple, true
2343 issue = Issue.find(1)
2343 issue = Issue.find(1)
2344 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
2344 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
2345 issue.save!
2345 issue.save!
2346
2346
2347 @request.session[:user_id] = 2
2347 @request.session[:user_id] = 2
2348 assert_difference('Journal.count') do
2348 assert_difference('Journal.count') do
2349 assert_difference('JournalDetail.count', 3) do
2349 assert_difference('JournalDetail.count', 3) do
2350 put :update, :id => 1,
2350 put :update, :id => 1,
2351 :issue => {
2351 :issue => {
2352 :subject => 'Custom field change',
2352 :subject => 'Custom field change',
2353 :custom_field_values => { '1' => ['', 'Oracle', 'PostgreSQL'] }
2353 :custom_field_values => { '1' => ['', 'Oracle', 'PostgreSQL'] }
2354 }
2354 }
2355 end
2355 end
2356 end
2356 end
2357 assert_redirected_to :action => 'show', :id => '1'
2357 assert_redirected_to :action => 'show', :id => '1'
2358 assert_equal ['Oracle', 'PostgreSQL'], Issue.find(1).custom_field_value(1).sort
2358 assert_equal ['Oracle', 'PostgreSQL'], Issue.find(1).custom_field_value(1).sort
2359 end
2359 end
2360
2360
2361 def test_put_update_with_status_and_assignee_change
2361 def test_put_update_with_status_and_assignee_change
2362 issue = Issue.find(1)
2362 issue = Issue.find(1)
2363 assert_equal 1, issue.status_id
2363 assert_equal 1, issue.status_id
2364 @request.session[:user_id] = 2
2364 @request.session[:user_id] = 2
2365 assert_difference('TimeEntry.count', 0) do
2365 assert_difference('TimeEntry.count', 0) do
2366 put :update,
2366 put :update,
2367 :id => 1,
2367 :id => 1,
2368 :issue => { :status_id => 2, :assigned_to_id => 3 },
2368 :issue => { :status_id => 2, :assigned_to_id => 3 },
2369 :notes => 'Assigned to dlopper',
2369 :notes => 'Assigned to dlopper',
2370 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
2370 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
2371 end
2371 end
2372 assert_redirected_to :action => 'show', :id => '1'
2372 assert_redirected_to :action => 'show', :id => '1'
2373 issue.reload
2373 issue.reload
2374 assert_equal 2, issue.status_id
2374 assert_equal 2, issue.status_id
2375 j = Journal.find(:first, :order => 'id DESC')
2375 j = Journal.find(:first, :order => 'id DESC')
2376 assert_equal 'Assigned to dlopper', j.notes
2376 assert_equal 'Assigned to dlopper', j.notes
2377 assert_equal 2, j.details.size
2377 assert_equal 2, j.details.size
2378
2378
2379 mail = ActionMailer::Base.deliveries.last
2379 mail = ActionMailer::Base.deliveries.last
2380 assert_mail_body_match "Status changed from New to Assigned", mail
2380 assert_mail_body_match "Status changed from New to Assigned", mail
2381 # subject should contain the new status
2381 # subject should contain the new status
2382 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
2382 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
2383 end
2383 end
2384
2384
2385 def test_put_update_with_note_only
2385 def test_put_update_with_note_only
2386 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
2386 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
2387 # anonymous user
2387 # anonymous user
2388 put :update,
2388 put :update,
2389 :id => 1,
2389 :id => 1,
2390 :notes => notes
2390 :notes => notes
2391 assert_redirected_to :action => 'show', :id => '1'
2391 assert_redirected_to :action => 'show', :id => '1'
2392 j = Journal.find(:first, :order => 'id DESC')
2392 j = Journal.find(:first, :order => 'id DESC')
2393 assert_equal notes, j.notes
2393 assert_equal notes, j.notes
2394 assert_equal 0, j.details.size
2394 assert_equal 0, j.details.size
2395 assert_equal User.anonymous, j.user
2395 assert_equal User.anonymous, j.user
2396
2396
2397 mail = ActionMailer::Base.deliveries.last
2397 mail = ActionMailer::Base.deliveries.last
2398 assert_mail_body_match notes, mail
2398 assert_mail_body_match notes, mail
2399 end
2399 end
2400
2400
2401 def test_put_update_with_note_and_spent_time
2401 def test_put_update_with_note_and_spent_time
2402 @request.session[:user_id] = 2
2402 @request.session[:user_id] = 2
2403 spent_hours_before = Issue.find(1).spent_hours
2403 spent_hours_before = Issue.find(1).spent_hours
2404 assert_difference('TimeEntry.count') do
2404 assert_difference('TimeEntry.count') do
2405 put :update,
2405 put :update,
2406 :id => 1,
2406 :id => 1,
2407 :notes => '2.5 hours added',
2407 :notes => '2.5 hours added',
2408 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
2408 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
2409 end
2409 end
2410 assert_redirected_to :action => 'show', :id => '1'
2410 assert_redirected_to :action => 'show', :id => '1'
2411
2411
2412 issue = Issue.find(1)
2412 issue = Issue.find(1)
2413
2413
2414 j = Journal.find(:first, :order => 'id DESC')
2414 j = Journal.find(:first, :order => 'id DESC')
2415 assert_equal '2.5 hours added', j.notes
2415 assert_equal '2.5 hours added', j.notes
2416 assert_equal 0, j.details.size
2416 assert_equal 0, j.details.size
2417
2417
2418 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
2418 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
2419 assert_not_nil t
2419 assert_not_nil t
2420 assert_equal 2.5, t.hours
2420 assert_equal 2.5, t.hours
2421 assert_equal spent_hours_before + 2.5, issue.spent_hours
2421 assert_equal spent_hours_before + 2.5, issue.spent_hours
2422 end
2422 end
2423
2423
2424 def test_put_update_with_attachment_only
2424 def test_put_update_with_attachment_only
2425 set_tmp_attachments_directory
2425 set_tmp_attachments_directory
2426
2426
2427 # Delete all fixtured journals, a race condition can occur causing the wrong
2427 # Delete all fixtured journals, a race condition can occur causing the wrong
2428 # journal to get fetched in the next find.
2428 # journal to get fetched in the next find.
2429 Journal.delete_all
2429 Journal.delete_all
2430
2430
2431 # anonymous user
2431 # anonymous user
2432 assert_difference 'Attachment.count' do
2432 assert_difference 'Attachment.count' do
2433 put :update, :id => 1,
2433 put :update, :id => 1,
2434 :notes => '',
2434 :notes => '',
2435 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2435 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2436 end
2436 end
2437
2437
2438 assert_redirected_to :action => 'show', :id => '1'
2438 assert_redirected_to :action => 'show', :id => '1'
2439 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
2439 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
2440 assert j.notes.blank?
2440 assert j.notes.blank?
2441 assert_equal 1, j.details.size
2441 assert_equal 1, j.details.size
2442 assert_equal 'testfile.txt', j.details.first.value
2442 assert_equal 'testfile.txt', j.details.first.value
2443 assert_equal User.anonymous, j.user
2443 assert_equal User.anonymous, j.user
2444
2444
2445 attachment = Attachment.first(:order => 'id DESC')
2445 attachment = Attachment.first(:order => 'id DESC')
2446 assert_equal Issue.find(1), attachment.container
2446 assert_equal Issue.find(1), attachment.container
2447 assert_equal User.anonymous, attachment.author
2447 assert_equal User.anonymous, attachment.author
2448 assert_equal 'testfile.txt', attachment.filename
2448 assert_equal 'testfile.txt', attachment.filename
2449 assert_equal 'text/plain', attachment.content_type
2449 assert_equal 'text/plain', attachment.content_type
2450 assert_equal 'test file', attachment.description
2450 assert_equal 'test file', attachment.description
2451 assert_equal 59, attachment.filesize
2451 assert_equal 59, attachment.filesize
2452 assert File.exists?(attachment.diskfile)
2452 assert File.exists?(attachment.diskfile)
2453 assert_equal 59, File.size(attachment.diskfile)
2453 assert_equal 59, File.size(attachment.diskfile)
2454
2454
2455 mail = ActionMailer::Base.deliveries.last
2455 mail = ActionMailer::Base.deliveries.last
2456 assert_mail_body_match 'testfile.txt', mail
2456 assert_mail_body_match 'testfile.txt', mail
2457 end
2457 end
2458
2458
2459 def test_put_update_with_failure_should_save_attachments
2459 def test_put_update_with_failure_should_save_attachments
2460 set_tmp_attachments_directory
2460 set_tmp_attachments_directory
2461 @request.session[:user_id] = 2
2461 @request.session[:user_id] = 2
2462
2462
2463 assert_no_difference 'Journal.count' do
2463 assert_no_difference 'Journal.count' do
2464 assert_difference 'Attachment.count' do
2464 assert_difference 'Attachment.count' do
2465 put :update, :id => 1,
2465 put :update, :id => 1,
2466 :issue => { :subject => '' },
2466 :issue => { :subject => '' },
2467 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2467 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2468 assert_response :success
2468 assert_response :success
2469 assert_template 'edit'
2469 assert_template 'edit'
2470 end
2470 end
2471 end
2471 end
2472
2472
2473 attachment = Attachment.first(:order => 'id DESC')
2473 attachment = Attachment.first(:order => 'id DESC')
2474 assert_equal 'testfile.txt', attachment.filename
2474 assert_equal 'testfile.txt', attachment.filename
2475 assert File.exists?(attachment.diskfile)
2475 assert File.exists?(attachment.diskfile)
2476 assert_nil attachment.container
2476 assert_nil attachment.container
2477
2477
2478 assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token}
2478 assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token}
2479 assert_tag 'span', :content => /testfile.txt/
2479 assert_tag 'span', :content => /testfile.txt/
2480 end
2480 end
2481
2481
2482 def test_put_update_with_failure_should_keep_saved_attachments
2482 def test_put_update_with_failure_should_keep_saved_attachments
2483 set_tmp_attachments_directory
2483 set_tmp_attachments_directory
2484 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2484 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2485 @request.session[:user_id] = 2
2485 @request.session[:user_id] = 2
2486
2486
2487 assert_no_difference 'Journal.count' do
2487 assert_no_difference 'Journal.count' do
2488 assert_no_difference 'Attachment.count' do
2488 assert_no_difference 'Attachment.count' do
2489 put :update, :id => 1,
2489 put :update, :id => 1,
2490 :issue => { :subject => '' },
2490 :issue => { :subject => '' },
2491 :attachments => {'p0' => {'token' => attachment.token}}
2491 :attachments => {'p0' => {'token' => attachment.token}}
2492 assert_response :success
2492 assert_response :success
2493 assert_template 'edit'
2493 assert_template 'edit'
2494 end
2494 end
2495 end
2495 end
2496
2496
2497 assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token}
2497 assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token}
2498 assert_tag 'span', :content => /testfile.txt/
2498 assert_tag 'span', :content => /testfile.txt/
2499 end
2499 end
2500
2500
2501 def test_put_update_should_attach_saved_attachments
2501 def test_put_update_should_attach_saved_attachments
2502 set_tmp_attachments_directory
2502 set_tmp_attachments_directory
2503 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2503 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2504 @request.session[:user_id] = 2
2504 @request.session[:user_id] = 2
2505
2505
2506 assert_difference 'Journal.count' do
2506 assert_difference 'Journal.count' do
2507 assert_difference 'JournalDetail.count' do
2507 assert_difference 'JournalDetail.count' do
2508 assert_no_difference 'Attachment.count' do
2508 assert_no_difference 'Attachment.count' do
2509 put :update, :id => 1,
2509 put :update, :id => 1,
2510 :notes => 'Attachment added',
2510 :notes => 'Attachment added',
2511 :attachments => {'p0' => {'token' => attachment.token}}
2511 :attachments => {'p0' => {'token' => attachment.token}}
2512 assert_redirected_to '/issues/1'
2512 assert_redirected_to '/issues/1'
2513 end
2513 end
2514 end
2514 end
2515 end
2515 end
2516
2516
2517 attachment.reload
2517 attachment.reload
2518 assert_equal Issue.find(1), attachment.container
2518 assert_equal Issue.find(1), attachment.container
2519
2519
2520 journal = Journal.first(:order => 'id DESC')
2520 journal = Journal.first(:order => 'id DESC')
2521 assert_equal 1, journal.details.size
2521 assert_equal 1, journal.details.size
2522 assert_equal 'testfile.txt', journal.details.first.value
2522 assert_equal 'testfile.txt', journal.details.first.value
2523 end
2523 end
2524
2524
2525 def test_put_update_with_attachment_that_fails_to_save
2525 def test_put_update_with_attachment_that_fails_to_save
2526 set_tmp_attachments_directory
2526 set_tmp_attachments_directory
2527
2527
2528 # Delete all fixtured journals, a race condition can occur causing the wrong
2528 # Delete all fixtured journals, a race condition can occur causing the wrong
2529 # journal to get fetched in the next find.
2529 # journal to get fetched in the next find.
2530 Journal.delete_all
2530 Journal.delete_all
2531
2531
2532 # Mock out the unsaved attachment
2532 # Mock out the unsaved attachment
2533 Attachment.any_instance.stubs(:create).returns(Attachment.new)
2533 Attachment.any_instance.stubs(:create).returns(Attachment.new)
2534
2534
2535 # anonymous user
2535 # anonymous user
2536 put :update,
2536 put :update,
2537 :id => 1,
2537 :id => 1,
2538 :notes => '',
2538 :notes => '',
2539 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
2539 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
2540 assert_redirected_to :action => 'show', :id => '1'
2540 assert_redirected_to :action => 'show', :id => '1'
2541 assert_equal '1 file(s) could not be saved.', flash[:warning]
2541 assert_equal '1 file(s) could not be saved.', flash[:warning]
2542 end
2542 end
2543
2543
2544 def test_put_update_with_no_change
2544 def test_put_update_with_no_change
2545 issue = Issue.find(1)
2545 issue = Issue.find(1)
2546 issue.journals.clear
2546 issue.journals.clear
2547 ActionMailer::Base.deliveries.clear
2547 ActionMailer::Base.deliveries.clear
2548
2548
2549 put :update,
2549 put :update,
2550 :id => 1,
2550 :id => 1,
2551 :notes => ''
2551 :notes => ''
2552 assert_redirected_to :action => 'show', :id => '1'
2552 assert_redirected_to :action => 'show', :id => '1'
2553
2553
2554 issue.reload
2554 issue.reload
2555 assert issue.journals.empty?
2555 assert issue.journals.empty?
2556 # No email should be sent
2556 # No email should be sent
2557 assert ActionMailer::Base.deliveries.empty?
2557 assert ActionMailer::Base.deliveries.empty?
2558 end
2558 end
2559
2559
2560 def test_put_update_should_send_a_notification
2560 def test_put_update_should_send_a_notification
2561 @request.session[:user_id] = 2
2561 @request.session[:user_id] = 2
2562 ActionMailer::Base.deliveries.clear
2562 ActionMailer::Base.deliveries.clear
2563 issue = Issue.find(1)
2563 issue = Issue.find(1)
2564 old_subject = issue.subject
2564 old_subject = issue.subject
2565 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
2565 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
2566
2566
2567 put :update, :id => 1, :issue => {:subject => new_subject,
2567 put :update, :id => 1, :issue => {:subject => new_subject,
2568 :priority_id => '6',
2568 :priority_id => '6',
2569 :category_id => '1' # no change
2569 :category_id => '1' # no change
2570 }
2570 }
2571 assert_equal 1, ActionMailer::Base.deliveries.size
2571 assert_equal 1, ActionMailer::Base.deliveries.size
2572 end
2572 end
2573
2573
2574 def test_put_update_with_invalid_spent_time_hours_only
2574 def test_put_update_with_invalid_spent_time_hours_only
2575 @request.session[:user_id] = 2
2575 @request.session[:user_id] = 2
2576 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
2576 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
2577
2577
2578 assert_no_difference('Journal.count') do
2578 assert_no_difference('Journal.count') do
2579 put :update,
2579 put :update,
2580 :id => 1,
2580 :id => 1,
2581 :notes => notes,
2581 :notes => notes,
2582 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
2582 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
2583 end
2583 end
2584 assert_response :success
2584 assert_response :success
2585 assert_template 'edit'
2585 assert_template 'edit'
2586
2586
2587 assert_error_tag :descendant => {:content => /Activity can't be blank/}
2587 assert_error_tag :descendant => {:content => /Activity can't be blank/}
2588 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
2588 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
2589 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
2589 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
2590 end
2590 end
2591
2591
2592 def test_put_update_with_invalid_spent_time_comments_only
2592 def test_put_update_with_invalid_spent_time_comments_only
2593 @request.session[:user_id] = 2
2593 @request.session[:user_id] = 2
2594 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
2594 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
2595
2595
2596 assert_no_difference('Journal.count') do
2596 assert_no_difference('Journal.count') do
2597 put :update,
2597 put :update,
2598 :id => 1,
2598 :id => 1,
2599 :notes => notes,
2599 :notes => notes,
2600 :time_entry => {"comments"=>"this is my comment", "activity_id"=>"", "hours"=>""}
2600 :time_entry => {"comments"=>"this is my comment", "activity_id"=>"", "hours"=>""}
2601 end
2601 end
2602 assert_response :success
2602 assert_response :success
2603 assert_template 'edit'
2603 assert_template 'edit'
2604
2604
2605 assert_error_tag :descendant => {:content => /Activity can't be blank/}
2605 assert_error_tag :descendant => {:content => /Activity can't be blank/}
2606 assert_error_tag :descendant => {:content => /Hours can't be blank/}
2606 assert_error_tag :descendant => {:content => /Hours can't be blank/}
2607 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
2607 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
2608 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => "this is my comment" }
2608 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => "this is my comment" }
2609 end
2609 end
2610
2610
2611 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
2611 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
2612 issue = Issue.find(2)
2612 issue = Issue.find(2)
2613 @request.session[:user_id] = 2
2613 @request.session[:user_id] = 2
2614
2614
2615 put :update,
2615 put :update,
2616 :id => issue.id,
2616 :id => issue.id,
2617 :issue => {
2617 :issue => {
2618 :fixed_version_id => 4
2618 :fixed_version_id => 4
2619 }
2619 }
2620
2620
2621 assert_response :redirect
2621 assert_response :redirect
2622 issue.reload
2622 issue.reload
2623 assert_equal 4, issue.fixed_version_id
2623 assert_equal 4, issue.fixed_version_id
2624 assert_not_equal issue.project_id, issue.fixed_version.project_id
2624 assert_not_equal issue.project_id, issue.fixed_version.project_id
2625 end
2625 end
2626
2626
2627 def test_put_update_should_redirect_back_using_the_back_url_parameter
2627 def test_put_update_should_redirect_back_using_the_back_url_parameter
2628 issue = Issue.find(2)
2628 issue = Issue.find(2)
2629 @request.session[:user_id] = 2
2629 @request.session[:user_id] = 2
2630
2630
2631 put :update,
2631 put :update,
2632 :id => issue.id,
2632 :id => issue.id,
2633 :issue => {
2633 :issue => {
2634 :fixed_version_id => 4
2634 :fixed_version_id => 4
2635 },
2635 },
2636 :back_url => '/issues'
2636 :back_url => '/issues'
2637
2637
2638 assert_response :redirect
2638 assert_response :redirect
2639 assert_redirected_to '/issues'
2639 assert_redirected_to '/issues'
2640 end
2640 end
2641
2641
2642 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
2642 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
2643 issue = Issue.find(2)
2643 issue = Issue.find(2)
2644 @request.session[:user_id] = 2
2644 @request.session[:user_id] = 2
2645
2645
2646 put :update,
2646 put :update,
2647 :id => issue.id,
2647 :id => issue.id,
2648 :issue => {
2648 :issue => {
2649 :fixed_version_id => 4
2649 :fixed_version_id => 4
2650 },
2650 },
2651 :back_url => 'http://google.com'
2651 :back_url => 'http://google.com'
2652
2652
2653 assert_response :redirect
2653 assert_response :redirect
2654 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
2654 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
2655 end
2655 end
2656
2656
2657 def test_get_bulk_edit
2657 def test_get_bulk_edit
2658 @request.session[:user_id] = 2
2658 @request.session[:user_id] = 2
2659 get :bulk_edit, :ids => [1, 2]
2659 get :bulk_edit, :ids => [1, 2]
2660 assert_response :success
2660 assert_response :success
2661 assert_template 'bulk_edit'
2661 assert_template 'bulk_edit'
2662
2662
2663 assert_tag :select, :attributes => {:name => 'issue[project_id]'}
2663 assert_tag :select, :attributes => {:name => 'issue[project_id]'}
2664 assert_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
2664 assert_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
2665
2665
2666 # Project specific custom field, date type
2666 # Project specific custom field, date type
2667 field = CustomField.find(9)
2667 field = CustomField.find(9)
2668 assert !field.is_for_all?
2668 assert !field.is_for_all?
2669 assert_equal 'date', field.field_format
2669 assert_equal 'date', field.field_format
2670 assert_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
2670 assert_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
2671
2671
2672 # System wide custom field
2672 # System wide custom field
2673 assert CustomField.find(1).is_for_all?
2673 assert CustomField.find(1).is_for_all?
2674 assert_tag :select, :attributes => {:name => 'issue[custom_field_values][1]'}
2674 assert_tag :select, :attributes => {:name => 'issue[custom_field_values][1]'}
2675
2675
2676 # Be sure we don't display inactive IssuePriorities
2676 # Be sure we don't display inactive IssuePriorities
2677 assert ! IssuePriority.find(15).active?
2677 assert ! IssuePriority.find(15).active?
2678 assert_no_tag :option, :attributes => {:value => '15'},
2678 assert_no_tag :option, :attributes => {:value => '15'},
2679 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
2679 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
2680 end
2680 end
2681
2681
2682 def test_get_bulk_edit_on_different_projects
2682 def test_get_bulk_edit_on_different_projects
2683 @request.session[:user_id] = 2
2683 @request.session[:user_id] = 2
2684 get :bulk_edit, :ids => [1, 2, 6]
2684 get :bulk_edit, :ids => [1, 2, 6]
2685 assert_response :success
2685 assert_response :success
2686 assert_template 'bulk_edit'
2686 assert_template 'bulk_edit'
2687
2687
2688 # Can not set issues from different projects as children of an issue
2688 # Can not set issues from different projects as children of an issue
2689 assert_no_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
2689 assert_no_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
2690
2690
2691 # Project specific custom field, date type
2691 # Project specific custom field, date type
2692 field = CustomField.find(9)
2692 field = CustomField.find(9)
2693 assert !field.is_for_all?
2693 assert !field.is_for_all?
2694 assert !field.project_ids.include?(Issue.find(6).project_id)
2694 assert !field.project_ids.include?(Issue.find(6).project_id)
2695 assert_no_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
2695 assert_no_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
2696 end
2696 end
2697
2697
2698 def test_get_bulk_edit_with_user_custom_field
2698 def test_get_bulk_edit_with_user_custom_field
2699 field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true)
2699 field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true)
2700
2700
2701 @request.session[:user_id] = 2
2701 @request.session[:user_id] = 2
2702 get :bulk_edit, :ids => [1, 2]
2702 get :bulk_edit, :ids => [1, 2]
2703 assert_response :success
2703 assert_response :success
2704 assert_template 'bulk_edit'
2704 assert_template 'bulk_edit'
2705
2705
2706 assert_tag :select,
2706 assert_tag :select,
2707 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
2707 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
2708 :children => {
2708 :children => {
2709 :only => {:tag => 'option'},
2709 :only => {:tag => 'option'},
2710 :count => Project.find(1).users.count + 2 # "no change" + "none" options
2710 :count => Project.find(1).users.count + 2 # "no change" + "none" options
2711 }
2711 }
2712 end
2712 end
2713
2713
2714 def test_get_bulk_edit_with_version_custom_field
2714 def test_get_bulk_edit_with_version_custom_field
2715 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true)
2715 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true)
2716
2716
2717 @request.session[:user_id] = 2
2717 @request.session[:user_id] = 2
2718 get :bulk_edit, :ids => [1, 2]
2718 get :bulk_edit, :ids => [1, 2]
2719 assert_response :success
2719 assert_response :success
2720 assert_template 'bulk_edit'
2720 assert_template 'bulk_edit'
2721
2721
2722 assert_tag :select,
2722 assert_tag :select,
2723 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
2723 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
2724 :children => {
2724 :children => {
2725 :only => {:tag => 'option'},
2725 :only => {:tag => 'option'},
2726 :count => Project.find(1).shared_versions.count + 2 # "no change" + "none" options
2726 :count => Project.find(1).shared_versions.count + 2 # "no change" + "none" options
2727 }
2727 }
2728 end
2728 end
2729
2729
2730 def test_get_bulk_edit_with_multi_custom_field
2730 def test_get_bulk_edit_with_multi_custom_field
2731 field = CustomField.find(1)
2731 field = CustomField.find(1)
2732 field.update_attribute :multiple, true
2732 field.update_attribute :multiple, true
2733
2733
2734 @request.session[:user_id] = 2
2734 @request.session[:user_id] = 2
2735 get :bulk_edit, :ids => [1, 2]
2735 get :bulk_edit, :ids => [1, 2]
2736 assert_response :success
2736 assert_response :success
2737 assert_template 'bulk_edit'
2737 assert_template 'bulk_edit'
2738
2738
2739 assert_tag :select,
2739 assert_tag :select,
2740 :attributes => {:name => "issue[custom_field_values][1][]"},
2740 :attributes => {:name => "issue[custom_field_values][1][]"},
2741 :children => {
2741 :children => {
2742 :only => {:tag => 'option'},
2742 :only => {:tag => 'option'},
2743 :count => field.possible_values.size + 1 # "none" options
2743 :count => field.possible_values.size + 1 # "none" options
2744 }
2744 }
2745 end
2745 end
2746
2746
2747 def test_bulk_edit_should_only_propose_statuses_allowed_for_all_issues
2747 def test_bulk_edit_should_only_propose_statuses_allowed_for_all_issues
2748 Workflow.delete_all
2748 Workflow.delete_all
2749 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 1)
2749 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 1)
2750 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
2750 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
2751 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
2751 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
2752 Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1)
2752 Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1)
2753 Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 3)
2753 Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 3)
2754 Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 5)
2754 Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 5)
2755 @request.session[:user_id] = 2
2755 @request.session[:user_id] = 2
2756 get :bulk_edit, :ids => [1, 2]
2756 get :bulk_edit, :ids => [1, 2]
2757
2757
2758 assert_response :success
2758 assert_response :success
2759 statuses = assigns(:available_statuses)
2759 statuses = assigns(:available_statuses)
2760 assert_not_nil statuses
2760 assert_not_nil statuses
2761 assert_equal [1, 3], statuses.map(&:id).sort
2761 assert_equal [1, 3], statuses.map(&:id).sort
2762
2762
2763 assert_tag 'select', :attributes => {:name => 'issue[status_id]'},
2763 assert_tag 'select', :attributes => {:name => 'issue[status_id]'},
2764 :children => {:count => 3} # 2 statuses + "no change" option
2764 :children => {:count => 3} # 2 statuses + "no change" option
2765 end
2765 end
2766
2766
2767 def test_bulk_edit_should_propose_target_project_open_shared_versions
2767 def test_bulk_edit_should_propose_target_project_open_shared_versions
2768 @request.session[:user_id] = 2
2768 @request.session[:user_id] = 2
2769 post :bulk_edit, :ids => [1, 2, 6], :issue => {:project_id => 1}
2769 post :bulk_edit, :ids => [1, 2, 6], :issue => {:project_id => 1}
2770 assert_response :success
2770 assert_response :success
2771 assert_template 'bulk_edit'
2771 assert_template 'bulk_edit'
2772 assert_equal Project.find(1).shared_versions.open.all.sort, assigns(:versions).sort
2772 assert_equal Project.find(1).shared_versions.open.all.sort, assigns(:versions).sort
2773 assert_tag 'select',
2773 assert_tag 'select',
2774 :attributes => {:name => 'issue[fixed_version_id]'},
2774 :attributes => {:name => 'issue[fixed_version_id]'},
2775 :descendant => {:tag => 'option', :content => '2.0'}
2775 :descendant => {:tag => 'option', :content => '2.0'}
2776 end
2776 end
2777
2777
2778 def test_bulk_edit_should_propose_target_project_categories
2778 def test_bulk_edit_should_propose_target_project_categories
2779 @request.session[:user_id] = 2
2779 @request.session[:user_id] = 2
2780 post :bulk_edit, :ids => [1, 2, 6], :issue => {:project_id => 1}
2780 post :bulk_edit, :ids => [1, 2, 6], :issue => {:project_id => 1}
2781 assert_response :success
2781 assert_response :success
2782 assert_template 'bulk_edit'
2782 assert_template 'bulk_edit'
2783 assert_equal Project.find(1).issue_categories.sort, assigns(:categories).sort
2783 assert_equal Project.find(1).issue_categories.sort, assigns(:categories).sort
2784 assert_tag 'select',
2784 assert_tag 'select',
2785 :attributes => {:name => 'issue[category_id]'},
2785 :attributes => {:name => 'issue[category_id]'},
2786 :descendant => {:tag => 'option', :content => 'Recipes'}
2786 :descendant => {:tag => 'option', :content => 'Recipes'}
2787 end
2787 end
2788
2788
2789 def test_bulk_update
2789 def test_bulk_update
2790 @request.session[:user_id] = 2
2790 @request.session[:user_id] = 2
2791 # update issues priority
2791 # update issues priority
2792 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
2792 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
2793 :issue => {:priority_id => 7,
2793 :issue => {:priority_id => 7,
2794 :assigned_to_id => '',
2794 :assigned_to_id => '',
2795 :custom_field_values => {'2' => ''}}
2795 :custom_field_values => {'2' => ''}}
2796
2796
2797 assert_response 302
2797 assert_response 302
2798 # check that the issues were updated
2798 # check that the issues were updated
2799 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
2799 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
2800
2800
2801 issue = Issue.find(1)
2801 issue = Issue.find(1)
2802 journal = issue.journals.find(:first, :order => 'created_on DESC')
2802 journal = issue.journals.find(:first, :order => 'created_on DESC')
2803 assert_equal '125', issue.custom_value_for(2).value
2803 assert_equal '125', issue.custom_value_for(2).value
2804 assert_equal 'Bulk editing', journal.notes
2804 assert_equal 'Bulk editing', journal.notes
2805 assert_equal 1, journal.details.size
2805 assert_equal 1, journal.details.size
2806 end
2806 end
2807
2807
2808 def test_bulk_update_with_group_assignee
2808 def test_bulk_update_with_group_assignee
2809 group = Group.find(11)
2809 group = Group.find(11)
2810 project = Project.find(1)
2810 project = Project.find(1)
2811 project.members << Member.new(:principal => group, :roles => [Role.givable.first])
2811 project.members << Member.new(:principal => group, :roles => [Role.givable.first])
2812
2812
2813 @request.session[:user_id] = 2
2813 @request.session[:user_id] = 2
2814 # update issues assignee
2814 # update issues assignee
2815 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
2815 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
2816 :issue => {:priority_id => '',
2816 :issue => {:priority_id => '',
2817 :assigned_to_id => group.id,
2817 :assigned_to_id => group.id,
2818 :custom_field_values => {'2' => ''}}
2818 :custom_field_values => {'2' => ''}}
2819
2819
2820 assert_response 302
2820 assert_response 302
2821 assert_equal [group, group], Issue.find_all_by_id([1, 2]).collect {|i| i.assigned_to}
2821 assert_equal [group, group], Issue.find_all_by_id([1, 2]).collect {|i| i.assigned_to}
2822 end
2822 end
2823
2823
2824 def test_bulk_update_on_different_projects
2824 def test_bulk_update_on_different_projects
2825 @request.session[:user_id] = 2
2825 @request.session[:user_id] = 2
2826 # update issues priority
2826 # update issues priority
2827 post :bulk_update, :ids => [1, 2, 6], :notes => 'Bulk editing',
2827 post :bulk_update, :ids => [1, 2, 6], :notes => 'Bulk editing',
2828 :issue => {:priority_id => 7,
2828 :issue => {:priority_id => 7,
2829 :assigned_to_id => '',
2829 :assigned_to_id => '',
2830 :custom_field_values => {'2' => ''}}
2830 :custom_field_values => {'2' => ''}}
2831
2831
2832 assert_response 302
2832 assert_response 302
2833 # check that the issues were updated
2833 # check that the issues were updated
2834 assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
2834 assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
2835
2835
2836 issue = Issue.find(1)
2836 issue = Issue.find(1)
2837 journal = issue.journals.find(:first, :order => 'created_on DESC')
2837 journal = issue.journals.find(:first, :order => 'created_on DESC')
2838 assert_equal '125', issue.custom_value_for(2).value
2838 assert_equal '125', issue.custom_value_for(2).value
2839 assert_equal 'Bulk editing', journal.notes
2839 assert_equal 'Bulk editing', journal.notes
2840 assert_equal 1, journal.details.size
2840 assert_equal 1, journal.details.size
2841 end
2841 end
2842
2842
2843 def test_bulk_update_on_different_projects_without_rights
2843 def test_bulk_update_on_different_projects_without_rights
2844 @request.session[:user_id] = 3
2844 @request.session[:user_id] = 3
2845 user = User.find(3)
2845 user = User.find(3)
2846 action = { :controller => "issues", :action => "bulk_update" }
2846 action = { :controller => "issues", :action => "bulk_update" }
2847 assert user.allowed_to?(action, Issue.find(1).project)
2847 assert user.allowed_to?(action, Issue.find(1).project)
2848 assert ! user.allowed_to?(action, Issue.find(6).project)
2848 assert ! user.allowed_to?(action, Issue.find(6).project)
2849 post :bulk_update, :ids => [1, 6], :notes => 'Bulk should fail',
2849 post :bulk_update, :ids => [1, 6], :notes => 'Bulk should fail',
2850 :issue => {:priority_id => 7,
2850 :issue => {:priority_id => 7,
2851 :assigned_to_id => '',
2851 :assigned_to_id => '',
2852 :custom_field_values => {'2' => ''}}
2852 :custom_field_values => {'2' => ''}}
2853 assert_response 403
2853 assert_response 403
2854 assert_not_equal "Bulk should fail", Journal.last.notes
2854 assert_not_equal "Bulk should fail", Journal.last.notes
2855 end
2855 end
2856
2856
2857 def test_bullk_update_should_send_a_notification
2857 def test_bullk_update_should_send_a_notification
2858 @request.session[:user_id] = 2
2858 @request.session[:user_id] = 2
2859 ActionMailer::Base.deliveries.clear
2859 ActionMailer::Base.deliveries.clear
2860 post(:bulk_update,
2860 post(:bulk_update,
2861 {
2861 {
2862 :ids => [1, 2],
2862 :ids => [1, 2],
2863 :notes => 'Bulk editing',
2863 :notes => 'Bulk editing',
2864 :issue => {
2864 :issue => {
2865 :priority_id => 7,
2865 :priority_id => 7,
2866 :assigned_to_id => '',
2866 :assigned_to_id => '',
2867 :custom_field_values => {'2' => ''}
2867 :custom_field_values => {'2' => ''}
2868 }
2868 }
2869 })
2869 })
2870
2870
2871 assert_response 302
2871 assert_response 302
2872 assert_equal 2, ActionMailer::Base.deliveries.size
2872 assert_equal 2, ActionMailer::Base.deliveries.size
2873 end
2873 end
2874
2874
2875 def test_bulk_update_project
2875 def test_bulk_update_project
2876 @request.session[:user_id] = 2
2876 @request.session[:user_id] = 2
2877 post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'}
2877 post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'}
2878 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
2878 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
2879 # Issues moved to project 2
2879 # Issues moved to project 2
2880 assert_equal 2, Issue.find(1).project_id
2880 assert_equal 2, Issue.find(1).project_id
2881 assert_equal 2, Issue.find(2).project_id
2881 assert_equal 2, Issue.find(2).project_id
2882 # No tracker change
2882 # No tracker change
2883 assert_equal 1, Issue.find(1).tracker_id
2883 assert_equal 1, Issue.find(1).tracker_id
2884 assert_equal 2, Issue.find(2).tracker_id
2884 assert_equal 2, Issue.find(2).tracker_id
2885 end
2885 end
2886
2886
2887 def test_bulk_update_project_on_single_issue_should_follow_when_needed
2887 def test_bulk_update_project_on_single_issue_should_follow_when_needed
2888 @request.session[:user_id] = 2
2888 @request.session[:user_id] = 2
2889 post :bulk_update, :id => 1, :issue => {:project_id => '2'}, :follow => '1'
2889 post :bulk_update, :id => 1, :issue => {:project_id => '2'}, :follow => '1'
2890 assert_redirected_to '/issues/1'
2890 assert_redirected_to '/issues/1'
2891 end
2891 end
2892
2892
2893 def test_bulk_update_project_on_multiple_issues_should_follow_when_needed
2893 def test_bulk_update_project_on_multiple_issues_should_follow_when_needed
2894 @request.session[:user_id] = 2
2894 @request.session[:user_id] = 2
2895 post :bulk_update, :id => [1, 2], :issue => {:project_id => '2'}, :follow => '1'
2895 post :bulk_update, :id => [1, 2], :issue => {:project_id => '2'}, :follow => '1'
2896 assert_redirected_to '/projects/onlinestore/issues'
2896 assert_redirected_to '/projects/onlinestore/issues'
2897 end
2897 end
2898
2898
2899 def test_bulk_update_tracker
2899 def test_bulk_update_tracker
2900 @request.session[:user_id] = 2
2900 @request.session[:user_id] = 2
2901 post :bulk_update, :ids => [1, 2], :issue => {:tracker_id => '2'}
2901 post :bulk_update, :ids => [1, 2], :issue => {:tracker_id => '2'}
2902 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
2902 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
2903 assert_equal 2, Issue.find(1).tracker_id
2903 assert_equal 2, Issue.find(1).tracker_id
2904 assert_equal 2, Issue.find(2).tracker_id
2904 assert_equal 2, Issue.find(2).tracker_id
2905 end
2905 end
2906
2906
2907 def test_bulk_update_status
2907 def test_bulk_update_status
2908 @request.session[:user_id] = 2
2908 @request.session[:user_id] = 2
2909 # update issues priority
2909 # update issues priority
2910 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
2910 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
2911 :issue => {:priority_id => '',
2911 :issue => {:priority_id => '',
2912 :assigned_to_id => '',
2912 :assigned_to_id => '',
2913 :status_id => '5'}
2913 :status_id => '5'}
2914
2914
2915 assert_response 302
2915 assert_response 302
2916 issue = Issue.find(1)
2916 issue = Issue.find(1)
2917 assert issue.closed?
2917 assert issue.closed?
2918 end
2918 end
2919
2919
2920 def test_bulk_update_priority
2920 def test_bulk_update_priority
2921 @request.session[:user_id] = 2
2921 @request.session[:user_id] = 2
2922 post :bulk_update, :ids => [1, 2], :issue => {:priority_id => 6}
2922 post :bulk_update, :ids => [1, 2], :issue => {:priority_id => 6}
2923
2923
2924 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
2924 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
2925 assert_equal 6, Issue.find(1).priority_id
2925 assert_equal 6, Issue.find(1).priority_id
2926 assert_equal 6, Issue.find(2).priority_id
2926 assert_equal 6, Issue.find(2).priority_id
2927 end
2927 end
2928
2928
2929 def test_bulk_update_with_notes
2929 def test_bulk_update_with_notes
2930 @request.session[:user_id] = 2
2930 @request.session[:user_id] = 2
2931 post :bulk_update, :ids => [1, 2], :notes => 'Moving two issues'
2931 post :bulk_update, :ids => [1, 2], :notes => 'Moving two issues'
2932
2932
2933 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
2933 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
2934 assert_equal 'Moving two issues', Issue.find(1).journals.sort_by(&:id).last.notes
2934 assert_equal 'Moving two issues', Issue.find(1).journals.sort_by(&:id).last.notes
2935 assert_equal 'Moving two issues', Issue.find(2).journals.sort_by(&:id).last.notes
2935 assert_equal 'Moving two issues', Issue.find(2).journals.sort_by(&:id).last.notes
2936 end
2936 end
2937
2937
2938 def test_bulk_update_parent_id
2938 def test_bulk_update_parent_id
2939 @request.session[:user_id] = 2
2939 @request.session[:user_id] = 2
2940 post :bulk_update, :ids => [1, 3],
2940 post :bulk_update, :ids => [1, 3],
2941 :notes => 'Bulk editing parent',
2941 :notes => 'Bulk editing parent',
2942 :issue => {:priority_id => '', :assigned_to_id => '', :status_id => '', :parent_issue_id => '2'}
2942 :issue => {:priority_id => '', :assigned_to_id => '', :status_id => '', :parent_issue_id => '2'}
2943
2943
2944 assert_response 302
2944 assert_response 302
2945 parent = Issue.find(2)
2945 parent = Issue.find(2)
2946 assert_equal parent.id, Issue.find(1).parent_id
2946 assert_equal parent.id, Issue.find(1).parent_id
2947 assert_equal parent.id, Issue.find(3).parent_id
2947 assert_equal parent.id, Issue.find(3).parent_id
2948 assert_equal [1, 3], parent.children.collect(&:id).sort
2948 assert_equal [1, 3], parent.children.collect(&:id).sort
2949 end
2949 end
2950
2950
2951 def test_bulk_update_custom_field
2951 def test_bulk_update_custom_field
2952 @request.session[:user_id] = 2
2952 @request.session[:user_id] = 2
2953 # update issues priority
2953 # update issues priority
2954 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
2954 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
2955 :issue => {:priority_id => '',
2955 :issue => {:priority_id => '',
2956 :assigned_to_id => '',
2956 :assigned_to_id => '',
2957 :custom_field_values => {'2' => '777'}}
2957 :custom_field_values => {'2' => '777'}}
2958
2958
2959 assert_response 302
2959 assert_response 302
2960
2960
2961 issue = Issue.find(1)
2961 issue = Issue.find(1)
2962 journal = issue.journals.find(:first, :order => 'created_on DESC')
2962 journal = issue.journals.find(:first, :order => 'created_on DESC')
2963 assert_equal '777', issue.custom_value_for(2).value
2963 assert_equal '777', issue.custom_value_for(2).value
2964 assert_equal 1, journal.details.size
2964 assert_equal 1, journal.details.size
2965 assert_equal '125', journal.details.first.old_value
2965 assert_equal '125', journal.details.first.old_value
2966 assert_equal '777', journal.details.first.value
2966 assert_equal '777', journal.details.first.value
2967 end
2967 end
2968
2968
2969 def test_bulk_update_custom_field_to_blank
2969 def test_bulk_update_custom_field_to_blank
2970 @request.session[:user_id] = 2
2970 @request.session[:user_id] = 2
2971 post :bulk_update, :ids => [1, 3], :notes => 'Bulk editing custom field',
2971 post :bulk_update, :ids => [1, 3], :notes => 'Bulk editing custom field',
2972 :issue => {:priority_id => '',
2972 :issue => {:priority_id => '',
2973 :assigned_to_id => '',
2973 :assigned_to_id => '',
2974 :custom_field_values => {'1' => '__none__'}}
2974 :custom_field_values => {'1' => '__none__'}}
2975 assert_response 302
2975 assert_response 302
2976 assert_equal '', Issue.find(1).custom_field_value(1)
2976 assert_equal '', Issue.find(1).custom_field_value(1)
2977 assert_equal '', Issue.find(3).custom_field_value(1)
2977 assert_equal '', Issue.find(3).custom_field_value(1)
2978 end
2978 end
2979
2979
2980 def test_bulk_update_multi_custom_field
2980 def test_bulk_update_multi_custom_field
2981 field = CustomField.find(1)
2981 field = CustomField.find(1)
2982 field.update_attribute :multiple, true
2982 field.update_attribute :multiple, true
2983
2983
2984 @request.session[:user_id] = 2
2984 @request.session[:user_id] = 2
2985 post :bulk_update, :ids => [1, 2, 3], :notes => 'Bulk editing multi custom field',
2985 post :bulk_update, :ids => [1, 2, 3], :notes => 'Bulk editing multi custom field',
2986 :issue => {:priority_id => '',
2986 :issue => {:priority_id => '',
2987 :assigned_to_id => '',
2987 :assigned_to_id => '',
2988 :custom_field_values => {'1' => ['MySQL', 'Oracle']}}
2988 :custom_field_values => {'1' => ['MySQL', 'Oracle']}}
2989
2989
2990 assert_response 302
2990 assert_response 302
2991
2991
2992 assert_equal ['MySQL', 'Oracle'], Issue.find(1).custom_field_value(1).sort
2992 assert_equal ['MySQL', 'Oracle'], Issue.find(1).custom_field_value(1).sort
2993 assert_equal ['MySQL', 'Oracle'], Issue.find(3).custom_field_value(1).sort
2993 assert_equal ['MySQL', 'Oracle'], Issue.find(3).custom_field_value(1).sort
2994 # the custom field is not associated with the issue tracker
2994 # the custom field is not associated with the issue tracker
2995 assert_nil Issue.find(2).custom_field_value(1)
2995 assert_nil Issue.find(2).custom_field_value(1)
2996 end
2996 end
2997
2997
2998 def test_bulk_update_multi_custom_field_to_blank
2998 def test_bulk_update_multi_custom_field_to_blank
2999 field = CustomField.find(1)
2999 field = CustomField.find(1)
3000 field.update_attribute :multiple, true
3000 field.update_attribute :multiple, true
3001
3001
3002 @request.session[:user_id] = 2
3002 @request.session[:user_id] = 2
3003 post :bulk_update, :ids => [1, 3], :notes => 'Bulk editing multi custom field',
3003 post :bulk_update, :ids => [1, 3], :notes => 'Bulk editing multi custom field',
3004 :issue => {:priority_id => '',
3004 :issue => {:priority_id => '',
3005 :assigned_to_id => '',
3005 :assigned_to_id => '',
3006 :custom_field_values => {'1' => ['__none__']}}
3006 :custom_field_values => {'1' => ['__none__']}}
3007 assert_response 302
3007 assert_response 302
3008 assert_equal [''], Issue.find(1).custom_field_value(1)
3008 assert_equal [''], Issue.find(1).custom_field_value(1)
3009 assert_equal [''], Issue.find(3).custom_field_value(1)
3009 assert_equal [''], Issue.find(3).custom_field_value(1)
3010 end
3010 end
3011
3011
3012 def test_bulk_update_unassign
3012 def test_bulk_update_unassign
3013 assert_not_nil Issue.find(2).assigned_to
3013 assert_not_nil Issue.find(2).assigned_to
3014 @request.session[:user_id] = 2
3014 @request.session[:user_id] = 2
3015 # unassign issues
3015 # unassign issues
3016 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
3016 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
3017 assert_response 302
3017 assert_response 302
3018 # check that the issues were updated
3018 # check that the issues were updated
3019 assert_nil Issue.find(2).assigned_to
3019 assert_nil Issue.find(2).assigned_to
3020 end
3020 end
3021
3021
3022 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
3022 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
3023 @request.session[:user_id] = 2
3023 @request.session[:user_id] = 2
3024
3024
3025 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
3025 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
3026
3026
3027 assert_response :redirect
3027 assert_response :redirect
3028 issues = Issue.find([1,2])
3028 issues = Issue.find([1,2])
3029 issues.each do |issue|
3029 issues.each do |issue|
3030 assert_equal 4, issue.fixed_version_id
3030 assert_equal 4, issue.fixed_version_id
3031 assert_not_equal issue.project_id, issue.fixed_version.project_id
3031 assert_not_equal issue.project_id, issue.fixed_version.project_id
3032 end
3032 end
3033 end
3033 end
3034
3034
3035 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
3035 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
3036 @request.session[:user_id] = 2
3036 @request.session[:user_id] = 2
3037 post :bulk_update, :ids => [1,2], :back_url => '/issues'
3037 post :bulk_update, :ids => [1,2], :back_url => '/issues'
3038
3038
3039 assert_response :redirect
3039 assert_response :redirect
3040 assert_redirected_to '/issues'
3040 assert_redirected_to '/issues'
3041 end
3041 end
3042
3042
3043 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
3043 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
3044 @request.session[:user_id] = 2
3044 @request.session[:user_id] = 2
3045 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
3045 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
3046
3046
3047 assert_response :redirect
3047 assert_response :redirect
3048 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
3048 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
3049 end
3049 end
3050
3050
3051 def test_bulk_update_with_failure_should_set_flash
3051 def test_bulk_update_with_failure_should_set_flash
3052 @request.session[:user_id] = 2
3052 @request.session[:user_id] = 2
3053 Issue.update_all("subject = ''", "id = 2") # Make it invalid
3053 Issue.update_all("subject = ''", "id = 2") # Make it invalid
3054 post :bulk_update, :ids => [1, 2], :issue => {:priority_id => 6}
3054 post :bulk_update, :ids => [1, 2], :issue => {:priority_id => 6}
3055
3055
3056 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3056 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3057 assert_equal 'Failed to save 1 issue(s) on 2 selected: #2.', flash[:error]
3057 assert_equal 'Failed to save 1 issue(s) on 2 selected: #2.', flash[:error]
3058 end
3058 end
3059
3059
3060 def test_bulk_copy_to_another_project
3060 def test_bulk_copy_to_another_project
3061 @request.session[:user_id] = 2
3061 @request.session[:user_id] = 2
3062 assert_difference 'Issue.count', 2 do
3062 assert_difference 'Issue.count', 2 do
3063 assert_no_difference 'Project.find(1).issues.count' do
3063 assert_no_difference 'Project.find(1).issues.count' do
3064 post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'}, :copy => '1'
3064 post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'}, :copy => '1'
3065 end
3065 end
3066 end
3066 end
3067 assert_redirected_to '/projects/ecookbook/issues'
3067 assert_redirected_to '/projects/ecookbook/issues'
3068
3069 copies = Issue.all(:order => 'id DESC', :limit => issues.size)
3070 copies.each do |copy|
3071 assert_equal 2, copy.project_id
3072 end
3068 end
3073 end
3069
3074
3070 def test_bulk_copy_should_allow_not_changing_the_issue_attributes
3075 def test_bulk_copy_should_allow_not_changing_the_issue_attributes
3071 @request.session[:user_id] = 2
3076 @request.session[:user_id] = 2
3072 issue_before_move = Issue.find(1)
3077 issues = [
3073 assert_difference 'Issue.count', 1 do
3078 Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 1, :priority_id => 2, :subject => 'issue 1', :author_id => 1, :assigned_to_id => nil),
3074 assert_no_difference 'Project.find(1).issues.count' do
3079 Issue.create!(:project_id => 2, :tracker_id => 3, :status_id => 2, :priority_id => 1, :subject => 'issue 2', :author_id => 2, :assigned_to_id => 3)
3075 post :bulk_update, :ids => [1], :copy => '1',
3080 ]
3076 :issue => {
3081
3077 :project_id => '2', :tracker_id => '', :assigned_to_id => '',
3082 assert_difference 'Issue.count', issues.size do
3078 :status_id => '', :start_date => '', :due_date => ''
3083 post :bulk_update, :ids => issues.map(&:id), :copy => '1',
3079 }
3084 :issue => {
3080 end
3085 :project_id => '', :tracker_id => '', :assigned_to_id => '',
3086 :status_id => '', :start_date => '', :due_date => ''
3087 }
3088 end
3089
3090 copies = Issue.all(:order => 'id DESC', :limit => issues.size)
3091 issues.each do |orig|
3092 copy = copies.detect {|c| c.subject == orig.subject}
3093 assert_not_nil copy
3094 assert_equal orig.project_id, copy.project_id
3095 assert_equal orig.tracker_id, copy.tracker_id
3096 assert_equal orig.status_id, copy.status_id
3097 assert_equal orig.assigned_to_id, copy.assigned_to_id
3098 assert_equal orig.priority_id, copy.priority_id
3081 end
3099 end
3082 issue_after_move = Issue.first(:order => 'id desc', :conditions => {:project_id => 2})
3083 assert_equal issue_before_move.tracker_id, issue_after_move.tracker_id
3084 assert_equal issue_before_move.status_id, issue_after_move.status_id
3085 assert_equal issue_before_move.assigned_to_id, issue_after_move.assigned_to_id
3086 end
3100 end
3087
3101
3088 def test_bulk_copy_should_allow_changing_the_issue_attributes
3102 def test_bulk_copy_should_allow_changing_the_issue_attributes
3089 # Fixes random test failure with Mysql
3103 # Fixes random test failure with Mysql
3090 # where Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2})
3104 # where Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2})
3091 # doesn't return the expected results
3105 # doesn't return the expected results
3092 Issue.delete_all("project_id=2")
3106 Issue.delete_all("project_id=2")
3093
3107
3094 @request.session[:user_id] = 2
3108 @request.session[:user_id] = 2
3095 assert_difference 'Issue.count', 2 do
3109 assert_difference 'Issue.count', 2 do
3096 assert_no_difference 'Project.find(1).issues.count' do
3110 assert_no_difference 'Project.find(1).issues.count' do
3097 post :bulk_update, :ids => [1, 2], :copy => '1',
3111 post :bulk_update, :ids => [1, 2], :copy => '1',
3098 :issue => {
3112 :issue => {
3099 :project_id => '2', :tracker_id => '', :assigned_to_id => '4',
3113 :project_id => '2', :tracker_id => '', :assigned_to_id => '4',
3100 :status_id => '3', :start_date => '2009-12-01', :due_date => '2009-12-31'
3114 :status_id => '1', :start_date => '2009-12-01', :due_date => '2009-12-31'
3101 }
3115 }
3102 end
3116 end
3103 end
3117 end
3104
3118
3105 copied_issues = Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2})
3119 copied_issues = Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2})
3106 assert_equal 2, copied_issues.size
3120 assert_equal 2, copied_issues.size
3107 copied_issues.each do |issue|
3121 copied_issues.each do |issue|
3108 assert_equal 2, issue.project_id, "Project is incorrect"
3122 assert_equal 2, issue.project_id, "Project is incorrect"
3109 assert_equal 4, issue.assigned_to_id, "Assigned to is incorrect"
3123 assert_equal 4, issue.assigned_to_id, "Assigned to is incorrect"
3110 assert_equal 3, issue.status_id, "Status is incorrect"
3124 assert_equal 1, issue.status_id, "Status is incorrect"
3111 assert_equal '2009-12-01', issue.start_date.to_s, "Start date is incorrect"
3125 assert_equal '2009-12-01', issue.start_date.to_s, "Start date is incorrect"
3112 assert_equal '2009-12-31', issue.due_date.to_s, "Due date is incorrect"
3126 assert_equal '2009-12-31', issue.due_date.to_s, "Due date is incorrect"
3113 end
3127 end
3114 end
3128 end
3115
3129
3116 def test_bulk_copy_should_allow_adding_a_note
3130 def test_bulk_copy_should_allow_adding_a_note
3117 @request.session[:user_id] = 2
3131 @request.session[:user_id] = 2
3118 assert_difference 'Issue.count', 1 do
3132 assert_difference 'Issue.count', 1 do
3119 post :bulk_update, :ids => [1], :copy => '1',
3133 post :bulk_update, :ids => [1], :copy => '1',
3120 :notes => 'Copying one issue',
3134 :notes => 'Copying one issue',
3121 :issue => {
3135 :issue => {
3122 :project_id => '', :tracker_id => '', :assigned_to_id => '4',
3136 :project_id => '', :tracker_id => '', :assigned_to_id => '4',
3123 :status_id => '3', :start_date => '2009-12-01', :due_date => '2009-12-31'
3137 :status_id => '3', :start_date => '2009-12-01', :due_date => '2009-12-31'
3124 }
3138 }
3125 end
3139 end
3126
3140
3127 issue = Issue.first(:order => 'id DESC')
3141 issue = Issue.first(:order => 'id DESC')
3128 assert_equal 1, issue.journals.size
3142 assert_equal 1, issue.journals.size
3129 journal = issue.journals.first
3143 journal = issue.journals.first
3130 assert_equal 0, journal.details.size
3144 assert_equal 0, journal.details.size
3131 assert_equal 'Copying one issue', journal.notes
3145 assert_equal 'Copying one issue', journal.notes
3132 end
3146 end
3133
3147
3134 def test_bulk_copy_to_another_project_should_follow_when_needed
3148 def test_bulk_copy_to_another_project_should_follow_when_needed
3135 @request.session[:user_id] = 2
3149 @request.session[:user_id] = 2
3136 post :bulk_update, :ids => [1], :copy => '1', :issue => {:project_id => 2}, :follow => '1'
3150 post :bulk_update, :ids => [1], :copy => '1', :issue => {:project_id => 2}, :follow => '1'
3137 issue = Issue.first(:order => 'id DESC')
3151 issue = Issue.first(:order => 'id DESC')
3138 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
3152 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
3139 end
3153 end
3140
3154
3141 def test_destroy_issue_with_no_time_entries
3155 def test_destroy_issue_with_no_time_entries
3142 assert_nil TimeEntry.find_by_issue_id(2)
3156 assert_nil TimeEntry.find_by_issue_id(2)
3143 @request.session[:user_id] = 2
3157 @request.session[:user_id] = 2
3144
3158
3145 assert_difference 'Issue.count', -1 do
3159 assert_difference 'Issue.count', -1 do
3146 delete :destroy, :id => 2
3160 delete :destroy, :id => 2
3147 end
3161 end
3148 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3162 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3149 assert_nil Issue.find_by_id(2)
3163 assert_nil Issue.find_by_id(2)
3150 end
3164 end
3151
3165
3152 def test_destroy_issues_with_time_entries
3166 def test_destroy_issues_with_time_entries
3153 @request.session[:user_id] = 2
3167 @request.session[:user_id] = 2
3154
3168
3155 assert_no_difference 'Issue.count' do
3169 assert_no_difference 'Issue.count' do
3156 delete :destroy, :ids => [1, 3]
3170 delete :destroy, :ids => [1, 3]
3157 end
3171 end
3158 assert_response :success
3172 assert_response :success
3159 assert_template 'destroy'
3173 assert_template 'destroy'
3160 assert_not_nil assigns(:hours)
3174 assert_not_nil assigns(:hours)
3161 assert Issue.find_by_id(1) && Issue.find_by_id(3)
3175 assert Issue.find_by_id(1) && Issue.find_by_id(3)
3162 assert_tag 'form',
3176 assert_tag 'form',
3163 :descendant => {:tag => 'input', :attributes => {:name => '_method', :value => 'delete'}}
3177 :descendant => {:tag => 'input', :attributes => {:name => '_method', :value => 'delete'}}
3164 end
3178 end
3165
3179
3166 def test_destroy_issues_and_destroy_time_entries
3180 def test_destroy_issues_and_destroy_time_entries
3167 @request.session[:user_id] = 2
3181 @request.session[:user_id] = 2
3168
3182
3169 assert_difference 'Issue.count', -2 do
3183 assert_difference 'Issue.count', -2 do
3170 assert_difference 'TimeEntry.count', -3 do
3184 assert_difference 'TimeEntry.count', -3 do
3171 delete :destroy, :ids => [1, 3], :todo => 'destroy'
3185 delete :destroy, :ids => [1, 3], :todo => 'destroy'
3172 end
3186 end
3173 end
3187 end
3174 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3188 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3175 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3189 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3176 assert_nil TimeEntry.find_by_id([1, 2])
3190 assert_nil TimeEntry.find_by_id([1, 2])
3177 end
3191 end
3178
3192
3179 def test_destroy_issues_and_assign_time_entries_to_project
3193 def test_destroy_issues_and_assign_time_entries_to_project
3180 @request.session[:user_id] = 2
3194 @request.session[:user_id] = 2
3181
3195
3182 assert_difference 'Issue.count', -2 do
3196 assert_difference 'Issue.count', -2 do
3183 assert_no_difference 'TimeEntry.count' do
3197 assert_no_difference 'TimeEntry.count' do
3184 delete :destroy, :ids => [1, 3], :todo => 'nullify'
3198 delete :destroy, :ids => [1, 3], :todo => 'nullify'
3185 end
3199 end
3186 end
3200 end
3187 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3201 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3188 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3202 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3189 assert_nil TimeEntry.find(1).issue_id
3203 assert_nil TimeEntry.find(1).issue_id
3190 assert_nil TimeEntry.find(2).issue_id
3204 assert_nil TimeEntry.find(2).issue_id
3191 end
3205 end
3192
3206
3193 def test_destroy_issues_and_reassign_time_entries_to_another_issue
3207 def test_destroy_issues_and_reassign_time_entries_to_another_issue
3194 @request.session[:user_id] = 2
3208 @request.session[:user_id] = 2
3195
3209
3196 assert_difference 'Issue.count', -2 do
3210 assert_difference 'Issue.count', -2 do
3197 assert_no_difference 'TimeEntry.count' do
3211 assert_no_difference 'TimeEntry.count' do
3198 delete :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
3212 delete :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
3199 end
3213 end
3200 end
3214 end
3201 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3215 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3202 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3216 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3203 assert_equal 2, TimeEntry.find(1).issue_id
3217 assert_equal 2, TimeEntry.find(1).issue_id
3204 assert_equal 2, TimeEntry.find(2).issue_id
3218 assert_equal 2, TimeEntry.find(2).issue_id
3205 end
3219 end
3206
3220
3207 def test_destroy_issues_from_different_projects
3221 def test_destroy_issues_from_different_projects
3208 @request.session[:user_id] = 2
3222 @request.session[:user_id] = 2
3209
3223
3210 assert_difference 'Issue.count', -3 do
3224 assert_difference 'Issue.count', -3 do
3211 delete :destroy, :ids => [1, 2, 6], :todo => 'destroy'
3225 delete :destroy, :ids => [1, 2, 6], :todo => 'destroy'
3212 end
3226 end
3213 assert_redirected_to :controller => 'issues', :action => 'index'
3227 assert_redirected_to :controller => 'issues', :action => 'index'
3214 assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6))
3228 assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6))
3215 end
3229 end
3216
3230
3217 def test_destroy_parent_and_child_issues
3231 def test_destroy_parent_and_child_issues
3218 parent = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'Parent Issue')
3232 parent = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'Parent Issue')
3219 child = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'Child Issue', :parent_issue_id => parent.id)
3233 child = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'Child Issue', :parent_issue_id => parent.id)
3220 assert child.is_descendant_of?(parent.reload)
3234 assert child.is_descendant_of?(parent.reload)
3221
3235
3222 @request.session[:user_id] = 2
3236 @request.session[:user_id] = 2
3223 assert_difference 'Issue.count', -2 do
3237 assert_difference 'Issue.count', -2 do
3224 delete :destroy, :ids => [parent.id, child.id], :todo => 'destroy'
3238 delete :destroy, :ids => [parent.id, child.id], :todo => 'destroy'
3225 end
3239 end
3226 assert_response 302
3240 assert_response 302
3227 end
3241 end
3228
3242
3229 def test_default_search_scope
3243 def test_default_search_scope
3230 get :index
3244 get :index
3231 assert_tag :div, :attributes => {:id => 'quick-search'},
3245 assert_tag :div, :attributes => {:id => 'quick-search'},
3232 :child => {:tag => 'form',
3246 :child => {:tag => 'form',
3233 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
3247 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
3234 end
3248 end
3235 end
3249 end
@@ -1,1255 +1,1263
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class IssueTest < ActiveSupport::TestCase
20 class IssueTest < ActiveSupport::TestCase
21 fixtures :projects, :users, :members, :member_roles, :roles,
21 fixtures :projects, :users, :members, :member_roles, :roles,
22 :groups_users,
22 :groups_users,
23 :trackers, :projects_trackers,
23 :trackers, :projects_trackers,
24 :enabled_modules,
24 :enabled_modules,
25 :versions,
25 :versions,
26 :issue_statuses, :issue_categories, :issue_relations, :workflows,
26 :issue_statuses, :issue_categories, :issue_relations, :workflows,
27 :enumerations,
27 :enumerations,
28 :issues,
28 :issues,
29 :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values,
29 :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values,
30 :time_entries
30 :time_entries
31
31
32 include Redmine::I18n
32 include Redmine::I18n
33
33
34 def test_create
34 def test_create
35 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
35 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
36 :status_id => 1, :priority => IssuePriority.all.first,
36 :status_id => 1, :priority => IssuePriority.all.first,
37 :subject => 'test_create',
37 :subject => 'test_create',
38 :description => 'IssueTest#test_create', :estimated_hours => '1:30')
38 :description => 'IssueTest#test_create', :estimated_hours => '1:30')
39 assert issue.save
39 assert issue.save
40 issue.reload
40 issue.reload
41 assert_equal 1.5, issue.estimated_hours
41 assert_equal 1.5, issue.estimated_hours
42 end
42 end
43
43
44 def test_create_minimal
44 def test_create_minimal
45 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
45 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
46 :status_id => 1, :priority => IssuePriority.all.first,
46 :status_id => 1, :priority => IssuePriority.all.first,
47 :subject => 'test_create')
47 :subject => 'test_create')
48 assert issue.save
48 assert issue.save
49 assert issue.description.nil?
49 assert issue.description.nil?
50 end
50 end
51
51
52 def test_create_with_required_custom_field
52 def test_create_with_required_custom_field
53 set_language_if_valid 'en'
53 set_language_if_valid 'en'
54 field = IssueCustomField.find_by_name('Database')
54 field = IssueCustomField.find_by_name('Database')
55 field.update_attribute(:is_required, true)
55 field.update_attribute(:is_required, true)
56
56
57 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
57 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
58 :status_id => 1, :subject => 'test_create',
58 :status_id => 1, :subject => 'test_create',
59 :description => 'IssueTest#test_create_with_required_custom_field')
59 :description => 'IssueTest#test_create_with_required_custom_field')
60 assert issue.available_custom_fields.include?(field)
60 assert issue.available_custom_fields.include?(field)
61 # No value for the custom field
61 # No value for the custom field
62 assert !issue.save
62 assert !issue.save
63 assert_equal ["Database can't be blank"], issue.errors.full_messages
63 assert_equal ["Database can't be blank"], issue.errors.full_messages
64 # Blank value
64 # Blank value
65 issue.custom_field_values = { field.id => '' }
65 issue.custom_field_values = { field.id => '' }
66 assert !issue.save
66 assert !issue.save
67 assert_equal ["Database can't be blank"], issue.errors.full_messages
67 assert_equal ["Database can't be blank"], issue.errors.full_messages
68 # Invalid value
68 # Invalid value
69 issue.custom_field_values = { field.id => 'SQLServer' }
69 issue.custom_field_values = { field.id => 'SQLServer' }
70 assert !issue.save
70 assert !issue.save
71 assert_equal ["Database is not included in the list"], issue.errors.full_messages
71 assert_equal ["Database is not included in the list"], issue.errors.full_messages
72 # Valid value
72 # Valid value
73 issue.custom_field_values = { field.id => 'PostgreSQL' }
73 issue.custom_field_values = { field.id => 'PostgreSQL' }
74 assert issue.save
74 assert issue.save
75 issue.reload
75 issue.reload
76 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
76 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
77 end
77 end
78
78
79 def test_create_with_group_assignment
79 def test_create_with_group_assignment
80 with_settings :issue_group_assignment => '1' do
80 with_settings :issue_group_assignment => '1' do
81 assert Issue.new(:project_id => 2, :tracker_id => 1, :author_id => 1,
81 assert Issue.new(:project_id => 2, :tracker_id => 1, :author_id => 1,
82 :subject => 'Group assignment',
82 :subject => 'Group assignment',
83 :assigned_to_id => 11).save
83 :assigned_to_id => 11).save
84 issue = Issue.first(:order => 'id DESC')
84 issue = Issue.first(:order => 'id DESC')
85 assert_kind_of Group, issue.assigned_to
85 assert_kind_of Group, issue.assigned_to
86 assert_equal Group.find(11), issue.assigned_to
86 assert_equal Group.find(11), issue.assigned_to
87 end
87 end
88 end
88 end
89
89
90 def assert_visibility_match(user, issues)
90 def assert_visibility_match(user, issues)
91 assert_equal issues.collect(&:id).sort, Issue.all.select {|issue| issue.visible?(user)}.collect(&:id).sort
91 assert_equal issues.collect(&:id).sort, Issue.all.select {|issue| issue.visible?(user)}.collect(&:id).sort
92 end
92 end
93
93
94 def test_visible_scope_for_anonymous
94 def test_visible_scope_for_anonymous
95 # Anonymous user should see issues of public projects only
95 # Anonymous user should see issues of public projects only
96 issues = Issue.visible(User.anonymous).all
96 issues = Issue.visible(User.anonymous).all
97 assert issues.any?
97 assert issues.any?
98 assert_nil issues.detect {|issue| !issue.project.is_public?}
98 assert_nil issues.detect {|issue| !issue.project.is_public?}
99 assert_nil issues.detect {|issue| issue.is_private?}
99 assert_nil issues.detect {|issue| issue.is_private?}
100 assert_visibility_match User.anonymous, issues
100 assert_visibility_match User.anonymous, issues
101 end
101 end
102
102
103 def test_visible_scope_for_anonymous_with_own_issues_visibility
103 def test_visible_scope_for_anonymous_with_own_issues_visibility
104 Role.anonymous.update_attribute :issues_visibility, 'own'
104 Role.anonymous.update_attribute :issues_visibility, 'own'
105 Issue.create!(:project_id => 1, :tracker_id => 1,
105 Issue.create!(:project_id => 1, :tracker_id => 1,
106 :author_id => User.anonymous.id,
106 :author_id => User.anonymous.id,
107 :subject => 'Issue by anonymous')
107 :subject => 'Issue by anonymous')
108
108
109 issues = Issue.visible(User.anonymous).all
109 issues = Issue.visible(User.anonymous).all
110 assert issues.any?
110 assert issues.any?
111 assert_nil issues.detect {|issue| issue.author != User.anonymous}
111 assert_nil issues.detect {|issue| issue.author != User.anonymous}
112 assert_visibility_match User.anonymous, issues
112 assert_visibility_match User.anonymous, issues
113 end
113 end
114
114
115 def test_visible_scope_for_anonymous_without_view_issues_permissions
115 def test_visible_scope_for_anonymous_without_view_issues_permissions
116 # Anonymous user should not see issues without permission
116 # Anonymous user should not see issues without permission
117 Role.anonymous.remove_permission!(:view_issues)
117 Role.anonymous.remove_permission!(:view_issues)
118 issues = Issue.visible(User.anonymous).all
118 issues = Issue.visible(User.anonymous).all
119 assert issues.empty?
119 assert issues.empty?
120 assert_visibility_match User.anonymous, issues
120 assert_visibility_match User.anonymous, issues
121 end
121 end
122
122
123 def test_visible_scope_for_non_member
123 def test_visible_scope_for_non_member
124 user = User.find(9)
124 user = User.find(9)
125 assert user.projects.empty?
125 assert user.projects.empty?
126 # Non member user should see issues of public projects only
126 # Non member user should see issues of public projects only
127 issues = Issue.visible(user).all
127 issues = Issue.visible(user).all
128 assert issues.any?
128 assert issues.any?
129 assert_nil issues.detect {|issue| !issue.project.is_public?}
129 assert_nil issues.detect {|issue| !issue.project.is_public?}
130 assert_nil issues.detect {|issue| issue.is_private?}
130 assert_nil issues.detect {|issue| issue.is_private?}
131 assert_visibility_match user, issues
131 assert_visibility_match user, issues
132 end
132 end
133
133
134 def test_visible_scope_for_non_member_with_own_issues_visibility
134 def test_visible_scope_for_non_member_with_own_issues_visibility
135 Role.non_member.update_attribute :issues_visibility, 'own'
135 Role.non_member.update_attribute :issues_visibility, 'own'
136 Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 9, :subject => 'Issue by non member')
136 Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 9, :subject => 'Issue by non member')
137 user = User.find(9)
137 user = User.find(9)
138
138
139 issues = Issue.visible(user).all
139 issues = Issue.visible(user).all
140 assert issues.any?
140 assert issues.any?
141 assert_nil issues.detect {|issue| issue.author != user}
141 assert_nil issues.detect {|issue| issue.author != user}
142 assert_visibility_match user, issues
142 assert_visibility_match user, issues
143 end
143 end
144
144
145 def test_visible_scope_for_non_member_without_view_issues_permissions
145 def test_visible_scope_for_non_member_without_view_issues_permissions
146 # Non member user should not see issues without permission
146 # Non member user should not see issues without permission
147 Role.non_member.remove_permission!(:view_issues)
147 Role.non_member.remove_permission!(:view_issues)
148 user = User.find(9)
148 user = User.find(9)
149 assert user.projects.empty?
149 assert user.projects.empty?
150 issues = Issue.visible(user).all
150 issues = Issue.visible(user).all
151 assert issues.empty?
151 assert issues.empty?
152 assert_visibility_match user, issues
152 assert_visibility_match user, issues
153 end
153 end
154
154
155 def test_visible_scope_for_member
155 def test_visible_scope_for_member
156 user = User.find(9)
156 user = User.find(9)
157 # User should see issues of projects for which he has view_issues permissions only
157 # User should see issues of projects for which he has view_issues permissions only
158 Role.non_member.remove_permission!(:view_issues)
158 Role.non_member.remove_permission!(:view_issues)
159 Member.create!(:principal => user, :project_id => 3, :role_ids => [2])
159 Member.create!(:principal => user, :project_id => 3, :role_ids => [2])
160 issues = Issue.visible(user).all
160 issues = Issue.visible(user).all
161 assert issues.any?
161 assert issues.any?
162 assert_nil issues.detect {|issue| issue.project_id != 3}
162 assert_nil issues.detect {|issue| issue.project_id != 3}
163 assert_nil issues.detect {|issue| issue.is_private?}
163 assert_nil issues.detect {|issue| issue.is_private?}
164 assert_visibility_match user, issues
164 assert_visibility_match user, issues
165 end
165 end
166
166
167 def test_visible_scope_for_member_with_groups_should_return_assigned_issues
167 def test_visible_scope_for_member_with_groups_should_return_assigned_issues
168 user = User.find(8)
168 user = User.find(8)
169 assert user.groups.any?
169 assert user.groups.any?
170 Member.create!(:principal => user.groups.first, :project_id => 1, :role_ids => [2])
170 Member.create!(:principal => user.groups.first, :project_id => 1, :role_ids => [2])
171 Role.non_member.remove_permission!(:view_issues)
171 Role.non_member.remove_permission!(:view_issues)
172
172
173 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3,
173 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3,
174 :status_id => 1, :priority => IssuePriority.all.first,
174 :status_id => 1, :priority => IssuePriority.all.first,
175 :subject => 'Assignment test',
175 :subject => 'Assignment test',
176 :assigned_to => user.groups.first,
176 :assigned_to => user.groups.first,
177 :is_private => true)
177 :is_private => true)
178
178
179 Role.find(2).update_attribute :issues_visibility, 'default'
179 Role.find(2).update_attribute :issues_visibility, 'default'
180 issues = Issue.visible(User.find(8)).all
180 issues = Issue.visible(User.find(8)).all
181 assert issues.any?
181 assert issues.any?
182 assert issues.include?(issue)
182 assert issues.include?(issue)
183
183
184 Role.find(2).update_attribute :issues_visibility, 'own'
184 Role.find(2).update_attribute :issues_visibility, 'own'
185 issues = Issue.visible(User.find(8)).all
185 issues = Issue.visible(User.find(8)).all
186 assert issues.any?
186 assert issues.any?
187 assert issues.include?(issue)
187 assert issues.include?(issue)
188 end
188 end
189
189
190 def test_visible_scope_for_admin
190 def test_visible_scope_for_admin
191 user = User.find(1)
191 user = User.find(1)
192 user.members.each(&:destroy)
192 user.members.each(&:destroy)
193 assert user.projects.empty?
193 assert user.projects.empty?
194 issues = Issue.visible(user).all
194 issues = Issue.visible(user).all
195 assert issues.any?
195 assert issues.any?
196 # Admin should see issues on private projects that he does not belong to
196 # Admin should see issues on private projects that he does not belong to
197 assert issues.detect {|issue| !issue.project.is_public?}
197 assert issues.detect {|issue| !issue.project.is_public?}
198 # Admin should see private issues of other users
198 # Admin should see private issues of other users
199 assert issues.detect {|issue| issue.is_private? && issue.author != user}
199 assert issues.detect {|issue| issue.is_private? && issue.author != user}
200 assert_visibility_match user, issues
200 assert_visibility_match user, issues
201 end
201 end
202
202
203 def test_visible_scope_with_project
203 def test_visible_scope_with_project
204 project = Project.find(1)
204 project = Project.find(1)
205 issues = Issue.visible(User.find(2), :project => project).all
205 issues = Issue.visible(User.find(2), :project => project).all
206 projects = issues.collect(&:project).uniq
206 projects = issues.collect(&:project).uniq
207 assert_equal 1, projects.size
207 assert_equal 1, projects.size
208 assert_equal project, projects.first
208 assert_equal project, projects.first
209 end
209 end
210
210
211 def test_visible_scope_with_project_and_subprojects
211 def test_visible_scope_with_project_and_subprojects
212 project = Project.find(1)
212 project = Project.find(1)
213 issues = Issue.visible(User.find(2), :project => project, :with_subprojects => true).all
213 issues = Issue.visible(User.find(2), :project => project, :with_subprojects => true).all
214 projects = issues.collect(&:project).uniq
214 projects = issues.collect(&:project).uniq
215 assert projects.size > 1
215 assert projects.size > 1
216 assert_equal [], projects.select {|p| !p.is_or_is_descendant_of?(project)}
216 assert_equal [], projects.select {|p| !p.is_or_is_descendant_of?(project)}
217 end
217 end
218
218
219 def test_visible_and_nested_set_scopes
219 def test_visible_and_nested_set_scopes
220 assert_equal 0, Issue.find(1).descendants.visible.all.size
220 assert_equal 0, Issue.find(1).descendants.visible.all.size
221 end
221 end
222
222
223 def test_open_scope
223 def test_open_scope
224 issues = Issue.open.all
224 issues = Issue.open.all
225 assert_nil issues.detect(&:closed?)
225 assert_nil issues.detect(&:closed?)
226 end
226 end
227
227
228 def test_open_scope_with_arg
228 def test_open_scope_with_arg
229 issues = Issue.open(false).all
229 issues = Issue.open(false).all
230 assert_equal issues, issues.select(&:closed?)
230 assert_equal issues, issues.select(&:closed?)
231 end
231 end
232
232
233 def test_errors_full_messages_should_include_custom_fields_errors
233 def test_errors_full_messages_should_include_custom_fields_errors
234 field = IssueCustomField.find_by_name('Database')
234 field = IssueCustomField.find_by_name('Database')
235
235
236 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
236 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
237 :status_id => 1, :subject => 'test_create',
237 :status_id => 1, :subject => 'test_create',
238 :description => 'IssueTest#test_create_with_required_custom_field')
238 :description => 'IssueTest#test_create_with_required_custom_field')
239 assert issue.available_custom_fields.include?(field)
239 assert issue.available_custom_fields.include?(field)
240 # Invalid value
240 # Invalid value
241 issue.custom_field_values = { field.id => 'SQLServer' }
241 issue.custom_field_values = { field.id => 'SQLServer' }
242
242
243 assert !issue.valid?
243 assert !issue.valid?
244 assert_equal 1, issue.errors.full_messages.size
244 assert_equal 1, issue.errors.full_messages.size
245 assert_equal "Database #{I18n.translate('activerecord.errors.messages.inclusion')}",
245 assert_equal "Database #{I18n.translate('activerecord.errors.messages.inclusion')}",
246 issue.errors.full_messages.first
246 issue.errors.full_messages.first
247 end
247 end
248
248
249 def test_update_issue_with_required_custom_field
249 def test_update_issue_with_required_custom_field
250 field = IssueCustomField.find_by_name('Database')
250 field = IssueCustomField.find_by_name('Database')
251 field.update_attribute(:is_required, true)
251 field.update_attribute(:is_required, true)
252
252
253 issue = Issue.find(1)
253 issue = Issue.find(1)
254 assert_nil issue.custom_value_for(field)
254 assert_nil issue.custom_value_for(field)
255 assert issue.available_custom_fields.include?(field)
255 assert issue.available_custom_fields.include?(field)
256 # No change to custom values, issue can be saved
256 # No change to custom values, issue can be saved
257 assert issue.save
257 assert issue.save
258 # Blank value
258 # Blank value
259 issue.custom_field_values = { field.id => '' }
259 issue.custom_field_values = { field.id => '' }
260 assert !issue.save
260 assert !issue.save
261 # Valid value
261 # Valid value
262 issue.custom_field_values = { field.id => 'PostgreSQL' }
262 issue.custom_field_values = { field.id => 'PostgreSQL' }
263 assert issue.save
263 assert issue.save
264 issue.reload
264 issue.reload
265 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
265 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
266 end
266 end
267
267
268 def test_should_not_update_attributes_if_custom_fields_validation_fails
268 def test_should_not_update_attributes_if_custom_fields_validation_fails
269 issue = Issue.find(1)
269 issue = Issue.find(1)
270 field = IssueCustomField.find_by_name('Database')
270 field = IssueCustomField.find_by_name('Database')
271 assert issue.available_custom_fields.include?(field)
271 assert issue.available_custom_fields.include?(field)
272
272
273 issue.custom_field_values = { field.id => 'Invalid' }
273 issue.custom_field_values = { field.id => 'Invalid' }
274 issue.subject = 'Should be not be saved'
274 issue.subject = 'Should be not be saved'
275 assert !issue.save
275 assert !issue.save
276
276
277 issue.reload
277 issue.reload
278 assert_equal "Can't print recipes", issue.subject
278 assert_equal "Can't print recipes", issue.subject
279 end
279 end
280
280
281 def test_should_not_recreate_custom_values_objects_on_update
281 def test_should_not_recreate_custom_values_objects_on_update
282 field = IssueCustomField.find_by_name('Database')
282 field = IssueCustomField.find_by_name('Database')
283
283
284 issue = Issue.find(1)
284 issue = Issue.find(1)
285 issue.custom_field_values = { field.id => 'PostgreSQL' }
285 issue.custom_field_values = { field.id => 'PostgreSQL' }
286 assert issue.save
286 assert issue.save
287 custom_value = issue.custom_value_for(field)
287 custom_value = issue.custom_value_for(field)
288 issue.reload
288 issue.reload
289 issue.custom_field_values = { field.id => 'MySQL' }
289 issue.custom_field_values = { field.id => 'MySQL' }
290 assert issue.save
290 assert issue.save
291 issue.reload
291 issue.reload
292 assert_equal custom_value.id, issue.custom_value_for(field).id
292 assert_equal custom_value.id, issue.custom_value_for(field).id
293 end
293 end
294
294
295 def test_should_not_update_custom_fields_on_changing_tracker_with_different_custom_fields
295 def test_should_not_update_custom_fields_on_changing_tracker_with_different_custom_fields
296 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => 'Test', :custom_field_values => {'2' => 'Test'})
296 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => 'Test', :custom_field_values => {'2' => 'Test'})
297 assert !Tracker.find(2).custom_field_ids.include?(2)
297 assert !Tracker.find(2).custom_field_ids.include?(2)
298
298
299 issue = Issue.find(issue.id)
299 issue = Issue.find(issue.id)
300 issue.attributes = {:tracker_id => 2, :custom_field_values => {'1' => ''}}
300 issue.attributes = {:tracker_id => 2, :custom_field_values => {'1' => ''}}
301
301
302 issue = Issue.find(issue.id)
302 issue = Issue.find(issue.id)
303 custom_value = issue.custom_value_for(2)
303 custom_value = issue.custom_value_for(2)
304 assert_not_nil custom_value
304 assert_not_nil custom_value
305 assert_equal 'Test', custom_value.value
305 assert_equal 'Test', custom_value.value
306 end
306 end
307
307
308 def test_assigning_tracker_id_should_reload_custom_fields_values
308 def test_assigning_tracker_id_should_reload_custom_fields_values
309 issue = Issue.new(:project => Project.find(1))
309 issue = Issue.new(:project => Project.find(1))
310 assert issue.custom_field_values.empty?
310 assert issue.custom_field_values.empty?
311 issue.tracker_id = 1
311 issue.tracker_id = 1
312 assert issue.custom_field_values.any?
312 assert issue.custom_field_values.any?
313 end
313 end
314
314
315 def test_assigning_attributes_should_assign_project_and_tracker_first
315 def test_assigning_attributes_should_assign_project_and_tracker_first
316 seq = sequence('seq')
316 seq = sequence('seq')
317 issue = Issue.new
317 issue = Issue.new
318 issue.expects(:project_id=).in_sequence(seq)
318 issue.expects(:project_id=).in_sequence(seq)
319 issue.expects(:tracker_id=).in_sequence(seq)
319 issue.expects(:tracker_id=).in_sequence(seq)
320 issue.expects(:subject=).in_sequence(seq)
320 issue.expects(:subject=).in_sequence(seq)
321 issue.attributes = {:tracker_id => 2, :project_id => 1, :subject => 'Test'}
321 issue.attributes = {:tracker_id => 2, :project_id => 1, :subject => 'Test'}
322 end
322 end
323
323
324 def test_assigning_tracker_and_custom_fields_should_assign_custom_fields
324 def test_assigning_tracker_and_custom_fields_should_assign_custom_fields
325 attributes = ActiveSupport::OrderedHash.new
325 attributes = ActiveSupport::OrderedHash.new
326 attributes['custom_field_values'] = { '1' => 'MySQL' }
326 attributes['custom_field_values'] = { '1' => 'MySQL' }
327 attributes['tracker_id'] = '1'
327 attributes['tracker_id'] = '1'
328 issue = Issue.new(:project => Project.find(1))
328 issue = Issue.new(:project => Project.find(1))
329 issue.attributes = attributes
329 issue.attributes = attributes
330 assert_equal 'MySQL', issue.custom_field_value(1)
330 assert_equal 'MySQL', issue.custom_field_value(1)
331 end
331 end
332
332
333 def test_should_update_issue_with_disabled_tracker
333 def test_should_update_issue_with_disabled_tracker
334 p = Project.find(1)
334 p = Project.find(1)
335 issue = Issue.find(1)
335 issue = Issue.find(1)
336
336
337 p.trackers.delete(issue.tracker)
337 p.trackers.delete(issue.tracker)
338 assert !p.trackers.include?(issue.tracker)
338 assert !p.trackers.include?(issue.tracker)
339
339
340 issue.reload
340 issue.reload
341 issue.subject = 'New subject'
341 issue.subject = 'New subject'
342 assert issue.save
342 assert issue.save
343 end
343 end
344
344
345 def test_should_not_set_a_disabled_tracker
345 def test_should_not_set_a_disabled_tracker
346 p = Project.find(1)
346 p = Project.find(1)
347 p.trackers.delete(Tracker.find(2))
347 p.trackers.delete(Tracker.find(2))
348
348
349 issue = Issue.find(1)
349 issue = Issue.find(1)
350 issue.tracker_id = 2
350 issue.tracker_id = 2
351 issue.subject = 'New subject'
351 issue.subject = 'New subject'
352 assert !issue.save
352 assert !issue.save
353 assert_not_nil issue.errors[:tracker_id]
353 assert_not_nil issue.errors[:tracker_id]
354 end
354 end
355
355
356 def test_category_based_assignment
356 def test_category_based_assignment
357 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3,
357 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3,
358 :status_id => 1, :priority => IssuePriority.all.first,
358 :status_id => 1, :priority => IssuePriority.all.first,
359 :subject => 'Assignment test',
359 :subject => 'Assignment test',
360 :description => 'Assignment test', :category_id => 1)
360 :description => 'Assignment test', :category_id => 1)
361 assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
361 assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
362 end
362 end
363
363
364 def test_new_statuses_allowed_to
364 def test_new_statuses_allowed_to
365 Workflow.delete_all
365 Workflow.delete_all
366
366
367 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2, :author => false, :assignee => false)
367 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2, :author => false, :assignee => false)
368 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3, :author => true, :assignee => false)
368 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3, :author => true, :assignee => false)
369 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4, :author => false, :assignee => true)
369 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4, :author => false, :assignee => true)
370 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5, :author => true, :assignee => true)
370 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5, :author => true, :assignee => true)
371 status = IssueStatus.find(1)
371 status = IssueStatus.find(1)
372 role = Role.find(1)
372 role = Role.find(1)
373 tracker = Tracker.find(1)
373 tracker = Tracker.find(1)
374 user = User.find(2)
374 user = User.find(2)
375
375
376 issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1)
376 issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1)
377 assert_equal [1, 2], issue.new_statuses_allowed_to(user).map(&:id)
377 assert_equal [1, 2], issue.new_statuses_allowed_to(user).map(&:id)
378
378
379 issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :author => user)
379 issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :author => user)
380 assert_equal [1, 2, 3, 5], issue.new_statuses_allowed_to(user).map(&:id)
380 assert_equal [1, 2, 3, 5], issue.new_statuses_allowed_to(user).map(&:id)
381
381
382 issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :assigned_to => user)
382 issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :assigned_to => user)
383 assert_equal [1, 2, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
383 assert_equal [1, 2, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
384
384
385 issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :author => user, :assigned_to => user)
385 issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :author => user, :assigned_to => user)
386 assert_equal [1, 2, 3, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
386 assert_equal [1, 2, 3, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
387 end
387 end
388
388
389 def test_new_statuses_allowed_to_should_return_all_transitions_for_admin
389 def test_new_statuses_allowed_to_should_return_all_transitions_for_admin
390 admin = User.find(1)
390 admin = User.find(1)
391 issue = Issue.find(1)
391 issue = Issue.find(1)
392 assert !admin.member_of?(issue.project)
392 assert !admin.member_of?(issue.project)
393 expected_statuses = [issue.status] + Workflow.find_all_by_old_status_id(issue.status_id).map(&:new_status).uniq.sort
393 expected_statuses = [issue.status] + Workflow.find_all_by_old_status_id(issue.status_id).map(&:new_status).uniq.sort
394
394
395 assert_equal expected_statuses, issue.new_statuses_allowed_to(admin)
395 assert_equal expected_statuses, issue.new_statuses_allowed_to(admin)
396 end
396 end
397
397
398 def test_new_statuses_allowed_to_should_return_default_and_current_status_when_copying
399 issue = Issue.find(1).copy
400 assert_equal [1], issue.new_statuses_allowed_to(User.find(2)).map(&:id)
401
402 issue = Issue.find(2).copy
403 assert_equal [1, 2], issue.new_statuses_allowed_to(User.find(2)).map(&:id)
404 end
405
398 def test_copy
406 def test_copy
399 issue = Issue.new.copy_from(1)
407 issue = Issue.new.copy_from(1)
400 assert issue.copy?
408 assert issue.copy?
401 assert issue.save
409 assert issue.save
402 issue.reload
410 issue.reload
403 orig = Issue.find(1)
411 orig = Issue.find(1)
404 assert_equal orig.subject, issue.subject
412 assert_equal orig.subject, issue.subject
405 assert_equal orig.tracker, issue.tracker
413 assert_equal orig.tracker, issue.tracker
406 assert_equal "125", issue.custom_value_for(2).value
414 assert_equal "125", issue.custom_value_for(2).value
407 end
415 end
408
416
409 def test_copy_should_copy_status
417 def test_copy_should_copy_status
410 orig = Issue.find(8)
418 orig = Issue.find(8)
411 assert orig.status != IssueStatus.default
419 assert orig.status != IssueStatus.default
412
420
413 issue = Issue.new.copy_from(orig)
421 issue = Issue.new.copy_from(orig)
414 assert issue.save
422 assert issue.save
415 issue.reload
423 issue.reload
416 assert_equal orig.status, issue.status
424 assert_equal orig.status, issue.status
417 end
425 end
418
426
419 def test_should_not_call_after_project_change_on_creation
427 def test_should_not_call_after_project_change_on_creation
420 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1, :subject => 'Test', :author_id => 1)
428 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1, :subject => 'Test', :author_id => 1)
421 issue.expects(:after_project_change).never
429 issue.expects(:after_project_change).never
422 issue.save!
430 issue.save!
423 end
431 end
424
432
425 def test_should_not_call_after_project_change_on_update
433 def test_should_not_call_after_project_change_on_update
426 issue = Issue.find(1)
434 issue = Issue.find(1)
427 issue.project = Project.find(1)
435 issue.project = Project.find(1)
428 issue.subject = 'No project change'
436 issue.subject = 'No project change'
429 issue.expects(:after_project_change).never
437 issue.expects(:after_project_change).never
430 issue.save!
438 issue.save!
431 end
439 end
432
440
433 def test_should_call_after_project_change_on_project_change
441 def test_should_call_after_project_change_on_project_change
434 issue = Issue.find(1)
442 issue = Issue.find(1)
435 issue.project = Project.find(2)
443 issue.project = Project.find(2)
436 issue.expects(:after_project_change).once
444 issue.expects(:after_project_change).once
437 issue.save!
445 issue.save!
438 end
446 end
439
447
440 def test_should_close_duplicates
448 def test_should_close_duplicates
441 # Create 3 issues
449 # Create 3 issues
442 project = Project.find(1)
450 project = Project.find(1)
443 issue1 = Issue.generate_for_project!(project)
451 issue1 = Issue.generate_for_project!(project)
444 issue2 = Issue.generate_for_project!(project)
452 issue2 = Issue.generate_for_project!(project)
445 issue3 = Issue.generate_for_project!(project)
453 issue3 = Issue.generate_for_project!(project)
446
454
447 # 2 is a dupe of 1
455 # 2 is a dupe of 1
448 IssueRelation.create!(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
456 IssueRelation.create!(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
449 # And 3 is a dupe of 2
457 # And 3 is a dupe of 2
450 IssueRelation.create!(:issue_from => issue3, :issue_to => issue2, :relation_type => IssueRelation::TYPE_DUPLICATES)
458 IssueRelation.create!(:issue_from => issue3, :issue_to => issue2, :relation_type => IssueRelation::TYPE_DUPLICATES)
451 # And 3 is a dupe of 1 (circular duplicates)
459 # And 3 is a dupe of 1 (circular duplicates)
452 IssueRelation.create!(:issue_from => issue3, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
460 IssueRelation.create!(:issue_from => issue3, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
453
461
454 assert issue1.reload.duplicates.include?(issue2)
462 assert issue1.reload.duplicates.include?(issue2)
455
463
456 # Closing issue 1
464 # Closing issue 1
457 issue1.init_journal(User.find(:first), "Closing issue1")
465 issue1.init_journal(User.find(:first), "Closing issue1")
458 issue1.status = IssueStatus.find :first, :conditions => {:is_closed => true}
466 issue1.status = IssueStatus.find :first, :conditions => {:is_closed => true}
459 assert issue1.save
467 assert issue1.save
460 # 2 and 3 should be also closed
468 # 2 and 3 should be also closed
461 assert issue2.reload.closed?
469 assert issue2.reload.closed?
462 assert issue3.reload.closed?
470 assert issue3.reload.closed?
463 end
471 end
464
472
465 def test_should_not_close_duplicated_issue
473 def test_should_not_close_duplicated_issue
466 project = Project.find(1)
474 project = Project.find(1)
467 issue1 = Issue.generate_for_project!(project)
475 issue1 = Issue.generate_for_project!(project)
468 issue2 = Issue.generate_for_project!(project)
476 issue2 = Issue.generate_for_project!(project)
469
477
470 # 2 is a dupe of 1
478 # 2 is a dupe of 1
471 IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
479 IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
472 # 2 is a dup of 1 but 1 is not a duplicate of 2
480 # 2 is a dup of 1 but 1 is not a duplicate of 2
473 assert !issue2.reload.duplicates.include?(issue1)
481 assert !issue2.reload.duplicates.include?(issue1)
474
482
475 # Closing issue 2
483 # Closing issue 2
476 issue2.init_journal(User.find(:first), "Closing issue2")
484 issue2.init_journal(User.find(:first), "Closing issue2")
477 issue2.status = IssueStatus.find :first, :conditions => {:is_closed => true}
485 issue2.status = IssueStatus.find :first, :conditions => {:is_closed => true}
478 assert issue2.save
486 assert issue2.save
479 # 1 should not be also closed
487 # 1 should not be also closed
480 assert !issue1.reload.closed?
488 assert !issue1.reload.closed?
481 end
489 end
482
490
483 def test_assignable_versions
491 def test_assignable_versions
484 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue')
492 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue')
485 assert_equal ['open'], issue.assignable_versions.collect(&:status).uniq
493 assert_equal ['open'], issue.assignable_versions.collect(&:status).uniq
486 end
494 end
487
495
488 def test_should_not_be_able_to_assign_a_new_issue_to_a_closed_version
496 def test_should_not_be_able_to_assign_a_new_issue_to_a_closed_version
489 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue')
497 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue')
490 assert !issue.save
498 assert !issue.save
491 assert_not_nil issue.errors[:fixed_version_id]
499 assert_not_nil issue.errors[:fixed_version_id]
492 end
500 end
493
501
494 def test_should_not_be_able_to_assign_a_new_issue_to_a_locked_version
502 def test_should_not_be_able_to_assign_a_new_issue_to_a_locked_version
495 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 2, :subject => 'New issue')
503 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 2, :subject => 'New issue')
496 assert !issue.save
504 assert !issue.save
497 assert_not_nil issue.errors[:fixed_version_id]
505 assert_not_nil issue.errors[:fixed_version_id]
498 end
506 end
499
507
500 def test_should_be_able_to_assign_a_new_issue_to_an_open_version
508 def test_should_be_able_to_assign_a_new_issue_to_an_open_version
501 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 3, :subject => 'New issue')
509 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 3, :subject => 'New issue')
502 assert issue.save
510 assert issue.save
503 end
511 end
504
512
505 def test_should_be_able_to_update_an_issue_assigned_to_a_closed_version
513 def test_should_be_able_to_update_an_issue_assigned_to_a_closed_version
506 issue = Issue.find(11)
514 issue = Issue.find(11)
507 assert_equal 'closed', issue.fixed_version.status
515 assert_equal 'closed', issue.fixed_version.status
508 issue.subject = 'Subject changed'
516 issue.subject = 'Subject changed'
509 assert issue.save
517 assert issue.save
510 end
518 end
511
519
512 def test_should_not_be_able_to_reopen_an_issue_assigned_to_a_closed_version
520 def test_should_not_be_able_to_reopen_an_issue_assigned_to_a_closed_version
513 issue = Issue.find(11)
521 issue = Issue.find(11)
514 issue.status_id = 1
522 issue.status_id = 1
515 assert !issue.save
523 assert !issue.save
516 assert_not_nil issue.errors[:base]
524 assert_not_nil issue.errors[:base]
517 end
525 end
518
526
519 def test_should_be_able_to_reopen_and_reassign_an_issue_assigned_to_a_closed_version
527 def test_should_be_able_to_reopen_and_reassign_an_issue_assigned_to_a_closed_version
520 issue = Issue.find(11)
528 issue = Issue.find(11)
521 issue.status_id = 1
529 issue.status_id = 1
522 issue.fixed_version_id = 3
530 issue.fixed_version_id = 3
523 assert issue.save
531 assert issue.save
524 end
532 end
525
533
526 def test_should_be_able_to_reopen_an_issue_assigned_to_a_locked_version
534 def test_should_be_able_to_reopen_an_issue_assigned_to_a_locked_version
527 issue = Issue.find(12)
535 issue = Issue.find(12)
528 assert_equal 'locked', issue.fixed_version.status
536 assert_equal 'locked', issue.fixed_version.status
529 issue.status_id = 1
537 issue.status_id = 1
530 assert issue.save
538 assert issue.save
531 end
539 end
532
540
533 def test_allowed_target_projects_on_move_should_include_projects_with_issue_tracking_enabled
541 def test_allowed_target_projects_on_move_should_include_projects_with_issue_tracking_enabled
534 assert_include Project.find(2), Issue.allowed_target_projects_on_move(User.find(2))
542 assert_include Project.find(2), Issue.allowed_target_projects_on_move(User.find(2))
535 end
543 end
536
544
537 def test_allowed_target_projects_on_move_should_not_include_projects_with_issue_tracking_disabled
545 def test_allowed_target_projects_on_move_should_not_include_projects_with_issue_tracking_disabled
538 Project.find(2).disable_module! :issue_tracking
546 Project.find(2).disable_module! :issue_tracking
539 assert_not_include Project.find(2), Issue.allowed_target_projects_on_move(User.find(2))
547 assert_not_include Project.find(2), Issue.allowed_target_projects_on_move(User.find(2))
540 end
548 end
541
549
542 def test_move_to_another_project_with_same_category
550 def test_move_to_another_project_with_same_category
543 issue = Issue.find(1)
551 issue = Issue.find(1)
544 issue.project = Project.find(2)
552 issue.project = Project.find(2)
545 assert issue.save
553 assert issue.save
546 issue.reload
554 issue.reload
547 assert_equal 2, issue.project_id
555 assert_equal 2, issue.project_id
548 # Category changes
556 # Category changes
549 assert_equal 4, issue.category_id
557 assert_equal 4, issue.category_id
550 # Make sure time entries were move to the target project
558 # Make sure time entries were move to the target project
551 assert_equal 2, issue.time_entries.first.project_id
559 assert_equal 2, issue.time_entries.first.project_id
552 end
560 end
553
561
554 def test_move_to_another_project_without_same_category
562 def test_move_to_another_project_without_same_category
555 issue = Issue.find(2)
563 issue = Issue.find(2)
556 issue.project = Project.find(2)
564 issue.project = Project.find(2)
557 assert issue.save
565 assert issue.save
558 issue.reload
566 issue.reload
559 assert_equal 2, issue.project_id
567 assert_equal 2, issue.project_id
560 # Category cleared
568 # Category cleared
561 assert_nil issue.category_id
569 assert_nil issue.category_id
562 end
570 end
563
571
564 def test_move_to_another_project_should_clear_fixed_version_when_not_shared
572 def test_move_to_another_project_should_clear_fixed_version_when_not_shared
565 issue = Issue.find(1)
573 issue = Issue.find(1)
566 issue.update_attribute(:fixed_version_id, 1)
574 issue.update_attribute(:fixed_version_id, 1)
567 issue.project = Project.find(2)
575 issue.project = Project.find(2)
568 assert issue.save
576 assert issue.save
569 issue.reload
577 issue.reload
570 assert_equal 2, issue.project_id
578 assert_equal 2, issue.project_id
571 # Cleared fixed_version
579 # Cleared fixed_version
572 assert_equal nil, issue.fixed_version
580 assert_equal nil, issue.fixed_version
573 end
581 end
574
582
575 def test_move_to_another_project_should_keep_fixed_version_when_shared_with_the_target_project
583 def test_move_to_another_project_should_keep_fixed_version_when_shared_with_the_target_project
576 issue = Issue.find(1)
584 issue = Issue.find(1)
577 issue.update_attribute(:fixed_version_id, 4)
585 issue.update_attribute(:fixed_version_id, 4)
578 issue.project = Project.find(5)
586 issue.project = Project.find(5)
579 assert issue.save
587 assert issue.save
580 issue.reload
588 issue.reload
581 assert_equal 5, issue.project_id
589 assert_equal 5, issue.project_id
582 # Keep fixed_version
590 # Keep fixed_version
583 assert_equal 4, issue.fixed_version_id
591 assert_equal 4, issue.fixed_version_id
584 end
592 end
585
593
586 def test_move_to_another_project_should_clear_fixed_version_when_not_shared_with_the_target_project
594 def test_move_to_another_project_should_clear_fixed_version_when_not_shared_with_the_target_project
587 issue = Issue.find(1)
595 issue = Issue.find(1)
588 issue.update_attribute(:fixed_version_id, 1)
596 issue.update_attribute(:fixed_version_id, 1)
589 issue.project = Project.find(5)
597 issue.project = Project.find(5)
590 assert issue.save
598 assert issue.save
591 issue.reload
599 issue.reload
592 assert_equal 5, issue.project_id
600 assert_equal 5, issue.project_id
593 # Cleared fixed_version
601 # Cleared fixed_version
594 assert_equal nil, issue.fixed_version
602 assert_equal nil, issue.fixed_version
595 end
603 end
596
604
597 def test_move_to_another_project_should_keep_fixed_version_when_shared_systemwide
605 def test_move_to_another_project_should_keep_fixed_version_when_shared_systemwide
598 issue = Issue.find(1)
606 issue = Issue.find(1)
599 issue.update_attribute(:fixed_version_id, 7)
607 issue.update_attribute(:fixed_version_id, 7)
600 issue.project = Project.find(2)
608 issue.project = Project.find(2)
601 assert issue.save
609 assert issue.save
602 issue.reload
610 issue.reload
603 assert_equal 2, issue.project_id
611 assert_equal 2, issue.project_id
604 # Keep fixed_version
612 # Keep fixed_version
605 assert_equal 7, issue.fixed_version_id
613 assert_equal 7, issue.fixed_version_id
606 end
614 end
607
615
608 def test_move_to_another_project_with_disabled_tracker
616 def test_move_to_another_project_with_disabled_tracker
609 issue = Issue.find(1)
617 issue = Issue.find(1)
610 target = Project.find(2)
618 target = Project.find(2)
611 target.tracker_ids = [3]
619 target.tracker_ids = [3]
612 target.save
620 target.save
613 issue.project = target
621 issue.project = target
614 assert issue.save
622 assert issue.save
615 issue.reload
623 issue.reload
616 assert_equal 2, issue.project_id
624 assert_equal 2, issue.project_id
617 assert_equal 3, issue.tracker_id
625 assert_equal 3, issue.tracker_id
618 end
626 end
619
627
620 def test_copy_to_the_same_project
628 def test_copy_to_the_same_project
621 issue = Issue.find(1)
629 issue = Issue.find(1)
622 copy = issue.copy
630 copy = issue.copy
623 assert_difference 'Issue.count' do
631 assert_difference 'Issue.count' do
624 copy.save!
632 copy.save!
625 end
633 end
626 assert_kind_of Issue, copy
634 assert_kind_of Issue, copy
627 assert_equal issue.project, copy.project
635 assert_equal issue.project, copy.project
628 assert_equal "125", copy.custom_value_for(2).value
636 assert_equal "125", copy.custom_value_for(2).value
629 end
637 end
630
638
631 def test_copy_to_another_project_and_tracker
639 def test_copy_to_another_project_and_tracker
632 issue = Issue.find(1)
640 issue = Issue.find(1)
633 copy = issue.copy(:project_id => 3, :tracker_id => 2)
641 copy = issue.copy(:project_id => 3, :tracker_id => 2)
634 assert_difference 'Issue.count' do
642 assert_difference 'Issue.count' do
635 copy.save!
643 copy.save!
636 end
644 end
637 copy.reload
645 copy.reload
638 assert_kind_of Issue, copy
646 assert_kind_of Issue, copy
639 assert_equal Project.find(3), copy.project
647 assert_equal Project.find(3), copy.project
640 assert_equal Tracker.find(2), copy.tracker
648 assert_equal Tracker.find(2), copy.tracker
641 # Custom field #2 is not associated with target tracker
649 # Custom field #2 is not associated with target tracker
642 assert_nil copy.custom_value_for(2)
650 assert_nil copy.custom_value_for(2)
643 end
651 end
644
652
645 context "#copy" do
653 context "#copy" do
646 setup do
654 setup do
647 @issue = Issue.find(1)
655 @issue = Issue.find(1)
648 end
656 end
649
657
650 should "not create a journal" do
658 should "not create a journal" do
651 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3)
659 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3)
652 copy.save!
660 copy.save!
653 assert_equal 0, copy.reload.journals.size
661 assert_equal 0, copy.reload.journals.size
654 end
662 end
655
663
656 should "allow assigned_to changes" do
664 should "allow assigned_to changes" do
657 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3)
665 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3)
658 assert_equal 3, copy.assigned_to_id
666 assert_equal 3, copy.assigned_to_id
659 end
667 end
660
668
661 should "allow status changes" do
669 should "allow status changes" do
662 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :status_id => 2)
670 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :status_id => 2)
663 assert_equal 2, copy.status_id
671 assert_equal 2, copy.status_id
664 end
672 end
665
673
666 should "allow start date changes" do
674 should "allow start date changes" do
667 date = Date.today
675 date = Date.today
668 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :start_date => date)
676 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :start_date => date)
669 assert_equal date, copy.start_date
677 assert_equal date, copy.start_date
670 end
678 end
671
679
672 should "allow due date changes" do
680 should "allow due date changes" do
673 date = Date.today
681 date = Date.today
674 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :due_date => date)
682 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :due_date => date)
675 assert_equal date, copy.due_date
683 assert_equal date, copy.due_date
676 end
684 end
677
685
678 should "set current user as author" do
686 should "set current user as author" do
679 User.current = User.find(9)
687 User.current = User.find(9)
680 copy = @issue.copy(:project_id => 3, :tracker_id => 2)
688 copy = @issue.copy(:project_id => 3, :tracker_id => 2)
681 assert_equal User.current, copy.author
689 assert_equal User.current, copy.author
682 end
690 end
683
691
684 should "create a journal with notes" do
692 should "create a journal with notes" do
685 date = Date.today
693 date = Date.today
686 notes = "Notes added when copying"
694 notes = "Notes added when copying"
687 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :start_date => date)
695 copy = @issue.copy(:project_id => 3, :tracker_id => 2, :start_date => date)
688 copy.init_journal(User.current, notes)
696 copy.init_journal(User.current, notes)
689 copy.save!
697 copy.save!
690
698
691 assert_equal 1, copy.journals.size
699 assert_equal 1, copy.journals.size
692 journal = copy.journals.first
700 journal = copy.journals.first
693 assert_equal 0, journal.details.size
701 assert_equal 0, journal.details.size
694 assert_equal notes, journal.notes
702 assert_equal notes, journal.notes
695 end
703 end
696 end
704 end
697
705
698 def test_recipients_should_include_previous_assignee
706 def test_recipients_should_include_previous_assignee
699 user = User.find(3)
707 user = User.find(3)
700 user.members.update_all ["mail_notification = ?", false]
708 user.members.update_all ["mail_notification = ?", false]
701 user.update_attribute :mail_notification, 'only_assigned'
709 user.update_attribute :mail_notification, 'only_assigned'
702
710
703 issue = Issue.find(2)
711 issue = Issue.find(2)
704 issue.assigned_to = nil
712 issue.assigned_to = nil
705 assert_include user.mail, issue.recipients
713 assert_include user.mail, issue.recipients
706 issue.save!
714 issue.save!
707 assert !issue.recipients.include?(user.mail)
715 assert !issue.recipients.include?(user.mail)
708 end
716 end
709
717
710 def test_recipients_should_not_include_users_that_cannot_view_the_issue
718 def test_recipients_should_not_include_users_that_cannot_view_the_issue
711 issue = Issue.find(12)
719 issue = Issue.find(12)
712 assert issue.recipients.include?(issue.author.mail)
720 assert issue.recipients.include?(issue.author.mail)
713 # copy the issue to a private project
721 # copy the issue to a private project
714 copy = issue.copy(:project_id => 5, :tracker_id => 2)
722 copy = issue.copy(:project_id => 5, :tracker_id => 2)
715 # author is not a member of project anymore
723 # author is not a member of project anymore
716 assert !copy.recipients.include?(copy.author.mail)
724 assert !copy.recipients.include?(copy.author.mail)
717 end
725 end
718
726
719 def test_recipients_should_include_the_assigned_group_members
727 def test_recipients_should_include_the_assigned_group_members
720 group_member = User.generate_with_protected!
728 group_member = User.generate_with_protected!
721 group = Group.generate!
729 group = Group.generate!
722 group.users << group_member
730 group.users << group_member
723
731
724 issue = Issue.find(12)
732 issue = Issue.find(12)
725 issue.assigned_to = group
733 issue.assigned_to = group
726 assert issue.recipients.include?(group_member.mail)
734 assert issue.recipients.include?(group_member.mail)
727 end
735 end
728
736
729 def test_watcher_recipients_should_not_include_users_that_cannot_view_the_issue
737 def test_watcher_recipients_should_not_include_users_that_cannot_view_the_issue
730 user = User.find(3)
738 user = User.find(3)
731 issue = Issue.find(9)
739 issue = Issue.find(9)
732 Watcher.create!(:user => user, :watchable => issue)
740 Watcher.create!(:user => user, :watchable => issue)
733 assert issue.watched_by?(user)
741 assert issue.watched_by?(user)
734 assert !issue.watcher_recipients.include?(user.mail)
742 assert !issue.watcher_recipients.include?(user.mail)
735 end
743 end
736
744
737 def test_issue_destroy
745 def test_issue_destroy
738 Issue.find(1).destroy
746 Issue.find(1).destroy
739 assert_nil Issue.find_by_id(1)
747 assert_nil Issue.find_by_id(1)
740 assert_nil TimeEntry.find_by_issue_id(1)
748 assert_nil TimeEntry.find_by_issue_id(1)
741 end
749 end
742
750
743 def test_blocked
751 def test_blocked
744 blocked_issue = Issue.find(9)
752 blocked_issue = Issue.find(9)
745 blocking_issue = Issue.find(10)
753 blocking_issue = Issue.find(10)
746
754
747 assert blocked_issue.blocked?
755 assert blocked_issue.blocked?
748 assert !blocking_issue.blocked?
756 assert !blocking_issue.blocked?
749 end
757 end
750
758
751 def test_blocked_issues_dont_allow_closed_statuses
759 def test_blocked_issues_dont_allow_closed_statuses
752 blocked_issue = Issue.find(9)
760 blocked_issue = Issue.find(9)
753
761
754 allowed_statuses = blocked_issue.new_statuses_allowed_to(users(:users_002))
762 allowed_statuses = blocked_issue.new_statuses_allowed_to(users(:users_002))
755 assert !allowed_statuses.empty?
763 assert !allowed_statuses.empty?
756 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
764 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
757 assert closed_statuses.empty?
765 assert closed_statuses.empty?
758 end
766 end
759
767
760 def test_unblocked_issues_allow_closed_statuses
768 def test_unblocked_issues_allow_closed_statuses
761 blocking_issue = Issue.find(10)
769 blocking_issue = Issue.find(10)
762
770
763 allowed_statuses = blocking_issue.new_statuses_allowed_to(users(:users_002))
771 allowed_statuses = blocking_issue.new_statuses_allowed_to(users(:users_002))
764 assert !allowed_statuses.empty?
772 assert !allowed_statuses.empty?
765 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
773 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
766 assert !closed_statuses.empty?
774 assert !closed_statuses.empty?
767 end
775 end
768
776
769 def test_rescheduling_an_issue_should_reschedule_following_issue
777 def test_rescheduling_an_issue_should_reschedule_following_issue
770 issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => '-', :start_date => Date.today, :due_date => Date.today + 2)
778 issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => '-', :start_date => Date.today, :due_date => Date.today + 2)
771 issue2 = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => '-', :start_date => Date.today, :due_date => Date.today + 2)
779 issue2 = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => '-', :start_date => Date.today, :due_date => Date.today + 2)
772 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
780 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
773 assert_equal issue1.due_date + 1, issue2.reload.start_date
781 assert_equal issue1.due_date + 1, issue2.reload.start_date
774
782
775 issue1.due_date = Date.today + 5
783 issue1.due_date = Date.today + 5
776 issue1.save!
784 issue1.save!
777 assert_equal issue1.due_date + 1, issue2.reload.start_date
785 assert_equal issue1.due_date + 1, issue2.reload.start_date
778 end
786 end
779
787
780 def test_rescheduling_a_stale_issue_should_not_raise_an_error
788 def test_rescheduling_a_stale_issue_should_not_raise_an_error
781 stale = Issue.find(1)
789 stale = Issue.find(1)
782 issue = Issue.find(1)
790 issue = Issue.find(1)
783 issue.subject = "Updated"
791 issue.subject = "Updated"
784 issue.save!
792 issue.save!
785
793
786 date = 10.days.from_now.to_date
794 date = 10.days.from_now.to_date
787 assert_nothing_raised do
795 assert_nothing_raised do
788 stale.reschedule_after(date)
796 stale.reschedule_after(date)
789 end
797 end
790 assert_equal date, stale.reload.start_date
798 assert_equal date, stale.reload.start_date
791 end
799 end
792
800
793 def test_overdue
801 def test_overdue
794 assert Issue.new(:due_date => 1.day.ago.to_date).overdue?
802 assert Issue.new(:due_date => 1.day.ago.to_date).overdue?
795 assert !Issue.new(:due_date => Date.today).overdue?
803 assert !Issue.new(:due_date => Date.today).overdue?
796 assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue?
804 assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue?
797 assert !Issue.new(:due_date => nil).overdue?
805 assert !Issue.new(:due_date => nil).overdue?
798 assert !Issue.new(:due_date => 1.day.ago.to_date, :status => IssueStatus.find(:first, :conditions => {:is_closed => true})).overdue?
806 assert !Issue.new(:due_date => 1.day.ago.to_date, :status => IssueStatus.find(:first, :conditions => {:is_closed => true})).overdue?
799 end
807 end
800
808
801 context "#behind_schedule?" do
809 context "#behind_schedule?" do
802 should "be false if the issue has no start_date" do
810 should "be false if the issue has no start_date" do
803 assert !Issue.new(:start_date => nil, :due_date => 1.day.from_now.to_date, :done_ratio => 0).behind_schedule?
811 assert !Issue.new(:start_date => nil, :due_date => 1.day.from_now.to_date, :done_ratio => 0).behind_schedule?
804 end
812 end
805
813
806 should "be false if the issue has no end_date" do
814 should "be false if the issue has no end_date" do
807 assert !Issue.new(:start_date => 1.day.from_now.to_date, :due_date => nil, :done_ratio => 0).behind_schedule?
815 assert !Issue.new(:start_date => 1.day.from_now.to_date, :due_date => nil, :done_ratio => 0).behind_schedule?
808 end
816 end
809
817
810 should "be false if the issue has more done than it's calendar time" do
818 should "be false if the issue has more done than it's calendar time" do
811 assert !Issue.new(:start_date => 50.days.ago.to_date, :due_date => 50.days.from_now.to_date, :done_ratio => 90).behind_schedule?
819 assert !Issue.new(:start_date => 50.days.ago.to_date, :due_date => 50.days.from_now.to_date, :done_ratio => 90).behind_schedule?
812 end
820 end
813
821
814 should "be true if the issue hasn't been started at all" do
822 should "be true if the issue hasn't been started at all" do
815 assert Issue.new(:start_date => 1.day.ago.to_date, :due_date => 1.day.from_now.to_date, :done_ratio => 0).behind_schedule?
823 assert Issue.new(:start_date => 1.day.ago.to_date, :due_date => 1.day.from_now.to_date, :done_ratio => 0).behind_schedule?
816 end
824 end
817
825
818 should "be true if the issue has used more calendar time than it's done ratio" do
826 should "be true if the issue has used more calendar time than it's done ratio" do
819 assert Issue.new(:start_date => 100.days.ago.to_date, :due_date => Date.today, :done_ratio => 90).behind_schedule?
827 assert Issue.new(:start_date => 100.days.ago.to_date, :due_date => Date.today, :done_ratio => 90).behind_schedule?
820 end
828 end
821 end
829 end
822
830
823 context "#assignable_users" do
831 context "#assignable_users" do
824 should "be Users" do
832 should "be Users" do
825 assert_kind_of User, Issue.find(1).assignable_users.first
833 assert_kind_of User, Issue.find(1).assignable_users.first
826 end
834 end
827
835
828 should "include the issue author" do
836 should "include the issue author" do
829 project = Project.find(1)
837 project = Project.find(1)
830 non_project_member = User.generate!
838 non_project_member = User.generate!
831 issue = Issue.generate_for_project!(project, :author => non_project_member)
839 issue = Issue.generate_for_project!(project, :author => non_project_member)
832
840
833 assert issue.assignable_users.include?(non_project_member)
841 assert issue.assignable_users.include?(non_project_member)
834 end
842 end
835
843
836 should "include the current assignee" do
844 should "include the current assignee" do
837 project = Project.find(1)
845 project = Project.find(1)
838 user = User.generate!
846 user = User.generate!
839 issue = Issue.generate_for_project!(project, :assigned_to => user)
847 issue = Issue.generate_for_project!(project, :assigned_to => user)
840 user.lock!
848 user.lock!
841
849
842 assert Issue.find(issue.id).assignable_users.include?(user)
850 assert Issue.find(issue.id).assignable_users.include?(user)
843 end
851 end
844
852
845 should "not show the issue author twice" do
853 should "not show the issue author twice" do
846 assignable_user_ids = Issue.find(1).assignable_users.collect(&:id)
854 assignable_user_ids = Issue.find(1).assignable_users.collect(&:id)
847 assert_equal 2, assignable_user_ids.length
855 assert_equal 2, assignable_user_ids.length
848
856
849 assignable_user_ids.each do |user_id|
857 assignable_user_ids.each do |user_id|
850 assert_equal 1, assignable_user_ids.select {|i| i == user_id}.length, "User #{user_id} appears more or less than once"
858 assert_equal 1, assignable_user_ids.select {|i| i == user_id}.length, "User #{user_id} appears more or less than once"
851 end
859 end
852 end
860 end
853
861
854 context "with issue_group_assignment" do
862 context "with issue_group_assignment" do
855 should "include groups" do
863 should "include groups" do
856 issue = Issue.new(:project => Project.find(2))
864 issue = Issue.new(:project => Project.find(2))
857
865
858 with_settings :issue_group_assignment => '1' do
866 with_settings :issue_group_assignment => '1' do
859 assert_equal %w(Group User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
867 assert_equal %w(Group User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
860 assert issue.assignable_users.include?(Group.find(11))
868 assert issue.assignable_users.include?(Group.find(11))
861 end
869 end
862 end
870 end
863 end
871 end
864
872
865 context "without issue_group_assignment" do
873 context "without issue_group_assignment" do
866 should "not include groups" do
874 should "not include groups" do
867 issue = Issue.new(:project => Project.find(2))
875 issue = Issue.new(:project => Project.find(2))
868
876
869 with_settings :issue_group_assignment => '0' do
877 with_settings :issue_group_assignment => '0' do
870 assert_equal %w(User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
878 assert_equal %w(User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
871 assert !issue.assignable_users.include?(Group.find(11))
879 assert !issue.assignable_users.include?(Group.find(11))
872 end
880 end
873 end
881 end
874 end
882 end
875 end
883 end
876
884
877 def test_create_should_send_email_notification
885 def test_create_should_send_email_notification
878 ActionMailer::Base.deliveries.clear
886 ActionMailer::Base.deliveries.clear
879 issue = Issue.new(:project_id => 1, :tracker_id => 1,
887 issue = Issue.new(:project_id => 1, :tracker_id => 1,
880 :author_id => 3, :status_id => 1,
888 :author_id => 3, :status_id => 1,
881 :priority => IssuePriority.all.first,
889 :priority => IssuePriority.all.first,
882 :subject => 'test_create', :estimated_hours => '1:30')
890 :subject => 'test_create', :estimated_hours => '1:30')
883
891
884 assert issue.save
892 assert issue.save
885 assert_equal 1, ActionMailer::Base.deliveries.size
893 assert_equal 1, ActionMailer::Base.deliveries.size
886 end
894 end
887
895
888 def test_stale_issue_should_not_send_email_notification
896 def test_stale_issue_should_not_send_email_notification
889 ActionMailer::Base.deliveries.clear
897 ActionMailer::Base.deliveries.clear
890 issue = Issue.find(1)
898 issue = Issue.find(1)
891 stale = Issue.find(1)
899 stale = Issue.find(1)
892
900
893 issue.init_journal(User.find(1))
901 issue.init_journal(User.find(1))
894 issue.subject = 'Subjet update'
902 issue.subject = 'Subjet update'
895 assert issue.save
903 assert issue.save
896 assert_equal 1, ActionMailer::Base.deliveries.size
904 assert_equal 1, ActionMailer::Base.deliveries.size
897 ActionMailer::Base.deliveries.clear
905 ActionMailer::Base.deliveries.clear
898
906
899 stale.init_journal(User.find(1))
907 stale.init_journal(User.find(1))
900 stale.subject = 'Another subjet update'
908 stale.subject = 'Another subjet update'
901 assert_raise ActiveRecord::StaleObjectError do
909 assert_raise ActiveRecord::StaleObjectError do
902 stale.save
910 stale.save
903 end
911 end
904 assert ActionMailer::Base.deliveries.empty?
912 assert ActionMailer::Base.deliveries.empty?
905 end
913 end
906
914
907 def test_journalized_description
915 def test_journalized_description
908 IssueCustomField.delete_all
916 IssueCustomField.delete_all
909
917
910 i = Issue.first
918 i = Issue.first
911 old_description = i.description
919 old_description = i.description
912 new_description = "This is the new description"
920 new_description = "This is the new description"
913
921
914 i.init_journal(User.find(2))
922 i.init_journal(User.find(2))
915 i.description = new_description
923 i.description = new_description
916 assert_difference 'Journal.count', 1 do
924 assert_difference 'Journal.count', 1 do
917 assert_difference 'JournalDetail.count', 1 do
925 assert_difference 'JournalDetail.count', 1 do
918 i.save!
926 i.save!
919 end
927 end
920 end
928 end
921
929
922 detail = JournalDetail.first(:order => 'id DESC')
930 detail = JournalDetail.first(:order => 'id DESC')
923 assert_equal i, detail.journal.journalized
931 assert_equal i, detail.journal.journalized
924 assert_equal 'attr', detail.property
932 assert_equal 'attr', detail.property
925 assert_equal 'description', detail.prop_key
933 assert_equal 'description', detail.prop_key
926 assert_equal old_description, detail.old_value
934 assert_equal old_description, detail.old_value
927 assert_equal new_description, detail.value
935 assert_equal new_description, detail.value
928 end
936 end
929
937
930 def test_blank_descriptions_should_not_be_journalized
938 def test_blank_descriptions_should_not_be_journalized
931 IssueCustomField.delete_all
939 IssueCustomField.delete_all
932 Issue.update_all("description = NULL", "id=1")
940 Issue.update_all("description = NULL", "id=1")
933
941
934 i = Issue.find(1)
942 i = Issue.find(1)
935 i.init_journal(User.find(2))
943 i.init_journal(User.find(2))
936 i.subject = "blank description"
944 i.subject = "blank description"
937 i.description = "\r\n"
945 i.description = "\r\n"
938
946
939 assert_difference 'Journal.count', 1 do
947 assert_difference 'Journal.count', 1 do
940 assert_difference 'JournalDetail.count', 1 do
948 assert_difference 'JournalDetail.count', 1 do
941 i.save!
949 i.save!
942 end
950 end
943 end
951 end
944 end
952 end
945
953
946 def test_journalized_multi_custom_field
954 def test_journalized_multi_custom_field
947 field = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
955 field = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
948 :tracker_ids => [1], :possible_values => ['value1', 'value2', 'value3'], :multiple => true)
956 :tracker_ids => [1], :possible_values => ['value1', 'value2', 'value3'], :multiple => true)
949
957
950 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :subject => 'Test', :author_id => 1)
958 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :subject => 'Test', :author_id => 1)
951
959
952 assert_difference 'Journal.count' do
960 assert_difference 'Journal.count' do
953 assert_difference 'JournalDetail.count' do
961 assert_difference 'JournalDetail.count' do
954 issue.init_journal(User.first)
962 issue.init_journal(User.first)
955 issue.custom_field_values = {field.id => ['value1']}
963 issue.custom_field_values = {field.id => ['value1']}
956 issue.save!
964 issue.save!
957 end
965 end
958 assert_difference 'JournalDetail.count' do
966 assert_difference 'JournalDetail.count' do
959 issue.init_journal(User.first)
967 issue.init_journal(User.first)
960 issue.custom_field_values = {field.id => ['value1', 'value2']}
968 issue.custom_field_values = {field.id => ['value1', 'value2']}
961 issue.save!
969 issue.save!
962 end
970 end
963 assert_difference 'JournalDetail.count', 2 do
971 assert_difference 'JournalDetail.count', 2 do
964 issue.init_journal(User.first)
972 issue.init_journal(User.first)
965 issue.custom_field_values = {field.id => ['value3', 'value2']}
973 issue.custom_field_values = {field.id => ['value3', 'value2']}
966 issue.save!
974 issue.save!
967 end
975 end
968 assert_difference 'JournalDetail.count', 2 do
976 assert_difference 'JournalDetail.count', 2 do
969 issue.init_journal(User.first)
977 issue.init_journal(User.first)
970 issue.custom_field_values = {field.id => nil}
978 issue.custom_field_values = {field.id => nil}
971 issue.save!
979 issue.save!
972 end
980 end
973 end
981 end
974 end
982 end
975
983
976 def test_description_eol_should_be_normalized
984 def test_description_eol_should_be_normalized
977 i = Issue.new(:description => "CR \r LF \n CRLF \r\n")
985 i = Issue.new(:description => "CR \r LF \n CRLF \r\n")
978 assert_equal "CR \r\n LF \r\n CRLF \r\n", i.description
986 assert_equal "CR \r\n LF \r\n CRLF \r\n", i.description
979 end
987 end
980
988
981 def test_saving_twice_should_not_duplicate_journal_details
989 def test_saving_twice_should_not_duplicate_journal_details
982 i = Issue.find(:first)
990 i = Issue.find(:first)
983 i.init_journal(User.find(2), 'Some notes')
991 i.init_journal(User.find(2), 'Some notes')
984 # initial changes
992 # initial changes
985 i.subject = 'New subject'
993 i.subject = 'New subject'
986 i.done_ratio = i.done_ratio + 10
994 i.done_ratio = i.done_ratio + 10
987 assert_difference 'Journal.count' do
995 assert_difference 'Journal.count' do
988 assert i.save
996 assert i.save
989 end
997 end
990 # 1 more change
998 # 1 more change
991 i.priority = IssuePriority.find(:first, :conditions => ["id <> ?", i.priority_id])
999 i.priority = IssuePriority.find(:first, :conditions => ["id <> ?", i.priority_id])
992 assert_no_difference 'Journal.count' do
1000 assert_no_difference 'Journal.count' do
993 assert_difference 'JournalDetail.count', 1 do
1001 assert_difference 'JournalDetail.count', 1 do
994 i.save
1002 i.save
995 end
1003 end
996 end
1004 end
997 # no more change
1005 # no more change
998 assert_no_difference 'Journal.count' do
1006 assert_no_difference 'Journal.count' do
999 assert_no_difference 'JournalDetail.count' do
1007 assert_no_difference 'JournalDetail.count' do
1000 i.save
1008 i.save
1001 end
1009 end
1002 end
1010 end
1003 end
1011 end
1004
1012
1005 def test_all_dependent_issues
1013 def test_all_dependent_issues
1006 IssueRelation.delete_all
1014 IssueRelation.delete_all
1007 assert IssueRelation.create!(:issue_from => Issue.find(1),
1015 assert IssueRelation.create!(:issue_from => Issue.find(1),
1008 :issue_to => Issue.find(2),
1016 :issue_to => Issue.find(2),
1009 :relation_type => IssueRelation::TYPE_PRECEDES)
1017 :relation_type => IssueRelation::TYPE_PRECEDES)
1010 assert IssueRelation.create!(:issue_from => Issue.find(2),
1018 assert IssueRelation.create!(:issue_from => Issue.find(2),
1011 :issue_to => Issue.find(3),
1019 :issue_to => Issue.find(3),
1012 :relation_type => IssueRelation::TYPE_PRECEDES)
1020 :relation_type => IssueRelation::TYPE_PRECEDES)
1013 assert IssueRelation.create!(:issue_from => Issue.find(3),
1021 assert IssueRelation.create!(:issue_from => Issue.find(3),
1014 :issue_to => Issue.find(8),
1022 :issue_to => Issue.find(8),
1015 :relation_type => IssueRelation::TYPE_PRECEDES)
1023 :relation_type => IssueRelation::TYPE_PRECEDES)
1016
1024
1017 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
1025 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
1018 end
1026 end
1019
1027
1020 def test_all_dependent_issues_with_persistent_circular_dependency
1028 def test_all_dependent_issues_with_persistent_circular_dependency
1021 IssueRelation.delete_all
1029 IssueRelation.delete_all
1022 assert IssueRelation.create!(:issue_from => Issue.find(1),
1030 assert IssueRelation.create!(:issue_from => Issue.find(1),
1023 :issue_to => Issue.find(2),
1031 :issue_to => Issue.find(2),
1024 :relation_type => IssueRelation::TYPE_PRECEDES)
1032 :relation_type => IssueRelation::TYPE_PRECEDES)
1025 assert IssueRelation.create!(:issue_from => Issue.find(2),
1033 assert IssueRelation.create!(:issue_from => Issue.find(2),
1026 :issue_to => Issue.find(3),
1034 :issue_to => Issue.find(3),
1027 :relation_type => IssueRelation::TYPE_PRECEDES)
1035 :relation_type => IssueRelation::TYPE_PRECEDES)
1028 # Validation skipping
1036 # Validation skipping
1029 assert IssueRelation.new(:issue_from => Issue.find(3),
1037 assert IssueRelation.new(:issue_from => Issue.find(3),
1030 :issue_to => Issue.find(1),
1038 :issue_to => Issue.find(1),
1031 :relation_type => IssueRelation::TYPE_PRECEDES).save(false)
1039 :relation_type => IssueRelation::TYPE_PRECEDES).save(false)
1032
1040
1033 assert_equal [2, 3], Issue.find(1).all_dependent_issues.collect(&:id).sort
1041 assert_equal [2, 3], Issue.find(1).all_dependent_issues.collect(&:id).sort
1034 end
1042 end
1035
1043
1036 def test_all_dependent_issues_with_persistent_multiple_circular_dependencies
1044 def test_all_dependent_issues_with_persistent_multiple_circular_dependencies
1037 IssueRelation.delete_all
1045 IssueRelation.delete_all
1038 assert IssueRelation.create!(:issue_from => Issue.find(1),
1046 assert IssueRelation.create!(:issue_from => Issue.find(1),
1039 :issue_to => Issue.find(2),
1047 :issue_to => Issue.find(2),
1040 :relation_type => IssueRelation::TYPE_RELATES)
1048 :relation_type => IssueRelation::TYPE_RELATES)
1041 assert IssueRelation.create!(:issue_from => Issue.find(2),
1049 assert IssueRelation.create!(:issue_from => Issue.find(2),
1042 :issue_to => Issue.find(3),
1050 :issue_to => Issue.find(3),
1043 :relation_type => IssueRelation::TYPE_RELATES)
1051 :relation_type => IssueRelation::TYPE_RELATES)
1044 assert IssueRelation.create!(:issue_from => Issue.find(3),
1052 assert IssueRelation.create!(:issue_from => Issue.find(3),
1045 :issue_to => Issue.find(8),
1053 :issue_to => Issue.find(8),
1046 :relation_type => IssueRelation::TYPE_RELATES)
1054 :relation_type => IssueRelation::TYPE_RELATES)
1047 # Validation skipping
1055 # Validation skipping
1048 assert IssueRelation.new(:issue_from => Issue.find(8),
1056 assert IssueRelation.new(:issue_from => Issue.find(8),
1049 :issue_to => Issue.find(2),
1057 :issue_to => Issue.find(2),
1050 :relation_type => IssueRelation::TYPE_RELATES).save(false)
1058 :relation_type => IssueRelation::TYPE_RELATES).save(false)
1051 assert IssueRelation.new(:issue_from => Issue.find(3),
1059 assert IssueRelation.new(:issue_from => Issue.find(3),
1052 :issue_to => Issue.find(1),
1060 :issue_to => Issue.find(1),
1053 :relation_type => IssueRelation::TYPE_RELATES).save(false)
1061 :relation_type => IssueRelation::TYPE_RELATES).save(false)
1054
1062
1055 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
1063 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
1056 end
1064 end
1057
1065
1058 context "#done_ratio" do
1066 context "#done_ratio" do
1059 setup do
1067 setup do
1060 @issue = Issue.find(1)
1068 @issue = Issue.find(1)
1061 @issue_status = IssueStatus.find(1)
1069 @issue_status = IssueStatus.find(1)
1062 @issue_status.update_attribute(:default_done_ratio, 50)
1070 @issue_status.update_attribute(:default_done_ratio, 50)
1063 @issue2 = Issue.find(2)
1071 @issue2 = Issue.find(2)
1064 @issue_status2 = IssueStatus.find(2)
1072 @issue_status2 = IssueStatus.find(2)
1065 @issue_status2.update_attribute(:default_done_ratio, 0)
1073 @issue_status2.update_attribute(:default_done_ratio, 0)
1066 end
1074 end
1067
1075
1068 teardown do
1076 teardown do
1069 Setting.issue_done_ratio = 'issue_field'
1077 Setting.issue_done_ratio = 'issue_field'
1070 end
1078 end
1071
1079
1072 context "with Setting.issue_done_ratio using the issue_field" do
1080 context "with Setting.issue_done_ratio using the issue_field" do
1073 setup do
1081 setup do
1074 Setting.issue_done_ratio = 'issue_field'
1082 Setting.issue_done_ratio = 'issue_field'
1075 end
1083 end
1076
1084
1077 should "read the issue's field" do
1085 should "read the issue's field" do
1078 assert_equal 0, @issue.done_ratio
1086 assert_equal 0, @issue.done_ratio
1079 assert_equal 30, @issue2.done_ratio
1087 assert_equal 30, @issue2.done_ratio
1080 end
1088 end
1081 end
1089 end
1082
1090
1083 context "with Setting.issue_done_ratio using the issue_status" do
1091 context "with Setting.issue_done_ratio using the issue_status" do
1084 setup do
1092 setup do
1085 Setting.issue_done_ratio = 'issue_status'
1093 Setting.issue_done_ratio = 'issue_status'
1086 end
1094 end
1087
1095
1088 should "read the Issue Status's default done ratio" do
1096 should "read the Issue Status's default done ratio" do
1089 assert_equal 50, @issue.done_ratio
1097 assert_equal 50, @issue.done_ratio
1090 assert_equal 0, @issue2.done_ratio
1098 assert_equal 0, @issue2.done_ratio
1091 end
1099 end
1092 end
1100 end
1093 end
1101 end
1094
1102
1095 context "#update_done_ratio_from_issue_status" do
1103 context "#update_done_ratio_from_issue_status" do
1096 setup do
1104 setup do
1097 @issue = Issue.find(1)
1105 @issue = Issue.find(1)
1098 @issue_status = IssueStatus.find(1)
1106 @issue_status = IssueStatus.find(1)
1099 @issue_status.update_attribute(:default_done_ratio, 50)
1107 @issue_status.update_attribute(:default_done_ratio, 50)
1100 @issue2 = Issue.find(2)
1108 @issue2 = Issue.find(2)
1101 @issue_status2 = IssueStatus.find(2)
1109 @issue_status2 = IssueStatus.find(2)
1102 @issue_status2.update_attribute(:default_done_ratio, 0)
1110 @issue_status2.update_attribute(:default_done_ratio, 0)
1103 end
1111 end
1104
1112
1105 context "with Setting.issue_done_ratio using the issue_field" do
1113 context "with Setting.issue_done_ratio using the issue_field" do
1106 setup do
1114 setup do
1107 Setting.issue_done_ratio = 'issue_field'
1115 Setting.issue_done_ratio = 'issue_field'
1108 end
1116 end
1109
1117
1110 should "not change the issue" do
1118 should "not change the issue" do
1111 @issue.update_done_ratio_from_issue_status
1119 @issue.update_done_ratio_from_issue_status
1112 @issue2.update_done_ratio_from_issue_status
1120 @issue2.update_done_ratio_from_issue_status
1113
1121
1114 assert_equal 0, @issue.read_attribute(:done_ratio)
1122 assert_equal 0, @issue.read_attribute(:done_ratio)
1115 assert_equal 30, @issue2.read_attribute(:done_ratio)
1123 assert_equal 30, @issue2.read_attribute(:done_ratio)
1116 end
1124 end
1117 end
1125 end
1118
1126
1119 context "with Setting.issue_done_ratio using the issue_status" do
1127 context "with Setting.issue_done_ratio using the issue_status" do
1120 setup do
1128 setup do
1121 Setting.issue_done_ratio = 'issue_status'
1129 Setting.issue_done_ratio = 'issue_status'
1122 end
1130 end
1123
1131
1124 should "change the issue's done ratio" do
1132 should "change the issue's done ratio" do
1125 @issue.update_done_ratio_from_issue_status
1133 @issue.update_done_ratio_from_issue_status
1126 @issue2.update_done_ratio_from_issue_status
1134 @issue2.update_done_ratio_from_issue_status
1127
1135
1128 assert_equal 50, @issue.read_attribute(:done_ratio)
1136 assert_equal 50, @issue.read_attribute(:done_ratio)
1129 assert_equal 0, @issue2.read_attribute(:done_ratio)
1137 assert_equal 0, @issue2.read_attribute(:done_ratio)
1130 end
1138 end
1131 end
1139 end
1132 end
1140 end
1133
1141
1134 test "#by_tracker" do
1142 test "#by_tracker" do
1135 User.current = User.anonymous
1143 User.current = User.anonymous
1136 groups = Issue.by_tracker(Project.find(1))
1144 groups = Issue.by_tracker(Project.find(1))
1137 assert_equal 3, groups.size
1145 assert_equal 3, groups.size
1138 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1146 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1139 end
1147 end
1140
1148
1141 test "#by_version" do
1149 test "#by_version" do
1142 User.current = User.anonymous
1150 User.current = User.anonymous
1143 groups = Issue.by_version(Project.find(1))
1151 groups = Issue.by_version(Project.find(1))
1144 assert_equal 3, groups.size
1152 assert_equal 3, groups.size
1145 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1153 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1146 end
1154 end
1147
1155
1148 test "#by_priority" do
1156 test "#by_priority" do
1149 User.current = User.anonymous
1157 User.current = User.anonymous
1150 groups = Issue.by_priority(Project.find(1))
1158 groups = Issue.by_priority(Project.find(1))
1151 assert_equal 4, groups.size
1159 assert_equal 4, groups.size
1152 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1160 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1153 end
1161 end
1154
1162
1155 test "#by_category" do
1163 test "#by_category" do
1156 User.current = User.anonymous
1164 User.current = User.anonymous
1157 groups = Issue.by_category(Project.find(1))
1165 groups = Issue.by_category(Project.find(1))
1158 assert_equal 2, groups.size
1166 assert_equal 2, groups.size
1159 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1167 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1160 end
1168 end
1161
1169
1162 test "#by_assigned_to" do
1170 test "#by_assigned_to" do
1163 User.current = User.anonymous
1171 User.current = User.anonymous
1164 groups = Issue.by_assigned_to(Project.find(1))
1172 groups = Issue.by_assigned_to(Project.find(1))
1165 assert_equal 2, groups.size
1173 assert_equal 2, groups.size
1166 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1174 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1167 end
1175 end
1168
1176
1169 test "#by_author" do
1177 test "#by_author" do
1170 User.current = User.anonymous
1178 User.current = User.anonymous
1171 groups = Issue.by_author(Project.find(1))
1179 groups = Issue.by_author(Project.find(1))
1172 assert_equal 4, groups.size
1180 assert_equal 4, groups.size
1173 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1181 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1174 end
1182 end
1175
1183
1176 test "#by_subproject" do
1184 test "#by_subproject" do
1177 User.current = User.anonymous
1185 User.current = User.anonymous
1178 groups = Issue.by_subproject(Project.find(1))
1186 groups = Issue.by_subproject(Project.find(1))
1179 # Private descendant not visible
1187 # Private descendant not visible
1180 assert_equal 1, groups.size
1188 assert_equal 1, groups.size
1181 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1189 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1182 end
1190 end
1183
1191
1184 def test_recently_updated_with_limit_scopes
1192 def test_recently_updated_with_limit_scopes
1185 #should return the last updated issue
1193 #should return the last updated issue
1186 assert_equal 1, Issue.recently_updated.with_limit(1).length
1194 assert_equal 1, Issue.recently_updated.with_limit(1).length
1187 assert_equal Issue.find(:first, :order => "updated_on DESC"), Issue.recently_updated.with_limit(1).first
1195 assert_equal Issue.find(:first, :order => "updated_on DESC"), Issue.recently_updated.with_limit(1).first
1188 end
1196 end
1189
1197
1190 def test_on_active_projects_scope
1198 def test_on_active_projects_scope
1191 assert Project.find(2).archive
1199 assert Project.find(2).archive
1192
1200
1193 before = Issue.on_active_project.length
1201 before = Issue.on_active_project.length
1194 # test inclusion to results
1202 # test inclusion to results
1195 issue = Issue.generate_for_project!(Project.find(1), :tracker => Project.find(2).trackers.first)
1203 issue = Issue.generate_for_project!(Project.find(1), :tracker => Project.find(2).trackers.first)
1196 assert_equal before + 1, Issue.on_active_project.length
1204 assert_equal before + 1, Issue.on_active_project.length
1197
1205
1198 # Move to an archived project
1206 # Move to an archived project
1199 issue.project = Project.find(2)
1207 issue.project = Project.find(2)
1200 assert issue.save
1208 assert issue.save
1201 assert_equal before, Issue.on_active_project.length
1209 assert_equal before, Issue.on_active_project.length
1202 end
1210 end
1203
1211
1204 context "Issue#recipients" do
1212 context "Issue#recipients" do
1205 setup do
1213 setup do
1206 @project = Project.find(1)
1214 @project = Project.find(1)
1207 @author = User.generate_with_protected!
1215 @author = User.generate_with_protected!
1208 @assignee = User.generate_with_protected!
1216 @assignee = User.generate_with_protected!
1209 @issue = Issue.generate_for_project!(@project, :assigned_to => @assignee, :author => @author)
1217 @issue = Issue.generate_for_project!(@project, :assigned_to => @assignee, :author => @author)
1210 end
1218 end
1211
1219
1212 should "include project recipients" do
1220 should "include project recipients" do
1213 assert @project.recipients.present?
1221 assert @project.recipients.present?
1214 @project.recipients.each do |project_recipient|
1222 @project.recipients.each do |project_recipient|
1215 assert @issue.recipients.include?(project_recipient)
1223 assert @issue.recipients.include?(project_recipient)
1216 end
1224 end
1217 end
1225 end
1218
1226
1219 should "include the author if the author is active" do
1227 should "include the author if the author is active" do
1220 assert @issue.author, "No author set for Issue"
1228 assert @issue.author, "No author set for Issue"
1221 assert @issue.recipients.include?(@issue.author.mail)
1229 assert @issue.recipients.include?(@issue.author.mail)
1222 end
1230 end
1223
1231
1224 should "include the assigned to user if the assigned to user is active" do
1232 should "include the assigned to user if the assigned to user is active" do
1225 assert @issue.assigned_to, "No assigned_to set for Issue"
1233 assert @issue.assigned_to, "No assigned_to set for Issue"
1226 assert @issue.recipients.include?(@issue.assigned_to.mail)
1234 assert @issue.recipients.include?(@issue.assigned_to.mail)
1227 end
1235 end
1228
1236
1229 should "not include users who opt out of all email" do
1237 should "not include users who opt out of all email" do
1230 @author.update_attribute(:mail_notification, :none)
1238 @author.update_attribute(:mail_notification, :none)
1231
1239
1232 assert !@issue.recipients.include?(@issue.author.mail)
1240 assert !@issue.recipients.include?(@issue.author.mail)
1233 end
1241 end
1234
1242
1235 should "not include the issue author if they are only notified of assigned issues" do
1243 should "not include the issue author if they are only notified of assigned issues" do
1236 @author.update_attribute(:mail_notification, :only_assigned)
1244 @author.update_attribute(:mail_notification, :only_assigned)
1237
1245
1238 assert !@issue.recipients.include?(@issue.author.mail)
1246 assert !@issue.recipients.include?(@issue.author.mail)
1239 end
1247 end
1240
1248
1241 should "not include the assigned user if they are only notified of owned issues" do
1249 should "not include the assigned user if they are only notified of owned issues" do
1242 @assignee.update_attribute(:mail_notification, :only_owner)
1250 @assignee.update_attribute(:mail_notification, :only_owner)
1243
1251
1244 assert !@issue.recipients.include?(@issue.assigned_to.mail)
1252 assert !@issue.recipients.include?(@issue.assigned_to.mail)
1245 end
1253 end
1246 end
1254 end
1247
1255
1248 def test_last_journal_id_with_journals_should_return_the_journal_id
1256 def test_last_journal_id_with_journals_should_return_the_journal_id
1249 assert_equal 2, Issue.find(1).last_journal_id
1257 assert_equal 2, Issue.find(1).last_journal_id
1250 end
1258 end
1251
1259
1252 def test_last_journal_id_without_journals_should_return_nil
1260 def test_last_journal_id_without_journals_should_return_nil
1253 assert_nil Issue.find(3).last_journal_id
1261 assert_nil Issue.find(3).last_journal_id
1254 end
1262 end
1255 end
1263 end
General Comments 0
You need to be logged in to leave comments. Login now