from pathlib import Path from app import _events_from_job_logs def test_job_log_event_parser_handles_prefix_suffix_and_ansi(): logs = """ 2026-06-02T10:00:00Z stdout: \x1b[32m{"ts":"2026-06-02T10:00:00+00:00","step":"bootstrap","status":"success","message":"ok"}\x1b[0m INFO prefix {"ts":"2026-06-02T10:01:00+00:00","step":"pi_run","status":"running","message":"Pi"} trailing text """ events = _events_from_job_logs(logs) assert [e["step"] for e in events] == ["bootstrap", "pi_run"] def test_progress_endpoint_fetches_deeper_job_log_window(): app = Path("app.py").read_text(encoding="utf-8") assert "max_lines=1000" in app assert "JSONDecoder().raw_decode" not in app # implementation uses decoder variable assert "decoder.raw_decode" in app assert "ANSI_RE" in app def test_frontend_progress_is_monotonic_per_run(): js = Path("web/static/progress.js").read_text(encoding="utf-8") assert "const progressStability" in js assert "function stabilizeProgressPayload" in js assert "Math.max(rawProgress,progressStability.progress)" in js assert "timelineFurthestIndex" in js assert "resetProgressStability(runId)" in Path("web/static/app.js").read_text(encoding="utf-8") def test_light_poll_cannot_reset_timeline_signature_to_earlier_steps(): js = Path("web/static/progress.js").read_text(encoding="utf-8") assert "rawIndex>=progressStability.furthestIndex" in js assert "cloneTimeline(progressStability.timeline)" in js def test_app_version_v67(): assert '"version": "v74-grouped-timeline-rings"' in Path("app.py").read_text(encoding="utf-8")