Mercurial > hg > soundsoftware-site
diff test/unit/issue_test.rb @ 1115:433d4f72a19b redmine-2.2
Update to Redmine SVN revision 11137 on 2.2-stable branch
author | Chris Cannam |
---|---|
date | Mon, 07 Jan 2013 12:01:42 +0000 |
parents | cbb26bc654de |
children | 622f24f53b42 261b3d9a4903 |
line wrap: on
line diff
--- a/test/unit/issue_test.rb Wed Jun 27 14:54:18 2012 +0100 +++ b/test/unit/issue_test.rb Mon Jan 07 12:01:42 2013 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang +# Copyright (C) 2006-2012 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 @@ -19,15 +19,22 @@ class IssueTest < ActiveSupport::TestCase fixtures :projects, :users, :members, :member_roles, :roles, + :groups_users, :trackers, :projects_trackers, :enabled_modules, :versions, :issue_statuses, :issue_categories, :issue_relations, :workflows, :enumerations, - :issues, + :issues, :journals, :journal_details, :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values, :time_entries + include Redmine::I18n + + def teardown + User.current = nil + end + def test_create issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, @@ -44,9 +51,36 @@ :subject => 'test_create') assert issue.save assert issue.description.nil? + assert_nil issue.estimated_hours + end + + def test_start_date_format_should_be_validated + set_language_if_valid 'en' + ['2012', 'ABC', '2012-15-20'].each do |invalid_date| + issue = Issue.new(:start_date => invalid_date) + assert !issue.valid? + assert_include 'Start date is not a valid date', issue.errors.full_messages, "No error found for invalid date #{invalid_date}" + end + end + + def test_due_date_format_should_be_validated + set_language_if_valid 'en' + ['2012', 'ABC', '2012-15-20'].each do |invalid_date| + issue = Issue.new(:due_date => invalid_date) + assert !issue.valid? + assert_include 'Due date is not a valid date', issue.errors.full_messages, "No error found for invalid date #{invalid_date}" + end + end + + def test_due_date_lesser_than_start_date_should_not_validate + set_language_if_valid 'en' + issue = Issue.new(:start_date => '2012-10-06', :due_date => '2012-10-02') + assert !issue.valid? + assert_include 'Due date must be greater than start date', issue.errors.full_messages end def test_create_with_required_custom_field + set_language_if_valid 'en' field = IssueCustomField.find_by_name('Database') field.update_attribute(:is_required, true) @@ -56,15 +90,15 @@ assert issue.available_custom_fields.include?(field) # No value for the custom field assert !issue.save - assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values) + assert_equal ["Database can't be blank"], issue.errors.full_messages # Blank value issue.custom_field_values = { field.id => '' } assert !issue.save - assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values) + assert_equal ["Database can't be blank"], issue.errors.full_messages # Invalid value issue.custom_field_values = { field.id => 'SQLServer' } assert !issue.save - assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values) + assert_equal ["Database is not included in the list"], issue.errors.full_messages # Valid value issue.custom_field_values = { field.id => 'PostgreSQL' } assert issue.save @@ -83,6 +117,44 @@ end end + def test_create_with_parent_issue_id + issue = Issue.new(:project_id => 1, :tracker_id => 1, + :author_id => 1, :subject => 'Group assignment', + :parent_issue_id => 1) + assert_save issue + assert_equal 1, issue.parent_issue_id + assert_equal Issue.find(1), issue.parent + end + + def test_create_with_sharp_parent_issue_id + issue = Issue.new(:project_id => 1, :tracker_id => 1, + :author_id => 1, :subject => 'Group assignment', + :parent_issue_id => "#1") + assert_save issue + assert_equal 1, issue.parent_issue_id + assert_equal Issue.find(1), issue.parent + end + + def test_create_with_invalid_parent_issue_id + set_language_if_valid 'en' + issue = Issue.new(:project_id => 1, :tracker_id => 1, + :author_id => 1, :subject => 'Group assignment', + :parent_issue_id => '01ABC') + assert !issue.save + assert_equal '01ABC', issue.parent_issue_id + assert_include 'Parent task is invalid', issue.errors.full_messages + end + + def test_create_with_invalid_sharp_parent_issue_id + set_language_if_valid 'en' + issue = Issue.new(:project_id => 1, :tracker_id => 1, + :author_id => 1, :subject => 'Group assignment', + :parent_issue_id => '#01ABC') + assert !issue.save + assert_equal '#01ABC', issue.parent_issue_id + assert_include 'Parent task is invalid', issue.errors.full_messages + end + def assert_visibility_match(user, issues) assert_equal issues.collect(&:id).sort, Issue.all.select {|issue| issue.visible?(user)}.collect(&:id).sort end @@ -96,18 +168,6 @@ assert_visibility_match User.anonymous, issues end - def test_visible_scope_for_anonymous_with_own_issues_visibility - Role.anonymous.update_attribute :issues_visibility, 'own' - Issue.create!(:project_id => 1, :tracker_id => 1, - :author_id => User.anonymous.id, - :subject => 'Issue by anonymous') - - issues = Issue.visible(User.anonymous).all - assert issues.any? - assert_nil issues.detect {|issue| issue.author != User.anonymous} - assert_visibility_match User.anonymous, issues - end - def test_visible_scope_for_anonymous_without_view_issues_permissions # Anonymous user should not see issues without permission Role.anonymous.remove_permission!(:view_issues) @@ -116,6 +176,20 @@ assert_visibility_match User.anonymous, issues end + def test_anonymous_should_not_see_private_issues_with_issues_visibility_set_to_default + assert Role.anonymous.update_attribute(:issues_visibility, 'default') + issue = Issue.generate!(:author => User.anonymous, :assigned_to => User.anonymous, :is_private => true) + assert_nil Issue.where(:id => issue.id).visible(User.anonymous).first + assert !issue.visible?(User.anonymous) + end + + def test_anonymous_should_not_see_private_issues_with_issues_visibility_set_to_own + assert Role.anonymous.update_attribute(:issues_visibility, 'own') + issue = Issue.generate!(:author => User.anonymous, :assigned_to => User.anonymous, :is_private => true) + assert_nil Issue.where(:id => issue.id).visible(User.anonymous).first + assert !issue.visible?(User.anonymous) + end + def test_visible_scope_for_non_member user = User.find(9) assert user.projects.empty? @@ -216,6 +290,16 @@ assert_equal 0, Issue.find(1).descendants.visible.all.size end + def test_open_scope + issues = Issue.open.all + assert_nil issues.detect(&:closed?) + end + + def test_open_scope_with_arg + issues = Issue.open(false).all + assert_equal issues, issues.select(&:closed?) + end + def test_errors_full_messages_should_include_custom_fields_errors field = IssueCustomField.find_by_name('Database') @@ -279,10 +363,9 @@ end def test_should_not_update_custom_fields_on_changing_tracker_with_different_custom_fields - issue = Issue.new(:project_id => 1) - issue.attributes = {:tracker_id => 1, :author_id => 1, :status_id => 1, :subject => 'Test', :custom_field_values => {'2' => 'Test'}} - issue.save! - + issue = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, + :status_id => 1, :subject => 'Test', + :custom_field_values => {'2' => 'Test'}) assert !Tracker.find(2).custom_field_ids.include?(2) issue = Issue.find(issue.id) @@ -301,14 +384,22 @@ assert issue.custom_field_values.any? end - def test_assigning_attributes_should_assign_tracker_id_first + def test_assigning_attributes_should_assign_project_and_tracker_first + seq = sequence('seq') + issue = Issue.new + issue.expects(:project_id=).in_sequence(seq) + issue.expects(:tracker_id=).in_sequence(seq) + issue.expects(:subject=).in_sequence(seq) + issue.attributes = {:tracker_id => 2, :project_id => 1, :subject => 'Test'} + end + + def test_assigning_tracker_and_custom_fields_should_assign_custom_fields attributes = ActiveSupport::OrderedHash.new attributes['custom_field_values'] = { '1' => 'MySQL' } attributes['tracker_id'] = '1' issue = Issue.new(:project => Project.find(1)) issue.attributes = attributes - assert_not_nil issue.custom_value_for(1) - assert_equal 'MySQL', issue.custom_value_for(1).value + assert_equal 'MySQL', issue.custom_field_value(1) end def test_should_update_issue_with_disabled_tracker @@ -343,32 +434,328 @@ end def test_new_statuses_allowed_to - Workflow.delete_all - - Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2, :author => false, :assignee => false) - Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3, :author => true, :assignee => false) - Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4, :author => false, :assignee => true) - Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5, :author => true, :assignee => true) + WorkflowTransition.delete_all + WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, + :old_status_id => 1, :new_status_id => 2, + :author => false, :assignee => false) + WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, + :old_status_id => 1, :new_status_id => 3, + :author => true, :assignee => false) + WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, + :new_status_id => 4, :author => false, + :assignee => true) + WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, + :old_status_id => 1, :new_status_id => 5, + :author => true, :assignee => true) status = IssueStatus.find(1) role = Role.find(1) tracker = Tracker.find(1) user = User.find(2) - issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1) + issue = Issue.generate!(:tracker => tracker, :status => status, + :project_id => 1, :author_id => 1) assert_equal [1, 2], issue.new_statuses_allowed_to(user).map(&:id) - issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :author => user) + issue = Issue.generate!(:tracker => tracker, :status => status, + :project_id => 1, :author => user) assert_equal [1, 2, 3, 5], issue.new_statuses_allowed_to(user).map(&:id) - issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :assigned_to => user) + issue = Issue.generate!(:tracker => tracker, :status => status, + :project_id => 1, :author_id => 1, + :assigned_to => user) assert_equal [1, 2, 4, 5], issue.new_statuses_allowed_to(user).map(&:id) - issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :author => user, :assigned_to => user) + issue = Issue.generate!(:tracker => tracker, :status => status, + :project_id => 1, :author => user, + :assigned_to => user) assert_equal [1, 2, 3, 4, 5], issue.new_statuses_allowed_to(user).map(&:id) end + def test_new_statuses_allowed_to_should_return_all_transitions_for_admin + admin = User.find(1) + issue = Issue.find(1) + assert !admin.member_of?(issue.project) + expected_statuses = [issue.status] + + WorkflowTransition.find_all_by_old_status_id( + issue.status_id).map(&:new_status).uniq.sort + assert_equal expected_statuses, issue.new_statuses_allowed_to(admin) + end + + def test_new_statuses_allowed_to_should_return_default_and_current_status_when_copying + issue = Issue.find(1).copy + assert_equal [1], issue.new_statuses_allowed_to(User.find(2)).map(&:id) + + issue = Issue.find(2).copy + assert_equal [1, 2], issue.new_statuses_allowed_to(User.find(2)).map(&:id) + end + + def test_safe_attributes_names_should_not_include_disabled_field + tracker = Tracker.new(:core_fields => %w(assigned_to_id fixed_version_id)) + + issue = Issue.new(:tracker => tracker) + assert_include 'tracker_id', issue.safe_attribute_names + assert_include 'status_id', issue.safe_attribute_names + assert_include 'subject', issue.safe_attribute_names + assert_include 'description', issue.safe_attribute_names + assert_include 'custom_field_values', issue.safe_attribute_names + assert_include 'custom_fields', issue.safe_attribute_names + assert_include 'lock_version', issue.safe_attribute_names + + tracker.core_fields.each do |field| + assert_include field, issue.safe_attribute_names + end + + tracker.disabled_core_fields.each do |field| + assert_not_include field, issue.safe_attribute_names + end + end + + def test_safe_attributes_should_ignore_disabled_fields + tracker = Tracker.find(1) + tracker.core_fields = %w(assigned_to_id due_date) + tracker.save! + + issue = Issue.new(:tracker => tracker) + issue.safe_attributes = {'start_date' => '2012-07-14', 'due_date' => '2012-07-14'} + assert_nil issue.start_date + assert_equal Date.parse('2012-07-14'), issue.due_date + end + + def test_safe_attributes_should_accept_target_tracker_enabled_fields + source = Tracker.find(1) + source.core_fields = [] + source.save! + target = Tracker.find(2) + target.core_fields = %w(assigned_to_id due_date) + target.save! + + issue = Issue.new(:tracker => source) + issue.safe_attributes = {'tracker_id' => 2, 'due_date' => '2012-07-14'} + assert_equal target, issue.tracker + assert_equal Date.parse('2012-07-14'), issue.due_date + end + + def test_safe_attributes_should_not_include_readonly_fields + WorkflowPermission.delete_all + WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, + :role_id => 1, :field_name => 'due_date', + :rule => 'readonly') + user = User.find(2) + + issue = Issue.new(:project_id => 1, :tracker_id => 1) + assert_equal %w(due_date), issue.read_only_attribute_names(user) + assert_not_include 'due_date', issue.safe_attribute_names(user) + + issue.send :safe_attributes=, {'start_date' => '2012-07-14', 'due_date' => '2012-07-14'}, user + assert_equal Date.parse('2012-07-14'), issue.start_date + assert_nil issue.due_date + end + + def test_safe_attributes_should_not_include_readonly_custom_fields + cf1 = IssueCustomField.create!(:name => 'Writable field', + :field_format => 'string', + :is_for_all => true, :tracker_ids => [1]) + cf2 = IssueCustomField.create!(:name => 'Readonly field', + :field_format => 'string', + :is_for_all => true, :tracker_ids => [1]) + WorkflowPermission.delete_all + WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, + :role_id => 1, :field_name => cf2.id.to_s, + :rule => 'readonly') + user = User.find(2) + issue = Issue.new(:project_id => 1, :tracker_id => 1) + assert_equal [cf2.id.to_s], issue.read_only_attribute_names(user) + assert_not_include cf2.id.to_s, issue.safe_attribute_names(user) + + issue.send :safe_attributes=, {'custom_field_values' => { + cf1.id.to_s => 'value1', cf2.id.to_s => 'value2' + }}, user + assert_equal 'value1', issue.custom_field_value(cf1) + assert_nil issue.custom_field_value(cf2) + + issue.send :safe_attributes=, {'custom_fields' => [ + {'id' => cf1.id.to_s, 'value' => 'valuea'}, + {'id' => cf2.id.to_s, 'value' => 'valueb'} + ]}, user + assert_equal 'valuea', issue.custom_field_value(cf1) + assert_nil issue.custom_field_value(cf2) + end + + def test_editable_custom_field_values_should_return_non_readonly_custom_values + cf1 = IssueCustomField.create!(:name => 'Writable field', :field_format => 'string', + :is_for_all => true, :tracker_ids => [1, 2]) + cf2 = IssueCustomField.create!(:name => 'Readonly field', :field_format => 'string', + :is_for_all => true, :tracker_ids => [1, 2]) + WorkflowPermission.delete_all + WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, + :field_name => cf2.id.to_s, :rule => 'readonly') + user = User.find(2) + + issue = Issue.new(:project_id => 1, :tracker_id => 1) + values = issue.editable_custom_field_values(user) + assert values.detect {|value| value.custom_field == cf1} + assert_nil values.detect {|value| value.custom_field == cf2} + + issue.tracker_id = 2 + values = issue.editable_custom_field_values(user) + assert values.detect {|value| value.custom_field == cf1} + assert values.detect {|value| value.custom_field == cf2} + end + + def test_safe_attributes_should_accept_target_tracker_writable_fields + WorkflowPermission.delete_all + WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, + :role_id => 1, :field_name => 'due_date', + :rule => 'readonly') + WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, + :role_id => 1, :field_name => 'start_date', + :rule => 'readonly') + user = User.find(2) + + issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1) + + issue.send :safe_attributes=, {'start_date' => '2012-07-12', + 'due_date' => '2012-07-14'}, user + assert_equal Date.parse('2012-07-12'), issue.start_date + assert_nil issue.due_date + + issue.send :safe_attributes=, {'start_date' => '2012-07-15', + 'due_date' => '2012-07-16', + 'tracker_id' => 2}, user + assert_equal Date.parse('2012-07-12'), issue.start_date + assert_equal Date.parse('2012-07-16'), issue.due_date + end + + def test_safe_attributes_should_accept_target_status_writable_fields + WorkflowPermission.delete_all + WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, + :role_id => 1, :field_name => 'due_date', + :rule => 'readonly') + WorkflowPermission.create!(:old_status_id => 2, :tracker_id => 1, + :role_id => 1, :field_name => 'start_date', + :rule => 'readonly') + user = User.find(2) + + issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1) + + issue.send :safe_attributes=, {'start_date' => '2012-07-12', + 'due_date' => '2012-07-14'}, + user + assert_equal Date.parse('2012-07-12'), issue.start_date + assert_nil issue.due_date + + issue.send :safe_attributes=, {'start_date' => '2012-07-15', + 'due_date' => '2012-07-16', + 'status_id' => 2}, + user + assert_equal Date.parse('2012-07-12'), issue.start_date + assert_equal Date.parse('2012-07-16'), issue.due_date + end + + def test_required_attributes_should_be_validated + cf = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', + :is_for_all => true, :tracker_ids => [1, 2]) + + WorkflowPermission.delete_all + WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, + :role_id => 1, :field_name => 'due_date', + :rule => 'required') + WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, + :role_id => 1, :field_name => 'category_id', + :rule => 'required') + WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, + :role_id => 1, :field_name => cf.id.to_s, + :rule => 'required') + + WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, + :role_id => 1, :field_name => 'start_date', + :rule => 'required') + WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, + :role_id => 1, :field_name => cf.id.to_s, + :rule => 'required') + user = User.find(2) + + issue = Issue.new(:project_id => 1, :tracker_id => 1, + :status_id => 1, :subject => 'Required fields', + :author => user) + assert_equal [cf.id.to_s, "category_id", "due_date"], + issue.required_attribute_names(user).sort + assert !issue.save, "Issue was saved" + assert_equal ["Category can't be blank", "Due date can't be blank", "Foo can't be blank"], + issue.errors.full_messages.sort + + issue.tracker_id = 2 + assert_equal [cf.id.to_s, "start_date"], issue.required_attribute_names(user).sort + assert !issue.save, "Issue was saved" + assert_equal ["Foo can't be blank", "Start date can't be blank"], + issue.errors.full_messages.sort + + issue.start_date = Date.today + issue.custom_field_values = {cf.id.to_s => 'bar'} + assert issue.save + end + + def test_required_attribute_names_for_multiple_roles_should_intersect_rules + WorkflowPermission.delete_all + WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, + :role_id => 1, :field_name => 'due_date', + :rule => 'required') + WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, + :role_id => 1, :field_name => 'start_date', + :rule => 'required') + user = User.find(2) + member = Member.find(1) + issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1) + + assert_equal %w(due_date start_date), issue.required_attribute_names(user).sort + + member.role_ids = [1, 2] + member.save! + assert_equal [], issue.required_attribute_names(user.reload) + + WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, + :role_id => 2, :field_name => 'due_date', + :rule => 'required') + assert_equal %w(due_date), issue.required_attribute_names(user) + + member.role_ids = [1, 2, 3] + member.save! + assert_equal [], issue.required_attribute_names(user.reload) + + WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, + :role_id => 2, :field_name => 'due_date', + :rule => 'readonly') + # required + readonly => required + assert_equal %w(due_date), issue.required_attribute_names(user) + end + + def test_read_only_attribute_names_for_multiple_roles_should_intersect_rules + WorkflowPermission.delete_all + WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, + :role_id => 1, :field_name => 'due_date', + :rule => 'readonly') + WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, + :role_id => 1, :field_name => 'start_date', + :rule => 'readonly') + user = User.find(2) + member = Member.find(1) + issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1) + + assert_equal %w(due_date start_date), issue.read_only_attribute_names(user).sort + + member.role_ids = [1, 2] + member.save! + assert_equal [], issue.read_only_attribute_names(user.reload) + + WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, + :role_id => 2, :field_name => 'due_date', + :rule => 'readonly') + assert_equal %w(due_date), issue.read_only_attribute_names(user) + end + def test_copy issue = Issue.new.copy_from(1) + assert issue.copy? assert issue.save issue.reload orig = Issue.find(1) @@ -387,23 +774,104 @@ assert_equal orig.status, issue.status end + def test_copy_should_add_relation_with_copied_issue + copied = Issue.find(1) + issue = Issue.new.copy_from(copied) + assert issue.save + issue.reload + + assert_equal 1, issue.relations.size + relation = issue.relations.first + assert_equal 'copied_to', relation.relation_type + assert_equal copied, relation.issue_from + assert_equal issue, relation.issue_to + end + + def test_copy_should_copy_subtasks + issue = Issue.generate_with_descendants! + + copy = issue.reload.copy + copy.author = User.find(7) + assert_difference 'Issue.count', 1+issue.descendants.count do + assert copy.save + end + copy.reload + assert_equal %w(Child1 Child2), copy.children.map(&:subject).sort + child_copy = copy.children.detect {|c| c.subject == 'Child1'} + assert_equal %w(Child11), child_copy.children.map(&:subject).sort + assert_equal copy.author, child_copy.author + end + + def test_copy_should_copy_subtasks_to_target_project + issue = Issue.generate_with_descendants! + + copy = issue.copy(:project_id => 3) + assert_difference 'Issue.count', 1+issue.descendants.count do + assert copy.save + end + assert_equal [3], copy.reload.descendants.map(&:project_id).uniq + end + + def test_copy_should_not_copy_subtasks_twice_when_saving_twice + issue = Issue.generate_with_descendants! + + copy = issue.reload.copy + assert_difference 'Issue.count', 1+issue.descendants.count do + assert copy.save + assert copy.save + end + end + + def test_should_not_call_after_project_change_on_creation + issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1, + :subject => 'Test', :author_id => 1) + issue.expects(:after_project_change).never + issue.save! + end + + def test_should_not_call_after_project_change_on_update + issue = Issue.find(1) + issue.project = Project.find(1) + issue.subject = 'No project change' + issue.expects(:after_project_change).never + issue.save! + end + + def test_should_call_after_project_change_on_project_change + issue = Issue.find(1) + issue.project = Project.find(2) + issue.expects(:after_project_change).once + issue.save! + end + + def test_adding_journal_should_update_timestamp + issue = Issue.find(1) + updated_on_was = issue.updated_on + + issue.init_journal(User.first, "Adding notes") + assert_difference 'Journal.count' do + assert issue.save + end + issue.reload + + assert_not_equal updated_on_was, issue.updated_on + end + def test_should_close_duplicates # Create 3 issues - issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, - :status_id => 1, :priority => IssuePriority.all.first, - :subject => 'Duplicates test', :description => 'Duplicates test') - assert issue1.save - issue2 = issue1.clone - assert issue2.save - issue3 = issue1.clone - assert issue3.save + issue1 = Issue.generate! + issue2 = Issue.generate! + issue3 = Issue.generate! # 2 is a dupe of 1 - IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES) + IssueRelation.create!(:issue_from => issue2, :issue_to => issue1, + :relation_type => IssueRelation::TYPE_DUPLICATES) # And 3 is a dupe of 2 - IssueRelation.create(:issue_from => issue3, :issue_to => issue2, :relation_type => IssueRelation::TYPE_DUPLICATES) + IssueRelation.create!(:issue_from => issue3, :issue_to => issue2, + :relation_type => IssueRelation::TYPE_DUPLICATES) # And 3 is a dupe of 1 (circular duplicates) - IssueRelation.create(:issue_from => issue3, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES) + IssueRelation.create!(:issue_from => issue3, :issue_to => issue1, + :relation_type => IssueRelation::TYPE_DUPLICATES) assert issue1.reload.duplicates.include?(issue2) @@ -417,16 +885,12 @@ end def test_should_not_close_duplicated_issue - # Create 3 issues - issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, - :status_id => 1, :priority => IssuePriority.all.first, - :subject => 'Duplicates test', :description => 'Duplicates test') - assert issue1.save - issue2 = issue1.clone - assert issue2.save + issue1 = Issue.generate! + issue2 = Issue.generate! # 2 is a dupe of 1 - IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES) + IssueRelation.create(:issue_from => issue2, :issue_to => issue1, + :relation_type => IssueRelation::TYPE_DUPLICATES) # 2 is a dup of 1 but 1 is not a duplicate of 2 assert !issue2.reload.duplicates.include?(issue1) @@ -439,24 +903,32 @@ end def test_assignable_versions - issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue') + issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, + :status_id => 1, :fixed_version_id => 1, + :subject => 'New issue') assert_equal ['open'], issue.assignable_versions.collect(&:status).uniq end def test_should_not_be_able_to_assign_a_new_issue_to_a_closed_version - issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue') + issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, + :status_id => 1, :fixed_version_id => 1, + :subject => 'New issue') assert !issue.save assert_not_nil issue.errors[:fixed_version_id] end def test_should_not_be_able_to_assign_a_new_issue_to_a_locked_version - issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 2, :subject => 'New issue') + issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, + :status_id => 1, :fixed_version_id => 2, + :subject => 'New issue') assert !issue.save assert_not_nil issue.errors[:fixed_version_id] end def test_should_be_able_to_assign_a_new_issue_to_an_open_version - issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 3, :subject => 'New issue') + issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, + :status_id => 1, :fixed_version_id => 3, + :subject => 'New issue') assert issue.save end @@ -488,9 +960,39 @@ assert issue.save end + def test_should_not_be_able_to_keep_unshared_version_when_changing_project + issue = Issue.find(2) + assert_equal 2, issue.fixed_version_id + issue.project_id = 3 + assert_nil issue.fixed_version_id + issue.fixed_version_id = 2 + assert !issue.save + assert_include 'Target version is not included in the list', issue.errors.full_messages + end + + def test_should_keep_shared_version_when_changing_project + Version.find(2).update_attribute :sharing, 'tree' + + issue = Issue.find(2) + assert_equal 2, issue.fixed_version_id + issue.project_id = 3 + assert_equal 2, issue.fixed_version_id + assert issue.save + end + + def test_allowed_target_projects_on_move_should_include_projects_with_issue_tracking_enabled + assert_include Project.find(2), Issue.allowed_target_projects_on_move(User.find(2)) + end + + def test_allowed_target_projects_on_move_should_not_include_projects_with_issue_tracking_disabled + Project.find(2).disable_module! :issue_tracking + assert_not_include Project.find(2), Issue.allowed_target_projects_on_move(User.find(2)) + end + def test_move_to_another_project_with_same_category issue = Issue.find(1) - assert issue.move_to_project(Project.find(2)) + issue.project = Project.find(2) + assert issue.save issue.reload assert_equal 2, issue.project_id # Category changes @@ -501,7 +1003,8 @@ def test_move_to_another_project_without_same_category issue = Issue.find(2) - assert issue.move_to_project(Project.find(2)) + issue.project = Project.find(2) + assert issue.save issue.reload assert_equal 2, issue.project_id # Category cleared @@ -511,7 +1014,8 @@ def test_move_to_another_project_should_clear_fixed_version_when_not_shared issue = Issue.find(1) issue.update_attribute(:fixed_version_id, 1) - assert issue.move_to_project(Project.find(2)) + issue.project = Project.find(2) + assert issue.save issue.reload assert_equal 2, issue.project_id # Cleared fixed_version @@ -521,7 +1025,8 @@ def test_move_to_another_project_should_keep_fixed_version_when_shared_with_the_target_project issue = Issue.find(1) issue.update_attribute(:fixed_version_id, 4) - assert issue.move_to_project(Project.find(5)) + issue.project = Project.find(5) + assert issue.save issue.reload assert_equal 5, issue.project_id # Keep fixed_version @@ -531,7 +1036,8 @@ def test_move_to_another_project_should_clear_fixed_version_when_not_shared_with_the_target_project issue = Issue.find(1) issue.update_attribute(:fixed_version_id, 1) - assert issue.move_to_project(Project.find(5)) + issue.project = Project.find(5) + assert issue.save issue.reload assert_equal 5, issue.project_id # Cleared fixed_version @@ -541,28 +1047,49 @@ def test_move_to_another_project_should_keep_fixed_version_when_shared_systemwide issue = Issue.find(1) issue.update_attribute(:fixed_version_id, 7) - assert issue.move_to_project(Project.find(2)) + issue.project = Project.find(2) + assert issue.save issue.reload assert_equal 2, issue.project_id # Keep fixed_version assert_equal 7, issue.fixed_version_id end + def test_move_to_another_project_should_keep_parent_if_valid + issue = Issue.find(1) + issue.update_attribute(:parent_issue_id, 2) + issue.project = Project.find(3) + assert issue.save + issue.reload + assert_equal 2, issue.parent_id + end + + def test_move_to_another_project_should_clear_parent_if_not_valid + issue = Issue.find(1) + issue.update_attribute(:parent_issue_id, 2) + issue.project = Project.find(2) + assert issue.save + issue.reload + assert_nil issue.parent_id + end + def test_move_to_another_project_with_disabled_tracker issue = Issue.find(1) target = Project.find(2) target.tracker_ids = [3] target.save - assert_equal false, issue.move_to_project(target) + issue.project = target + assert issue.save issue.reload - assert_equal 1, issue.project_id + assert_equal 2, issue.project_id + assert_equal 3, issue.tracker_id end def test_copy_to_the_same_project issue = Issue.find(1) - copy = nil + copy = issue.copy assert_difference 'Issue.count' do - copy = issue.move_to_project(issue.project, nil, :copy => true) + copy.save! end assert_kind_of Issue, copy assert_equal issue.project, copy.project @@ -571,9 +1098,9 @@ def test_copy_to_another_project_and_tracker issue = Issue.find(1) - copy = nil + copy = issue.copy(:project_id => 3, :tracker_id => 2) assert_difference 'Issue.count' do - copy = issue.move_to_project(Project.find(3), Tracker.find(2), :copy => true) + copy.save! end copy.reload assert_kind_of Issue, copy @@ -583,74 +1110,124 @@ assert_nil copy.custom_value_for(2) end - context "#move_to_project" do - context "as a copy" do - setup do - @issue = Issue.find(1) - @copy = nil - end + context "#copy" do + setup do + @issue = Issue.find(1) + end - should "not create a journal" do - @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:assigned_to_id => 3}}) - assert_equal 0, @copy.reload.journals.size - end + should "not create a journal" do + copy = @issue.copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3) + copy.save! + assert_equal 0, copy.reload.journals.size + end - should "allow assigned_to changes" do - @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:assigned_to_id => 3}}) - assert_equal 3, @copy.assigned_to_id - end + should "allow assigned_to changes" do + copy = @issue.copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3) + assert_equal 3, copy.assigned_to_id + end - should "allow status changes" do - @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:status_id => 2}}) - assert_equal 2, @copy.status_id - end + should "allow status changes" do + copy = @issue.copy(:project_id => 3, :tracker_id => 2, :status_id => 2) + assert_equal 2, copy.status_id + end - should "allow start date changes" do - date = Date.today - @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:start_date => date}}) - assert_equal date, @copy.start_date - end + should "allow start date changes" do + date = Date.today + copy = @issue.copy(:project_id => 3, :tracker_id => 2, :start_date => date) + assert_equal date, copy.start_date + end - should "allow due date changes" do - date = Date.today - @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:due_date => date}}) + should "allow due date changes" do + date = Date.today + copy = @issue.copy(:project_id => 3, :tracker_id => 2, :due_date => date) + assert_equal date, copy.due_date + end - assert_equal date, @copy.due_date - end + should "set current user as author" do + User.current = User.find(9) + copy = @issue.copy(:project_id => 3, :tracker_id => 2) + assert_equal User.current, copy.author + end - should "set current user as author" do - User.current = User.find(9) - @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {}}) + should "create a journal with notes" do + date = Date.today + notes = "Notes added when copying" + copy = @issue.copy(:project_id => 3, :tracker_id => 2, :start_date => date) + copy.init_journal(User.current, notes) + copy.save! - assert_equal User.current, @copy.author - end + assert_equal 1, copy.journals.size + journal = copy.journals.first + assert_equal 0, journal.details.size + assert_equal notes, journal.notes + end + end - should "keep journal notes" do - date = Date.today - notes = "Notes added when copying" - User.current = User.find(9) - @issue.init_journal(User.current, notes) - @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:start_date => date}}) + def test_valid_parent_project + issue = Issue.find(1) + issue_in_same_project = Issue.find(2) + issue_in_child_project = Issue.find(5) + issue_in_grandchild_project = Issue.generate!(:project_id => 6, :tracker_id => 1) + issue_in_other_child_project = Issue.find(6) + issue_in_different_tree = Issue.find(4) - assert_equal 1, @copy.journals.size - journal = @copy.journals.first - assert_equal 0, journal.details.size - assert_equal notes, journal.notes - end + with_settings :cross_project_subtasks => '' do + assert_equal true, issue.valid_parent_project?(issue_in_same_project) + assert_equal false, issue.valid_parent_project?(issue_in_child_project) + assert_equal false, issue.valid_parent_project?(issue_in_grandchild_project) + assert_equal false, issue.valid_parent_project?(issue_in_different_tree) end + + with_settings :cross_project_subtasks => 'system' do + assert_equal true, issue.valid_parent_project?(issue_in_same_project) + assert_equal true, issue.valid_parent_project?(issue_in_child_project) + assert_equal true, issue.valid_parent_project?(issue_in_different_tree) + end + + with_settings :cross_project_subtasks => 'tree' do + assert_equal true, issue.valid_parent_project?(issue_in_same_project) + assert_equal true, issue.valid_parent_project?(issue_in_child_project) + assert_equal true, issue.valid_parent_project?(issue_in_grandchild_project) + assert_equal false, issue.valid_parent_project?(issue_in_different_tree) + + assert_equal true, issue_in_child_project.valid_parent_project?(issue_in_same_project) + assert_equal true, issue_in_child_project.valid_parent_project?(issue_in_other_child_project) + end + + with_settings :cross_project_subtasks => 'descendants' do + assert_equal true, issue.valid_parent_project?(issue_in_same_project) + assert_equal false, issue.valid_parent_project?(issue_in_child_project) + assert_equal false, issue.valid_parent_project?(issue_in_grandchild_project) + assert_equal false, issue.valid_parent_project?(issue_in_different_tree) + + assert_equal true, issue_in_child_project.valid_parent_project?(issue) + assert_equal false, issue_in_child_project.valid_parent_project?(issue_in_other_child_project) + end + end + + def test_recipients_should_include_previous_assignee + user = User.find(3) + user.members.update_all ["mail_notification = ?", false] + user.update_attribute :mail_notification, 'only_assigned' + + issue = Issue.find(2) + issue.assigned_to = nil + assert_include user.mail, issue.recipients + issue.save! + assert !issue.recipients.include?(user.mail) end def test_recipients_should_not_include_users_that_cannot_view_the_issue issue = Issue.find(12) assert issue.recipients.include?(issue.author.mail) - # move the issue to a private project - copy = issue.move_to_project(Project.find(5), Tracker.find(2), :copy => true) + # copy the issue to a private project + copy = issue.copy(:project_id => 5, :tracker_id => 2) # author is not a member of project anymore assert !copy.recipients.include?(copy.author.mail) end def test_recipients_should_include_the_assigned_group_members - group_member = User.generate_with_protected! + group_member = User.generate! group = Group.generate! group.users << group_member @@ -673,6 +1250,30 @@ assert_nil TimeEntry.find_by_issue_id(1) end + def test_destroying_a_deleted_issue_should_not_raise_an_error + issue = Issue.find(1) + Issue.find(1).destroy + + assert_nothing_raised do + assert_no_difference 'Issue.count' do + issue.destroy + end + assert issue.destroyed? + end + end + + def test_destroying_a_stale_issue_should_not_raise_an_error + issue = Issue.find(1) + Issue.find(1).update_attribute :subject, "Updated" + + assert_nothing_raised do + assert_difference 'Issue.count', -1 do + issue.destroy + end + assert issue.destroyed? + end + end + def test_blocked blocked_issue = Issue.find(9) blocking_issue = Issue.find(10) @@ -699,15 +1300,129 @@ assert !closed_statuses.empty? end - def test_rescheduling_an_issue_should_reschedule_following_issue - issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => '-', :start_date => Date.today, :due_date => Date.today + 2) - issue2 = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => '-', :start_date => Date.today, :due_date => Date.today + 2) - IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES) - assert_equal issue1.due_date + 1, issue2.reload.start_date + def test_reschedule_an_issue_without_dates + with_settings :non_working_week_days => [] do + issue = Issue.new(:start_date => nil, :due_date => nil) + issue.reschedule_on '2012-10-09'.to_date + assert_equal '2012-10-09'.to_date, issue.start_date + assert_equal '2012-10-09'.to_date, issue.due_date + end - issue1.due_date = Date.today + 5 + with_settings :non_working_week_days => %w(6 7) do + issue = Issue.new(:start_date => nil, :due_date => nil) + issue.reschedule_on '2012-10-09'.to_date + assert_equal '2012-10-09'.to_date, issue.start_date + assert_equal '2012-10-09'.to_date, issue.due_date + + issue = Issue.new(:start_date => nil, :due_date => nil) + issue.reschedule_on '2012-10-13'.to_date + assert_equal '2012-10-15'.to_date, issue.start_date + assert_equal '2012-10-15'.to_date, issue.due_date + end + end + + def test_reschedule_an_issue_with_start_date + with_settings :non_working_week_days => [] do + issue = Issue.new(:start_date => '2012-10-09', :due_date => nil) + issue.reschedule_on '2012-10-13'.to_date + assert_equal '2012-10-13'.to_date, issue.start_date + assert_equal '2012-10-13'.to_date, issue.due_date + end + + with_settings :non_working_week_days => %w(6 7) do + issue = Issue.new(:start_date => '2012-10-09', :due_date => nil) + issue.reschedule_on '2012-10-11'.to_date + assert_equal '2012-10-11'.to_date, issue.start_date + assert_equal '2012-10-11'.to_date, issue.due_date + + issue = Issue.new(:start_date => '2012-10-09', :due_date => nil) + issue.reschedule_on '2012-10-13'.to_date + assert_equal '2012-10-15'.to_date, issue.start_date + assert_equal '2012-10-15'.to_date, issue.due_date + end + end + + def test_reschedule_an_issue_with_start_and_due_dates + with_settings :non_working_week_days => [] do + issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-15') + issue.reschedule_on '2012-10-13'.to_date + assert_equal '2012-10-13'.to_date, issue.start_date + assert_equal '2012-10-19'.to_date, issue.due_date + end + + with_settings :non_working_week_days => %w(6 7) do + issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-19') # 8 working days + issue.reschedule_on '2012-10-11'.to_date + assert_equal '2012-10-11'.to_date, issue.start_date + assert_equal '2012-10-23'.to_date, issue.due_date + + issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-19') + issue.reschedule_on '2012-10-13'.to_date + assert_equal '2012-10-15'.to_date, issue.start_date + assert_equal '2012-10-25'.to_date, issue.due_date + end + end + + def test_rescheduling_an_issue_to_a_later_due_date_should_reschedule_following_issue + issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17') + issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17') + IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, + :relation_type => IssueRelation::TYPE_PRECEDES) + assert_equal Date.parse('2012-10-18'), issue2.reload.start_date + + issue1.due_date = '2012-10-23' issue1.save! - assert_equal issue1.due_date + 1, issue2.reload.start_date + issue2.reload + assert_equal Date.parse('2012-10-24'), issue2.start_date + assert_equal Date.parse('2012-10-26'), issue2.due_date + end + + def test_rescheduling_an_issue_to_an_earlier_due_date_should_reschedule_following_issue + issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17') + issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17') + IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, + :relation_type => IssueRelation::TYPE_PRECEDES) + assert_equal Date.parse('2012-10-18'), issue2.reload.start_date + + issue1.start_date = '2012-09-17' + issue1.due_date = '2012-09-18' + issue1.save! + issue2.reload + assert_equal Date.parse('2012-09-19'), issue2.start_date + assert_equal Date.parse('2012-09-21'), issue2.due_date + end + + def test_rescheduling_reschedule_following_issue_earlier_should_consider_other_preceding_issues + issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17') + issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17') + issue3 = Issue.generate!(:start_date => '2012-10-01', :due_date => '2012-10-02') + IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, + :relation_type => IssueRelation::TYPE_PRECEDES) + IssueRelation.create!(:issue_from => issue3, :issue_to => issue2, + :relation_type => IssueRelation::TYPE_PRECEDES) + assert_equal Date.parse('2012-10-18'), issue2.reload.start_date + + issue1.start_date = '2012-09-17' + issue1.due_date = '2012-09-18' + issue1.save! + issue2.reload + # Issue 2 must start after Issue 3 + assert_equal Date.parse('2012-10-03'), issue2.start_date + assert_equal Date.parse('2012-10-05'), issue2.due_date + end + + def test_rescheduling_a_stale_issue_should_not_raise_an_error + with_settings :non_working_week_days => [] do + stale = Issue.find(1) + issue = Issue.find(1) + issue.subject = "Updated" + issue.save! + date = 10.days.from_now.to_date + assert_nothing_raised do + stale.reschedule_on!(date) + end + assert_equal date, stale.reload.start_date + end end def test_overdue @@ -715,28 +1430,41 @@ assert !Issue.new(:due_date => Date.today).overdue? assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue? assert !Issue.new(:due_date => nil).overdue? - assert !Issue.new(:due_date => 1.day.ago.to_date, :status => IssueStatus.find(:first, :conditions => {:is_closed => true})).overdue? + assert !Issue.new(:due_date => 1.day.ago.to_date, + :status => IssueStatus.find(:first, + :conditions => {:is_closed => true}) + ).overdue? end context "#behind_schedule?" do should "be false if the issue has no start_date" do - assert !Issue.new(:start_date => nil, :due_date => 1.day.from_now.to_date, :done_ratio => 0).behind_schedule? + assert !Issue.new(:start_date => nil, + :due_date => 1.day.from_now.to_date, + :done_ratio => 0).behind_schedule? end should "be false if the issue has no end_date" do - assert !Issue.new(:start_date => 1.day.from_now.to_date, :due_date => nil, :done_ratio => 0).behind_schedule? + assert !Issue.new(:start_date => 1.day.from_now.to_date, + :due_date => nil, + :done_ratio => 0).behind_schedule? end should "be false if the issue has more done than it's calendar time" do - assert !Issue.new(:start_date => 50.days.ago.to_date, :due_date => 50.days.from_now.to_date, :done_ratio => 90).behind_schedule? + assert !Issue.new(:start_date => 50.days.ago.to_date, + :due_date => 50.days.from_now.to_date, + :done_ratio => 90).behind_schedule? end should "be true if the issue hasn't been started at all" do - assert Issue.new(:start_date => 1.day.ago.to_date, :due_date => 1.day.from_now.to_date, :done_ratio => 0).behind_schedule? + assert Issue.new(:start_date => 1.day.ago.to_date, + :due_date => 1.day.from_now.to_date, + :done_ratio => 0).behind_schedule? end should "be true if the issue has used more calendar time than it's done ratio" do - assert Issue.new(:start_date => 100.days.ago.to_date, :due_date => Date.today, :done_ratio => 90).behind_schedule? + assert Issue.new(:start_date => 100.days.ago.to_date, + :due_date => Date.today, + :done_ratio => 90).behind_schedule? end end @@ -746,17 +1474,15 @@ end should "include the issue author" do - project = Project.find(1) non_project_member = User.generate! - issue = Issue.generate_for_project!(project, :author => non_project_member) + issue = Issue.generate!(:author => non_project_member) assert issue.assignable_users.include?(non_project_member) end should "include the current assignee" do - project = Project.find(1) user = User.generate! - issue = Issue.generate_for_project!(project, :assigned_to => user) + issue = Issue.generate!(:assigned_to => user) user.lock! assert Issue.find(issue.id).assignable_users.include?(user) @@ -767,7 +1493,8 @@ assert_equal 2, assignable_user_ids.length assignable_user_ids.each do |user_id| - assert_equal 1, assignable_user_ids.select {|i| i == user_id}.length, "User #{user_id} appears more or less than once" + assert_equal 1, assignable_user_ids.select {|i| i == user_id}.length, + "User #{user_id} appears more or less than once" end end @@ -863,6 +1590,40 @@ end end + def test_journalized_multi_custom_field + field = IssueCustomField.create!(:name => 'filter', :field_format => 'list', + :is_filter => true, :is_for_all => true, + :tracker_ids => [1], + :possible_values => ['value1', 'value2', 'value3'], + :multiple => true) + + issue = Issue.create!(:project_id => 1, :tracker_id => 1, + :subject => 'Test', :author_id => 1) + + assert_difference 'Journal.count' do + assert_difference 'JournalDetail.count' do + issue.init_journal(User.first) + issue.custom_field_values = {field.id => ['value1']} + issue.save! + end + assert_difference 'JournalDetail.count' do + issue.init_journal(User.first) + issue.custom_field_values = {field.id => ['value1', 'value2']} + issue.save! + end + assert_difference 'JournalDetail.count', 2 do + issue.init_journal(User.first) + issue.custom_field_values = {field.id => ['value3', 'value2']} + issue.save! + end + assert_difference 'JournalDetail.count', 2 do + issue.init_journal(User.first) + issue.custom_field_values = {field.id => nil} + issue.save! + end + end + end + def test_description_eol_should_be_normalized i = Issue.new(:description => "CR \r LF \n CRLF \r\n") assert_equal "CR \r\n LF \r\n CRLF \r\n", i.description @@ -915,11 +1676,12 @@ assert IssueRelation.create!(:issue_from => Issue.find(2), :issue_to => Issue.find(3), :relation_type => IssueRelation::TYPE_PRECEDES) - # Validation skipping - assert IssueRelation.new(:issue_from => Issue.find(3), - :issue_to => Issue.find(1), - :relation_type => IssueRelation::TYPE_PRECEDES).save(false) + r = IssueRelation.create!(:issue_from => Issue.find(3), + :issue_to => Issue.find(7), + :relation_type => IssueRelation::TYPE_PRECEDES) + IssueRelation.update_all("issue_to_id = 1", ["id = ?", r.id]) + assert_equal [2, 3], Issue.find(1).all_dependent_issues.collect(&:id).sort end @@ -934,13 +1696,16 @@ assert IssueRelation.create!(:issue_from => Issue.find(3), :issue_to => Issue.find(8), :relation_type => IssueRelation::TYPE_RELATES) - # Validation skipping - assert IssueRelation.new(:issue_from => Issue.find(8), - :issue_to => Issue.find(2), - :relation_type => IssueRelation::TYPE_RELATES).save(false) - assert IssueRelation.new(:issue_from => Issue.find(3), - :issue_to => Issue.find(1), - :relation_type => IssueRelation::TYPE_RELATES).save(false) + + r = IssueRelation.create!(:issue_from => Issue.find(8), + :issue_to => Issue.find(7), + :relation_type => IssueRelation::TYPE_RELATES) + IssueRelation.update_all("issue_to_id = 2", ["id = ?", r.id]) + + r = IssueRelation.create!(:issue_from => Issue.find(3), + :issue_to => Issue.find(7), + :relation_type => IssueRelation::TYPE_RELATES) + IssueRelation.update_all("issue_to_id = 1", ["id = ?", r.id]) assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort end @@ -955,6 +1720,10 @@ @issue_status2.update_attribute(:default_done_ratio, 0) end + teardown do + Setting.issue_done_ratio = 'issue_field' + end + context "with Setting.issue_done_ratio using the issue_field" do setup do Setting.issue_done_ratio = 'issue_field' @@ -1067,26 +1836,9 @@ assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i} end - context ".allowed_target_projects_on_move" do - should "return all active projects for admin users" do - User.current = User.find(1) - assert_equal Project.active.count, Issue.allowed_target_projects_on_move.size - end - - should "return allowed projects for non admin users" do - User.current = User.find(2) - Role.non_member.remove_permission! :move_issues - assert_equal 3, Issue.allowed_target_projects_on_move.size - - Role.non_member.add_permission! :move_issues - assert_equal Project.active.count, Issue.allowed_target_projects_on_move.size - end - end - - def test_recently_updated_with_limit_scopes + def test_recently_updated_scope #should return the last updated issue - assert_equal 1, Issue.recently_updated.with_limit(1).length - assert_equal Issue.find(:first, :order => "updated_on DESC"), Issue.recently_updated.with_limit(1).first + assert_equal Issue.reorder("updated_on DESC").first, Issue.recently_updated.limit(1).first end def test_on_active_projects_scope @@ -1094,7 +1846,7 @@ before = Issue.on_active_project.length # test inclusion to results - issue = Issue.generate_for_project!(Project.find(1), :tracker => Project.find(2).trackers.first) + issue = Issue.generate!(:tracker => Project.find(2).trackers.first) assert_equal before + 1, Issue.on_active_project.length # Move to an archived project @@ -1106,9 +1858,9 @@ context "Issue#recipients" do setup do @project = Project.find(1) - @author = User.generate_with_protected! - @assignee = User.generate_with_protected! - @issue = Issue.generate_for_project!(@project, :assigned_to => @assignee, :author => @author) + @author = User.generate! + @assignee = User.generate! + @issue = Issue.generate!(:project => @project, :assigned_to => @assignee, :author => @author) end should "include project recipients" do @@ -1145,6 +1897,43 @@ assert !@issue.recipients.include?(@issue.assigned_to.mail) end + end + def test_last_journal_id_with_journals_should_return_the_journal_id + assert_equal 2, Issue.find(1).last_journal_id + end + + def test_last_journal_id_without_journals_should_return_nil + assert_nil Issue.find(3).last_journal_id + end + + def test_journals_after_should_return_journals_with_greater_id + assert_equal [Journal.find(2)], Issue.find(1).journals_after('1') + assert_equal [], Issue.find(1).journals_after('2') + end + + def test_journals_after_with_blank_arg_should_return_all_journals + assert_equal [Journal.find(1), Journal.find(2)], Issue.find(1).journals_after('') + end + + def test_css_classes_should_include_priority + issue = Issue.new(:priority => IssuePriority.find(8)) + classes = issue.css_classes.split(' ') + assert_include 'priority-8', classes + assert_include 'priority-highest', classes + end + + def test_save_attachments_with_hash_should_save_attachments_in_keys_order + set_tmp_attachments_directory + issue = Issue.generate! + issue.save_attachments({ + 'p0' => {'file' => mock_file_with_options(:original_filename => 'upload')}, + '3' => {'file' => mock_file_with_options(:original_filename => 'bar')}, + '1' => {'file' => mock_file_with_options(:original_filename => 'foo')} + }) + issue.attach_saved_attachments + + assert_equal 3, issue.reload.attachments.count + assert_equal %w(upload foo bar), issue.attachments.map(&:filename) end end