test_decode_readers_service.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. from __future__ import annotations
  2. from uuid import uuid4
  3. from acquisition.domain import CandidateItem, MediaAsset
  4. from core.models import Card, CardExtract, ExtractedContent, Post
  5. from decode_content.readers.service import (
  6. UnsupportedPostModeError,
  7. post_from_candidate_item,
  8. read_item,
  9. read_post,
  10. )
  11. def test_post_from_candidate_item_prefers_cdn_then_oss_then_source():
  12. item_id = uuid4()
  13. item = CandidateItem(
  14. id=item_id,
  15. platform="xiaohongshu",
  16. platform_item_id="abc",
  17. unique_key="xhs:abc",
  18. canonical_url="https://xhs/item/abc",
  19. content_mode="image_post",
  20. title="脚本结构",
  21. author_name="sam",
  22. raw_summary="正文",
  23. source_payload={"topic_list": ["脚本"]},
  24. )
  25. media = [
  26. MediaAsset(item_id=item_id, media_type="image", source_url="source1", cdn_url="cdn1", position=2),
  27. MediaAsset(item_id=item_id, media_type="image", source_url="source0", oss_url="oss0", position=1),
  28. ]
  29. post = post_from_candidate_item(item, media)
  30. assert post.id == "xiaohongshu_abc"
  31. assert post.content_id == "abc"
  32. assert post.content_mode == "image_post"
  33. assert post.image_urls == ["oss0", "cdn1"]
  34. assert [card.index for card in post.cards] == [1, 2]
  35. assert post.body_text == "正文"
  36. assert post.topic_list == ["脚本"]
  37. def test_post_from_candidate_item_prefers_full_body_text_over_summary():
  38. item_id = uuid4()
  39. item = CandidateItem(
  40. id=item_id,
  41. platform="weixin",
  42. platform_item_id="wx1",
  43. content_mode="article",
  44. title="长文",
  45. body_text="完整正文",
  46. raw_summary="摘要",
  47. )
  48. post = post_from_candidate_item(item, [])
  49. assert post.body_text == "完整正文"
  50. def test_post_from_candidate_item_reads_legacy_nested_body_before_summary():
  51. item_id = uuid4()
  52. item = CandidateItem(
  53. id=item_id,
  54. platform="xiaohongshu",
  55. platform_item_id="x1",
  56. content_mode="image_post",
  57. raw_summary="摘要",
  58. source_payload={"detail": {"data": {"data": {"body_text": "旧 raw 正文"}}}},
  59. )
  60. post = post_from_candidate_item(item, [])
  61. assert post.body_text == "旧 raw 正文"
  62. def test_read_post_imgtext_merges_text_cards_and_media():
  63. post = Post(
  64. id="xhs_1",
  65. platform="xiaohongshu",
  66. url="u",
  67. content_id="1",
  68. image_urls=["img1", "img2"],
  69. cards=[Card(index=1, kind="image", url="img1"), Card(index=2, kind="image", url="img2")],
  70. )
  71. def image_reader(p: Post) -> ExtractedContent:
  72. assert p.id == "xhs_1"
  73. return ExtractedContent(
  74. text="主体",
  75. from_image="图片知识",
  76. cards=[CardExtract(index=1, content="卡1"), CardExtract(index=2, content="卡2")],
  77. )
  78. result = read_post(post, image_reader=image_reader)
  79. assert "图片知识" in result.text
  80. assert result.cards[0].content == "卡1"
  81. assert result.media == {"type": "image", "video_url": None, "images": ["img1", "img2"]}
  82. def test_read_item_routes_video_to_video_reader():
  83. item_id = uuid4()
  84. item = CandidateItem(
  85. id=item_id,
  86. platform="douyin",
  87. platform_item_id="v1",
  88. content_mode="video_post",
  89. title="视频",
  90. )
  91. media = [MediaAsset(item_id=item_id, media_type="video", cdn_url="https://cdn/video.mp4", position=1)]
  92. def video_reader(post: Post) -> ExtractedContent:
  93. assert post.video_urls == ["https://cdn/video.mp4"]
  94. post.cards = [Card(index=1, kind="segment", url=post.video_urls[0], start=0, end=12)]
  95. return ExtractedContent(text="视频理解", cards=[CardExtract(index=1, content="片段知识")])
  96. result = read_item(item, media, video_reader=video_reader)
  97. assert result.media["type"] == "video"
  98. assert result.cards[0].kind == "segment"
  99. assert result.cards[0].content == "片段知识"
  100. def test_read_post_rejects_unsupported_and_missing_video():
  101. unsupported = Post(
  102. id="dy_x",
  103. platform="douyin",
  104. url="u",
  105. content_id="x",
  106. content_mode="unsupported",
  107. )
  108. missing_video = Post(
  109. id="dy_v",
  110. platform="douyin",
  111. url="u",
  112. content_id="v",
  113. content_mode="video_post",
  114. )
  115. import pytest
  116. with pytest.raises(UnsupportedPostModeError, match="unsupported_content_mode"):
  117. read_post(unsupported)
  118. with pytest.raises(UnsupportedPostModeError, match="video_url_missing"):
  119. read_post(missing_video)