@@ -67,7 +67,7 @@ class ConnectedComponentsTestCase(DAGFixtureMixin, TestCase):
6767 """Tests for NodeManager.connected_components()."""
6868
6969 def test_returns_two_components (self ):
70- """DAGFixtureMixin has a main graph and an island — should be 2 components."""
70+ """DAGFixtureMixin has a main graph and an island - should be 2 components."""
7171 components = NetworkNode .objects .connected_components ()
7272 self .assertEqual (len (components ), 2 )
7373
@@ -82,7 +82,7 @@ def test_each_component_is_queryset(self):
8282 """Each component should be a standard Django QuerySet (not raw)."""
8383 components = NetworkNode .objects .connected_components ()
8484 for component in components :
85- # Can call .filter() — this verifies it's a regular QuerySet
85+ # Can call .filter() - this verifies it's a regular QuerySet
8686 self .assertTrue (component .filter (pk__isnull = False ).exists ())
8787
8888 def test_empty_graph (self ):
@@ -101,20 +101,92 @@ def test_single_island(self):
101101
102102
103103class ConnectedGraphFilterTestCase (DAGFixtureMixin , TestCase ):
104- """Tests that ConnectedGraphQuery no-op filter methods are exercised (lines 430-446) ."""
104+ """Tests that ConnectedGraphQuery filter methods work correctly ."""
105105
106- def test_connected_graph_with_all_filter_params (self ):
107- """Pass all filter kwargs -- ConnectedGraphQuery stubs them all as no-ops."""
108- edge_set = EdgeSet .objects .create (name = "cg_set" )
106+ def setUp (self ):
107+ super ().setUp ()
108+ self .edge_set = EdgeSet .objects .create (name = "cg_set" )
109+ # Assign all edges to edge_set
110+ NetworkEdge .objects .all ().update (edge_set = self .edge_set )
111+
112+ def test_connected_graph_disallow_nodes (self ):
113+ """Disallowing a node should exclude it and nodes only reachable through it."""
114+ disallowed = NetworkNode .objects .filter (pk = self .a1 .pk )
115+ result = self .root .connected_graph (disallowed_nodes_queryset = disallowed )
116+ result_names = set (result .values_list ("name" , flat = True ))
117+ self .assertNotIn ("a1" , result_names )
118+ # root, a2, a3 should still be present
119+ self .assertIn ("root" , result_names )
120+ self .assertIn ("a2" , result_names )
121+ self .assertIn ("a3" , result_names )
122+
123+ def test_connected_graph_disallow_edges (self ):
124+ """Disallowing an edge should prevent traversal along it."""
125+ edge_root_a1 = NetworkEdge .objects .get (parent = self .root , child = self .a1 )
126+ disallowed = NetworkEdge .objects .filter (pk = edge_root_a1 .pk )
127+ result = self .root .connected_graph (disallowed_edges_queryset = disallowed )
128+ result_names = set (result .values_list ("name" , flat = True ))
129+ # a1 and b1 may still be reachable via a2->b1 path, but a1 is only reachable via root->a1
130+ self .assertIn ("root" , result_names )
131+
132+ def test_connected_graph_allow_nodes (self ):
133+ """Only traverse through allowed nodes."""
134+ allowed = NetworkNode .objects .filter (name__in = ["root" , "a1" , "b1" ])
135+ result = self .root .connected_graph (allowed_nodes_queryset = allowed )
136+ result_names = set (result .values_list ("name" , flat = True ))
137+ self .assertIn ("root" , result_names )
138+ self .assertIn ("a1" , result_names )
139+ self .assertIn ("b1" , result_names )
140+ self .assertNotIn ("a2" , result_names )
141+ self .assertNotIn ("a3" , result_names )
142+
143+ def test_connected_graph_allow_edges (self ):
144+ """Only traverse along allowed edges."""
145+ edge_root_a1 = NetworkEdge .objects .get (parent = self .root , child = self .a1 )
146+ edge_a1_b1 = NetworkEdge .objects .get (parent = self .a1 , child = self .b1 )
147+ allowed = NetworkEdge .objects .filter (pk__in = [edge_root_a1 .pk , edge_a1_b1 .pk ])
148+ result = self .root .connected_graph (allowed_edges_queryset = allowed )
149+ result_names = set (result .values_list ("name" , flat = True ))
150+ self .assertIn ("root" , result_names )
151+ self .assertIn ("a1" , result_names )
152+ self .assertIn ("b1" , result_names )
153+ self .assertNotIn ("a2" , result_names )
154+
155+ def test_connected_graph_limiting_edges_set_fk (self ):
156+ """Limiting edges by FK should restrict traversal to those edges."""
157+ other_set = EdgeSet .objects .create (name = "other_set" )
158+ # Only assign root->a1 and a1->b1 to other_set
159+ NetworkEdge .objects .filter (parent = self .root , child = self .a1 ).update (edge_set = other_set )
160+ NetworkEdge .objects .filter (parent = self .a1 , child = self .b1 ).update (edge_set = other_set )
161+ result = self .root .connected_graph (limiting_edges_set_fk = other_set )
162+ result_names = set (result .values_list ("name" , flat = True ))
163+ self .assertIn ("root" , result_names )
164+ self .assertIn ("a1" , result_names )
165+ self .assertIn ("b1" , result_names )
166+ self .assertNotIn ("a2" , result_names )
167+
168+ def test_connected_graph_limiting_nodes_set_fk_noop (self ):
169+ """limiting_nodes_set_fk is still a no-op but should not error."""
109170 node_set = NodeSet .objects .create (name = "cg_ns" )
110- result = self .root .connected_graph (
111- limiting_nodes_set_fk = node_set ,
112- limiting_edges_set_fk = edge_set ,
113- disallowed_nodes_queryset = NetworkNode .objects .filter (pk = self .island .pk ),
114- disallowed_edges_queryset = NetworkEdge .objects .all (),
115- allowed_nodes_queryset = NetworkNode .objects .all (),
116- allowed_edges_queryset = NetworkEdge .objects .all (),
117- )
118- # All filters are no-ops, so result should include the full connected component
119- self .assertIn (self .root , result )
120- self .assertIn (self .a1 , result )
171+ result = self .root .connected_graph (limiting_nodes_set_fk = node_set )
172+ # No-op, so all connected nodes should be returned
173+ result_names = set (result .values_list ("name" , flat = True ))
174+ self .assertIn ("root" , result_names )
175+ self .assertIn ("a1" , result_names )
176+
177+ def test_connected_graph_max_depth (self ):
178+ """max_depth should limit how far connected_graph traverses."""
179+ # With max_depth=1, from root we should reach immediate neighbors only
180+ result = self .root .connected_graph (max_depth = 2 )
181+ result_names = set (result .values_list ("name" , flat = True ))
182+ # max_depth=2 means path array can be at most length 2 (root + 1 hop)
183+ self .assertIn ("root" , result_names )
184+ self .assertIn ("a1" , result_names )
185+ self .assertIn ("a2" , result_names )
186+ self .assertIn ("a3" , result_names )
187+
188+ def test_connected_graph_no_duplicate_rows (self ):
189+ """Ensure the rewritten CTE does not produce duplicate rows."""
190+ result = self .root .connected_graph ()
191+ pks = list (result .values_list ("pk" , flat = True ))
192+ self .assertEqual (len (pks ), len (set (pks )))
0 commit comments