mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
2327 lines
84 KiB
Ruby
2327 lines
84 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
#-- copyright
|
|
# OpenProject is an open source project management software.
|
|
# Copyright (C) the OpenProject GmbH
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License version 3.
|
|
#
|
|
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
|
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
|
# Copyright (C) 2010-2013 the ChiliProject Team
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy 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.
|
|
#
|
|
# See COPYRIGHT and LICENSE files for more details.
|
|
#++
|
|
|
|
require "spec_helper"
|
|
|
|
RSpec.describe WorkPackages::SetAttributesService,
|
|
type: :model do
|
|
shared_let(:status_0_pct_complete) { create(:status, default_done_ratio: 0, name: "0% complete") }
|
|
shared_let(:status_50_pct_complete) { create(:status, default_done_ratio: 50, name: "50% complete") }
|
|
shared_let(:status_70_pct_complete) { create(:status, default_done_ratio: 70, name: "70% complete") }
|
|
shared_let(:status_99_pct_closed) { create(:closed_status, default_done_ratio: 99, name: "Closed 99% complete") }
|
|
|
|
let(:today) { Time.zone.today }
|
|
let(:user) { build_stubbed(:user) }
|
|
let(:project) do
|
|
p = build_stubbed(:project)
|
|
allow(p).to receive(:shared_versions).and_return([])
|
|
|
|
p
|
|
end
|
|
|
|
let(:work_package) do
|
|
wp = build_stubbed(:work_package, project:, subject: "work_package", status: status_0_pct_complete)
|
|
wp.type = initial_type
|
|
wp.clear_changes_information
|
|
|
|
wp
|
|
end
|
|
let(:new_work_package) { WorkPackage.new }
|
|
let(:initial_type) { build_stubbed(:type) }
|
|
let(:milestone_type) { build_stubbed(:type_milestone) }
|
|
let(:statuses) { [] }
|
|
let(:contract_class) { WorkPackages::UpdateContract }
|
|
let(:mock_contract) do
|
|
class_double(contract_class,
|
|
new: mock_contract_instance)
|
|
end
|
|
let(:mock_contract_instance) do
|
|
instance_double(contract_class,
|
|
assignable_statuses: statuses,
|
|
errors: contract_errors,
|
|
validate: contract_valid)
|
|
end
|
|
let(:contract_valid) { true }
|
|
let(:contract_errors) do
|
|
instance_double(ActiveModel::Errors)
|
|
end
|
|
let(:instance) do
|
|
described_class.new(user:,
|
|
model: work_package,
|
|
contract_class: mock_contract)
|
|
end
|
|
|
|
shared_examples_for "service call" do |description: nil|
|
|
subject(:service_result) { instance.call(call_attributes) }
|
|
|
|
before { allow(work_package).to receive(:save) }
|
|
|
|
it description || "sets the value" do
|
|
all_expected_attributes = {}
|
|
all_expected_attributes.merge!(expected_attributes) if defined?(expected_attributes)
|
|
|
|
if defined?(expected_kept_attributes)
|
|
kept = work_package.attributes.slice(*expected_kept_attributes)
|
|
|
|
if kept.size != expected_kept_attributes.size
|
|
raise ArgumentError, "expected_kept_attributes contains attributes that are not present in the work_package: " \
|
|
"#{expected_kept_attributes - kept.keys} not present in #{work_package.attributes}"
|
|
end
|
|
|
|
all_expected_attributes.merge!(kept)
|
|
end
|
|
|
|
next if all_expected_attributes.blank?
|
|
|
|
subject
|
|
|
|
aggregate_failures do
|
|
expect(subject).to be_success
|
|
expect(work_package).to have_attributes(all_expected_attributes)
|
|
# work package is not saved and no errors are created by the service
|
|
# (that's contract's responsibility and it is mocked in this test)
|
|
expect(work_package).not_to have_received(:save)
|
|
expect(subject.errors).to be_empty
|
|
end
|
|
end
|
|
|
|
context "when the contract does not validate" do
|
|
let(:contract_valid) { false }
|
|
|
|
it "is unsuccessful, does not persist the changes and exposes the contract's errors", :aggregate_failures do
|
|
expect(subject).not_to be_success
|
|
expect(work_package).not_to have_received(:save)
|
|
expect(subject.errors).to eql mock_contract_instance.errors
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when updating subject before calling the service" do
|
|
let(:call_attributes) { {} }
|
|
let(:expected_attributes) { { subject: "blubs blubs" } }
|
|
|
|
before do
|
|
work_package.attributes = expected_attributes
|
|
end
|
|
|
|
it_behaves_like "service call"
|
|
end
|
|
|
|
context "when updating subject via attributes" do
|
|
let(:call_attributes) { expected_attributes }
|
|
let(:expected_attributes) { { subject: "blubs blubs" } }
|
|
|
|
it_behaves_like "service call"
|
|
end
|
|
|
|
# Scenarios specified in https://community.openproject.org/wp/40749
|
|
# Just checking that everything is correctly wired up. All other scenarios tested in:
|
|
# - spec/services/work_packages/set_attributes_service/derive_progress_values_status_based_spec.rb
|
|
# - spec/services/work_packages/set_attributes_service/derive_progress_values_work_based_spec.rb
|
|
describe "deriving progress values attributes" do
|
|
context "in status-based mode",
|
|
with_settings: { work_package_done_ratio: "status" } do
|
|
context "given a work package with work, remaining work, and status with % complete being set" do
|
|
before do
|
|
work_package.status = status_50_pct_complete
|
|
work_package.done_ratio = work_package.status.default_done_ratio
|
|
work_package.estimated_hours = 10.0
|
|
work_package.remaining_hours = 5.0
|
|
work_package.clear_changes_information
|
|
end
|
|
|
|
context "when work is changed" do
|
|
let(:call_attributes) { { estimated_hours: 5.0 } }
|
|
let(:expected_attributes) { { remaining_hours: 2.5 } }
|
|
|
|
it_behaves_like "service call", description: "recomputes remaining work accordingly"
|
|
end
|
|
|
|
context "when another status is set" do
|
|
let(:call_attributes) { { status: status_70_pct_complete } }
|
|
let(:expected_attributes) { { remaining_hours: 3.0, done_ratio: 70 } }
|
|
|
|
it_behaves_like "service call",
|
|
description: "sets the % complete value to the status default % complete value " \
|
|
"and recomputes remaining work accordingly"
|
|
end
|
|
end
|
|
|
|
context "given a work package with work and remaining work being empty, and a status with 0% complete" do
|
|
before do
|
|
work_package.status = status_0_pct_complete
|
|
work_package.done_ratio = work_package.status.default_done_ratio
|
|
work_package.estimated_hours = nil
|
|
work_package.remaining_hours = nil
|
|
work_package.clear_changes_information
|
|
end
|
|
|
|
context "when another status with another % complete value is set" do
|
|
let(:call_attributes) { { status: status_70_pct_complete } }
|
|
let(:expected_attributes) { { remaining_hours: nil } }
|
|
|
|
it_behaves_like "service call",
|
|
description: "remaining work remains empty"
|
|
end
|
|
|
|
context "when work is set" do
|
|
let(:call_attributes) { { estimated_hours: 10.0 } }
|
|
let(:expected_attributes) { { remaining_hours: 10.0 } }
|
|
|
|
it_behaves_like "service call",
|
|
description: "remaining work is derived from work and % complete value of the status"
|
|
end
|
|
end
|
|
end
|
|
|
|
context "in work-based mode",
|
|
with_settings: { work_package_done_ratio: "field" } do
|
|
context "given a work package with work, remaining work, and % complete being set" do
|
|
before do
|
|
work_package.estimated_hours = 10.0
|
|
work_package.remaining_hours = 3.0
|
|
work_package.done_ratio = 70
|
|
work_package.clear_changes_information
|
|
end
|
|
|
|
context "when remaining work is cleared" do
|
|
let(:call_attributes) { { remaining_hours: nil } }
|
|
let(:expected_attributes) { { estimated_hours: nil, done_ratio: 70 } }
|
|
|
|
it_behaves_like "service call", description: "keeps % complete, and clears work"
|
|
end
|
|
|
|
context "when work is increased" do
|
|
# work changed by +10h
|
|
let(:call_attributes) { { estimated_hours: 10.0 + 10.0 } }
|
|
let(:expected_attributes) do
|
|
{ remaining_hours: 3.0 + 10.0, done_ratio: 35 }
|
|
end
|
|
|
|
it_behaves_like "service call",
|
|
description: "remaining work is increased by the same amount, and % complete is derived"
|
|
end
|
|
|
|
context "when work and remaining work are both changed to values with more than 2 decimals" do
|
|
let(:call_attributes) { { estimated_hours: 10.123456, remaining_hours: 5.6789 } }
|
|
let(:expected_attributes) { { estimated_hours: 10.12, remaining_hours: 5.68, done_ratio: 44 } }
|
|
|
|
it_behaves_like "service call", description: "rounds work and remaining work to 2 decimals " \
|
|
"and updates % complete accordingly"
|
|
end
|
|
|
|
context "when remaining work is changed to a value greater than work" do
|
|
let(:call_attributes) { { remaining_hours: 200.0 } }
|
|
let(:expected_kept_attributes) { %w[done_ratio] }
|
|
|
|
it_behaves_like "service call", description: "is an error state (to be detected by contract), and % Complete is kept"
|
|
end
|
|
|
|
context "when both work and remaining work are changed" do
|
|
let(:call_attributes) { { estimated_hours: 20, remaining_hours: 2 } }
|
|
let(:expected_attributes) { call_attributes.merge(done_ratio: 90) }
|
|
|
|
it_behaves_like "service call", description: "updates % complete accordingly"
|
|
end
|
|
end
|
|
|
|
context "given a work package with work and remaining work being empty, and % complete being set" do
|
|
before do
|
|
work_package.estimated_hours = nil
|
|
work_package.remaining_hours = nil
|
|
work_package.done_ratio = 60
|
|
work_package.clear_changes_information
|
|
end
|
|
|
|
context "when work is set" do
|
|
let(:call_attributes) { { estimated_hours: 10.0 } }
|
|
let(:expected_attributes) { { remaining_hours: 4.0 } }
|
|
let(:expected_kept_attributes) { %w[done_ratio] }
|
|
|
|
it_behaves_like "service call", description: "% complete is kept and remaining work is derived"
|
|
end
|
|
end
|
|
|
|
context "given a work package with work, remaining work, and % complete being empty" do
|
|
before do
|
|
work_package.estimated_hours = nil
|
|
work_package.remaining_hours = nil
|
|
work_package.done_ratio = nil
|
|
work_package.clear_changes_information
|
|
end
|
|
|
|
context "when work is set" do
|
|
let(:call_attributes) { { estimated_hours: 10.0 } }
|
|
let(:expected_attributes) do
|
|
{ remaining_hours: 10.0, done_ratio: 0 }
|
|
end
|
|
|
|
it_behaves_like "service call", description: "remaining work is set to the same value and % complete is set to 0%"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "updating % complete when closing work package" do
|
|
before do
|
|
work_package.status = status_50_pct_complete
|
|
work_package.estimated_hours = 10.0
|
|
work_package.remaining_hours = 5.0
|
|
work_package.done_ratio = 50
|
|
work_package.clear_changes_information
|
|
end
|
|
|
|
context "in work-based mode",
|
|
with_settings: { work_package_done_ratio: "field" } do
|
|
context "when `percent_complete_on_status_closed` is set to `no_change`",
|
|
with_settings: { percent_complete_on_status_closed: "no_change" } do
|
|
context "when status is closed" do
|
|
let(:call_attributes) { { status: status_99_pct_closed } }
|
|
let(:expected_kept_attributes) { %w[estimated_hours remaining_hours done_ratio] }
|
|
|
|
it_behaves_like "service call", description: "does not change % complete"
|
|
end
|
|
end
|
|
|
|
context "when `percent_complete_on_status_closed` is set to `set_100p`",
|
|
with_settings: { percent_complete_on_status_closed: "set_100p" } do
|
|
context "when status is closed" do
|
|
let(:call_attributes) { { status: status_99_pct_closed } }
|
|
let(:expected_attributes) { { done_ratio: 100, remaining_hours: 0.0 } }
|
|
let(:expected_kept_attributes) { %w[estimated_hours] }
|
|
|
|
it_behaves_like "service call", description: "changes % complete to 100% and derives remaining work accordingly"
|
|
end
|
|
end
|
|
end
|
|
|
|
context "in status-based mode",
|
|
with_settings: { work_package_done_ratio: "status" } do
|
|
context "when `percent_complete_on_status_closed` is set to `no_change`",
|
|
with_settings: { percent_complete_on_status_closed: "no_change" } do
|
|
context "when status is closed" do
|
|
let(:call_attributes) { { status: status_99_pct_closed } }
|
|
let(:expected_attributes) { { done_ratio: 99, remaining_hours: 0.1 } }
|
|
let(:expected_kept_attributes) { %w[estimated_hours] }
|
|
|
|
it_behaves_like "service call", description: "changes % complete to the status's default % complete value " \
|
|
"and derives remaining work accordingly"
|
|
end
|
|
end
|
|
|
|
context "when `percent_complete_on_status_closed` is set to `set_100p`",
|
|
with_settings: { percent_complete_on_status_closed: "set_100p" } do
|
|
context "when status is closed" do
|
|
let(:call_attributes) { { status: status_99_pct_closed } }
|
|
let(:expected_attributes) { { done_ratio: 99, remaining_hours: 0.1 } }
|
|
let(:expected_kept_attributes) { %w[estimated_hours] }
|
|
|
|
it_behaves_like "service call", description: "changes % complete to the status's default % complete value " \
|
|
"and derives remaining work accordingly"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "for status" do
|
|
let(:default_status) { build_stubbed(:default_status) }
|
|
let(:other_status) { build_stubbed(:status) }
|
|
let(:new_statuses) { [other_status, default_status] }
|
|
|
|
before do
|
|
allow(Status).to receive(:default).and_return(default_status)
|
|
end
|
|
|
|
context "with no value set before for a new work package" do
|
|
let(:call_attributes) { {} }
|
|
let(:expected_attributes) { { status: default_status } }
|
|
let(:work_package) { new_work_package }
|
|
|
|
before { work_package.status = nil }
|
|
|
|
it_behaves_like "service call"
|
|
end
|
|
|
|
context "with an invalid value that is not part of the type.statuses for a new work package" do
|
|
let(:invalid_status) { create(:status) }
|
|
let(:type) { create(:type) }
|
|
let(:call_attributes) { { status: invalid_status, type: } }
|
|
let(:expected_attributes) { { status: default_status, type: } }
|
|
let(:work_package) { new_work_package }
|
|
|
|
it_behaves_like "service call"
|
|
end
|
|
|
|
context "with valid value and without a type present for a new work package" do
|
|
let(:status) { create(:status) }
|
|
let(:call_attributes) { { status:, type: nil } }
|
|
let(:expected_attributes) { { status: } }
|
|
let(:work_package) { new_work_package }
|
|
|
|
it_behaves_like "service call"
|
|
end
|
|
|
|
context "with a valid value that is part of the type.statuses for a new work package" do
|
|
let(:type) { create(:type) }
|
|
let(:status) { create(:status, workflow_for_type: type) }
|
|
let(:call_attributes) { { status:, type: } }
|
|
let(:expected_attributes) { { status:, type: } }
|
|
let(:work_package) { new_work_package }
|
|
|
|
it_behaves_like "service call"
|
|
end
|
|
|
|
context "with no value set on existing work package" do
|
|
let(:call_attributes) { {} }
|
|
let(:expected_attributes) { {} }
|
|
|
|
before do
|
|
work_package.status = nil
|
|
end
|
|
|
|
it_behaves_like "service call" do
|
|
it "stays nil" do
|
|
subject
|
|
|
|
expect(work_package.status)
|
|
.to be_nil
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when updating status before calling the service" do
|
|
let(:call_attributes) { {} }
|
|
let(:expected_attributes) { { status: other_status } }
|
|
|
|
before do
|
|
work_package.attributes = expected_attributes
|
|
end
|
|
|
|
it_behaves_like "service call"
|
|
end
|
|
|
|
context "when updating status via attributes" do
|
|
let(:call_attributes) { expected_attributes }
|
|
let(:expected_attributes) { { status: other_status } }
|
|
|
|
it_behaves_like "service call"
|
|
end
|
|
end
|
|
|
|
context "for author" do
|
|
let(:other_user) { build_stubbed(:user) }
|
|
|
|
context "with no value set before for a new work package" do
|
|
let(:call_attributes) { {} }
|
|
let(:expected_attributes) { {} }
|
|
let(:work_package) { new_work_package }
|
|
|
|
it_behaves_like "service call" do
|
|
it "sets the service's author" do
|
|
instance.call(call_attributes)
|
|
|
|
expect(work_package.author).to eql user
|
|
end
|
|
|
|
it "notes the author to be system changed" do
|
|
subject
|
|
|
|
expect(work_package.changed_by_system["author_id"]).to eql [nil, user.id]
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with no value set on existing work package" do
|
|
let(:call_attributes) { {} }
|
|
let(:expected_attributes) { {} }
|
|
|
|
before do
|
|
work_package.author = nil
|
|
end
|
|
|
|
it_behaves_like "service call" do
|
|
it "stays nil" do
|
|
subject
|
|
|
|
expect(work_package.author)
|
|
.to be_nil
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when updating author before calling the service" do
|
|
let(:call_attributes) { {} }
|
|
let(:expected_attributes) { { author: other_user } }
|
|
|
|
before do
|
|
work_package.attributes = expected_attributes
|
|
end
|
|
|
|
it_behaves_like "service call"
|
|
end
|
|
|
|
context "when updating author via attributes" do
|
|
let(:call_attributes) { expected_attributes }
|
|
let(:expected_attributes) { { author: other_user } }
|
|
|
|
it_behaves_like "service call"
|
|
end
|
|
end
|
|
|
|
context "with the actual contract" do
|
|
let(:invalid_wp) do
|
|
build(:work_package, subject: "").tap do |wp|
|
|
wp.save!(validate: false)
|
|
end
|
|
end
|
|
let(:user) { build_stubbed(:admin) }
|
|
let(:instance) do
|
|
described_class.new(user:,
|
|
model: invalid_wp,
|
|
contract_class:)
|
|
end
|
|
|
|
context "with a currently invalid subject" do
|
|
let(:call_attributes) { expected_attributes }
|
|
let(:expected_attributes) { { subject: "ABC" } }
|
|
let(:contract_valid) { true }
|
|
|
|
subject { instance.call(call_attributes) }
|
|
|
|
it "is successful" do
|
|
expect(subject).to be_success
|
|
expect(subject.errors).to be_empty
|
|
end
|
|
end
|
|
end
|
|
|
|
context "for start_date & due_date & duration" do
|
|
context "with a parent scheduled manually" do
|
|
let(:expected_attributes) { {} }
|
|
let(:work_package) { new_work_package }
|
|
let(:parent) do
|
|
build_stubbed(:work_package,
|
|
schedule_manually: true,
|
|
start_date: parent_start_date,
|
|
due_date: parent_due_date)
|
|
end
|
|
let(:parent_start_date) { Time.zone.today - 5.days }
|
|
let(:parent_due_date) { Time.zone.today + 10.days }
|
|
|
|
context "with the parent having dates and work package not providing its own dates" do
|
|
let(:call_attributes) { { parent: } }
|
|
let(:parent_start_date) { Time.zone.today - 5.days }
|
|
let(:parent_due_date) { Time.zone.today + 10.days }
|
|
|
|
it_behaves_like "service call", description: "sets the start and due dates to the parent's dates" do
|
|
let(:expected_attributes) do
|
|
{
|
|
start_date: parent_start_date,
|
|
due_date: parent_due_date
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with the parent having dates and work package not providing its own dates " \
|
|
"and with the parent's soonest_start being later than its start_date " \
|
|
"(e.g. because the parent is manually scheduled and his predecessor " \
|
|
"due date is later than its own start date)" do
|
|
let(:call_attributes) { { parent: } }
|
|
let(:parent_start_date) { Time.zone.today - 5.days }
|
|
let(:parent_due_date) { Time.zone.today + 10.days }
|
|
|
|
before do
|
|
allow(parent)
|
|
.to receive(:soonest_start)
|
|
.and_return(parent_start_date + 3.days)
|
|
end
|
|
|
|
it_behaves_like "service call", description: "sets the start and due dates to the parent's dates" do
|
|
let(:expected_attributes) do
|
|
{
|
|
start_date: parent_start_date,
|
|
due_date: parent_due_date
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with the parent having start date (no due) and work package not providing its own dates" do
|
|
let(:call_attributes) { { parent: } }
|
|
let(:parent_due_date) { nil }
|
|
|
|
it_behaves_like "service call", description: "sets the start to the parent's start date and sets due date to nil" do
|
|
let(:expected_attributes) do
|
|
{
|
|
start_date: parent_start_date,
|
|
due_date: nil
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with the parent having due date (no start) and work package not providing its own dates" do
|
|
let(:call_attributes) { { parent: } }
|
|
let(:parent_start_date) { nil }
|
|
|
|
it_behaves_like "service call", description: "sets the start to the parent's start date and sets due date to nil" do
|
|
let(:expected_attributes) do
|
|
{
|
|
start_date: nil,
|
|
due_date: parent_due_date
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with the parent having dates but work package providing its own dates" do
|
|
let(:call_attributes) { { parent:, start_date: Time.zone.today, due_date: Time.zone.today + 1.day } }
|
|
|
|
it_behaves_like "service call", description: "sets the start and due dates to the provided dates" do
|
|
let(:expected_attributes) do
|
|
{
|
|
start_date: Time.zone.today,
|
|
due_date: Time.zone.today + 1.day
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with the parent having dates but work package providing its own start_date" do
|
|
let(:call_attributes) { { parent:, start_date: Time.zone.today } }
|
|
|
|
it_behaves_like "service call",
|
|
description: "sets the start_date to the provided date and the due_date to the parent's due_date" do
|
|
let(:expected_attributes) do
|
|
{
|
|
start_date: Time.zone.today,
|
|
due_date: parent_due_date
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with the parent having dates but work package providing its own due_date" do
|
|
let(:call_attributes) { { parent:, due_date: Time.zone.today + 4.days } }
|
|
|
|
it_behaves_like "service call",
|
|
description: "sets the start_date to the parent's start date and the due_date to the provided date" do
|
|
let(:expected_attributes) do
|
|
{
|
|
start_date: parent_start_date,
|
|
due_date: Time.zone.today + 4.days
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with the parent having dates but work package providing its own empty start_date" do
|
|
let(:call_attributes) { { parent:, start_date: nil } }
|
|
|
|
it_behaves_like "service call",
|
|
description: "sets the start_date to nil and the due_date to the parent's due_date" do
|
|
let(:expected_attributes) do
|
|
{
|
|
start_date: nil,
|
|
due_date: parent_due_date
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with the parent having dates but work package providing its own empty due_date" do
|
|
let(:call_attributes) { { parent:, due_date: nil } }
|
|
|
|
it_behaves_like "service call",
|
|
description: "sets the start_date to the parent's start date and the due_date to nil" do
|
|
let(:expected_attributes) do
|
|
{
|
|
start_date: parent_start_date,
|
|
due_date: nil
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with the parent having dates but work package providing its own start date that is before parent's due date" do
|
|
let(:call_attributes) { { parent:, start_date: parent_due_date - 4.days } }
|
|
|
|
it_behaves_like "service call",
|
|
description: "sets the start_date to the provided date and the due_date to the parent's due_date" do
|
|
let(:expected_attributes) do
|
|
{
|
|
start_date: parent_due_date - 4.days,
|
|
due_date: parent_due_date
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with the parent having dates but work package providing its own start date that is after the parent's due date" do
|
|
let(:call_attributes) { { parent:, start_date: parent_due_date + 1.day } }
|
|
|
|
it_behaves_like "service call",
|
|
description: "sets the start_date to the provided date and the due_date to nil" do
|
|
let(:expected_attributes) do
|
|
{
|
|
start_date: parent_due_date + 1.day,
|
|
due_date: nil
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with the parent having dates but work package providing its own due date that is before the parent's start date" do
|
|
let(:call_attributes) { { parent:, due_date: parent_start_date - 3.days } }
|
|
|
|
it_behaves_like "service call",
|
|
description: "leaves the start date empty and sets the due date to the provided date" do
|
|
let(:expected_attributes) do
|
|
{
|
|
start_date: nil,
|
|
due_date: parent_start_date - 3.days
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with providing a parent_id that is invalid" do
|
|
let(:call_attributes) { { parent_id: -1 } }
|
|
let(:work_package) { build_stubbed(:work_package, start_date: Time.zone.today, due_date: Time.zone.today + 2.days) }
|
|
|
|
it_behaves_like "service call", description: "keeps its own dates" do
|
|
let(:expected_kept_attributes) { %w[start_date due_date] }
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with no value set for a new work package and with default setting active",
|
|
with_settings: { work_package_startdate_is_adddate: true } do
|
|
let(:call_attributes) { {} }
|
|
let(:expected_attributes) { {} }
|
|
let(:work_package) { new_work_package }
|
|
|
|
it_behaves_like "service call" do
|
|
it "sets the start date to today" do
|
|
subject
|
|
|
|
expect(work_package.start_date)
|
|
.to eql Time.zone.today
|
|
end
|
|
|
|
it "sets the duration to nil" do
|
|
subject
|
|
|
|
expect(work_package.duration)
|
|
.to be_nil
|
|
end
|
|
|
|
context "when the work package type is milestone" do
|
|
before do
|
|
work_package.type = milestone_type
|
|
end
|
|
|
|
it "sets the duration to 1" do
|
|
subject
|
|
|
|
expect(work_package.duration)
|
|
.to eq 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with a value set for a new work package and with default setting active",
|
|
with_settings: { work_package_startdate_is_adddate: true } do
|
|
let(:call_attributes) { { start_date: Time.zone.today + 1.day } }
|
|
let(:expected_attributes) { {} }
|
|
let(:work_package) { new_work_package }
|
|
|
|
it_behaves_like "service call" do
|
|
it "stays that value" do
|
|
subject
|
|
|
|
expect(work_package.start_date)
|
|
.to eq(Time.zone.today + 1.day)
|
|
end
|
|
|
|
it "sets the duration to nil" do
|
|
subject
|
|
|
|
expect(work_package.duration)
|
|
.to be_nil
|
|
end
|
|
|
|
context "when the work package type is milestone" do
|
|
before do
|
|
work_package.type = milestone_type
|
|
end
|
|
|
|
it "sets the duration to 1" do
|
|
subject
|
|
|
|
expect(work_package.duration)
|
|
.to eq 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with date values set to the same date on a new work package" do
|
|
let(:call_attributes) { { start_date: Time.zone.today, due_date: Time.zone.today } }
|
|
let(:expected_attributes) { {} }
|
|
let(:work_package) { new_work_package }
|
|
|
|
it_behaves_like "service call" do
|
|
it "sets the start date value" do
|
|
subject
|
|
|
|
expect(work_package.start_date)
|
|
.to eq(Time.zone.today)
|
|
end
|
|
|
|
it "sets the due date value" do
|
|
subject
|
|
|
|
expect(work_package.due_date)
|
|
.to eq(Time.zone.today)
|
|
end
|
|
|
|
it "sets the duration to 1" do
|
|
subject
|
|
|
|
expect(work_package.duration)
|
|
.to eq 1
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with date values set on a new work package" do
|
|
let(:call_attributes) { { start_date: Time.zone.today, due_date: Time.zone.today + 5.days } }
|
|
let(:expected_attributes) { {} }
|
|
let(:work_package) { new_work_package }
|
|
|
|
it_behaves_like "service call" do
|
|
it "sets the start date value" do
|
|
subject
|
|
|
|
expect(work_package.start_date)
|
|
.to eq(Time.zone.today)
|
|
end
|
|
|
|
it "sets the due date value" do
|
|
subject
|
|
|
|
expect(work_package.due_date)
|
|
.to eq(Time.zone.today + 5.days)
|
|
end
|
|
|
|
it "sets the duration to 6" do
|
|
subject
|
|
|
|
expect(work_package.duration)
|
|
.to eq 6
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with start date changed on a manually scheduled work package" do
|
|
let(:work_package) do
|
|
build_stubbed(:work_package, schedule_manually: true,
|
|
start_date: Time.zone.today,
|
|
due_date: Time.zone.today + 5.days)
|
|
end
|
|
let(:call_attributes) { { start_date: Time.zone.today + 1.day } }
|
|
let(:expected_attributes) { {} }
|
|
|
|
it_behaves_like "service call" do
|
|
it "sets the start date value" do
|
|
subject
|
|
|
|
expect(work_package.start_date)
|
|
.to eq(Time.zone.today + 1.day)
|
|
end
|
|
|
|
it "keeps the due date value" do
|
|
subject
|
|
|
|
expect(work_package.due_date)
|
|
.to eq(Time.zone.today + 5.days)
|
|
end
|
|
|
|
it "updates the duration" do
|
|
subject
|
|
|
|
expect(work_package.duration)
|
|
.to eq 5
|
|
end
|
|
end
|
|
end
|
|
|
|
# TODO: update this test: on an automatically scheduled work package, the
|
|
# start date is set to the soonest start date and cannot be changed. An
|
|
# error is to be expected!
|
|
context "with start date changed on an automatically scheduled work package" do
|
|
let(:work_package) do
|
|
build_stubbed(:work_package, schedule_manually: false,
|
|
start_date: Time.zone.today,
|
|
due_date: Time.zone.today + 5.days)
|
|
end
|
|
let(:call_attributes) { { start_date: Time.zone.today + 1.day } }
|
|
let(:expected_attributes) { {} }
|
|
|
|
it_behaves_like "service call" do
|
|
it "sets the start date value" do
|
|
subject
|
|
|
|
expect(work_package.start_date)
|
|
.to eq(Time.zone.today + 1.day)
|
|
end
|
|
|
|
it "keeps the due date value" do
|
|
subject
|
|
|
|
expect(work_package.due_date)
|
|
.to eq(Time.zone.today + 5.days)
|
|
end
|
|
|
|
it "updates the duration" do
|
|
subject
|
|
|
|
expect(work_package.duration)
|
|
.to eq 5
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with due date changed" do
|
|
let(:work_package) { build_stubbed(:work_package, start_date: Time.zone.today, due_date: Time.zone.today + 5.days) }
|
|
let(:call_attributes) { { due_date: Time.zone.today + 1.day } }
|
|
let(:expected_attributes) { {} }
|
|
|
|
it_behaves_like "service call" do
|
|
it "keeps the start date value" do
|
|
subject
|
|
|
|
expect(work_package.start_date)
|
|
.to eq(Time.zone.today)
|
|
end
|
|
|
|
it "sets the due date value" do
|
|
subject
|
|
|
|
expect(work_package.due_date)
|
|
.to eq(Time.zone.today + 1.day)
|
|
end
|
|
|
|
it "updates the duration" do
|
|
subject
|
|
|
|
expect(work_package.duration)
|
|
.to eq 2
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with start date nilled" do
|
|
let(:traits) { [] }
|
|
let(:work_package) do
|
|
build_stubbed(:work_package, *traits, start_date: Time.zone.today, due_date: Time.zone.today + 5.days)
|
|
end
|
|
let(:call_attributes) { { start_date: nil } }
|
|
let(:expected_attributes) { {} }
|
|
|
|
it_behaves_like "service call" do
|
|
it "sets the start date to nil" do
|
|
subject
|
|
|
|
expect(work_package.start_date)
|
|
.to be_nil
|
|
end
|
|
|
|
it "keeps the due date value" do
|
|
subject
|
|
|
|
expect(work_package.due_date)
|
|
.to eq(Time.zone.today + 5.days)
|
|
end
|
|
|
|
it "sets the duration to nil" do
|
|
subject
|
|
|
|
expect(work_package.duration)
|
|
.to be_nil
|
|
end
|
|
|
|
context "when the work package type is milestone" do
|
|
let(:traits) { [:is_milestone] }
|
|
|
|
it "sets the duration to 1" do
|
|
subject
|
|
|
|
expect(work_package.duration)
|
|
.to eq 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with due date nilled" do
|
|
let(:traits) { [] }
|
|
let(:work_package) do
|
|
build_stubbed(:work_package, *traits, start_date: Time.zone.today, due_date: Time.zone.today + 5.days)
|
|
end
|
|
let(:call_attributes) { { due_date: nil } }
|
|
let(:expected_attributes) { {} }
|
|
|
|
it_behaves_like "service call" do
|
|
it "keeps the start date" do
|
|
subject
|
|
|
|
expect(work_package.start_date)
|
|
.to eq(Time.zone.today)
|
|
end
|
|
|
|
it "nils the due date" do
|
|
subject
|
|
|
|
expect(work_package.due_date)
|
|
.to be_nil
|
|
end
|
|
|
|
it "sets the duration to nil" do
|
|
subject
|
|
|
|
expect(work_package.duration)
|
|
.to be_nil
|
|
end
|
|
|
|
context "when the work package type is milestone" do
|
|
let(:traits) { [:is_milestone] }
|
|
|
|
it "sets the duration to 1" do
|
|
subject
|
|
|
|
expect(work_package.duration)
|
|
.to eq 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with negative duration" do
|
|
let(:work_package) do
|
|
build_stubbed(:work_package, start_date: Time.zone.today, due_date: Time.zone.today + 5.days)
|
|
end
|
|
let(:call_attributes) { { duration: -1 } }
|
|
let(:expected_attributes) { {} }
|
|
|
|
it_behaves_like "service call" do
|
|
it "keeps the dates and duration values (error to be detected by contract)" do
|
|
subject
|
|
|
|
expect(work_package.start_date)
|
|
.to eq(Time.zone.today)
|
|
expect(work_package.due_date)
|
|
.to eq(Time.zone.today + 5.days)
|
|
expect(work_package.duration)
|
|
.to eq(-1)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with zero duration" do
|
|
let(:work_package) do
|
|
build_stubbed(:work_package, start_date: Time.zone.today, due_date: Time.zone.today + 5.days)
|
|
end
|
|
let(:call_attributes) { { duration: 0 } }
|
|
let(:expected_attributes) { {} }
|
|
|
|
it_behaves_like "service call" do
|
|
it "keeps the dates and duration values (error to be detected by contract)" do
|
|
subject
|
|
|
|
expect(work_package.start_date)
|
|
.to eq(Time.zone.today)
|
|
expect(work_package.due_date)
|
|
.to eq(Time.zone.today + 5.days)
|
|
expect(work_package.duration)
|
|
.to eq(0)
|
|
end
|
|
end
|
|
|
|
context "when the work package has a soonest_start from a predecessor and a due date (Regression #63598)" do
|
|
before do
|
|
allow(instance).to receive(:new_start_date).and_return(Time.zone.yesterday)
|
|
end
|
|
|
|
it_behaves_like "service call" do
|
|
it "keeps the dates and duration values (error to be detected by contract)" do
|
|
subject
|
|
|
|
expect(work_package.start_date)
|
|
.to eq(Time.zone.yesterday)
|
|
expect(work_package.due_date)
|
|
.to eq(Time.zone.today + 5.days)
|
|
expect(work_package.duration)
|
|
.to eq(0)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when the work package has a soonest_start from a predecessor and no due date (Regression #63598)" do
|
|
let(:work_package) do
|
|
build_stubbed(:work_package, start_date: Time.zone.today, due_date: nil)
|
|
end
|
|
|
|
before do
|
|
allow(instance).to receive(:new_start_date).and_return(Time.zone.yesterday)
|
|
end
|
|
|
|
it_behaves_like "service call" do
|
|
it "keeps the dates and duration values (error to be detected by contract)" do
|
|
subject
|
|
|
|
expect(work_package.start_date)
|
|
.to eq(Time.zone.yesterday)
|
|
expect(work_package.due_date)
|
|
.to be_nil
|
|
expect(work_package.duration)
|
|
.to eq(0)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with invalid duration (when the duration is text)" do
|
|
let(:work_package) do
|
|
build_stubbed(:work_package, start_date: Time.zone.today, due_date: Time.zone.today + 5.days)
|
|
end
|
|
let(:call_attributes) { { duration: "I am invalid" } }
|
|
let(:expected_attributes) { {} }
|
|
|
|
it_behaves_like "service call" do
|
|
it "keeps the dates, sets duration to 0 but remembers the string that was used (error to be detected by contract)" do
|
|
subject
|
|
|
|
expect(work_package.start_date)
|
|
.to eq(Time.zone.today)
|
|
expect(work_package.due_date)
|
|
.to eq(Time.zone.today + 5.days)
|
|
expect(work_package.duration)
|
|
.to eq(0)
|
|
expect(work_package.duration_before_type_cast)
|
|
.to eq("I am invalid")
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when deriving one value from the two others" do
|
|
# rubocop:disable Layout/ExtraSpacing, Layout/SpaceInsideArrayPercentLiteral, Layout/SpaceInsidePercentLiteralDelimiters, Layout/LineLength
|
|
all_possible_scenarios = [
|
|
{ initial: %i[start_date due_date duration], set: %i[], expected: {} },
|
|
{ initial: %i[start_date ], set: %i[], expected: {} },
|
|
{ initial: %i[ due_date ], set: %i[], expected: {} },
|
|
{ initial: %i[ duration], set: %i[], expected: {} },
|
|
{ initial: %i[ ], set: %i[], expected: {} },
|
|
|
|
{ initial: %i[start_date due_date duration], set: %i[start_date], expected: { change: :duration } },
|
|
{ initial: %i[start_date ], set: %i[start_date], expected: {} },
|
|
{ initial: %i[ due_date ], set: %i[start_date], expected: { change: :duration } },
|
|
{ initial: %i[ duration], set: %i[start_date], expected: { change: :due_date } },
|
|
{ initial: %i[ ], set: %i[start_date], expected: {} },
|
|
|
|
{ initial: %i[start_date due_date duration], nilled: %i[start_date], expected: { nilify: :duration } },
|
|
{ initial: %i[start_date ], nilled: %i[start_date], expected: {} },
|
|
{ initial: %i[ due_date ], nilled: %i[start_date], expected: {} },
|
|
{ initial: %i[ duration], nilled: %i[start_date], expected: {} },
|
|
{ initial: %i[ ], nilled: %i[start_date], expected: {} },
|
|
|
|
{ initial: %i[start_date due_date duration], set: %i[due_date], expected: { change: :duration } },
|
|
{ initial: %i[start_date ], set: %i[due_date], expected: { change: :duration } },
|
|
{ initial: %i[ due_date ], set: %i[due_date], expected: {} },
|
|
{ initial: %i[ duration], set: %i[due_date], expected: { change: :start_date } },
|
|
{ initial: %i[ ], set: %i[due_date], expected: {} },
|
|
|
|
{ initial: %i[start_date due_date duration], nilled: %i[due_date], expected: { nilify: :duration } },
|
|
{ initial: %i[start_date ], nilled: %i[due_date], expected: {} },
|
|
{ initial: %i[ due_date ], nilled: %i[due_date], expected: {} },
|
|
{ initial: %i[ duration], nilled: %i[due_date], expected: {} },
|
|
{ initial: %i[ ], nilled: %i[due_date], expected: {} },
|
|
|
|
{ initial: %i[start_date due_date duration], set: %i[duration], expected: { change: :due_date } },
|
|
{ initial: %i[start_date ], set: %i[duration], expected: { change: :due_date } },
|
|
{ initial: %i[ due_date ], set: %i[duration], expected: { change: :start_date } },
|
|
{ initial: %i[ duration], set: %i[duration], expected: {} },
|
|
{ initial: %i[ ], set: %i[duration], expected: {} },
|
|
|
|
{ initial: %i[start_date due_date duration], nilled: %i[duration], expected: { nilify: :due_date } },
|
|
{ initial: %i[start_date ], nilled: %i[duration], expected: {} },
|
|
{ initial: %i[ due_date ], nilled: %i[duration], expected: {} },
|
|
{ initial: %i[ duration], nilled: %i[duration], expected: {} },
|
|
{ initial: %i[ ], nilled: %i[duration], expected: {} },
|
|
|
|
{ initial: %i[start_date due_date duration], set: %i[start_date due_date], expected: { change: :duration } },
|
|
{ initial: %i[start_date ], set: %i[start_date due_date], expected: { change: :duration } },
|
|
{ initial: %i[ due_date ], set: %i[start_date due_date], expected: { change: :duration } },
|
|
{ initial: %i[ duration], set: %i[start_date due_date], expected: { change: :duration } },
|
|
{ initial: %i[ ], set: %i[start_date due_date], expected: { change: :duration } },
|
|
|
|
{ initial: %i[start_date due_date duration], set: %i[start_date], nilled: %i[due_date], expected: { nilify: :duration } },
|
|
{ initial: %i[start_date ], set: %i[start_date], nilled: %i[due_date], expected: {} },
|
|
{ initial: %i[ due_date ], set: %i[start_date], nilled: %i[due_date], expected: {} },
|
|
{ initial: %i[ duration], set: %i[start_date], nilled: %i[due_date], expected: { nilify: :duration, same: :due_date } },
|
|
{ initial: %i[ ], set: %i[start_date], nilled: %i[due_date], expected: {} },
|
|
|
|
{ initial: %i[start_date due_date duration], set: %i[due_date], nilled: %i[start_date], expected: { nilify: :duration } },
|
|
{ initial: %i[start_date ], set: %i[due_date], nilled: %i[start_date], expected: {} },
|
|
{ initial: %i[ due_date ], set: %i[due_date], nilled: %i[start_date], expected: {} },
|
|
{ initial: %i[ duration], set: %i[due_date], nilled: %i[start_date], expected: { nilify: :duration, same: :start_date } },
|
|
{ initial: %i[ ], set: %i[due_date], nilled: %i[start_date], expected: {} },
|
|
|
|
{ initial: %i[start_date due_date duration], nilled: %i[start_date due_date], expected: {} },
|
|
{ initial: %i[start_date ], nilled: %i[start_date due_date], expected: {} },
|
|
{ initial: %i[ due_date ], nilled: %i[start_date due_date], expected: {} },
|
|
{ initial: %i[ duration], nilled: %i[start_date due_date], expected: {} },
|
|
{ initial: %i[ ], nilled: %i[start_date due_date], expected: {} },
|
|
|
|
{ initial: %i[start_date due_date duration], set: %i[start_date duration], expected: { change: :due_date } },
|
|
{ initial: %i[start_date ], set: %i[start_date duration], expected: { change: :due_date } },
|
|
{ initial: %i[ due_date ], set: %i[start_date duration], expected: { change: :due_date } },
|
|
{ initial: %i[ duration], set: %i[start_date duration], expected: { change: :due_date } },
|
|
{ initial: %i[ ], set: %i[start_date duration], expected: { change: :due_date } },
|
|
|
|
{ initial: %i[start_date due_date duration], set: %i[start_date], nilled: %i[duration], expected: { nilify: :due_date } },
|
|
{ initial: %i[start_date ], set: %i[start_date], nilled: %i[duration], expected: {} },
|
|
{ initial: %i[ due_date ], set: %i[start_date], nilled: %i[duration], expected: { nilify: :due_date, same: :duration } },
|
|
{ initial: %i[ duration], set: %i[start_date], nilled: %i[duration], expected: {} },
|
|
{ initial: %i[ ], set: %i[start_date], nilled: %i[duration], expected: {} },
|
|
|
|
{ initial: %i[start_date due_date duration], set: %i[duration], nilled: %i[start_date], expected: { nilify: :due_date } },
|
|
{ initial: %i[start_date ], set: %i[duration], nilled: %i[start_date], expected: {} },
|
|
{ initial: %i[ due_date ], set: %i[duration], nilled: %i[start_date], expected: { nilify: :due_date, same: :start_date } },
|
|
{ initial: %i[ duration], set: %i[duration], nilled: %i[start_date], expected: {} },
|
|
{ initial: %i[ ], set: %i[duration], nilled: %i[start_date], expected: {} },
|
|
|
|
{ initial: %i[start_date due_date duration], nilled: %i[start_date duration], expected: {} },
|
|
{ initial: %i[start_date ], nilled: %i[start_date duration], expected: {} },
|
|
{ initial: %i[ due_date ], nilled: %i[start_date duration], expected: {} },
|
|
{ initial: %i[ duration], nilled: %i[start_date duration], expected: {} },
|
|
{ initial: %i[ ], nilled: %i[start_date duration], expected: {} },
|
|
|
|
{ initial: %i[start_date due_date duration], set: %i[due_date duration], expected: { change: :start_date } },
|
|
{ initial: %i[start_date ], set: %i[due_date duration], expected: { change: :start_date } },
|
|
{ initial: %i[ due_date ], set: %i[due_date duration], expected: { change: :start_date } },
|
|
{ initial: %i[ duration], set: %i[due_date duration], expected: { change: :start_date } },
|
|
{ initial: %i[ ], set: %i[due_date duration], expected: { change: :start_date } },
|
|
|
|
{ initial: %i[start_date due_date duration], set: %i[due_date], nilled: %i[duration], expected: { nilify: :start_date } },
|
|
{ initial: %i[start_date ], set: %i[due_date], nilled: %i[duration], expected: { nilify: :start_date, same: :duration } },
|
|
{ initial: %i[ due_date ], set: %i[due_date], nilled: %i[duration], expected: {} },
|
|
{ initial: %i[ duration], set: %i[due_date], nilled: %i[duration], expected: {} },
|
|
{ initial: %i[ ], set: %i[due_date], nilled: %i[duration], expected: {} },
|
|
|
|
{ initial: %i[start_date due_date duration], set: %i[duration], nilled: %i[due_date], expected: { nilify: :start_date } },
|
|
{ initial: %i[start_date ], set: %i[duration], nilled: %i[due_date], expected: { nilify: :start_date, same: :due_date } },
|
|
{ initial: %i[ due_date ], set: %i[duration], nilled: %i[due_date], expected: {} },
|
|
{ initial: %i[ duration], set: %i[duration], nilled: %i[due_date], expected: {} },
|
|
{ initial: %i[ ], set: %i[duration], nilled: %i[due_date], expected: {} },
|
|
|
|
{ initial: %i[start_date due_date duration], nilled: %i[due_date duration], expected: {} },
|
|
{ initial: %i[start_date ], nilled: %i[due_date duration], expected: {} },
|
|
{ initial: %i[ due_date ], nilled: %i[due_date duration], expected: {} },
|
|
{ initial: %i[ duration], nilled: %i[due_date duration], expected: {} },
|
|
{ initial: %i[ ], nilled: %i[due_date duration], expected: {} },
|
|
|
|
{ initial: %i[start_date due_date duration], set: %i[start_date due_date duration], expected: {} },
|
|
{ initial: %i[start_date ], set: %i[start_date due_date duration], expected: {} },
|
|
{ initial: %i[ due_date ], set: %i[start_date due_date duration], expected: {} },
|
|
{ initial: %i[ duration], set: %i[start_date due_date duration], expected: {} },
|
|
{ initial: %i[ ], set: %i[start_date due_date duration], expected: {} }
|
|
]
|
|
# rubocop:enable Layout/ExtraSpacing, Layout/SpaceInsideArrayPercentLiteral, Layout/SpaceInsidePercentLiteralDelimiters, Layout/LineLength
|
|
|
|
let(:initial_attributes) { { start_date: today, due_date: today + 49.days, duration: 50 } }
|
|
let(:set_attributes) { { start_date: today + 10.days, due_date: today + 12.days, duration: 3 } }
|
|
let(:nil_attributes) { { start_date: nil, due_date: nil, duration: nil } }
|
|
|
|
all_possible_scenarios.each do |scenario|
|
|
initial = scenario[:initial]
|
|
set = scenario[:set] || []
|
|
nilled = scenario[:nilled] || []
|
|
expected = scenario[:expected]
|
|
expected_change = expected[:change]
|
|
expected_same = expected[:same]
|
|
expected_nilify = expected[:nilify]
|
|
unchanged = %i[start_date due_date duration] - set - nilled - expected.values + [expected_same].compact
|
|
|
|
context_description = []
|
|
context_description << "with initial values for #{initial.inspect}" if initial.any?
|
|
context_description << "without any initial values" if initial.none?
|
|
context_description << "with #{set.inspect} set" if set.any?
|
|
context_description << "with #{nilled.inspect} nilled" if nilled.any?
|
|
context_description << "without any attributes set" if set.none? && nilled.none?
|
|
|
|
context context_description.join(", and ") do
|
|
let(:work_package_attributes) { nil_attributes.merge(initial_attributes.slice(*initial)) }
|
|
let(:work_package) { build_stubbed(:work_package, work_package_attributes) }
|
|
let(:call_attributes) { nil_attributes.slice(*nilled).merge(set_attributes.slice(*set)) }
|
|
|
|
it_behaves_like "service call" do
|
|
if expected_change
|
|
it "changes #{expected_change.inspect}" do
|
|
expect { subject }
|
|
.to change(work_package, expected_change)
|
|
end
|
|
end
|
|
|
|
if expected_nilify
|
|
it "sets #{expected_nilify.inspect} to nil" do
|
|
expect { subject }
|
|
.to change(work_package, expected_nilify).to(nil)
|
|
end
|
|
end
|
|
|
|
if unchanged.any?
|
|
it "does not change #{unchanged.map(&:inspect).join(' and ')}" do
|
|
expect { subject }
|
|
.not_to change { work_package.slice(*unchanged) }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with non-working days" do
|
|
shared_let(:working_days) { week_with_saturday_and_sunday_as_weekend }
|
|
let(:monday) { Time.zone.today.beginning_of_week }
|
|
let(:tuesday) { monday + 1.day }
|
|
let(:wednesday) { monday + 2.days }
|
|
let(:friday) { monday + 4.days }
|
|
let(:sunday) { monday + 6.days }
|
|
let(:next_monday) { monday + 7.days }
|
|
let(:next_tuesday) { monday + 8.days }
|
|
|
|
context "when start date changes" do
|
|
let(:work_package) do
|
|
build_stubbed(:work_package, start_date: monday, due_date: next_monday)
|
|
end
|
|
let(:call_attributes) { { start_date: wednesday } }
|
|
|
|
it_behaves_like "service call" do
|
|
it "updates the duration without including non-working days" do
|
|
expect { subject }
|
|
.to change(work_package, :duration)
|
|
.from(6)
|
|
.to(4)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when due date changes" do
|
|
let(:work_package) do
|
|
build_stubbed(:work_package, start_date: monday, due_date: next_monday)
|
|
end
|
|
let(:call_attributes) { { due_date: monday + 14.days } }
|
|
|
|
it_behaves_like "service call" do
|
|
it "updates the duration without including non-working days" do
|
|
expect { subject }
|
|
.to change(work_package, :duration)
|
|
.from(6)
|
|
.to(11)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when duration changes" do
|
|
let(:work_package) do
|
|
build_stubbed(:work_package, start_date: monday, due_date: next_monday)
|
|
end
|
|
let(:call_attributes) { { duration: "13" } }
|
|
|
|
it_behaves_like "service call" do
|
|
it "updates the due date from start date and duration and skips the non-working days" do
|
|
expect { subject }
|
|
.to change(work_package, :due_date)
|
|
.from(next_monday)
|
|
.to(monday + 16.days)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when duration and end_date both change" do
|
|
let(:work_package) do
|
|
build_stubbed(:work_package, start_date: monday, due_date: next_monday)
|
|
end
|
|
let(:call_attributes) { { due_date: next_tuesday, duration: 4 } }
|
|
|
|
it_behaves_like "service call" do
|
|
it "updates the start date and skips the non-working days" do
|
|
expect { subject }
|
|
.to change(work_package, :start_date)
|
|
.from(monday)
|
|
.to(monday.next_occurring(:thursday))
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when "ignore non-working days" is switched to true' do
|
|
let(:work_package) do
|
|
build_stubbed(:work_package, start_date: monday, due_date: next_monday, ignore_non_working_days: false)
|
|
end
|
|
let(:call_attributes) { { ignore_non_working_days: true } }
|
|
|
|
it_behaves_like "service call" do
|
|
it "keeps the dates and updates duration to include the non-working days" do
|
|
# start_date and due_date are checked too to ensure they did not change
|
|
expect { subject }
|
|
.to change { work_package.slice(:start_date, :due_date, :duration) }
|
|
.from(start_date: monday, due_date: next_monday, duration: 6)
|
|
.to(start_date: monday, due_date: next_monday, duration: 8)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when "ignore non-working days" is switched to false' do
|
|
let(:work_package) do
|
|
build_stubbed(:work_package, start_date: monday, due_date: next_monday, ignore_non_working_days: true)
|
|
end
|
|
let(:call_attributes) { { ignore_non_working_days: false } }
|
|
|
|
it_behaves_like "service call" do
|
|
it "keeps the dates and updates duration to include the non-working days" do
|
|
# start_date and duration are checked too to ensure they did not change
|
|
expect { subject }
|
|
.to change { work_package.slice(:start_date, :due_date, :duration) }
|
|
.from(start_date: monday, due_date: next_monday, duration: 8)
|
|
.to(start_date: monday, due_date: next_monday, duration: 6)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when "ignore non-working days" is switched to false and "start date" is on a non-working day' do
|
|
let(:work_package) do
|
|
build_stubbed(:work_package, start_date: monday - 1.day, due_date: friday, ignore_non_working_days: true)
|
|
end
|
|
let(:call_attributes) { { ignore_non_working_days: false } }
|
|
|
|
it_behaves_like "service call" do
|
|
it "updates the start date to be on next working day, keeps due date, and updates duration accordingly" do
|
|
expect { subject }
|
|
.to change { work_package.slice(:start_date, :due_date, :duration) }
|
|
.from(start_date: monday - 1.day, due_date: friday, duration: 6)
|
|
.to(start_date: monday, due_date: friday, duration: 5)
|
|
end
|
|
end
|
|
|
|
context 'with a new work package when "ignore non-working days" is switched to false, ' \
|
|
"start date is on a non-working day, and duration is explicitly set" do
|
|
let(:work_package) do
|
|
build(:work_package, start_date: monday - 1.day, due_date: friday, ignore_non_working_days: true)
|
|
end
|
|
let(:call_attributes) { { ignore_non_working_days: false, duration: 6 } }
|
|
|
|
it_behaves_like "service call" do
|
|
it "updates the start date to be on next working day, and updates due date to accommodate duration" do
|
|
expect { subject }
|
|
.to change { work_package.slice(:start_date, :due_date, :duration) }
|
|
.from(start_date: monday - 1.day, due_date: friday, duration: 6)
|
|
.to(start_date: monday, due_date: next_monday, duration: 6)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when "ignore non-working days" is switched to false and "finish date" is on a non-working day' do
|
|
let(:work_package) do
|
|
build_stubbed(:work_package, start_date: nil, due_date: sunday, ignore_non_working_days: true)
|
|
end
|
|
let(:call_attributes) { { ignore_non_working_days: false } }
|
|
|
|
it_behaves_like "service call" do
|
|
it "updates the finish date to be on next working day" do
|
|
expect { subject }
|
|
.to change { work_package.slice(:start_date, :due_date, :duration) }
|
|
.from(start_date: nil, due_date: sunday, duration: nil)
|
|
.to(start_date: nil, due_date: next_monday, duration: nil)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when "ignore non-working days" is changed AND "finish date" is cleared' do
|
|
let(:work_package) do
|
|
build_stubbed(:work_package, start_date: monday, due_date: next_monday, ignore_non_working_days: true)
|
|
end
|
|
let(:call_attributes) { { ignore_non_working_days: false, due_date: nil } }
|
|
|
|
it_behaves_like "service call" do
|
|
it "does not recompute the due date and nilifies the due date and the duration instead" do
|
|
expect { subject }
|
|
.to change { work_package.slice(:start_date, :due_date, :duration) }
|
|
.from(start_date: monday, due_date: next_monday, duration: 8)
|
|
.to(start_date: monday, due_date: nil, duration: nil)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when "ignore non-working days" is changed to true AND "finish date" is set to another date' do
|
|
let(:work_package) do
|
|
build_stubbed(:work_package, start_date: monday, due_date: wednesday, ignore_non_working_days: true)
|
|
end
|
|
let(:call_attributes) { { due_date: next_monday, ignore_non_working_days: false } }
|
|
|
|
it_behaves_like "service call" do
|
|
it "keeps the dates and updates the duration to include the non-working days" do
|
|
expect { subject }
|
|
.to change { work_package.slice(:start_date, :due_date, :duration) }
|
|
.from(start_date: monday, due_date: wednesday, duration: 3)
|
|
.to(start_date: monday, due_date: next_monday, duration: 6)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when "ignore non-working days" is changed AND "start date" and "finish date" are set to other dates' do
|
|
let(:work_package) do
|
|
build_stubbed(:work_package, start_date: monday, due_date: next_monday, ignore_non_working_days: true)
|
|
end
|
|
let(:call_attributes) { { start_date: friday, due_date: next_tuesday, ignore_non_working_days: false } }
|
|
|
|
it_behaves_like "service call" do
|
|
it "updates the duration from start date and due date" do
|
|
expect { subject }
|
|
.to change { work_package.slice(:start_date, :due_date, :duration) }
|
|
.from(start_date: monday, due_date: next_monday, duration: 8)
|
|
.to(start_date: friday, due_date: next_tuesday, duration: 3)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when "ignore non-working days" is changed AND "finish date" and "duration" are changed' do
|
|
let(:work_package) do
|
|
build_stubbed(:work_package, start_date: monday, due_date: next_monday, ignore_non_working_days: true)
|
|
end
|
|
let(:call_attributes) { { due_date: next_tuesday, duration: 3, ignore_non_working_days: false } }
|
|
|
|
it_behaves_like "service call" do
|
|
it "updates the start date from due date and duration according to the ignore non-working days value" do
|
|
expect { subject }
|
|
.to change { work_package.slice(:start_date, :due_date, :duration) }
|
|
.from(start_date: monday, due_date: next_monday, duration: 8)
|
|
.to(start_date: friday, due_date: next_tuesday, duration: 3)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "without changing anything, when scheduling mode is automatic " \
|
|
"and start date initial date is different from the calculated soonest start" do
|
|
let(:work_package) do
|
|
build_stubbed(:work_package, start_date: monday,
|
|
due_date: friday,
|
|
ignore_non_working_days: true)
|
|
end
|
|
let(:call_attributes) { {} }
|
|
|
|
before do
|
|
allow(work_package).to receive(:soonest_start).and_return(wednesday)
|
|
end
|
|
|
|
context "when scheduling mode is automatic" do
|
|
before do
|
|
work_package.schedule_manually = false
|
|
end
|
|
|
|
it_behaves_like "service call" do
|
|
it "moves the start date to the calculated soonest start, keeps duration and adjusts due date" do
|
|
expect { subject }
|
|
.to change { work_package.slice(:start_date, :due_date, :duration) }
|
|
.from(start_date: monday, due_date: friday, duration: 5)
|
|
.to(start_date: wednesday, due_date: sunday, duration: 5)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when scheduling mode is manual" do
|
|
before do
|
|
work_package.schedule_manually = true
|
|
end
|
|
|
|
it_behaves_like "service call" do
|
|
it "does not change any dates" do
|
|
expect { subject }
|
|
.not_to change { work_package.slice(:start_date, :due_date, :duration) }
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when scheduling mode is initially manual and is changed to automatic" do
|
|
let(:call_attributes) { { schedule_manually: false } }
|
|
|
|
before do
|
|
work_package.schedule_manually = true
|
|
end
|
|
|
|
it_behaves_like "service call" do
|
|
it "moves the start date to the calculated soonest start, keeps duration and adjusts due date" do
|
|
expect { subject }
|
|
.to change { work_package.slice(:start_date, :due_date, :duration) }
|
|
.from(start_date: monday, due_date: friday, duration: 5)
|
|
.to(start_date: wednesday, due_date: sunday, duration: 5)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when scheduling mode is initially automatic and is changed to manual" do
|
|
let(:call_attributes) { { schedule_manually: true } }
|
|
|
|
before do
|
|
work_package.schedule_manually = false
|
|
end
|
|
|
|
it_behaves_like "service call" do
|
|
it "does not change any dates" do
|
|
expect { subject }
|
|
.not_to change { work_package.slice(:start_date, :due_date, :duration) }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "for priority" do
|
|
let(:default_priority) { build_stubbed(:priority) }
|
|
let(:other_priority) { build_stubbed(:priority) }
|
|
|
|
before do
|
|
scope = class_double(IssuePriority)
|
|
|
|
allow(IssuePriority)
|
|
.to receive(:active)
|
|
.and_return(scope)
|
|
allow(scope)
|
|
.to receive(:default)
|
|
.and_return(default_priority)
|
|
end
|
|
|
|
context "with no value set before for a new work package" do
|
|
let(:call_attributes) { {} }
|
|
let(:expected_attributes) { {} }
|
|
let(:work_package) { new_work_package }
|
|
|
|
before do
|
|
work_package.priority = nil
|
|
end
|
|
|
|
it_behaves_like "service call" do
|
|
it "sets the default priority" do
|
|
subject
|
|
|
|
expect(work_package.priority)
|
|
.to eql default_priority
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when updating priority before calling the service" do
|
|
let(:call_attributes) { {} }
|
|
let(:expected_attributes) { { priority: other_priority } }
|
|
|
|
before do
|
|
work_package.attributes = expected_attributes
|
|
end
|
|
|
|
it_behaves_like "service call"
|
|
end
|
|
|
|
context "when updating priority via attributes" do
|
|
let(:call_attributes) { expected_attributes }
|
|
let(:expected_attributes) { { priority: other_priority } }
|
|
|
|
it_behaves_like "service call"
|
|
end
|
|
end
|
|
|
|
context "when switching the type" do
|
|
let(:target_type) { build_stubbed(:type, is_milestone:) }
|
|
let(:work_package) do
|
|
build_stubbed(:work_package, start_date: Time.zone.today - 6.days, due_date: Time.zone.today)
|
|
end
|
|
|
|
context "to a non-milestone type" do
|
|
let(:is_milestone) { false }
|
|
|
|
it "keeps the start date" do
|
|
instance.call(type: target_type)
|
|
|
|
expect(work_package.start_date)
|
|
.to eql Time.zone.today - 6.days
|
|
end
|
|
|
|
it "keeps the due date" do
|
|
instance.call(type: target_type)
|
|
|
|
expect(work_package.due_date)
|
|
.to eql Time.zone.today
|
|
end
|
|
|
|
it "keeps duration" do
|
|
instance.call(type: target_type)
|
|
|
|
expect(work_package.duration).to be 7
|
|
end
|
|
end
|
|
|
|
context "to a milestone type" do
|
|
let(:is_milestone) { true }
|
|
|
|
context "with both dates set" do
|
|
it "sets the start date to the due date" do
|
|
instance.call(type: target_type)
|
|
|
|
expect(work_package.start_date).to eq work_package.due_date
|
|
end
|
|
|
|
it "keeps the due date" do
|
|
instance.call(type: target_type)
|
|
|
|
expect(work_package.due_date).to eql Time.zone.today
|
|
end
|
|
|
|
it "sets the duration to 1 (to be changed to 0 later on)" do
|
|
instance.call(type: target_type)
|
|
|
|
expect(work_package.duration).to eq 1
|
|
end
|
|
end
|
|
|
|
context "with only the start date set" do
|
|
let(:work_package) do
|
|
build_stubbed(:work_package, start_date: Time.zone.today - 6.days)
|
|
end
|
|
|
|
it "keeps the start date" do
|
|
instance.call(type: target_type)
|
|
|
|
expect(work_package.start_date).to eql Time.zone.today - 6.days
|
|
end
|
|
|
|
it "set the due date to the start date" do
|
|
instance.call(type: target_type)
|
|
|
|
expect(work_package.due_date).to eql work_package.start_date
|
|
end
|
|
|
|
it "keeps the duration at 1 (to be changed to 0 later on)" do
|
|
instance.call(type: target_type)
|
|
|
|
expect(work_package.duration).to eq 1
|
|
end
|
|
|
|
context "with a new work package" do
|
|
let(:work_package) do
|
|
build(:work_package, start_date: Time.zone.today - 6.days)
|
|
end
|
|
let(:call_attributes) { { type: target_type, start_date: Time.zone.today - 6.days, due_date: nil, duration: nil } }
|
|
|
|
before do
|
|
instance.call(call_attributes)
|
|
end
|
|
|
|
it "keeps the start date" do
|
|
expect(work_package.start_date).to eq Time.zone.today - 6.days
|
|
end
|
|
|
|
it "set the due date to the start date" do
|
|
expect(work_package.due_date).to eq work_package.start_date
|
|
end
|
|
|
|
it "keeps the duration at 1 (to be changed to 0 later on)" do
|
|
expect(work_package.duration).to eq 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when switching the project" do
|
|
let(:new_project) { build_stubbed(:project) }
|
|
let(:version) { build_stubbed(:version) }
|
|
let(:category) { build_stubbed(:category) }
|
|
let(:new_category) { build_stubbed(:category, name: category.name) }
|
|
let(:new_statuses) { [work_package.status] }
|
|
let(:new_versions) { [] }
|
|
let(:type) { work_package.type }
|
|
let(:new_types) { [type] }
|
|
let(:default_type) { build_stubbed(:type_standard) }
|
|
let(:other_type) { build_stubbed(:type) }
|
|
let(:yet_another_type) { build_stubbed(:type) }
|
|
|
|
let(:call_attributes) { {} }
|
|
let(:new_project_categories) do
|
|
instance_double(ActiveRecord::Relation).tap do |categories_stub|
|
|
allow(new_project)
|
|
.to receive(:categories)
|
|
.and_return(categories_stub)
|
|
end
|
|
end
|
|
|
|
before do
|
|
without_partial_double_verification do
|
|
allow(new_project_categories)
|
|
.to receive(:find_by)
|
|
.with(name: category.name)
|
|
.and_return nil
|
|
allow(new_project)
|
|
.to receive_messages(shared_versions: new_versions, types: new_types)
|
|
allow(new_types)
|
|
.to receive(:order)
|
|
.with(:position)
|
|
.and_return(new_types)
|
|
end
|
|
end
|
|
|
|
shared_examples_for "updating the project" do
|
|
context "for version" do
|
|
before do
|
|
work_package.version = version
|
|
end
|
|
|
|
context "when not shared in new project" do
|
|
it "sets to nil" do
|
|
subject
|
|
|
|
expect(work_package.version)
|
|
.to be_nil
|
|
end
|
|
end
|
|
|
|
context "when shared in the new project" do
|
|
let(:new_versions) { [version] }
|
|
|
|
it "keeps the version" do
|
|
subject
|
|
|
|
expect(work_package.version)
|
|
.to eql version
|
|
end
|
|
end
|
|
end
|
|
|
|
context "for category" do
|
|
before do
|
|
work_package.category = category
|
|
end
|
|
|
|
context "when no category of same name in new project" do
|
|
it "sets to nil" do
|
|
subject
|
|
|
|
expect(work_package.category)
|
|
.to be_nil
|
|
end
|
|
end
|
|
|
|
context "when category of same name in new project" do
|
|
before do
|
|
allow(new_project_categories)
|
|
.to receive(:find_by)
|
|
.with(name: category.name)
|
|
.and_return new_category
|
|
end
|
|
|
|
it "uses the equally named category" do
|
|
subject
|
|
|
|
expect(work_package.category)
|
|
.to eql new_category
|
|
end
|
|
|
|
it "adds change to system changes" do
|
|
subject
|
|
|
|
expect(work_package.changed_by_system["category_id"])
|
|
.to eql [nil, new_category.id]
|
|
end
|
|
end
|
|
end
|
|
|
|
context "for type" do
|
|
context "when the work package has a type already set" do
|
|
let(:work_package) do
|
|
build_stubbed(:work_package, project:, type: initial_type)
|
|
end
|
|
|
|
it "leaves the type" do
|
|
subject
|
|
|
|
expect(work_package.type)
|
|
.to eql initial_type
|
|
end
|
|
end
|
|
|
|
context "when the work package has no type set" do
|
|
let(:work_package) do
|
|
build_stubbed(:work_package, project:, type: nil)
|
|
end
|
|
|
|
let(:new_types) { [other_type] }
|
|
|
|
it "uses the first type (by position)" do
|
|
subject
|
|
|
|
expect(work_package.type)
|
|
.to eql other_type
|
|
end
|
|
|
|
it "adds change to system changes" do
|
|
subject
|
|
|
|
expect(work_package.changed_by_system["type_id"])
|
|
.to eql [nil, other_type.id]
|
|
end
|
|
end
|
|
|
|
context "and also setting a new type via attributes" do
|
|
let(:new_types) { [yet_another_type] }
|
|
let(:expected_attributes) { { project: new_project, type: yet_another_type } }
|
|
|
|
it "sets the desired type" do
|
|
subject
|
|
|
|
expect(work_package.type)
|
|
.to eql yet_another_type
|
|
end
|
|
|
|
it "does not set the change to system changes" do
|
|
subject
|
|
|
|
expect(work_package.changed_by_system)
|
|
.not_to include("type_id")
|
|
end
|
|
end
|
|
end
|
|
|
|
context "for parent" do
|
|
let(:parent_work_package) { build_stubbed(:work_package, project:) }
|
|
let(:work_package) do
|
|
build_stubbed(:work_package, project:, type: initial_type, parent: parent_work_package)
|
|
end
|
|
|
|
context "with cross project relations allowed", with_settings: { cross_project_work_package_relations: true } do
|
|
it "keeps the parent" do
|
|
expect(subject)
|
|
.to be_success
|
|
|
|
expect(work_package.parent)
|
|
.to eql(parent_work_package)
|
|
end
|
|
end
|
|
|
|
context "with cross project relations disabled", with_settings: { cross_project_work_package_relations: false } do
|
|
it "deletes the parent" do
|
|
expect(subject)
|
|
.to be_success
|
|
|
|
expect(work_package.parent)
|
|
.to be_nil
|
|
end
|
|
end
|
|
end
|
|
|
|
context "for semantic identifier" do
|
|
let(:work_package) do
|
|
build_stubbed(:work_package, project:, sequence_number: 7, identifier: "OLD-7")
|
|
end
|
|
|
|
it "clears sequence_number" do
|
|
subject
|
|
|
|
expect(work_package.sequence_number).to be_nil
|
|
end
|
|
|
|
it "clears identifier" do
|
|
subject
|
|
|
|
expect(work_package.identifier).to be_nil
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when updating project before calling the service" do
|
|
let(:call_attributes) { {} }
|
|
let(:expected_attributes) { { project: new_project } }
|
|
|
|
before do
|
|
work_package.attributes = expected_attributes
|
|
end
|
|
|
|
it_behaves_like "service call" do
|
|
it_behaves_like "updating the project"
|
|
end
|
|
end
|
|
|
|
context "when updating project via attributes" do
|
|
let(:call_attributes) { expected_attributes }
|
|
let(:expected_attributes) { { project: new_project } }
|
|
|
|
it_behaves_like "service call" do
|
|
it_behaves_like "updating the project"
|
|
end
|
|
end
|
|
end
|
|
|
|
context "for custom fields" do
|
|
subject { instance.call(call_attributes) }
|
|
|
|
context "for non existing fields" do
|
|
let(:call_attributes) { { custom_field_891: "1" } }
|
|
|
|
before do
|
|
subject
|
|
end
|
|
|
|
it "is successful" do
|
|
expect(subject).to be_success
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when switching back to automatic scheduling for a successor" do
|
|
shared_let(:project) { create(:project) }
|
|
let(:predecessor) do
|
|
create(:work_package,
|
|
subject: "predecessor",
|
|
project:,
|
|
ignore_non_working_days: true,
|
|
schedule_manually: true,
|
|
start_date: soonest_start - 1.day,
|
|
due_date: soonest_start - 1.day)
|
|
end
|
|
let(:work_package) do
|
|
create(:work_package,
|
|
subject: "work_package",
|
|
project:,
|
|
ignore_non_working_days: true,
|
|
schedule_manually: true,
|
|
start_date: Time.zone.today,
|
|
due_date: Time.zone.today + 5.days) do |wp|
|
|
create(:follows_relation, from: wp, to: predecessor)
|
|
end
|
|
end
|
|
let(:call_attributes) { { schedule_manually: false } }
|
|
let(:expected_attributes) { {} }
|
|
let(:soonest_start) { Time.zone.today + 1.day }
|
|
|
|
context "when the soonest start date is later than the current start date" do
|
|
let(:soonest_start) { Time.zone.today + 3.days }
|
|
|
|
include_examples "service call", description: "sets the start date to the soonest possible start date" do
|
|
let(:expected_attributes) do
|
|
{
|
|
start_date: Time.zone.today + 3.days,
|
|
due_date: Time.zone.today + 8.days
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when the soonest start date is earlier than the current start date" do
|
|
let(:soonest_start) { Time.zone.today - 3.days }
|
|
|
|
include_examples "service call", description: "sets the start date to the soonest possible start date" do
|
|
let(:expected_attributes) do
|
|
{
|
|
start_date: Time.zone.today - 3.days,
|
|
due_date: Time.zone.today + 2.days
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when the soonest start date is a non-working day" do
|
|
shared_let(:working_days) { week_with_saturday_and_sunday_as_weekend }
|
|
let(:saturday) { Time.zone.today.beginning_of_week.next_occurring(:saturday) }
|
|
let(:next_monday) { saturday.next_occurring(:monday) }
|
|
let(:soonest_start) { saturday }
|
|
|
|
before do
|
|
work_package.ignore_non_working_days = false
|
|
end
|
|
|
|
include_examples "service call",
|
|
description: "sets the start date to the soonest possible start date being a working day" do
|
|
let(:expected_attributes) do
|
|
{
|
|
start_date: next_monday,
|
|
due_date: WorkPackages::Shared::WorkingDays.new.soonest_working_day(Time.zone.today + 5.days)
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when the soonest start date is before the current start date" do
|
|
let(:soonest_start) { Time.zone.today - 3.days }
|
|
|
|
include_examples "service call", description: "sets the start date to the soonest possible start date" do
|
|
let(:expected_attributes) do
|
|
{
|
|
start_date: soonest_start,
|
|
due_date: Time.zone.today + 2.days
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when the soonest start date is nil (no predecessors)" do
|
|
before do
|
|
work_package # create the relation
|
|
predecessor.destroy # destroy the predecessor AND the relation
|
|
end
|
|
|
|
include_examples "service call", description: "sets the start date to the soonest possible start date" do
|
|
let(:expected_attributes) do
|
|
{
|
|
start_date: Time.zone.today,
|
|
due_date: Time.zone.today + 5.days
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when the work package also has a manually scheduled child" do
|
|
let!(:child) do
|
|
create(:work_package,
|
|
subject: "child",
|
|
parent: work_package,
|
|
schedule_manually: true,
|
|
start_date: child_start_date,
|
|
due_date: child_due_date)
|
|
end
|
|
let(:child_start_date) { Time.zone.today + 2.days }
|
|
let(:child_due_date) { Time.zone.today + 10.days }
|
|
|
|
context "when the soonest start is before the child's start date" do
|
|
let(:soonest_start) { child_start_date - 1.day }
|
|
|
|
include_examples "service call",
|
|
description: "sets the dates to the child dates, without moving parent to soonest start" do
|
|
let(:expected_attributes) do
|
|
{
|
|
start_date: child_start_date,
|
|
due_date: child_due_date
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when the soonest start is after the child's start date" do
|
|
let(:soonest_start) { child_start_date + 1.day }
|
|
|
|
include_examples "service call",
|
|
description: "sets the dates to the child dates, regardless of the soonest date" do
|
|
let(:expected_attributes) do
|
|
{
|
|
start_date: child_start_date,
|
|
due_date: child_due_date
|
|
}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when switching back to automatic scheduling for a parent" do
|
|
shared_let(:project) { create(:project) }
|
|
let!(:work_package) do
|
|
create(:work_package,
|
|
subject: "work_package",
|
|
project:,
|
|
ignore_non_working_days: true,
|
|
schedule_manually: true,
|
|
start_date: Time.zone.today,
|
|
due_date: Time.zone.today + 5.days)
|
|
end
|
|
let!(:child) do
|
|
create(:work_package,
|
|
subject: "child",
|
|
project:,
|
|
parent: work_package,
|
|
ignore_non_working_days: true,
|
|
schedule_manually: true,
|
|
start_date: child_start_date,
|
|
due_date: child_due_date)
|
|
end
|
|
let(:call_attributes) { { schedule_manually: false } }
|
|
|
|
context "when the child has dates" do
|
|
let(:child_start_date) { Time.zone.today + 2.days }
|
|
let(:child_due_date) { Time.zone.today + 3.days }
|
|
|
|
include_examples "service call", description: "sets the parent dates to the child dates" do
|
|
let(:expected_attributes) do
|
|
{
|
|
start_date: child_start_date,
|
|
due_date: child_due_date
|
|
}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "ignore_non_working_days when switching back to automatic scheduling" do
|
|
shared_let(:project) { create(:project) }
|
|
let!(:work_package) do
|
|
create(:work_package,
|
|
subject: "work_package",
|
|
project:,
|
|
ignore_non_working_days:,
|
|
schedule_manually: true)
|
|
end
|
|
let(:call_attributes) { { schedule_manually: false } }
|
|
|
|
context "without any children" do
|
|
context "when ignoring non working days" do
|
|
let(:ignore_non_working_days) { true }
|
|
|
|
include_examples "service call", description: "keeps its ignore non-working days value" do
|
|
let(:expected_attributes) do
|
|
{
|
|
ignore_non_working_days: true
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when not ignoring non working days" do
|
|
let(:ignore_non_working_days) { false }
|
|
|
|
include_examples "service call", description: "keeps its ignore non-working days value" do
|
|
let(:expected_attributes) do
|
|
{
|
|
ignore_non_working_days: false
|
|
}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with one child ignoring non working days" do
|
|
let(:ignore_non_working_days) { false }
|
|
let!(:child) do
|
|
create(:work_package,
|
|
subject: "child",
|
|
project:,
|
|
parent: work_package,
|
|
ignore_non_working_days: true)
|
|
end
|
|
|
|
include_examples "service call", description: "sets the parent to ignore non-working days" do
|
|
let(:expected_attributes) do
|
|
{
|
|
ignore_non_working_days: true
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with one child not ignoring non working days" do
|
|
let(:ignore_non_working_days) { true }
|
|
let!(:child) do
|
|
create(:work_package,
|
|
subject: "child",
|
|
project:,
|
|
parent: work_package,
|
|
ignore_non_working_days: false)
|
|
end
|
|
|
|
include_examples "service call", description: "sets the parent to not ignore non-working days" do
|
|
let(:expected_attributes) do
|
|
{
|
|
ignore_non_working_days: false
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with two children: one ignoring and the other not ignoring non working days" do
|
|
let(:ignore_non_working_days) { false }
|
|
let!(:child1) do
|
|
create(:work_package,
|
|
subject: "child",
|
|
project:,
|
|
parent: work_package,
|
|
ignore_non_working_days: false)
|
|
end
|
|
let!(:child2) do
|
|
create(:work_package,
|
|
subject: "child",
|
|
project:,
|
|
parent: work_package,
|
|
ignore_non_working_days: true)
|
|
end
|
|
|
|
include_examples "service call", description: "sets the parent to ignore non-working days" do
|
|
let(:expected_attributes) do
|
|
{
|
|
ignore_non_working_days: true
|
|
}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when the type defines a pattern for subject" do
|
|
let(:type) { build_stubbed(:type, patterns: { subject: { blueprint: "{{type}} {{project_name}}", enabled: true } }) }
|
|
let(:work_package) { WorkPackage.new(type:, project:) }
|
|
let(:resolved_subject) { "#{type.name} #{project.name}" }
|
|
let(:pattern_resolver) do
|
|
instance_double(WorkPackageTypes::PatternResolver, resolve: resolved_subject).tap do |resolver|
|
|
allow(WorkPackageTypes::PatternResolver).to receive(:new).and_return(resolver)
|
|
end
|
|
end
|
|
|
|
# Testing this because the behaviour used to be different.
|
|
it "does not set the resolved subject from the pattern" do
|
|
instance.call({})
|
|
|
|
expect(work_package.subject).to be_blank
|
|
end
|
|
|
|
it "keeps an overridden subject" do
|
|
instance.call(subject: "My custom subject")
|
|
|
|
expect(work_package.subject).to eq("My custom subject")
|
|
end
|
|
end
|
|
end
|