"""`ValidationReport.render()` is deterministic, rule-naming, and bounded. The CLI (task 05) prints this text verbatim and the content-gate criterion greps it for rule names, so the render must be stable (same report, same bytes — input order cannot leak in), mention every rule id it holds, stay free of ANSI escapes, and not explode into a wall of text for a broken level. """ from __future__ import annotations import pytest from undercooked.sim.validation import LevelViolation, RuleId, ValidationReport pytestmark = pytest.mark.unit def _violation(rule: str, level_id: str, message: str) -> LevelViolation: return LevelViolation(rule=rule, level_id=level_id, message=message) def _ten_violation_report() -> ValidationReport: errors = tuple( _violation(rule, f"level_{index % 2}", f"synthetic {rule} message {index}") for index, rule in enumerate(RuleId.ALL[:10]) ) warnings = (_violation(RuleId.TARGET_IMPLAUSIBLE, "level_0", "tight but fair"),) return ValidationReport(violations=errors, warnings=warnings) def test_render_is_deterministic_across_runs_and_input_order() -> None: report = _ten_violation_report() assert report.render() == report.render() shuffled = ValidationReport( violations=tuple(reversed(report.violations)), warnings=report.warnings, ) assert shuffled.render() == report.render() def test_render_names_every_rule_level_and_severity_without_ansi() -> None: rendered = _ten_violation_report().render() for violation in _ten_violation_report().violations: assert violation.rule in rendered assert violation.message in rendered assert "level_0" in rendered and "level_1" in rendered assert "ERROR" in rendered and "WARN" in rendered assert "\x1b" not in rendered def test_render_stays_under_40_lines_for_a_10_violation_level() -> None: assert len(_ten_violation_report().render().splitlines()) <= 40 def test_render_orders_errors_by_rule_within_a_level() -> None: report = ValidationReport( violations=( _violation(RuleId.TRAVEL_TOO_SHORT, "l1", "b travels too little"), _violation(RuleId.GRID_SHAPE, "l1", "a shape disagrees"), ) ) error_lines = [line for line in report.render().splitlines() if "ERROR" in line] assert len(error_lines) == 2 assert RuleId.GRID_SHAPE in error_lines[0] assert RuleId.TRAVEL_TOO_SHORT in error_lines[1] def test_clean_report_renders_one_ok_line() -> None: report = ValidationReport() assert report.ok assert report.render() == "content OK: no violations" def test_warnings_only_report_renders_ok_and_warn_lines() -> None: report = ValidationReport( warnings=(_violation(RuleId.TARGET_IMPLAUSIBLE, "l1", "tight but fair"),) ) rendered = report.render() assert report.ok # warnings never fail assert "WARN" in rendered and "ERROR" not in rendered assert "0 error(s), 1 warning(s)" in rendered