Fix tests

This commit is contained in:
Chris Coutinho
2025-05-06 16:44:33 +02:00
parent e1de793af8
commit dea882c2f5
5 changed files with 249 additions and 209 deletions
+71 -18
View File
@@ -154,27 +154,68 @@ class NextcloudClient:
"category": note.get("category"), "category": note.get("category"),
"modified": note.get("modified"), "modified": note.get("modified"),
} }
) )
return search_results raise e
def delete_webdav_resource(self, *, path: str):
"""Delete a resource (file or directory) via WebDAV DELETE."""
# Ensure path ends with a slash if it's a directory
if not path.endswith('/'):
# This is a heuristic; a more robust solution would check resource type first
# but for the specific case of deleting the attachment directory, this is acceptable.
path_with_slash = f"{path}/"
else:
path_with_slash = path
webdav_path = f"{self._get_webdav_base_path()}/{path_with_slash.lstrip('/')}"
logger.info("Deleting WebDAV resource: %s", webdav_path)
headers = {"OCS-APIRequest": "true"}
try:
response = self._client.delete(webdav_path, headers=headers)
response.raise_for_status() # Raises for 4xx/5xx status codes
logger.info("Successfully deleted WebDAV resource '%s' (Status: %s)", webdav_path, response.status_code)
# DELETE typically returns 204 No Content on success
return {"status_code": response.status_code}
except HTTPStatusError as e:
logger.error(
"HTTP error deleting WebDAV resource '%s': %s",
webdav_path,
e,
)
# It's expected to get a 404 if the resource doesn't exist, which is fine.
# We only re-raise if it's not a 404.
if e.response.status_code != 404:
raise e
else:
logger.info("Resource '%s' not found, no deletion needed.", webdav_path)
return {"status_code": 404} # Indicate resource was not found
except Exception as e:
logger.error(
"Unexpected error deleting WebDAV resource '%s': %s",
webdav_path,
e,
)
raise e
def notes_delete_note(self, *, note_id: int): def notes_delete_note(self, *, note_id: int):
# First delete the note through the Notes API # First delete the note through the Notes API
response = self._client.delete(f"/apps/notes/api/v1/notes/{note_id}") response = self._client.delete(f"/apps/notes/api/v1/notes/{note_id}")
response.raise_for_status() response.raise_for_status()
json_response = response.json() json_response = response.json()
# Note that we don't try to delete the attachments directory via WebDAV # Now, attempt to delete the associated attachments directory via WebDAV
# This is because Nextcloud Notes app does not delete the attachments # Add a trailing slash as suggested
# when a note is deleted - this is the expected behavior of the app attachment_dir_path = f"Notes/.attachments.{note_id}/"
# and not a bug. Attachments remain orphaned in the system. logger.info(f"Attempting to delete attachment directory for note {note_id} via WebDAV: {attachment_dir_path}")
try:
# If we did try to delete them, it would fail with a 401 Unauthorized self.delete_webdav_resource(path=attachment_dir_path)
# or CSRF check error because WebDAV requires a different authentication logger.info(f"Successfully attempted to delete attachment directory for note {note_id}.")
# method for DELETE operations than for GET/PUT operations. except Exception as e:
# Log the error but don't re-raise, as note deletion itself was successful
# The test_note_attachment_cleanup.py test explicitly verifies this behavior logger.error(f"Failed to delete attachment directory for note {note_id}: {e}")
# to ensure our understanding is correct.
return json_response return json_response
# Removed incorrect get_note_attachment method that used Notes API # Removed incorrect get_note_attachment method that used Notes API
@@ -200,14 +241,24 @@ class NextcloudClient:
if not mime_type: if not mime_type:
mime_type = "application/octet-stream" # Default if guessing fails mime_type = "application/octet-stream" # Default if guessing fails
headers = {"Content-Type": mime_type} headers = {"Content-Type": mime_type, "OCS-APIRequest": "true"}
try: try:
# First check if we can access WebDAV at all with current credentials # First check if we can access WebDAV at all with current credentials
# by checking the Notes directory # by checking the Notes directory
notes_dir_path = f"{webdav_base}/Notes" notes_dir_path = f"{webdav_base}/Notes"
logger.info("Testing WebDAV access to Notes directory: %s", notes_dir_path) logger.info("Testing WebDAV access to Notes directory: %s", notes_dir_path)
# Log details of the auth being used by the client for this specific request
if self._client.auth:
auth_header = self._client.auth.auth_flow(self._client.build_request("GET", notes_dir_path)).__next__().headers.get("Authorization")
logger.info("Authorization header for PROPFIND (Notes dir): %s", auth_header if auth_header else "Not present or generated by auth flow")
else:
logger.info("No httpx.Auth object configured on the client for PROPFIND (Notes dir).")
propfind_headers = {"Depth": "0", "OCS-APIRequest": "true"}
logger.info("Headers for PROPFIND (Notes dir): %s", propfind_headers)
notes_dir_response = self._client.request("PROPFIND", notes_dir_path, notes_dir_response = self._client.request("PROPFIND", notes_dir_path,
headers={"Depth": "0"}) headers=propfind_headers)
if notes_dir_response.status_code == 401: if notes_dir_response.status_code == 401:
logger.error("WebDAV authentication failed for Notes directory. Please verify WebDAV permissions.") logger.error("WebDAV authentication failed for Notes directory. Please verify WebDAV permissions.")
@@ -226,7 +277,9 @@ class NextcloudClient:
# Ensure the parent directory exists using MKCOL # Ensure the parent directory exists using MKCOL
parent_dir_path = f"{webdav_base}/Notes/.attachments.{note_id}" parent_dir_path = f"{webdav_base}/Notes/.attachments.{note_id}"
logger.info("Creating attachments directory: %s", parent_dir_path) logger.info("Creating attachments directory: %s", parent_dir_path)
mkcol_response = self._client.request("MKCOL", parent_dir_path) mkcol_headers = {"OCS-APIRequest": "true"}
logger.info("Headers for MKCOL (Attachments dir): %s", mkcol_headers)
mkcol_response = self._client.request("MKCOL", parent_dir_path, headers=mkcol_headers)
# MKCOL should return 201 Created or 405 Method Not Allowed (if exists) # MKCOL should return 201 Created or 405 Method Not Allowed (if exists)
# We can ignore 405, but raise for other errors # We can ignore 405, but raise for other errors
if mkcol_response.status_code not in [201, 405]: if mkcol_response.status_code not in [201, 405]:
+16
View File
@@ -0,0 +1,16 @@
import pytest
import os
import logging
from nextcloud_mcp_server.client import NextcloudClient
logger = logging.getLogger(__name__)
@pytest.fixture(scope="session")
def nc_client() -> NextcloudClient:
"""
Fixture to create a NextcloudClient instance for integration tests.
"""
assert os.getenv("NEXTCLOUD_HOST"), "NEXTCLOUD_HOST env var not set"
assert os.getenv("NEXTCLOUD_USERNAME"), "NEXTCLOUD_USERNAME env var not set"
assert os.getenv("NEXTCLOUD_PASSWORD"), "NEXTCLOUD_PASSWORD env var not set"
return NextcloudClient.from_env()
+80 -101
View File
@@ -190,40 +190,32 @@ def test_note_attachment_integration(nc_client: NextcloudClient):
attachment_mime = "text/plain" attachment_mime = "text/plain"
logger.info(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: # Assuming WebDAV should work now, directly call add_note_attachment
# 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( note_id=note_id,
note_id=note_id, filename=attachment_filename,
filename=attachment_filename, content=attachment_content,
content=attachment_content, mime_type=attachment_mime
mime_type=attachment_mime )
)
assert upload_response and "status_code" in upload_response
# If we get here, WebDAV is working - continue with attachment tests assert upload_response["status_code"] in [201, 204]
assert upload_response and "status_code" in upload_response logger.info(f"Attachment '{attachment_filename}' added successfully (Status: {upload_response['status_code']}).")
assert upload_response["status_code"] in [201, 204] time.sleep(1) # Allow time for upload processing
logger.info(f"Attachment '{attachment_filename}' added successfully (Status: {upload_response['status_code']}).")
time.sleep(1) # Allow time for upload processing
# --- Get and Verify Attachment --- # --- Get and Verify Attachment ---
logger.info(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
) )
logger.info(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
logger.info("Retrieved attachment content and mime type verified successfully.") logger.info("Retrieved attachment content and mime type verified successfully.")
except HTTPStatusError as e:
if e.response.status_code == 401:
pytest.skip("Skipping attachment tests due to WebDAV permission issues (401 Unauthorized)")
else:
raise # Re-raise other HTTP errors
finally: finally:
# --- Delete Note (Cleanup) --- # --- Delete Note (Cleanup) ---
@@ -274,39 +266,31 @@ def test_note_attachment_with_category_integration(nc_client: NextcloudClient):
attachment_mime = "text/plain" attachment_mime = "text/plain"
logger.info(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: # Assuming WebDAV should work now, directly call add_note_attachment
# 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( note_id=note_id,
note_id=note_id, filename=attachment_filename,
filename=attachment_filename, content=attachment_content,
content=attachment_content, mime_type=attachment_mime
mime_type=attachment_mime )
)
assert upload_response and "status_code" in upload_response
# If we get here, WebDAV is working - continue with attachment tests assert upload_response["status_code"] in [201, 204]
assert upload_response and "status_code" in upload_response logger.info(f"Attachment '{attachment_filename}' added successfully (Status: {upload_response['status_code']}).")
assert upload_response["status_code"] in [201, 204] time.sleep(1)
logger.info(f"Attachment '{attachment_filename}' added successfully (Status: {upload_response['status_code']}).")
time.sleep(1)
# --- Get and Verify Attachment --- # --- Get and Verify Attachment ---
logger.info(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
) )
logger.info(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
logger.info("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:
if e.response.status_code == 401:
pytest.skip("Skipping attachment tests due to WebDAV permission issues (401 Unauthorized)")
else:
raise # Re-raise other HTTP errors
finally: finally:
# --- Delete Note (Cleanup) --- # --- Delete Note (Cleanup) ---
@@ -359,33 +343,33 @@ def test_attachment_cleanup_behavior(nc_client: NextcloudClient):
attachment_content = f"Content for cleanup test".encode('utf-8') attachment_content = f"Content for cleanup test".encode('utf-8')
logger.info(f"Adding attachment '{attachment_filename}' to note ID: {note_id}") logger.info(f"Adding attachment '{attachment_filename}' to note ID: {note_id}")
try: # Removed try block as we expect WebDAV to work or fail the test
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=attachment_content, content=attachment_content,
mime_type="text/plain" mime_type="text/plain"
) )
assert upload_response["status_code"] in [201, 204] assert upload_response["status_code"] in [201, 204]
logger.info(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 ---
retrieved_content, _ = nc_client.get_note_attachment( retrieved_content, _ = nc_client.get_note_attachment(
note_id=note_id, note_id=note_id,
filename=attachment_filename filename=attachment_filename
) )
assert retrieved_content == attachment_content assert retrieved_content == attachment_content
logger.info("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 # No longer needed as we expect it to work or fail
except HTTPStatusError as e: # except HTTPStatusError as e: # Removed the try/except block that skipped on 401
if e.response.status_code == 401: # if e.response.status_code == 401:
logger.info(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 ---
logger.info(f"Deleting note ID: {note_id}") logger.info(f"Deleting note ID: {note_id}")
@@ -399,17 +383,12 @@ def test_attachment_cleanup_behavior(nc_client: NextcloudClient):
assert excinfo.value.response.status_code == 404 assert excinfo.value.response.status_code == 404
logger.info(f"Verified note deletion (404 received)") logger.info(f"Verified note deletion (404 received)")
# --- Document the expected behavior: attachments remain after note deletion --- # --- Verify Attachment Is Deleted (New Behavior) ---
try: logger.info(f"Verifying attachment '{attachment_filename}' is deleted for note ID: {note_id}")
# Try to get the attachment - expected to still exist with pytest.raises(HTTPStatusError) as excinfo_attach_del:
retrieved_content, _ = nc_client.get_note_attachment( nc_client.get_note_attachment(
note_id=note_id, note_id=note_id,
filename=attachment_filename filename=attachment_filename
) )
logger.info("EXPECTED BEHAVIOR: Attachment still exists after note deletion") assert excinfo_attach_del.value.response.status_code == 404
logger.info("This matches the behavior of the official Nextcloud Notes app") logger.info(f"Attachment '{attachment_filename}' correctly not found (404) after note deletion.")
except HTTPStatusError as e:
if e.response.status_code == 404:
logger.info("NOTE: Attachment was deleted with the note (unexpected but not a problem)")
else:
logger.info(f"Unexpected error when checking attachment: {e.response.status_code}")
+31 -42
View File
@@ -10,31 +10,21 @@ 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")
def nc_client() -> NextcloudClient:
"""
Fixture to create a NextcloudClient instance for integration tests.
"""
assert os.getenv("NEXTCLOUD_HOST"), "NEXTCLOUD_HOST env var not set"
assert os.getenv("NEXTCLOUD_USERNAME"), "NEXTCLOUD_USERNAME env var not set"
assert os.getenv("NEXTCLOUD_PASSWORD"), "NEXTCLOUD_PASSWORD env var not set"
return NextcloudClient.from_env()
@pytest.mark.integration @pytest.mark.integration
def test_attachment_remains_after_note_deletion(nc_client: NextcloudClient): def test_attachment_deleted_after_note_deletion(nc_client: NextcloudClient):
""" """
Test to verify and document that when a note is deleted, its attachments remain Test to verify that when a note is deleted, its attachments are also deleted
in the system. This is the expected behavior of the Nextcloud Notes app. by the MCP client's modified notes_delete_note method.
""" """
# --- Create Note --- # --- Create Note ---
unique_id = str(uuid.uuid4()) unique_id = str(uuid.uuid4())
note_title = f"Attachment Cleanup Test {unique_id}" note_title = f"Attachment Cleanup Test {unique_id}"
note_content = f"# Test for attachment cleanup behavior\n\nThis note will be deleted, but attachments should remain." note_content = f"# Test for attachment cleanup behavior\n\nThis note and its attachments should be deleted."
note_category = "CleanupTests" note_category = "CleanupTests"
created_note = None created_note = None
note_id = None note_id = None
try: try:
# Create the note # Create the note
logger.info(f"Creating note: {note_title}") logger.info(f"Creating note: {note_title}")
@@ -47,11 +37,11 @@ def test_attachment_remains_after_note_deletion(nc_client: NextcloudClient):
note_id = created_note["id"] note_id = created_note["id"]
logger.info(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
attachment_filename = f"orphan_test_{unique_id}.txt" attachment_filename = f"cleanup_test_{unique_id}.txt"
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
logger.info(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(
@@ -60,44 +50,43 @@ def test_attachment_remains_after_note_deletion(nc_client: NextcloudClient):
content=attachment_content, content=attachment_content,
mime_type="text/plain" mime_type="text/plain"
) )
assert upload_response["status_code"] in [201, 204] assert upload_response["status_code"] in [201, 204]
logger.info(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 before deletion
logger.info(f"Verifying attachment exists before deletion...")
content, mime_type = nc_client.get_note_attachment( content, mime_type = nc_client.get_note_attachment(
note_id=note_id, note_id=note_id,
filename=attachment_filename filename=attachment_filename
) )
assert content == attachment_content, "Attachment content mismatch before deletion"
assert content == attachment_content, "Attachment content mismatch" logger.info("Attachment verified before deletion")
logger.info("Attachment verified")
# Now delete the note (which should also delete the attachment directory)
# Now delete the note
logger.info(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)
logger.info(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
logger.info(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 is deleted (expected behavior: it should be)
logger.info(f"Checking if attachment still exists after note deletion...") logger.info(f"Checking if attachment is deleted after note deletion...")
orphaned_content, orphaned_mime = nc_client.get_note_attachment( with pytest.raises(HTTPStatusError) as excinfo:
note_id=note_id, nc_client.get_note_attachment(
filename=attachment_filename note_id=note_id,
) filename=attachment_filename
)
# If we get here without an exception, the attachment still exists # We expect a 404 because the attachment (and its directory) should be gone
logger.info("CONFIRMED: Attachment still exists after note deletion") assert excinfo.value.response.status_code == 404
logger.info("This is the expected behavior of the Nextcloud Notes app") logger.info("CONFIRMED: Attachment is deleted after note deletion (404 Not Found)")
assert orphaned_content == attachment_content, "Orphaned attachment content mismatch"
finally: finally:
# No cleanup needed since we've already deleted the note # No cleanup needed as the test itself cleans up the note and attachment
pass pass
+51 -48
View File
@@ -2,73 +2,76 @@
import sys import sys
import os import os
import base64 import base64
from nextcloud_mcp_server.client import NextcloudClient from nextcloud_mcp_server.client import NextcloudClient, HTTPStatusError
import logging import logging
import time
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def test_webdav_auth(): def test_webdav_auth_with_attachment():
""" """
Test function to verify WebDAV authentication and compare with current implementation. Test function to verify WebDAV authentication by attempting to use add_note_attachment.
""" """
# Create client using standard method
client = NextcloudClient.from_env() client = NextcloudClient.from_env()
print("Client authentication type:", type(client._client.auth).__name__) print("Client authentication type:", type(client._client.auth).__name__)
# Get WebDAV base path
username = os.environ["NEXTCLOUD_USERNAME"] username = os.environ["NEXTCLOUD_USERNAME"]
password = os.environ["NEXTCLOUD_PASSWORD"]
webdav_base = client._get_webdav_base_path() webdav_base = client._get_webdav_base_path()
# Test path for Notes directory
notes_path = f"{webdav_base}/Notes" notes_path = f"{webdav_base}/Notes"
print(f"Testing WebDAV access to: {notes_path}") print(f"Target WebDAV Notes path for PROPFIND check: {notes_path}")
# 1. Test with existing client auth temp_note_id = None
try: try:
print("\nTest 1: Using existing client authentication") # 1. Create a temporary note to get a note_id
response = client._client.request("PROPFIND", notes_path, headers={"Depth": "0"}) print("\nCreating a temporary note...")
print(f"Status code: {response.status_code}") temp_note_title = f"Temp Note for WebDAV Test - {int(time.time())}"
if response.status_code >= 400: created_note = client.notes_create_note(title=temp_note_title, content="Test content")
print(f"Error: {response.text}") temp_note_id = created_note.get("id")
else: if not temp_note_id:
print("Success! Current auth method works") print("Error: Failed to create temporary note.")
except Exception as e: return 1
print(f"Error: {str(e)}") print(f"Temporary note created with ID: {temp_note_id}")
# 2. Test with explicit Authorization header # 2. Attempt to add an attachment (this will trigger the internal PROPFIND)
try: print(f"\nTest: Attempting add_note_attachment for note_id {temp_note_id} (uses client's BasicAuth)")
print("\nTest 2: Using explicit Authorization header") dummy_content = b"This is a test attachment."
# Create base64 encoded credentials dummy_filename = "test_attachment.txt"
auth_string = f"{username}:{password}"
auth_bytes = auth_string.encode('ascii')
base64_bytes = base64.b64encode(auth_bytes)
base64_auth = base64_bytes.decode('ascii')
# Make request with explicit Authorization header # The add_note_attachment method itself contains the PROPFIND check
headers = { # and will log details if it fails.
"Depth": "0", response_data = client.add_note_attachment(
"Authorization": f"Basic {base64_auth}" note_id=temp_note_id,
} filename=dummy_filename,
content=dummy_content,
# Use client without auth to test explicit header mime_type="text/plain"
response = client._client.request(
"PROPFIND",
notes_path,
headers=headers,
auth=None # Override client auth
) )
print(f"add_note_attachment response: {response_data}")
print(f"Status code: {response.status_code}") if response_data and response_data.get("status_code") in [201, 204]:
if response.status_code >= 400: print("Success! add_note_attachment (and its internal PROPFIND) worked.")
print(f"Error: {response.text}")
else: else:
print("Success! Explicit authorization header works") print("Failure or unexpected response from add_note_attachment.")
# The client.py logs should show details of the PROPFIND if it failed.
except HTTPStatusError as e:
print(f"HTTPStatusError during add_note_attachment: {e.response.status_code} - {e.response.text}")
if e.response.status_code == 401:
print("Reproduced 401 Unauthorized during add_note_attachment's PROPFIND check!")
else:
print("An HTTP error other than 401 occurred.")
except Exception as e: except Exception as e:
print(f"Error: {str(e)}") print(f"An unexpected error occurred: {str(e)}")
finally:
# 3. Clean up: Delete the temporary note
if temp_note_id:
print(f"\nCleaning up: Deleting temporary note ID {temp_note_id}...")
try:
client.notes_delete_note(note_id=temp_note_id)
print(f"Successfully deleted temporary note ID {temp_note_id}.")
except Exception as e_del:
print(f"Error deleting temporary note ID {temp_note_id}: {str(e_del)}")
return 0 return 0
if __name__ == "__main__": if __name__ == "__main__":
sys.exit(test_webdav_auth()) sys.exit(test_webdav_auth_with_attachment())