import pytest from app.service.promotion_layout_planner import PromotionLayoutPlanner def _draft() -> dict[str, object]: return { "style_code": "balanced_allocation", "title": "测试材料", "chapters": [ {"title": "产品基本信息", "body": "产品内容"}, {"title": "风险揭示", "body": "风险内容"}, ], } def test_default_plan_uses_content_sensitive_layouts() -> None: plan = PromotionLayoutPlanner.default_plan(_draft(), has_photo=True, has_chart=False) assert plan["cover_layout"] == "cover_left_photo_right" assert plan["page_plans"][1]["layout"] == "full_width_disclosure" assert plan["design_profile"]["manager_layout"] == "profile_feature" def test_strategy_heavy_material_uses_asymmetric_design_profile() -> None: draft = { **_draft(), "chapters": [ { "title": "投资范围、策略与限制", "body": "投资策略说明" * 20, } ], } plan = PromotionLayoutPlanner.default_plan(draft, has_photo=True, has_chart=True) assert plan["design_profile"]["design_tone"] == "institutional_tech" assert plan["design_profile"]["content_structure"] == "strategy_right_emphasis" assert plan["design_profile"]["column_ratio"] == "40_60" def test_low_density_chapter_uses_visual_focus_layout() -> None: draft = { **_draft(), "chapters": [{"title": "投资观点", "body": "长期配置。"}], } plan = PromotionLayoutPlanner.default_plan(draft, has_photo=False, has_chart=False) assert plan["page_plans"][0]["content_density"] == "low" assert plan["page_plans"][0]["layout"] == "visual_focus" def test_content_density_respects_existing_chart_signal() -> None: chapter = {"title": "业绩", "body": "简要说明", "chart_index": 0} assert PromotionLayoutPlanner.content_density(chapter) == "medium" def test_validate_rejects_unknown_layout_and_keeps_fallback() -> None: fallback = PromotionLayoutPlanner.default_plan(_draft(), has_photo=False, has_chart=False) invalid = { **fallback, "page_plans": [ {"chapter_title": "产品基本信息", "layout": "free_coordinates"}, {"chapter_title": "风险揭示", "layout": "full_width_disclosure"}, ], } result = PromotionLayoutPlanner._validate(invalid, _draft(), fallback) assert result == fallback def test_validate_preserves_chapter_titles_and_only_accepts_complete_plan() -> None: fallback = PromotionLayoutPlanner.default_plan(_draft(), has_photo=False, has_chart=False) value = { "cover_layout": "cover_visual_right", "poster_layout": "poster_balanced", "background_theme": "geometric_grid", "design_profile": { "design_tone": "quiet_data", "content_structure": "balanced_split", "column_ratio": "50_50", "manager_layout": "profile_compact", "performance_layout": "metrics_first", "background_role": "soft_paper", }, "page_plans": [ {"chapter_title": "风险揭示", "layout": "full_width_disclosure"}, {"chapter_title": "产品基本信息", "layout": "two_columns"}, ], } result = PromotionLayoutPlanner._validate(value, _draft(), fallback) assert [item["chapter_title"] for item in result["page_plans"]] == [ "产品基本信息", "风险揭示", ] assert result["page_plans"][0]["layout"] == "two_columns" @pytest.mark.asyncio async def test_plan_falls_back_when_model_fails(monkeypatch: pytest.MonkeyPatch) -> None: async def fail_resolve(*, agent_type: str, task_type: str) -> list[object]: raise RuntimeError("模型不可用") monkeypatch.setattr( "app.service.promotion_layout_planner.DatabaseModelEndpointResolver.resolve", fail_resolve, ) result = await PromotionLayoutPlanner().plan(_draft(), has_photo=False, has_chart=False) assert result == PromotionLayoutPlanner.default_plan( _draft(), has_photo=False, has_chart=False )