Skip to content

Commit

Permalink
Merge pull request #69 from Ori-Roza/general-refactor
Browse files Browse the repository at this point in the history
General refactor
  • Loading branch information
Ori-Roza authored May 12, 2024
2 parents ef39e86 + a003975 commit ccda5f9
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 44 deletions.
30 changes: 15 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@


The drf-api-action Python package is designed to elevate your testing experience for Django Rest Framework (DRF) REST endpoints.
With the action_api fixture, this package empowers you to effortlessly test your REST endpoints as if they were conventional functions.
With the api_action fixture, this package empowers you to effortlessly test your REST endpoints as if they were conventional functions.

Features:

* **Simplified Testing:** Testing DRF REST endpoints using the action_api plugin, treating them like regular functions.
* **Simplified Testing:** Testing DRF REST endpoints using the api_action plugin, treating them like regular functions.

* **Seamless Integration:** you don't need to do anything in existing server code.

Expand Down Expand Up @@ -41,7 +41,7 @@ from tests.test_server.test_app.views import DummyViewSetFixture

#### Step 2: use the following action_api mark decorator:

`@pytest.mark.action_api(view_set_class={YOUR VIEW_SET})`
`@pytest.mark.api_action(view_set_class={YOUR VIEW_SET})`

e.g:
our ViewSet is called `DummyViewSetFixture`
Expand All @@ -51,8 +51,8 @@ import pytest
from tests.test_server.test_app.views import DummyViewSetFixture


@pytest.mark.action_api(view_set_class=DummyViewSetFixture)
def test_call_as_api_fixture(db, action_api):
@pytest.mark.api_action(view_set_class=DummyViewSetFixture)
def test_call_as_api_fixture(db, api_action):
pass
```
Now you can use all `DummyViewSetFixture` functionality!
Expand All @@ -68,12 +68,12 @@ from tests.test_server.test_app.models import DummyModel
from tests.test_server.test_app.views import DummyViewSetFixture


@pytest.mark.action_api(view_set_class=DummyViewSetFixture)
def test_call_as_api_fixture(db, action_api):
@pytest.mark.api_action(view_set_class=DummyViewSetFixture)
def test_call_as_api_fixture(db, api_action):
dummy_model = DummyModel()
dummy_model.dummy_int = 1
dummy_model.save()
res = action_api.api_dummy(pk=1)
res = api_action.api_dummy(pk=1)
assert res["dummy_int"] == 1

```
Expand All @@ -84,9 +84,9 @@ import pytest
from tests.test_server.test_app.views import DummyViewSetFixture


@pytest.mark.action_api(view_set_class=DummyViewSetFixture)
def test_dummy(db, action_api):
result = action_api.dummy(pk='bbb')
@pytest.mark.api_action(view_set_class=DummyViewSetFixture)
def test_dummy(db, api_action):
result = api_action.dummy(pk='bbb')
assert result['dummy_int'] == 1
```

Expand Down Expand Up @@ -125,21 +125,21 @@ filter_kwargs = {'pk': 'bb'}
Call endpoints with pagination:
```python
@pytest.mark.action_api(view_set_class=DummyAPIViewSet)
def test_pagination_data(db, action_api):
@pytest.mark.api_action(view_set_class=DummyAPIViewSet)
def test_pagination_data(db, api_action):
for i in range(1, 3):
dummy_model = DummyModel()
dummy_model.dummy_int = 1
dummy_model.save()

response = action_api.by_dummy_int(dummy_int=1, page=1)
response = api_action.by_dummy_int(dummy_int=1, page=1)

obj = response['results'][0]
assert obj['dummy_int'] == 1

assert extract_page_number(response['next']) == 2

response = action_api.by_dummy_int(dummy_int=1, page=2)
response = api_action.by_dummy_int(dummy_int=1, page=2)
assert extract_page_number(response['previous']) == 1
assert extract_page_number(response['next']) is None

Expand Down
10 changes: 6 additions & 4 deletions drf_api_action/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,30 @@

def run_function(self, func):
def api_item(*args, **kwargs):
# here we retrieve serializer_class from @action decorator in order to inject it to the ViewSet
serializer_class = func.kwargs['serializer_class']
return run_as_api(self, func, serializer_class, *args, **kwargs)

return api_item


@pytest.fixture
def action_api(request):
def api_action(request):
"""
Make Dango WebView endpoints accessible
"""
from drf_api_action.mixins import APIRestMixin # pylint: disable=import-outside-toplevel

if request.keywords['action_api'].kwargs.get("view_set_class") is None:
raise ActionsAPIException('using action_api fixture must require a view_set_class kwarg')
if request.keywords['api_action'].kwargs.get("view_set_class") is None:
raise ActionsAPIException('using api_action fixture must require a view_set_class kwarg')

view_set_class = request.keywords['action_api'].kwargs["view_set_class"]
view_set_class = request.keywords['api_action'].kwargs["view_set_class"]

