From a7147871c5a0a7bbac3bbe4f5e3a3258bc40de2b Mon Sep 17 00:00:00 2001 From: Marcello Rocha Date: Tue, 5 Mar 2024 23:31:19 +0100 Subject: [PATCH] Hooks the job to the other job --- app/services/projects/copy_service.rb | 6 +- .../one_drive/copy_template_folder_command.rb | 11 +- .../copy/storages_dependent_service.rb | 7 + .../file_links/copy_file_links_service.rb | 111 +++++++++ .../copy_project_folders_service.rb | 43 +--- .../storages/copy_project_folders_job.rb | 106 +++----- .../copy_template_folder_command_spec.rb | 8 +- .../copy_project_folders_service_spec.rb | 61 ++--- .../storages/copy_project_folders_job_spec.rb | 25 ++ .../projects/copy_service_integration_spec.rb | 227 +----------------- 10 files changed, 225 insertions(+), 380 deletions(-) create mode 100644 modules/storages/app/services/storages/file_links/copy_file_links_service.rb diff --git a/app/services/projects/copy_service.rb b/app/services/projects/copy_service.rb index dea7dff54097..90baf52fd2d1 100644 --- a/app/services/projects/copy_service.rb +++ b/app/services/projects/copy_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) 2012-2024 the OpenProject GmbH @@ -41,9 +43,7 @@ def self.copy_dependencies ::Projects::Copy::QueriesDependentService, ::Projects::Copy::BoardsDependentService, ::Projects::Copy::OverviewDependentService, - ::Projects::Copy::StoragesDependentService, - ::Projects::Copy::StorageProjectFoldersDependentService, - ::Projects::Copy::FileLinksDependentService + ::Projects::Copy::StoragesDependentService ] end diff --git a/modules/storages/app/common/storages/peripherals/storage_interaction/one_drive/copy_template_folder_command.rb b/modules/storages/app/common/storages/peripherals/storage_interaction/one_drive/copy_template_folder_command.rb index 3c9f58be8b72..1a226667ed8c 100644 --- a/modules/storages/app/common/storages/peripherals/storage_interaction/one_drive/copy_template_folder_command.rb +++ b/modules/storages/app/common/storages/peripherals/storage_interaction/one_drive/copy_template_folder_command.rb @@ -62,9 +62,7 @@ def call(source_location:, destination_name:) def handle_response(response) case response in { status: 202 } - id = extract_id_from_url(response.headers[:location]) - - ServiceResult.success(result: { id:, url: response.headers[:location] }) + ServiceResult.success(result: { id: nil, url: response.headers[:location] }) in { status: 401 } ServiceResult.failure(result: :unauthorized) in { status: 403 } @@ -78,13 +76,6 @@ def handle_response(response) end end - def extract_id_from_url(url) - extractor_regex = /.+\/items\/(?\w+)\?/ - match_data = extractor_regex.match(url) - - match_data[:item_id] if match_data - end - def copy_path_for(source_location) "/v1.0/drives/#{@storage.drive_id}/items/#{source_location}/copy?@microsoft.graph.conflictBehavior=fail" end diff --git a/modules/storages/app/services/projects/copy/storages_dependent_service.rb b/modules/storages/app/services/projects/copy/storages_dependent_service.rb index a90c577ab15f..c6bb319e3f87 100644 --- a/modules/storages/app/services/projects/copy/storages_dependent_service.rb +++ b/modules/storages/app/services/projects/copy/storages_dependent_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) 2012-2024 the OpenProject GmbH @@ -52,6 +54,11 @@ def copy_dependency(*) .on_failure { |r| add_error!(source_project_storage.class.to_s, r.to_active_model_errors) } .result + Storages::CopyProjectFoldersJob.perform_later(source_id: source_project_storage.id, + target_id: project_storage_copy.id, + user_id: User.current.id, + work_package_map: state.work_package_id_lookup) + array << { source: source_project_storage, target: project_storage_copy } end end diff --git a/modules/storages/app/services/storages/file_links/copy_file_links_service.rb b/modules/storages/app/services/storages/file_links/copy_file_links_service.rb new file mode 100644 index 000000000000..1c50496786c7 --- /dev/null +++ b/modules/storages/app/services/storages/file_links/copy_file_links_service.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 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. +#++ + +module Storages + module FileLinks + class CopyFileLinksService + def self.call(source:, target:, user:, work_packages_map:) + new(source:, target:, user:, work_packages_map:).call + end + + def initialize(source:, target:, user:, work_packages_map:) + @source = source + @target = target + @user = user + @work_packages_map = work_packages_map.to_h { |key, value| [key.to_i, value.to_i] } + end + + def call + source_file_links = FileLink + .includes(:creator) + .where(container_id: @work_packages_map.keys, container_type: "WorkPackage") + + return create_unmanaged_file_links(source_file_links) if @source.project_folder_manual? + + create_managed_file_links(source_file_links) + end + + private + + def create_managed_file_links(source_file_links) + target_files = target_files_map.result + source_files = source_files_info(source_file_links).result + + location_map = build_location_map(source_files, target_files) + + source_file_links.find_each do |source_link| + attributes = source_link.dup.attributes + + attributes.merge!( + 'creator_id' => @user.id, + 'container_id' => @work_packages_map[source_link.container_id], + 'origin_id' => location_map[source_link.origin_id] + ) + + FileLinks::CreateService.new(user: @user).call(attributes) + end + end + + def build_location_map(source_files, target_location_map) + source_location_map = source_files.to_h { |info| [info.id, info.location] } + + source_location_map.each_with_object({}) do |(id, location), output| + target = location.gsub(@source.managed_project_folder_path, @target.managed_project_folder_path) + + output[id] = target_location_map[target] + end + end + + def source_files_info(source_file_links) + Peripherals::Registry + .resolve("#{@source.storage.short_provider_type}.queries.files_info") + .call(storage: @source.storage, user: @user, file_ids: source_file_links.pluck(:origin_id)) + end + + def target_files_map + Peripherals::Registry + .resolve("#{@source.storage.short_provider_type}.queries.folder_files_file_ids_deep_query") + .call(storage: @source.storage, folder: Peripherals::ParentFolder.new(@target.project_folder_location)) + end + + def create_unmanaged_file_links(source_file_links) + source_file_links.find_each do |source_file_link| + attributes = source_file_link.dup.attributes + + attributes['creator_id'] = @user.id + attributes['container_id'] = @work_packages_map[source_file_link.container_id] + + # TODO: Do something when this fails + ::Storages::FileLinks::CreateService.new(user: @user).call(attributes) + end + end + end + end +end diff --git a/modules/storages/app/services/storages/project_storages/copy_project_folders_service.rb b/modules/storages/app/services/storages/project_storages/copy_project_folders_service.rb index edb899a23ac4..2cb9af09f5e8 100644 --- a/modules/storages/app/services/storages/project_storages/copy_project_folders_service.rb +++ b/modules/storages/app/services/storages/project_storages/copy_project_folders_service.rb @@ -32,44 +32,19 @@ module Storages module ProjectStorages class CopyProjectFoldersService # We might need the User too - def self.call(source_id:, target_id:) - new(source_id, target_id).call + def self.call(source:, target:) + new.call(source, target) end - def initialize(source_id, target_id) - @source = get_project_storage(source_id) - @target = get_project_storage(target_id) - end - - def call - return ServiceResult.success if @source.project_folder_inactive? - return update_target(@source.project_folder_id) if @source.project_folder_manual? - - copy_result = copy_project_folder.on_failure { |failed_result| return failed_result }.result - - update_target(copy_result[:id]) if copy_result[:id] - - ServiceResult.failure(result: copy_result[:url], errors: :polling_required) - end + def call(source, target) + return ServiceResult.success(result: { id: nil }) if source.project_folder_inactive? + return ServiceResult.success(result: { id: source.project_folder_id }) if source.project_folder_manual? - private - - def copy_project_folder Peripherals::Registry - .resolve("#{@source.storage.short_provider_type}.commands.copy_template_folder") - .call(storage: @source.storage, - source_path: @source.project_folder_location, - destination_path: @target.managed_project_folder_path) - end - - def update_target(project_folder_id) - ProjectStorages::UpdateService - .new(user: User.system, model: @target) - .call({ project_folder_id:, project_folder_mode: @source.project_folder_mode }) - end - - def get_project_storage(id) - ProjectStorage.includes(:project, :storage).find(id) + .resolve("#{source.storage.short_provider_type}.commands.copy_template_folder") + .call(storage: source.storage, + source_path: source.project_folder_location, + destination_path: target.managed_project_folder_path) end end end diff --git a/modules/storages/app/workers/storages/copy_project_folders_job.rb b/modules/storages/app/workers/storages/copy_project_folders_job.rb index 5f28078abe0c..7f0a2f1ec3e7 100644 --- a/modules/storages/app/workers/storages/copy_project_folders_job.rb +++ b/modules/storages/app/workers/storages/copy_project_folders_job.rb @@ -1,6 +1,31 @@ # frozen_string_literal: true #-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 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. #++ module Storages @@ -14,91 +39,34 @@ def perform(user_id:, source_id:, target_id:, work_package_map:) target = ProjectStorage.find(target_id) source = ProjectStorage.find(source_id) user = User.find(user_id) - new_work_package_map = work_package_map.transform_keys(&:to_i) + # TODO: Do Something when this fails project_folder_result = if polling? results_from_polling else - initiate_project_folder_copy(source, target) + ProjectStorages::CopyProjectFoldersService + .call(source:, target:) + .on_success { |success| prepare_polling(success.result) } end - project_folder_id = project_folder_result.on_failure { |failed_result| return failed_result }.result - # TODO: Do Something when this fails ProjectStorages::UpdateService.new(user:, model: target) - .call(project_folder_id:, project_folder_mode: source.project_folder_mode) + .call(project_folder_id: project_folder_result.result[:id], + project_folder_mode: source.project_folder_mode) - # We only get here on a successful execution - create_target_file_links(source, target, new_work_package_map, user) + # TODO: Collect errors + FileLinks::CopyFileLinksService.call(source:, target:, user:, work_packages_map: work_package_map) end private - def create_target_file_links(source, target, work_package_map, user) - source_file_links = FileLink - .includes(:creator) - .where(container_id: work_package_map.keys, container_type: "WorkPackage") - - return create_unmanaged_file_links(source_file_links, work_package_map, user) if source.project_folder_manual? - - target_files = Peripherals::Registry - .resolve("#{source.storage.short_provider_type}.queries.folder_files_file_ids_deep_query") - .call(storage: source.storage, folder: Peripherals::ParentFolder.new(target.project_folder_location)) - .result - - source_files = Peripherals::Registry - .resolve("#{source.storage.short_provider_type}.queries.files_info") - .call(storage: source.storage, user:, file_ids: source_file_links.pluck(:origin_id)) - .result - - source_location_map = source_files.to_h { |info| [info.id, info.location] } - - source_file_links.find_each do |source_link| - attributes = source_link.dup.attributes - - attributes['creator_id'] = user.id - attributes['container_id'] = work_package_map[source_link.container_id] - - source_link_location = source_location_map[source_link.origin_id] - target_link_location = source_link_location.gsub(source.managed_project_folder_path, target.managed_project_folder_path) - - attributes['origin_id'] = target_files[target_link_location] + def prepare_polling(result) + return if result[:id] - FileLinks::CreateService.new(user:).call(attributes) - end - end - - def create_unmanaged_file_links(source_file_links, work_package_map, user) - source_file_links.find_each do |source_file_link| - attributes = source_file_link.dup.attributes - - attributes['creator_id'] = user.id - attributes['container_id'] = work_package_map[source_file_link.container_id] - - # TODO: Do something when this fails - FileLinks::CreateService.new(user:).call(attributes) - end - end - - def initiate_project_folder_copy(source, target) - return ServiceResult.success if source.project_folder_inactive? - return ServiceResult.success(result: source.project_folder_id) if source.project_folder_manual? - - copy_result = issue_command(source, target).on_failure { |failed_result| return failed_result }.result - return ServiceResult.success(result: copy_result[:id]) if copy_result[:id] - - Thread.current[job_id] = copy_result[:url] + Thread.current[job_id] = result[:url] raise Errors::PollingRequired, "#{job_id} Storage requires polling" end - def issue_command(source, target) - Peripherals::Registry - .resolve("#{source.storage.short_provider_type}.commands.copy_template_folder") - .call(storage: source.storage, - source_path: source.project_folder_location, - destination_path: target.managed_project_folder_path) - end - def polling? !!Thread.current[job_id] end @@ -110,7 +78,7 @@ def results_from_polling raise(Errors::PollingRequired, "#{job_id} Polling not completed yet") if response[:status] != 'completed' Thread.current[job_id] = nil - ServiceResult.success(result: response[:resourceId]) + ServiceResult.success(result: { id: response[:resourceId] }) end end end diff --git a/modules/storages/spec/common/storages/peripherals/storage_interaction/one_drive/copy_template_folder_command_spec.rb b/modules/storages/spec/common/storages/peripherals/storage_interaction/one_drive/copy_template_folder_command_spec.rb index 0c3f85307650..932e03acbff1 100644 --- a/modules/storages/spec/common/storages/peripherals/storage_interaction/one_drive/copy_template_folder_command_spec.rb +++ b/modules/storages/spec/common/storages/peripherals/storage_interaction/one_drive/copy_template_folder_command_spec.rb @@ -97,7 +97,7 @@ expect(command_result).to be_success expect(command_result.result[:url]).to match %r ensure - delete_copied_folder(command_result.result[:id]) + delete_copied_folder(command_result.result[:url]) end describe 'error handling' do @@ -190,7 +190,11 @@ def existing_folder_tuples end end - def delete_copied_folder(location) + def delete_copied_folder(url) + extractor_regex = /.+\/items\/(?\w+)\?/ + match_data = extractor_regex.match(url) + location = match_data[:item_id] + Storages::Peripherals::Registry .resolve('one_drive.commands.delete_folder') .call(storage:, location:) diff --git a/modules/storages/spec/services/storages/project_storages/copy_project_folders_service_spec.rb b/modules/storages/spec/services/storages/project_storages/copy_project_folders_service_spec.rb index ea8c1bef6cfa..2e9179302b74 100644 --- a/modules/storages/spec/services/storages/project_storages/copy_project_folders_service_spec.rb +++ b/modules/storages/spec/services/storages/project_storages/copy_project_folders_service_spec.rb @@ -41,44 +41,22 @@ context "with automatically managed project folders" do let(:source) { create(:project_storage, :as_automatically_managed, storage:) } - it 'updates the target project storage to point to newly copied remote folder' do + it 'if polling is required, returns a nil id and an url' do Storages::Peripherals::Registry .stub("#{source.storage.short_provider_type}.commands.copy_template_folder", - ->(args) do - # Validating the arguments ensure that the call is correctly made - expect(args[:storage]).to eq(source.storage) - expect(args[:source_path]).to eq(source.project_folder_location) - expect(args[:destination_path]).to eq(target.managed_project_folder_path) - - # Return a success for the provider copy with no polling required - ServiceResult.success(result: { id: 'newly_created_remote_folder', url: 'https://resource.url' }) - end) - - expect(service.call(source_id: source.id, target_id: target.id)).to be_success - - target.reload - expect(target.project_folder_mode).to eq(source.project_folder_mode) - expect(target.project_folder_id).to eq('newly_created_remote_folder') - end - - it 'if polling is required, returns an error with the polling url' do - Storages::Peripherals::Registry - .stub("#{source.storage.short_provider_type}.commands.copy_template_folder", - ->(args) do - # Validating the arguments ensure that the call is correctly made - expect(args[:storage]).to eq(source.storage) - expect(args[:source_path]).to eq(source.project_folder_location) - expect(args[:destination_path]).to eq(target.managed_project_folder_path) + ->(storage:, source_path:, destination_path:) do + expect(storage).to eq(source.storage) + expect(source_path).to eq(source.project_folder_location) + expect(destination_path).to eq(target.managed_project_folder_path) # Return a success for the provider copy with no polling required ServiceResult.success(result: { id: nil, url: 'https://polling.url.de/cool/subresources' }) end) - result = service.call(source_id: source.id, target_id: target.id) + result = service.call(source:, target:) - expect(result).to be_failure - expect(result.result).to eq('https://polling.url.de/cool/subresources') - expect(result.errors).to eq(:polling_required) + expect(result).to be_success + expect(result.result).to eq({ id: nil, url: 'https://polling.url.de/cool/subresources' }) end end @@ -86,27 +64,28 @@ let(:source) { create(:project_storage, project_folder_id: 'this_is_a_unique_id', project_folder_mode: 'manual') } it "succeeds" do - expect(service.call(source_id: source.id, target_id: target.id)).to be_success + result = service.call(source:, target:) + expect(result).to be_success end - it "updates to the target project storage to point to the same project_folder_id than the source" do - expect { service.call(source_id: source.id, target_id: target.id) } - .to change { target.reload.project_folder_id } - .to(source.project_folder_id) - .and(change { target.reload.project_folder_mode } - .to(source.project_folder_mode)) + it 'returns the source folder id' do + result = service.call(source:, target:) + + expect(result.result[:id]).to eq(source.project_folder_id) end end context "with non-managed project folders" do - let(:source) { create(:project_storage, project_folder_id: 'this_is_a_unique_id') } + let(:source) { create(:project_storage, project_folder_id: nil, project_folder_mode: 'inactive') } it "succeeds" do - expect(service.call(source_id: source.id, target_id: target.id)).to be_success + expect(service.call(source:, target:)).to be_success end - it "doesn't require any updates to the target project storage" do - expect { service.call(source_id: source.id, target_id: target.id) }.not_to change(target, :project_folder_id) + it 'returns the origin folder id (nil)' do + result = service.call(source:, target:) + + expect(result.result[:id]).to eq(source.project_folder_id) end end end diff --git a/modules/storages/spec/workers/storages/copy_project_folders_job_spec.rb b/modules/storages/spec/workers/storages/copy_project_folders_job_spec.rb index a10ea634e09d..4d479773ef10 100644 --- a/modules/storages/spec/workers/storages/copy_project_folders_job_spec.rb +++ b/modules/storages/spec/workers/storages/copy_project_folders_job_spec.rb @@ -1,6 +1,31 @@ # frozen_string_literal: true #-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 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' diff --git a/spec/services/projects/copy_service_integration_spec.rb b/spec/services/projects/copy_service_integration_spec.rb index c95a328eb85e..64cb1ef9d6b6 100644 --- a/spec/services/projects/copy_service_integration_spec.rb +++ b/spec/services/projects/copy_service_integration_spec.rb @@ -102,8 +102,6 @@ boards overview storages - storage_project_folders - file_links ) ) end @@ -234,45 +232,6 @@ let(:storage1) { source_automatic_project_storage.storage } let(:storage2) { source_manual_project_storage.storage } # rubocop:enable RSpec/IndexedLet - let(:host) { storage1.host } - let!(:file_outside_project_folder_link) do - create(:file_link, - origin_id: "100", - origin_name: "file_name1.txt", - container: source_wp, - storage: storage1) - end - let!(:file_inside_automatic_project_folder_link) do - create(:file_link, - origin_id: "101", - origin_name: "file_name2.txt", - container: source_wp, - storage: storage1) - end - let!(:folder_inside_automatic_project_folder_link) do - create(:file_link, - origin_id: "103", - origin_name: "This is a folder", - container: source_wp, - storage: storage1) - end - let!(:file_inside_manual_project_folder_link) do - create(:file_link, - origin_id: "102", - origin_name: "file_name3.txt", - container: source_wp, - storage: storage2) - end - let!(:oauth_client) do - create(:oauth_client, - client_id: "nwz34rWsolvJvchfQ1bVHXfMb1ETK89lCBgzrLhWx3ACW5nKfmdcyf5ftlCyKGbk", - client_secret: "A08n6CRBOOr41iqkWRynnP6BbmEnau7LeP9t9xrIbiYX46iXgmIZgqhJoDFjUMEq", - integration: storage1) - end - let!(:oauth_client_token) { create(:oauth_client_token, oauth_client:, user: current_user) } - let(:destination_url) { %r{#{host}/remote.php/dav/files/OpenProject/OpenProject/Target%20Project%20Name%20} } - let(:source_url) { %r{#{host}/remote.php/dav/files/OpenProject/OpenProject/Source%20Project%20Name%20} } - let(:new_project_folder_id) { "819" } shared_let(:source_automatic_project_storage) do storage = create(:nextcloud_storage) @@ -284,161 +243,6 @@ create(:project_storage, storage:, project: source, project_folder_id: '345', project_folder_mode: 'manual') end - before do - stub_request(:head, destination_url).to_return(status: 404) - stub_request(:copy, source_url).to_return(status: 201) - stub_request(:propfind, destination_url).with(headers: { 'Depth' => '1' }).to_return do |_request| - project_id = Project.where(name: "Target Project Name").pick(:id) - body = <<~XML - - - - /remote.php/dav/files/OpenProject/OpenProject/Target%20Project%20Name%20(#{project_id})/ - - - #{new_project_folder_id} - - HTTP/1.1 200 OK - - - - XML - { status: 200, body:, headers: {} } - end - stub_request(:propfind, destination_url).with(headers: { 'Depth' => 'infinity' }).to_return do |_request| - project_id = Project.where(name: "Target Project Name").pick(:id) - body = <<~XML - - - - /remote.php/dav/files/OpenProject/OpenProject/Target%20Project%20Name%20(#{project_id})/ - - - #{new_project_folder_id} - - HTTP/1.1 200 OK - - - - - - HTTP/1.1 404 Not Found - - - - /remote.php/dav/files/OpenProject/OpenProject/Target%20Project%20Name%20(#{project_id})/#{file_inside_automatic_project_folder_link.origin_name} - - - 430 - - HTTP/1.1 200 OK - - - - - - HTTP/1.1 404 Not Found - - - - /remote.php/dav/files/OpenProject/OpenProject/Target%20Project%20Name%20(#{project_id})/#{folder_inside_automatic_project_folder_link.origin_name}/ - - - 431 - - HTTP/1.1 200 OK - - - - - - HTTP/1.1 404 Not Found - - - - XML - { status: 200, body:, headers: {} } - end - - filesinfo_response_body = <<~JSON - { - "ocs": { - "meta": { - "status": "ok", - "statuscode": 100, - "message": "OK", - "totalitems": "", - "itemsperpage": "" - }, - "data": { - "#{file_outside_project_folder_link.origin_id}": { - "status": "OK", - "statuscode": 200, - "id": #{file_outside_project_folder_link.origin_id}, - "name": "#{file_outside_project_folder_link.origin_name}", - "mtime": 1688632254, - "ctime": 0, - "mimetype": "application\\/pdf", - "size": 15181180, - "owner_name": "admin", - "owner_id": "admin", - "trashed": false, - "modifier_name": "admin", - "modifier_id": "admin", - "dav_permissions": "RGDNVW", - "path": "files\\/#{file_outside_project_folder_link.origin_name}" - }, - "#{file_inside_automatic_project_folder_link.origin_id}": { - "status": "OK", - "statuscode": 200, - "id": #{file_inside_automatic_project_folder_link.origin_id}, - "name": "#{file_inside_automatic_project_folder_link.origin_name}", - "mtime": 1689687843, - "ctime": 0, - "mimetype": "image\\/jpeg", - "size": 94064, - "owner_name": "admin", - "owner_id": "admin", - "trashed": false, - "modifier_name": null, - "modifier_id": null, - "dav_permissions": "RMGDNVW", - "path": "files\\/OpenProject\\/Source Project Name (#{source.id})\\/#{file_inside_automatic_project_folder_link.origin_name}" - }, - "#{folder_inside_automatic_project_folder_link.origin_id}": { - "status": "OK", - "statuscode": 200, - "id": #{folder_inside_automatic_project_folder_link.origin_id}, - "name": "#{folder_inside_automatic_project_folder_link.origin_name}", - "mtime": 1689687111, - "ctime": 0, - "mimetype": "application\\/x-op-directory", - "size": 0, - "owner_name": "admin", - "owner_id": "admin", - "trashed": false, - "modifier_name": null, - "modifier_id": null, - "dav_permissions": "RMGDNVCK", - "path": "files\\/OpenProject\\/Source Project Name (#{source.id})\\/#{folder_inside_automatic_project_folder_link.origin_name}" - } - } - } - } - JSON - stub_request(:post, "#{host}/ocs/v1.php/apps/integration_openproject/filesinfo") - .with(body: '{"fileIds":["100","101","103"]}') - .to_return(status: 200, body: filesinfo_response_body, headers: { 'Content-Type' => 'application/json' }) - end - # rubocop:disable RSpec/ExampleLength # rubocop:disable RSpec/MultipleExpectations it 'copies all dependencies and set attributes' do @@ -483,36 +287,17 @@ expect(automatic_project_storage_copy.id).not_to eq(source_automatic_project_storage.id) expect(automatic_project_storage_copy.project_id).to eq(project_copy.id) expect(automatic_project_storage_copy.creator_id).to eq(current_user.id) - expect(automatic_project_storage_copy.project_folder_id).to eq("819") - expect(automatic_project_storage_copy.project_folder_mode).to eq('automatic') + expect(automatic_project_storage_copy.project_folder_id).to be_nil + expect(automatic_project_storage_copy.project_folder_mode).to eq('inactive') manual_project_storage_copy = project_copy.project_storages.find_by(storage: storage2) expect(manual_project_storage_copy.id).not_to eq(source_manual_project_storage.id) expect(manual_project_storage_copy.project_id).to eq(project_copy.id) expect(manual_project_storage_copy.creator_id).to eq(current_user.id) - expect(manual_project_storage_copy.project_folder_id).to eq("345") - expect(manual_project_storage_copy.project_folder_mode).to eq('manual') - - wp_copy = project_copy.work_packages.where(subject: "source wp").first - expect(wp_copy.file_links.count).to eq(4) - file_outside_project_folder_link_copy = wp_copy.file_links.find_by(origin_name: "file_name1.txt") - file_inside_automatic_project_folder_link_copy = wp_copy.file_links.find_by(origin_name: "file_name2.txt") - file_inside_manual_project_folder_link_copy = wp_copy.file_links.find_by(origin_name: "file_name3.txt") - folder_inside_automatic_project_folder_link_copy = wp_copy.file_links.find_by(origin_name: "This is a folder") - expect(file_outside_project_folder_link_copy.id).not_to eq(file_outside_project_folder_link.id) - expect(file_outside_project_folder_link_copy.origin_id).to eq(file_outside_project_folder_link.origin_id) - expect(file_outside_project_folder_link_copy.storage_id).to eq(file_outside_project_folder_link.storage_id) - expect(file_inside_automatic_project_folder_link_copy.id).not_to eq(file_inside_automatic_project_folder_link.id) - expect(file_inside_automatic_project_folder_link_copy.origin_id).to eq("430") - expect(file_inside_automatic_project_folder_link_copy.storage_id) - .to eq(file_inside_automatic_project_folder_link.storage_id) - expect(folder_inside_automatic_project_folder_link_copy.id).not_to eq(folder_inside_automatic_project_folder_link.id) - expect(folder_inside_automatic_project_folder_link_copy.origin_id).to eq("431") - expect(folder_inside_automatic_project_folder_link_copy.storage_id) - .to eq(folder_inside_automatic_project_folder_link.storage_id) - expect(file_inside_manual_project_folder_link_copy.id).not_to eq(file_inside_manual_project_folder_link.id) - expect(file_inside_manual_project_folder_link_copy.origin_id).to eq("102") - expect(file_inside_manual_project_folder_link_copy.storage_id).to eq(file_inside_manual_project_folder_link.storage_id) + expect(manual_project_storage_copy.project_folder_id).to be_nil + expect(manual_project_storage_copy.project_folder_mode).to eq('inactive') + + expect(Storages::CopyProjectFoldersJob).to have_been_enqueued.exactly(2).times end # rubocop:enable RSpec/ExampleLength # rubocop:enable RSpec/MultipleExpectations