From 973c126dfc8494dce84f21ada3da72be1a71d91e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Prendergast?= Date: Thu, 16 Apr 2026 21:55:02 -0400 Subject: [PATCH] fix: list_published queries drafts endpoint instead of published posts list_published() was calling get_drafts() and filtering by post_date, which returns 0 results because the drafts API only returns unpublished content. Fixed by adding get_published_posts() to APIWrapper (wrapping the library's get_published_posts method, handling its dict response format) and updating list_published() to call it directly. Added two unit tests to prevent regression. Co-Authored-By: Claude Sonnet 4.6 --- src/handlers/post_handler.py | 13 +------------ src/utils/api_wrapper.py | 25 +++++++++++++++++++++++++ tests/unit/test_post_handler.py | 25 +++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 12 deletions(-) diff --git a/src/handlers/post_handler.py b/src/handlers/post_handler.py index 0055c9d..baac18b 100644 --- a/src/handlers/post_handler.py +++ b/src/handlers/post_handler.py @@ -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 diff --git a/src/utils/api_wrapper.py b/src/utils/api_wrapper.py index 347d49b..2415f8f 100644 --- a/src/utils/api_wrapper.py +++ b/src/utils/api_wrapper.py @@ -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: diff --git a/tests/unit/test_post_handler.py b/tests/unit/test_post_handler.py index 2ded45e..09fe9e6 100644 --- a/tests/unit/test_post_handler.py +++ b/tests/unit/test_post_handler.py @@ -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"""