diff --git a/backend/tests/test_cloud.py b/backend/tests/test_cloud.py index c54bebd..917b4a0 100644 --- a/backend/tests/test_cloud.py +++ b/backend/tests/test_cloud.py @@ -292,6 +292,42 @@ async def test_oauth_callback_invalid_state(async_client, db_session, monkeypatc app.state.redis = None +async def test_nextcloud_connect_persists(async_client, db_session): + """POST /api/cloud/connections/webdav with provider=nextcloud creates a connection. + + Regression for Phase 12 UAT blocker: POST /api/cloud/connections/webdav failed with + UndefinedColumn (display_name_override) when the DB was at revision 0005. + Uses unittest.mock.patch to stub SSRF validation and health probe at sys.modules level. + """ + from unittest.mock import patch, AsyncMock + + auth = await _create_user_and_token(db_session, role="user") + + # provider=nextcloud routes to NextcloudBackend (not WebDAVBackend) in the + # cloud_backend_factory — patch validate_cloud_url in all namespaces that hold + # a from-import binding. asyncio.to_thread is accessed as a module attribute + # in both backends, so a single AsyncMock covers both. + with patch("api.cloud.connections.validate_cloud_url", return_value=None), \ + patch("storage.webdav_backend.validate_cloud_url", return_value=None), \ + patch("storage.nextcloud_backend.validate_cloud_url", return_value=None), \ + patch("asyncio.to_thread", new_callable=AsyncMock, return_value=True): + resp = await async_client.post( + "/api/cloud/connections/webdav", + json={ + "server_url": "https://nc.example.com/remote.php/dav", + "username": "testuser", + "password": "testpass", + "provider": "nextcloud", + }, + headers=auth["headers"], + ) + + assert resp.status_code == 201, f"Unexpected: {resp.status_code} — {resp.text}" + data = resp.json() + assert data["provider"] == "nextcloud" + assert "credentials_enc" not in data + + async def test_webdav_connect_validates(async_client, db_session, monkeypatch): """POST /api/cloud/connections/webdav with localhost URL returns 422 (SSRF blocked).""" auth = await _create_user_and_token(db_session, role="user") diff --git a/backend/tests/test_compose_migrations.py b/backend/tests/test_compose_migrations.py index 1b7cabb..eecae81 100644 --- a/backend/tests/test_compose_migrations.py +++ b/backend/tests/test_compose_migrations.py @@ -21,6 +21,15 @@ import yaml REPO_ROOT = pathlib.Path(__file__).resolve().parents[2] COMPOSE_PATH = REPO_ROOT / "docker-compose.yml" +# When running inside the Docker container the repo root is not mounted; +# skip the whole module rather than erroring with FileNotFoundError. +if not COMPOSE_PATH.exists(): + pytest.skip( + f"docker-compose.yml not found at {COMPOSE_PATH} — " + "these static analysis tests must run from the host repo root, not inside a container.", + allow_module_level=True, + ) + APPLICATION_SERVICES = {"backend", "celery-worker", "celery-beat"}