All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m11s
- Run tests for all 10 services via per-service pytest subprocesses - Group results by service with section headers - Clickable summary cards filter by outcome (passed/failed/errors/skipped) - Service filter nav using ~nav-link buttons in menu bar - Full menu integration: ~header-row + ~header-child + ~menu-row - Show logo image via cart-mini rendering - Mount full service directories in docker-compose for test access - Add 24 unit test files across 9 services Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
104 lines
3.6 KiB
Python
104 lines
3.6 KiB
Python
"""Unit tests for Ghost sync helper functions."""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
from blog.bp.blog.ghost.ghost_sync import _iso, _build_ap_post_data
|
|
|
|
|
|
class TestIso:
|
|
def test_none(self):
|
|
assert _iso(None) is None
|
|
|
|
def test_empty_string(self):
|
|
assert _iso("") is None
|
|
|
|
def test_z_suffix(self):
|
|
result = _iso("2024-01-15T10:30:00Z")
|
|
assert isinstance(result, datetime)
|
|
assert result.tzinfo is not None
|
|
assert result.year == 2024
|
|
assert result.month == 1
|
|
assert result.hour == 10
|
|
|
|
def test_offset_suffix(self):
|
|
result = _iso("2024-06-01T08:00:00+00:00")
|
|
assert isinstance(result, datetime)
|
|
assert result.hour == 8
|
|
|
|
|
|
class TestBuildApPostData:
|
|
def _post(self, **kwargs):
|
|
defaults = {
|
|
"title": "My Post",
|
|
"plaintext": "Some body text.",
|
|
"custom_excerpt": None,
|
|
"excerpt": None,
|
|
"feature_image": None,
|
|
"feature_image_alt": None,
|
|
"html": None,
|
|
}
|
|
defaults.update(kwargs)
|
|
return SimpleNamespace(**defaults)
|
|
|
|
def _tag(self, slug):
|
|
return SimpleNamespace(slug=slug)
|
|
|
|
def test_basic_post(self):
|
|
post = self._post()
|
|
result = _build_ap_post_data(post, "https://blog.example.com/post/", [])
|
|
assert result["name"] == "My Post"
|
|
assert "My Post" in result["content"]
|
|
assert "Some body text." in result["content"]
|
|
assert result["url"] == "https://blog.example.com/post/"
|
|
|
|
def test_no_title(self):
|
|
post = self._post(title=None)
|
|
result = _build_ap_post_data(post, "https://example.com/", [])
|
|
assert result["name"] == ""
|
|
|
|
def test_feature_image(self):
|
|
post = self._post(feature_image="https://img.com/photo.jpg",
|
|
feature_image_alt="A photo")
|
|
result = _build_ap_post_data(post, "https://example.com/", [])
|
|
assert "attachment" in result
|
|
assert result["attachment"][0]["url"] == "https://img.com/photo.jpg"
|
|
assert result["attachment"][0]["name"] == "A photo"
|
|
|
|
def test_inline_images_capped_at_4(self):
|
|
html = "".join(f'<img src="https://img.com/{i}.jpg">' for i in range(10))
|
|
post = self._post(html=html)
|
|
result = _build_ap_post_data(post, "https://example.com/", [])
|
|
assert len(result["attachment"]) == 4
|
|
|
|
def test_tags(self):
|
|
tags = [self._tag("my-tag"), self._tag("another")]
|
|
post = self._post()
|
|
result = _build_ap_post_data(post, "https://example.com/", tags)
|
|
assert "tag" in result
|
|
assert len(result["tag"]) == 2
|
|
assert result["tag"][0]["name"] == "#mytag" # dashes removed
|
|
assert result["tag"][0]["type"] == "Hashtag"
|
|
|
|
def test_hashtag_in_content(self):
|
|
tags = [self._tag("web-dev")]
|
|
post = self._post()
|
|
result = _build_ap_post_data(post, "https://example.com/", tags)
|
|
assert "#webdev" in result["content"]
|
|
|
|
def test_no_duplicate_images(self):
|
|
post = self._post(
|
|
feature_image="https://img.com/same.jpg",
|
|
html='<img src="https://img.com/same.jpg">',
|
|
)
|
|
result = _build_ap_post_data(post, "https://example.com/", [])
|
|
assert len(result["attachment"]) == 1
|
|
|
|
def test_multiline_body(self):
|
|
post = self._post(plaintext="Para one.\n\nPara two.\n\nPara three.")
|
|
result = _build_ap_post_data(post, "https://example.com/", [])
|
|
assert result["content"].count("<p>") >= 4 # title + 3 paras + read more
|