"""Unit tests for the report command's loading-status presentation layer.""" from __future__ import annotations import io import sys import unittest from datetime import datetime from unittest import mock from weekly_activity.aggregate import SourceSpec, collect_specs from weekly_activity.cli import _collect_report_sources from weekly_activity.model import ActivityReport, Section class StubSource: """Deterministic stand-in satisfying the ActivitySource interface.""" def __init__(self, *, fail: bool = False) -> None: self._fail = fail self.name = "stub" def collect(self, username: str, since: datetime, until: datetime) -> ActivityReport: if self._fail: raise RuntimeError("some query failed") return ActivityReport( source=username, username=username, since=since, until=until, sections=[Section(title="Patches", entries=["one commit"])], ) def spec(name: str, *, fail: bool = False) -> SourceSpec: source = StubSource(fail=fail) return SourceSpec(name=name, username="me", make=lambda: source) WINDOW = (datetime(2026, 8, 19), datetime(2026, 8, 26)) class FakeTty(io.StringIO): """Capture buffer that claims to be a terminal so status lines activate.""" def isatty(self) -> bool: return True class ReportStatusTest(unittest.TestCase): def test_status_is_silent_without_a_tty(self) -> None: specs = [spec("alpha"), spec("beta")] buffer = io.StringIO() with mock.patch.object(sys, "stdout", buffer): collected = _collect_report_sources(specs, *WINDOW) self.assertEqual(buffer.getvalue(), "") self.assertEqual(collected, collect_specs(specs, *WINDOW)) def test_status_names_current_source_then_clears_on_a_tty(self) -> None: specs = [spec("launchpad"), spec("github/personal"), spec("broken", fail=True)] buffer = FakeTty() with mock.patch.object(sys, "stdout", buffer): collected = _collect_report_sources(specs, *WINDOW) text = buffer.getvalue() self.assertIn("# Generating report: pulling launchpad data...", text) self.assertIn("# Generating report: pulling github/personal data...", text) self.assertNotIn("# Generating report: pulling broken data...\n", text) # The final erase leaves nothing visible behind the transient status. self.assertTrue(text.endswith("\x1b[2K\r")) self.assertEqual(collected, collect_specs(specs, *WINDOW)) errors = [item.error for item in collected if item.error is not None] self.assertTrue(errors and all("RuntimeError" in err for err in errors)) if __name__ == "__main__": unittest.main()