Преглед изворни кода

test(tables): Add reusable OrderableColumnsTestCase

Introduce `TableTestCases.OrderableColumnsTestCase`, a shared base class
that automatically discovers sortable columns from list-view querysets
and verifies each renders without exceptions in both ascending and
descending order.

Add per-table smoke tests across circuits, core, dcim, extras, ipam,
tenancy, users, virtualization, vpn, and wireless apps.

Fixes #21766
Martin Hauser пре 8 часа
родитељ
комит
209c60ea6e

+ 46 - 48
netbox/circuits/tests/test_tables.py

@@ -1,48 +1,46 @@
-from django.test import RequestFactory, TestCase, tag
-
-from circuits.models import CircuitGroupAssignment, CircuitTermination
-from circuits.tables import CircuitGroupAssignmentTable, CircuitTerminationTable
-
-
-@tag('regression')
-class CircuitTerminationTableTest(TestCase):
-    def test_every_orderable_field_does_not_throw_exception(self):
-        terminations = CircuitTermination.objects.all()
-        disallowed = {
-            'actions',
-        }
-
-        orderable_columns = [
-            column.name
-            for column in CircuitTerminationTable(terminations).columns
-            if column.orderable and column.name not in disallowed
-        ]
-        fake_request = RequestFactory().get('/')
-
-        for col in orderable_columns:
-            for direction in ('-', ''):
-                table = CircuitTerminationTable(terminations)
-                table.order_by = f'{direction}{col}'
-                table.as_html(fake_request)
-
-
-@tag('regression')
-class CircuitGroupAssignmentTableTest(TestCase):
-    def test_every_orderable_field_does_not_throw_exception(self):
-        assignment = CircuitGroupAssignment.objects.all()
-        disallowed = {
-            'actions',
-        }
-
-        orderable_columns = [
-            column.name
-            for column in CircuitGroupAssignmentTable(assignment).columns
-            if column.orderable and column.name not in disallowed
-        ]
-        fake_request = RequestFactory().get('/')
-
-        for col in orderable_columns:
-            for direction in ('-', ''):
-                table = CircuitGroupAssignmentTable(assignment)
-                table.order_by = f'{direction}{col}'
-                table.as_html(fake_request)
+from circuits.tables import *
+from utilities.testing import TableTestCases
+
+
+class CircuitTypeTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = CircuitTypeTable
+
+
+class CircuitTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = CircuitTable
+
+
+class CircuitTerminationTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = CircuitTerminationTable
+
+
+class CircuitGroupTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = CircuitGroupTable
+
+
+class CircuitGroupAssignmentTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = CircuitGroupAssignmentTable
+
+
+class ProviderTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ProviderTable
+
+
+class ProviderAccountTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ProviderAccountTable
+
+
+class ProviderNetworkTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ProviderNetworkTable
+
+
+class VirtualCircuitTypeTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = VirtualCircuitTypeTable
+
+
+class VirtualCircuitTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = VirtualCircuitTable
+
+
+class VirtualCircuitTerminationTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = VirtualCircuitTerminationTable

+ 26 - 0
netbox/core/tests/test_tables.py

@@ -0,0 +1,26 @@
+from core.models import ObjectChange
+from core.tables import *
+from utilities.testing import TableTestCases
+
+
+class DataSourceTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = DataSourceTable
+
+
+class DataFileTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = DataFileTable
+
+
+class JobTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = JobTable
+
+
+class ObjectChangeTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ObjectChangeTable
+    queryset_sources = [
+        ('ObjectChangeListView', ObjectChange.objects.valid_models()),
+    ]
+
+
+class ConfigRevisionTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ConfigRevisionTable

+ 204 - 0
netbox/dcim/tests/test_tables.py

