"""REQ-239..243 (v1.20) + REQ-245,251,252 (v1.21): S&P theme + slide render pipeline + deck-refinement tests. v1.20 validates: - The Marp deck frontmatter references nova-sp-theme.css - The CSS file contains the S&P colors (#D6002A, #1B1B1B) - The mermaid theme JSON contains the S&P colors - Every .mmd has a corresponding .png - The render_slides.sh script exists and is executable - The CI workflow file exists v1.21 adds (REQ-245,251,252): - Deck renamed to nova-autonomous-cloud-delivery* - No maturity badges in the Marp deck - No version in the Marp footer/title slide - 18 main + 1 appendix slides - No D-###/REQ-###/internal .py paths in audience-facing slides - Title is "Nova — The Autonomous Cloud Delivery Platform" """ import re from pathlib import Path import pytest ROOT = Path(__file__).resolve().parent.parent PRESENTATIONS = ROOT / "docs" / "presentations" ASSETS = PRESENTATIONS / "assets" THEME_CSS = ASSETS / "nova-sp-theme.css" THEME_JSON = ASSETS / "mmd" / "sp-theme.json" MARP_DECK = PRESENTATIONS / "nova-autonomous-cloud-delivery-marp.md" SOURCE_MD = PRESENTATIONS / "nova-autonomous-cloud-delivery.md" RENDER_SCRIPT = ROOT / "scripts" / "render_slides.sh" SLIDES_WORKFLOW = ROOT / ".github" / "workflows" / "slides.yml" def test_sp_theme_css_exists(): """REQ-239: nova-sp-theme.css exists.""" assert THEME_CSS.is_file(), f"theme CSS not found: {THEME_CSS}" def test_sp_theme_css_has_snp_colors(): """REQ-239: CSS contains S&P Red and Black.""" css = THEME_CSS.read_text() assert "#D6002A" in css, "S&P Red (#D6002A) missing from theme CSS" assert "#1B1B1B" in css, "S&P Black (#1B1B1B) missing from theme CSS" def test_sp_theme_json_has_snp_colors(): """The mermaid theme JSON also has S&P colors.""" json_text = THEME_JSON.read_text() assert "#D6002A" in json_text, "S&P Red missing from mermaid theme" assert "#1B1B1B" in json_text, "S&P Black missing from mermaid theme" def test_marp_deck_uses_sp_theme(): """REQ-239: Marp deck frontmatter references nova-sp-theme.css.""" text = MARP_DECK.read_text() # The frontmatter is between the first two --- fm_match = re.match(r'^---\n(.*?)\n---', text, re.DOTALL) assert fm_match, "Marp frontmatter not found" frontmatter = fm_match.group(1) assert "nova-sp" in frontmatter, \ "Marp deck does not reference nova-sp theme" def test_marp_deck_not_using_default_theme(): """The Marp deck must not use 'theme: default'.""" text = MARP_DECK.read_text() fm_match = re.match(r'^---\n(.*?)\n---', text, re.DOTALL) assert fm_match, "Marp frontmatter not found" frontmatter = fm_match.group(1) assert "theme: default" not in frontmatter, \ "Marp deck still uses 'theme: default' — should use nova-sp-theme.css" def test_render_slides_script_exists(): """REQ-240: render_slides.sh exists and is executable.""" assert RENDER_SCRIPT.is_file(), "render_slides.sh not found" import os assert os.access(RENDER_SCRIPT, os.X_OK), "render_slides.sh not executable" def test_render_slides_script_renders_mermaid(): """REQ-240: render_slides.sh renders mermaid diagrams.""" text = RENDER_SCRIPT.read_text() assert "mermaid-cli" in text or "mmdc" in text, \ "render_slides.sh does not invoke mermaid-cli" assert "sp-theme.json" in text, \ "render_slides.sh does not reference sp-theme.json" def test_render_slides_script_renders_marp(): """REQ-240: render_slides.sh renders Marp HTML + PPTX.""" text = RENDER_SCRIPT.read_text() assert "marp-cli" in text, "render_slides.sh does not invoke marp-cli" assert ".html" in text, "render_slides.sh does not produce HTML" assert ".pptx" in text, "render_slides.sh does not produce PPTX" def test_render_slides_default_deck_renamed(): """REQ-245: render_slides.sh default deck is nova-autonomous-cloud-delivery.""" text = RENDER_SCRIPT.read_text() assert "nova-autonomous-cloud-delivery" in text, \ "render_slides.sh does not default to nova-autonomous-cloud-delivery" def test_slides_ci_workflow_exists(): """REQ-241: CI workflow for slides exists.""" assert SLIDES_WORKFLOW.is_file(), "slides.yml workflow not found" def test_slides_ci_workflow_triggers_on_presentations(): """REQ-241: CI workflow triggers on docs/presentations/ changes.""" text = SLIDES_WORKFLOW.read_text() assert "docs/presentations" in text, \ "slides.yml does not trigger on docs/presentations/" assert "render_slides.sh" in text, \ "slides.yml does not invoke render_slides.sh" def test_every_mmd_has_png(): """REQ-240: every .mmd file has a corresponding .png.""" mmd_dir = ASSETS / "mmd" png_dir = ASSETS / "png" if not mmd_dir.is_dir(): pytest.skip("no .mmd directory") mmd_files = sorted(mmd_dir.glob("*.mmd")) assert len(mmd_files) > 0, "no .mmd files found" missing = [] for mmd in mmd_files: png = png_dir / f"{mmd.stem}.png" if not png.is_file(): missing.append(mmd.name) assert not missing, f"PNGs missing for: {missing}" def test_readme_no_retired_decks(): """REQ-243: presentations README does not list retired decks.""" readme = (PRESENTATIONS / "README.md").read_text() assert "how-the-platform-works" not in readme, \ "README still references retired 'how-the-platform-works' deck" assert "the-developer-experience" not in readme, \ "README still references retired 'the-developer-experience' deck" def test_readme_no_old_deck_name(): """REQ-245: README references the new deck name, not the old one.""" readme = (PRESENTATIONS / "README.md").read_text() assert "nova-autonomous-cloud-delivery" in readme, \ "README does not reference nova-autonomous-cloud-delivery" def test_old_deck_files_removed(): """REQ-245: the old nova-no-humans-platform* files are gone.""" old_files = sorted(PRESENTATIONS.glob("nova-no-humans-platform*")) assert not old_files, f"old deck files still present: {old_files}" def test_marp_deck_no_badges(): """REQ-252: no maturity badges in the Marp deck.""" text = MARP_DECK.read_text() assert "badge" not in text, "Marp deck still contains badge spans" def test_marp_deck_no_version_in_footer(): """REQ-251: no version (v1.x) in the Marp frontmatter footer/header.""" text = MARP_DECK.read_text() fm_match = re.match(r'^---\n(.*?)\n---', text, re.DOTALL) assert fm_match, "Marp frontmatter not found" frontmatter = fm_match.group(1) # No v1.x version string in the footer or header lines assert not re.search(r"v1\.\d+", frontmatter), \ f"Marp frontmatter still contains a version: {frontmatter}" # No "Act" pagination artifact assert "Act %" not in frontmatter, \ "Marp frontmatter still contains 'Act %{page}' artifact" def test_marp_deck_title_slide_no_version_subtitle(): """REQ-251: the title slide does not carry a version subtitle.""" text = MARP_DECK.read_text() # The title slide is the first slide after the frontmatter # Find the title block (between the frontmatter and the first --- separator) after_fm = text.split("---\n", 2)[2] if text.startswith("---") else text first_slide = after_fm.split("\n---\n")[0] # The old subtitle was "v1.18 — Citizen Developer & Production-Grade Guidance" assert "v1.18" not in first_slide, \ "Title slide still contains 'v1.18' subtitle" assert "Citizen Developer & Production-Grade Guidance" not in first_slide, \ "Title slide still contains the old version subtitle" def test_marp_deck_title_is_autonomous_cloud_delivery(): """REQ-245: the deck title is 'Nova — The Autonomous Cloud Delivery Platform'.""" text = MARP_DECK.read_text() assert "Autonomous Cloud Delivery Platform" in text, \ "Deck title is not 'Autonomous Cloud Delivery Platform'" # The old title should not appear in the audience-facing deck # (speaker notes are not in the marp deck, so this is safe) assert "No-Humans Infrastructure Platform" not in text, \ "Deck still carries the old 'No-Humans Infrastructure Platform' title" def test_marp_deck_slide_count(): """REQ-245: 18 main slides + 1 appendix = 19 slides total.""" text = MARP_DECK.read_text() # Count slide separators: each slide ends with --- (except the last) # The frontmatter is one --- ... --- block, then each slide is separated by --- # Count "## Slide" and "## Appendix" headings slide_headings = re.findall(r"^## (?:Slide|Appendix) ", text, re.MULTILINE) main_slides = re.findall(r"^## Slide ", text, re.MULTILINE) appendix_slides = re.findall(r"^## Appendix ", text, re.MULTILINE) assert len(main_slides) == 18, \ f"expected 18 main slides, found {len(main_slides)}: {slide_headings}" assert len(appendix_slides) == 1, \ f"expected 1 appendix slide, found {len(appendix_slides)}" def test_marp_deck_no_internal_citations(): """REQ-252: no D-### decision IDs, REQ-### requirement IDs, or internal .py file paths in the audience-facing Marp deck.""" text = MARP_DECK.read_text() # Decision IDs like D-121, D-083 assert not re.search(r"\bD-\d{3}\b", text), \ "Marp deck contains D-### decision IDs" # Requirement IDs like REQ-245 assert not re.search(r"\bREQ-\d{3}\b", text), \ "Marp deck contains REQ-### requirement IDs" # Internal python file paths like outbox_writer.py, confidence_signal.py # (allow .py only inside code blocks for the ROI formula? No — the deck # should not cite internal file paths at all) assert not re.search(r"\b(outbox_writer|confidence_signal|hitl_gates|" r"attestation_matrix|checkov_adapter|infracost_adapter|" r"contract_resolver|run_platform)\.py\b", text), \ "Marp deck contains internal .py file paths" def test_source_md_no_internal_citations_in_slides(): """REQ-252: the source-of-truth markdown keeps internal citations only in speaker notes, not in the audience-facing slide body. Speaker notes are blockquoted (> ) — we check non-blockquote lines for D-###/REQ-###.""" text = SOURCE_MD.read_text() # Split into lines; exclude blockquote lines (speaker notes) and the # header frontmatter (> ... at the top) in_note = False body_lines = [] for line in text.splitlines(): if line.lstrip().startswith(">"): in_note = True continue if in_note and line.strip() == "": in_note = False continue if not in_note: body_lines.append(line) body = "\n".join(body_lines) # Decision IDs and REQ IDs should not appear in the slide body assert not re.search(r"\bD-\d{3}\b", body), \ "Source markdown slide body contains D-### decision IDs" assert not re.search(r"\bREQ-\d{3}\b", body), \ "Source markdown slide body contains REQ-### requirement IDs" def test_source_md_no_badges(): """REQ-252: no maturity badges in the source-of-truth markdown.""" text = SOURCE_MD.read_text() assert "badge" not in text.lower(), \ "Source markdown still contains badge spans"