diff --git a/modules/storages/app/common/storages/peripherals/nextcloud_registry.rb b/modules/storages/app/common/storages/peripherals/nextcloud_registry.rb index 88ec1b803187..93b35affbade 100644 --- a/modules/storages/app/common/storages/peripherals/nextcloud_registry.rb +++ b/modules/storages/app/common/storages/peripherals/nextcloud_registry.rb @@ -35,7 +35,6 @@ module Peripherals register(:auth_check, StorageInteraction::Nextcloud::AuthCheckQuery) register(:capabilities, StorageInteraction::Nextcloud::CapabilitiesQuery) register(:download_link, StorageInteraction::Nextcloud::DownloadLinkQuery) - register(:file_ids, StorageInteraction::Nextcloud::FileIdsQuery) register(:file_info, StorageInteraction::Nextcloud::FileInfoQuery) register(:files_info, StorageInteraction::Nextcloud::FilesInfoQuery) register(:files, StorageInteraction::Nextcloud::FilesQuery) diff --git a/modules/storages/app/common/storages/peripherals/storage_interaction/nextcloud/copy_template_folder_command.rb b/modules/storages/app/common/storages/peripherals/storage_interaction/nextcloud/copy_template_folder_command.rb index 6b3aef7fdfba..c69ef2e6d210 100644 --- a/modules/storages/app/common/storages/peripherals/storage_interaction/nextcloud/copy_template_folder_command.rb +++ b/modules/storages/app/common/storages/peripherals/storage_interaction/nextcloud/copy_template_folder_command.rb @@ -46,15 +46,17 @@ def initialize(storage) def call(auth_strategy:, source_path:, destination_path:) with_tagged_logger do - valid_input_result = validate_inputs(source_path, destination_path).on_failure { return _1 } + Authentication[auth_strategy].call(storage: @storage) do |http| + valid_input_result = validate_inputs(source_path, destination_path).on_failure { return _1 } - remote_urls = build_origin_urls(**valid_input_result.result) + remote_urls = build_origin_urls(**valid_input_result.result) - ensure_remote_folder_does_not_exist(auth_strategy, remote_urls[:destination_url]).on_failure { return _1 } + ensure_remote_folder_does_not_exist(http, remote_urls[:destination_url]).on_failure { return _1 } - copy_folder(auth_strategy, **remote_urls).on_failure { return _1 } + copy_folder(http, **remote_urls).on_failure { return _1 } - get_folder_id(valid_input_result.result[:destination_path]) + get_folder_id(auth_strategy, valid_input_result.result[:destination_path]) + end end end @@ -76,9 +78,9 @@ def build_origin_urls(source_path:, destination_path:) { source_url:, destination_url: } end - def ensure_remote_folder_does_not_exist(auth_strategy, destination_url) + def ensure_remote_folder_does_not_exist(http, destination_url) info "Checking if #{destination_url} does not already exists." - response = Authentication[auth_strategy].call(storage: @storage) { |http| http.head(destination_url) } + response = http.head(destination_url) case response in { status: 200..299 } @@ -98,13 +100,11 @@ def ensure_remote_folder_does_not_exist(auth_strategy, destination_url) end end - def copy_folder(auth_strategy, source_url:, destination_url:) + def copy_folder(http, source_url:, destination_url:) info "Copying #{source_url} to #{destination_url}" - response = Authentication[auth_strategy].call(storage: @storage) do |http| - http.request("COPY", source_url, headers: { "Destination" => destination_url, "Depth" => "infinity" }) - end - - handle_response(response) + handle_response http.request("COPY", + source_url, + headers: { "Destination" => destination_url, "Depth" => "infinity" }) end # rubocop:disable Metrics/AbcSize @@ -133,14 +133,14 @@ def handle_response(response) errors: Util.storage_error(response:, code: :error, source:)) end end - # rubocop:enable Metrics/AbcSize - def get_folder_id(destination_path) - call = Registry - .resolve("#{@storage.short_provider_type}.queries.file_ids") - .call(storage: @storage, path: destination_path) + # rubocop:enable Metrics/AbcSize - call.map { |result| @data.with(id: result[destination_path]["fileid"]) } + def get_folder_id(auth_strategy, destination_path) + Registry + .resolve("nextcloud.queries.file_path_to_id_map") + .call(storage: @storage, auth_strategy:, folder: ParentFolder.new(destination_path), depth: 0) + .map { |result| @data.with(id: result[destination_path].id) } end def source = self.class diff --git a/modules/storages/app/common/storages/peripherals/storage_interaction/nextcloud/file_ids_query.rb b/modules/storages/app/common/storages/peripherals/storage_interaction/nextcloud/file_ids_query.rb deleted file mode 100644 index 6b03b12b84e5..000000000000 --- a/modules/storages/app/common/storages/peripherals/storage_interaction/nextcloud/file_ids_query.rb +++ /dev/null @@ -1,56 +0,0 @@ -# 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. -#++ - -module Storages - module Peripherals - module StorageInteraction - module Nextcloud - class FileIdsQuery - include TaggedLogging - def self.call(storage:, path:) - new(storage).call(path:) - end - - def initialize(storage) - @query = Internal::PropfindQueryLegacy.new(storage) - end - - def call(path:) - query_params = { depth: "1", path:, props: %w[oc:fileid] } - with_tagged_logger do - info "Requesting File Ids on path: #{path} and args: #{query_params.inspect}" - @query.call(**query_params) - end - end - end - end - end - end -end diff --git a/modules/storages/app/common/storages/peripherals/storage_interaction/nextcloud/file_path_to_id_map_query.rb b/modules/storages/app/common/storages/peripherals/storage_interaction/nextcloud/file_path_to_id_map_query.rb index 9ac13dd9f5cd..cb0a766c5077 100644 --- a/modules/storages/app/common/storages/peripherals/storage_interaction/nextcloud/file_path_to_id_map_query.rb +++ b/modules/storages/app/common/storages/peripherals/storage_interaction/nextcloud/file_path_to_id_map_query.rb @@ -33,8 +33,8 @@ module Peripherals module StorageInteraction module Nextcloud class FilePathToIdMapQuery - def self.call(storage:, auth_strategy:, folder:) - new(storage).call(auth_strategy:, folder:) + def self.call(storage:, auth_strategy:, folder:, depth: Float::INFINITY) + new(storage).call(auth_strategy:, folder:, depth:) end def initialize(storage) @@ -42,16 +42,15 @@ def initialize(storage) @propfind_query = Internal::PropfindQuery.new(storage) end - def call(auth_strategy:, folder:) + def call(auth_strategy:, folder:, depth:) origin_user_id = Util.origin_user_id(caller: self.class, storage: @storage, auth_strategy:) - .on_failure do |result| - return result - end + .on_failure { return _1 } + .result - Authentication[auth_strategy].call(storage: @storage, http_options:) do |http| + Authentication[auth_strategy].call(storage: @storage, http_options: headers(depth)) do |http| # nc:acl-list is only required to avoid https://community.openproject.org/wp/49628. See comment #4. @propfind_query.call(http:, - username: origin_user_id.result, + username: origin_user_id, path: folder.path, props: %w[oc:fileid nc:acl-list]) .map do |obj| @@ -62,8 +61,8 @@ def call(auth_strategy:, folder:) private - def http_options - Util.webdav_request_with_depth("infinity") + def headers(depth) + Util.webdav_request_with_depth(depth.to_s.downcase) end end end diff --git a/modules/storages/app/common/storages/peripherals/storage_interaction/one_drive/file_path_to_id_map_query.rb b/modules/storages/app/common/storages/peripherals/storage_interaction/one_drive/file_path_to_id_map_query.rb index a13c61ff1143..7e4e7aa8299d 100644 --- a/modules/storages/app/common/storages/peripherals/storage_interaction/one_drive/file_path_to_id_map_query.rb +++ b/modules/storages/app/common/storages/peripherals/storage_interaction/one_drive/file_path_to_id_map_query.rb @@ -36,8 +36,8 @@ class FilePathToIdMapQuery CHILDREN_FIELDS = %w[id name file folder parentReference].freeze FOLDER_FIELDS = %w[id name parentReference].freeze - def self.call(storage:, auth_strategy:, folder:) - new(storage).call(auth_strategy:, folder:) + def self.call(storage:, auth_strategy:, folder:, depth: Float::INFINITY) + new(storage).call(auth_strategy:, folder:, depth:) end def initialize(storage) @@ -46,15 +46,18 @@ def initialize(storage) @drive_item_query = Internal::DriveItemQuery.new(storage) end - def call(auth_strategy:, folder:) + # rubocop:disable Metrics/AbcSize + def call(auth_strategy:, folder:, depth:) Authentication[auth_strategy].call(storage: @storage) do |http| - fetch_result = fetch_folder(http, folder) - return fetch_result if fetch_result.failure? + fetched_folder = fetch_folder(http, folder) + .on_failure { return _1 } + .result - file_ids_dictionary = fetch_result.result + file_ids_dictionary = fetched_folder queue = [folder] + level = 0 - while queue.any? + while queue.any? && level < depth dir = queue.shift visit = visit(http, dir) @@ -63,12 +66,15 @@ def call(auth_strategy:, folder:) entry, to_queue = visit.result.values_at(:entry, :to_queue) file_ids_dictionary = file_ids_dictionary.merge(entry) queue.concat(to_queue) + level += 1 end ServiceResult.success(result: file_ids_dictionary) end end + # rubocop:enable Metrics/AbcSize + private def visit(http, folder) diff --git a/modules/storages/app/services/storages/nextcloud_managed_folder_sync_service.rb b/modules/storages/app/services/storages/nextcloud_managed_folder_sync_service.rb index 0bd046524fde..617ab1aec04d 100644 --- a/modules/storages/app/services/storages/nextcloud_managed_folder_sync_service.rb +++ b/modules/storages/app/services/storages/nextcloud_managed_folder_sync_service.rb @@ -34,9 +34,14 @@ class NextcloudManagedFolderSyncService < BaseService FILE_PERMISSIONS = OpenProject::Storages::Engine.external_file_permissions - include Injector["nextcloud.commands.create_folder", "nextcloud.commands.rename_file", "nextcloud.commands.set_permissions", - "nextcloud.queries.group_users", "nextcloud.queries.file_ids", "nextcloud.authentication.userless", - "nextcloud.commands.add_user_to_group", "nextcloud.commands.remove_user_from_group"] + include Injector["nextcloud.commands.create_folder", + "nextcloud.commands.rename_file", + "nextcloud.commands.set_permissions", + "nextcloud.queries.group_users", + "nextcloud.queries.file_path_to_id_map", + "nextcloud.authentication.userless", + "nextcloud.commands.add_user_to_group", + "nextcloud.commands.remove_user_from_group"] def self.i18n_key = "NextcloudSyncService" @@ -76,7 +81,7 @@ def prepare_remote_folders remote_folders = remote_root_folder_map(@storage.group_folder).on_failure { return _1 }.result info "Found #{remote_folders.count} remote folders" - ensure_root_folder_permissions(remote_folders["/#{@storage.group_folder}/"]["fileid"]).on_failure { return _1 } + ensure_root_folder_permissions(remote_folders["/#{@storage.group_folder}"].id).on_failure { return _1 } ensure_folders_exist(remote_folders).on_success { hide_inactive_folders(remote_folders) } end @@ -169,8 +174,8 @@ def hide_inactive_folders(remote_folders) info "Hiding folders related to inactive projects" project_folder_ids = active_project_storages_scope.pluck(:project_folder_id).compact - remote_folders.except("/#{@storage.group_folder}/").each do |(path, attrs)| - folder_id = attrs["fileid"] + remote_folders.except("/#{@storage.group_folder}").each do |(path, file)| + folder_id = file.id next if project_folder_ids.include?(folder_id) @@ -196,7 +201,7 @@ def hide_inactive_folders(remote_folders) def ensure_folders_exist(remote_folders) info "Ensuring that automatically managed project folders exist and are correctly named." - id_folder_map = remote_folders.to_h { |folder, properties| [properties["fileid"], folder] } + id_folder_map = remote_folders.to_h { |path, file| [file.id, path] } active_project_storages_scope.includes(:project).map do |project_storage| unless id_folder_map.key?(project_storage.project_folder_id) @@ -215,7 +220,7 @@ def ensure_folders_exist(remote_folders) # @param current_path [String] current name of the remote project storage folder # @return [ServiceResult, nil] def rename_folder(project_storage, current_path) - return if current_path == project_storage.managed_project_folder_path + return if UrlBuilder.path(current_path) == UrlBuilder.path(project_storage.managed_project_folder_path) name = project_storage.managed_project_folder_name file_id = project_storage.project_folder_id @@ -283,7 +288,11 @@ def ensure_root_folder_permissions(root_folder_id) def remote_root_folder_map(group_folder) info "Retrieving already existing folders under #{group_folder}" - file_ids.call(storage: @storage, path: group_folder).on_failure do |service_result| + file_path_to_id_map.call(storage: @storage, + auth_strategy:, + folder: Peripherals::ParentFolder.new(group_folder), + depth: 1) + .on_failure do |service_result| log_storage_error(service_result.errors, { folder: group_folder }) add_error(:remote_folders, service_result.errors, options: { group_folder:, username: @storage.username }).fail! end diff --git a/modules/storages/spec/common/storages/peripherals/registry_spec.rb b/modules/storages/spec/common/storages/peripherals/registry_spec.rb index 4657f0375011..4f614910bb0e 100644 --- a/modules/storages/spec/common/storages/peripherals/registry_spec.rb +++ b/modules/storages/spec/common/storages/peripherals/registry_spec.rb @@ -213,128 +213,6 @@ end end - describe "#file_ids_query" do - let(:nextcloud_subpath) { "" } - let(:url) { "https://example.com#{nextcloud_subpath}" } - let(:expected_request_body) do - <<~XML - - - - - - - XML - end - let(:expected_response_body) do - <<~XML - - - - #{nextcloud_subpath}/remote.php/dav/files/OpenProject/OpenProject/ - - - 349 - - HTTP/1.1 200 OK - - - - #{nextcloud_subpath}/remote.php/dav/files/OpenProject/OpenProject/asd/ - - - 783 - - HTTP/1.1 200 OK - - - - #{nextcloud_subpath}/remote.php/dav/files/OpenProject/OpenProject/Project%231/ - - - 773 - - HTTP/1.1 200 OK - - - - #{nextcloud_subpath}/remote.php/dav/files/OpenProject/OpenProject/Project%20%232/ - - - 381 - - HTTP/1.1 200 OK - - - - #{nextcloud_subpath}/remote.php/dav/files/OpenProject/OpenProject/Project%232/ - - - 398 - - HTTP/1.1 200 OK - - - - #{nextcloud_subpath}/remote.php/dav/files/OpenProject/OpenProject/qwe/ - - - 767 - - HTTP/1.1 200 OK - - - - #{nextcloud_subpath}/remote.php/dav/files/OpenProject/OpenProject/qweekk/ - - - 802 - - HTTP/1.1 200 OK - - - - XML - end - - before do - stub_request(:propfind, "#{url}/remote.php/dav/files/OpenProject/OpenProject").with( - body: expected_request_body, - headers: { - "Authorization" => "Basic T3BlblByb2plY3Q6T3BlblByb2plY3RTZWN1cmVQYXNzd29yZA==", - "Depth" => "1" - } - ).to_return(status: 200, body: expected_response_body, headers: {}) - end - - shared_examples "a file_ids_query response" do - it "responds with a list of paths and attributes for each of them" do - result = registry.resolve("nextcloud.queries.file_ids") - .call(storage:, path: "OpenProject") - .result - expect(result).to eq({ "/OpenProject/" => { "fileid" => "349" }, - "/OpenProject/Project #2/" => { "fileid" => "381" }, - "/OpenProject/Project#1/" => { "fileid" => "773" }, - "/OpenProject/Project#2/" => { "fileid" => "398" }, - "/OpenProject/asd/" => { "fileid" => "783" }, - "/OpenProject/qwe/" => { "fileid" => "767" }, - "/OpenProject/qweekk/" => { "fileid" => "802" } }) - end - end - - it_behaves_like "a file_ids_query response" - - context "when NC is deployed under subpath" do - let(:nexcloud_subpath) { "/subpath" } - - it_behaves_like "a file_ids_query response" - end - end - describe "#delete_folder_command" do let(:auth_strategy) { Storages::Peripherals::StorageInteraction::AuthenticationStrategies::BasicAuth.strategy } diff --git a/modules/storages/spec/common/storages/peripherals/storage_interaction/nextcloud/file_path_to_id_map_query_spec.rb b/modules/storages/spec/common/storages/peripherals/storage_interaction/nextcloud/file_path_to_id_map_query_spec.rb index 89e5416a4958..781bf3080012 100644 --- a/modules/storages/spec/common/storages/peripherals/storage_interaction/nextcloud/file_path_to_id_map_query_spec.rb +++ b/modules/storages/spec/common/storages/peripherals/storage_interaction/nextcloud/file_path_to_id_map_query_spec.rb @@ -42,26 +42,56 @@ it_behaves_like "file_path_to_id_map_query: basic query setup" - context "with parent folder being root", vcr: "nextcloud/file_path_to_id_map_query_root" do + context "with parent folder being root" do let(:folder) { Storages::Peripherals::ParentFolder.new("/") } - let(:expected_ids) do - { - "/" => "2", - "/Folder with spaces" => "165", - "/Folder with spaces/New Requests" => "166", - "/Folder with spaces/New Requests/request_001.md" => "167", - "/Folder with spaces/New Requests/request_002.md" => "168", - "/Folder" => "169", - "/Folder/android-studio-2021.3.1.17-linux.tar.gz" => "267", - "/Folder/empty" => "172", - "/Folder/Ümlæûts" => "350", - "/Folder/Ümlæûts/Anrüchiges deutsches Dokument.docx" => "351", - "/Practical_guide_to_BAGGM_Digital.pdf" => "295", - "/Readme.md" => "268" - } + + context "with unset depth (defaults to INFINITY)", vcr: "nextcloud/file_path_to_id_map_query_root_depth_infinite" do + let(:expected_ids) do + { + "/" => "2", + "/Folder with spaces" => "165", + "/Folder with spaces/New Requests" => "166", + "/Folder with spaces/New Requests/I❤️you death star.md" => "167", + "/Folder with spaces/New Requests/request_002.md" => "168", + "/Folder with spaces/Ümläuts & spe¢iæl characters" => "360", + "/Folder with spaces/Ümläuts & spe¢iæl characters/what_have_you_done.md" => "361", + "/My files" => "169", + "/My files/android-studio-linux.tar.gz" => "267", + "/My files/empty" => "172", + "/My files/Ümlæûts" => "350", + "/My files/Ümlæûts/Anrüchiges deutsches Dokument.docx" => "351", + "/Practical_guide_to_BAGGM_Digital.pdf" => "295", + "/Readme.md" => "268", + "/VCR" => "773", + "/VCR/placeholder" => "790" + } + end + + it_behaves_like "file_path_to_id_map_query: successful query" end - it_behaves_like "file_path_to_id_map_query: successful query" + context "with depth 0", vcr: "nextcloud/file_path_to_id_map_query_root_depth_0" do + let(:depth) { 0 } + let(:expected_ids) { { "/" => "2" } } + + it_behaves_like "file_path_to_id_map_query: successful query" + end + + context "with depth 1", vcr: "nextcloud/file_path_to_id_map_query_root_depth_1" do + let(:depth) { 1 } + let(:expected_ids) do + { + "/" => "2", + "/Folder with spaces" => "165", + "/My files" => "169", + "/Practical_guide_to_BAGGM_Digital.pdf" => "295", + "/Readme.md" => "268", + "/VCR" => "773" + } + end + + it_behaves_like "file_path_to_id_map_query: successful query" + end end context "with a given parent folder", vcr: "nextcloud/file_path_to_id_map_query_parent_folder" do diff --git a/modules/storages/spec/common/storages/peripherals/storage_interaction/one_drive/file_path_to_id_map_query_spec.rb b/modules/storages/spec/common/storages/peripherals/storage_interaction/one_drive/file_path_to_id_map_query_spec.rb index 30e21b95feba..b6a157ab4a7e 100644 --- a/modules/storages/spec/common/storages/peripherals/storage_interaction/one_drive/file_path_to_id_map_query_spec.rb +++ b/modules/storages/spec/common/storages/peripherals/storage_interaction/one_drive/file_path_to_id_map_query_spec.rb @@ -41,30 +41,54 @@ context "with parent folder being root", vcr: "one_drive/file_path_to_id_map_query_root" do let(:folder) { Storages::Peripherals::ParentFolder.new("/") } - let(:expected_ids) do - { - "/" => "01AZJL5PN6Y2GOVW7725BZO354PWSELRRZ", - "/Folder with spaces" => "01AZJL5PKU2WV3U3RKKFF2A7ZCWVBXRTEU", - "/Folder with spaces/very empty folder" => "01AZJL5PMGEIRPHZPHRRH2NM3D734VIR7H", - "/Folder with spaces/wordle1.png" => "01AZJL5PPMSBBO3R2BIZHJFCELSW3RP7GN", - "/Folder with spaces/wordle2.png" => "01AZJL5PIIFUD6A765KBAIAEMYACAFB2WP", - "/Folder with spaces/wordle3.png" => "01AZJL5PL4AUJEU43CQZFJKN7BQPRP3BLF", - "/Folder" => "01AZJL5PMAXGDWAAKMEBALX4Q6GSN5BSBR", - "/Folder/Images" => "01AZJL5PMIF7ND3KH6FVDLZYP3E36ERFGI", - "/Folder/Subfolder" => "01AZJL5PPWP5UOATNRJJBYJG5TACDHEUAG", - "/Folder/Ümlæûts" => "01AZJL5PNQYF5NM3KWYNA3RJHJIB2XMMMB", - "/Folder/Document.docx" => "01AZJL5PJTICED3C5YSVAY6NWTBNA2XERU", - "/Folder/Sheet.xlsx" => "01AZJL5PLB7SH7633RMBHIH6KVMQRU4RJS", - "/Folder/Images/der_laufende.jpeg" => "01AZJL5PLZFCARRQIDFJF36UL2WTLXTNSY", - "/Folder/Images/written_in_stone.webp" => "01AZJL5PLNCKWYI752YBHYYJ6RBFZWOZ46", - "/Folder/Subfolder/NextcloudHub.md" => "01AZJL5PNCQCEBFI3N7JGZSX5AOX32Z3LA", - "/Folder/Subfolder/test.txt" => "01AZJL5PLOL2KZTJNVFBCJWFXYGYVBQVMZ", - "/Folder/Ümlæûts/Anrüchiges deutsches Dokument.docx" => "01AZJL5PNDURPQGKUSGFCJQJMNNWXKTHSE", - "/Permissions Folder" => "01AZJL5PN3LVLHH2RSZZDJ6ZFAD3OWSGYB" - } + + context "with unset depth (defaults to INFINITY)" do + let(:expected_ids) do + { + "/" => "01AZJL5PN6Y2GOVW7725BZO354PWSELRRZ", + "/Folder with spaces" => "01AZJL5PKU2WV3U3RKKFF2A7ZCWVBXRTEU", + "/Folder with spaces/very empty folder" => "01AZJL5PMGEIRPHZPHRRH2NM3D734VIR7H", + "/Folder with spaces/wordle1.png" => "01AZJL5PPMSBBO3R2BIZHJFCELSW3RP7GN", + "/Folder with spaces/wordle2.png" => "01AZJL5PIIFUD6A765KBAIAEMYACAFB2WP", + "/Folder with spaces/wordle3.png" => "01AZJL5PL4AUJEU43CQZFJKN7BQPRP3BLF", + "/Folder" => "01AZJL5PMAXGDWAAKMEBALX4Q6GSN5BSBR", + "/Folder/Images" => "01AZJL5PMIF7ND3KH6FVDLZYP3E36ERFGI", + "/Folder/Subfolder" => "01AZJL5PPWP5UOATNRJJBYJG5TACDHEUAG", + "/Folder/Ümlæûts" => "01AZJL5PNQYF5NM3KWYNA3RJHJIB2XMMMB", + "/Folder/Document.docx" => "01AZJL5PJTICED3C5YSVAY6NWTBNA2XERU", + "/Folder/Sheet.xlsx" => "01AZJL5PLB7SH7633RMBHIH6KVMQRU4RJS", + "/Folder/Images/der_laufende.jpeg" => "01AZJL5PLZFCARRQIDFJF36UL2WTLXTNSY", + "/Folder/Images/written_in_stone.webp" => "01AZJL5PLNCKWYI752YBHYYJ6RBFZWOZ46", + "/Folder/Subfolder/NextcloudHub.md" => "01AZJL5PNCQCEBFI3N7JGZSX5AOX32Z3LA", + "/Folder/Subfolder/test.txt" => "01AZJL5PLOL2KZTJNVFBCJWFXYGYVBQVMZ", + "/Folder/Ümlæûts/Anrüchiges deutsches Dokument.docx" => "01AZJL5PNDURPQGKUSGFCJQJMNNWXKTHSE", + "/Permissions Folder" => "01AZJL5PN3LVLHH2RSZZDJ6ZFAD3OWSGYB" + } + end + + it_behaves_like "file_path_to_id_map_query: successful query" end - it_behaves_like "file_path_to_id_map_query: successful query" + context "with a depth of 0" do + let(:depth) { 0 } + let(:expected_ids) { { "/" => "01AZJL5PN6Y2GOVW7725BZO354PWSELRRZ" } } + + it_behaves_like "file_path_to_id_map_query: successful query" + end + + context "with a depth of 1" do + let(:depth) { 1 } + let(:expected_ids) do + { + "/" => "01AZJL5PN6Y2GOVW7725BZO354PWSELRRZ", + "/Folder with spaces" => "01AZJL5PKU2WV3U3RKKFF2A7ZCWVBXRTEU", + "/Folder" => "01AZJL5PMAXGDWAAKMEBALX4Q6GSN5BSBR", + "/Permissions Folder" => "01AZJL5PN3LVLHH2RSZZDJ6ZFAD3OWSGYB" + } + end + + it_behaves_like "file_path_to_id_map_query: successful query" + end end context "with a given parent folder", vcr: "one_drive/file_path_to_id_map_query_parent_folder" do diff --git a/modules/storages/spec/services/storages/nextcloud_managed_folder_sync_service_spec.rb b/modules/storages/spec/services/storages/nextcloud_managed_folder_sync_service_spec.rb index 68f90093dcb2..ee1a1a10edbe 100644 --- a/modules/storages/spec/services/storages/nextcloud_managed_folder_sync_service_spec.rb +++ b/modules/storages/spec/services/storages/nextcloud_managed_folder_sync_service_spec.rb @@ -55,10 +55,14 @@ module Storages shared_let(:remote_identities) do [create(:remote_identity, user: admin, oauth_client: storage.oauth_client, origin_user_id: "admin"), - create(:remote_identity, user: multiple_projects_user, oauth_client: storage.oauth_client, - origin_user_id: "multiple_projects_user"), - create(:remote_identity, user: single_project_user, oauth_client: storage.oauth_client, - origin_user_id: "single_project_user")] + create(:remote_identity, + user: multiple_projects_user, + oauth_client: storage.oauth_client, + origin_user_id: "multiple_projects_user"), + create(:remote_identity, + user: single_project_user, + oauth_client: storage.oauth_client, + origin_user_id: "single_project_user")] end shared_let(:non_member_role) { create(:non_member, permissions: ["read_files"]) } @@ -69,12 +73,14 @@ module Storages create(:project, :archived, name: "INACTIVE PROJECT", members: { multiple_projects_user => ordinary_role }) end shared_let(:project) do - create(:project, name: "[Sample] Project Name / Ehüu ///", - members: { multiple_projects_user => ordinary_role, single_project_user => ordinary_role }) + create(:project, + name: "[Sample] Project Name / Ehüu ///", + members: { multiple_projects_user => ordinary_role, single_project_user => ordinary_role }) end shared_let(:renamed_project) do - create(:project, name: "Renamed Project #23", - members: { multiple_projects_user => ordinary_role }) + create(:project, + name: "Renamed Project #23", + members: { multiple_projects_user => ordinary_role }) end let!(:public_storage) { create(:project_storage, :as_automatically_managed, storage:, project: public_project) } @@ -89,7 +95,7 @@ module Storages storage:, project: renamed_project, project_folder_id: "9001") end - let(:file_ids) { class_double(Peripherals::StorageInteraction::Nextcloud::FileIdsQuery) } + let(:file_path_to_id_map) { class_double(Peripherals::StorageInteraction::Nextcloud::FilePathToIdMapQuery) } let(:group_users) { class_double(Peripherals::StorageInteraction::Nextcloud::GroupUsersQuery) } let(:rename_file) { class_double(Peripherals::StorageInteraction::Nextcloud::RenameFileCommand) } let(:set_permissions) { class_double(Peripherals::StorageInteraction::Nextcloud::SetPermissionsCommand) } @@ -99,12 +105,14 @@ module Storages let(:auth_strategy) { Peripherals::StorageInteraction::AuthenticationStrategies::Strategy.new(key: :basic_auth) } let(:root_folder_id) { "root_folder_id" } - let(:file_ids_result) do + let(:file_path_to_id_map_result) do + inactive_storage_path = inactive_storage.managed_project_folder_path.chomp("/") + ServiceResult.success( result: { - "/OpenProject/" => { "fileid" => root_folder_id }, - inactive_storage.managed_project_folder_path => { "fileid" => inactive_storage.project_folder_id }, - "/OpenProject/Another Name for this Project/" => { "fileid" => renamed_storage.project_folder_id } + "/OpenProject" => StorageFileId.new(root_folder_id), + inactive_storage_path => StorageFileId.new(inactive_storage.project_folder_id), + "/OpenProject/Another Name for this Project" => StorageFileId.new(renamed_storage.project_folder_id) } ) end @@ -138,7 +146,7 @@ module Storages let(:create_folder_result) { build_create_folder_result } before do - Peripherals::Registry.stub("nextcloud.queries.file_ids", file_ids) + Peripherals::Registry.stub("nextcloud.queries.file_path_to_id_map", file_path_to_id_map) Peripherals::Registry.stub("nextcloud.queries.group_users", group_users) Peripherals::Registry.stub("nextcloud.commands.add_user_to_group", add_user) Peripherals::Registry.stub("nextcloud.commands.create_folder", create_folder) @@ -148,7 +156,10 @@ module Storages Peripherals::Registry.stub("nextcloud.authentication.userless", -> { auth_strategy }) # We arent using ParentFolder nor AuthStrategies on FileIds - allow(file_ids).to receive(:call).with(storage:, path: storage.group).and_return(file_ids_result) + folder = Peripherals::ParentFolder.new(storage.group) + allow(file_path_to_id_map).to receive(:call).with(storage:, auth_strategy:, folder:, depth: 1) + .and_return(file_path_to_id_map_result) + # Setting the Group Permissions allow(set_permissions).to receive(:call).with(storage:, auth_strategy:, input_data: root_permission_input) .and_return(root_permissions_result) @@ -191,11 +202,11 @@ module Storages end context "when a project is renamed" do - let(:file_ids_result) do + let(:file_path_to_id_map_result) do ServiceResult.success( result: { - "/OpenProject/" => { "fileid" => root_folder_id }, - "/OpenProject/OBVIOUSLY NON RENAMED/" => { "fileid" => renamed_storage.project_folder_id } + "/OpenProject" => StorageFileId.new(root_folder_id), + "/OpenProject/OBVIOUSLY NON RENAMED" => StorageFileId.new(renamed_storage.project_folder_id) } ) end @@ -221,7 +232,9 @@ module Storages end context "with a public project" do - let(:file_ids_result) { ServiceResult.success(result: { "/OpenProject/" => { "fileid" => root_folder_id } }) } + let(:file_path_to_id_map_result) do + ServiceResult.success(result: { "/OpenProject" => StorageFileId.new(root_folder_id) }) + end before { ProjectStorage.where.not(id: public_storage.id).delete_all } @@ -260,8 +273,10 @@ module Storages let(:error_prefix) { "services.errors.models.nextcloud_sync_service" } context "when the initial fetch of remote folders fails" do - let(:file_ids_result) do - errors = storage_error(:unauthorized, "error body", Peripherals::StorageInteraction::Nextcloud::FileIdsQuery) + let(:file_path_to_id_map_result) do + errors = storage_error(:unauthorized, + "error body", + Peripherals::StorageInteraction::Nextcloud::FilePathToIdMapQuery) ServiceResult.failure(result: :unauthorized, errors:) end @@ -350,7 +365,8 @@ module Storages end it "interrupts the flow" do - commands = [file_ids, set_permissions, group_users, add_user, create_folder, remove_user, rename_file] + commands = [file_path_to_id_map, set_permissions, group_users, add_user, create_folder, remove_user, + rename_file] service.call(storage) expect(commands).to all(have_received(:call).at_least(:once)) end @@ -367,10 +383,12 @@ def storage_error(code, data, source) def build_create_folder_result { - public_storage.managed_project_folder_name => ServiceResult.success(result: - StorageFile.new(id: "public_id", name: public_storage.managed_project_folder_name)), - project_storage.managed_project_folder_name => ServiceResult.success(result: - StorageFile.new(id: "normal_project_id", name: project_storage.managed_project_folder_name)) + public_storage.managed_project_folder_name => + ServiceResult.success(result: StorageFile.new(id: "public_id", + name: public_storage.managed_project_folder_name)), + project_storage.managed_project_folder_name => + ServiceResult.success(result: StorageFile.new(id: "normal_project_id", + name: project_storage.managed_project_folder_name)) } end diff --git a/modules/storages/spec/support/fixtures/vcr_cassettes/nextcloud/file_path_to_id_map_query_root.yml b/modules/storages/spec/support/fixtures/vcr_cassettes/nextcloud/file_path_to_id_map_query_root.yml deleted file mode 100644 index 562e2ad833f8..000000000000 --- a/modules/storages/spec/support/fixtures/vcr_cassettes/nextcloud/file_path_to_id_map_query_root.yml +++ /dev/null @@ -1,98 +0,0 @@ ---- -http_interactions: -- request: - method: propfind - uri: https://nextcloud.local/remote.php/dav/files/admin - body: - encoding: UTF-8 - string: | - - - - - - - - headers: - Depth: - - infinity - Authorization: - - Bearer - User-Agent: - - httpx.rb/1.2.4 - Accept: - - "*/*" - Accept-Encoding: - - gzip, deflate - Content-Type: - - application/xml; charset=utf-8 - Content-Length: - - '192' - response: - status: - code: 207 - message: Multi-Status - headers: - Cache-Control: - - no-store, no-cache, must-revalidate - Content-Encoding: - - gzip - Content-Security-Policy: - - default-src 'none'; - Content-Type: - - application/xml; charset=utf-8 - Date: - - Mon, 29 Apr 2024 09:21:37 GMT - Dav: - - 1, 3, extended-mkcol, access-control, calendarserver-principal-property-search, - nextcloud-checksum-update, nc-calendar-search, nc-enable-birthday-calendar - Expires: - - Thu, 19 Nov 1981 08:52:00 GMT - Pragma: - - no-cache - Referrer-Policy: - - no-referrer - Server: - - Apache/2.4.59 (Debian) - Set-Cookie: - - oc07ul6b4oaw=dce80de6ee27126cc7f063c499bbf2d2; path=/; secure; HttpOnly; SameSite=Lax, - oc_sessionPassphrase=qHbGrRqzApLz65yOgbjo0F50qZOP7tl5seLjGa5MF5xAsfWUBS86U4TbnP6wBuwrqH1oAgAR19gOq6FYJvymPZW3tkMSKR9kS5YaIuyIWuqjif2txirDkqaymU7cOOVG; - path=/; secure; HttpOnly; SameSite=Lax, oc07ul6b4oaw=dce80de6ee27126cc7f063c499bbf2d2; - path=/; secure; HttpOnly; SameSite=Lax, __Host-nc_sameSiteCookielax=true; - path=/; httponly;secure; expires=Fri, 31-Dec-2100 23:59:59 GMT; SameSite=lax, - __Host-nc_sameSiteCookiestrict=true; path=/; httponly;secure; expires=Fri, - 31-Dec-2100 23:59:59 GMT; SameSite=strict, oc07ul6b4oaw=dce80de6ee27126cc7f063c499bbf2d2; - path=/; secure; HttpOnly; SameSite=Lax, oc07ul6b4oaw=dce80de6ee27126cc7f063c499bbf2d2; - path=/; secure; HttpOnly; SameSite=Lax, oc07ul6b4oaw=dce80de6ee27126cc7f063c499bbf2d2; - path=/; secure; HttpOnly; SameSite=Lax, oc07ul6b4oaw=dce80de6ee27126cc7f063c499bbf2d2; - path=/; secure; HttpOnly; SameSite=Lax, oc07ul6b4oaw=dce80de6ee27126cc7f063c499bbf2d2; - path=/; secure; HttpOnly; SameSite=Lax, oc07ul6b4oaw=dce80de6ee27126cc7f063c499bbf2d2; - path=/; secure; HttpOnly; SameSite=Lax, oc07ul6b4oaw=dce80de6ee27126cc7f063c499bbf2d2; - path=/; secure; HttpOnly; SameSite=Lax - Vary: - - Brief,Prefer - X-Content-Type-Options: - - nosniff - X-Debug-Token: - - 6Cb1TBf2Ullw34vWD3yf - X-Frame-Options: - - SAMEORIGIN - X-Permitted-Cross-Domain-Policies: - - none - X-Powered-By: - - PHP/8.2.18 - X-Request-Id: - - 6Cb1TBf2Ullw34vWD3yf - X-Robots-Tag: - - noindex, nofollow - X-Xss-Protection: - - 1; mode=block - Content-Length: - - '477' - body: - encoding: UTF-8 - string: | - - /remote.php/dav/files/admin/2HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/Folder/169HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/Folder/android-studio-2021.3.1.17-linux.tar.gz267HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/Folder/empty/172HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/Folder/%c3%9cml%c3%a6%c3%bbts/350HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/Folder/%c3%9cml%c3%a6%c3%bbts/Anr%c3%bcchiges%20deutsches%20Dokument.docx351HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/Folder%20with%20spaces/165HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/Folder%20with%20spaces/New%20Requests/166HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/Folder%20with%20spaces/New%20Requests/request_001.md167HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/Folder%20with%20spaces/New%20Requests/request_002.md168HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/Practical_guide_to_BAGGM_Digital.pdf295HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/Readme.md268HTTP/1.1 200 OKHTTP/1.1 404 Not Found - recorded_at: Mon, 29 Apr 2024 09:21:38 GMT -recorded_with: VCR 6.2.0 diff --git a/modules/storages/spec/support/fixtures/vcr_cassettes/nextcloud/file_path_to_id_map_query_root_depth_0.yml b/modules/storages/spec/support/fixtures/vcr_cassettes/nextcloud/file_path_to_id_map_query_root_depth_0.yml new file mode 100644 index 000000000000..a6ee967ab950 --- /dev/null +++ b/modules/storages/spec/support/fixtures/vcr_cassettes/nextcloud/file_path_to_id_map_query_root_depth_0.yml @@ -0,0 +1,98 @@ +--- +http_interactions: +- request: + method: propfind + uri: https://nextcloud.local/remote.php/dav/files/admin + body: + encoding: UTF-8 + string: | + + + + + + + + headers: + Depth: + - '0' + Authorization: + - Bearer + User-Agent: + - httpx.rb/1.3.1 + Accept: + - "*/*" + Accept-Encoding: + - gzip, deflate + Content-Type: + - application/xml; charset=utf-8 + Content-Length: + - '192' + response: + status: + code: 207 + message: Multi-Status + headers: + Cache-Control: + - no-store, no-cache, must-revalidate + Content-Encoding: + - gzip + Content-Security-Policy: + - default-src 'none'; + Content-Type: + - application/xml; charset=utf-8 + Date: + - Mon, 09 Sep 2024 14:06:46 GMT + Dav: + - 1, 3, extended-mkcol, access-control, calendarserver-principal-property-search, + nextcloud-checksum-update, nc-calendar-search, nc-enable-birthday-calendar + Expires: + - Thu, 19 Nov 1981 08:52:00 GMT + Pragma: + - no-cache + Referrer-Policy: + - no-referrer + Server: + - Apache/2.4.59 (Debian) + Set-Cookie: + - oc07ul6b4oaw=0e2bf236478ffa7e206813fcffc10e28; path=/; secure; HttpOnly; SameSite=Lax, + oc_sessionPassphrase=JfSm9aY2oVbQ8US%2BXKTpHqyK5QRr6DP2otXqhoqXfhj8xJylyuwTTw4e2RkNlGalL4NVCyy9aT9vrISN5lf563XTkeVliDatBMabXlUn67CvF%2Byg%2BTuXTTo%2FlyMdeg5d; + path=/; secure; HttpOnly; SameSite=Lax, oc07ul6b4oaw=0e2bf236478ffa7e206813fcffc10e28; + path=/; secure; HttpOnly; SameSite=Lax, __Host-nc_sameSiteCookielax=true; + path=/; httponly;secure; expires=Fri, 31-Dec-2100 23:59:59 GMT; SameSite=lax, + __Host-nc_sameSiteCookiestrict=true; path=/; httponly;secure; expires=Fri, + 31-Dec-2100 23:59:59 GMT; SameSite=strict, oc07ul6b4oaw=0e2bf236478ffa7e206813fcffc10e28; + path=/; secure; HttpOnly; SameSite=Lax, oc07ul6b4oaw=0e2bf236478ffa7e206813fcffc10e28; + path=/; secure; HttpOnly; SameSite=Lax, oc07ul6b4oaw=0e2bf236478ffa7e206813fcffc10e28; + path=/; secure; HttpOnly; SameSite=Lax, oc07ul6b4oaw=0e2bf236478ffa7e206813fcffc10e28; + path=/; secure; HttpOnly; SameSite=Lax, oc07ul6b4oaw=0e2bf236478ffa7e206813fcffc10e28; + path=/; secure; HttpOnly; SameSite=Lax, oc07ul6b4oaw=0e2bf236478ffa7e206813fcffc10e28; + path=/; secure; HttpOnly; SameSite=Lax, oc07ul6b4oaw=0e2bf236478ffa7e206813fcffc10e28; + path=/; secure; HttpOnly; SameSite=Lax + Vary: + - Brief,Prefer + X-Content-Type-Options: + - nosniff + X-Debug-Token: + - mEyWbELH6q2kJulQSYjA + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Powered-By: + - PHP/8.2.21 + X-Request-Id: + - mEyWbELH6q2kJulQSYjA + X-Robots-Tag: + - noindex, nofollow + X-Xss-Protection: + - 1; mode=block + Content-Length: + - '244' + body: + encoding: UTF-8 + string: | + + /remote.php/dav/files/admin/2HTTP/1.1 200 OKHTTP/1.1 404 Not Found + recorded_at: Mon, 09 Sep 2024 14:06:47 GMT +recorded_with: VCR 6.3.1 diff --git a/modules/storages/spec/support/fixtures/vcr_cassettes/nextcloud/file_path_to_id_map_query_root_depth_1.yml b/modules/storages/spec/support/fixtures/vcr_cassettes/nextcloud/file_path_to_id_map_query_root_depth_1.yml new file mode 100644 index 000000000000..c2d711e9f897 --- /dev/null +++ b/modules/storages/spec/support/fixtures/vcr_cassettes/nextcloud/file_path_to_id_map_query_root_depth_1.yml @@ -0,0 +1,98 @@ +--- +http_interactions: +- request: + method: propfind + uri: https://nextcloud.local/remote.php/dav/files/admin + body: + encoding: UTF-8 + string: | + + + + + + + + headers: + Depth: + - '1' + Authorization: + - Bearer + User-Agent: + - httpx.rb/1.3.1 + Accept: + - "*/*" + Accept-Encoding: + - gzip, deflate + Content-Type: + - application/xml; charset=utf-8 + Content-Length: + - '192' + response: + status: + code: 207 + message: Multi-Status + headers: + Cache-Control: + - no-store, no-cache, must-revalidate + Content-Encoding: + - gzip + Content-Security-Policy: + - default-src 'none'; + Content-Type: + - application/xml; charset=utf-8 + Date: + - Mon, 09 Sep 2024 14:06:47 GMT + Dav: + - 1, 3, extended-mkcol, access-control, calendarserver-principal-property-search, + nextcloud-checksum-update, nc-calendar-search, nc-enable-birthday-calendar + Expires: + - Thu, 19 Nov 1981 08:52:00 GMT + Pragma: + - no-cache + Referrer-Policy: + - no-referrer + Server: + - Apache/2.4.59 (Debian) + Set-Cookie: + - oc07ul6b4oaw=f663195643887b49aa2e556e7d87c05f; path=/; secure; HttpOnly; SameSite=Lax, + oc_sessionPassphrase=si2bSDRidz74z0Y8C3%2Bxrcse79aZZyLN6mIITel6H2PgF6i%2FJzFSs%2BAN23RJAfN9hlu%2B%2BpP5H2Y6Sv8iF221RcPRjB0ba9GdjJQa2wSiBWgbX3mBx3hqFlKB4qsGr2ah; + path=/; secure; HttpOnly; SameSite=Lax, oc07ul6b4oaw=f663195643887b49aa2e556e7d87c05f; + path=/; secure; HttpOnly; SameSite=Lax, __Host-nc_sameSiteCookielax=true; + path=/; httponly;secure; expires=Fri, 31-Dec-2100 23:59:59 GMT; SameSite=lax, + __Host-nc_sameSiteCookiestrict=true; path=/; httponly;secure; expires=Fri, + 31-Dec-2100 23:59:59 GMT; SameSite=strict, oc07ul6b4oaw=f663195643887b49aa2e556e7d87c05f; + path=/; secure; HttpOnly; SameSite=Lax, oc07ul6b4oaw=f663195643887b49aa2e556e7d87c05f; + path=/; secure; HttpOnly; SameSite=Lax, oc07ul6b4oaw=f663195643887b49aa2e556e7d87c05f; + path=/; secure; HttpOnly; SameSite=Lax, oc07ul6b4oaw=f663195643887b49aa2e556e7d87c05f; + path=/; secure; HttpOnly; SameSite=Lax, oc07ul6b4oaw=f663195643887b49aa2e556e7d87c05f; + path=/; secure; HttpOnly; SameSite=Lax, oc07ul6b4oaw=f663195643887b49aa2e556e7d87c05f; + path=/; secure; HttpOnly; SameSite=Lax, oc07ul6b4oaw=f663195643887b49aa2e556e7d87c05f; + path=/; secure; HttpOnly; SameSite=Lax + Vary: + - Brief,Prefer + X-Content-Type-Options: + - nosniff + X-Debug-Token: + - f0xXuPYZTmTW8miDlwYf + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Powered-By: + - PHP/8.2.21 + X-Request-Id: + - f0xXuPYZTmTW8miDlwYf + X-Robots-Tag: + - noindex, nofollow + X-Xss-Protection: + - 1; mode=block + Content-Length: + - '427' + body: + encoding: UTF-8 + string: | + + /remote.php/dav/files/admin/2HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/Folder%20with%20spaces/165HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/My%20files/169HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/Practical_guide_to_BAGGM_Digital.pdf295HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/Readme.md268HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/VCR/773uservcrvcr3131HTTP/1.1 200 OK + recorded_at: Mon, 09 Sep 2024 14:06:47 GMT +recorded_with: VCR 6.3.1 diff --git a/modules/storages/spec/support/fixtures/vcr_cassettes/nextcloud/file_path_to_id_map_query_root_depth_infinite.yml b/modules/storages/spec/support/fixtures/vcr_cassettes/nextcloud/file_path_to_id_map_query_root_depth_infinite.yml new file mode 100644 index 000000000000..2329bfda2bb5 --- /dev/null +++ b/modules/storages/spec/support/fixtures/vcr_cassettes/nextcloud/file_path_to_id_map_query_root_depth_infinite.yml @@ -0,0 +1,98 @@ +--- +http_interactions: +- request: + method: propfind + uri: https://nextcloud.local/remote.php/dav/files/admin + body: + encoding: UTF-8 + string: | + + + + + + + + headers: + Depth: + - infinity + Authorization: + - Bearer + User-Agent: + - httpx.rb/1.3.1 + Accept: + - "*/*" + Accept-Encoding: + - gzip, deflate + Content-Type: + - application/xml; charset=utf-8 + Content-Length: + - '192' + response: + status: + code: 207 + message: Multi-Status + headers: + Cache-Control: + - no-store, no-cache, must-revalidate + Content-Encoding: + - gzip + Content-Security-Policy: + - default-src 'none'; + Content-Type: + - application/xml; charset=utf-8 + Date: + - Mon, 09 Sep 2024 14:04:03 GMT + Dav: + - 1, 3, extended-mkcol, access-control, calendarserver-principal-property-search, + nextcloud-checksum-update, nc-calendar-search, nc-enable-birthday-calendar + Expires: + - Thu, 19 Nov 1981 08:52:00 GMT + Pragma: + - no-cache + Referrer-Policy: + - no-referrer + Server: + - Apache/2.4.59 (Debian) + Set-Cookie: + - oc07ul6b4oaw=71064cc5df3a5e7bd8d9179c340c4dab; path=/; secure; HttpOnly; SameSite=Lax, + oc_sessionPassphrase=H%2BVMPNSuNA3ELOSV68a9AG722Qq%2Fs09dgww4ZYabMV0YHZyv842kM5QuOLv6lCMEl42anma5TpUAbJHW0wrR2dc7%2BAqBKdwwFwL6rGEfXPqtbAz%2BeON8ybKhnzw0tbn7; + path=/; secure; HttpOnly; SameSite=Lax, oc07ul6b4oaw=71064cc5df3a5e7bd8d9179c340c4dab; + path=/; secure; HttpOnly; SameSite=Lax, __Host-nc_sameSiteCookielax=true; + path=/; httponly;secure; expires=Fri, 31-Dec-2100 23:59:59 GMT; SameSite=lax, + __Host-nc_sameSiteCookiestrict=true; path=/; httponly;secure; expires=Fri, + 31-Dec-2100 23:59:59 GMT; SameSite=strict, oc07ul6b4oaw=71064cc5df3a5e7bd8d9179c340c4dab; + path=/; secure; HttpOnly; SameSite=Lax, oc07ul6b4oaw=71064cc5df3a5e7bd8d9179c340c4dab; + path=/; secure; HttpOnly; SameSite=Lax, oc07ul6b4oaw=71064cc5df3a5e7bd8d9179c340c4dab; + path=/; secure; HttpOnly; SameSite=Lax, oc07ul6b4oaw=71064cc5df3a5e7bd8d9179c340c4dab; + path=/; secure; HttpOnly; SameSite=Lax, oc07ul6b4oaw=71064cc5df3a5e7bd8d9179c340c4dab; + path=/; secure; HttpOnly; SameSite=Lax, oc07ul6b4oaw=71064cc5df3a5e7bd8d9179c340c4dab; + path=/; secure; HttpOnly; SameSite=Lax, oc07ul6b4oaw=71064cc5df3a5e7bd8d9179c340c4dab; + path=/; secure; HttpOnly; SameSite=Lax + Vary: + - Brief,Prefer + X-Content-Type-Options: + - nosniff + X-Debug-Token: + - tHdjNhNVN1e8HHQSUi3w + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Powered-By: + - PHP/8.2.21 + X-Request-Id: + - tHdjNhNVN1e8HHQSUi3w + X-Robots-Tag: + - noindex, nofollow + X-Xss-Protection: + - 1; mode=block + Content-Length: + - '666' + body: + encoding: UTF-8 + string: | + + /remote.php/dav/files/admin/2HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/Folder%20with%20spaces/165HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/Folder%20with%20spaces/New%20Requests/166HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/Folder%20with%20spaces/New%20Requests/I%e2%9d%a4%ef%b8%8fyou%20death%20star.md167HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/Folder%20with%20spaces/New%20Requests/request_002.md168HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/Folder%20with%20spaces/%c3%9cml%c3%a4uts%20%26%20spe%c2%a2i%c3%a6l%20characters/360HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/Folder%20with%20spaces/%c3%9cml%c3%a4uts%20%26%20spe%c2%a2i%c3%a6l%20characters/what_have_you_done.md361HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/My%20files/169HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/My%20files/android-studio-linux.tar.gz267HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/My%20files/empty/172HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/My%20files/%c3%9cml%c3%a6%c3%bbts/350HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/My%20files/%c3%9cml%c3%a6%c3%bbts/Anr%c3%bcchiges%20deutsches%20Dokument.docx351HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/Practical_guide_to_BAGGM_Digital.pdf295HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/Readme.md268HTTP/1.1 200 OKHTTP/1.1 404 Not Found/remote.php/dav/files/admin/VCR/773uservcrvcr3131HTTP/1.1 200 OK/remote.php/dav/files/admin/VCR/placeholder/790HTTP/1.1 200 OKHTTP/1.1 404 Not Found + recorded_at: Mon, 09 Sep 2024 14:04:03 GMT +recorded_with: VCR 6.3.1 diff --git a/modules/storages/spec/support/shared_examples_for_adapters/file_path_to_id_map_query_examples.rb b/modules/storages/spec/support/shared_examples_for_adapters/file_path_to_id_map_query_examples.rb index d609e50588ed..6abec5e19218 100644 --- a/modules/storages/spec/support/shared_examples_for_adapters/file_path_to_id_map_query_examples.rb +++ b/modules/storages/spec/support/shared_examples_for_adapters/file_path_to_id_map_query_examples.rb @@ -40,13 +40,18 @@ method = described_class.method(:call) expect(method.parameters).to contain_exactly(%i[keyreq storage], %i[keyreq auth_strategy], - %i[keyreq folder]) + %i[keyreq folder], + %i[key depth]) end end RSpec.shared_examples_for "file_path_to_id_map_query: successful query" do it "returns a map of locations to file ids" do - result = described_class.call(storage:, auth_strategy:, folder:) + result = if defined?(depth) + described_class.call(storage:, auth_strategy:, folder:, depth:) + else + described_class.call(storage:, auth_strategy:, folder:) + end expect(result).to be_success