124 lines
3.6 KiB
Python
124 lines
3.6 KiB
Python
from types import SimpleNamespace
|
|
|
|
from rest_framework_json_api import serializers
|
|
|
|
from enhanced_drf_jsonapi.api import (
|
|
HardenedGenericAPIView,
|
|
HardenedModelViewSet,
|
|
PreloadIncludesMixin,
|
|
ReasonableModelSerializer,
|
|
ReasonableModelViewSet,
|
|
)
|
|
from enhanced_drf_jsonapi.exceptions import HardenedExceptionHandlingMixin
|
|
|
|
|
|
class QuerySetSpy:
|
|
def __init__(self):
|
|
self.select_calls = []
|
|
self.prefetch_calls = []
|
|
|
|
def select_related(self, *fields):
|
|
self.select_calls.append(fields)
|
|
return self
|
|
|
|
def prefetch_related(self, *fields):
|
|
self.prefetch_calls.append(fields)
|
|
return self
|
|
|
|
|
|
class QuerySetProvider:
|
|
queryset_spy = None
|
|
|
|
def get_queryset(self, *args, **kwargs):
|
|
return self.queryset_spy
|
|
|
|
|
|
class IncludeView(PreloadIncludesMixin, QuerySetProvider):
|
|
select_for_includes = {
|
|
"__all__": ["owner"],
|
|
"author": ["author", "author__profile"],
|
|
}
|
|
prefetch_for_includes = {
|
|
"author": ["author__books"],
|
|
"comments": lambda view: [f"comments_for_{view.request.marker}"],
|
|
}
|
|
|
|
|
|
def test_preload_includes_applies_requested_and_unconditional_rules(monkeypatch):
|
|
queryset = QuerySetSpy()
|
|
view = IncludeView()
|
|
view.queryset_spy = queryset
|
|
view.request = SimpleNamespace(marker="request")
|
|
monkeypatch.setattr(
|
|
"enhanced_drf_jsonapi.api.get_included_resources",
|
|
lambda request: ["author", "comments"],
|
|
)
|
|
|
|
assert view.get_queryset() is queryset
|
|
assert queryset.select_calls == [("author", "author__profile"), ("owner",)]
|
|
assert queryset.prefetch_calls == [
|
|
("author__books",),
|
|
("comments_for_request",),
|
|
]
|
|
|
|
|
|
def serializer_fields(monkeypatch, method, *, include="", data=None, configured=True):
|
|
monkeypatch.setattr(
|
|
serializers.ModelSerializer,
|
|
"get_field_names",
|
|
lambda self, declared_fields, info: ["id", "name"],
|
|
)
|
|
request = SimpleNamespace(
|
|
method=method,
|
|
query_params={"include": include} if include else {},
|
|
data=data or {},
|
|
)
|
|
serializer = ReasonableModelSerializer(context={"request": request})
|
|
if configured:
|
|
serializer.included_serializers = {"owner": object, "comments": object}
|
|
return serializer.get_field_names({}, None)
|
|
|
|
|
|
def test_serializer_adds_requested_get_relationships_without_duplicates(monkeypatch):
|
|
fields = serializer_fields(monkeypatch, "GET", include="owner,comments,missing,owner")
|
|
assert fields == ["id", "name", "owner", "comments"]
|
|
|
|
|
|
def test_serializer_adds_relationships_from_post_and_patch_payloads(monkeypatch):
|
|
assert serializer_fields(monkeypatch, "POST", data={"owner": {}, "other": 1}) == [
|
|
"id",
|
|
"name",
|
|
"owner",
|
|
]
|
|
assert serializer_fields(monkeypatch, "PATCH", data={"comments": []}) == [
|
|
"id",
|
|
"name",
|
|
"comments",
|
|
]
|
|
|
|
|
|
def test_serializer_is_safe_without_request_or_included_serializers(monkeypatch):
|
|
monkeypatch.setattr(
|
|
serializers.ModelSerializer,
|
|
"get_field_names",
|
|
lambda self, declared_fields, info: ["id"],
|
|
)
|
|
assert ReasonableModelSerializer().get_field_names({}, None) == ["id"]
|
|
assert serializer_fields(monkeypatch, "GET", include="owner", configured=False) == [
|
|
"id",
|
|
"name",
|
|
]
|
|
|
|
|
|
def test_view_classes_preserve_methods_and_offer_opt_in_hardening():
|
|
assert ReasonableModelViewSet.http_method_names == [
|
|
"get",
|
|
"post",
|
|
"patch",
|
|
"delete",
|
|
"head",
|
|
"options",
|
|
]
|
|
assert issubclass(HardenedModelViewSet, HardenedExceptionHandlingMixin)
|
|
assert issubclass(HardenedGenericAPIView, HardenedExceptionHandlingMixin)
|