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.
113 lines
4.2 KiB
Python
113 lines
4.2 KiB
Python
# Feature: llm-fulltext-matching, Property 7: Tabellen-Rationale ist gültiges Markdown und längenbegrenzt
|
|
"""Property-based tests for ``_format_rationale_for_table``.
|
|
|
|
For any input string ``R``, the helper
|
|
:func:`teamlandkarte_mcp.mcp_server._format_rationale_for_table` must:
|
|
|
|
1. Produce output of at most 280 characters.
|
|
2. Never emit a literal pipe ``|`` (so the Markdown table stays valid).
|
|
3. Never emit ``\\n`` or ``\\r`` (whitespace is collapsed to spaces).
|
|
4. Match the canonical normalized form when the normalized form fits in
|
|
280 characters; otherwise end with the ellipsis character ``…``
|
|
(U+2026) and equal ``normalized[:279].rstrip() + "…"``.
|
|
|
|
The "normalized" form is defined as
|
|
``" ".join(R.replace("|", "/").replace("\\r", " ").replace("\\n", " ").split())``.
|
|
|
|
**Validates: Requirements 8.4, 8.5**
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from hypothesis import given, settings
|
|
from hypothesis import strategies as st
|
|
|
|
from teamlandkarte_mcp.mcp_server import _format_rationale_for_table
|
|
|
|
|
|
# Surrogate code points (Cs) are intentionally excluded; Hypothesis can
|
|
# otherwise produce lone surrogates that would break the assertions on
|
|
# string equality. Control characters (``\\n``, ``\\r``, ``\\t``...) are
|
|
# kept on purpose: the helper is supposed to handle them explicitly.
|
|
_TEXT_STRATEGY = st.text(
|
|
alphabet=st.characters(blacklist_categories=("Cs",)),
|
|
max_size=600,
|
|
)
|
|
|
|
|
|
def _normalize(text: str) -> str:
|
|
"""Replicate the helper's normalization (without truncation)."""
|
|
cleaned = text.replace("|", "/").replace("\r", " ").replace("\n", " ")
|
|
return " ".join(cleaned.split())
|
|
|
|
|
|
@settings(max_examples=100, deadline=None)
|
|
@given(text=_TEXT_STRATEGY)
|
|
def test_format_rationale_is_bounded_safe_and_canonical(text: str) -> None:
|
|
"""Property 7: bounded length, no pipes/newlines, deterministic shape."""
|
|
out = _format_rationale_for_table(text)
|
|
|
|
# 1. Length is bounded by 280.
|
|
assert len(out) <= 280, (
|
|
f"output longer than 280 chars: {len(out)} for input {text!r}"
|
|
)
|
|
|
|
# 2. Pipe characters are translated.
|
|
assert "|" not in out, f"pipe leaked into output: {out!r}"
|
|
|
|
# 3. Newlines/carriage returns are translated.
|
|
assert "\n" not in out, f"newline leaked into output: {out!r}"
|
|
assert "\r" not in out, f"carriage return leaked into output: {out!r}"
|
|
|
|
# 4. Match canonical form, with truncation behaviour.
|
|
normalized = _normalize(text)
|
|
if len(normalized) <= 280:
|
|
assert out == normalized, (
|
|
f"expected verbatim normalized form, got {out!r} "
|
|
f"(normalized={normalized!r})"
|
|
)
|
|
# Note: ``out`` may legitimately end with ``…`` if the input itself
|
|
# contained an ellipsis. The contract is that the helper does not
|
|
# *append* an ellipsis when the input fits, which is implied by
|
|
# ``out == normalized``.
|
|
else:
|
|
expected = normalized[:279].rstrip() + "\u2026"
|
|
assert out == expected, (
|
|
f"expected truncated form {expected!r}, got {out!r}"
|
|
)
|
|
assert out.endswith("\u2026"), (
|
|
"truncated output must end with the ellipsis character"
|
|
)
|
|
|
|
|
|
@settings(max_examples=100, deadline=None)
|
|
@given(
|
|
text=st.text(
|
|
alphabet=st.characters(
|
|
blacklist_categories=("Cs",),
|
|
blacklist_characters="|\r\n",
|
|
),
|
|
min_size=281,
|
|
max_size=600,
|
|
)
|
|
)
|
|
def test_format_rationale_truncates_when_normalized_too_long(text: str) -> None:
|
|
"""Property 7 (forced truncation): outputs longer than 280 are shortened.
|
|
|
|
Generates text without pipes/newlines so the normalized form preserves
|
|
most of the input length and almost certainly exceeds 280 characters
|
|
(after whitespace collapsing). When that happens, the output must end
|
|
with ``…`` and equal ``normalized[:279].rstrip() + "…"``.
|
|
"""
|
|
out = _format_rationale_for_table(text)
|
|
normalized = _normalize(text)
|
|
|
|
assert len(out) <= 280
|
|
if len(normalized) > 280:
|
|
assert out.endswith("\u2026")
|
|
assert out == normalized[:279].rstrip() + "\u2026"
|
|
else:
|
|
# Whitespace collapse may rarely shrink below 281; in that case
|
|
# the helper returns the verbatim normalized form.
|
|
assert out == normalized
|