Files
Orchestrator/bahn/teamlandkarte-mcp/tests/test_profile_serialization_pbt.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

147 lines
4.7 KiB
Python

# Feature: llm-fulltext-matching, Property 1: Profil-Serialisierung ist deterministisch und feldvollständig
"""Property-based tests for deterministic profile serialization.
These tests cover the deterministic serialization helpers
``serialize_capacity_profile`` and ``serialize_task_profile`` defined in
``teamlandkarte_mcp.matching.profiles``. They verify three core properties
that hold for every randomly generated profile:
1. **Determinism**: calling the serializer twice on the same profile yields
exactly identical strings.
2. **Field-heading completeness**: the serialized output always contains
every field heading required by the spec.
3. **Partner-name visibility**: when a ``CapacityReferenceEntry`` has a
non-empty ``partner_name``, that name appears as a substring of the
serialized capacity profile.
**Validates: Requirements 3.3, 3.4, 3.6, 3.7, 4.3, 4.4**
"""
from __future__ import annotations
from hypothesis import given, settings
from hypothesis import strategies as st
from teamlandkarte_mcp.matching.profiles import (
CapacityProfile,
CapacityReferenceEntry,
TaskProfile,
serialize_capacity_profile,
serialize_task_profile,
)
# Small text strategy to keep generation fast and deterministic.
_text = st.text(max_size=40)
# `partner_name` may be empty (NULL `partner_id` / Join-Mismatch) or any
# short non-empty text.
_partner_name = st.one_of(st.just(""), st.text(min_size=1, max_size=20))
# The builder strips empty `projects`, but the serializer renders every
# entry it receives. Use non-whitespace projects so containment assertions
# are unambiguous.
_projects = st.text(min_size=1, max_size=40).filter(lambda s: s.strip() != "")
_reference = st.builds(
CapacityReferenceEntry,
partner_name=_partner_name,
projects=_projects,
)
_capacity_profile = st.builds(
CapacityProfile,
id=_text,
owner_name=_text,
role_name=_text,
competences=st.lists(_text, max_size=5),
description=_text,
references=st.lists(_reference, max_size=5),
certificates=st.lists(
_text.filter(lambda s: s.strip() != ""), max_size=5
),
)
_task_profile = st.builds(
TaskProfile,
id=_text,
title=_text,
description=_text,
skills=st.lists(_text, max_size=5),
)
_CAPACITY_HEADINGS = (
"Rolle:",
"Kompetenzen:",
"Beschreibung:",
"Referenzen:",
"Zertifikate:",
)
_TASK_HEADINGS = (
"Titel:",
"Beschreibung:",
"Gesuchte Kompetenzen:",
)
@settings(max_examples=100, deadline=None)
@given(profile=_capacity_profile)
def test_capacity_profile_serialization_is_deterministic_and_complete(
profile: CapacityProfile,
) -> None:
"""Property 1 (Capacity): determinism, heading completeness, partner name.
For any :class:`CapacityProfile` (including references with empty or
non-empty ``partner_name``) the serializer must:
* produce identical strings on repeated calls (determinism),
* always include every field heading required by the spec, and
* include every non-empty ``partner_name`` from the references list as
a substring of the serialized output.
"""
first = serialize_capacity_profile(profile)
second = serialize_capacity_profile(profile)
# Determinism: two consecutive calls return the exact same string.
assert first == second, "serialize_capacity_profile is not deterministic"
# Every field heading must always be present.
for heading in _CAPACITY_HEADINGS:
assert heading in first, (
f"Heading {heading!r} missing from capacity serialization: {first!r}"
)
# Each reference with a non-empty partner_name must appear verbatim.
for ref in profile.references:
if ref.partner_name:
assert ref.partner_name in first, (
f"Partner name {ref.partner_name!r} missing from "
f"serialization: {first!r}"
)
@settings(max_examples=100, deadline=None)
@given(profile=_task_profile)
def test_task_profile_serialization_is_deterministic_and_complete(
profile: TaskProfile,
) -> None:
"""Property 1 (Task): determinism and heading completeness.
For any :class:`TaskProfile` the serializer must produce identical
strings on repeated calls and always include every field heading
required by the spec.
"""
first = serialize_task_profile(profile)
second = serialize_task_profile(profile)
# Determinism: two consecutive calls return the exact same string.
assert first == second, "serialize_task_profile is not deterministic"
# Every field heading must always be present.
for heading in _TASK_HEADINGS:
assert heading in first, (
f"Heading {heading!r} missing from task serialization: {first!r}"
)