OSDN Git Service

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