@@ -0,0 +1,204 @@
+from dcim.models import ConsolePort, Interface, PowerPort
+from dcim.tables import *
+from utilities.testing import TableTestCases
+
+#
+# Sites
+#
+
+
+class RegionTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = RegionTable
+
+
+class SiteGroupTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = SiteGroupTable
+
+
+class SiteTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = SiteTable
+
+
+class LocationTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = LocationTable
+
+
+#
+# Racks
+#
+
+class RackRoleTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = RackRoleTable
+
+
+class RackTypeTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = RackTypeTable
+
+
+class RackTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = RackTable
+
+
+class RackReservationTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = RackReservationTable
+
+
+#
+# Device types
+#
+
+class ManufacturerTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ManufacturerTable
+
+
+class DeviceTypeTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = DeviceTypeTable
+
+
+#
+# Module types
+#
+
+class ModuleTypeProfileTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ModuleTypeProfileTable
+
+
+class ModuleTypeTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ModuleTypeTable
+
+
+class ModuleTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ModuleTable
+
+
+#
+# Devices
+#
+
+class DeviceRoleTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = DeviceRoleTable
+
+
+class PlatformTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = PlatformTable
+
+
+class DeviceTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = DeviceTable
+
+
+#
+# Device components
+#
+
+class ConsolePortTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ConsolePortTable
+
+
+class ConsoleServerPortTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ConsoleServerPortTable
+
+
+class PowerPortTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = PowerPortTable
+
+
+class PowerOutletTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = PowerOutletTable
+
+
+class InterfaceTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = InterfaceTable
+
+
+class FrontPortTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = FrontPortTable
+
+
+class RearPortTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = RearPortTable
+
+
+class ModuleBayTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ModuleBayTable
+
+
+class DeviceBayTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = DeviceBayTable
+
+
+class InventoryItemTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = InventoryItemTable
+
+
+class InventoryItemRoleTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = InventoryItemRoleTable
+
+
+#
+# Connections
+#
+
+class ConsoleConnectionTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ConsoleConnectionTable
+    queryset_sources = [
+        ('ConsoleConnectionsListView', ConsolePort.objects.filter(_path__is_complete=True)),
+    ]
+
+
+class PowerConnectionTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = PowerConnectionTable
+    queryset_sources = [
+        ('PowerConnectionsListView', PowerPort.objects.filter(_path__is_complete=True)),
+    ]
+
+
+class InterfaceConnectionTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = InterfaceConnectionTable
+    queryset_sources = [
+        ('InterfaceConnectionsListView', Interface.objects.filter(_path__is_complete=True)),
+    ]
+
+
+#
+# Cables
+#
+
+class CableTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = CableTable
+
+
+#
+# Power
+#
+
+class PowerPanelTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = PowerPanelTable
+
+
+class PowerFeedTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = PowerFeedTable
+
+
+#
+# Virtual chassis
+#
+
+class VirtualChassisTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = VirtualChassisTable
+
+
+#
+# Virtual device contexts
+#
+
+class VirtualDeviceContextTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = VirtualDeviceContextTable
+
+
+#
+# MAC addresses
+#
+
+class MACAddressTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = MACAddressTable

+ 84 - 24
netbox/extras/tests/test_tables.py

