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,279 @@
|
||||
"""Unit tests for the capacity details enrichment feature.
|
||||
|
||||
Tests verify that get_capacity_details correctly displays description,
|
||||
references, and certificates sections with proper formatting and ordering.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date
|
||||
|
||||
import pytest
|
||||
|
||||
from teamlandkarte_mcp.mcp_server import build_server
|
||||
from teamlandkarte_mcp.models import Capacity, Task
|
||||
|
||||
|
||||
class _FakeDB:
|
||||
"""Fake DB client for testing get_capacity_details enrichment."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
caps: list[Capacity],
|
||||
*,
|
||||
description: str | None = None,
|
||||
references: list[dict] | None = None,
|
||||
certificates: list[str] | None = None,
|
||||
) -> None:
|
||||
self._caps = caps
|
||||
self._description = description
|
||||
self._references = references if references is not None else []
|
||||
self._certificates = certificates if certificates is not None else []
|
||||
|
||||
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 ["Cloud Engineer"]
|
||||
|
||||
def get_all_competence_names(self) -> list[str]:
|
||||
return ["AWS"]
|
||||
|
||||
def get_open_tasks(self, limit: int = 20) -> list[Task]:
|
||||
return []
|
||||
|
||||
def get_task_by_id(self, task_id: str):
|
||||
return None
|
||||
|
||||
def get_task_by_name(self, name: str):
|
||||
return None
|
||||
|
||||
def get_all_capacities_with_competences(self) -> list[Capacity]:
|
||||
return list(self._caps)
|
||||
|
||||
def get_recent_free_capacities(self, limit: int = 20) -> list[Capacity]:
|
||||
return list(self._caps)[:limit]
|
||||
|
||||
def get_capacity_by_id(self, capacity_id: int | str) -> Capacity | None:
|
||||
cid = int(capacity_id)
|
||||
for c in self._caps:
|
||||
if int(c.id) == cid:
|
||||
return c
|
||||
return None
|
||||
|
||||
def get_capacity_description(self, capacity_id: int | str) -> str | None:
|
||||
return self._description
|
||||
|
||||
def get_capacity_references(self, capacity_id: int | str) -> list[dict]:
|
||||
return self._references
|
||||
|
||||
def get_capacity_certificates(self, capacity_id: int | str) -> list[str]:
|
||||
return self._certificates
|
||||
|
||||
|
||||
def _make_config(tmp_path) -> str:
|
||||
"""Write a minimal config.toml and return its path."""
|
||||
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=10
|
||||
max_size=10
|
||||
""".strip(),
|
||||
encoding="utf-8",
|
||||
)
|
||||
return str(cfg)
|
||||
|
||||
|
||||
def _make_capacity(cap_id: int = 1) -> Capacity:
|
||||
return Capacity(
|
||||
id=cap_id,
|
||||
owner_name="Team Alpha",
|
||||
role_name="Cloud Engineer",
|
||||
role_level=None,
|
||||
begin_date=date(2026, 1, 1),
|
||||
end_date=date(2026, 6, 30),
|
||||
competences=["AWS", "Terraform"],
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2.1 Test full data scenario
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_full_data_scenario(monkeypatch, tmp_path) -> None:
|
||||
"""All enrichment sections are rendered with correct content and ordering."""
|
||||
|
||||
description = "Erfahrener Cloud-Architekt mit Fokus auf AWS-Infrastruktur."
|
||||
references = [
|
||||
{"partner_name": "Deutsche Bahn", "projects": "Projekt Reiseportal, Projekt Fahrplan"},
|
||||
{"partner_name": "", "projects": "Internes Migrationsprojekt"},
|
||||
{"partner_name": "Siemens", "projects": "IoT Platform"},
|
||||
]
|
||||
certificates = [
|
||||
"AWS Solutions Architect Professional",
|
||||
"Kubernetes Administrator (CKA)",
|
||||
]
|
||||
|
||||
cap = _make_capacity()
|
||||
fake_db = _FakeDB(
|
||||
[cap],
|
||||
description=description,
|
||||
references=references,
|
||||
certificates=certificates,
|
||||
)
|
||||
|
||||
import teamlandkarte_mcp.mcp_server as mod
|
||||
|
||||
monkeypatch.setattr(mod, "create_db_client", lambda *_: fake_db)
|
||||
|
||||
srv = build_server(_make_config(tmp_path))
|
||||
content, structured = await srv.call_tool(
|
||||
"get_capacity_details", {"capacity_id": 1}
|
||||
)
|
||||
out = (structured or {}).get("result") or getattr(content[0], "text", "")
|
||||
|
||||
# Section headers present
|
||||
assert "## Beschreibung" in out
|
||||
assert "## Referenzen" in out
|
||||
assert "## Zertifizierungen" in out
|
||||
assert "## Next steps" in out
|
||||
|
||||
# Description content
|
||||
assert description in out
|
||||
|
||||
# References content: partner with name uses bold formatting
|
||||
assert "**Deutsche Bahn**: Projekt Reiseportal, Projekt Fahrplan" in out
|
||||
# Reference without partner_name shows only projects (no bold prefix)
|
||||
assert "- Internes Migrationsprojekt" in out
|
||||
assert "**Siemens**: IoT Platform" in out
|
||||
# Ensure the empty-partner reference does NOT have bold empty prefix
|
||||
assert "****:" not in out
|
||||
|
||||
# Certificates content
|
||||
assert "- AWS Solutions Architect Professional" in out
|
||||
assert "- Kubernetes Administrator (CKA)" in out
|
||||
|
||||
# Section ordering: Beschreibung < Referenzen < Zertifizierungen < Next steps
|
||||
idx_beschreibung = out.index("## Beschreibung")
|
||||
idx_referenzen = out.index("## Referenzen")
|
||||
idx_zertifizierungen = out.index("## Zertifizierungen")
|
||||
idx_next_steps = out.index("## Next steps")
|
||||
|
||||
assert idx_beschreibung < idx_referenzen < idx_zertifizierungen < idx_next_steps
|
||||
|
||||
# Capacity table appears before all enrichment sections
|
||||
assert "| capacity_id |" in out
|
||||
idx_table = out.index("| capacity_id |")
|
||||
assert idx_table < idx_beschreibung
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2.2 Test empty-state scenario
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_empty_state_scenario(monkeypatch, tmp_path) -> None:
|
||||
"""When all enrichment data is empty/None, (keine) placeholders appear."""
|
||||
|
||||
cap = _make_capacity()
|
||||
fake_db = _FakeDB(
|
||||
[cap],
|
||||
description=None,
|
||||
references=[],
|
||||
certificates=[],
|
||||
)
|
||||
|
||||
import teamlandkarte_mcp.mcp_server as mod
|
||||
|
||||
monkeypatch.setattr(mod, "create_db_client", lambda *_: fake_db)
|
||||
|
||||
srv = build_server(_make_config(tmp_path))
|
||||
content, structured = await srv.call_tool(
|
||||
"get_capacity_details", {"capacity_id": 1}
|
||||
)
|
||||
out = (structured or {}).get("result") or getattr(content[0], "text", "")
|
||||
|
||||
# All three sections show (keine) placeholders
|
||||
assert "Beschreibung: (keine)" in out
|
||||
assert "Referenzen: (keine)" in out
|
||||
assert "Zertifizierungen: (keine)" in out
|
||||
|
||||
# Section headers still present
|
||||
assert "## Beschreibung" in out
|
||||
assert "## Referenzen" in out
|
||||
assert "## Zertifizierungen" in out
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2.3 Test capacity-not-found unchanged
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_capacity_not_found_unchanged(monkeypatch, tmp_path) -> None:
|
||||
"""When capacity is not found, error message is returned without enrichment calls."""
|
||||
|
||||
fake_db = _FakeDB(
|
||||
[], # No capacities → get_capacity_by_id returns None
|
||||
description="Should not appear",
|
||||
references=[{"partner_name": "X", "projects": "Y"}],
|
||||
certificates=["Cert"],
|
||||
)
|
||||
|
||||
import teamlandkarte_mcp.mcp_server as mod
|
||||
|
||||
monkeypatch.setattr(mod, "create_db_client", lambda *_: fake_db)
|
||||
|
||||
srv = build_server(_make_config(tmp_path))
|
||||
content, structured = await srv.call_tool(
|
||||
"get_capacity_details", {"capacity_id": 999}
|
||||
)
|
||||
out = (structured or {}).get("result") or getattr(content[0], "text", "")
|
||||
|
||||
# Returns the standard error message
|
||||
assert out == "Capacity not found: 999"
|
||||
|
||||
# Enrichment data should NOT appear
|
||||
assert "## Beschreibung" not in out
|
||||
assert "## Referenzen" not in out
|
||||
assert "## Zertifizierungen" not in out
|
||||
assert "Should not appear" not in out
|
||||
Reference in New Issue
Block a user