OSDN Git Service

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