Files
volumen/tests/test_post_edge_cases.py
T

151 lines
5.2 KiB
Python
Raw Normal View History

2026-07-26 18:15:29 +02:00
"""Coverage tests for ``Post`` properties — non-happy-path branches."""
from __future__ import annotations
from datetime import date, timedelta
from volumen.post import Post
def _post(metadata: dict[str, object] | None = None, body: str = "") -> Post:
return Post(metadata=metadata, body=body)
class TestTagsProperty:
def test_tags_default_is_empty(self) -> None:
assert _post().tags == []
def test_tags_list_of_strings(self) -> None:
assert _post({"tags": ["python", "blog"]}).tags == ["python", "blog"]
def test_tags_non_list_coerces_to_empty(self) -> None:
# When ``tags`` is set to something that isn't a list, the property
# returns an empty list rather than raising.
assert _post({"tags": "python"}).tags == []
assert _post({"tags": 42}).tags == []
assert _post({"tags": None}).tags == []
class TestTranslationsProperty:
def test_default_is_empty(self) -> None:
assert _post().translations == {}
def test_dict_coerces_values_to_strings(self) -> None:
out = _post({"translations": {"cs": "cs-slug", "de": "de-slug"}}).translations
assert out == {"cs": "cs-slug", "de": "de-slug"}
def test_non_dict_returns_empty(self) -> None:
assert _post({"translations": ["cs", "de"]}).translations == {}
assert _post({"translations": "cs"}).translations == {}
class TestFediverseCreator:
def test_none(self) -> None:
assert _post().fediverse_creator is None
def test_empty_string_is_none(self) -> None:
assert _post({"fediverse_creator": ""}).fediverse_creator is None
assert _post({"fediverse_creator": " "}).fediverse_creator is None
def test_valid_fediverse_creator_regex(self) -> None:
p = _post({"fediverse_creator": "@alice@example.com"})
assert p.fediverse_creator == "@alice@example.com"
assert p.valid_fediverse_creator is True
def test_invalid_fediverse_creator(self) -> None:
# No `@host` part → regex doesn't match.
p = _post({"fediverse_creator": "alice"})
assert p.fediverse_creator == "alice"
assert p.valid_fediverse_creator is False
class TestPublishAt:
def test_none(self) -> None:
assert _post().publish_at is None
def test_date_object(self) -> None:
assert _post({"publish_at": date(2026, 6, 1)}).publish_at == date(2026, 6, 1)
def test_iso_string(self) -> None:
assert _post({"publish_at": "2026-06-01"}).publish_at == date(2026, 6, 1)
def test_invalid_string_returns_none(self) -> None:
assert _post({"publish_at": "not-a-date"}).publish_at is None
assert _post({"publish_at": 12345}).publish_at is None
class TestScheduled:
def test_past_date_is_not_scheduled(self) -> None:
past = (date.today() - timedelta(days=1)).isoformat()
assert _post({"publish_at": past}).scheduled is False
def test_future_date_is_scheduled(self) -> None:
future = (date.today() + timedelta(days=1)).isoformat()
assert _post({"publish_at": future}).scheduled is True
def test_no_publish_at(self) -> None:
assert _post().scheduled is False
class TestReadingTime:
def test_empty_body_minimum_one_minute(self) -> None:
assert _post(body="").reading_time == 1
def test_short_body(self) -> None:
# 5 words → ceil(5/200) = 1.
assert _post(body="one two three four five").reading_time == 1
def test_long_body(self) -> None:
body = " ".join(f"word{i}" for i in range(500))
# 500 words → ceil(500/200) = 3.
assert _post(body=body).reading_time == 3
class TestExcerpt:
def test_explicit_excerpt(self) -> None:
assert _post({"excerpt": "Short summary"}).excerpt == "Short summary"
def test_derived_from_body(self) -> None:
# No explicit excerpt → derived from first non-heading paragraph.
p = _post(body="Hello **world**.")
assert "Hello" in p.excerpt
assert "<" not in p.excerpt # HTML stripped
assert "*" not in p.excerpt # Markdown stripped
def test_derived_empty_when_only_headings(self) -> None:
assert _post(body="# Heading\n\n## Subheading").excerpt == ""
class TestSummaryAndDetail:
def test_summary_includes_all_known_fields(self) -> None:
p = _post(
{
"title": "T",
"slug": "s",
"date": "2026-01-15",
"lang": "en",
"tags": ["x"],
"author": "A",
"fediverse_creator": "@a@example.com",
"cover": "media/c.webp",
},
body="Body.",
)
s = p.summary()
assert s["slug"] == "s"
assert s["title"] == "T"
assert s["date"] == "2026-01-15"
assert s["lang"] == "en"
assert s["tags"] == ["x"]
assert s["author"] == "A"
assert s["fediverse_creator"] == "@a@example.com"
assert s["cover"] == "media/c.webp"
assert s["url"] == "/api/volumen/posts/s"
def test_detail_includes_body_and_html(self) -> None:
p = _post({"title": "T", "slug": "s"}, body="Hello.")
d = p.detail()
assert d["body"] == "Hello."
assert "html" in d
assert "<p>" in d["html"]