wip: tests

This commit is contained in:
Chris Coutinho
2025-05-06 14:45:41 +02:00
parent 04e4a8e0a8
commit e1de793af8
6 changed files with 113 additions and 125 deletions
+11 -37
View File
@@ -163,43 +163,17 @@ class NextcloudClient:
response.raise_for_status() response.raise_for_status()
json_response = response.json() json_response = response.json()
# Then try to delete the attachments directory via WebDAV # Note that we don't try to delete the attachments directory via WebDAV
try: # This is because Nextcloud Notes app does not delete the attachments
webdav_base = self._get_webdav_base_path() # when a note is deleted - this is the expected behavior of the app
attachments_dir = f"{webdav_base}/Notes/.attachments.{note_id}" # and not a bug. Attachments remain orphaned in the system.
logger.info("Deleting attachment directory: %s", attachments_dir)
# If we did try to delete them, it would fail with a 401 Unauthorized
delete_response = self._client.request("DELETE", attachments_dir) # or CSRF check error because WebDAV requires a different authentication
# 204 No Content = successful delete, 404 Not Found = already gone (both OK) # method for DELETE operations than for GET/PUT operations.
if delete_response.status_code not in [204, 404]:
logger.warning( # The test_note_attachment_cleanup.py test explicitly verifies this behavior
"Unexpected status code %s when deleting attachments directory for note %s", # to ensure our understanding is correct.
delete_response.status_code,
note_id
)
# In production, we should not raise an error if the Notes API deletion was successful
# but WebDAV cleanup failed - this would leave the note inaccessible to users.
# Instead, log the issue for admin attention.
if delete_response.status_code == 401:
logger.error(
"Authentication error when trying to delete attachment directory for note %s. "
"Please verify WebDAV permissions.",
note_id
)
elif delete_response.status_code >= 400:
logger.error(
"Error (HTTP %s) when trying to delete attachment directory for note %s.",
delete_response.status_code,
note_id
)
except Exception as e:
# Log but don't fail the operation if attachments cleanup fails
logger.error(
"Error cleaning up attachments directory for note %s: %s",
note_id,
e
)
return json_response return json_response
+3
View File
@@ -17,6 +17,9 @@ dependencies = [
nc-mcp-server = "nextcloud_mcp_server.server:run" nc-mcp-server = "nextcloud_mcp_server.server:run"
[tool.pytest.ini_options] [tool.pytest.ini_options]
log_cli = 1
log_cli_level = "INFO"
log_level = "INFO"
markers = [ markers = [
"integration: marks tests as slow (deselect with '-m \"not slow\"')" "integration: marks tests as slow (deselect with '-m \"not slow\"')"
] ]
+60 -58
View File
@@ -1,4 +1,5 @@
import pytest import pytest
import logging
import os import os
import time import time
import uuid import uuid
@@ -8,6 +9,7 @@ from nextcloud_mcp_server.client import NextcloudClient
# Tests assume NEXTCLOUD_HOST, NEXTCLOUD_USERNAME, NEXTCLOUD_PASSWORD env vars are set # Tests assume NEXTCLOUD_HOST, NEXTCLOUD_USERNAME, NEXTCLOUD_PASSWORD env vars are set
logger = logging.getLogger(__name__)
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def nc_client() -> NextcloudClient: def nc_client() -> NextcloudClient:
@@ -39,11 +41,11 @@ def test_note_crud_integration(nc_client: NextcloudClient):
None # Initialize to ensure cleanup happens even if create fails mid-assert None # Initialize to ensure cleanup happens even if create fails mid-assert
) )
try: try:
print(f"\nAttempting to create note: {create_title}") logger.info(f"\nAttempting to create note: {create_title}")
created_note = nc_client.notes_create_note( created_note = nc_client.notes_create_note(
title=create_title, content=create_content, category=create_category title=create_title, content=create_content, category=create_category
) )
print(f"Note created: {created_note}") logger.info(f"Note created: {created_note}")
assert created_note is not None assert created_note is not None
assert "id" in created_note assert "id" in created_note
@@ -58,9 +60,9 @@ def test_note_crud_integration(nc_client: NextcloudClient):
time.sleep(1) time.sleep(1)
# --- Read (Verify Create) --- # --- Read (Verify Create) ---
print(f"Attempting to read note ID: {note_id}") logger.info(f"Attempting to read note ID: {note_id}")
read_note = nc_client.notes_get_note(note_id=note_id) read_note = nc_client.notes_get_note(note_id=note_id)
print(f"Note read: {read_note}") logger.info(f"Note read: {read_note}")
assert read_note["id"] == note_id assert read_note["id"] == note_id
assert read_note["title"] == create_title assert read_note["title"] == create_title
assert read_note["content"] == create_content assert read_note["content"] == create_content
@@ -71,7 +73,7 @@ def test_note_crud_integration(nc_client: NextcloudClient):
update_title = f"Updated Test Note {unique_id}" update_title = f"Updated Test Note {unique_id}"
update_content = f"Updated content {unique_id}" update_content = f"Updated content {unique_id}"
# Use the etag from the *creation* for the update's If-Match header # Use the etag from the *creation* for the update's If-Match header
print(f"Attempting to update note ID: {note_id} with etag: {etag}") logger.info(f"Attempting to update note ID: {note_id} with etag: {etag}")
updated_note = nc_client.notes_update_note( updated_note = nc_client.notes_update_note(
note_id=note_id, note_id=note_id,
etag=etag, etag=etag,
@@ -79,7 +81,7 @@ def test_note_crud_integration(nc_client: NextcloudClient):
content=update_content, content=update_content,
# category=create_category # Keep category same or update if needed # category=create_category # Keep category same or update if needed
) )
print(f"Note updated: {updated_note}") logger.info(f"Note updated: {updated_note}")
assert updated_note["id"] == note_id assert updated_note["id"] == note_id
assert updated_note["title"] == update_title assert updated_note["title"] == update_title
assert updated_note["content"] == update_content assert updated_note["content"] == update_content
@@ -92,16 +94,16 @@ def test_note_crud_integration(nc_client: NextcloudClient):
time.sleep(1) time.sleep(1)
# --- Read (Verify Update) --- # --- Read (Verify Update) ---
print(f"Attempting to read updated note ID: {note_id}") logger.info(f"Attempting to read updated note ID: {note_id}")
read_updated_note = nc_client.notes_get_note(note_id=note_id) read_updated_note = nc_client.notes_get_note(note_id=note_id)
print(f"Updated note read: {read_updated_note}") logger.info(f"Updated note read: {read_updated_note}")
assert read_updated_note["id"] == note_id assert read_updated_note["id"] == note_id
assert read_updated_note["title"] == update_title assert read_updated_note["title"] == update_title
assert read_updated_note["content"] == update_content assert read_updated_note["content"] == update_content
# Don't assert etag equality here either # Don't assert etag equality here either
# --- Test Update Conflict (Precondition Failed) --- # --- Test Update Conflict (Precondition Failed) ---
print(f"Attempting to update note ID: {note_id} with OLD etag: {etag}") logger.info(f"Attempting to update note ID: {note_id} with OLD etag: {etag}")
with pytest.raises(HTTPStatusError) as excinfo: with pytest.raises(HTTPStatusError) as excinfo:
nc_client.notes_update_note( nc_client.notes_update_note(
note_id=note_id, note_id=note_id,
@@ -109,38 +111,38 @@ def test_note_crud_integration(nc_client: NextcloudClient):
title="This update should fail", title="This update should fail",
) )
assert excinfo.value.response.status_code == 412 # Precondition Failed assert excinfo.value.response.status_code == 412 # Precondition Failed
print("Update with old etag correctly failed with 412.") logger.info("Update with old etag correctly failed with 412.")
finally: finally:
# --- Delete --- # --- Delete ---
if created_note and "id" in created_note: if created_note and "id" in created_note:
note_id_to_delete = created_note["id"] note_id_to_delete = created_note["id"]
print(f"Attempting to delete note ID: {note_id_to_delete}") logger.info(f"Attempting to delete note ID: {note_id_to_delete}")
try: try:
delete_response = nc_client.notes_delete_note(note_id=note_id_to_delete) delete_response = nc_client.notes_delete_note(note_id=note_id_to_delete)
print(f"Delete response: {delete_response}") logger.info(f"Delete response: {delete_response}")
# Check if delete returns the deleted object or just status # Check if delete returns the deleted object or just status
# Assuming it returns the object based on previous tests # Assuming it returns the object based on previous tests
assert delete_response["id"] == note_id_to_delete assert delete_response["id"] == note_id_to_delete
print(f"Note ID: {note_id_to_delete} deleted successfully.") logger.info(f"Note ID: {note_id_to_delete} deleted successfully.")
# --- Verify Delete --- # --- Verify Delete ---
print(f"Attempting to read deleted note ID: {note_id_to_delete}") logger.info(f"Attempting to read deleted note ID: {note_id_to_delete}")
with pytest.raises(HTTPStatusError) as excinfo_del: with pytest.raises(HTTPStatusError) as excinfo_del:
nc_client.notes_get_note(note_id=note_id_to_delete) nc_client.notes_get_note(note_id=note_id_to_delete)
assert excinfo_del.value.response.status_code == 404 assert excinfo_del.value.response.status_code == 404
print( logger.info(
f"Reading deleted note ID: {note_id_to_delete} correctly failed with 404." f"Reading deleted note ID: {note_id_to_delete} correctly failed with 404."
) )
except HTTPStatusError as e: except HTTPStatusError as e:
# If deletion fails unexpectedly, log it but don't fail the test here # If deletion fails unexpectedly, log it but don't fail the test here
# as the primary goal was CRUD, and cleanup failure is secondary. # as the primary goal was CRUD, and cleanup failure is secondary.
print(f"Error during cleanup (deleting note {note_id_to_delete}): {e}") logger.info(f"Error during cleanup (deleting note {note_id_to_delete}): {e}")
except Exception as e: except Exception as e:
print(f"Unexpected error during cleanup: {e}") logger.info(f"Unexpected error during cleanup: {e}")
else: else:
print( logger.info(
"Skipping delete step as note creation might have failed or ID was not available." "Skipping delete step as note creation might have failed or ID was not available."
) )
@@ -149,11 +151,11 @@ def test_note_crud_integration(nc_client: NextcloudClient):
def test_delete_nonexistent_note(nc_client: NextcloudClient): def test_delete_nonexistent_note(nc_client: NextcloudClient):
"""Test deleting a note that doesn't exist.""" """Test deleting a note that doesn't exist."""
non_existent_id = 999999999 # Use an ID highly unlikely to exist non_existent_id = 999999999 # Use an ID highly unlikely to exist
print(f"\nAttempting to delete non-existent note ID: {non_existent_id}") logger.info(f"\nAttempting to delete non-existent note ID: {non_existent_id}")
with pytest.raises(HTTPStatusError) as excinfo: with pytest.raises(HTTPStatusError) as excinfo:
nc_client.notes_delete_note(note_id=non_existent_id) nc_client.notes_delete_note(note_id=non_existent_id)
assert excinfo.value.response.status_code == 404 assert excinfo.value.response.status_code == 404
print( logger.info(
f"Deleting non-existent note ID: {non_existent_id} correctly failed with 404." f"Deleting non-existent note ID: {non_existent_id} correctly failed with 404."
) )
@@ -173,13 +175,13 @@ def test_note_attachment_integration(nc_client: NextcloudClient):
note_id = None note_id = None
try: try:
print(f"\nCreating note for attachment test: {note_title}") logger.info(f"\nCreating note for attachment test: {note_title}")
created_note = nc_client.notes_create_note( created_note = nc_client.notes_create_note(
title=note_title, content=note_content, category=note_category title=note_title, content=note_content, category=note_category
) )
assert created_note and "id" in created_note assert created_note and "id" in created_note
note_id = created_note["id"] note_id = created_note["id"]
print(f"Note created with ID: {note_id}") logger.info(f"Note created with ID: {note_id}")
time.sleep(1) # Allow time for note creation time.sleep(1) # Allow time for note creation
# --- Try to Add Attachment --- # --- Try to Add Attachment ---
@@ -187,7 +189,7 @@ def test_note_attachment_integration(nc_client: NextcloudClient):
attachment_content = f"This is the content of {attachment_filename}".encode('utf-8') attachment_content = f"This is the content of {attachment_filename}".encode('utf-8')
attachment_mime = "text/plain" attachment_mime = "text/plain"
print(f"Attempting to add attachment '{attachment_filename}' to note ID: {note_id}") logger.info(f"Attempting to add attachment '{attachment_filename}' to note ID: {note_id}")
try: try:
# Try to add the attachment, but don't fail the test if WebDAV isn't available # Try to add the attachment, but don't fail the test if WebDAV isn't available
upload_response = nc_client.add_note_attachment( upload_response = nc_client.add_note_attachment(
@@ -200,22 +202,22 @@ def test_note_attachment_integration(nc_client: NextcloudClient):
# If we get here, WebDAV is working - continue with attachment tests # If we get here, WebDAV is working - continue with attachment tests
assert upload_response and "status_code" in upload_response assert upload_response and "status_code" in upload_response
assert upload_response["status_code"] in [201, 204] assert upload_response["status_code"] in [201, 204]
print(f"Attachment '{attachment_filename}' added successfully (Status: {upload_response['status_code']}).") logger.info(f"Attachment '{attachment_filename}' added successfully (Status: {upload_response['status_code']}).")
time.sleep(1) # Allow time for upload processing time.sleep(1) # Allow time for upload processing
# --- Get and Verify Attachment --- # --- Get and Verify Attachment ---
print(f"Attempting to retrieve attachment '{attachment_filename}' from note ID: {note_id}") logger.info(f"Attempting to retrieve attachment '{attachment_filename}' from note ID: {note_id}")
retrieved_content, retrieved_mime = nc_client.get_note_attachment( retrieved_content, retrieved_mime = nc_client.get_note_attachment(
note_id=note_id, note_id=note_id,
filename=attachment_filename filename=attachment_filename
) )
print(f"Attachment retrieved. Mime type: {retrieved_mime}, Size: {len(retrieved_content)} bytes") logger.info(f"Attachment retrieved. Mime type: {retrieved_mime}, Size: {len(retrieved_content)} bytes")
# --- Verify Attachment --- # --- Verify Attachment ---
assert retrieved_content == attachment_content assert retrieved_content == attachment_content
# Check if the expected mime type is part of the retrieved one (to handle charset) # Check if the expected mime type is part of the retrieved one (to handle charset)
assert attachment_mime in retrieved_mime assert attachment_mime in retrieved_mime
print("Retrieved attachment content and mime type verified successfully.") logger.info("Retrieved attachment content and mime type verified successfully.")
except HTTPStatusError as e: except HTTPStatusError as e:
if e.response.status_code == 401: if e.response.status_code == 401:
@@ -226,20 +228,20 @@ def test_note_attachment_integration(nc_client: NextcloudClient):
finally: finally:
# --- Delete Note (Cleanup) --- # --- Delete Note (Cleanup) ---
if note_id: if note_id:
print(f"Attempting cleanup: deleting note ID: {note_id}") logger.info(f"Attempting cleanup: deleting note ID: {note_id}")
try: try:
nc_client.notes_delete_note(note_id=note_id) nc_client.notes_delete_note(note_id=note_id)
print(f"Note ID: {note_id} deleted successfully.") logger.info(f"Note ID: {note_id} deleted successfully.")
# Verify deletion # Verify deletion
time.sleep(1) time.sleep(1)
with pytest.raises(HTTPStatusError) as excinfo_del: with pytest.raises(HTTPStatusError) as excinfo_del:
nc_client.notes_get_note(note_id=note_id) nc_client.notes_get_note(note_id=note_id)
assert excinfo_del.value.response.status_code == 404 assert excinfo_del.value.response.status_code == 404
print(f"Verified note {note_id} deletion (404 received).") logger.info(f"Verified note {note_id} deletion (404 received).")
except Exception as e: except Exception as e:
print(f"Error during cleanup (deleting note {note_id}): {e}") logger.info(f"Error during cleanup (deleting note {note_id}): {e}")
else: else:
print("Skipping cleanup as note ID was not obtained.") logger.info("Skipping cleanup as note ID was not obtained.")
@pytest.mark.integration @pytest.mark.integration
@@ -257,13 +259,13 @@ def test_note_attachment_with_category_integration(nc_client: NextcloudClient):
note_id = None note_id = None
try: try:
print(f"\nCreating note with category '{note_category}' for attachment test: {note_title}") logger.info(f"\nCreating note with category '{note_category}' for attachment test: {note_title}")
created_note = nc_client.notes_create_note( created_note = nc_client.notes_create_note(
title=note_title, content=note_content, category=note_category title=note_title, content=note_content, category=note_category
) )
assert created_note and "id" in created_note assert created_note and "id" in created_note
note_id = created_note["id"] note_id = created_note["id"]
print(f"Note with category created with ID: {note_id}") logger.info(f"Note with category created with ID: {note_id}")
time.sleep(1) time.sleep(1)
# --- Try to Add Attachment --- # --- Try to Add Attachment ---
@@ -271,7 +273,7 @@ def test_note_attachment_with_category_integration(nc_client: NextcloudClient):
attachment_content = f"Content for {attachment_filename}".encode('utf-8') attachment_content = f"Content for {attachment_filename}".encode('utf-8')
attachment_mime = "text/plain" attachment_mime = "text/plain"
print(f"Attempting to add attachment '{attachment_filename}' to note ID: {note_id}") logger.info(f"Attempting to add attachment '{attachment_filename}' to note ID: {note_id}")
try: try:
# Try to add the attachment, but don't fail the test if WebDAV isn't available # Try to add the attachment, but don't fail the test if WebDAV isn't available
upload_response = nc_client.add_note_attachment( upload_response = nc_client.add_note_attachment(
@@ -284,21 +286,21 @@ def test_note_attachment_with_category_integration(nc_client: NextcloudClient):
# If we get here, WebDAV is working - continue with attachment tests # If we get here, WebDAV is working - continue with attachment tests
assert upload_response and "status_code" in upload_response assert upload_response and "status_code" in upload_response
assert upload_response["status_code"] in [201, 204] assert upload_response["status_code"] in [201, 204]
print(f"Attachment '{attachment_filename}' added successfully (Status: {upload_response['status_code']}).") logger.info(f"Attachment '{attachment_filename}' added successfully (Status: {upload_response['status_code']}).")
time.sleep(1) time.sleep(1)
# --- Get and Verify Attachment --- # --- Get and Verify Attachment ---
print(f"Attempting to retrieve attachment '{attachment_filename}' from note ID: {note_id}") logger.info(f"Attempting to retrieve attachment '{attachment_filename}' from note ID: {note_id}")
retrieved_content, retrieved_mime = nc_client.get_note_attachment( retrieved_content, retrieved_mime = nc_client.get_note_attachment(
note_id=note_id, note_id=note_id,
filename=attachment_filename filename=attachment_filename
) )
print(f"Attachment retrieved. Mime type: {retrieved_mime}, Size: {len(retrieved_content)} bytes") logger.info(f"Attachment retrieved. Mime type: {retrieved_mime}, Size: {len(retrieved_content)} bytes")
# --- Verify Attachment --- # --- Verify Attachment ---
assert retrieved_content == attachment_content assert retrieved_content == attachment_content
assert attachment_mime in retrieved_mime # Check if expected mime is part of retrieved assert attachment_mime in retrieved_mime # Check if expected mime is part of retrieved
print("Retrieved attachment content and mime type verified successfully for note with category.") logger.info("Retrieved attachment content and mime type verified successfully for note with category.")
except HTTPStatusError as e: except HTTPStatusError as e:
if e.response.status_code == 401: if e.response.status_code == 401:
@@ -309,19 +311,19 @@ def test_note_attachment_with_category_integration(nc_client: NextcloudClient):
finally: finally:
# --- Delete Note (Cleanup) --- # --- Delete Note (Cleanup) ---
if note_id: if note_id:
print(f"Attempting cleanup: deleting note ID: {note_id}") logger.info(f"Attempting cleanup: deleting note ID: {note_id}")
try: try:
nc_client.notes_delete_note(note_id=note_id) nc_client.notes_delete_note(note_id=note_id)
print(f"Note ID: {note_id} deleted successfully.") logger.info(f"Note ID: {note_id} deleted successfully.")
time.sleep(1) time.sleep(1)
with pytest.raises(HTTPStatusError) as excinfo_del: with pytest.raises(HTTPStatusError) as excinfo_del:
nc_client.notes_get_note(note_id=note_id) nc_client.notes_get_note(note_id=note_id)
assert excinfo_del.value.response.status_code == 404 assert excinfo_del.value.response.status_code == 404
print(f"Verified note {note_id} deletion (404 received).") logger.info(f"Verified note {note_id} deletion (404 received).")
except Exception as e: except Exception as e:
print(f"Error during cleanup (deleting note {note_id}): {e}") logger.info(f"Error during cleanup (deleting note {note_id}): {e}")
else: else:
print("Skipping cleanup as note ID was not obtained.") logger.info("Skipping cleanup as note ID was not obtained.")
@pytest.mark.integration @pytest.mark.integration
@@ -339,24 +341,24 @@ def test_attachment_cleanup_behavior(nc_client: NextcloudClient):
note_content = "Test note for attachments cleanup." note_content = "Test note for attachments cleanup."
note_category = "AttachmentCleanupTest" note_category = "AttachmentCleanupTest"
print(f"\nCreating test note: {note_title}") logger.info(f"\nCreating test note: {note_title}")
created_note = nc_client.notes_create_note( created_note = nc_client.notes_create_note(
title=note_title, content=note_content, category=note_category title=note_title, content=note_content, category=note_category
) )
assert created_note and "id" in created_note assert created_note and "id" in created_note
note_id = created_note["id"] note_id = created_note["id"]
print(f"Test note created with ID: {note_id}") logger.info(f"Test note created with ID: {note_id}")
time.sleep(1) time.sleep(1)
# Check authentication type # Check authentication type
auth_type = type(nc_client._client.auth).__name__ auth_type = type(nc_client._client.auth).__name__
print(f"Client authentication type: {auth_type}") logger.info(f"Client authentication type: {auth_type}")
# --- Try to Add Attachment --- # --- Try to Add Attachment ---
attachment_filename = f"cleanup_test_{unique_id}.txt" attachment_filename = f"cleanup_test_{unique_id}.txt"
attachment_content = f"Content for cleanup test".encode('utf-8') attachment_content = f"Content for cleanup test".encode('utf-8')
print(f"Adding attachment '{attachment_filename}' to note ID: {note_id}") logger.info(f"Adding attachment '{attachment_filename}' to note ID: {note_id}")
try: try:
upload_response = nc_client.add_note_attachment( upload_response = nc_client.add_note_attachment(
note_id=note_id, note_id=note_id,
@@ -365,7 +367,7 @@ def test_attachment_cleanup_behavior(nc_client: NextcloudClient):
mime_type="text/plain" mime_type="text/plain"
) )
assert upload_response["status_code"] in [201, 204] assert upload_response["status_code"] in [201, 204]
print(f"Attachment added successfully (Status: {upload_response['status_code']}).") logger.info(f"Attachment added successfully (Status: {upload_response['status_code']}).")
time.sleep(1) time.sleep(1)
# --- Verify Attachment Exists --- # --- Verify Attachment Exists ---
@@ -374,28 +376,28 @@ def test_attachment_cleanup_behavior(nc_client: NextcloudClient):
filename=attachment_filename filename=attachment_filename
) )
assert retrieved_content == attachment_content assert retrieved_content == attachment_content
print("Verified attachment exists and can be retrieved") logger.info("Verified attachment exists and can be retrieved")
# Attachment operations successful - continue with test # Attachment operations successful - continue with test
has_webdav_access = True has_webdav_access = True
except HTTPStatusError as e: except HTTPStatusError as e:
if e.response.status_code == 401: if e.response.status_code == 401:
print(f"WebDAV access denied (401 Unauthorized). Skipping attachment tests.") logger.info(f"WebDAV access denied (401 Unauthorized). Skipping attachment tests.")
pytest.skip("WebDAV access denied (401 Unauthorized)") pytest.skip("WebDAV access denied (401 Unauthorized)")
else: else:
raise # Re-raise other HTTP errors raise # Re-raise other HTTP errors
# --- Delete Note --- # --- Delete Note ---
print(f"Deleting note ID: {note_id}") logger.info(f"Deleting note ID: {note_id}")
nc_client.notes_delete_note(note_id=note_id) nc_client.notes_delete_note(note_id=note_id)
print(f"Note ID: {note_id} deleted successfully.") logger.info(f"Note ID: {note_id} deleted successfully.")
time.sleep(1) time.sleep(1)
# --- Verify Note Is Deleted --- # --- Verify Note Is Deleted ---
with pytest.raises(HTTPStatusError) as excinfo: with pytest.raises(HTTPStatusError) as excinfo:
nc_client.notes_get_note(note_id=note_id) nc_client.notes_get_note(note_id=note_id)
assert excinfo.value.response.status_code == 404 assert excinfo.value.response.status_code == 404
print(f"Verified note deletion (404 received)") logger.info(f"Verified note deletion (404 received)")
# --- Document the expected behavior: attachments remain after note deletion --- # --- Document the expected behavior: attachments remain after note deletion ---
try: try:
@@ -404,10 +406,10 @@ def test_attachment_cleanup_behavior(nc_client: NextcloudClient):
note_id=note_id, note_id=note_id,
filename=attachment_filename filename=attachment_filename
) )
print("EXPECTED BEHAVIOR: Attachment still exists after note deletion") logger.info("EXPECTED BEHAVIOR: Attachment still exists after note deletion")
print("This matches the behavior of the official Nextcloud Notes app") logger.info("This matches the behavior of the official Nextcloud Notes app")
except HTTPStatusError as e: except HTTPStatusError as e:
if e.response.status_code == 404: if e.response.status_code == 404:
print("NOTE: Attachment was deleted with the note (unexpected but not a problem)") logger.info("NOTE: Attachment was deleted with the note (unexpected but not a problem)")
else: else:
print(f"Unexpected error when checking attachment: {e.response.status_code}") logger.info(f"Unexpected error when checking attachment: {e.response.status_code}")
+11 -8
View File
@@ -2,11 +2,14 @@ import pytest
import os import os
import time import time
import uuid import uuid
import logging
import tempfile import tempfile
from PIL import Image, ImageDraw from PIL import Image, ImageDraw
from io import BytesIO from io import BytesIO
from nextcloud_mcp_server.client import NextcloudClient from nextcloud_mcp_server.client import NextcloudClient
logger = logging.getLogger(__name__)
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def nc_client() -> NextcloudClient: def nc_client() -> NextcloudClient:
""" """
@@ -50,7 +53,7 @@ def test_note_with_embedded_image(nc_client: NextcloudClient, test_image):
initial_content = "# Embedded Image Test\n\nThis note demonstrates how to properly embed images in Nextcloud Notes." initial_content = "# Embedded Image Test\n\nThis note demonstrates how to properly embed images in Nextcloud Notes."
# Create the note # Create the note
print(f"Creating test note: {note_title}") logger.info(f"Creating test note: {note_title}")
note = nc_client.notes_create_note( note = nc_client.notes_create_note(
title=note_title, title=note_title,
content=initial_content, content=initial_content,
@@ -58,7 +61,7 @@ def test_note_with_embedded_image(nc_client: NextcloudClient, test_image):
) )
note_id = note["id"] note_id = note["id"]
note_etag = note["etag"] note_etag = note["etag"]
print(f"Note created with ID: {note_id}") logger.info(f"Note created with ID: {note_id}")
try: try:
# Read the test image content # Read the test image content
@@ -69,14 +72,14 @@ def test_note_with_embedded_image(nc_client: NextcloudClient, test_image):
attachment_filename = f"test_image_{unique_id}.png" attachment_filename = f"test_image_{unique_id}.png"
# Upload the image as an attachment # Upload the image as an attachment
print(f"Uploading image attachment '{attachment_filename}' to note {note_id}...") logger.info(f"Uploading image attachment '{attachment_filename}' to note {note_id}...")
upload_response = nc_client.add_note_attachment( upload_response = nc_client.add_note_attachment(
note_id=note_id, note_id=note_id,
filename=attachment_filename, filename=attachment_filename,
content=image_content, content=image_content,
mime_type="image/png" mime_type="image/png"
) )
print(f"Image uploaded: {upload_response}") logger.info(f"Image uploaded: {upload_response}")
# Update the note content to include the embedded image using Markdown syntax # Update the note content to include the embedded image using Markdown syntax
# This is the correct syntax for embedding images in Nextcloud Notes # This is the correct syntax for embedding images in Nextcloud Notes
@@ -98,7 +101,7 @@ This note demonstrates how to properly embed images in Nextcloud Notes.
""" """
# Update the note with the image references # Update the note with the image references
print("Updating note content with image references...") logger.info("Updating note content with image references...")
updated_note = nc_client.notes_update_note( updated_note = nc_client.notes_update_note(
note_id=note_id, note_id=note_id,
etag=note_etag, etag=note_etag,
@@ -108,7 +111,7 @@ This note demonstrates how to properly embed images in Nextcloud Notes.
# Verify the updated note has the correct content # Verify the updated note has the correct content
retrieved_note = nc_client.notes_get_note(note_id=note_id) retrieved_note = nc_client.notes_get_note(note_id=note_id)
assert ".attachments." in retrieved_note["content"], "Image reference not found in note content" assert ".attachments." in retrieved_note["content"], "Image reference not found in note content"
print("Note updated successfully with image references") logger.info("Note updated successfully with image references")
# Verify we can retrieve the image attachment # Verify we can retrieve the image attachment
retrieved_content, mime_type = nc_client.get_note_attachment( retrieved_content, mime_type = nc_client.get_note_attachment(
@@ -118,9 +121,9 @@ This note demonstrates how to properly embed images in Nextcloud Notes.
assert len(retrieved_content) > 0, "Retrieved image content is empty" assert len(retrieved_content) > 0, "Retrieved image content is empty"
assert mime_type.startswith("image/"), f"Expected image mime type, got {mime_type}" assert mime_type.startswith("image/"), f"Expected image mime type, got {mime_type}"
print("Test completed successfully - image was embedded in the note and can be retrieved") logger.info("Test completed successfully - image was embedded in the note and can be retrieved")
finally: finally:
# Clean up - delete the test note # Clean up - delete the test note
print(f"Cleaning up - deleting test note {note_id}") logger.info(f"Cleaning up - deleting test note {note_id}")
nc_client.notes_delete_note(note_id=note_id) nc_client.notes_delete_note(note_id=note_id)
+14 -11
View File
@@ -1,10 +1,13 @@
import pytest import pytest
import os import os
import time import time
import logging
import uuid import uuid
from httpx import HTTPStatusError from httpx import HTTPStatusError
from nextcloud_mcp_server.client import NextcloudClient from nextcloud_mcp_server.client import NextcloudClient
logger = logging.getLogger(__name__)
# Tests assume NEXTCLOUD_HOST, NEXTCLOUD_USERNAME, NEXTCLOUD_PASSWORD env vars are set # Tests assume NEXTCLOUD_HOST, NEXTCLOUD_USERNAME, NEXTCLOUD_PASSWORD env vars are set
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
@@ -34,7 +37,7 @@ def test_attachment_remains_after_note_deletion(nc_client: NextcloudClient):
try: try:
# Create the note # Create the note
print(f"Creating note: {note_title}") logger.info(f"Creating note: {note_title}")
created_note = nc_client.notes_create_note( created_note = nc_client.notes_create_note(
title=note_title, title=note_title,
content=note_content, content=note_content,
@@ -42,7 +45,7 @@ def test_attachment_remains_after_note_deletion(nc_client: NextcloudClient):
) )
assert created_note and "id" in created_note assert created_note and "id" in created_note
note_id = created_note["id"] note_id = created_note["id"]
print(f"Note created with ID: {note_id}") logger.info(f"Note created with ID: {note_id}")
time.sleep(1) time.sleep(1)
# Create a simple text attachment # Create a simple text attachment
@@ -50,7 +53,7 @@ def test_attachment_remains_after_note_deletion(nc_client: NextcloudClient):
attachment_content = f"This is a test attachment for note {note_id}".encode('utf-8') attachment_content = f"This is a test attachment for note {note_id}".encode('utf-8')
# Attach the file to the note # Attach the file to the note
print(f"Attaching text file to note {note_id}...") logger.info(f"Attaching text file to note {note_id}...")
upload_response = nc_client.add_note_attachment( upload_response = nc_client.add_note_attachment(
note_id=note_id, note_id=note_id,
filename=attachment_filename, filename=attachment_filename,
@@ -59,7 +62,7 @@ def test_attachment_remains_after_note_deletion(nc_client: NextcloudClient):
) )
assert upload_response["status_code"] in [201, 204] assert upload_response["status_code"] in [201, 204]
print(f"Attachment added successfully (Status: {upload_response['status_code']}).") logger.info(f"Attachment added successfully (Status: {upload_response['status_code']}).")
time.sleep(1) time.sleep(1)
# Verify the attachment exists # Verify the attachment exists
@@ -69,30 +72,30 @@ def test_attachment_remains_after_note_deletion(nc_client: NextcloudClient):
) )
assert content == attachment_content, "Attachment content mismatch" assert content == attachment_content, "Attachment content mismatch"
print("Attachment verified") logger.info("Attachment verified")
# Now delete the note # Now delete the note
print(f"Deleting note ID: {note_id}") logger.info(f"Deleting note ID: {note_id}")
nc_client.notes_delete_note(note_id=note_id) nc_client.notes_delete_note(note_id=note_id)
print(f"Note deleted successfully.") logger.info(f"Note deleted successfully.")
time.sleep(1) time.sleep(1)
# Verify the note is deleted # Verify the note is deleted
with pytest.raises(HTTPStatusError) as excinfo: with pytest.raises(HTTPStatusError) as excinfo:
nc_client.notes_get_note(note_id=note_id) nc_client.notes_get_note(note_id=note_id)
assert excinfo.value.response.status_code == 404 assert excinfo.value.response.status_code == 404
print(f"Verified note deletion (404 Not Found)") logger.info(f"Verified note deletion (404 Not Found)")
# Now check if the attachment still exists (expected behavior: it should) # Now check if the attachment still exists (expected behavior: it should)
print(f"Checking if attachment still exists after note deletion...") logger.info(f"Checking if attachment still exists after note deletion...")
orphaned_content, orphaned_mime = nc_client.get_note_attachment( orphaned_content, orphaned_mime = nc_client.get_note_attachment(
note_id=note_id, note_id=note_id,
filename=attachment_filename filename=attachment_filename
) )
# If we get here without an exception, the attachment still exists # If we get here without an exception, the attachment still exists
print("CONFIRMED: Attachment still exists after note deletion") logger.info("CONFIRMED: Attachment still exists after note deletion")
print("This is the expected behavior of the Nextcloud Notes app") logger.info("This is the expected behavior of the Nextcloud Notes app")
assert orphaned_content == attachment_content, "Orphaned attachment content mismatch" assert orphaned_content == attachment_content, "Orphaned attachment content mismatch"
finally: finally:
+14 -11
View File
@@ -2,11 +2,14 @@ import pytest
import os import os
import time import time
import uuid import uuid
import logging
import tempfile import tempfile
from httpx import HTTPStatusError from httpx import HTTPStatusError
from PIL import Image, ImageDraw from PIL import Image, ImageDraw
from nextcloud_mcp_server.client import NextcloudClient from nextcloud_mcp_server.client import NextcloudClient
logger = logging.getLogger(__name__)
# Tests assume NEXTCLOUD_HOST, NEXTCLOUD_USERNAME, NEXTCLOUD_PASSWORD env vars are set # Tests assume NEXTCLOUD_HOST, NEXTCLOUD_USERNAME, NEXTCLOUD_PASSWORD env vars are set
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
@@ -57,7 +60,7 @@ def test_note_with_image_attachment(nc_client: NextcloudClient, test_image):
try: try:
# Create the note # Create the note
print(f"Creating note: {note_title}") logger.info(f"Creating note: {note_title}")
created_note = nc_client.notes_create_note( created_note = nc_client.notes_create_note(
title=note_title, title=note_title,
content=note_content, content=note_content,
@@ -65,7 +68,7 @@ def test_note_with_image_attachment(nc_client: NextcloudClient, test_image):
) )
assert created_note and "id" in created_note assert created_note and "id" in created_note
note_id = created_note["id"] note_id = created_note["id"]
print(f"Note created with ID: {note_id}") logger.info(f"Note created with ID: {note_id}")
time.sleep(1) time.sleep(1)
# Read the test image # Read the test image
@@ -74,7 +77,7 @@ def test_note_with_image_attachment(nc_client: NextcloudClient, test_image):
# Attach the image to the note # Attach the image to the note
attachment_filename = f"test_image_{unique_id}.png" attachment_filename = f"test_image_{unique_id}.png"
print(f"Attaching image to note {note_id}...") logger.info(f"Attaching image to note {note_id}...")
upload_response = nc_client.add_note_attachment( upload_response = nc_client.add_note_attachment(
note_id=note_id, note_id=note_id,
filename=attachment_filename, filename=attachment_filename,
@@ -83,7 +86,7 @@ def test_note_with_image_attachment(nc_client: NextcloudClient, test_image):
) )
assert upload_response["status_code"] in [201, 204] assert upload_response["status_code"] in [201, 204]
print(f"Image attached successfully (Status: {upload_response['status_code']}).") logger.info(f"Image attached successfully (Status: {upload_response['status_code']}).")
time.sleep(1) time.sleep(1)
# Update the note content to include a reference to the attached image # Update the note content to include a reference to the attached image
@@ -100,7 +103,7 @@ Files path: `/Notes/.attachments.{note_id}/{attachment_filename}`
""" """
# Update the note content # Update the note content
print("Updating note content to include image reference...") logger.info("Updating note content to include image reference...")
updated_note = nc_client.notes_update_note( updated_note = nc_client.notes_update_note(
note_id=note_id, note_id=note_id,
etag=created_note["etag"], etag=created_note["etag"],
@@ -109,8 +112,8 @@ Files path: `/Notes/.attachments.{note_id}/{attachment_filename}`
# Retrieve the note to verify content # Retrieve the note to verify content
retrieved_note = nc_client.notes_get_note(note_id=note_id) retrieved_note = nc_client.notes_get_note(note_id=note_id)
print("Retrieved note content:") logger.info("Retrieved note content:")
print(retrieved_note["content"]) logger.info(retrieved_note["content"])
# Verify the image attachment can be retrieved # Verify the image attachment can be retrieved
content, mime_type = nc_client.get_note_attachment( content, mime_type = nc_client.get_note_attachment(
@@ -120,14 +123,14 @@ Files path: `/Notes/.attachments.{note_id}/{attachment_filename}`
assert content == image_content, "Attachment content mismatch" assert content == image_content, "Attachment content mismatch"
assert mime_type.startswith("image/"), f"Expected image mime type, got {mime_type}" assert mime_type.startswith("image/"), f"Expected image mime type, got {mime_type}"
print("Image attachment verified") logger.info("Image attachment verified")
finally: finally:
# Cleanup # Cleanup
if note_id: if note_id:
print(f"Cleaning up - deleting note ID: {note_id}") logger.info(f"Cleaning up - deleting note ID: {note_id}")
try: try:
nc_client.notes_delete_note(note_id=note_id) nc_client.notes_delete_note(note_id=note_id)
print(f"Note {note_id} deleted") logger.info(f"Note {note_id} deleted")
except Exception as e: except Exception as e:
print(f"Error during cleanup: {e}") logger.info(f"Error during cleanup: {e}")