ADR search and handling categories in notes

This commit is contained in:
Chris Coutinho
2025-05-07 23:06:22 +02:00
parent c6ce5bd338
commit c1763ebc6a
7 changed files with 776 additions and 54 deletions
+4 -1
View File
@@ -76,17 +76,20 @@ def temporary_note_with_attachment(nc_client: NextcloudClient, temporary_note: d
"""
note_data = temporary_note
note_id = note_data["id"]
note_category = note_data.get("category") # Get category from the note data
unique_suffix = uuid.uuid4().hex[:8]
attachment_filename = f"temp_attach_{unique_suffix}.txt"
attachment_content = f"Content for {attachment_filename}".encode('utf-8')
attachment_mime = "text/plain"
logger.info(f"Adding attachment '{attachment_filename}' to temporary note ID: {note_id}")
logger.info(f"Adding attachment '{attachment_filename}' to temporary note ID: {note_id} (category: '{note_category or ''}')")
try:
# Pass the category to add_note_attachment
upload_response = nc_client.add_note_attachment(
note_id=note_id,
filename=attachment_filename,
content=attachment_content,
category=note_category, # Pass the fetched category
mime_type=attachment_mime
)
assert upload_response.get("status_code") in [201, 204], f"Failed to upload attachment: {upload_response}"
+167 -4
View File
@@ -20,11 +20,14 @@ def test_attachments_add_and_get(nc_client: NextcloudClient, temporary_note_with
"""
note_data, attachment_filename, attachment_content = temporary_note_with_attachment
note_id = note_data["id"]
note_category = note_data.get("category") # Get category from fixture data
logger.info(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 = nc_client.get_note_attachment(
note_id=note_id,
filename=attachment_filename
filename=attachment_filename,
category=note_category
)
logger.info(f"Attachment retrieved. Mime type: {retrieved_mime}, Size: {len(retrieved_content)} bytes")
@@ -49,10 +52,12 @@ def test_attachments_add_to_note_with_category(nc_client: NextcloudClient, tempo
attachment_mime = "text/plain"
logger.info(f"Attempting to add attachment '{attachment_filename}' to note ID: {note_id}")
# Pass category to add_note_attachment
upload_response = nc_client.add_note_attachment(
note_id=note_id,
filename=attachment_filename,
content=attachment_content,
category=note_category, # Pass the note's category
mime_type=attachment_mime
)
assert upload_response and "status_code" in upload_response
@@ -62,9 +67,11 @@ def test_attachments_add_to_note_with_category(nc_client: NextcloudClient, tempo
# Get and Verify Attachment
logger.info(f"Attempting to retrieve attachment '{attachment_filename}' from note ID: {note_id}")
# Pass category to get_note_attachment
retrieved_content, retrieved_mime = nc_client.get_note_attachment(
note_id=note_id,
filename=attachment_filename
filename=attachment_filename,
category=note_category # Pass the note's category
)
logger.info(f"Attachment retrieved. Mime type: {retrieved_mime}, Size: {len(retrieved_content)} bytes")
@@ -80,6 +87,7 @@ def test_attachments_cleanup_on_note_delete(nc_client: NextcloudClient, temporar
"""
note_data, attachment_filename, _ = temporary_note_with_attachment
note_id = note_data["id"]
note_category = note_data.get("category") # Get category from fixture data
# Fixture setup already added the attachment.
# Fixture teardown (from temporary_note) will delete the note.
@@ -105,13 +113,168 @@ def test_attachments_cleanup_on_note_delete(nc_client: NextcloudClient, temporar
# Verify Attachment Is Deleted (via 404 on GET)
logger.info(f"Verifying attachment '{attachment_filename}' is deleted for note ID: {note_id}")
with pytest.raises(HTTPStatusError) as excinfo_attach:
# 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.
nc_client.get_note_attachment(
note_id=note_id,
filename=attachment_filename
filename=attachment_filename,
category=note_category # Pass category, though note fetch should fail first
)
# Expect 404 because the parent directory (.attachments.NOTE_ID) should be gone
# Expect 404 because the note itself is gone
assert excinfo_attach.value.response.status_code == 404
logger.info(f"Attachment '{attachment_filename}' correctly not found (404) after note deletion.")
# Directly verify attachment directory doesn't exist using WebDAV PROPFIND
logger.info(f"Directly verifying attachment directory doesn't exist via PROPFIND")
webdav_base = nc_client._get_webdav_base_path()
category_path_part = f"{note_category}/" if note_category else ""
attachment_dir_path = f"{webdav_base}/Notes/{category_path_part}.attachments.{note_id}"
propfind_headers = {"Depth": "0", "OCS-APIRequest": "true"}
try:
propfind_resp = nc_client._client.request("PROPFIND", attachment_dir_path, headers=propfind_headers)
status = propfind_resp.status_code
if status in [200, 207]: # Successful PROPFIND means directory exists
logger.error(f"Attachment directory still exists! PROPFIND returned {status}")
assert False, f"Expected attachment directory to be gone, but PROPFIND returned {status}!"
except HTTPStatusError as e:
assert e.response.status_code == 404, f"Expected PROPFIND to fail with 404, got {e.response.status_code}"
logger.info(f"Verified attachment directory does not exist via PROPFIND (404 received)")
# Note: The temporary_note fixture will still run its cleanup,
# but it will find the note already deleted (404) and handle it gracefully.
def test_attachments_category_change_handling(nc_client: NextcloudClient):
"""
Tests attachment handling when a note's category is changed.
Verifies attachment retrieval works before and after category change,
and that cleanup targets the correct final location.
"""
note_id = None
initial_category = "CategoryA"
new_category = "CategoryB"
unique_suffix = uuid.uuid4().hex[:8]
note_title = f"Category Change Test {unique_suffix}"
attachment_filename = f"cat_change_{unique_suffix}.txt"
attachment_content = f"Content for {attachment_filename}".encode('utf-8')
try:
# 1. Create note with initial category
logger.info(f"Creating note '{note_title}' in category '{initial_category}'")
created_note = nc_client.notes_create_note(
title=note_title, content="Initial content", category=initial_category
)
note_id = created_note["id"]
etag1 = created_note["etag"]
logger.info(f"Note created with ID: {note_id}, Etag: {etag1}")
time.sleep(1)
# 2. Add attachment (passing initial category)
logger.info(f"Adding attachment '{attachment_filename}' to note {note_id} (in {initial_category})")
upload_response = nc_client.add_note_attachment(
note_id=note_id, filename=attachment_filename, content=attachment_content, category=initial_category, mime_type="text/plain"
)
assert upload_response["status_code"] in [201, 204]
logger.info("Attachment added successfully.")
time.sleep(1)
# 3. Verify attachment retrieval from initial category (passing initial category)
logger.info(f"Verifying attachment retrieval from initial category '{initial_category}'")
retrieved_content1, _ = nc_client.get_note_attachment(note_id=note_id, filename=attachment_filename, category=initial_category)
assert retrieved_content1 == attachment_content
logger.info("Attachment retrieved successfully from initial category.")
# 4. Update note category
logger.info(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 = nc_client.notes_get_note(note_id=note_id)
current_etag = current_note_data["etag"]
updated_note = nc_client.notes_update_note(
note_id=note_id, etag=current_etag, category=new_category, title=note_title, content="Updated content" # Pass required fields
)
etag3 = updated_note["etag"]
assert updated_note["category"] == new_category
logger.info(f"Note category updated successfully. New Etag: {etag3}")
time.sleep(1)
# 5. Verify attachment retrieval from *new* category (passing new category)
logger.info(f"Verifying attachment retrieval from new category '{new_category}'")
retrieved_content2, _ = nc_client.get_note_attachment(note_id=note_id, filename=attachment_filename, category=new_category)
assert retrieved_content2 == attachment_content
logger.info("Attachment retrieved successfully from new category.")
# 5.1 Verify old category attachment directory is gone via WebDAV PROPFIND
logger.info(f"Directly checking if old attachment directory exists in WebDAV")
webdav_base = nc_client._get_webdav_base_path()
old_attachment_dir_path = f"{webdav_base}/Notes/{initial_category}/.attachments.{note_id}"
propfind_headers = {"Depth": "0", "OCS-APIRequest": "true"}
try:
propfind_resp = nc_client._client.request("PROPFIND", old_attachment_dir_path, headers=propfind_headers)
status = propfind_resp.status_code
if status in [200, 207]: # Successful PROPFIND means directory exists
logger.error(f"Old attachment directory still exists! PROPFIND returned {status}")
assert False, f"Expected old directory to be gone, but PROPFIND returned {status} - directory still exists!"
except HTTPStatusError as e:
assert e.response.status_code == 404, f"Expected PROPFIND to fail with 404, got {e.response.status_code}"
logger.info(f"Verified old attachment directory does not exist via PROPFIND (404 received)")
# 5.2 Verify new category attachment directory exists via WebDAV PROPFIND
logger.info(f"Directly checking if new attachment directory exists in WebDAV")
new_attachment_dir_path = f"{webdav_base}/Notes/{new_category}/.attachments.{note_id}"
try:
propfind_resp = nc_client._client.request("PROPFIND", new_attachment_dir_path, headers=propfind_headers)
status = propfind_resp.status_code
assert status in [207, 200], f"Expected PROPFIND to return success (207/200), got {status}"
logger.info(f"Verified new attachment directory exists via PROPFIND ({status} received)")
except HTTPStatusError as e:
logger.error(f"New attachment directory not found! PROPFIND failed with {e.response.status_code}")
assert False, f"Expected new attachment directory to exist, but PROPFIND failed with {e.response.status_code}"
finally:
# 6. Cleanup: Delete the note (client should use the *final* category for cleanup path)
if note_id:
logger.info(f"Cleaning up note ID: {note_id} (last known category: '{new_category}')")
try:
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:
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
nc_client.get_note_attachment(note_id=note_id, filename=attachment_filename, category=new_category)
assert excinfo_attach_del.value.response.status_code == 404
logger.info("Verified attachment cannot be retrieved after note deletion (404).")
# 6.1 Verify both old and new attachment directories are gone via WebDAV PROPFIND
logger.info("Directly verifying attachment directories don't exist via PROPFIND")
webdav_base = nc_client._get_webdav_base_path()
# Check new category attachment directory
new_attachment_dir_path = f"{webdav_base}/Notes/{new_category}/.attachments.{note_id}"
propfind_headers = {"Depth": "0", "OCS-APIRequest": "true"}
try:
resp = nc_client._client.request("PROPFIND", new_attachment_dir_path, headers=propfind_headers)
if resp.status_code in [200, 207]: # Successful PROPFIND means directory exists
assert False, f"New category attachment directory still exists!"
except HTTPStatusError as e:
assert e.response.status_code == 404, f"Expected PROPFIND to fail with 404, got {e.response.status_code}"
logger.info("Verified new category attachment directory is gone via PROPFIND")
# Check old category attachment directory
old_attachment_dir_path = f"{webdav_base}/Notes/{initial_category}/.attachments.{note_id}"
try:
resp = nc_client._client.request("PROPFIND", old_attachment_dir_path, headers=propfind_headers)
if resp.status_code in [200, 207]: # Successful PROPFIND means directory exists
assert False, f"Old category attachment directory still exists!"
except HTTPStatusError as e:
assert e.response.status_code == 404, f"Expected PROPFIND to fail with 404, got {e.response.status_code}"
logger.info("Verified old category attachment directory is gone via PROPFIND")
logger.info("Verified all attachment directories are properly cleaned up.")
except Exception as e:
logger.error(f"Error during cleanup for note {note_id}: {e}")
+48 -4
View File
@@ -53,17 +53,34 @@ def test_note_with_embedded_image(nc_client: NextcloudClient, temporary_note: di
attachment_filename = f"test_image_{unique_suffix}.png" # Make filename unique per run
# 1. Upload the image as an attachment
logger.info(f"Uploading image attachment '{attachment_filename}' to note {note_id}...")
note_category = note_data.get("category") # Get category from fixture data
logger.info(f"Uploading image attachment '{attachment_filename}' to note {note_id} (category: '{note_category or ''}')...")
upload_response = nc_client.add_note_attachment(
note_id=note_id,
filename=attachment_filename,
content=image_content,
category=note_category, # Pass the category
mime_type="image/png"
)
assert upload_response and upload_response.get("status_code") in [201, 204]
logger.info(f"Image uploaded successfully (Status: {upload_response.get('status_code')}).")
time.sleep(1) # Allow potential processing time
# 1.1 Verify attachment directory exists via WebDAV PROPFIND
logger.info(f"Directly checking if attachment directory exists in WebDAV")
webdav_base = nc_client._get_webdav_base_path()
category_path_part = f"{note_category}/" if note_category else ""
attachment_dir_path = f"{webdav_base}/Notes/{category_path_part}.attachments.{note_id}"
propfind_headers = {"Depth": "0", "OCS-APIRequest": "true"}
try:
propfind_resp = nc_client._client.request("PROPFIND", attachment_dir_path, headers=propfind_headers)
status = propfind_resp.status_code
assert status in [207, 200], f"Expected PROPFIND to return success (207/200), got {status}"
logger.info(f"Verified attachment directory exists via PROPFIND ({status} received)")
except HTTPStatusError as e:
logger.error(f"Attachment directory not found! PROPFIND failed with {e.response.status_code}")
assert False, f"Expected attachment directory to exist, but PROPFIND failed with {e.response.status_code}"
# 2. Update the note content to include the embedded image references
updated_content = f"""{note_data['content']}
@@ -94,13 +111,40 @@ def test_note_with_embedded_image(nc_client: NextcloudClient, temporary_note: di
logger.info("Verified image reference exists in updated note content.")
# 4. Verify the image attachment can be retrieved
logger.info(f"Retrieving image attachment '{attachment_filename}'...")
logger.info(f"Retrieving image attachment '{attachment_filename}' (category: '{note_category or ''}')...")
# Pass category to get_note_attachment
retrieved_img_content, mime_type = nc_client.get_note_attachment(
note_id=note_id,
filename=attachment_filename
filename=attachment_filename,
category=note_category
)
assert retrieved_img_content == image_content
assert mime_type.startswith("image/png")
logger.info("Successfully retrieved and verified image attachment content and mime type.")
# Note cleanup is handled by the temporary_note fixture
# 5. Manually trigger deletion to verify cleanup (instead of waiting for fixture teardown)
logger.info(f"Manually deleting note ID: {note_id} to verify proper attachment cleanup")
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:
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).")
# 7. Verify attachment directory is deleted via WebDAV PROPFIND
logger.info(f"Directly verifying attachment directory doesn't exist via PROPFIND")
try:
propfind_resp = nc_client._client.request("PROPFIND", attachment_dir_path, headers=propfind_headers)
status = propfind_resp.status_code
if status in [200, 207]: # Successful PROPFIND means directory exists
logger.error(f"Attachment directory still exists! PROPFIND returned {status}")
assert False, f"Expected attachment directory to be gone, but PROPFIND returned {status}!"
except HTTPStatusError as e:
assert e.response.status_code == 404, f"Expected PROPFIND to fail with 404, got {e.response.status_code}"
logger.info(f"Verified attachment directory does not exist via PROPFIND (404 received)")
# Note: The temporary_note fixture will still run its cleanup,
# but it will find the note already deleted (404) and handle it gracefully.
+161
View File
@@ -0,0 +1,161 @@
import pytest
import logging
import time
import uuid
from httpx import HTTPStatusError
from nextcloud_mcp_server.client import NextcloudClient
logger = logging.getLogger(__name__)
# Mark all tests in this module as integration tests
pytestmark = pytest.mark.integration
def test_category_change_cleans_up_old_attachments_directory(nc_client: NextcloudClient):
"""
Tests that when a note's category is changed, the old attachment directory is properly cleaned up.
"""
note_id = None
initial_category = "CategoryTest1"
new_category = "CategoryTest2"
unique_suffix = uuid.uuid4().hex[:8]
note_title = f"Category Cleanup Test {unique_suffix}"
attachment_filename = f"cleanup_test_{unique_suffix}.txt"
attachment_content = f"Content for {attachment_filename}".encode('utf-8')
try:
# 1. Create note with initial category
logger.info(f"Creating note '{note_title}' in category '{initial_category}'")
created_note = nc_client.notes_create_note(
title=note_title, content="Initial content", category=initial_category
)
note_id = created_note["id"]
etag1 = created_note["etag"]
logger.info(f"Note created with ID: {note_id}, Etag: {etag1}")
time.sleep(1)
# 2. Add attachment (passing initial category)
logger.info(f"Adding attachment '{attachment_filename}' to note {note_id} (in {initial_category})")
upload_response = nc_client.add_note_attachment(
note_id=note_id, filename=attachment_filename, content=attachment_content, category=initial_category, mime_type="text/plain"
)
assert upload_response["status_code"] in [201, 204]
logger.info("Attachment added successfully.")
time.sleep(1)
# 3. Verify attachment retrieval from initial category
logger.info(f"Verifying attachment retrieval from initial category '{initial_category}'")
retrieved_content1, _ = nc_client.get_note_attachment(note_id=note_id, filename=attachment_filename, category=initial_category)
assert retrieved_content1 == attachment_content
logger.info("Attachment retrieved successfully from initial category.")
# 4. Construct and check the WebDAV path for the initial category's attachment directory
initial_webdav_path = f"Notes/{initial_category}/.attachments.{note_id}"
logger.info(f"Initial WebDAV path for attachments: {initial_webdav_path}")
# Here we would check if the directory exists, but the WebDAV client doesn't directly
# expose directory listing functionality, so we'll infer from attachment retrieval success
# 5. Update note category
logger.info(f"Updating note {note_id} category from '{initial_category}' to '{new_category}'")
current_note_data = nc_client.notes_get_note(note_id=note_id)
current_etag = current_note_data["etag"]
updated_note = nc_client.notes_update_note(
note_id=note_id, etag=current_etag, category=new_category, title=note_title, content="Updated content"
)
etag3 = updated_note["etag"]
assert updated_note["category"] == new_category
logger.info(f"Note category updated successfully. New Etag: {etag3}")
time.sleep(1)
# 6. Verify attachment retrieval from new category
logger.info(f"Verifying attachment retrieval from new category '{new_category}'")
retrieved_content2, _ = nc_client.get_note_attachment(note_id=note_id, filename=attachment_filename, category=new_category)
assert retrieved_content2 == attachment_content
logger.info("Attachment retrieved successfully from new category.")
# 7. Try to retrieve from old category - this should fail
logger.info(f"Trying to retrieve attachment from old category '{initial_category}' - should fail")
try:
nc_client.get_note_attachment(note_id=note_id, filename=attachment_filename, category=initial_category)
# If we get here, it means the old directory still exists (a problem)
logger.error("ISSUE DETECTED: Was able to retrieve attachment from old category path!")
assert False, "Old category attachment directory still exists and accessible!"
except HTTPStatusError as e:
# This is the expected outcome - old directory should be gone
logger.info(f"Correctly got error accessing old category path: {e.response.status_code}")
assert e.response.status_code == 404, f"Expected 404, got {e.response.status_code}"
logger.info("Verified old category attachment directory is not accessible (good!)")
# 7.1 Directly check old attachment directory existence using WebDAV PROPFIND
logger.info(f"Directly checking if old attachment directory exists in WebDAV")
webdav_base = nc_client._get_webdav_base_path()
old_attachment_dir_path = f"{webdav_base}/Notes/{initial_category}/.attachments.{note_id}"
propfind_headers = {"Depth": "0", "OCS-APIRequest": "true"}
try:
propfind_resp = nc_client._client.request("PROPFIND", old_attachment_dir_path, headers=propfind_headers)
status = propfind_resp.status_code
if status in [200, 207]: # Success codes indicate the directory exists (a problem)
logger.error(f"Old attachment directory still exists! PROPFIND returned {status}")
assert False, f"Expected old attachment directory to be gone, but it still exists (PROPFIND returned {status})!"
# If we got another status code (like 404), it's also good - the directory doesn't exist
logger.info(f"Verified old attachment directory does not exist (PROPFIND returned {status})")
except HTTPStatusError as e:
# 404 is expected - directory should not exist
assert e.response.status_code == 404, f"Expected PROPFIND to fail with 404, got {e.response.status_code}"
logger.info(f"Verified old attachment directory does not exist via PROPFIND (404 received)")
finally:
# 8. Cleanup: Delete the note
if note_id:
logger.info(f"Cleaning up note ID: {note_id}")
try:
nc_client.notes_delete_note(note_id=note_id)
logger.info(f"Note {note_id} deleted.")
time.sleep(1)
# 9. Verify both old and new attachment paths are gone
logger.info("Verifying all attachment paths are gone")
with pytest.raises(HTTPStatusError) as excinfo_new:
nc_client.get_note_attachment(note_id=note_id, filename=attachment_filename, category=new_category)
assert excinfo_new.value.response.status_code == 404
with pytest.raises(HTTPStatusError) as excinfo_old:
nc_client.get_note_attachment(note_id=note_id, filename=attachment_filename, category=initial_category)
assert excinfo_old.value.response.status_code == 404
# 9.1 Directly verify directories don't exist using WebDAV PROPFIND
logger.info("Directly verifying attachment directories don't exist via PROPFIND")
webdav_base = nc_client._get_webdav_base_path()
# Check new category attachment directory
new_attachment_dir_path = f"{webdav_base}/Notes/{new_category}/.attachments.{note_id}"
propfind_headers = {"Depth": "0", "OCS-APIRequest": "true"}
try:
propfind_resp = nc_client._client.request("PROPFIND", new_attachment_dir_path, headers=propfind_headers)
status = propfind_resp.status_code
if status in [200, 207]: # Success codes indicate the directory exists (a problem)
logger.error(f"New category attachment directory still exists! PROPFIND returned {status}")
assert False, f"Expected new category attachment directory to be gone, but it still exists (PROPFIND returned {status})!"
# If we got another status code (like 404), it's also good - the directory doesn't exist
logger.info(f"Verified new category attachment directory does not exist (PROPFIND returned {status})")
except HTTPStatusError as e:
assert e.response.status_code == 404, f"Expected PROPFIND to fail with 404, got {e.response.status_code}"
logger.info("Verified new category attachment directory is gone via PROPFIND")
# Check old category attachment directory
old_attachment_dir_path = f"{webdav_base}/Notes/{initial_category}/.attachments.{note_id}"
try:
propfind_resp = nc_client._client.request("PROPFIND", old_attachment_dir_path, headers=propfind_headers)
status = propfind_resp.status_code
if status in [200, 207]: # Success codes indicate the directory exists (a problem)
logger.error(f"Old category attachment directory still exists! PROPFIND returned {status}")
assert False, f"Expected old category attachment directory to be gone, but it still exists (PROPFIND returned {status})!"
# If we got another status code (like 404), it's also good - the directory doesn't exist
logger.info(f"Verified old category attachment directory does not exist (PROPFIND returned {status})")
except HTTPStatusError as e:
assert e.response.status_code == 404, f"Expected PROPFIND to fail with 404, got {e.response.status_code}"
logger.info("Verified old category attachment directory is gone via PROPFIND")
logger.info("Verified all attachment directories are properly cleaned up.")
except Exception as e:
logger.error(f"Error during cleanup for note {note_id}: {e}")