test(P5): ci + tests + readme for single-doc dual-pptx pipeline (REQ-273,274,275)
CI workflows: install python-pptx, pin CLI versions, stage both PPTX + inlined HTML. test_slides_pipeline.py: inverted theme assertion (now default+inline), deleted source-md tests, added 8 new tests (penetrate absence, image inlining, python-pptx, benefit class, single source, speaker-notes comments, default theme, css retained). New test_pptx_generator.py: slide count, title colors, slide titles, table rendering, image embedding, benefit callout. README rewritten for 3-step single-document + dual-PPTX + image-inlining pipeline. ---ci--- project: acdl phase: 5 milestone: v1.23 status: execute phase_role: execution ---/ci---
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
"""REQ-274: tests for `scripts/render_pptx.py` — the structured, editable
|
||||
python-pptx deck produced alongside the MARP-rendered PPTX.
|
||||
|
||||
The python-pptx deck is a native OOXML presentation: real text boxes,
|
||||
native tables, embedded pictures, and italic benefit callouts. These
|
||||
tests are offline (no AWS, no network) and assert the structural
|
||||
properties of the committed `*-python.pptx` artifact.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from typing import cast
|
||||
|
||||
import pytest
|
||||
from pptx import Presentation
|
||||
from pptx.enum.shapes import MSO_SHAPE_TYPE
|
||||
from pptx.presentation import Presentation as PresentationT
|
||||
from pptx.shapes.autoshape import Shape
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
PRESENTATIONS = ROOT / "docs" / "presentations"
|
||||
MARPT_DECK = PRESENTATIONS / "nova-autonomous-cloud-delivery-marp.md"
|
||||
PYTHON_PPTX = PRESENTATIONS / "nova-autonomous-cloud-delivery-python.pptx"
|
||||
|
||||
# Title slide + 20 main slides + 1 appendix slide.
|
||||
EXPECTED_SLIDE_COUNT = 22
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def prs() -> PresentationT:
|
||||
"""Load the committed python-pptx deck once for the whole module."""
|
||||
assert PYTHON_PPTX.is_file(), f"python-pptx PPTX not found: {PYTHON_PPTX}"
|
||||
return Presentation(str(PYTHON_PPTX))
|
||||
|
||||
|
||||
def _slide_titles(prs: PresentationT) -> list[str]:
|
||||
"""Return the first non-empty text-frame line per slide (the title)."""
|
||||
titles: list[str] = []
|
||||
for slide in prs.slides:
|
||||
for shape in slide.shapes:
|
||||
if not shape.has_text_frame:
|
||||
continue
|
||||
text = cast(Shape, shape).text_frame.text.strip()
|
||||
if not text:
|
||||
continue
|
||||
# The title is the first non-empty line of the first non-empty
|
||||
# text frame we find on the slide.
|
||||
first_line = text.split("\n")[0].strip()
|
||||
if first_line:
|
||||
titles.append(first_line)
|
||||
break
|
||||
else:
|
||||
titles.append("")
|
||||
return titles
|
||||
|
||||
|
||||
def test_slide_count(prs: PresentationT):
|
||||
"""REQ-269/274: the python-pptx deck has 22 slides
|
||||
(title + 20 main + 1 appendix)."""
|
||||
assert len(prs.slides) == EXPECTED_SLIDE_COUNT, \
|
||||
f"expected {EXPECTED_SLIDE_COUNT} slides, got {len(prs.slides)}"
|
||||
|
||||
|
||||
def test_title_slide_colors(prs: PresentationT):
|
||||
"""REQ-269: the title slide (slide 0) has a solid-filled background
|
||||
shape carrying the S&P Red (#D6002A) brand color (the title slide is
|
||||
a red-bar-on-black layout)."""
|
||||
title_slide = prs.slides[0]
|
||||
red_found = False
|
||||
black_found = False
|
||||
for shape in title_slide.shapes:
|
||||
fill = getattr(shape, "fill", None)
|
||||
if fill is None:
|
||||
continue
|
||||
try:
|
||||
if fill.type != 1: # MSO_FILL.SOLID
|
||||
continue
|
||||
except Exception:
|
||||
continue
|
||||
rgb = str(fill.fore_color.rgb).upper()
|
||||
if rgb == "D6002A":
|
||||
red_found = True
|
||||
if rgb == "1B1B1B":
|
||||
black_found = True
|
||||
assert red_found, \
|
||||
"title slide has no solid-fill shape with S&P Red (#D6002A)"
|
||||
|
||||
|
||||
def test_expected_slide_titles(prs: PresentationT):
|
||||
"""REQ-269/274: spot-check that key slide titles match the markdown
|
||||
deck (The Problem, Nova's Vision, Recap + Ask)."""
|
||||
titles = _slide_titles(prs)
|
||||
# Build a flat lowercase concatenation for substring checks.
|
||||
flat = " | ".join(titles).lower()
|
||||
expected = [
|
||||
"the problem",
|
||||
"nova's vision",
|
||||
"recap + ask",
|
||||
]
|
||||
missing = [t for t in expected if t not in flat]
|
||||
assert not missing, \
|
||||
f"missing expected slide titles in python-pptx deck: {missing}; " \
|
||||
f"found titles: {titles}"
|
||||
|
||||
|
||||
def test_table_rendering(prs: PresentationT):
|
||||
"""REQ-269: a slide with a table (the RACI slide) has a native PPTX
|
||||
table shape (GraphicFrame with has_table=True)."""
|
||||
table_slides = []
|
||||
for idx, slide in enumerate(prs.slides):
|
||||
for shape in slide.shapes:
|
||||
if shape.shape_type == MSO_SHAPE_TYPE.TABLE or getattr(
|
||||
shape, "has_table", False
|
||||
):
|
||||
table_slides.append(idx)
|
||||
break
|
||||
assert table_slides, \
|
||||
"no slide in the python-pptx deck has a native PPTX table shape"
|
||||
|
||||
|
||||
def test_image_embedding(prs: PresentationT):
|
||||
"""REQ-269: a slide with an image (the Platform Pipeline slide)
|
||||
has a native PPTX picture shape."""
|
||||
picture_slides = []
|
||||
for idx, slide in enumerate(prs.slides):
|
||||
for shape in slide.shapes:
|
||||
if shape.shape_type == MSO_SHAPE_TYPE.PICTURE:
|
||||
picture_slides.append(idx)
|
||||
break
|
||||
assert picture_slides, \
|
||||
"no slide in the python-pptx deck has a native PPTX picture shape"
|
||||
|
||||
|
||||
def test_benefit_callout_present(prs: PresentationT):
|
||||
"""REQ-269/274: at least one slide has an italic text run (the
|
||||
benefit callout, rendered as italic body text by render_pptx.py)."""
|
||||
italic_runs = 0
|
||||
for slide in prs.slides:
|
||||
for shape in slide.shapes:
|
||||
if not shape.has_text_frame:
|
||||
continue
|
||||
for paragraph in cast(Shape, shape).text_frame.paragraphs:
|
||||
for run in paragraph.runs:
|
||||
if run.font.italic and run.text.strip():
|
||||
italic_runs += 1
|
||||
assert italic_runs > 0, \
|
||||
"no italic text runs found in the python-pptx deck (benefit callout)"
|
||||
Reference in New Issue
Block a user