Files
openproject/spec/services/work_packages/copy_service_integration_spec.rb

413 lines
12 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::CopyService, "integration", type: :model do
shared_let(:custom_field) { create(:work_package_custom_field) }
shared_let(:type) do
create(:type_standard,
custom_fields: [custom_field])
end
shared_let(:project) { create(:project, types: [type]) }
shared_let(:user) do
create(:user, member_with_permissions: { project => %i[view_work_packages add_work_packages manage_subtasks] })
end
before_all do
set_factory_default(:project, project)
set_factory_default(:project_with_types, project)
set_factory_default(:type, type)
set_factory_default(:user, user)
end
shared_let(:project_phase_definition) { create(:project_phase_definition) }
shared_let(:work_package, reload: true) do
create(:work_package, author: user, project:, type:, project_phase_definition:)
end
let(:instance) { described_class.new(work_package:, user:) }
let(:custom_value) do
create(:work_package_custom_value,
custom_field:,
customized: work_package,
value: false)
end
let(:source_project) { project }
let(:source_type) { type }
let(:attributes) { {} }
let(:copy) do
service_result
.result
end
let(:service_result) do
instance
.call(**attributes)
end
current_user { user }
describe "#call" do
shared_examples_for "copied work package" do
subject { copy }
it { expect(subject.id).not_to eq(work_package.id) }
it { is_expected.to be_persisted }
end
context "with the same project" do
it_behaves_like "copied work package"
describe "#project" do
subject { copy.project }
it { is_expected.to eq(source_project) }
end
describe "copied watchers" do
let(:watcher_user) do
create(:user,
member_with_permissions: { source_project => %i(view_work_packages) })
end
before do
work_package.add_watcher(watcher_user)
end
it "copies the watcher and does not add the copying user as a watcher" do
expect(copy.watcher_users)
.to contain_exactly(watcher_user)
end
end
describe "#project_phase_definition" do
it "is the one of the copied work package" do
expect(copy.project_phase_definition)
.to eql project_phase_definition
end
end
end
describe "to a different project" do
let(:target_type) { create(:type, custom_fields: target_custom_fields) }
let(:target_project) do
p = create(:project,
types: [target_type],
work_package_custom_fields: target_custom_fields)
create(:member,
project: p,
roles: [target_role],
user:)
p
end
let(:target_custom_fields) { [] }
let(:target_role) { create(:project_role, permissions: target_permissions) }
let(:target_permissions) { %i(add_work_packages manage_subtasks) }
let(:attributes) { { project: target_project, type: target_type } }
it_behaves_like "copied work package"
context "project" do
subject { copy.project_id }
it { is_expected.to eq(target_project.id) }
end
context "type" do
subject { copy.type_id }
it { is_expected.to eq(target_type.id) }
end
context "custom_fields" do
before do
custom_value
end
subject { copy.custom_value_for(custom_field) }
it { is_expected.to be_nil }
end
context "required custom field in the target project" do
let(:custom_field) { create(:work_package_custom_field, field_format: "text", is_required: true, is_for_all: false) }
let(:target_custom_fields) { [custom_field] }
it "does not copy the work package" do
expect(service_result).to be_failure
end
end
context "with work, remaining work, and % complete set in the source work package" do
before do
work_package.update(
estimated_hours: 10.0,
remaining_hours: 6.0,
done_ratio: 40
)
end
it "copies them all values over" do
expect(copy.estimated_hours).to eq(10.0)
expect(copy.remaining_hours).to eq(6.0)
expect(copy.done_ratio).to eq(40)
end
end
context "with only % complete set in the source work package" do
before do
work_package.update(
estimated_hours: nil,
remaining_hours: nil,
done_ratio: 40
)
end
it "copies the % complete value over" do
expect(copy.done_ratio).to eq(40)
end
end
describe "#project_phase_definition" do
it "is the one of the copied work package" do
expect(copy.project_phase_definition)
.to eql project_phase_definition
end
end
describe "#attributes" do
before do
target_project.types << work_package.type
end
context "assigned_to" do
let(:target_user) { create(:user) }
let(:target_project_member) do
create(:member,
project: target_project,
principal: target_user,
roles: [create(:project_role, permissions: [:work_package_assigned])])
end
let(:attributes) { { project: target_project, assigned_to_id: target_user.id } }
before do
target_project_member
end
it_behaves_like "copied work package"
subject { copy.assigned_to_id }
it { is_expected.to eq(target_user.id) }
end
context "status" do
let(:target_status) { create(:status) }
let(:attributes) { { project: target_project, status_id: target_status.id } }
it_behaves_like "copied work package"
subject { copy.status_id }
it { is_expected.to eq(target_status.id) }
end
context "date" do
let(:target_date) { Date.today + 14 }
context "start" do
let(:attributes) { { project: target_project, start_date: target_date } }
it_behaves_like "copied work package"
subject { copy.start_date }
it { is_expected.to eq(target_date) }
end
context "end" do
let(:attributes) { { project: target_project, due_date: target_date } }
it_behaves_like "copied work package"
subject { copy.due_date }
it { is_expected.to eq(target_date) }
end
end
end
describe "with children" do
let(:instance) { described_class.new(work_package: child, user:) }
let!(:child) do
create(:work_package, parent: work_package, project: source_project)
end
let(:grandchild) do
create(:work_package, parent: child, project: source_project)
end
context "cross project relations deactivated" do
before do
allow(Setting)
.to receive(:cross_project_work_package_relations?)
.and_return(false)
end
it do
expect(service_result).to be_failure
end
it do
expect(child.reload.project).to eql(source_project)
end
describe "grandchild" do
before do
grandchild
end
it { expect(grandchild.reload.project).to eql(source_project) }
end
end
context "cross project relations activated" do
before do
allow(Setting).to receive(:cross_project_work_package_relations?).and_return(true)
end
it "is success" do
expect(service_result)
.to be_success
end
it "has the original parent as its parent" do
expect(copy.parent).to eql(child.parent)
end
it do
expect(copy.project).to eql(target_project)
end
describe "grandchild" do
before do
grandchild
end
it { expect(grandchild.reload.project).to eql(source_project) }
it { expect(copy.descendants).to be_empty }
end
end
end
end
describe "with start and due dates overwritten but not duration" do
let(:attributes) { { start_date: Time.zone.today - 5.days, due_date: Time.zone.today + 5.days } }
it_behaves_like "copied work package"
end
context "with attachments" do
let!(:attachment) do
create(:attachment,
container: work_package)
end
context "when specifying to copy attachments (default)" do
it "copies the attachment" do
expect(copy.attachments.length)
.to eq 1
expect(copy.attachments.first.attributes.slice(:digest, :file, :filesize))
.to eq attachment.attributes.slice(:digest, :file, :filesize)
expect(copy.attachments.first.id)
.not_to eq attachment.id
end
end
context "when referencing the attachment in the description" do
let(:text) do
<<~MARKDOWN
# Some text here
![attachment#{attachment.id}](/api/v3/attachments/#{attachment.id}/content)
MARKDOWN
end
before do
work_package.update_column(:description, text)
end
it "updates the attachment reference" do
expect(work_package.description).to include "/api/v3/attachments/#{attachment.id}/content"
expect(copy.attachments.length).to eq 1
expect(copy.attachments.first.id).not_to eq attachment.id
expect(copy.description).not_to include "/api/v3/attachments/#{attachment.id}/content"
expect(copy.description).to include "/api/v3/attachments/#{copy.attachments.first.id}/content"
end
end
context "when specifying to not copy attachments" do
let(:attributes) { { copy_attachments: false } }
it "copies the attachment" do
expect(copy.attachments.length)
.to eq 0
end
end
end
context "with a type auto-generating subjects" do
let(:type_with_pattern) do
create(:type, patterns: { subject: { blueprint: "{{type}} {{id}} {{project_name}}", enabled: true } }) do |type|
project.types << type
end
end
before do
work_package.update!(type: type_with_pattern)
end
it "is success" do
expect(service_result)
.to be_success
end
it "sets the auto generated subject" do
expect(copy.subject).to eq("#{type_with_pattern.name} #{copy.id} #{project.name}")
end
end
end
end