From 9dbb9bb51c9d0218dc4b76a68ecc13f4c29b237d Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 19 Nov 2025 16:20:44 -0500 Subject: [PATCH] Update cable path tests --- netbox/dcim/models/cables.py | 76 ++++-------------- netbox/dcim/tests/test_cablepaths.py | 4 +- netbox/dcim/tests/test_cablepaths2.py | 109 ++++++++++++++------------ 3 files changed, 77 insertions(+), 112 deletions(-) diff --git a/netbox/dcim/models/cables.py b/netbox/dcim/models/cables.py index 517cfd7ca..92fda318f 100644 --- a/netbox/dcim/models/cables.py +++ b/netbox/dcim/models/cables.py @@ -695,7 +695,9 @@ class CablePath(models.Model): position_stack.append([terminations[0].cable_position]) # Step 2: Determine the attached links (Cable or WirelessLink), if any - links = [termination.link for termination in terminations if termination.link is not None] + links = list(dict.fromkeys( + termination.link for termination in terminations if termination.link is not None + )) if len(links) == 0: if len(path) == 1: # If this is the start of the path and no link exists, return None @@ -775,83 +777,39 @@ class CablePath(models.Model): if isinstance(remote_terminations[0], FrontPort): # Follow FrontPorts to their corresponding RearPorts - if position_stack: - positions = position_stack.pop() + if any(rt.positions for rt in remote_terminations): q_filter = Q() for rt in remote_terminations: - position = positions.pop() - q_filter |= Q(front_port=rt, front_port_position=position) + q_filter |= Q(front_port=rt, front_port_position__in=rt.positions) port_assignments = PortAssignment.objects.filter(q_filter) else: port_assignments = PortAssignment.objects.filter(front_port__in=remote_terminations) - if not port_assignments: - print(f'No front-to-rear port assignments found for {remote_terminations}') break - position_stack.append([assignment.rear_port_position for assignment in port_assignments]) - terminations = [assignment.rear_port for assignment in port_assignments] + + # Compile the list of RearPorts without duplication or altering their ordering + terminations = list(dict.fromkeys(assignment.rear_port for assignment in port_assignments)) + # if not(len(terminations) == 1 and terminations[0].positions == 1): + if any(t.positions > 1 for t in terminations): + position_stack.append([assignment.rear_port_position for assignment in port_assignments]) elif isinstance(remote_terminations[0], RearPort): # Follow RearPorts to their corresponding FrontPorts - if position_stack: + if remote_terminations[0].positions > 1 and position_stack: positions = position_stack.pop() q_filter = Q() for rt in remote_terminations: - position = positions.pop() - q_filter |= Q(rear_port=rt, rear_port_position=position) + q_filter |= Q(rear_port=rt, rear_port_position__in=positions) port_assignments = PortAssignment.objects.filter(q_filter) + elif remote_terminations[0].positions > 1: + is_split = True + break else: port_assignments = PortAssignment.objects.filter(rear_port__in=remote_terminations) - if not port_assignments: - print(f'No rear-to-front port assignments found for {remote_terminations}') break - position_stack.append([assignment.front_port_position for assignment in port_assignments]) - terminations = [assignment.front_port for assignment in port_assignments] - # if len(remote_terminations) == 1 and remote_terminations[0].positions == 1: - # front_ports = FrontPort.objects.filter( - # rear_port_id__in=[rp.pk for rp in remote_terminations], - # rear_port_position=1 - # ) - # # Obtain the individual front ports based on the termination and all positions - # elif len(remote_terminations) > 1 and position_stack: - # positions = position_stack.pop() - # - # # Ensure we have a number of positions equal to the amount of remote terminations - # if len(remote_terminations) != len(positions): - # raise UnsupportedCablePath( - # _("All positions counts within the path on opposite ends of links must match") - # ) - # - # # Get our front ports - # q_filter = Q() - # for rt in remote_terminations: - # position = positions.pop() - # q_filter |= Q(rear_port_id=rt.pk, rear_port_position=position) - # if q_filter is Q(): - # raise UnsupportedCablePath(_("Remote termination position filter is missing")) - # front_ports = FrontPort.objects.filter(q_filter) - # # Obtain the individual front ports based on the termination and position - # elif position_stack: - # front_ports = FrontPort.objects.filter( - # rear_port_id=remote_terminations[0].pk, - # rear_port_position__in=position_stack.pop() - # ) - # # If all rear ports have a single position, we can just get the front ports - # elif all([rp.positions == 1 for rp in remote_terminations]): - # front_ports = FrontPort.objects.filter(rear_port_id__in=[rp.pk for rp in remote_terminations]) - # - # if len(front_ports) != len(remote_terminations): - # # Some rear ports does not have a front port - # is_split = True - # break - # else: - # # No position indicated: path has split, so we stop at the RearPorts - # is_split = True - # break - # - # terminations = front_ports + terminations = [assignment.front_port for assignment in port_assignments] elif isinstance(remote_terminations[0], CircuitTermination): # Follow a CircuitTermination to its corresponding CircuitTermination (A to Z or vice versa) diff --git a/netbox/dcim/tests/test_cablepaths.py b/netbox/dcim/tests/test_cablepaths.py index e9322980b..fe7a6aea7 100644 --- a/netbox/dcim/tests/test_cablepaths.py +++ b/netbox/dcim/tests/test_cablepaths.py @@ -1025,8 +1025,6 @@ class LegacyCablePathTests(CablePathTestCase): b_terminations=[rearport3] ) cable4.save() - for path in CablePath.objects.all(): - print(path.path_objects) self.assertPathExists( ( interface1, cable1, frontport1_1, rearport1, cable3, frontport2, rearport2, @@ -1079,7 +1077,7 @@ class LegacyCablePathTests(CablePathTestCase): interface1 = Interface.objects.create(device=self.device, name='Interface 1') interface2 = Interface.objects.create(device=self.device, name='Interface 2') interface3 = Interface.objects.create(device=self.device, name='Interface 3') - rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=4) + rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=2) frontport1_1 = FrontPort.objects.create(device=self.device, name='Front Port 1:1') frontport1_2 = FrontPort.objects.create(device=self.device, name='Front Port 1:2') PortAssignment.objects.bulk_create([ diff --git a/netbox/dcim/tests/test_cablepaths2.py b/netbox/dcim/tests/test_cablepaths2.py index c7895c6d2..44bf4f082 100644 --- a/netbox/dcim/tests/test_cablepaths2.py +++ b/netbox/dcim/tests/test_cablepaths2.py @@ -1,5 +1,3 @@ -from unittest import skipIf - from circuits.models import CircuitTermination from dcim.choices import CableProfileChoices from dcim.models import * @@ -363,13 +361,13 @@ class CablePathTests(CablePathTestCase): Interface.objects.create(device=self.device, name='Interface 3'), Interface.objects.create(device=self.device, name='Interface 4'), ] - rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=1) - frontport1 = FrontPort.objects.create( - device=self.device, - name='Front Port 1', - rear_port=rearport1, - rear_port_position=1 - ) + rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1') + frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 1') + PortAssignment.objects.bulk_create([ + PortAssignment( + front_port=frontport1, front_port_position=None, rear_port=rearport1, rear_port_position=1, + ), + ]) # Create cables cable1 = Cable( @@ -439,18 +437,24 @@ class CablePathTests(CablePathTestCase): ] rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=4) rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2', positions=4) - frontport1_1 = FrontPort.objects.create( - device=self.device, name='Front Port 1:1', rear_port=rearport1, rear_port_position=1 - ) - frontport1_2 = FrontPort.objects.create( - device=self.device, name='Front Port 1:2', rear_port=rearport1, rear_port_position=2 - ) - frontport2_1 = FrontPort.objects.create( - device=self.device, name='Front Port 2:1', rear_port=rearport2, rear_port_position=1 - ) - frontport2_2 = FrontPort.objects.create( - device=self.device, name='Front Port 2:2', rear_port=rearport2, rear_port_position=2 - ) + frontport1_1 = FrontPort.objects.create(device=self.device, name='Front Port 1:1') + frontport1_2 = FrontPort.objects.create(device=self.device, name='Front Port 1:2') + frontport2_1 = FrontPort.objects.create(device=self.device, name='Front Port 2:1') + frontport2_2 = FrontPort.objects.create(device=self.device, name='Front Port 2:2') + PortAssignment.objects.bulk_create([ + PortAssignment( + front_port=frontport1_1, front_port_position=None, rear_port=rearport1, rear_port_position=1, + ), + PortAssignment( + front_port=frontport1_2, front_port_position=None, rear_port=rearport1, rear_port_position=2, + ), + PortAssignment( + front_port=frontport2_1, front_port_position=None, rear_port=rearport2, rear_port_position=1, + ), + PortAssignment( + front_port=frontport2_2, front_port_position=None, rear_port=rearport2, rear_port_position=2, + ), + ]) # Create cables cable1 = Cable( @@ -654,25 +658,31 @@ class CablePathTests(CablePathTestCase): Interface.objects.create(device=self.device, name='Interface 2'), ] rear_ports = [ - RearPort.objects.create(device=self.device, name='Rear Port 1', positions=1), - RearPort.objects.create(device=self.device, name='Rear Port 2', positions=1), - RearPort.objects.create(device=self.device, name='Rear Port 3', positions=1), - RearPort.objects.create(device=self.device, name='Rear Port 4', positions=1), + RearPort.objects.create(device=self.device, name='Rear Port 1'), + RearPort.objects.create(device=self.device, name='Rear Port 2'), + RearPort.objects.create(device=self.device, name='Rear Port 3'), + RearPort.objects.create(device=self.device, name='Rear Port 4'), ] front_ports = [ - FrontPort.objects.create( - device=self.device, name='Front Port 1', rear_port=rear_ports[0], rear_port_position=1 - ), - FrontPort.objects.create( - device=self.device, name='Front Port 2', rear_port=rear_ports[1], rear_port_position=1 - ), - FrontPort.objects.create( - device=self.device, name='Front Port 3', rear_port=rear_ports[2], rear_port_position=1 - ), - FrontPort.objects.create( - device=self.device, name='Front Port 4', rear_port=rear_ports[3], rear_port_position=1 - ), + FrontPort.objects.create(device=self.device, name='Front Port 1'), + FrontPort.objects.create(device=self.device, name='Front Port 2'), + FrontPort.objects.create(device=self.device, name='Front Port 3'), + FrontPort.objects.create(device=self.device, name='Front Port 4'), ] + PortAssignment.objects.bulk_create([ + PortAssignment( + front_port=front_ports[0], front_port_position=None, rear_port=rear_ports[0], rear_port_position=1, + ), + PortAssignment( + front_port=front_ports[1], front_port_position=None, rear_port=rear_ports[1], rear_port_position=1, + ), + PortAssignment( + front_port=front_ports[2], front_port_position=None, rear_port=rear_ports[2], rear_port_position=1, + ), + PortAssignment( + front_port=front_ports[3], front_port_position=None, rear_port=rear_ports[3], rear_port_position=1, + ), + ]) # Create cables cable1 = Cable( @@ -723,8 +733,6 @@ class CablePathTests(CablePathTestCase): # Test SVG generation CableTraceSVG(interfaces[0]).render() - # TODO: Revisit this test under FR #20564 - @skipIf(True, "Waiting for FR #20564") def test_223_single_path_via_multiple_pass_throughs_with_breakouts(self): """ [IF1] --C1-- [FP1] [RP1] --C2-- [IF3] @@ -736,14 +744,18 @@ class CablePathTests(CablePathTestCase): Interface.objects.create(device=self.device, name='Interface 3'), Interface.objects.create(device=self.device, name='Interface 4'), ] - rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=1) - rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2', positions=1) - frontport1 = FrontPort.objects.create( - device=self.device, name='Front Port 1', rear_port=rearport1, rear_port_position=1 - ) - frontport2 = FrontPort.objects.create( - device=self.device, name='Front Port 2', rear_port=rearport2, rear_port_position=1 - ) + rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1') + rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2') + frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 1') + frontport2 = FrontPort.objects.create(device=self.device, name='Front Port 2') + PortAssignment.objects.bulk_create([ + PortAssignment( + front_port=frontport1, front_port_position=None, rear_port=rearport1, rear_port_position=1, + ), + PortAssignment( + front_port=frontport2, front_port_position=None, rear_port=rearport2, rear_port_position=1, + ), + ]) # Create cables cable1 = Cable( @@ -761,9 +773,6 @@ class CablePathTests(CablePathTestCase): cable2.clean() cable2.save() - for path in CablePath.objects.all(): - print(f'{path}: {path.path_objects}') - # Validate paths self.assertPathExists( (interfaces[0], cable1, [frontport1, frontport2], [rearport1, rearport2], cable2, interfaces[2]),