@@ -149,6 +149,161 @@ def test_get_install_dependencies():
149149 assert install_nodes == ["a==2.0" , "d==6.0" , "b==3.0" , "e==6.0" ]
150150
151151
152+ def test_graph_add_dependency_with_constraint ():
153+ """Test that constraints are properly stored in dependency nodes."""
154+ graph = dependency_graph .DependencyGraph ()
155+
156+ # Add top-level dependency with constraint
157+ graph .add_dependency (
158+ parent_name = None ,
159+ parent_version = None ,
160+ req_type = requirements_file .RequirementType .INSTALL ,
161+ req = Requirement ("package-a>=1.0" ),
162+ req_version = Version ("2.0" ),
163+ download_url = "url" ,
164+ constraint = "package-a>=1.0,<3.0" ,
165+ )
166+
167+ # Verify constraint is stored
168+ node = graph .nodes ["package-a==2.0" ]
169+ assert node .constraint == "package-a>=1.0,<3.0"
170+
171+ # Add child dependency with its own constraint
172+ graph .add_dependency (
173+ parent_name = canonicalize_name ("package-a" ),
174+ parent_version = Version ("2.0" ),
175+ req_type = requirements_file .RequirementType .INSTALL ,
176+ req = Requirement ("package-b>=2.0" ),
177+ req_version = Version ("2.5.0" ),
178+ download_url = "url-b" ,
179+ constraint = "package-b>=2.0,<3.0" ,
180+ )
181+
182+ # Verify child constraint is stored
183+ child_node = graph .nodes ["package-b==2.5.0" ]
184+ assert child_node .constraint == "package-b>=2.0,<3.0"
185+
186+
187+ def test_graph_constraint_serialization ():
188+ """Test that constraints survive to_dict/from_dict roundtrip."""
189+ graph = dependency_graph .DependencyGraph ()
190+
191+ # Add dependencies with various constraints
192+ graph .add_dependency (
193+ parent_name = None ,
194+ parent_version = None ,
195+ req_type = requirements_file .RequirementType .TOP_LEVEL ,
196+ req = Requirement ("pkg-with-constraint" ),
197+ req_version = Version ("1.5.0" ),
198+ download_url = "url" ,
199+ constraint = "pkg-with-constraint>=1.0,<2.0" ,
200+ )
201+
202+ graph .add_dependency (
203+ parent_name = canonicalize_name ("pkg-with-constraint" ),
204+ parent_version = Version ("1.5.0" ),
205+ req_type = requirements_file .RequirementType .INSTALL ,
206+ req = Requirement ("dependency-pkg" ),
207+ req_version = Version ("3.0.0" ),
208+ download_url = "url-dep" ,
209+ constraint = "dependency-pkg==3.0.0" ,
210+ )
211+
212+ # Add dependency without constraint
213+ graph .add_dependency (
214+ parent_name = canonicalize_name ("pkg-with-constraint" ),
215+ parent_version = Version ("1.5.0" ),
216+ req_type = requirements_file .RequirementType .BUILD_SYSTEM ,
217+ req = Requirement ("build-pkg" ),
218+ req_version = Version ("1.0.0" ),
219+ download_url = "url-build" ,
220+ )
221+
222+ # Serialize and deserialize
223+ graph_dict = graph ._to_dict ()
224+ restored_graph = dependency_graph .DependencyGraph .from_dict (graph_dict )
225+
226+ # Verify constraints are preserved
227+ node1 = restored_graph .nodes ["pkg-with-constraint==1.5.0" ]
228+ assert node1 .constraint == "pkg-with-constraint>=1.0,<2.0"
229+
230+ node2 = restored_graph .nodes ["dependency-pkg==3.0.0" ]
231+ assert node2 .constraint == "dependency-pkg==3.0.0"
232+
233+ # Verify empty constraint is preserved
234+ node3 = restored_graph .nodes ["build-pkg==1.0.0" ]
235+ assert node3 .constraint == ""
236+
237+
238+ def test_graph_duplicate_node_constraint_behavior ():
239+ """Test behavior when same package is added with different constraints.
240+
241+ When a node with the same key (name==version) is added multiple times,
242+ the first node is reused, including its constraint value.
243+ """
244+ graph = dependency_graph .DependencyGraph ()
245+
246+ # Add package with first constraint
247+ graph .add_dependency (
248+ parent_name = None ,
249+ parent_version = None ,
250+ req_type = requirements_file .RequirementType .INSTALL ,
251+ req = Requirement ("shared-pkg>=1.0" ),
252+ req_version = Version ("2.0" ),
253+ download_url = "url1" ,
254+ constraint = "shared-pkg>=1.0,<3.0" ,
255+ )
256+
257+ # Add another toplevel
258+ graph .add_dependency (
259+ parent_name = None ,
260+ parent_version = None ,
261+ req_type = requirements_file .RequirementType .INSTALL ,
262+ req = Requirement ("other-pkg" ),
263+ req_version = Version ("1.0" ),
264+ download_url = "url2" ,
265+ )
266+
267+ # Add same package as dependency with different constraint
268+ graph .add_dependency (
269+ parent_name = canonicalize_name ("other-pkg" ),
270+ parent_version = Version ("1.0" ),
271+ req_type = requirements_file .RequirementType .INSTALL ,
272+ req = Requirement ("shared-pkg>=2.0" ),
273+ req_version = Version ("2.0" ),
274+ download_url = "url1" ,
275+ constraint = "shared-pkg>=2.0,<4.0" , # Different constraint
276+ )
277+
278+ # The first constraint should be retained (existing node is reused)
279+ node = graph .nodes ["shared-pkg==2.0" ]
280+ assert node .constraint == "shared-pkg>=1.0,<3.0"
281+
282+ # Verify both parents exist
283+ assert len (node .parents ) == 2
284+
285+
286+ def test_graph_constraint_in_to_dict ():
287+ """Test that to_dict() includes constraint information."""
288+ graph = dependency_graph .DependencyGraph ()
289+
290+ graph .add_dependency (
291+ parent_name = None ,
292+ parent_version = None ,
293+ req_type = requirements_file .RequirementType .TOP_LEVEL ,
294+ req = Requirement ("test-pkg" ),
295+ req_version = Version ("1.0.0" ),
296+ download_url = "https://example.com/test-pkg.tar.gz" ,
297+ constraint = "test-pkg>=1.0,<2.0" ,
298+ )
299+
300+ graph_dict = graph ._to_dict ()
301+
302+ # Verify constraint is in the serialized format
303+ assert "test-pkg==1.0.0" in graph_dict
304+ assert graph_dict ["test-pkg==1.0.0" ]["constraint" ] == "test-pkg>=1.0,<2.0"
305+
306+
152307def test_cycles_get_install_dependencies ():
153308 graph = dependency_graph .DependencyGraph .from_dict (raw_graph )
154309 # create cycle: a depends on d and d depends on a
0 commit comments