From d61e33113c8b86b6fa4edf7441666c56c2e7d2f7 Mon Sep 17 00:00:00 2001 From: Chris Coutinho Date: Sat, 13 Dec 2025 15:47:27 +0100 Subject: [PATCH] fix(news): revert get_item() to use get_items() + filter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reverts the "perf(news): use direct API endpoint for get_item()" change from commit 92c4bf3 which incorrectly assumed GET /items/{itemId} exists. The News API (v1-2, v1-3, v2) does not provide a direct endpoint to retrieve individual items. The only /items/{itemId} routes are POST operations for marking items read/unread/starred. Changes: - Restore original get_item() implementation that fetches all items and filters in Python - Update exception from HTTPStatusError to ValueError - Restore documentation explaining API limitation - Update unit tests to mock get_items() instead of _make_request() - Add test for ValueError when item not found Fixes vector processor 405 errors when indexing news items. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- nextcloud_mcp_server/client/news.py | 15 ++++++++++--- tests/client/news/test_news_api.py | 33 +++++++++++++++++++++++------ 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/nextcloud_mcp_server/client/news.py b/nextcloud_mcp_server/client/news.py index 5085fdc..679a882 100644 --- a/nextcloud_mcp_server/client/news.py +++ b/nextcloud_mcp_server/client/news.py @@ -228,6 +228,10 @@ class NewsClient(BaseNextcloudClient): async def get_item(self, item_id: int) -> dict[str, Any]: """Get a specific item by ID. + Note: The News API doesn't have a direct single-item endpoint, + so we fetch all items and filter. For efficiency, consider + caching or using get_items with specific feed if known. + Args: item_id: Item ID @@ -235,10 +239,15 @@ class NewsClient(BaseNextcloudClient): Item data Raises: - HTTPStatusError: 404 if item not found + ValueError: If item not found """ - response = await self._make_request("GET", f"{self.API_BASE}/items/{item_id}") - return response.json() + # Fetch all items and find the one we need + # This is inefficient but the API doesn't provide a direct endpoint + items = await self.get_items(batch_size=-1, get_read=True) + for item in items: + if item.get("id") == item_id: + return item + raise ValueError(f"Item {item_id} not found") async def get_updated_items( self, diff --git a/tests/client/news/test_news_api.py b/tests/client/news/test_news_api.py index 091693f..650ffd8 100644 --- a/tests/client/news/test_news_api.py +++ b/tests/client/news/test_news_api.py @@ -310,14 +310,16 @@ async def test_news_api_get_items_unread_only(mocker): async def test_news_api_get_item(mocker): - """Test that get_item fetches a single item by ID.""" - item = create_mock_news_item(item_id=123, title="Single Item") - mock_response = create_mock_response(status_code=200, json_data=item) + """Test that get_item fetches all items and filters for the requested ID.""" + # Create multiple items, only one should be returned + items = [ + create_mock_news_item(item_id=100, title="Other Item 1"), + create_mock_news_item(item_id=123, title="Single Item"), + create_mock_news_item(item_id=200, title="Other Item 2"), + ] mock_client = mocker.AsyncMock(spec=httpx.AsyncClient) - mock_make_request = mocker.patch.object( - NewsClient, "_make_request", return_value=mock_response - ) + mock_get_items = mocker.patch.object(NewsClient, "get_items", return_value=items) client = NewsClient(mock_client, "testuser") result = await client.get_item(item_id=123) @@ -325,7 +327,24 @@ async def test_news_api_get_item(mocker): assert result["id"] == 123 assert result["title"] == "Single Item" - mock_make_request.assert_called_once_with("GET", "/apps/news/api/v1-3/items/123") + # Verify it fetched all items with correct params + mock_get_items.assert_called_once_with(batch_size=-1, get_read=True) + + +async def test_news_api_get_item_not_found(mocker): + """Test that get_item raises ValueError when item not found.""" + items = [ + create_mock_news_item(item_id=100, title="Item 1"), + create_mock_news_item(item_id=200, title="Item 2"), + ] + + mock_client = mocker.AsyncMock(spec=httpx.AsyncClient) + mocker.patch.object(NewsClient, "get_items", return_value=items) + + client = NewsClient(mock_client, "testuser") + + with pytest.raises(ValueError, match="Item 999 not found"): + await client.get_item(item_id=999) async def test_news_api_get_updated_items(mocker):