Skip to content

Commit 2e4eef2

Browse files
committed
Fix update_share: support clearing password, expiration, note, label, and toggling hide_download/public_upload
1 parent 29bad50 commit 2e4eef2

3 files changed

Lines changed: 102 additions & 26 deletions

File tree

PROGRESS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,6 @@
6262
| Errors || 10 |
6363
| Config || 12 |
6464
| State || 2 |
65-
| Shares | 5 | 33 |
65+
| Shares | 5 | 40 |
6666
| System Tags | 6 | 22 |
67-
| **Total** | **47** | **349** |
67+
| **Total** | **47** | **356** |

src/nextcloud_mcp/tools/shares.py

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -151,48 +151,47 @@ def _register_update_share(mcp: FastMCP) -> None:
151151
@require_permission(PermissionLevel.WRITE)
152152
async def update_share(
153153
share_id: int,
154-
permissions: int = 0,
155-
password: str = "",
156-
expire_date: str = "",
157-
note: str = "",
158-
label: str = "",
159-
public_upload: bool = False,
160-
hide_download: bool = False,
154+
permissions: int | None = None,
155+
password: str | None = None,
156+
expire_date: str | None = None,
157+
note: str | None = None,
158+
label: str | None = None,
159+
public_upload: bool | None = None,
160+
hide_download: bool | None = None,
161161
) -> str:
162162
"""Update properties of an existing share.
163163
164-
Only provided (non-default) parameters are changed. To remove a password or
165-
expiration, use delete_share and create a new one.
164+
Only provided parameters are changed. Omitted parameters keep their current value.
166165
167166
Args:
168167
share_id: The numeric share ID to update.
169168
permissions: New permission flags (1=read, 2=update, 4=create, 8=delete, 16=share).
170-
password: Set or change password (link/email shares only).
171-
expire_date: Set expiration date in "YYYY-MM-DD" format.
172-
note: Set or update the share note.
173-
label: Set or update the share label.
174-
public_upload: Enable/disable public upload on shared folders (link shares only).
175-
hide_download: Hide the download button on public link shares.
169+
password: Set or change password (link/email shares only). Pass "" to remove password.
170+
expire_date: Set expiration in "YYYY-MM-DD" format. Pass "" to remove expiration.
171+
note: Set or update the share note. Pass "" to clear.
172+
label: Set or update the share label. Pass "" to clear.
173+
public_upload: Enable (true) or disable (false) public upload on shared folders (link shares only).
174+
hide_download: Show (false) or hide (true) the download button on public link shares.
176175
177176
Returns:
178177
JSON object with the updated share details.
179178
"""
180179
client = get_client()
181180
data: dict[str, str | int] = {}
182-
if permissions > 0:
181+
if permissions is not None:
183182
data["permissions"] = permissions
184-
if password:
183+
if password is not None:
185184
data["password"] = password
186-
if expire_date:
185+
if expire_date is not None:
187186
data["expireDate"] = expire_date
188-
if note:
187+
if note is not None:
189188
data["note"] = note
190-
if label:
189+
if label is not None:
191190
data["label"] = label
192-
if public_upload:
193-
data["publicUpload"] = "true"
194-
if hide_download:
195-
data["hideDownload"] = "true"
191+
if public_upload is not None:
192+
data["publicUpload"] = "true" if public_upload else "false"
193+
if hide_download is not None:
194+
data["hideDownload"] = "true" if hide_download else "false"
196195
result = await client.ocs_put(f"{SHARES_API}/{share_id}", data=data)
197196
return json.dumps(_format_share(result), indent=2, default=str)
198197

tests/integration/test_shares.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,83 @@ async def test_update_password(self, nc_mcp: McpTestHelper) -> None:
238238
updated = json.loads(result)
239239
assert updated.get("has_password") is True
240240

