OSDN Git Service

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