from __future__ import annotations from uuid import uuid4 from acquisition.domain import CandidateItem, MediaAsset from core.models import Card, CardExtract, ExtractedContent, Post from decode_content.readers.service import ( UnsupportedPostModeError, post_from_candidate_item, read_item, read_post, ) def test_post_from_candidate_item_prefers_cdn_then_oss_then_source(): item_id = uuid4() item = CandidateItem( id=item_id, platform="xiaohongshu", platform_item_id="abc", unique_key="xhs:abc", canonical_url="https://xhs/item/abc", content_mode="image_post", title="脚本结构", author_name="sam", raw_summary="正文", source_payload={"topic_list": ["脚本"]}, ) media = [ MediaAsset(item_id=item_id, media_type="image", source_url="source1", cdn_url="cdn1", position=2), MediaAsset(item_id=item_id, media_type="image", source_url="source0", oss_url="oss0", position=1), ] post = post_from_candidate_item(item, media) assert post.id == "xiaohongshu_abc" assert post.content_id == "abc" assert post.content_mode == "image_post" assert post.image_urls == ["oss0", "cdn1"] assert [card.index for card in post.cards] == [1, 2] assert post.body_text == "正文" assert post.topic_list == ["脚本"] def test_post_from_candidate_item_prefers_full_body_text_over_summary(): item_id = uuid4() item = CandidateItem( id=item_id, platform="weixin", platform_item_id="wx1", content_mode="article", title="长文", body_text="完整正文", raw_summary="摘要", ) post = post_from_candidate_item(item, []) assert post.body_text == "完整正文" def test_post_from_candidate_item_reads_legacy_nested_body_before_summary(): item_id = uuid4() item = CandidateItem( id=item_id, platform="xiaohongshu", platform_item_id="x1", content_mode="image_post", raw_summary="摘要", source_payload={"detail": {"data": {"data": {"body_text": "旧 raw 正文"}}}}, ) post = post_from_candidate_item(item, []) assert post.body_text == "旧 raw 正文" def test_read_post_imgtext_merges_text_cards_and_media(): post = Post( id="xhs_1", platform="xiaohongshu", url="u", content_id="1", image_urls=["img1", "img2"], cards=[Card(index=1, kind="image", url="img1"), Card(index=2, kind="image", url="img2")], ) def image_reader(p: Post) -> ExtractedContent: assert p.id == "xhs_1" return ExtractedContent( text="主体", from_image="图片知识", cards=[CardExtract(index=1, content="卡1"), CardExtract(index=2, content="卡2")], ) result = read_post(post, image_reader=image_reader) assert "图片知识" in result.text assert result.cards[0].content == "卡1" assert result.media == {"type": "image", "video_url": None, "images": ["img1", "img2"]} def test_read_item_routes_video_to_video_reader(): item_id = uuid4() item = CandidateItem( id=item_id, platform="douyin", platform_item_id="v1", content_mode="video_post", title="视频", ) media = [MediaAsset(item_id=item_id, media_type="video", cdn_url="https://cdn/video.mp4", position=1)] def video_reader(post: Post) -> ExtractedContent: assert post.video_urls == ["https://cdn/video.mp4"] post.cards = [Card(index=1, kind="segment", url=post.video_urls[0], start=0, end=12)] return ExtractedContent(text="视频理解", cards=[CardExtract(index=1, content="片段知识")]) result = read_item(item, media, video_reader=video_reader) assert result.media["type"] == "video" assert result.cards[0].kind == "segment" assert result.cards[0].content == "片段知识" def test_read_post_rejects_unsupported_and_missing_video(): unsupported = Post( id="dy_x", platform="douyin", url="u", content_id="x", content_mode="unsupported", ) missing_video = Post( id="dy_v", platform="douyin", url="u", content_id="v", content_mode="video_post", ) import pytest with pytest.raises(UnsupportedPostModeError, match="unsupported_content_mode"): read_post(unsupported) with pytest.raises(UnsupportedPostModeError, match="video_url_missing"): read_post(missing_video)