OSDN Git Service

Change Project#notified_users to check for the 'all' notification option. #6541
[redminele/redmine.git] / test / unit / project_test.rb
1 # redMine - project management software
2 # Copyright (C) 2006-2007  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
20 class ProjectTest < ActiveSupport::TestCase
21   fixtures :all
22
23   def setup
24     @ecookbook = Project.find(1)
25     @ecookbook_sub1 = Project.find(3)
26     User.current = nil
27   end
28   
29   should_validate_presence_of :name
30   should_validate_presence_of :identifier
31
32   should_validate_uniqueness_of :name
33   should_validate_uniqueness_of :identifier
34
35   context "associations" do
36     should_have_many :members
37     should_have_many :users, :through => :members
38     should_have_many :member_principals
39     should_have_many :principals, :through => :member_principals
40     should_have_many :enabled_modules
41     should_have_many :issues
42     should_have_many :issue_changes, :through => :issues
43     should_have_many :versions
44     should_have_many :time_entries
45     should_have_many :queries
46     should_have_many :documents
47     should_have_many :news
48     should_have_many :issue_categories
49     should_have_many :boards
50     should_have_many :changesets, :through => :repository
51
52     should_have_one :repository
53     should_have_one :wiki
54
55     should_have_and_belong_to_many :trackers
56     should_have_and_belong_to_many :issue_custom_fields
57   end
58
59   def test_truth
60     assert_kind_of Project, @ecookbook
61     assert_equal "eCookbook", @ecookbook.name
62   end
63   
64   def test_update
65     assert_equal "eCookbook", @ecookbook.name
66     @ecookbook.name = "eCook"
67     assert @ecookbook.save, @ecookbook.errors.full_messages.join("; ")
68     @ecookbook.reload
69     assert_equal "eCook", @ecookbook.name
70   end
71   
72   def test_validate_identifier
73     to_test = {"abc" => true,
74                "ab12" => true,
75                "ab-12" => true,
76                "12" => false,
77                "new" => false}
78                
79     to_test.each do |identifier, valid|
80       p = Project.new
81       p.identifier = identifier
82       p.valid?
83       assert_equal valid, p.errors.on('identifier').nil?
84     end
85   end
86
87   def test_members_should_be_active_users
88     Project.all.each do |project|
89       assert_nil project.members.detect {|m| !(m.user.is_a?(User) && m.user.active?) }
90     end
91   end
92   
93   def test_users_should_be_active_users
94     Project.all.each do |project|
95       assert_nil project.users.detect {|u| !(u.is_a?(User) && u.active?) }
96     end
97   end
98   
99   def test_archive
100     user = @ecookbook.members.first.user
101     @ecookbook.archive
102     @ecookbook.reload
103     
104     assert !@ecookbook.active?
105     assert !user.projects.include?(@ecookbook)
106     # Subproject are also archived
107     assert !@ecookbook.children.empty?
108     assert @ecookbook.descendants.active.empty?
109   end
110   
111   def test_archive_should_fail_if_versions_are_used_by_non_descendant_projects
112     # Assign an issue of a project to a version of a child project
113     Issue.find(4).update_attribute :fixed_version_id, 4
114     
115     assert_no_difference "Project.count(:all, :conditions => 'status = #{Project::STATUS_ARCHIVED}')" do
116       assert_equal false, @ecookbook.archive
117     end
118     @ecookbook.reload
119     assert @ecookbook.active?
120   end
121   
122   def test_unarchive
123     user = @ecookbook.members.first.user
124     @ecookbook.archive
125     # A subproject of an archived project can not be unarchived
126     assert !@ecookbook_sub1.unarchive
127     
128     # Unarchive project
129     assert @ecookbook.unarchive
130     @ecookbook.reload
131     assert @ecookbook.active?
132     assert user.projects.include?(@ecookbook)
133     # Subproject can now be unarchived
134     @ecookbook_sub1.reload
135     assert @ecookbook_sub1.unarchive
136   end
137   
138   def test_destroy
139     # 2 active members
140     assert_equal 2, @ecookbook.members.size
141     # and 1 is locked
142     assert_equal 3, Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).size
143     # some boards
144     assert @ecookbook.boards.any?
145     
146     @ecookbook.destroy
147     # make sure that the project non longer exists
148     assert_raise(ActiveRecord::RecordNotFound) { Project.find(@ecookbook.id) }
149     # make sure related data was removed
150     assert_nil Member.first(:conditions => {:project_id => @ecookbook.id})
151     assert_nil Board.first(:conditions => {:project_id => @ecookbook.id})
152     assert_nil Issue.first(:conditions => {:project_id => @ecookbook.id})
153   end
154   
155   def test_move_an_orphan_project_to_a_root_project
156     sub = Project.find(2)
157     sub.set_parent! @ecookbook
158     assert_equal @ecookbook.id, sub.parent.id
159     @ecookbook.reload
160     assert_equal 4, @ecookbook.children.size
161   end
162   
163   def test_move_an_orphan_project_to_a_subproject
164     sub = Project.find(2)
165     assert sub.set_parent!(@ecookbook_sub1)
166   end
167   
168   def test_move_a_root_project_to_a_project
169     sub = @ecookbook
170     assert sub.set_parent!(Project.find(2))
171   end
172   
173   def test_should_not_move_a_project_to_its_children
174     sub = @ecookbook
175     assert !(sub.set_parent!(Project.find(3)))
176   end
177   
178   def test_set_parent_should_add_roots_in_alphabetical_order
179     ProjectCustomField.delete_all
180     Project.delete_all
181     Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(nil)
182     Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(nil)
183     Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(nil)
184     Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(nil)
185     
186     assert_equal 4, Project.count
187     assert_equal Project.all.sort_by(&:name), Project.all.sort_by(&:lft)
188   end
189   
190   def test_set_parent_should_add_children_in_alphabetical_order
191     ProjectCustomField.delete_all
192     parent = Project.create!(:name => 'Parent', :identifier => 'parent')
193     Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(parent)
194     Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(parent)
195     Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(parent)
196     Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(parent)
197     
198     parent.reload
199     assert_equal 4, parent.children.size
200     assert_equal parent.children.sort_by(&:name), parent.children
201   end
202   
203   def test_rebuild_should_sort_children_alphabetically
204     ProjectCustomField.delete_all
205     parent = Project.create!(:name => 'Parent', :identifier => 'parent')
206     Project.create!(:name => 'Project C', :identifier => 'project-c').move_to_child_of(parent)
207     Project.create!(:name => 'Project B', :identifier => 'project-b').move_to_child_of(parent)
208     Project.create!(:name => 'Project D', :identifier => 'project-d').move_to_child_of(parent)
209     Project.create!(:name => 'Project A', :identifier => 'project-a').move_to_child_of(parent)
210     
211     Project.update_all("lft = NULL, rgt = NULL")
212     Project.rebuild!
213     
214     parent.reload
215     assert_equal 4, parent.children.size
216     assert_equal parent.children.sort_by(&:name), parent.children
217   end
218
219
220   def test_set_parent_should_update_issue_fixed_version_associations_when_a_fixed_version_is_moved_out_of_the_hierarchy
221     # Parent issue with a hierarchy project's fixed version
222     parent_issue = Issue.find(1)
223     parent_issue.update_attribute(:fixed_version_id, 4)
224     parent_issue.reload
225     assert_equal 4, parent_issue.fixed_version_id
226
227     # Should keep fixed versions for the issues
228     issue_with_local_fixed_version = Issue.find(5)
229     issue_with_local_fixed_version.update_attribute(:fixed_version_id, 4)
230     issue_with_local_fixed_version.reload
231     assert_equal 4, issue_with_local_fixed_version.fixed_version_id
232
233     # Local issue with hierarchy fixed_version
234     issue_with_hierarchy_fixed_version = Issue.find(13)
235     issue_with_hierarchy_fixed_version.update_attribute(:fixed_version_id, 6)
236     issue_with_hierarchy_fixed_version.reload
237     assert_equal 6, issue_with_hierarchy_fixed_version.fixed_version_id
238     
239     # Move project out of the issue's hierarchy
240     moved_project = Project.find(3)
241     moved_project.set_parent!(Project.find(2))
242     parent_issue.reload
243     issue_with_local_fixed_version.reload
244     issue_with_hierarchy_fixed_version.reload
245     
246     assert_equal 4, issue_with_local_fixed_version.fixed_version_id, "Fixed version was not keep on an issue local to the moved project"
247     assert_equal nil, issue_with_hierarchy_fixed_version.fixed_version_id, "Fixed version is still set after moving the Project out of the hierarchy where the version is defined in"
248     assert_equal nil, parent_issue.fixed_version_id, "Fixed version is still set after moving the Version out of the hierarchy for the issue."
249   end
250   
251   def test_parent
252     p = Project.find(6).parent
253     assert p.is_a?(Project)
254     assert_equal 5, p.id
255   end
256   
257   def test_ancestors
258     a = Project.find(6).ancestors
259     assert a.first.is_a?(Project)
260     assert_equal [1, 5], a.collect(&:id)
261   end
262   
263   def test_root
264     r = Project.find(6).root
265     assert r.is_a?(Project)
266     assert_equal 1, r.id
267   end
268   
269   def test_children
270     c = Project.find(1).children
271     assert c.first.is_a?(Project)
272     assert_equal [5, 3, 4], c.collect(&:id)
273   end
274   
275   def test_descendants
276     d = Project.find(1).descendants
277     assert d.first.is_a?(Project)
278     assert_equal [5, 6, 3, 4], d.collect(&:id)
279   end
280   
281   def test_allowed_parents_should_be_empty_for_non_member_user
282     Role.non_member.add_permission!(:add_project)
283     user = User.find(9)
284     assert user.memberships.empty?
285     User.current = user
286     assert Project.new.allowed_parents.compact.empty?
287   end
288   
289   def test_allowed_parents_with_add_subprojects_permission
290     Role.find(1).remove_permission!(:add_project)
291     Role.find(1).add_permission!(:add_subprojects)
292     User.current = User.find(2)
293     # new project
294     assert !Project.new.allowed_parents.include?(nil)
295     assert Project.new.allowed_parents.include?(Project.find(1))
296     # existing root project
297     assert Project.find(1).allowed_parents.include?(nil)
298     # existing child
299     assert Project.find(3).allowed_parents.include?(Project.find(1))
300     assert !Project.find(3).allowed_parents.include?(nil)
301   end
302
303   def test_allowed_parents_with_add_project_permission
304     Role.find(1).add_permission!(:add_project)
305     Role.find(1).remove_permission!(:add_subprojects)
306     User.current = User.find(2)
307     # new project
308     assert Project.new.allowed_parents.include?(nil)
309     assert !Project.new.allowed_parents.include?(Project.find(1))
310     # existing root project
311     assert Project.find(1).allowed_parents.include?(nil)
312     # existing child
313     assert Project.find(3).allowed_parents.include?(Project.find(1))
314     assert Project.find(3).allowed_parents.include?(nil)
315   end
316
317   def test_allowed_parents_with_add_project_and_subprojects_permission
318     Role.find(1).add_permission!(:add_project)
319     Role.find(1).add_permission!(:add_subprojects)
320     User.current = User.find(2)
321     # new project
322     assert Project.new.allowed_parents.include?(nil)
323     assert Project.new.allowed_parents.include?(Project.find(1))
324     # existing root project
325     assert Project.find(1).allowed_parents.include?(nil)
326     # existing child
327     assert Project.find(3).allowed_parents.include?(Project.find(1))
328     assert Project.find(3).allowed_parents.include?(nil)
329   end
330   
331   def test_users_by_role
332     users_by_role = Project.find(1).users_by_role
333     assert_kind_of Hash, users_by_role
334     role = Role.find(1)
335     assert_kind_of Array, users_by_role[role]
336     assert users_by_role[role].include?(User.find(2))
337   end
338   
339   def test_rolled_up_trackers
340     parent = Project.find(1)
341     parent.trackers = Tracker.find([1,2])
342     child = parent.children.find(3)
343   
344     assert_equal [1, 2], parent.tracker_ids
345     assert_equal [2, 3], child.trackers.collect(&:id)
346     
347     assert_kind_of Tracker, parent.rolled_up_trackers.first
348     assert_equal Tracker.find(1), parent.rolled_up_trackers.first
349     
350     assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id)
351     assert_equal [2, 3], child.rolled_up_trackers.collect(&:id)
352   end
353   
354   def test_rolled_up_trackers_should_ignore_archived_subprojects
355     parent = Project.find(1)
356     parent.trackers = Tracker.find([1,2])
357     child = parent.children.find(3)
358     child.trackers = Tracker.find([1,3])
359     parent.children.each(&:archive)
360     
361     assert_equal [1,2], parent.rolled_up_trackers.collect(&:id)
362   end
363
364   context "#rolled_up_versions" do
365     setup do
366       @project = Project.generate!
367       @parent_version_1 = Version.generate!(:project => @project)
368       @parent_version_2 = Version.generate!(:project => @project)
369     end
370     
371     should "include the versions for the current project" do
372       assert_same_elements [@parent_version_1, @parent_version_2], @project.rolled_up_versions
373     end
374     
375     should "include versions for a subproject" do
376       @subproject = Project.generate!
377       @subproject.set_parent!(@project)
378       @subproject_version = Version.generate!(:project => @subproject)
379
380       assert_same_elements [
381                             @parent_version_1,
382                             @parent_version_2,
383                             @subproject_version
384                            ], @project.rolled_up_versions
385     end
386     
387     should "include versions for a sub-subproject" do
388       @subproject = Project.generate!
389       @subproject.set_parent!(@project)
390       @sub_subproject = Project.generate!
391       @sub_subproject.set_parent!(@subproject)
392       @sub_subproject_version = Version.generate!(:project => @sub_subproject)
393
394       @project.reload
395
396       assert_same_elements [
397                             @parent_version_1,
398                             @parent_version_2,
399                             @sub_subproject_version
400                            ], @project.rolled_up_versions
401     end
402
403     
404     should "only check active projects" do
405       @subproject = Project.generate!
406       @subproject.set_parent!(@project)
407       @subproject_version = Version.generate!(:project => @subproject)
408       assert @subproject.archive
409
410       @project.reload
411
412       assert !@subproject.active?
413       assert_same_elements [@parent_version_1, @parent_version_2], @project.rolled_up_versions
414     end
415   end
416   
417   def test_shared_versions_none_sharing
418     p = Project.find(5)
419     v = Version.create!(:name => 'none_sharing', :project => p, :sharing => 'none')
420     assert p.shared_versions.include?(v)
421     assert !p.children.first.shared_versions.include?(v)
422     assert !p.root.shared_versions.include?(v)
423     assert !p.siblings.first.shared_versions.include?(v)
424     assert !p.root.siblings.first.shared_versions.include?(v)
425   end
426
427   def test_shared_versions_descendants_sharing
428     p = Project.find(5)
429     v = Version.create!(:name => 'descendants_sharing', :project => p, :sharing => 'descendants')
430     assert p.shared_versions.include?(v)
431     assert p.children.first.shared_versions.include?(v)
432     assert !p.root.shared_versions.include?(v)
433     assert !p.siblings.first.shared_versions.include?(v)
434     assert !p.root.siblings.first.shared_versions.include?(v)
435   end
436   
437   def test_shared_versions_hierarchy_sharing
438     p = Project.find(5)
439     v = Version.create!(:name => 'hierarchy_sharing', :project => p, :sharing => 'hierarchy')
440     assert p.shared_versions.include?(v)
441     assert p.children.first.shared_versions.include?(v)
442     assert p.root.shared_versions.include?(v)
443     assert !p.siblings.first.shared_versions.include?(v)
444     assert !p.root.siblings.first.shared_versions.include?(v)
445   end
446
447   def test_shared_versions_tree_sharing
448     p = Project.find(5)
449     v = Version.create!(:name => 'tree_sharing', :project => p, :sharing => 'tree')
450     assert p.shared_versions.include?(v)
451     assert p.children.first.shared_versions.include?(v)
452     assert p.root.shared_versions.include?(v)
453     assert p.siblings.first.shared_versions.include?(v)
454     assert !p.root.siblings.first.shared_versions.include?(v)
455   end
456
457   def test_shared_versions_system_sharing
458     p = Project.find(5)
459     v = Version.create!(:name => 'system_sharing', :project => p, :sharing => 'system')
460     assert p.shared_versions.include?(v)
461     assert p.children.first.shared_versions.include?(v)
462     assert p.root.shared_versions.include?(v)
463     assert p.siblings.first.shared_versions.include?(v)
464     assert p.root.siblings.first.shared_versions.include?(v)
465   end
466
467   def test_shared_versions
468     parent = Project.find(1)
469     child = parent.children.find(3)
470     private_child = parent.children.find(5)
471     
472     assert_equal [1,2,3], parent.version_ids.sort
473     assert_equal [4], child.version_ids
474     assert_equal [6], private_child.version_ids
475     assert_equal [7], Version.find_all_by_sharing('system').collect(&:id)
476
477     assert_equal 6, parent.shared_versions.size
478     parent.shared_versions.each do |version|
479       assert_kind_of Version, version
480     end
481
482     assert_equal [1,2,3,4,6,7], parent.shared_versions.collect(&:id).sort
483   end
484
485   def test_shared_versions_should_ignore_archived_subprojects
486     parent = Project.find(1)
487     child = parent.children.find(3)
488     child.archive
489     parent.reload
490     
491     assert_equal [1,2,3], parent.version_ids.sort
492     assert_equal [4], child.version_ids
493     assert !parent.shared_versions.collect(&:id).include?(4)
494   end
495
496   def test_shared_versions_visible_to_user
497     user = User.find(3)
498     parent = Project.find(1)
499     child = parent.children.find(5)
500     
501     assert_equal [1,2,3], parent.version_ids.sort
502     assert_equal [6], child.version_ids
503
504     versions = parent.shared_versions.visible(user)
505     
506     assert_equal 4, versions.size
507     versions.each do |version|
508       assert_kind_of Version, version
509     end
510
511     assert !versions.collect(&:id).include?(6)
512   end
513
514   
515   def test_next_identifier
516     ProjectCustomField.delete_all
517     Project.create!(:name => 'last', :identifier => 'p2008040')
518     assert_equal 'p2008041', Project.next_identifier
519   end
520
521   def test_next_identifier_first_project
522     Project.delete_all
523     assert_nil Project.next_identifier
524   end
525   
526
527   def test_enabled_module_names_should_not_recreate_enabled_modules
528     project = Project.find(1)
529     # Remove one module
530     modules = project.enabled_modules.slice(0..-2)
531     assert modules.any?
532     assert_difference 'EnabledModule.count', -1 do
533       project.enabled_module_names = modules.collect(&:name)
534     end
535     project.reload
536     # Ids should be preserved
537     assert_equal project.enabled_module_ids.sort, modules.collect(&:id).sort
538   end
539
540   def test_copy_from_existing_project
541     source_project = Project.find(1)
542     copied_project = Project.copy_from(1)
543
544     assert copied_project
545     # Cleared attributes
546     assert copied_project.id.blank?
547     assert copied_project.name.blank?
548     assert copied_project.identifier.blank?
549     
550     # Duplicated attributes
551     assert_equal source_project.description, copied_project.description
552     assert_equal source_project.enabled_modules, copied_project.enabled_modules
553     assert_equal source_project.trackers, copied_project.trackers
554
555     # Default attributes
556     assert_equal 1, copied_project.status
557   end
558
559   def test_activities_should_use_the_system_activities
560     project = Project.find(1)
561     assert_equal project.activities, TimeEntryActivity.find(:all, :conditions => {:active => true} )
562   end
563
564
565   def test_activities_should_use_the_project_specific_activities
566     project = Project.find(1)
567     overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project})
568     assert overridden_activity.save!
569
570     assert project.activities.include?(overridden_activity), "Project specific Activity not found"
571   end
572
573   def test_activities_should_not_include_the_inactive_project_specific_activities
574     project = Project.find(1)
575     overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
576     assert overridden_activity.save!
577
578     assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity found"
579   end
580
581   def test_activities_should_not_include_project_specific_activities_from_other_projects
582     project = Project.find(1)
583     overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(2)})
584     assert overridden_activity.save!
585
586     assert !project.activities.include?(overridden_activity), "Project specific Activity found on a different project"
587   end
588
589   def test_activities_should_handle_nils
590     overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(1), :parent => TimeEntryActivity.find(:first)})
591     TimeEntryActivity.delete_all
592
593     # No activities
594     project = Project.find(1)
595     assert project.activities.empty?
596
597     # No system, one overridden
598     assert overridden_activity.save!
599     project.reload
600     assert_equal [overridden_activity], project.activities
601   end
602
603   def test_activities_should_override_system_activities_with_project_activities
604     project = Project.find(1)
605     parent_activity = TimeEntryActivity.find(:first)
606     overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => parent_activity})
607     assert overridden_activity.save!
608
609     assert project.activities.include?(overridden_activity), "Project specific Activity not found"
610     assert !project.activities.include?(parent_activity), "System Activity found when it should have been overridden"
611   end
612
613   def test_activities_should_include_inactive_activities_if_specified
614     project = Project.find(1)
615     overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
616     assert overridden_activity.save!
617
618     assert project.activities(true).include?(overridden_activity), "Inactive Project specific Activity not found"
619   end
620
621   test 'activities should not include active System activities if the project has an override that is inactive' do
622     project = Project.find(1)
623     system_activity = TimeEntryActivity.find_by_name('Design')
624     assert system_activity.active?
625     overridden_activity = TimeEntryActivity.generate!(:project => project, :parent => system_activity, :active => false)
626     assert overridden_activity.save!
627     
628     assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity not found"
629     assert !project.activities.include?(system_activity), "System activity found when the project has an inactive override"
630   end
631   
632   def test_close_completed_versions
633     Version.update_all("status = 'open'")
634     project = Project.find(1)
635     assert_not_nil project.versions.detect {|v| v.completed? && v.status == 'open'}
636     assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
637     project.close_completed_versions
638     project.reload
639     assert_nil project.versions.detect {|v| v.completed? && v.status != 'closed'}
640     assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
641   end
642
643   context "Project#copy" do
644     setup do
645       ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
646       Project.destroy_all :identifier => "copy-test"
647       @source_project = Project.find(2)
648       @project = Project.new(:name => 'Copy Test', :identifier => 'copy-test')
649       @project.trackers = @source_project.trackers
650       @project.enabled_module_names = @source_project.enabled_modules.collect(&:name)
651     end
652
653     should "copy issues" do
654       @source_project.issues << Issue.generate!(:status => IssueStatus.find_by_name('Closed'),
655                                                 :subject => "copy issue status",
656                                                 :tracker_id => 1,
657                                                 :assigned_to_id => 2,
658                                                 :project_id => @source_project.id)
659       assert @project.valid?
660       assert @project.issues.empty?
661       assert @project.copy(@source_project)
662
663       assert_equal @source_project.issues.size, @project.issues.size
664       @project.issues.each do |issue|
665         assert issue.valid?
666         assert ! issue.assigned_to.blank?
667         assert_equal @project, issue.project
668       end
669       
670       copied_issue = @project.issues.first(:conditions => {:subject => "copy issue status"})
671       assert copied_issue
672       assert copied_issue.status
673       assert_equal "Closed", copied_issue.status.name
674     end
675
676     should "change the new issues to use the copied version" do
677       User.current = User.find(1)
678       assigned_version = Version.generate!(:name => "Assigned Issues", :status => 'open')
679       @source_project.versions << assigned_version
680       assert_equal 3, @source_project.versions.size
681       Issue.generate_for_project!(@source_project,
682                                   :fixed_version_id => assigned_version.id,
683                                   :subject => "change the new issues to use the copied version",
684                                   :tracker_id => 1,
685                                   :project_id => @source_project.id)
686       
687       assert @project.copy(@source_project)
688       @project.reload
689       copied_issue = @project.issues.first(:conditions => {:subject => "change the new issues to use the copied version"})
690
691       assert copied_issue
692       assert copied_issue.fixed_version
693       assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name
694       assert_not_equal assigned_version.id, copied_issue.fixed_version.id # Different record
695     end
696
697     should "copy issue relations" do
698       Setting.cross_project_issue_relations = '1'
699
700       second_issue = Issue.generate!(:status_id => 5,
701                                      :subject => "copy issue relation",
702                                      :tracker_id => 1,
703                                      :assigned_to_id => 2,
704                                      :project_id => @source_project.id)
705       source_relation = IssueRelation.generate!(:issue_from => Issue.find(4),
706                                                 :issue_to => second_issue,
707                                                 :relation_type => "relates")
708       source_relation_cross_project = IssueRelation.generate!(:issue_from => Issue.find(1),
709                                                               :issue_to => second_issue,
710                                                               :relation_type => "duplicates")
711
712       assert @project.copy(@source_project)
713       assert_equal @source_project.issues.count, @project.issues.count
714       copied_issue = @project.issues.find_by_subject("Issue on project 2") # Was #4
715       copied_second_issue = @project.issues.find_by_subject("copy issue relation")
716
717       # First issue with a relation on project
718       assert_equal 1, copied_issue.relations.size, "Relation not copied"
719       copied_relation = copied_issue.relations.first
720       assert_equal "relates", copied_relation.relation_type
721       assert_equal copied_second_issue.id, copied_relation.issue_to_id
722       assert_not_equal source_relation.id, copied_relation.id
723
724       # Second issue with a cross project relation
725       assert_equal 2, copied_second_issue.relations.size, "Relation not copied"
726       copied_relation = copied_second_issue.relations.select {|r| r.relation_type == 'duplicates'}.first
727       assert_equal "duplicates", copied_relation.relation_type
728       assert_equal 1, copied_relation.issue_from_id, "Cross project relation not kept"
729       assert_not_equal source_relation_cross_project.id, copied_relation.id
730     end
731
732     should "copy memberships" do
733       assert @project.valid?
734       assert @project.members.empty?
735       assert @project.copy(@source_project)
736
737       assert_equal @source_project.memberships.size, @project.memberships.size
738       @project.memberships.each do |membership|
739         assert membership
740         assert_equal @project, membership.project
741       end
742     end
743
744     should "copy project specific queries" do
745       assert @project.valid?
746       assert @project.queries.empty?
747       assert @project.copy(@source_project)
748
749       assert_equal @source_project.queries.size, @project.queries.size
750       @project.queries.each do |query|
751         assert query
752         assert_equal @project, query.project
753       end
754     end
755
756     should "copy versions" do
757       @source_project.versions << Version.generate!
758       @source_project.versions << Version.generate!
759
760       assert @project.versions.empty?
761       assert @project.copy(@source_project)
762
763       assert_equal @source_project.versions.size, @project.versions.size
764       @project.versions.each do |version|
765         assert version
766         assert_equal @project, version.project
767       end
768     end
769
770     should "copy wiki" do
771       assert_difference 'Wiki.count' do
772         assert @project.copy(@source_project)
773       end
774
775       assert @project.wiki
776       assert_not_equal @source_project.wiki, @project.wiki
777       assert_equal "Start page", @project.wiki.start_page
778     end
779
780     should "copy wiki pages and content with hierarchy" do
781       assert_difference 'WikiPage.count', @source_project.wiki.pages.size do
782         assert @project.copy(@source_project)
783       end
784       
785       assert @project.wiki
786       assert_equal @source_project.wiki.pages.size, @project.wiki.pages.size
787
788       @project.wiki.pages.each do |wiki_page|
789         assert wiki_page.content
790         assert !@source_project.wiki.pages.include?(wiki_page)
791       end
792       
793       parent = @project.wiki.find_page('Parent_page')
794       child1 = @project.wiki.find_page('Child_page_1')
795       child2 = @project.wiki.find_page('Child_page_2')
796       assert_equal parent, child1.parent
797       assert_equal parent, child2.parent
798     end
799
800     should "copy issue categories" do
801       assert @project.copy(@source_project)
802
803       assert_equal 2, @project.issue_categories.size
804       @project.issue_categories.each do |issue_category|
805         assert !@source_project.issue_categories.include?(issue_category)
806       end
807     end
808
809     should "copy boards" do
810       assert @project.copy(@source_project)
811
812       assert_equal 1, @project.boards.size
813       @project.boards.each do |board|
814         assert !@source_project.boards.include?(board)
815       end
816     end
817
818     should "change the new issues to use the copied issue categories" do
819       issue = Issue.find(4)
820       issue.update_attribute(:category_id, 3)
821
822       assert @project.copy(@source_project)
823
824       @project.issues.each do |issue|
825         assert issue.category
826         assert_equal "Stock management", issue.category.name # Same name
827         assert_not_equal IssueCategory.find(3), issue.category # Different record
828       end
829     end
830     
831     should "limit copy with :only option" do
832       assert @project.members.empty?
833       assert @project.issue_categories.empty?
834       assert @source_project.issues.any?
835     
836       assert @project.copy(@source_project, :only => ['members', 'issue_categories'])
837
838       assert @project.members.any?
839       assert @project.issue_categories.any?
840       assert @project.issues.empty?
841     end
842     
843   end
844
845   context "#start_date" do
846     setup do
847       ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
848       @project = Project.generate!(:identifier => 'test0')
849       @project.trackers << Tracker.generate!
850     end
851     
852     should "be nil if there are no issues on the project" do
853       assert_nil @project.start_date
854     end
855
856     should "be nil if issue tracking is disabled" do
857       Issue.generate_for_project!(@project, :start_date => Date.today)
858       @project.enabled_modules.find_all_by_name('issue_tracking').each {|m| m.destroy}
859       @project.reload
860       
861       assert_nil @project.start_date
862     end
863     
864     should "be tested when issues have no start date"
865
866     should "be the earliest start date of it's issues" do
867       early = 7.days.ago.to_date
868       Issue.generate_for_project!(@project, :start_date => Date.today)
869       Issue.generate_for_project!(@project, :start_date => early)
870
871       assert_equal early, @project.start_date
872     end
873
874   end
875
876   context "#due_date" do
877     setup do
878       ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
879       @project = Project.generate!(:identifier => 'test0')
880       @project.trackers << Tracker.generate!
881     end
882     
883     should "be nil if there are no issues on the project" do
884       assert_nil @project.due_date
885     end
886
887     should "be nil if issue tracking is disabled" do
888       Issue.generate_for_project!(@project, :due_date => Date.today)
889       @project.enabled_modules.find_all_by_name('issue_tracking').each {|m| m.destroy}
890       @project.reload
891       
892       assert_nil @project.due_date
893     end
894     
895     should "be tested when issues have no due date"
896
897     should "be the latest due date of it's issues" do
898       future = 7.days.from_now.to_date
899       Issue.generate_for_project!(@project, :due_date => future)
900       Issue.generate_for_project!(@project, :due_date => Date.today)
901
902       assert_equal future, @project.due_date
903     end
904
905     should "be the latest due date of it's versions" do
906       future = 7.days.from_now.to_date
907       @project.versions << Version.generate!(:effective_date => future)
908       @project.versions << Version.generate!(:effective_date => Date.today)
909       
910
911       assert_equal future, @project.due_date
912
913     end
914
915     should "pick the latest date from it's issues and versions" do
916       future = 7.days.from_now.to_date
917       far_future = 14.days.from_now.to_date
918       Issue.generate_for_project!(@project, :due_date => far_future)
919       @project.versions << Version.generate!(:effective_date => future)
920       
921       assert_equal far_future, @project.due_date
922     end
923
924   end
925
926   context "Project#completed_percent" do
927     setup do
928       ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
929       @project = Project.generate!(:identifier => 'test0')
930       @project.trackers << Tracker.generate!
931     end
932
933     context "no versions" do
934       should "be 100" do
935         assert_equal 100, @project.completed_percent
936       end
937     end
938
939     context "with versions" do
940       should "return 0 if the versions have no issues" do
941         Version.generate!(:project => @project)
942         Version.generate!(:project => @project)
943
944         assert_equal 0, @project.completed_percent
945       end
946
947       should "return 100 if the version has only closed issues" do
948         v1 = Version.generate!(:project => @project)
949         Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v1)
950         v2 = Version.generate!(:project => @project)
951         Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v2)
952
953         assert_equal 100, @project.completed_percent
954       end
955
956       should "return the averaged completed percent of the versions (not weighted)" do
957         v1 = Version.generate!(:project => @project)
958         Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v1)
959         v2 = Version.generate!(:project => @project)
960         Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v2)
961
962         assert_equal 50, @project.completed_percent
963       end
964
965     end
966   end
967
968   context "#notified_users" do
969     setup do
970       @project = Project.generate!
971       @role = Role.generate!
972       
973       @user_with_membership_notification = User.generate!(:mail_notification => 'selected')
974       Member.generate!(:project => @project, :roles => [@role], :principal => @user_with_membership_notification, :mail_notification => true)
975
976       @all_events_user = User.generate!(:mail_notification => 'all')
977       Member.generate!(:project => @project, :roles => [@role], :principal => @all_events_user)
978
979       @no_events_user = User.generate!(:mail_notification => 'none')
980       Member.generate!(:project => @project, :roles => [@role], :principal => @no_events_user)
981
982       @only_my_events_user = User.generate!(:mail_notification => 'only_my_events')
983       Member.generate!(:project => @project, :roles => [@role], :principal => @only_my_events_user)
984
985       @only_assigned_user = User.generate!(:mail_notification => 'only_assigned')
986       Member.generate!(:project => @project, :roles => [@role], :principal => @only_assigned_user)
987
988       @only_owned_user = User.generate!(:mail_notification => 'only_owner')
989       Member.generate!(:project => @project, :roles => [@role], :principal => @only_owned_user)
990     end
991     
992     should "include members with a mail notification" do
993       assert @project.notified_users.include?(@user_with_membership_notification)
994     end
995     
996     should "include users with the 'all' notification option" do
997       assert @project.notified_users.include?(@all_events_user)
998     end
999     
1000     should "not include users with the 'none' notification option" do
1001       assert !@project.notified_users.include?(@no_events_user)
1002     end
1003     
1004     should "not include users with the 'only_my_events' notification option" do
1005       assert !@project.notified_users.include?(@only_my_events_user)
1006     end
1007     
1008     should "not include users with the 'only_assigned' notification option" do
1009       assert !@project.notified_users.include?(@only_assigned_user)
1010     end
1011     
1012     should "not include users with the 'only_owner' notification option" do
1013       assert !@project.notified_users.include?(@only_owned_user)
1014     end
1015   end
1016   
1017 end