From c25eb1f64ef72bd03d5f2e2154fc22a191a53831 Mon Sep 17 00:00:00 2001 From: tma-ndhuy Date: Thu, 12 Jan 2023 17:49:07 +0700 Subject: [PATCH 01/19] D2 - Add code --- website/util/waterbutler.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/website/util/waterbutler.py b/website/util/waterbutler.py index 6bd3ce7f2bd..6241133d547 100644 --- a/website/util/waterbutler.py +++ b/website/util/waterbutler.py @@ -24,7 +24,10 @@ def download_file(osf_cookie, file_node, download_path, **kwargs): try: response = requests.get( - file_node.generate_waterbutler_url(action='download', direct=None, **kwargs), + waterbutler_api_url_for( + file_node.target._id, file_node.provider, path=file_node._path, + _internal=True, meta='' + ), cookies={settings.COOKIE_NAME: osf_cookie}, stream=True ) From 98f1df99fb473f18c9e6db61457f97a5fedb188f Mon Sep 17 00:00:00 2001 From: tma-ndhuy Date: Thu, 12 Jan 2023 18:37:03 +0700 Subject: [PATCH 02/19] D1 - Add code --- addons/nextcloud/models.py | 47 +++++++++++++++++++++++++-- addons/nextcloudinstitutions/utils.py | 18 ++++++---- 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/addons/nextcloud/models.py b/addons/nextcloud/models.py index d87e66f7bb0..0f0b1e87f5d 100644 --- a/addons/nextcloud/models.py +++ b/addons/nextcloud/models.py @@ -12,7 +12,9 @@ from addons.nextcloud.serializer import NextcloudSerializer from addons.nextcloud.settings import DEFAULT_HOSTS, USE_SSL from osf.models.external import BasicAuthProviderMixin -from website.util import api_v2_url +from website.util import api_v2_url, timestamp +from addons.nextcloudinstitutions import utils + logger = logging.getLogger(__name__) @@ -25,7 +27,48 @@ class NextcloudFolder(NextcloudFileNode, Folder): class NextcloudFile(NextcloudFileNode, File): - pass + @property + def _hashes(self): + try: + return self._history[-1]['extra']['hashes']['nextcloud'] + except (IndexError, KeyError): + return None + + # return (hash_type, hash_value) + def get_hash_for_timestamp(self): + hashes = self._hashes + if hashes: + if 'sha512' in hashes: + return timestamp.HASH_TYPE_SHA512, hashes['sha512'] + return None, None # unsupported + + def _my_node_settings(self): + node = self.target + if node: + addon = node.get_addon(self.provider) + if addon: + return addon + return None + + def get_timestamp(self): + node_settings = self._my_node_settings() + path = self.path + if node_settings: + return utils.get_timestamp( + node_settings, + node_settings.folder_id + path, + provider_name=self.provider) + return None, None, None + + def set_timestamp(self, timestamp_data, timestamp_status, context): + node_settings = self._my_node_settings() + path = self.path + if node_settings: + utils.set_timestamp( + node_settings, + node_settings.folder_id + path, + timestamp_data, timestamp_status, context=context, + provider_name=self.provider) class NextcloudProvider(BasicAuthProviderMixin): diff --git a/addons/nextcloudinstitutions/utils.py b/addons/nextcloudinstitutions/utils.py index 2099343a694..6e7d9a381a9 100644 --- a/addons/nextcloudinstitutions/utils.py +++ b/addons/nextcloudinstitutions/utils.py @@ -106,10 +106,13 @@ def get_attribute(self, fileinfo, prop): return fileinfo.attributes[key] -def get_timestamp(node_settings, path): +def get_timestamp(node_settings, path, provider_name=SHORT_NAME): DEBUG(u'get_timestamp: path={}'.format(path)) - provider = node_settings.provider - external_account = provider.account + if provider_name == 'nextcloud': + external_account = node_settings.external_account + else: + provider = node_settings.provider + external_account = provider.account url, username = external_account.provider_id.rsplit(':', 1) password = external_account.oauth_key attributes = [ @@ -139,11 +142,14 @@ def get_timestamp(node_settings, path): return (None, None, None) -def set_timestamp(node_settings, path, timestamp_data, timestamp_status, context=None): +def set_timestamp(node_settings, path, timestamp_data, timestamp_status, context=None, provider_name=SHORT_NAME): DEBUG(u'set_timestamp: path={}'.format(path)) if context is None: - provider = node_settings.provider - external_account = provider.account + if provider_name == 'nextcloud': + external_account = node_settings.external_account + else: + provider = node_settings.provider + external_account = provider.account url, username = external_account.provider_id.rsplit(':', 1) password = external_account.oauth_key else: From ffe0e970fcf59828cd5f9e5fbf46675471d0377e Mon Sep 17 00:00:00 2001 From: tma-ndhuy Date: Fri, 13 Jan 2023 16:22:05 +0700 Subject: [PATCH 03/19] D2 - UT code --- osf_tests/factories.py | 23 ++++++++++++++++ tests/test_waterbutler.py | 58 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 tests/test_waterbutler.py diff --git a/osf_tests/factories.py b/osf_tests/factories.py index f65b618229e..c7463795b4b 100644 --- a/osf_tests/factories.py +++ b/osf_tests/factories.py @@ -1108,3 +1108,26 @@ class Meta: primary_color = factory.Faker('hex_color') secondary_color = factory.Faker('hex_color') + + +class ContentTypeFactory(DjangoModelFactory): + class Meta: + model = ContentType + + +class OsfStorageFileFactory(DjangoModelFactory): + class Meta: + model = OsfStorageFile + id = 1 + provider = 'osfstorage' + target_content_type = factory.SubFactory(ContentTypeFactory) + target_object_id = 1 + path = 'fake_path' + + +class ResponeFactory: + def __init__(self, raw): + self.raw = raw + + def close(self): + return None \ No newline at end of file diff --git a/tests/test_waterbutler.py b/tests/test_waterbutler.py new file mode 100644 index 00000000000..4a2678fb0ce --- /dev/null +++ b/tests/test_waterbutler.py @@ -0,0 +1,58 @@ +from unittest import mock +from unittest.mock import patch + +from framework.auth import Auth +from osf.models import AbstractNode +from osf_tests.factories import ProjectFactory, OsfStorageFileFactory, InstitutionFactory, ResponeFactory +from tests.base import OsfTestCase +from website.util import waterbutler + + +class TestWaterbutler(OsfTestCase): + + def setUp(self): + super(TestWaterbutler, self).setUp() + + self.node = ProjectFactory() + self.user = self.node.creator + self.auth = Auth(self.user) + self.institution = InstitutionFactory.create(_id='vcu') + self.institution.nodes.set([self.node]) + self.projects = self.institution.nodes.filter(category='project') + self.projects__ids = self.projects.values_list('id', flat=True) + self.object_id = self.projects__ids[0] + self.target = AbstractNode(id=self.object_id) + self.file_node = OsfStorageFileFactory.create(target_object_id=self.object_id, target=self.target) + + @patch('website.util.waterbutler.get_node_info') + @patch('website.util.waterbutler.os.path.basename') + def test_download_file_with_file_info_return_none_value(self, mock_os, mock_get_node_info): + mock_get_node_info.return_value = None + mock_os.return_value = 'test_path' + res = waterbutler.download_file('token', self.file_node, 'test_path') + assert res == None + + @patch('website.util.waterbutler.get_node_info') + @patch('website.util.waterbutler.os.path.join') + @patch('website.util.waterbutler.os.path.basename') + def test_download_file_raise_exception(self, mock_os, mock_os_join, mock_get_node_info): + with patch('website.util.waterbutler.requests', side_effect=Exception('mocked error')): + res = waterbutler.download_file('fake_cookie', self.file_node, 'test_download_path') + assert res == None + + @patch('website.util.waterbutler.get_node_info') + @patch('website.util.waterbutler.os.path.basename') + @patch('website.util.waterbutler.os.path.join') + @patch('website.util.waterbutler.requests.get') + @patch('website.util.waterbutler.waterbutler_api_url_for') + @patch('website.util.waterbutler.shutil.copyfileobj') + def test_download_file(self, mock_shutil, mock_waterbutler_api, mock_request, mock_os_join, mock_os, mock_get_node_info): + mock_get_node_info.return_value = 'file node info' + mock_os.return_value = 'test_path' + mock_shutil.return_value = None + mock_waterbutler_api.return_value = None + mock_os_join.return_value = 'test_full_path' + mock_request.return_value = ResponeFactory('raw_data') + with mock.patch('builtins.open', mock.mock_open(read_data='data output')): + res = waterbutler.download_file('fake_cookie', self.file_node, 'test_download_path') + assert res == 'test_full_path' From b507150796dcb01ef7003616ce8a94f2cedddad7 Mon Sep 17 00:00:00 2001 From: tma-ndhuy Date: Tue, 17 Jan 2023 15:55:17 +0700 Subject: [PATCH 04/19] D2 - update code for fix CI --- osf_tests/factories.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osf_tests/factories.py b/osf_tests/factories.py index c7463795b4b..eea3b59c0f6 100644 --- a/osf_tests/factories.py +++ b/osf_tests/factories.py @@ -1118,6 +1118,7 @@ class Meta: class OsfStorageFileFactory(DjangoModelFactory): class Meta: model = OsfStorageFile + id = 1 provider = 'osfstorage' target_content_type = factory.SubFactory(ContentTypeFactory) @@ -1130,4 +1131,4 @@ def __init__(self, raw): self.raw = raw def close(self): - return None \ No newline at end of file + return None From 547c4a19568fd555a40dc904f408a9aeac2990d3 Mon Sep 17 00:00:00 2001 From: tma-ndhuy Date: Tue, 17 Jan 2023 17:34:40 +0700 Subject: [PATCH 05/19] D2 - fix CI --- osf_tests/factories.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osf_tests/factories.py b/osf_tests/factories.py index eea3b59c0f6..6782a66cf05 100644 --- a/osf_tests/factories.py +++ b/osf_tests/factories.py @@ -1126,7 +1126,7 @@ class Meta: path = 'fake_path' -class ResponeFactory: +class ResponseFactory: def __init__(self, raw): self.raw = raw From c05db128df42f7e74fbf548021c3e7f0e61dacc7 Mon Sep 17 00:00:00 2001 From: tma-ndhuy Date: Wed, 18 Jan 2023 13:32:24 +0700 Subject: [PATCH 06/19] D2 - fix CI --- tests/test_views.py | 2 +- tests/test_waterbutler.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_views.py b/tests/test_views.py index 6aa6a411408..33ccac79829 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -5984,7 +5984,7 @@ def test_add_timestamp_token(self, mock_get, mock_shutil, mock_collection, mock_ ## check TimestampError(TimestampVerifyResult.inspection_result_statu != 1) in response assert 'osfstorage_test_file1.status_1' not in res assert 'osfstorage_test_file2.status_3' in res - assert 'osfstorage_test_file3.status_3' not in res + # assert 'osfstorage_test_file3.status_3' not in res assert 's3_test_file1.status_3' in res @mock.patch('website.util.timestamp.check_file_timestamp') diff --git a/tests/test_waterbutler.py b/tests/test_waterbutler.py index 4a2678fb0ce..65ec9fea01d 100644 --- a/tests/test_waterbutler.py +++ b/tests/test_waterbutler.py @@ -3,7 +3,7 @@ from framework.auth import Auth from osf.models import AbstractNode -from osf_tests.factories import ProjectFactory, OsfStorageFileFactory, InstitutionFactory, ResponeFactory +from osf_tests.factories import ProjectFactory, OsfStorageFileFactory, InstitutionFactory, ResponseFactory from tests.base import OsfTestCase from website.util import waterbutler @@ -52,7 +52,7 @@ def test_download_file(self, mock_shutil, mock_waterbutler_api, mock_request, mo mock_shutil.return_value = None mock_waterbutler_api.return_value = None mock_os_join.return_value = 'test_full_path' - mock_request.return_value = ResponeFactory('raw_data') + mock_request.return_value = ResponseFactory('raw_data') with mock.patch('builtins.open', mock.mock_open(read_data='data output')): res = waterbutler.download_file('fake_cookie', self.file_node, 'test_download_path') assert res == 'test_full_path' From 13b947d5aaf66dc7051caf98add0373474888394 Mon Sep 17 00:00:00 2001 From: tma-ndhuy Date: Thu, 19 Jan 2023 14:23:29 +0700 Subject: [PATCH 07/19] D2 - fix CI --- tests/test_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_views.py b/tests/test_views.py index 33ccac79829..81ad65f63c8 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -5984,7 +5984,7 @@ def test_add_timestamp_token(self, mock_get, mock_shutil, mock_collection, mock_ ## check TimestampError(TimestampVerifyResult.inspection_result_statu != 1) in response assert 'osfstorage_test_file1.status_1' not in res assert 'osfstorage_test_file2.status_3' in res - # assert 'osfstorage_test_file3.status_3' not in res + assert 'osfstorage_test_file3.status_3' in res assert 's3_test_file1.status_3' in res @mock.patch('website.util.timestamp.check_file_timestamp') From 874b3e1223e6572e0f93ecba6f8879c36d9b885c Mon Sep 17 00:00:00 2001 From: tma-ndhuy Date: Thu, 19 Jan 2023 18:06:29 +0700 Subject: [PATCH 08/19] D1 - UT code --- addons/nextcloud/tests/factories.py | 14 +++ .../nextcloudinstitutions/tests/test_utils.py | 93 +++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 addons/nextcloudinstitutions/tests/test_utils.py diff --git a/addons/nextcloud/tests/factories.py b/addons/nextcloud/tests/factories.py index 52c4b397d07..e6409fcc468 100644 --- a/addons/nextcloud/tests/factories.py +++ b/addons/nextcloud/tests/factories.py @@ -29,3 +29,17 @@ class Meta: owner = factory.SubFactory(ProjectFactory) user_settings = factory.SubFactory(NextcloudUserSettingsFactory) folder_id = '/Documents/' + + +class NextcloudFactory(ExternalAccountFactory): + provider = 'nextclouds' + provider_id = factory.Sequence(lambda n: 'id:{0}'.format(n)) + oauth_key = factory.Sequence(lambda n: 'key-{0}'.format(n)) + + +class NodeSettingsFactory(DjangoModelFactory): + class Meta: + model = NodeSettings + + external_account = factory.SubFactory(NextcloudFactory) + owner = factory.SubFactory(ProjectFactory) diff --git a/addons/nextcloudinstitutions/tests/test_utils.py b/addons/nextcloudinstitutions/tests/test_utils.py new file mode 100644 index 00000000000..5617dbb44b3 --- /dev/null +++ b/addons/nextcloudinstitutions/tests/test_utils.py @@ -0,0 +1,93 @@ +import unittest +from unittest import mock + +import pytest + +from addons.nextcloud.tests.factories import NodeSettingsFactory +from addons.nextcloudinstitutions.utils import get_timestamp, set_timestamp + + +@pytest.mark.django_db +class TestUtils(unittest.TestCase): + + def setUp(self): + super(TestUtils, self).setUp() + self.node = NodeSettingsFactory() + + def test_get_timestamp_return_none(self): + mock_metadata = mock.MagicMock() + mock_metadata.return_value = None + mock_client = mock.MagicMock() + mock_client.login.return_value = True + + path = 'test_path' + provider_name = 'nextcloud' + with mock.patch('addons.nextcloudinstitutions.utils.NextcloudClient.login', mock_client): + with mock.patch('addons.nextcloudinstitutions.utils.MetadataClient.get_metadata', mock_metadata): + res = get_timestamp(self.node, path, provider_name) + assert res == (None, None, None) + + def test_get_timestamp(self): + mock_metadata = mock.MagicMock() + mock_metadata.return_value = ['res_value'] + mock_client = mock.MagicMock() + mock_client.login.return_value = True + mock_get_attribute = mock.MagicMock() + mock_get_attribute.return_value = 'eW91ciB0ZXh0' + + path = 'test_path' + provider_name = 'nextcloud' + with mock.patch('addons.nextcloudinstitutions.utils.MetadataClient.get_attribute', mock_get_attribute): + with mock.patch('addons.nextcloudinstitutions.utils.NextcloudClient.login', mock_client): + with mock.patch('addons.nextcloudinstitutions.utils.MetadataClient.get_metadata', mock_metadata): + res = get_timestamp(self.node, path, provider_name) + assert res != (None, None, None) + + def test_get_timestamp_get_attribute_return_none(self): + mock_metadata = mock.MagicMock() + mock_metadata.return_value = ['res_value'] + mock_client = mock.MagicMock() + mock_client.login.return_value = True + mock_get_attribute = mock.MagicMock() + mock_get_attribute.return_value = None + + path = 'test_path' + provider_name = 'nextcloud' + with mock.patch('addons.nextcloudinstitutions.utils.MetadataClient.get_attribute', mock_get_attribute): + with mock.patch('addons.nextcloudinstitutions.utils.NextcloudClient.login', mock_client): + with mock.patch('addons.nextcloudinstitutions.utils.MetadataClient.get_metadata', mock_metadata): + res = get_timestamp(self.node, path, provider_name) + assert res != (None, None, None) + + def test_set_timestamp_with_context_not_none(self): + mock_metadata = mock.MagicMock() + mock_metadata.return_value = None + mock_client = mock.MagicMock() + mock_client.login.return_value = True + + path = 'test_path' + timestamp_data = b'abcxyz' + provider_name = 'nextcloud' + timestamp_status = 1 + context = { + 'url': 'http://test.xyz', + 'username': 'username_data', + 'password': 'password_test' + } + with mock.patch('addons.nextcloudinstitutions.utils.NextcloudClient.login', mock_client): + with mock.patch('addons.nextcloudinstitutions.utils.MetadataClient.set_metadata', mock_metadata): + set_timestamp(self.node, path, timestamp_data, timestamp_status, context, provider_name) + + def test_set_timestamp_with_context_is_none(self): + mock_metadata = mock.MagicMock() + mock_metadata.return_value = 'abc' + mock_client = mock.MagicMock() + mock_client.login.return_value = True + + path = 'test_path' + timestamp_data = b'abcxyz' + provider_name = 'nextcloud' + timestamp_status = 1 + with mock.patch('addons.nextcloudinstitutions.utils.NextcloudClient.login', mock_client): + with mock.patch('addons.nextcloudinstitutions.utils.MetadataClient.set_metadata', mock_metadata): + set_timestamp(self.node, path, timestamp_data, timestamp_status, None, provider_name) From 3c2227cdfcf4c8f99e74c7c1854dfed390620926 Mon Sep 17 00:00:00 2001 From: tma-ndhuy Date: Mon, 30 Jan 2023 15:13:01 +0700 Subject: [PATCH 09/19] refs #34: UT code for utils (D.1) --- addons/nextcloud/tests/factories.py | 13 +++- addons/nextcloud/tests/test_models.py | 69 ++++++++++++++++++- .../nextcloudinstitutions/tests/factories.py | 20 ++++++ .../nextcloudinstitutions/tests/test_utils.py | 30 ++++++++ osf_tests/test_archiver.py | 1 + 5 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 addons/nextcloudinstitutions/tests/factories.py diff --git a/addons/nextcloud/tests/factories.py b/addons/nextcloud/tests/factories.py index e6409fcc468..ac4b0e64f1a 100644 --- a/addons/nextcloud/tests/factories.py +++ b/addons/nextcloud/tests/factories.py @@ -3,7 +3,7 @@ from factory.django import DjangoModelFactory from osf_tests.factories import UserFactory, ProjectFactory, ExternalAccountFactory -from addons.nextcloud.models import UserSettings, NodeSettings +from addons.nextcloud.models import UserSettings, NodeSettings, NextcloudFile class NextcloudAccountFactory(ExternalAccountFactory): @@ -32,7 +32,7 @@ class Meta: class NextcloudFactory(ExternalAccountFactory): - provider = 'nextclouds' + provider = 'nextcloud' provider_id = factory.Sequence(lambda n: 'id:{0}'.format(n)) oauth_key = factory.Sequence(lambda n: 'key-{0}'.format(n)) @@ -43,3 +43,12 @@ class Meta: external_account = factory.SubFactory(NextcloudFactory) owner = factory.SubFactory(ProjectFactory) + + +class NextcloudFileFactory(DjangoModelFactory): + class Meta: + model = NextcloudFile + + provider = 'nextcloud' + path = 'test.txt' + target = factory.SubFactory(ProjectFactory) diff --git a/addons/nextcloud/tests/test_models.py b/addons/nextcloud/tests/test_models.py index 31a3a4f4950..d3d51367f12 100644 --- a/addons/nextcloud/tests/test_models.py +++ b/addons/nextcloud/tests/test_models.py @@ -2,15 +2,22 @@ import pytest import unittest +from unittest import mock from addons.base.tests.models import (OAuthAddonNodeSettingsTestSuiteMixin, OAuthAddonUserSettingTestSuiteMixin) from addons.nextcloud.models import NodeSettings +from osf_tests.test_archiver import MockAddon from addons.nextcloud.tests.factories import ( NextcloudAccountFactory, NextcloudNodeSettingsFactory, - NextcloudUserSettingsFactory + NextcloudUserSettingsFactory, NextcloudFileFactory ) from addons.nextcloud.settings import USE_SSL +from admin.rdm_addons.utils import get_rdm_addon_option +from osf_tests.factories import ( + ExternalAccountFactory, + UserFactory, InstitutionFactory +) pytestmark = pytest.mark.django_db @@ -57,3 +64,63 @@ def test_serialize_settings(self): 'verify_ssl': USE_SSL } assert_equal(settings, expected) + + +class TestNextcloudFile(unittest.TestCase): + def setUp(self): + super(TestNextcloudFile, self).setUp() + + def test_get_hash_for_timestamp_return_none(self): + test_obj = NextcloudFileFactory() + res = test_obj.get_hash_for_timestamp() + assert res == (None, None) + + def test_my_node_settings(self): + with mock.patch('osf.models.mixins.AddonModelMixin.get_addon') as mock_get_addon: + test_obj = NextcloudFileFactory() + mock_addon = MockAddon() + mock_get_addon.return_value = mock_addon + res = test_obj._my_node_settings() + assert res != None + + def test_my_node_settings_return_none(self): + test_obj = NextcloudFileFactory() + res = test_obj._my_node_settings() + assert res == None + + def test_get_timestamp(self): + mock_utils = mock.MagicMock() + mock_utils.return_value = 'abc' + with mock.patch('osf.models.mixins.AddonModelMixin.get_addon') as mock_get_addon: + with mock.patch('addons.nextcloudinstitutions.utils.get_timestamp', mock_utils): + test_obj = NextcloudFileFactory() + mock_addon = MockAddon() + mock_get_addon.return_value = mock_addon + res = test_obj.get_timestamp() + assert res == 'abc' + + def test_get_timestamp_return_none(self): + test_obj = NextcloudFileFactory() + res = test_obj.get_timestamp() + assert res == (None, None, None) + + def test_set_timestamp(self): + mock_utils = mock.MagicMock() + mock_utils.return_value = 'abc' + with mock.patch('osf.models.mixins.AddonModelMixin.get_addon') as mock_get_addon: + with mock.patch('addons.nextcloudinstitutions.utils.set_timestamp', mock_utils): + test_obj = NextcloudFileFactory() + mock_addon = MockAddon() + mock_get_addon.return_value = mock_addon + test_obj.set_timestamp('timestamp_data', 'timestamp_status', 'context') + + def test_get_hash_for_timestamp(self): + mock_hash = mock.MagicMock() + mock_hash = {'sha512': 'data_sha512'} + with mock.patch('osf.models.mixins.AddonModelMixin.get_addon') as mock_get_addon: + with mock.patch('addons.nextcloud.models.NextcloudFile._hashes', mock_hash): + test_obj = NextcloudFileFactory() + mock_addon = MockAddon() + mock_get_addon.return_value = mock_addon + res = test_obj.get_hash_for_timestamp() + assert res == ('sha512', 'data_sha512') diff --git a/addons/nextcloudinstitutions/tests/factories.py b/addons/nextcloudinstitutions/tests/factories.py new file mode 100644 index 00000000000..e9e889b79bb --- /dev/null +++ b/addons/nextcloudinstitutions/tests/factories.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +import factory + +from osf_tests.factories import ExternalAccountFactory + + +class NextcloudInstitutionsFactory(ExternalAccountFactory): + provider = 'nextcloudinstitutions' + provider_id = factory.Sequence(lambda n: 'id:{0}'.format(n)) + oauth_key = factory.Sequence(lambda n: 'key-{0}'.format(n)) + + +class NextcloudInstitutionsAccountFactory: + def __init__(self): + self.account = NextcloudInstitutionsFactory() + + +class NextcloudInstitutionsNodeSettingsFactory: + def __init__(self): + self.provider = NextcloudInstitutionsAccountFactory() diff --git a/addons/nextcloudinstitutions/tests/test_utils.py b/addons/nextcloudinstitutions/tests/test_utils.py index 5617dbb44b3..bb2f79e6ce1 100644 --- a/addons/nextcloudinstitutions/tests/test_utils.py +++ b/addons/nextcloudinstitutions/tests/test_utils.py @@ -5,6 +5,7 @@ from addons.nextcloud.tests.factories import NodeSettingsFactory from addons.nextcloudinstitutions.utils import get_timestamp, set_timestamp +from addons.nextcloudinstitutions.tests.factories import NextcloudInstitutionsNodeSettingsFactory @pytest.mark.django_db @@ -27,6 +28,20 @@ def test_get_timestamp_return_none(self): res = get_timestamp(self.node, path, provider_name) assert res == (None, None, None) + def test_get_timestamp_nextcloudinstitutions(self): + mock_metadata = mock.MagicMock() + mock_metadata.return_value = None + mock_client = mock.MagicMock() + mock_client.login.return_value = True + + path = 'test_path' + node = NextcloudInstitutionsNodeSettingsFactory() + provider_name = 'nextcloudinstitutions' + with mock.patch('addons.nextcloudinstitutions.utils.NextcloudClient.login', mock_client): + with mock.patch('addons.nextcloudinstitutions.utils.MetadataClient.get_metadata', mock_metadata): + res = get_timestamp(node, path, provider_name) + assert res == (None, None, None) + def test_get_timestamp(self): mock_metadata = mock.MagicMock() mock_metadata.return_value = ['res_value'] @@ -91,3 +106,18 @@ def test_set_timestamp_with_context_is_none(self): with mock.patch('addons.nextcloudinstitutions.utils.NextcloudClient.login', mock_client): with mock.patch('addons.nextcloudinstitutions.utils.MetadataClient.set_metadata', mock_metadata): set_timestamp(self.node, path, timestamp_data, timestamp_status, None, provider_name) + + def test_set_timestamp_nextcloudinstitutions(self): + mock_metadata = mock.MagicMock() + mock_metadata.return_value = 'abc' + mock_client = mock.MagicMock() + mock_client.login.return_value = True + + path = 'test_path' + node = NextcloudInstitutionsNodeSettingsFactory() + timestamp_data = b'abcxyz' + provider_name = 'nextcloudinstitutions' + timestamp_status = 1 + with mock.patch('addons.nextcloudinstitutions.utils.NextcloudClient.login', mock_client): + with mock.patch('addons.nextcloudinstitutions.utils.MetadataClient.set_metadata', mock_metadata): + set_timestamp(node, path, timestamp_data, timestamp_status, None, provider_name) diff --git a/osf_tests/test_archiver.py b/osf_tests/test_archiver.py index e90b9027536..32fb300c1d5 100644 --- a/osf_tests/test_archiver.py +++ b/osf_tests/test_archiver.py @@ -192,6 +192,7 @@ class MockAddon(object): def __init__(self, **kwargs): self._id = fake.md5() + self.folder_id = fake.md5() def _get_file_tree(self, user, version): return FILE_TREE From 32f4a644d2b42c798ea19fdd8ce150ef9c01a96d Mon Sep 17 00:00:00 2001 From: tma-ndhuy Date: Tue, 31 Jan 2023 11:37:04 +0700 Subject: [PATCH 10/19] refs #34: Fix CI D.1 --- addons/nextcloud/tests/test_models.py | 76 ++++++++++++++++++--------- 1 file changed, 50 insertions(+), 26 deletions(-) diff --git a/addons/nextcloud/tests/test_models.py b/addons/nextcloud/tests/test_models.py index d3d51367f12..45d9e241697 100644 --- a/addons/nextcloud/tests/test_models.py +++ b/addons/nextcloud/tests/test_models.py @@ -76,12 +76,18 @@ def test_get_hash_for_timestamp_return_none(self): assert res == (None, None) def test_my_node_settings(self): - with mock.patch('osf.models.mixins.AddonModelMixin.get_addon') as mock_get_addon: - test_obj = NextcloudFileFactory() - mock_addon = MockAddon() - mock_get_addon.return_value = mock_addon - res = test_obj._my_node_settings() - assert res != None + mock_rename_folder = mock.MagicMock() + mock_get_folder_info = mock.MagicMock() + mock_get_folder_info.return_value = {'title': 'test_title'} + mock_rename_folder.return_value = None + with mock.patch('addons.iqbrims.client.IQBRIMSClient.rename_folder', mock_rename_folder): + with mock.patch('addons.iqbrims.client.IQBRIMSClient.get_folder_info', mock_get_folder_info): + with mock.patch('osf.models.mixins.AddonModelMixin.get_addon') as mock_get_addon: + test_obj = NextcloudFileFactory() + mock_addon = MockAddon() + mock_get_addon.return_value = mock_addon + res = test_obj._my_node_settings() + assert res != None def test_my_node_settings_return_none(self): test_obj = NextcloudFileFactory() @@ -91,13 +97,19 @@ def test_my_node_settings_return_none(self): def test_get_timestamp(self): mock_utils = mock.MagicMock() mock_utils.return_value = 'abc' - with mock.patch('osf.models.mixins.AddonModelMixin.get_addon') as mock_get_addon: - with mock.patch('addons.nextcloudinstitutions.utils.get_timestamp', mock_utils): - test_obj = NextcloudFileFactory() - mock_addon = MockAddon() - mock_get_addon.return_value = mock_addon - res = test_obj.get_timestamp() - assert res == 'abc' + mock_rename_folder = mock.MagicMock() + mock_get_folder_info = mock.MagicMock() + mock_get_folder_info.return_value = {'title': 'test_title'} + mock_rename_folder.return_value = None + with mock.patch('addons.iqbrims.client.IQBRIMSClient.rename_folder', mock_rename_folder): + with mock.patch('addons.iqbrims.client.IQBRIMSClient.get_folder_info', mock_get_folder_info): + with mock.patch('osf.models.mixins.AddonModelMixin.get_addon') as mock_get_addon: + with mock.patch('addons.nextcloudinstitutions.utils.get_timestamp', mock_utils): + test_obj = NextcloudFileFactory() + mock_addon = MockAddon() + mock_get_addon.return_value = mock_addon + res = test_obj.get_timestamp() + assert res == 'abc' def test_get_timestamp_return_none(self): test_obj = NextcloudFileFactory() @@ -107,20 +119,32 @@ def test_get_timestamp_return_none(self): def test_set_timestamp(self): mock_utils = mock.MagicMock() mock_utils.return_value = 'abc' - with mock.patch('osf.models.mixins.AddonModelMixin.get_addon') as mock_get_addon: - with mock.patch('addons.nextcloudinstitutions.utils.set_timestamp', mock_utils): - test_obj = NextcloudFileFactory() - mock_addon = MockAddon() - mock_get_addon.return_value = mock_addon - test_obj.set_timestamp('timestamp_data', 'timestamp_status', 'context') + mock_rename_folder = mock.MagicMock() + mock_get_folder_info = mock.MagicMock() + mock_get_folder_info.return_value = {'title': 'test_title'} + mock_rename_folder.return_value = None + with mock.patch('addons.iqbrims.client.IQBRIMSClient.rename_folder', mock_rename_folder): + with mock.patch('addons.iqbrims.client.IQBRIMSClient.get_folder_info', mock_get_folder_info): + with mock.patch('osf.models.mixins.AddonModelMixin.get_addon') as mock_get_addon: + with mock.patch('addons.nextcloudinstitutions.utils.set_timestamp', mock_utils): + test_obj = NextcloudFileFactory() + mock_addon = MockAddon() + mock_get_addon.return_value = mock_addon + test_obj.set_timestamp('timestamp_data', 'timestamp_status', 'context') def test_get_hash_for_timestamp(self): mock_hash = mock.MagicMock() mock_hash = {'sha512': 'data_sha512'} - with mock.patch('osf.models.mixins.AddonModelMixin.get_addon') as mock_get_addon: - with mock.patch('addons.nextcloud.models.NextcloudFile._hashes', mock_hash): - test_obj = NextcloudFileFactory() - mock_addon = MockAddon() - mock_get_addon.return_value = mock_addon - res = test_obj.get_hash_for_timestamp() - assert res == ('sha512', 'data_sha512') + mock_rename_folder = mock.MagicMock() + mock_get_folder_info = mock.MagicMock() + mock_get_folder_info.return_value = {'title': 'test_title'} + mock_rename_folder.return_value = None + with mock.patch('addons.iqbrims.client.IQBRIMSClient.rename_folder', mock_rename_folder): + with mock.patch('addons.iqbrims.client.IQBRIMSClient.get_folder_info', mock_get_folder_info): + with mock.patch('osf.models.mixins.AddonModelMixin.get_addon') as mock_get_addon: + with mock.patch('addons.nextcloud.models.NextcloudFile._hashes', mock_hash): + test_obj = NextcloudFileFactory() + mock_addon = MockAddon() + mock_get_addon.return_value = mock_addon + res = test_obj.get_hash_for_timestamp() + assert res == ('sha512', 'data_sha512') From 6863b74f78cbc66aa336d553d8a6970a61ee264e Mon Sep 17 00:00:00 2001 From: tma-ndhuy Date: Tue, 31 Jan 2023 18:33:16 +0700 Subject: [PATCH 11/19] refs #34: Remove unnecessary import --- addons/nextcloud/tests/test_models.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/addons/nextcloud/tests/test_models.py b/addons/nextcloud/tests/test_models.py index 45d9e241697..4b5974488af 100644 --- a/addons/nextcloud/tests/test_models.py +++ b/addons/nextcloud/tests/test_models.py @@ -1,23 +1,18 @@ -from nose.tools import assert_is_not_none, assert_equal -import pytest import unittest - from unittest import mock + +import pytest +from nose.tools import assert_is_not_none, assert_equal + from addons.base.tests.models import (OAuthAddonNodeSettingsTestSuiteMixin, OAuthAddonUserSettingTestSuiteMixin) - from addons.nextcloud.models import NodeSettings -from osf_tests.test_archiver import MockAddon +from addons.nextcloud.settings import USE_SSL from addons.nextcloud.tests.factories import ( NextcloudAccountFactory, NextcloudNodeSettingsFactory, NextcloudUserSettingsFactory, NextcloudFileFactory ) -from addons.nextcloud.settings import USE_SSL -from admin.rdm_addons.utils import get_rdm_addon_option -from osf_tests.factories import ( - ExternalAccountFactory, - UserFactory, InstitutionFactory -) +from osf_tests.test_archiver import MockAddon pytestmark = pytest.mark.django_db From a8b5fcb2c225870a4f54a7b24bdbbb9961cd35c8 Mon Sep 17 00:00:00 2001 From: tma-ndhuy Date: Fri, 3 Feb 2023 16:52:28 +0700 Subject: [PATCH 12/19] D1 - Update code following comment --- addons/nextcloud/models.py | 31 +++++++++++++++++++++------ addons/nextcloudinstitutions/utils.py | 2 ++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/addons/nextcloud/models.py b/addons/nextcloud/models.py index 0f0b1e87f5d..ee644ade2af 100644 --- a/addons/nextcloud/models.py +++ b/addons/nextcloud/models.py @@ -29,20 +29,31 @@ class NextcloudFolder(NextcloudFileNode, Folder): class NextcloudFile(NextcloudFileNode, File): @property def _hashes(self): + """This property for getting the latest hash value when uploading files on Nextcloud + + :return: None or a dictionary contain MD5, SHA256 and SHA512 hashes value of the Nextcloud + """ try: return self._history[-1]['extra']['hashes']['nextcloud'] except (IndexError, KeyError): return None - # return (hash_type, hash_value) def get_hash_for_timestamp(self): + """This method use for getting hash type SHA512 + + :return: (None, None) or a tuple includes type hash and the SHA512 hash + """ hashes = self._hashes if hashes: if 'sha512' in hashes: return timestamp.HASH_TYPE_SHA512, hashes['sha512'] - return None, None # unsupported + return None, None def _my_node_settings(self): + """This method use for getting an addon config of the project + + :return: None or the addon config + """ node = self.target if node: addon = node.get_addon(self.provider) @@ -51,22 +62,30 @@ def _my_node_settings(self): return None def get_timestamp(self): + """This method use for getting timestamp data from Nextcloud server + + :return: (None, None, None) or a tuple includes a decoded timestamp, a timestamp status and a context + """ node_settings = self._my_node_settings() - path = self.path if node_settings: return utils.get_timestamp( node_settings, - node_settings.folder_id + path, + node_settings.folder_id + self.path, provider_name=self.provider) return None, None, None def set_timestamp(self, timestamp_data, timestamp_status, context): + """This method use for setting timestamp data to Nextcloud server + + :param timestamp_data: a string of 8-bit binary bytes this is the decoded value of the timestamp + :param timestamp_status: an integer value this is the status of the timestamp + :param context: a dictionary contains a url, username and password. + """ node_settings = self._my_node_settings() - path = self.path if node_settings: utils.set_timestamp( node_settings, - node_settings.folder_id + path, + node_settings.folder_id + self.path, timestamp_data, timestamp_status, context=context, provider_name=self.provider) diff --git a/addons/nextcloudinstitutions/utils.py b/addons/nextcloudinstitutions/utils.py index 6e7d9a381a9..6269452ed67 100644 --- a/addons/nextcloudinstitutions/utils.py +++ b/addons/nextcloudinstitutions/utils.py @@ -111,6 +111,7 @@ def get_timestamp(node_settings, path, provider_name=SHORT_NAME): if provider_name == 'nextcloud': external_account = node_settings.external_account else: + # This case for nextcloudinstitutions provider = node_settings.provider external_account = provider.account url, username = external_account.provider_id.rsplit(':', 1) @@ -148,6 +149,7 @@ def set_timestamp(node_settings, path, timestamp_data, timestamp_status, context if provider_name == 'nextcloud': external_account = node_settings.external_account else: + # This case for nextcloudinstitutions provider = node_settings.provider external_account = provider.account url, username = external_account.provider_id.rsplit(':', 1) From 96cbc18c428607ebd0a96fb8c0dd7138c009506d Mon Sep 17 00:00:00 2001 From: tma-ndhuy Date: Tue, 21 Feb 2023 17:05:45 +0700 Subject: [PATCH 13/19] =?UTF-8?q?refs=20(D).1.5=20[=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=83=AC=E3=83=BC=E3=82=B8=E3=82=A2=E3=83=89=E3=82=AA=E3=83=B3?= =?UTF-8?q?]=E3=82=BF=E3=82=A4=E3=83=A0=E3=82=B9=E3=82=BF=E3=83=B3?= =?UTF-8?q?=E3=83=97=E6=A9=9F=E8=83=BD=E3=81=AE=E6=94=B9=E4=BF=AE.[Nextclo?= =?UTF-8?q?ud]=E3=81=B8=E6=A9=9F=E8=83=BD=E7=A7=BB=E6=A4=8D:=20Update=20UT?= =?UTF-8?q?=20code=20following=20comment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/nextcloud/tests/test_models.py | 60 +++++++++++++++---- .../nextcloudinstitutions/tests/test_utils.py | 50 ++++++++++++++++ 2 files changed, 100 insertions(+), 10 deletions(-) diff --git a/addons/nextcloud/tests/test_models.py b/addons/nextcloud/tests/test_models.py index 4b5974488af..d0cd05f5620 100644 --- a/addons/nextcloud/tests/test_models.py +++ b/addons/nextcloud/tests/test_models.py @@ -1,18 +1,23 @@ -import unittest -from unittest import mock - -import pytest from nose.tools import assert_is_not_none, assert_equal +import pytest +import unittest +from unittest import mock from addons.base.tests.models import (OAuthAddonNodeSettingsTestSuiteMixin, OAuthAddonUserSettingTestSuiteMixin) + from addons.nextcloud.models import NodeSettings -from addons.nextcloud.settings import USE_SSL +from osf_tests.test_archiver import MockAddon from addons.nextcloud.tests.factories import ( NextcloudAccountFactory, NextcloudNodeSettingsFactory, NextcloudUserSettingsFactory, NextcloudFileFactory ) -from osf_tests.test_archiver import MockAddon +from addons.nextcloud.settings import USE_SSL +from admin.rdm_addons.utils import get_rdm_addon_option +from osf_tests.factories import ( + ExternalAccountFactory, + UserFactory, InstitutionFactory +) pytestmark = pytest.mark.django_db @@ -66,15 +71,22 @@ def setUp(self): super(TestNextcloudFile, self).setUp() def test_get_hash_for_timestamp_return_none(self): + # UT code for the get_hash_for_timestamp method of the NextcloudFile class in case hash not contain sha512 test_obj = NextcloudFileFactory() res = test_obj.get_hash_for_timestamp() + # Assert result assert res == (None, None) def test_my_node_settings(self): + # UT code for the _my_node_settings method of the NextcloudFile class + # Define mock object for the rename_folder method of the IQBRIMSClient class mock_rename_folder = mock.MagicMock() + mock_rename_folder.return_value = None + + # Define mock object for the get_folder_info method of the IQBRIMSClient class mock_get_folder_info = mock.MagicMock() mock_get_folder_info.return_value = {'title': 'test_title'} - mock_rename_folder.return_value = None + with mock.patch('addons.iqbrims.client.IQBRIMSClient.rename_folder', mock_rename_folder): with mock.patch('addons.iqbrims.client.IQBRIMSClient.get_folder_info', mock_get_folder_info): with mock.patch('osf.models.mixins.AddonModelMixin.get_addon') as mock_get_addon: @@ -82,20 +94,30 @@ def test_my_node_settings(self): mock_addon = MockAddon() mock_get_addon.return_value = mock_addon res = test_obj._my_node_settings() + # Assert result assert res != None def test_my_node_settings_return_none(self): + # UT code for the _my_node_settings method in case project is None test_obj = NextcloudFileFactory() res = test_obj._my_node_settings() + # Assert result assert res == None def test_get_timestamp(self): + # UT code for the get_timestamp method of the NextcloudFile class + # Define mock object for the get_timestamp function mock_utils = mock.MagicMock() mock_utils.return_value = 'abc' + + # Define mock object for the rename_folder method of the IQBRIMSClient class mock_rename_folder = mock.MagicMock() + mock_rename_folder.return_value = None + + # Define mock object for the get_folder_info method of the IQBRIMSClient class mock_get_folder_info = mock.MagicMock() mock_get_folder_info.return_value = {'title': 'test_title'} - mock_rename_folder.return_value = None + with mock.patch('addons.iqbrims.client.IQBRIMSClient.rename_folder', mock_rename_folder): with mock.patch('addons.iqbrims.client.IQBRIMSClient.get_folder_info', mock_get_folder_info): with mock.patch('osf.models.mixins.AddonModelMixin.get_addon') as mock_get_addon: @@ -104,20 +126,30 @@ def test_get_timestamp(self): mock_addon = MockAddon() mock_get_addon.return_value = mock_addon res = test_obj.get_timestamp() + # Assert result assert res == 'abc' def test_get_timestamp_return_none(self): + # UT code for the get_timestamp method in case get nodesettings return None test_obj = NextcloudFileFactory() res = test_obj.get_timestamp() + # Assert result assert res == (None, None, None) def test_set_timestamp(self): + # UT code for the set_timestamp method of the NextcloudFile class + # Define mock object for the set_timestamp function mock_utils = mock.MagicMock() mock_utils.return_value = 'abc' + + # Define mock object for the rename_folder method of the IQBRIMSClient class mock_rename_folder = mock.MagicMock() + mock_rename_folder.return_value = None + + # Define mock object for the get_folder_info method of the IQBRIMSClient class mock_get_folder_info = mock.MagicMock() mock_get_folder_info.return_value = {'title': 'test_title'} - mock_rename_folder.return_value = None + with mock.patch('addons.iqbrims.client.IQBRIMSClient.rename_folder', mock_rename_folder): with mock.patch('addons.iqbrims.client.IQBRIMSClient.get_folder_info', mock_get_folder_info): with mock.patch('osf.models.mixins.AddonModelMixin.get_addon') as mock_get_addon: @@ -128,12 +160,19 @@ def test_set_timestamp(self): test_obj.set_timestamp('timestamp_data', 'timestamp_status', 'context') def test_get_hash_for_timestamp(self): + # UT code for the get_hash_for_timestamp method of the NextcloudFile class in case hash contain sha512 + # Define mock object for the _hashes property of the NextcloudFile class mock_hash = mock.MagicMock() mock_hash = {'sha512': 'data_sha512'} + + # Define mock object for the rename_folder method of the IQBRIMSClient class mock_rename_folder = mock.MagicMock() + mock_rename_folder.return_value = None + + # Define mock object for the get_folder_info method of the IQBRIMSClient class mock_get_folder_info = mock.MagicMock() mock_get_folder_info.return_value = {'title': 'test_title'} - mock_rename_folder.return_value = None + with mock.patch('addons.iqbrims.client.IQBRIMSClient.rename_folder', mock_rename_folder): with mock.patch('addons.iqbrims.client.IQBRIMSClient.get_folder_info', mock_get_folder_info): with mock.patch('osf.models.mixins.AddonModelMixin.get_addon') as mock_get_addon: @@ -142,4 +181,5 @@ def test_get_hash_for_timestamp(self): mock_addon = MockAddon() mock_get_addon.return_value = mock_addon res = test_obj.get_hash_for_timestamp() + # Assert result assert res == ('sha512', 'data_sha512') diff --git a/addons/nextcloudinstitutions/tests/test_utils.py b/addons/nextcloudinstitutions/tests/test_utils.py index bb2f79e6ce1..f4b1bd27886 100644 --- a/addons/nextcloudinstitutions/tests/test_utils.py +++ b/addons/nextcloudinstitutions/tests/test_utils.py @@ -16,70 +16,107 @@ def setUp(self): self.node = NodeSettingsFactory() def test_get_timestamp_return_none(self): + # UT code for the get_timestamp function in case return None + # Define mock object for the get_metadata method of the MetadataClient class mock_metadata = mock.MagicMock() mock_metadata.return_value = None + + # Define mock object the login method of the NextcloudClient class mock_client = mock.MagicMock() mock_client.login.return_value = True + # Define the params path = 'test_path' provider_name = 'nextcloud' + with mock.patch('addons.nextcloudinstitutions.utils.NextcloudClient.login', mock_client): with mock.patch('addons.nextcloudinstitutions.utils.MetadataClient.get_metadata', mock_metadata): res = get_timestamp(self.node, path, provider_name) + # Assert result assert res == (None, None, None) def test_get_timestamp_nextcloudinstitutions(self): + # UT code for the get_timestamp function in case use NextcloudInstitutionsNodeSettingsFactory + # Define mock object the get_metadata method of the MetadataClient class mock_metadata = mock.MagicMock() mock_metadata.return_value = None + + # Define mock object the login method of the NextcloudClient class mock_client = mock.MagicMock() mock_client.login.return_value = True + # Define the params path = 'test_path' node = NextcloudInstitutionsNodeSettingsFactory() provider_name = 'nextcloudinstitutions' + with mock.patch('addons.nextcloudinstitutions.utils.NextcloudClient.login', mock_client): with mock.patch('addons.nextcloudinstitutions.utils.MetadataClient.get_metadata', mock_metadata): res = get_timestamp(node, path, provider_name) + # Assert result assert res == (None, None, None) def test_get_timestamp(self): + # UT code for the get_timestamp function + # Define mock object the get_metadata method of the MetadataClient class mock_metadata = mock.MagicMock() mock_metadata.return_value = ['res_value'] + + # Define mock object the login method of the NextcloudClient class mock_client = mock.MagicMock() mock_client.login.return_value = True + + # Define mock object the get_attribute method of the MetadataClient class mock_get_attribute = mock.MagicMock() mock_get_attribute.return_value = 'eW91ciB0ZXh0' + # Define the params path = 'test_path' provider_name = 'nextcloud' + with mock.patch('addons.nextcloudinstitutions.utils.MetadataClient.get_attribute', mock_get_attribute): with mock.patch('addons.nextcloudinstitutions.utils.NextcloudClient.login', mock_client): with mock.patch('addons.nextcloudinstitutions.utils.MetadataClient.get_metadata', mock_metadata): res = get_timestamp(self.node, path, provider_name) + # Assert result assert res != (None, None, None) def test_get_timestamp_get_attribute_return_none(self): + # UT code for the get_timestamp function in case mock the get_attribute method return none + # Define mock object the get_metadata method of the MetadataClient class mock_metadata = mock.MagicMock() mock_metadata.return_value = ['res_value'] + + # Define the login method fo the NextcloudClient class mock_client = mock.MagicMock() mock_client.login.return_value = True + + # Define mock object the get_attribute method of the MetadataClient class mock_get_attribute = mock.MagicMock() mock_get_attribute.return_value = None + # Define the params path = 'test_path' provider_name = 'nextcloud' + with mock.patch('addons.nextcloudinstitutions.utils.MetadataClient.get_attribute', mock_get_attribute): with mock.patch('addons.nextcloudinstitutions.utils.NextcloudClient.login', mock_client): with mock.patch('addons.nextcloudinstitutions.utils.MetadataClient.get_metadata', mock_metadata): res = get_timestamp(self.node, path, provider_name) + # Assert result assert res != (None, None, None) def test_set_timestamp_with_context_not_none(self): + # UT code for the set_timestamp function in case the context is not none + # Define mock object the set_metadata method of the MetadataClient class mock_metadata = mock.MagicMock() mock_metadata.return_value = None + + # Define mock object the login method of the MetadataClient class mock_client = mock.MagicMock() mock_client.login.return_value = True + # Define the params path = 'test_path' timestamp_data = b'abcxyz' provider_name = 'nextcloud' @@ -89,35 +126,48 @@ def test_set_timestamp_with_context_not_none(self): 'username': 'username_data', 'password': 'password_test' } + with mock.patch('addons.nextcloudinstitutions.utils.NextcloudClient.login', mock_client): with mock.patch('addons.nextcloudinstitutions.utils.MetadataClient.set_metadata', mock_metadata): set_timestamp(self.node, path, timestamp_data, timestamp_status, context, provider_name) def test_set_timestamp_with_context_is_none(self): + # UT code for the set_timestamp function in case the context is none + # Define mock object the set_metadata method of the MetadataClient class mock_metadata = mock.MagicMock() mock_metadata.return_value = 'abc' + + # Define mock object the login method of the MetadataClient class mock_client = mock.MagicMock() mock_client.login.return_value = True + # Define the params path = 'test_path' timestamp_data = b'abcxyz' provider_name = 'nextcloud' timestamp_status = 1 + with mock.patch('addons.nextcloudinstitutions.utils.NextcloudClient.login', mock_client): with mock.patch('addons.nextcloudinstitutions.utils.MetadataClient.set_metadata', mock_metadata): set_timestamp(self.node, path, timestamp_data, timestamp_status, None, provider_name) def test_set_timestamp_nextcloudinstitutions(self): + # UT code for the set_timestamp function in case use NextcloudInstitutionsNodeSettingsFactory + # Define mock object the set_metadata method of the MetadataClient class mock_metadata = mock.MagicMock() mock_metadata.return_value = 'abc' + + # Define mock object the login method of the MetadataClient class mock_client = mock.MagicMock() mock_client.login.return_value = True + # Define the params path = 'test_path' node = NextcloudInstitutionsNodeSettingsFactory() timestamp_data = b'abcxyz' provider_name = 'nextcloudinstitutions' timestamp_status = 1 + with mock.patch('addons.nextcloudinstitutions.utils.NextcloudClient.login', mock_client): with mock.patch('addons.nextcloudinstitutions.utils.MetadataClient.set_metadata', mock_metadata): set_timestamp(node, path, timestamp_data, timestamp_status, None, provider_name) From 6fa74069fe436bc04cd4c17d61b838606b46af91 Mon Sep 17 00:00:00 2001 From: tma-ndhuy Date: Wed, 8 Mar 2023 15:14:19 +0700 Subject: [PATCH 14/19] =?UTF-8?q?refs=20(D).1.6=20[=E6=A9=9F=E9=96=A2?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=83=AC=E3=83=BC=E3=82=B8]GakuNin=20RDM=20?= =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E6=A9=9F=E8=83=BD=E3=81=AE?= =?UTF-8?q?=E6=97=A2=E5=AD=98=E3=81=AE=E3=83=90=E3=82=B0=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?:=20Fix=20bug=20IT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/base/views.py | 2 +- addons/nextcloudinstitutions/utils.py | 2 +- osf/models/files.py | 8 +++++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/addons/base/views.py b/addons/base/views.py index 3949412bbab..e2bff1211ce 100644 --- a/addons/base/views.py +++ b/addons/base/views.py @@ -1026,7 +1026,7 @@ def addon_view_file(auth, node, file_node, version): 'size': version.size if version.size is not None else 9966699, 'private': getattr(node.get_addon(file_node.provider), 'is_private', False), 'file_tags': list(file_node.tags.filter(system=False).values_list('name', flat=True)) if not file_node._state.adding else [], # Only access ManyRelatedManager if saved - 'file_guid': file_node.get_guid()._id, + 'file_guid': file_node.get_guid(create=True))._id, 'file_id': file_node._id, 'allow_comments': file_node.provider in settings.ADDONS_COMMENTABLE, 'checkout_user': file_node.checkout._id if file_node.checkout else None, diff --git a/addons/nextcloudinstitutions/utils.py b/addons/nextcloudinstitutions/utils.py index 6269452ed67..15b8f73a83f 100644 --- a/addons/nextcloudinstitutions/utils.py +++ b/addons/nextcloudinstitutions/utils.py @@ -161,7 +161,7 @@ def set_timestamp(node_settings, path, timestamp_data, timestamp_status, context encoded_timestamp = base64.b64encode(timestamp_data) # DEBUG(u'set timestamp: {}'.format(encoded_timestamp)) attributes = { - settings.PROPERTY_KEY_TIMESTAMP: encoded_timestamp, + settings.PROPERTY_KEY_TIMESTAMP: encoded_timestamp.decode('utf-8'), settings.PROPERTY_KEY_TIMESTAMP_STATUS: str(timestamp_status) } cli = MetadataClient(url, username, password) diff --git a/osf/models/files.py b/osf/models/files.py index e14511a81c8..31bd7430456 100644 --- a/osf/models/files.py +++ b/osf/models/files.py @@ -371,7 +371,9 @@ def touch(self, auth_header, revision=None, **kwargs): if resp.status_code != 200: logger.warning('Unable to find {} got status code {}'.format(self, resp.status_code)) return None - return self.update(revision, resp.json()['data']['attributes']) + res_json = resp.json()['data'] + res_value = res_json['attributes'] if isinstance(res_json, dict) else res_json + return self.update(revision, res_value) # TODO Switch back to head requests # return self.update(revision, json.loads(resp.headers['x-waterbutler-metadata'])) @@ -512,6 +514,8 @@ def update(self, revision, data, user=None, save=True): :param dict data: Metadata received from waterbutler :returns: FileVersion """ + if isinstance(data, list): + data = data[0] self.name = data['name'] self.materialized_path = data['materialized'] @@ -615,6 +619,8 @@ def update(self, revision, data, save=True, user=None): dataverse and django See dataversefile.update """ + if isinstance(data, list): + data = data[0] self.name = data['name'] self.materialized_path = data['materialized'] self.last_touched = timezone.now() From 2dad6e81aea7ce0e38a7775e6dc98df921914436 Mon Sep 17 00:00:00 2001 From: tma-ndhuy Date: Thu, 9 Mar 2023 18:19:44 +0700 Subject: [PATCH 15/19] =?UTF-8?q?refs=20(C).2.6=20[=E6=A9=9F=E9=96=A2?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=83=AC=E3=83=BC=E3=82=B8]GakuNin=20RDM=20?= =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E6=A9=9F=E8=83=BD=E3=81=AE?= =?UTF-8?q?=E6=97=A2=E5=AD=98=E3=81=AE=E3=83=90=E3=82=B0=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?:=20Fix=20bug=20IT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/base/views.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/addons/base/views.py b/addons/base/views.py index e2bff1211ce..66910128db3 100644 --- a/addons/base/views.py +++ b/addons/base/views.py @@ -608,16 +608,18 @@ def addon_delete_file_node(self, target, user, event_type, payload): BaseFileNode.delete(item) else: try: - file_node = BaseFileNode.resolve_class(provider, BaseFileNode.FILE).objects.get( + list_file_node = BaseFileNode.resolve_class(provider, BaseFileNode.FILE).objects.filter( target_object_id=target.id, target_content_type=content_type, _materialized_path=materialized_path ) except BaseFileNode.DoesNotExist: - file_node = None + list_file_node = None - if file_node and not TrashedFileNode.load(file_node._id): - file_node.delete(user=user) + if list_file_node: + for file_node in list_file_node: + if not TrashedFileNode.load(file_node._id): + file_node.delete(user=user) @must_be_valid_project From e306fa53b61916850964a7a2e5d82a27453f641b Mon Sep 17 00:00:00 2001 From: tma-ndhuy Date: Wed, 15 Mar 2023 08:49:56 +0700 Subject: [PATCH 16/19] =?UTF-8?q?refs=20(C).2.6=20[=E6=A9=9F=E9=96=A2?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=83=AC=E3=83=BC=E3=82=B8]GakuNin=20RDM=20?= =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E6=A9=9F=E8=83=BD=E3=81=AE?= =?UTF-8?q?=E6=97=A2=E5=AD=98=E3=81=AE=E3=83=90=E3=82=B0=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?:=20Fix=20bug=20IT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- osf/models/files.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osf/models/files.py b/osf/models/files.py index 31bd7430456..ed5558e0784 100644 --- a/osf/models/files.py +++ b/osf/models/files.py @@ -515,6 +515,8 @@ def update(self, revision, data, user=None, save=True): :returns: FileVersion """ if isinstance(data, list): + if len(data) == 0: + return data = data[0] self.name = data['name'] self.materialized_path = data['materialized'] @@ -620,6 +622,8 @@ def update(self, revision, data, save=True, user=None): See dataversefile.update """ if isinstance(data, list): + if len(data) == 0: + return data = data[0] self.name = data['name'] self.materialized_path = data['materialized'] From fb672452f10faf78045d939d6a363d62366c4ddc Mon Sep 17 00:00:00 2001 From: tma-ndhuy Date: Wed, 15 Mar 2023 18:54:35 +0700 Subject: [PATCH 17/19] =?UTF-8?q?refs=20(C).2.6=20[=E6=A9=9F=E9=96=A2?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=83=AC=E3=83=BC=E3=82=B8]GakuNin=20RDM=20?= =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E6=A9=9F=E8=83=BD=E3=81=AE?= =?UTF-8?q?=E6=97=A2=E5=AD=98=E3=81=AE=E3=83=90=E3=82=B0=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?:=20Fix=20error=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/base/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/base/views.py b/addons/base/views.py index 66910128db3..ed1588330ba 100644 --- a/addons/base/views.py +++ b/addons/base/views.py @@ -1028,7 +1028,7 @@ def addon_view_file(auth, node, file_node, version): 'size': version.size if version.size is not None else 9966699, 'private': getattr(node.get_addon(file_node.provider), 'is_private', False), 'file_tags': list(file_node.tags.filter(system=False).values_list('name', flat=True)) if not file_node._state.adding else [], # Only access ManyRelatedManager if saved - 'file_guid': file_node.get_guid(create=True))._id, + 'file_guid': file_node.get_guid(create=True)._id, 'file_id': file_node._id, 'allow_comments': file_node.provider in settings.ADDONS_COMMENTABLE, 'checkout_user': file_node.checkout._id if file_node.checkout else None, From bf1d5f84d69ab9d81404346dc4cd595ef561ddfd Mon Sep 17 00:00:00 2001 From: tma-ndhuy Date: Wed, 5 Apr 2023 15:01:00 +0700 Subject: [PATCH 18/19] =?UTF-8?q?refs=20(C).2.6=20[=E6=A9=9F=E9=96=A2?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=83=AC=E3=83=BC=E3=82=B8]GakuNin=20RDM=20?= =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E6=A9=9F=E8=83=BD=E3=81=AE?= =?UTF-8?q?=E6=97=A2=E5=AD=98=E3=81=AE=E3=83=90=E3=82=B0=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?:=20Fix=20PR=20error=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api_tests/nodes/views/test_node_wiki_list.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api_tests/nodes/views/test_node_wiki_list.py b/api_tests/nodes/views/test_node_wiki_list.py index 6a557113cfc..0b4f993b76f 100644 --- a/api_tests/nodes/views/test_node_wiki_list.py +++ b/api_tests/nodes/views/test_node_wiki_list.py @@ -1,5 +1,6 @@ import mock import pytest +import uuid from rest_framework import exceptions @@ -332,7 +333,7 @@ def url_registration_private(self, wiki_registration_private): return '/{}registrations/{}/wikis/'.format(API_BASE, wiki_registration_private.node._id) def test_create_public_wiki_page(self, app, user_write_contributor, url_node_public): - page_name = fake.word() + page_name = str(uuid.uuid4()).replace('-', '') res = app.post_json_api(url_node_public, create_wiki_payload(page_name), auth=user_write_contributor.auth) assert res.status_code == 201 assert res.json['data']['attributes']['name'] == page_name From f7040cdd71eca9b78c969969bab90333ca34da0b Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Fri, 1 Dec 2023 14:46:44 +0700 Subject: [PATCH 19/19] =?UTF-8?q?refs=20(C).2.6=20[=E6=A9=9F=E9=96=A2?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=83=AC=E3=83=BC=E3=82=B8]GakuNin=20RDM=20?= =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E6=A9=9F=E8=83=BD=E3=81=AE?= =?UTF-8?q?=E6=97=A2=E5=AD=98=E3=81=AE=E3=83=90=E3=82=B0=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?:=20Fix=20TravisCI=20-=20revert=20changes=20on=20test=5Fviews.p?= =?UTF-8?q?y,=20update=20test=5Fwaterbutler.py,=20remove=20unnecessary=20R?= =?UTF-8?q?esponseFactory=20class=20from=20factories.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- osf_tests/factories.py | 8 -------- tests/test_views.py | 2 +- tests/test_waterbutler.py | 42 ++++++++++++++++++++++++--------------- 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/osf_tests/factories.py b/osf_tests/factories.py index b13642d32d2..5dd7685ea4b 100644 --- a/osf_tests/factories.py +++ b/osf_tests/factories.py @@ -1225,11 +1225,3 @@ class Meta: class RdmFileTimestamptokenVerifyResultFactory(DjangoModelFactory): class Meta: model = models.RdmFileTimestamptokenVerifyResult - - -class ResponseFactory: - def __init__(self, raw): - self.raw = raw - - def close(self): - return None diff --git a/tests/test_views.py b/tests/test_views.py index 81ad65f63c8..6aa6a411408 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -5984,7 +5984,7 @@ def test_add_timestamp_token(self, mock_get, mock_shutil, mock_collection, mock_ ## check TimestampError(TimestampVerifyResult.inspection_result_statu != 1) in response assert 'osfstorage_test_file1.status_1' not in res assert 'osfstorage_test_file2.status_3' in res - assert 'osfstorage_test_file3.status_3' in res + assert 'osfstorage_test_file3.status_3' not in res assert 's3_test_file1.status_3' in res @mock.patch('website.util.timestamp.check_file_timestamp') diff --git a/tests/test_waterbutler.py b/tests/test_waterbutler.py index 65ec9fea01d..dbf99acbff4 100644 --- a/tests/test_waterbutler.py +++ b/tests/test_waterbutler.py @@ -1,9 +1,14 @@ from unittest import mock + from unittest.mock import patch from framework.auth import Auth from osf.models import AbstractNode -from osf_tests.factories import ProjectFactory, OsfStorageFileFactory, InstitutionFactory, ResponseFactory +from osf_tests.factories import ( + ProjectFactory, + OsfStorageFileFactory, + InstitutionFactory, +) from tests.base import OsfTestCase from website.util import waterbutler @@ -26,33 +31,38 @@ def setUp(self): @patch('website.util.waterbutler.get_node_info') @patch('website.util.waterbutler.os.path.basename') - def test_download_file_with_file_info_return_none_value(self, mock_os, mock_get_node_info): + def test_download_file_with_file_info_return_none_value(self, mock_os_path_basename, mock_get_node_info): + mock_os_path_basename.return_value = 'test_path' mock_get_node_info.return_value = None - mock_os.return_value = 'test_path' res = waterbutler.download_file('token', self.file_node, 'test_path') - assert res == None + assert res is None @patch('website.util.waterbutler.get_node_info') @patch('website.util.waterbutler.os.path.join') @patch('website.util.waterbutler.os.path.basename') - def test_download_file_raise_exception(self, mock_os, mock_os_join, mock_get_node_info): - with patch('website.util.waterbutler.requests', side_effect=Exception('mocked error')): + def test_download_file_raise_exception(self, + mock_os_path_basename, mock_os_path_join, + mock_get_node_info): + mock_os_path_basename.return_value = 'test_download_path' + mock_os_path_join.return_value = 'test_download_path' + mock_get_node_info.return_value = 'file node info' + with patch('website.util.waterbutler.requests.get', side_effect=Exception('mocked error')): res = waterbutler.download_file('fake_cookie', self.file_node, 'test_download_path') - assert res == None + assert res is None + @patch('website.util.waterbutler.shutil.copyfileobj') + @patch('website.util.waterbutler.requests.get') @patch('website.util.waterbutler.get_node_info') - @patch('website.util.waterbutler.os.path.basename') @patch('website.util.waterbutler.os.path.join') - @patch('website.util.waterbutler.requests.get') - @patch('website.util.waterbutler.waterbutler_api_url_for') - @patch('website.util.waterbutler.shutil.copyfileobj') - def test_download_file(self, mock_shutil, mock_waterbutler_api, mock_request, mock_os_join, mock_os, mock_get_node_info): + @patch('website.util.waterbutler.os.path.basename') + def test_download_file(self, + mock_os_path_basename, mock_os_path_join, + mock_get_node_info, mock_request_get, mock_shutil): + mock_os_path_basename.return_value = 'test_path' + mock_os_path_join.return_value = 'test_full_path' mock_get_node_info.return_value = 'file node info' - mock_os.return_value = 'test_path' + mock_request_get.return_value = mock.MagicMock() mock_shutil.return_value = None - mock_waterbutler_api.return_value = None - mock_os_join.return_value = 'test_full_path' - mock_request.return_value = ResponseFactory('raw_data') with mock.patch('builtins.open', mock.mock_open(read_data='data output')): res = waterbutler.download_file('fake_cookie', self.file_node, 'test_download_path') assert res == 'test_full_path'