from pathlib import Path from types import SimpleNamespace from unittest.mock import MagicMock, patch from django.http import HttpResponse from django.test import RequestFactory, SimpleTestCase from netbox_utilities.topology_views import ( _inject_topology_widths, _topology_data_for_visible_devices, _visible_topology_device_urls, get_topology_rack_width_data, install_topology_rack_width_support, is_topology_rack_elevation_request, serialize_topology_placement, topology_rack_width_enabled, ) class TopologyViewsRackWidthTest(SimpleTestCase): def setUp(self): self.request = RequestFactory().get("/plugins/netbox_topology_views/rack-elevation/?rack_id=3") self.request.user = MagicMock() def test_detects_only_topology_rack_elevation_route(self): self.request.resolver_match = SimpleNamespace(view_name="plugins:netbox_topology_views:rack_elevation") self.assertTrue(is_topology_rack_elevation_request(self.request)) self.request.resolver_match = SimpleNamespace(view_name="dcim:rack_list") self.assertFalse(is_topology_rack_elevation_request(self.request)) def test_skips_integration_when_optional_plugin_is_missing(self): self.request.resolver_match = SimpleNamespace(view_name="plugins:netbox_topology_views:rack_elevation") with ( patch("netbox_utilities.topology_views.apps.is_installed", return_value=False), patch("netbox_utilities.topology_views.get_plugin_config", return_value=True), ): self.assertFalse(topology_rack_width_enabled(self.request)) def test_skips_integration_when_admin_disabled_it(self): self.request.resolver_match = SimpleNamespace(view_name="plugins:netbox_topology_views:rack_elevation") with ( patch("netbox_utilities.topology_views.apps.is_installed", return_value=True), patch("netbox_utilities.topology_views.get_plugin_config", return_value=False), ): self.assertFalse(topology_rack_width_enabled(self.request)) def test_serializes_fractional_width_geometry_and_device_url(self): placement = SimpleNamespace( device_id=17, device=SimpleNamespace(pk=17, get_absolute_url=lambda: "/dcim/devices/17/"), width=3, horizontal_position=2, ) self.assertEqual( serialize_topology_placement(placement), { "device_id": 17, "url": "/dcim/devices/17/", "width": 3, "horizontal_position": 2, "left_percent": 33.33333333, "width_percent": 33.33333333, "width_source": "stored", }, ) @patch("netbox_utilities.topology_views.topology_rack_width_enabled", return_value=True) def test_queries_all_permitted_devices_and_infers_shared_unit_widths(self, _enabled): self.request.resolver_match = SimpleNamespace(view_name="plugins:netbox_topology_views:rack_elevation") rack_queryset = MagicMock() rack_queryset.filter.return_value = rack_queryset rack_queryset.values.return_value = "selected-rack-query" rack_model = MagicMock() rack_model.objects.restrict.return_value = rack_queryset device_queryset = MagicMock() selected_devices = MagicMock() device_queryset.filter.return_value = selected_devices device_model = MagicMock() device_model.objects.restrict.return_value = device_queryset devices = [ SimpleNamespace( pk=17, rack_id=3, face="front", position=11, device_type=SimpleNamespace(u_height=1), netbox_utilities_rack_placement=None, get_absolute_url=lambda: "/dcim/devices/17/", ), SimpleNamespace( pk=18, rack_id=3, face="front", position=11, device_type=SimpleNamespace(u_height=1), netbox_utilities_rack_placement=None, get_absolute_url=lambda: "/dcim/devices/18/", ), ] selected_devices.select_related.return_value.order_by.return_value = devices with patch.dict("sys.modules", {"dcim.models": SimpleNamespace(Device=device_model, Rack=rack_model)}): result = get_topology_rack_width_data(self.request) rack_queryset.filter.assert_called_once_with(pk__in=["3"]) device_queryset.filter.assert_called_once_with( rack_id__in="selected-rack-query", position__gt=0, device_type__u_height__gt=0, ) self.assertEqual(result["status"], "ready") self.assertEqual(result["schema_version"], 3) self.assertTrue(result["complete"]) self.assertEqual(len(result["devices"]), 2) self.assertEqual(result["devices"][0]["width"], 2) self.assertEqual(result["devices"][0]["horizontal_position"], 1) self.assertEqual(result["devices"][1]["horizontal_position"], 2) self.assertEqual(result["devices"][0]["width_source"], "inferred") def test_frontend_supports_live_view_and_all_rack_export_formats(self): script = (Path(__file__).parents[1] / "static" / "netbox_utilities" / "topology-rack-width.js").read_text( encoding="utf-8" ) self.assertIn("netbox-utilities-partial-width", script) self.assertIn("horizontalGeometry", script) self.assertIn("buildRackSvg", script) self.assertIn("buildRackDrawio", script) self.assertIn("format === 'svg'", script) self.assertIn("format === 'drawio'", script) self.assertIn("canvas.toBlob", script) self.assertIn("event.stopImmediatePropagation()", script) self.assertIn("rackExportCompatible", script) self.assertIn("liveViewCompatible", script) self.assertIn("payload?.devices", script) self.assertIn("payload.status !== 'ready'", script) self.assertIn("payload?.schema_version !== 3", script) self.assertIn("placement.source", script) self.assertLess(script.index("liveViewCompatible"), script.index("netbox-utilities-partial-width")) self.assertLess(script.index("exportGraphic(graphic"), script.index("event.preventDefault()")) @patch("netbox_utilities.topology_views.topology_rack_width_enabled", return_value=True) def test_malformed_partial_placement_fails_open(self, _enabled): self.request.resolver_match = SimpleNamespace(view_name="plugins:netbox_topology_views:rack_elevation") with patch("dcim.models.Rack.objects.restrict", side_effect=AttributeError("stale")): result = get_topology_rack_width_data(self.request) self.assertEqual(result["status"], "native-fallback") self.assertEqual(result["devices"], []) self.assertFalse(result["complete"]) def test_partial_width_css_uses_topology_device_slot_geometry(self): stylesheet = (Path(__file__).parents[1] / "static" / "netbox_utilities" / "netbox_utilities.css").read_text( encoding="utf-8" ) self.assertIn(".rack-device.netbox-utilities-partial-width", stylesheet) self.assertIn("--netbox-utilities-rack-device-left", stylesheet) self.assertIn("--netbox-utilities-rack-device-width", stylesheet) def test_extracts_only_devices_rendered_by_the_authorized_topology_view(self): html = """ Fritzbox Grandstream Not a rack device """ self.assertEqual( _visible_topology_device_urls(html), {334: "/dcim/devices/334/", 340: "/dcim/devices/340/"}, ) @patch("dcim.models.Device") def test_builds_both_stored_half_widths_from_visible_he11_devices(self, device_model): devices = [ SimpleNamespace( pk=334, rack_id=3, face="front", position=11, device_type=SimpleNamespace(u_height=1), netbox_utilities_rack_placement=SimpleNamespace(width=2, horizontal_position=1), get_absolute_url=lambda: "/dcim/devices/334/", ), SimpleNamespace( pk=340, rack_id=3, face="front", position=11, device_type=SimpleNamespace(u_height=1), netbox_utilities_rack_placement=SimpleNamespace(width=2, horizontal_position=2), get_absolute_url=lambda: "/dcim/devices/340/", ), ] device_model.objects.filter.return_value.select_related.return_value.order_by.return_value = devices result = _topology_data_for_visible_devices({334: "/dcim/devices/334/", 340: "/dcim/devices/340/"}) self.assertTrue(result["complete"]) self.assertEqual( [(item["device_id"], item["width"], item["horizontal_position"]) for item in result["devices"]], [(334, 2, 1), (340, 2, 2)], ) @patch("netbox_utilities.topology_views._topology_data_for_visible_devices") def test_injects_stored_widths_into_the_completed_foreign_response(self, get_data): get_data.return_value = { "devices": [ { "device_id": 334, "url": "/dcim/devices/334/", "width": 2, "horizontal_position": 1, "width_source": "stored", "left_percent": 0, "width_percent": 50, }, { "device_id": 340, "url": "/dcim/devices/340/", "width": 2, "horizontal_position": 2, "width_source": "stored", "left_percent": 50, "width_percent": 50, }, ], "status": "ready", "schema_version": 3, "complete": True, } response = HttpResponse( '
A' 'B' ) result = _inject_topology_widths(response) html = result.content.decode() get_data.assert_called_once_with({334: "/dcim/devices/334/", 340: "/dcim/devices/340/"}) self.assertIn('id="netbox-utilities-topology-rack-width-styles"', html) self.assertIn('.rack-device[href="/dcim/devices/334/"]', html) self.assertIn("left: calc(50% + 3px) !important", html) self.assertIn("netbox_utilities/topology-rack-width.js?v=0.9.6", html) def test_patches_the_optional_topology_view_only_once(self): def original_get(self, request): return self, request class RackElevationView: get = original_get module = SimpleNamespace(RackElevationView=RackElevationView) with ( patch("netbox_utilities.topology_views.apps.is_installed", return_value=True), patch("netbox_utilities.topology_views.get_plugin_config", return_value=True), patch("netbox_utilities.topology_views.import_module", return_value=module), ): self.assertTrue(install_topology_rack_width_support()) patched_get = RackElevationView.get self.assertTrue(install_topology_rack_width_support()) self.assertIs(RackElevationView.get, patched_get) self.assertIs(RackElevationView._netbox_utilities_original_get, original_get)