Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 1 addition & 12 deletions src/handlers/post_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,18 +369,7 @@ async def list_published(self, limit: int = 10) -> List[Dict[str, Any]]:
if limit < 1 or limit > 25:
raise ValueError("limit must be between 1 and 25")

# Get all posts and filter for published only
all_posts = self.client.get_drafts(limit=min(limit, 25))
published = []

for post in all_posts:
# Check if it's published (has a post_date)
if post.get("post_date"):
published.append(post)
if len(published) >= limit:
break

return published
return self.client.get_published_posts(limit=limit)

async def get_post(self, post_id: str) -> Dict[str, Any]:
"""Get a specific post by ID
Expand Down
25 changes: 25 additions & 0 deletions src/utils/api_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,31 @@ def get_draft(self, post_id: str) -> Dict[str, Any]:
)
raise SubstackAPIError(f"Failed to get post {post_id}: {str(e)}")

def get_published_posts(self, limit: int = 10) -> List[Dict[str, Any]]:
"""Get published posts with error handling"""
try:
result = self.client.get_published_posts(limit=limit)
# get_published_posts returns {'posts': [...]} not a bare list
if isinstance(result, dict) and "posts" in result:
items = result["posts"]
elif isinstance(result, list):
items = result
else:
logger.warning(
f"Unexpected get_published_posts response type: {type(result)}"
)
return []

posts = []
for item in items:
checked = self._handle_response(item, "get_published_posts[item]")
if isinstance(checked, dict):
posts.append(checked)
return posts
except Exception as e:
logger.error(f"get_published_posts error: {type(e).__name__}: {str(e)}")
return []

def get_drafts(self, limit: int = 10) -> List[Dict[str, Any]]:
"""Get drafts with error handling"""
try:
Expand Down
25 changes: 25 additions & 0 deletions tests/unit/test_post_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,31 @@ async def test_invalid_content_type(self):
title="Test", content="Content", content_type="invalid"
)

@pytest.mark.asyncio
async def test_list_published_uses_published_endpoint(self):
"""list_published must call get_published_posts, not get_drafts"""
mock_posts = [
{"id": "post-1", "title": "Published 1", "post_date": "2026-04-08T12:00:00Z"},
{"id": "post-2", "title": "Published 2", "post_date": "2026-03-23T12:00:00Z"},
]
self.mock_client.get_published_posts = Mock(return_value=mock_posts)

result = await self.handler.list_published(limit=10)

assert len(result) == 2
assert result[0]["title"] == "Published 1"
self.mock_client.get_published_posts.assert_called_once_with(limit=10)
self.mock_client.get_drafts.assert_not_called()

@pytest.mark.asyncio
async def test_list_published_respects_limit(self):
"""list_published passes limit to get_published_posts"""
self.mock_client.get_published_posts = Mock(return_value=[])

await self.handler.list_published(limit=5)

self.mock_client.get_published_posts.assert_called_once_with(limit=5)

@pytest.mark.asyncio
async def test_error_handling(self):
"""Test error handling when API calls fail"""
Expand Down