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.
147 lines
4.7 KiB
Python
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}"
|
|
)
|