| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- from __future__ import annotations
- import json
- import pytest
- from core.models import Post
- from decode_content.payloads import IngestPayloadValidationError, build_payload, build_payloads, validate_ingest_payload
- def _post() -> Post:
- return Post(
- id="xhs_1",
- platform="xiaohongshu",
- url="https://xhs/1",
- content_id="1",
- unique_key="xhs:1",
- title="脚本教程",
- content_type="note",
- content_mode="image_post",
- media_count=2,
- cover_url="https://cdn.test/cover.webp",
- author_name="作者",
- )
- def test_build_how_payload_uses_formal_dim_attribute_and_scope_union():
- knowledge = {
- "id": "k1",
- "type": "how",
- "title": "先定受众再写脚本",
- "purpose": "写出稳定脚本",
- "业务阶段": ["脚本"],
- "steps": [
- {
- "input": "选题",
- "directive": "收窄受众",
- "output": "受众画像",
- "创作阶段": "定向",
- "动作": "收窄",
- "作用域": [
- {"scope_type": "intent", "value": "吸引注意", "link": "复用"},
- {"scope_type": "intent", "value": "吸引注意", "link": "复用"},
- ],
- }
- ],
- }
- payload = build_payload(_post(), knowledge)
- assert payload["dim_attributes"] == ["how"]
- assert payload["source"]["source_metadata"] == {
- "platform": "xiaohongshu",
- "url": "https://xhs/1",
- "unique_key": "xhs:1",
- "platform_item_id": "1",
- "content_type": "note",
- "content_mode": "image_post",
- "media_count": 2,
- "cover_url": "https://cdn.test/cover.webp",
- }
- assert "输入:选题" in payload["content"]
- assert "指引:收窄受众" in payload["content"]
- assert payload["scopes"] == [{"scope_type": "intent", "value": "吸引注意"}]
- assert {"key": "创作阶段", "type": "str", "value": "定向"} in payload["custom_ext"]
- def test_build_what_and_why_payload_content_is_string_contract():
- what = {
- "id": "w1",
- "type": "what",
- "title": "脚本三要素",
- "kind": "子集",
- "概要": "脚本由开头中段结尾构成",
- "维度拆分规则": "按表达顺序",
- "body": [{"item_name": "开头", "item_desc": "抓注意"}],
- "业务阶段": ["脚本"],
- "作用域": [{"scope_type": "form", "value": "脚本结构", "top": []}],
- }
- why = {
- "id": "y1",
- "type": "why",
- "title": "冲突带来停留",
- "阐述": "冲突能制造期待,所以提升停留。",
- "业务阶段": ["选题"],
- "作用域": [{"scope_type": "effect", "value": "提升停留", "score": 0.95}],
- }
- what_payload, why_payload = build_payloads(_post(), [what, why])
- assert what_payload["dim_attributes"] == ["what"]
- assert json.loads(what_payload["content"])["body"][0]["item_name"] == "开头"
- assert {"key": "概要", "type": "str", "value": "脚本由开头中段结尾构成"} in what_payload["custom_ext"]
- assert why_payload["dim_attributes"] == ["why"]
- assert why_payload["content"] == "冲突能制造期待,所以提升停留。"
- assert "score" not in why_payload["scopes"][0]
- def test_build_why_payload_accepts_explanation_alias():
- payload = build_payload(
- _post(),
- {
- "id": "y2",
- "type": "why",
- "title": "动作变化带来画面节奏",
- "explanation": "动作变化能制造连续观看的期待。",
- "业务阶段": ["脚本"],
- "作用域": [{"scope_type": "effect", "value": "提升节奏感"}],
- },
- )
- assert payload["content"] == "动作变化能制造连续观看的期待。"
- def test_validate_ingest_payload_blocks_external_api_contract_violations():
- payload = build_payload(
- _post(),
- {
- "id": "h1",
- "type": "how",
- "title": "先定受众再写脚本",
- "purpose": "写出稳定脚本",
- "业务阶段": ["脚本"],
- "steps": [
- {
- "input": "选题",
- "directive": "收窄受众",
- "output": "受众画像",
- "作用域": [{"scope_type": "intent", "value": "吸引注意"}],
- }
- ],
- },
- )
- bad = {**payload, "content": ""}
- with pytest.raises(IngestPayloadValidationError, match="content"):
- validate_ingest_payload(bad)
- bad = {**payload, "source": {**payload["source"], "id": "x" * 65}}
- with pytest.raises(IngestPayloadValidationError, match="source.id"):
- validate_ingest_payload(bad)
- bad = {**payload, "title": "x" * 513}
- with pytest.raises(IngestPayloadValidationError, match="title"):
- validate_ingest_payload(bad)
- bad = {**payload, "source": {**payload["source"], "author": "x" * 129}}
- with pytest.raises(IngestPayloadValidationError, match="author"):
- validate_ingest_payload(bad)
- bad = {**payload, "scopes": [{"scope_type": "intent", "value": "x" * 129}]}
- with pytest.raises(IngestPayloadValidationError, match="scopes"):
- validate_ingest_payload(bad)
- bad = {**payload, "custom_ext": [{"key": "推荐指数", "type": "bad", "value": "5"}]}
- with pytest.raises(IngestPayloadValidationError, match="custom_ext"):
- validate_ingest_payload(bad)
- def test_validate_ingest_payload_rejects_legacy_dim_attributes():
- payload = build_payload(
- _post(),
- {
- "id": "h1",
- "type": "how",
- "title": "先定受众再写脚本",
- "purpose": "写出稳定脚本",
- "业务阶段": ["脚本"],
- "steps": [
- {
- "input": "选题",
- "directive": "收窄受众",
- "output": "受众画像",
- "作用域": [{"scope_type": "intent", "value": "吸引注意"}],
- }
- ],
- },
- )
- legacy_values = [
- "how" + "\u5de5\u5e8f",
- "what" + "\u6784\u6210",
- "why" + "\u539f\u7406",
- ]
- for legacy_value in legacy_values:
- bad = {**payload, "dim_attributes": [legacy_value]}
- with pytest.raises(IngestPayloadValidationError, match="dim_attributes"):
- validate_ingest_payload(bad)
|