@@ -1,24 +1,84 @@
-from django.test import RequestFactory, TestCase, tag
-
-from extras.models import EventRule
-from extras.tables import EventRuleTable
-
-
-@tag('regression')
-class EventRuleTableTest(TestCase):
-    def test_every_orderable_field_does_not_throw_exception(self):
-        rule = EventRule.objects.all()
-        disallowed = {
-            'actions',
-        }
-
-        orderable_columns = [
-            column.name for column in EventRuleTable(rule).columns if column.orderable and column.name not in disallowed
-        ]
-        fake_request = RequestFactory().get('/')
-
-        for col in orderable_columns:
-            for direction in ('-', ''):
-                table = EventRuleTable(rule)
-                table.order_by = f'{direction}{col}'
-                table.as_html(fake_request)
+from extras.models import Bookmark, Notification, Subscription
+from extras.tables import *
+from utilities.testing import TableTestCases
+
+
+class CustomFieldTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = CustomFieldTable
+
+
+class CustomFieldChoiceSetTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = CustomFieldChoiceSetTable
+
+
+class CustomLinkTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = CustomLinkTable
+
+
+class ExportTemplateTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ExportTemplateTable
+
+
+class SavedFilterTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = SavedFilterTable
+
+
+class TableConfigTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = TableConfigTable
+
+
+class BookmarkTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = BookmarkTable
+    queryset_sources = [
+        ('BookmarkListView', Bookmark.objects.all()),
+    ]
+
+
+class NotificationGroupTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = NotificationGroupTable
+
+
+class NotificationTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = NotificationTable
+    queryset_sources = [
+        ('NotificationListView', Notification.objects.all()),
+    ]
+
+
+class SubscriptionTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = SubscriptionTable
+    queryset_sources = [
+        ('SubscriptionListView', Subscription.objects.all()),
+    ]
+
+
+class WebhookTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = WebhookTable
+
+
+class EventRuleTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = EventRuleTable
+
+
+class TagTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = TagTable
+
+
+class ConfigContextProfileTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ConfigContextProfileTable
+
+
+class ConfigContextTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ConfigContextTable
+
+
+class ConfigTemplateTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ConfigTemplateTable
+
+
+class ImageAttachmentTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ImageAttachmentTable
+
+
+class JournalEntryTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = JournalEntryTable

+ 82 - 2
netbox/ipam/tests/test_tables.py

@@ -1,9 +1,10 @@
 from django.test import RequestFactory, TestCase
 from netaddr import IPNetwork
 
-from ipam.models import IPAddress, IPRange, Prefix
-from ipam.tables import AnnotatedIPAddressTable
+from ipam.models import FHRPGroupAssignment, IPAddress, IPRange, Prefix
+from ipam.tables import *
 from ipam.utils import annotate_ip_space
+from utilities.testing import TableTestCases
 
 
 class AnnotatedIPAddressTableTest(TestCase):
@@ -168,3 +169,82 @@ class AnnotatedIPAddressTableTest(TestCase):
         # Pools are fully usable
         self.assertEqual(available.first_ip, '2001:db8:1::/126')
         self.assertEqual(available.size, 4)
+
+
+#
+# Table ordering tests
+#
+
+class VRFTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = VRFTable
+
+
+class RouteTargetTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = RouteTargetTable
+
+
+class RIRTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = RIRTable
+
+
+class AggregateTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = AggregateTable
+
+
+class RoleTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = RoleTable
+
+
+class PrefixTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = PrefixTable
+
+
+class IPRangeTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = IPRangeTable
+
+
+class IPAddressTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = IPAddressTable
+
+
+class FHRPGroupTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = FHRPGroupTable
+
+
+class FHRPGroupAssignmentTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = FHRPGroupAssignmentTable
+    queryset_sources = [
+        ('FHRPGroupAssignmentTable', FHRPGroupAssignment.objects.all()),
+    ]
+
+
+class VLANGroupTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = VLANGroupTable
+
+
+class VLANTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = VLANTable
+
+
+class VLANTranslationPolicyTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = VLANTranslationPolicyTable
+
+
+class VLANTranslationRuleTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = VLANTranslationRuleTable
+
+
+class ASNRangeTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ASNRangeTable
+
+
+class ASNTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ASNTable
+
+
+class ServiceTemplateTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ServiceTemplateTable
+
+
+class ServiceTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ServiceTable

+ 26 - 0
netbox/tenancy/tests/test_tables.py

@@ -0,0 +1,26 @@
+from tenancy.tables import *
+from utilities.testing import TableTestCases
+
+
+class TenantGroupTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = TenantGroupTable
+
+
+class TenantTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = TenantTable
+
+
+class ContactGroupTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ContactGroupTable
+
+
+class ContactRoleTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ContactRoleTable
+
+
+class ContactTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ContactTable
+
+
+class ContactAssignmentTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ContactAssignmentTable

+ 26 - 24
netbox/users/tests/test_tables.py

