import pytest from fam_edge.ai_orchestrator.json_parser import parse_vlm_json, validate_schema, VLMOutputInvalidError def _base(): return { "global_summary": "客厅监控摘要", "events": [ {"timestamp": "00:00:03", "description": "人物A走进客厅", "people": ["人物A"], "is_attention_event": False, "person_appearances": [ {"uid": "人物A", "features": {"gender": "男"}, "action": "走动", "bbox": [10, 20, 500, 400]} ]} ], "people_mentioned": ["人物A"], } def test_direct_json_parses(): import json raw = json.dumps(_base(), ensure_ascii=False) result = parse_vlm_json(raw) assert result["global_summary"] == "客厅监控摘要" assert len(result["events"]) == 1 assert result["events"][0]["person_appearances"][0]["bbox"] == [10.0, 20.0, 500.0, 400.0] def test_markdown_fence_extraction(): import json raw = f"这是模型的解释文字\n```json\n{json.dumps(_base(), ensure_ascii=False)}\n```\n谢谢" result = parse_vlm_json(raw) assert result["events"][0]["timestamp"] == "00:00:03" def test_greedy_brace_extraction(): import json raw = f"废话前缀 {json.dumps(_base(), ensure_ascii=False)} 废话后缀" result = parse_vlm_json(raw) assert result["people_mentioned"] == ["人物A"] def test_invalid_json_raises(): with pytest.raises(VLMOutputInvalidError): parse_vlm_json("这不是 JSON,也没有大括号") def test_missing_required_field_raises(): with pytest.raises(VLMOutputInvalidError): validate_schema({"events": []}) def test_frame_details_legacy_compat(): data = { "global_summary": "旧结构", "frame_details": [ {"frame_timestamp": "00:00:05", "action": "走动", "person": "人物A", "is_attention_event": True} ], } result = validate_schema(data) assert len(result["events"]) == 1 assert result["events"][0]["description"] == "走动" assert result["events"][0]["people"] == ["人物A"] assert result["events"][0]["is_attention_event"] is True def test_bbox_missing_or_null_becomes_none(): data = _base() data["events"][0]["person_appearances"][0]["bbox"] = None result = validate_schema(data) assert result["events"][0]["person_appearances"][0]["bbox"] is None def test_bbox_wrong_shape_becomes_none(): data = _base() data["events"][0]["person_appearances"][0]["bbox"] = [1, 2, 3] # 长度不对 result = validate_schema(data) assert result["events"][0]["person_appearances"][0]["bbox"] is None def test_bbox_non_numeric_becomes_none(): data = _base() data["events"][0]["person_appearances"][0]["bbox"] = ["a", "b", "c", "d"] result = validate_schema(data) assert result["events"][0]["person_appearances"][0]["bbox"] is None