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:
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ from starlette.applications import Starlette
|
||||
from starlette.routing import Route
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
from nextcloud_mcp_server.api.management import get_pdf_preview
|
||||
from nextcloud_mcp_server.api.visualization import get_pdf_preview
|
||||
|
||||
pytestmark = pytest.mark.unit
|
||||
|
||||
@@ -68,12 +68,12 @@ class TestPdfPreviewParameterValidation:
|
||||
"""Test that missing file_path parameter returns 400."""
|
||||
with (
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.validate_token_and_get_user",
|
||||
"nextcloud_mcp_server.api.visualization.validate_token_and_get_user",
|
||||
new_callable=AsyncMock,
|
||||
return_value=("testuser", True),
|
||||
),
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.extract_bearer_token",
|
||||
"nextcloud_mcp_server.api.visualization.extract_bearer_token",
|
||||
return_value="test-token",
|
||||
),
|
||||
):
|
||||
@@ -93,12 +93,12 @@ class TestPdfPreviewParameterValidation:
|
||||
"""Test that invalid page number (0 or negative) returns 400."""
|
||||
with (
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.validate_token_and_get_user",
|
||||
"nextcloud_mcp_server.api.visualization.validate_token_and_get_user",
|
||||
new_callable=AsyncMock,
|
||||
return_value=("testuser", True),
|
||||
),
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.extract_bearer_token",
|
||||
"nextcloud_mcp_server.api.visualization.extract_bearer_token",
|
||||
return_value="test-token",
|
||||
),
|
||||
):
|
||||
@@ -126,12 +126,12 @@ class TestPdfPreviewParameterValidation:
|
||||
"""Test that scale outside valid range returns 400."""
|
||||
with (
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.validate_token_and_get_user",
|
||||
"nextcloud_mcp_server.api.visualization.validate_token_and_get_user",
|
||||
new_callable=AsyncMock,
|
||||
return_value=("testuser", True),
|
||||
),
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.extract_bearer_token",
|
||||
"nextcloud_mcp_server.api.visualization.extract_bearer_token",
|
||||
return_value="test-token",
|
||||
),
|
||||
):
|
||||
@@ -159,12 +159,12 @@ class TestPdfPreviewParameterValidation:
|
||||
"""Test that non-numeric page parameter returns 400."""
|
||||
with (
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.validate_token_and_get_user",
|
||||
"nextcloud_mcp_server.api.visualization.validate_token_and_get_user",
|
||||
new_callable=AsyncMock,
|
||||
return_value=("testuser", True),
|
||||
),
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.extract_bearer_token",
|
||||
"nextcloud_mcp_server.api.visualization.extract_bearer_token",
|
||||
return_value="test-token",
|
||||
),
|
||||
):
|
||||
@@ -186,7 +186,7 @@ class TestPdfPreviewAuthentication:
|
||||
def test_unauthorized_without_token_returns_401(self):
|
||||
"""Test that request without token returns 401."""
|
||||
with patch(
|
||||
"nextcloud_mcp_server.api.management.validate_token_and_get_user",
|
||||
"nextcloud_mcp_server.api.visualization.validate_token_and_get_user",
|
||||
new_callable=AsyncMock,
|
||||
side_effect=Exception("Invalid token"),
|
||||
):
|
||||
@@ -201,7 +201,7 @@ class TestPdfPreviewAuthentication:
|
||||
def test_unauthorized_with_invalid_token_returns_401(self):
|
||||
"""Test that request with invalid token returns 401."""
|
||||
with patch(
|
||||
"nextcloud_mcp_server.api.management.validate_token_and_get_user",
|
||||
"nextcloud_mcp_server.api.visualization.validate_token_and_get_user",
|
||||
new_callable=AsyncMock,
|
||||
side_effect=Exception("Token expired"),
|
||||
):
|
||||
@@ -235,12 +235,12 @@ class TestPdfPreviewRendering:
|
||||
|
||||
with (
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.validate_token_and_get_user",
|
||||
"nextcloud_mcp_server.api.visualization.validate_token_and_get_user",
|
||||
new_callable=AsyncMock,
|
||||
return_value=("testuser", True),
|
||||
),
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.extract_bearer_token",
|
||||
"nextcloud_mcp_server.api.visualization.extract_bearer_token",
|
||||
return_value="test-token",
|
||||
),
|
||||
patch(
|
||||
@@ -284,12 +284,12 @@ class TestPdfPreviewRendering:
|
||||
|
||||
with (
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.validate_token_and_get_user",
|
||||
"nextcloud_mcp_server.api.visualization.validate_token_and_get_user",
|
||||
new_callable=AsyncMock,
|
||||
return_value=("testuser", True),
|
||||
),
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.extract_bearer_token",
|
||||
"nextcloud_mcp_server.api.visualization.extract_bearer_token",
|
||||
return_value="test-token",
|
||||
),
|
||||
patch(
|
||||
@@ -324,12 +324,12 @@ class TestPdfPreviewRendering:
|
||||
|
||||
with (
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.validate_token_and_get_user",
|
||||
"nextcloud_mcp_server.api.visualization.validate_token_and_get_user",
|
||||
new_callable=AsyncMock,
|
||||
return_value=("testuser", True),
|
||||
),
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.extract_bearer_token",
|
||||
"nextcloud_mcp_server.api.visualization.extract_bearer_token",
|
||||
return_value="test-token",
|
||||
),
|
||||
patch(
|
||||
@@ -363,12 +363,12 @@ class TestPdfPreviewRendering:
|
||||
|
||||
with (
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.validate_token_and_get_user",
|
||||
"nextcloud_mcp_server.api.visualization.validate_token_and_get_user",
|
||||
new_callable=AsyncMock,
|
||||
return_value=("testuser", True),
|
||||
),
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.extract_bearer_token",
|
||||
"nextcloud_mcp_server.api.visualization.extract_bearer_token",
|
||||
return_value="test-token",
|
||||
),
|
||||
patch(
|
||||
@@ -407,12 +407,12 @@ class TestPdfPreviewEdgeCases:
|
||||
|
||||
with (
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.validate_token_and_get_user",
|
||||
"nextcloud_mcp_server.api.visualization.validate_token_and_get_user",
|
||||
new_callable=AsyncMock,
|
||||
return_value=("testuser", True),
|
||||
),
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.extract_bearer_token",
|
||||
"nextcloud_mcp_server.api.visualization.extract_bearer_token",
|
||||
return_value="test-token",
|
||||
),
|
||||
patch(
|
||||
@@ -438,12 +438,12 @@ class TestPdfPreviewEdgeCases:
|
||||
"""Test handling when Nextcloud host is not configured."""
|
||||
with (
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.validate_token_and_get_user",
|
||||
"nextcloud_mcp_server.api.visualization.validate_token_and_get_user",
|
||||
new_callable=AsyncMock,
|
||||
return_value=("testuser", True),
|
||||
),
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.extract_bearer_token",
|
||||
"nextcloud_mcp_server.api.visualization.extract_bearer_token",
|
||||
return_value="test-token",
|
||||
),
|
||||
):
|
||||
@@ -476,12 +476,12 @@ class TestPdfPreviewEdgeCases:
|
||||
|
||||
with (
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.validate_token_and_get_user",
|
||||
"nextcloud_mcp_server.api.visualization.validate_token_and_get_user",
|
||||
new_callable=AsyncMock,
|
||||
return_value=("testuser", True),
|
||||
),
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.extract_bearer_token",
|
||||
"nextcloud_mcp_server.api.visualization.extract_bearer_token",
|
||||
return_value="test-token",
|
||||
),
|
||||
patch(
|
||||
@@ -518,12 +518,12 @@ class TestPdfPreviewEdgeCases:
|
||||
|
||||
with (
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.validate_token_and_get_user",
|
||||
"nextcloud_mcp_server.api.visualization.validate_token_and_get_user",
|
||||
new_callable=AsyncMock,
|
||||
return_value=("testuser", True),
|
||||
),
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.extract_bearer_token",
|
||||
"nextcloud_mcp_server.api.visualization.extract_bearer_token",
|
||||
return_value="test-token",
|
||||
),
|
||||
patch(
|
||||
@@ -556,12 +556,12 @@ class TestPdfPreviewSecurityValidation:
|
||||
"""Test that path traversal attempts are blocked with 400."""
|
||||
with (
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.validate_token_and_get_user",
|
||||
"nextcloud_mcp_server.api.visualization.validate_token_and_get_user",
|
||||
new_callable=AsyncMock,
|
||||
return_value=("testuser", True),
|
||||
),
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.extract_bearer_token",
|
||||
"nextcloud_mcp_server.api.visualization.extract_bearer_token",
|
||||
return_value="test-token",
|
||||
),
|
||||
):
|
||||
@@ -605,12 +605,12 @@ class TestPdfPreviewSecurityValidation:
|
||||
|
||||
with (
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.validate_token_and_get_user",
|
||||
"nextcloud_mcp_server.api.visualization.validate_token_and_get_user",
|
||||
new_callable=AsyncMock,
|
||||
return_value=("testuser", True),
|
||||
),
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.extract_bearer_token",
|
||||
"nextcloud_mcp_server.api.visualization.extract_bearer_token",
|
||||
return_value="test-token",
|
||||
),
|
||||
patch(
|
||||
@@ -647,12 +647,12 @@ class TestPdfPreviewSecurityValidation:
|
||||
|
||||
with (
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.validate_token_and_get_user",
|
||||
"nextcloud_mcp_server.api.visualization.validate_token_and_get_user",
|
||||
new_callable=AsyncMock,
|
||||
return_value=("testuser", True),
|
||||
),
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.extract_bearer_token",
|
||||
"nextcloud_mcp_server.api.visualization.extract_bearer_token",
|
||||
return_value="test-token",
|
||||
),
|
||||
patch(
|
||||
@@ -691,12 +691,12 @@ class TestPdfPreviewSecurityValidation:
|
||||
|
||||
with (
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.validate_token_and_get_user",
|
||||
"nextcloud_mcp_server.api.visualization.validate_token_and_get_user",
|
||||
new_callable=AsyncMock,
|
||||
return_value=("testuser", True),
|
||||
),
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.extract_bearer_token",
|
||||
"nextcloud_mcp_server.api.visualization.extract_bearer_token",
|
||||
return_value="test-token",
|
||||
),
|
||||
patch(
|
||||
|
||||
Reference in New Issue
Block a user