@@ -1,24 +1,26 @@
-from django.test import RequestFactory, TestCase, tag
-
-from users.models import Token
-from users.tables import TokenTable
-
-
-class TokenTableTest(TestCase):
-    @tag('regression')
-    def test_every_orderable_field_does_not_throw_exception(self):
-        tokens = Token.objects.all()
-        disallowed = {'actions'}
-
-        orderable_columns = [
-            column.name for column in TokenTable(tokens).columns
-            if column.orderable and column.name not in disallowed
-        ]
-        fake_request = RequestFactory().get("/")
-
-        for col in orderable_columns:
-            for direction in ('-', ''):
-                with self.subTest(col=col, direction=direction):
-                    table = TokenTable(tokens)
-                    table.order_by = f'{direction}{col}'
-                    table.as_html(fake_request)
+from users.tables import *
+from utilities.testing import TableTestCases
+
+
+class TokenTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = TokenTable
+
+
+class UserTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = UserTable
+
+
+class GroupTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = GroupTable
+
+
+class ObjectPermissionTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ObjectPermissionTable
+
+
+class OwnerGroupTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = OwnerGroupTable
+
+
+class OwnerTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = OwnerTable

+ 1 - 0
netbox/utilities/testing/__init__.py

@@ -1,5 +1,6 @@
 from .api import *
 from .base import *
 from .filtersets import *
+from .tables import *
 from .utils import *
 from .views import *

+ 130 - 0
netbox/utilities/testing/tables.py

@@ -0,0 +1,130 @@
+import inspect
+from importlib import import_module
+
+from django.test import RequestFactory
+
+from netbox.views import generic
+
+from .base import TestCase
+
+__all__ = (
+    "ModelTableTestCase",
+    "TableTestCases",
+)
+
+
+class ModelTableTestCase(TestCase):
+    """
+    Shared helpers for model-backed table tests.
+
+    Concrete subclasses should set `table` and may override `get_queryset()`
+    or `excluded_orderable_columns` as needed.
+    """
+    table = None
+    excluded_orderable_columns = frozenset({"actions"})
+
+    # Optional explicit override for odd cases
+    queryset_sources = None
+
+    # Only these view types are considered sortable queryset sources by default
+    queryset_source_view_classes = (generic.ObjectListView,)
+
+    @classmethod
+    def validate_table_test_case(cls):
+        if cls.table is None:
+            raise AssertionError(f"{cls.__name__} must define `table`")
+        if getattr(cls.table._meta, "model", None) is None:
+            raise AssertionError(f"{cls.__name__}.table must be model-backed")
+
+    def get_request(self):
+        request = RequestFactory().get("/")
+        request.user = self.user
+        return request
+
+    def get_table(self, queryset):
+        return self.table(queryset)
+
+    @classmethod
+    def is_queryset_source_view(cls, view):
+        model = cls.table._meta.model
+        app_label = model._meta.app_label
+
+        return (
+            inspect.isclass(view)
+            and view.__module__.startswith(f"{app_label}.views")
+            and getattr(view, "table", None) is cls.table
+            and getattr(view, "queryset", None) is not None
+            and issubclass(view, cls.queryset_source_view_classes)
+        )
+
+    @classmethod
+    def get_queryset_sources(cls):
+        """
+        Return iterable of (label, queryset) pairs to test.
+
+        By default, only discover list-style views that declare this table.
+        That keeps bulk edit/delete confirmation tables out of the ordering
+        smoke test.
+        """
+        if cls.queryset_sources is not None:
+            return tuple(cls.queryset_sources)
+
+        model = cls.table._meta.model
+        app_label = model._meta.app_label
+        module = import_module(f"{app_label}.views")
+
+        sources = []
+        for _, view in inspect.getmembers(module, inspect.isclass):
+            if not cls.is_queryset_source_view(view):
+                continue
+
+            queryset = view.queryset
+            if hasattr(queryset, "all"):
+                queryset = queryset.all()
+
+            sources.append((view.__name__, queryset))
+
+        if not sources:
+            raise AssertionError(
+                f"{cls.__name__} could not find any list-style queryset source for "
+                f"{cls.table.__module__}.{cls.table.__name__}; "
+                "set `queryset_sources` explicitly if needed."
+            )
+
+        return tuple(sources)
+
+    def iter_orderable_columns(self, queryset):
+        for column in self.get_table(queryset).columns:
+            if not column.orderable:
+                continue
+            if column.name in self.excluded_orderable_columns:
+                continue
+            yield column.name
+
+
+class TableTestCases:
+    """
+    Keep test_* methods nested to avoid unittest auto-discovering the reusable
+    base classes directly.
+    """
+
+    class OrderableColumnsTestCase(ModelTableTestCase):
+        @classmethod
+        def setUpClass(cls):
+            super().setUpClass()
+            cls.validate_table_test_case()
+
+        def test_every_orderable_column_renders(self):
+            request = self.get_request()
+
+            for source_name, queryset in self.get_queryset_sources():
+                for column_name in self.iter_orderable_columns(queryset):
+                    for direction, prefix in (("asc", ""), ("desc", "-")):
+                        with self.cleanupSubTest(
+                            source=source_name,
+                            column=column_name,
+                            direction=direction,
+                        ):
+                            table = self.get_table(queryset)
+                            table.order_by = f"{prefix}{column_name}"
+                            table.as_html(request)

