Files
sanguo_vnpy_v2/tests/api/test_llm_json_utils.py

64 lines
2.6 KiB
Python

# tests/api/test_llm_json_utils.py
"""robust_json_parse 五级兜底(QA client.py:36-121 范式+tickflow 尾部修复)."""
import pytest
from sanguo_api.llm.json_utils import JSONParseError, robust_json_parse
@pytest.mark.parametrize("text", [
'{"title": "a"}', # L1 直读
'好的,结果如下:\n```json\n{"title": "a"}\n```', # L2 围栏
'前置说明 {"title": "a"} 后置废话', # L3 首个配平块
' latex 残留 \\(x\\) {"title": "a"}', # L4 反斜杠修复
])
def test_parse_recovers(text):
assert robust_json_parse(text) == {"title": "a"}
def test_l5_trailing_comma_and_garbage():
assert robust_json_parse('{"title": "a",}') == {"title": "a"}
assert robust_json_parse('{"title": "a"} extra }') == {"title": "a"}
def test_l5_string_aware_trailing_comma():
"""P2-2: 字符串内 ", }" 原值保全,仅外层尾逗号被清——曾把字段值
"a, }" 损坏为 "a }" 后当成功返回."""
assert robust_json_parse('{"s": "a, }",}') == {"s": "a, }"}
assert robust_json_parse('{"s": "a, ]",}') == {"s": "a, ]"}
def test_l5_clean_trailing_commas_escape_aware():
"""转义引号不终止字符串扫描:字符串内 ,} 保留,外层尾逗号仍清."""
from sanguo_api.llm.json_utils import _clean_trailing_commas
assert _clean_trailing_commas('{"s": "a\\", b",}') == '{"s": "a\\", b"}'
assert _clean_trailing_commas('{"a": 1, }') == '{"a": 1 }'
def test_non_dict_raises():
with pytest.raises(JSONParseError):
robust_json_parse('["a", "b"]')
def test_garbage_raises():
with pytest.raises(JSONParseError):
robust_json_parse('完全不是 JSON')
def test_non_str_input_raises_json_parse_error():
"""P2-3: 非 str 输入(含 None)曾 TypeError 裸逃逸,穿透 502 契约变原生 500."""
for bad in (None, 42, {"a": 1}, ["a"], b"{}"):
with pytest.raises(JSONParseError):
robust_json_parse(bad)
def test_l4_l5_fixes_cumulative():
"""P3-1: L4 反斜杠修复与 L5 尾逗号清洗须累积叠加——并行候选下「LaTeX
残留+尾逗号」同现时两级各自单独修都不够,整段降级链全败(假阴性 502)."""
assert robust_json_parse('{"s": "\\(x\\)",}') == {"s": "(x)"}
def test_balanced_start_skips_brace_inside_string():
"""P3-2: 首个 { 落在字符串内时旧 find("{") 起点错位,配平块从头就坏,
后续 L4/L5 全被拖死——起点扫描须字符串感知(复用 in_str/esc 状态机)."""
assert robust_json_parse('他说"{不是json" 然后 {"a": 1}') == {"a": 1}