Files
Orchestrator/bahn/teamlandkarte-mcp/tests/test_team_profile_serialization_unit.py
T
ankn a5f8fb49ab 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.
2026-06-30 20:39:52 +02:00

362 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Feature: team-profile-matching, Task 5.5: Unit tests for build_team_profile / serialize_team_profile
"""Example-based unit tests for the team-profile builder and serializer.
These tests complement the property-based tests in
``tests/test_team_profile_serialization_pbt.py`` by pinning down concrete
example outputs for ``build_team_profile`` and ``serialize_team_profile``
defined in ``teamlandkarte_mcp.matching.profiles``.
Covered scenarios (see task 5.5 in the team-profile-matching spec):
* Empty strings on every text field plus empty competence/reference lists
still produce an output that contains all seven fixed German headings,
with empty values rendered after the heading and lists rendered as
``Kompetenzen: (keine)`` / ``Referenzen: (keine)``.
* Mixed top / non-top competences are rendered with the ``(Top)`` suffix
only on the top entries.
* References with a non-empty ``partner_name`` use the
``Partner: <name> Projekte: <projects>`` form (en-dash U+2013).
* References with an empty ``partner_name`` use the bare
``Projekte: <projects>`` form (no ``Partner:`` token, no placeholder).
* ``build_team_profile`` performs a 1:1 mapping from ``Team`` to
``TeamProfile`` without re-sorting, including the ouid, team_id and the
competence / reference lists.
* Empty competence / reference lists pass through ``build_team_profile``
unchanged.
**Validates: Requirements 5.1, 5.3, 5.4, 5.5, 5.6**
"""
from __future__ import annotations
from teamlandkarte_mcp.matching.profiles import (
TeamCompetenceEntry,
TeamProfile,
TeamReferenceEntry,
build_team_profile,
serialize_team_profile,
)
from teamlandkarte_mcp.models import Team, TeamCompetence, TeamReference
# ---------------------------------------------------------------------------
# build_team_profile
# ---------------------------------------------------------------------------
def test_build_team_profile_maps_all_fields_one_to_one() -> None:
"""``build_team_profile`` copies every ``Team`` field onto the profile.
Validates requirement 5.1: every team field (team_id, ouid, team_name,
focus_name, about_us, offerings, interests, competences, references)
appears on the resulting :class:`TeamProfile` with the exact same
value, in the exact same list order. ``team_id`` is converted to
``str`` for the profile ``id`` field.
"""
team = Team(
team_id="T-42",
ouid="ou-9",
team_name="Alpha Team",
focus_name="Backend",
about_us="Wir bauen APIs.",
offerings="Beratung",
interests="Cloud",
competences=[
TeamCompetence(name="Python", top_competency=True),
TeamCompetence(name="FastAPI", top_competency=False),
],
references=[
TeamReference(partner_name="Acme", projects="Project X"),
TeamReference(partner_name="", projects="Project Y"),
],
)
profile = build_team_profile(team)
assert profile.id == "T-42"
assert profile.ouid == "ou-9"
assert profile.team_name == "Alpha Team"
assert profile.focus_name == "Backend"
assert profile.about_us == "Wir bauen APIs."
assert profile.offerings == "Beratung"
assert profile.interests == "Cloud"
# Competences are preserved 1:1 and in the original order.
assert profile.competences == [
TeamCompetenceEntry(name="Python", top_competency=True),
TeamCompetenceEntry(name="FastAPI", top_competency=False),
]
# References are preserved 1:1 and in the original order.
assert profile.references == [
TeamReferenceEntry(partner_name="Acme", projects="Project X"),
TeamReferenceEntry(partner_name="", projects="Project Y"),
]
def test_build_team_profile_passes_empty_lists_through() -> None:
"""Empty ``competences`` and ``references`` lists pass through unchanged.
Validates requirement 5.1: an empty list on the input team yields an
empty list on the resulting profile, without dropping the profile or
inserting placeholder entries.
"""
team = Team(
team_id="T-empty",
ouid="ou-empty",
team_name="",
focus_name="",
about_us="",
offerings="",
interests="",
competences=[],
references=[],
)
profile = build_team_profile(team)
assert profile.competences == []
assert profile.references == []
# The string fields stay empty without any placeholder.
assert profile.team_name == ""
assert profile.focus_name == ""
assert profile.about_us == ""
assert profile.offerings == ""
assert profile.interests == ""
def test_build_team_profile_does_not_reorder_competences() -> None:
"""``build_team_profile`` preserves the input order of competences.
The DB layer is the single source of truth for the deterministic
competence order ``(top_competency desc, name asc)``; the builder
must not re-sort. Validates requirement 5.1 / 13.4.
"""
team = Team(
team_id="T-1",
ouid="ou-1",
team_name="Team",
focus_name="",
about_us="",
offerings="",
interests="",
competences=[
# Intentionally not in (top desc, name asc) order so a
# re-sort would be visible.
TeamCompetence(name="Zeta", top_competency=False),
TeamCompetence(name="Alpha", top_competency=True),
TeamCompetence(name="Beta", top_competency=False),
],
references=[],
)
profile = build_team_profile(team)
assert [c.name for c in profile.competences] == ["Zeta", "Alpha", "Beta"]
assert [c.top_competency for c in profile.competences] == [False, True, False]
# ---------------------------------------------------------------------------
# serialize_team_profile
# ---------------------------------------------------------------------------
def test_serialize_team_profile_empty_profile_emits_all_headings() -> None:
"""An empty profile still emits the seven fixed German headings.
Validates requirements 5.4 (fixed heading order) and the ``(keine)``
fallback for empty competence and reference lists.
"""
profile = TeamProfile(
id="",
ouid="",
team_name="",
focus_name="",
about_us="",
offerings="",
interests="",
competences=[],
references=[],
)
output = serialize_team_profile(profile)
expected = "\n".join(
[
"Teamname: ",
"Schwerpunkt: ",
"Über uns: ",
"Leistungen: ",
"Interessen: ",
"Kompetenzen: (keine)",
"Referenzen: (keine)",
]
)
assert output == expected
def test_serialize_team_profile_mixed_top_and_non_top_competences() -> None:
"""Top competences carry the ``(Top)`` suffix; non-top entries do not.
Validates requirement 5.5: the marker is rendered as ``" (Top)"``
directly after the competence name, exactly for entries with
``top_competency=True``.
"""
profile = TeamProfile(
id="T-1",
ouid="ou-1",
team_name="Team Alpha",
focus_name="Backend",
about_us="",
offerings="",
interests="",
competences=[
TeamCompetenceEntry(name="Python", top_competency=True),
TeamCompetenceEntry(name="FastAPI", top_competency=False),
TeamCompetenceEntry(name="SQL", top_competency=True),
],
references=[],
)
output = serialize_team_profile(profile)
assert "Kompetenzen:\n- Python (Top)\n- FastAPI\n- SQL (Top)" in output
# Non-top entries must not accidentally pick up the marker.
assert "FastAPI (Top)" not in output
def test_serialize_team_profile_reference_with_partner_name() -> None:
"""References with a partner render as ``Partner: <name> Projekte: <projects>``.
Validates requirement 5.6: both partner name and projects text are
visible, joined with the en-dash U+2013 (``\u2013``).
"""
profile = TeamProfile(
id="T-1",
ouid="ou-1",
team_name="Team",
focus_name="",
about_us="",
offerings="",
interests="",
competences=[],
references=[
TeamReferenceEntry(partner_name="Acme", projects="Project X"),
],
)
output = serialize_team_profile(profile)
assert "Referenzen:\n- Partner: Acme \u2013 Projekte: Project X" in output
def test_serialize_team_profile_reference_without_partner_name() -> None:
"""References without a partner render as bare ``Projekte: <projects>``.
Validates requirement 5.3: an empty ``partner_name`` suppresses the
``Partner: `` token entirely, and no placeholder is inserted.
"""
profile = TeamProfile(
id="T-1",
ouid="ou-1",
team_name="Team",
focus_name="",
about_us="",
offerings="",
interests="",
competences=[],
references=[
TeamReferenceEntry(partner_name="", projects="Project Y"),
],
)
output = serialize_team_profile(profile)
# Locate the references block to make the absence-assertion robust
# against unrelated text in other fields.
refs_start = output.index("Referenzen:")
refs_block = output[refs_start:]
assert "- Projekte: Project Y" in refs_block
assert "Partner: " not in refs_block
# No en-dash filler when there is no partner.
assert "\u2013" not in refs_block
def test_serialize_team_profile_mixed_references_with_and_without_partner() -> None:
"""Mixed references render each entry with the appropriate shape.
Validates requirements 5.3 and 5.6 jointly: in a single profile, a
reference with a partner uses the ``Partner: ... Projekte: ...``
form, and a reference without a partner uses the bare
``Projekte: ...`` form. Order is preserved 1:1.
"""
profile = TeamProfile(
id="T-1",
ouid="ou-1",
team_name="Team",
focus_name="",
about_us="",
offerings="",
interests="",
competences=[],
references=[
TeamReferenceEntry(partner_name="Acme", projects="Project X"),
TeamReferenceEntry(partner_name="", projects="Project Y"),
],
)
output = serialize_team_profile(profile)
expected_block = (
"Referenzen:\n"
"- Partner: Acme \u2013 Projekte: Project X\n"
"- Projekte: Project Y"
)
assert expected_block in output
def test_serialize_team_profile_full_example_round_trip() -> None:
"""End-to-end example: build then serialize a populated team.
Validates requirements 5.1, 5.3, 5.4, 5.5, 5.6 together: the full
serialized output for a representative team matches the expected
fixed-format string exactly, including heading order, ``(Top)``
markers, and reference shapes.
"""
team = Team(
team_id="T-42",
ouid="ou-9",
team_name="Alpha Team",
focus_name="Backend",
about_us="Wir bauen APIs.",
offerings="Beratung",
interests="Cloud",
competences=[
TeamCompetence(name="Python", top_competency=True),
TeamCompetence(name="FastAPI", top_competency=False),
],
references=[
TeamReference(partner_name="Acme", projects="Project X"),
TeamReference(partner_name="", projects="Project Y"),
],
)
output = serialize_team_profile(build_team_profile(team))
expected = "\n".join(
[
"Teamname: Alpha Team",
"Schwerpunkt: Backend",
"Über uns: Wir bauen APIs.",
"Leistungen: Beratung",
"Interessen: Cloud",
"Kompetenzen:",
"- Python (Top)",
"- FastAPI",
"Referenzen:",
"- Partner: Acme \u2013 Projekte: Project X",
"- Projekte: Project Y",
]
)
assert output == expected