##// END OF EJS Templates
Fixed: issue details view discloses relations to issues that the user is not allowed to view (#2589)....
Jean-Philippe Lang -
r2341:f021c856c19a
parent child
Show More
@@ -1,287 +1,292
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Issue < ActiveRecord::Base
19 19 belongs_to :project
20 20 belongs_to :tracker
21 21 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
22 22 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
23 23 belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
24 24 belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
25 25 belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id'
26 26 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
27 27
28 28 has_many :journals, :as => :journalized, :dependent => :destroy
29 29 has_many :time_entries, :dependent => :delete_all
30 30 has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
31 31
32 32 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
33 33 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
34 34
35 35 acts_as_attachable :after_remove => :attachment_removed
36 36 acts_as_customizable
37 37 acts_as_watchable
38 38 acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"],
39 39 :include => [:project, :journals],
40 40 # sort by id so that limited eager loading doesn't break with postgresql
41 41 :order_column => "#{table_name}.id"
42 42 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id}: #{o.subject}"},
43 43 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}},
44 44 :type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') }
45 45
46 46 acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
47 47 :author_key => :author_id
48 48
49 49 validates_presence_of :subject, :priority, :project, :tracker, :author, :status
50 50 validates_length_of :subject, :maximum => 255
51 51 validates_inclusion_of :done_ratio, :in => 0..100
52 52 validates_numericality_of :estimated_hours, :allow_nil => true
53 53
54 54 named_scope :visible, lambda {|*args| { :include => :project,
55 55 :conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } }
56 56
57 # Returns true if usr or current user is allowed to view the issue
58 def visible?(usr=nil)
59 (usr || User.current).allowed_to?(:view_issues, self.project)
60 end
61
57 62 def after_initialize
58 63 if new_record?
59 64 # set default values for new records only
60 65 self.status ||= IssueStatus.default
61 66 self.priority ||= Enumeration.default('IPRI')
62 67 end
63 68 end
64 69
65 70 # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
66 71 def available_custom_fields
67 72 (project && tracker) ? project.all_issue_custom_fields.select {|c| tracker.custom_fields.include? c } : []
68 73 end
69 74
70 75 def copy_from(arg)
71 76 issue = arg.is_a?(Issue) ? arg : Issue.find(arg)
72 77 self.attributes = issue.attributes.dup
73 78 self.custom_values = issue.custom_values.collect {|v| v.clone}
74 79 self
75 80 end
76 81
77 82 # Moves/copies an issue to a new project and tracker
78 83 # Returns the moved/copied issue on success, false on failure
79 84 def move_to(new_project, new_tracker = nil, options = {})
80 85 options ||= {}
81 86 issue = options[:copy] ? self.clone : self
82 87 transaction do
83 88 if new_project && issue.project_id != new_project.id
84 89 # delete issue relations
85 90 unless Setting.cross_project_issue_relations?
86 91 issue.relations_from.clear
87 92 issue.relations_to.clear
88 93 end
89 94 # issue is moved to another project
90 95 # reassign to the category with same name if any
91 96 new_category = issue.category.nil? ? nil : new_project.issue_categories.find_by_name(issue.category.name)
92 97 issue.category = new_category
93 98 issue.fixed_version = nil
94 99 issue.project = new_project
95 100 end
96 101 if new_tracker
97 102 issue.tracker = new_tracker
98 103 end
99 104 if options[:copy]
100 105 issue.custom_field_values = self.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
101 106 issue.status = self.status
102 107 end
103 108 if issue.save
104 109 unless options[:copy]
105 110 # Manually update project_id on related time entries
106 111 TimeEntry.update_all("project_id = #{new_project.id}", {:issue_id => id})
107 112 end
108 113 else
109 114 Issue.connection.rollback_db_transaction
110 115 return false
111 116 end
112 117 end
113 118 return issue
114 119 end
115 120
116 121 def priority_id=(pid)
117 122 self.priority = nil
118 123 write_attribute(:priority_id, pid)
119 124 end
120 125
121 126 def estimated_hours=(h)
122 127 write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
123 128 end
124 129
125 130 def validate
126 131 if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
127 132 errors.add :due_date, :activerecord_error_not_a_date
128 133 end
129 134
130 135 if self.due_date and self.start_date and self.due_date < self.start_date
131 136 errors.add :due_date, :activerecord_error_greater_than_start_date
132 137 end
133 138
134 139 if start_date && soonest_start && start_date < soonest_start
135 140 errors.add :start_date, :activerecord_error_invalid
136 141 end
137 142 end
138 143
139 144 def validate_on_create
140 145 errors.add :tracker_id, :activerecord_error_invalid unless project.trackers.include?(tracker)
141 146 end
142 147
143 148 def before_create
144 149 # default assignment based on category
145 150 if assigned_to.nil? && category && category.assigned_to
146 151 self.assigned_to = category.assigned_to
147 152 end
148 153 end
149 154
150 155 def before_save
151 156 if @current_journal
152 157 # attributes changes
153 158 (Issue.column_names - %w(id description)).each {|c|
154 159 @current_journal.details << JournalDetail.new(:property => 'attr',
155 160 :prop_key => c,
156 161 :old_value => @issue_before_change.send(c),
157 162 :value => send(c)) unless send(c)==@issue_before_change.send(c)
158 163 }
159 164 # custom fields changes
160 165 custom_values.each {|c|
161 166 next if (@custom_values_before_change[c.custom_field_id]==c.value ||
162 167 (@custom_values_before_change[c.custom_field_id].blank? && c.value.blank?))
163 168 @current_journal.details << JournalDetail.new(:property => 'cf',
164 169 :prop_key => c.custom_field_id,
165 170 :old_value => @custom_values_before_change[c.custom_field_id],
166 171 :value => c.value)
167 172 }
168 173 @current_journal.save
169 174 end
170 175 # Save the issue even if the journal is not saved (because empty)
171 176 true
172 177 end
173 178
174 179 def after_save
175 180 # Reload is needed in order to get the right status
176 181 reload
177 182
178 183 # Update start/due dates of following issues
179 184 relations_from.each(&:set_issue_to_dates)
180 185
181 186 # Close duplicates if the issue was closed
182 187 if @issue_before_change && !@issue_before_change.closed? && self.closed?
183 188 duplicates.each do |duplicate|
184 189 # Reload is need in case the duplicate was updated by a previous duplicate
185 190 duplicate.reload
186 191 # Don't re-close it if it's already closed
187 192 next if duplicate.closed?
188 193 # Same user and notes
189 194 duplicate.init_journal(@current_journal.user, @current_journal.notes)
190 195 duplicate.update_attribute :status, self.status
191 196 end
192 197 end
193 198 end
194 199
195 200 def init_journal(user, notes = "")
196 201 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
197 202 @issue_before_change = self.clone
198 203 @issue_before_change.status = self.status
199 204 @custom_values_before_change = {}
200 205 self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
201 206 # Make sure updated_on is updated when adding a note.
202 207 updated_on_will_change!
203 208 @current_journal
204 209 end
205 210
206 211 # Return true if the issue is closed, otherwise false
207 212 def closed?
208 213 self.status.is_closed?
209 214 end
210 215
211 216 # Returns true if the issue is overdue
212 217 def overdue?
213 218 !due_date.nil? && (due_date < Date.today)
214 219 end
215 220
216 221 # Users the issue can be assigned to
217 222 def assignable_users
218 223 project.assignable_users
219 224 end
220 225
221 226 # Returns an array of status that user is able to apply
222 227 def new_statuses_allowed_to(user)
223 228 statuses = status.find_new_statuses_allowed_to(user.role_for_project(project), tracker)
224 229 statuses << status unless statuses.empty?
225 230 statuses.uniq.sort
226 231 end
227 232
228 233 # Returns the mail adresses of users that should be notified for the issue
229 234 def recipients
230 235 recipients = project.recipients
231 236 # Author and assignee are always notified unless they have been locked
232 237 recipients << author.mail if author && author.active?
233 238 recipients << assigned_to.mail if assigned_to && assigned_to.active?
234 239 recipients.compact.uniq
235 240 end
236 241
237 242 def spent_hours
238 243 @spent_hours ||= time_entries.sum(:hours) || 0
239 244 end
240 245
241 246 def relations
242 247 (relations_from + relations_to).sort
243 248 end
244 249
245 250 def all_dependent_issues
246 251 dependencies = []
247 252 relations_from.each do |relation|
248 253 dependencies << relation.issue_to
249 254 dependencies += relation.issue_to.all_dependent_issues
250 255 end
251 256 dependencies
252 257 end
253 258
254 259 # Returns an array of issues that duplicate this one
255 260 def duplicates
256 261 relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from}
257 262 end
258 263
259 264 # Returns the due date or the target due date if any
260 265 # Used on gantt chart
261 266 def due_before
262 267 due_date || (fixed_version ? fixed_version.effective_date : nil)
263 268 end
264 269
265 270 def duration
266 271 (start_date && due_date) ? due_date - start_date : 0
267 272 end
268 273
269 274 def soonest_start
270 275 @soonest_start ||= relations_to.collect{|relation| relation.successor_soonest_start}.compact.min
271 276 end
272 277
273 278 def to_s
274 279 "#{tracker} ##{id}: #{subject}"
275 280 end
276 281
277 282 private
278 283
279 284 # Callback on attachment deletion
280 285 def attachment_removed(obj)
281 286 journal = init_journal(User.current)
282 287 journal.details << JournalDetail.new(:property => 'attachment',
283 288 :prop_key => obj.id,
284 289 :old_value => obj.filename)
285 290 journal.save
286 291 end
287 292 end
@@ -1,32 +1,32
1 1 <div class="contextual">
2 2 <% if authorize_for('issue_relations', 'new') %>
3 3 <%= toggle_link l(:button_add), 'new-relation-form'%>
4 4 <% end %>
5 5 </div>
6 6
7 7 <p><strong><%=l(:label_related_issues)%></strong></p>
8 8
9 9 <% if @issue.relations.any? %>
10 10 <table style="width:100%">
11 <% @issue.relations.each do |relation| %>
11 <% @issue.relations.select {|r| r.other_issue(@issue).visible? }.each do |relation| %>
12 12 <tr>
13 13 <td><%= l(relation.label_for(@issue)) %> <%= "(#{lwr(:actionview_datehelper_time_in_words_day, relation.delay)})" if relation.delay && relation.delay != 0 %>
14 14 <%= h(relation.other_issue(@issue).project) + ' - ' if Setting.cross_project_issue_relations? %> <%= link_to_issue relation.other_issue(@issue) %></td>
15 15 <td><%=h relation.other_issue(@issue).subject %></td>
16 16 <td><%= relation.other_issue(@issue).status.name %></td>
17 17 <td><%= format_date(relation.other_issue(@issue).start_date) %></td>
18 18 <td><%= format_date(relation.other_issue(@issue).due_date) %></td>
19 19 <td><%= link_to_remote(image_tag('delete.png'), { :url => {:controller => 'issue_relations', :action => 'destroy', :issue_id => @issue, :id => relation},
20 20 :method => :post
21 21 }, :title => l(:label_relation_delete)) if authorize_for('issue_relations', 'destroy') %></td>
22 22 </tr>
23 23 <% end %>
24 24 </table>
25 25 <% end %>
26 26
27 27 <% remote_form_for(:relation, @relation,
28 28 :url => {:controller => 'issue_relations', :action => 'new', :issue_id => @issue},
29 29 :method => :post,
30 30 :html => {:id => 'new-relation-form', :style => (@relation ? '' : 'display: none;')}) do |f| %>
31 31 <%= render :partial => 'issue_relations/form', :locals => {:f => f}%>
32 32 <% end %>
@@ -1,948 +1,963
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.dirname(__FILE__) + '/../test_helper'
19 19 require 'issues_controller'
20 20
21 21 # Re-raise errors caught by the controller.
22 22 class IssuesController; def rescue_action(e) raise e end; end
23 23
24 24 class IssuesControllerTest < Test::Unit::TestCase
25 25 fixtures :projects,
26 26 :users,
27 27 :roles,
28 28 :members,
29 29 :issues,
30 30 :issue_statuses,
31 31 :versions,
32 32 :trackers,
33 33 :projects_trackers,
34 34 :issue_categories,
35 35 :enabled_modules,
36 36 :enumerations,
37 37 :attachments,
38 38 :workflows,
39 39 :custom_fields,
40 40 :custom_values,
41 41 :custom_fields_trackers,
42 42 :time_entries,
43 43 :journals,
44 44 :journal_details
45 45
46 46 def setup
47 47 @controller = IssuesController.new
48 48 @request = ActionController::TestRequest.new
49 49 @response = ActionController::TestResponse.new
50 50 User.current = nil
51 51 end
52 52
53 53 def test_index_routing
54 54 assert_routing(
55 55 {:method => :get, :path => '/issues'},
56 56 :controller => 'issues', :action => 'index'
57 57 )
58 58 end
59 59
60 60 def test_index
61 61 get :index
62 62 assert_response :success
63 63 assert_template 'index.rhtml'
64 64 assert_not_nil assigns(:issues)
65 65 assert_nil assigns(:project)
66 66 assert_tag :tag => 'a', :content => /Can't print recipes/
67 67 assert_tag :tag => 'a', :content => /Subproject issue/
68 68 # private projects hidden
69 69 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
70 70 assert_no_tag :tag => 'a', :content => /Issue on project 2/
71 71 end
72 72
73 73 def test_index_should_not_list_issues_when_module_disabled
74 74 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
75 75 get :index
76 76 assert_response :success
77 77 assert_template 'index.rhtml'
78 78 assert_not_nil assigns(:issues)
79 79 assert_nil assigns(:project)
80 80 assert_no_tag :tag => 'a', :content => /Can't print recipes/
81 81 assert_tag :tag => 'a', :content => /Subproject issue/
82 82 end
83 83
84 84 def test_index_with_project_routing
85 85 assert_routing(
86 86 {:method => :get, :path => '/projects/23/issues'},
87 87 :controller => 'issues', :action => 'index', :project_id => '23'
88 88 )
89 89 end
90 90
91 91 def test_index_should_not_list_issues_when_module_disabled
92 92 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
93 93 get :index
94 94 assert_response :success
95 95 assert_template 'index.rhtml'
96 96 assert_not_nil assigns(:issues)
97 97 assert_nil assigns(:project)
98 98 assert_no_tag :tag => 'a', :content => /Can't print recipes/
99 99 assert_tag :tag => 'a', :content => /Subproject issue/
100 100 end
101 101
102 102 def test_index_with_project_routing
103 103 assert_routing(
104 104 {:method => :get, :path => 'projects/23/issues'},
105 105 :controller => 'issues', :action => 'index', :project_id => '23'
106 106 )
107 107 end
108 108
109 109 def test_index_with_project
110 110 Setting.display_subprojects_issues = 0
111 111 get :index, :project_id => 1
112 112 assert_response :success
113 113 assert_template 'index.rhtml'
114 114 assert_not_nil assigns(:issues)
115 115 assert_tag :tag => 'a', :content => /Can't print recipes/
116 116 assert_no_tag :tag => 'a', :content => /Subproject issue/
117 117 end
118 118
119 119 def test_index_with_project_and_subprojects
120 120 Setting.display_subprojects_issues = 1
121 121 get :index, :project_id => 1
122 122 assert_response :success
123 123 assert_template 'index.rhtml'
124 124 assert_not_nil assigns(:issues)
125 125 assert_tag :tag => 'a', :content => /Can't print recipes/
126 126 assert_tag :tag => 'a', :content => /Subproject issue/
127 127 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
128 128 end
129 129
130 130 def test_index_with_project_and_subprojects_should_show_private_subprojects
131 131 @request.session[:user_id] = 2
132 132 Setting.display_subprojects_issues = 1
133 133 get :index, :project_id => 1
134 134 assert_response :success
135 135 assert_template 'index.rhtml'
136 136 assert_not_nil assigns(:issues)
137 137 assert_tag :tag => 'a', :content => /Can't print recipes/
138 138 assert_tag :tag => 'a', :content => /Subproject issue/
139 139 assert_tag :tag => 'a', :content => /Issue of a private subproject/
140 140 end
141 141
142 142 def test_index_with_project_routing_formatted
143 143 assert_routing(
144 144 {:method => :get, :path => 'projects/23/issues.pdf'},
145 145 :controller => 'issues', :action => 'index', :project_id => '23', :format => 'pdf'
146 146 )
147 147 assert_routing(
148 148 {:method => :get, :path => 'projects/23/issues.atom'},
149 149 :controller => 'issues', :action => 'index', :project_id => '23', :format => 'atom'
150 150 )
151 151 end
152 152
153 153 def test_index_with_project_and_filter
154 154 get :index, :project_id => 1, :set_filter => 1
155 155 assert_response :success
156 156 assert_template 'index.rhtml'
157 157 assert_not_nil assigns(:issues)
158 158 end
159 159
160 160 def test_index_csv_with_project
161 161 get :index, :format => 'csv'
162 162 assert_response :success
163 163 assert_not_nil assigns(:issues)
164 164 assert_equal 'text/csv', @response.content_type
165 165
166 166 get :index, :project_id => 1, :format => 'csv'
167 167 assert_response :success
168 168 assert_not_nil assigns(:issues)
169 169 assert_equal 'text/csv', @response.content_type
170 170 end
171 171
172 172 def test_index_formatted
173 173 assert_routing(
174 174 {:method => :get, :path => 'issues.pdf'},
175 175 :controller => 'issues', :action => 'index', :format => 'pdf'
176 176 )
177 177 assert_routing(
178 178 {:method => :get, :path => 'issues.atom'},
179 179 :controller => 'issues', :action => 'index', :format => 'atom'
180 180 )
181 181 end
182 182
183 183 def test_index_pdf
184 184 get :index, :format => 'pdf'
185 185 assert_response :success
186 186 assert_not_nil assigns(:issues)
187 187 assert_equal 'application/pdf', @response.content_type
188 188
189 189 get :index, :project_id => 1, :format => 'pdf'
190 190 assert_response :success
191 191 assert_not_nil assigns(:issues)
192 192 assert_equal 'application/pdf', @response.content_type
193 193 end
194 194
195 195 def test_index_sort
196 196 get :index, :sort_key => 'tracker'
197 197 assert_response :success
198 198
199 199 sort_params = @request.session['issuesindex_sort']
200 200 assert sort_params.is_a?(Hash)
201 201 assert_equal 'tracker', sort_params[:key]
202 202 assert_equal 'ASC', sort_params[:order]
203 203 end
204 204
205 205 def test_gantt
206 206 get :gantt, :project_id => 1
207 207 assert_response :success
208 208 assert_template 'gantt.rhtml'
209 209 assert_not_nil assigns(:gantt)
210 210 events = assigns(:gantt).events
211 211 assert_not_nil events
212 212 # Issue with start and due dates
213 213 i = Issue.find(1)
214 214 assert_not_nil i.due_date
215 215 assert events.include?(Issue.find(1))
216 216 # Issue with without due date but targeted to a version with date
217 217 i = Issue.find(2)
218 218 assert_nil i.due_date
219 219 assert events.include?(i)
220 220 end
221 221
222 222 def test_cross_project_gantt
223 223 get :gantt
224 224 assert_response :success
225 225 assert_template 'gantt.rhtml'
226 226 assert_not_nil assigns(:gantt)
227 227 events = assigns(:gantt).events
228 228 assert_not_nil events
229 229 end
230 230
231 231 def test_gantt_export_to_pdf
232 232 get :gantt, :project_id => 1, :format => 'pdf'
233 233 assert_response :success
234 234 assert_equal 'application/pdf', @response.content_type
235 235 assert @response.body.starts_with?('%PDF')
236 236 assert_not_nil assigns(:gantt)
237 237 end
238 238
239 239 def test_cross_project_gantt_export_to_pdf
240 240 get :gantt, :format => 'pdf'
241 241 assert_response :success
242 242 assert_equal 'application/pdf', @response.content_type
243 243 assert @response.body.starts_with?('%PDF')
244 244 assert_not_nil assigns(:gantt)
245 245 end
246 246
247 247 if Object.const_defined?(:Magick)
248 248 def test_gantt_image
249 249 get :gantt, :project_id => 1, :format => 'png'
250 250 assert_response :success
251 251 assert_equal 'image/png', @response.content_type
252 252 end
253 253 else
254 254 puts "RMagick not installed. Skipping tests !!!"
255 255 end
256 256
257 257 def test_calendar
258 258 get :calendar, :project_id => 1
259 259 assert_response :success
260 260 assert_template 'calendar'
261 261 assert_not_nil assigns(:calendar)
262 262 end
263 263
264 264 def test_cross_project_calendar
265 265 get :calendar
266 266 assert_response :success
267 267 assert_template 'calendar'
268 268 assert_not_nil assigns(:calendar)
269 269 end
270 270
271 271 def test_changes
272 272 get :changes, :project_id => 1
273 273 assert_response :success
274 274 assert_not_nil assigns(:journals)
275 275 assert_equal 'application/atom+xml', @response.content_type
276 276 end
277 277
278 278 def test_show_routing
279 279 assert_routing(
280 280 {:method => :get, :path => '/issues/64'},
281 281 :controller => 'issues', :action => 'show', :id => '64'
282 282 )
283 283 end
284 284
285 285 def test_show_routing_formatted
286 286 assert_routing(
287 287 {:method => :get, :path => '/issues/2332.pdf'},
288 288 :controller => 'issues', :action => 'show', :id => '2332', :format => 'pdf'
289 289 )
290 290 assert_routing(
291 291 {:method => :get, :path => '/issues/23123.atom'},
292 292 :controller => 'issues', :action => 'show', :id => '23123', :format => 'atom'
293 293 )
294 294 end
295 295
296 296 def test_show_by_anonymous
297 297 get :show, :id => 1
298 298 assert_response :success
299 299 assert_template 'show.rhtml'
300 300 assert_not_nil assigns(:issue)
301 301 assert_equal Issue.find(1), assigns(:issue)
302 302
303 303 # anonymous role is allowed to add a note
304 304 assert_tag :tag => 'form',
305 305 :descendant => { :tag => 'fieldset',
306 306 :child => { :tag => 'legend',
307 307 :content => /Notes/ } }
308 308 end
309 309
310 310 def test_show_by_manager
311 311 @request.session[:user_id] = 2
312 312 get :show, :id => 1
313 313 assert_response :success
314 314
315 315 assert_tag :tag => 'form',
316 316 :descendant => { :tag => 'fieldset',
317 317 :child => { :tag => 'legend',
318 318 :content => /Change properties/ } },
319 319 :descendant => { :tag => 'fieldset',
320 320 :child => { :tag => 'legend',
321 321 :content => /Log time/ } },
322 322 :descendant => { :tag => 'fieldset',
323 323 :child => { :tag => 'legend',
324 324 :content => /Notes/ } }
325 325 end
326 326
327 def test_show_should_not_disclose_relations_to_invisible_issues
328 Setting.cross_project_issue_relations = '1'
329 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
330 # Relation to a private project issue
331 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
332
333 get :show, :id => 1
334 assert_response :success
335
336 assert_tag :div, :attributes => { :id => 'relations' },
337 :descendant => { :tag => 'a', :content => /#2$/ }
338 assert_no_tag :div, :attributes => { :id => 'relations' },
339 :descendant => { :tag => 'a', :content => /#4$/ }
340 end
341
327 342 def test_new_routing
328 343 assert_routing(
329 344 {:method => :get, :path => '/projects/1/issues/new'},
330 345 :controller => 'issues', :action => 'new', :project_id => '1'
331 346 )
332 347 assert_recognizes(
333 348 {:controller => 'issues', :action => 'new', :project_id => '1'},
334 349 {:method => :post, :path => '/projects/1/issues'}
335 350 )
336 351 end
337 352
338 353 def test_show_export_to_pdf
339 354 get :show, :id => 3, :format => 'pdf'
340 355 assert_response :success
341 356 assert_equal 'application/pdf', @response.content_type
342 357 assert @response.body.starts_with?('%PDF')
343 358 assert_not_nil assigns(:issue)
344 359 end
345 360
346 361 def test_get_new
347 362 @request.session[:user_id] = 2
348 363 get :new, :project_id => 1, :tracker_id => 1
349 364 assert_response :success
350 365 assert_template 'new'
351 366
352 367 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
353 368 :value => 'Default string' }
354 369 end
355 370
356 371 def test_get_new_without_tracker_id
357 372 @request.session[:user_id] = 2
358 373 get :new, :project_id => 1
359 374 assert_response :success
360 375 assert_template 'new'
361 376
362 377 issue = assigns(:issue)
363 378 assert_not_nil issue
364 379 assert_equal Project.find(1).trackers.first, issue.tracker
365 380 end
366 381
367 382 def test_update_new_form
368 383 @request.session[:user_id] = 2
369 384 xhr :post, :new, :project_id => 1,
370 385 :issue => {:tracker_id => 2,
371 386 :subject => 'This is the test_new issue',
372 387 :description => 'This is the description',
373 388 :priority_id => 5}
374 389 assert_response :success
375 390 assert_template 'new'
376 391 end
377 392
378 393 def test_post_new
379 394 @request.session[:user_id] = 2
380 395 post :new, :project_id => 1,
381 396 :issue => {:tracker_id => 3,
382 397 :subject => 'This is the test_new issue',
383 398 :description => 'This is the description',
384 399 :priority_id => 5,
385 400 :estimated_hours => '',
386 401 :custom_field_values => {'2' => 'Value for field 2'}}
387 402 assert_redirected_to :action => 'show'
388 403
389 404 issue = Issue.find_by_subject('This is the test_new issue')
390 405 assert_not_nil issue
391 406 assert_equal 2, issue.author_id
392 407 assert_equal 3, issue.tracker_id
393 408 assert_nil issue.estimated_hours
394 409 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
395 410 assert_not_nil v
396 411 assert_equal 'Value for field 2', v.value
397 412 end
398 413
399 414 def test_post_new_and_continue
400 415 @request.session[:user_id] = 2
401 416 post :new, :project_id => 1,
402 417 :issue => {:tracker_id => 3,
403 418 :subject => 'This is first issue',
404 419 :priority_id => 5},
405 420 :continue => ''
406 421 assert_redirected_to :controller => 'issues', :action => 'new', :tracker_id => 3
407 422 end
408 423
409 424 def test_post_new_without_custom_fields_param
410 425 @request.session[:user_id] = 2
411 426 post :new, :project_id => 1,
412 427 :issue => {:tracker_id => 1,
413 428 :subject => 'This is the test_new issue',
414 429 :description => 'This is the description',
415 430 :priority_id => 5}
416 431 assert_redirected_to :action => 'show'
417 432 end
418 433
419 434 def test_post_new_with_required_custom_field_and_without_custom_fields_param
420 435 field = IssueCustomField.find_by_name('Database')
421 436 field.update_attribute(:is_required, true)
422 437
423 438 @request.session[:user_id] = 2
424 439 post :new, :project_id => 1,
425 440 :issue => {:tracker_id => 1,
426 441 :subject => 'This is the test_new issue',
427 442 :description => 'This is the description',
428 443 :priority_id => 5}
429 444 assert_response :success
430 445 assert_template 'new'
431 446 issue = assigns(:issue)
432 447 assert_not_nil issue
433 448 assert_equal 'activerecord_error_invalid', issue.errors.on(:custom_values)
434 449 end
435 450
436 451 def test_post_new_with_watchers
437 452 @request.session[:user_id] = 2
438 453 ActionMailer::Base.deliveries.clear
439 454
440 455 assert_difference 'Watcher.count', 2 do
441 456 post :new, :project_id => 1,
442 457 :issue => {:tracker_id => 1,
443 458 :subject => 'This is a new issue with watchers',
444 459 :description => 'This is the description',
445 460 :priority_id => 5,
446 461 :watcher_user_ids => ['2', '3']}
447 462 end
448 463 issue = Issue.find_by_subject('This is a new issue with watchers')
449 464 assert_not_nil issue
450 465 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
451 466
452 467 # Watchers added
453 468 assert_equal [2, 3], issue.watcher_user_ids.sort
454 469 assert issue.watched_by?(User.find(3))
455 470 # Watchers notified
456 471 mail = ActionMailer::Base.deliveries.last
457 472 assert_kind_of TMail::Mail, mail
458 473 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
459 474 end
460 475
461 476 def test_post_should_preserve_fields_values_on_validation_failure
462 477 @request.session[:user_id] = 2
463 478 post :new, :project_id => 1,
464 479 :issue => {:tracker_id => 1,
465 480 # empty subject
466 481 :subject => '',
467 482 :description => 'This is a description',
468 483 :priority_id => 6,
469 484 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
470 485 assert_response :success
471 486 assert_template 'new'
472 487
473 488 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
474 489 :content => 'This is a description'
475 490 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
476 491 :child => { :tag => 'option', :attributes => { :selected => 'selected',
477 492 :value => '6' },
478 493 :content => 'High' }
479 494 # Custom fields
480 495 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
481 496 :child => { :tag => 'option', :attributes => { :selected => 'selected',
482 497 :value => 'Oracle' },
483 498 :content => 'Oracle' }
484 499 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
485 500 :value => 'Value for field 2'}
486 501 end
487 502
488 503 def test_copy_routing
489 504 assert_routing(
490 505 {:method => :get, :path => '/projects/world_domination/issues/567/copy'},
491 506 :controller => 'issues', :action => 'new', :project_id => 'world_domination', :copy_from => '567'
492 507 )
493 508 end
494 509
495 510 def test_copy_issue
496 511 @request.session[:user_id] = 2
497 512 get :new, :project_id => 1, :copy_from => 1
498 513 assert_template 'new'
499 514 assert_not_nil assigns(:issue)
500 515 orig = Issue.find(1)
501 516 assert_equal orig.subject, assigns(:issue).subject
502 517 end
503 518
504 519 def test_edit_routing
505 520 assert_routing(
506 521 {:method => :get, :path => '/issues/1/edit'},
507 522 :controller => 'issues', :action => 'edit', :id => '1'
508 523 )
509 524 assert_recognizes( #TODO: use a PUT on the issue URI isntead, need to adjust form
510 525 {:controller => 'issues', :action => 'edit', :id => '1'},
511 526 {:method => :post, :path => '/issues/1/edit'}
512 527 )
513 528 end
514 529
515 530 def test_get_edit
516 531 @request.session[:user_id] = 2
517 532 get :edit, :id => 1
518 533 assert_response :success
519 534 assert_template 'edit'
520 535 assert_not_nil assigns(:issue)
521 536 assert_equal Issue.find(1), assigns(:issue)
522 537 end
523 538
524 539 def test_get_edit_with_params
525 540 @request.session[:user_id] = 2
526 541 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
527 542 assert_response :success
528 543 assert_template 'edit'
529 544
530 545 issue = assigns(:issue)
531 546 assert_not_nil issue
532 547
533 548 assert_equal 5, issue.status_id
534 549 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
535 550 :child => { :tag => 'option',
536 551 :content => 'Closed',
537 552 :attributes => { :selected => 'selected' } }
538 553
539 554 assert_equal 7, issue.priority_id
540 555 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
541 556 :child => { :tag => 'option',
542 557 :content => 'Urgent',
543 558 :attributes => { :selected => 'selected' } }
544 559 end
545 560
546 561 def test_reply_routing
547 562 assert_routing(
548 563 {:method => :post, :path => '/issues/1/quoted'},
549 564 :controller => 'issues', :action => 'reply', :id => '1'
550 565 )
551 566 end
552 567
553 568 def test_reply_to_issue
554 569 @request.session[:user_id] = 2
555 570 get :reply, :id => 1
556 571 assert_response :success
557 572 assert_select_rjs :show, "update"
558 573 end
559 574
560 575 def test_reply_to_note
561 576 @request.session[:user_id] = 2
562 577 get :reply, :id => 1, :journal_id => 2
563 578 assert_response :success
564 579 assert_select_rjs :show, "update"
565 580 end
566 581
567 582 def test_post_edit_without_custom_fields_param
568 583 @request.session[:user_id] = 2
569 584 ActionMailer::Base.deliveries.clear
570 585
571 586 issue = Issue.find(1)
572 587 assert_equal '125', issue.custom_value_for(2).value
573 588 old_subject = issue.subject
574 589 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
575 590
576 591 assert_difference('Journal.count') do
577 592 assert_difference('JournalDetail.count', 2) do
578 593 post :edit, :id => 1, :issue => {:subject => new_subject,
579 594 :priority_id => '6',
580 595 :category_id => '1' # no change
581 596 }
582 597 end
583 598 end
584 599 assert_redirected_to :action => 'show', :id => '1'
585 600 issue.reload
586 601 assert_equal new_subject, issue.subject
587 602 # Make sure custom fields were not cleared
588 603 assert_equal '125', issue.custom_value_for(2).value
589 604
590 605 mail = ActionMailer::Base.deliveries.last
591 606 assert_kind_of TMail::Mail, mail
592 607 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
593 608 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
594 609 end
595 610
596 611 def test_post_edit_with_custom_field_change
597 612 @request.session[:user_id] = 2
598 613 issue = Issue.find(1)
599 614 assert_equal '125', issue.custom_value_for(2).value
600 615
601 616 assert_difference('Journal.count') do
602 617 assert_difference('JournalDetail.count', 3) do
603 618 post :edit, :id => 1, :issue => {:subject => 'Custom field change',
604 619 :priority_id => '6',
605 620 :category_id => '1', # no change
606 621 :custom_field_values => { '2' => 'New custom value' }
607 622 }
608 623 end
609 624 end
610 625 assert_redirected_to :action => 'show', :id => '1'
611 626 issue.reload
612 627 assert_equal 'New custom value', issue.custom_value_for(2).value
613 628
614 629 mail = ActionMailer::Base.deliveries.last
615 630 assert_kind_of TMail::Mail, mail
616 631 assert mail.body.include?("Searchable field changed from 125 to New custom value")
617 632 end
618 633
619 634 def test_post_edit_with_status_and_assignee_change
620 635 issue = Issue.find(1)
621 636 assert_equal 1, issue.status_id
622 637 @request.session[:user_id] = 2
623 638 assert_difference('TimeEntry.count', 0) do
624 639 post :edit,
625 640 :id => 1,
626 641 :issue => { :status_id => 2, :assigned_to_id => 3 },
627 642 :notes => 'Assigned to dlopper',
628 643 :time_entry => { :hours => '', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
629 644 end
630 645 assert_redirected_to :action => 'show', :id => '1'
631 646 issue.reload
632 647 assert_equal 2, issue.status_id
633 648 j = issue.journals.find(:first, :order => 'id DESC')
634 649 assert_equal 'Assigned to dlopper', j.notes
635 650 assert_equal 2, j.details.size
636 651
637 652 mail = ActionMailer::Base.deliveries.last
638 653 assert mail.body.include?("Status changed from New to Assigned")
639 654 end
640 655
641 656 def test_post_edit_with_note_only
642 657 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
643 658 # anonymous user
644 659 post :edit,
645 660 :id => 1,
646 661 :notes => notes
647 662 assert_redirected_to :action => 'show', :id => '1'
648 663 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
649 664 assert_equal notes, j.notes
650 665 assert_equal 0, j.details.size
651 666 assert_equal User.anonymous, j.user
652 667
653 668 mail = ActionMailer::Base.deliveries.last
654 669 assert mail.body.include?(notes)
655 670 end
656 671
657 672 def test_post_edit_with_note_and_spent_time
658 673 @request.session[:user_id] = 2
659 674 spent_hours_before = Issue.find(1).spent_hours
660 675 assert_difference('TimeEntry.count') do
661 676 post :edit,
662 677 :id => 1,
663 678 :notes => '2.5 hours added',
664 679 :time_entry => { :hours => '2.5', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
665 680 end
666 681 assert_redirected_to :action => 'show', :id => '1'
667 682
668 683 issue = Issue.find(1)
669 684
670 685 j = issue.journals.find(:first, :order => 'id DESC')
671 686 assert_equal '2.5 hours added', j.notes
672 687 assert_equal 0, j.details.size
673 688
674 689 t = issue.time_entries.find(:first, :order => 'id DESC')
675 690 assert_not_nil t
676 691 assert_equal 2.5, t.hours
677 692 assert_equal spent_hours_before + 2.5, issue.spent_hours
678 693 end
679 694
680 695 def test_post_edit_with_attachment_only
681 696 set_tmp_attachments_directory
682 697
683 698 # Delete all fixtured journals, a race condition can occur causing the wrong
684 699 # journal to get fetched in the next find.
685 700 Journal.delete_all
686 701
687 702 # anonymous user
688 703 post :edit,
689 704 :id => 1,
690 705 :notes => '',
691 706 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
692 707 assert_redirected_to :action => 'show', :id => '1'
693 708 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
694 709 assert j.notes.blank?
695 710 assert_equal 1, j.details.size
696 711 assert_equal 'testfile.txt', j.details.first.value
697 712 assert_equal User.anonymous, j.user
698 713
699 714 mail = ActionMailer::Base.deliveries.last
700 715 assert mail.body.include?('testfile.txt')
701 716 end
702 717
703 718 def test_post_edit_with_no_change
704 719 issue = Issue.find(1)
705 720 issue.journals.clear
706 721 ActionMailer::Base.deliveries.clear
707 722
708 723 post :edit,
709 724 :id => 1,
710 725 :notes => ''
711 726 assert_redirected_to :action => 'show', :id => '1'
712 727
713 728 issue.reload
714 729 assert issue.journals.empty?
715 730 # No email should be sent
716 731 assert ActionMailer::Base.deliveries.empty?
717 732 end
718 733
719 734 def test_post_edit_with_invalid_spent_time
720 735 @request.session[:user_id] = 2
721 736 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
722 737
723 738 assert_no_difference('Journal.count') do
724 739 post :edit,
725 740 :id => 1,
726 741 :notes => notes,
727 742 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
728 743 end
729 744 assert_response :success
730 745 assert_template 'edit'
731 746
732 747 assert_tag :textarea, :attributes => { :name => 'notes' },
733 748 :content => notes
734 749 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
735 750 end
736 751
737 752 def test_bulk_edit
738 753 @request.session[:user_id] = 2
739 754 # update issues priority
740 755 post :bulk_edit, :ids => [1, 2], :priority_id => 7,
741 756 :assigned_to_id => '',
742 757 :custom_field_values => {'2' => ''},
743 758 :notes => 'Bulk editing'
744 759 assert_response 302
745 760 # check that the issues were updated
746 761 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
747 762
748 763 issue = Issue.find(1)
749 764 journal = issue.journals.find(:first, :order => 'created_on DESC')
750 765 assert_equal '125', issue.custom_value_for(2).value
751 766 assert_equal 'Bulk editing', journal.notes
752 767 assert_equal 1, journal.details.size
753 768 end
754 769
755 770 def test_bulk_edit_custom_field
756 771 @request.session[:user_id] = 2
757 772 # update issues priority
758 773 post :bulk_edit, :ids => [1, 2], :priority_id => '',
759 774 :assigned_to_id => '',
760 775 :custom_field_values => {'2' => '777'},
761 776 :notes => 'Bulk editing custom field'
762 777 assert_response 302
763 778
764 779 issue = Issue.find(1)
765 780 journal = issue.journals.find(:first, :order => 'created_on DESC')
766 781 assert_equal '777', issue.custom_value_for(2).value
767 782 assert_equal 1, journal.details.size
768 783 assert_equal '125', journal.details.first.old_value
769 784 assert_equal '777', journal.details.first.value
770 785 end
771 786
772 787 def test_bulk_unassign
773 788 assert_not_nil Issue.find(2).assigned_to
774 789 @request.session[:user_id] = 2
775 790 # unassign issues
776 791 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :assigned_to_id => 'none'
777 792 assert_response 302
778 793 # check that the issues were updated
779 794 assert_nil Issue.find(2).assigned_to
780 795 end
781 796
782 797 def test_move_routing
783 798 assert_routing(
784 799 {:method => :get, :path => '/issues/1/move'},
785 800 :controller => 'issues', :action => 'move', :id => '1'
786 801 )
787 802 assert_recognizes(
788 803 {:controller => 'issues', :action => 'move', :id => '1'},
789 804 {:method => :post, :path => '/issues/1/move'}
790 805 )
791 806 end
792 807
793 808 def test_move_one_issue_to_another_project
794 809 @request.session[:user_id] = 1
795 810 post :move, :id => 1, :new_project_id => 2
796 811 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
797 812 assert_equal 2, Issue.find(1).project_id
798 813 end
799 814
800 815 def test_bulk_move_to_another_project
801 816 @request.session[:user_id] = 1
802 817 post :move, :ids => [1, 2], :new_project_id => 2
803 818 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
804 819 # Issues moved to project 2
805 820 assert_equal 2, Issue.find(1).project_id
806 821 assert_equal 2, Issue.find(2).project_id
807 822 # No tracker change
808 823 assert_equal 1, Issue.find(1).tracker_id
809 824 assert_equal 2, Issue.find(2).tracker_id
810 825 end
811 826
812 827 def test_bulk_move_to_another_tracker
813 828 @request.session[:user_id] = 1
814 829 post :move, :ids => [1, 2], :new_tracker_id => 2
815 830 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
816 831 assert_equal 2, Issue.find(1).tracker_id
817 832 assert_equal 2, Issue.find(2).tracker_id
818 833 end
819 834
820 835 def test_bulk_copy_to_another_project
821 836 @request.session[:user_id] = 1
822 837 assert_difference 'Issue.count', 2 do
823 838 assert_no_difference 'Project.find(1).issues.count' do
824 839 post :move, :ids => [1, 2], :new_project_id => 2, :copy_options => {:copy => '1'}
825 840 end
826 841 end
827 842 assert_redirected_to 'projects/ecookbook/issues'
828 843 end
829 844
830 845 def test_context_menu_one_issue
831 846 @request.session[:user_id] = 2
832 847 get :context_menu, :ids => [1]
833 848 assert_response :success
834 849 assert_template 'context_menu'
835 850 assert_tag :tag => 'a', :content => 'Edit',
836 851 :attributes => { :href => '/issues/1/edit',
837 852 :class => 'icon-edit' }
838 853 assert_tag :tag => 'a', :content => 'Closed',
839 854 :attributes => { :href => '/issues/1/edit?issue%5Bstatus_id%5D=5',
840 855 :class => '' }
841 856 assert_tag :tag => 'a', :content => 'Immediate',
842 857 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;priority_id=8',
843 858 :class => '' }
844 859 assert_tag :tag => 'a', :content => 'Dave Lopper',
845 860 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1',
846 861 :class => '' }
847 862 assert_tag :tag => 'a', :content => 'Copy',
848 863 :attributes => { :href => '/projects/ecookbook/issues/1/copy',
849 864 :class => 'icon-copy' }
850 865 assert_tag :tag => 'a', :content => 'Move',
851 866 :attributes => { :href => '/issues/move?ids%5B%5D=1',
852 867 :class => 'icon-move' }
853 868 assert_tag :tag => 'a', :content => 'Delete',
854 869 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
855 870 :class => 'icon-del' }
856 871 end
857 872
858 873 def test_context_menu_one_issue_by_anonymous
859 874 get :context_menu, :ids => [1]
860 875 assert_response :success
861 876 assert_template 'context_menu'
862 877 assert_tag :tag => 'a', :content => 'Delete',
863 878 :attributes => { :href => '#',
864 879 :class => 'icon-del disabled' }
865 880 end
866 881
867 882 def test_context_menu_multiple_issues_of_same_project
868 883 @request.session[:user_id] = 2
869 884 get :context_menu, :ids => [1, 2]
870 885 assert_response :success
871 886 assert_template 'context_menu'
872 887 assert_tag :tag => 'a', :content => 'Edit',
873 888 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
874 889 :class => 'icon-edit' }
875 890 assert_tag :tag => 'a', :content => 'Immediate',
876 891 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;priority_id=8',
877 892 :class => '' }
878 893 assert_tag :tag => 'a', :content => 'Dave Lopper',
879 894 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
880 895 :class => '' }
881 896 assert_tag :tag => 'a', :content => 'Move',
882 897 :attributes => { :href => '/issues/move?ids%5B%5D=1&amp;ids%5B%5D=2',
883 898 :class => 'icon-move' }
884 899 assert_tag :tag => 'a', :content => 'Delete',
885 900 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
886 901 :class => 'icon-del' }
887 902 end
888 903
889 904 def test_context_menu_multiple_issues_of_different_project
890 905 @request.session[:user_id] = 2
891 906 get :context_menu, :ids => [1, 2, 4]
892 907 assert_response :success
893 908 assert_template 'context_menu'
894 909 assert_tag :tag => 'a', :content => 'Delete',
895 910 :attributes => { :href => '#',
896 911 :class => 'icon-del disabled' }
897 912 end
898 913
899 914 def test_destroy_routing
900 915 assert_recognizes( #TODO: use DELETE on issue URI (need to change forms)
901 916 {:controller => 'issues', :action => 'destroy', :id => '1'},
902 917 {:method => :post, :path => '/issues/1/destroy'}
903 918 )
904 919 end
905 920
906 921 def test_destroy_issue_with_no_time_entries
907 922 assert_nil TimeEntry.find_by_issue_id(2)
908 923 @request.session[:user_id] = 2
909 924 post :destroy, :id => 2
910 925 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
911 926 assert_nil Issue.find_by_id(2)
912 927 end
913 928
914 929 def test_destroy_issues_with_time_entries
915 930 @request.session[:user_id] = 2
916 931 post :destroy, :ids => [1, 3]
917 932 assert_response :success
918 933 assert_template 'destroy'
919 934 assert_not_nil assigns(:hours)
920 935 assert Issue.find_by_id(1) && Issue.find_by_id(3)
921 936 end
922 937
923 938 def test_destroy_issues_and_destroy_time_entries
924 939 @request.session[:user_id] = 2
925 940 post :destroy, :ids => [1, 3], :todo => 'destroy'
926 941 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
927 942 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
928 943 assert_nil TimeEntry.find_by_id([1, 2])
929 944 end
930 945
931 946 def test_destroy_issues_and_assign_time_entries_to_project
932 947 @request.session[:user_id] = 2
933 948 post :destroy, :ids => [1, 3], :todo => 'nullify'
934 949 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
935 950 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
936 951 assert_nil TimeEntry.find(1).issue_id
937 952 assert_nil TimeEntry.find(2).issue_id
938 953 end
939 954
940 955 def test_destroy_issues_and_reassign_time_entries_to_another_issue
941 956 @request.session[:user_id] = 2
942 957 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
943 958 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
944 959 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
945 960 assert_equal 2, TimeEntry.find(1).issue_id
946 961 assert_equal 2, TimeEntry.find(2).issue_id
947 962 end
948 963 end
General Comments 0
You need to be logged in to leave comments. Login now