Migrate all repos into monorepo context folders
Bahn: aisupport, Analyse-O2C-C2S, awesome-bahn-mcp-servers, beam-mcp,
Confluence_Bot, db-planet-mcp-server, O2C-Harness, project-audit,
Projekt-KIQ-HP, teamlandkarte-mcp
Dhive: Jury-Voting
Privat: CV, NoteGraph (NOTE: NoteGraph needs complete redo after consolidation)
Shared: AI-Orchestrator, OrgMyLife, power_skills_and_more
Shared/references: symphony (read-only)
Bahn repos remain available as independent remotes - this monorepo
pulls them in via subtree, the originals are untouched.
This commit is contained in:
@@ -0,0 +1,350 @@
|
||||
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
|
||||
Reference in New Issue
Block a user