from pathlib import Path from PIL import Image from pptx import Presentation from app.service.promotion_pdf_adapter import PromotionPdfAdapter from app.service.promotion_renderer import PromotionPptxRenderer def _draft(style_code: str = "balanced_allocation") -> dict[str, object]: return { "style_code": style_code, "subtitle": "测试产品 产品推介材料", "chapters": [ {"title": "测试产品", "body": "仅供合规审核和专业投顾使用"}, {"title": "产品基本信息", "body": "基金类型:混合型\n运作方式:开放式"}, {"title": "收益与风险指标", "body": "最大回撤反映历史区间内的最大跌幅。"}, { "title": "风险揭示", "body": ( "基金的过往业绩并不预示其未来表现," "基金管理人管理的其他基金的业绩并不构成基金业绩表现的保证。" ), }, ], } def test_renderer_creates_readable_pptx_with_fixed_slide_order(tmp_path: Path) -> None: output = tmp_path / "material.pptx" result = PromotionPptxRenderer().render(_draft(), output) assert result == str(output) assert output.is_file() presentation = Presentation(output) assert len(presentation.slides) == 4 titles = [ slide.shapes[1].text for slide in presentation.slides if len(slide.shapes) > 1 and hasattr(slide.shapes[1], "text") ] assert titles[:3] == ["测试产品", "产品基本信息", "收益与风险指标"] def test_renderer_keeps_style_code_outside_content_layout(tmp_path: Path) -> None: first = tmp_path / "first.pptx" second = tmp_path / "second.pptx" PromotionPptxRenderer().render(_draft("steady_professional"), first) changed = _draft("steady_professional") changed["chapters"] = [ {"title": "另一个标题", "body": "内容发生变化"}, {"title": "第二页", "body": "第二组内容"}, ] PromotionPptxRenderer().render(changed, second) first_presentation = Presentation(first) second_presentation = Presentation(second) assert first_presentation.slide_width == second_presentation.slide_width assert first_presentation.slide_height == second_presentation.slide_height def test_pdf_adapter_does_not_claim_conversion_without_converter() -> None: adapter = PromotionPdfAdapter(enabled=False) assert adapter.convert("missing.pptx", "output.pdf") is None def test_renderer_preserves_photo_aspect_ratio(tmp_path: Path) -> None: photo = tmp_path / "manager.png" Image.new("RGB", (800, 600), color="white").save(photo) output = tmp_path / "material-with-photo.pptx" PromotionPptxRenderer().render(_draft(), output, photo_path=str(photo)) presentation = Presentation(output) picture = next(shape for shape in presentation.slides[0].shapes if shape.shape_type == 13) assert round(picture.width / picture.height, 2) == round(800 / 600, 2) def test_renderer_applies_reference_palette_to_cover_and_content_slides( tmp_path: Path, ) -> None: output = tmp_path / "styled-material.pptx" PromotionPptxRenderer().render(_draft("steady_professional"), output) presentation = Presentation(output) cover = presentation.slides[0] content = presentation.slides[1] assert cover.shapes[0].fill.fore_color.rgb == (201, 76, 47) assert content.shapes[0].fill.fore_color.rgb == (255, 249, 242)