test: Update tests with updated API

This commit is contained in:
Chris Coutinho
2025-07-06 09:35:10 +02:00
parent a1c186aa95
commit d47e2bb8f0
6 changed files with 76 additions and 58 deletions
+36 -14
View File
@@ -8,24 +8,46 @@ The Nextcloud MCP (Model Context Protocol) server allows Large Language Models (
## Features
Currently, the server primarily interacts with the Nextcloud Notes API, providing tools and resources to manage notes.
The server provides integration with multiple Nextcloud apps, enabling LLMs to interact with your Nextcloud data through a rich set of tools and resources.
### Available Tools
## Supported Nextcloud Apps
* `nc_notes_create_note`: Create a new note.
* `nc_notes_update_note`: Update an existing note by ID.
* `nc_notes_append_content`: Append content to an existing note with a clear separator.
* `nc_notes_delete_note`: Delete a note by ID.
* `nc_notes_search_notes`: Search notes by title or content.
* `nc_get_note`: Get a specific note by ID.
| App | Support Status | Description |
|-----|----------------|-------------|
| **Notes** | ✅ Full Support | Create, read, update, delete, and search notes. Handle attachments via WebDAV. |
| **Tables** | ⚠️ Row Operations | Read table schemas and perform CRUD operations on table rows. Table management not yet supported. |
### Available Resources
## Available Tools
* `notes://{note_id}`: Access a specific note by its ID.
* `notes://all`: Access all notes.
* `notes://settings`: Access note settings.
* `nc://capabilities`: Access Nextcloud server capabilities.
* `nc://Notes/{note_id}/attachments/{attachment_filename}`: Access attachments for notes.
### Notes Tools
| Tool | Description |
|------|-------------|
| `nc_get_note` | Get a specific note by ID |
| `nc_notes_create_note` | Create a new note with title, content, and category |
| `nc_notes_update_note` | Update an existing note by ID |
| `nc_notes_append_content` | Append content to an existing note with a clear separator |
| `nc_notes_delete_note` | Delete a note by ID |
| `nc_notes_search_notes` | Search notes by title or content |
### Tables Tools
| Tool | Description |
|------|-------------|
| `nc_tables_list_tables` | List all tables available to the user |
| `nc_tables_get_schema` | Get the schema/structure of a specific table including columns and views |
| `nc_tables_read_table` | Read rows from a table with optional pagination |
| `nc_tables_insert_row` | Insert a new row into a table |
| `nc_tables_update_row` | Update an existing row in a table |
| `nc_tables_delete_row` | Delete a row from a table |
## Available Resources
| Resource | Description |
|----------|-------------|
| `nc://capabilities` | Access Nextcloud server capabilities |
| `notes://settings` | Access Notes app settings |
| `nc://Notes/{note_id}/attachments/{attachment_filename}` | Access attachments for notes |
### Note Attachments
-1
View File
@@ -38,7 +38,6 @@ logger = logging.getLogger(__name__)
@mcp.resource("nc://capabilities")
async def nc_get_capabilities():
"""Get the Nextcloud Host capabilities"""
# client = NextcloudClient.from_env()
ctx = (
mcp.get_context()
) # https://github.com/modelcontextprotocol/python-sdk/issues/244
+3 -3
View File
@@ -52,7 +52,7 @@ async def temporary_note(nc_client: NextcloudClient):
logger.info(f"Creating temporary note: {note_title}")
try:
created_note_data = await nc_client.notes_create_note(
created_note_data = await nc_client.notes.create_note(
title=note_title, content=note_content, category=note_category
)
note_id = created_note_data.get("id")
@@ -66,7 +66,7 @@ async def temporary_note(nc_client: NextcloudClient):
if note_id:
logger.info(f"Cleaning up temporary note ID: {note_id}")
try:
await nc_client.notes_delete_note(note_id=note_id)
await nc_client.notes.delete_note(note_id=note_id)
logger.info(f"Successfully deleted temporary note ID: {note_id}")
except HTTPStatusError as e:
# Ignore 404 if note was already deleted by the test itself
@@ -102,7 +102,7 @@ async def temporary_note_with_attachment(
)
try:
# Pass the category to add_note_attachment
upload_response = await nc_client.add_note_attachment(
upload_response = await nc_client.webdav.add_note_attachment(
note_id=note_id,
filename=attachment_filename,
content=attachment_content,
+15 -15
View File
@@ -29,7 +29,7 @@ async def test_attachments_add_and_get(
f"Attempting to retrieve attachment '{attachment_filename}' added by fixture for note ID: {note_id}"
)
# Pass category to get_note_attachment
retrieved_content, retrieved_mime = await nc_client.get_note_attachment(
retrieved_content, retrieved_mime = await nc_client.webdav.get_note_attachment(
note_id=note_id, filename=attachment_filename, category=note_category
)
logger.info(
@@ -67,7 +67,7 @@ async def test_attachments_add_to_note_with_category(
f"Attempting to add attachment '{attachment_filename}' to note ID: {note_id}"
)
# Pass category to add_note_attachment
upload_response = await nc_client.add_note_attachment(
upload_response = await nc_client.webdav.add_note_attachment(
note_id=note_id,
filename=attachment_filename,
content=attachment_content,
@@ -86,7 +86,7 @@ async def test_attachments_add_to_note_with_category(
f"Attempting to retrieve attachment '{attachment_filename}' from note ID: {note_id}"
)
# Pass category to get_note_attachment
retrieved_content, retrieved_mime = await nc_client.get_note_attachment(
retrieved_content, retrieved_mime = await nc_client.webdav.get_note_attachment(
note_id=note_id,
filename=attachment_filename,
category=note_category, # Pass the note's category
@@ -127,13 +127,13 @@ async def test_attachments_cleanup_on_note_delete(
# Manually delete the note
logger.info(f"Manually deleting note ID: {note_id} within the test.")
await nc_client.notes_delete_note(note_id=note_id)
await nc_client.notes.delete_note(note_id=note_id)
logger.info(f"Note ID: {note_id} deleted successfully.")
time.sleep(1)
# Verify Note Is Deleted
with pytest.raises(HTTPStatusError) as excinfo_note:
await nc_client.notes_get_note(note_id=note_id)
await nc_client.notes.get_note(note_id=note_id)
assert excinfo_note.value.response.status_code == 404
logger.info(f"Verified note {note_id} deletion (404 received).")
@@ -145,7 +145,7 @@ async def test_attachments_cleanup_on_note_delete(
# Pass category to get_note_attachment - although it should fail anyway
# because the note (and thus details) are gone.
# The client method will raise 404 from the initial notes_get_note call.
await nc_client.get_note_attachment(
await nc_client.webdav.get_note_attachment(
note_id=note_id,
filename=attachment_filename,
category=note_category, # Pass category, though note fetch should fail first
@@ -205,7 +205,7 @@ async def test_attachments_category_change_handling(nc_client: NextcloudClient):
try:
# 1. Create note with initial category
logger.info(f"Creating note '{note_title}' in category '{initial_category}'")
created_note = await nc_client.notes_create_note(
created_note = await nc_client.notes.create_note(
title=note_title, content="Initial content", category=initial_category
)
note_id = created_note["id"]
@@ -217,7 +217,7 @@ async def test_attachments_category_change_handling(nc_client: NextcloudClient):
logger.info(
f"Adding attachment '{attachment_filename}' to note {note_id} (in {initial_category})"
)
upload_response = await nc_client.add_note_attachment(
upload_response = await nc_client.webdav.add_note_attachment(
note_id=note_id,
filename=attachment_filename,
content=attachment_content,
@@ -232,7 +232,7 @@ async def test_attachments_category_change_handling(nc_client: NextcloudClient):
logger.info(
f"Verifying attachment retrieval from initial category '{initial_category}'"
)
retrieved_content1, _ = await nc_client.get_note_attachment(
retrieved_content1, _ = await nc_client.webdav.get_note_attachment(
note_id=note_id, filename=attachment_filename, category=initial_category
)
assert retrieved_content1 == attachment_content
@@ -243,9 +243,9 @@ async def test_attachments_category_change_handling(nc_client: NextcloudClient):
f"Updating note {note_id} category from '{initial_category}' to '{new_category}'"
)
# Need to fetch the latest etag after attachment add (WebDAV ops don't update note etag)
current_note_data = await nc_client.notes_get_note(note_id=note_id)
current_note_data = await nc_client.notes.get_note(note_id=note_id)
current_etag = current_note_data["etag"]
updated_note = await nc_client.notes_update_note(
updated_note = await nc_client.notes.update(
note_id=note_id,
etag=current_etag,
category=new_category,
@@ -261,7 +261,7 @@ async def test_attachments_category_change_handling(nc_client: NextcloudClient):
logger.info(
f"Verifying attachment retrieval from new category '{new_category}'"
)
retrieved_content2, _ = await nc_client.get_note_attachment(
retrieved_content2, _ = await nc_client.webdav.get_note_attachment(
note_id=note_id, filename=attachment_filename, category=new_category
)
assert retrieved_content2 == attachment_content
@@ -326,18 +326,18 @@ async def test_attachments_category_change_handling(nc_client: NextcloudClient):
f"Cleaning up note ID: {note_id} (last known category: '{new_category}')"
)
try:
await nc_client.notes_delete_note(note_id=note_id)
await nc_client.notes.delete_note(note_id=note_id)
logger.info(f"Note {note_id} deleted.")
time.sleep(1)
# Verify note deletion
with pytest.raises(HTTPStatusError) as excinfo_note_del:
await nc_client.notes_get_note(note_id=note_id)
await nc_client.notes.get_note(note_id=note_id)
assert excinfo_note_del.value.response.status_code == 404
logger.info("Verified note deleted (404).")
# Verify attachment deletion (should fail with 404 on the initial note fetch)
with pytest.raises(HTTPStatusError) as excinfo_attach_del:
# Pass the *last known* category, although the note fetch should fail first
await nc_client.get_note_attachment(
await nc_client.webdav.get_note_attachment(
note_id=note_id,
filename=attachment_filename,
category=new_category,
+6 -6
View File
@@ -62,7 +62,7 @@ async def test_note_with_embedded_image(
logger.info(
f"Uploading image attachment '{attachment_filename}' to note {note_id} (category: '{note_category or ''}')..."
)
upload_response = await nc_client.add_note_attachment(
upload_response = await nc_client.webdav.add_note_attachment(
note_id=note_id,
filename=attachment_filename,
content=image_content,
@@ -115,7 +115,7 @@ async def test_note_with_embedded_image(
<img src=".attachments.{note_id}/{attachment_filename}" alt="Test Image HTML" width="150" />
"""
logger.info("Updating note content with image references...")
updated_note = await nc_client.notes_update_note(
updated_note = await nc_client.notes.update(
note_id=note_id,
etag=note_etag, # Use etag from the created note
content=updated_content,
@@ -128,7 +128,7 @@ async def test_note_with_embedded_image(
time.sleep(1)
# 3. Verify the updated note content
retrieved_note = await nc_client.notes_get_note(note_id=note_id)
retrieved_note = await nc_client.notes.get_note(note_id=note_id)
assert f".attachments.{note_id}/{attachment_filename}" in retrieved_note["content"]
logger.info("Verified image reference exists in updated note content.")
@@ -137,7 +137,7 @@ async def test_note_with_embedded_image(
f"Retrieving image attachment '{attachment_filename}' (category: '{note_category or ''}')..."
)
# Pass category to get_note_attachment
retrieved_img_content, mime_type = await nc_client.get_note_attachment(
retrieved_img_content, mime_type = await nc_client.webdav.get_note_attachment(
note_id=note_id, filename=attachment_filename, category=note_category
)
assert retrieved_img_content == image_content
@@ -150,13 +150,13 @@ async def test_note_with_embedded_image(
logger.info(
f"Manually deleting note ID: {note_id} to verify proper attachment cleanup"
)
await nc_client.notes_delete_note(note_id=note_id)
await nc_client.notes.delete_note(note_id=note_id)
logger.info(f"Note ID: {note_id} deleted successfully.")
time.sleep(1)
# 6. Verify note is deleted
with pytest.raises(HTTPStatusError) as excinfo_note:
await nc_client.notes_get_note(note_id=note_id)
await nc_client.notes.get_note(note_id=note_id)
assert excinfo_note.value.response.status_code == 404
logger.info(f"Verified note {note_id} deletion (404 received).")
+16 -19
View File
@@ -24,7 +24,7 @@ async def test_notes_api_create_and_read(
note_id = created_note_data["id"]
logger.info(f"Reading note created by fixture, ID: {note_id}")
read_note = await nc_client.notes_get_note(note_id=note_id)
read_note = await nc_client.notes.get_note(note_id=note_id)
assert read_note["id"] == note_id
assert read_note["title"] == created_note_data["title"]
@@ -46,7 +46,7 @@ async def test_notes_api_update(nc_client: NextcloudClient, temporary_note: dict
update_content = f"Updated Content {uuid.uuid4().hex[:8]}"
logger.info(f"Attempting to update note ID: {note_id} with etag: {original_etag}")
updated_note = await nc_client.notes_update_note(
updated_note = await nc_client.notes.update(
note_id=note_id,
etag=original_etag,
title=update_title,
@@ -66,7 +66,7 @@ async def test_notes_api_update(nc_client: NextcloudClient, temporary_note: dict
# Optional: Verify update by reading again
await asyncio.sleep(1) # Allow potential propagation delay
read_updated_note = await nc_client.notes_get_note(note_id=note_id)
read_updated_note = await nc_client.notes.get_note(note_id=note_id)
assert read_updated_note["title"] == update_title
assert read_updated_note["content"] == update_content
logger.info(f"Successfully updated and verified note ID: {note_id}")
@@ -85,7 +85,7 @@ async def test_notes_api_update_conflict(
# Perform a first update to change the etag
first_update_title = f"First Update {uuid.uuid4().hex[:8]}"
logger.info(f"Performing first update on note ID: {note_id} to change etag.")
first_updated_note = await nc_client.notes_update_note(
first_updated_note = await nc_client.notes.update(
note_id=note_id,
etag=original_etag,
title=first_update_title,
@@ -102,7 +102,7 @@ async def test_notes_api_update_conflict(
f"Attempting second update on note ID: {note_id} with OLD etag: {original_etag}"
)
with pytest.raises(HTTPStatusError) as excinfo:
await nc_client.notes_update_note(
await nc_client.notes.update(
note_id=note_id,
etag=original_etag, # Use the stale etag
title="This update should fail due to conflict",
@@ -119,7 +119,7 @@ async def test_notes_api_delete_nonexistent(nc_client: NextcloudClient):
non_existent_id = 999999999 # Use an ID highly unlikely to exist
logger.info(f"\nAttempting to delete non-existent note ID: {non_existent_id}")
with pytest.raises(HTTPStatusError) as excinfo:
await nc_client.notes_delete_note(note_id=non_existent_id)
await nc_client.notes.delete_note(note_id=non_existent_id)
assert excinfo.value.response.status_code == 404
logger.info(
f"Deleting non-existent note ID: {non_existent_id} correctly failed with 404."
@@ -139,7 +139,7 @@ async def test_notes_api_append_content_to_existing_note(
append_text = f"Appended content {uuid.uuid4().hex[:8]}"
logger.info(f"Appending content to note ID: {note_id}")
updated_note = await nc_client.notes_append_content(
updated_note = await nc_client.notes.append_content(
note_id=note_id, content=append_text
)
logger.info(f"Note after append: {updated_note}")
@@ -155,7 +155,7 @@ async def test_notes_api_append_content_to_existing_note(
# Verify by reading the note again
await asyncio.sleep(1) # Allow potential propagation delay
read_note = await nc_client.notes_get_note(note_id=note_id)
read_note = await nc_client.notes.get_note(note_id=note_id)
assert read_note["content"] == expected_content
logger.info(f"Successfully appended content to note ID: {note_id}")
@@ -169,7 +169,7 @@ async def test_notes_api_append_content_to_empty_note(nc_client: NextcloudClient
test_category = "Test"
logger.info("Creating empty note for append test")
empty_note = await nc_client.notes_create_note(
empty_note = await nc_client.notes.create_note(
title=test_title,
content="",
category=test_category, # Empty content
@@ -180,7 +180,7 @@ async def test_notes_api_append_content_to_empty_note(nc_client: NextcloudClient
append_text = f"First content {uuid.uuid4().hex[:8]}"
logger.info(f"Appending content to empty note ID: {note_id}")
updated_note = await nc_client.notes_append_content(
updated_note = await nc_client.notes.append_content(
note_id=note_id, content=append_text
)
@@ -189,14 +189,14 @@ async def test_notes_api_append_content_to_empty_note(nc_client: NextcloudClient
# Verify by reading the note again
await asyncio.sleep(1)
read_note = await nc_client.notes_get_note(note_id=note_id)
read_note = await nc_client.notes.get_note(note_id=note_id)
assert read_note["content"] == append_text
logger.info(f"Successfully appended content to empty note ID: {note_id}")
finally:
# Clean up the test note
try:
await nc_client.notes_delete_note(note_id=note_id)
await nc_client.notes.delete_note(note_id=note_id)
logger.info(f"Cleaned up test note ID: {note_id}")
except Exception as e:
logger.warning(f"Failed to clean up test note ID: {note_id}: {e}")
@@ -218,7 +218,7 @@ async def test_notes_api_append_content_multiple_times(
logger.info(f"Performing multiple appends to note ID: {note_id}")
# First append
updated_note = await nc_client.notes_append_content(
updated_note = await nc_client.notes.append_content(
note_id=note_id, content=first_append
)
@@ -226,7 +226,7 @@ async def test_notes_api_append_content_multiple_times(
assert updated_note["content"] == expected_content_after_first
# Second append
updated_note = await nc_client.notes_append_content(
updated_note = await nc_client.notes.append_content(
note_id=note_id, content=second_append
)
@@ -237,7 +237,7 @@ async def test_notes_api_append_content_multiple_times(
# Verify by reading the note again
await asyncio.sleep(1)
read_note = await nc_client.notes_get_note(note_id=note_id)
read_note = await nc_client.notes.get_note(note_id=note_id)
assert read_note["content"] == expected_content_after_second
logger.info(f"Successfully performed multiple appends to note ID: {note_id}")
@@ -250,13 +250,10 @@ async def test_notes_api_append_content_nonexistent_note(nc_client: NextcloudCli
logger.info(f"Attempting to append to non-existent note ID: {non_existent_id}")
with pytest.raises(HTTPStatusError) as excinfo:
await nc_client.notes_append_content(
await nc_client.notes.append_content(
note_id=non_existent_id, content="This should fail"
)
assert excinfo.value.response.status_code == 404
logger.info(
f"Appending to non-existent note ID: {non_existent_id} correctly failed with 404."
)
# --- Attachment tests moved to test_attachments.py ---