From 1075385e6466761f451afdb8dc2325ba367b5b87 Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Wed, 13 Sep 2023 13:35:05 +0300 Subject: [PATCH] Use `VCR` for stubbing outbound HTTP requests (#13596) https://community.openproject.org/work_packages/49797 --- .env.test.local.example | 37 +++++ Gemfile | 2 + Gemfile.lock | 2 + .../docker-compose.core-override.example.yml | 11 ++ .../nextcloud/download_link_query.rb | 5 +- .../peripherals/files_info_query_spec.rb | 129 ++++++------------ .../spec/factories/storage_factory.rb | 73 ++++++++-- modules/storages/spec/spec_helper.rb | 5 + .../nextcloud/files_info_query_not_found.yml | 88 ++++++++++++ ...les_info_query_only_one_not_authorized.yml | 88 ++++++++++++ .../nextcloud/files_info_query_success.yml | 89 ++++++++++++ .../files_info_query_unauthorized.yml | 82 +++++++++++ spec/support/vcr.rb | 53 +++++++ 13 files changed, 563 insertions(+), 101 deletions(-) create mode 100644 .env.test.local.example create mode 100644 modules/storages/spec/support/fixtures/vcr_cassettes/nextcloud/files_info_query_not_found.yml create mode 100644 modules/storages/spec/support/fixtures/vcr_cassettes/nextcloud/files_info_query_only_one_not_authorized.yml create mode 100644 modules/storages/spec/support/fixtures/vcr_cassettes/nextcloud/files_info_query_success.yml create mode 100644 modules/storages/spec/support/fixtures/vcr_cassettes/nextcloud/files_info_query_unauthorized.yml create mode 100644 spec/support/vcr.rb diff --git a/.env.test.local.example b/.env.test.local.example new file mode 100644 index 00000000000..85514575377 --- /dev/null +++ b/.env.test.local.example @@ -0,0 +1,37 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2020 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. +#++ + +NEXTCLOUD_LOCAL_OAUTH_CLIENT_ID= +NEXTCLOUD_LOCAL_OAUTH_CLIENT_SECRET= + +NEXTCLOUD_LOCAL_OPENPROJECT_UID= +NEXTCLOUD_LOCAL_OPENPROJECT_SECRET= +NEXTCLOUD_LOCAL_OPENPROJECT_REDIRECT_URI=https://nextcloud.local/index.php/apps/integration_openproject/oauth-redirect + +NEXTCLOUD_LOCAL_OAUTH_CLIENT_ACCESS_TOKEN= +NEXTCLOUD_LOCAL_OAUTH_CLIENT_REFRESH_TOKEN= diff --git a/Gemfile b/Gemfile index e640927a422..e192ffde4a6 100644 --- a/Gemfile +++ b/Gemfile @@ -251,6 +251,8 @@ group :test do gem 'fuubar', '~> 2.5.0' gem 'timecop', '~> 0.9.0' + # Record your test suite's HTTP interactions and replay them during future test runs for fast, deterministic, accurate tests. + gem 'vcr' # Mock backend requests (for ruby tests) gem 'webmock', '~> 3.12', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 8808f6e8413..30855a7c53b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -961,6 +961,7 @@ GEM validate_url (1.0.15) activemodel (>= 3.0.0) public_suffix + vcr (6.2.0) view_component (3.5.0) activesupport (>= 5.2.0, < 8.0) concurrent-ruby (~> 1.0) @@ -1170,6 +1171,7 @@ DEPENDENCIES typed_dag (~> 2.0.2) tzinfo-data (~> 1.2023.1) validate_url + vcr view_component warden (~> 1.2) warden-basic_auth (~> 0.2.1) diff --git a/docker/dev/tls/docker-compose.core-override.example.yml b/docker/dev/tls/docker-compose.core-override.example.yml index 747c03849b6..55d090998c7 100644 --- a/docker/dev/tls/docker-compose.core-override.example.yml +++ b/docker/dev/tls/docker-compose.core-override.example.yml @@ -13,6 +13,17 @@ services: # It must be amended accordingly to OS. - /etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt:ro + backend-test: + # Connect the backend-test container to the same network as the backend for nextcloud HTTP interactions + networks: + - external + volumes: + # Linux + - /etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt:ro + # Mac OS + # - ~/.step/certs:/etc/ssl/certs + # - ~/.step/certs:/usr/local/share/ca-certificates + frontend: networks: - external diff --git a/modules/storages/app/common/storages/peripherals/storage_interaction/nextcloud/download_link_query.rb b/modules/storages/app/common/storages/peripherals/storage_interaction/nextcloud/download_link_query.rb index 5c74aa6da02..01d3fb13283 100644 --- a/modules/storages/app/common/storages/peripherals/storage_interaction/nextcloud/download_link_query.rb +++ b/modules/storages/app/common/storages/peripherals/storage_interaction/nextcloud/download_link_query.rb @@ -47,11 +47,12 @@ module Storages::Peripherals::StorageInteraction::Nextcloud service_result = begin response = Util.http(@uri).post( Util.join_uri_path(@uri.path, '/ocs/v2.php/apps/dav/api/v1/direct'), - { fileId: file_link.origin_id }, + { fileId: file_link.origin_id }.to_json, { 'Authorization' => "Bearer #{token.access_token}", 'OCS-APIRequest' => 'true', - 'Accept' => 'application/json' + 'Accept' => 'application/json', + 'Content-Type' => 'application/json' } ) case response diff --git a/modules/storages/spec/common/peripherals/files_info_query_spec.rb b/modules/storages/spec/common/peripherals/files_info_query_spec.rb index af613fbd6f5..d549bac822d 100644 --- a/modules/storages/spec/common/peripherals/files_info_query_spec.rb +++ b/modules/storages/spec/common/peripherals/files_info_query_spec.rb @@ -31,23 +31,20 @@ require 'spec_helper' require_module_spec_helper -RSpec.describe Storages::Peripherals::StorageInteraction::Nextcloud::FilesInfoQuery, webmock: true do +RSpec.describe Storages::Peripherals::StorageInteraction::Nextcloud::FilesInfoQuery, + :vcr, webmock: true do using Storages::Peripherals::ServiceResultRefinements let(:user) { create(:user) } - let(:url) { 'https://example.com' } - let(:origin_user_id) { 'admin' } - let(:storage) { build(:nextcloud_storage, :as_not_automatically_managed, host: url) } - let(:oauth_client) { create(:oauth_client, integration: storage) } - let(:token) { create(:oauth_client_token, origin_user_id:, access_token: 'xyz', oauth_client:, user:) } - - before { token } + let(:storage) do + create(:nextcloud_storage_with_local_connection, :as_not_automatically_managed, oauth_client_token_user: user) + end subject { described_class.new(storage) } describe '#call' do - let(:file_ids) { %w[354 355] } + let(:file_ids) { %w[182 203 222] } context 'without outbound request involved' do context 'with an empty array of file ids' do @@ -69,65 +66,8 @@ RSpec.describe Storages::Peripherals::StorageInteraction::Nextcloud::FilesInfoQu end end - context 'with outbound request successful' do - let(:expected_response_body) do - <<~JSON - { - "ocs": { - "meta": { - "status": "ok", - "statuscode": 100, - "message": "OK", - "totalitems": "", - "itemsperpage": "" - }, - "data": { - "354": { - "status": "OK", - "statuscode": 200, - "id": 354, - "name": "Demo project (1)", - "mtime": 1689162221, - "ctime": 0, - "mimetype": "application/x-op-directory", - "size": 989752, - "owner_name": "admin", - "owner_id": "admin", - "trashed": false, - "modifier_name": null, - "modifier_id": null, - "dav_permissions": "RMGDNVCK", - "path": "files/OpenProject/Demo project (1)" - }, - "355": { - "status": "OK", - "statuscode": 200, - "id": 355, - "name": "minecraft.jpg", - "mtime": 1689162221, - "ctime": 0, - "mimetype": "image/jpeg", - "size": 989752, - "owner_name": "admin", - "owner_id": "admin", - "trashed": false, - "modifier_name": null, - "modifier_id": null, - "dav_permissions": "RMGDNVW", - "path": "files/OpenProject/Demo project (1)/minecraft.jpg" - } - } - } - } - JSON - end - - before do - stub_request(:post, "https://example.com/ocs/v1.php/apps/integration_openproject/filesinfo") - .with(body: { fileIds: file_ids }.to_json) - .to_return(status: 200, body: expected_response_body) - end - + context 'with outbound request successful', + vcr: 'nextcloud/files_info_query_success' do context 'with an array of file ids' do it 'must return an array of file information when called' do result = subject.call(user:, file_ids:) @@ -135,7 +75,7 @@ RSpec.describe Storages::Peripherals::StorageInteraction::Nextcloud::FilesInfoQu result.match( on_success: ->(file_infos) do - expect(file_infos.size).to eq(2) + expect(file_infos.size).to eq(3) expect(file_infos).to all(be_a(Storages::StorageFileInfo)) end, on_failure: ->(error) { fail "Expected success, got #{error}" } @@ -144,14 +84,16 @@ RSpec.describe Storages::Peripherals::StorageInteraction::Nextcloud::FilesInfoQu end end - context 'with outbound request not authorized' do - before do - stub_request(:post, "https://example.com/ocs/v1.php/apps/integration_openproject/filesinfo") - .with(body: { fileIds: file_ids }.to_json) - .to_return(status: 401) - end - + context 'with outbound request not authorized', + vcr: 'nextcloud/files_info_query_unauthorized' do context 'with an array of file ids' do + before do + token = build_stubbed(:oauth_client_token, oauth_client: storage.oauth_client) + allow(Storages::Peripherals::StorageInteraction::Nextcloud::Util) + .to receive(:token) + .and_yield(token) + end + it 'must return an error when called' do subject.call(user:, file_ids:).match( on_success: ->(file_infos) { fail "Expected failure, got #{file_infos}" }, @@ -162,17 +104,34 @@ RSpec.describe Storages::Peripherals::StorageInteraction::Nextcloud::FilesInfoQu end context 'with outbound request not found' do - before do - stub_request(:post, "https://example.com/ocs/v1.php/apps/integration_openproject/filesinfo") - .with(body: { fileIds: file_ids }.to_json) - .to_return(status: 404) - end + context 'with a single file id', + vcr: 'nextcloud/files_info_query_not_found' do + let(:file_ids) { %w[1234] } - context 'with an array of file ids' do - it 'must return an error when called' do + it 'returns an HTTP 200 with individual status code per file ID' do subject.call(user:, file_ids:).match( - on_success: ->(file_infos) { fail "Expected failure, got #{file_infos}" }, - on_failure: ->(error) { expect(error.code).to eq(:not_found) } + on_success: ->(file_infos) do + expect(file_infos.size).to eq(1) + expect(file_infos.first.to_h).to include(status: 'Not Found', status_code: 404) + end, + on_failure: ->(error) { fail "Expected success, got #{error}" } + ) + end + end + end + + context 'with outbound request not authorized' do + context 'with multiple file IDs, one of which is not authorized', + vcr: 'nextcloud/files_info_query_only_one_not_authorized' do + let(:file_ids) { %w[182 1234] } + + it 'returns an HTTP 200 with individual status code per file ID' do + subject.call(user:, file_ids:).match( + on_success: ->(file_infos) do + expect(file_infos.size).to eq(2) + expect(file_infos.map(&:status_code)).to contain_exactly(403, 404) + end, + on_failure: ->(error) { fail "Expected success, got #{error}" } ) end end diff --git a/modules/storages/spec/factories/storage_factory.rb b/modules/storages/spec/factories/storage_factory.rb index 883468d5888..fcfe8ba81dd 100644 --- a/modules/storages/spec/factories/storage_factory.rb +++ b/modules/storages/spec/factories/storage_factory.rb @@ -39,23 +39,68 @@ FactoryBot.define do oauth_client { build(:oauth_client) } end # rubocop:enable FactoryBot/FactoryAssociationWithStrategy + end - factory :one_drive_storage, class: "Storages::OneDriveStorage" do - host { nil } + factory :nextcloud_storage, + parent: :storage, + class: '::Storages::NextcloudStorage' do + provider_type { Storages::Storage::PROVIDER_TYPE_NEXTCLOUD } + sequence(:host) { |n| "https://host#{n}.example.com" } + + trait :as_automatically_managed do + automatically_managed { true } + username { 'OpenProject' } + password { 'Password123' } end - factory :nextcloud_storage, class: 'Storages::NextcloudStorage' do - sequence(:host) { |n| "https://host#{n}.example.com" } - - trait :as_automatically_managed do - automatically_managed { true } - username { 'OpenProject' } - password { 'Password123' } - end - - trait :as_not_automatically_managed do - automatically_managed { false } - end + trait :as_not_automatically_managed do + automatically_managed { false } end end + + factory :nextcloud_storage_with_local_connection, + parent: :nextcloud_storage, + traits: [:as_not_automatically_managed] do + transient do + oauth_client_token_user { association :user } + end + + name { 'Nextcloud Local' } + host { 'https://nextcloud.local' } + + initialize_with do + Storages::NextcloudStorage.create_or_find_by(attributes.except(:oauth_client, :oauth_application)) + end + + after(:create) do |storage, evaluator| + create(:oauth_client, + client_id: ENV.fetch('NEXTCLOUD_LOCAL_OAUTH_CLIENT_ID', 'MISSING_NEXTCLOUD_LOCAL_OAUTH_CLIENT_ID'), + client_secret: ENV.fetch('NEXTCLOUD_LOCAL_OAUTH_CLIENT_SECRET', 'MISSING_NEXTCLOUD_LOCAL_OAUTH_CLIENT_SECRET'), + integration: storage) + + create(:oauth_application, + uid: ENV.fetch('NEXTCLOUD_LOCAL_OPENPROJECT_UID', 'MISSING_NEXTCLOUD_LOCAL_OPENPROJECT_UID'), + secret: ENV.fetch('NEXTCLOUD_LOCAL_OPENPROJECT_SECRET', 'MISSING_NEXTCLOUD_LOCAL_OPENPROJECT_SECRET'), + redirect_uri: ENV.fetch('NEXTCLOUD_LOCAL_OPENPROJECT_REDIRECT_URI', + "https://nextcloud.local/index.php/apps/integration_openproject/oauth-redirect"), + scopes: 'api_v3', + integration: storage) + + create(:oauth_client_token, + oauth_client: storage.oauth_client, + user: evaluator.oauth_client_token_user, + access_token: ENV.fetch('NEXTCLOUD_LOCAL_OAUTH_CLIENT_ACCESS_TOKEN', + 'MISSING_NEXTCLOUD_LOCAL_OAUTH_CLIENT_ACCESS_TOKEN'), + refresh_token: ENV.fetch('NEXTCLOUD_LOCAL_OAUTH_CLIENT_REFRESH_TOKEN', + 'MISSING_NEXTCLOUD_LOCAL_OAUTH_CLIENT_REFRESH_TOKEN'), + token_type: 'bearer', + origin_user_id: 'admin') + end + end + + factory :one_drive_storage, + parent: :storage, + class: '::Storages::OneDriveStorage' do + host { nil } + end end diff --git a/modules/storages/spec/spec_helper.rb b/modules/storages/spec/spec_helper.rb index e51dfc5b54b..753262a9e2e 100644 --- a/modules/storages/spec/spec_helper.rb +++ b/modules/storages/spec/spec_helper.rb @@ -36,6 +36,11 @@ require 'spec_helper' require 'dry/container/stub' +# Record Storages Cassettes in module +VCR.configure do |config| + config.cassette_library_dir = 'modules/storages/spec/support/fixtures/vcr_cassettes' +end + # Loads files from relative support/ directory Dir[File.join(File.dirname(__FILE__), 'support/**/*.rb')].each { |f| require f } diff --git a/modules/storages/spec/support/fixtures/vcr_cassettes/nextcloud/files_info_query_not_found.yml b/modules/storages/spec/support/fixtures/vcr_cassettes/nextcloud/files_info_query_not_found.yml new file mode 100644 index 00000000000..587d0aac370 --- /dev/null +++ b/modules/storages/spec/support/fixtures/vcr_cassettes/nextcloud/files_info_query_not_found.yml @@ -0,0 +1,88 @@ +--- +http_interactions: +- request: + method: post + uri: https://nextcloud.local/ocs/v1.php/apps/integration_openproject/filesinfo + body: + encoding: UTF-8 + string: '{"fileIds":["1234"]}' + headers: + Authorization: + - Bearer L49hIQGzShBYfjblMAtTDtT15rtZ8wagxFIbwQTzD0LRIfUOBmOYz9wY5pvIY9zkLMpXLcK9 + Accept: + - application/json + Content-Type: + - application/json + Ocs-Apirequest: + - 'true' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Access-Control-Allow-Credentials: + - 'true' + Cache-Control: + - no-cache, no-store, must-revalidate + Content-Length: + - '154' + Content-Security-Policy: + - default-src 'none';base-uri 'none';manifest-src 'self';frame-ancestors 'none' + Content-Type: + - application/json; charset=utf-8 + Date: + - Wed, 30 Aug 2023 15:26:14 GMT + Expires: + - Thu, 19 Nov 1981 08:52:00 GMT + Feature-Policy: + - autoplay 'none';camera 'none';fullscreen 'none';geolocation 'none';microphone + 'none';payment 'none' + Pragma: + - no-cache + Referrer-Policy: + - no-referrer + Server: + - Apache/2.4.56 (Debian) + Set-Cookie: + - __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 + - oc_sessionPassphrase=mubP6L1KhzkAbwW%2BdVOdSsS10ZEeOJUYFAEVeqbp1%2BA37vA9h98ve5%2BxUBz1zPeIpCJRBNvPfF1sOmZBn5HM8qGFwvc9b9scbO4E9GovlEPYW0X%2B1WMN0eKqmW45nR7A; + path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=cc97b31ff8dede5d34607ee68aff20e8; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=cc97b31ff8dede5d34607ee68aff20e8; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=cc97b31ff8dede5d34607ee68aff20e8; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=cc97b31ff8dede5d34607ee68aff20e8; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=cc97b31ff8dede5d34607ee68aff20e8; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=cc97b31ff8dede5d34607ee68aff20e8; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=cc97b31ff8dede5d34607ee68aff20e8; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=cc97b31ff8dede5d34607ee68aff20e8; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=cc97b31ff8dede5d34607ee68aff20e8; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=ff3381b14b648bcb48e62a82cbb42c87; path=/; secure; HttpOnly; SameSite=Lax + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Powered-By: + - PHP/8.2.8 + X-Request-Id: + - v3tT2TMs2xjqxJOvWJ2f + X-Robots-Tag: + - noindex, nofollow + X-Xss-Protection: + - 1; mode=block + body: + encoding: UTF-8 + string: '{"ocs":{"meta":{"status":"ok","statuscode":100,"message":"OK","totalitems":"","itemsperpage":""},"data":{"1234":{"status":"Not + Found","statuscode":404}}}}' + recorded_at: Wed, 30 Aug 2023 15:26:14 GMT +recorded_with: VCR 6.2.0 diff --git a/modules/storages/spec/support/fixtures/vcr_cassettes/nextcloud/files_info_query_only_one_not_authorized.yml b/modules/storages/spec/support/fixtures/vcr_cassettes/nextcloud/files_info_query_only_one_not_authorized.yml new file mode 100644 index 00000000000..89b4f2157d1 --- /dev/null +++ b/modules/storages/spec/support/fixtures/vcr_cassettes/nextcloud/files_info_query_only_one_not_authorized.yml @@ -0,0 +1,88 @@ +--- +http_interactions: +- request: + method: post + uri: https://nextcloud.local/ocs/v1.php/apps/integration_openproject/filesinfo + body: + encoding: UTF-8 + string: '{"fileIds":["182","1234"]}' + headers: + Authorization: + - Bearer L49hIQGzShBYfjblMAtTDtT15rtZ8wagxFIbwQTzD0LRIfUOBmOYz9wY5pvIY9zkLMpXLcK9 + Accept: + - application/json + Content-Type: + - application/json + Ocs-Apirequest: + - 'true' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Access-Control-Allow-Credentials: + - 'true' + Cache-Control: + - no-cache, no-store, must-revalidate + Content-Length: + - '200' + Content-Security-Policy: + - default-src 'none';base-uri 'none';manifest-src 'self';frame-ancestors 'none' + Content-Type: + - application/json; charset=utf-8 + Date: + - Wed, 30 Aug 2023 15:42:44 GMT + Expires: + - Thu, 19 Nov 1981 08:52:00 GMT + Feature-Policy: + - autoplay 'none';camera 'none';fullscreen 'none';geolocation 'none';microphone + 'none';payment 'none' + Pragma: + - no-cache + Referrer-Policy: + - no-referrer + Server: + - Apache/2.4.56 (Debian) + Set-Cookie: + - __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 + - oc_sessionPassphrase=jrT6r0yM%2BFS1HHed4hww77b%2B4Nv2Sf1UYogAKMFI2xarG0FxxSDQcuuwf%2FhVigK4HNXAOjdtVY%2BcAZJvM3KyVy%2FZIVkiIH7C3CR%2FQEFybCQPE2ecRh9dnS6UkGX7nz1R; + path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=2097f255d8b54122037144f5504cd33e; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=69f515e7c5bfb387de23f92cf0bbebcc; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=69f515e7c5bfb387de23f92cf0bbebcc; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=69f515e7c5bfb387de23f92cf0bbebcc; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=69f515e7c5bfb387de23f92cf0bbebcc; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=69f515e7c5bfb387de23f92cf0bbebcc; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=69f515e7c5bfb387de23f92cf0bbebcc; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=69f515e7c5bfb387de23f92cf0bbebcc; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=69f515e7c5bfb387de23f92cf0bbebcc; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=69f515e7c5bfb387de23f92cf0bbebcc; path=/; secure; HttpOnly; SameSite=Lax + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Powered-By: + - PHP/8.2.8 + X-Request-Id: + - RqDl34ieFVXiEvFcPeJV + X-Robots-Tag: + - noindex, nofollow + X-Xss-Protection: + - 1; mode=block + body: + encoding: UTF-8 + string: '{"ocs":{"meta":{"status":"ok","statuscode":100,"message":"OK","totalitems":"","itemsperpage":""},"data":{"182":{"status":"Forbidden","statuscode":403},"1234":{"status":"Not + Found","statuscode":404}}}}' + recorded_at: Wed, 30 Aug 2023 15:42:45 GMT +recorded_with: VCR 6.2.0 diff --git a/modules/storages/spec/support/fixtures/vcr_cassettes/nextcloud/files_info_query_success.yml b/modules/storages/spec/support/fixtures/vcr_cassettes/nextcloud/files_info_query_success.yml new file mode 100644 index 00000000000..aece85b382e --- /dev/null +++ b/modules/storages/spec/support/fixtures/vcr_cassettes/nextcloud/files_info_query_success.yml @@ -0,0 +1,89 @@ +--- +http_interactions: +- request: + method: post + uri: https://nextcloud.local/ocs/v1.php/apps/integration_openproject/filesinfo + body: + encoding: UTF-8 + string: '{"fileIds":["182","203","222"]}' + headers: + Authorization: + - Bearer L49hIQGzShBYfjblMAtTDtT15rtZ8wagxFIbwQTzD0LRIfUOBmOYz9wY5pvIY9zkLMpXLcK9 + Accept: + - application/json + Content-Type: + - application/json + Ocs-Apirequest: + - 'true' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Access-Control-Allow-Credentials: + - 'true' + Cache-Control: + - no-cache, no-store, must-revalidate + Content-Length: + - '577' + Content-Security-Policy: + - default-src 'none';base-uri 'none';manifest-src 'self';frame-ancestors 'none' + Content-Type: + - application/json; charset=utf-8 + Date: + - Wed, 30 Aug 2023 15:45:26 GMT + Expires: + - Thu, 19 Nov 1981 08:52:00 GMT + Feature-Policy: + - autoplay 'none';camera 'none';fullscreen 'none';geolocation 'none';microphone + 'none';payment 'none' + Pragma: + - no-cache + Referrer-Policy: + - no-referrer + Server: + - Apache/2.4.56 (Debian) + Set-Cookie: + - __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 + - oc_sessionPassphrase=bZysxrG%2Fl16DjX2dkVG88Pgrm1tYYZ7EzUwAVCaBnEnVxo8pbldMJx2U9QF%2FXm%2Blyz8WZSNH10txYYv%2BXIBIOhZQCpNue%2BiOgD4BTSq%2BBL4VlFQ4OEnyKTMeCwrk1vr%2F; + path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=57a237ced5d8cbea7fa030db90ed0f3f; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=e4353119162ec0f0020b925c614ce423; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=e4353119162ec0f0020b925c614ce423; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=e4353119162ec0f0020b925c614ce423; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=e4353119162ec0f0020b925c614ce423; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=e4353119162ec0f0020b925c614ce423; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=e4353119162ec0f0020b925c614ce423; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=e4353119162ec0f0020b925c614ce423; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=e4353119162ec0f0020b925c614ce423; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=e4353119162ec0f0020b925c614ce423; path=/; secure; HttpOnly; SameSite=Lax + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Powered-By: + - PHP/8.2.8 + X-Request-Id: + - Oi4F9CkhBWYYmLeEmoUp + X-Robots-Tag: + - noindex, nofollow + X-Xss-Protection: + - 1; mode=block + body: + encoding: UTF-8 + string: '{"ocs":{"meta":{"status":"ok","statuscode":100,"message":"OK","totalitems":"","itemsperpage":""},"data":{"182":{"status":"Forbidden","statuscode":403},"203":{"status":"Forbidden","statuscode":403},"222":{"status":"OK","statuscode":200,"id":222,"name":"Screenshot + 2023-08-15 at 3.00.54 PM.jpg","mtime":1692187580,"ctime":0,"mimetype":"image\/jpeg","size":81944,"owner_name":"member","owner_id":"member","trashed":false,"modifier_name":null,"modifier_id":null,"dav_permissions":"RMGDNVW","path":"files\/OpenProject\/Scrum + project (2)\/Screenshot 2023-08-15 at 3.00.54 PM.jpg"}}}}' + recorded_at: Wed, 30 Aug 2023 15:45:26 GMT +recorded_with: VCR 6.2.0 diff --git a/modules/storages/spec/support/fixtures/vcr_cassettes/nextcloud/files_info_query_unauthorized.yml b/modules/storages/spec/support/fixtures/vcr_cassettes/nextcloud/files_info_query_unauthorized.yml new file mode 100644 index 00000000000..01f8bd7556d --- /dev/null +++ b/modules/storages/spec/support/fixtures/vcr_cassettes/nextcloud/files_info_query_unauthorized.yml @@ -0,0 +1,82 @@ +--- +http_interactions: +- request: + method: post + uri: https://nextcloud.local/ocs/v1.php/apps/integration_openproject/filesinfo + body: + encoding: UTF-8 + string: '{"fileIds":["182","203","222"]}' + headers: + Authorization: + - Bearer 1234567890-1 + Accept: + - application/json + Content-Type: + - application/json + Ocs-Apirequest: + - 'true' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + User-Agent: + - Ruby + response: + status: + code: 401 + message: Unauthorized + headers: + Access-Control-Allow-Credentials: + - 'true' + Cache-Control: + - no-cache, no-store, must-revalidate + Content-Length: + - '140' + Content-Security-Policy: + - default-src 'none';base-uri 'none';manifest-src 'self';frame-ancestors 'none' + Content-Type: + - application/json; charset=utf-8 + Date: + - Thu, 31 Aug 2023 15:56:02 GMT + Expires: + - Thu, 19 Nov 1981 08:52:00 GMT + Feature-Policy: + - autoplay 'none';camera 'none';fullscreen 'none';geolocation 'none';microphone + 'none';payment 'none' + Pragma: + - no-cache + Referrer-Policy: + - no-referrer + Server: + - Apache/2.4.56 (Debian) + Set-Cookie: + - __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 + - oc_sessionPassphrase=jerIWxh2u207KZvn8P9QfqUQqhxH1PU9dwZGLgmFmmE7YvuUvHLkMAXQJ6cxw0Tv622PCSD3z4wvuv%2BQZYmocMUcRJxkjuSMaUrwXB83Ys2%2BO%2BegSfLT12NgAQPSGY82; + path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=40ab489740e67cfe180e2c0fce9972a4; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=8e5f3e155930f7d2a03ce41bb8c67e49; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=8e5f3e155930f7d2a03ce41bb8c67e49; path=/; secure; HttpOnly; SameSite=Lax + - ocybhqi440sc=8e5f3e155930f7d2a03ce41bb8c67e49; path=/; secure; HttpOnly; SameSite=Lax + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Powered-By: + - PHP/8.2.8 + X-Request-Id: + - HbnpSYoZ0evEYxmtZRPO + X-Robots-Tag: + - noindex, nofollow + X-Xss-Protection: + - 1; mode=block + body: + encoding: UTF-8 + string: '{"ocs":{"meta":{"status":"failure","statuscode":997,"message":"Current + user is not logged in","totalitems":"","itemsperpage":""},"data":[]}}' + recorded_at: Thu, 31 Aug 2023 15:56:02 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/support/vcr.rb b/spec/support/vcr.rb new file mode 100644 index 00000000000..eec49cf7b7b --- /dev/null +++ b/spec/support/vcr.rb @@ -0,0 +1,53 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2023 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 'vcr' + +VCR.configure do |config| + config.cassette_library_dir = 'spec/support/fixtures/vcr_cassettes' + config.hook_into :webmock + config.configure_rspec_metadata! + config.before_record do |i| + i.response.body.force_encoding('UTF-8') + end +end + +VCR.turn_off! + +RSpec.configure do |config| + config.around(:example, :vcr) do |example| + # Only enable VCR's webmock integration for tests tagged with :vcr otherwise interferes with WebMock + # See: https://github.com/vcr/vcr/issues/146 + # + VCR.turn_on! + example.run + ensure + # Switch off VCR to prevent VCR from interfering with other tests + VCR.turn_off! + end +end