From 13d26f0319c8bd0636e4f30e786b614713ece19d Mon Sep 17 00:00:00 2001 From: Dev Kamani Date: Thu, 6 Aug 2026 23:23:03 +0530 Subject: [PATCH 1/4] test(lexical-graph): add graph_utils boundary and Cypher escaping tests (#424) --- .../tests/test_graph_utils_boundary.py | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 lexical-graph/tests/test_graph_utils_boundary.py diff --git a/lexical-graph/tests/test_graph_utils_boundary.py b/lexical-graph/tests/test_graph_utils_boundary.py new file mode 100644 index 000000000..09be4dffd --- /dev/null +++ b/lexical-graph/tests/test_graph_utils_boundary.py @@ -0,0 +1,92 @@ +""" +Boundary and edge-case unit test coverage for graph_utils and Cypher escaping. +Resolves awslabs/graphrag-toolkit issue #424. +""" +import pytest +from unittest.mock import MagicMock + +# Import graph_utils components for boundary validation +try: + from graphrag_toolkit.lexical_graph.graph_utils import ( + escape_cypher_label, + formatter_for_type, + parse_metadata_filters_recursive, + ) +except ImportError: + # Local fallback for isolated environment testing + from graph_utils import ( + escape_cypher_label, + formatter_for_type, + parse_metadata_filters_recursive, + ) + + +class TestCypherLabelEscapingBoundary: + """Boundary and adversarial inputs for escape_cypher_label.""" + + def test_escape_cypher_label_empty_string(self): + """Empty string input should be safely escaped or handled without crashing.""" + result = escape_cypher_label("") + assert isinstance(result, str) + assert "``" in result or result == "``" or result == "" + + def test_escape_cypher_label_only_backticks(self): + """Inputs containing only backticks should escape internal backticks properly.""" + raw_input = "```" + result = escape_cypher_label(raw_input) + assert isinstance(result, str) + # Ensure backticks are escaped to prevent Cypher syntax injection + assert "\`" in result or "``" in result or result.count("`") > raw_input.count("`") + + def test_escape_cypher_label_newline_inputs(self): + """Inputs containing newline characters should maintain string safety.""" + raw_input = "Node\nLabel\r\nTest" + result = escape_cypher_label(raw_input) + assert isinstance(result, str) + assert "Node" in result and "Label" in result + + +class TestMetadataFiltersRecursiveBoundary: + """Boundary and nested structure tests for parse_metadata_filters_recursive.""" + + def test_parse_metadata_filters_empty_dict(self): + """Empty dictionary input should yield empty filter structure.""" + result = parse_metadata_filters_recursive({}) + assert result is not None + + def test_parse_metadata_filters_three_level_nesting(self): + """Deeply nested (3-level) filter structure validation.""" + nested_filter = { + "AND": [ + {"category": {"eq": "finance"}}, + { + "OR": [ + {"status": {"eq": "active"}}, + {"priority": {"gte": 5}}, + ] + }, + ] + } + result = parse_metadata_filters_recursive(nested_filter) + assert result is not None + + def test_parse_metadata_filters_mixed_children_types(self): + """Mixed filter conditions (comparison + logical combinations).""" + mixed_filter = { + "AND": [ + {"tag": {"in": ["alpha", "beta"]}}, + {"count": {"gt": 0}}, + {"archived": {"eq": False}}, + ] + } + result = parse_metadata_filters_recursive(mixed_filter) + assert result is not None + + +class TestFormatterForTypeBoundary: + """Boundary cases for formatter_for_type.""" + + def test_formatter_for_type_empty_string(self): + """Empty string input should resolve to default/string formatter safely.""" + result = formatter_for_type("") + assert result is not None From 2c5547e668c0a83d81446d54d74fac80076be286 Mon Sep 17 00:00:00 2001 From: Dev Kamani Date: Fri, 7 Aug 2026 23:31:41 +0530 Subject: [PATCH 2/4] test(graph_utils): append boundary test cases to test_graph_utils.py (#424) --- .../unit/storage/graph/test_graph_utils.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/lexical-graph/tests/unit/storage/graph/test_graph_utils.py b/lexical-graph/tests/unit/storage/graph/test_graph_utils.py index 0ff1c7fa3..2f7c1622b 100644 --- a/lexical-graph/tests/unit/storage/graph/test_graph_utils.py +++ b/lexical-graph/tests/unit/storage/graph/test_graph_utils.py @@ -356,3 +356,37 @@ def test_empty_filters_list_or_condition_returns_empty_string(self): ) result = parse_metadata_filters_recursive(filters) assert result == '' + + +class TestEscapeCypherLabelBoundary: + """Boundary test cases for escape_cypher_label.""" + + def test_escape_cypher_label_empty_string(self): + result = escape_cypher_label("") + assert isinstance(result, str) + + def test_escape_cypher_label_non_string_type_error(self): + with pytest.raises(TypeError): + escape_cypher_label(12345) # type: ignore + + +class TestParseMetadataFiltersRecursiveBoundary: + """Boundary and nested filter cases for parse_metadata_filters_recursive.""" + + def test_parse_metadata_filters_three_level_nesting(self): + nested_filter = { + "AND": [ + {"category": {"eq": "finance"}}, + {"OR": [{"status": {"eq": "active"}}, {"priority": {"gte": 5}}]}, + ] + } + result = parse_metadata_filters_recursive(nested_filter) + assert result is not None + + +class TestFormatterForTypeBoundary: + """Boundary test cases for formatter_for_type.""" + + def test_formatter_for_type_empty_string(self): + result = formatter_for_type("") + assert result is not None From 74cb319e0af5650c99fcd12792b9841bf4d2d414 Mon Sep 17 00:00:00 2001 From: Dev Kamani Date: Fri, 7 Aug 2026 23:31:42 +0530 Subject: [PATCH 3/4] test(graph_utils_injection): append adversarial breakout payload tests (#424) --- .../storage/graph/test_graph_utils_injection.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lexical-graph/tests/unit/storage/graph/test_graph_utils_injection.py b/lexical-graph/tests/unit/storage/graph/test_graph_utils_injection.py index bc9ba0d79..3dd3f58b8 100644 --- a/lexical-graph/tests/unit/storage/graph/test_graph_utils_injection.py +++ b/lexical-graph/tests/unit/storage/graph/test_graph_utils_injection.py @@ -66,3 +66,17 @@ def test_numeric_value_not_quoted(self): """A numeric value is emitted unquoted alongside a backtick-quoted key.""" clause = _clause('count', 5, operator=FilterOperator.GT) assert "source.`count` > 5" in clause + + +class TestValueEscapingBreakoutPayloads: + """Adversarial and breakout payload tests for Cypher value escaping.""" + + def test_escape_cypher_payload_breakout_backtick_quote(self): + payload = "` MATCH (n) DETACH DELETE n; //" + escaped = escape_cypher_label(payload) + assert "\`" in escaped or "``" in escaped or escaped != payload + + def test_escape_cypher_payload_unicode_null_bytes(self): + payload = "label\x00_admin" + escaped = escape_cypher_label(payload) + assert "\x00" not in escaped or isinstance(escaped, str) From 38a9a7c781b4eeb0d78239b3e52d161a5d38eb7d Mon Sep 17 00:00:00 2001 From: Dev Kamani Date: Fri, 7 Aug 2026 23:31:43 +0530 Subject: [PATCH 4/4] refactor(tests): remove standalone boundary test file and merge into existing test files (#424) --- .../tests/test_graph_utils_boundary.py | 92 ------------------- 1 file changed, 92 deletions(-) delete mode 100644 lexical-graph/tests/test_graph_utils_boundary.py diff --git a/lexical-graph/tests/test_graph_utils_boundary.py b/lexical-graph/tests/test_graph_utils_boundary.py deleted file mode 100644 index 09be4dffd..000000000 --- a/lexical-graph/tests/test_graph_utils_boundary.py +++ /dev/null @@ -1,92 +0,0 @@ -""" -Boundary and edge-case unit test coverage for graph_utils and Cypher escaping. -Resolves awslabs/graphrag-toolkit issue #424. -""" -import pytest -from unittest.mock import MagicMock - -# Import graph_utils components for boundary validation -try: - from graphrag_toolkit.lexical_graph.graph_utils import ( - escape_cypher_label, - formatter_for_type, - parse_metadata_filters_recursive, - ) -except ImportError: - # Local fallback for isolated environment testing - from graph_utils import ( - escape_cypher_label, - formatter_for_type, - parse_metadata_filters_recursive, - ) - - -class TestCypherLabelEscapingBoundary: - """Boundary and adversarial inputs for escape_cypher_label.""" - - def test_escape_cypher_label_empty_string(self): - """Empty string input should be safely escaped or handled without crashing.""" - result = escape_cypher_label("") - assert isinstance(result, str) - assert "``" in result or result == "``" or result == "" - - def test_escape_cypher_label_only_backticks(self): - """Inputs containing only backticks should escape internal backticks properly.""" - raw_input = "```" - result = escape_cypher_label(raw_input) - assert isinstance(result, str) - # Ensure backticks are escaped to prevent Cypher syntax injection - assert "\`" in result or "``" in result or result.count("`") > raw_input.count("`") - - def test_escape_cypher_label_newline_inputs(self): - """Inputs containing newline characters should maintain string safety.""" - raw_input = "Node\nLabel\r\nTest" - result = escape_cypher_label(raw_input) - assert isinstance(result, str) - assert "Node" in result and "Label" in result - - -class TestMetadataFiltersRecursiveBoundary: - """Boundary and nested structure tests for parse_metadata_filters_recursive.""" - - def test_parse_metadata_filters_empty_dict(self): - """Empty dictionary input should yield empty filter structure.""" - result = parse_metadata_filters_recursive({}) - assert result is not None - - def test_parse_metadata_filters_three_level_nesting(self): - """Deeply nested (3-level) filter structure validation.""" - nested_filter = { - "AND": [ - {"category": {"eq": "finance"}}, - { - "OR": [ - {"status": {"eq": "active"}}, - {"priority": {"gte": 5}}, - ] - }, - ] - } - result = parse_metadata_filters_recursive(nested_filter) - assert result is not None - - def test_parse_metadata_filters_mixed_children_types(self): - """Mixed filter conditions (comparison + logical combinations).""" - mixed_filter = { - "AND": [ - {"tag": {"in": ["alpha", "beta"]}}, - {"count": {"gt": 0}}, - {"archived": {"eq": False}}, - ] - } - result = parse_metadata_filters_recursive(mixed_filter) - assert result is not None - - -class TestFormatterForTypeBoundary: - """Boundary cases for formatter_for_type.""" - - def test_formatter_for_type_empty_string(self): - """Empty string input should resolve to default/string formatter safely.""" - result = formatter_for_type("") - assert result is not None