diff --git a/netbox/netbox/graphql/filter_mixins.py b/netbox/netbox/graphql/filter_mixins.py index 3a24170e3..b815e59a8 100644 --- a/netbox/netbox/graphql/filter_mixins.py +++ b/netbox/netbox/graphql/filter_mixins.py @@ -4,7 +4,7 @@ from typing import List import django_filters import strawberry import strawberry_django -from django.core.exceptions import FieldDoesNotExist +from django.core.exceptions import FieldDoesNotExist, ValidationError from strawberry import auto from ipam.fields import ASNField from netbox.graphql.scalars import BigInt @@ -205,5 +205,7 @@ class BaseFilterMixin: if not filterset.is_valid(): # filterset.errors is errorDict - return first error as exception k, v = next(iter(filterset.errors.items())) - raise Exception(f"{k}: {v[0]}") + return filterset.qs.none() + # We could raise validation error but strawberry logs it all to the + # console i.e. raise ValidationError(f"{k}: {v[0]}") return filterset.qs diff --git a/netbox/netbox/tests/test_graphql.py b/netbox/netbox/tests/test_graphql.py index 2cf9ee87b..256a4399d 100644 --- a/netbox/netbox/tests/test_graphql.py +++ b/netbox/netbox/tests/test_graphql.py @@ -1,7 +1,13 @@ +import json + from django.test import override_settings from django.urls import reverse -from utilities.testing import disable_warnings, TestCase +from core.models import ObjectType +from rest_framework import status +from users.models import ObjectPermission, Token +from users.models import User +from utilities.testing import disable_warnings, APITestCase, TestCase class GraphQLTestCase(TestCase): @@ -34,3 +40,40 @@ class GraphQLTestCase(TestCase): response = self.client.get(url, **header) with disable_warnings('django.request'): self.assertHttpStatus(response, 302) # Redirect to login page + + +class GraphQLAPITestCase(APITestCase): + + @override_settings(LOGIN_REQUIRED=True) + @override_settings(EXEMPT_VIEW_PERMISSIONS=['*', 'auth.user']) + def test_graphql_filter_objects(self): + from dcim.models import Site, Location + + site = Site.objects.create(name='Site A', slug='site-a') + location = Location.objects.create(site=site, name='Location A1', slug='location-a1') + + url = reverse('graphql') + field_name = 'location_list' + query = '{location_list(filters: {site_id: "' + str(site.id) + '"}) {id site {id}}}' + + # Add object-level permission + obj_perm = ObjectPermission( + name='Test permission', + actions=['view'] + ) + obj_perm.save() + obj_perm.users.add(self.user) + obj_perm.object_types.add(ObjectType.objects.get_for_model(Location)) + + response = self.client.post(url, data={'query': query}, format="json", **self.header) + self.assertHttpStatus(response, status.HTTP_200_OK) + data = json.loads(response.content) + self.assertNotIn('errors', data) + self.assertGreater(len(data['data']['location_list']), 0) + + query = '{location_list(filters: {site_id: "' + str(site.id + 1234) + '"}) {id site {id}}}' + response = self.client.post(url, data={'query': query}, format="json", **self.header) + + self.assertHttpStatus(response, status.HTTP_200_OK) + data = json.loads(response.content) + self.assertEqual(len(data['data']['location_list']), 0)