api.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. import inspect
  2. import json
  3. import strawberry_django
  4. from django.conf import settings
  5. from django.contrib.contenttypes.models import ContentType
  6. from django.test import override_settings
  7. from django.urls import reverse
  8. from rest_framework import status
  9. from rest_framework.test import APIClient
  10. from strawberry.types.base import StrawberryList, StrawberryOptional
  11. from strawberry.types.lazy_type import LazyType
  12. from strawberry.types.union import StrawberryUnion
  13. from core.choices import ObjectChangeActionChoices
  14. from core.models import ObjectChange, ObjectType
  15. from ipam.graphql.types import IPAddressFamilyType
  16. from users.models import ObjectPermission, Token, User
  17. from utilities.api import get_graphql_type_for_model
  18. from .base import ModelTestCase
  19. from .utils import disable_logging, disable_warnings
  20. __all__ = (
  21. 'APITestCase',
  22. 'APIViewTestCases',
  23. )
  24. #
  25. # REST/GraphQL API Tests
  26. #
  27. class APITestCase(ModelTestCase):
  28. """
  29. Base test case for API requests.
  30. client_class: Test client class
  31. view_namespace: Namespace for API views. If None, the model's app_label will be used.
  32. """
  33. client_class = APIClient
  34. view_namespace = None
  35. def setUp(self):
  36. """
  37. Create a user and token for API calls.
  38. """
  39. # Create the test user and assign permissions
  40. self.user = User.objects.create_user(username='testuser')
  41. self.add_permissions(*self.user_permissions)
  42. self.token = Token.objects.create(user=self.user)
  43. self.header = {'HTTP_AUTHORIZATION': f'Token {self.token.key}'}
  44. def _get_view_namespace(self):
  45. return f'{self.view_namespace or self.model._meta.app_label}-api'
  46. def _get_detail_url(self, instance):
  47. viewname = f'{self._get_view_namespace()}:{instance._meta.model_name}-detail'
  48. return reverse(viewname, kwargs={'pk': instance.pk})
  49. def _get_list_url(self):
  50. viewname = f'{self._get_view_namespace()}:{self.model._meta.model_name}-list'
  51. return reverse(viewname)
  52. class APIViewTestCases:
  53. class GetObjectViewTestCase(APITestCase):
  54. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'], LOGIN_REQUIRED=False)
  55. def test_get_object_anonymous(self):
  56. """
  57. GET a single object as an unauthenticated user.
  58. """
  59. url = self._get_detail_url(self._get_queryset().first())
  60. if (self.model._meta.app_label, self.model._meta.model_name) in settings.EXEMPT_EXCLUDE_MODELS:
  61. # Models listed in EXEMPT_EXCLUDE_MODELS should not be accessible to anonymous users
  62. with disable_warnings('django.request'):
  63. self.assertHttpStatus(self.client.get(url, **self.header), status.HTTP_403_FORBIDDEN)
  64. else:
  65. response = self.client.get(url, **self.header)
  66. self.assertHttpStatus(response, status.HTTP_200_OK)
  67. def test_get_object_without_permission(self):
  68. """
  69. GET a single object as an authenticated user without the required permission.
  70. """
  71. url = self._get_detail_url(self._get_queryset().first())
  72. # Try GET without permission
  73. with disable_warnings('django.request'):
  74. self.assertHttpStatus(self.client.get(url, **self.header), status.HTTP_403_FORBIDDEN)
  75. def test_get_object(self):
  76. """
  77. GET a single object as an authenticated user with permission to view the object.
  78. """
  79. self.assertGreaterEqual(self._get_queryset().count(), 2,
  80. f"Test requires the creation of at least two {self.model} instances")
  81. instance1, instance2 = self._get_queryset()[:2]
  82. # Add object-level permission
  83. obj_perm = ObjectPermission(
  84. name='Test permission',
  85. constraints={'pk': instance1.pk},
  86. actions=['view']
  87. )
  88. obj_perm.save()
  89. obj_perm.users.add(self.user)
  90. obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
  91. # Try GET to permitted object
  92. url = self._get_detail_url(instance1)
  93. self.assertHttpStatus(self.client.get(url, **self.header), status.HTTP_200_OK)
  94. # Try GET to non-permitted object
  95. url = self._get_detail_url(instance2)
  96. self.assertHttpStatus(self.client.get(url, **self.header), status.HTTP_404_NOT_FOUND)
  97. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  98. def test_options_object(self):
  99. """
  100. Make an OPTIONS request for a single object.
  101. """
  102. url = self._get_detail_url(self._get_queryset().first())
  103. response = self.client.options(url, **self.header)
  104. self.assertHttpStatus(response, status.HTTP_200_OK)
  105. class ListObjectsViewTestCase(APITestCase):
  106. brief_fields = []
  107. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'], LOGIN_REQUIRED=False)
  108. def test_list_objects_anonymous(self):
  109. """
  110. GET a list of objects as an unauthenticated user.
  111. """
  112. url = self._get_list_url()
  113. if (self.model._meta.app_label, self.model._meta.model_name) in settings.EXEMPT_EXCLUDE_MODELS:
  114. # Models listed in EXEMPT_EXCLUDE_MODELS should not be accessible to anonymous users
  115. with disable_warnings('django.request'):
  116. self.assertHttpStatus(self.client.get(url, **self.header), status.HTTP_403_FORBIDDEN)
  117. else:
  118. response = self.client.get(url, **self.header)
  119. self.assertHttpStatus(response, status.HTTP_200_OK)
  120. self.assertEqual(len(response.data['results']), self._get_queryset().count())
  121. def test_list_objects_brief(self):
  122. """
  123. GET a list of objects using the "brief" parameter.
  124. """
  125. self.add_permissions(f'{self.model._meta.app_label}.view_{self.model._meta.model_name}')
  126. url = f'{self._get_list_url()}?brief=1'
  127. response = self.client.get(url, **self.header)
  128. self.assertEqual(len(response.data['results']), self._get_queryset().count())
  129. self.assertEqual(sorted(response.data['results'][0]), self.brief_fields)
  130. def test_list_objects_without_permission(self):
  131. """
  132. GET a list of objects as an authenticated user without the required permission.
  133. """
  134. url = self._get_list_url()
  135. # Try GET without permission
  136. with disable_warnings('django.request'):
  137. self.assertHttpStatus(self.client.get(url, **self.header), status.HTTP_403_FORBIDDEN)
  138. def test_list_objects(self):
  139. """
  140. GET a list of objects as an authenticated user with permission to view the objects.
  141. """
  142. self.assertGreaterEqual(self._get_queryset().count(), 3,
  143. f"Test requires the creation of at least three {self.model} instances")
  144. instance1, instance2 = self._get_queryset()[:2]
  145. # Add object-level permission
  146. obj_perm = ObjectPermission(
  147. name='Test permission',
  148. constraints={'pk__in': [instance1.pk, instance2.pk]},
  149. actions=['view']
  150. )
  151. obj_perm.save()
  152. obj_perm.users.add(self.user)
  153. obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
  154. # Try GET to permitted objects
  155. response = self.client.get(self._get_list_url(), **self.header)
  156. self.assertHttpStatus(response, status.HTTP_200_OK)
  157. self.assertEqual(len(response.data['results']), 2)
  158. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  159. def test_options_objects(self):
  160. """
  161. Make an OPTIONS request for a list endpoint.
  162. """
  163. response = self.client.options(self._get_list_url(), **self.header)
  164. self.assertHttpStatus(response, status.HTTP_200_OK)
  165. class CreateObjectViewTestCase(APITestCase):
  166. create_data = []
  167. validation_excluded_fields = []
  168. def test_create_object_without_permission(self):
  169. """
  170. POST a single object without permission.
  171. """
  172. url = self._get_list_url()
  173. # Try POST without permission
  174. with disable_warnings('django.request'):
  175. response = self.client.post(url, self.create_data[0], format='json', **self.header)
  176. self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
  177. def test_create_object(self):
  178. """
  179. POST a single object with permission.
  180. """
  181. # Add object-level permission
  182. obj_perm = ObjectPermission(
  183. name='Test permission',
  184. actions=['add']
  185. )
  186. obj_perm.save()
  187. obj_perm.users.add(self.user)
  188. obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
  189. initial_count = self._get_queryset().count()
  190. response = self.client.post(self._get_list_url(), self.create_data[0], format='json', **self.header)
  191. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  192. self.assertEqual(self._get_queryset().count(), initial_count + 1)
  193. instance = self._get_queryset().get(pk=response.data['id'])
  194. self.assertInstanceEqual(
  195. instance,
  196. self.create_data[0],
  197. exclude=self.validation_excluded_fields,
  198. api=True
  199. )
  200. # Verify ObjectChange creation
  201. if hasattr(self.model, 'to_objectchange'):
  202. objectchanges = ObjectChange.objects.filter(
  203. changed_object_type=ContentType.objects.get_for_model(instance),
  204. changed_object_id=instance.pk
  205. )
  206. self.assertEqual(len(objectchanges), 1)
  207. self.assertEqual(objectchanges[0].action, ObjectChangeActionChoices.ACTION_CREATE)
  208. def test_bulk_create_objects(self):
  209. """
  210. POST a set of objects in a single request.
  211. """
  212. # Add object-level permission
  213. obj_perm = ObjectPermission(
  214. name='Test permission',
  215. actions=['add']
  216. )
  217. obj_perm.save()
  218. obj_perm.users.add(self.user)
  219. obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
  220. initial_count = self._get_queryset().count()
  221. response = self.client.post(self._get_list_url(), self.create_data, format='json', **self.header)
  222. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  223. self.assertEqual(len(response.data), len(self.create_data))
  224. self.assertEqual(self._get_queryset().count(), initial_count + len(self.create_data))
  225. for i, obj in enumerate(response.data):
  226. for field in self.create_data[i]:
  227. if field not in self.validation_excluded_fields:
  228. self.assertIn(field, obj, f"Bulk create field '{field}' missing from object {i} in response")
  229. for i, obj in enumerate(response.data):
  230. self.assertInstanceEqual(
  231. self._get_queryset().get(pk=obj['id']),
  232. self.create_data[i],
  233. exclude=self.validation_excluded_fields,
  234. api=True
  235. )
  236. class UpdateObjectViewTestCase(APITestCase):
  237. update_data = {}
  238. bulk_update_data = None
  239. validation_excluded_fields = []
  240. def test_update_object_without_permission(self):
  241. """
  242. PATCH a single object without permission.
  243. """
  244. url = self._get_detail_url(self._get_queryset().first())
  245. update_data = self.update_data or getattr(self, 'create_data')[0]
  246. # Try PATCH without permission
  247. with disable_warnings('django.request'):
  248. response = self.client.patch(url, update_data, format='json', **self.header)
  249. self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
  250. def test_update_object(self):
  251. """
  252. PATCH a single object identified by its numeric ID.
  253. """
  254. instance = self._get_queryset().first()
  255. url = self._get_detail_url(instance)
  256. update_data = self.update_data or getattr(self, 'create_data')[0]
  257. # Add object-level permission
  258. obj_perm = ObjectPermission(
  259. name='Test permission',
  260. actions=['change']
  261. )
  262. obj_perm.save()
  263. obj_perm.users.add(self.user)
  264. obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
  265. response = self.client.patch(url, update_data, format='json', **self.header)
  266. self.assertHttpStatus(response, status.HTTP_200_OK)
  267. instance.refresh_from_db()
  268. self.assertInstanceEqual(
  269. instance,
  270. update_data,
  271. exclude=self.validation_excluded_fields,
  272. api=True
  273. )
  274. # Verify ObjectChange creation
  275. if hasattr(self.model, 'to_objectchange'):
  276. objectchanges = ObjectChange.objects.filter(
  277. changed_object_type=ContentType.objects.get_for_model(instance),
  278. changed_object_id=instance.pk
  279. )
  280. self.assertEqual(len(objectchanges), 1)
  281. self.assertEqual(objectchanges[0].action, ObjectChangeActionChoices.ACTION_UPDATE)
  282. def test_bulk_update_objects(self):
  283. """
  284. PATCH a set of objects in a single request.
  285. """
  286. if self.bulk_update_data is None:
  287. self.skipTest("Bulk update data not set")
  288. # Add object-level permission
  289. obj_perm = ObjectPermission(
  290. name='Test permission',
  291. actions=['change']
  292. )
  293. obj_perm.save()
  294. obj_perm.users.add(self.user)
  295. obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
  296. id_list = list(self._get_queryset().values_list('id', flat=True)[:3])
  297. self.assertEqual(len(id_list), 3, "Insufficient number of objects to test bulk update")
  298. data = [
  299. {'id': id, **self.bulk_update_data} for id in id_list
  300. ]
  301. response = self.client.patch(self._get_list_url(), data, format='json', **self.header)
  302. self.assertHttpStatus(response, status.HTTP_200_OK)
  303. for i, obj in enumerate(response.data):
  304. for field in self.bulk_update_data:
  305. self.assertIn(field, obj, f"Bulk update field '{field}' missing from object {i} in response")
  306. for instance in self._get_queryset().filter(pk__in=id_list):
  307. self.assertInstanceEqual(instance, self.bulk_update_data, api=True)
  308. class DeleteObjectViewTestCase(APITestCase):
  309. def test_delete_object_without_permission(self):
  310. """
  311. DELETE a single object without permission.
  312. """
  313. url = self._get_detail_url(self._get_queryset().first())
  314. # Try DELETE without permission
  315. with disable_warnings('django.request'):
  316. response = self.client.delete(url, **self.header)
  317. self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
  318. def test_delete_object(self):
  319. """
  320. DELETE a single object identified by its numeric ID.
  321. """
  322. instance = self._get_queryset().first()
  323. url = self._get_detail_url(instance)
  324. # Add object-level permission
  325. obj_perm = ObjectPermission(
  326. name='Test permission',
  327. actions=['delete']
  328. )
  329. obj_perm.save()
  330. obj_perm.users.add(self.user)
  331. obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
  332. response = self.client.delete(url, **self.header)
  333. self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
  334. self.assertFalse(self._get_queryset().filter(pk=instance.pk).exists())
  335. # Verify ObjectChange creation
  336. if hasattr(self.model, 'to_objectchange'):
  337. objectchanges = ObjectChange.objects.filter(
  338. changed_object_type=ContentType.objects.get_for_model(instance),
  339. changed_object_id=instance.pk
  340. )
  341. self.assertEqual(len(objectchanges), 1)
  342. self.assertEqual(objectchanges[0].action, ObjectChangeActionChoices.ACTION_DELETE)
  343. def test_bulk_delete_objects(self):
  344. """
  345. DELETE a set of objects in a single request.
  346. """
  347. # Add object-level permission
  348. obj_perm = ObjectPermission(
  349. name='Test permission',
  350. actions=['delete']
  351. )
  352. obj_perm.save()
  353. obj_perm.users.add(self.user)
  354. obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
  355. # Target the three most recently created objects to avoid triggering recursive deletions
  356. # (e.g. with MPTT objects)
  357. id_list = list(self._get_queryset().order_by('-id').values_list('id', flat=True)[:3])
  358. self.assertEqual(len(id_list), 3, "Insufficient number of objects to test bulk deletion")
  359. data = [{"id": id} for id in id_list]
  360. initial_count = self._get_queryset().count()
  361. response = self.client.delete(self._get_list_url(), data, format='json', **self.header)
  362. self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
  363. self.assertEqual(self._get_queryset().count(), initial_count - 3)
  364. class GraphQLTestCase(APITestCase):
  365. def _get_graphql_base_name(self):
  366. """
  367. Return graphql_base_name, if set. Otherwise, construct the base name for the query
  368. field from the model's verbose name.
  369. """
  370. base_name = self.model._meta.verbose_name.lower().replace(' ', '_')
  371. return getattr(self, 'graphql_base_name', base_name)
  372. def _build_query_with_filter(self, name, filter_string):
  373. """
  374. Called by either _build_query or _build_filtered_query - construct the actual
  375. query given a name and filter string
  376. """
  377. type_class = get_graphql_type_for_model(self.model)
  378. # Compile list of fields to include
  379. fields_string = ''
  380. file_fields = (strawberry_django.fields.types.DjangoFileType, strawberry_django.fields.types.DjangoImageType)
  381. for field in type_class.__strawberry_definition__.fields:
  382. if (
  383. field.type in file_fields or (
  384. type(field.type) is StrawberryOptional and field.type.of_type in file_fields
  385. )
  386. ):
  387. # image / file fields nullable or not...
  388. fields_string += f'{field.name} {{ name }}\n'
  389. elif type(field.type) is StrawberryList and type(field.type.of_type) is LazyType:
  390. # List of related objects (queryset)
  391. fields_string += f'{field.name} {{ id }}\n'
  392. elif type(field.type) is StrawberryList and type(field.type.of_type) is StrawberryUnion:
  393. # this would require a fragment query
  394. continue
  395. elif type(field.type) is StrawberryUnion:
  396. # this would require a fragment query
  397. continue
  398. elif type(field.type) is StrawberryOptional and type(field.type.of_type) is StrawberryUnion:
  399. # this would require a fragment query
  400. continue
  401. elif type(field.type) is StrawberryOptional and type(field.type.of_type) is LazyType:
  402. fields_string += f'{field.name} {{ id }}\n'
  403. elif hasattr(field, 'is_relation') and field.is_relation:
  404. # Note: StrawberryField types do not have is_relation
  405. fields_string += f'{field.name} {{ id }}\n'
  406. elif inspect.isclass(field.type) and issubclass(field.type, IPAddressFamilyType):
  407. fields_string += f'{field.name} {{ value, label }}\n'
  408. else:
  409. fields_string += f'{field.name}\n'
  410. query = f"""
  411. {{
  412. {name}{filter_string} {{
  413. {fields_string}
  414. }}
  415. }}
  416. """
  417. return query
  418. def _build_filtered_query(self, name, **filters):
  419. """
  420. Create a filtered query: i.e. device_list(filters: {name: {i_contains: "akron"}}){.
  421. """
  422. # TODO: This should be extended to support AND, OR multi-lookups
  423. if filters:
  424. for field_name, params in filters.items():
  425. lookup = params['lookup']
  426. value = params['value']
  427. if lookup:
  428. query = f'{{{lookup}: "{value}"}}'
  429. filter_string = f'{field_name}: {query}'
  430. else:
  431. filter_string = f'{field_name}: "{value}"'
  432. filter_string = f'(filters: {{{filter_string}}})'
  433. else:
  434. filter_string = ''
  435. return self._build_query_with_filter(name, filter_string)
  436. def _build_query(self, name, **filters):
  437. """
  438. Create a normal query - unfiltered or with a string query: i.e. site(name: "aaa"){.
  439. """
  440. if filters:
  441. filter_string = ', '.join(f'{k}:{v}' for k, v in filters.items())
  442. filter_string = f'({filter_string})'
  443. else:
  444. filter_string = ''
  445. return self._build_query_with_filter(name, filter_string)
  446. @override_settings(LOGIN_REQUIRED=True)
  447. def test_graphql_get_object(self):
  448. url = reverse('graphql')
  449. field_name = self._get_graphql_base_name()
  450. object_id = self._get_queryset().first().pk
  451. query = self._build_query(field_name, id=object_id)
  452. # Non-authenticated requests should fail
  453. header = {
  454. 'HTTP_ACCEPT': 'application/json',
  455. }
  456. with disable_warnings('django.request'):
  457. response = self.client.post(url, data={'query': query}, format="json", **header)
  458. self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
  459. # Add constrained permission
  460. obj_perm = ObjectPermission(
  461. name='Test permission',
  462. actions=['view'],
  463. constraints={'id': 0} # Impossible constraint
  464. )
  465. obj_perm.save()
  466. obj_perm.users.add(self.user)
  467. obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
  468. # Request should succeed but return empty result
  469. with disable_logging():
  470. response = self.client.post(url, data={'query': query}, format="json", **self.header)
  471. self.assertHttpStatus(response, status.HTTP_200_OK)
  472. data = json.loads(response.content)
  473. self.assertIn('errors', data)
  474. self.assertIsNone(data['data'])
  475. # Remove permission constraint
  476. obj_perm.constraints = None
  477. obj_perm.save()
  478. # Request should return requested object
  479. response = self.client.post(url, data={'query': query}, format="json", **self.header)
  480. self.assertHttpStatus(response, status.HTTP_200_OK)
  481. data = json.loads(response.content)
  482. self.assertNotIn('errors', data)
  483. self.assertIsNotNone(data['data'])
  484. @override_settings(LOGIN_REQUIRED=True)
  485. def test_graphql_list_objects(self):
  486. url = reverse('graphql')
  487. field_name = f'{self._get_graphql_base_name()}_list'
  488. query = self._build_query(field_name)
  489. # Non-authenticated requests should fail
  490. header = {
  491. 'HTTP_ACCEPT': 'application/json',
  492. }
  493. with disable_warnings('django.request'):
  494. response = self.client.post(url, data={'query': query}, format="json", **header)
  495. self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
  496. # Add constrained permission
  497. obj_perm = ObjectPermission(
  498. name='Test permission',
  499. actions=['view'],
  500. constraints={'id': 0} # Impossible constraint
  501. )
  502. obj_perm.save()
  503. obj_perm.users.add(self.user)
  504. obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
  505. # Request should succeed but return empty results list
  506. response = self.client.post(url, data={'query': query}, format="json", **self.header)
  507. self.assertHttpStatus(response, status.HTTP_200_OK)
  508. data = json.loads(response.content)
  509. self.assertNotIn('errors', data)
  510. self.assertEqual(len(data['data'][field_name]), 0)
  511. # Remove permission constraint
  512. obj_perm.constraints = None
  513. obj_perm.save()
  514. # Request should return all objects
  515. response = self.client.post(url, data={'query': query}, format="json", **self.header)
  516. self.assertHttpStatus(response, status.HTTP_200_OK)
  517. data = json.loads(response.content)
  518. self.assertNotIn('errors', data)
  519. self.assertEqual(len(data['data'][field_name]), self.model.objects.count())
  520. @override_settings(LOGIN_REQUIRED=True)
  521. def test_graphql_filter_objects(self):
  522. if not hasattr(self, 'graphql_filter'):
  523. return
  524. url = reverse('graphql')
  525. field_name = f'{self._get_graphql_base_name()}_list'
  526. query = self._build_filtered_query(field_name, **self.graphql_filter)
  527. # Add object-level permission
  528. obj_perm = ObjectPermission(
  529. name='Test permission',
  530. actions=['view']
  531. )
  532. obj_perm.save()
  533. obj_perm.users.add(self.user)
  534. obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
  535. response = self.client.post(url, data={'query': query}, format="json", **self.header)
  536. self.assertHttpStatus(response, status.HTTP_200_OK)
  537. data = json.loads(response.content)
  538. self.assertNotIn('errors', data)
  539. self.assertGreater(len(data['data'][field_name]), 0)
  540. class APIViewTestCase(
  541. GetObjectViewTestCase,
  542. ListObjectsViewTestCase,
  543. CreateObjectViewTestCase,
  544. UpdateObjectViewTestCase,
  545. DeleteObjectViewTestCase,
  546. GraphQLTestCase
  547. ):
  548. pass