"""Unit tests for the Summary Reporter module.""" from __future__ import annotations from scripts.pat_manager.models import CheckResult, PatEntry, PatStatus, RotationResult from scripts.pat_manager.reporter import SummaryReporter def make_entry(service: str = "GitLab", token_name: str = "ci-token") -> PatEntry: """Helper to create a PatEntry for testing.""" return PatEntry( service=service, token_name=token_name, expiry_date="2025-08-15", renewal_method="auto" if service == "GitLab" else "manual", ) def make_result( service: str = "GitLab", token_name: str = "ci-token", status: PatStatus = PatStatus.HEALTHY, days_remaining: int | None = 45, error_message: str | None = None, ) -> CheckResult: """Helper to create a CheckResult for testing.""" entry = make_entry(service, token_name) return CheckResult( entry=entry, status=status, days_remaining=days_remaining, error_message=error_message, ) class TestSummaryReporter: """Tests for SummaryReporter.report().""" def test_report_header(self) -> None: """Report starts with the expected header.""" reporter = SummaryReporter() report = reporter.report([], []) assert "=== PAT Lifecycle Check Summary ===" in report def test_report_contains_table_headers(self) -> None: """Report contains column headers.""" reporter = SummaryReporter() results = [make_result()] report = reporter.report(results, []) assert "Service" in report assert "Token Name" in report assert "Status" in report assert "Details" in report def test_report_one_healthy_pat(self) -> None: """Report shows healthy PAT with days remaining.""" reporter = SummaryReporter() results = [make_result(days_remaining=45)] report = reporter.report(results, []) assert "GitLab" in report assert "ci-token" in report assert "healthy" in report assert "45 days remaining" in report def test_report_expiring_soon_pat(self) -> None: """Report shows expiring soon PAT with days remaining.""" reporter = SummaryReporter() results = [ make_result( service="Jira", token_name="jira-api", status=PatStatus.EXPIRING_SOON, days_remaining=7, ) ] report = reporter.report(results, []) assert "Jira" in report assert "jira-api" in report assert "expiring soon" in report assert "7 days remaining" in report def test_report_expired_pat(self) -> None: """Report shows expired PAT with 0 days remaining.""" reporter = SummaryReporter() results = [ make_result( service="Confluence", token_name="confluence-bot", status=PatStatus.EXPIRED, days_remaining=0, ) ] report = reporter.report(results, []) assert "Confluence" in report assert "confluence-bot" in report assert "expired" in report assert "0 days remaining" in report def test_report_check_failed_pat(self) -> None: """Report shows check failed PAT with error message.""" reporter = SummaryReporter() results = [ make_result( service="OrgMyLife", token_name="orchestrator-key", status=PatStatus.CHECK_FAILED, days_remaining=None, error_message="Connection timeout", ) ] report = reporter.report(results, []) assert "OrgMyLife" in report assert "orchestrator-key" in report assert "check failed" in report assert "Connection timeout" in report def test_report_multiple_pats(self) -> None: """Report contains one line per PAT.""" reporter = SummaryReporter() results = [ make_result(service="GitLab", token_name="token-a", days_remaining=45), make_result( service="Jira", token_name="token-b", status=PatStatus.EXPIRING_SOON, days_remaining=7, ), make_result( service="Confluence", token_name="token-c", status=PatStatus.EXPIRED, days_remaining=0, ), ] report = reporter.report(results, []) assert "token-a" in report assert "token-b" in report assert "token-c" in report def test_report_no_rotation_section_when_empty(self) -> None: """Report omits rotation section when no rotations occurred.""" reporter = SummaryReporter() results = [make_result()] report = reporter.report(results, []) assert "Rotation Results" not in report def test_report_includes_rotation_section(self) -> None: """Report includes rotation section with successful rotations.""" reporter = SummaryReporter() entry = make_entry("GitLab", "ci-pipeline-token") results = [make_result()] rotations = [ RotationResult( success=True, new_token="glpat-new-value", new_expiry_date="2025-08-01", error_message=None, entry=entry, old_expiry_date="2025-07-01", ) ] report = reporter.report(results, rotations) assert "--- Rotation Results ---" in report assert "GitLab/ci-pipeline-token" in report assert "Rotated successfully" in report assert "old: 2025-07-01" in report assert "new: 2025-08-01" in report def test_report_excludes_failed_rotations(self) -> None: """Report rotation section only shows successful rotations.""" reporter = SummaryReporter() results = [make_result()] rotations = [ RotationResult( success=False, new_token=None, new_expiry_date=None, error_message="Rotation failed", entry=make_entry("GitLab", "failed-token"), old_expiry_date="2025-07-01", ) ] report = reporter.report(results, rotations) assert "Rotation Results" not in report def test_report_empty_results(self) -> None: """Report handles empty results list gracefully.""" reporter = SummaryReporter() report = reporter.report([], []) assert "=== PAT Lifecycle Check Summary ===" in report