diff --git a/netbox/circuits/tests/test_views.py b/netbox/circuits/tests/test_views.py index d2cb8e5ab..9cc7af6ae 100644 --- a/netbox/circuits/tests/test_views.py +++ b/netbox/circuits/tests/test_views.py @@ -2,10 +2,10 @@ import datetime from circuits.choices import * from circuits.models import Circuit, CircuitType, Provider -from utilities.testing import StandardTestCases +from utilities.testing import ViewTestCases -class ProviderTestCase(StandardTestCases.Views): +class ProviderTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = Provider @classmethod @@ -46,14 +46,9 @@ class ProviderTestCase(StandardTestCases.Views): } -class CircuitTypeTestCase(StandardTestCases.Views): +class CircuitTypeTestCase(ViewTestCases.OrganizationalObjectViewTestCase): model = CircuitType - # Disable inapplicable tests - test_get_object = None - test_delete_object = None - test_bulk_edit_objects = None - @classmethod def setUpTestData(cls): @@ -77,7 +72,7 @@ class CircuitTypeTestCase(StandardTestCases.Views): ) -class CircuitTestCase(StandardTestCases.Views): +class CircuitTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = Circuit @classmethod diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index 75e3f9871..704dedb40 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -11,7 +11,7 @@ from dcim.choices import * from dcim.constants import * from dcim.models import * from ipam.models import VLAN -from utilities.testing import StandardTestCases +from utilities.testing import ViewTestCases def create_test_device(name): @@ -27,14 +27,9 @@ def create_test_device(name): return device -class RegionTestCase(StandardTestCases.Views): +class RegionTestCase(ViewTestCases.OrganizationalObjectViewTestCase): model = Region - # Disable inapplicable tests - test_get_object = None - test_delete_object = None - test_bulk_edit_objects = None - @classmethod def setUpTestData(cls): @@ -61,7 +56,7 @@ class RegionTestCase(StandardTestCases.Views): ) -class SiteTestCase(StandardTestCases.Views): +class SiteTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = Site @classmethod @@ -118,14 +113,9 @@ class SiteTestCase(StandardTestCases.Views): } -class RackGroupTestCase(StandardTestCases.Views): +class RackGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase): model = RackGroup - # Disable inapplicable tests - test_get_object = None - test_delete_object = None - test_bulk_edit_objects = None - @classmethod def setUpTestData(cls): @@ -152,14 +142,9 @@ class RackGroupTestCase(StandardTestCases.Views): ) -class RackRoleTestCase(StandardTestCases.Views): +class RackRoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase): model = RackRole - # Disable inapplicable tests - test_get_object = None - test_delete_object = None - test_bulk_edit_objects = None - @classmethod def setUpTestData(cls): @@ -184,7 +169,7 @@ class RackRoleTestCase(StandardTestCases.Views): ) -class RackReservationTestCase(StandardTestCases.Views): +class RackReservationTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = RackReservation # Disable inapplicable tests @@ -226,7 +211,7 @@ class RackReservationTestCase(StandardTestCases.Views): } -class RackTestCase(StandardTestCases.Views): +class RackTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = Rack @classmethod @@ -302,14 +287,9 @@ class RackTestCase(StandardTestCases.Views): } -class ManufacturerTestCase(StandardTestCases.Views): +class ManufacturerTestCase(ViewTestCases.OrganizationalObjectViewTestCase): model = Manufacturer - # Disable inapplicable tests - test_get_object = None - test_delete_object = None - test_bulk_edit_objects = None - @classmethod def setUpTestData(cls): @@ -332,7 +312,7 @@ class ManufacturerTestCase(StandardTestCases.Views): ) -class DeviceTypeTestCase(StandardTestCases.Views): +class DeviceTypeTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = DeviceType @classmethod @@ -528,18 +508,9 @@ device-bays: # DeviceType components # -class ConsolePortTemplateTestCase(StandardTestCases.Views): +class ConsolePortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase): model = ConsolePortTemplate - # Disable inapplicable views - test_get_object = None - test_list_objects = None - test_create_object = None - test_import_objects = None - - def test_bulk_create_objects(self): - return self._test_bulk_create_objects(expected_count=3) - @classmethod def setUpTestData(cls): manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') @@ -572,18 +543,9 @@ class ConsolePortTemplateTestCase(StandardTestCases.Views): } -class ConsoleServerPortTemplateTestCase(StandardTestCases.Views): +class ConsoleServerPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase): model = ConsoleServerPortTemplate - # Disable inapplicable views - test_get_object = None - test_list_objects = None - test_create_object = None - test_import_objects = None - - def test_bulk_create_objects(self): - return self._test_bulk_create_objects(expected_count=3) - @classmethod def setUpTestData(cls): manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') @@ -616,18 +578,9 @@ class ConsoleServerPortTemplateTestCase(StandardTestCases.Views): } -class PowerPortTemplateTestCase(StandardTestCases.Views): +class PowerPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase): model = PowerPortTemplate - # Disable inapplicable views - test_get_object = None - test_list_objects = None - test_create_object = None - test_import_objects = None - - def test_bulk_create_objects(self): - return self._test_bulk_create_objects(expected_count=3) - @classmethod def setUpTestData(cls): manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') @@ -666,18 +619,9 @@ class PowerPortTemplateTestCase(StandardTestCases.Views): } -class PowerOutletTemplateTestCase(StandardTestCases.Views): +class PowerOutletTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase): model = PowerOutletTemplate - # Disable inapplicable views - test_get_object = None - test_list_objects = None - test_create_object = None - test_import_objects = None - - def test_bulk_create_objects(self): - return self._test_bulk_create_objects(expected_count=3) - @classmethod def setUpTestData(cls): manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') @@ -716,18 +660,9 @@ class PowerOutletTemplateTestCase(StandardTestCases.Views): } -class InterfaceTemplateTestCase(StandardTestCases.Views): +class InterfaceTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase): model = InterfaceTemplate - # Disable inapplicable views - test_get_object = None - test_list_objects = None - test_create_object = None - test_import_objects = None - - def test_bulk_create_objects(self): - return self._test_bulk_create_objects(expected_count=3) - @classmethod def setUpTestData(cls): manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') @@ -763,18 +698,9 @@ class InterfaceTemplateTestCase(StandardTestCases.Views): } -class FrontPortTemplateTestCase(StandardTestCases.Views): +class FrontPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase): model = FrontPortTemplate - # Disable inapplicable views - test_get_object = None - test_list_objects = None - test_create_object = None - test_import_objects = None - - def test_bulk_create_objects(self): - return self._test_bulk_create_objects(expected_count=3) - @classmethod def setUpTestData(cls): manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') @@ -818,18 +744,9 @@ class FrontPortTemplateTestCase(StandardTestCases.Views): } -class RearPortTemplateTestCase(StandardTestCases.Views): +class RearPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase): model = RearPortTemplate - # Disable inapplicable views - test_get_object = None - test_list_objects = None - test_create_object = None - test_import_objects = None - - def test_bulk_create_objects(self): - return self._test_bulk_create_objects(expected_count=3) - @classmethod def setUpTestData(cls): manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') @@ -864,19 +781,12 @@ class RearPortTemplateTestCase(StandardTestCases.Views): } -class DeviceBayTemplateTestCase(StandardTestCases.Views): +class DeviceBayTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase): model = DeviceBayTemplate # Disable inapplicable views - test_get_object = None - test_list_objects = None - test_create_object = None - test_import_objects = None test_bulk_edit_objects = None - def test_bulk_create_objects(self): - return self._test_bulk_create_objects(expected_count=3) - @classmethod def setUpTestData(cls): manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') @@ -903,14 +813,9 @@ class DeviceBayTemplateTestCase(StandardTestCases.Views): } -class DeviceRoleTestCase(StandardTestCases.Views): +class DeviceRoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase): model = DeviceRole - # Disable inapplicable tests - test_get_object = None - test_delete_object = None - test_bulk_edit_objects = None - @classmethod def setUpTestData(cls): @@ -936,14 +841,9 @@ class DeviceRoleTestCase(StandardTestCases.Views): ) -class PlatformTestCase(StandardTestCases.Views): +class PlatformTestCase(ViewTestCases.OrganizationalObjectViewTestCase): model = Platform - # Disable inapplicable tests - test_get_object = None - test_delete_object = None - test_bulk_edit_objects = None - @classmethod def setUpTestData(cls): @@ -971,7 +871,7 @@ class PlatformTestCase(StandardTestCases.Views): ) -class DeviceTestCase(StandardTestCases.Views): +class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = Device @classmethod @@ -1056,16 +956,9 @@ class DeviceTestCase(StandardTestCases.Views): } -class ConsolePortTestCase(StandardTestCases.Views): +class ConsolePortTestCase(ViewTestCases.DeviceComponentViewTestCase): model = ConsolePort - # Disable inapplicable views - test_get_object = None - test_create_object = None - - def test_bulk_create_objects(self): - return self._test_bulk_create_objects(expected_count=3) - @classmethod def setUpTestData(cls): device = create_test_device('Device 1') @@ -1105,16 +998,9 @@ class ConsolePortTestCase(StandardTestCases.Views): ) -class ConsoleServerPortTestCase(StandardTestCases.Views): +class ConsoleServerPortTestCase(ViewTestCases.DeviceComponentViewTestCase): model = ConsoleServerPort - # Disable inapplicable views - test_get_object = None - test_create_object = None - - def test_bulk_create_objects(self): - return self._test_bulk_create_objects(expected_count=3) - @classmethod def setUpTestData(cls): device = create_test_device('Device 1') @@ -1155,16 +1041,9 @@ class ConsoleServerPortTestCase(StandardTestCases.Views): ) -class PowerPortTestCase(StandardTestCases.Views): +class PowerPortTestCase(ViewTestCases.DeviceComponentViewTestCase): model = PowerPort - # Disable inapplicable views - test_get_object = None - test_create_object = None - - def test_bulk_create_objects(self): - return self._test_bulk_create_objects(expected_count=3) - @classmethod def setUpTestData(cls): device = create_test_device('Device 1') @@ -1210,16 +1089,9 @@ class PowerPortTestCase(StandardTestCases.Views): ) -class PowerOutletTestCase(StandardTestCases.Views): +class PowerOutletTestCase(ViewTestCases.DeviceComponentViewTestCase): model = PowerOutlet - # Disable inapplicable views - test_get_object = None - test_create_object = None - - def test_bulk_create_objects(self): - return self._test_bulk_create_objects(expected_count=3) - @classmethod def setUpTestData(cls): device = create_test_device('Device 1') @@ -1272,15 +1144,12 @@ class PowerOutletTestCase(StandardTestCases.Views): ) -class InterfaceTestCase(StandardTestCases.Views): +class InterfaceTestCase( + ViewTestCases.GetObjectViewTestCase, + ViewTestCases.DeviceComponentViewTestCase, +): model = Interface - # Disable inapplicable views - test_create_object = None - - def test_bulk_create_objects(self): - return self._test_bulk_create_objects(expected_count=3) - @classmethod def setUpTestData(cls): device = create_test_device('Device 1') @@ -1356,16 +1225,9 @@ class InterfaceTestCase(StandardTestCases.Views): ) -class FrontPortTestCase(StandardTestCases.Views): +class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase): model = FrontPort - # Disable inapplicable views - test_get_object = None - test_create_object = None - - def test_bulk_create_objects(self): - return self._test_bulk_create_objects(expected_count=3) - @classmethod def setUpTestData(cls): device = create_test_device('Device 1') @@ -1420,16 +1282,9 @@ class FrontPortTestCase(StandardTestCases.Views): ) -class RearPortTestCase(StandardTestCases.Views): +class RearPortTestCase(ViewTestCases.DeviceComponentViewTestCase): model = RearPort - # Disable inapplicable views - test_get_object = None - test_create_object = None - - def test_bulk_create_objects(self): - return self._test_bulk_create_objects(expected_count=3) - @classmethod def setUpTestData(cls): device = create_test_device('Device 1') @@ -1471,19 +1326,12 @@ class RearPortTestCase(StandardTestCases.Views): ) -class DeviceBayTestCase(StandardTestCases.Views): +class DeviceBayTestCase(ViewTestCases.DeviceComponentViewTestCase): model = DeviceBay # Disable inapplicable views - test_get_object = None - test_create_object = None - - # TODO test_bulk_edit_objects = None - def test_bulk_create_objects(self): - return self._test_bulk_create_objects(expected_count=3) - @classmethod def setUpTestData(cls): device1 = create_test_device('Device 1') @@ -1520,16 +1368,9 @@ class DeviceBayTestCase(StandardTestCases.Views): ) -class InventoryItemTestCase(StandardTestCases.Views): +class InventoryItemTestCase(ViewTestCases.DeviceComponentViewTestCase): model = InventoryItem - # Disable inapplicable views - test_get_object = None - test_create_object = None - - def test_bulk_create_objects(self): - return self._test_bulk_create_objects(expected_count=3) - @classmethod def setUpTestData(cls): device = create_test_device('Device 1') @@ -1581,7 +1422,7 @@ class InventoryItemTestCase(StandardTestCases.Views): ) -class CableTestCase(StandardTestCases.Views): +class CableTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = Cable # TODO: Creation URL needs termination context @@ -1655,7 +1496,7 @@ class CableTestCase(StandardTestCases.Views): } -class VirtualChassisTestCase(StandardTestCases.Views): +class VirtualChassisTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = VirtualChassis # Disable inapplicable tests @@ -1709,7 +1550,7 @@ class VirtualChassisTestCase(StandardTestCases.Views): Device.objects.filter(pk=device6.pk).update(virtual_chassis=vc3, vc_position=2) -class PowerPanelTestCase(StandardTestCases.Views): +class PowerPanelTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = PowerPanel # Disable inapplicable tests @@ -1750,7 +1591,7 @@ class PowerPanelTestCase(StandardTestCases.Views): ) -class PowerFeedTestCase(StandardTestCases.Views): +class PowerFeedTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = PowerFeed @classmethod diff --git a/netbox/extras/tests/test_views.py b/netbox/extras/tests/test_views.py index ecb25a78c..370055b26 100644 --- a/netbox/extras/tests/test_views.py +++ b/netbox/extras/tests/test_views.py @@ -7,10 +7,10 @@ from django.urls import reverse from dcim.models import Site from extras.choices import ObjectChangeActionChoices from extras.models import ConfigContext, ObjectChange, Tag -from utilities.testing import StandardTestCases, TestCase +from utilities.testing import ViewTestCases, TestCase -class TagTestCase(StandardTestCases.Views): +class TagTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = Tag # Disable inapplicable tests @@ -38,7 +38,7 @@ class TagTestCase(StandardTestCases.Views): } -class ConfigContextTestCase(StandardTestCases.Views): +class ConfigContextTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = ConfigContext # Disable inapplicable tests diff --git a/netbox/ipam/tests/test_views.py b/netbox/ipam/tests/test_views.py index cfa06788c..66e649005 100644 --- a/netbox/ipam/tests/test_views.py +++ b/netbox/ipam/tests/test_views.py @@ -5,10 +5,10 @@ from netaddr import IPNetwork from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site from ipam.choices import * from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF -from utilities.testing import StandardTestCases +from utilities.testing import ViewTestCases -class VRFTestCase(StandardTestCases.Views): +class VRFTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = VRF @classmethod @@ -43,14 +43,9 @@ class VRFTestCase(StandardTestCases.Views): } -class RIRTestCase(StandardTestCases.Views): +class RIRTestCase(ViewTestCases.OrganizationalObjectViewTestCase): model = RIR - # Disable inapplicable tests - test_get_object = None - test_delete_object = None - test_bulk_edit_objects = None - @classmethod def setUpTestData(cls): @@ -74,7 +69,7 @@ class RIRTestCase(StandardTestCases.Views): ) -class AggregateTestCase(StandardTestCases.Views): +class AggregateTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = Aggregate @classmethod @@ -115,14 +110,9 @@ class AggregateTestCase(StandardTestCases.Views): } -class RoleTestCase(StandardTestCases.Views): +class RoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase): model = Role - # Disable inapplicable tests - test_get_object = None - test_delete_object = None - test_bulk_edit_objects = None - @classmethod def setUpTestData(cls): @@ -147,7 +137,7 @@ class RoleTestCase(StandardTestCases.Views): ) -class PrefixTestCase(StandardTestCases.Views): +class PrefixTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = Prefix @classmethod @@ -207,7 +197,7 @@ class PrefixTestCase(StandardTestCases.Views): } -class IPAddressTestCase(StandardTestCases.Views): +class IPAddressTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = IPAddress @classmethod @@ -254,14 +244,9 @@ class IPAddressTestCase(StandardTestCases.Views): } -class VLANGroupTestCase(StandardTestCases.Views): +class VLANGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase): model = VLANGroup - # Disable inapplicable tests - test_get_object = None - test_delete_object = None - test_bulk_edit_objects = None - @classmethod def setUpTestData(cls): @@ -287,7 +272,7 @@ class VLANGroupTestCase(StandardTestCases.Views): ) -class VLANTestCase(StandardTestCases.Views): +class VLANTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = VLAN @classmethod @@ -346,7 +331,7 @@ class VLANTestCase(StandardTestCases.Views): } -class ServiceTestCase(StandardTestCases.Views): +class ServiceTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = Service # Disable inapplicable tests diff --git a/netbox/secrets/tests/test_views.py b/netbox/secrets/tests/test_views.py index 94f4cbd6a..96439a10d 100644 --- a/netbox/secrets/tests/test_views.py +++ b/netbox/secrets/tests/test_views.py @@ -4,18 +4,13 @@ from django.urls import reverse from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site from secrets.models import Secret, SecretRole, SessionKey, UserKey -from utilities.testing import StandardTestCases +from utilities.testing import ViewTestCases from .constants import PRIVATE_KEY, PUBLIC_KEY -class SecretRoleTestCase(StandardTestCases.Views): +class SecretRoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase): model = SecretRole - # Disable inapplicable tests - test_get_object = None - test_delete_object = None - test_bulk_edit_objects = None - @classmethod def setUpTestData(cls): @@ -41,7 +36,7 @@ class SecretRoleTestCase(StandardTestCases.Views): ) -class SecretTestCase(StandardTestCases.Views): +class SecretTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = Secret # Disable inapplicable tests diff --git a/netbox/tenancy/tests/test_views.py b/netbox/tenancy/tests/test_views.py index a44ca2932..27e2c1591 100644 --- a/netbox/tenancy/tests/test_views.py +++ b/netbox/tenancy/tests/test_views.py @@ -1,15 +1,10 @@ from tenancy.models import Tenant, TenantGroup -from utilities.testing import StandardTestCases +from utilities.testing import ViewTestCases -class TenantGroupTestCase(StandardTestCases.Views): +class TenantGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase): model = TenantGroup - # Disable inapplicable tests - test_get_object = None - test_delete_object = None - test_bulk_edit_objects = None - @classmethod def setUpTestData(cls): @@ -32,7 +27,7 @@ class TenantGroupTestCase(StandardTestCases.Views): ) -class TenantTestCase(StandardTestCases.Views): +class TenantTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = Tenant @classmethod diff --git a/netbox/utilities/testing/testcases.py b/netbox/utilities/testing/testcases.py index b5e2e1bab..8d1b1a1be 100644 --- a/netbox/utilities/testing/testcases.py +++ b/netbox/utilities/testing/testcases.py @@ -57,6 +57,53 @@ class TestCase(_TestCase): expected_status, response.status_code, getattr(response, 'data', 'No data') )) + +class ModelViewTestCase(TestCase): + """ + Base TestCase for model views. Subclass to test individual views. + """ + model = None + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + if self.model is None: + raise Exception("Test case requires model to be defined") + + def _get_base_url(self): + """ + Return the base format for a URL for the test's model. Override this to test for a model which belongs + to a different app (e.g. testing Interfaces within the virtualization app). + """ + return '{}:{}_{{}}'.format( + self.model._meta.app_label, + self.model._meta.model_name + ) + + def _get_url(self, action, instance=None): + """ + Return the URL name for a specific action. An instance must be specified for + get/edit/delete views. + """ + url_format = self._get_base_url() + + if action in ('list', 'add', 'import', 'bulk_edit', 'bulk_delete'): + return reverse(url_format.format(action)) + + elif action in ('get', 'edit', 'delete'): + if instance is None: + raise Exception("Resolving {} URL requires specifying an instance".format(action)) + # Attempt to resolve using slug first + if hasattr(self.model, 'slug'): + try: + return reverse(url_format.format(action), kwargs={'slug': instance.slug}) + except NoReverseMatch: + pass + return reverse(url_format.format(action), kwargs={'pk': instance.pk}) + + else: + raise Exception("Invalid action for URL resolution: {}".format(action)) + def assertInstanceEqual(self, instance, data): """ Compare a model instance to a dictionary, checking that its attribute values match those specified @@ -94,108 +141,14 @@ class APITestCase(TestCase): self.header = {'HTTP_AUTHORIZATION': 'Token {}'.format(self.token.key)} -class StandardTestCases: +class ViewTestCases: """ We keep any TestCases with test_* methods inside a class to prevent unittest from trying to run them. """ - - class Views(TestCase): + class GetObjectViewTestCase(ModelViewTestCase): """ - Stock TestCase suitable for testing all standard View functions: - - List objects - - View single object - - Create new object - - Modify existing object - - Delete existing object - - Import multiple new objects + Retrieve a single instance. """ - model = None - - # Data to be sent when creating/editing individual objects - form_data = {} - - # CSV lines used for bulk import of new objects - csv_data = () - - # Form data used when creating multiple objects - bulk_create_data = {} - - # Form data to be used when editing multiple objects at once - bulk_edit_data = {} - - maxDiff = None - - def __init__(self, *args, **kwargs): - - super().__init__(*args, **kwargs) - - if self.model is None: - raise Exception("Test case requires model to be defined") - - # - # URL functions - # - - def _get_base_url(self): - """ - Return the base format for a URL for the test's model. Override this to test for a model which belongs - to a different app (e.g. testing Interfaces within the virtualization app). - """ - return '{}:{}_{{}}'.format( - self.model._meta.app_label, - self.model._meta.model_name - ) - - def _get_url(self, action, instance=None): - """ - Return the URL name for a specific action. An instance must be specified for - get/edit/delete views. - """ - url_format = self._get_base_url() - - if action in ('list', 'add', 'import', 'bulk_edit', 'bulk_delete'): - return reverse(url_format.format(action)) - - elif action in ('get', 'edit', 'delete'): - if instance is None: - raise Exception("Resolving {} URL requires specifying an instance".format(action)) - # Attempt to resolve using slug first - if hasattr(self.model, 'slug'): - try: - return reverse(url_format.format(action), kwargs={'slug': instance.slug}) - except NoReverseMatch: - pass - return reverse(url_format.format(action), kwargs={'pk': instance.pk}) - - else: - raise Exception("Invalid action for URL resolution: {}".format(action)) - - # - # Standard view tests - # These methods will run by default. To disable a test, nullify its method on the subclasses TestCase: - # - # test_list_objects = None - # - - @override_settings(EXEMPT_VIEW_PERMISSIONS=[]) - def test_list_objects(self): - # Attempt to make the request without required permissions - with disable_warnings('django.request'): - self.assertHttpStatus(self.client.get(self._get_url('list')), 403) - - # Assign the required permission and submit again - self.add_permissions( - '{}.view_{}'.format(self.model._meta.app_label, self.model._meta.model_name) - ) - response = self.client.get(self._get_url('list')) - self.assertHttpStatus(response, 200) - - # Built-in CSV export - if hasattr(self.model, 'csv_headers'): - response = self.client.get('{}?export'.format(self._get_url('list'))) - self.assertHttpStatus(response, 200) - self.assertEqual(response.get('Content-Type'), 'text/csv') - @override_settings(EXEMPT_VIEW_PERMISSIONS=[]) def test_get_object(self): instance = self.model.objects.first() @@ -211,6 +164,12 @@ class StandardTestCases: response = self.client.get(instance.get_absolute_url()) self.assertHttpStatus(response, 200) + class CreateObjectViewTestCase(ModelViewTestCase): + """ + Create a single new instance. + """ + form_data = {} + @override_settings(EXEMPT_VIEW_PERMISSIONS=[]) def test_create_object(self): initial_count = self.model.objects.count() @@ -235,6 +194,12 @@ class StandardTestCases: instance = self.model.objects.order_by('-pk').first() self.assertInstanceEqual(instance, self.form_data) + class EditObjectViewTestCase(ModelViewTestCase): + """ + Edit a single existing instance. + """ + form_data = {} + @override_settings(EXEMPT_VIEW_PERMISSIONS=[]) def test_edit_object(self): instance = self.model.objects.first() @@ -259,6 +224,10 @@ class StandardTestCases: instance = self.model.objects.get(pk=instance.pk) self.assertInstanceEqual(instance, self.form_data) + class DeleteObjectViewTestCase(ModelViewTestCase): + """ + Delete a single instance. + """ @override_settings(EXEMPT_VIEW_PERMISSIONS=[]) def test_delete_object(self): instance = self.model.objects.first() @@ -283,6 +252,66 @@ class StandardTestCases: with self.assertRaises(ObjectDoesNotExist): self.model.objects.get(pk=instance.pk) + class ListObjectsViewTestCase(ModelViewTestCase): + """ + Retrieve multiple instances. + """ + @override_settings(EXEMPT_VIEW_PERMISSIONS=[]) + def test_list_objects(self): + # Attempt to make the request without required permissions + with disable_warnings('django.request'): + self.assertHttpStatus(self.client.get(self._get_url('list')), 403) + + # Assign the required permission and submit again + self.add_permissions( + '{}.view_{}'.format(self.model._meta.app_label, self.model._meta.model_name) + ) + response = self.client.get(self._get_url('list')) + self.assertHttpStatus(response, 200) + + # Built-in CSV export + if hasattr(self.model, 'csv_headers'): + response = self.client.get('{}?export'.format(self._get_url('list'))) + self.assertHttpStatus(response, 200) + self.assertEqual(response.get('Content-Type'), 'text/csv') + + class BulkCreateObjectsViewTestCase(ModelViewTestCase): + """ + Create multiple instances using a single form. Expects the creation of three new instances by default. + """ + bulk_create_count = 3 + bulk_create_data = {} + + @override_settings(EXEMPT_VIEW_PERMISSIONS=[]) + def test_bulk_create_objects(self): + initial_count = self.model.objects.count() + request = { + 'path': self._get_url('add'), + 'data': post_data(self.bulk_create_data), + 'follow': False, # Do not follow 302 redirects + } + + # Attempt to make the request without required permissions + with disable_warnings('django.request'): + self.assertHttpStatus(self.client.post(**request), 403) + + # Assign the required permission and submit again + self.add_permissions( + '{}.add_{}'.format(self.model._meta.app_label, self.model._meta.model_name) + ) + response = self.client.post(**request) + self.assertHttpStatus(response, 302) + + self.assertEqual(initial_count + self.bulk_create_count, self.model.objects.count()) + for instance in self.model.objects.order_by('-pk')[:self.bulk_create_count]: + self.assertInstanceEqual(instance, self.bulk_create_data) + + class ImportObjectsViewTestCase(ModelViewTestCase): + """ + Create multiple instances from imported data. + """ + csv_data = () + @override_settings(EXEMPT_VIEW_PERMISSIONS=[]) def test_import_objects(self): initial_count = self.model.objects.count() @@ -307,6 +336,12 @@ class StandardTestCases: self.assertEqual(self.model.objects.count(), initial_count + len(self.csv_data) - 1) + class BulkEditObjectsViewTestCase(ModelViewTestCase): + """ + Edit multiple instances. + """ + bulk_edit_data = {} + @override_settings(EXEMPT_VIEW_PERMISSIONS=[]) def test_bulk_edit_objects(self): # Bulk edit the first three objects only @@ -338,6 +373,10 @@ class StandardTestCases: for i, instance in enumerate(self.model.objects.filter(pk__in=pk_list)): self.assertInstanceEqual(instance, self.bulk_edit_data) + class BulkDeleteObjectsViewTestCase(ModelViewTestCase): + """ + Delete multiple instances. + """ @override_settings(EXEMPT_VIEW_PERMISSIONS=[]) def test_bulk_delete_objects(self): pk_list = self.model.objects.values_list('pk', flat=True) @@ -366,31 +405,55 @@ class StandardTestCases: # Check that all objects were deleted self.assertEqual(self.model.objects.count(), 0) - # - # Optional view tests - # These methods will run only if the required data - # + class PrimaryObjectViewTestCase( + GetObjectViewTestCase, + CreateObjectViewTestCase, + EditObjectViewTestCase, + DeleteObjectViewTestCase, + ListObjectsViewTestCase, + ImportObjectsViewTestCase, + BulkEditObjectsViewTestCase, + BulkDeleteObjectsViewTestCase, + ): + """ + TestCase suitable for testing all standard View functions for primary objects + """ + maxDiff = None - @override_settings(EXEMPT_VIEW_PERMISSIONS=[]) - def _test_bulk_create_objects(self, expected_count): - initial_count = self.model.objects.count() - request = { - 'path': self._get_url('add'), - 'data': post_data(self.bulk_create_data), - 'follow': False, # Do not follow 302 redirects - } + class OrganizationalObjectViewTestCase( + CreateObjectViewTestCase, + EditObjectViewTestCase, + ListObjectsViewTestCase, + ImportObjectsViewTestCase, + BulkDeleteObjectsViewTestCase, + ): + """ + TestCase suitable for all organizational objects + """ + maxDiff = None - # Attempt to make the request without required permissions - with disable_warnings('django.request'): - self.assertHttpStatus(self.client.post(**request), 403) + class DeviceComponentTemplateViewTestCase( + EditObjectViewTestCase, + DeleteObjectViewTestCase, + BulkCreateObjectsViewTestCase, + BulkEditObjectsViewTestCase, + BulkDeleteObjectsViewTestCase, + ): + """ + TestCase suitable for testing device component template models (ConsolePortTemplates, InterfaceTemplates, etc.) + """ + maxDiff = None - # Assign the required permission and submit again - self.add_permissions( - '{}.add_{}'.format(self.model._meta.app_label, self.model._meta.model_name) - ) - response = self.client.post(**request) - self.assertHttpStatus(response, 302) - - self.assertEqual(initial_count + expected_count, self.model.objects.count()) - for instance in self.model.objects.order_by('-pk')[:expected_count]: - self.assertInstanceEqual(instance, self.bulk_create_data) + class DeviceComponentViewTestCase( + EditObjectViewTestCase, + DeleteObjectViewTestCase, + ListObjectsViewTestCase, + BulkCreateObjectsViewTestCase, + ImportObjectsViewTestCase, + BulkEditObjectsViewTestCase, + BulkDeleteObjectsViewTestCase, + ): + """ + TestCase suitable for testing device component models (ConsolePorts, Interfaces, etc.) + """ + maxDiff = None diff --git a/netbox/virtualization/tests/test_views.py b/netbox/virtualization/tests/test_views.py index 6cedf9803..639908977 100644 --- a/netbox/virtualization/tests/test_views.py +++ b/netbox/virtualization/tests/test_views.py @@ -3,19 +3,14 @@ from netaddr import EUI from dcim.choices import InterfaceModeChoices from dcim.models import DeviceRole, Interface, Platform, Site from ipam.models import VLAN -from utilities.testing import StandardTestCases +from utilities.testing import ViewTestCases from virtualization.choices import * from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine -class ClusterGroupTestCase(StandardTestCases.Views): +class ClusterGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase): model = ClusterGroup - # Disable inapplicable tests - test_get_object = None - test_delete_object = None - test_bulk_edit_objects = None - @classmethod def setUpTestData(cls): @@ -38,14 +33,9 @@ class ClusterGroupTestCase(StandardTestCases.Views): ) -class ClusterTypeTestCase(StandardTestCases.Views): +class ClusterTypeTestCase(ViewTestCases.OrganizationalObjectViewTestCase): model = ClusterType - # Disable inapplicable tests - test_get_object = None - test_delete_object = None - test_bulk_edit_objects = None - @classmethod def setUpTestData(cls): @@ -68,7 +58,7 @@ class ClusterTypeTestCase(StandardTestCases.Views): ) -class ClusterTestCase(StandardTestCases.Views): +class ClusterTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = Cluster @classmethod @@ -124,7 +114,7 @@ class ClusterTestCase(StandardTestCases.Views): } -class VirtualMachineTestCase(StandardTestCases.Views): +class VirtualMachineTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = VirtualMachine @classmethod @@ -193,17 +183,16 @@ class VirtualMachineTestCase(StandardTestCases.Views): } -class InterfaceTestCase(StandardTestCases.Views): +class InterfaceTestCase( + ViewTestCases.GetObjectViewTestCase, + ViewTestCases.DeviceComponentViewTestCase, +): model = Interface # Disable inapplicable tests test_list_objects = None - test_create_object = None test_import_objects = None - def test_bulk_create_objects(self): - return self._test_bulk_create_objects(expected_count=3) - def _get_base_url(self): # Interface belongs to the DCIM app, so we have to override the base URL return 'virtualization:interface_{}'