test_api.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. import datetime
  2. from django.contrib.contenttypes.models import ContentType
  3. from django.urls import reverse
  4. from django.utils import timezone
  5. from rest_framework import status
  6. from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Rack, RackGroup, RackRole, Site
  7. from extras.api.views import ReportViewSet, ScriptViewSet
  8. from extras.models import ConfigContext, Graph, ExportTemplate, Tag
  9. from extras.reports import Report
  10. from extras.scripts import BooleanVar, IntegerVar, Script, StringVar
  11. from utilities.testing import APITestCase, APIViewTestCases
  12. class AppTest(APITestCase):
  13. def test_root(self):
  14. url = reverse('extras-api:api-root')
  15. response = self.client.get('{}?format=api'.format(url), **self.header)
  16. self.assertEqual(response.status_code, 200)
  17. class GraphTest(APIViewTestCases.APIViewTestCase):
  18. model = Graph
  19. brief_fields = ['id', 'name', 'url']
  20. create_data = [
  21. {
  22. 'type': 'dcim.site',
  23. 'name': 'Graph 4',
  24. 'source': 'http://example.com/graphs.py?site={{ obj.name }}&foo=4',
  25. },
  26. {
  27. 'type': 'dcim.site',
  28. 'name': 'Graph 5',
  29. 'source': 'http://example.com/graphs.py?site={{ obj.name }}&foo=5',
  30. },
  31. {
  32. 'type': 'dcim.site',
  33. 'name': 'Graph 6',
  34. 'source': 'http://example.com/graphs.py?site={{ obj.name }}&foo=6',
  35. },
  36. ]
  37. @classmethod
  38. def setUpTestData(cls):
  39. ct = ContentType.objects.get_for_model(Site)
  40. graphs = (
  41. Graph(type=ct, name='Graph 1', source='http://example.com/graphs.py?site={{ obj.name }}&foo=1'),
  42. Graph(type=ct, name='Graph 2', source='http://example.com/graphs.py?site={{ obj.name }}&foo=2'),
  43. Graph(type=ct, name='Graph 3', source='http://example.com/graphs.py?site={{ obj.name }}&foo=3'),
  44. )
  45. Graph.objects.bulk_create(graphs)
  46. class ExportTemplateTest(APIViewTestCases.APIViewTestCase):
  47. model = ExportTemplate
  48. brief_fields = ['id', 'name', 'url']
  49. create_data = [
  50. {
  51. 'content_type': 'dcim.device',
  52. 'name': 'Test Export Template 4',
  53. 'template_code': '{% for obj in queryset %}{{ obj.name }}\n{% endfor %}',
  54. },
  55. {
  56. 'content_type': 'dcim.device',
  57. 'name': 'Test Export Template 5',
  58. 'template_code': '{% for obj in queryset %}{{ obj.name }}\n{% endfor %}',
  59. },
  60. {
  61. 'content_type': 'dcim.device',
  62. 'name': 'Test Export Template 6',
  63. 'template_code': '{% for obj in queryset %}{{ obj.name }}\n{% endfor %}',
  64. },
  65. ]
  66. @classmethod
  67. def setUpTestData(cls):
  68. ct = ContentType.objects.get_for_model(Device)
  69. export_templates = (
  70. ExportTemplate(
  71. content_type=ct,
  72. name='Export Template 1',
  73. template_code='{% for obj in queryset %}{{ obj.name }}\n{% endfor %}'
  74. ),
  75. ExportTemplate(
  76. content_type=ct,
  77. name='Export Template 2',
  78. template_code='{% for obj in queryset %}{{ obj.name }}\n{% endfor %}'
  79. ),
  80. ExportTemplate(
  81. content_type=ct,
  82. name='Export Template 3',
  83. template_code='{% for obj in queryset %}{{ obj.name }}\n{% endfor %}'
  84. ),
  85. )
  86. ExportTemplate.objects.bulk_create(export_templates)
  87. class TagTest(APIViewTestCases.APIViewTestCase):
  88. model = Tag
  89. brief_fields = ['color', 'id', 'name', 'slug', 'url']
  90. create_data = [
  91. {
  92. 'name': 'Tag 4',
  93. 'slug': 'tag-4',
  94. },
  95. {
  96. 'name': 'Tag 5',
  97. 'slug': 'tag-5',
  98. },
  99. {
  100. 'name': 'Tag 6',
  101. 'slug': 'tag-6',
  102. },
  103. ]
  104. @classmethod
  105. def setUpTestData(cls):
  106. tags = (
  107. Tag(name='Tag 1', slug='tag-1'),
  108. Tag(name='Tag 2', slug='tag-2'),
  109. Tag(name='Tag 3', slug='tag-3'),
  110. )
  111. Tag.objects.bulk_create(tags)
  112. class ConfigContextTest(APIViewTestCases.APIViewTestCase):
  113. model = ConfigContext
  114. brief_fields = ['id', 'name', 'url']
  115. create_data = [
  116. {
  117. 'name': 'Config Context 4',
  118. 'data': {'more_foo': True},
  119. },
  120. {
  121. 'name': 'Config Context 5',
  122. 'data': {'more_bar': False},
  123. },
  124. {
  125. 'name': 'Config Context 6',
  126. 'data': {'more_baz': None},
  127. },
  128. ]
  129. @classmethod
  130. def setUpTestData(cls):
  131. config_contexts = (
  132. ConfigContext(name='Config Context 1', weight=100, data={'foo': 123}),
  133. ConfigContext(name='Config Context 2', weight=200, data={'bar': 456}),
  134. ConfigContext(name='Config Context 3', weight=300, data={'baz': 789}),
  135. )
  136. ConfigContext.objects.bulk_create(config_contexts)
  137. def test_render_configcontext_for_object(self):
  138. """
  139. Test rendering config context data for a device.
  140. """
  141. manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
  142. devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
  143. devicerole = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
  144. site = Site.objects.create(name='Site-1', slug='site-1')
  145. device = Device.objects.create(name='Device 1', device_type=devicetype, device_role=devicerole, site=site)
  146. # Test default config contexts (created at test setup)
  147. rendered_context = device.get_config_context()
  148. self.assertEqual(rendered_context['foo'], 123)
  149. self.assertEqual(rendered_context['bar'], 456)
  150. self.assertEqual(rendered_context['baz'], 789)
  151. # Add another context specific to the site
  152. configcontext4 = ConfigContext(
  153. name='Config Context 4',
  154. data={'site_data': 'ABC'}
  155. )
  156. configcontext4.save()
  157. configcontext4.sites.add(site)
  158. rendered_context = device.get_config_context()
  159. self.assertEqual(rendered_context['site_data'], 'ABC')
  160. # Override one of the default contexts
  161. configcontext5 = ConfigContext(
  162. name='Config Context 5',
  163. weight=2000,
  164. data={'foo': 999}
  165. )
  166. configcontext5.save()
  167. configcontext5.sites.add(site)
  168. rendered_context = device.get_config_context()
  169. self.assertEqual(rendered_context['foo'], 999)
  170. # Add a context which does NOT match our device and ensure it does not apply
  171. site2 = Site.objects.create(name='Site 2', slug='site-2')
  172. configcontext6 = ConfigContext(
  173. name='Config Context 6',
  174. weight=2000,
  175. data={'bar': 999}
  176. )
  177. configcontext6.save()
  178. configcontext6.sites.add(site2)
  179. rendered_context = device.get_config_context()
  180. self.assertEqual(rendered_context['bar'], 456)
  181. class ReportTest(APITestCase):
  182. class TestReport(Report):
  183. def test_foo(self):
  184. self.log_success(None, "Report completed")
  185. def get_test_report(self, *args):
  186. return self.TestReport()
  187. def setUp(self):
  188. super().setUp()
  189. # Monkey-patch the API viewset's _get_script method to return our test script above
  190. ReportViewSet._retrieve_report = self.get_test_report
  191. def test_get_report(self):
  192. url = reverse('extras-api:report-detail', kwargs={'pk': None})
  193. response = self.client.get(url, **self.header)
  194. self.assertEqual(response.data['name'], self.TestReport.__name__)
  195. def test_run_report(self):
  196. self.add_permissions('extras.run_script')
  197. url = reverse('extras-api:report-run', kwargs={'pk': None})
  198. response = self.client.post(url, {}, format='json', **self.header)
  199. self.assertHttpStatus(response, status.HTTP_200_OK)
  200. self.assertEqual(response.data['result']['status']['value'], 'pending')
  201. class ScriptTest(APITestCase):
  202. class TestScript(Script):
  203. class Meta:
  204. name = "Test script"
  205. var1 = StringVar()
  206. var2 = IntegerVar()
  207. var3 = BooleanVar()
  208. def run(self, data, commit=True):
  209. self.log_info(data['var1'])
  210. self.log_success(data['var2'])
  211. self.log_failure(data['var3'])
  212. return 'Script complete'
  213. def get_test_script(self, *args):
  214. return self.TestScript
  215. def setUp(self):
  216. super().setUp()
  217. # Monkey-patch the API viewset's _get_script method to return our test script above
  218. ScriptViewSet._get_script = self.get_test_script
  219. def test_get_script(self):
  220. url = reverse('extras-api:script-detail', kwargs={'pk': None})
  221. response = self.client.get(url, **self.header)
  222. self.assertEqual(response.data['name'], self.TestScript.Meta.name)
  223. self.assertEqual(response.data['vars']['var1'], 'StringVar')
  224. self.assertEqual(response.data['vars']['var2'], 'IntegerVar')
  225. self.assertEqual(response.data['vars']['var3'], 'BooleanVar')
  226. def test_run_script(self):
  227. script_data = {
  228. 'var1': 'FooBar',
  229. 'var2': 123,
  230. 'var3': False,
  231. }
  232. data = {
  233. 'data': script_data,
  234. 'commit': True,
  235. }
  236. url = reverse('extras-api:script-detail', kwargs={'pk': None})
  237. response = self.client.post(url, data, format='json', **self.header)
  238. self.assertHttpStatus(response, status.HTTP_200_OK)
  239. self.assertEqual(response.data['result']['status']['value'], 'pending')
  240. class CreatedUpdatedFilterTest(APITestCase):
  241. def setUp(self):
  242. super().setUp()
  243. self.site1 = Site.objects.create(name='Test Site 1', slug='test-site-1')
  244. self.rackgroup1 = RackGroup.objects.create(site=self.site1, name='Test Rack Group 1', slug='test-rack-group-1')
  245. self.rackrole1 = RackRole.objects.create(name='Test Rack Role 1', slug='test-rack-role-1', color='ff0000')
  246. self.rack1 = Rack.objects.create(
  247. site=self.site1, group=self.rackgroup1, role=self.rackrole1, name='Test Rack 1', u_height=42,
  248. )
  249. self.rack2 = Rack.objects.create(
  250. site=self.site1, group=self.rackgroup1, role=self.rackrole1, name='Test Rack 2', u_height=42,
  251. )
  252. # change the created and last_updated of one
  253. Rack.objects.filter(pk=self.rack2.pk).update(
  254. last_updated=datetime.datetime(2001, 2, 3, 1, 2, 3, 4, tzinfo=timezone.utc),
  255. created=datetime.datetime(2001, 2, 3)
  256. )
  257. def test_get_rack_created(self):
  258. self.add_permissions('dcim.view_rack')
  259. url = reverse('dcim-api:rack-list')
  260. response = self.client.get('{}?created=2001-02-03'.format(url), **self.header)
  261. self.assertEqual(response.data['count'], 1)
  262. self.assertEqual(response.data['results'][0]['id'], self.rack2.pk)
  263. def test_get_rack_created_gte(self):
  264. self.add_permissions('dcim.view_rack')
  265. url = reverse('dcim-api:rack-list')
  266. response = self.client.get('{}?created__gte=2001-02-04'.format(url), **self.header)
  267. self.assertEqual(response.data['count'], 1)
  268. self.assertEqual(response.data['results'][0]['id'], self.rack1.pk)
  269. def test_get_rack_created_lte(self):
  270. self.add_permissions('dcim.view_rack')
  271. url = reverse('dcim-api:rack-list')
  272. response = self.client.get('{}?created__lte=2001-02-04'.format(url), **self.header)
  273. self.assertEqual(response.data['count'], 1)
  274. self.assertEqual(response.data['results'][0]['id'], self.rack2.pk)
  275. def test_get_rack_last_updated(self):
  276. self.add_permissions('dcim.view_rack')
  277. url = reverse('dcim-api:rack-list')
  278. response = self.client.get('{}?last_updated=2001-02-03%2001:02:03.000004'.format(url), **self.header)
  279. self.assertEqual(response.data['count'], 1)
  280. self.assertEqual(response.data['results'][0]['id'], self.rack2.pk)
  281. def test_get_rack_last_updated_gte(self):
  282. self.add_permissions('dcim.view_rack')
  283. url = reverse('dcim-api:rack-list')
  284. response = self.client.get('{}?last_updated__gte=2001-02-04%2001:02:03.000004'.format(url), **self.header)
  285. self.assertEqual(response.data['count'], 1)
  286. self.assertEqual(response.data['results'][0]['id'], self.rack1.pk)
  287. def test_get_rack_last_updated_lte(self):
  288. self.add_permissions('dcim.view_rack')
  289. url = reverse('dcim-api:rack-list')
  290. response = self.client.get('{}?last_updated__lte=2001-02-04%2001:02:03.000004'.format(url), **self.header)
  291. self.assertEqual(response.data['count'], 1)
  292. self.assertEqual(response.data['results'][0]['id'], self.rack2.pk)