from __future__ import annotations from teamlandkarte_mcp.cache.search_cache import SearchCache def test_filter_id_increments_under_same_search_id() -> None: cache = SearchCache(ttl_minutes=60, max_size=10) search_id = cache.store_search( task_id=None, requirements={}, results={"by_category": {}} ) filter_id_1 = cache.add_filter( search_id, filtered_results={"by_category": {}}, filter_meta={} ) filter_id_2 = cache.add_filter( search_id, filtered_results={"by_category": {}}, filter_meta={} ) assert filter_id_1 == "filter-1" assert filter_id_2 == "filter-2" entry = cache.get(search_id) assert entry is not None assert filter_id_1 in entry.filters assert filter_id_2 in entry.filters # --------------------------------------------------------------------------- # Team-search coverage (Profile_Type="team") # --------------------------------------------------------------------------- # # The SearchCache itself is search-type-agnostic - it stores whatever # ``results`` payload it receives. The tests below exercise the same # cache primitives (``store_search``, ``add_filter``, ``get``) for # ``team_search`` payloads in both ``matching_method`` flavours # (``score`` and ``llm_fulltext``) so the entire cache surface is # covered for both ``Profile_Type`` values. # # _Requirements: 8.1, 8.2, 8.3, 12.7_ def _team_payload(matching_method: str) -> dict: """Return a minimal team_search payload mimicking the shapes that ``find_matching_teams`` persists in either ``matching_method`` mode. """ base_team = { "team_id": "t1", "ouid": "ou-1", "team_name": "Team Alpha", "focus_name": "Backend Developer", "about_us": "", "offerings": "", "interests": "", "competences": [{"name": "python", "top_competency": True}], "references": [], "category": "Top", } if matching_method == "score": scored_team = { **base_team, "competence_score": 1.0, "role_score": 1.0, "overall_score": 1.0, } return { "search_type": "team_search", "matching_method": "score", "reference": { "availability_date_start": None, "availability_date_end": None, }, "summary": {"Top": 1, "Good": 0, "Partial": 0, "Low": 0, "Irrelevant": 0}, "by_category": { "Top": [scored_team], "Good": [], "Partial": [], "Low": [], "Irrelevant": [], }, } # llm_fulltext llm_team = {**base_team, "rationale": "Backend match."} return { "search_type": "team_search", "matching_method": "llm_fulltext", "reference": { "availability_date_start": None, "availability_date_end": None, }, "summary": {"Top": 1, "Good": 0, "Partial": 0, "Low": 0, "Irrelevant": 0}, "by_category": { "Top": [llm_team], "Good": [], "Partial": [], "Low": [], "Irrelevant": [], }, "errors": [], } def test_team_search_cache_roundtrip_score_mode() -> None: """``team_search`` payloads round-trip through the cache and keep their ``search_type``/``matching_method`` markers (Anforderungen 8.1, 8.2, 8.3).""" cache = SearchCache(ttl_minutes=60, max_size=10) payload = _team_payload("score") search_id = cache.store_search( task_id=None, requirements={"role_name": "Backend Developer", "competences": ["python"]}, results=payload, ) entry = cache.get(search_id) assert entry is not None assert entry.results["search_type"] == "team_search" assert entry.results["matching_method"] == "score" # The cached items keep the team-specific fields used by the # team-search column layout (Anforderung 8.4). top = entry.results["by_category"]["Top"] assert top and top[0]["team_id"] == "t1" assert "competence_score" in top[0] assert "role_score" in top[0] assert "overall_score" in top[0] def test_team_search_cache_roundtrip_llm_fulltext_mode() -> None: """``team_search`` LLM-mode payloads survive a cache round trip including the ``rationale`` field used by ``Begründung``.""" cache = SearchCache(ttl_minutes=60, max_size=10) payload = _team_payload("llm_fulltext") search_id = cache.store_search( task_id=None, requirements={"role_name": "Backend Developer", "competences": ["python"]}, results=payload, ) entry = cache.get(search_id) assert entry is not None assert entry.results["search_type"] == "team_search" assert entry.results["matching_method"] == "llm_fulltext" # ``errors`` list is preserved for the LLM-fulltext path # (Anforderung 7.6). assert entry.results.get("errors") == [] top = entry.results["by_category"]["Top"] assert top and top[0]["team_id"] == "t1" assert top[0]["category"] == "Top" assert top[0]["rationale"] == "Backend match." def test_team_search_filter_ids_increment_independently() -> None: """``add_filter`` produces incrementing ids for ``team_search`` entries just like for capacity searches. """ cache = SearchCache(ttl_minutes=60, max_size=10) search_id = cache.store_search( task_id=None, requirements={"role_name": "Backend Developer", "competences": ["python"]}, results=_team_payload("score"), ) fid1 = cache.add_filter( search_id, filtered_results={"by_category": {}, "search_type": "team_search", "matching_method": "score"}, filter_meta={"role_filter": "Backend"}, ) fid2 = cache.add_filter( search_id, filtered_results={"by_category": {}, "search_type": "team_search", "matching_method": "score"}, filter_meta={"role_filter": "Frontend"}, ) assert fid1 == "filter-1" assert fid2 == "filter-2" entry = cache.get(search_id) assert entry is not None # The filter payloads keep the team_search marker independent of # the parent entry, so downstream consumers can distinguish them. for fid in (fid1, fid2): fdata = entry.filters[fid] assert fdata["results"]["search_type"] == "team_search" assert fdata["results"]["matching_method"] == "score"