class WrapperApiClass(APIRestMixin, view_set_class):
def __getattribute__(self, item):
class_attribute = super().__getattribute__(item)

# running our logic on endpoints only
if callable(class_attribute) and hasattr(class_attribute, 'detail'):
return run_function(self, class_attribute)

Expand Down
22 changes: 12 additions & 10 deletions drf_api_action/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,30 @@ def __init__(self, data, query_params):
self.query_params = query_params

def build_absolute_uri(self, _=None):
"""
mocking django/http/request.py::HTTPRequest::build_absolute_uri
It's irrelevant since we do not provide any web resource
"""
return ''


def run_as_api(self, func, serializer_class, *args, **kw):
kw.update({"serializer_class": serializer_class})
# adding to the view the request & kwargs including the serializer class
kw.update({"serializer_class": serializer_class}) # adding serializer class from @action
# decorator into our instance
request = CustomRequest(kw, kw)
self.kwargs = kw
self.request = request
self.kwargs = kw # adding our enhanced kwargs into instance kwargs
self.request = request # mocking request with our arguments as data in the instance

try:
if hasattr(func, 'detail'):
# called from pytest fixture
ret = func(request, **kw)
else:
# called straight from viewer
ret = func(self, request, **kw)
ret = func(request, **kw)
if isinstance(ret.data, list): # multiple results
results = [dict(res) for res in ret.data]
else:
else: # only one json
results = {k.lower(): v for k, v in ret.data.items()}
except Exception as error: # pylint: disable=broad-except
error_type = type(error)
# re-constructing the error with the actual traceback
raised_exception = ActionsAPIExceptionMiddleware(*error.args,
error_type=error_type,
traceback=error.__traceback__) # fixing stack frames
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "drf_api_action"
version = "1.2.0"
version = "1.2.1"
description = "drf-api-action elevates DRF testing by simplifying REST endpoint testing to a seamless, function-like experience."
readme = "README.md"
authors = ["Ori Roza <[email protected]>"]
Expand Down Expand Up @@ -41,7 +41,7 @@ classifiers = [
DJANGO_SETTINGS_MODULE = "tests.test_server.settings"
python_files = "tests.py test_*.py *_tests.py"
markers = [
"action_api: Viewset"
"api_action: Viewset"
]
testpaths = ["tests"]

Expand Down
26 changes: 13 additions & 13 deletions tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,46 +6,46 @@
from tests.test_server.test_app.views import DummyViewSetFixture, DummyAPIViewSet


@pytest.mark.action_api(view_set_class=DummyViewSetFixture)
def test_call_as_api_fixture(db, action_api):
@pytest.mark.api_action(view_set_class=DummyViewSetFixture)
def test_call_as_api_fixture(db, api_action):
dummy_model = DummyModel()
dummy_model.dummy_int = 1
dummy_model.save()
res = action_api.api_dummy(pk=1)
res = api_action.api_dummy(pk=1)
assert res["dummy_int"] == 1


@pytest.mark.action_api(view_set_class=DummyAPIViewSet)
def test_pagination(db, action_api):
@pytest.mark.api_action(view_set_class=DummyAPIViewSet)
def test_pagination(db, api_action):
dummy_model = DummyModel()
dummy_model.dummy_int = 1
dummy_model.save()
obj = action_api.by_dummy_int(dummy_int=1)["results"][0]
obj = api_action.by_dummy_int(dummy_int=1)["results"][0]
assert obj['dummy_int'] == 1


@pytest.mark.action_api(view_set_class=DummyAPIViewSet)
def test_pagination_data(db, action_api):
@pytest.mark.api_action(view_set_class=DummyAPIViewSet)
def test_pagination_data(db, api_action):
for i in range(1, 3):
dummy_model = DummyModel()
dummy_model.dummy_int = 1
dummy_model.save()

response = action_api.by_dummy_int(dummy_int=1)
response = api_action.by_dummy_int(dummy_int=1)
assert extract_page_number(response['next']) == 2

obj = response['results'][0]
assert obj['dummy_int'] == 1

response = action_api.by_dummy_int(dummy_int=1, page=2)
response = api_action.by_dummy_int(dummy_int=1, page=2)
assert extract_page_number(response['previous']) == 1
assert extract_page_number(response['next']) is None


@pytest.mark.action_api(view_set_class=DummyAPIViewSet)
def test_exceptions(db, action_api):
@pytest.mark.api_action(view_set_class=DummyAPIViewSet)
def test_exceptions(db, api_action):
dummy_model = DummyModel()
dummy_model.dummy_int = 1
dummy_model.save()
with pytest.raises(ValidationError):
_ = action_api.by_dummy_int(dummy_int=-1)
_ = api_action.by_dummy_int(dummy_int=-1)

0 comments on commit ccda5f9

Please sign in to comment.