"""Unit tests verifying that user-influenced and exception-derived strings are HTML-escaped before they are rendered into ``HTMLResponse`` content. The handlers under test are decorated with ``@requires("authenticated")``, so we install an ``AuthenticationMiddleware`` backed by a trivial backend that always reports the request as authenticated. We then monkeypatch the internal helpers (``_get_authenticated_client``, ``is_nextcloud_admin``, ``get_preset``) to drive the handler down the specific code path we want to exercise. """ import pytest from starlette.applications import Starlette from starlette.authentication import ( AuthCredentials, AuthenticationBackend, SimpleUser, ) from starlette.middleware import Middleware from starlette.middleware.authentication import AuthenticationMiddleware from starlette.routing import Route from starlette.testclient import TestClient from nextcloud_mcp_server.auth import webhook_routes from nextcloud_mcp_server.auth.webhook_routes import ( disable_webhook_preset, enable_webhook_preset, ) pytestmark = pytest.mark.unit class _AlwaysAuthBackend(AuthenticationBackend): async def authenticate(self, conn): return AuthCredentials(["authenticated"]), SimpleUser("testuser") def _make_app() -> Starlette: return Starlette( routes=[ Route( "/app/webhooks/enable/{preset_id:path}", enable_webhook_preset, methods=["POST"], ), Route( "/app/webhooks/disable/{preset_id:path}", disable_webhook_preset, methods=["DELETE"], ), ], middleware=[Middleware(AuthenticationMiddleware, backend=_AlwaysAuthBackend())], ) def _stub_admin_path(monkeypatch): """Make the handler progress past auth/admin checks without real I/O.""" async def _fake_client(_request): return object() # never actually used because get_preset returns None async def _fake_is_admin(_request, _client): return True monkeypatch.setattr(webhook_routes, "_get_authenticated_client", _fake_client) monkeypatch.setattr(webhook_routes, "is_nextcloud_admin", _fake_is_admin) def test_enable_unknown_preset_id_is_html_escaped(monkeypatch): """A `" with TestClient(app) as client: response = client.post(f"/app/webhooks/enable/{payload}") assert response.status_code == 404 assert "<script>alert(1)</script>" in response.text assert "" not in response.text def test_disable_unknown_preset_id_is_html_escaped(monkeypatch): _stub_admin_path(monkeypatch) monkeypatch.setattr(webhook_routes, "get_preset", lambda _id: None) app = _make_app() payload = "" with TestClient(app) as client: response = client.delete(f"/app/webhooks/disable/{payload}") assert response.status_code == 404 assert "<script>alert(2)</script>" in response.text assert "" not in response.text def test_enable_exception_message_is_html_escaped(monkeypatch): """If the handler raises, the exception text must be escaped before it lands in the 500 response body.""" async def _boom(_request): raise RuntimeError("
") monkeypatch.setattr(webhook_routes, "_get_authenticated_client", _boom) app = _make_app() with TestClient(app) as client: response = client.post("/app/webhooks/enable/notes_sync") assert response.status_code == 500 assert "</p><script>x</script>" in response.text assert "" not in response.text def test_disable_exception_message_is_html_escaped(monkeypatch): async def _boom(_request): raise RuntimeError("") monkeypatch.setattr(webhook_routes, "_get_authenticated_client", _boom) app = _make_app() with TestClient(app) as client: response = client.delete("/app/webhooks/disable/notes_sync") assert response.status_code == 500 assert "</p><script>y</script>" in response.text assert "" not in response.text