@@ -225,6 +225,48 @@ def test_schema_additional_parameter_validation() -> None:
225225 GraphSchema .model_validate (schema_dict )
226226
227227
228+ def test_schema_constraint_validation_property_not_in_node_type () -> None :
229+ schema_dict : dict [str , Any ] = {
230+ "node_types" : [
231+ {
232+ "label" : "Person" ,
233+ "properties" : [{"name" : "name" , "type" : "STRING" }],
234+ }
235+ ],
236+ "constraints" : [
237+ {"type" : "UNIQUENESS" , "node_type" : "Person" , "property_name" : "email" }
238+ ]
239+ }
240+
241+ with pytest .raises (SchemaValidationError ) as exc_info :
242+ GraphSchema .model_validate (schema_dict )
243+
244+ assert "Constraint references undefined property" in str (exc_info .value )
245+ assert "on node type 'Person'" in str (exc_info .value )
246+
247+
248+ def test_schema_constraint_with_additional_properties_allows_unknown_property () -> None :
249+ # if additional_properties is True, we can define constraints that are not in the node_type
250+ schema_dict : dict [str , Any ] = {
251+ "node_types" : [
252+ {
253+ "label" : "Person" ,
254+ "properties" : [{"name" : "name" , "type" : "STRING" }],
255+ "additional_properties" : True ,
256+ }
257+ ],
258+ "constraints" : [
259+ {"type" : "UNIQUENESS" , "node_type" : "Person" , "property_name" : "email" }
260+ ],
261+ }
262+
263+ # Should NOT raise - email is allowed because additional_properties=True
264+ schema = GraphSchema .model_validate (schema_dict )
265+
266+ assert len (schema .constraints ) == 1
267+ assert schema .constraints [0 ].property_name == "email"
268+
269+
228270def test_schema_with_valid_constraints () -> None :
229271 schema_dict : dict [str , Any ] = {
230272 "node_types" : [
@@ -1100,6 +1142,28 @@ def schema_json_with_relationships_without_labels() -> str:
11001142 """
11011143
11021144
1145+ @pytest .fixture
1146+ def schema_json_with_nonexistent_property_constraint () -> str :
1147+ return """
1148+ {
1149+ "node_types": [
1150+ {
1151+ "label": "Person",
1152+ "properties": [
1153+ {"name": "name", "type": "STRING"}
1154+ ]
1155+ }
1156+ ],
1157+ "relationship_types": [],
1158+ "patterns": [],
1159+ "constraints": [
1160+ {"type": "UNIQUENESS", "node_type": "Person", "property_name": "name"},
1161+ {"type": "UNIQUENESS", "node_type": "Person", "property_name": "nonexistent_property"}
1162+ ]
1163+ }
1164+ """
1165+
1166+
11031167@pytest .mark .asyncio
11041168async def test_schema_from_text_filters_invalid_node_patterns (
11051169 schema_from_text : SchemaFromTextExtractor ,
@@ -1237,6 +1301,26 @@ async def test_schema_from_text_filters_invalid_constraints(
12371301 assert schema .constraints [0 ].property_name == "name"
12381302
12391303
1304+ @pytest .mark .asyncio
1305+ async def test_schema_from_text_filters_constraint_with_nonexistent_property (
1306+ schema_from_text : SchemaFromTextExtractor ,
1307+ mock_llm : AsyncMock ,
1308+ schema_json_with_nonexistent_property_constraint : str ,
1309+ ) -> None :
1310+ # configure the mock LLM to return schema with constraint on nonexistent property
1311+ mock_llm .ainvoke .return_value = LLMResponse (
1312+ content = schema_json_with_nonexistent_property_constraint
1313+ )
1314+
1315+ # run the schema extraction
1316+ schema = await schema_from_text .run (text = "Sample text for extraction" )
1317+
1318+ # verify that only the valid constraint (with "name" property) remains
1319+ # the constraint with "nonexistent_property" should be filtered out
1320+ assert len (schema .constraints ) == 1
1321+ assert schema .constraints [0 ].property_name == "name"
1322+
1323+
12401324@pytest .mark .asyncio
12411325async def test_schema_from_text_handles_null_constraints (
12421326 schema_from_text : SchemaFromTextExtractor ,
0 commit comments