From 9af9b716cede6666989f177f0e8abe74e81171e2 Mon Sep 17 00:00:00 2001 From: Bob Date: Wed, 28 Oct 2026 19:57:02 +0000 Subject: [PATCH 1/2] fix(query): reject truncated dictionary keys as parse errors Git-Session-Id: e7890338-d6fc-4b47-843e-a159a67ce343 --- aw_query/query2.py | 2 +- tests/test_query2.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/aw_query/query2.py b/aw_query/query2.py index 07d2570b..37f99e6e 100644 --- a/aw_query/query2.py +++ b/aw_query/query2.py @@ -242,7 +242,7 @@ def parse(string: str, namespace: dict) -> QToken: key = QString.parse(key_str, {}).value entries_str = entries_str.strip() # Remove : - if entries_str[0] != ":": + if not entries_str.startswith(":"): raise QueryParseException("Key in dict is not followed by a :") entries_str = entries_str[1:] # parse val diff --git a/tests/test_query2.py b/tests/test_query2.py index 66a4ef39..4f958867 100644 --- a/tests/test_query2.py +++ b/tests/test_query2.py @@ -115,6 +115,38 @@ def test_dict(): d = QDict.parse(d_str, ns) +@pytest.mark.parametrize( + "literal", + ['{"a"}', '{"a" }', '{"a"\t\n}', '{"a": 1, "b"}', '{"outer": {"a"}}'], +) +def test_dict_truncated_key(literal): + with pytest.raises(QueryParseException, match="Key in dict is not followed by a :"): + QDict.parse(literal, {}) + + start = datetime(2026, 1, 1, tzinfo=timezone.utc) + with pytest.raises(QueryParseException, match="Key in dict is not followed by a :"): + query("test", f"RETURN = {literal};", start, start, mock_ds) + + +@pytest.mark.parametrize("literal", ['{"a":}', '{"a": }', '{"a" 1}']) +def test_dict_missing_colon_or_value(literal): + with pytest.raises(QueryParseException): + QDict.parse(literal, {}) + + +@pytest.mark.parametrize( + "literal, expected", + [ + ("{}", {}), + ('{"a": 1}', {"a": 1}), + ('{"a": 1, "b": 2}', {"a": 1, "b": 2}), + ('{"outer": {"a": 1}, "empty": {}}', {"outer": {"a": 1}, "empty": {}}), + ], +) +def test_dict_valid_controls(literal, expected): + assert QDict.parse(literal, {}).interpret(mock_ds, {}) == expected + + def test_list(): ds = mock_ds ns: Dict[str, Any] = {} From 784c271da9e1ef34947582dd62d96393081cd89f Mon Sep 17 00:00:00 2001 From: Bob Date: Wed, 28 Oct 2026 19:59:42 +0000 Subject: [PATCH 2/2] ci: pin Linux to a runner that provides Python 3.8 Git-Session-Id: e7890338-d6fc-4b47-843e-a159a67ce343 --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b1ec2c20..31672c20 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,8 @@ jobs: RELEASE: false strategy: matrix: - os: [ubuntu-latest, windows-latest, macOS-latest] + # Python 3.8 is unavailable on Ubuntu 26.04 (ubuntu-latest). + os: [ubuntu-22.04, windows-latest, macOS-latest] python_version: [3.8] steps: - uses: actions/checkout@v3