Files
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

146 lines
4.6 KiB
Python

"""Property-based tests for the Summary Reporter module using Hypothesis."""
from __future__ import annotations
from hypothesis import given, settings, assume
from hypothesis import strategies as st
from scripts.pat_manager.models import CheckResult, PatEntry, PatStatus
from scripts.pat_manager.reporter import SummaryReporter
# --- Strategies ---
_SERVICES_AND_METHODS = [
("GitLab", "auto"),
("Jira", "manual"),
("Confluence", "manual"),
("OrgMyLife", "manual"),
]
_ALL_STATUSES = [
PatStatus.HEALTHY,
PatStatus.EXPIRING_SOON,
PatStatus.EXPIRED,
PatStatus.CHECK_FAILED,
]
@st.composite
def simple_token_names(draw: st.DrawFn) -> str:
"""Generate simple token names using alphanumeric + hyphens for reliable report matching."""
name = draw(
st.text(
alphabet=st.characters(
whitelist_categories=("Ll", "Lu", "Nd"),
whitelist_characters="-",
),
min_size=1,
max_size=64,
)
)
# Ensure name starts with a letter (not hyphen) and is non-empty after strip
assume(len(name.strip()) > 0)
assume(name[0].isalpha())
return name
@st.composite
def valid_iso_dates(draw: st.DrawFn) -> str:
"""Generate valid ISO 8601 date strings (YYYY-MM-DD)."""
year = draw(st.integers(min_value=2020, max_value=2099))
month = draw(st.integers(min_value=1, max_value=12))
if month in (1, 3, 5, 7, 8, 10, 12):
max_day = 31
elif month in (4, 6, 9, 11):
max_day = 30
elif month == 2:
if year % 4 == 0 and (year % 100 != 0 or year % 400 == 0):
max_day = 29
else:
max_day = 28
else:
max_day = 28
day = draw(st.integers(min_value=1, max_value=max_day))
return f"{year:04d}-{month:02d}-{day:02d}"
@st.composite
def check_results(draw: st.DrawFn) -> CheckResult:
"""Generate a CheckResult with a random status and appropriate fields."""
service, renewal_method = draw(st.sampled_from(_SERVICES_AND_METHODS))
token_name = draw(simple_token_names())
expiry_date = draw(st.one_of(st.none(), valid_iso_dates()))
status = draw(st.sampled_from(_ALL_STATUSES))
# Generate appropriate days_remaining and error_message based on status
if status == PatStatus.CHECK_FAILED:
days_remaining = None
error_message = draw(
st.text(
alphabet=st.characters(whitelist_categories=("L", "N", "Z")),
min_size=3,
max_size=50,
)
)
elif status == PatStatus.EXPIRED:
days_remaining = 0
error_message = None
elif status == PatStatus.EXPIRING_SOON:
days_remaining = draw(st.integers(min_value=1, max_value=14))
error_message = None
else: # HEALTHY
days_remaining = draw(st.integers(min_value=15, max_value=365))
error_message = None
entry = PatEntry(
service=service,
token_name=token_name,
expiry_date=expiry_date,
renewal_method=renewal_method,
)
return CheckResult(
entry=entry,
status=status,
days_remaining=days_remaining,
error_message=error_message,
)
# --- Property 11 Test ---
# Feature: pat-renewal, Property 11: Summary report completeness
class TestSummaryReportCompleteness:
"""
**Validates: Requirements 5.3**
Property 11: For any list of check results, the generated summary report
SHALL contain one line per result, and each line SHALL include the service
name, token identifier, and classification status.
"""
@given(results=st.lists(check_results(), min_size=1, max_size=10))
@settings(max_examples=100)
def test_report_contains_service_token_and_status_for_each_result(
self, results: list[CheckResult]
) -> None:
"""For each check result, the report contains the service name,
token name, and status value."""
reporter = SummaryReporter()
report = reporter.report(results, [])
for result in results:
# The report must contain the service name
assert result.entry.service in report, (
f"Service '{result.entry.service}' not found in report"
)
# The report must contain the token name
assert result.entry.token_name in report, (
f"Token name '{result.entry.token_name}' not found in report"
)
# The report must contain the status value
assert result.status.value in report, (
f"Status '{result.status.value}' not found in report"
)