Skip to main content
Glama
cbcoutinho

Nextcloud MCP Server

by cbcoutinho
test_news_api.py19.8 kB
"""Unit tests for NewsClient API methods.""" import logging import httpx import pytest from nextcloud_mcp_server.client.news import NewsClient, NewsItemType from tests.client.conftest import ( create_mock_error_response, create_mock_news_feed_response, create_mock_news_feeds_response, create_mock_news_folder_response, create_mock_news_folders_response, create_mock_news_item, create_mock_news_items_response, create_mock_news_status_response, create_mock_response, ) logger = logging.getLogger(__name__) # Mark all tests in this module as unit tests pytestmark = pytest.mark.unit # ============================================================================ # Folder Tests # ============================================================================ async def test_news_api_get_folders(mocker): """Test that get_folders correctly parses the API response.""" mock_response = create_mock_news_folders_response( folders=[ {"id": 1, "name": "Tech"}, {"id": 2, "name": "News"}, ] ) mock_client = mocker.AsyncMock(spec=httpx.AsyncClient) mock_make_request = mocker.patch.object( NewsClient, "_make_request", return_value=mock_response ) client = NewsClient(mock_client, "testuser") folders = await client.get_folders() assert len(folders) == 2 assert folders[0]["id"] == 1 assert folders[0]["name"] == "Tech" assert folders[1]["name"] == "News" mock_make_request.assert_called_once_with("GET", "/apps/news/api/v1-3/folders") async def test_news_api_create_folder(mocker): """Test that create_folder correctly creates a folder.""" mock_response = create_mock_news_folder_response(folder_id=3, name="New Folder") mock_client = mocker.AsyncMock(spec=httpx.AsyncClient) mock_make_request = mocker.patch.object( NewsClient, "_make_request", return_value=mock_response ) client = NewsClient(mock_client, "testuser") folder = await client.create_folder(name="New Folder") assert folder["id"] == 3 assert folder["name"] == "New Folder" mock_make_request.assert_called_once_with( "POST", "/apps/news/api/v1-3/folders", json={"name": "New Folder"} ) async def test_news_api_rename_folder(mocker): """Test that rename_folder makes the correct API call.""" mock_response = create_mock_response(status_code=200, json_data={}) mock_client = mocker.AsyncMock(spec=httpx.AsyncClient) mock_make_request = mocker.patch.object( NewsClient, "_make_request", return_value=mock_response ) client = NewsClient(mock_client, "testuser") await client.rename_folder(folder_id=1, name="Renamed") mock_make_request.assert_called_once_with( "PUT", "/apps/news/api/v1-3/folders/1", json={"name": "Renamed"} ) async def test_news_api_delete_folder(mocker): """Test that delete_folder makes the correct API call.""" mock_response = create_mock_response(status_code=200, json_data={}) mock_client = mocker.AsyncMock(spec=httpx.AsyncClient) mock_make_request = mocker.patch.object( NewsClient, "_make_request", return_value=mock_response ) client = NewsClient(mock_client, "testuser") await client.delete_folder(folder_id=1) mock_make_request.assert_called_once_with("DELETE", "/apps/news/api/v1-3/folders/1") # ============================================================================ # Feed Tests # ============================================================================ async def test_news_api_get_feeds(mocker): """Test that get_feeds correctly parses the API response.""" mock_response = create_mock_news_feeds_response( feeds=[ {"id": 1, "url": "https://example.com/feed1", "title": "Feed 1"}, {"id": 2, "url": "https://example.com/feed2", "title": "Feed 2"}, ], starred_count=5, newest_item_id=100, ) mock_client = mocker.AsyncMock(spec=httpx.AsyncClient) mock_make_request = mocker.patch.object( NewsClient, "_make_request", return_value=mock_response ) client = NewsClient(mock_client, "testuser") result = await client.get_feeds() assert len(result["feeds"]) == 2 assert result["starredCount"] == 5 assert result["newestItemId"] == 100 mock_make_request.assert_called_once_with("GET", "/apps/news/api/v1-3/feeds") async def test_news_api_create_feed(mocker): """Test that create_feed correctly creates a feed.""" mock_response = create_mock_news_feed_response( feed_id=10, url="https://example.com/new-feed", title="New Feed" ) mock_client = mocker.AsyncMock(spec=httpx.AsyncClient) mock_make_request = mocker.patch.object( NewsClient, "_make_request", return_value=mock_response ) client = NewsClient(mock_client, "testuser") feed = await client.create_feed(url="https://example.com/new-feed") assert feed["id"] == 10 assert feed["url"] == "https://example.com/new-feed" mock_make_request.assert_called_once_with( "POST", "/apps/news/api/v1-3/feeds", json={"url": "https://example.com/new-feed"}, ) async def test_news_api_create_feed_with_folder(mocker): """Test that create_feed correctly creates a feed in a folder.""" mock_response = create_mock_news_feed_response( feed_id=10, url="https://example.com/feed", folder_id=5 ) mock_client = mocker.AsyncMock(spec=httpx.AsyncClient) mock_make_request = mocker.patch.object( NewsClient, "_make_request", return_value=mock_response ) client = NewsClient(mock_client, "testuser") feed = await client.create_feed(url="https://example.com/feed", folder_id=5) assert feed["folderId"] == 5 mock_make_request.assert_called_once_with( "POST", "/apps/news/api/v1-3/feeds", json={"url": "https://example.com/feed", "folderId": 5}, ) async def test_news_api_delete_feed(mocker): """Test that delete_feed makes the correct API call.""" mock_response = create_mock_response(status_code=200, json_data={}) mock_client = mocker.AsyncMock(spec=httpx.AsyncClient) mock_make_request = mocker.patch.object( NewsClient, "_make_request", return_value=mock_response ) client = NewsClient(mock_client, "testuser") await client.delete_feed(feed_id=10) mock_make_request.assert_called_once_with("DELETE", "/apps/news/api/v1-3/feeds/10") async def test_news_api_move_feed(mocker): """Test that move_feed makes the correct API call.""" mock_response = create_mock_response(status_code=200, json_data={}) mock_client = mocker.AsyncMock(spec=httpx.AsyncClient) mock_make_request = mocker.patch.object( NewsClient, "_make_request", return_value=mock_response ) client = NewsClient(mock_client, "testuser") await client.move_feed(feed_id=10, folder_id=5) mock_make_request.assert_called_once_with( "POST", "/apps/news/api/v1-3/feeds/10/move", json={"folderId": 5} ) async def test_news_api_rename_feed(mocker): """Test that rename_feed makes the correct API call.""" mock_response = create_mock_response(status_code=200, json_data={}) mock_client = mocker.AsyncMock(spec=httpx.AsyncClient) mock_make_request = mocker.patch.object( NewsClient, "_make_request", return_value=mock_response ) client = NewsClient(mock_client, "testuser") await client.rename_feed(feed_id=10, title="Renamed Feed") mock_make_request.assert_called_once_with( "POST", "/apps/news/api/v1-3/feeds/10/rename", json={"feedTitle": "Renamed Feed"}, ) # ============================================================================ # Item Tests # ============================================================================ async def test_news_api_get_items(mocker): """Test that get_items correctly parses the API response.""" items = [ create_mock_news_item(item_id=1, title="Article 1"), create_mock_news_item(item_id=2, title="Article 2"), ] mock_response = create_mock_news_items_response(items=items) mock_client = mocker.AsyncMock(spec=httpx.AsyncClient) mock_make_request = mocker.patch.object( NewsClient, "_make_request", return_value=mock_response ) client = NewsClient(mock_client, "testuser") result = await client.get_items() assert len(result) == 2 assert result[0]["title"] == "Article 1" assert result[1]["title"] == "Article 2" # Verify default parameters call_args = mock_make_request.call_args assert call_args[0] == ("GET", "/apps/news/api/v1-3/items") params = call_args[1]["params"] assert params["batchSize"] == 50 assert params["type"] == NewsItemType.ALL async def test_news_api_get_items_starred(mocker): """Test that get_items with STARRED type filters correctly.""" items = [create_mock_news_item(item_id=1, starred=True)] mock_response = create_mock_news_items_response(items=items) mock_client = mocker.AsyncMock(spec=httpx.AsyncClient) mock_make_request = mocker.patch.object( NewsClient, "_make_request", return_value=mock_response ) client = NewsClient(mock_client, "testuser") result = await client.get_items(type_=NewsItemType.STARRED) assert len(result) == 1 assert result[0]["starred"] is True call_args = mock_make_request.call_args params = call_args[1]["params"] assert params["type"] == NewsItemType.STARRED async def test_news_api_get_items_unread_only(mocker): """Test that get_items with get_read=False filters correctly.""" items = [create_mock_news_item(item_id=1, unread=True)] mock_response = create_mock_news_items_response(items=items) mock_client = mocker.AsyncMock(spec=httpx.AsyncClient) mock_make_request = mocker.patch.object( NewsClient, "_make_request", return_value=mock_response ) client = NewsClient(mock_client, "testuser") result = await client.get_items(get_read=False) assert len(result) == 1 call_args = mock_make_request.call_args params = call_args[1]["params"] assert params["getRead"] == "false" async def test_news_api_get_item(mocker): """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_get_items = mocker.patch.object(NewsClient, "get_items", return_value=items) client = NewsClient(mock_client, "testuser") result = await client.get_item(item_id=123) assert result["id"] == 123 assert result["title"] == "Single Item" # 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): """Test that get_updated_items correctly calls the updated endpoint.""" items = [create_mock_news_item(item_id=1)] mock_response = create_mock_news_items_response(items=items) mock_client = mocker.AsyncMock(spec=httpx.AsyncClient) mock_make_request = mocker.patch.object( NewsClient, "_make_request", return_value=mock_response ) client = NewsClient(mock_client, "testuser") result = await client.get_updated_items(last_modified=1700000000) assert len(result) == 1 call_args = mock_make_request.call_args assert call_args[0] == ("GET", "/apps/news/api/v1-3/items/updated") params = call_args[1]["params"] assert params["lastModified"] == 1700000000 async def test_news_api_mark_item_read(mocker): """Test that mark_item_read makes the correct API call.""" mock_response = create_mock_response(status_code=200, json_data={}) mock_client = mocker.AsyncMock(spec=httpx.AsyncClient) mock_make_request = mocker.patch.object( NewsClient, "_make_request", return_value=mock_response ) client = NewsClient(mock_client, "testuser") await client.mark_item_read(item_id=123) mock_make_request.assert_called_once_with( "POST", "/apps/news/api/v1-3/items/123/read" ) async def test_news_api_mark_item_unread(mocker): """Test that mark_item_unread makes the correct API call.""" mock_response = create_mock_response(status_code=200, json_data={}) mock_client = mocker.AsyncMock(spec=httpx.AsyncClient) mock_make_request = mocker.patch.object( NewsClient, "_make_request", return_value=mock_response ) client = NewsClient(mock_client, "testuser") await client.mark_item_unread(item_id=123) mock_make_request.assert_called_once_with( "POST", "/apps/news/api/v1-3/items/123/unread" ) async def test_news_api_star_item(mocker): """Test that star_item makes the correct API call.""" mock_response = create_mock_response(status_code=200, json_data={}) mock_client = mocker.AsyncMock(spec=httpx.AsyncClient) mock_make_request = mocker.patch.object( NewsClient, "_make_request", return_value=mock_response ) client = NewsClient(mock_client, "testuser") await client.star_item(item_id=123) mock_make_request.assert_called_once_with( "POST", "/apps/news/api/v1-3/items/123/star" ) async def test_news_api_unstar_item(mocker): """Test that unstar_item makes the correct API call.""" mock_response = create_mock_response(status_code=200, json_data={}) mock_client = mocker.AsyncMock(spec=httpx.AsyncClient) mock_make_request = mocker.patch.object( NewsClient, "_make_request", return_value=mock_response ) client = NewsClient(mock_client, "testuser") await client.unstar_item(item_id=123) mock_make_request.assert_called_once_with( "POST", "/apps/news/api/v1-3/items/123/unstar" ) async def test_news_api_mark_items_read_multiple(mocker): """Test that mark_items_read makes the correct API call for multiple items.""" mock_response = create_mock_response(status_code=200, json_data={}) mock_client = mocker.AsyncMock(spec=httpx.AsyncClient) mock_make_request = mocker.patch.object( NewsClient, "_make_request", return_value=mock_response ) client = NewsClient(mock_client, "testuser") await client.mark_items_read(item_ids=[1, 2, 3]) mock_make_request.assert_called_once_with( "POST", "/apps/news/api/v1-3/items/read/multiple", json={"itemIds": [1, 2, 3]} ) async def test_news_api_star_items_multiple(mocker): """Test that star_items makes the correct API call for multiple items.""" mock_response = create_mock_response(status_code=200, json_data={}) mock_client = mocker.AsyncMock(spec=httpx.AsyncClient) mock_make_request = mocker.patch.object( NewsClient, "_make_request", return_value=mock_response ) client = NewsClient(mock_client, "testuser") await client.star_items(item_ids=[1, 2, 3]) mock_make_request.assert_called_once_with( "POST", "/apps/news/api/v1-3/items/star/multiple", json={"itemIds": [1, 2, 3]} ) # ============================================================================ # Status Tests # ============================================================================ async def test_news_api_get_status(mocker): """Test that get_status correctly parses the API response.""" mock_response = create_mock_news_status_response( version="25.0.0", warnings={"improperlyConfiguredCron": False}, ) mock_client = mocker.AsyncMock(spec=httpx.AsyncClient) mock_make_request = mocker.patch.object( NewsClient, "_make_request", return_value=mock_response ) client = NewsClient(mock_client, "testuser") status = await client.get_status() assert status["version"] == "25.0.0" assert "warnings" in status mock_make_request.assert_called_once_with("GET", "/apps/news/api/v1-3/status") async def test_news_api_get_version(mocker): """Test that get_version correctly parses the API response.""" mock_response = create_mock_response( status_code=200, json_data={"version": "25.0.0"} ) mock_client = mocker.AsyncMock(spec=httpx.AsyncClient) mock_make_request = mocker.patch.object( NewsClient, "_make_request", return_value=mock_response ) client = NewsClient(mock_client, "testuser") version = await client.get_version() assert version == "25.0.0" mock_make_request.assert_called_once_with("GET", "/apps/news/api/v1-3/version") # ============================================================================ # Error Handling Tests # ============================================================================ async def test_news_api_create_folder_conflict(mocker): """Test that create_folder raises HTTPStatusError on 409 conflict.""" error_response = create_mock_error_response(409, "Folder name already exists") mock_client = mocker.AsyncMock(spec=httpx.AsyncClient) mock_make_request = mocker.patch.object(NewsClient, "_make_request") mock_make_request.side_effect = httpx.HTTPStatusError( "409 Conflict", request=httpx.Request("POST", "http://test.local"), response=error_response, ) client = NewsClient(mock_client, "testuser") with pytest.raises(httpx.HTTPStatusError) as excinfo: await client.create_folder(name="Existing Folder") assert excinfo.value.response.status_code == 409 async def test_news_api_delete_feed_not_found(mocker): """Test that delete_feed raises HTTPStatusError on 404.""" error_response = create_mock_error_response(404, "Feed not found") mock_client = mocker.AsyncMock(spec=httpx.AsyncClient) mock_make_request = mocker.patch.object(NewsClient, "_make_request") mock_make_request.side_effect = httpx.HTTPStatusError( "404 Not Found", request=httpx.Request("DELETE", "http://test.local"), response=error_response, ) client = NewsClient(mock_client, "testuser") with pytest.raises(httpx.HTTPStatusError) as excinfo: await client.delete_feed(feed_id=999999) assert excinfo.value.response.status_code == 404 async def test_news_api_create_feed_invalid_url(mocker): """Test that create_feed raises HTTPStatusError on 422 for invalid URL.""" error_response = create_mock_error_response(422, "Invalid feed URL") mock_client = mocker.AsyncMock(spec=httpx.AsyncClient) mock_make_request = mocker.patch.object(NewsClient, "_make_request") mock_make_request.side_effect = httpx.HTTPStatusError( "422 Unprocessable Entity", request=httpx.Request("POST", "http://test.local"), response=error_response, ) client = NewsClient(mock_client, "testuser") with pytest.raises(httpx.HTTPStatusError) as excinfo: await client.create_feed(url="not-a-valid-url") assert excinfo.value.response.status_code == 422

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/cbcoutinho/nextcloud-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server