test_extractor.py 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. """M3 离线测试:注入假 http_post,验证消息构造 + JSON 解析(不调真实 Gemini)。
  2. 真实 Gemini 验收见 scripts/smoke_extract.py(须在能联网调 OpenRouter 的云端跑)。
  3. """
  4. from __future__ import annotations
  5. import json
  6. from creation_knowledge.integrations.extractor import GeminiExtractor
  7. from core.models import Post
  8. class _FakeResp:
  9. def __init__(self, payload: dict):
  10. self._payload = payload
  11. def raise_for_status(self):
  12. return None
  13. def json(self):
  14. return self._payload
  15. def _fake_post_returning(content: str):
  16. captured = {}
  17. def _post(url, headers=None, json=None, timeout=None):
  18. captured["url"] = url
  19. captured["headers"] = headers
  20. captured["json"] = json
  21. return _FakeResp({"choices": [{"message": {"content": content}}]})
  22. return _post, captured
  23. def _post(**kw) -> Post:
  24. return Post(
  25. id="xhs_x", url="u", content_id="x",
  26. title="只要学会这几样,写短视频脚本真的不难",
  27. body_text="", topic_list=["短视频"],
  28. image_urls=["https://img/1.webp", "https://img/2.webp"],
  29. **kw,
  30. )
  31. def test_build_messages_has_text_and_images():
  32. client = GeminiExtractor(api_key="k", http_post=lambda **kw: None)
  33. msgs = client.build_messages(_post())
  34. assert msgs[0]["role"] == "system"
  35. user_parts = msgs[1]["content"]
  36. assert user_parts[0]["type"] == "text"
  37. images = [p for p in user_parts if p["type"] == "image_url"]
  38. assert len(images) == 2
  39. assert images[0]["image_url"]["url"] == "https://img/1.webp"
  40. def test_parse_plain_json():
  41. fake, captured = _fake_post_returning(
  42. json.dumps({"text": "脚本要素:标题/地点/分镜", "from_image": "九宫格讲了6要素",
  43. "from_video": "", "is_empty": False}, ensure_ascii=False)
  44. )
  45. client = GeminiExtractor(api_key="k", http_post=fake)
  46. out = client.extract(_post())
  47. assert out.is_empty is False
  48. assert "from_image" and out.from_image
  49. assert "脚本" in out.text
  50. # 鉴权头 + 模型名正确传入
  51. assert captured["headers"]["Authorization"].startswith("Bearer ")
  52. assert captured["json"]["model"]
  53. def test_build_messages_labels_each_card():
  54. client = GeminiExtractor(api_key="k", http_post=lambda **kw: None)
  55. parts = client.build_messages(_post())[1]["content"]
  56. labels = [p["text"] for p in parts if p["type"] == "text"]
  57. assert any("【卡片1】" in t for t in labels)
  58. assert any("【卡片2】" in t for t in labels)
  59. def test_parse_per_card_output():
  60. fake, _ = _fake_post_returning(json.dumps(
  61. {"text": "x", "cards": [{"index": 1, "content": "卡1要点"},
  62. {"index": 2, "content": "卡2要点"}],
  63. "is_empty": False}, ensure_ascii=False))
  64. out = GeminiExtractor(api_key="k", http_post=fake).extract(_post())
  65. assert len(out.cards) == 2
  66. assert out.cards[0].index == 1 and out.cards[0].content == "卡1要点"
  67. def test_parse_json_with_code_fence():
  68. fenced = "```json\n" + json.dumps(
  69. {"text": "x", "from_image": "y", "from_video": "", "is_empty": "false"},
  70. ensure_ascii=False) + "\n```"
  71. fake, _ = _fake_post_returning(fenced)
  72. client = GeminiExtractor(api_key="k", http_post=fake)
  73. out = client.extract(_post())
  74. assert out.is_empty is False
  75. assert out.from_image == "y"