fix(deck): address review — notesPath key, scopes, modernize types

PR #781 review round 1:

- 🔴 Fix notesPath key: the Notes API returns the folder under camelCase
  ``notesPath`` (see models/notes.py:43), but `deck_attach_note` was
  looking up snake_case ``notes_path`` and silently falling back to
  ``"Notes"``. Users with a non-default notes folder would have produced
  shares pointing at non-existent files (404 on click in Deck UI).

- 🔴 Add wire-through unit test that would have caught the above:
  extract `_resolve_note_attach_path(client, note_id)` as a testable
  helper that encapsulates the camelCase-key lookup. Three new tests:
  custom notesPath honored, missing key falls back to default, null
  category handled.

- 🟡 Modernize new fields on `DeckAttachmentExtendedData` to PEP 604
  (`X | None`) per CLAUDE.md.

- 🟡 Drop unnecessary string forward reference on
  `ListAttachmentsResponse.results` — DeckAttachment is defined earlier
  in the same module.

- 🟢 Move `pytestmark = pytest.mark.unit` to module level in
  test_sharing_client.py to match the convention in test_deck_server.py.

Per user request: `deck_attach_file` is now scoped `deck.write` +
``files.read`` (was just `deck.write`) so the generic file-share
permission story is consistent — only `deck_attach_note` keeps
`notes.read` since it specifically reads from the Notes app. Docstring
updated to emphasise the tool is generic over the user's Files
(PDFs/images/etc., not just markdown).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2026-05-10 23:28:37 +02:00
co-authored by Claude Opus 4.7
parent c0a974c498
commit 271904c227
4 changed files with 96 additions and 21 deletions
+2 -2
View File
@@ -11,6 +11,8 @@ from httpx import AsyncClient
from nextcloud_mcp_server.client.sharing import SharingClient
pytestmark = pytest.mark.unit
@pytest.fixture
def sharing_client(mocker):
@@ -32,7 +34,6 @@ def _ok_share_response(mocker, share_id: int = 4242, **extra):
return response
@pytest.mark.unit
async def test_create_share_deck_type_payload(sharing_client, mocker):
"""create_share(share_type=12) must POST exactly what the Deck UI does:
{path, shareType: 12, shareWith: "<cardId>"} to /ocs/v2.php/apps/files_sharing/api/v1/shares.
@@ -65,7 +66,6 @@ async def test_create_share_deck_type_payload(sharing_client, mocker):
assert call.kwargs["headers"]["OCS-APIRequest"] == "true"
@pytest.mark.unit
async def test_create_share_raises_on_ocs_failure(sharing_client, mocker):
"""OCS error responses (statuscode != 100/200) raise RuntimeError."""
response = mocker.Mock()