From 43b2c36066e9ab232cf3ca66dfe09f945498aef7 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 30 Jan 2020 16:03:52 -0500 Subject: [PATCH 01/25] Introduced a custom TestCase --- netbox/utilities/testing.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/netbox/utilities/testing.py b/netbox/utilities/testing.py index 791eb64cb..39b43ab83 100644 --- a/netbox/utilities/testing.py +++ b/netbox/utilities/testing.py @@ -2,11 +2,44 @@ import logging from contextlib import contextmanager from django.contrib.auth.models import Permission, User +from django.test import Client, TestCase as _TestCase from rest_framework.test import APITestCase as _APITestCase from users.models import Token +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) + + 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) + + class APITestCase(_APITestCase): def setUp(self): From 61ac7c44ba2cfb55b484dc4d0e36bf775c2e59f6 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 30 Jan 2020 16:37:40 -0500 Subject: [PATCH 02/25] Migrate view tests to use new TestCase class --- netbox/circuits/tests/test_views.py | 48 ++-- netbox/dcim/tests/test_views.py | 326 +++++++++------------- netbox/ipam/tests/test_views.py | 132 ++++----- netbox/secrets/tests/test_views.py | 52 ++-- netbox/tenancy/tests/test_views.py | 33 +-- netbox/virtualization/tests/test_views.py | 63 ++--- 6 files changed, 271 insertions(+), 383 deletions(-) diff --git a/netbox/circuits/tests/test_views.py b/netbox/circuits/tests/test_views.py index 576437ef1..d10d2df85 100644 --- a/netbox/circuits/tests/test_views.py +++ b/netbox/circuits/tests/test_views.py @@ -1,23 +1,18 @@ import urllib.parse -from django.test import Client, TestCase from django.urls import reverse from circuits.models import Circuit, CircuitType, Provider -from utilities.testing import create_test_user +from utilities.testing import TestCase class ProviderTestCase(TestCase): + user_permissions = ( + 'circuits.view_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), @@ -42,6 +37,7 @@ class ProviderTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_provider_import(self): + self.add_permissions('circuits.add_provider') csv_data = ( "name,slug", @@ -57,16 +53,12 @@ class ProviderTestCase(TestCase): class CircuitTypeTestCase(TestCase): + user_permissions = ( + 'circuits.view_circuittype', + ) - 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'), @@ -82,6 +74,7 @@ class CircuitTypeTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_circuittype_import(self): + self.add_permissions('circuits.add_circuittype') csv_data = ( "name,slug", @@ -97,16 +90,12 @@ class CircuitTypeTestCase(TestCase): class CircuitTestCase(TestCase): + user_permissions = ( + 'circuits.view_circuit', + ) - 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() @@ -138,6 +127,7 @@ class CircuitTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_circuit_import(self): + self.add_permissions('circuits.add_circuit') csv_data = ( "cid,provider,type", diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index 856862a3e..45887f171 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -1,26 +1,22 @@ import urllib.parse import yaml -from django.test import Client, TestCase +from django.contrib.auth.models import User 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 TestCase class RegionTestCase(TestCase): + user_permissions = ( + 'dcim.view_region', + ) - def setUp(self): - user = create_test_user( - permissions=[ - 'dcim.view_region', - 'dcim.add_region', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): # Create three Regions for i in range(1, 4): @@ -34,6 +30,7 @@ class RegionTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_region_import(self): + self.add_permissions('dcim.add_region') csv_data = ( "name,slug", @@ -49,16 +46,12 @@ class RegionTestCase(TestCase): class SiteTestCase(TestCase): + user_permissions = ( + 'dcim.view_site', + ) - 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() @@ -86,6 +79,7 @@ class SiteTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_site_import(self): + self.add_permissions('dcim.add_site') csv_data = ( "name,slug", @@ -101,16 +95,12 @@ class SiteTestCase(TestCase): class RackGroupTestCase(TestCase): + user_permissions = ( + 'dcim.view_rackgroup', + ) - 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() @@ -129,6 +119,7 @@ class RackGroupTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_rackgroup_import(self): + self.add_permissions('dcim.add_rackgroup') csv_data = ( "site,name,slug", @@ -144,16 +135,12 @@ class RackGroupTestCase(TestCase): class RackRoleTestCase(TestCase): + user_permissions = ( + 'dcim.view_rackrole', + ) - 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'), @@ -169,6 +156,7 @@ class RackRoleTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_rackrole_import(self): + self.add_permissions('dcim.add_rackrole') csv_data = ( "name,slug,color", @@ -184,11 +172,14 @@ class RackRoleTestCase(TestCase): class RackReservationTestCase(TestCase): + user_permissions = ( + 'dcim.view_rackreservation', + ) - def setUp(self): - user = create_test_user(permissions=['dcim.view_rackreservation']) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): + + user = User.objects.create_user(username='testuser2') site = Site(name='Site 1', slug='site-1') site.save() @@ -211,16 +202,12 @@ class RackReservationTestCase(TestCase): class RackTestCase(TestCase): + user_permissions = ( + 'dcim.view_rack', + ) - def setUp(self): - user = create_test_user( - permissions=[ - 'dcim.view_rack', - 'dcim.add_rack', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): site = Site(name='Site 1', slug='site-1') site.save() @@ -248,6 +235,7 @@ class RackTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_rack_import(self): + self.add_permissions('dcim.add_rack') csv_data = ( "site,name,width,u_height", @@ -263,16 +251,12 @@ class RackTestCase(TestCase): class ManufacturerTypeTestCase(TestCase): + user_permissions = ( + 'dcim.view_manufacturer', + ) - 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'), @@ -288,6 +272,7 @@ class ManufacturerTypeTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_manufacturer_import(self): + self.add_permissions('dcim.add_manufacturer') csv_data = ( "name,slug", @@ -303,11 +288,12 @@ class ManufacturerTypeTestCase(TestCase): class DeviceTypeTestCase(TestCase): + user_permissions = ( + 'dcim.view_devicetype', + ) - 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() @@ -420,9 +406,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=[ - 'dcim.view_devicetype', + # Add all required permissions to the test user + self.add_permissions( 'dcim.add_devicetype', 'dcim.add_consoleporttemplate', 'dcim.add_consoleserverporttemplate', @@ -432,8 +417,7 @@ device-bays: 'dcim.add_frontporttemplate', 'dcim.add_rearporttemplate', 'dcim.add_devicebaytemplate', - ]) - self.client.force_login(user) + ) form_data = { 'data': IMPORT_DATA, @@ -489,16 +473,12 @@ device-bays: class DeviceRoleTestCase(TestCase): + user_permissions = ( + 'dcim.view_devicerole', + ) - def setUp(self): - user = create_test_user( - permissions=[ - 'dcim.view_devicerole', - 'dcim.add_devicerole', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): DeviceRole.objects.bulk_create([ DeviceRole(name='Device Role 1', slug='device-role-1'), @@ -514,6 +494,7 @@ class DeviceRoleTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_devicerole_import(self): + self.add_permissions('dcim.add_devicerole') csv_data = ( "name,slug,color", @@ -529,16 +510,12 @@ class DeviceRoleTestCase(TestCase): class PlatformTestCase(TestCase): + user_permissions = ( + 'dcim.view_platform', + ) - def setUp(self): - user = create_test_user( - permissions=[ - 'dcim.view_platform', - 'dcim.add_platform', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): Platform.objects.bulk_create([ Platform(name='Platform 1', slug='platform-1'), @@ -554,6 +531,7 @@ class PlatformTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_platform_import(self): + self.add_permissions('dcim.add_platform') csv_data = ( "name,slug", @@ -569,16 +547,12 @@ class PlatformTestCase(TestCase): class DeviceTestCase(TestCase): + user_permissions = ( + 'dcim.view_device', + ) - def setUp(self): - user = create_test_user( - permissions=[ - 'dcim.view_device', - 'dcim.add_device', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): site = Site(name='Site 1', slug='site-1') site.save() @@ -616,6 +590,7 @@ class DeviceTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_device_import(self): + self.add_permissions('dcim.add_device') csv_data = ( "device_role,manufacturer,model_name,status,site,name", @@ -631,16 +606,12 @@ class DeviceTestCase(TestCase): 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() @@ -671,6 +642,7 @@ class ConsolePortTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_consoleport_import(self): + self.add_permissions('dcim.add_consoleport') csv_data = ( "device,name", @@ -686,16 +658,12 @@ class ConsolePortTestCase(TestCase): 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() @@ -726,6 +694,7 @@ class ConsoleServerPortTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_consoleserverport_import(self): + self.add_permissions('dcim.add_consoleserverport') csv_data = ( "device,name", @@ -741,16 +710,12 @@ class ConsoleServerPortTestCase(TestCase): 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() @@ -781,6 +746,7 @@ class PowerPortTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_powerport_import(self): + self.add_permissions('dcim.add_powerport') csv_data = ( "device,name", @@ -796,16 +762,12 @@ class PowerPortTestCase(TestCase): 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() @@ -836,6 +798,7 @@ class PowerOutletTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_poweroutlet_import(self): + self.add_permissions('dcim.add_poweroutlet') csv_data = ( "device,name", @@ -851,16 +814,12 @@ class PowerOutletTestCase(TestCase): 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() @@ -891,6 +850,7 @@ class InterfaceTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_interface_import(self): + self.add_permissions('dcim.add_interface') csv_data = ( "device,name,type", @@ -906,16 +866,12 @@ class InterfaceTestCase(TestCase): 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() @@ -958,6 +914,7 @@ class FrontPortTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_frontport_import(self): + self.add_permissions('dcim.add_frontport') csv_data = ( "device,name,type,rear_port,rear_port_position", @@ -973,16 +930,12 @@ class FrontPortTestCase(TestCase): 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() @@ -1013,6 +966,7 @@ class RearPortTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_rearport_import(self): + self.add_permissions('dcim.add_rearport') csv_data = ( "device,name,type,positions", @@ -1028,16 +982,12 @@ class RearPortTestCase(TestCase): 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() @@ -1072,6 +1022,7 @@ class DeviceBayTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_devicebay_import(self): + self.add_permissions('dcim.add_devicebay') csv_data = ( "device,name", @@ -1087,16 +1038,12 @@ class DeviceBayTestCase(TestCase): 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() @@ -1130,6 +1077,7 @@ class InventoryItemTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_inventoryitem_import(self): + self.add_permissions('dcim.add_inventoryitem') csv_data = ( "device,name", @@ -1145,16 +1093,12 @@ class InventoryItemTestCase(TestCase): class CableTestCase(TestCase): + user_permissions = ( + 'dcim.view_cable', + ) - def setUp(self): - user = create_test_user( - permissions=[ - 'dcim.view_cable', - 'dcim.add_cable', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): site = Site(name='Site 1', slug='site-1') site.save() @@ -1219,6 +1163,7 @@ class CableTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_cable_import(self): + self.add_permissions('dcim.add_cable') csv_data = ( "side_a_device,side_a_type,side_a_name,side_b_device,side_b_type,side_b_name", @@ -1234,11 +1179,12 @@ class CableTestCase(TestCase): class VirtualChassisTestCase(TestCase): + user_permissions = ( + 'dcim.view_virtualchassis', + ) - 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') diff --git a/netbox/ipam/tests/test_views.py b/netbox/ipam/tests/test_views.py index 6f08f2d47..66742e1a9 100644 --- a/netbox/ipam/tests/test_views.py +++ b/netbox/ipam/tests/test_views.py @@ -1,26 +1,21 @@ from netaddr import IPNetwork import urllib.parse -from django.test import Client, TestCase from django.urls import reverse from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site from ipam.choices import ServiceProtocolChoices from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF -from utilities.testing import create_test_user +from utilities.testing import TestCase class VRFTestCase(TestCase): + user_permissions = ( + 'ipam.view_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'), @@ -45,6 +40,7 @@ class VRFTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_vrf_import(self): + self.add_permissions('ipam.add_vrf') csv_data = ( "name", @@ -60,16 +56,12 @@ class VRFTestCase(TestCase): class RIRTestCase(TestCase): + user_permissions = ( + 'ipam.view_rir', + ) - 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'), @@ -85,6 +77,7 @@ class RIRTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_rir_import(self): + self.add_permissions('ipam.add_rir') csv_data = ( "name,slug", @@ -100,16 +93,12 @@ class RIRTestCase(TestCase): class AggregateTestCase(TestCase): + user_permissions = ( + 'ipam.view_aggregate', + ) - def setUp(self): - user = create_test_user( - permissions=[ - 'ipam.view_aggregate', - 'ipam.add_aggregate', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): rir = RIR(name='RIR 1', slug='rir-1') rir.save() @@ -137,6 +126,7 @@ class AggregateTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_aggregate_import(self): + self.add_permissions('ipam.add_aggregate') csv_data = ( "prefix,rir", @@ -152,16 +142,12 @@ class AggregateTestCase(TestCase): class RoleTestCase(TestCase): + user_permissions = ( + 'ipam.view_role', + ) - 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'), @@ -177,6 +163,7 @@ class RoleTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_role_import(self): + self.add_permissions('ipam.add_role') csv_data = ( "name,slug,weight", @@ -192,16 +179,12 @@ class RoleTestCase(TestCase): class PrefixTestCase(TestCase): + user_permissions = ( + 'ipam.view_prefix', + ) - def setUp(self): - user = create_test_user( - permissions=[ - 'ipam.view_prefix', - 'ipam.add_prefix', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): site = Site(name='Site 1', slug='site-1') site.save() @@ -229,6 +212,7 @@ class PrefixTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_prefix_import(self): + self.add_permissions('ipam.add_prefix') csv_data = ( "prefix,status", @@ -244,16 +228,12 @@ class PrefixTestCase(TestCase): class IPAddressTestCase(TestCase): + user_permissions = ( + 'ipam.view_ipaddress', + ) - def setUp(self): - user = create_test_user( - permissions=[ - 'ipam.view_ipaddress', - 'ipam.add_ipaddress', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): vrf = VRF(name='VRF 1', rd='65000:1') vrf.save() @@ -281,6 +261,7 @@ class IPAddressTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_ipaddress_import(self): + self.add_permissions('ipam.add_ipaddress') csv_data = ( "address,status", @@ -296,16 +277,12 @@ class IPAddressTestCase(TestCase): class VLANGroupTestCase(TestCase): + user_permissions = ( + 'ipam.view_vlangroup', + ) - def setUp(self): - user = create_test_user( - permissions=[ - 'ipam.view_vlangroup', - 'ipam.add_vlangroup', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): site = Site(name='Site 1', slug='site-1') site.save() @@ -327,6 +304,7 @@ class VLANGroupTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_vlangroup_import(self): + self.add_permissions('ipam.add_vlangroup') csv_data = ( "name,slug", @@ -342,16 +320,12 @@ class VLANGroupTestCase(TestCase): class VLANTestCase(TestCase): + user_permissions = ( + 'ipam.view_vlan', + ) - def setUp(self): - user = create_test_user( - permissions=[ - 'ipam.view_vlan', - 'ipam.add_vlan', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): vlangroup = VLANGroup(name='VLAN Group 1', slug='vlan-group-1') vlangroup.save() @@ -379,6 +353,7 @@ class VLANTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_vlan_import(self): + self.add_permissions('ipam.add_vlan') csv_data = ( "vid,name,status", @@ -394,11 +369,12 @@ class VLANTestCase(TestCase): class ServiceTestCase(TestCase): + user_permissions = ( + 'ipam.view_service', + ) - 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() diff --git a/netbox/secrets/tests/test_views.py b/netbox/secrets/tests/test_views.py index 43ae10dc6..14074630b 100644 --- a/netbox/secrets/tests/test_views.py +++ b/netbox/secrets/tests/test_views.py @@ -1,26 +1,21 @@ 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 TestCase from .constants import PRIVATE_KEY, PUBLIC_KEY class SecretRoleTestCase(TestCase): + user_permissions = ( + 'secrets.view_secretrole', + ) - def setUp(self): - user = create_test_user( - permissions=[ - 'secrets.view_secretrole', - 'secrets.add_secretrole', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): SecretRole.objects.bulk_create([ SecretRole(name='Secret Role 1', slug='secret-role-1'), @@ -36,6 +31,7 @@ class SecretRoleTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_secretrole_import(self): + self.add_permissions('secrets.add_secretrole') csv_data = ( "name,slug", @@ -51,24 +47,12 @@ class SecretRoleTestCase(TestCase): class SecretTestCase(TestCase): + user_permissions = ( + 'secrets.view_secret', + ) - def setUp(self): - user = create_test_user( - permissions=[ - 'secrets.view_secret', - 'secrets.add_secret', - ] - ) - - # 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) + @classmethod + def setUpTestData(cls): site = Site(name='Site 1', slug='site-1') site.save() @@ -94,6 +78,17 @@ class SecretTestCase(TestCase): Secret(device=device, role=secretrole, name='Secret 3', ciphertext=b'1234567890'), ]) + def setUp(self): + + super().setUp() + + # 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_list(self): url = reverse('secrets:secret_list') @@ -111,6 +106,7 @@ class SecretTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_secret_import(self): + self.add_permissions('secrets.add_secret') csv_data = ( "device,role,name,plaintext", diff --git a/netbox/tenancy/tests/test_views.py b/netbox/tenancy/tests/test_views.py index 10ee354d4..3cb04d6b2 100644 --- a/netbox/tenancy/tests/test_views.py +++ b/netbox/tenancy/tests/test_views.py @@ -1,23 +1,18 @@ 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 TestCase class TenantGroupTestCase(TestCase): + user_permissions = ( + 'tenancy.view_tenantgroup', + ) - def setUp(self): - user = create_test_user( - permissions=[ - 'tenancy.view_tenantgroup', - 'tenancy.add_tenantgroup', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): TenantGroup.objects.bulk_create([ TenantGroup(name='Tenant Group 1', slug='tenant-group-1'), @@ -33,6 +28,7 @@ class TenantGroupTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_tenantgroup_import(self): + self.add_permissions('tenancy.add_tenantgroup') csv_data = ( "name,slug", @@ -48,16 +44,12 @@ class TenantGroupTestCase(TestCase): class TenantTestCase(TestCase): + user_permissions = ( + 'tenancy.view_tenant', + ) - def setUp(self): - user = create_test_user( - permissions=[ - 'tenancy.view_tenant', - 'tenancy.add_tenant', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): tenantgroup = TenantGroup(name='Tenant Group 1', slug='tenant-group-1') tenantgroup.save() @@ -85,6 +77,7 @@ class TenantTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_tenant_import(self): + self.add_permissions('tenancy.add_tenant') csv_data = ( "name,slug", diff --git a/netbox/virtualization/tests/test_views.py b/netbox/virtualization/tests/test_views.py index 57af2ffc8..67df8fe1e 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 utilities.testing import TestCase from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine class ClusterGroupTestCase(TestCase): + user_permissions = ( + 'virtualization.view_clustergroup', + ) - def setUp(self): - user = create_test_user( - permissions=[ - 'virtualization.view_clustergroup', - 'virtualization.add_clustergroup', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): ClusterGroup.objects.bulk_create([ ClusterGroup(name='Cluster Group 1', slug='cluster-group-1'), @@ -33,6 +28,7 @@ class ClusterGroupTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_clustergroup_import(self): + self.add_permissions('virtualization.add_clustergroup') csv_data = ( "name,slug", @@ -48,16 +44,12 @@ class ClusterGroupTestCase(TestCase): class ClusterTypeTestCase(TestCase): + user_permissions = ( + 'virtualization.view_clustertype', + ) - 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'), @@ -73,6 +65,7 @@ class ClusterTypeTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_clustertype_import(self): + self.add_permissions('virtualization.add_clustertype') csv_data = ( "name,slug", @@ -88,16 +81,12 @@ class ClusterTypeTestCase(TestCase): class ClusterTestCase(TestCase): + user_permissions = ( + 'virtualization.view_cluster', + ) - def setUp(self): - user = create_test_user( - permissions=[ - 'virtualization.view_cluster', - 'virtualization.add_cluster', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): clustergroup = ClusterGroup(name='Cluster Group 1', slug='cluster-group-1') clustergroup.save() @@ -129,6 +118,7 @@ class ClusterTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_cluster_import(self): + self.add_permissions('virtualization.add_cluster') csv_data = ( "name,type", @@ -144,16 +134,12 @@ class ClusterTestCase(TestCase): class VirtualMachineTestCase(TestCase): + user_permissions = ( + 'virtualization.view_virtualmachine', + ) - def setUp(self): - user = create_test_user( - permissions=[ - 'virtualization.view_virtualmachine', - 'virtualization.add_virtualmachine', - ] - ) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): clustertype = ClusterType(name='Cluster Type 1', slug='cluster-type-1') clustertype.save() @@ -184,6 +170,7 @@ class VirtualMachineTestCase(TestCase): self.assertEqual(response.status_code, 200) def test_virtualmachine_import(self): + self.add_permissions('virtualization.add_virtualmachine') csv_data = ( "name,cluster", From 179abcc79de43893482a7e6adc6e6a8f8f7adc01 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 30 Jan 2020 17:57:34 -0500 Subject: [PATCH 03/25] Refactor APITestCase to subclass TestCase --- netbox/utilities/testing.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/netbox/utilities/testing.py b/netbox/utilities/testing.py index 39b43ab83..f86a32ab0 100644 --- a/netbox/utilities/testing.py +++ b/netbox/utilities/testing.py @@ -3,7 +3,7 @@ from contextlib import contextmanager from django.contrib.auth.models import Permission, User from django.test import Client, TestCase as _TestCase -from rest_framework.test import APITestCase as _APITestCase +from rest_framework.test import APIClient from users.models import Token @@ -21,6 +21,10 @@ class TestCase(_TestCase): 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 ._. @@ -39,8 +43,22 @@ class TestCase(_TestCase): perm = Permission.objects.get(content_type__app_label=app, codename=codename) self.user.user_permissions.remove(perm) + # + # Convenience methods + # -class APITestCase(_APITestCase): + 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): """ @@ -50,15 +68,6 @@ class APITestCase(_APITestCase): 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()): """ From 67fafb2b9dc700a5d91dd6ea243bd82bba73638d Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 30 Jan 2020 18:08:25 -0500 Subject: [PATCH 04/25] Use assertHttpStatus for evaluating HTTP response codes --- netbox/circuits/tests/test_views.py | 20 ++--- netbox/dcim/tests/test_views.py | 96 +++++++++++------------ netbox/ipam/tests/test_views.py | 46 +++++------ netbox/netbox/tests/test_views.py | 6 +- netbox/secrets/tests/test_views.py | 10 +-- netbox/tenancy/tests/test_views.py | 10 +-- netbox/virtualization/tests/test_views.py | 20 ++--- 7 files changed, 102 insertions(+), 106 deletions(-) diff --git a/netbox/circuits/tests/test_views.py b/netbox/circuits/tests/test_views.py index d10d2df85..6ecc88e5c 100644 --- a/netbox/circuits/tests/test_views.py +++ b/netbox/circuits/tests/test_views.py @@ -13,7 +13,6 @@ class ProviderTestCase(TestCase): @classmethod def setUpTestData(cls): - Provider.objects.bulk_create([ Provider(name='Provider 1', slug='provider-1', asn=65001), Provider(name='Provider 2', slug='provider-2', asn=65002), @@ -21,24 +20,21 @@ class ProviderTestCase(TestCase): ]) def test_provider_list(self): - url = reverse('circuits:provider_list') params = { "q": "test", } response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_provider(self): - provider = Provider.objects.first() response = self.client.get(provider.get_absolute_url()) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_provider_import(self): self.add_permissions('circuits.add_provider') - csv_data = ( "name,slug", "Provider 4,provider-4", @@ -48,7 +44,7 @@ class ProviderTestCase(TestCase): response = self.client.post(reverse('circuits:provider_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(Provider.objects.count(), 6) @@ -71,7 +67,7 @@ class CircuitTypeTestCase(TestCase): url = reverse('circuits:circuittype_list') response = self.client.get(url) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_circuittype_import(self): self.add_permissions('circuits.add_circuittype') @@ -85,7 +81,7 @@ class CircuitTypeTestCase(TestCase): response = self.client.post(reverse('circuits:circuittype_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(CircuitType.objects.count(), 6) @@ -118,13 +114,13 @@ class CircuitTestCase(TestCase): } response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_circuit(self): circuit = Circuit.objects.first() response = self.client.get(circuit.get_absolute_url()) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_circuit_import(self): self.add_permissions('circuits.add_circuit') @@ -138,5 +134,5 @@ class CircuitTestCase(TestCase): response = self.client.post(reverse('circuits:circuit_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(Circuit.objects.count(), 6) diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index 45887f171..6a07e0153 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -27,7 +27,7 @@ class RegionTestCase(TestCase): url = reverse('dcim:region_list') response = self.client.get(url) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_region_import(self): self.add_permissions('dcim.add_region') @@ -41,7 +41,7 @@ class RegionTestCase(TestCase): response = self.client.post(reverse('dcim:region_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(Region.objects.count(), 6) @@ -70,13 +70,13 @@ class SiteTestCase(TestCase): } response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_site(self): site = Site.objects.first() response = self.client.get(site.get_absolute_url()) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_site_import(self): self.add_permissions('dcim.add_site') @@ -90,7 +90,7 @@ class SiteTestCase(TestCase): response = self.client.post(reverse('dcim:site_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(Site.objects.count(), 6) @@ -116,7 +116,7 @@ class RackGroupTestCase(TestCase): url = reverse('dcim:rackgroup_list') response = self.client.get(url) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_rackgroup_import(self): self.add_permissions('dcim.add_rackgroup') @@ -130,7 +130,7 @@ class RackGroupTestCase(TestCase): response = self.client.post(reverse('dcim:rackgroup_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(RackGroup.objects.count(), 6) @@ -153,7 +153,7 @@ class RackRoleTestCase(TestCase): url = reverse('dcim:rackrole_list') response = self.client.get(url) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_rackrole_import(self): self.add_permissions('dcim.add_rackrole') @@ -167,7 +167,7 @@ class RackRoleTestCase(TestCase): response = self.client.post(reverse('dcim:rackrole_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(RackRole.objects.count(), 6) @@ -198,7 +198,7 @@ class RackReservationTestCase(TestCase): url = reverse('dcim:rackreservation_list') response = self.client.get(url) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) class RackTestCase(TestCase): @@ -226,13 +226,13 @@ class RackTestCase(TestCase): } response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_rack(self): rack = Rack.objects.first() response = self.client.get(rack.get_absolute_url()) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_rack_import(self): self.add_permissions('dcim.add_rack') @@ -246,7 +246,7 @@ class RackTestCase(TestCase): response = self.client.post(reverse('dcim:rack_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(Rack.objects.count(), 6) @@ -269,7 +269,7 @@ class ManufacturerTypeTestCase(TestCase): url = reverse('dcim:manufacturer_list') response = self.client.get(url) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_manufacturer_import(self): self.add_permissions('dcim.add_manufacturer') @@ -283,7 +283,7 @@ class ManufacturerTypeTestCase(TestCase): response = self.client.post(reverse('dcim:manufacturer_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(Manufacturer.objects.count(), 6) @@ -312,14 +312,14 @@ class DeviceTypeTestCase(TestCase): } response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_devicetype_export(self): url = reverse('dcim:devicetype_list') response = self.client.get('{}?export'.format(url)) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) data = list(yaml.load_all(response.content, Loader=yaml.SafeLoader)) self.assertEqual(len(data), 3) self.assertEqual(data[0]['manufacturer'], 'Manufacturer 1') @@ -329,7 +329,7 @@ class DeviceTypeTestCase(TestCase): devicetype = DeviceType.objects.first() response = self.client.get(devicetype.get_absolute_url()) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_devicetype_import(self): @@ -424,7 +424,7 @@ device-bays: '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') @@ -491,7 +491,7 @@ class DeviceRoleTestCase(TestCase): url = reverse('dcim:devicerole_list') response = self.client.get(url) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_devicerole_import(self): self.add_permissions('dcim.add_devicerole') @@ -505,7 +505,7 @@ class DeviceRoleTestCase(TestCase): response = self.client.post(reverse('dcim:devicerole_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(DeviceRole.objects.count(), 6) @@ -528,7 +528,7 @@ class PlatformTestCase(TestCase): url = reverse('dcim:platform_list') response = self.client.get(url) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_platform_import(self): self.add_permissions('dcim.add_platform') @@ -542,7 +542,7 @@ class PlatformTestCase(TestCase): response = self.client.post(reverse('dcim:platform_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(Platform.objects.count(), 6) @@ -581,13 +581,13 @@ class DeviceTestCase(TestCase): } response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_device(self): device = Device.objects.first() response = self.client.get(device.get_absolute_url()) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_device_import(self): self.add_permissions('dcim.add_device') @@ -601,7 +601,7 @@ class DeviceTestCase(TestCase): response = self.client.post(reverse('dcim:device_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(Device.objects.count(), 6) @@ -639,7 +639,7 @@ 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') @@ -653,7 +653,7 @@ 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) @@ -691,7 +691,7 @@ 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') @@ -705,7 +705,7 @@ 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) @@ -743,7 +743,7 @@ 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') @@ -757,7 +757,7 @@ 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) @@ -795,7 +795,7 @@ 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') @@ -809,7 +809,7 @@ 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) @@ -847,7 +847,7 @@ 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') @@ -861,7 +861,7 @@ 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) @@ -911,7 +911,7 @@ 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') @@ -925,7 +925,7 @@ 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) @@ -963,7 +963,7 @@ 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') @@ -977,7 +977,7 @@ 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) @@ -1019,7 +1019,7 @@ 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') @@ -1033,7 +1033,7 @@ 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) @@ -1074,7 +1074,7 @@ 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') @@ -1088,7 +1088,7 @@ 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) @@ -1154,13 +1154,13 @@ class CableTestCase(TestCase): } response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_cable(self): cable = Cable.objects.first() response = self.client.get(cable.get_absolute_url()) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_cable_import(self): self.add_permissions('dcim.add_cable') @@ -1174,7 +1174,7 @@ class CableTestCase(TestCase): response = self.client.post(reverse('dcim:cable_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(Cable.objects.count(), 6) @@ -1228,4 +1228,4 @@ class VirtualChassisTestCase(TestCase): url = reverse('dcim:virtualchassis_list') response = self.client.get(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 66742e1a9..1ea5f2e2b 100644 --- a/netbox/ipam/tests/test_views.py +++ b/netbox/ipam/tests/test_views.py @@ -31,13 +31,13 @@ class VRFTestCase(TestCase): } response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_vrf(self): vrf = VRF.objects.first() response = self.client.get(vrf.get_absolute_url()) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_vrf_import(self): self.add_permissions('ipam.add_vrf') @@ -51,7 +51,7 @@ class VRFTestCase(TestCase): response = self.client.post(reverse('ipam:vrf_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(VRF.objects.count(), 6) @@ -74,7 +74,7 @@ class RIRTestCase(TestCase): url = reverse('ipam:rir_list') response = self.client.get(url) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_rir_import(self): self.add_permissions('ipam.add_rir') @@ -88,7 +88,7 @@ class RIRTestCase(TestCase): response = self.client.post(reverse('ipam:rir_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(RIR.objects.count(), 6) @@ -117,13 +117,13 @@ class AggregateTestCase(TestCase): } response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_aggregate(self): aggregate = Aggregate.objects.first() response = self.client.get(aggregate.get_absolute_url()) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_aggregate_import(self): self.add_permissions('ipam.add_aggregate') @@ -137,7 +137,7 @@ class AggregateTestCase(TestCase): response = self.client.post(reverse('ipam:aggregate_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(Aggregate.objects.count(), 6) @@ -160,7 +160,7 @@ class RoleTestCase(TestCase): url = reverse('ipam:role_list') response = self.client.get(url) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_role_import(self): self.add_permissions('ipam.add_role') @@ -174,7 +174,7 @@ class RoleTestCase(TestCase): response = self.client.post(reverse('ipam:role_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(Role.objects.count(), 6) @@ -203,13 +203,13 @@ class PrefixTestCase(TestCase): } response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_prefix(self): prefix = Prefix.objects.first() response = self.client.get(prefix.get_absolute_url()) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_prefix_import(self): self.add_permissions('ipam.add_prefix') @@ -223,7 +223,7 @@ class PrefixTestCase(TestCase): response = self.client.post(reverse('ipam:prefix_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(Prefix.objects.count(), 6) @@ -252,13 +252,13 @@ class IPAddressTestCase(TestCase): } response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_ipaddress(self): ipaddress = IPAddress.objects.first() response = self.client.get(ipaddress.get_absolute_url()) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_ipaddress_import(self): self.add_permissions('ipam.add_ipaddress') @@ -272,7 +272,7 @@ class IPAddressTestCase(TestCase): response = self.client.post(reverse('ipam:ipaddress_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(IPAddress.objects.count(), 6) @@ -301,7 +301,7 @@ class VLANGroupTestCase(TestCase): } response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_vlangroup_import(self): self.add_permissions('ipam.add_vlangroup') @@ -315,7 +315,7 @@ class VLANGroupTestCase(TestCase): response = self.client.post(reverse('ipam:vlangroup_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(VLANGroup.objects.count(), 6) @@ -344,13 +344,13 @@ class VLANTestCase(TestCase): } response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_vlan(self): vlan = VLAN.objects.first() response = self.client.get(vlan.get_absolute_url()) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_vlan_import(self): self.add_permissions('ipam.add_vlan') @@ -364,7 +364,7 @@ class VLANTestCase(TestCase): response = self.client.post(reverse('ipam:vlan_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(VLAN.objects.count(), 6) @@ -405,10 +405,10 @@ class ServiceTestCase(TestCase): } response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_service(self): service = Service.objects.first() response = self.client.get(service.get_absolute_url()) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 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 14074630b..336a33320 100644 --- a/netbox/secrets/tests/test_views.py +++ b/netbox/secrets/tests/test_views.py @@ -28,7 +28,7 @@ class SecretRoleTestCase(TestCase): url = reverse('secrets:secretrole_list') response = self.client.get(url, follow=True) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_secretrole_import(self): self.add_permissions('secrets.add_secretrole') @@ -42,7 +42,7 @@ class SecretRoleTestCase(TestCase): response = self.client.post(reverse('secrets:secretrole_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(SecretRole.objects.count(), 6) @@ -97,13 +97,13 @@ class SecretTestCase(TestCase): } response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)), follow=True) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_secret(self): secret = Secret.objects.first() response = self.client.get(secret.get_absolute_url(), follow=True) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_secret_import(self): self.add_permissions('secrets.add_secret') @@ -121,5 +121,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 3cb04d6b2..8646abe38 100644 --- a/netbox/tenancy/tests/test_views.py +++ b/netbox/tenancy/tests/test_views.py @@ -25,7 +25,7 @@ class TenantGroupTestCase(TestCase): url = reverse('tenancy:tenantgroup_list') response = self.client.get(url, follow=True) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_tenantgroup_import(self): self.add_permissions('tenancy.add_tenantgroup') @@ -39,7 +39,7 @@ class TenantGroupTestCase(TestCase): response = self.client.post(reverse('tenancy:tenantgroup_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(TenantGroup.objects.count(), 6) @@ -68,13 +68,13 @@ class TenantTestCase(TestCase): } response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)), follow=True) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 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) + self.assertHttpStatus(response, 200) def test_tenant_import(self): self.add_permissions('tenancy.add_tenant') @@ -88,5 +88,5 @@ class TenantTestCase(TestCase): response = self.client.post(reverse('tenancy:tenant_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(Tenant.objects.count(), 6) diff --git a/netbox/virtualization/tests/test_views.py b/netbox/virtualization/tests/test_views.py index 67df8fe1e..df346d11e 100644 --- a/netbox/virtualization/tests/test_views.py +++ b/netbox/virtualization/tests/test_views.py @@ -25,7 +25,7 @@ class ClusterGroupTestCase(TestCase): url = reverse('virtualization:clustergroup_list') response = self.client.get(url) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_clustergroup_import(self): self.add_permissions('virtualization.add_clustergroup') @@ -39,7 +39,7 @@ class ClusterGroupTestCase(TestCase): response = self.client.post(reverse('virtualization:clustergroup_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(ClusterGroup.objects.count(), 6) @@ -62,7 +62,7 @@ class ClusterTypeTestCase(TestCase): url = reverse('virtualization:clustertype_list') response = self.client.get(url) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_clustertype_import(self): self.add_permissions('virtualization.add_clustertype') @@ -76,7 +76,7 @@ class ClusterTypeTestCase(TestCase): response = self.client.post(reverse('virtualization:clustertype_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(ClusterType.objects.count(), 6) @@ -109,13 +109,13 @@ class ClusterTestCase(TestCase): } response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_cluster(self): cluster = Cluster.objects.first() response = self.client.get(cluster.get_absolute_url()) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_cluster_import(self): self.add_permissions('virtualization.add_cluster') @@ -129,7 +129,7 @@ class ClusterTestCase(TestCase): response = self.client.post(reverse('virtualization:cluster_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(Cluster.objects.count(), 6) @@ -161,13 +161,13 @@ class VirtualMachineTestCase(TestCase): } response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_virtualmachine(self): virtualmachine = VirtualMachine.objects.first() response = self.client.get(virtualmachine.get_absolute_url()) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_virtualmachine_import(self): self.add_permissions('virtualization.add_virtualmachine') @@ -181,5 +181,5 @@ class VirtualMachineTestCase(TestCase): response = self.client.post(reverse('virtualization:virtualmachine_import'), {'csv': '\n'.join(csv_data)}) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) self.assertEqual(VirtualMachine.objects.count(), 6) From a44c4d14e4dd4ad3cf71e4d41cf2cd0096418ee6 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 30 Jan 2020 18:13:02 -0500 Subject: [PATCH 05/25] Convert view tests under extras to the new TestCase --- netbox/extras/tests/test_views.py | 41 +++++++++++++++++-------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/netbox/extras/tests/test_views.py b/netbox/extras/tests/test_views.py index 792390121..fc77a81f5 100644 --- a/netbox/extras/tests/test_views.py +++ b/netbox/extras/tests/test_views.py @@ -2,21 +2,21 @@ 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 TestCase class TagTestCase(TestCase): + user_permissions = ( + 'extras.view_tag', + ) - def setUp(self): - user = create_test_user(permissions=['extras.view_tag']) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): Tag.objects.bulk_create([ Tag(name='Tag 1', slug='tag-1'), @@ -32,15 +32,16 @@ class TagTestCase(TestCase): } response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) class ConfigContextTestCase(TestCase): + user_permissions = ( + 'extras.view_configcontext', + ) - def setUp(self): - user = create_test_user(permissions=['extras.view_configcontext']) - self.client = Client() - self.client.force_login(user) + @classmethod + def setUpTestData(cls): site = Site(name='Site 1', slug='site-1') site.save() @@ -62,26 +63,28 @@ class ConfigContextTestCase(TestCase): } response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 200) def test_configcontext(self): configcontext = ConfigContext.objects.first() response = self.client.get(configcontext.get_absolute_url()) - self.assertEqual(response.status_code, 200) + self.assertHttpStatus(response, 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 +99,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) From e01c984c01fcd45a9976067f5bd043d661038739 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 30 Jan 2020 20:48:26 -0500 Subject: [PATCH 06/25] Introduced a custom model_to_dict() --- netbox/utilities/testing.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/netbox/utilities/testing.py b/netbox/utilities/testing.py index f86a32ab0..629810995 100644 --- a/netbox/utilities/testing.py +++ b/netbox/utilities/testing.py @@ -2,6 +2,7 @@ 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 from django.test import Client, TestCase as _TestCase from rest_framework.test import APIClient @@ -69,6 +70,24 @@ class APITestCase(TestCase): self.header = {'HTTP_AUTHORIZATION': 'Token {}'.format(self.token.key)} +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 + - 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) + + if 'tags' in model_dict: + model_dict['tags'] = ','.join(sorted([tag.name for tag in model_dict['tags']])) + + return model_dict + + def create_test_user(username='testuser', permissions=list()): """ Create a User with the given permissions. From 98cce7eee4aa4fe2642ad45b3eea0e982de050da Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 30 Jan 2020 21:56:29 -0500 Subject: [PATCH 07/25] Added ViewTestCase (WIP) --- netbox/circuits/tests/test_views.py | 153 +++++++++++----------------- netbox/utilities/testing.py | 146 ++++++++++++++++++++++++++ 2 files changed, 204 insertions(+), 95 deletions(-) diff --git a/netbox/circuits/tests/test_views.py b/netbox/circuits/tests/test_views.py index 6ecc88e5c..bebc3b287 100644 --- a/netbox/circuits/tests/test_views.py +++ b/netbox/circuits/tests/test_views.py @@ -1,58 +1,59 @@ -import urllib.parse - -from django.urls import reverse +import datetime +from circuits.choices import * from circuits.models import Circuit, CircuitType, Provider -from utilities.testing import TestCase +from utilities.testing import ViewTestCase -class ProviderTestCase(TestCase): - user_permissions = ( - 'circuits.view_provider', +class ProviderTestCase(ViewTestCase): + model = Provider + 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', + } + csv_data = ( + "name,slug", + "Provider 4,provider-4", + "Provider 5,provider-5", + "Provider 6,provider-6", ) @classmethod def setUpTestData(cls): + Provider.objects.bulk_create([ Provider(name='Provider 1', slug='provider-1', asn=65001), Provider(name='Provider 2', slug='provider-2', asn=65002), Provider(name='Provider 3', slug='provider-3', asn=65003), ]) - def test_provider_list(self): - url = reverse('circuits:provider_list') - params = { - "q": "test", - } - response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertHttpStatus(response, 200) - - def test_provider(self): - provider = Provider.objects.first() - response = self.client.get(provider.get_absolute_url()) - self.assertHttpStatus(response, 200) - - def test_provider_import(self): - self.add_permissions('circuits.add_provider') - 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.assertHttpStatus(response, 200) - self.assertEqual(Provider.objects.count(), 6) - - -class CircuitTypeTestCase(TestCase): - user_permissions = ( - 'circuits.view_circuittype', +class CircuitTypeTestCase(ViewTestCase): + model = CircuitType + views = ('list', 'add', 'edit', 'import') + form_data = { + 'name': 'Circuit Type X', + 'slug': 'circuit-type-x', + 'description': 'A new circuit type', + } + csv_data = ( + "name,slug", + "Circuit Type 4,circuit-type-4", + "Circuit Type 5,circuit-type-5", + "Circuit Type 6,circuit-type-6", ) + # Disable inapplicable tests + test_get_object = None + test_delete_object = None + @classmethod def setUpTestData(cls): @@ -62,32 +63,26 @@ class CircuitTypeTestCase(TestCase): CircuitType(name='Circuit Type 3', slug='circuit-type-3'), ]) - def test_circuittype_list(self): - url = reverse('circuits:circuittype_list') - - response = self.client.get(url) - self.assertHttpStatus(response, 200) - - def test_circuittype_import(self): - self.add_permissions('circuits.add_circuittype') - - 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.assertHttpStatus(response, 200) - self.assertEqual(CircuitType.objects.count(), 6) - - -class CircuitTestCase(TestCase): - user_permissions = ( - 'circuits.view_circuit', +class CircuitTestCase(ViewTestCase): + model = Circuit + # TODO: Determine how to lazily resolve related objects + form_data = { + 'cid': 'Circuit X', + 'provider': Provider.objects.first(), + 'type': CircuitType.objects.first(), + 'status': CircuitStatusChoices.STATUS_ACTIVE, + 'tenant': None, + 'install_date': datetime.date(2020, 1, 1), + 'commit_rate': 1000, + 'description': 'A new circuit', + 'comments': 'Some comments', + } + 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", ) @classmethod @@ -104,35 +99,3 @@ class CircuitTestCase(TestCase): Circuit(cid='Circuit 2', provider=provider, type=circuittype), 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, - } - - response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) - self.assertHttpStatus(response, 200) - - def test_circuit(self): - - circuit = Circuit.objects.first() - response = self.client.get(circuit.get_absolute_url()) - self.assertHttpStatus(response, 200) - - def test_circuit_import(self): - self.add_permissions('circuits.add_circuit') - - 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.assertHttpStatus(response, 200) - self.assertEqual(Circuit.objects.count(), 6) diff --git a/netbox/utilities/testing.py b/netbox/utilities/testing.py index 629810995..d0004c8dd 100644 --- a/netbox/utilities/testing.py +++ b/netbox/utilities/testing.py @@ -2,8 +2,10 @@ import logging from contextlib import contextmanager from django.contrib.auth.models import Permission, User +from django.core.exceptions import ObjectDoesNotExist from django.forms.models import model_to_dict as _model_to_dict from django.test import Client, TestCase as _TestCase +from django.urls import reverse from rest_framework.test import APIClient from users.models import Token @@ -70,6 +72,133 @@ class APITestCase(TestCase): self.header = {'HTTP_AUTHORIZATION': 'Token {}'.format(self.token.key)} +# TODO: Omit this from tests +class ViewTestCase(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 = {} + + def __init__(self, *args, **kwargs): + + super().__init__(*args, **kwargs) + + self.base_url_name = '{}:{}_{{}}'.format(self.model._meta.app_label, self.model._meta.model_name) + + def test_list_objects(self): + response = self.client.get(reverse(self.base_url_name.format('list'))) + self.assertHttpStatus(response, 200) + + def test_get_object(self): + instance = self.model.objects.first() + response = self.client.get(instance.get_absolute_url()) + self.assertHttpStatus(response, 200) + + def test_create_object(self): + initial_count = self.model.objects.count() + request = { + 'path': reverse(self.base_url_name.format('add')), + 'data': post_data(self.form_data), + 'follow': True, + } + print(request['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('{}.add_{}'.format(self.model._meta.app_label, self.model._meta.model_name)) + response = self.client.post(**request) + self.assertHttpStatus(response, 200) + + self.assertEqual(initial_count, self.model.objects.count() + 1) + instance = self.model.objects.order_by('-pk').first() + self.assertDictEqual(model_to_dict(instance), self.form_data) + + def test_edit_object(self): + instance = self.model.objects.first() + + # Determine the proper kwargs to pass to the edit URL + if hasattr(instance, 'slug'): + kwargs = {'slug': instance.slug} + else: + kwargs = {'pk': instance.pk} + + request = { + 'path': reverse(self.base_url_name.format('edit'), kwargs=kwargs), + 'data': post_data(self.form_data), + 'follow': True, + } + + # 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, 200) + + instance = self.model.objects.get(pk=instance.pk) + self.assertDictEqual(model_to_dict(instance), self.form_data) + + def test_delete_object(self): + instance = self.model.objects.first() + + # Determine the proper kwargs to pass to the deletion URL + if hasattr(instance, 'slug'): + kwargs = {'slug': instance.slug} + else: + kwargs = {'pk': instance.pk} + + request = { + 'path': reverse(self.base_url_name.format('delete'), kwargs=kwargs), + 'data': {'confirm': True}, + 'follow': True, + } + + # 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, 200) + + with self.assertRaises(ObjectDoesNotExist): + self.model.objects.get(pk=instance.pk) + + def test_import_objects(self): + request = { + 'path': reverse(self.base_url_name.format('import')), + 'data': { + 'csv': '\n'.join(self.csv_data) + } + } + initial_count = self.model.objects.count() + + # 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, 200) + + self.assertEqual(self.model.objects.count(), initial_count + len(self.csv_data) - 1) + + def model_to_dict(instance, fields=None, exclude=None): """ Customized wrapper for Django's built-in model_to_dict(). Does the following: @@ -88,6 +217,23 @@ def model_to_dict(instance, fields=None, exclude=None): 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 hasattr(value, 'pk'): + ret[key] = getattr(value, 'pk') + else: + ret[key] = str(value) + + return ret + + def create_test_user(username='testuser', permissions=list()): """ Create a User with the given permissions. From 939b5f2e292f6f885c8f1d49068ffe36a8caec8f Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 31 Jan 2020 09:00:01 -0500 Subject: [PATCH 08/25] Reorganize test classes to prevent unittest from running the base TestCases --- netbox/circuits/tests/test_views.py | 8 +- netbox/utilities/testing.py | 286 -------------------------- netbox/utilities/testing/__init__.py | 2 + netbox/utilities/testing/testcases.py | 200 ++++++++++++++++++ netbox/utilities/testing/utils.py | 92 +++++++++ 5 files changed, 298 insertions(+), 290 deletions(-) delete mode 100644 netbox/utilities/testing.py create mode 100644 netbox/utilities/testing/__init__.py create mode 100644 netbox/utilities/testing/testcases.py create mode 100644 netbox/utilities/testing/utils.py diff --git a/netbox/circuits/tests/test_views.py b/netbox/circuits/tests/test_views.py index bebc3b287..18f40dfbd 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 ViewTestCase +from utilities.testing import StandardTestCases -class ProviderTestCase(ViewTestCase): +class ProviderTestCase(StandardTestCases.Views): model = Provider form_data = { 'name': 'Provider X', @@ -35,7 +35,7 @@ class ProviderTestCase(ViewTestCase): ]) -class CircuitTypeTestCase(ViewTestCase): +class CircuitTypeTestCase(StandardTestCases.Views): model = CircuitType views = ('list', 'add', 'edit', 'import') form_data = { @@ -64,7 +64,7 @@ class CircuitTypeTestCase(ViewTestCase): ]) -class CircuitTestCase(ViewTestCase): +class CircuitTestCase(StandardTestCases.Views): model = Circuit # TODO: Determine how to lazily resolve related objects form_data = { diff --git a/netbox/utilities/testing.py b/netbox/utilities/testing.py deleted file mode 100644 index d0004c8dd..000000000 --- a/netbox/utilities/testing.py +++ /dev/null @@ -1,286 +0,0 @@ -import logging -from contextlib import contextmanager - -from django.contrib.auth.models import Permission, User -from django.core.exceptions import ObjectDoesNotExist -from django.forms.models import model_to_dict as _model_to_dict -from django.test import Client, TestCase as _TestCase -from django.urls import reverse -from rest_framework.test import APIClient - -from users.models import Token - - -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)} - - -# TODO: Omit this from tests -class ViewTestCase(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 = {} - - def __init__(self, *args, **kwargs): - - super().__init__(*args, **kwargs) - - self.base_url_name = '{}:{}_{{}}'.format(self.model._meta.app_label, self.model._meta.model_name) - - def test_list_objects(self): - response = self.client.get(reverse(self.base_url_name.format('list'))) - self.assertHttpStatus(response, 200) - - def test_get_object(self): - instance = self.model.objects.first() - response = self.client.get(instance.get_absolute_url()) - self.assertHttpStatus(response, 200) - - def test_create_object(self): - initial_count = self.model.objects.count() - request = { - 'path': reverse(self.base_url_name.format('add')), - 'data': post_data(self.form_data), - 'follow': True, - } - print(request['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('{}.add_{}'.format(self.model._meta.app_label, self.model._meta.model_name)) - response = self.client.post(**request) - self.assertHttpStatus(response, 200) - - self.assertEqual(initial_count, self.model.objects.count() + 1) - instance = self.model.objects.order_by('-pk').first() - self.assertDictEqual(model_to_dict(instance), self.form_data) - - def test_edit_object(self): - instance = self.model.objects.first() - - # Determine the proper kwargs to pass to the edit URL - if hasattr(instance, 'slug'): - kwargs = {'slug': instance.slug} - else: - kwargs = {'pk': instance.pk} - - request = { - 'path': reverse(self.base_url_name.format('edit'), kwargs=kwargs), - 'data': post_data(self.form_data), - 'follow': True, - } - - # 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, 200) - - instance = self.model.objects.get(pk=instance.pk) - self.assertDictEqual(model_to_dict(instance), self.form_data) - - def test_delete_object(self): - instance = self.model.objects.first() - - # Determine the proper kwargs to pass to the deletion URL - if hasattr(instance, 'slug'): - kwargs = {'slug': instance.slug} - else: - kwargs = {'pk': instance.pk} - - request = { - 'path': reverse(self.base_url_name.format('delete'), kwargs=kwargs), - 'data': {'confirm': True}, - 'follow': True, - } - - # 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, 200) - - with self.assertRaises(ObjectDoesNotExist): - self.model.objects.get(pk=instance.pk) - - def test_import_objects(self): - request = { - 'path': reverse(self.base_url_name.format('import')), - 'data': { - 'csv': '\n'.join(self.csv_data) - } - } - initial_count = self.model.objects.count() - - # 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, 200) - - self.assertEqual(self.model.objects.count(), initial_count + len(self.csv_data) - 1) - - -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 - - 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) - - if 'tags' in model_dict: - model_dict['tags'] = ','.join(sorted([tag.name for tag in model_dict['tags']])) - - 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 hasattr(value, 'pk'): - ret[key] = getattr(value, 'pk') - 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/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..ef9660fa7 --- /dev/null +++ b/netbox/utilities/testing/testcases.py @@ -0,0 +1,200 @@ +from django.contrib.auth.models import Permission, User +from django.core.exceptions import ObjectDoesNotExist +from django.test import Client, TestCase as _TestCase +from django.urls import reverse +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 = {} + + def __init__(self, *args, **kwargs): + + super().__init__(*args, **kwargs) + + if self.model is not None: + self.base_url_name = '{}:{}_{{}}'.format(self.model._meta.app_label, self.model._meta.model_name) + + def test_list_objects(self): + response = self.client.get(reverse(self.base_url_name.format('list'))) + self.assertHttpStatus(response, 200) + + def test_get_object(self): + instance = self.model.objects.first() + response = self.client.get(instance.get_absolute_url()) + self.assertHttpStatus(response, 200) + + def test_create_object(self): + initial_count = self.model.objects.count() + request = { + 'path': reverse(self.base_url_name.format('add')), + 'data': post_data(self.form_data), + 'follow': True, + } + + # 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, 200) + + 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) + + def test_edit_object(self): + instance = self.model.objects.first() + + # Determine the proper kwargs to pass to the edit URL + if hasattr(instance, 'slug'): + kwargs = {'slug': instance.slug} + else: + kwargs = {'pk': instance.pk} + + request = { + 'path': reverse(self.base_url_name.format('edit'), kwargs=kwargs), + 'data': post_data(self.form_data), + 'follow': True, + } + + # 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, 200) + + instance = self.model.objects.get(pk=instance.pk) + self.assertDictEqual(model_to_dict(instance), self.form_data) + + def test_delete_object(self): + instance = self.model.objects.first() + + # Determine the proper kwargs to pass to the deletion URL + if hasattr(instance, 'slug'): + kwargs = {'slug': instance.slug} + else: + kwargs = {'pk': instance.pk} + + request = { + 'path': reverse(self.base_url_name.format('delete'), kwargs=kwargs), + 'data': {'confirm': True}, + 'follow': True, + } + + # 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, 200) + + with self.assertRaises(ObjectDoesNotExist): + self.model.objects.get(pk=instance.pk) + + def test_import_objects(self): + initial_count = self.model.objects.count() + request = { + 'path': reverse(self.base_url_name.format('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('{}.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..8066cc710 --- /dev/null +++ b/netbox/utilities/testing/utils.py @@ -0,0 +1,92 @@ +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 + - 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) + + if 'tags' in model_dict: + model_dict['tags'] = ','.join(sorted([tag.name for tag in model_dict['tags']])) + + 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 hasattr(value, 'pk'): + ret[key] = getattr(value, 'pk') + 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) From 78d43a5d66bedbb5e41443082949106d528e586a Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 31 Jan 2020 09:27:41 -0500 Subject: [PATCH 09/25] Move form/CSV data declaration under setUpTestData --- netbox/circuits/tests/test_views.py | 99 +++++++++++++++-------------- netbox/utilities/testing/utils.py | 2 - 2 files changed, 52 insertions(+), 49 deletions(-) diff --git a/netbox/circuits/tests/test_views.py b/netbox/circuits/tests/test_views.py index 18f40dfbd..1e065f458 100644 --- a/netbox/circuits/tests/test_views.py +++ b/netbox/circuits/tests/test_views.py @@ -7,23 +7,6 @@ from utilities.testing import StandardTestCases class ProviderTestCase(StandardTestCases.Views): model = Provider - 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', - } - csv_data = ( - "name,slug", - "Provider 4,provider-4", - "Provider 5,provider-5", - "Provider 6,provider-6", - ) @classmethod def setUpTestData(cls): @@ -34,21 +17,28 @@ class ProviderTestCase(StandardTestCases.Views): Provider(name='Provider 3', slug='provider-3', asn=65003), ]) + 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', + } + + cls.csv_data = ( + "name,slug", + "Provider 4,provider-4", + "Provider 5,provider-5", + "Provider 6,provider-6", + ) + class CircuitTypeTestCase(StandardTestCases.Views): model = CircuitType - views = ('list', 'add', 'edit', 'import') - form_data = { - 'name': 'Circuit Type X', - 'slug': 'circuit-type-x', - 'description': 'A new circuit type', - } - csv_data = ( - "name,slug", - "Circuit Type 4,circuit-type-4", - "Circuit Type 5,circuit-type-5", - "Circuit Type 6,circuit-type-6", - ) # Disable inapplicable tests test_get_object = None @@ -63,27 +53,22 @@ class CircuitTypeTestCase(StandardTestCases.Views): CircuitType(name='Circuit Type 3', slug='circuit-type-3'), ]) + cls.form_data = { + 'name': 'Circuit Type X', + 'slug': 'circuit-type-x', + 'description': 'A new circuit type', + } + + cls.csv_data = ( + "name,slug", + "Circuit Type 4,circuit-type-4", + "Circuit Type 5,circuit-type-5", + "Circuit Type 6,circuit-type-6", + ) + class CircuitTestCase(StandardTestCases.Views): model = Circuit - # TODO: Determine how to lazily resolve related objects - form_data = { - 'cid': 'Circuit X', - 'provider': Provider.objects.first(), - 'type': CircuitType.objects.first(), - 'status': CircuitStatusChoices.STATUS_ACTIVE, - 'tenant': None, - 'install_date': datetime.date(2020, 1, 1), - 'commit_rate': 1000, - 'description': 'A new circuit', - 'comments': 'Some comments', - } - 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", - ) @classmethod def setUpTestData(cls): @@ -99,3 +84,23 @@ class CircuitTestCase(StandardTestCases.Views): Circuit(cid='Circuit 2', provider=provider, type=circuittype), Circuit(cid='Circuit 3', provider=provider, type=circuittype), ]) + + 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', + } + + 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", + ) diff --git a/netbox/utilities/testing/utils.py b/netbox/utilities/testing/utils.py index 8066cc710..61b3ed64d 100644 --- a/netbox/utilities/testing/utils.py +++ b/netbox/utilities/testing/utils.py @@ -32,8 +32,6 @@ def post_data(data): for key, value in data.items(): if value is None: ret[key] = '' - elif hasattr(value, 'pk'): - ret[key] = getattr(value, 'pk') else: ret[key] = str(value) From 7daf1df22da42efe3312a4f7a40ddd442dc68150 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 31 Jan 2020 10:30:13 -0500 Subject: [PATCH 10/25] Add _get_url() for View test case --- netbox/utilities/testing/testcases.py | 57 +++++++++++++++++---------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/netbox/utilities/testing/testcases.py b/netbox/utilities/testing/testcases.py index ef9660fa7..90138c3d9 100644 --- a/netbox/utilities/testing/testcases.py +++ b/netbox/utilities/testing/testcases.py @@ -1,7 +1,7 @@ from django.contrib.auth.models import Permission, User from django.core.exceptions import ObjectDoesNotExist from django.test import Client, TestCase as _TestCase -from django.urls import reverse +from django.urls import reverse, NoReverseMatch from rest_framework.test import APIClient from users.models import Token @@ -88,15 +88,44 @@ class StandardTestCases: form_data = {} csv_data = {} + maxDiff = None + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - if self.model is not None: - self.base_url_name = '{}:{}_{{}}'.format(self.model._meta.app_label, self.model._meta.model_name) + 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)) def test_list_objects(self): - response = self.client.get(reverse(self.base_url_name.format('list'))) + response = self.client.get(self._get_url('list')) self.assertHttpStatus(response, 200) def test_get_object(self): @@ -107,7 +136,7 @@ class StandardTestCases: def test_create_object(self): initial_count = self.model.objects.count() request = { - 'path': reverse(self.base_url_name.format('add')), + 'path': self._get_url('add'), 'data': post_data(self.form_data), 'follow': True, } @@ -128,14 +157,8 @@ class StandardTestCases: def test_edit_object(self): instance = self.model.objects.first() - # Determine the proper kwargs to pass to the edit URL - if hasattr(instance, 'slug'): - kwargs = {'slug': instance.slug} - else: - kwargs = {'pk': instance.pk} - request = { - 'path': reverse(self.base_url_name.format('edit'), kwargs=kwargs), + 'path': self._get_url('edit', instance), 'data': post_data(self.form_data), 'follow': True, } @@ -155,14 +178,8 @@ class StandardTestCases: def test_delete_object(self): instance = self.model.objects.first() - # Determine the proper kwargs to pass to the deletion URL - if hasattr(instance, 'slug'): - kwargs = {'slug': instance.slug} - else: - kwargs = {'pk': instance.pk} - request = { - 'path': reverse(self.base_url_name.format('delete'), kwargs=kwargs), + 'path': self._get_url('delete', instance), 'data': {'confirm': True}, 'follow': True, } @@ -182,7 +199,7 @@ class StandardTestCases: def test_import_objects(self): initial_count = self.model.objects.count() request = { - 'path': reverse(self.base_url_name.format('import')), + 'path': self._get_url('import'), 'data': { 'csv': '\n'.join(self.csv_data) } From 6a17be740b18d1797f5e18b075adb0a286c3aa2a Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 31 Jan 2020 11:50:12 -0500 Subject: [PATCH 11/25] post_data(): Ignore iterables --- netbox/utilities/testing/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/netbox/utilities/testing/utils.py b/netbox/utilities/testing/utils.py index 61b3ed64d..cdd08d5ac 100644 --- a/netbox/utilities/testing/utils.py +++ b/netbox/utilities/testing/utils.py @@ -32,6 +32,8 @@ def post_data(data): 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) From a208cbdf0b6439fd9fc59050ea9c9afb304bc75d Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 31 Jan 2020 12:14:51 -0500 Subject: [PATCH 12/25] model_to_dict(): Remove fields that start with an underscore --- netbox/utilities/testing/utils.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/netbox/utilities/testing/utils.py b/netbox/utilities/testing/utils.py index cdd08d5ac..0cc2e6ce0 100644 --- a/netbox/utilities/testing/utils.py +++ b/netbox/utilities/testing/utils.py @@ -9,6 +9,7 @@ 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'] @@ -17,6 +18,10 @@ def model_to_dict(instance, fields=None, exclude=None): model_dict = _model_to_dict(instance, fields=fields, exclude=_exclude) + for key in list(model_dict.keys()): + if key.startswith('_'): + del model_dict[key] + if 'tags' in model_dict: model_dict['tags'] = ','.join(sorted([tag.name for tag in model_dict['tags']])) From c14496d0c42919273a72adf32fa592dfb09bb660 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 31 Jan 2020 12:28:50 -0500 Subject: [PATCH 13/25] DeviceForm.manufacturer should not be a required field --- netbox/dcim/forms.py | 1 + 1 file changed, 1 insertion(+) 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={ From 86ef739c1201c0196179aa4a718df675b3b079f3 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 31 Jan 2020 12:32:33 -0500 Subject: [PATCH 14/25] Migrate (most) DCIM view tests to use StandardTestCases --- netbox/dcim/tests/test_views.py | 589 +++++++++++++++----------------- 1 file changed, 267 insertions(+), 322 deletions(-) diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index 6a07e0153..35fdf3aa4 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -1,54 +1,53 @@ import urllib.parse +from decimal import Decimal +import pytz import yaml 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 TestCase +from utilities.testing import StandardTestCases, TestCase -class RegionTestCase(TestCase): - user_permissions = ( - 'dcim.view_region', - ) +class RegionTestCase(StandardTestCases.Views): + model = Region + + # 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.assertHttpStatus(response, 200) - - def test_region_import(self): - self.add_permissions('dcim.add_region') - - 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.assertHttpStatus(response, 200) - self.assertEqual(Region.objects.count(), 6) - - -class SiteTestCase(TestCase): - user_permissions = ( - 'dcim.view_site', - ) +class SiteTestCase(StandardTestCases.Views): + model = Site @classmethod def setUpTestData(cls): @@ -62,42 +61,41 @@ 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.assertHttpStatus(response, 200) - - def test_site(self): - - site = Site.objects.first() - response = self.client.get(site.get_absolute_url()) - self.assertHttpStatus(response, 200) - - def test_site_import(self): - self.add_permissions('dcim.add_site') - - 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.assertHttpStatus(response, 200) - self.assertEqual(Site.objects.count(), 6) +class RackGroupTestCase(StandardTestCases.Views): + model = RackGroup - -class RackGroupTestCase(TestCase): - user_permissions = ( - 'dcim.view_rackgroup', - ) + # Disable inapplicable tests + test_get_object = None + test_delete_object = None @classmethod def setUpTestData(cls): @@ -111,33 +109,26 @@ 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.assertHttpStatus(response, 200) - - def test_rackgroup_import(self): - self.add_permissions('dcim.add_rackgroup') - - 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.assertHttpStatus(response, 200) - self.assertEqual(RackGroup.objects.count(), 6) +class RackRoleTestCase(StandardTestCases.Views): + model = RackRole - -class RackRoleTestCase(TestCase): - user_permissions = ( - 'dcim.view_rackrole', - ) + # Disable inapplicable tests + test_get_object = None + test_delete_object = None @classmethod def setUpTestData(cls): @@ -148,33 +139,28 @@ 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.assertHttpStatus(response, 200) - - def test_rackrole_import(self): - self.add_permissions('dcim.add_rackrole') - - 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.assertHttpStatus(response, 200) - self.assertEqual(RackRole.objects.count(), 6) +class RackReservationTestCase(StandardTestCases.Views): + model = RackReservation - -class RackReservationTestCase(TestCase): - user_permissions = ( - 'dcim.view_rackreservation', - ) + # Disable inapplicable tests + test_get_object = None + test_create_object = None # TODO: Fix URL name for view + test_import_objects = None @classmethod def setUpTestData(cls): @@ -193,24 +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.assertHttpStatus(response, 200) + cls.form_data = { + 'rack': rack.pk, + 'units': [10, 11, 12], + 'user': user.pk, + 'tenant': None, + 'description': 'New reservation', + } -class RackTestCase(TestCase): - user_permissions = ( - 'dcim.view_rack', - ) +class RackTestCase(StandardTestCases.Views): + model = Rack + + # TODO: Remove this when #4067 is fixed + test_create_object = None @classmethod def setUpTestData(cls): - site = Site(name='Site 1', slug='site-1') - site.save() + 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), @@ -218,42 +207,41 @@ 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.assertHttpStatus(response, 200) - - def test_rack(self): - - rack = Rack.objects.first() - response = self.client.get(rack.get_absolute_url()) - self.assertHttpStatus(response, 200) - - def test_rack_import(self): - self.add_permissions('dcim.add_rack') - - 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.assertHttpStatus(response, 200) - self.assertEqual(Rack.objects.count(), 6) +class ManufacturerTestCase(StandardTestCases.Views): + model = Manufacturer - -class ManufacturerTypeTestCase(TestCase): - user_permissions = ( - 'dcim.view_manufacturer', - ) + # Disable inapplicable tests + test_get_object = None + test_delete_object = None @classmethod def setUpTestData(cls): @@ -264,33 +252,21 @@ 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.assertHttpStatus(response, 200) - - def test_manufacturer_import(self): - self.add_permissions('dcim.add_manufacturer') - - 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.assertHttpStatus(response, 200) - self.assertEqual(Manufacturer.objects.count(), 6) - - -class DeviceTypeTestCase(TestCase): - user_permissions = ( - 'dcim.view_devicetype', - ) +class DeviceTypeTestCase(StandardTestCases.Views): + model = DeviceType @classmethod def setUpTestData(cls): @@ -304,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.assertHttpStatus(response, 200) - - def test_devicetype_export(self): - - url = reverse('dcim:devicetype_list') - - response = self.client.get('{}?export'.format(url)) - self.assertHttpStatus(response, 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.assertHttpStatus(response, 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 @@ -471,11 +434,24 @@ device-bays: db1 = DeviceBayTemplate.objects.first() self.assertEqual(db1.name, 'Device Bay 1') + def test_devicetype_export(self): -class DeviceRoleTestCase(TestCase): - user_permissions = ( - 'dcim.view_devicerole', - ) + 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') + + +class DeviceRoleTestCase(StandardTestCases.Views): + model = DeviceRole + + # Disable inapplicable tests + test_get_object = None + test_delete_object = None @classmethod def setUpTestData(cls): @@ -486,85 +462,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.assertHttpStatus(response, 200) - - def test_devicerole_import(self): - self.add_permissions('dcim.add_devicerole') - - 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.assertHttpStatus(response, 200) - self.assertEqual(DeviceRole.objects.count(), 6) +class PlatformTestCase(StandardTestCases.Views): + model = Platform - -class PlatformTestCase(TestCase): - user_permissions = ( - 'dcim.view_platform', - ) + # Disable inapplicable tests + test_get_object = None + test_delete_object = None @classmethod def setUpTestData(cls): + 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.assertHttpStatus(response, 200) - - def test_platform_import(self): - self.add_permissions('dcim.add_platform') - - 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.assertHttpStatus(response, 200) - self.assertEqual(Platform.objects.count(), 6) - - -class DeviceTestCase(TestCase): - user_permissions = ( - 'dcim.view_device', - ) +class DeviceTestCase(StandardTestCases.Views): + model = Device @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(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), @@ -572,39 +531,39 @@ 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.assertHttpStatus(response, 200) - - def test_device(self): - - device = Device.objects.first() - response = self.client.get(device.get_absolute_url()) - self.assertHttpStatus(response, 200) - - def test_device_import(self): - self.add_permissions('dcim.add_device') - - 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.assertHttpStatus(response, 200) - self.assertEqual(Device.objects.count(), 6) - +# TODO: Convert to StandardTestCases.Views class ConsolePortTestCase(TestCase): user_permissions = ( 'dcim.view_consoleport', @@ -657,6 +616,7 @@ class ConsolePortTestCase(TestCase): self.assertEqual(ConsolePort.objects.count(), 6) +# TODO: Convert to StandardTestCases.Views class ConsoleServerPortTestCase(TestCase): user_permissions = ( 'dcim.view_consoleserverport', @@ -709,6 +669,7 @@ class ConsoleServerPortTestCase(TestCase): self.assertEqual(ConsoleServerPort.objects.count(), 6) +# TODO: Convert to StandardTestCases.Views class PowerPortTestCase(TestCase): user_permissions = ( 'dcim.view_powerport', @@ -761,6 +722,7 @@ class PowerPortTestCase(TestCase): self.assertEqual(PowerPort.objects.count(), 6) +# TODO: Convert to StandardTestCases.Views class PowerOutletTestCase(TestCase): user_permissions = ( 'dcim.view_poweroutlet', @@ -813,6 +775,7 @@ class PowerOutletTestCase(TestCase): self.assertEqual(PowerOutlet.objects.count(), 6) +# TODO: Convert to StandardTestCases.Views class InterfaceTestCase(TestCase): user_permissions = ( 'dcim.view_interface', @@ -865,6 +828,7 @@ class InterfaceTestCase(TestCase): self.assertEqual(Interface.objects.count(), 6) +# TODO: Convert to StandardTestCases.Views class FrontPortTestCase(TestCase): user_permissions = ( 'dcim.view_frontport', @@ -929,6 +893,7 @@ class FrontPortTestCase(TestCase): self.assertEqual(FrontPort.objects.count(), 6) +# TODO: Convert to StandardTestCases.Views class RearPortTestCase(TestCase): user_permissions = ( 'dcim.view_rearport', @@ -981,6 +946,7 @@ class RearPortTestCase(TestCase): self.assertEqual(RearPort.objects.count(), 6) +# TODO: Convert to StandardTestCases.Views class DeviceBayTestCase(TestCase): user_permissions = ( 'dcim.view_devicebay', @@ -1037,6 +1003,7 @@ class DeviceBayTestCase(TestCase): self.assertEqual(DeviceBay.objects.count(), 6) +# TODO: Convert to StandardTestCases.Views class InventoryItemTestCase(TestCase): user_permissions = ( 'dcim.view_inventoryitem', @@ -1092,26 +1059,19 @@ class InventoryItemTestCase(TestCase): self.assertEqual(InventoryItem.objects.count(), 6) -class CableTestCase(TestCase): - user_permissions = ( - 'dcim.view_cable', - ) +class CableTestCase(StandardTestCases.Views): + model = Cable + + # TODO: Creation URL needs termination context + test_create_object = None @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(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') + 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) @@ -1121,67 +1081,59 @@ 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.assertHttpStatus(response, 200) - - def test_cable(self): - - cable = Cable.objects.first() - response = self.client.get(cable.get_absolute_url()) - self.assertHttpStatus(response, 200) - - def test_cable_import(self): - self.add_permissions('dcim.add_cable') - - 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.assertHttpStatus(response, 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): - user_permissions = ( - 'dcim.view_virtualchassis', - ) + # TODO: Requires special form handling + test_create_object = None + test_edit_object = None @classmethod def setUpTestData(cls): @@ -1222,10 +1174,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.assertHttpStatus(response, 200) From c9d0dcecf301bc2fcb3c91fe4c78d888e69f0d5f Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 31 Jan 2020 13:44:34 -0500 Subject: [PATCH 15/25] model_to_dict(): Convert object lists to PK lists --- netbox/utilities/testing/utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/netbox/utilities/testing/utils.py b/netbox/utilities/testing/utils.py index 0cc2e6ce0..7ff45a30d 100644 --- a/netbox/utilities/testing/utils.py +++ b/netbox/utilities/testing/utils.py @@ -22,6 +22,10 @@ def model_to_dict(instance, fields=None, exclude=None): if key.startswith('_'): del model_dict[key] + # 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]] + if 'tags' in model_dict: model_dict['tags'] = ','.join(sorted([tag.name for tag in model_dict['tags']])) From ab7b9216411fdabd925506cf0b8f8097e1370c6e Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 31 Jan 2020 13:45:09 -0500 Subject: [PATCH 16/25] Convert extras view tests to StandardTestCases --- netbox/extras/tests/test_views.py | 70 ++++++++++++++++--------------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/netbox/extras/tests/test_views.py b/netbox/extras/tests/test_views.py index fc77a81f5..0fc60dcd9 100644 --- a/netbox/extras/tests/test_views.py +++ b/netbox/extras/tests/test_views.py @@ -7,44 +7,47 @@ 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 TestCase +from utilities.testing import StandardTestCases, TestCase -class TagTestCase(TestCase): - user_permissions = ( - 'extras.view_tag', - ) +class TagTestCase(StandardTestCases.Views): + model = Tag + + # Disable inapplicable tests + test_create_object = None + test_import_objects = None @classmethod def setUpTestData(cls): - Tag.objects.bulk_create([ + 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.assertHttpStatus(response, 200) +class ConfigContextTestCase(StandardTestCases.Views): + model = ConfigContext -class ConfigContextTestCase(TestCase): - user_permissions = ( - 'extras.view_configcontext', - ) + # Disable inapplicable tests + test_import_objects = None + + # TODO: Resolve model discrepancies when creating/editing ConfigContexts + test_create_object = None + test_edit_object = None @classmethod def setUpTestData(cls): - site = Site(name='Site 1', slug='site-1') - site.save() + site = Site.objects.create(name='Site 1', slug='site-1') # Create three ConfigContexts for i in range(1, 4): @@ -55,22 +58,21 @@ 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.assertHttpStatus(response, 200) - - def test_configcontext(self): - - configcontext = ConfigContext.objects.first() - response = self.client.get(configcontext.get_absolute_url()) - self.assertHttpStatus(response, 200) - class ObjectChangeTestCase(TestCase): user_permissions = ( From 936e3424bbffeba660543a397d9a6201b9fbbd96 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 31 Jan 2020 14:12:48 -0500 Subject: [PATCH 17/25] Refactor model_to_dict() to better handle tags --- netbox/utilities/testing/utils.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/netbox/utilities/testing/utils.py b/netbox/utilities/testing/utils.py index 7ff45a30d..6d20d4fff 100644 --- a/netbox/utilities/testing/utils.py +++ b/netbox/utilities/testing/utils.py @@ -22,13 +22,14 @@ def model_to_dict(instance, fields=None, exclude=None): 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]] - if 'tags' in model_dict: - model_dict['tags'] = ','.join(sorted([tag.name for tag in model_dict['tags']])) - return model_dict From 250bda2bf6089c98ff54f6fda2b6616f7c6b3077 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 31 Jan 2020 14:13:30 -0500 Subject: [PATCH 18/25] Extend and correct evaluation of view permissions --- netbox/utilities/testing/testcases.py | 54 +++++++++++++++++++++------ 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/netbox/utilities/testing/testcases.py b/netbox/utilities/testing/testcases.py index 90138c3d9..f43bacc54 100644 --- a/netbox/utilities/testing/testcases.py +++ b/netbox/utilities/testing/testcases.py @@ -1,6 +1,6 @@ from django.contrib.auth.models import Permission, User from django.core.exceptions import ObjectDoesNotExist -from django.test import Client, TestCase as _TestCase +from django.test import Client, TestCase as _TestCase, override_settings from django.urls import reverse, NoReverseMatch from rest_framework.test import APIClient @@ -124,21 +124,41 @@ class StandardTestCases: 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': True, + 'follow': False, # Do not follow 302 redirects } # Attempt to make the request without required permissions @@ -146,21 +166,24 @@ class StandardTestCases: 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)) + self.add_permissions( + '{}.add_{}'.format(self.model._meta.app_label, self.model._meta.model_name) + ) response = self.client.post(**request) - self.assertHttpStatus(response, 200) + 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': True, + 'follow': False, # Do not follow 302 redirects } # Attempt to make the request without required permissions @@ -168,20 +191,23 @@ class StandardTestCases: 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)) + self.add_permissions( + '{}.change_{}'.format(self.model._meta.app_label, self.model._meta.model_name) + ) response = self.client.post(**request) - self.assertHttpStatus(response, 200) + 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': True, + 'follow': False, # Do not follow 302 redirects } # Attempt to make the request without required permissions @@ -189,13 +215,16 @@ class StandardTestCases: 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)) + self.add_permissions( + '{}.delete_{}'.format(self.model._meta.app_label, self.model._meta.model_name) + ) response = self.client.post(**request) - self.assertHttpStatus(response, 200) + 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 = { @@ -210,7 +239,10 @@ class StandardTestCases: 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)) + 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) From 8881bba6968c69062ba75a51d5a64ab14919b6c7 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 31 Jan 2020 14:22:56 -0500 Subject: [PATCH 19/25] Suppress tag view test until #4071 is fixed --- netbox/extras/tests/test_views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/netbox/extras/tests/test_views.py b/netbox/extras/tests/test_views.py index 0fc60dcd9..0b9a0ffdf 100644 --- a/netbox/extras/tests/test_views.py +++ b/netbox/extras/tests/test_views.py @@ -17,6 +17,9 @@ class TagTestCase(StandardTestCases.Views): test_create_object = None test_import_objects = None + # TODO: Restore test when #4071 is resolved + test_get_object = None + @classmethod def setUpTestData(cls): From 3668aa21fe6434c67c58018bf3d9e2d2e0ef4a3b Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 31 Jan 2020 14:29:56 -0500 Subject: [PATCH 20/25] Fix DeviceTypeTestCase permissions assignment for custom tests --- netbox/dcim/tests/test_views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index 35fdf3aa4..8fc883812 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -371,6 +371,7 @@ device-bays: # Add all required permissions to the test user self.add_permissions( + 'dcim.view_devicetype', 'dcim.add_devicetype', 'dcim.add_consoleporttemplate', 'dcim.add_consoleserverporttemplate', @@ -437,6 +438,7 @@ device-bays: def test_devicetype_export(self): url = reverse('dcim:devicetype_list') + self.add_permissions('dcim.view_devicetype') response = self.client.get('{}?export'.format(url)) self.assertEqual(response.status_code, 200) From b361cb00f2519ff6185c948db8fb2e9abc4910c4 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 31 Jan 2020 15:19:10 -0500 Subject: [PATCH 21/25] Convert IPAM view tests to use StandardTestCases --- netbox/ipam/tests/test_views.py | 362 +++++++++++--------------------- 1 file changed, 128 insertions(+), 234 deletions(-) diff --git a/netbox/ipam/tests/test_views.py b/netbox/ipam/tests/test_views.py index 1ea5f2e2b..ca6a4c42b 100644 --- a/netbox/ipam/tests/test_views.py +++ b/netbox/ipam/tests/test_views.py @@ -1,18 +1,17 @@ -from netaddr import IPNetwork +import datetime import urllib.parse 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 TestCase +from utilities.testing import StandardTestCases, TestCase -class VRFTestCase(TestCase): - user_permissions = ( - 'ipam.view_vrf', - ) +class VRFTestCase(StandardTestCases.Views): + model = VRF @classmethod def setUpTestData(cls): @@ -23,42 +22,29 @@ 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.assertHttpStatus(response, 200) - - def test_vrf(self): - - vrf = VRF.objects.first() - response = self.client.get(vrf.get_absolute_url()) - self.assertHttpStatus(response, 200) - - def test_vrf_import(self): - self.add_permissions('ipam.add_vrf') - - 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.assertHttpStatus(response, 200) - self.assertEqual(VRF.objects.count(), 6) +class RIRTestCase(StandardTestCases.Views): + model = RIR - -class RIRTestCase(TestCase): - user_permissions = ( - 'ipam.view_rir', - ) + # Disable inapplicable tests + test_get_object = None + test_delete_object = None @classmethod def setUpTestData(cls): @@ -69,39 +55,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.assertHttpStatus(response, 200) - - def test_rir_import(self): - self.add_permissions('ipam.add_rir') - - 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.assertHttpStatus(response, 200) - self.assertEqual(RIR.objects.count(), 6) - - -class AggregateTestCase(TestCase): - user_permissions = ( - 'ipam.view_aggregate', - ) +class AggregateTestCase(StandardTestCases.Views): + model = Aggregate @classmethod def setUpTestData(cls): - 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), @@ -109,42 +83,29 @@ 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.assertHttpStatus(response, 200) - - def test_aggregate(self): - - aggregate = Aggregate.objects.first() - response = self.client.get(aggregate.get_absolute_url()) - self.assertHttpStatus(response, 200) - - def test_aggregate_import(self): - self.add_permissions('ipam.add_aggregate') - - 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.assertHttpStatus(response, 200) - self.assertEqual(Aggregate.objects.count(), 6) +class RoleTestCase(StandardTestCases.Views): + model = Role - -class RoleTestCase(TestCase): - user_permissions = ( - 'ipam.view_role', - ) + # Disable inapplicable tests + test_get_object = None + test_delete_object = None @classmethod def setUpTestData(cls): @@ -155,39 +116,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.assertHttpStatus(response, 200) - - def test_role_import(self): - self.add_permissions('ipam.add_role') - - 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.assertHttpStatus(response, 200) - self.assertEqual(Role.objects.count(), 6) - - -class PrefixTestCase(TestCase): - user_permissions = ( - 'ipam.view_prefix', - ) +class PrefixTestCase(StandardTestCases.Views): + model = Prefix @classmethod def setUpTestData(cls): - 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), @@ -195,48 +148,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.assertHttpStatus(response, 200) - - def test_prefix(self): - - prefix = Prefix.objects.first() - response = self.client.get(prefix.get_absolute_url()) - self.assertHttpStatus(response, 200) - - def test_prefix_import(self): - self.add_permissions('ipam.add_prefix') - - 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.assertHttpStatus(response, 200) - self.assertEqual(Prefix.objects.count(), 6) - - -class IPAddressTestCase(TestCase): - user_permissions = ( - 'ipam.view_ipaddress', - ) +class IPAddressTestCase(StandardTestCases.Views): + model = IPAddress @classmethod def setUpTestData(cls): - 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), @@ -244,48 +183,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.assertHttpStatus(response, 200) - - def test_ipaddress(self): - - ipaddress = IPAddress.objects.first() - response = self.client.get(ipaddress.get_absolute_url()) - self.assertHttpStatus(response, 200) - - def test_ipaddress_import(self): - self.add_permissions('ipam.add_ipaddress') - - 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.assertHttpStatus(response, 200) - self.assertEqual(IPAddress.objects.count(), 6) +class VLANGroupTestCase(StandardTestCases.Views): + model = VLANGroup - -class VLANGroupTestCase(TestCase): - user_permissions = ( - 'ipam.view_vlangroup', - ) + # Disable inapplicable tests + test_get_object = None + test_delete_object = None @classmethod def setUpTestData(cls): - 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), @@ -293,42 +222,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.assertHttpStatus(response, 200) - - def test_vlangroup_import(self): - self.add_permissions('ipam.add_vlangroup') - - 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.assertHttpStatus(response, 200) - self.assertEqual(VLANGroup.objects.count(), 6) - - -class VLANTestCase(TestCase): - user_permissions = ( - 'ipam.view_vlan', - ) +class VLANTestCase(StandardTestCases.Views): + model = VLAN @classmethod def setUpTestData(cls): - 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'), @@ -336,60 +252,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.assertHttpStatus(response, 200) - - def test_vlan(self): - - vlan = VLAN.objects.first() - response = self.client.get(vlan.get_absolute_url()) - self.assertHttpStatus(response, 200) - - def test_vlan_import(self): - self.add_permissions('ipam.add_vlan') - - 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.assertHttpStatus(response, 200) - self.assertEqual(VLAN.objects.count(), 6) +class ServiceTestCase(StandardTestCases.Views): + model = Service + # Disable inapplicable tests + test_import_objects = None -class ServiceTestCase(TestCase): - user_permissions = ( - 'ipam.view_service', - ) + # TODO: Resolve URL for Service creation + test_create_object = None @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), @@ -397,18 +296,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.assertHttpStatus(response, 200) - - def test_service(self): - - service = Service.objects.first() - response = self.client.get(service.get_absolute_url()) - self.assertHttpStatus(response, 200) From e8e39dc5e3ec65400f73b641120da77fe130c0c2 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 31 Jan 2020 15:37:58 -0500 Subject: [PATCH 22/25] Convert secrets view tests to use StandardTestCases --- netbox/secrets/tests/test_views.py | 90 +++++++++++------------------- 1 file changed, 34 insertions(+), 56 deletions(-) diff --git a/netbox/secrets/tests/test_views.py b/netbox/secrets/tests/test_views.py index 336a33320..87c1c3683 100644 --- a/netbox/secrets/tests/test_views.py +++ b/netbox/secrets/tests/test_views.py @@ -5,14 +5,16 @@ 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 TestCase +from utilities.testing import StandardTestCases, TestCase from .constants import PRIVATE_KEY, PUBLIC_KEY -class SecretRoleTestCase(TestCase): - user_permissions = ( - 'secrets.view_secretrole', - ) +class SecretRoleTestCase(StandardTestCases.Views): + model = SecretRole + + # Disable inapplicable tests + test_get_object = None + test_delete_object = None @classmethod def setUpTestData(cls): @@ -23,54 +25,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.assertHttpStatus(response, 200) - - def test_secretrole_import(self): - self.add_permissions('secrets.add_secretrole') - - 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.assertHttpStatus(response, 200) - self.assertEqual(SecretRole.objects.count(), 6) +class SecretTestCase(StandardTestCases.Views): + model = Secret + # Disable inapplicable tests + test_create_object = None -class SecretTestCase(TestCase): - user_permissions = ( - 'secrets.view_secret', - ) + # TODO: Check permissions enforcement on secrets.views.secret_edit + test_edit_object = None @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() - - 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'), @@ -78,6 +66,12 @@ class SecretTestCase(TestCase): Secret(device=device, role=secretrole, name='Secret 3', ciphertext=b'1234567890'), ]) + cls.form_data = { + 'device': device.pk, + 'role': secretrole.pk, + 'name': 'Secret X', + } + def setUp(self): super().setUp() @@ -89,23 +83,7 @@ class SecretTestCase(TestCase): self.session_key = SessionKey(userkey=userkey) self.session_key.save(master_key) - def test_secret_list(self): - - url = reverse('secrets:secret_list') - params = { - "role": SecretRole.objects.first().slug, - } - - response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)), follow=True) - self.assertHttpStatus(response, 200) - - def test_secret(self): - - secret = Secret.objects.first() - response = self.client.get(secret.get_absolute_url(), follow=True) - self.assertHttpStatus(response, 200) - - def test_secret_import(self): + def test_import_objects(self): self.add_permissions('secrets.add_secret') csv_data = ( From 5517145ae3e86e4bbcc654358884c693099a68eb Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 31 Jan 2020 15:44:10 -0500 Subject: [PATCH 23/25] Convert tenancy view tests to use StandardTestCases --- netbox/tenancy/tests/test_views.py | 76 +++++++++--------------------- 1 file changed, 23 insertions(+), 53 deletions(-) diff --git a/netbox/tenancy/tests/test_views.py b/netbox/tenancy/tests/test_views.py index 8646abe38..1825a4ff9 100644 --- a/netbox/tenancy/tests/test_views.py +++ b/netbox/tenancy/tests/test_views.py @@ -1,15 +1,13 @@ -import urllib.parse - -from django.urls import reverse - from tenancy.models import Tenant, TenantGroup -from utilities.testing import TestCase +from utilities.testing import StandardTestCases -class TenantGroupTestCase(TestCase): - user_permissions = ( - 'tenancy.view_tenantgroup', - ) +class TenantGroupTestCase(StandardTestCases.Views): + model = TenantGroup + + # Disable inapplicable tests + test_get_object = None + test_delete_object = None @classmethod def setUpTestData(cls): @@ -20,39 +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.assertHttpStatus(response, 200) - - def test_tenantgroup_import(self): - self.add_permissions('tenancy.add_tenantgroup') - - 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.assertHttpStatus(response, 200) - self.assertEqual(TenantGroup.objects.count(), 6) - - -class TenantTestCase(TestCase): - user_permissions = ( - 'tenancy.view_tenant', - ) +class TenantTestCase(StandardTestCases.Views): + model = Tenant @classmethod def setUpTestData(cls): - 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), @@ -60,33 +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.assertHttpStatus(response, 200) - - def test_tenant(self): - - tenant = Tenant.objects.first() - response = self.client.get(tenant.get_absolute_url(), follow=True) - self.assertHttpStatus(response, 200) - - def test_tenant_import(self): - self.add_permissions('tenancy.add_tenant') - - 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.assertHttpStatus(response, 200) - self.assertEqual(Tenant.objects.count(), 6) From e50eab2342b35f107141d29b61667fc55010a5a8 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 31 Jan 2020 15:57:33 -0500 Subject: [PATCH 24/25] Convert virtualization view tests to use StandardTestCases --- netbox/virtualization/tests/test_views.py | 167 ++++++++-------------- 1 file changed, 60 insertions(+), 107 deletions(-) diff --git a/netbox/virtualization/tests/test_views.py b/netbox/virtualization/tests/test_views.py index df346d11e..ed065678b 100644 --- a/netbox/virtualization/tests/test_views.py +++ b/netbox/virtualization/tests/test_views.py @@ -1,15 +1,15 @@ -import urllib.parse - -from django.urls import reverse - -from utilities.testing import TestCase +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): - user_permissions = ( - 'virtualization.view_clustergroup', - ) +class ClusterGroupTestCase(StandardTestCases.Views): + model = ClusterGroup + + # Disable inapplicable tests + test_get_object = None + test_delete_object = None @classmethod def setUpTestData(cls): @@ -20,33 +20,25 @@ 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.assertHttpStatus(response, 200) - - def test_clustergroup_import(self): - self.add_permissions('virtualization.add_clustergroup') - - 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.assertHttpStatus(response, 200) - self.assertEqual(ClusterGroup.objects.count(), 6) +class ClusterTypeTestCase(StandardTestCases.Views): + model = ClusterType - -class ClusterTypeTestCase(TestCase): - user_permissions = ( - 'virtualization.view_clustertype', - ) + # Disable inapplicable tests + test_get_object = None + test_delete_object = None @classmethod def setUpTestData(cls): @@ -57,42 +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.assertHttpStatus(response, 200) - - def test_clustertype_import(self): - self.add_permissions('virtualization.add_clustertype') - - 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.assertHttpStatus(response, 200) - self.assertEqual(ClusterType.objects.count(), 6) - - -class ClusterTestCase(TestCase): - user_permissions = ( - 'virtualization.view_cluster', - ) +class ClusterTestCase(StandardTestCases.Views): + model = Cluster @classmethod def setUpTestData(cls): - 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), @@ -100,52 +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.assertHttpStatus(response, 200) - - def test_cluster(self): - - cluster = Cluster.objects.first() - response = self.client.get(cluster.get_absolute_url()) - self.assertHttpStatus(response, 200) - - def test_cluster_import(self): - self.add_permissions('virtualization.add_cluster') - - 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.assertHttpStatus(response, 200) - self.assertEqual(Cluster.objects.count(), 6) - - -class VirtualMachineTestCase(TestCase): - user_permissions = ( - 'virtualization.view_virtualmachine', - ) +class VirtualMachineTestCase(StandardTestCases.Views): + model = VirtualMachine @classmethod def setUpTestData(cls): - 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), @@ -153,33 +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.assertHttpStatus(response, 200) - - def test_virtualmachine(self): - - virtualmachine = VirtualMachine.objects.first() - response = self.client.get(virtualmachine.get_absolute_url()) - self.assertHttpStatus(response, 200) - - def test_virtualmachine_import(self): - self.add_permissions('virtualization.add_virtualmachine') - - 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.assertHttpStatus(response, 200) - self.assertEqual(VirtualMachine.objects.count(), 6) From eb9538d6da0cc77443be3070dc4db596531b69df Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 31 Jan 2020 15:59:26 -0500 Subject: [PATCH 25/25] Clean up imports --- netbox/ipam/tests/test_views.py | 4 +--- netbox/secrets/tests/test_views.py | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/netbox/ipam/tests/test_views.py b/netbox/ipam/tests/test_views.py index ca6a4c42b..db8326fbd 100644 --- a/netbox/ipam/tests/test_views.py +++ b/netbox/ipam/tests/test_views.py @@ -1,13 +1,11 @@ import datetime -import urllib.parse -from django.urls import reverse 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, TestCase +from utilities.testing import StandardTestCases class VRFTestCase(StandardTestCases.Views): diff --git a/netbox/secrets/tests/test_views.py b/netbox/secrets/tests/test_views.py index 87c1c3683..1da689e53 100644 --- a/netbox/secrets/tests/test_views.py +++ b/netbox/secrets/tests/test_views.py @@ -1,11 +1,10 @@ import base64 -import urllib.parse 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, TestCase +from utilities.testing import StandardTestCases from .constants import PRIVATE_KEY, PUBLIC_KEY