Skip to content

Commit

Permalink
(wip) addon operation invocations (part 4: tests)
Browse files Browse the repository at this point in the history
  • Loading branch information
aaxelb committed Mar 13, 2024
1 parent 8e1770a commit 4b51e53
Show file tree
Hide file tree
Showing 7 changed files with 244 additions and 34 deletions.
2 changes: 1 addition & 1 deletion addon_service/management/commands/fill_garbage.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down
30 changes: 20 additions & 10 deletions addon_service/tests/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
207 changes: 207 additions & 0 deletions addon_service/tests/test_by_type/test_addon_operation_invocation.py
Original file line number Diff line number Diff line change
@@ -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),
)
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -64,6 +64,7 @@ def test_post(self):
"data": {
"type": "authorized-storage-accounts",
"attributes": {
"authorized_capabilities": ["ACCESS"],
"username": "<placeholder-username>",
"password": "<placeholder-password>",
},
Expand Down Expand Up @@ -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",
},
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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": ""}
Expand Down
25 changes: 4 additions & 21 deletions addon_service/tests/test_by_type/test_external_storage_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def test_get(self):
self.assertEqual(
set(_content["data"]["relationships"].keys()),
{
"authorized_storage_accounts",
"addon_imp",
},
)

Expand All @@ -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)
2 changes: 1 addition & 1 deletion addon_toolkit/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def __post_init__(self):

@dataclasses.dataclass
class PageArg:
cursor: str
cursor: str = ""


@dataclasses.dataclass
Expand Down

0 comments on commit 4b51e53

Please sign in to comment.