test_api.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. from django.test import Client, TestCase, override_settings
  2. from django.urls import reverse
  3. from drf_spectacular.drainage import GENERATOR_STATS
  4. from rest_framework import status
  5. from core.models import ObjectType
  6. from dcim.models import Region, Site
  7. from extras.choices import CustomFieldTypeChoices
  8. from extras.models import CustomField
  9. from ipam.models import VLAN
  10. from netbox.config import get_config
  11. from utilities.testing import APITestCase, disable_warnings
  12. class WritableNestedSerializerTest(APITestCase):
  13. """
  14. Test the operation of WritableNestedSerializer using VLANSerializer as our test subject.
  15. """
  16. def setUp(self):
  17. super().setUp()
  18. self.region_a = Region.objects.create(name='Region A', slug='region-a')
  19. self.site1 = Site.objects.create(region=self.region_a, name='Site 1', slug='site-1')
  20. self.site2 = Site.objects.create(region=self.region_a, name='Site 2', slug='site-2')
  21. def test_related_by_pk(self):
  22. data = {
  23. 'vid': 100,
  24. 'name': 'Test VLAN 100',
  25. 'site': self.site1.pk,
  26. }
  27. url = reverse('ipam-api:vlan-list')
  28. self.add_permissions('ipam.add_vlan')
  29. response = self.client.post(url, data, format='json', **self.header)
  30. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  31. self.assertEqual(response.data['site']['id'], self.site1.pk)
  32. vlan = VLAN.objects.get(pk=response.data['id'])
  33. self.assertEqual(vlan.site, self.site1)
  34. def test_related_by_pk_no_match(self):
  35. data = {
  36. 'vid': 100,
  37. 'name': 'Test VLAN 100',
  38. 'site': 999,
  39. }
  40. url = reverse('ipam-api:vlan-list')
  41. self.add_permissions('ipam.add_vlan')
  42. with disable_warnings('django.request'):
  43. response = self.client.post(url, data, format='json', **self.header)
  44. self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
  45. self.assertEqual(VLAN.objects.count(), 0)
  46. self.assertTrue(response.data['site'][0].startswith("Related object not found"))
  47. def test_related_by_attributes(self):
  48. data = {
  49. 'vid': 100,
  50. 'name': 'Test VLAN 100',
  51. 'site': {
  52. 'name': 'Site 1'
  53. },
  54. }
  55. url = reverse('ipam-api:vlan-list')
  56. self.add_permissions('ipam.add_vlan')
  57. response = self.client.post(url, data, format='json', **self.header)
  58. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  59. self.assertEqual(response.data['site']['id'], self.site1.pk)
  60. vlan = VLAN.objects.get(pk=response.data['id'])
  61. self.assertEqual(vlan.site, self.site1)
  62. def test_related_by_attributes_no_match(self):
  63. data = {
  64. 'vid': 100,
  65. 'name': 'Test VLAN 100',
  66. 'site': {
  67. 'name': 'Site X'
  68. },
  69. }
  70. url = reverse('ipam-api:vlan-list')
  71. self.add_permissions('ipam.add_vlan')
  72. with disable_warnings('django.request'):
  73. response = self.client.post(url, data, format='json', **self.header)
  74. self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
  75. self.assertEqual(VLAN.objects.count(), 0)
  76. self.assertTrue(response.data['site'][0].startswith("Related object not found"))
  77. def test_related_by_attributes_multiple_matches(self):
  78. data = {
  79. 'vid': 100,
  80. 'name': 'Test VLAN 100',
  81. 'site': {
  82. 'region': {
  83. "name": "Region A",
  84. },
  85. },
  86. }
  87. url = reverse('ipam-api:vlan-list')
  88. self.add_permissions('ipam.add_vlan')
  89. with disable_warnings('django.request'):
  90. response = self.client.post(url, data, format='json', **self.header)
  91. self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
  92. self.assertEqual(VLAN.objects.count(), 0)
  93. self.assertTrue(response.data['site'][0].startswith("Multiple objects match"))
  94. def test_related_by_invalid(self):
  95. data = {
  96. 'vid': 100,
  97. 'name': 'Test VLAN 100',
  98. 'site': 'XXX',
  99. }
  100. url = reverse('ipam-api:vlan-list')
  101. self.add_permissions('ipam.add_vlan')
  102. with disable_warnings('django.request'):
  103. response = self.client.post(url, data, format='json', **self.header)
  104. self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
  105. self.assertEqual(VLAN.objects.count(), 0)
  106. class APIPaginationTestCase(APITestCase):
  107. user_permissions = ('dcim.view_site',)
  108. @classmethod
  109. def setUpTestData(cls):
  110. cls.url = reverse('dcim-api:site-list')
  111. # Create a large number of Sites for testing
  112. Site.objects.bulk_create([
  113. Site(name=f'Site {i}', slug=f'site-{i}') for i in range(1, 101)
  114. ])
  115. def test_default_page_size(self):
  116. response = self.client.get(self.url, format='json', **self.header)
  117. page_size = get_config().PAGINATE_COUNT
  118. self.assertLess(page_size, 100, "Default page size not sufficient for data set")
  119. self.assertHttpStatus(response, status.HTTP_200_OK)
  120. self.assertEqual(response.data['count'], 100)
  121. self.assertTrue(response.data['next'].endswith(f'?limit={page_size}&offset={page_size}'))
  122. self.assertIsNone(response.data['previous'])
  123. self.assertEqual(len(response.data['results']), page_size)
  124. @override_settings(MAX_PAGE_SIZE=30)
  125. def test_default_page_size_with_small_max_page_size(self):
  126. response = self.client.get(self.url, format='json', **self.header)
  127. page_size = get_config().MAX_PAGE_SIZE
  128. self.assertLess(page_size, 100, "Default page size not sufficient for data set")
  129. self.assertHttpStatus(response, status.HTTP_200_OK)
  130. self.assertEqual(response.data['count'], 100)
  131. self.assertTrue(response.data['next'].endswith(f'?limit={page_size}&offset={page_size}'))
  132. self.assertIsNone(response.data['previous'])
  133. self.assertEqual(len(response.data['results']), page_size)
  134. def test_custom_page_size(self):
  135. response = self.client.get(f'{self.url}?limit=10', format='json', **self.header)
  136. self.assertHttpStatus(response, status.HTTP_200_OK)
  137. self.assertEqual(response.data['count'], 100)
  138. self.assertTrue(response.data['next'].endswith('?limit=10&offset=10'))
  139. self.assertIsNone(response.data['previous'])
  140. self.assertEqual(len(response.data['results']), 10)
  141. @override_settings(MAX_PAGE_SIZE=80)
  142. def test_max_page_size(self):
  143. response = self.client.get(f'{self.url}?limit=0', format='json', **self.header)
  144. self.assertHttpStatus(response, status.HTTP_200_OK)
  145. self.assertEqual(response.data['count'], 100)
  146. self.assertTrue(response.data['next'].endswith('?limit=80&offset=80'))
  147. self.assertIsNone(response.data['previous'])
  148. self.assertEqual(len(response.data['results']), 80)
  149. @override_settings(MAX_PAGE_SIZE=0)
  150. def test_max_page_size_disabled(self):
  151. response = self.client.get(f'{self.url}?limit=0', format='json', **self.header)
  152. self.assertHttpStatus(response, status.HTTP_200_OK)
  153. self.assertEqual(response.data['count'], 100)
  154. self.assertIsNone(response.data['next'])
  155. self.assertIsNone(response.data['previous'])
  156. self.assertEqual(len(response.data['results']), 100)
  157. class APIOrderingTestCase(APITestCase):
  158. user_permissions = ('dcim.view_site',)
  159. @classmethod
  160. def setUpTestData(cls):
  161. cls.url = reverse('dcim-api:site-list')
  162. sites = (
  163. Site(name='Site 1', slug='site-1', facility='C', description='Z'),
  164. Site(name='Site 2', slug='site-2', facility='C', description='Y'),
  165. Site(name='Site 3', slug='site-3', facility='B', description='X'),
  166. Site(name='Site 4', slug='site-4', facility='B', description='W'),
  167. Site(name='Site 5', slug='site-5', facility='A', description='V'),
  168. Site(name='Site 6', slug='site-6', facility='A', description='U'),
  169. )
  170. Site.objects.bulk_create(sites)
  171. def test_default_order(self):
  172. response = self.client.get(self.url, format='json', **self.header)
  173. self.assertHttpStatus(response, status.HTTP_200_OK)
  174. self.assertEqual(response.data['count'], 6)
  175. self.assertListEqual(
  176. [s['name'] for s in response.data['results']],
  177. ['Site 1', 'Site 2', 'Site 3', 'Site 4', 'Site 5', 'Site 6']
  178. )
  179. def test_order_single_field(self):
  180. response = self.client.get(f'{self.url}?ordering=description', format='json', **self.header)
  181. self.assertHttpStatus(response, status.HTTP_200_OK)
  182. self.assertEqual(response.data['count'], 6)
  183. self.assertListEqual(
  184. [s['name'] for s in response.data['results']],
  185. ['Site 6', 'Site 5', 'Site 4', 'Site 3', 'Site 2', 'Site 1']
  186. )
  187. def test_order_reversed(self):
  188. response = self.client.get(f'{self.url}?ordering=-name', format='json', **self.header)
  189. self.assertHttpStatus(response, status.HTTP_200_OK)
  190. self.assertEqual(response.data['count'], 6)
  191. self.assertListEqual(
  192. [s['name'] for s in response.data['results']],
  193. ['Site 6', 'Site 5', 'Site 4', 'Site 3', 'Site 2', 'Site 1']
  194. )
  195. def test_order_multiple_fields(self):
  196. response = self.client.get(f'{self.url}?ordering=facility,name', format='json', **self.header)
  197. self.assertHttpStatus(response, status.HTTP_200_OK)
  198. self.assertEqual(response.data['count'], 6)
  199. self.assertListEqual(
  200. [s['name'] for s in response.data['results']],
  201. ['Site 5', 'Site 6', 'Site 3', 'Site 4', 'Site 1', 'Site 2']
  202. )
  203. class APIDocsTestCase(TestCase):
  204. def setUp(self):
  205. self.client = Client()
  206. # Populate a CustomField to activate CustomFieldSerializer
  207. object_type = ObjectType.objects.get_for_model(Site)
  208. self.cf_text = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='test')
  209. self.cf_text.save()
  210. self.cf_text.object_types.set([object_type])
  211. self.cf_text.save()
  212. def test_api_docs(self):
  213. url = reverse('api_docs')
  214. response = self.client.get(url)
  215. self.assertEqual(response.status_code, 200)
  216. url = reverse('schema')
  217. with GENERATOR_STATS.silence(): # Suppress schema generator warnings
  218. response = self.client.get(url)
  219. self.assertEqual(response.status_code, 200)