+ 26 - 0
netbox/virtualization/tests/test_tables.py

@@ -0,0 +1,26 @@
+from utilities.testing import TableTestCases
+from virtualization.tables import *
+
+
+class ClusterTypeTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ClusterTypeTable
+
+
+class ClusterGroupTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ClusterGroupTable
+
+
+class ClusterTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = ClusterTable
+
+
+class VirtualMachineTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = VirtualMachineTable
+
+
+class VMInterfaceTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = VMInterfaceTable
+
+
+class VirtualDiskTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = VirtualDiskTable

+ 37 - 18
netbox/vpn/tests/test_tables.py

@@ -1,23 +1,42 @@
-from django.test import RequestFactory, TestCase, tag
+from utilities.testing import TableTestCases
+from vpn.tables import *
 
-from vpn.models import TunnelTermination
-from vpn.tables import TunnelTerminationTable
 
+class TunnelGroupTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = TunnelGroupTable
 
-@tag('regression')
-class TunnelTerminationTableTest(TestCase):
-    def test_every_orderable_field_does_not_throw_exception(self):
-        terminations = TunnelTermination.objects.all()
-        fake_request = RequestFactory().get("/")
-        disallowed = {'actions'}
 
-        orderable_columns = [
-            column.name for column in TunnelTerminationTable(terminations).columns
-            if column.orderable and column.name not in disallowed
-        ]
+class TunnelTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = TunnelTable
 
-        for col in orderable_columns:
-            for dir in ('-', ''):
-                table = TunnelTerminationTable(terminations)
-                table.order_by = f'{dir}{col}'
-                table.as_html(fake_request)
+
+class TunnelTerminationTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = TunnelTerminationTable
+
+
+class IKEProposalTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = IKEProposalTable
+
+
+class IKEPolicyTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = IKEPolicyTable
+
+
+class IPSecProposalTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = IPSecProposalTable
+
+
+class IPSecPolicyTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = IPSecPolicyTable
+
+
+class IPSecProfileTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = IPSecProfileTable
+
+
+class L2VPNTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = L2VPNTable
+
+
+class L2VPNTerminationTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = L2VPNTerminationTable

+ 14 - 0
netbox/wireless/tests/test_tables.py

@@ -0,0 +1,14 @@
+from utilities.testing import TableTestCases
+from wireless.tables import *
+
+
+class WirelessLANGroupTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = WirelessLANGroupTable
+
+
+class WirelessLANTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = WirelessLANTable
+
+
+class WirelessLinkTableTest(TableTestCases.OrderableColumnsTestCase):
+    table = WirelessLinkTable