OSDN Git Service

Adds a 'Move and follow' button on Move/Copy view to be redirected to the created...
[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_deny_anonymous_access_without_permission
362     Role.anonymous.remove_permission!(:view_issues)
363     get :show, :id => 1
364     assert_response :redirect
365   end
366   
367   def test_show_should_deny_non_member_access_without_permission
368     Role.non_member.remove_permission!(:view_issues)
369     @request.session[:user_id] = 9
370     get :show, :id => 1
371     assert_response 403
372   end
373   
374   def test_show_should_deny_member_access_without_permission
375     Role.find(1).remove_permission!(:view_issues)
376     @request.session[:user_id] = 2
377     get :show, :id => 1
378     assert_response 403
379   end
380   
381   def test_show_should_not_disclose_relations_to_invisible_issues
382     Setting.cross_project_issue_relations = '1'
383     IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
384     # Relation to a private project issue
385     IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
386     
387     get :show, :id => 1
388     assert_response :success
389     
390     assert_tag :div, :attributes => { :id => 'relations' },
391                      :descendant => { :tag => 'a', :content => /#2$/ }
392     assert_no_tag :div, :attributes => { :id => 'relations' },
393                         :descendant => { :tag => 'a', :content => /#4$/ }
394   end
395   
396   def test_show_atom
397     get :show, :id => 2, :format => 'atom'
398     assert_response :success
399     assert_template 'changes.rxml'
400     # Inline image
401     assert @response.body.include?("&lt;img src=\"http://test.host/attachments/download/10\" alt=\"\" /&gt;"), "Body did not match. Body: #{@response.body}"
402   end
403   
404   def test_new_routing
405     assert_routing(
406       {:method => :get, :path => '/projects/1/issues/new'},
407       :controller => 'issues', :action => 'new', :project_id => '1'
408     )
409     assert_recognizes(
410       {:controller => 'issues', :action => 'new', :project_id => '1'},
411       {:method => :post, :path => '/projects/1/issues'}
412     )
413   end
414
415   def test_show_export_to_pdf
416     get :show, :id => 3, :format => 'pdf'
417     assert_response :success
418     assert_equal 'application/pdf', @response.content_type
419     assert @response.body.starts_with?('%PDF')
420     assert_not_nil assigns(:issue)
421   end
422
423   def test_get_new
424     @request.session[:user_id] = 2
425     get :new, :project_id => 1, :tracker_id => 1
426     assert_response :success
427     assert_template 'new'
428     
429     assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
430                                                  :value => 'Default string' }
431   end
432
433   def test_get_new_without_tracker_id
434     @request.session[:user_id] = 2
435     get :new, :project_id => 1
436     assert_response :success
437     assert_template 'new'
438     
439     issue = assigns(:issue)
440     assert_not_nil issue
441     assert_equal Project.find(1).trackers.first, issue.tracker
442   end
443   
444   def test_get_new_with_no_default_status_should_display_an_error
445     @request.session[:user_id] = 2
446     IssueStatus.delete_all
447     
448     get :new, :project_id => 1
449     assert_response 500
450     assert_not_nil flash[:error]
451     assert_tag :tag => 'div', :attributes => { :class => /error/ },
452                               :content => /No default issue/
453   end
454   
455   def test_get_new_with_no_tracker_should_display_an_error
456     @request.session[:user_id] = 2
457     Tracker.delete_all
458     
459     get :new, :project_id => 1
460     assert_response 500
461     assert_not_nil flash[:error]
462     assert_tag :tag => 'div', :attributes => { :class => /error/ },
463                               :content => /No tracker/
464   end
465   
466   def test_update_new_form
467     @request.session[:user_id] = 2
468     xhr :post, :new, :project_id => 1,
469                      :issue => {:tracker_id => 2, 
470                                 :subject => 'This is the test_new issue',
471                                 :description => 'This is the description',
472                                 :priority_id => 5}
473     assert_response :success
474     assert_template 'new'
475   end 
476   
477   def test_post_new
478     @request.session[:user_id] = 2
479     assert_difference 'Issue.count' do
480       post :new, :project_id => 1, 
481                  :issue => {:tracker_id => 3,
482                             :subject => 'This is the test_new issue',
483                             :description => 'This is the description',
484                             :priority_id => 5,
485                             :estimated_hours => '',
486                             :custom_field_values => {'2' => 'Value for field 2'}}
487     end
488     assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
489     
490     issue = Issue.find_by_subject('This is the test_new issue')
491     assert_not_nil issue
492     assert_equal 2, issue.author_id
493     assert_equal 3, issue.tracker_id
494     assert_nil issue.estimated_hours
495     v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
496     assert_not_nil v
497     assert_equal 'Value for field 2', v.value
498   end
499   
500   def test_post_new_and_continue
501     @request.session[:user_id] = 2
502     post :new, :project_id => 1, 
503                :issue => {:tracker_id => 3,
504                           :subject => 'This is first issue',
505                           :priority_id => 5},
506                :continue => ''
507     assert_redirected_to :controller => 'issues', :action => 'new', :tracker_id => 3
508   end
509   
510   def test_post_new_without_custom_fields_param
511     @request.session[:user_id] = 2
512     assert_difference 'Issue.count' do
513       post :new, :project_id => 1, 
514                  :issue => {:tracker_id => 1,
515                             :subject => 'This is the test_new issue',
516                             :description => 'This is the description',
517                             :priority_id => 5}
518     end
519     assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
520   end
521
522   def test_post_new_with_required_custom_field_and_without_custom_fields_param
523     field = IssueCustomField.find_by_name('Database')
524     field.update_attribute(:is_required, true)
525
526     @request.session[:user_id] = 2
527     post :new, :project_id => 1, 
528                :issue => {:tracker_id => 1,
529                           :subject => 'This is the test_new issue',
530                           :description => 'This is the description',
531                           :priority_id => 5}
532     assert_response :success
533     assert_template 'new'
534     issue = assigns(:issue)
535     assert_not_nil issue
536     assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
537   end
538   
539   def test_post_new_with_watchers
540     @request.session[:user_id] = 2
541     ActionMailer::Base.deliveries.clear
542     
543     assert_difference 'Watcher.count', 2 do
544       post :new, :project_id => 1, 
545                  :issue => {:tracker_id => 1,
546                             :subject => 'This is a new issue with watchers',
547                             :description => 'This is the description',
548                             :priority_id => 5,
549                             :watcher_user_ids => ['2', '3']}
550     end
551     issue = Issue.find_by_subject('This is a new issue with watchers')
552     assert_not_nil issue
553     assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
554     
555     # Watchers added
556     assert_equal [2, 3], issue.watcher_user_ids.sort
557     assert issue.watched_by?(User.find(3))
558     # Watchers notified
559     mail = ActionMailer::Base.deliveries.last
560     assert_kind_of TMail::Mail, mail
561     assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
562   end
563   
564   def test_post_new_should_send_a_notification
565     ActionMailer::Base.deliveries.clear
566     @request.session[:user_id] = 2
567     assert_difference 'Issue.count' do
568       post :new, :project_id => 1, 
569                  :issue => {:tracker_id => 3,
570                             :subject => 'This is the test_new issue',
571                             :description => 'This is the description',
572                             :priority_id => 5,
573                             :estimated_hours => '',
574                             :custom_field_values => {'2' => 'Value for field 2'}}
575     end
576     assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
577     
578     assert_equal 1, ActionMailer::Base.deliveries.size
579   end
580   
581   def test_post_should_preserve_fields_values_on_validation_failure
582     @request.session[:user_id] = 2
583     post :new, :project_id => 1, 
584                :issue => {:tracker_id => 1,
585                           # empty subject
586                           :subject => '',
587                           :description => 'This is a description',
588                           :priority_id => 6,
589                           :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
590     assert_response :success
591     assert_template 'new'
592     
593     assert_tag :textarea, :attributes => { :name => 'issue[description]' },
594                           :content => 'This is a description'
595     assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
596                         :child => { :tag => 'option', :attributes => { :selected => 'selected',
597                                                                        :value => '6' },
598                                                       :content => 'High' }  
599     # Custom fields
600     assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
601                         :child => { :tag => 'option', :attributes => { :selected => 'selected',
602                                                                        :value => 'Oracle' },
603                                                       :content => 'Oracle' }  
604     assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
605                                         :value => 'Value for field 2'}
606   end
607   
608   def test_copy_routing
609     assert_routing(
610       {:method => :get, :path => '/projects/world_domination/issues/567/copy'},
611       :controller => 'issues', :action => 'new', :project_id => 'world_domination', :copy_from => '567'
612     )
613   end
614   
615   def test_copy_issue
616     @request.session[:user_id] = 2
617     get :new, :project_id => 1, :copy_from => 1
618     assert_template 'new'
619     assert_not_nil assigns(:issue)
620     orig = Issue.find(1)
621     assert_equal orig.subject, assigns(:issue).subject
622   end
623   
624   def test_edit_routing
625     assert_routing(
626       {:method => :get, :path => '/issues/1/edit'},
627       :controller => 'issues', :action => 'edit', :id => '1'
628     )
629     assert_recognizes( #TODO: use a PUT on the issue URI isntead, need to adjust form
630       {:controller => 'issues', :action => 'edit', :id => '1'},
631       {:method => :post, :path => '/issues/1/edit'}
632     )
633   end
634   
635   def test_get_edit
636     @request.session[:user_id] = 2
637     get :edit, :id => 1
638     assert_response :success
639     assert_template 'edit'
640     assert_not_nil assigns(:issue)
641     assert_equal Issue.find(1), assigns(:issue)
642   end
643   
644   def test_get_edit_with_params
645     @request.session[:user_id] = 2
646     get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
647     assert_response :success
648     assert_template 'edit'
649     
650     issue = assigns(:issue)
651     assert_not_nil issue
652     
653     assert_equal 5, issue.status_id
654     assert_tag :select, :attributes => { :name => 'issue[status_id]' },
655                         :child => { :tag => 'option', 
656                                     :content => 'Closed',
657                                     :attributes => { :selected => 'selected' } }
658                                     
659     assert_equal 7, issue.priority_id
660     assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
661                         :child => { :tag => 'option', 
662                                     :content => 'Urgent',
663                                     :attributes => { :selected => 'selected' } }
664   end
665   
666   def test_reply_routing
667     assert_routing(
668       {:method => :post, :path => '/issues/1/quoted'},
669       :controller => 'issues', :action => 'reply', :id => '1'
670     )
671   end
672   
673   def test_reply_to_issue
674     @request.session[:user_id] = 2
675     get :reply, :id => 1
676     assert_response :success
677     assert_select_rjs :show, "update"
678   end
679
680   def test_reply_to_note
681     @request.session[:user_id] = 2
682     get :reply, :id => 1, :journal_id => 2
683     assert_response :success
684     assert_select_rjs :show, "update"
685   end
686
687   def test_post_edit_without_custom_fields_param
688     @request.session[:user_id] = 2
689     ActionMailer::Base.deliveries.clear
690     
691     issue = Issue.find(1)
692     assert_equal '125', issue.custom_value_for(2).value
693     old_subject = issue.subject
694     new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
695     
696     assert_difference('Journal.count') do
697       assert_difference('JournalDetail.count', 2) do
698         post :edit, :id => 1, :issue => {:subject => new_subject,
699                                          :priority_id => '6',
700                                          :category_id => '1' # no change
701                                         }
702       end
703     end
704     assert_redirected_to :action => 'show', :id => '1'
705     issue.reload
706     assert_equal new_subject, issue.subject
707     # Make sure custom fields were not cleared
708     assert_equal '125', issue.custom_value_for(2).value
709     
710     mail = ActionMailer::Base.deliveries.last
711     assert_kind_of TMail::Mail, mail
712     assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
713     assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
714   end
715   
716   def test_post_edit_with_custom_field_change
717     @request.session[:user_id] = 2
718     issue = Issue.find(1)
719     assert_equal '125', issue.custom_value_for(2).value
720     
721     assert_difference('Journal.count') do
722       assert_difference('JournalDetail.count', 3) do
723         post :edit, :id => 1, :issue => {:subject => 'Custom field change',
724                                          :priority_id => '6',
725                                          :category_id => '1', # no change
726                                          :custom_field_values => { '2' => 'New custom value' }
727                                         }
728       end
729     end
730     assert_redirected_to :action => 'show', :id => '1'
731     issue.reload
732     assert_equal 'New custom value', issue.custom_value_for(2).value
733     
734     mail = ActionMailer::Base.deliveries.last
735     assert_kind_of TMail::Mail, mail
736     assert mail.body.include?("Searchable field changed from 125 to New custom value")
737   end
738   
739   def test_post_edit_with_status_and_assignee_change
740     issue = Issue.find(1)
741     assert_equal 1, issue.status_id
742     @request.session[:user_id] = 2
743     assert_difference('TimeEntry.count', 0) do
744       post :edit,
745            :id => 1,
746            :issue => { :status_id => 2, :assigned_to_id => 3 },
747            :notes => 'Assigned to dlopper',
748            :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
749     end
750     assert_redirected_to :action => 'show', :id => '1'
751     issue.reload
752     assert_equal 2, issue.status_id
753     j = issue.journals.find(:first, :order => 'id DESC')
754     assert_equal 'Assigned to dlopper', j.notes
755     assert_equal 2, j.details.size
756     
757     mail = ActionMailer::Base.deliveries.last
758     assert mail.body.include?("Status changed from New to Assigned")
759     # subject should contain the new status
760     assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
761   end
762   
763   def test_post_edit_with_note_only
764     notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
765     # anonymous user
766     post :edit,
767          :id => 1,
768          :notes => notes
769     assert_redirected_to :action => 'show', :id => '1'
770     j = Issue.find(1).journals.find(:first, :order => 'id DESC')
771     assert_equal notes, j.notes
772     assert_equal 0, j.details.size
773     assert_equal User.anonymous, j.user
774     
775     mail = ActionMailer::Base.deliveries.last
776     assert mail.body.include?(notes)
777   end
778   
779   def test_post_edit_with_note_and_spent_time
780     @request.session[:user_id] = 2
781     spent_hours_before = Issue.find(1).spent_hours
782     assert_difference('TimeEntry.count') do
783       post :edit,
784            :id => 1,
785            :notes => '2.5 hours added',
786            :time_entry => { :hours => '2.5', :comments => '', :activity_id => TimeEntryActivity.first }
787     end
788     assert_redirected_to :action => 'show', :id => '1'
789     
790     issue = Issue.find(1)
791     
792     j = issue.journals.find(:first, :order => 'id DESC')
793     assert_equal '2.5 hours added', j.notes
794     assert_equal 0, j.details.size
795     
796     t = issue.time_entries.find(:first, :order => 'id DESC')
797     assert_not_nil t
798     assert_equal 2.5, t.hours
799     assert_equal spent_hours_before + 2.5, issue.spent_hours
800   end
801   
802   def test_post_edit_with_attachment_only
803     set_tmp_attachments_directory
804     
805     # Delete all fixtured journals, a race condition can occur causing the wrong
806     # journal to get fetched in the next find.
807     Journal.delete_all
808
809     # anonymous user
810     post :edit,
811          :id => 1,
812          :notes => '',
813          :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
814     assert_redirected_to :action => 'show', :id => '1'
815     j = Issue.find(1).journals.find(:first, :order => 'id DESC')
816     assert j.notes.blank?
817     assert_equal 1, j.details.size
818     assert_equal 'testfile.txt', j.details.first.value
819     assert_equal User.anonymous, j.user
820     
821     mail = ActionMailer::Base.deliveries.last
822     assert mail.body.include?('testfile.txt')
823   end
824   
825   def test_post_edit_with_no_change
826     issue = Issue.find(1)
827     issue.journals.clear
828     ActionMailer::Base.deliveries.clear
829     
830     post :edit,
831          :id => 1,
832          :notes => ''
833     assert_redirected_to :action => 'show', :id => '1'
834     
835     issue.reload
836     assert issue.journals.empty?
837     # No email should be sent
838     assert ActionMailer::Base.deliveries.empty?
839   end
840
841   def test_post_edit_should_send_a_notification
842     @request.session[:user_id] = 2
843     ActionMailer::Base.deliveries.clear
844     issue = Issue.find(1)
845     old_subject = issue.subject
846     new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
847     
848     post :edit, :id => 1, :issue => {:subject => new_subject,
849                                      :priority_id => '6',
850                                      :category_id => '1' # no change
851                                     }
852     assert_equal 1, ActionMailer::Base.deliveries.size
853   end
854   
855   def test_post_edit_with_invalid_spent_time
856     @request.session[:user_id] = 2
857     notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
858     
859     assert_no_difference('Journal.count') do
860       post :edit,
861            :id => 1,
862            :notes => notes,
863            :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
864     end
865     assert_response :success
866     assert_template 'edit'
867     
868     assert_tag :textarea, :attributes => { :name => 'notes' },
869                           :content => notes
870     assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
871   end
872   
873   def test_get_bulk_edit
874     @request.session[:user_id] = 2
875     get :bulk_edit, :ids => [1, 2]
876     assert_response :success
877     assert_template 'bulk_edit'
878   end
879
880   def test_bulk_edit
881     @request.session[:user_id] = 2
882     # update issues priority
883     post :bulk_edit, :ids => [1, 2], :priority_id => 7,
884                                      :assigned_to_id => '',
885                                      :custom_field_values => {'2' => ''},
886                                      :notes => 'Bulk editing'
887     assert_response 302
888     # check that the issues were updated
889     assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
890     
891     issue = Issue.find(1)
892     journal = issue.journals.find(:first, :order => 'created_on DESC')
893     assert_equal '125', issue.custom_value_for(2).value
894     assert_equal 'Bulk editing', journal.notes
895     assert_equal 1, journal.details.size
896   end
897
898   def test_bullk_edit_should_send_a_notification
899     @request.session[:user_id] = 2
900     ActionMailer::Base.deliveries.clear
901     post(:bulk_edit,
902          {
903            :ids => [1, 2],
904            :priority_id => 7,
905            :assigned_to_id => '',
906            :custom_field_values => {'2' => ''},
907            :notes => 'Bulk editing'
908          })
909
910     assert_response 302
911     assert_equal 2, ActionMailer::Base.deliveries.size
912   end
913
914   def test_bulk_edit_status
915     @request.session[:user_id] = 2
916     # update issues priority
917     post :bulk_edit, :ids => [1, 2], :priority_id => '',
918                                      :assigned_to_id => '',
919                                      :status_id => '5',
920                                      :notes => 'Bulk editing status'
921     assert_response 302
922     issue = Issue.find(1)
923     assert issue.closed?
924   end
925
926   def test_bulk_edit_custom_field
927     @request.session[:user_id] = 2
928     # update issues priority
929     post :bulk_edit, :ids => [1, 2], :priority_id => '',
930                                      :assigned_to_id => '',
931                                      :custom_field_values => {'2' => '777'},
932                                      :notes => 'Bulk editing custom field'
933     assert_response 302
934     
935     issue = Issue.find(1)
936     journal = issue.journals.find(:first, :order => 'created_on DESC')
937     assert_equal '777', issue.custom_value_for(2).value
938     assert_equal 1, journal.details.size
939     assert_equal '125', journal.details.first.old_value
940     assert_equal '777', journal.details.first.value
941   end
942
943   def test_bulk_unassign
944     assert_not_nil Issue.find(2).assigned_to
945     @request.session[:user_id] = 2
946     # unassign issues
947     post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :assigned_to_id => 'none'
948     assert_response 302
949     # check that the issues were updated
950     assert_nil Issue.find(2).assigned_to
951   end
952   
953   def test_move_routing
954     assert_routing(
955       {:method => :get, :path => '/issues/1/move'},
956       :controller => 'issues', :action => 'move', :id => '1'
957     )
958     assert_recognizes(
959       {:controller => 'issues', :action => 'move', :id => '1'},
960       {:method => :post, :path => '/issues/1/move'}
961     )
962   end
963   
964   def test_move_one_issue_to_another_project
965     @request.session[:user_id] = 2
966     post :move, :id => 1, :new_project_id => 2
967     assert_redirected_to :action => 'index', :project_id => 'ecookbook'
968     assert_equal 2, Issue.find(1).project_id
969   end
970
971   def test_move_one_issue_to_another_project_should_follow_when_needed
972     @request.session[:user_id] = 2
973     post :move, :id => 1, :new_project_id => 2, :follow => '1'
974     assert_redirected_to '/issues/1'
975   end
976
977   def test_bulk_move_to_another_project
978     @request.session[:user_id] = 2
979     post :move, :ids => [1, 2], :new_project_id => 2
980     assert_redirected_to :action => 'index', :project_id => 'ecookbook'
981     # Issues moved to project 2
982     assert_equal 2, Issue.find(1).project_id
983     assert_equal 2, Issue.find(2).project_id
984     # No tracker change
985     assert_equal 1, Issue.find(1).tracker_id
986     assert_equal 2, Issue.find(2).tracker_id
987   end
988  
989   def test_bulk_move_to_another_tracker
990     @request.session[:user_id] = 2
991     post :move, :ids => [1, 2], :new_tracker_id => 2
992     assert_redirected_to :action => 'index', :project_id => 'ecookbook'
993     assert_equal 2, Issue.find(1).tracker_id
994     assert_equal 2, Issue.find(2).tracker_id
995   end
996
997   def test_bulk_copy_to_another_project
998     @request.session[:user_id] = 2
999     assert_difference 'Issue.count', 2 do
1000       assert_no_difference 'Project.find(1).issues.count' do
1001         post :move, :ids => [1, 2], :new_project_id => 2, :copy_options => {:copy => '1'}
1002       end
1003     end
1004     assert_redirected_to 'projects/ecookbook/issues'
1005   end
1006
1007   def test_copy_to_another_project_should_follow_when_needed
1008     @request.session[:user_id] = 2
1009     post :move, :ids => [1], :new_project_id => 2, :copy_options => {:copy => '1'}, :follow => '1'
1010     issue = Issue.first(:order => 'id DESC')
1011     assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
1012   end
1013   
1014   def test_context_menu_one_issue
1015     @request.session[:user_id] = 2
1016     get :context_menu, :ids => [1]
1017     assert_response :success
1018     assert_template 'context_menu'
1019     assert_tag :tag => 'a', :content => 'Edit',
1020                             :attributes => { :href => '/issues/1/edit',
1021                                              :class => 'icon-edit' }
1022     assert_tag :tag => 'a', :content => 'Closed',
1023                             :attributes => { :href => '/issues/1/edit?issue%5Bstatus_id%5D=5',
1024                                              :class => '' }
1025     assert_tag :tag => 'a', :content => 'Immediate',
1026                             :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;priority_id=8',
1027                                              :class => '' }
1028     assert_tag :tag => 'a', :content => 'Dave Lopper',
1029                             :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1',
1030                                              :class => '' }
1031     assert_tag :tag => 'a', :content => 'Copy',
1032                             :attributes => { :href => '/projects/ecookbook/issues/1/copy',
1033                                              :class => 'icon-copy' }
1034     assert_tag :tag => 'a', :content => 'Move',
1035                             :attributes => { :href => '/issues/move?ids%5B%5D=1',
1036                                              :class => 'icon-move' }
1037     assert_tag :tag => 'a', :content => 'Delete',
1038                             :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
1039                                              :class => 'icon-del' }
1040   end
1041
1042   def test_context_menu_one_issue_by_anonymous
1043     get :context_menu, :ids => [1]
1044     assert_response :success
1045     assert_template 'context_menu'
1046     assert_tag :tag => 'a', :content => 'Delete',
1047                             :attributes => { :href => '#',
1048                                              :class => 'icon-del disabled' }
1049   end
1050   
1051   def test_context_menu_multiple_issues_of_same_project
1052     @request.session[:user_id] = 2
1053     get :context_menu, :ids => [1, 2]
1054     assert_response :success
1055     assert_template 'context_menu'
1056     assert_tag :tag => 'a', :content => 'Edit',
1057                             :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
1058                                              :class => 'icon-edit' }
1059     assert_tag :tag => 'a', :content => 'Immediate',
1060                             :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;priority_id=8',
1061                                              :class => '' }
1062     assert_tag :tag => 'a', :content => 'Dave Lopper',
1063                             :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
1064                                              :class => '' }
1065     assert_tag :tag => 'a', :content => 'Move',
1066                             :attributes => { :href => '/issues/move?ids%5B%5D=1&amp;ids%5B%5D=2',
1067                                              :class => 'icon-move' }
1068     assert_tag :tag => 'a', :content => 'Delete',
1069                             :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
1070                                              :class => 'icon-del' }
1071   end
1072
1073   def test_context_menu_multiple_issues_of_different_project
1074     @request.session[:user_id] = 2
1075     get :context_menu, :ids => [1, 2, 4]
1076     assert_response :success
1077     assert_template 'context_menu'
1078     assert_tag :tag => 'a', :content => 'Delete',
1079                             :attributes => { :href => '#',
1080                                              :class => 'icon-del disabled' }
1081   end
1082   
1083   def test_destroy_routing
1084     assert_recognizes( #TODO: use DELETE on issue URI (need to change forms)
1085       {:controller => 'issues', :action => 'destroy', :id => '1'},
1086       {:method => :post, :path => '/issues/1/destroy'}
1087     )
1088   end
1089   
1090   def test_destroy_issue_with_no_time_entries
1091     assert_nil TimeEntry.find_by_issue_id(2)
1092     @request.session[:user_id] = 2
1093     post :destroy, :id => 2
1094     assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1095     assert_nil Issue.find_by_id(2)
1096   end
1097
1098   def test_destroy_issues_with_time_entries
1099     @request.session[:user_id] = 2
1100     post :destroy, :ids => [1, 3]
1101     assert_response :success
1102     assert_template 'destroy'
1103     assert_not_nil assigns(:hours)
1104     assert Issue.find_by_id(1) && Issue.find_by_id(3)
1105   end
1106
1107   def test_destroy_issues_and_destroy_time_entries
1108     @request.session[:user_id] = 2
1109     post :destroy, :ids => [1, 3], :todo => 'destroy'
1110     assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1111     assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1112     assert_nil TimeEntry.find_by_id([1, 2])
1113   end
1114
1115   def test_destroy_issues_and_assign_time_entries_to_project
1116     @request.session[:user_id] = 2
1117     post :destroy, :ids => [1, 3], :todo => 'nullify'
1118     assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1119     assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1120     assert_nil TimeEntry.find(1).issue_id
1121     assert_nil TimeEntry.find(2).issue_id
1122   end
1123   
1124   def test_destroy_issues_and_reassign_time_entries_to_another_issue
1125     @request.session[:user_id] = 2
1126     post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
1127     assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1128     assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1129     assert_equal 2, TimeEntry.find(1).issue_id
1130     assert_equal 2, TimeEntry.find(2).issue_id
1131   end
1132   
1133   def test_default_search_scope
1134     get :index
1135     assert_tag :div, :attributes => {:id => 'quick-search'},
1136                      :child => {:tag => 'form',
1137                                 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
1138   end
1139 end