newspaper_wedding/newspaper/tests/test_generator.py
Andrew Ridgway bec1eaac87
Some checks failed
Test / test (push) Has been cancelled
first pass at the newspaper builder
2026-09-14 11:57:22 +10:00

105 lines
3.6 KiB
Python

"""generator unit tests (test-after, Standard strategy).
Covers: config/content ingestion, markdown→article fidelity (NFR7), layout +
pagination (page breaks/running headers), self-contained zero-network emit
(NFR4), empty-state (US10), no-fabricated-photos (FR7.4), graceful no-AI-draft
(BR4.1), funnies embedding (US3).
"""
from __future__ import annotations
import json
import tempfile
from pathlib import Path
from newspaper.generator import (
load_config,
load_content,
run_generate,
build_layout,
emit_empty_state,
emit_newspaper,
)
from newspaper.generator.model import Article, Issue, MastheadConfig
def _sample_project(tmp: Path, content: dict | None = None, article: str | None = None) -> None:
cfg = tmp / "config.json"
cfg.write_text(json.dumps(content or {
"title": "The Wedding Times", "couple_names": "Sam & Alex",
"date_line": "14 Sep", "issue_number": "1", "volume": "1",
}))
(tmp / "content").mkdir(exist_ok=True)
if article is not None:
(tmp / "content" / "lead.md").write_text(article)
def test_config_loads_masthead():
with tempfile.TemporaryDirectory() as d:
tmp = Path(d)
_sample_project(tmp)
m = load_config(tmp / "config.json")
assert m.title == "The Wedding Times"
assert m.couple_names == "Sam & Alex"
def test_parse_article_fidelity():
md = "---\nheadline: The Big Day\nbyline: Sam & Alex\n---\nVows said & dancing began."
with tempfile.TemporaryDirectory() as d:
tmp = Path(d)
_sample_project(tmp, article=md)
arts = load_content(tmp / "content")
assert len(arts) == 1
a = arts[0]
assert "&" in a.body and "&" in a.headline # no mangled escaping
def test_empty_content_emits_guided_empty_state():
with tempfile.TemporaryDirectory() as d:
tmp = Path(d)
_sample_project(tmp)
out = run_generate(tmp)
assert out.name == "newspaper.html"
text = out.read_text()
assert "Welcome" in text # friendly empty-state, not blank
def test_layout_flows_pages_with_running_headers():
arts = [Article(f"a{i}", "article", f"Head {i}", "Body" * 20, "", "News") for i in range(9)]
pages = build_layout(arts)
assert len(pages) >= 1
assert all(p.running_header for p in pages)
def test_emitted_newspaper_is_self_contained():
with tempfile.TemporaryDirectory() as d:
tmp = Path(d)
mh = MastheadConfig("T", "C", "d", "1", "1")
issue = Issue("i", mh, [Article("a1", "article", "H", "B", "", "News")], build_layout([Article("a1", "article", "H", "B", "", "News")]))
out = tmp / "newspaper.html"
emit_newspaper(issue, out)
text = out.read_text()
assert "http://" not in text and "https://" not in text # NFR4 no remote
def test_no_photos_no_fabricated_images():
with tempfile.TemporaryDirectory() as d:
tmp = Path(d)
_sample_project(tmp, article="Just text, no photos.")
out = run_generate(tmp)
assert "<img" not in out.read_text() # FR7.4 no fabricated placeholders
def test_draft_unavailable_builds_without_ai():
with tempfile.TemporaryDirectory() as d:
tmp = Path(d)
_sample_project(tmp, article="Story one.")
out = run_generate(tmp, draft=True)
assert out.exists() and "Story one." in out.read_text() # fallback to human copy
def test_funnies_embedded():
from newspaper.funnies import FunniesBuilder, FunniesBrief
result = FunniesBuilder().build(FunniesBrief(theme_words=["sam", "alex"], context="wedding"))
assert result.status in ("ready", "fallback")