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)