diff --git a/addons/base/exceptions.py b/addons/base/exceptions.py index cc3a70cdf3a..3edda785149 100644 --- a/addons/base/exceptions.py +++ b/addons/base/exceptions.py @@ -1,21 +1,27 @@ -""" -Custom exceptions for add-ons. -""" - class AddonError(Exception): pass + class InvalidFolderError(AddonError): pass + class InvalidAuthError(AddonError): pass + class HookError(AddonError): pass + class QueryError(AddonError): pass + class DoesNotExist(AddonError): pass + + +class NotApplicableError(AddonError): + """This exception is used by non-storage and/or non-oauth add-ons when they don't need or have certain features.""" + pass diff --git a/addons/base/serializer.py b/addons/base/serializer.py index 9bd10dd8e90..6187408df64 100644 --- a/addons/base/serializer.py +++ b/addons/base/serializer.py @@ -141,7 +141,7 @@ class StorageAddonSerializer(OAuthAddonSerializer): REQUIRED_URLS = ('auth', 'importAuth', 'folders', 'files', 'config', 'deauthorize', 'accounts') @abc.abstractmethod - def credentials_are_valid(self, user_settings): + def credentials_are_valid(self, user_settings, client=None): pass @abc.abstractmethod @@ -154,7 +154,7 @@ def serialize_settings(self, node_settings, current_user, client=None): current_user_settings = current_user.get_addon(self.addon_short_name) user_is_owner = user_settings is not None and user_settings.owner == current_user - valid_credentials = self.credentials_are_valid(user_settings, client) + valid_credentials = self.credentials_are_valid(user_settings, client=client) result = { 'userIsOwner': user_is_owner, diff --git a/addons/base/tests/base.py b/addons/base/tests/base.py index 04b6a7ad6f7..86939652292 100644 --- a/addons/base/tests/base.py +++ b/addons/base/tests/base.py @@ -1,9 +1,6 @@ -from nose.tools import * # noqa (PEP8 asserts) +from django.conf import settings as django_settings from framework.auth import Auth - -from django.conf import settings - from osf_tests.factories import AuthUserFactory, ProjectFactory @@ -28,23 +25,27 @@ class AddonTestCase(object): """ DISABLE_OUTGOING_CONNECTIONS = True - DB_NAME = getattr(settings, 'TEST_DB_ADDON_NAME', 'osf_addon') + DB_NAME = getattr(django_settings, 'TEST_DB_ADDON_NAME', 'osf_addon') ADDON_SHORT_NAME = None OWNERS = ['user', 'node'] NODE_USER_FIELD = 'user_settings' + def __init__(self, *args, **kwargs): + super(AddonTestCase,self).__init__(*args, **kwargs) + self.node_settings = None + self.project = None + self.user = None + self.user_settings = None + # Optional overrides - def create_user(self): + @staticmethod + def create_user(): return AuthUserFactory.build() def create_project(self): return ProjectFactory(creator=self.user) def set_user_settings(self, settings): - """Make any necessary modifications to the user settings object, - e.g. setting access_token. - - """ raise NotImplementedError('Must define set_user_settings(self, settings) method') def set_node_settings(self, settings): @@ -52,7 +53,6 @@ def set_node_settings(self, settings): def create_user_settings(self): """Initialize user settings object if requested by `self.OWNERS`. - """ if 'user' not in self.OWNERS: return @@ -64,9 +64,7 @@ def create_user_settings(self): def create_node_settings(self): """Initialize node settings object if requested by `self.OWNERS`, - additionally linking to user settings if requested by - `self.NODE_USER_FIELD`. - + additionally linking to user settings if requested by `self.NODE_USER_FIELD`. """ if 'node' not in self.OWNERS: return @@ -86,19 +84,26 @@ def setUp(self): if not self.ADDON_SHORT_NAME: raise ValueError('Must define ADDON_SHORT_NAME in the test class.') self.user.save() - self.project = self.create_project() self.project.save() - self.create_user_settings() self.create_node_settings() + class OAuthAddonTestCaseMixin(object): @property def ExternalAccountFactory(self): raise NotImplementedError() + def __init__(self, *args, **kwargs): + super(OAuthAddonTestCaseMixin, self).__init__(*args, **kwargs) + self.auth = None + self.external_account = None + self.project = None + self.user_settings = None + self.user = None + def set_user_settings(self, settings): self.external_account = self.ExternalAccountFactory() self.external_account.save() diff --git a/addons/base/tests/views.py b/addons/base/tests/views.py index 8c3860b2826..9a78ca49cdf 100644 --- a/addons/base/tests/views.py +++ b/addons/base/tests/views.py @@ -1,13 +1,12 @@ -from rest_framework import status as http_status -from future.moves.urllib.parse import urlparse, urljoin, parse_qs - +from future.moves.urllib.parse import urlparse, parse_qs import mock +from nose.tools import * # noqa import responses +from rest_framework import status as http_status + from addons.base.tests.base import OAuthAddonTestCaseMixin from framework.auth import Auth from framework.exceptions import HTTPError -from nose.tools import (assert_equal, assert_false, assert_in, assert_is_none, - assert_not_equal, assert_raises, assert_true) from osf_tests.factories import AuthUserFactory, ProjectFactory from osf.utils import permissions from website.util import api_url_for, web_url_for @@ -15,6 +14,14 @@ class OAuthAddonAuthViewsTestCaseMixin(OAuthAddonTestCaseMixin): + @property + def ADDON_SHORT_NAME(self): + raise NotImplementedError() + + @property + def ExternalAccountFactory(self): + raise NotImplementedError() + @property def Provider(self): raise NotImplementedError() @@ -68,8 +75,21 @@ def test_delete_external_account_not_owner(self): res = self.app.delete(url, auth=other_user.auth, expect_errors=True) assert_equal(res.status_code, http_status.HTTP_403_FORBIDDEN) + class OAuthAddonConfigViewsTestCaseMixin(OAuthAddonTestCaseMixin): + def __init__(self, *args, **kwargs): + super(OAuthAddonConfigViewsTestCaseMixin,self).__init__(*args, **kwargs) + self.node_settings = None + + @property + def ADDON_SHORT_NAME(self): + raise NotImplementedError() + + @property + def ExternalAccountFactory(self): + raise NotImplementedError() + @property def folder(self): raise NotImplementedError("This test suite must expose a 'folder' property.") @@ -218,8 +238,35 @@ def test_deauthorize_node(self): last_log = self.project.logs.latest() assert_equal(last_log.action, '{0}_node_deauthorized'.format(self.ADDON_SHORT_NAME)) + class OAuthCitationAddonConfigViewsTestCaseMixin(OAuthAddonConfigViewsTestCaseMixin): + def __init__(self, *args, **kwargs): + super(OAuthAddonConfigViewsTestCaseMixin,self).__init__(*args, **kwargs) + self.mock_verify = None + self.node_settings = None + self.provider = None + + @property + def ADDON_SHORT_NAME(self): + raise NotImplementedError() + + @property + def ExternalAccountFactory(self): + raise NotImplementedError() + + @property + def folder(self): + raise NotImplementedError() + + @property + def Serializer(self): + raise NotImplementedError() + + @property + def client(self): + raise NotImplementedError() + @property def citationsProvider(self): raise NotImplementedError() diff --git a/addons/boa/apps.py b/addons/boa/apps.py index e28794ba8ce..dd2a0e2e3c8 100644 --- a/addons/boa/apps.py +++ b/addons/boa/apps.py @@ -1,12 +1,11 @@ import os + from addons.base.apps import BaseAddonAppConfig from addons.boa.settings import MAX_UPLOAD_SIZE HERE = os.path.dirname(os.path.abspath(__file__)) -TEMPLATE_PATH = os.path.join( - HERE, - 'templates' -) +TEMPLATE_PATH = os.path.join(HERE, 'templates') + class BoaAddonAppConfig(BaseAddonAppConfig): diff --git a/addons/boa/models.py b/addons/boa/models.py index aacbac241e0..bde51f59b2a 100644 --- a/addons/boa/models.py +++ b/addons/boa/models.py @@ -1,19 +1,15 @@ -# -*- coding: utf-8 -*- -import logging - -from addons.base.models import (BaseOAuthNodeSettings, BaseOAuthUserSettings, - BaseStorageAddon) from django.db import models -from framework.auth import Auth + +from addons.base.exceptions import NotApplicableError +from addons.base.models import BaseOAuthNodeSettings, BaseOAuthUserSettings, BaseStorageAddon from addons.boa.serializer import BoaSerializer from addons.boa.settings import DEFAULT_HOSTS +from framework.auth import Auth from osf.models.external import BasicAuthProviderMixin -# from website.util import api_v2_url -logger = logging.getLogger(__name__) class BoaProvider(BasicAuthProviderMixin): - """An alternative to `ExternalProvider` not tied to OAuth""" + """Boa provider, an alternative to `ExternalProvider` which is not tied to OAuth""" name = 'Boa' short_name = 'boa' @@ -21,9 +17,7 @@ class BoaProvider(BasicAuthProviderMixin): def __init__(self, account=None, host=None, username=None, password=None): if username: username = username.lower() - return super(BoaProvider, self).__init__( - account=account, host=host, username=username, password=password - ) + super(BoaProvider, self).__init__(account=account, host=host, username=username, password=password) def __repr__(self): return '<{name}: {status}>'.format( @@ -33,6 +27,7 @@ def __repr__(self): class UserSettings(BaseOAuthUserSettings): + oauth_provider = BoaProvider serializer = BoaSerializer @@ -43,13 +38,12 @@ def to_json(self, user): class NodeSettings(BaseOAuthNodeSettings, BaseStorageAddon): + oauth_provider = BoaProvider serializer = BoaSerializer folder_id = models.TextField(blank=True, null=True) - user_settings = models.ForeignKey( - UserSettings, null=True, blank=True, on_delete=models.CASCADE - ) + user_settings = models.ForeignKey(UserSettings, null=True, blank=True, on_delete=models.CASCADE) _api = None @@ -67,7 +61,9 @@ def folder_path(self): def folder_name(self): return self.folder_id - # def set_folder(self, folder, auth=None): # NOTE: no for Boa + def set_folder(self, folder, auth=None): + """Not applicable to remote computing add-on.""" + raise NotApplicableError def fetch_folder_name(self): if self.folder_id == '/': @@ -85,16 +81,16 @@ def deauthorize(self, auth=None, add_log=True): self.clear_auth() # Also performs a .save() def serialize_waterbutler_credentials(self): - # required by superclass, not actually used - pass + """Not applicable to remote computing add-on.""" + raise NotApplicableError def serialize_waterbutler_settings(self): - # required by superclass, not actually used - pass + """Not applicable to remote computing add-on.""" + raise NotApplicableError def create_waterbutler_log(self, *args, **kwargs): - # required by superclass, not actually used - pass + """Not applicable to remote computing add-on.""" + raise NotApplicableError def after_delete(self, user): self.deauthorize(Auth(user=user), add_log=True) @@ -104,4 +100,6 @@ def on_delete(self): self.deauthorize(add_log=False) self.save() - # def get_folders(self, **kwargs): # NOTE: no for boa + def get_folders(self, **kwargs): + """Not applicable to remote computing add-on.""" + raise NotApplicableError diff --git a/addons/boa/routes.py b/addons/boa/routes.py index c3b9f1f742a..0bc818e1925 100644 --- a/addons/boa/routes.py +++ b/addons/boa/routes.py @@ -1,9 +1,6 @@ -# -*- coding: utf-8 -*- -from framework.routing import Rule, json_renderer - from addons.boa import views +from framework.routing import Rule, json_renderer -# JSON endpoints api_routes = { 'rules': [ Rule( @@ -22,21 +19,18 @@ json_renderer, ), Rule( - ['/project//boa/settings/', - '/project//node//boa/settings/'], - 'put', - views.boa_set_config, - json_renderer - ), - Rule( - ['/project//boa/settings/', - '/project//node//boa/settings/'], + [ + '/project//boa/settings/', + '/project//node//boa/settings/' + ], 'get', views.boa_get_config, json_renderer ), Rule( - ['/settings/boa/accounts/'], + [ + '/settings/boa/accounts/' + ], 'post', views.boa_add_user_account, json_renderer @@ -50,14 +44,6 @@ views.boa_import_auth, json_renderer ), - # NOTE: not for Boa - # Rule( - # ['/project//boa/folders/', - # '/project//node//boa/folders/'], - # 'get', - # views.boa_folder_list, - # json_renderer - # ), Rule( [ '/project//boa/submit-job/', diff --git a/addons/boa/serializer.py b/addons/boa/serializer.py index 05caf76ee37..7a3275ff170 100644 --- a/addons/boa/serializer.py +++ b/addons/boa/serializer.py @@ -1,36 +1,39 @@ +from boaapi.boa_client import BoaClient, BOA_API_ENDPOINT, BoaException + +from addons.base.exceptions import NotApplicableError from addons.base.serializer import StorageAddonSerializer from addons.boa.settings import DEFAULT_HOSTS +from framework import sentry from website.util import web_url_for -from boaapi.boa_client import BoaClient, BOA_API_ENDPOINT -from boaapi.util import BoaException - class BoaSerializer(StorageAddonSerializer): addon_short_name = 'boa' def serialized_folder(self, node_settings): - # required by superclass, not actually used - return None + """Not applicable to remote computing add-ons""" + raise NotApplicableError def credentials_are_valid(self, user_settings, client=None): - node = self.node_settings - external_account = node.external_account + if client is not None: + sentry.log_message('Client ignored for Boa Serializer in credentials_are_valid()') + external_account = self.node_settings.external_account if external_account is None: return False provider = self.node_settings.oauth_provider(external_account) try: - boa = BoaClient(endpoint=BOA_API_ENDPOINT) - boa.login(provider.username, provider.password) - boa.close() + boa_client = BoaClient(endpoint=BOA_API_ENDPOINT) + boa_client.login(provider.username, provider.password) + boa_client.close() return True except BoaException: return False @property def addon_serialized_urls(self): + node = self.node_settings.owner user_settings = self.node_settings.user_settings or self.user_settings @@ -39,15 +42,12 @@ def addon_serialized_urls(self): 'accounts': node.api_url_for('boa_account_list'), 'importAuth': node.api_url_for('boa_import_auth'), 'deauthorize': node.api_url_for('boa_deauthorize_node'), - # 'folders': node.api_url_for('boa_folder_list'), 'folders': None, - # 'files': node.web_url_for('collect_file_trees'), 'files': None, - 'config': node.api_url_for('boa_set_config'), + 'config': None, } if user_settings: - result['owner'] = web_url_for('profile_view_id', - uid=user_settings.owner._id) + result['owner'] = web_url_for('profile_view_id', uid=user_settings.owner._id) return result @property @@ -63,6 +63,8 @@ def serialized_user_settings(self): return result def serialize_settings(self, node_settings, current_user, client=None): - ret = super(BoaSerializer, self).serialize_settings(node_settings, current_user, client) + if client is not None: + sentry.log_message('Client ignored for Boa Serializer in serialize_settings()') + ret = super(BoaSerializer, self).serialize_settings(node_settings, current_user, client=client) ret['hosts'] = DEFAULT_HOSTS return ret diff --git a/addons/boa/tests/async_mock.py b/addons/boa/tests/async_mock.py deleted file mode 100644 index 41cf6b56e60..00000000000 --- a/addons/boa/tests/async_mock.py +++ /dev/null @@ -1,6 +0,0 @@ -from unittest.mock import MagicMock - - -class AsyncMock(MagicMock): - async def __call__(self, *args, **kwargs): - return super(AsyncMock, self).__call__(*args, **kwargs) diff --git a/addons/boa/tests/factories.py b/addons/boa/tests/factories.py index 2ca68a14b50..c432eea1c24 100644 --- a/addons/boa/tests/factories.py +++ b/addons/boa/tests/factories.py @@ -1,31 +1,36 @@ -# -*- coding: utf-8 -*- -import factory -from factory.django import DjangoModelFactory -from osf_tests.factories import UserFactory, ProjectFactory, ExternalAccountFactory +from factory import DjangoModelFactory, Sequence, SubFactory from addons.boa.models import UserSettings, NodeSettings +from osf_tests.factories import UserFactory, ProjectFactory, ExternalAccountFactory + +BOA_HOST = 'http://localhost:9999/boa/?q=boa/api' +BOA_USERNAME = 'fake-boa-username' +BOA_PASSWORD = 'fake-boa-password' class BoaAccountFactory(ExternalAccountFactory): + provider = 'boa' - provider_id = factory.Sequence(lambda n: 'id-{0}'.format(n)) - profile_url = factory.Sequence(lambda n: 'https://localhost/{0}/boa'.format(n)) - oauth_secret = factory.Sequence(lambda n: 'https://localhost/{0}/boa'.format(n)) - display_name = 'catname' - oauth_key = 'meoword' + provider_name = 'Fake Boa Provider' + provider_id = Sequence(lambda n: '{0}:{1}-{2}'.format(BOA_HOST, BOA_USERNAME, n)) + profile_url = Sequence(lambda n: 'http://localhost:9999/{0}/boa'.format(n)) + oauth_secret = Sequence(lambda n: 'secret-{0}'.format(n)) + oauth_key = BOA_PASSWORD + display_name = 'Fake Boa' class BoaUserSettingsFactory(DjangoModelFactory): + class Meta: model = UserSettings - owner = factory.SubFactory(UserFactory) + owner = SubFactory(UserFactory) class BoaNodeSettingsFactory(DjangoModelFactory): + class Meta: model = NodeSettings - owner = factory.SubFactory(ProjectFactory) - user_settings = factory.SubFactory(BoaUserSettingsFactory) - folder_id = '/Documents/' + owner = SubFactory(ProjectFactory) + user_settings = SubFactory(BoaUserSettingsFactory) diff --git a/addons/boa/tests/test_models.py b/addons/boa/tests/test_models.py index 3ae00468eff..57e08072d30 100644 --- a/addons/boa/tests/test_models.py +++ b/addons/boa/tests/test_models.py @@ -1,59 +1,41 @@ -from nose.tools import assert_is_not_none, assert_equal import pytest import unittest -from addons.base.tests.models import (OAuthAddonNodeSettingsTestSuiteMixin, - OAuthAddonUserSettingTestSuiteMixin) - -from addons.boa.models import NodeSettings -from addons.boa.tests.factories import ( - BoaAccountFactory, BoaNodeSettingsFactory, - BoaUserSettingsFactory -) -from addons.boa.settings import USE_SSL +from addons.base.exceptions import NotApplicableError +from addons.base.tests.models import OAuthAddonNodeSettingsTestSuiteMixin, OAuthAddonUserSettingTestSuiteMixin +from addons.boa.tests.utils import BoaAddonTestCaseBaseMixin +from framework.auth import Auth +from osf.models import NodeLog pytestmark = pytest.mark.django_db -class TestUserSettings(OAuthAddonUserSettingTestSuiteMixin, unittest.TestCase): - - short_name = 'boa' - full_name = 'boa' - UserSettingsFactory = BoaUserSettingsFactory - ExternalAccountFactory = BoaAccountFactory +class TestUserSettings(BoaAddonTestCaseBaseMixin, OAuthAddonUserSettingTestSuiteMixin, unittest.TestCase): -class TestNodeSettings(OAuthAddonNodeSettingsTestSuiteMixin, unittest.TestCase): + pass - short_name = 'boa' - full_name = 'boa' - ExternalAccountFactory = BoaAccountFactory - NodeSettingsFactory = BoaNodeSettingsFactory - NodeSettingsClass = NodeSettings - UserSettingsFactory = BoaUserSettingsFactory - def _node_settings_class_kwargs(self, node, user_settings): - return { - 'user_settings': self.user_settings, - 'folder_id': '/Documents', - 'owner': self.node - } +class TestNodeSettings(BoaAddonTestCaseBaseMixin, OAuthAddonNodeSettingsTestSuiteMixin, unittest.TestCase): - def test_serialize_credentials(self): - credentials = self.node_settings.serialize_waterbutler_credentials() + def test_set_folder(self): + with pytest.raises(NotApplicableError): + self.node_settings.set_folder('fake_folder_id', auth=Auth(self.user)) - assert_is_not_none(self.node_settings.external_account.oauth_secret) - expected = { - 'host': self.node_settings.external_account.oauth_secret, - 'password': 'meoword', - 'username': 'catname' - } + def test_create_log(self): + with pytest.raises(NotApplicableError): + self.node_settings.create_waterbutler_log( + auth=Auth(user=self.user), + action=NodeLog.FILE_ADDED, + metadata={ + 'path': 'fake_path', + 'materialized': 'fake_materialized_path', + } + ) - assert_equal(credentials, expected) + def test_serialize_credentials(self): + with pytest.raises(NotApplicableError): + _ = self.node_settings.serialize_waterbutler_credentials() def test_serialize_settings(self): - settings = self.node_settings.serialize_waterbutler_settings() - expected = { - 'folder': self.node_settings.folder_id, - 'verify_ssl': USE_SSL - } - assert_equal(settings, expected) + with pytest.raises(NotApplicableError): + _ = self.node_settings.serialize_waterbutler_settings() diff --git a/addons/boa/tests/test_serializer.py b/addons/boa/tests/test_serializer.py index 8d2720c9e62..100c044797f 100644 --- a/addons/boa/tests/test_serializer.py +++ b/addons/boa/tests/test_serializer.py @@ -2,16 +2,17 @@ import pytest from tests.base import OsfTestCase -from addons.base.tests.serializers import OAuthAddonSerializerTestSuiteMixin -from addons.boa.tests.factories import BoaAccountFactory -from addons.boa.serializer import BoaSerializer +from addons.base.exceptions import NotApplicableError +from addons.base.tests.serializers import StorageAddonSerializerTestSuiteMixin +from addons.boa.tests.utils import BoaAddonTestCaseBaseMixin pytestmark = pytest.mark.django_db -class TestBoaSerializer(OAuthAddonSerializerTestSuiteMixin, OsfTestCase): - addon_short_name = 'boa' - Serializer = BoaSerializer - ExternalAccountFactory = BoaAccountFactory + +class TestBoaSerializer(BoaAddonTestCaseBaseMixin, StorageAddonSerializerTestSuiteMixin, OsfTestCase): + + def set_provider_id(self, pid=None): + self.node_settings.folder_id = pid def setUp(self): self.mock_credentials = mock.patch('addons.boa.serializer.BoaSerializer.credentials_are_valid') @@ -22,3 +23,9 @@ def setUp(self): def tearDown(self): self.mock_credentials.stop() super(TestBoaSerializer, self).tearDown() + + def test_serialize_settings_authorized_folder_is_set(self): + self.set_provider_id(pid='foo') + with pytest.raises(NotApplicableError): + with mock.patch.object(type(self.node_settings), 'has_auth', return_value=True): + _ = self.ser.serialize_settings(self.node_settings, self.user, self.client) diff --git a/addons/boa/tests/test_tasks.py b/addons/boa/tests/test_tasks.py index e5ef5e8601a..629510e691b 100644 --- a/addons/boa/tests/test_tasks.py +++ b/addons/boa/tests/test_tasks.py @@ -4,19 +4,23 @@ from http.client import HTTPMessage import mock import pytest -from unittest.mock import ANY +from unittest.mock import ANY, MagicMock from urllib.error import HTTPError from addons.boa import settings as boa_settings from addons.boa.boa_error_code import BoaErrorCode from addons.boa.tasks import submit_to_boa, submit_to_boa_async, handle_boa_error -from addons.boa.tests.async_mock import AsyncMock from osf_tests.factories import AuthUserFactory, ProjectFactory from tests.base import OsfTestCase from website import settings as osf_settings from website.mails import ADDONS_BOA_JOB_COMPLETE, ADDONS_BOA_JOB_FAILURE +class AsyncMock(MagicMock): + async def __call__(self, *args, **kwargs): + return super(AsyncMock, self).__call__(*args, **kwargs) + + class TestBoaErrorHandling(OsfTestCase): def setUp(self): diff --git a/addons/boa/tests/test_views.py b/addons/boa/tests/test_views.py index f11d0ae8e24..fccf5501f18 100644 --- a/addons/boa/tests/test_views.py +++ b/addons/boa/tests/test_views.py @@ -1,66 +1,279 @@ -# -*- coding: utf-8 -*- -from nose.tools import assert_in, assert_equal import mock import pytest - from rest_framework import status as http_status -from addons.base.tests.views import ( - OAuthAddonAuthViewsTestCaseMixin, OAuthAddonConfigViewsTestCaseMixin -) -from addons.boa.models import BoaProvider +from addons.base.tests.views import OAuthAddonAuthViewsTestCaseMixin, OAuthAddonConfigViewsTestCaseMixin +from addons.boa.tests.factories import BOA_HOST, BOA_PASSWORD +from addons.boa.tests.utils import BoaBasicAuthAddonTestCase +from addons.boa.boa_error_code import BoaErrorCode +from api.base.utils import waterbutler_api_url_for +from osf_tests.factories import AuthUserFactory +from osf.utils import permissions from tests.base import OsfTestCase -from addons.boa.serializer import BoaSerializer -from addons.boa.tests.utils import BoaAddonTestCase +from website import settings as osf_settings pytestmark = pytest.mark.django_db -class TestAuthViews(OAuthAddonAuthViewsTestCaseMixin, BoaAddonTestCase, OsfTestCase): - @property - def Provider(self): - return BoaProvider +class TestAuthViews(BoaBasicAuthAddonTestCase, OAuthAddonAuthViewsTestCaseMixin, OsfTestCase): def test_oauth_start(self): + """Not applicable to non-oauth add-ons.""" pass def test_oauth_finish(self): + """Not applicable to non-oauth add-ons.""" pass -class TestConfigViews(BoaAddonTestCase, OAuthAddonConfigViewsTestCaseMixin, OsfTestCase): - Serializer = BoaSerializer - client = BoaProvider - - @property - def folder(self): - return {'name': '/Documents/', 'path': '/Documents/'} +class TestConfigViews(BoaBasicAuthAddonTestCase, OAuthAddonConfigViewsTestCaseMixin, OsfTestCase): def setUp(self): super(TestConfigViews, self).setUp() - self.mock_ser_api = mock.patch('boa.Client.login') - self.mock_ser_api.start() - self.set_node_settings(self.node_settings) + self.mock_boa_client_login = mock.patch('boaapi.boa_client.BoaClient.login') + self.mock_boa_client_close = mock.patch('boaapi.boa_client.BoaClient.close') + self.mock_boa_client_login.start() + self.mock_boa_client_close.start() def tearDown(self): - self.mock_ser_api.stop() + self.mock_boa_client_close.stop() + self.mock_boa_client_login.stop() super(TestConfigViews, self).tearDown() - @mock.patch('addons.boa.models.NodeSettings.get_folders') - def test_folder_list(self, mock_connection): - #test_get_datasets - mock_connection.return_value = ['/Documents/', '/Pictures/', '/Videos/'] + def test_folder_list(self): + """Not applicable to remote computing add-ons.""" + pass - super(TestConfigViews, self).test_folder_list() + def test_set_config(self): + """Not applicable to remote computing add-ons.""" + pass def test_get_config(self): - url = self.project.api_url_for( - '{0}_get_config'.format(self.ADDON_SHORT_NAME)) + """Lacking coverage for remote computing add-ons and thus replaced by: + * ``test_get_config_owner_with_external_account()`` + * ``test_get_config_owner_without_external_account()`` + * ``test_get_config_write_contrib_with_external_account()`` + * ``test_get_config_write_contrib_without_external_account()`` + * ``test_get_config_admin_contrib_with_external_account()`` + * ``test_get_config_admin_contrib_without_external_account()`` + """ + pass + + def test_get_config_unauthorized(self): + """Lacking coverage for remote computing add-ons and thus replaced by: + * ``test_get_config_read_contrib_with_valid_credentials()`` + * ``test_get_config_read_contrib_without_valid_credentials()`` + """ + pass + + def test_get_config_owner_with_external_account(self): + + self.node_settings.set_auth(self.external_account, self.user) + serialized = self.Serializer().serialize_settings( + self.node_settings, + self.user, + self.client + ) + assert self.node_settings.external_account is not None + assert serialized['validCredentials'] is True + + url = self.project.api_url_for('{0}_get_config'.format(self.ADDON_SHORT_NAME)) res = self.app.get(url, auth=self.user.auth) - assert_equal(res.status_code, http_status.HTTP_200_OK) - assert_in('result', res.json) + assert res.status_code == http_status.HTTP_200_OK + assert 'result' in res.json + assert serialized == res.json['result'] + + def test_get_config_owner_without_external_account(self): + serialized = self.Serializer().serialize_settings( self.node_settings, self.user, + self.client + ) + assert self.node_settings.external_account is None + assert serialized['validCredentials'] is False + + url = self.project.api_url_for('{0}_get_config'.format(self.ADDON_SHORT_NAME)) + res = self.app.get(url, auth=self.user.auth) + assert res.status_code == http_status.HTTP_200_OK + assert 'result' in res.json + assert serialized == res.json['result'] + + def test_get_config_write_contrib_with_external_account(self): + + user_write = AuthUserFactory() + self.node_settings.set_auth(self.external_account, self.user) + self.project.add_contributor(user_write, permissions=permissions.WRITE, auth=self.auth, save=True) + serialized = self.Serializer().serialize_settings( + self.node_settings, + user_write, + self.client + ) + assert self.node_settings.external_account is not None + assert serialized['validCredentials'] is True + + url = self.project.api_url_for('{0}_get_config'.format(self.ADDON_SHORT_NAME)) + res = self.app.get(url, auth=user_write.auth) + assert res.status_code == http_status.HTTP_200_OK + assert 'result' in res.json + assert serialized == res.json['result'] + + def test_get_config_write_contrib_without_external_account(self): + + user_write = AuthUserFactory() + self.project.add_contributor(user_write, permissions=permissions.WRITE, auth=self.auth, save=True) + serialized = self.Serializer().serialize_settings( + self.node_settings, + user_write, + self.client + ) + assert self.node_settings.external_account is None + assert serialized['validCredentials'] is False + + url = self.project.api_url_for('{0}_get_config'.format(self.ADDON_SHORT_NAME)) + res = self.app.get(url, auth=user_write.auth) + assert res.status_code == http_status.HTTP_200_OK + assert 'result' in res.json + assert serialized == res.json['result'] + + def test_get_config_admin_contrib_with_external_account(self): + + user_admin = AuthUserFactory() + self.node_settings.set_auth(self.external_account, self.user) + self.project.add_contributor(user_admin, permissions=permissions.ADMIN, auth=self.auth, save=True) + serialized = self.Serializer().serialize_settings( + self.node_settings, + user_admin, + self.client ) - assert_equal(serialized, res.json['result']) + assert self.node_settings.external_account is not None + assert serialized['validCredentials'] is True + + url = self.project.api_url_for('{0}_get_config'.format(self.ADDON_SHORT_NAME)) + res = self.app.get(url, auth=user_admin.auth) + assert res.status_code == http_status.HTTP_200_OK + assert 'result' in res.json + assert serialized == res.json['result'] + + def test_get_config_admin_contrib_without_external_account(self): + + user_admin = AuthUserFactory() + self.project.add_contributor(user_admin, permissions=permissions.ADMIN, auth=self.auth, save=True) + serialized = self.Serializer().serialize_settings( + self.node_settings, + user_admin, + self.client + ) + assert self.node_settings.external_account is None + assert serialized['validCredentials'] is False + + url = self.project.api_url_for('{0}_get_config'.format(self.ADDON_SHORT_NAME)) + res = self.app.get(url, auth=user_admin.auth) + assert res.status_code == http_status.HTTP_200_OK + assert 'result' in res.json + assert serialized == res.json['result'] + + def test_get_config_read_contrib_with_valid_credentials(self): + + user_read_only = AuthUserFactory() + self.project.add_contributor(user_read_only, permissions=permissions.READ, auth=self.auth, save=True) + + url = self.project.api_url_for('{0}_get_config'.format(self.ADDON_SHORT_NAME)) + with mock.patch.object(type(self.Serializer()), 'credentials_are_valid', return_value=True): + res = self.app.get(url, auth=user_read_only.auth, expect_errors=True) + assert res.status_code == http_status.HTTP_403_FORBIDDEN + + def test_get_config_read_contrib_without_valid_credentials(self): + + user_read_only = AuthUserFactory() + self.project.add_contributor(user_read_only, permissions=permissions.READ, auth=self.auth, save=True) + + url = self.project.api_url_for('{0}_get_config'.format(self.ADDON_SHORT_NAME)) + with mock.patch.object(type(self.Serializer()), 'credentials_are_valid', return_value=False): + res = self.app.get(url, auth=user_read_only.auth, expect_errors=True) + assert res.status_code == http_status.HTTP_403_FORBIDDEN + + +class TestBoaSubmitViews(BoaBasicAuthAddonTestCase, OsfTestCase): + + def setUp(self): + super(TestBoaSubmitViews, self).setUp() + self.folder_name = 'fake_boa_folder' + self.file_name = 'fake_boa_file.boa' + self.dataset = 'fake_boa_dataset' + self.download_url = f'{osf_settings.WATERBUTLER_URL}/v1/resources/{self.project._primary_key}/' \ + f'providers/osfstorage/1a2b3c4d5e6f7g8' + self.upload_url = f'{osf_settings.WATERBUTLER_URL}/v1/resources/{self.project._id}/' \ + f'providers/osfstorage/8g7f6e5d4c3b2a1?kind=file' + self.download_url_internal = f'{osf_settings.WATERBUTLER_INTERNAL_URL}/v1/resources/' \ + f'{self.project._primary_key}/providers/osfstorage/1a2b3c4d5e6f7g8' + self.upload_url_internal = f'{osf_settings.WATERBUTLER_INTERNAL_URL}/v1/resources/' \ + f'{self.project._id}/providers/osfstorage/8g7f6e5d4c3b2a1?kind=file' + self.payload_sub_folder = { + 'data': { + 'links': {'download': self.download_url, }, + 'name': self.file_name, + 'materialized': f'/{self.folder_name}/{self.file_name}', + 'nodeId': self.project._id, + }, + 'parent': { + 'links': {'upload': self.upload_url, }, + }, + 'dataset': self.dataset, + } + self.payload_addon_root = { + 'data': { + 'links': {'download': self.download_url, }, + 'name': self.file_name, + 'materialized': f'/{self.file_name}', + 'nodeId': self.project._id, + }, + 'parent': { + 'isAddonRoot': True, + }, + 'dataset': self.dataset, + } + + def tearDown(self): + super(TestBoaSubmitViews, self).tearDown() + + def test_boa_submit_job_from_addon_root(self): + with mock.patch('addons.boa.tasks.submit_to_boa.s', return_value=BoaErrorCode.NO_ERROR) as mock_submit_s: + self.node_settings.set_auth(self.external_account, self.user) + base_url = self.project.osfstorage_region.waterbutler_url + addon_root_url = waterbutler_api_url_for(self.project._id, 'osfstorage', _internal=True, base_url=base_url) + upload_url_root = f'{addon_root_url}?kind=file' + url = self.project.api_url_for('boa_submit_job') + res = self.app.post_json(url, self.payload_addon_root, auth=self.user.auth) + assert res.status_code == http_status.HTTP_200_OK + mock_submit_s.assert_called_with( + BOA_HOST, + mock.ANY, + BOA_PASSWORD, + self.user._id, + self.project._id, + self.dataset, + self.file_name, + f'/{self.file_name}', + self.download_url_internal, + upload_url_root, + ) + + def test_boa_submit_job_from_sub_folder(self): + with mock.patch('addons.boa.tasks.submit_to_boa.s', return_value=BoaErrorCode.NO_ERROR) as mock_submit_s: + self.node_settings.set_auth(self.external_account, self.user) + url = self.project.api_url_for('boa_submit_job') + res = self.app.post_json(url, self.payload_sub_folder, auth=self.user.auth) + assert res.status_code == http_status.HTTP_200_OK + mock_submit_s.assert_called_with( + BOA_HOST, + mock.ANY, + BOA_PASSWORD, + self.user._id, + self.project._id, + self.dataset, + self.file_name, + f'/{self.folder_name}/{self.file_name}', + self.download_url_internal, + self.upload_url_internal, + ) diff --git a/addons/boa/tests/utils.py b/addons/boa/tests/utils.py index cd69ce9a16b..b03e9d803c9 100644 --- a/addons/boa/tests/utils.py +++ b/addons/boa/tests/utils.py @@ -1,22 +1,33 @@ -from addons.base.tests.base import OAuthAddonTestCaseMixin, AddonTestCase -from addons.boa.models import BoaProvider, NodeSettings -from addons.boa.tests.factories import ( - BoaAccountFactory, BoaNodeSettingsFactory, - BoaUserSettingsFactory -) +from addons.base.tests.base import AddonTestCase, OAuthAddonTestCaseMixin +from addons.boa.models import BoaProvider, BoaSerializer, NodeSettings +from addons.boa.tests.factories import BoaAccountFactory, BoaNodeSettingsFactory, BoaUserSettingsFactory -class BoaAddonTestCase(OAuthAddonTestCaseMixin, AddonTestCase): + +class BoaAddonTestCaseBaseMixin(object): short_name = 'boa' full_name = 'Boa' + client = None # Non-oauth add-on does not have client + folder = None # Remote computing add-on does not have folder + addon_short_name = 'boa' ADDON_SHORT_NAME = 'boa' - ExternalAccountFactory = BoaAccountFactory Provider = BoaProvider + Serializer = BoaSerializer + ExternalAccountFactory = BoaAccountFactory NodeSettingsFactory = BoaNodeSettingsFactory NodeSettingsClass = NodeSettings UserSettingsFactory = BoaUserSettingsFactory - # folder = { - # 'path': '/Documents/', - # 'name': '/Documents', - # 'id': '/Documents/' - # } + + +class BoaBasicAuthAddonTestCase(BoaAddonTestCaseBaseMixin, OAuthAddonTestCaseMixin, AddonTestCase): + + def __init__(self, *args, **kwargs): + super(BoaBasicAuthAddonTestCase, self).__init__(*args, **kwargs) + self.auth = None + self.external_account = None + + def set_user_settings(self, settings): + super(BoaBasicAuthAddonTestCase, self).set_user_settings(settings) + + def set_node_settings(self, settings): + super(BoaBasicAuthAddonTestCase, self).set_node_settings(settings) diff --git a/addons/boa/views.py b/addons/boa/views.py index f0bc9c9bc8e..e5e96dc6349 100644 --- a/addons/boa/views.py +++ b/addons/boa/views.py @@ -22,48 +22,31 @@ SHORT_NAME = 'boa' FULL_NAME = 'Boa' -boa_account_list = generic_views.account_list( - SHORT_NAME, - BoaSerializer -) - -boa_import_auth = generic_views.import_auth( - SHORT_NAME, - BoaSerializer -) - -boa_deauthorize_node = generic_views.deauthorize_node( - SHORT_NAME -) +boa_account_list = generic_views.account_list(SHORT_NAME, BoaSerializer) +boa_import_auth = generic_views.import_auth(SHORT_NAME, BoaSerializer) +boa_deauthorize_node = generic_views.deauthorize_node(SHORT_NAME) +boa_get_config = generic_views.get_config(SHORT_NAME, BoaSerializer) @must_be_logged_in def boa_add_user_account(auth, **kwargs): - """ - Verifies new external account credentials and adds to user's list - - This view expects `username` and `password` fields in the JSON - body of the request. + """Verifies new external account credentials and adds to user's list. + This view expects `username` and `password` fields in the JSON body of the request. """ username = request.json.get('username') password = request.json.get('password') try: - b = BoaClient(endpoint=BOA_API_ENDPOINT) - b.login(username, password) - b.close() + boa_client = BoaClient(endpoint=BOA_API_ENDPOINT) + boa_client.login(username, password) + boa_client.close() except BoaException: - return { - 'message': 'Boa Login failed.' - }, http_status.HTTP_401_UNAUTHORIZED + return {'message': 'Boa Login failed.'}, http_status.HTTP_401_UNAUTHORIZED - provider = BoaProvider( - account=None, host=BOA_API_ENDPOINT, username=username, password=password - ) + provider = BoaProvider(account=None, host=BOA_API_ENDPOINT, username=username, password=password) try: provider.account.save() - except ValidationError: # as vexc: - # ... or get the old one + except ValidationError: provider.account = ExternalAccount.objects.get( provider=provider.short_name, provider_id='{}:{}'.format(BOA_API_ENDPOINT, username).lower() @@ -71,7 +54,7 @@ def boa_add_user_account(auth, **kwargs): if provider.account.oauth_key != password: provider.account.oauth_key = password provider.account.save() - except Exception: # as exc: + except Exception: return {} user = auth.user @@ -80,36 +63,8 @@ def boa_add_user_account(auth, **kwargs): user.get_or_add_addon('boa', auth=auth) user.save() - return {} -# @must_have_addon(SHORT_NAME, 'user') -# @must_have_addon(SHORT_NAME, 'node') -# def boa_folder_list(node_addon, user_addon, **kwargs): -# """ Returns all the subsequent folders under the folder id passed. -# Not easily generalizable due to `path` kwarg. -# """ -# path = request.args.get('path') -# return node_addon.get_folders(path=path) - - -def _set_folder(node_addon, folder, auth): - node_addon.set_folder(folder['path'], auth=auth) - node_addon.save() - - -boa_set_config = generic_views.set_config( - SHORT_NAME, - FULL_NAME, - BoaSerializer, - _set_folder -) - -boa_get_config = generic_views.get_config( - SHORT_NAME, - BoaSerializer -) - @must_be_logged_in @must_have_addon(SHORT_NAME, 'user') @@ -141,7 +96,7 @@ def boa_submit_job(node_addon, user_addon, **kwargs): if is_addon_root: base_url = project_node.osfstorage_region.waterbutler_url parent_wb_url = waterbutler_api_url_for(project_guid, 'osfstorage', _internal=True, base_url=base_url) - output_upload_url = f'{parent_wb_url}?kind=file&name=' + output_upload_url = f'{parent_wb_url}?kind=file' else: output_upload_url = req_params['parent']['links']['upload'].replace(osf_settings.WATERBUTLER_URL, osf_settings.WATERBUTLER_INTERNAL_URL) diff --git a/addons/owncloud/serializer.py b/addons/owncloud/serializer.py index f4025a2eb49..363280c73b3 100644 --- a/addons/owncloud/serializer.py +++ b/addons/owncloud/serializer.py @@ -17,6 +17,8 @@ def serialized_folder(self, node_settings): def credentials_are_valid(self, user_settings, client=None): node = self.node_settings external_account = node.external_account + if external_account is None: + return False provider = self.node_settings.oauth_provider(external_account) try: diff --git a/addons/owncloud/tests/test_models.py b/addons/owncloud/tests/test_models.py index 445679effe4..58322d081c1 100644 --- a/addons/owncloud/tests/test_models.py +++ b/addons/owncloud/tests/test_models.py @@ -1,35 +1,18 @@ -from nose.tools import assert_is_not_none, assert_equal import pytest import unittest -from addons.base.tests.models import (OAuthAddonNodeSettingsTestSuiteMixin, - OAuthAddonUserSettingTestSuiteMixin) - -from addons.owncloud.models import NodeSettings -from addons.owncloud.tests.factories import ( - OwnCloudAccountFactory, OwnCloudNodeSettingsFactory, - OwnCloudUserSettingsFactory -) +from addons.base.tests.models import OAuthAddonNodeSettingsTestSuiteMixin, OAuthAddonUserSettingTestSuiteMixin from addons.owncloud.settings import USE_SSL +from addons.owncloud.tests.utils import OwnCloudAddonTestCaseBaseMixin pytestmark = pytest.mark.django_db -class TestUserSettings(OAuthAddonUserSettingTestSuiteMixin, unittest.TestCase): - - short_name = 'owncloud' - full_name = 'ownCloud' - UserSettingsFactory = OwnCloudUserSettingsFactory - ExternalAccountFactory = OwnCloudAccountFactory +class TestUserSettings(OwnCloudAddonTestCaseBaseMixin, OAuthAddonUserSettingTestSuiteMixin, unittest.TestCase): + pass -class TestNodeSettings(OAuthAddonNodeSettingsTestSuiteMixin, unittest.TestCase): - short_name = 'owncloud' - full_name = 'ownCloud' - ExternalAccountFactory = OwnCloudAccountFactory - NodeSettingsFactory = OwnCloudNodeSettingsFactory - NodeSettingsClass = NodeSettings - UserSettingsFactory = OwnCloudUserSettingsFactory +class TestNodeSettings(OwnCloudAddonTestCaseBaseMixin, OAuthAddonNodeSettingsTestSuiteMixin, unittest.TestCase): def _node_settings_class_kwargs(self, node, user_settings): return { @@ -41,14 +24,14 @@ def _node_settings_class_kwargs(self, node, user_settings): def test_serialize_credentials(self): credentials = self.node_settings.serialize_waterbutler_credentials() - assert_is_not_none(self.node_settings.external_account.oauth_secret) + assert self.node_settings.external_account.oauth_secret is not None expected = { 'host': self.node_settings.external_account.oauth_secret, 'password': 'meoword', 'username': 'catname' } - assert_equal(credentials, expected) + assert credentials == expected def test_serialize_settings(self): settings = self.node_settings.serialize_waterbutler_settings() @@ -56,4 +39,4 @@ def test_serialize_settings(self): 'folder': self.node_settings.folder_id, 'verify_ssl': USE_SSL } - assert_equal(settings, expected) + assert settings == expected diff --git a/addons/owncloud/tests/test_serializer.py b/addons/owncloud/tests/test_serializer.py index 962236d4d7e..54a41476f4a 100644 --- a/addons/owncloud/tests/test_serializer.py +++ b/addons/owncloud/tests/test_serializer.py @@ -3,18 +3,14 @@ from tests.base import OsfTestCase from addons.base.tests.serializers import StorageAddonSerializerTestSuiteMixin -from addons.owncloud.tests.factories import OwnCloudAccountFactory -from addons.owncloud.serializer import OwnCloudSerializer +from addons.owncloud.tests.utils import OwnCloudAddonTestCaseBaseMixin pytestmark = pytest.mark.django_db -class TestOwnCloudSerializer(StorageAddonSerializerTestSuiteMixin, OsfTestCase): - addon_short_name = 'owncloud' - Serializer = OwnCloudSerializer - ExternalAccountFactory = OwnCloudAccountFactory - client = None - def set_provider_id(self, pid): +class TestOwnCloudSerializer(OwnCloudAddonTestCaseBaseMixin, StorageAddonSerializerTestSuiteMixin, OsfTestCase): + + def set_provider_id(self, pid=None): self.node_settings.folder_id = pid def setUp(self): diff --git a/addons/owncloud/tests/test_views.py b/addons/owncloud/tests/test_views.py index ab0f42f71c9..27e548e32e8 100644 --- a/addons/owncloud/tests/test_views.py +++ b/addons/owncloud/tests/test_views.py @@ -1,25 +1,15 @@ -# -*- coding: utf-8 -*- -from nose.tools import assert_in, assert_equal import mock import pytest - from rest_framework import status as http_status -from addons.base.tests.views import ( - OAuthAddonAuthViewsTestCaseMixin, OAuthAddonConfigViewsTestCaseMixin -) -from addons.owncloud.models import OwnCloudProvider +from addons.base.tests.views import OAuthAddonAuthViewsTestCaseMixin, OAuthAddonConfigViewsTestCaseMixin +from addons.owncloud.tests.utils import OwnCloudBasicAuthAddonTestCase from tests.base import OsfTestCase -from addons.owncloud.serializer import OwnCloudSerializer -from addons.owncloud.tests.utils import OwnCloudAddonTestCase pytestmark = pytest.mark.django_db -class TestAuthViews(OAuthAddonAuthViewsTestCaseMixin, OwnCloudAddonTestCase, OsfTestCase): - @property - def Provider(self): - return OwnCloudProvider +class TestAuthViews(OwnCloudBasicAuthAddonTestCase, OAuthAddonAuthViewsTestCaseMixin, OsfTestCase): def test_oauth_start(self): pass @@ -28,39 +18,53 @@ def test_oauth_finish(self): pass -class TestConfigViews(OwnCloudAddonTestCase, OAuthAddonConfigViewsTestCaseMixin, OsfTestCase): - Serializer = OwnCloudSerializer - client = OwnCloudProvider - - @property - def folder(self): - return {'name': '/Documents/', 'path': '/Documents/'} +class TestConfigViews(OwnCloudBasicAuthAddonTestCase, OAuthAddonConfigViewsTestCaseMixin, OsfTestCase): def setUp(self): super(TestConfigViews, self).setUp() - self.mock_ser_api = mock.patch('owncloud.Client.login') - self.mock_ser_api.start() - self.set_node_settings(self.node_settings) + self.mock_owncloud_login = mock.patch('owncloud.Client.login') + self.mock_owncloud_logout = mock.patch('owncloud.Client.logout') + self.mock_owncloud_login.start() + self.mock_owncloud_logout.start() def tearDown(self): - self.mock_ser_api.stop() + self.mock_owncloud_logout.stop() + self.mock_owncloud_login.stop() super(TestConfigViews, self).tearDown() @mock.patch('addons.owncloud.models.NodeSettings.get_folders') - def test_folder_list(self, mock_connection): - #test_get_datasets - mock_connection.return_value = ['/Documents/', '/Pictures/', '/Videos/'] - + def test_folder_list(self, mock_get_folders): + mock_get_folders.return_value = ['/Documents/', '/Pictures/', '/Videos/'] super(TestConfigViews, self).test_folder_list() def test_get_config(self): - url = self.project.api_url_for( - '{0}_get_config'.format(self.ADDON_SHORT_NAME)) + """Lacking coverage for non-oauth add-ons and thus replaced by: + * ``test_get_config_with_external_account()`` + * ``test_get_config_without_external_account()`` + """ + pass + + def test_get_config_with_external_account(self): + + self.node_settings.set_auth(self.external_account, self.user) + serialized = self.Serializer().serialize_settings(self.node_settings, self.user) + assert self.node_settings.external_account is not None + assert serialized['validCredentials'] is True + + url = self.project.api_url_for('{0}_get_config'.format(self.ADDON_SHORT_NAME)) + res = self.app.get(url, auth=self.user.auth) + assert res.status_code == http_status.HTTP_200_OK + assert 'result' in res.json + assert serialized == res.json['result'] + + def test_get_config_without_external_account(self): + + serialized = self.Serializer().serialize_settings(self.node_settings, self.user) + assert self.node_settings.external_account is None + assert serialized['validCredentials'] is False + + url = self.project.api_url_for('{0}_get_config'.format(self.ADDON_SHORT_NAME)) res = self.app.get(url, auth=self.user.auth) - assert_equal(res.status_code, http_status.HTTP_200_OK) - assert_in('result', res.json) - serialized = self.Serializer().serialize_settings( - self.node_settings, - self.user, - ) - assert_equal(serialized, res.json['result']) + assert res.status_code == http_status.HTTP_200_OK + assert 'result' in res.json + assert serialized == res.json['result'] diff --git a/addons/owncloud/tests/utils.py b/addons/owncloud/tests/utils.py index 53f93658721..2e4735b2eca 100644 --- a/addons/owncloud/tests/utils.py +++ b/addons/owncloud/tests/utils.py @@ -1,22 +1,37 @@ -from addons.base.tests.base import OAuthAddonTestCaseMixin, AddonTestCase -from addons.owncloud.models import OwnCloudProvider, NodeSettings +from addons.base.tests.base import AddonTestCase, OAuthAddonTestCaseMixin +from addons.owncloud.models import NodeSettings, OwnCloudProvider, OwnCloudSerializer from addons.owncloud.tests.factories import ( - OwnCloudAccountFactory, OwnCloudNodeSettingsFactory, - OwnCloudUserSettingsFactory + OwnCloudAccountFactory, + OwnCloudNodeSettingsFactory, + OwnCloudUserSettingsFactory, ) -class OwnCloudAddonTestCase(OAuthAddonTestCaseMixin, AddonTestCase): + +class OwnCloudAddonTestCaseBaseMixin(object): short_name = 'owncloud' full_name = 'OwnCloud' + client = None # Non-oauth add-on does not have client + folder = {'path': '/Documents/', 'name': '/Documents', 'id': '/Documents/'} + addon_short_name = 'owncloud' ADDON_SHORT_NAME = 'owncloud' - ExternalAccountFactory = OwnCloudAccountFactory Provider = OwnCloudProvider + Serializer = OwnCloudSerializer + ExternalAccountFactory = OwnCloudAccountFactory NodeSettingsFactory = OwnCloudNodeSettingsFactory NodeSettingsClass = NodeSettings UserSettingsFactory = OwnCloudUserSettingsFactory - folder = { - 'path': '/Documents/', - 'name': '/Documents', - 'id': '/Documents/' - } + + +class OwnCloudBasicAuthAddonTestCase(OwnCloudAddonTestCaseBaseMixin, OAuthAddonTestCaseMixin, AddonTestCase): + + def __init__(self, *args, **kwargs): + super(OwnCloudBasicAuthAddonTestCase, self).__init__(*args, **kwargs) + self.auth = None + self.external_account = None + + def set_user_settings(self, settings): + super(OwnCloudBasicAuthAddonTestCase, self).set_user_settings(settings) + + def set_node_settings(self, settings): + super(OwnCloudBasicAuthAddonTestCase, self).set_node_settings(settings) diff --git a/tests/test_rubeus.py b/tests/test_rubeus.py index bae64700250..1c3ebb1984c 100644 --- a/tests/test_rubeus.py +++ b/tests/test_rubeus.py @@ -11,6 +11,7 @@ from website.util import rubeus from website.util.rubeus import sort_by_name + class TestRubeus(OsfTestCase): def setUp(self): @@ -316,8 +317,9 @@ def setUp(self): self.serializer = rubeus.NodeFileCollector(node=self.project, auth=self.auth) def test_collect_addons(self): - ret = self.serializer._collect_addons(self.project) - assert_equal(ret, [serialized]) + return_value, active_addons = self.serializer._collect_addons(self.project) + assert_equal(return_value, [serialized]) + assert_equal(len(active_addons), 1) def test_sort_by_name(self): files = [ diff --git a/website/util/rubeus.py b/website/util/rubeus.py index aacda00038a..a2b16067b43 100644 --- a/website/util/rubeus.py +++ b/website/util/rubeus.py @@ -241,6 +241,7 @@ def _serialize_node(self, node, parent=None, grid_root=None, children=None, def _get_nodes(self, node, grid_root=None): data = [] + active_addons = [] if node.can_view(auth=self.auth): serialized_addons, active_addons = self._collect_addons(node) serialized_children = [ @@ -251,18 +252,17 @@ def _get_nodes(self, node, grid_root=None): return self._serialize_node(node, children=data, active_addons=active_addons) def _collect_addons(self, node): - rv = [] + return_value = [] active_addons = [] for addon in node.get_addons(): if addon.has_auth: active_addons.append(addon.config.short_name) - if addon.config.has_hgrid_files: # WARNING: get_hgrid_data can return None if the addon is added but has no credentials. try: temp = addon.config.get_hgrid_data(addon, self.auth, **self.extra) except Exception as e: - logger.warn( + logger.warning( getattr( e, 'data', @@ -270,7 +270,7 @@ def _collect_addons(self, node): ) ) sentry.log_exception() - rv.append({ + return_value.append({ KIND: FOLDER, 'unavailable': True, 'iconUrl': addon.config.icon_url, @@ -280,8 +280,8 @@ def _collect_addons(self, node): 'name': '{} is currently unavailable'.format(addon.config.full_name), }) continue - rv.extend(sort_by_name(temp) or []) - return rv, active_addons + return_value.extend(sort_by_name(temp) or []) + return return_value, active_addons # TODO: these might belong in addons module