Files

148 lines
5.4 KiB
Python

import copy
import importlib.util
import io
import json
import tempfile
import unittest
from pathlib import Path
from PIL import Image
from pptx import Presentation
SKILL_DIR = Path(__file__).resolve().parents[1]
SCRIPT = SKILL_DIR / "scripts" / "build_pptx.py"
SPEC = importlib.util.spec_from_file_location("build_pptx", SCRIPT)
build_pptx = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(build_pptx)
class BuildPptxTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
template = SKILL_DIR / "templates" / "deck.example.json"
cls.deck = json.loads(template.read_text(encoding="utf-8"))
def test_builds_example_deck(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
input_path = root / "deck.json"
input_path.write_text(
json.dumps(self.deck, ensure_ascii=False),
encoding="utf-8",
)
loaded_path, deck = build_pptx.load_deck(input_path)
output = build_pptx.build_presentation(
loaded_path,
deck,
root / "deck.pptx",
)
presentation = Presentation(output)
self.assertEqual(len(presentation.slides), len(self.deck["slides"]))
all_text = "\n".join(
shape.text
for slide in presentation.slides
for shape in slide.shapes
if hasattr(shape, "text")
)
self.assertIn("目标场景与问题", all_text)
self.assertIn("下一步", all_text)
for slide, item in zip(presentation.slides, self.deck["slides"]):
self.assertEqual(
slide.notes_slide.notes_text_frame.text.strip(),
item["notes"].strip(),
)
def test_requires_notes_outside_section_slides(self):
deck = copy.deepcopy(self.deck)
deck["slides"][1].pop("notes")
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp) / "deck.json"
path.write_text(json.dumps(deck, ensure_ascii=False), encoding="utf-8")
with self.assertRaisesRegex(ValueError, r"slides\[1\]\.notes"):
build_pptx.load_deck(path)
def test_downsamples_large_image_slide(self):
deck = copy.deepcopy(self.deck)
deck["slides"].insert(
3,
{
"type": "image",
"title": "参考架构",
"image": "architecture.png",
"caption": "示例架构图",
"notes": "说明组件边界。",
},
)
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
Image.new("RGB", (5000, 3000), "white").save(root / "architecture.png")
path = root / "deck.json"
path.write_text(json.dumps(deck, ensure_ascii=False), encoding="utf-8")
loaded_path, loaded = build_pptx.load_deck(path)
output = build_pptx.build_presentation(
loaded_path, loaded, root / "deck.pptx"
)
presentation = Presentation(output)
self.assertEqual(len(presentation.slides), len(deck["slides"]))
pictures = [
shape
for slide in presentation.slides
for shape in slide.shapes
if shape.shape_type == 13
]
self.assertEqual(len(pictures), 1)
with Image.open(io.BytesIO(pictures[0].image.blob)) as embedded:
self.assertLess(embedded.width, 5000)
def test_rejects_image_outside_deck_directory(self):
deck = copy.deepcopy(self.deck)
deck["slides"].insert(
1,
{
"type": "image",
"title": "外部图片",
"image": "../outside.png",
"notes": "越界图片。",
},
)
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp) / "deck"
root.mkdir()
Image.new("RGB", (10, 10), "white").save(Path(tmp) / "outside.png")
path = root / "deck.json"
path.write_text(json.dumps(deck, ensure_ascii=False), encoding="utf-8")
loaded_path, loaded = build_pptx.load_deck(path)
with self.assertRaisesRegex(ValueError, "必须位于"):
build_pptx.build_presentation(
loaded_path, loaded, root / "deck.pptx"
)
def test_rejects_too_many_bullets(self):
deck = copy.deepcopy(self.deck)
deck["slides"][1]["bullets"] = [f"项目 {index}" for index in range(7)]
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp) / "deck.json"
path.write_text(
json.dumps(deck, ensure_ascii=False),
encoding="utf-8",
)
with self.assertRaisesRegex(ValueError, "最多 6 项"):
build_pptx.load_deck(path)
def test_rejects_unknown_slide_type(self):
deck = copy.deepcopy(self.deck)
deck["slides"][0]["type"] = "unknown"
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp) / "deck.json"
path.write_text(
json.dumps(deck, ensure_ascii=False),
encoding="utf-8",
)
with self.assertRaisesRegex(ValueError, "未知"):
build_pptx.load_deck(path)
if __name__ == "__main__":
unittest.main()