"""Check progress reporter (TTY and NO_COLOR behaviour).""" from __future__ import annotations import io from pathlib import Path import pytest from applepy.check_progress import ( CheckProgressReporter, _ascii_progress_bar, _completion_percent, check_run_failed, ) from applepy.context import RunContext from applepy.findings import Finding, Severity from applepy.registry import CheckRegistry from applepy.runner import run_phase def test_ascii_progress_bar_edges() -> None: assert _ascii_progress_bar(0, 4, 8) == "[--------]" assert _ascii_progress_bar(4, 4, 8) == "[########]" assert "?" in _ascii_progress_bar(0, 0, 6) def test_completion_percent_saturates() -> None: assert _completion_percent(0, 10) == 0 assert _completion_percent(5, 10) == 50 assert _completion_percent(10, 10) == 100 assert _completion_percent(0, 0) == 100 def test_check_done_includes_percent_and_bar(monkeypatch: pytest.MonkeyPatch) -> None: buf = io.StringIO() monkeypatch.setenv("NO_COLOR", "1") r = CheckProgressReporter(stream=buf) r.check_done(3, 10, "short_name", 0.05, 0, False) line = buf.getvalue() assert " 30%" in line assert "[###" in line or "[##" in line def test_phase_end_colours_nonzero_counts_when_force_colour( monkeypatch: pytest.MonkeyPatch, ) -> None: buf = io.StringIO() monkeypatch.delenv("NO_COLOR", raising=False) monkeypatch.setenv("FORCE_COLOR", "1") monkeypatch.setattr(buf, "isatty", lambda: True) r = CheckProgressReporter(stream=buf) f = Finding( id="h1", title="t", category="c", severity=Severity.HIGH, description="d", evidence="e", worksheet="W", ) r.phase_end("unprivileged", [f]) text = buf.getvalue() assert "\033[33m" in text assert "high:" in text def test_phase_lines_contain_no_escapes_when_colour_disabled(monkeypatch: pytest.MonkeyPatch) -> None: buf = io.StringIO() monkeypatch.setenv("NO_COLOR", "1") r = CheckProgressReporter(stream=buf) r.phase_begin("unprivileged", 2) r.check_start(1, 2, "a") r.check_done(1, 2, "a", 0.1, 1, False) f = Finding( id="x", title="t", category="c", severity=Severity.INFORMATIONAL, description="d", evidence="e", worksheet="W", ) r.phase_end("unprivileged", [f]) r.scan_complete([f], "/tmp/out") text = buf.getvalue() assert "\033" not in text assert "Unprivileged phase" in text assert "Scan complete" in text assert "critical: 0" in text assert "informational: 1" in text def test_check_run_failed_detects_run_prefix() -> None: f = Finding( id="run-deadbeef01", title="Check failed", category="Scanner reliability", severity=Severity.LOW, description="d", evidence="e", worksheet="Scanner reliability", ) assert check_run_failed([f]) is True assert check_run_failed([]) is False def test_run_phase_invokes_progress_sequential(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: buf = io.StringIO() monkeypatch.setenv("NO_COLOR", "1") r = CheckProgressReporter(stream=buf) reg = CheckRegistry() def _one(_ctx: RunContext) -> list[Finding]: return [] reg.register("z_check", _one, phases=("unprivileged",)) base = RunContext(home=tmp_path, output_dir=tmp_path, phase="unprivileged") run_phase(reg, "unprivileged", base, parallel=False, progress=r) out = buf.getvalue() assert "z_check" in out assert "running" in out assert "ok" in out