-# redMine - project management software
-# Copyright (C) 2006-2007 Jean-Philippe Lang
+# Redmine - project management software
+# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-require File.dirname(__FILE__) + '/../test_helper'
+require File.expand_path('../../test_helper', __FILE__)
class ProjectTest < ActiveSupport::TestCase
fixtures :all
should_validate_presence_of :name
should_validate_presence_of :identifier
- should_validate_uniqueness_of :name
should_validate_uniqueness_of :identifier
context "associations" do
assert_equal "eCookbook", @ecookbook.name
end
+ def test_default_attributes
+ with_settings :default_projects_public => '1' do
+ assert_equal true, Project.new.is_public
+ assert_equal false, Project.new(:is_public => false).is_public
+ end
+
+ with_settings :default_projects_public => '0' do
+ assert_equal false, Project.new.is_public
+ assert_equal true, Project.new(:is_public => true).is_public
+ end
+
+ with_settings :sequential_project_identifiers => '1' do
+ assert !Project.new.identifier.blank?
+ assert Project.new(:identifier => '').identifier.blank?
+ end
+
+ with_settings :sequential_project_identifiers => '0' do
+ assert Project.new.identifier.blank?
+ assert !Project.new(:identifier => 'test').blank?
+ end
+
+ with_settings :default_projects_modules => ['issue_tracking', 'repository'] do
+ assert_equal ['issue_tracking', 'repository'], Project.new.enabled_module_names
+ end
+
+ assert_equal Tracker.all, Project.new.trackers
+ assert_equal Tracker.find(1, 3), Project.new(:tracker_ids => [1, 3]).trackers
+ end
+
def test_update
assert_equal "eCookbook", @ecookbook.name
@ecookbook.name = "eCook"
@ecookbook.reload
assert !@ecookbook.active?
+ assert @ecookbook.archived?
assert !user.projects.include?(@ecookbook)
# Subproject are also archived
assert !@ecookbook.children.empty?
assert @ecookbook.unarchive
@ecookbook.reload
assert @ecookbook.active?
+ assert !@ecookbook.archived?
assert user.projects.include?(@ecookbook)
# Subproject can now be unarchived
@ecookbook_sub1.reload
# make sure that the project non longer exists
assert_raise(ActiveRecord::RecordNotFound) { Project.find(@ecookbook.id) }
# make sure related data was removed
- assert Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).empty?
- assert Board.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).empty?
+ assert_nil Member.first(:conditions => {:project_id => @ecookbook.id})
+ assert_nil Board.first(:conditions => {:project_id => @ecookbook.id})
+ assert_nil Issue.first(:conditions => {:project_id => @ecookbook.id})
+ end
+
+ def test_destroying_root_projects_should_clear_data
+ Project.roots.each do |root|
+ root.destroy
+ end
+
+ assert_equal 0, Project.count, "Projects were not deleted: #{Project.all.inspect}"
+ assert_equal 0, Member.count, "Members were not deleted: #{Member.all.inspect}"
+ assert_equal 0, MemberRole.count
+ assert_equal 0, Issue.count
+ assert_equal 0, Journal.count
+ assert_equal 0, JournalDetail.count
+ assert_equal 0, Attachment.count
+ assert_equal 0, EnabledModule.count
+ assert_equal 0, IssueCategory.count
+ assert_equal 0, IssueRelation.count
+ assert_equal 0, Board.count
+ assert_equal 0, Message.count
+ assert_equal 0, News.count
+ assert_equal 0, Query.count(:conditions => "project_id IS NOT NULL")
+ assert_equal 0, Repository.count
+ assert_equal 0, Changeset.count
+ assert_equal 0, Change.count
+ assert_equal 0, Comment.count
+ assert_equal 0, TimeEntry.count
+ assert_equal 0, Version.count
+ assert_equal 0, Watcher.count
+ assert_equal 0, Wiki.count
+ assert_equal 0, WikiPage.count
+ assert_equal 0, WikiContent.count
+ assert_equal 0, WikiContent::Version.count
+ assert_equal 0, Project.connection.select_all("SELECT * FROM projects_trackers").size
+ assert_equal 0, Project.connection.select_all("SELECT * FROM custom_fields_projects").size
+ assert_equal 0, CustomValue.count(:conditions => {:customized_type => ['Project', 'Issue', 'TimeEntry', 'Version']})
end
def test_move_an_orphan_project_to_a_root_project
user = User.find(9)
assert user.memberships.empty?
User.current = user
- assert Project.new.allowed_parents.empty?
+ assert Project.new.allowed_parents.compact.empty?
+ end
+
+ def test_allowed_parents_with_add_subprojects_permission
+ Role.find(1).remove_permission!(:add_project)
+ Role.find(1).add_permission!(:add_subprojects)
+ User.current = User.find(2)
+ # new project
+ assert !Project.new.allowed_parents.include?(nil)
+ assert Project.new.allowed_parents.include?(Project.find(1))
+ # existing root project
+ assert Project.find(1).allowed_parents.include?(nil)
+ # existing child
+ assert Project.find(3).allowed_parents.include?(Project.find(1))
+ assert !Project.find(3).allowed_parents.include?(nil)
+ end
+
+ def test_allowed_parents_with_add_project_permission
+ Role.find(1).add_permission!(:add_project)
+ Role.find(1).remove_permission!(:add_subprojects)
+ User.current = User.find(2)
+ # new project
+ assert Project.new.allowed_parents.include?(nil)
+ assert !Project.new.allowed_parents.include?(Project.find(1))
+ # existing root project
+ assert Project.find(1).allowed_parents.include?(nil)
+ # existing child
+ assert Project.find(3).allowed_parents.include?(Project.find(1))
+ assert Project.find(3).allowed_parents.include?(nil)
+ end
+
+ def test_allowed_parents_with_add_project_and_subprojects_permission
+ Role.find(1).add_permission!(:add_project)
+ Role.find(1).add_permission!(:add_subprojects)
+ User.current = User.find(2)
+ # new project
+ assert Project.new.allowed_parents.include?(nil)
+ assert Project.new.allowed_parents.include?(Project.find(1))
+ # existing root project
+ assert Project.find(1).allowed_parents.include?(nil)
+ # existing child
+ assert Project.find(3).allowed_parents.include?(Project.find(1))
+ assert Project.find(3).allowed_parents.include?(nil)
end
def test_users_by_role
assert_equal [1,2], parent.rolled_up_trackers.collect(&:id)
end
+
+ context "#rolled_up_versions" do
+ setup do
+ @project = Project.generate!
+ @parent_version_1 = Version.generate!(:project => @project)
+ @parent_version_2 = Version.generate!(:project => @project)
+ end
+
+ should "include the versions for the current project" do
+ assert_same_elements [@parent_version_1, @parent_version_2], @project.rolled_up_versions
+ end
+
+ should "include versions for a subproject" do
+ @subproject = Project.generate!
+ @subproject.set_parent!(@project)
+ @subproject_version = Version.generate!(:project => @subproject)
+
+ assert_same_elements [
+ @parent_version_1,
+ @parent_version_2,
+ @subproject_version
+ ], @project.rolled_up_versions
+ end
+
+ should "include versions for a sub-subproject" do
+ @subproject = Project.generate!
+ @subproject.set_parent!(@project)
+ @sub_subproject = Project.generate!
+ @sub_subproject.set_parent!(@subproject)
+ @sub_subproject_version = Version.generate!(:project => @sub_subproject)
+
+ @project.reload
+
+ assert_same_elements [
+ @parent_version_1,
+ @parent_version_2,
+ @sub_subproject_version
+ ], @project.rolled_up_versions
+ end
+
+
+ should "only check active projects" do
+ @subproject = Project.generate!
+ @subproject.set_parent!(@project)
+ @subproject_version = Version.generate!(:project => @subproject)
+ assert @subproject.archive
+
+ @project.reload
+
+ assert !@subproject.active?
+ assert_same_elements [@parent_version_1, @parent_version_2], @project.rolled_up_versions
+ end
+ end
def test_shared_versions_none_sharing
p = Project.find(5)
assert_nil Project.next_identifier
end
+ def test_enabled_module_names
+ with_settings :default_projects_modules => ['issue_tracking', 'repository'] do
+ project = Project.new
+
+ project.enabled_module_names = %w(issue_tracking news)
+ assert_equal %w(issue_tracking news), project.enabled_module_names.sort
+ end
+ end
+
+ context "enabled_modules" do
+ setup do
+ @project = Project.find(1)
+ end
+
+ should "define module by names and preserve ids" do
+ # Remove one module
+ modules = @project.enabled_modules.slice(0..-2)
+ assert modules.any?
+ assert_difference 'EnabledModule.count', -1 do
+ @project.enabled_module_names = modules.collect(&:name)
+ end
+ @project.reload
+ # Ids should be preserved
+ assert_equal @project.enabled_module_ids.sort, modules.collect(&:id).sort
+ end
+
+ should "enable a module" do
+ @project.enabled_module_names = []
+ @project.reload
+ assert_equal [], @project.enabled_module_names
+ #with string
+ @project.enable_module!("issue_tracking")
+ assert_equal ["issue_tracking"], @project.enabled_module_names
+ #with symbol
+ @project.enable_module!(:gantt)
+ assert_equal ["issue_tracking", "gantt"], @project.enabled_module_names
+ #don't add a module twice
+ @project.enable_module!("issue_tracking")
+ assert_equal ["issue_tracking", "gantt"], @project.enabled_module_names
+ end
+
+ should "disable a module" do
+ #with string
+ assert @project.enabled_module_names.include?("issue_tracking")
+ @project.disable_module!("issue_tracking")
+ assert ! @project.reload.enabled_module_names.include?("issue_tracking")
+ #with symbol
+ assert @project.enabled_module_names.include?("gantt")
+ @project.disable_module!(:gantt)
+ assert ! @project.reload.enabled_module_names.include?("gantt")
+ #with EnabledModule object
+ first_module = @project.enabled_modules.first
+ @project.disable_module!(first_module)
+ assert ! @project.reload.enabled_module_names.include?(first_module.name)
+ end
+ end
def test_enabled_module_names_should_not_recreate_enabled_modules
project = Project.find(1)
assert project.activities(true).include?(overridden_activity), "Inactive Project specific Activity not found"
end
+
+ test 'activities should not include active System activities if the project has an override that is inactive' do
+ project = Project.find(1)
+ system_activity = TimeEntryActivity.find_by_name('Design')
+ assert system_activity.active?
+ overridden_activity = TimeEntryActivity.generate!(:project => project, :parent => system_activity, :active => false)
+ assert overridden_activity.save!
+
+ assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity not found"
+ assert !project.activities.include?(system_activity), "System activity found when the project has an inactive override"
+ end
def test_close_completed_versions
Version.update_all("status = 'open'")
end
should "copy issues" do
- @source_project.issues << Issue.generate!(:status_id => 5,
+ @source_project.issues << Issue.generate!(:status => IssueStatus.find_by_name('Closed'),
:subject => "copy issue status",
:tracker_id => 1,
:assigned_to_id => 2,
assert_not_equal assigned_version.id, copied_issue.fixed_version.id # Different record
end
- should "copy members" do
+ should "copy issue relations" do
+ Setting.cross_project_issue_relations = '1'
+
+ second_issue = Issue.generate!(:status_id => 5,
+ :subject => "copy issue relation",
+ :tracker_id => 1,
+ :assigned_to_id => 2,
+ :project_id => @source_project.id)
+ source_relation = IssueRelation.generate!(:issue_from => Issue.find(4),
+ :issue_to => second_issue,
+ :relation_type => "relates")
+ source_relation_cross_project = IssueRelation.generate!(:issue_from => Issue.find(1),
+ :issue_to => second_issue,
+ :relation_type => "duplicates")
+
+ assert @project.copy(@source_project)
+ assert_equal @source_project.issues.count, @project.issues.count
+ copied_issue = @project.issues.find_by_subject("Issue on project 2") # Was #4
+ copied_second_issue = @project.issues.find_by_subject("copy issue relation")
+
+ # First issue with a relation on project
+ assert_equal 1, copied_issue.relations.size, "Relation not copied"
+ copied_relation = copied_issue.relations.first
+ assert_equal "relates", copied_relation.relation_type
+ assert_equal copied_second_issue.id, copied_relation.issue_to_id
+ assert_not_equal source_relation.id, copied_relation.id
+
+ # Second issue with a cross project relation
+ assert_equal 2, copied_second_issue.relations.size, "Relation not copied"
+ copied_relation = copied_second_issue.relations.select {|r| r.relation_type == 'duplicates'}.first
+ assert_equal "duplicates", copied_relation.relation_type
+ assert_equal 1, copied_relation.issue_from_id, "Cross project relation not kept"
+ assert_not_equal source_relation_cross_project.id, copied_relation.id
+ end
+
+ should "copy memberships" do
assert @project.valid?
assert @project.members.empty?
assert @project.copy(@source_project)
- assert_equal @source_project.members.size, @project.members.size
- @project.members.each do |member|
- assert member
- assert_equal @project, member.project
+ assert_equal @source_project.memberships.size, @project.memberships.size
+ @project.memberships.each do |membership|
+ assert membership
+ assert_equal @project, membership.project
end
end
+
+ should "copy memberships with groups and additional roles" do
+ group = Group.create!(:lastname => "Copy group")
+ user = User.find(7)
+ group.users << user
+ # group role
+ Member.create!(:project_id => @source_project.id, :principal => group, :role_ids => [2])
+ member = Member.find_by_user_id_and_project_id(user.id, @source_project.id)
+ # additional role
+ member.role_ids = [1]
+
+ assert @project.copy(@source_project)
+ member = Member.find_by_user_id_and_project_id(user.id, @project.id)
+ assert_not_nil member
+ assert_equal [1, 2], member.role_ids.sort
+ end
should "copy project specific queries" do
assert @project.valid?
assert_equal "Start page", @project.wiki.start_page
end
- should "copy wiki pages and content" do
- assert @project.copy(@source_project)
-
+ should "copy wiki pages and content with hierarchy" do
+ assert_difference 'WikiPage.count', @source_project.wiki.pages.size do
+ assert @project.copy(@source_project)
+ end
+
assert @project.wiki
- assert_equal 1, @project.wiki.pages.length
+ assert_equal @source_project.wiki.pages.size, @project.wiki.pages.size
@project.wiki.pages.each do |wiki_page|
assert wiki_page.content
assert !@source_project.wiki.pages.include?(wiki_page)
end
+
+ parent = @project.wiki.find_page('Parent_page')
+ child1 = @project.wiki.find_page('Child_page_1')
+ child2 = @project.wiki.find_page('Child_page_2')
+ assert_equal parent, child1.parent
+ assert_equal parent, child2.parent
end
- should "copy custom fields"
-
should "copy issue categories" do
assert @project.copy(@source_project)
assert @project.issues.empty?
end
- should "copy issue relations"
- should "link issue relations if cross project issue relations are valid"
+ end
+
+ context "#start_date" do
+ setup do
+ ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
+ @project = Project.generate!(:identifier => 'test0')
+ @project.trackers << Tracker.generate!
+ end
+
+ should "be nil if there are no issues on the project" do
+ assert_nil @project.start_date
+ end
+
+ should "be tested when issues have no start date"
+
+ should "be the earliest start date of it's issues" do
+ early = 7.days.ago.to_date
+ Issue.generate_for_project!(@project, :start_date => Date.today)
+ Issue.generate_for_project!(@project, :start_date => early)
+
+ assert_equal early, @project.start_date
+ end
+
+ end
+
+ context "#due_date" do
+ setup do
+ ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
+ @project = Project.generate!(:identifier => 'test0')
+ @project.trackers << Tracker.generate!
+ end
+
+ should "be nil if there are no issues on the project" do
+ assert_nil @project.due_date
+ end
+
+ should "be tested when issues have no due date"
+
+ should "be the latest due date of it's issues" do
+ future = 7.days.from_now.to_date
+ Issue.generate_for_project!(@project, :due_date => future)
+ Issue.generate_for_project!(@project, :due_date => Date.today)
+
+ assert_equal future, @project.due_date
+ end
+
+ should "be the latest due date of it's versions" do
+ future = 7.days.from_now.to_date
+ @project.versions << Version.generate!(:effective_date => future)
+ @project.versions << Version.generate!(:effective_date => Date.today)
+
+
+ assert_equal future, @project.due_date
+
+ end
+
+ should "pick the latest date from it's issues and versions" do
+ future = 7.days.from_now.to_date
+ far_future = 14.days.from_now.to_date
+ Issue.generate_for_project!(@project, :due_date => far_future)
+ @project.versions << Version.generate!(:effective_date => future)
+
+ assert_equal far_future, @project.due_date
+ end
+
+ end
+
+ context "Project#completed_percent" do
+ setup do
+ ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
+ @project = Project.generate!(:identifier => 'test0')
+ @project.trackers << Tracker.generate!
+ end
+
+ context "no versions" do
+ should "be 100" do
+ assert_equal 100, @project.completed_percent
+ end
+ end
+ context "with versions" do
+ should "return 0 if the versions have no issues" do
+ Version.generate!(:project => @project)
+ Version.generate!(:project => @project)
+
+ assert_equal 0, @project.completed_percent
+ end
+
+ should "return 100 if the version has only closed issues" do
+ v1 = Version.generate!(:project => @project)
+ Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v1)
+ v2 = Version.generate!(:project => @project)
+ Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v2)
+
+ assert_equal 100, @project.completed_percent
+ end
+
+ should "return the averaged completed percent of the versions (not weighted)" do
+ v1 = Version.generate!(:project => @project)
+ Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v1)
+ v2 = Version.generate!(:project => @project)
+ Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v2)
+
+ assert_equal 50, @project.completed_percent
+ end
+
+ end
end
+ context "#notified_users" do
+ setup do
+ @project = Project.generate!
+ @role = Role.generate!
+
+ @user_with_membership_notification = User.generate!(:mail_notification => 'selected')
+ Member.generate!(:project => @project, :roles => [@role], :principal => @user_with_membership_notification, :mail_notification => true)
+
+ @all_events_user = User.generate!(:mail_notification => 'all')
+ Member.generate!(:project => @project, :roles => [@role], :principal => @all_events_user)
+
+ @no_events_user = User.generate!(:mail_notification => 'none')
+ Member.generate!(:project => @project, :roles => [@role], :principal => @no_events_user)
+
+ @only_my_events_user = User.generate!(:mail_notification => 'only_my_events')
+ Member.generate!(:project => @project, :roles => [@role], :principal => @only_my_events_user)
+
+ @only_assigned_user = User.generate!(:mail_notification => 'only_assigned')
+ Member.generate!(:project => @project, :roles => [@role], :principal => @only_assigned_user)
+
+ @only_owned_user = User.generate!(:mail_notification => 'only_owner')
+ Member.generate!(:project => @project, :roles => [@role], :principal => @only_owned_user)
+ end
+
+ should "include members with a mail notification" do
+ assert @project.notified_users.include?(@user_with_membership_notification)
+ end
+
+ should "include users with the 'all' notification option" do
+ assert @project.notified_users.include?(@all_events_user)
+ end
+
+ should "not include users with the 'none' notification option" do
+ assert !@project.notified_users.include?(@no_events_user)
+ end
+
+ should "not include users with the 'only_my_events' notification option" do
+ assert !@project.notified_users.include?(@only_my_events_user)
+ end
+
+ should "not include users with the 'only_assigned' notification option" do
+ assert !@project.notified_users.include?(@only_assigned_user)
+ end
+
+ should "not include users with the 'only_owner' notification option" do
+ assert !@project.notified_users.include?(@only_owned_user)
+ end
+ end
+
end