diff --git a/addon_service/management/commands/fill_garbage.py b/addon_service/management/commands/fill_garbage.py index 1256aad0..b8064876 100644 --- a/addon_service/management/commands/fill_garbage.py +++ b/addon_service/management/commands/fill_garbage.py @@ -54,7 +54,7 @@ def handle_label(self, label, **options): _soi = db.AddonOperationInvocation.objects.create( invocation_status=InvocationStatus.STARTING, operation_identifier=_op.natural_key_str, - operation_kwargs={"item_id": "foo"}, + operation_kwargs={"item": {"item_id": "foo"}, "page": {}}, thru_addon=_csa, by_user=_iu, ) diff --git a/addon_service/tests/_helpers.py b/addon_service/tests/_helpers.py index 7b466fc3..4582e5ab 100644 --- a/addon_service/tests/_helpers.py +++ b/addon_service/tests/_helpers.py @@ -4,20 +4,11 @@ from django.contrib.sessions.backends.db import SessionStore from rest_framework import exceptions as drf_exceptions from rest_framework.test import APIRequestFactory +from rest_framework_json_api.utils import get_resource_type_from_model from app import settings -def get_test_request(user=None, method="get", path="", cookies=None): - _factory_method = getattr(APIRequestFactory(), method) - _request = _factory_method(path) # note that path is optional for view tests - _request.session = SessionStore() # Add cookies if provided - if cookies: - for name, value in cookies.items(): - _request.COOKIES[name] = value - return _request - - class MockOSF: def __init__(self, permissions=None): """A lightweight, configurable mock of OSF for testing remote permissions. @@ -103,3 +94,22 @@ def _mock_resource_check(self, request, uri, required_permission, *args, **kwarg if required_permission.lower() not in permissions: raise drf_exceptions.PermissionDenied return uri # mimicking behavior from the check being mocked + + +# TODO: use this more often in tests +def jsonapi_ref(obj) -> dict: + """return a jsonapi resource reference (as json-serializable dict)""" + return { + "type": get_resource_type_from_model(obj.__class__), + "id": obj.pk, + } + + +def get_test_request(user=None, method="get", path="", cookies=None): + _factory_method = getattr(APIRequestFactory(), method) + _request = _factory_method(path) # note that path is optional for view tests + _request.session = SessionStore() # Add cookies if provided + if cookies: + for name, value in cookies.items(): + _request.COOKIES[name] = value + return _request diff --git a/addon_service/tests/test_by_type/test_addon_operation_invocation.py b/addon_service/tests/test_by_type/test_addon_operation_invocation.py new file mode 100644 index 00000000..a5b06dab --- /dev/null +++ b/addon_service/tests/test_by_type/test_addon_operation_invocation.py @@ -0,0 +1,207 @@ +import json +import unittest +from http import HTTPStatus + +from django.test import TestCase +from django.urls import reverse +from rest_framework.test import APITestCase + +from addon_service import models +from addon_service.addon_operation_invocation.views import ( + AddonOperationInvocationViewSet, +) +from addon_service.tests import _factories +from addon_service.tests._helpers import ( + get_test_request, + jsonapi_ref, +) + + +class TestAddonOperationInvocationCreate(APITestCase): + @classmethod + def setUpTestData(cls): + cls._configured_addon = _factories.ConfiguredStorageAddonFactory() + cls._operation = models.AddonOperationModel.get_by_natural_key_str( + "BLARG:blargblarg" + ) + + @property + def _list_path(self): + return reverse("addon-operation-invocations-list") + + def _payload_for_post(self): + return { + "data": { + "type": "addon-operation-invocations", + "attributes": { + "operation_kwargs": {"item": {"item_id": "foo"}}, + }, + "relationships": { + "operation": { + "data": jsonapi_ref(self._operation), + }, + "thru_addon": { + "data": jsonapi_ref(self._configured_addon), + }, + }, + } + } + + def test_post(self): + _resp = self.client.post( + self._list_path, + data=json.dumps(self._payload_for_post()), + content_type="application/vnd.api+json", + ) + self.assertEqual(_resp.status_code, HTTPStatus.CREATED) + self.assertEqual( + _resp.data["operation_result"], + { + "item_ids": ["hello"], + "next_cursor": None, + "total_count": 1, + }, + ) + self.assertEqual( + _resp.data["invocation_status"], + "SUCCESS", + ) + + +@unittest.skip("TODO") +class TestAddonOperationInvocationErrors(APITestCase): + @classmethod + def setUpTestData(cls): + cls._invocation = _factories.AddonOperationInvocationFactory() + + @property + def _detail_path(self): + return reverse( + "addon-operation-invocations-detail", + kwargs={"pk": self._invocation.pk}, + ) + + @property + def _list_path(self): + return reverse("addon-operation-invocations-list") + + def _related_path(self, related_field): + return reverse( + "addon-operation-invocations-related", + kwargs={ + "pk": self._invocation.pk, + "related_field": related_field, + }, + ) + + def test_methods_not_allowed(self): + _methods_not_allowed = { + self._list_path: {"get", "patch", "put"}, + self._detail_path: {"patch", "put", "post", "delete"}, + self._related_path("thru_addon"): {"patch", "put", "post", "delete"}, + self._related_path("by_user"): {"patch", "put", "post", "delete"}, + self._related_path("operation"): {"patch", "put", "post", "delete"}, + } + for _path, _methods in _methods_not_allowed.items(): + for _method in _methods: + with self.subTest(path=_path, method=_method): + _client_method = getattr(self.client, _method) + _resp = _client_method(_path) + self.assertEqual(_resp.status_code, HTTPStatus.METHOD_NOT_ALLOWED) + + +# unit-test data model +@unittest.skip("TODO") +class TestAddonOperationInvocationModel(TestCase): + @classmethod + def setUpTestData(cls): + cls._configured_addon = _factories.AddonOperationInvocationFactory() + + def test_can_load(self): + _resource_from_db = models.AddonOperationInvocation.objects.get( + id=self._configured_addon.id + ) + self.assertEqual(self._configured_addon.pk, _resource_from_db.pk) + + +# unit-test viewset (call the view with test requests) +@unittest.skip("TODO") +class TestAddonOperationInvocationViewSet(TestCase): + @classmethod + def setUpTestData(cls): + cls._invocation = _factories.AddonOperationInvocationFactory() + cls._view = AddonOperationInvocationViewSet.as_view( + { + "post": "create", + "get": "retrieve", + "delete": "destroy", + } + ) + + def test_get(self): + _resp = self._view( + get_test_request(), + pk=self._invocation.pk, + ) + self.assertEqual(_resp.status_code, HTTPStatus.OK) + _content = json.loads(_resp.rendered_content) + self.assertEqual( + set(_content["data"]["attributes"].keys()), + { + "root_folder", + "connected_capabilities", + }, + ) + self.assertEqual( + _content["data"]["attributes"]["connected_capabilities"], + ["ACCESS"], + ) + self.assertEqual( + set(_content["data"]["relationships"].keys()), + { + "authorized_resource", + "base_account", + "connected_operations", + }, + ) + + @unittest.expectedFailure # TODO + def test_unauthorized(self): + _anon_resp = self._view(get_test_request(), pk=self._user.pk) + self.assertEqual(_anon_resp.status_code, HTTPStatus.UNAUTHORIZED) + + @unittest.expectedFailure # TODO + def test_wrong_user(self): + _another_user = _factories.UserReferenceFactory() + _resp = self._view( + get_test_request(user=_another_user), + pk=self._user.pk, + ) + self.assertEqual(_resp.status_code, HTTPStatus.FORBIDDEN) + + # def test_create(self): + # _post_req = get_test_request(user=self._user, method='post') + # self._view(_post_req, pk= + + +@unittest.skip("TODO") +class TestAddonOperationInvocationRelatedView(TestCase): + @classmethod + def setUpTestData(cls): + cls._invocation = _factories.AddonOperationInvocationFactory() + cls._related_view = AddonOperationInvocationViewSet.as_view( + {"get": "retrieve_related"}, + ) + + def test_get_related(self): + _resp = self._related_view( + get_test_request(), + pk=self._invocation.pk, + related_field="base_account", + ) + self.assertEqual(_resp.status_code, HTTPStatus.OK) + _content = json.loads(_resp.rendered_content) + self.assertEqual( + _content["data"]["id"], + str(self._invocation.base_account_id), + ) diff --git a/addon_service/tests/test_by_type/test_authorized_storage_account.py b/addon_service/tests/test_by_type/test_authorized_storage_account.py index 8cbea0a2..cd32ae44 100644 --- a/addon_service/tests/test_by_type/test_authorized_storage_account.py +++ b/addon_service/tests/test_by_type/test_authorized_storage_account.py @@ -49,7 +49,7 @@ def _related_path(self, related_field): }, ) - def test_get(self): + def test_get_detail(self): _resp = self.client.get(self._detail_path) self.assertEqual(_resp.status_code, HTTPStatus.OK) self.assertEqual( @@ -64,6 +64,7 @@ def test_post(self): "data": { "type": "authorized-storage-accounts", "attributes": { + "authorized_capabilities": ["ACCESS"], "username": "", "password": "", }, @@ -158,14 +159,20 @@ def test_get(self): set(_content["data"]["attributes"].keys()), { "default_root_folder", + "authorized_capabilities", }, ) + self.assertEqual( + _content["data"]["attributes"]["authorized_capabilities"], + ["ACCESS"], + ) self.assertEqual( set(_content["data"]["relationships"].keys()), { "account_owner", "external_storage_service", "configured_storage_addons", + "authorized_operations", }, ) diff --git a/addon_service/tests/test_by_type/test_configured_storage_addon.py b/addon_service/tests/test_by_type/test_configured_storage_addon.py index 4c0051c1..ecabcc76 100644 --- a/addon_service/tests/test_by_type/test_configured_storage_addon.py +++ b/addon_service/tests/test_by_type/test_configured_storage_addon.py @@ -108,6 +108,9 @@ class ConfiguredStorageAddonPOSTTests(BaseAPITest): default_payload = { "data": { "type": "configured-storage-addons", + "attributes": { + "connected_capabilities": ["ACCESS"], + }, "relationships": { "base_account": { "data": {"type": "authorized-storage-accounts", "id": ""} diff --git a/addon_service/tests/test_by_type/test_external_storage_service.py b/addon_service/tests/test_by_type/test_external_storage_service.py index fcece35f..ade64f49 100644 --- a/addon_service/tests/test_by_type/test_external_storage_service.py +++ b/addon_service/tests/test_by_type/test_external_storage_service.py @@ -114,7 +114,7 @@ def test_get(self): self.assertEqual( set(_content["data"]["relationships"].keys()), { - "authorized_storage_accounts", + "addon_imp", }, ) @@ -141,28 +141,11 @@ def setUpTestData(cls): {"get": "retrieve_related"}, ) - def test_get_related__empty(self): + def test_get_related(self): _resp = self._related_view( get_test_request(), pk=self._ess.pk, - related_field="authorized_storage_accounts", + related_field="addon_imp", ) self.assertEqual(_resp.status_code, HTTPStatus.OK) - self.assertEqual(_resp.data, []) - - def test_get_related__several(self): - _accounts = _factories.AuthorizedStorageAccountFactory.create_batch( - size=5, - external_storage_service=self._ess, - ) - _resp = self._related_view( - get_test_request(), - pk=self._ess.pk, - related_field="authorized_storage_accounts", - ) - self.assertEqual(_resp.status_code, HTTPStatus.OK) - _content = json.loads(_resp.rendered_content) - self.assertEqual( - {_datum["id"] for _datum in _content["data"]}, - {str(_account.pk) for _account in _accounts}, - ) + self.assertEqual(_resp.data["name"], self._ess.addon_imp.name) diff --git a/addon_toolkit/storage.py b/addon_toolkit/storage.py index 5f518d22..8ee39769 100644 --- a/addon_toolkit/storage.py +++ b/addon_toolkit/storage.py @@ -31,7 +31,7 @@ def __post_init__(self): @dataclasses.dataclass class PageArg: - cursor: str + cursor: str = "" @dataclasses.dataclass