| from pathlib import Path | |
| from src.progress import progress_from_events | |
| def test_old_failed_step_does_not_poison_later_validation_waiting(): | |
| result = progress_from_events([ | |
| {"ts": "2026-06-02T10:00:00+00:00", "step": "repair", "status": "failed", "message": "first attempt failed"}, | |
| {"ts": "2026-06-02T10:01:00+00:00", "step": "api_validation", "status": "waiting", "message": "waiting validation"}, | |
| ], state={"status": "running"}) | |
| assert result["status"] == "running" | |
| assert result["current_step"] == "api_validation" | |
| statuses = {item["step"]: item["status"] for item in result["timeline"]} | |
| assert statuses["repair"] == "done" | |
| assert statuses["api_validation"] == "running" | |
| def test_failed_state_only_wins_when_current_step_failed(): | |
| result = progress_from_events([ | |
| {"ts": "2026-06-02T10:00:00+00:00", "step": "api_validation", "status": "failed"}, | |
| ], state={"status": "failed"}) | |
| assert result["status"] == "failed" | |
| statuses = {item["step"]: item["status"] for item in result["timeline"]} | |
| assert statuses["api_validation"] == "failed" | |
| def test_events_list_has_no_view_all_button_and_is_scrollable(): | |
| html = Path("web/index.html").read_text(encoding="utf-8") | |
| js = Path("web/static/progress.js").read_text(encoding="utf-8") | |
| css = Path("web/static/app.css").read_text(encoding="utf-8") | |
| assert "viewAllEvents" not in html | |
| assert "toggleAllEvents" not in js | |
| assert "root.scrollTop=0" in js | |
| assert "max-height:184px" in css | |
| def test_timeline_ensures_visible_without_recentering(): | |
| js = Path("web/static/progress.js").read_text(encoding="utf-8") | |
| assert "function ensureTimelineStepVisible" in js | |
| assert "target.offsetLeft-(root.clientWidth-target.clientWidth)/2" not in js | |
| assert "targetRight>visibleRight-margin" in js | |
| def test_polling_has_inflight_guard_and_lower_latency(): | |
| js = Path("web/static/app.js").read_text(encoding="utf-8") | |
| assert "state.progressPollInFlight" in js | |
| assert "setInterval(refreshProgress,2000)" in js | |
| assert ">8000" in js | |
| def test_resume_latest_falls_back_to_latest_run(): | |
| app = Path("app.py").read_text(encoding="utf-8") | |
| js = Path("web/static/app.js").read_text(encoding="utf-8") | |
| assert "selected = resumable or (runs[0] if runs else None)" in app | |
| assert '"latest_run"' in app | |
| assert "await loadRuns(true)" in js | |
| assert "setButtonBusy('resumeLatestRun'" in js | |
| def test_app_version_v70(): | |
| assert '"version": "v70-progress-resume-events-latency"' in Path("app.py").read_text(encoding="utf-8") | |