Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 86 additions & 10 deletions tests/copilot_usage/test_render_detail.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
_render_recent_events,
_render_shutdown_cycles,
_safe_event_data,
_truncate,
render_session_detail,
)

Expand Down Expand Up @@ -86,23 +87,20 @@ class TestRenderCodeChanges:

def test_none_produces_no_output(self) -> None:
"""code_changes=None → returns immediately without printing."""
buf = io.StringIO()
console = Console(file=buf, force_terminal=True)
buf, console = _buf_console()
_render_code_changes(None, target_console=console)
assert buf.getvalue() == ""

def test_all_zero_produces_no_output(self) -> None:
"""All fields zero/empty → returns without printing."""
buf = io.StringIO()
console = Console(file=buf, force_terminal=True)
buf, console = _buf_console()
changes = CodeChanges(linesAdded=0, linesRemoved=0, filesModified=[])
_render_code_changes(changes, target_console=console)
assert buf.getvalue() == ""

def test_with_data_shows_table(self) -> None:
"""Non-zero code changes → renders a table with stats."""
buf = io.StringIO()
console = Console(file=buf, force_terminal=True)
buf, console = _buf_console()
changes = CodeChanges(linesAdded=10, linesRemoved=2, filesModified=["a.py"])
_render_code_changes(changes, target_console=console)
output = buf.getvalue()
Expand All @@ -112,8 +110,7 @@ def test_with_data_shows_table(self) -> None:

def test_files_present_zero_line_counts_renders_table(self) -> None:
"""filesModified non-empty with zero line deltas → table IS rendered."""
buf = io.StringIO()
console = Console(file=buf, force_terminal=True)
buf, console = _buf_console()
changes = CodeChanges(linesAdded=0, linesRemoved=0, filesModified=["a.py"])
_render_code_changes(changes, target_console=console)
output = _strip_ansi(buf.getvalue())
Expand All @@ -125,8 +122,7 @@ def test_files_present_zero_line_counts_renders_table(self) -> None:

def test_zero_files_positive_line_counts_renders_table(self) -> None:
"""Empty filesModified with positive line counts → table IS rendered."""
buf = io.StringIO()
console = Console(file=buf, force_terminal=True)
buf, console = _buf_console()
changes = CodeChanges(linesAdded=5, linesRemoved=2, filesModified=[])
_render_code_changes(changes, target_console=console)
output = buf.getvalue()
Expand Down Expand Up @@ -957,3 +953,83 @@ def test_build_event_details_returns_empty_for_unrecognized_type() -> None:
"""Wildcard case must return '' for event types without explicit handling."""
ev = SessionEvent(type=EventType.SESSION_RESUME, data={})
assert _build_event_details(ev) == ""


# ---------------------------------------------------------------------------
# _build_event_details — SESSION_SHUTDOWN arm (issue #1058)
# ---------------------------------------------------------------------------


class TestBuildEventDetailsSessionShutdown:
"""Tests for the SESSION_SHUTDOWN branch of _build_event_details."""

def test_non_empty_shutdown_type(self) -> None:
"""Non-empty shutdownType must render as 'type=<value>'."""
ev = SessionEvent(
type=EventType.SESSION_SHUTDOWN,
data={
"shutdownType": "routine",
"totalPremiumRequests": 0,
"totalApiDurationMs": 0,
"modelMetrics": {},
},
)
assert _build_event_details(ev) == "type=routine"

def test_empty_shutdown_type(self) -> None:
"""Empty shutdownType must render as ''."""
ev = SessionEvent(
type=EventType.SESSION_SHUTDOWN,
data={
"shutdownType": "",
"totalPremiumRequests": 0,
"totalApiDurationMs": 0,
"modelMetrics": {},
},
)
assert _build_event_details(ev) == ""

def test_malformed_data_returns_empty(self) -> None:
"""Malformed data (int shutdownType) triggers ValidationError → ''."""
ev = SessionEvent(
type=EventType.SESSION_SHUTDOWN,
data={"shutdownType": 99}, # int triggers ValidationError
)
assert _build_event_details(ev) == ""


# ---------------------------------------------------------------------------
# _render_shutdown_cycles — None timestamp path (issue #1058)
# ---------------------------------------------------------------------------


class TestRenderShutdownCyclesNoneTimestamp:
"""A shutdown cycle with ts=None must display '—' in the Date column."""

def test_none_timestamp_renders_dash(self) -> None:
"""A shutdown cycle with ts=None must display '—' in the Date column."""
sd = SessionShutdownData(
shutdownType="routine",
totalPremiumRequests=1,
totalApiDurationMs=500,
modelMetrics={},
)
summary = SessionSummary(
session_id="no-ts",
shutdown_cycles=[(None, sd)],
)
buf, console = _buf_console()
_render_shutdown_cycles(summary, target_console=console)
output = _strip_ansi(buf.getvalue())
assert "—" in output


# ---------------------------------------------------------------------------
# _truncate — max_len ≤ 0 guard (issue #1058)
# ---------------------------------------------------------------------------


@pytest.mark.parametrize("max_len", [0, -1, -100])
def test_truncate_non_positive_max_len_returns_empty(max_len: int) -> None:
"""_truncate must return '' for any max_len ≤ 0."""
assert _truncate("hello", max_len) == ""
Loading