from __future__ import annotations from dataclasses import dataclass from datetime import date, datetime from typing import Any from unittest.mock import AsyncMock import pytest import teamlandkarte_mcp.mcp_server as mcp_mod from teamlandkarte_mcp.mcp_server import build_server def _result_to_text(result: Any) -> str: if isinstance(result, tuple) and len(result) == 2: content, structured = result if isinstance(structured, dict) and isinstance(structured.get("result"), str): return structured["result"] if isinstance(content, list) and content: text = getattr(content[0], "text", None) if isinstance(text, str): return text return str(result) async def _call_tool( srv: Any, name: str, args: dict[str, Any] | None = None, ) -> str: if args is None: args = {} return _result_to_text(await srv.call_tool(name, args)) @pytest.mark.asyncio async def test_integration_capacity_search_filter_and_pagination( monkeypatch, tmp_path, ) -> None: cfg = tmp_path / "config.toml" cfg.write_text( """ [database] host='x' port=1 username='u' password='p' backend='trino' [matching] competence_weight=0.8 role_weight=0.2 require_confirmation=false [cache] db_ttl_hours=1 search_ttl_minutes=60 max_size=100 """.strip(), encoding="utf-8", ) from teamlandkarte_mcp.models import Capacity class _FakeDB: def test_connection(self) -> None: return def get_table_columns(self, table: str) -> list[str]: if table == "teamlandkarte_v_capacity_roles_latest": return ["name", "active", "staffing_board_relevant"] if table == "teamlandkarte_v_capacities_latest": return ["creation_date"] if table == "teamlandkarte_v_teams_latest": return [ "team_id", "ouid", "about_us", "offerings", "interests", "focus_name", ] if table == "teamlandkarte_v_teammeter_organizational_units_latest": return ["id", "name"] if table == "teamlandkarte_v_teammeter_team_competences_latest": return ["ouid", "competence_id", "top_competency"] if table == "teamlandkarte_v_team_references_latest": return ["ouid", "partner_id", "projects"] return [] def get_all_role_names(self) -> list[str]: return ["X"] def get_all_competence_names(self) -> list[str]: return ["A"] def get_open_tasks(self, limit: int = 20): return [] def get_all_capacities_with_competences(self): return [ Capacity( id=1, owner_name="A", role_name="X", role_level=None, begin_date=date(2025, 2, 1), end_date=date(2025, 7, 31), competences=["A"], ), Capacity( id=2, owner_name="B", role_name="X", role_level=None, begin_date=date(2025, 3, 15), end_date=date(2025, 6, 15), competences=["A"], ), ] def get_recent_free_capacities(self, limit: int = 20): return [] monkeypatch.setattr( mcp_mod, "create_db_client", lambda *_args, **_kwargs: _FakeDB(), ) # Mock LLM chat_completion to avoid network calls. monkeypatch.setattr( "teamlandkarte_mcp.azure.openai_client.AzureOpenAIClient.chat_completion", AsyncMock(return_value='{"similarity": 1.0}'), ) srv = build_server(str(cfg)) res = await _call_tool( srv, "find_matching_capacities", { "role_name": "X", "competences": ["A"], "date_start": "2025-03-01", "date_end": "2025-06-30", }, ) assert "SEARCH_ID=" in res search_id = res.split("SEARCH_ID=")[1].splitlines()[0].strip() filtered = await _call_tool( srv, "filter_search_results", {"search_id": search_id, "is_fully_available": True}, ) assert "FILTER_ID=" in filtered filter_id = filtered.split("FILTER_ID=")[1].splitlines()[0].strip() assert "Filtered total results: 1" in filtered page = await _call_tool( srv, "get_results_by_category", { "search_id": search_id, "filter_id": filter_id, "category": "Low", "page": 1, "page_size": 20, }, ) assert "Total items in category: 1" in page assert "| 1 |" in page assert "| 2 |" not in page @pytest.mark.asyncio async def test_integration_task_search_filters_and_pagination( monkeypatch, tmp_path, ) -> None: cfg = tmp_path / "config.toml" cfg.write_text( """ [database] host='x' port=1 username='u' password='p' backend='trino' [matching] competence_weight=0.8 role_weight=0.2 require_confirmation=false [cache] db_ttl_hours=1 search_ttl_minutes=60 max_size=100 """.strip(), encoding="utf-8", ) @dataclass class _Task: id: str name: str | None = None title: str = "" description: str = "" created_date: datetime = datetime(2025, 1, 1) start_date: date | None = None end_date: date | None = None skills: list[str] = None # type: ignore[assignment] def __post_init__(self): if self.skills is None: self.skills = [] class _FakeDB: def test_connection(self) -> None: return def get_table_columns(self, table: str) -> list[str]: if table == "teamlandkarte_v_capacity_roles_latest": return ["name", "active", "staffing_board_relevant"] if table == "teamlandkarte_v_capacities_latest": return ["creation_date"] if table == "teamlandkarte_v_teams_latest": return [ "team_id", "ouid", "about_us", "offerings", "interests", "focus_name", ] if table == "teamlandkarte_v_teammeter_organizational_units_latest": return ["id", "name"] if table == "teamlandkarte_v_teammeter_team_competences_latest": return ["ouid", "competence_id", "top_competency"] if table == "teamlandkarte_v_team_references_latest": return ["ouid", "partner_id", "projects"] return [] def get_all_role_names(self) -> list[str]: return ["Frontend Developer", "Backend Engineer"] def get_all_competence_names(self) -> list[str]: return ["React", "TypeScript", "Python", "PostgreSQL"] def get_open_tasks(self, limit: int = 20): tasks = [ _Task( id="T1", title="Frontend Developer", description="React and TypeScript", created_date=datetime(2025, 1, 1), start_date=date(2025, 5, 1), end_date=date(2025, 7, 31), skills=["React", "TypeScript"], ), _Task( id="T2", title="Backend Engineer", description="Python and PostgreSQL", created_date=datetime(2025, 1, 1), start_date=date(2025, 5, 1), end_date=date(2025, 7, 31), skills=["Python"], ), ] return tasks if limit == 0 else tasks[:limit] def get_task_by_id(self, task_id: str): # pragma: no cover return None def get_task_by_name(self, name: str): # pragma: no cover return None def get_all_capacities_with_competences(self): # pragma: no cover return [] def get_recent_free_capacities( self, limit: int = 20, ): # pragma: no cover return [] def get_capacity_by_id(self, capacity_id): from teamlandkarte_mcp.models import Capacity return Capacity( id=1, owner_name="A", role_name="Frontend Developer", role_level=None, begin_date=date(2025, 4, 1), end_date=date(2025, 8, 31), competences=["React", "TypeScript"], ) monkeypatch.setattr( mcp_mod, "create_db_client", lambda *_args, **_kwargs: _FakeDB(), ) # Mock LLM chat_completion to avoid network calls. monkeypatch.setattr( "teamlandkarte_mcp.azure.openai_client.AzureOpenAIClient.chat_completion", AsyncMock(return_value='{"similarity": 1.0}'), ) srv = build_server(str(cfg)) res = await _call_tool(srv, "find_matching_tasks", {"capacity_id": 1}) assert "SEARCH_ID=" in res search_id = res.split("SEARCH_ID=")[1].splitlines()[0].strip() out = await _call_tool( srv, "filter_search_results", {"search_id": search_id, "task_text_filter": "typescript"}, ) assert "Filtered total results: 1" in out assert "T1" in out assert "T2" not in out out2 = await _call_tool( srv, "filter_search_results", {"search_id": search_id, "task_competence_filter": ["python"]}, ) assert "Filtered total results: 1" in out2 assert "T2" in out2 page = await _call_tool( srv, "get_results_by_category", { "search_id": search_id, "category": "Top", "page": 1, "page_size": 20, }, ) assert "Total items in category:" in page assert "task_id" in page