refactor(api): split management.py into domain-focused modules

Split the monolithic management.py (1988 lines) into 4 focused modules:
- management.py: Server status, user sessions, shared helpers (~520 lines)
- passwords.py: App password provisioning for BasicAuth mode (~300 lines)
- webhooks.py: Webhook registration management (~290 lines)
- visualization.py: Search and PDF preview endpoints (~810 lines)

Backward compatibility maintained via __init__.py re-exports.
Updated test imports to use new module paths.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2026-01-26 21:28:18 +01:00
co-authored by Claude Opus 4.5
parent 61ce873411
commit 2e7774654b
8 changed files with 1674 additions and 1522 deletions
@@ -18,8 +18,8 @@ from starlette.applications import Starlette
from starlette.routing import Route
from starlette.testclient import TestClient
from nextcloud_mcp_server.api import management
from nextcloud_mcp_server.api.management import (
from nextcloud_mcp_server.api import passwords
from nextcloud_mcp_server.api.passwords import (
delete_app_password,
get_app_password_status,
provision_app_password,
@@ -32,9 +32,9 @@ pytestmark = pytest.mark.unit
@pytest.fixture(autouse=True)
def clear_rate_limit():
"""Clear rate limit state before each test."""
management._rate_limit_attempts.clear()
passwords._rate_limit_attempts.clear()
yield
management._rate_limit_attempts.clear()
passwords._rate_limit_attempts.clear()
@pytest.fixture
@@ -199,7 +199,7 @@ async def test_provision_app_password_success(temp_storage, mocker):
mock_client.__aexit__ = AsyncMock()
mocker.patch(
"nextcloud_mcp_server.api.management.httpx.AsyncClient",
"nextcloud_mcp_server.api.passwords.httpx.AsyncClient",
return_value=mock_client,
)
@@ -243,7 +243,7 @@ async def test_provision_app_password_nextcloud_validation_fails(mocker):
mock_client.__aexit__ = AsyncMock()
mocker.patch(
"nextcloud_mcp_server.api.management.httpx.AsyncClient",
"nextcloud_mcp_server.api.passwords.httpx.AsyncClient",
return_value=mock_client,
)
@@ -362,7 +362,7 @@ async def test_delete_app_password_success(temp_storage, mocker):
mock_client.__aexit__ = AsyncMock()
mocker.patch(
"nextcloud_mcp_server.api.management.httpx.AsyncClient",
"nextcloud_mcp_server.api.passwords.httpx.AsyncClient",
return_value=mock_client,
)
@@ -406,7 +406,7 @@ async def test_delete_app_password_not_found(temp_storage, mocker):
mock_client.__aexit__ = AsyncMock()
mocker.patch(
"nextcloud_mcp_server.api.management.httpx.AsyncClient",
"nextcloud_mcp_server.api.passwords.httpx.AsyncClient",
return_value=mock_client,
)
@@ -445,7 +445,7 @@ async def test_delete_app_password_invalid_credentials(mocker):
mock_client.__aexit__ = AsyncMock()
mocker.patch(
"nextcloud_mcp_server.api.management.httpx.AsyncClient",
"nextcloud_mcp_server.api.passwords.httpx.AsyncClient",
return_value=mock_client,
)
@@ -515,7 +515,7 @@ async def test_provision_app_password_rate_limiting(mocker):
mock_client.__aexit__ = AsyncMock()
mocker.patch(
"nextcloud_mcp_server.api.management.httpx.AsyncClient",
"nextcloud_mcp_server.api.passwords.httpx.AsyncClient",
return_value=mock_client,
)
@@ -574,7 +574,7 @@ async def test_rate_limiting_is_per_user(mocker):
mock_client.__aexit__ = AsyncMock()
mocker.patch(
"nextcloud_mcp_server.api.management.httpx.AsyncClient",
"nextcloud_mcp_server.api.passwords.httpx.AsyncClient",
return_value=mock_client,
)