256 lines
8.7 KiB
Python
256 lines
8.7 KiB
Python
import logging
|
|
|
|
import httpx
|
|
import pytest
|
|
|
|
from nextcloud_mcp_server.client.notes import NotesClient
|
|
from tests.client.conftest import create_mock_error_response, create_mock_note_response
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Mark all tests in this module as unit tests
|
|
pytestmark = pytest.mark.unit
|
|
|
|
|
|
async def test_notes_api_get_note(mocker):
|
|
"""Test that get_note correctly parses the API response."""
|
|
# Create mock response
|
|
mock_response = create_mock_note_response(
|
|
note_id=123,
|
|
title="Test Note",
|
|
content="Test content",
|
|
category="Test",
|
|
etag="abc123",
|
|
)
|
|
|
|
# Mock the _make_request method
|
|
mock_client = mocker.AsyncMock(spec=httpx.AsyncClient)
|
|
mock_make_request = mocker.patch.object(
|
|
NotesClient, "_make_request", return_value=mock_response
|
|
)
|
|
|
|
# Create client and test
|
|
client = NotesClient(mock_client, "testuser")
|
|
note = await client.get_note(note_id=123)
|
|
|
|
# Verify the response was parsed correctly
|
|
assert note["id"] == 123
|
|
assert note["title"] == "Test Note"
|
|
assert note["content"] == "Test content"
|
|
assert note["category"] == "Test"
|
|
assert note["etag"] == "abc123"
|
|
|
|
# Verify the correct API endpoint was called
|
|
mock_make_request.assert_called_once_with("GET", "/apps/notes/api/v1/notes/123")
|
|
|
|
|
|
async def test_notes_api_create_note(mocker):
|
|
"""Test that create_note correctly parses the API response."""
|
|
mock_response = create_mock_note_response(
|
|
note_id=456,
|
|
title="New Note",
|
|
content="New content",
|
|
category="Category",
|
|
etag="def456",
|
|
)
|
|
|
|
mock_client = mocker.AsyncMock(spec=httpx.AsyncClient)
|
|
mock_make_request = mocker.patch.object(
|
|
NotesClient, "_make_request", return_value=mock_response
|
|
)
|
|
|
|
client = NotesClient(mock_client, "testuser")
|
|
note = await client.create_note(
|
|
title="New Note", content="New content", category="Category"
|
|
)
|
|
|
|
assert note["id"] == 456
|
|
assert note["title"] == "New Note"
|
|
assert note["content"] == "New content"
|
|
assert note["category"] == "Category"
|
|
|
|
# Verify the correct API call was made
|
|
mock_make_request.assert_called_once_with(
|
|
"POST",
|
|
"/apps/notes/api/v1/notes",
|
|
json={"title": "New Note", "content": "New content", "category": "Category"},
|
|
)
|
|
|
|
|
|
async def test_notes_api_update(mocker):
|
|
"""Test that update correctly parses the API response and handles etag."""
|
|
# Mock the update response (no category passed, so no GET call happens)
|
|
update_response = create_mock_note_response(
|
|
note_id=123,
|
|
title="Updated Title",
|
|
content="Updated content",
|
|
category="Test",
|
|
etag="new_etag",
|
|
)
|
|
|
|
mock_client = mocker.AsyncMock(spec=httpx.AsyncClient)
|
|
|
|
# Mock _make_request to return the update response
|
|
mock_make_request = mocker.patch.object(NotesClient, "_make_request")
|
|
mock_make_request.return_value = update_response
|
|
|
|
client = NotesClient(mock_client, "testuser")
|
|
updated_note = await client.update(
|
|
note_id=123,
|
|
etag="abc123",
|
|
title="Updated Title",
|
|
content="Updated content",
|
|
)
|
|
|
|
assert updated_note["id"] == 123
|
|
assert updated_note["title"] == "Updated Title"
|
|
assert updated_note["content"] == "Updated content"
|
|
assert updated_note["etag"] == "new_etag"
|
|
|
|
# Verify the PUT request was made with the correct etag header (only 1 call since no category)
|
|
assert mock_make_request.call_count == 1
|
|
put_call = mock_make_request.call_args_list[0]
|
|
assert put_call[0] == ("PUT", "/apps/notes/api/v1/notes/123")
|
|
assert put_call[1]["headers"]["If-Match"] == '"abc123"'
|
|
|
|
|
|
async def test_notes_api_update_conflict(mocker):
|
|
"""Test that update raises HTTPStatusError on 412 conflict."""
|
|
# Mock the 412 error response
|
|
error_response = create_mock_error_response(412, "Precondition Failed")
|
|
|
|
mock_client = mocker.AsyncMock(spec=httpx.AsyncClient)
|
|
mock_make_request = mocker.patch.object(NotesClient, "_make_request")
|
|
mock_make_request.side_effect = httpx.HTTPStatusError(
|
|
"412 Precondition Failed",
|
|
request=httpx.Request("PUT", "http://test.local"),
|
|
response=error_response,
|
|
)
|
|
|
|
client = NotesClient(mock_client, "testuser")
|
|
|
|
with pytest.raises(httpx.HTTPStatusError) as excinfo:
|
|
await client.update(
|
|
note_id=123,
|
|
etag="old_etag",
|
|
title="This should fail",
|
|
)
|
|
|
|
assert excinfo.value.response.status_code == 412
|
|
|
|
|
|
async def test_notes_api_delete_note(mocker):
|
|
"""Test that delete_note makes the correct API call."""
|
|
# Mock get_note response (to fetch category for cleanup)
|
|
get_response = create_mock_note_response(note_id=123, category="Test")
|
|
|
|
# Mock delete response
|
|
delete_response = create_mock_note_response(note_id=123)
|
|
|
|
mock_client = mocker.AsyncMock(spec=httpx.AsyncClient)
|
|
mock_make_request = mocker.patch.object(NotesClient, "_make_request")
|
|
mock_make_request.side_effect = [get_response, delete_response]
|
|
|
|
client = NotesClient(mock_client, "testuser")
|
|
await client.delete_note(note_id=123)
|
|
|
|
# Verify DELETE was called
|
|
assert any(call[0][0] == "DELETE" for call in mock_make_request.call_args_list)
|
|
|
|
|
|
async def test_notes_api_delete_nonexistent(mocker):
|
|
"""Test that deleting a non-existent note raises 404."""
|
|
# Mock 404 error when fetching note details
|
|
error_response = create_mock_error_response(404, "Not Found")
|
|
|
|
mock_client = mocker.AsyncMock(spec=httpx.AsyncClient)
|
|
mock_make_request = mocker.patch.object(NotesClient, "_make_request")
|
|
mock_make_request.side_effect = httpx.HTTPStatusError(
|
|
"404 Not Found",
|
|
request=httpx.Request("GET", "http://test.local"),
|
|
response=error_response,
|
|
)
|
|
|
|
client = NotesClient(mock_client, "testuser")
|
|
|
|
with pytest.raises(httpx.HTTPStatusError) as excinfo:
|
|
await client.delete_note(note_id=999999999)
|
|
|
|
assert excinfo.value.response.status_code == 404
|
|
|
|
|
|
async def test_notes_api_append_content(mocker):
|
|
"""Test that append_content correctly appends to existing content."""
|
|
# Mock get_note response (to fetch current content)
|
|
get_response = create_mock_note_response(
|
|
note_id=123,
|
|
content="Original content",
|
|
etag="old_etag",
|
|
)
|
|
|
|
# Mock update response with appended content
|
|
update_response = create_mock_note_response(
|
|
note_id=123,
|
|
content="Original content\n---\nAppended content",
|
|
etag="new_etag",
|
|
)
|
|
|
|
mock_client = mocker.AsyncMock(spec=httpx.AsyncClient)
|
|
mock_make_request = mocker.patch.object(NotesClient, "_make_request")
|
|
# First call: GET (from get_note), second call: PUT (from update)
|
|
mock_make_request.side_effect = [get_response, update_response]
|
|
|
|
client = NotesClient(mock_client, "testuser")
|
|
updated_note = await client.append_content(note_id=123, content="Appended content")
|
|
|
|
assert updated_note["content"] == "Original content\n---\nAppended content"
|
|
assert updated_note["etag"] == "new_etag"
|
|
|
|
|
|
async def test_notes_api_append_content_to_empty_note(mocker):
|
|
"""Test that appending to empty note doesn't add separator."""
|
|
# Mock get_note response with empty content
|
|
get_response = create_mock_note_response(
|
|
note_id=123,
|
|
content="",
|
|
etag="old_etag",
|
|
)
|
|
|
|
# Mock update response with just the appended text (no separator)
|
|
update_response = create_mock_note_response(
|
|
note_id=123,
|
|
content="First content",
|
|
etag="new_etag",
|
|
)
|
|
|
|
mock_client = mocker.AsyncMock(spec=httpx.AsyncClient)
|
|
mock_make_request = mocker.patch.object(NotesClient, "_make_request")
|
|
# First call: GET (from get_note), second call: PUT (from update)
|
|
mock_make_request.side_effect = [get_response, update_response]
|
|
|
|
client = NotesClient(mock_client, "testuser")
|
|
updated_note = await client.append_content(note_id=123, content="First content")
|
|
|
|
# For empty notes, no separator should be added
|
|
assert updated_note["content"] == "First content"
|
|
|
|
|
|
async def test_notes_api_append_content_nonexistent_note(mocker):
|
|
"""Test that appending to a non-existent note raises 404."""
|
|
error_response = create_mock_error_response(404, "Not Found")
|
|
|
|
mock_client = mocker.AsyncMock(spec=httpx.AsyncClient)
|
|
mock_make_request = mocker.patch.object(NotesClient, "_make_request")
|
|
mock_make_request.side_effect = httpx.HTTPStatusError(
|
|
"404 Not Found",
|
|
request=httpx.Request("GET", "http://test.local"),
|
|
response=error_response,
|
|
)
|
|
|
|
client = NotesClient(mock_client, "testuser")
|
|
|
|
with pytest.raises(httpx.HTTPStatusError) as excinfo:
|
|
await client.append_content(note_id=999999999, content="This should fail")
|
|
|
|
assert excinfo.value.response.status_code == 404
|