Testing and Validation
The test suite for pptcraft is organized to validate the full stack, from low-level OOXML manipulation to high-level server integration. The architecture relies heavily on mocking the LLM backend to ensure deterministic, fast, and offline-capable unit tests for the engine and rendering logic. Integration tests verify that the FastAPI server correctly handles authentication, origin validation, and returns valid binary artifacts.
Engine and Render Unit Tests
Section titled “Engine and Render Unit Tests”The engine tests (tests/test_engine.py) cover the full Phase B path by mocking the Qwen LLM responses, eliminating the need for a live localhost:8010 instance 1.
- Render Layer: Tests verify that the
render_one_slide_pptxfunction produces valid PPTX files that can be reopened bypython-pptx. This includes verifying title slides round-trip correctly and that chart slides contain the necessary embedded workbooks and chart XML parts. - Engine Phase A (Storyline): Tests confirm that
engine.generate_storylinecorrectly parses JSON responses from the mocked LLM and returns a structured list of slide layouts. - Engine Phase B (Build One Slide): Tests validate
engine.build_one_slideby checking that it produces valid slides, records iteration latency, and handles validation failures. Specifically, the engine is tested to ensure it re-prompts the LLM if the initial output contains placeholder residue (e.g., “Lorem ipsum”) 2. - Prompts: Tests ensure that system prompts are byte-stable across calls to support prefix-cache hits, and that JSON extraction handles both fenced and raw JSON formats.
OOXML Core Tests
Section titled “OOXML Core Tests”The OOXML core tests (tests/test_ooxml_core.py) focus on the low-level manipulation of the PPTX ZIP structure, ensuring that edits are surgical and that the resulting files remain valid and editable.
- Unpack/Repack: Tests verify that loading a PPTX and converting it back to bytes results in a file that
python-pptxcan reopen cleanly 3. - Theme Edits: Tests confirm that palette and font changes are applied to every theme part in the deck, not just the first one, ensuring multi-master safety.
- Surgical Slide Edits: Tests validate that
apply_paragraphsandset_run_textcorrectly modify XML nodes and that the changes round-trip throughpython-pptx. - Native Charts: Tests ensure that chart insertion produces both
ppt/charts/*.xmlandppt/embeddings/*.xlsxfiles, with the chart XML referencing the embedded workbook viac:externalData. This guarantees “Edit Data” parity in PowerPoint. - Validation: Tests verify the validator correctly identifies blockers such as shapes outside the canvas bounds (
out_of_bounds) and placeholder residue (placeholder_residue).
Server and Integration Tests
Section titled “Server and Integration Tests”Integration tests (tests/test_server_routes.py and tests/test_smoke.py) ensure the server behaves correctly under various conditions, including authentication, origin validation, and CLI functionality.
- Authentication and Origin: Tests verify that the
/api/sessionendpoint requires a Bearer token and rejects requests from disallowed origins (e.g.,evil.example.com), while accepting valid Office origins 4. - Manifest Endpoint: Tests confirm that
/manifest.xmlreturns valid XML with the correctOfficeApproot, permissions, and minimum version requirements. - Health and Edit Routes: Tests check that the health route is accessible with valid auth and that the edit slide stub returns a base64-encoded PPTX that starts with the ZIP magic bytes
PK. - CLI and Package Smoke Tests: Tests ensure the CLI exposes all expected subcommands (
serve,manifest,cert, etc.) and that the manifest rendering produces valid XML. Additionally, schema round-trips and stub slide generation are verified to ensure data integrity 5.
"""Engine + render tests with mocked Qwen.
Covers the full Phase B path against canned LLM responses so we don't
depend on a live `localhost:8010` to land green.
"""
from __future__ import annotations
import io
import json
from pptx import Presentation
from ppt_craft import engine, prompts
from ppt_craft.qwen import QwenResponse
from ppt_craft.render import render_one_slide_pptx
from ppt_craft.schema import Chart, ChartSeries, Paragraph, Run, Slide, Visual
# ── Render layer (no LLM) ────────────────────────────────────────────
def test_render_title_slide_round_trips(tmp_path):
slide = Slide(
id="slide-1",
layout="title",
title="Local Claude",
body=[Paragraph(runs=[Run(text="Q2 capacity review", size_pt=24)])],
)
pptx_bytes = render_one_slide_pptx(theme="corporate", slide=slide)
out = tmp_path / "title.pptx"
out.write_bytes(pptx_bytes)
prs = Presentation(out)
assert len(prs.slides) == 1
assert prs.slides[0].shapes.title.text == "Local Claude"
def test_render_chart_slide_has_native_chart(tmp_path):
"""Renderer should produce a slide whose chart part has the embedded workbook."""
import zipfile
scratch = tmp_path / "scratch"
scratch.mkdir()
slide, pptx_bytes, iterations = engine.build_one_slide(
intent="GB10 Q2 capacity review",
theme="corporate",
audience=None,
storyline_entry={"layout": "title_content", "title": "Where we are", "summary": "Headline metrics"},
prior_titles=[],
qwen_url="http://mock",
scratch=scratch,
log=lambda s: None,
)
assert slide.title == "Where we are"
assert len(slide.body) == 2
assert slide.body[0].runs[0].bold is True
# Render produced valid PPTX bytes.
prs = Presentation(io.BytesIO(pptx_bytes))
assert len(prs.slides) == 1
# Iterations recorded latency.
assert iterations[0].iteration == 1
assert iterations[0].qwen_latency_ms >= 0
def test_build_one_slide_repairs_on_validation_failure(monkeypatch, tmp_path):
"""First attempt has bare 'Lorem ipsum' (placeholder_residue → blocker?).
Our placeholder_residue check is severity=major, so the engine should
re-prompt with the repair user message and accept the fixed payload.
"""
bad_payload = {
"layout": "title_content",
"title": "Bad",
"body": [{"runs": [{"text": "Lorem ipsum dolor"}]}],
}
fixed_payload = {
"layout": "title_content",
"title": "Good",
"body": [{"runs": [{"text": "Throughput up 18% week-on-week"}]}],
}
"""OOXML editing core: unpack/repack, slide edits, themes, charts, validators.
Combines the four planned tests (test_ooxml_slide_edit, test_charts_editable,
test_multimaster, test_insert_slides_b64) into one file for the POC. Splits
out as the codebase grows.
"""
from __future__ import annotations
import io
import zipfile
from lxml import etree
from pptx import Presentation
from pptx.util import Emu
from ppt_craft.ooxml import slide_edit, theme_edit, validate
from ppt_craft.ooxml.charts import add_chart_to_slide
from ppt_craft.ooxml.unpack import enumerate_parts, load, slide_size_emu, write_xml
from ppt_craft.schema import Chart, ChartSeries, Paragraph, Run
from ppt_craft.stub_slide import build_stub_one_slide_pptx
def _fresh_one_slide_pptx() -> bytes:
return build_stub_one_slide_pptx(title="Title", subtitle="Subtitle")
# ── Unpack / repack ────────────────────────────────────────────────
def test_unpack_repack_byte_equal_when_untouched():
"""load → to_bytes with no edits must reopen + parse cleanly."""
raw = _fresh_one_slide_pptx()
pkg = load(raw)
out = pkg.to_bytes()
# Reopens cleanly via python-pptx
prs = Presentation(io.BytesIO(out))
assert len(prs.slides) == 1
"""Server route smoke tests - auth, origin allowlist, manifest endpoint."""
from __future__ import annotations
import pytest
from fastapi.testclient import TestClient
from ppt_craft.server import AppState, build_app
@pytest.fixture
def client() -> TestClient:
state = AppState(auth_token="test-token", host="gb10.local", port=3030, qwen_url="http://localhost:8010")
app = build_app(state)
return TestClient(app)
def test_root_returns_html(client):
r = client.get("/")
assert r.status_code == 200
assert "ppt-craft" in r.text
def test_taskpane_html_served(client):
r = client.get("/taskpane.html")
# Either the real SPA shell or the pre-build placeholder - both 2xx-ish
assert r.status_code in (200, 503)
def test_manifest_xml_route(client):
r = client.get("/manifest.xml")
assert r.status_code == 200
assert r.headers["content-type"].startswith("application/xml")
assert "OfficeApp" in r.text
assert 'MinVersion="1.8"' in r.text
def test_session_requires_bearer(client):
r = client.post(
"/api/session",
"""Smoke tests: package imports, CLI shape, manifest emission."""
from __future__ import annotations
import pytest
from click.testing import CliRunner
from ppt_craft.cli import cli
def test_cli_help_runs():
runner = CliRunner()
result = runner.invoke(cli, ["--help"])
assert result.exit_code == 0, result.output
assert "Local Claude-style PowerPoint add-in" in result.output
def test_cli_subcommands_present():
runner = CliRunner()
result = runner.invoke(cli, ["--help"])
assert result.exit_code == 0, result.output
for sub in ("serve", "manifest", "cert", "themes", "verify-deck", "explain", "draft"):
assert sub in result.output, f"missing subcommand {sub} in:\n{result.output}"
def test_manifest_renders_valid_xml(tmp_path):
"""Manifest must round-trip through stdlib xml parser."""
import xml.etree.ElementTree as ET
from ppt_craft import manifest as manifest_mod
xml = manifest_mod.render_manifest(
host="gb10.local",
port=3030,
install_id="00000000-0000-0000-0000-000000000001",
)
root = ET.fromstring(xml)
# OfficeApp root in the appforoffice/1.1 namespace
assert root.tag.endswith("OfficeApp"), root.tag
# Id pinned to the supplied UUID