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.
362 lines
12 KiB
Python
362 lines
12 KiB
Python
# 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
|