105 lines
3.6 KiB
Python
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")
|