241+
@pytest.mark.asyncio
242+
async def test_update_remove_password(self, nc_mcp: McpTestHelper) -> None:
243+
await _setup_share_file(nc_mcp)
244+
created = json.loads(
245+
await nc_mcp.call("create_share", path=f"/{SHARE_FILE}", share_type=3, password="s3Cr3t!Pw9#xK")
246+
)
247+
share_id = int(created["id"])
248+
assert created.get("has_password") is True
249+
result = await nc_mcp.call("update_share", share_id=share_id, password="")
250+
updated = json.loads(result)
251+
assert updated.get("has_password") is not True
252+
253+
@pytest.mark.asyncio
254+
async def test_update_remove_expiration(self, nc_mcp: McpTestHelper) -> None:
255+
await _setup_share_file(nc_mcp)
256+
created = json.loads(
257+
await nc_mcp.call("create_share", path=f"/{SHARE_FILE}", share_type=3, expire_date="2099-12-31")
258+
)
259+
share_id = int(created["id"])
260+
assert created["expiration"] is not None
261+
result = await nc_mcp.call("update_share", share_id=share_id, expire_date="")
262+
updated = json.loads(result)
263+
assert updated["expiration"] is None
264+
265+
@pytest.mark.asyncio
266+
async def test_update_clear_note(self, nc_mcp: McpTestHelper) -> None:
267+
await _setup_share_file(nc_mcp)
268+
created = json.loads(
269+
await nc_mcp.call("create_share", path=f"/{SHARE_FILE}", share_type=3, note="initial note")
270+
)
271+
share_id = int(created["id"])
272+
assert created["note"] == "initial note"
273+
result = await nc_mcp.call("update_share", share_id=share_id, note="")
274+
updated = json.loads(result)
275+
assert updated["note"] == ""
276+
277+
@pytest.mark.asyncio
278+
async def test_update_clear_label(self, nc_mcp: McpTestHelper) -> None:
279+
await _setup_share_file(nc_mcp)
280+
created = json.loads(
281+
await nc_mcp.call("create_share", path=f"/{SHARE_FILE}", share_type=3, label="Initial Label")
282+
)
283+
share_id = int(created["id"])
284+
assert created["label"] == "Initial Label"
285+
result = await nc_mcp.call("update_share", share_id=share_id, label="")
286+
updated = json.loads(result)
287+
assert updated["label"] == ""
288+
289+
@pytest.mark.asyncio
290+
async def test_update_enable_hide_download(self, nc_mcp: McpTestHelper) -> None:
291+
await _setup_share_file(nc_mcp)
292+
created = json.loads(await nc_mcp.call("create_share", path=f"/{SHARE_FILE}", share_type=3))
293+
share_id = int(created["id"])
294+
result = await nc_mcp.call("update_share", share_id=share_id, hide_download=True)
295+
updated = json.loads(result)
296+
assert updated.get("hide_download") == 1
297+
298+
@pytest.mark.asyncio
299+
async def test_update_disable_hide_download(self, nc_mcp: McpTestHelper) -> None:
300+
await _setup_share_file(nc_mcp)
301+
created = json.loads(await nc_mcp.call("create_share", path=f"/{SHARE_FILE}", share_type=3))
302+
share_id = int(created["id"])
303+
await nc_mcp.call("update_share", share_id=share_id, hide_download=True)
304+
result = await nc_mcp.call("update_share", share_id=share_id, hide_download=False)
305+
updated = json.loads(result)
306+
assert not updated.get("hide_download")
307+
308+
@pytest.mark.asyncio
309+
async def test_update_disable_public_upload(self, nc_mcp: McpTestHelper) -> None:
310+
await _setup_share_file(nc_mcp)
311+
created = json.loads(await nc_mcp.call("create_share", path=f"/{SHARE_DIR}", share_type=3, public_upload=True))
312+
share_id = int(created["id"])
313+
assert created["permissions"] & 4, "Should have create permission"
314+
result = await nc_mcp.call("update_share", share_id=share_id, public_upload=False)
315+
updated = json.loads(result)
316+
assert not (updated["permissions"] & 4), "Create permission should be removed"
317+
241318
@pytest.mark.asyncio
242319
async def test_update_nonexistent_share_fails(self, nc_mcp: McpTestHelper) -> None:
243320
with pytest.raises(ToolError):

0 commit comments

Comments
 (0)