# Copyright (c) "Neo4j" # Neo4j Sweden AB [https://neo4j.com] # # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # # https://www.apache.org/licenses/LICENSE-2.0 # # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import annotations import pytest from neo4j import Driver from neo4j_graphrag.schema import ( BASE_ENTITY_LABEL, NODE_PROPERTIES_QUERY, REL_PROPERTIES_QUERY, REL_QUERY, get_structured_schema, query_database, ) @pytest.mark.usefixtures("setup_neo4j_for_schema_query") def test_cypher_returns_correct_node_properties(driver: Driver) -> None: node_properties = query_database( driver, NODE_PROPERTIES_QUERY, params={"EXCLUDED_LABELS": [BASE_ENTITY_LABEL], "SAMPLE": 1000}, ) expected_node_properties = [ { "output": { "properties": [{"property": "property_a", "type": "STRING"}], "label": "LabelA", } } ] assert node_properties == expected_node_properties @pytest.mark.usefixtures("setup_neo4j_for_schema_query") def test_cypher_returns_correct_relationship_properties(driver: Driver) -> None: relationships_properties = query_database( driver, REL_PROPERTIES_QUERY, params={"EXCLUDED_LABELS": [BASE_ENTITY_LABEL], "SAMPLE": 1000}, ) expected_relationships_properties = [ { "output": { "type": "REL_TYPE", "properties": [{"property": "rel_prop", "type": "STRING"}], } } ] assert relationships_properties == expected_relationships_properties @pytest.mark.usefixtures("setup_neo4j_for_schema_query") def test_cypher_returns_correct_relationships(driver: Driver) -> None: relationships = query_database( driver, REL_QUERY, params={"EXCLUDED_LABELS": [BASE_ENTITY_LABEL], "SAMPLE": 1000}, ) expected_relationships = [ {"output": {"start": "LabelA", "type": "REL_TYPE", "end": "LabelB"}}, {"output": {"start": "LabelA", "type": "REL_TYPE", "end": "LabelC"}}, ] assert ( sorted(relationships, key=lambda x: x["output"]["end"]) == expected_relationships ) @pytest.mark.usefixtures("setup_neo4j_for_schema_query") def test_get_structured_schema_returns_correct_node_properties(driver: Driver) -> None: result = get_structured_schema(driver) assert result["node_props"]["LabelA"] == [ {"property": "property_a", "type": "STRING"} ] @pytest.mark.usefixtures("setup_neo4j_for_schema_query") def test_get_enhanced_structured_schema_returns_correct_node_properties( driver: Driver, ) -> None: result = get_structured_schema(driver, True) assert result["node_props"]["LabelA"] == [ { "property": "property_a", "type": "STRING", "values": ["a"], "distinct_count": 1, } ] @pytest.mark.usefixtures("setup_neo4j_for_schema_query") def test_get_structured_schema_returns_correct_relationship_properties( driver: Driver, ) -> None: result = get_structured_schema(driver) assert result["rel_props"]["REL_TYPE"] == [ {"property": "rel_prop", "type": "STRING"} ] @pytest.mark.usefixtures("setup_neo4j_for_schema_query") def test_get_enhanced_structured_schema_returns_correct_relationship_properties( driver: Driver, ) -> None: result = get_structured_schema(driver, True) assert result["rel_props"]["REL_TYPE"] == [ { "property": "rel_prop", "type": "STRING", "values": ["abc"], "distinct_count": 1, } ] @pytest.mark.usefixtures("setup_neo4j_for_schema_query") def test_get_structured_schema_returns_correct_relationships(driver: Driver) -> None: result = get_structured_schema(driver) assert sorted(result["relationships"], key=lambda x: x["end"]) == [ {"end": "LabelB", "start": "LabelA", "type": "REL_TYPE"}, {"end": "LabelC", "start": "LabelA", "type": "REL_TYPE"}, ] @pytest.mark.enterprise_only @pytest.mark.usefixtures("setup_neo4j_for_schema_query") def test_get_structured_schema_returns_correct_constraints(driver: Driver) -> None: query_database(driver, "DROP CONSTRAINT test_constraint IF EXISTS") query_database( driver, "CREATE CONSTRAINT test_constraint IF NOT EXISTS FOR (n:LabelA) REQUIRE n.property_a IS NOT NULL", ) result = get_structured_schema(driver) assert result["metadata"]["constraint"][0].get("name") == "test_constraint" assert result["metadata"]["constraint"][0].get("type") == "NODE_PROPERTY_EXISTENCE" assert result["metadata"]["constraint"][0].get("entityType") == "NODE" assert result["metadata"]["constraint"][0].get("labelsOrTypes") == ["LabelA"] assert result["metadata"]["constraint"][0].get("properties") == ["property_a"] @pytest.mark.usefixtures("setup_neo4j_for_schema_query") def test_get_structured_schema_returns_correct_indexes(driver: Driver) -> None: query_database(driver, "DROP INDEX node_range_index IF EXISTS") query_database( driver, "CREATE INDEX node_range_index FOR (n:LabelA) ON (n.property_a)" ) result = get_structured_schema(driver) assert result["metadata"]["index"][0].get("label") == "LabelA" assert result["metadata"]["index"][0].get("properties") == ["property_a"] assert result["metadata"]["index"][0].get("type") == "RANGE" @pytest.mark.usefixtures("setup_neo4j_for_schema_query") def test_neo4j_sanitize_values(driver: Driver) -> None: output = query_database(driver, "RETURN range(0,130,1) AS result", sanitize=True) assert output == [{}] def test_enhanced_schema_exception(driver: Driver) -> None: query_database(driver, "MATCH (n) DETACH DELETE n") query_database( driver, "CREATE (:Node {foo: 'bar'}), (:Node {foo: 1}), (:Node {foo: [1,2]}), " "(: EmptyNode)", ) query_database( driver, "MATCH (a:Node {foo: 'bar'}), (b:Node {foo: 1}), " "(c:Node {foo: [1,2]}), (d: EmptyNode) " "CREATE (a)-[:REL {foo: 'bar'}]->(b), (b)-[:REL {foo: 1}]->(c), " "(c)-[:REL {foo: [1,2]}]->(a), (d)-[:EMPTY_REL {}]->(d)", ) result = get_structured_schema(driver, True) del result["metadata"] assert list(result.keys()) == ["node_props", "rel_props", "relationships"] node_props = result["node_props"] assert list(node_props.keys()) == ["Node"] assert len(node_props["Node"]) == 1 assert list(node_props["Node"][0].keys()) == ["property", "type"] assert node_props["Node"][0]["property"] == "foo" assert node_props["Node"][0]["type"] in ["STRING", "INTEGER", "LIST"] rel_props = result["rel_props"] assert list(rel_props.keys()) == ["REL"] assert len(rel_props["REL"]) == 1 assert list(rel_props["REL"][0].keys()) == ["property", "type"] assert rel_props["REL"][0]["property"] == "foo" assert rel_props["REL"][0]["type"] in ["STRING", "INTEGER", "LIST"] expected_rels = [ { "end": "Node", "start": "Node", "type": "REL", }, {"end": "EmptyNode", "start": "EmptyNode", "type": "EMPTY_REL"}, ] rels = result["relationships"] assert rels == expected_rels