114 lines
4.2 KiB
Python
114 lines
4.2 KiB
Python
"""core/pptx_guard.py 的 focused tests——「整页贴图」伪 pptx 判定。
|
|
|
|
用 python-pptx 真实构造两类 deck:光栅贴图(每页一张整版图、零文本)必须命中;
|
|
正常/边界 deck(有文本、大背景图配文字、单页、含表格)必须放行。
|
|
"""
|
|
import io
|
|
import time
|
|
import unittest
|
|
from pathlib import Path
|
|
from tempfile import TemporaryDirectory
|
|
|
|
from pptx import Presentation
|
|
from pptx.util import Emu, Inches
|
|
|
|
from core import pptx_guard
|
|
|
|
|
|
def _png_bytes() -> io.BytesIO:
|
|
from PIL import Image
|
|
|
|
buf = io.BytesIO()
|
|
Image.new("RGB", (32, 18), (200, 30, 30)).save(buf, format="PNG")
|
|
buf.seek(0)
|
|
return buf
|
|
|
|
|
|
def _make_deck(path: Path, pages: list[dict]) -> Path:
|
|
"""pages: [{'pic': 覆盖率 0~1 或 None, 'text': str}]"""
|
|
prs = Presentation()
|
|
blank = prs.slide_layouts[6]
|
|
sw, sh = prs.slide_width, prs.slide_height
|
|
for spec in pages:
|
|
slide = prs.slides.add_slide(blank)
|
|
ratio = spec.get("pic")
|
|
if ratio:
|
|
side = ratio ** 0.5 # 面积占比 → 边长占比
|
|
slide.shapes.add_picture(
|
|
_png_bytes(), 0, 0, Emu(int(sw * side)), Emu(int(sh * side)))
|
|
text = spec.get("text")
|
|
if text:
|
|
box = slide.shapes.add_textbox(
|
|
Inches(1), Inches(1), Inches(4), Inches(1))
|
|
box.text_frame.text = text
|
|
prs.save(str(path))
|
|
return path
|
|
|
|
|
|
class RasterDeckDetectionTests(unittest.TestCase):
|
|
def test_full_page_paste_deck_is_flagged(self):
|
|
with TemporaryDirectory() as tmp:
|
|
p = _make_deck(Path(tmp) / "fake.pptx",
|
|
[{"pic": 1.0}] * 3)
|
|
self.assertIsNotNone(pptx_guard.check_raster_deck(p))
|
|
|
|
def test_native_text_deck_passes(self):
|
|
with TemporaryDirectory() as tmp:
|
|
p = _make_deck(Path(tmp) / "native.pptx",
|
|
[{"text": "标题"}, {"text": "正文要点"}])
|
|
self.assertIsNone(pptx_guard.check_raster_deck(p))
|
|
|
|
def test_big_background_with_text_passes(self):
|
|
with TemporaryDirectory() as tmp:
|
|
p = _make_deck(Path(tmp) / "bg.pptx",
|
|
[{"pic": 1.0, "text": "封面标题"},
|
|
{"pic": 0.95, "text": "章节页"}])
|
|
self.assertIsNone(pptx_guard.check_raster_deck(p))
|
|
|
|
def test_single_slide_poster_passes(self):
|
|
with TemporaryDirectory() as tmp:
|
|
p = _make_deck(Path(tmp) / "poster.pptx", [{"pic": 1.0}])
|
|
self.assertIsNone(pptx_guard.check_raster_deck(p))
|
|
|
|
def test_small_pictures_no_text_passes(self):
|
|
# 图片没铺满整版(<85%)就不算贴图页,哪怕全 deck 无文本
|
|
with TemporaryDirectory() as tmp:
|
|
p = _make_deck(Path(tmp) / "album.pptx",
|
|
[{"pic": 0.5}, {"pic": 0.5}])
|
|
self.assertIsNone(pptx_guard.check_raster_deck(p))
|
|
|
|
def test_unparseable_file_passes(self):
|
|
with TemporaryDirectory() as tmp:
|
|
p = Path(tmp) / "broken.pptx"
|
|
p.write_bytes(b"PK\x03\x04 not a real pptx")
|
|
self.assertIsNone(pptx_guard.check_raster_deck(p))
|
|
|
|
|
|
class ScanTests(unittest.TestCase):
|
|
def test_scan_reports_new_bad_deck_and_skips_hidden_and_old(self):
|
|
with TemporaryDirectory() as tmp:
|
|
root = Path(tmp)
|
|
since = time.time()
|
|
_make_deck(root / "bad.pptx", [{"pic": 1.0}] * 2)
|
|
hidden = root / ".build"
|
|
hidden.mkdir()
|
|
_make_deck(hidden / "ignored.pptx", [{"pic": 1.0}] * 2)
|
|
msg = pptx_guard.scan_and_report(root, since)
|
|
self.assertIsNotNone(msg)
|
|
self.assertIn("bad.pptx", msg)
|
|
self.assertIn("[产物机检 ERROR]", msg)
|
|
self.assertNotIn("ignored.pptx", msg)
|
|
# 早于 since 的文件不复检
|
|
self.assertIsNone(
|
|
pptx_guard.scan_and_report(root, time.time() + 60))
|
|
|
|
def test_scan_clean_dir_returns_none(self):
|
|
with TemporaryDirectory() as tmp:
|
|
root = Path(tmp)
|
|
_make_deck(root / "ok.pptx", [{"text": "有字"}, {"text": "有字"}])
|
|
self.assertIsNone(pptx_guard.scan_and_report(root, time.time() - 60))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|