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,145 @@
|
||||
"""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"
|
||||
)
|
||||
Reference in New Issue
Block a user