from unittest.mock import AsyncMock, MagicMock import pytest def _env(monkeypatch, *, token="tok"): monkeypatch.setenv("NEXTCLOUD_WEBDAV_URL", "http://nc") monkeypatch.setenv("NEXTCLOUD_USER", "u") monkeypatch.setenv("NEXTCLOUD_APP_PASSWORD", "p") monkeypatch.setenv("OLLAMA_URL", "http://ollama") monkeypatch.setenv("OLLAMA_EMBED_MODEL", "qwen3-embedding:0.6b") monkeypatch.setenv("QDRANT_URL", "http://qdrant") monkeypatch.setenv("QDRANT_COLLECTION", "rag_test") monkeypatch.setenv("WEBHOOK_SECRET", "s") if token is not None: monkeypatch.setenv("RAG_MCP_TOKEN", token) from app.config import get_settings get_settings.cache_clear() async def _drain(app, scope): sent = [] async def receive(): return {"type": "http.request", "body": b"", "more_body": False} async def send(msg): sent.append(msg) await app(scope, receive, send) return sent def _http_scope(auth_header: bytes | None): headers = [(b"authorization", auth_header)] if auth_header is not None else [] return {"type": "http", "headers": headers} async def test_middleware_rejects_missing_token(monkeypatch): from app.mcp_server import BearerAuthMiddleware inner = AsyncMock() mw = BearerAuthMiddleware(inner, "secret") sent = await _drain(mw, _http_scope(None)) assert sent[0]["status"] == 401 inner.assert_not_awaited() async def test_middleware_rejects_wrong_token(monkeypatch): from app.mcp_server import BearerAuthMiddleware inner = AsyncMock() mw = BearerAuthMiddleware(inner, "secret") sent = await _drain(mw, _http_scope(b"Bearer nope")) assert sent[0]["status"] == 401 inner.assert_not_awaited() async def test_middleware_passes_correct_token(): from app.mcp_server import BearerAuthMiddleware inner = AsyncMock() mw = BearerAuthMiddleware(inner, "secret") await _drain(mw, _http_scope(b"Bearer secret")) inner.assert_awaited_once() async def test_middleware_passes_through_non_http_scope(): from app.mcp_server import BearerAuthMiddleware inner = AsyncMock() mw = BearerAuthMiddleware(inner, "secret") await _drain(mw, {"type": "lifespan"}) inner.assert_awaited_once() def test_build_app_exits_without_token(monkeypatch): _env(monkeypatch, token=None) import app.mcp_server as m with pytest.raises(SystemExit): m.build_app() async def test_rag_search_embeds_query_and_forwards_filters(monkeypatch): _env(monkeypatch) import app.mcp_server as m embed = AsyncMock(return_value=[[0.5] * 4]) search = MagicMock(return_value=[{"text": "hit", "score": 0.9}]) monkeypatch.setattr(m, "embed_texts", embed) monkeypatch.setattr(m, "search_chunks", search) monkeypatch.setattr(m, "_qdrant", lambda: "CLIENT") out = await m.rag_search("was ist BPMN?", limit=3, semester="2.Semester") embed.assert_awaited_once_with(["was ist BPMN?"], model="qwen3-embedding:0.6b") args, kwargs = search.call_args assert args[0] == "CLIENT" assert args[1] == "rag_test" assert args[2] == [0.5] * 4 assert kwargs == { "limit": 3, "semester": "2.Semester", "fach": None, "typ": None, } assert out == [{"text": "hit", "score": 0.9}] async def test_get_file_chunks_delegates(monkeypatch): _env(monkeypatch) import app.mcp_server as m getter = MagicMock(return_value=[{"chunk_index": 0, "text": "x"}]) monkeypatch.setattr(m, "get_chunks_by_path", getter) monkeypatch.setattr(m, "_qdrant", lambda: "CLIENT") out = await m.get_file_chunks("Documents/x.pdf") getter.assert_called_once_with("CLIENT", "rag_test", "Documents/x.pdf") assert out == [{"chunk_index": 0, "text": "x"}]