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.
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user