94 lines
3.5 KiB
Python
94 lines
3.5 KiB
Python
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)
|