"""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 "
" in d["html"]