OpenProject is the leading open source project management software.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
openproject/spec/services/projects/copy_service_integration_sp...

675 lines
24 KiB

#-- encoding: UTF-8
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2021 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'
describe Projects::CopyService, 'integration', type: :model do
shared_let(:status_locked) { FactoryBot.create :status, is_readonly: true }
shared_let(:source) { FactoryBot.create :project, enabled_module_names: %w[wiki work_package_tracking] }
shared_let(:source_wp) { FactoryBot.create :work_package, project: source, subject: 'source wp' }
shared_let(:source_wp_locked) do
FactoryBot.create :work_package, project: source, subject: 'source wp locked', status: status_locked
end
shared_let(:source_query) { FactoryBot.create :query, project: source, name: 'My query' }
shared_let(:source_category) { FactoryBot.create :category, project: source, name: 'Stock management' }
shared_let(:source_version) { FactoryBot.create :version, project: source, name: 'Version A' }
shared_let(:source_wiki_page) { FactoryBot.create(:wiki_page_with_content, wiki: source.wiki) }
shared_let(:source_child_wiki_page) { FactoryBot.create(:wiki_page_with_content, wiki: source.wiki, parent: source_wiki_page) }
shared_let(:source_forum) { FactoryBot.create(:forum, project: source) }
shared_let(:source_topic) { FactoryBot.create(:message, forum: source_forum) }
let(:current_user) do
FactoryBot.create(:user,
member_in_project: source,
member_through_role: role)
end
let(:role) { FactoryBot.create :role, permissions: %i[copy_projects view_work_packages] }
shared_let(:new_project_role) { FactoryBot.create :role, permissions: %i[] }
let(:instance) do
described_class.new(source: source, user: current_user)
end
let(:only_args) { nil }
let(:target_project_params) do
{ name: 'Some name', identifier: 'some-identifier' }
end
let(:params) do
{ target_project_params: target_project_params, only: only_args }
end
before do
with_enterprise_token(:readonly_work_packages)
allow(Setting)
.to receive(:new_project_user_role_id)
.and_return(new_project_role.id.to_s)
end
describe 'call' do
subject { instance.call(params) }
let(:project_copy) { subject.result }
context 'restricting only to members and categories' do
let(:only_args) { %w[members categories] }
it 'should limit copying' do
expect(subject).to be_success
expect(project_copy.members.count).to eq 1
expect(project_copy.categories.count).to eq 1
expect(project_copy.work_packages.count).to eq 0
expect(project_copy.forums.count).to eq 0
expect(project_copy.wiki.pages.count).to eq 0
expect(project_copy.versions.count).to eq 0
expect(project_copy.queries.count).to eq 0
end
end
it 'will copy all dependencies and set attributes' do
expect(subject).to be_success
expect(project_copy.members.count).to eq 1
expect(project_copy.categories.count).to eq 1
# normal wp and locked wp
expect(project_copy.work_packages.count).to eq 2
expect(project_copy.forums.count).to eq 1
expect(project_copy.forums.first.messages.count).to eq 1
expect(project_copy.wiki).to be_present
expect(project_copy.wiki.pages.count).to eq 2
expect(project_copy.queries.count).to eq 1
expect(project_copy.versions.count).to eq 1
expect(project_copy.wiki.pages.root.content.text).to eq source_wiki_page.content.text
expect(project_copy.wiki.pages.leaves.first.content.text).to eq source_child_wiki_page.content.text
expect(project_copy.wiki.start_page).to eq 'Wiki'
# Cleared attributes
expect(project_copy).to be_persisted
expect(project_copy.name).to eq 'Some name'
expect(project_copy.name).to eq 'Some name'
expect(project_copy.identifier).to eq 'some-identifier'
# Duplicated attributes
expect(project_copy.description).to eq source.description
expect(source.enabled_module_names.sort - %w[repository]).to eq project_copy.enabled_module_names.sort
expect(project_copy.types).to eq source.types
# Default attributes
expect(project_copy).to be_active
# Default role being assigned according to setting
# merged with the role the user already had.
member = project_copy.members.first
expect(member.principal)
.to eql(current_user)
expect(member.roles)
.to match_array [role, new_project_role]
end
it 'will copy the work package with category' do
source_wp.update!(category: source_category)
expect(subject).to be_success
wp = project_copy.work_packages.find_by(subject: source_wp.subject)
expect(wp.category.name).to eq 'Stock management'
# Category got copied
expect(wp.category.id).not_to eq source_category.id
end
describe '#public' do
before do
source.update!(public: public)
end
context 'when not public' do
let(:public) { false }
it 'copies correctly' do
expect(subject).to be_success
expect(project_copy.public).to eq public
end
end
context 'when public' do
let(:public) { true }
it 'copies correctly' do
expect(subject).to be_success
expect(project_copy.public).to eq public
end
end
end
context 'with an assigned version' do
let!(:assigned_version) { FactoryBot.create(:version, name: 'Assigned Issues', project: source, status: 'open') }
before do
source_wp.update!(version: assigned_version)
assigned_version.update!(status: 'closed')
end
it 'will update the version' do
expect(subject).to be_success
wp = project_copy.work_packages.find_by(subject: source_wp.subject)
expect(wp.version.name).to eq 'Assigned Issues'
expect(wp.version).to be_closed
expect(wp.version.id).not_to eq assigned_version.id
end
end
context 'with group memberships' do
let(:only_args) { %w[members] }
let!(:user) { FactoryBot.create :user }
let!(:another_role) { FactoryBot.create(:role) }
let!(:group) do
FactoryBot.create :group, members: [user]
end
it 'will copy them as well' do
Members::CreateService
.new(user: current_user, contract_class: EmptyContract)
.call(principal: group, roles: [another_role], project: source)
source.users.reload
expect(source.users).to include current_user
expect(source.users).to include user
expect(project_copy.groups).to include group
expect(source.member_principals.count).to eq 3
expect(subject).to be_success
expect(project_copy.member_principals.count).to eq 3
expect(project_copy.groups).to include group
expect(project_copy.users).to include current_user
expect(project_copy.users).to include user
group_member = Member.find_by(user_id: group.id, project_id: project_copy.id)
expect(group_member).to be_present
expect(group_member.roles.map(&:id)).to eq [another_role.id]
member = Member.find_by(user_id: user.id, project_id: project_copy.id)
expect(member).to be_present
expect(member.roles.map(&:id)).to eq [another_role.id]
expect(member.member_roles.first.inherited_from).to eq group_member.member_roles.first.id
end
end
context 'with work package relations', with_settings: { cross_project_work_package_relations: '1' } do
let!(:source_wp2) { FactoryBot.create(:work_package, project: source, subject: 'source wp2') }
let!(:source_relation) { FactoryBot.create(:relation, from: source_wp, to: source_wp2, relation_type: 'relates') }
let!(:other_project) { FactoryBot.create(:project) }
let!(:other_wp) { FactoryBot.create(:work_package, project: other_project, subject: 'other wp') }
let!(:cross_relation) { FactoryBot.create(:relation, from: source_wp, to: other_wp, relation_type: 'duplicates') }
let(:only_args) { %w[work_packages] }
it 'should the relations relations' do
expect(subject).to be_success
expect(source.work_packages.count).to eq(project_copy.work_packages.count)
copied_wp = project_copy.work_packages.find_by(subject: 'source wp')
copied_wp_2 = project_copy.work_packages.find_by(subject: 'source wp2')
# First issue with a relation on project
# copied relation + reflexive relation
expect(copied_wp.relations.direct.count).to eq 2
relates_relation = copied_wp.relations.direct.find { |r| r.relation_type == 'relates' }
expect(relates_relation.from_id).to eq copied_wp.id
expect(relates_relation.to_id).to eq copied_wp_2.id
# Second issue with a cross project relation
# copied relation + reflexive relation
duplicates_relation = copied_wp.relations.direct.find { |r| r.relation_type == 'duplicates' }
expect(duplicates_relation.from_id).to eq copied_wp.id
expect(duplicates_relation.to_id).to eq other_wp.id
end
end
describe '#copy_wiki' do
it 'will not copy wiki pages without content' do
source.wiki.pages << FactoryBot.create(:wiki_page)
expect(source.wiki.pages.count).to eq 3
expect(subject).to be_success
expect(subject.errors).to be_empty
expect(project_copy.wiki.pages.count).to eq 2
end
it 'will copy menu items' do
source.wiki.wiki_menu_items << FactoryBot.create(:wiki_menu_item_with_parent, wiki: source.wiki)
expect(subject).to be_success
expect(project_copy.wiki.wiki_menu_items.count).to eq 3
end
end
describe 'valid queries' do
context 'with a filter' do
let!(:query) do
query = FactoryBot.build(:query, project: source)
query.add_filter('subject', '~', ['bogus'])
query.save!
end
it 'produces a valid query in the new project' do
expect(subject).to be_success
expect(project_copy.queries.all?(&:valid?)).to eq(true)
expect(project_copy.queries.count).to eq 2
end
end
context 'with a filter to be mapped' do
let!(:query) do
query = FactoryBot.build(:query, project: source)
query.add_filter('parent', '=', [source_wp.id.to_s])
# Not valid due to wp not visible
query.save!(validate: false)
query
end
it 'produces a valid query that is mapped in the new project' do
expect(subject).to be_success
copied_wp = project_copy.work_packages.find_by(subject: 'source wp')
copied = project_copy.queries.find_by(name: query.name)
expect(copied.filters[1].values).to eq [copied_wp.id.to_s]
end
end
end
describe 'query menu items' do
let!(:query) do
query = FactoryBot.build(:query, project: source, name: 'Query with item')
query.add_filter('subject', '~', ['bogus'])
query.save!
MenuItems::QueryMenuItem.create(
navigatable_id: query.id,
name: 'some-uuid',
title: 'My query title'
)
query
end
it 'copies the menu item' do
expect(subject).to be_success
query = project_copy.queries.find_by(name: 'Query with item')
expect(query).to be_present
expect(query.query_menu_item.title).to eq('My query title')
end
end
describe 'work packages' do
let(:work_package) { FactoryBot.create(:work_package, project: source) }
let(:work_package2) { FactoryBot.create(:work_package, project: source) }
let(:work_package3) { FactoryBot.create(:work_package, project: source) }
let(:only_args) { %w[work_packages] }
describe '#attachments' do
let!(:attachment) { FactoryBot.create(:attachment, container: work_package) }
context 'when requested' do
let(:only_args) { %i[work_packages work_package_attachments] }
it 'copies them' do
expect(subject).to be_success
expect(project_copy.work_packages.count).to eq(3)
wp = project_copy.work_packages.find_by(subject: work_package.subject)
expect(wp.attachments.count).to eq(1)
expect(wp.attachments.first.author).to eql(current_user)
end
end
context 'when not requested' do
it 'ignores them' do
expect(subject).to be_success
expect(project_copy.work_packages.count).to eq(3)
wp = project_copy.work_packages.find_by(subject: work_package.subject)
expect(wp.attachments.count).to eq(0)
end
end
end
describe 'in an ordered query (Feature #31317)' do
let!(:query) do
FactoryBot.create(:query, name: 'Manual query', user: current_user, project: source, show_hierarchies: false).tap do |q|
q.sort_criteria = [[:manual_sorting, 'asc']]
q.save!
end
end
before do
::OrderedWorkPackage.create(query: query, work_package: work_package, position: 100)
::OrderedWorkPackage.create(query: query, work_package: work_package2, position: 0)
::OrderedWorkPackage.create(query: query, work_package: work_package3, position: 50)
end
let(:only_args) { %w[work_packages queries] }
it 'copies the query and order' do
expect(subject).to be_success
expect(project_copy.work_packages.count).to eq(5)
expect(project_copy.queries.count).to eq(2)
manual_query = project_copy.queries.find_by name: 'Manual query'
expect(manual_query).to be_manually_sorted
expect(query.ordered_work_packages.count).to eq 3
original_order = query.ordered_work_packages.map { |ow| ow.work_package.subject }
copied_order = manual_query.ordered_work_packages.map { |ow| ow.work_package.subject }
expect(copied_order).to eq(original_order)
end
context 'if one work package is a cross project reference' do
let(:other_project) { FactoryBot.create :project }
before do
work_package2.update! project: other_project
end
let(:only_args) { %w[work_packages queries] }
it 'copies the query and order' do
expect(subject).to be_success
# Only 4 out of the 5 work packages got copied this time
expect(project_copy.work_packages.count).to eq(4)
expect(project_copy.queries.count).to eq(2)
manual_query = project_copy.queries.find_by name: 'Manual query'
expect(manual_query).to be_manually_sorted
expect(query.ordered_work_packages.count).to eq 3
original_order = query.ordered_work_packages.map { |ow| ow.work_package.subject }
copied_order = manual_query.ordered_work_packages.map { |ow| ow.work_package.subject }
expect(copied_order).to eq(original_order)
# Expect reference to the original work package
referenced = query.ordered_work_packages.detect { |ow| ow.work_package == work_package2 }
expect(referenced).to be_present
end
end
end
describe '#parent' do
before do
work_package.parent = work_package2
work_package.save!
work_package2.parent = work_package3
work_package2.save!
end
it do
expect(subject).to be_success
grandparent_wp_copy = project_copy.work_packages.find_by(subject: work_package3.subject)
parent_wp_copy = project_copy.work_packages.find_by(subject: work_package2.subject)
child_wp_copy = project_copy.work_packages.find_by(subject: work_package.subject)
[grandparent_wp_copy,
parent_wp_copy,
child_wp_copy].each do |wp|
expect(wp).to be_present
end
expect(child_wp_copy.parent).to eq(parent_wp_copy)
expect(parent_wp_copy.parent).to eq(grandparent_wp_copy)
end
end
describe '#category' do
let(:only_args) { %w[work_packages categories] }
before do
wp = work_package
wp.category = FactoryBot.create(:category, project: source)
wp.save
source.work_packages << wp.reload
end
it do
expect(subject).to be_success
wp = project_copy.work_packages.find_by(subject: work_package.subject)
expect(cat = wp.category).not_to eq(nil)
expect(cat.project).to eq(project_copy)
end
end
describe '#watchers' do
let(:watcher_role) { FactoryBot.create(:role, permissions: [:view_work_packages]) }
let(:watcher) { FactoryBot.create(:user, member_in_project: source, member_through_role: watcher_role) }
let(:only_args) { %w[work_packages members] }
describe '#active_watcher' do
before do
wp = work_package
wp.add_watcher watcher
wp.save
source.work_packages << wp
end
it 'does copy active watchers but does not add the copying user as a watcher' do
expect(subject).to be_success
expect(project_copy.work_packages[0].watcher_users)
.to match_array([watcher])
end
end
describe '#locked_watcher' do
before do
user = watcher
wp = work_package
wp.add_watcher user
wp.save
user.lock!
source.work_packages << wp
end
it 'does not copy locked watchers and does not add the copying user as a watcher' do
expect(subject).to be_success
expect(project_copy.work_packages[0].watcher_users).to be_empty
end
end
describe 'versions' do
let(:version) { FactoryBot.create(:version, project: source) }
let(:version2) { FactoryBot.create(:version, project: source) }
let(:only_args) { %w[versions work_packages] }
before do
work_package.update_column(:version_id, version.id)
work_package2.update_column(:version_id, version2.id)
work_package3
end
it 'assigns the work packages to copies of the versions' do
expect(subject).to be_success
expect(project_copy.work_packages.detect { |wp| wp.subject == work_package.subject }.version.name)
.to eql version.name
expect(project_copy.work_packages.detect { |wp| wp.subject == work_package2.subject }.version.name)
.to eql version2.name
expect(project_copy.work_packages.detect { |wp| wp.subject == work_package3.subject }.version)
.to be_nil
end
end
end
describe 'assigned_to' do
before do
work_package.update_column(:assigned_to_id, current_user.id)
end
context 'with the members being copied' do
let(:only_args) { %w[members work_packages] }
it 'copies the assigned_to' do
expect(subject).to be_success
expect(project_copy.users).to include current_user
expect(project_copy.work_packages[0].assigned_to)
.to eql current_user
end
end
context 'with the assignee not being a member' do
let(:only_args) { %w[work_packages] }
it 'nils the assigned_to' do
expect(subject).to be_success
expect(project_copy.work_packages[0].assigned_to)
.to be_nil
end
end
end
describe 'responsible' do
before do
work_package.update_column(:responsible_id, current_user.id)
end
context 'with the members being copied' do
let(:only_args) { %w[members work_packages] }
it 'copies the responsible' do
expect(subject).to be_success
expect(project_copy.users).to include current_user
expect(project_copy.work_packages[0].responsible)
.to eql current_user
end
end
context 'with the responsible not being a member' do
let(:only_args) { %w[work_packages] }
it 'nils the assigned_to' do
expect(subject).to be_success
expect(project_copy.work_packages[0].responsible)
.to be_nil
end
end
end
describe 'work package user custom field' do
let(:custom_field) do
FactoryBot.create(:user_wp_custom_field).tap do |cf|
source.work_package_custom_fields << cf
work_package.type.custom_fields << cf
end
end
before do
custom_field
work_package.reload
work_package.send(:"custom_field_#{custom_field.id}=", current_user.id)
work_package.save!(validate: false)
end
context 'with the value being a member' do
let(:only_args) { %w[members work_packages] }
it 'copies the custom_field' do
expect(subject).to be_success
wp = project_copy.work_packages.find_by(subject: work_package.subject)
expect(wp.send(:"custom_field_#{custom_field.id}"))
.to eql current_user
end
end
context 'with the value not being a member' do
let(:only_args) { %w[work_packages] }
it 'nils the custom_field' do
expect(subject).to be_success
wp = project_copy.work_packages.find_by(subject: work_package.subject)
expect(wp.send(:"custom_field_#{custom_field.id}"))
.to be_nil
end
end
end
end
describe 'project custom fields' do
context 'with user project CF' do
let(:user_custom_field) { FactoryBot.create(:user_project_custom_field) }
let(:user_value) do
FactoryBot.create(:user,
member_in_project: source,
member_through_role: role)
end
before do
source.custom_values << CustomValue.new(custom_field: user_custom_field, value: user_value.id.to_s)
end
let(:only_args) { %w[wiki] }
it 'copies the custom_field' do
expect(subject).to be_success
cv = project_copy.custom_values.reload.find_by(custom_field: user_custom_field)
expect(cv).to be_present
expect(cv.value).to eq user_value.id.to_s
expect(cv.typed_value).to eq user_value
end
end
context 'with multi selection project list CF' do
let(:list_custom_field) { FactoryBot.create(:list_project_custom_field, multi_value: true) }
before do
source.custom_values << CustomValue.new(custom_field: list_custom_field, value: list_custom_field.value_of('A'))
source.custom_values << CustomValue.new(custom_field: list_custom_field, value: list_custom_field.value_of('B'))
source.save!
end
let(:only_args) { %w[wiki] }
it 'copies the custom_field' do
expect(subject).to be_success
cv = project_copy.custom_values.reload.where(custom_field: list_custom_field).to_a
expect(cv).to be_kind_of Array
expect(cv.count).to eq 2
expect(cv.map(&:formatted_value)).to contain_exactly('A', 'B')
end
end
end
end
end