diff --git a/netbox/circuits/tests/test_views.py b/netbox/circuits/tests/test_views.py index 576437ef1..1e065f458 100644 --- a/netbox/circuits/tests/test_views.py +++ b/netbox/circuits/tests/test_views.py @@ -1,23 +1,15 @@ -import urllib.parse - -from django.test import Client, TestCase -from django.urls import reverse +import datetime +from circuits.choices import * from circuits.models import Circuit, CircuitType, Provider -from utilities.testing import create_test_user +from utilities.testing import StandardTestCases -class ProviderTestCase(TestCase): +class ProviderTestCase(StandardTestCases.Views): + model = Provider - def setUp(self): - user = create_test_user( - permissions=[ - 'circuits.view_provider', - 'circuits.add_provider', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): Provider.objects.bulk_create([ Provider(name='Provider 1', slug='provider-1', asn=65001), @@ -25,48 +17,35 @@ class ProviderTestCase(TestCase): Provider(name='Provider 3', slug='provider-3', asn=65003), ]) - def test_provider_list(self): - - url = reverse('circuits:provider_list') - params = { - "q": "test", + cls.form_data = { + 'name': 'Provider X', + 'slug': 'provider-x', + 'asn': 65123, + 'account': '1234', + 'portal_url': 'http://example.com/portal', + 'noc_contact': 'noc@example.com', + 'admin_contact': 'admin@example.com', + 'comments': 'Another provider', + 'tags': 'Alpha,Bravo,Charlie', } - response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) - - def test_provider(self): - - provider = Provider.objects.first() - response = self.client.get(provider.get_absolute_url()) - self.assertEqual(response.status_code, 200) - - def test_provider_import(self): - - csv_data = ( + cls.csv_data = ( "name,slug", "Provider 4,provider-4", "Provider 5,provider-5", "Provider 6,provider-6", ) - response = self.client.post(reverse('circuits:provider_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) - self.assertEqual(Provider.objects.count(), 6) +class CircuitTypeTestCase(StandardTestCases.Views): + model = CircuitType + # Disable inapplicable tests + test_get_object = None + test_delete_object = None -class CircuitTypeTestCase(TestCase): - - def setUp(self): - user = create_test_user( - permissions=[ - 'circuits.view_circuittype', - 'circuits.add_circuittype', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): CircuitType.objects.bulk_create([ CircuitType(name='Circuit Type 1', slug='circuit-type-1'), @@ -74,39 +53,25 @@ class CircuitTypeTestCase(TestCase): CircuitType(name='Circuit Type 3', slug='circuit-type-3'), ]) - def test_circuittype_list(self): + cls.form_data = { + 'name': 'Circuit Type X', + 'slug': 'circuit-type-x', + 'description': 'A new circuit type', + } - url = reverse('circuits:circuittype_list') - - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - - def test_circuittype_import(self): - - csv_data = ( + cls.csv_data = ( "name,slug", "Circuit Type 4,circuit-type-4", "Circuit Type 5,circuit-type-5", "Circuit Type 6,circuit-type-6", ) - response = self.client.post(reverse('circuits:circuittype_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) - self.assertEqual(CircuitType.objects.count(), 6) +class CircuitTestCase(StandardTestCases.Views): + model = Circuit - -class CircuitTestCase(TestCase): - - def setUp(self): - user = create_test_user( - permissions=[ - 'circuits.view_circuit', - 'circuits.add_circuit', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): provider = Provider(name='Provider 1', slug='provider-1', asn=65001) provider.save() @@ -120,33 +85,22 @@ class CircuitTestCase(TestCase): Circuit(cid='Circuit 3', provider=provider, type=circuittype), ]) - def test_circuit_list(self): - - url = reverse('circuits:circuit_list') - params = { - "provider": Provider.objects.first().slug, - "type": CircuitType.objects.first().slug, + cls.form_data = { + 'cid': 'Circuit X', + 'provider': provider.pk, + 'type': circuittype.pk, + 'status': CircuitStatusChoices.STATUS_ACTIVE, + 'tenant': None, + 'install_date': datetime.date(2020, 1, 1), + 'commit_rate': 1000, + 'description': 'A new circuit', + 'comments': 'Some comments', + 'tags': 'Alpha,Bravo,Charlie', } - response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) - - def test_circuit(self): - - circuit = Circuit.objects.first() - response = self.client.get(circuit.get_absolute_url()) - self.assertEqual(response.status_code, 200) - - def test_circuit_import(self): - - csv_data = ( + cls.csv_data = ( "cid,provider,type", "Circuit 4,Provider 1,Circuit Type 1", "Circuit 5,Provider 1,Circuit Type 1", "Circuit 6,Provider 1,Circuit Type 1", ) - - response = self.client.post(reverse('circuits:circuit_import'), {'csv': '\n'.join(csv_data)}) - - self.assertEqual(response.status_code, 200) - self.assertEqual(Circuit.objects.count(), 6) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 7efd400fb..dbb3a3bb4 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -1549,6 +1549,7 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): ) manufacturer = forms.ModelChoiceField( queryset=Manufacturer.objects.all(), + required=False, widget=APISelect( api_url="/api/dcim/manufacturers/", filter_for={ diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index 856862a3e..8fc883812 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -1,64 +1,56 @@ import urllib.parse +from decimal import Decimal +import pytz import yaml -from django.test import Client, TestCase +from django.contrib.auth.models import User +from django.contrib.contenttypes.models import ContentType from django.urls import reverse from dcim.choices import * from dcim.constants import * from dcim.models import * -from utilities.testing import create_test_user +from utilities.testing import StandardTestCases, TestCase -class RegionTestCase(TestCase): +class RegionTestCase(StandardTestCases.Views): + model = Region - def setUp(self): - user = create_test_user( - permissions=[ - 'dcim.view_region', - 'dcim.add_region', - ] - ) - self.client = Client() - self.client.force_login(user) + # Disable inapplicable tests + test_get_object = None + test_delete_object = None + + @classmethod + def setUpTestData(cls): # Create three Regions - for i in range(1, 4): - Region(name='Region {}'.format(i), slug='region-{}'.format(i)).save() + regions = ( + Region(name='Region 1', slug='region-1'), + Region(name='Region 2', slug='region-2'), + Region(name='Region 3', slug='region-3'), + ) + for region in regions: + region.save() - def test_region_list(self): + cls.form_data = { + 'name': 'Region X', + 'slug': 'region-x', + 'parent': regions[2].pk, + } - url = reverse('dcim:region_list') - - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - - def test_region_import(self): - - csv_data = ( + cls.csv_data = ( "name,slug", "Region 4,region-4", "Region 5,region-5", "Region 6,region-6", ) - response = self.client.post(reverse('dcim:region_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) - self.assertEqual(Region.objects.count(), 6) +class SiteTestCase(StandardTestCases.Views): + model = Site - -class SiteTestCase(TestCase): - - def setUp(self): - user = create_test_user( - permissions=[ - 'dcim.view_site', - 'dcim.add_site', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): region = Region(name='Region 1', slug='region-1') region.save() @@ -69,48 +61,44 @@ class SiteTestCase(TestCase): Site(name='Site 3', slug='site-3', region=region), ]) - def test_site_list(self): - - url = reverse('dcim:site_list') - params = { - "region": Region.objects.first().slug, + cls.form_data = { + 'name': 'Site X', + 'slug': 'site-x', + 'status': SiteStatusChoices.STATUS_PLANNED, + 'region': region.pk, + 'tenant': None, + 'facility': 'Facility X', + 'asn': 65001, + 'time_zone': pytz.UTC, + 'description': 'Site description', + 'physical_address': '742 Evergreen Terrace, Springfield, USA', + 'shipping_address': '742 Evergreen Terrace, Springfield, USA', + 'latitude': Decimal('35.780000'), + 'longitude': Decimal('-78.642000'), + 'contact_name': 'Hank Hill', + 'contact_phone': '123-555-9999', + 'contact_email': 'hank@stricklandpropane.com', + 'comments': 'Test site', + 'tags': 'Alpha,Bravo,Charlie', } - response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) - - def test_site(self): - - site = Site.objects.first() - response = self.client.get(site.get_absolute_url()) - self.assertEqual(response.status_code, 200) - - def test_site_import(self): - - csv_data = ( + cls.csv_data = ( "name,slug", "Site 4,site-4", "Site 5,site-5", "Site 6,site-6", ) - response = self.client.post(reverse('dcim:site_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) - self.assertEqual(Site.objects.count(), 6) +class RackGroupTestCase(StandardTestCases.Views): + model = RackGroup + # Disable inapplicable tests + test_get_object = None + test_delete_object = None -class RackGroupTestCase(TestCase): - - def setUp(self): - user = create_test_user( - permissions=[ - 'dcim.view_rackgroup', - 'dcim.add_rackgroup', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): site = Site(name='Site 1', slug='site-1') site.save() @@ -121,39 +109,29 @@ class RackGroupTestCase(TestCase): RackGroup(name='Rack Group 3', slug='rack-group-3', site=site), ]) - def test_rackgroup_list(self): + cls.form_data = { + 'name': 'Rack Group X', + 'slug': 'rack-group-x', + 'site': site.pk, + } - url = reverse('dcim:rackgroup_list') - - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - - def test_rackgroup_import(self): - - csv_data = ( + cls.csv_data = ( "site,name,slug", "Site 1,Rack Group 4,rack-group-4", "Site 1,Rack Group 5,rack-group-5", "Site 1,Rack Group 6,rack-group-6", ) - response = self.client.post(reverse('dcim:rackgroup_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) - self.assertEqual(RackGroup.objects.count(), 6) +class RackRoleTestCase(StandardTestCases.Views): + model = RackRole + # Disable inapplicable tests + test_get_object = None + test_delete_object = None -class RackRoleTestCase(TestCase): - - def setUp(self): - user = create_test_user( - permissions=[ - 'dcim.view_rackrole', - 'dcim.add_rackrole', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): RackRole.objects.bulk_create([ RackRole(name='Rack Role 1', slug='rack-role-1'), @@ -161,34 +139,33 @@ class RackRoleTestCase(TestCase): RackRole(name='Rack Role 3', slug='rack-role-3'), ]) - def test_rackrole_list(self): + cls.form_data = { + 'name': 'Rack Role X', + 'slug': 'rack-role-x', + 'color': 'c0c0c0', + 'description': 'New role', + } - url = reverse('dcim:rackrole_list') - - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - - def test_rackrole_import(self): - - csv_data = ( + cls.csv_data = ( "name,slug,color", "Rack Role 4,rack-role-4,ff0000", "Rack Role 5,rack-role-5,00ff00", "Rack Role 6,rack-role-6,0000ff", ) - response = self.client.post(reverse('dcim:rackrole_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) - self.assertEqual(RackRole.objects.count(), 6) +class RackReservationTestCase(StandardTestCases.Views): + model = RackReservation + # Disable inapplicable tests + test_get_object = None + test_create_object = None # TODO: Fix URL name for view + test_import_objects = None -class RackReservationTestCase(TestCase): + @classmethod + def setUpTestData(cls): - def setUp(self): - user = create_test_user(permissions=['dcim.view_rackreservation']) - self.client = Client() - self.client.force_login(user) + user = User.objects.create_user(username='testuser2') site = Site(name='Site 1', slug='site-1') site.save() @@ -202,28 +179,27 @@ class RackReservationTestCase(TestCase): RackReservation(rack=rack, user=user, units=[7, 8, 9], description='Reservation 3'), ]) - def test_rackreservation_list(self): - - url = reverse('dcim:rackreservation_list') - - response = self.client.get(url) - self.assertEqual(response.status_code, 200) + cls.form_data = { + 'rack': rack.pk, + 'units': [10, 11, 12], + 'user': user.pk, + 'tenant': None, + 'description': 'New reservation', + } -class RackTestCase(TestCase): +class RackTestCase(StandardTestCases.Views): + model = Rack - def setUp(self): - user = create_test_user( - permissions=[ - 'dcim.view_rack', - 'dcim.add_rack', - ] - ) - self.client = Client() - self.client.force_login(user) + # TODO: Remove this when #4067 is fixed + test_create_object = None - site = Site(name='Site 1', slug='site-1') - site.save() + @classmethod + def setUpTestData(cls): + + site = Site.objects.create(name='Site 1', slug='site-1') + rackgroup = RackGroup.objects.create(name='Rack Group 1', slug='rack-group-1', site=site) + rackrole = RackRole.objects.create(name='Rack Role 1', slug='rack-role-1') Rack.objects.bulk_create([ Rack(name='Rack 1', site=site), @@ -231,48 +207,44 @@ class RackTestCase(TestCase): Rack(name='Rack 3', site=site), ]) - def test_rack_list(self): - - url = reverse('dcim:rack_list') - params = { - "site": Site.objects.first().slug, + cls.form_data = { + 'name': 'Rack X', + 'facility_id': 'Facility X', + 'site': site.pk, + 'group': rackgroup.pk, + 'tenant': None, + 'status': RackStatusChoices.STATUS_PLANNED, + 'role': rackrole.pk, + 'serial': '123456', + 'asset_tag': 'ABCDEF', + 'type': RackTypeChoices.TYPE_CABINET, + 'width': RackWidthChoices.WIDTH_19IN, + 'u_height': 48, + 'desc_units': False, + 'outer_width': 500, + 'outer_depth': 500, + 'outer_unit': RackDimensionUnitChoices.UNIT_MILLIMETER, + 'comments': 'Some comments', + 'tags': 'Alpha,Bravo,Charlie', } - response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) - - def test_rack(self): - - rack = Rack.objects.first() - response = self.client.get(rack.get_absolute_url()) - self.assertEqual(response.status_code, 200) - - def test_rack_import(self): - - csv_data = ( + cls.csv_data = ( "site,name,width,u_height", "Site 1,Rack 4,19,42", "Site 1,Rack 5,19,42", "Site 1,Rack 6,19,42", ) - response = self.client.post(reverse('dcim:rack_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) - self.assertEqual(Rack.objects.count(), 6) +class ManufacturerTestCase(StandardTestCases.Views): + model = Manufacturer + # Disable inapplicable tests + test_get_object = None + test_delete_object = None -class ManufacturerTypeTestCase(TestCase): - - def setUp(self): - user = create_test_user( - permissions=[ - 'dcim.view_manufacturer', - 'dcim.add_manufacturer', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): Manufacturer.objects.bulk_create([ Manufacturer(name='Manufacturer 1', slug='manufacturer-1'), @@ -280,34 +252,24 @@ class ManufacturerTypeTestCase(TestCase): Manufacturer(name='Manufacturer 3', slug='manufacturer-3'), ]) - def test_manufacturer_list(self): + cls.form_data = { + 'name': 'Manufacturer X', + 'slug': 'manufacturer-x', + } - url = reverse('dcim:manufacturer_list') - - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - - def test_manufacturer_import(self): - - csv_data = ( + cls.csv_data = ( "name,slug", "Manufacturer 4,manufacturer-4", "Manufacturer 5,manufacturer-5", "Manufacturer 6,manufacturer-6", ) - response = self.client.post(reverse('dcim:manufacturer_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) - self.assertEqual(Manufacturer.objects.count(), 6) +class DeviceTypeTestCase(StandardTestCases.Views): + model = DeviceType - -class DeviceTypeTestCase(TestCase): - - def setUp(self): - user = create_test_user(permissions=['dcim.view_devicetype']) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): manufacturer = Manufacturer(name='Manufacturer 1', slug='manufacturer-1') manufacturer.save() @@ -318,35 +280,22 @@ class DeviceTypeTestCase(TestCase): DeviceType(model='Device Type 3', slug='device-type-3', manufacturer=manufacturer), ]) - def test_devicetype_list(self): - - url = reverse('dcim:devicetype_list') - params = { - "manufacturer": Manufacturer.objects.first().slug, + cls.form_data = { + 'manufacturer': manufacturer.pk, + 'model': 'Device Type X', + 'slug': 'device-type-x', + 'part_number': '123ABC', + 'u_height': 2, + 'is_full_depth': True, + 'subdevice_role': '', # CharField + 'comments': 'Some comments', + 'tags': 'Alpha,Bravo,Charlie', } - response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) - - def test_devicetype_export(self): - - url = reverse('dcim:devicetype_list') - - response = self.client.get('{}?export'.format(url)) - self.assertEqual(response.status_code, 200) - data = list(yaml.load_all(response.content, Loader=yaml.SafeLoader)) - self.assertEqual(len(data), 3) - self.assertEqual(data[0]['manufacturer'], 'Manufacturer 1') - self.assertEqual(data[0]['model'], 'Device Type 1') - - def test_devicetype(self): - - devicetype = DeviceType.objects.first() - response = self.client.get(devicetype.get_absolute_url()) - self.assertEqual(response.status_code, 200) - - def test_devicetype_import(self): - + def test_import_objects(self): + """ + Custom import test for YAML-based imports (versus CSV) + """ IMPORT_DATA = """ manufacturer: Generic model: TEST-1000 @@ -420,8 +369,8 @@ device-bays: # Create the manufacturer Manufacturer(name='Generic', slug='generic').save() - # Authenticate as user with necessary permissions - user = create_test_user(username='testuser2', permissions=[ + # Add all required permissions to the test user + self.add_permissions( 'dcim.view_devicetype', 'dcim.add_devicetype', 'dcim.add_consoleporttemplate', @@ -432,15 +381,14 @@ device-bays: 'dcim.add_frontporttemplate', 'dcim.add_rearporttemplate', 'dcim.add_devicebaytemplate', - ]) - self.client.force_login(user) + ) form_data = { 'data': IMPORT_DATA, 'format': 'yaml' } response = self.client.post(reverse('dcim:devicetype_import'), data=form_data, follow=True) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) dt = DeviceType.objects.get(model='TEST-1000') @@ -487,18 +435,28 @@ device-bays: db1 = DeviceBayTemplate.objects.first() self.assertEqual(db1.name, 'Device Bay 1') + def test_devicetype_export(self): -class DeviceRoleTestCase(TestCase): + url = reverse('dcim:devicetype_list') + self.add_permissions('dcim.view_devicetype') - def setUp(self): - user = create_test_user( - permissions=[ - 'dcim.view_devicerole', - 'dcim.add_devicerole', - ] - ) - self.client = Client() - self.client.force_login(user) + response = self.client.get('{}?export'.format(url)) + self.assertEqual(response.status_code, 200) + data = list(yaml.load_all(response.content, Loader=yaml.SafeLoader)) + self.assertEqual(len(data), 3) + self.assertEqual(data[0]['manufacturer'], 'Manufacturer 1') + self.assertEqual(data[0]['model'], 'Device Type 1') + + +class DeviceRoleTestCase(StandardTestCases.Views): + model = DeviceRole + + # Disable inapplicable tests + test_get_object = None + test_delete_object = None + + @classmethod + def setUpTestData(cls): DeviceRole.objects.bulk_create([ DeviceRole(name='Device Role 1', slug='device-role-1'), @@ -506,91 +464,68 @@ class DeviceRoleTestCase(TestCase): DeviceRole(name='Device Role 3', slug='device-role-3'), ]) - def test_devicerole_list(self): + cls.form_data = { + 'name': 'Devie Role X', + 'slug': 'device-role-x', + 'color': 'c0c0c0', + 'vm_role': False, + 'description': 'New device role', + } - url = reverse('dcim:devicerole_list') - - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - - def test_devicerole_import(self): - - csv_data = ( + cls.csv_data = ( "name,slug,color", "Device Role 4,device-role-4,ff0000", "Device Role 5,device-role-5,00ff00", "Device Role 6,device-role-6,0000ff", ) - response = self.client.post(reverse('dcim:devicerole_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) - self.assertEqual(DeviceRole.objects.count(), 6) +class PlatformTestCase(StandardTestCases.Views): + model = Platform + # Disable inapplicable tests + test_get_object = None + test_delete_object = None -class PlatformTestCase(TestCase): + @classmethod + def setUpTestData(cls): - def setUp(self): - user = create_test_user( - permissions=[ - 'dcim.view_platform', - 'dcim.add_platform', - ] - ) - self.client = Client() - self.client.force_login(user) + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') Platform.objects.bulk_create([ - Platform(name='Platform 1', slug='platform-1'), - Platform(name='Platform 2', slug='platform-2'), - Platform(name='Platform 3', slug='platform-3'), + Platform(name='Platform 1', slug='platform-1', manufacturer=manufacturer), + Platform(name='Platform 2', slug='platform-2', manufacturer=manufacturer), + Platform(name='Platform 3', slug='platform-3', manufacturer=manufacturer), ]) - def test_platform_list(self): + cls.form_data = { + 'name': 'Platform X', + 'slug': 'platform-x', + 'manufacturer': manufacturer.pk, + 'napalm_driver': 'junos', + 'napalm_args': None, + } - url = reverse('dcim:platform_list') - - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - - def test_platform_import(self): - - csv_data = ( + cls.csv_data = ( "name,slug", "Platform 4,platform-4", "Platform 5,platform-5", "Platform 6,platform-6", ) - response = self.client.post(reverse('dcim:platform_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) - self.assertEqual(Platform.objects.count(), 6) +class DeviceTestCase(StandardTestCases.Views): + model = Device + @classmethod + def setUpTestData(cls): -class DeviceTestCase(TestCase): - - def setUp(self): - user = create_test_user( - permissions=[ - 'dcim.view_device', - 'dcim.add_device', - ] - ) - self.client = Client() - self.client.force_login(user) - - site = Site(name='Site 1', slug='site-1') - site.save() - - manufacturer = Manufacturer(name='Manufacturer 1', slug='manufacturer-1') - manufacturer.save() - - devicetype = DeviceType(model='Device Type 1', manufacturer=manufacturer) - devicetype.save() - - devicerole = DeviceRole(name='Device Role 1', slug='device-role-1') - devicerole.save() + site = Site.objects.create(name='Site 1', slug='site-1') + rack = Rack.objects.create(name='Rack 1', site=site) + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + devicetype = DeviceType.objects.create(model='Device Type 1', manufacturer=manufacturer) + devicerole = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + platform = Platform.objects.create(name='Platform 1', slug='platform-1') Device.objects.bulk_create([ Device(name='Device 1', site=site, device_type=devicetype, device_role=devicerole), @@ -598,49 +533,46 @@ class DeviceTestCase(TestCase): Device(name='Device 3', site=site, device_type=devicetype, device_role=devicerole), ]) - def test_device_list(self): - - url = reverse('dcim:device_list') - params = { - "device_type_id": DeviceType.objects.first().pk, - "role": DeviceRole.objects.first().slug, + cls.form_data = { + 'device_type': devicetype.pk, + 'device_role': devicerole.pk, + 'tenant': None, + 'platform': platform.pk, + 'name': 'Device X', + 'serial': '123456', + 'asset_tag': 'ABCDEF', + 'site': site.pk, + 'rack': rack.pk, + 'position': 1, + 'face': DeviceFaceChoices.FACE_FRONT, + 'status': DeviceStatusChoices.STATUS_PLANNED, + 'primary_ip4': None, + 'primary_ip6': None, + 'cluster': None, + 'virtual_chassis': None, + 'vc_position': None, + 'vc_priority': None, + 'comments': 'A new device', + 'tags': 'Alpha,Bravo,Charlie', + 'local_context_data': None, } - response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) - - def test_device(self): - - device = Device.objects.first() - response = self.client.get(device.get_absolute_url()) - self.assertEqual(response.status_code, 200) - - def test_device_import(self): - - csv_data = ( + cls.csv_data = ( "device_role,manufacturer,model_name,status,site,name", "Device Role 1,Manufacturer 1,Device Type 1,Active,Site 1,Device 4", "Device Role 1,Manufacturer 1,Device Type 1,Active,Site 1,Device 5", "Device Role 1,Manufacturer 1,Device Type 1,Active,Site 1,Device 6", ) - response = self.client.post(reverse('dcim:device_import'), {'csv': '\n'.join(csv_data)}) - - self.assertEqual(response.status_code, 200) - self.assertEqual(Device.objects.count(), 6) - +# TODO: Convert to StandardTestCases.Views class ConsolePortTestCase(TestCase): + user_permissions = ( + 'dcim.view_consoleport', + ) - def setUp(self): - user = create_test_user( - permissions=[ - 'dcim.view_consoleport', - 'dcim.add_consoleport', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): site = Site(name='Site 1', slug='site-1') site.save() @@ -668,9 +600,10 @@ class ConsolePortTestCase(TestCase): url = reverse('dcim:consoleport_list') response = self.client.get(url) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_consoleport_import(self): + self.add_permissions('dcim.add_consoleport') csv_data = ( "device,name", @@ -681,21 +614,18 @@ class ConsolePortTestCase(TestCase): response = self.client.post(reverse('dcim:consoleport_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(ConsolePort.objects.count(), 6) +# TODO: Convert to StandardTestCases.Views class ConsoleServerPortTestCase(TestCase): + user_permissions = ( + 'dcim.view_consoleserverport', + ) - def setUp(self): - user = create_test_user( - permissions=[ - 'dcim.view_consoleserverport', - 'dcim.add_consoleserverport', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): site = Site(name='Site 1', slug='site-1') site.save() @@ -723,9 +653,10 @@ class ConsoleServerPortTestCase(TestCase): url = reverse('dcim:consoleserverport_list') response = self.client.get(url) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_consoleserverport_import(self): + self.add_permissions('dcim.add_consoleserverport') csv_data = ( "device,name", @@ -736,21 +667,18 @@ class ConsoleServerPortTestCase(TestCase): response = self.client.post(reverse('dcim:consoleserverport_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(ConsoleServerPort.objects.count(), 6) +# TODO: Convert to StandardTestCases.Views class PowerPortTestCase(TestCase): + user_permissions = ( + 'dcim.view_powerport', + ) - def setUp(self): - user = create_test_user( - permissions=[ - 'dcim.view_powerport', - 'dcim.add_powerport', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): site = Site(name='Site 1', slug='site-1') site.save() @@ -778,9 +706,10 @@ class PowerPortTestCase(TestCase): url = reverse('dcim:powerport_list') response = self.client.get(url) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_powerport_import(self): + self.add_permissions('dcim.add_powerport') csv_data = ( "device,name", @@ -791,21 +720,18 @@ class PowerPortTestCase(TestCase): response = self.client.post(reverse('dcim:powerport_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(PowerPort.objects.count(), 6) +# TODO: Convert to StandardTestCases.Views class PowerOutletTestCase(TestCase): + user_permissions = ( + 'dcim.view_poweroutlet', + ) - def setUp(self): - user = create_test_user( - permissions=[ - 'dcim.view_poweroutlet', - 'dcim.add_poweroutlet', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): site = Site(name='Site 1', slug='site-1') site.save() @@ -833,9 +759,10 @@ class PowerOutletTestCase(TestCase): url = reverse('dcim:poweroutlet_list') response = self.client.get(url) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_poweroutlet_import(self): + self.add_permissions('dcim.add_poweroutlet') csv_data = ( "device,name", @@ -846,21 +773,18 @@ class PowerOutletTestCase(TestCase): response = self.client.post(reverse('dcim:poweroutlet_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(PowerOutlet.objects.count(), 6) +# TODO: Convert to StandardTestCases.Views class InterfaceTestCase(TestCase): + user_permissions = ( + 'dcim.view_interface', + ) - def setUp(self): - user = create_test_user( - permissions=[ - 'dcim.view_interface', - 'dcim.add_interface', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): site = Site(name='Site 1', slug='site-1') site.save() @@ -888,9 +812,10 @@ class InterfaceTestCase(TestCase): url = reverse('dcim:interface_list') response = self.client.get(url) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_interface_import(self): + self.add_permissions('dcim.add_interface') csv_data = ( "device,name,type", @@ -901,21 +826,18 @@ class InterfaceTestCase(TestCase): response = self.client.post(reverse('dcim:interface_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(Interface.objects.count(), 6) +# TODO: Convert to StandardTestCases.Views class FrontPortTestCase(TestCase): + user_permissions = ( + 'dcim.view_frontport', + ) - def setUp(self): - user = create_test_user( - permissions=[ - 'dcim.view_frontport', - 'dcim.add_frontport', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): site = Site(name='Site 1', slug='site-1') site.save() @@ -955,9 +877,10 @@ class FrontPortTestCase(TestCase): url = reverse('dcim:frontport_list') response = self.client.get(url) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_frontport_import(self): + self.add_permissions('dcim.add_frontport') csv_data = ( "device,name,type,rear_port,rear_port_position", @@ -968,21 +891,18 @@ class FrontPortTestCase(TestCase): response = self.client.post(reverse('dcim:frontport_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(FrontPort.objects.count(), 6) +# TODO: Convert to StandardTestCases.Views class RearPortTestCase(TestCase): + user_permissions = ( + 'dcim.view_rearport', + ) - def setUp(self): - user = create_test_user( - permissions=[ - 'dcim.view_rearport', - 'dcim.add_rearport', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): site = Site(name='Site 1', slug='site-1') site.save() @@ -1010,9 +930,10 @@ class RearPortTestCase(TestCase): url = reverse('dcim:rearport_list') response = self.client.get(url) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_rearport_import(self): + self.add_permissions('dcim.add_rearport') csv_data = ( "device,name,type,positions", @@ -1023,21 +944,18 @@ class RearPortTestCase(TestCase): response = self.client.post(reverse('dcim:rearport_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(RearPort.objects.count(), 6) +# TODO: Convert to StandardTestCases.Views class DeviceBayTestCase(TestCase): + user_permissions = ( + 'dcim.view_devicebay', + ) - def setUp(self): - user = create_test_user( - permissions=[ - 'dcim.view_devicebay', - 'dcim.add_devicebay', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): site = Site(name='Site 1', slug='site-1') site.save() @@ -1069,9 +987,10 @@ class DeviceBayTestCase(TestCase): url = reverse('dcim:devicebay_list') response = self.client.get(url) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_devicebay_import(self): + self.add_permissions('dcim.add_devicebay') csv_data = ( "device,name", @@ -1082,21 +1001,18 @@ class DeviceBayTestCase(TestCase): response = self.client.post(reverse('dcim:devicebay_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(DeviceBay.objects.count(), 6) +# TODO: Convert to StandardTestCases.Views class InventoryItemTestCase(TestCase): + user_permissions = ( + 'dcim.view_inventoryitem', + ) - def setUp(self): - user = create_test_user( - permissions=[ - 'dcim.view_inventoryitem', - 'dcim.add_inventoryitem', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): site = Site(name='Site 1', slug='site-1') site.save() @@ -1127,9 +1043,10 @@ class InventoryItemTestCase(TestCase): } response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_inventoryitem_import(self): + self.add_permissions('dcim.add_inventoryitem') csv_data = ( "device,name", @@ -1140,34 +1057,23 @@ class InventoryItemTestCase(TestCase): response = self.client.post(reverse('dcim:inventoryitem_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(InventoryItem.objects.count(), 6) -class CableTestCase(TestCase): +class CableTestCase(StandardTestCases.Views): + model = Cable - def setUp(self): - user = create_test_user( - permissions=[ - 'dcim.view_cable', - 'dcim.add_cable', - ] - ) - self.client = Client() - self.client.force_login(user) + # TODO: Creation URL needs termination context + test_create_object = None - site = Site(name='Site 1', slug='site-1') - site.save() - - manufacturer = Manufacturer(name='Manufacturer 1', slug='manufacturer-1') - manufacturer.save() - - devicetype = DeviceType(model='Device Type 1', manufacturer=manufacturer) - devicetype.save() - - devicerole = DeviceRole(name='Device Role 1', slug='device-role-1') - devicerole.save() + @classmethod + def setUpTestData(cls): + site = Site.objects.create(name='Site 1', slug='site-1') + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + devicetype = DeviceType.objects.create(model='Device Type 1', manufacturer=manufacturer) + devicerole = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') device1 = Device(name='Device 1', site=site, device_type=devicetype, device_role=devicerole) device1.save() device2 = Device(name='Device 2', site=site, device_type=devicetype, device_role=devicerole) @@ -1177,68 +1083,62 @@ class CableTestCase(TestCase): device4 = Device(name='Device 4', site=site, device_type=devicetype, device_role=devicerole) device4.save() - iface1 = Interface(device=device1, name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED) - iface1.save() - iface2 = Interface(device=device1, name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED) - iface2.save() - iface3 = Interface(device=device1, name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED) - iface3.save() - iface4 = Interface(device=device2, name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED) - iface4.save() - iface5 = Interface(device=device2, name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED) - iface5.save() - iface6 = Interface(device=device2, name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED) - iface6.save() + interfaces = ( + Interface(device=device1, name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=device1, name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=device1, name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=device2, name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=device2, name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=device2, name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=device3, name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=device3, name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=device3, name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=device4, name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=device4, name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=device4, name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + ) + Interface.objects.bulk_create(interfaces) - # Interfaces for CSV import testing - Interface(device=device3, name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED).save() - Interface(device=device3, name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED).save() - Interface(device=device3, name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED).save() - Interface(device=device4, name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED).save() - Interface(device=device4, name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED).save() - Interface(device=device4, name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED).save() + Cable(termination_a=interfaces[0], termination_b=interfaces[3], type=CableTypeChoices.TYPE_CAT6).save() + Cable(termination_a=interfaces[1], termination_b=interfaces[4], type=CableTypeChoices.TYPE_CAT6).save() + Cable(termination_a=interfaces[2], termination_b=interfaces[5], type=CableTypeChoices.TYPE_CAT6).save() - Cable(termination_a=iface1, termination_b=iface4, type=CableTypeChoices.TYPE_CAT6).save() - Cable(termination_a=iface2, termination_b=iface5, type=CableTypeChoices.TYPE_CAT6).save() - Cable(termination_a=iface3, termination_b=iface6, type=CableTypeChoices.TYPE_CAT6).save() - - def test_cable_list(self): - - url = reverse('dcim:cable_list') - params = { - "type": CableTypeChoices.TYPE_CAT6, + interface_ct = ContentType.objects.get_for_model(Interface) + cls.form_data = { + # Changing terminations not supported when editing an existing Cable + 'termination_a_type': interface_ct.pk, + 'termination_a_id': interfaces[0].pk, + 'termination_b_type': interface_ct.pk, + 'termination_b_id': interfaces[3].pk, + 'type': CableTypeChoices.TYPE_CAT6, + 'status': CableStatusChoices.STATUS_PLANNED, + 'label': 'New cable', + 'color': 'c0c0c0', + 'length': 100, + 'length_unit': CableLengthUnitChoices.UNIT_FOOT, } - response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) - - def test_cable(self): - - cable = Cable.objects.first() - response = self.client.get(cable.get_absolute_url()) - self.assertEqual(response.status_code, 200) - - def test_cable_import(self): - - csv_data = ( + cls.csv_data = ( "side_a_device,side_a_type,side_a_name,side_b_device,side_b_type,side_b_name", "Device 3,interface,Interface 1,Device 4,interface,Interface 1", "Device 3,interface,Interface 2,Device 4,interface,Interface 2", "Device 3,interface,Interface 3,Device 4,interface,Interface 3", ) - response = self.client.post(reverse('dcim:cable_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) - self.assertEqual(Cable.objects.count(), 6) +class VirtualChassisTestCase(StandardTestCases.Views): + model = VirtualChassis + # Disable inapplicable tests + test_get_object = None + test_import_objects = None -class VirtualChassisTestCase(TestCase): + # TODO: Requires special form handling + test_create_object = None + test_edit_object = None - def setUp(self): - user = create_test_user(permissions=['dcim.view_virtualchassis']) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): site = Site.objects.create(name='Site 1', slug='site-1') manufacturer = Manufacturer.objects.create(name='Manufacturer', slug='manufacturer-1') @@ -1276,10 +1176,3 @@ class VirtualChassisTestCase(TestCase): Device.objects.filter(pk=device4.pk).update(virtual_chassis=vc2, vc_position=2) vc3 = VirtualChassis.objects.create(master=device5, domain='test-domain-3') Device.objects.filter(pk=device6.pk).update(virtual_chassis=vc3, vc_position=2) - - def test_virtualchassis_list(self): - - url = reverse('dcim:virtualchassis_list') - - response = self.client.get(url) - self.assertEqual(response.status_code, 200) diff --git a/netbox/extras/tests/test_views.py b/netbox/extras/tests/test_views.py index 792390121..0b9a0ffdf 100644 --- a/netbox/extras/tests/test_views.py +++ b/netbox/extras/tests/test_views.py @@ -2,48 +2,55 @@ import urllib.parse import uuid from django.contrib.auth.models import User -from django.test import Client, TestCase 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 create_test_user +from utilities.testing import StandardTestCases, TestCase -class TagTestCase(TestCase): +class TagTestCase(StandardTestCases.Views): + model = Tag - def setUp(self): - user = create_test_user(permissions=['extras.view_tag']) - self.client = Client() - self.client.force_login(user) + # Disable inapplicable tests + test_create_object = None + test_import_objects = None - Tag.objects.bulk_create([ + # TODO: Restore test when #4071 is resolved + test_get_object = None + + @classmethod + def setUpTestData(cls): + + Tag.objects.bulk_create(( Tag(name='Tag 1', slug='tag-1'), Tag(name='Tag 2', slug='tag-2'), Tag(name='Tag 3', slug='tag-3'), - ]) + )) - def test_tag_list(self): - - url = reverse('extras:tag_list') - params = { - "q": "tag", + cls.form_data = { + 'name': 'Tag X', + 'slug': 'tag-x', + 'color': 'c0c0c0', + 'comments': 'Some comments', } - response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) +class ConfigContextTestCase(StandardTestCases.Views): + model = ConfigContext -class ConfigContextTestCase(TestCase): + # Disable inapplicable tests + test_import_objects = None - def setUp(self): - user = create_test_user(permissions=['extras.view_configcontext']) - self.client = Client() - self.client.force_login(user) + # TODO: Resolve model discrepancies when creating/editing ConfigContexts + test_create_object = None + test_edit_object = None - site = Site(name='Site 1', slug='site-1') - site.save() + @classmethod + def setUpTestData(cls): + + site = Site.objects.create(name='Site 1', slug='site-1') # Create three ConfigContexts for i in range(1, 4): @@ -54,34 +61,35 @@ class ConfigContextTestCase(TestCase): configcontext.save() configcontext.sites.add(site) - def test_configcontext_list(self): - - url = reverse('extras:configcontext_list') - params = { - "q": "foo", + cls.form_data = { + 'name': 'Config Context X', + 'weight': 200, + 'description': 'A new config context', + 'is_active': True, + 'regions': [], + 'sites': [site.pk], + 'roles': [], + 'platforms': [], + 'tenant_groups': [], + 'tenants': [], + 'tags': [], + 'data': '{"foo": 123}', } - response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) - - def test_configcontext(self): - - configcontext = ConfigContext.objects.first() - response = self.client.get(configcontext.get_absolute_url()) - self.assertEqual(response.status_code, 200) - class ObjectChangeTestCase(TestCase): + user_permissions = ( + 'extras.view_objectchange', + ) - def setUp(self): - user = create_test_user(permissions=['extras.view_objectchange']) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): site = Site(name='Site 1', slug='site-1') site.save() # Create three ObjectChanges + user = User.objects.create_user(username='testuser2') for i in range(1, 4): oc = site.to_objectchange(action=ObjectChangeActionChoices.ACTION_UPDATE) oc.user = user @@ -96,10 +104,10 @@ class ObjectChangeTestCase(TestCase): } response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_objectchange(self): objectchange = ObjectChange.objects.first() response = self.client.get(objectchange.get_absolute_url()) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) diff --git a/netbox/ipam/tests/test_views.py b/netbox/ipam/tests/test_views.py index 6f08f2d47..db8326fbd 100644 --- a/netbox/ipam/tests/test_views.py +++ b/netbox/ipam/tests/test_views.py @@ -1,26 +1,18 @@ -from netaddr import IPNetwork -import urllib.parse +import datetime -from django.test import Client, TestCase -from django.urls import reverse +from netaddr import IPNetwork from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site -from ipam.choices import ServiceProtocolChoices +from ipam.choices import * from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF -from utilities.testing import create_test_user +from utilities.testing import StandardTestCases -class VRFTestCase(TestCase): +class VRFTestCase(StandardTestCases.Views): + model = VRF - def setUp(self): - user = create_test_user( - permissions=[ - 'ipam.view_vrf', - 'ipam.add_vrf', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): VRF.objects.bulk_create([ VRF(name='VRF 1', rd='65000:1'), @@ -28,48 +20,32 @@ class VRFTestCase(TestCase): VRF(name='VRF 3', rd='65000:3'), ]) - def test_vrf_list(self): - - url = reverse('ipam:vrf_list') - params = { - "q": "65000", + cls.form_data = { + 'name': 'VRF X', + 'rd': '65000:999', + 'tenant': None, + 'enforce_unique': True, + 'description': 'A new VRF', + 'tags': 'Alpha,Bravo,Charlie', } - response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) - - def test_vrf(self): - - vrf = VRF.objects.first() - response = self.client.get(vrf.get_absolute_url()) - self.assertEqual(response.status_code, 200) - - def test_vrf_import(self): - - csv_data = ( + cls.csv_data = ( "name", "VRF 4", "VRF 5", "VRF 6", ) - response = self.client.post(reverse('ipam:vrf_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) - self.assertEqual(VRF.objects.count(), 6) +class RIRTestCase(StandardTestCases.Views): + model = RIR + # Disable inapplicable tests + test_get_object = None + test_delete_object = None -class RIRTestCase(TestCase): - - def setUp(self): - user = create_test_user( - permissions=[ - 'ipam.view_rir', - 'ipam.add_rir', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): RIR.objects.bulk_create([ RIR(name='RIR 1', slug='rir-1'), @@ -77,42 +53,27 @@ class RIRTestCase(TestCase): RIR(name='RIR 3', slug='rir-3'), ]) - def test_rir_list(self): + cls.form_data = { + 'name': 'RIR X', + 'slug': 'rir-x', + 'is_private': True, + } - url = reverse('ipam:rir_list') - - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - - def test_rir_import(self): - - csv_data = ( + cls.csv_data = ( "name,slug", "RIR 4,rir-4", "RIR 5,rir-5", "RIR 6,rir-6", ) - response = self.client.post(reverse('ipam:rir_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) - self.assertEqual(RIR.objects.count(), 6) +class AggregateTestCase(StandardTestCases.Views): + model = Aggregate + @classmethod + def setUpTestData(cls): -class AggregateTestCase(TestCase): - - def setUp(self): - user = create_test_user( - permissions=[ - 'ipam.view_aggregate', - 'ipam.add_aggregate', - ] - ) - self.client = Client() - self.client.force_login(user) - - rir = RIR(name='RIR 1', slug='rir-1') - rir.save() + rir = RIR.objects.create(name='RIR 1', slug='rir-1') Aggregate.objects.bulk_create([ Aggregate(family=4, prefix=IPNetwork('10.1.0.0/16'), rir=rir), @@ -120,48 +81,32 @@ class AggregateTestCase(TestCase): Aggregate(family=4, prefix=IPNetwork('10.3.0.0/16'), rir=rir), ]) - def test_aggregate_list(self): - - url = reverse('ipam:aggregate_list') - params = { - "rir": RIR.objects.first().slug, + cls.form_data = { + 'family': 4, + 'prefix': IPNetwork('10.99.0.0/16'), + 'rir': rir.pk, + 'date_added': datetime.date(2020, 1, 1), + 'description': 'A new aggregate', + 'tags': 'Alpha,Bravo,Charlie', } - response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) - - def test_aggregate(self): - - aggregate = Aggregate.objects.first() - response = self.client.get(aggregate.get_absolute_url()) - self.assertEqual(response.status_code, 200) - - def test_aggregate_import(self): - - csv_data = ( + cls.csv_data = ( "prefix,rir", "10.4.0.0/16,RIR 1", "10.5.0.0/16,RIR 1", "10.6.0.0/16,RIR 1", ) - response = self.client.post(reverse('ipam:aggregate_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) - self.assertEqual(Aggregate.objects.count(), 6) +class RoleTestCase(StandardTestCases.Views): + model = Role + # Disable inapplicable tests + test_get_object = None + test_delete_object = None -class RoleTestCase(TestCase): - - def setUp(self): - user = create_test_user( - permissions=[ - 'ipam.view_role', - 'ipam.add_role', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): Role.objects.bulk_create([ Role(name='Role 1', slug='role-1'), @@ -169,42 +114,31 @@ class RoleTestCase(TestCase): Role(name='Role 3', slug='role-3'), ]) - def test_role_list(self): + cls.form_data = { + 'name': 'Role X', + 'slug': 'role-x', + 'weight': 200, + 'description': 'A new role', + } - url = reverse('ipam:role_list') - - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - - def test_role_import(self): - - csv_data = ( + cls.csv_data = ( "name,slug,weight", "Role 4,role-4,1000", "Role 5,role-5,1000", "Role 6,role-6,1000", ) - response = self.client.post(reverse('ipam:role_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) - self.assertEqual(Role.objects.count(), 6) +class PrefixTestCase(StandardTestCases.Views): + model = Prefix + @classmethod + def setUpTestData(cls): -class PrefixTestCase(TestCase): - - def setUp(self): - user = create_test_user( - permissions=[ - 'ipam.view_prefix', - 'ipam.add_prefix', - ] - ) - self.client = Client() - self.client.force_login(user) - - site = Site(name='Site 1', slug='site-1') - site.save() + site = Site.objects.create(name='Site 1', slug='site-1') + vrf = VRF.objects.create(name='VRF 1', rd='65000:1') + role = Role.objects.create(name='Role 1', slug='role-1') + # vlan = VLAN.objects.create(vid=123, name='VLAN 123') Prefix.objects.bulk_create([ Prefix(family=4, prefix=IPNetwork('10.1.0.0/16'), site=site), @@ -212,51 +146,34 @@ class PrefixTestCase(TestCase): Prefix(family=4, prefix=IPNetwork('10.3.0.0/16'), site=site), ]) - def test_prefix_list(self): - - url = reverse('ipam:prefix_list') - params = { - "site": Site.objects.first().slug, + cls.form_data = { + 'prefix': IPNetwork('192.0.2.0/24'), + 'site': site.pk, + 'vrf': vrf.pk, + 'tenant': None, + 'vlan': None, + 'status': PrefixStatusChoices.STATUS_RESERVED, + 'role': role.pk, + 'is_pool': True, + 'description': 'A new prefix', + 'tags': 'Alpha,Bravo,Charlie', } - response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) - - def test_prefix(self): - - prefix = Prefix.objects.first() - response = self.client.get(prefix.get_absolute_url()) - self.assertEqual(response.status_code, 200) - - def test_prefix_import(self): - - csv_data = ( + cls.csv_data = ( "prefix,status", "10.4.0.0/16,Active", "10.5.0.0/16,Active", "10.6.0.0/16,Active", ) - response = self.client.post(reverse('ipam:prefix_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) - self.assertEqual(Prefix.objects.count(), 6) +class IPAddressTestCase(StandardTestCases.Views): + model = IPAddress + @classmethod + def setUpTestData(cls): -class IPAddressTestCase(TestCase): - - def setUp(self): - user = create_test_user( - permissions=[ - 'ipam.view_ipaddress', - 'ipam.add_ipaddress', - ] - ) - self.client = Client() - self.client.force_login(user) - - vrf = VRF(name='VRF 1', rd='65000:1') - vrf.save() + vrf = VRF.objects.create(name='VRF 1', rd='65000:1') IPAddress.objects.bulk_create([ IPAddress(family=4, address=IPNetwork('192.0.2.1/24'), vrf=vrf), @@ -264,51 +181,38 @@ class IPAddressTestCase(TestCase): IPAddress(family=4, address=IPNetwork('192.0.2.3/24'), vrf=vrf), ]) - def test_ipaddress_list(self): - - url = reverse('ipam:ipaddress_list') - params = { - "vrf": VRF.objects.first().rd, + cls.form_data = { + 'vrf': vrf.pk, + 'address': IPNetwork('192.0.2.99/24'), + 'tenant': None, + 'status': IPAddressStatusChoices.STATUS_RESERVED, + 'role': IPAddressRoleChoices.ROLE_ANYCAST, + 'interface': None, + 'nat_inside': None, + 'dns_name': 'example', + 'description': 'A new IP address', + 'tags': 'Alpha,Bravo,Charlie', } - response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) - - def test_ipaddress(self): - - ipaddress = IPAddress.objects.first() - response = self.client.get(ipaddress.get_absolute_url()) - self.assertEqual(response.status_code, 200) - - def test_ipaddress_import(self): - - csv_data = ( + cls.csv_data = ( "address,status", "192.0.2.4/24,Active", "192.0.2.5/24,Active", "192.0.2.6/24,Active", ) - response = self.client.post(reverse('ipam:ipaddress_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) - self.assertEqual(IPAddress.objects.count(), 6) +class VLANGroupTestCase(StandardTestCases.Views): + model = VLANGroup + # Disable inapplicable tests + test_get_object = None + test_delete_object = None -class VLANGroupTestCase(TestCase): + @classmethod + def setUpTestData(cls): - def setUp(self): - user = create_test_user( - permissions=[ - 'ipam.view_vlangroup', - 'ipam.add_vlangroup', - ] - ) - self.client = Client() - self.client.force_login(user) - - site = Site(name='Site 1', slug='site-1') - site.save() + site = Site.objects.create(name='Site 1', slug='site-1') VLANGroup.objects.bulk_create([ VLANGroup(name='VLAN Group 1', slug='vlan-group-1', site=site), @@ -316,45 +220,29 @@ class VLANGroupTestCase(TestCase): VLANGroup(name='VLAN Group 3', slug='vlan-group-3', site=site), ]) - def test_vlangroup_list(self): - - url = reverse('ipam:vlangroup_list') - params = { - "site": Site.objects.first().slug, + cls.form_data = { + 'name': 'VLAN Group X', + 'slug': 'vlan-group-x', + 'site': site.pk, } - response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) - - def test_vlangroup_import(self): - - csv_data = ( + cls.csv_data = ( "name,slug", "VLAN Group 4,vlan-group-4", "VLAN Group 5,vlan-group-5", "VLAN Group 6,vlan-group-6", ) - response = self.client.post(reverse('ipam:vlangroup_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) - self.assertEqual(VLANGroup.objects.count(), 6) +class VLANTestCase(StandardTestCases.Views): + model = VLAN + @classmethod + def setUpTestData(cls): -class VLANTestCase(TestCase): - - def setUp(self): - user = create_test_user( - permissions=[ - 'ipam.view_vlan', - 'ipam.add_vlan', - ] - ) - self.client = Client() - self.client.force_login(user) - - vlangroup = VLANGroup(name='VLAN Group 1', slug='vlan-group-1') - vlangroup.save() + site = Site.objects.create(name='Site 1', slug='site-1') + vlangroup = VLANGroup.objects.create(name='VLAN Group 1', slug='vlan-group-1', site=site) + role = Role.objects.create(name='Role 1', slug='role-1') VLAN.objects.bulk_create([ VLAN(group=vlangroup, vid=101, name='VLAN101'), @@ -362,58 +250,43 @@ class VLANTestCase(TestCase): VLAN(group=vlangroup, vid=103, name='VLAN103'), ]) - def test_vlan_list(self): - - url = reverse('ipam:vlan_list') - params = { - "group": VLANGroup.objects.first().slug, + cls.form_data = { + 'site': site.pk, + 'group': vlangroup.pk, + 'vid': 999, + 'name': 'VLAN999', + 'tenant': None, + 'status': VLANStatusChoices.STATUS_RESERVED, + 'role': role.pk, + 'description': 'A new VLAN', + 'tags': 'Alpha,Bravo,Charlie', } - response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) - - def test_vlan(self): - - vlan = VLAN.objects.first() - response = self.client.get(vlan.get_absolute_url()) - self.assertEqual(response.status_code, 200) - - def test_vlan_import(self): - - csv_data = ( + cls.csv_data = ( "vid,name,status", "104,VLAN104,Active", "105,VLAN105,Active", "106,VLAN106,Active", ) - response = self.client.post(reverse('ipam:vlan_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) - self.assertEqual(VLAN.objects.count(), 6) +class ServiceTestCase(StandardTestCases.Views): + model = Service + # Disable inapplicable tests + test_import_objects = None -class ServiceTestCase(TestCase): + # TODO: Resolve URL for Service creation + test_create_object = None - def setUp(self): - user = create_test_user(permissions=['ipam.view_service']) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): - site = Site(name='Site 1', slug='site-1') - site.save() - - manufacturer = Manufacturer(name='Manufacturer 1', slug='manufacturer-1') - manufacturer.save() - - devicetype = DeviceType(manufacturer=manufacturer, model='Device Type 1') - devicetype.save() - - devicerole = DeviceRole(name='Device Role 1', slug='device-role-1') - devicerole.save() - - device = Device(name='Device 1', site=site, device_type=devicetype, device_role=devicerole) - device.save() + site = Site.objects.create(name='Site 1', slug='site-1') + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1') + devicerole = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + device = Device.objects.create(name='Device 1', site=site, device_type=devicetype, device_role=devicerole) Service.objects.bulk_create([ Service(device=device, name='Service 1', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=101), @@ -421,18 +294,13 @@ class ServiceTestCase(TestCase): Service(device=device, name='Service 3', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=103), ]) - def test_service_list(self): - - url = reverse('ipam:service_list') - params = { - "device_id": Device.objects.first(), + cls.form_data = { + 'device': device.pk, + 'virtual_machine': None, + 'name': 'Service X', + 'protocol': ServiceProtocolChoices.PROTOCOL_TCP, + 'port': 999, + 'ipaddresses': [], + 'description': 'A new service', + 'tags': 'Alpha,Bravo,Charlie', } - - response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) - - def test_service(self): - - service = Service.objects.first() - response = self.client.get(service.get_absolute_url()) - self.assertEqual(response.status_code, 200) diff --git a/netbox/netbox/tests/test_views.py b/netbox/netbox/tests/test_views.py index db84dcd1a..1942471b0 100644 --- a/netbox/netbox/tests/test_views.py +++ b/netbox/netbox/tests/test_views.py @@ -1,6 +1,6 @@ import urllib.parse -from django.test import TestCase +from utilities.testing import TestCase from django.urls import reverse @@ -11,7 +11,7 @@ class HomeViewTestCase(TestCase): url = reverse('home') response = self.client.get(url) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_search(self): @@ -21,4 +21,4 @@ class HomeViewTestCase(TestCase): } response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) diff --git a/netbox/secrets/tests/test_views.py b/netbox/secrets/tests/test_views.py index 43ae10dc6..1da689e53 100644 --- a/netbox/secrets/tests/test_views.py +++ b/netbox/secrets/tests/test_views.py @@ -1,26 +1,22 @@ import base64 -import urllib.parse -from django.test import Client, TestCase 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 create_test_user +from utilities.testing import StandardTestCases from .constants import PRIVATE_KEY, PUBLIC_KEY -class SecretRoleTestCase(TestCase): +class SecretRoleTestCase(StandardTestCases.Views): + model = SecretRole - def setUp(self): - user = create_test_user( - permissions=[ - 'secrets.view_secretrole', - 'secrets.add_secretrole', - ] - ) - self.client = Client() - self.client.force_login(user) + # Disable inapplicable tests + test_get_object = None + test_delete_object = None + + @classmethod + def setUpTestData(cls): SecretRole.objects.bulk_create([ SecretRole(name='Secret Role 1', slug='secret-role-1'), @@ -28,65 +24,40 @@ class SecretRoleTestCase(TestCase): SecretRole(name='Secret Role 3', slug='secret-role-3'), ]) - def test_secretrole_list(self): + cls.form_data = { + 'name': 'Secret Role X', + 'slug': 'secret-role-x', + 'description': 'A secret role', + 'users': [], + 'groups': [], + } - url = reverse('secrets:secretrole_list') - - response = self.client.get(url, follow=True) - self.assertEqual(response.status_code, 200) - - def test_secretrole_import(self): - - csv_data = ( + cls.csv_data = ( "name,slug", "Secret Role 4,secret-role-4", "Secret Role 5,secret-role-5", "Secret Role 6,secret-role-6", ) - response = self.client.post(reverse('secrets:secretrole_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) - self.assertEqual(SecretRole.objects.count(), 6) +class SecretTestCase(StandardTestCases.Views): + model = Secret + # Disable inapplicable tests + test_create_object = None -class SecretTestCase(TestCase): + # TODO: Check permissions enforcement on secrets.views.secret_edit + test_edit_object = None - def setUp(self): - user = create_test_user( - permissions=[ - 'secrets.view_secret', - 'secrets.add_secret', - ] - ) + @classmethod + def setUpTestData(cls): - # Set up a master key - userkey = UserKey(user=user, public_key=PUBLIC_KEY) - userkey.save() - master_key = userkey.get_master_key(PRIVATE_KEY) - self.session_key = SessionKey(userkey=userkey) - self.session_key.save(master_key) - - self.client = Client() - self.client.force_login(user) - - site = Site(name='Site 1', slug='site-1') - site.save() - - manufacturer = Manufacturer(name='Manufacturer 1', slug='manufacturer-1') - manufacturer.save() - - devicetype = DeviceType(manufacturer=manufacturer, model='Device Type 1') - devicetype.save() - - devicerole = DeviceRole(name='Device Role 1', slug='device-role-1') - devicerole.save() - - device = Device(name='Device 1', site=site, device_type=devicetype, device_role=devicerole) - device.save() - - secretrole = SecretRole(name='Secret Role 1', slug='secret-role-1') - secretrole.save() + site = Site.objects.create(name='Site 1', slug='site-1') + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1') + devicerole = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + device = Device.objects.create(name='Device 1', site=site, device_type=devicetype, device_role=devicerole) + secretrole = SecretRole.objects.create(name='Secret Role 1', slug='secret-role-1') Secret.objects.bulk_create([ Secret(device=device, role=secretrole, name='Secret 1', ciphertext=b'1234567890'), @@ -94,23 +65,25 @@ class SecretTestCase(TestCase): Secret(device=device, role=secretrole, name='Secret 3', ciphertext=b'1234567890'), ]) - def test_secret_list(self): - - url = reverse('secrets:secret_list') - params = { - "role": SecretRole.objects.first().slug, + cls.form_data = { + 'device': device.pk, + 'role': secretrole.pk, + 'name': 'Secret X', } - response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)), follow=True) - self.assertEqual(response.status_code, 200) + def setUp(self): - def test_secret(self): + super().setUp() - secret = Secret.objects.first() - response = self.client.get(secret.get_absolute_url(), follow=True) - self.assertEqual(response.status_code, 200) + # Set up a master key for the test user + userkey = UserKey(user=self.user, public_key=PUBLIC_KEY) + userkey.save() + master_key = userkey.get_master_key(PRIVATE_KEY) + self.session_key = SessionKey(userkey=userkey) + self.session_key.save(master_key) - def test_secret_import(self): + def test_import_objects(self): + self.add_permissions('secrets.add_secret') csv_data = ( "device,role,name,plaintext", @@ -125,5 +98,5 @@ class SecretTestCase(TestCase): response = self.client.post(reverse('secrets:secret_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(Secret.objects.count(), 6) diff --git a/netbox/tenancy/tests/test_views.py b/netbox/tenancy/tests/test_views.py index 10ee354d4..1825a4ff9 100644 --- a/netbox/tenancy/tests/test_views.py +++ b/netbox/tenancy/tests/test_views.py @@ -1,23 +1,16 @@ -import urllib.parse - -from django.test import Client, TestCase -from django.urls import reverse - from tenancy.models import Tenant, TenantGroup -from utilities.testing import create_test_user +from utilities.testing import StandardTestCases -class TenantGroupTestCase(TestCase): +class TenantGroupTestCase(StandardTestCases.Views): + model = TenantGroup - def setUp(self): - user = create_test_user( - permissions=[ - 'tenancy.view_tenantgroup', - 'tenancy.add_tenantgroup', - ] - ) - self.client = Client() - self.client.force_login(user) + # Disable inapplicable tests + test_get_object = None + test_delete_object = None + + @classmethod + def setUpTestData(cls): TenantGroup.objects.bulk_create([ TenantGroup(name='Tenant Group 1', slug='tenant-group-1'), @@ -25,42 +18,26 @@ class TenantGroupTestCase(TestCase): TenantGroup(name='Tenant Group 3', slug='tenant-group-3'), ]) - def test_tenantgroup_list(self): + cls.form_data = { + 'name': 'Tenant Group X', + 'slug': 'tenant-group-x', + } - url = reverse('tenancy:tenantgroup_list') - - response = self.client.get(url, follow=True) - self.assertEqual(response.status_code, 200) - - def test_tenantgroup_import(self): - - csv_data = ( + cls.csv_data = ( "name,slug", "Tenant Group 4,tenant-group-4", "Tenant Group 5,tenant-group-5", "Tenant Group 6,tenant-group-6", ) - response = self.client.post(reverse('tenancy:tenantgroup_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) - self.assertEqual(TenantGroup.objects.count(), 6) +class TenantTestCase(StandardTestCases.Views): + model = Tenant + @classmethod + def setUpTestData(cls): -class TenantTestCase(TestCase): - - def setUp(self): - user = create_test_user( - permissions=[ - 'tenancy.view_tenant', - 'tenancy.add_tenant', - ] - ) - self.client = Client() - self.client.force_login(user) - - tenantgroup = TenantGroup(name='Tenant Group 1', slug='tenant-group-1') - tenantgroup.save() + tenantgroup = TenantGroup.objects.create(name='Tenant Group 1', slug='tenant-group-1') Tenant.objects.bulk_create([ Tenant(name='Tenant 1', slug='tenant-1', group=tenantgroup), @@ -68,32 +45,18 @@ class TenantTestCase(TestCase): Tenant(name='Tenant 3', slug='tenant-3', group=tenantgroup), ]) - def test_tenant_list(self): - - url = reverse('tenancy:tenant_list') - params = { - "group": TenantGroup.objects.first().slug, + cls.form_data = { + 'name': 'Tenant X', + 'slug': 'tenant-x', + 'group': tenantgroup.pk, + 'description': 'A new tenant', + 'comments': 'Some comments', + 'tags': 'Alpha,Bravo,Charlie', } - response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)), follow=True) - self.assertEqual(response.status_code, 200) - - def test_tenant(self): - - tenant = Tenant.objects.first() - response = self.client.get(tenant.get_absolute_url(), follow=True) - self.assertEqual(response.status_code, 200) - - def test_tenant_import(self): - - csv_data = ( + cls.csv_data = ( "name,slug", "Tenant 4,tenant-4", "Tenant 5,tenant-5", "Tenant 6,tenant-6", ) - - response = self.client.post(reverse('tenancy:tenant_import'), {'csv': '\n'.join(csv_data)}) - - self.assertEqual(response.status_code, 200) - self.assertEqual(Tenant.objects.count(), 6) diff --git a/netbox/utilities/testing.py b/netbox/utilities/testing.py deleted file mode 100644 index 791eb64cb..000000000 --- a/netbox/utilities/testing.py +++ /dev/null @@ -1,79 +0,0 @@ -import logging -from contextlib import contextmanager - -from django.contrib.auth.models import Permission, User -from rest_framework.test import APITestCase as _APITestCase - -from users.models import Token - - -class APITestCase(_APITestCase): - - def setUp(self): - """ - Create a superuser and token for API calls. - """ - self.user = User.objects.create(username='testuser', is_superuser=True) - self.token = Token.objects.create(user=self.user) - self.header = {'HTTP_AUTHORIZATION': 'Token {}'.format(self.token.key)} - - def assertHttpStatus(self, response, expected_status): - """ - Provide more detail in the event of an unexpected HTTP response. - """ - err_message = "Expected HTTP status {}; received {}: {}" - self.assertEqual(response.status_code, expected_status, err_message.format( - expected_status, response.status_code, getattr(response, 'data', 'No data') - )) - - -def create_test_user(username='testuser', permissions=list()): - """ - Create a User with the given permissions. - """ - user = User.objects.create_user(username=username) - for perm_name in permissions: - app, codename = perm_name.split('.') - perm = Permission.objects.get(content_type__app_label=app, codename=codename) - user.user_permissions.add(perm) - - return user - - -def choices_to_dict(choices_list): - """ - Convert a list of field choices to a dictionary suitable for direct comparison with a ChoiceSet. For example: - - [ - { - "value": "choice-1", - "label": "First Choice" - }, - { - "value": "choice-2", - "label": "Second Choice" - } - ] - - Becomes: - - { - "choice-1": "First Choice", - "choice-2": "Second Choice - } - """ - return { - choice['value']: choice['label'] for choice in choices_list - } - - -@contextmanager -def disable_warnings(logger_name): - """ - Temporarily suppress expected warning messages to keep the test output clean. - """ - logger = logging.getLogger(logger_name) - current_level = logger.level - logger.setLevel(logging.ERROR) - yield - logger.setLevel(current_level) diff --git a/netbox/utilities/testing/__init__.py b/netbox/utilities/testing/__init__.py new file mode 100644 index 000000000..30e452215 --- /dev/null +++ b/netbox/utilities/testing/__init__.py @@ -0,0 +1,2 @@ +from .testcases import * +from .utils import * diff --git a/netbox/utilities/testing/testcases.py b/netbox/utilities/testing/testcases.py new file mode 100644 index 000000000..f43bacc54 --- /dev/null +++ b/netbox/utilities/testing/testcases.py @@ -0,0 +1,249 @@ +from django.contrib.auth.models import Permission, User +from django.core.exceptions import ObjectDoesNotExist +from django.test import Client, TestCase as _TestCase, override_settings +from django.urls import reverse, NoReverseMatch +from rest_framework.test import APIClient + +from users.models import Token +from .utils import disable_warnings, model_to_dict, post_data + + +class TestCase(_TestCase): + user_permissions = () + + def setUp(self): + + # Create the test user and assign permissions + self.user = User.objects.create_user(username='testuser') + self.add_permissions(*self.user_permissions) + + # Initialize the test client + self.client = Client() + self.client.force_login(self.user) + + # + # Permissions management + # + + def add_permissions(self, *names): + """ + Assign a set of permissions to the test user. Accepts permission names in the form ._. + """ + for name in names: + app, codename = name.split('.') + perm = Permission.objects.get(content_type__app_label=app, codename=codename) + self.user.user_permissions.add(perm) + + def remove_permissions(self, *names): + """ + Remove a set of permissions from the test user, if assigned. + """ + for name in names: + app, codename = name.split('.') + perm = Permission.objects.get(content_type__app_label=app, codename=codename) + self.user.user_permissions.remove(perm) + + # + # Convenience methods + # + + def assertHttpStatus(self, response, expected_status): + """ + TestCase method. Provide more detail in the event of an unexpected HTTP response. + """ + err_message = "Expected HTTP status {}; received {}: {}" + self.assertEqual(response.status_code, expected_status, err_message.format( + expected_status, response.status_code, getattr(response, 'data', 'No data') + )) + + +class APITestCase(TestCase): + client_class = APIClient + + def setUp(self): + """ + Create a superuser and token for API calls. + """ + self.user = User.objects.create(username='testuser', is_superuser=True) + self.token = Token.objects.create(user=self.user) + self.header = {'HTTP_AUTHORIZATION': 'Token {}'.format(self.token.key)} + + +class StandardTestCases: + """ + We keep any TestCases with test_* methods inside a class to prevent unittest from trying to run them. + """ + + class Views(TestCase): + """ + 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 + """ + model = None + form_data = {} + csv_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") + + 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 = '{}:{}_{{}}'.format( + self.model._meta.app_label, + self.model._meta.model_name + ) + + if action in ('list', 'add', 'import'): + 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)) + + @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) + + @override_settings(EXEMPT_VIEW_PERMISSIONS=[]) + def test_get_object(self): + instance = self.model.objects.first() + + # Attempt to make the request without required permissions + with disable_warnings('django.request'): + self.assertHttpStatus(self.client.get(instance.get_absolute_url()), 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(instance.get_absolute_url()) + self.assertHttpStatus(response, 200) + + @override_settings(EXEMPT_VIEW_PERMISSIONS=[]) + def test_create_object(self): + initial_count = self.model.objects.count() + request = { + 'path': self._get_url('add'), + 'data': post_data(self.form_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 + 1, self.model.objects.count()) + instance = self.model.objects.order_by('-pk').first() + self.assertDictEqual(model_to_dict(instance), self.form_data) + + @override_settings(EXEMPT_VIEW_PERMISSIONS=[]) + def test_edit_object(self): + instance = self.model.objects.first() + + request = { + 'path': self._get_url('edit', instance), + 'data': post_data(self.form_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( + '{}.change_{}'.format(self.model._meta.app_label, self.model._meta.model_name) + ) + response = self.client.post(**request) + self.assertHttpStatus(response, 302) + + instance = self.model.objects.get(pk=instance.pk) + self.assertDictEqual(model_to_dict(instance), self.form_data) + + @override_settings(EXEMPT_VIEW_PERMISSIONS=[]) + def test_delete_object(self): + instance = self.model.objects.first() + + request = { + 'path': self._get_url('delete', instance), + 'data': {'confirm': True}, + '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( + '{}.delete_{}'.format(self.model._meta.app_label, self.model._meta.model_name) + ) + response = self.client.post(**request) + self.assertHttpStatus(response, 302) + + with self.assertRaises(ObjectDoesNotExist): + self.model.objects.get(pk=instance.pk) + + @override_settings(EXEMPT_VIEW_PERMISSIONS=[]) + def test_import_objects(self): + initial_count = self.model.objects.count() + request = { + 'path': self._get_url('import'), + 'data': { + 'csv': '\n'.join(self.csv_data) + } + } + + # 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( + '{}.view_{}'.format(self.model._meta.app_label, self.model._meta.model_name), + '{}.add_{}'.format(self.model._meta.app_label, self.model._meta.model_name) + ) + response = self.client.post(**request) + self.assertHttpStatus(response, 200) + + self.assertEqual(self.model.objects.count(), initial_count + len(self.csv_data) - 1) diff --git a/netbox/utilities/testing/utils.py b/netbox/utilities/testing/utils.py new file mode 100644 index 000000000..6d20d4fff --- /dev/null +++ b/netbox/utilities/testing/utils.py @@ -0,0 +1,102 @@ +import logging +from contextlib import contextmanager + +from django.contrib.auth.models import Permission, User +from django.forms.models import model_to_dict as _model_to_dict + + +def model_to_dict(instance, fields=None, exclude=None): + """ + Customized wrapper for Django's built-in model_to_dict(). Does the following: + - Excludes the instance ID field + - Exclude any fields prepended with an underscore + - Convert any assigned tags to a comma-separated string + """ + _exclude = ['id'] + if exclude is not None: + _exclude += exclude + + model_dict = _model_to_dict(instance, fields=fields, exclude=_exclude) + + for key in list(model_dict.keys()): + if key.startswith('_'): + del model_dict[key] + + # TODO: Differentiate between tags assigned to the instance and a M2M field for tags (ex: ConfigContext) + elif key == 'tags': + model_dict[key] = ','.join(sorted([tag.name for tag in model_dict['tags']])) + + # Convert ManyToManyField to list of instance PKs + elif model_dict[key] and type(model_dict[key]) in (list, tuple) and hasattr(model_dict[key][0], 'pk'): + model_dict[key] = [obj.pk for obj in model_dict[key]] + + return model_dict + + +def post_data(data): + """ + Take a dictionary of test data (suitable for comparison to an instance) and return a dict suitable for POSTing. + """ + ret = {} + + for key, value in data.items(): + if value is None: + ret[key] = '' + elif type(value) in (list, tuple): + ret[key] = value + else: + ret[key] = str(value) + + return ret + + +def create_test_user(username='testuser', permissions=list()): + """ + Create a User with the given permissions. + """ + user = User.objects.create_user(username=username) + for perm_name in permissions: + app, codename = perm_name.split('.') + perm = Permission.objects.get(content_type__app_label=app, codename=codename) + user.user_permissions.add(perm) + + return user + + +def choices_to_dict(choices_list): + """ + Convert a list of field choices to a dictionary suitable for direct comparison with a ChoiceSet. For example: + + [ + { + "value": "choice-1", + "label": "First Choice" + }, + { + "value": "choice-2", + "label": "Second Choice" + } + ] + + Becomes: + + { + "choice-1": "First Choice", + "choice-2": "Second Choice + } + """ + return { + choice['value']: choice['label'] for choice in choices_list + } + + +@contextmanager +def disable_warnings(logger_name): + """ + Temporarily suppress expected warning messages to keep the test output clean. + """ + logger = logging.getLogger(logger_name) + current_level = logger.level + logger.setLevel(logging.ERROR) + yield + logger.setLevel(current_level) diff --git a/netbox/virtualization/tests/test_views.py b/netbox/virtualization/tests/test_views.py index 57af2ffc8..ed065678b 100644 --- a/netbox/virtualization/tests/test_views.py +++ b/netbox/virtualization/tests/test_views.py @@ -1,23 +1,18 @@ -import urllib.parse - -from django.test import Client, TestCase -from django.urls import reverse - -from utilities.testing import create_test_user +from dcim.models import DeviceRole, Platform, Site +from utilities.testing import StandardTestCases +from virtualization.choices import * from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine -class ClusterGroupTestCase(TestCase): +class ClusterGroupTestCase(StandardTestCases.Views): + model = ClusterGroup - def setUp(self): - user = create_test_user( - permissions=[ - 'virtualization.view_clustergroup', - 'virtualization.add_clustergroup', - ] - ) - self.client = Client() - self.client.force_login(user) + # Disable inapplicable tests + test_get_object = None + test_delete_object = None + + @classmethod + def setUpTestData(cls): ClusterGroup.objects.bulk_create([ ClusterGroup(name='Cluster Group 1', slug='cluster-group-1'), @@ -25,39 +20,28 @@ class ClusterGroupTestCase(TestCase): ClusterGroup(name='Cluster Group 3', slug='cluster-group-3'), ]) - def test_clustergroup_list(self): + cls.form_data = { + 'name': 'Cluster Group X', + 'slug': 'cluster-group-x', + } - url = reverse('virtualization:clustergroup_list') - - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - - def test_clustergroup_import(self): - - csv_data = ( + cls.csv_data = ( "name,slug", "Cluster Group 4,cluster-group-4", "Cluster Group 5,cluster-group-5", "Cluster Group 6,cluster-group-6", ) - response = self.client.post(reverse('virtualization:clustergroup_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) - self.assertEqual(ClusterGroup.objects.count(), 6) +class ClusterTypeTestCase(StandardTestCases.Views): + model = ClusterType + # Disable inapplicable tests + test_get_object = None + test_delete_object = None -class ClusterTypeTestCase(TestCase): - - def setUp(self): - user = create_test_user( - permissions=[ - 'virtualization.view_clustertype', - 'virtualization.add_clustertype', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): ClusterType.objects.bulk_create([ ClusterType(name='Cluster Type 1', slug='cluster-type-1'), @@ -65,45 +49,28 @@ class ClusterTypeTestCase(TestCase): ClusterType(name='Cluster Type 3', slug='cluster-type-3'), ]) - def test_clustertype_list(self): + cls.form_data = { + 'name': 'Cluster Type X', + 'slug': 'cluster-type-x', + } - url = reverse('virtualization:clustertype_list') - - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - - def test_clustertype_import(self): - - csv_data = ( + cls.csv_data = ( "name,slug", "Cluster Type 4,cluster-type-4", "Cluster Type 5,cluster-type-5", "Cluster Type 6,cluster-type-6", ) - response = self.client.post(reverse('virtualization:clustertype_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) - self.assertEqual(ClusterType.objects.count(), 6) +class ClusterTestCase(StandardTestCases.Views): + model = Cluster + @classmethod + def setUpTestData(cls): -class ClusterTestCase(TestCase): - - def setUp(self): - user = create_test_user( - permissions=[ - 'virtualization.view_cluster', - 'virtualization.add_cluster', - ] - ) - self.client = Client() - self.client.force_login(user) - - clustergroup = ClusterGroup(name='Cluster Group 1', slug='cluster-group-1') - clustergroup.save() - - clustertype = ClusterType(name='Cluster Type 1', slug='cluster-type-1') - clustertype.save() + site = Site.objects.create(name='Site 1', slug='site-1') + clustergroup = ClusterGroup.objects.create(name='Cluster Group 1', slug='cluster-group-1') + clustertype = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1') Cluster.objects.bulk_create([ Cluster(name='Cluster 1', group=clustergroup, type=clustertype), @@ -111,55 +78,34 @@ class ClusterTestCase(TestCase): Cluster(name='Cluster 3', group=clustergroup, type=clustertype), ]) - def test_cluster_list(self): - - url = reverse('virtualization:cluster_list') - params = { - "group": ClusterGroup.objects.first().slug, - "type": ClusterType.objects.first().slug, + cls.form_data = { + 'name': 'Cluster X', + 'group': clustergroup.pk, + 'type': clustertype.pk, + 'tenant': None, + 'site': site.pk, + 'comments': 'Some comments', + 'tags': 'Alpha,Bravo,Charlie', } - response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) - - def test_cluster(self): - - cluster = Cluster.objects.first() - response = self.client.get(cluster.get_absolute_url()) - self.assertEqual(response.status_code, 200) - - def test_cluster_import(self): - - csv_data = ( + cls.csv_data = ( "name,type", "Cluster 4,Cluster Type 1", "Cluster 5,Cluster Type 1", "Cluster 6,Cluster Type 1", ) - response = self.client.post(reverse('virtualization:cluster_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) - self.assertEqual(Cluster.objects.count(), 6) +class VirtualMachineTestCase(StandardTestCases.Views): + model = VirtualMachine + @classmethod + def setUpTestData(cls): -class VirtualMachineTestCase(TestCase): - - def setUp(self): - user = create_test_user( - permissions=[ - 'virtualization.view_virtualmachine', - 'virtualization.add_virtualmachine', - ] - ) - self.client = Client() - self.client.force_login(user) - - clustertype = ClusterType(name='Cluster Type 1', slug='cluster-type-1') - clustertype.save() - - cluster = Cluster(name='Cluster 1', type=clustertype) - cluster.save() + devicerole = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + platform = Platform.objects.create(name='Platform 1', slug='platform-1') + clustertype = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1') + cluster = Cluster.objects.create(name='Cluster 1', type=clustertype) VirtualMachine.objects.bulk_create([ VirtualMachine(name='Virtual Machine 1', cluster=cluster), @@ -167,32 +113,26 @@ class VirtualMachineTestCase(TestCase): VirtualMachine(name='Virtual Machine 3', cluster=cluster), ]) - def test_virtualmachine_list(self): - - url = reverse('virtualization:virtualmachine_list') - params = { - "cluster_id": Cluster.objects.first().pk, + cls.form_data = { + 'cluster': cluster.pk, + 'tenant': None, + 'platform': None, + 'name': 'Virtual Machine X', + 'status': VirtualMachineStatusChoices.STATUS_STAGED, + 'role': devicerole.pk, + 'primary_ip4': None, + 'primary_ip6': None, + 'vcpus': 4, + 'memory': 32768, + 'disk': 4000, + 'comments': 'Some comments', + 'tags': 'Alpha,Bravo,Charlie', + 'local_context_data': None, } - response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) - - def test_virtualmachine(self): - - virtualmachine = VirtualMachine.objects.first() - response = self.client.get(virtualmachine.get_absolute_url()) - self.assertEqual(response.status_code, 200) - - def test_virtualmachine_import(self): - - csv_data = ( + cls.csv_data = ( "name,cluster", "Virtual Machine 4,Cluster 1", "Virtual Machine 5,Cluster 1", "Virtual Machine 6,Cluster 1", ) - - response = self.client.post(reverse('virtualization:virtualmachine_import'), {'csv': '\n'.join(csv_data)}) - - self.assertEqual(response.status_code, 200) - self.assertEqual(VirtualMachine.objects.count(), 6)