|
| 1 | +""" |
| 2 | +VisionExtractChain._parse_response 单元测试 |
| 3 | +
|
| 4 | +@ai-context: 用例源自内测真实故障——模型输出被 max_tokens 截断产生残缺 JSON, |
| 5 | +旧逻辑将原始 JSON 片段直接返回为 text 并泄漏到 UI 时间线。 |
| 6 | +""" |
| 7 | + |
| 8 | +import pytest |
| 9 | + |
| 10 | +from chains.vision_extract_chain import VisionExtractChain |
| 11 | + |
| 12 | + |
| 13 | +@pytest.fixture |
| 14 | +def chain() -> VisionExtractChain: |
| 15 | + # _parse_response 不触碰 provider,传 None 即可 |
| 16 | + return VisionExtractChain(provider=None) # type: ignore[arg-type] |
| 17 | + |
| 18 | + |
| 19 | +class TestParseResponse: |
| 20 | + def test_valid_json(self, chain): |
| 21 | + content = '{"text": "牛顿第二定律", "formulas": ["$F=ma$"], "diagrams": [], "keyPoints": [], "codeBlocks": [], "concepts": []}' |
| 22 | + result = chain._parse_response(content) |
| 23 | + assert result["text"] == "牛顿第二定律" |
| 24 | + assert result["formulas"] == ["$F=ma$"] |
| 25 | + |
| 26 | + def test_fenced_json(self, chain): |
| 27 | + content = '```json\n{"text": "板书内容", "formulas": [], "diagrams": [], "keyPoints": [], "codeBlocks": [], "concepts": []}\n```' |
| 28 | + result = chain._parse_response(content) |
| 29 | + assert result["text"] == "板书内容" |
| 30 | + |
| 31 | + def test_truncated_json_salvages_text_field(self, chain): |
| 32 | + """截断残缺 JSON:抢救 text 字段值,不泄漏 JSON 语法到 UI""" |
| 33 | + content = '{"text": "打鼾的危害讲解", "formulas": [], "keyPoints": ["打鼾虽正常' |
| 34 | + result = chain._parse_response(content) |
| 35 | + assert result["text"] == "打鼾的危害讲解" |
| 36 | + assert '"keyPoints"' not in result["text"] |
| 37 | + |
| 38 | + def test_truncated_json_without_text_field_returns_empty(self, chain): |
| 39 | + """残缺 JSON 连 text 字段都不完整时返回空文本,而非泄漏原文""" |
| 40 | + content = '```json\n{"keyPoints": ["要点1", "要' |
| 41 | + result = chain._parse_response(content) |
| 42 | + assert result["text"] == "" |
| 43 | + |
| 44 | + def test_plain_text_passthrough(self, chain): |
| 45 | + """非 JSON 形态的纯文本输出原样保留""" |
| 46 | + content = "这是一段普通的板书文字描述" |
| 47 | + result = chain._parse_response(content) |
| 48 | + assert result["text"] == content |
| 49 | + |
| 50 | + def test_salvaged_text_unescapes(self, chain): |
| 51 | + """抢救的 text 字段应还原转义字符""" |
| 52 | + content = '{"text": "第一行\\n第二行", "formulas": [' |
| 53 | + result = chain._parse_response(content) |
| 54 | + assert result["text"] == "第一行\n第二行" |
0 commit comments