Michael Rabinovich
gallery: searchable fixture picker, URL-persisted picks, cached render proxies
4a9408a | """Unit tests for the report-proxy route and the inline iframe viewer. | |
| The Space exposes two paths for the per-submission HTML report: | |
| - ``/reports/{submission_id}.html`` (FastAPI route): re-serves the | |
| file with ``Content-Type: text/html``. Works under Bearer auth | |
| for programmatic clients; gets 404'd by HF's edge for logged-in | |
| browser users on a private Space (no auth carryover across | |
| same-origin pathname navigations). Kept as a backdoor / for the | |
| future public migration. | |
| - ``_format_detail_and_report`` (row-click handler): server-side | |
| fetches the report via ``hf_hub_download`` and inlines it into | |
| an ``<iframe srcdoc=...>``. No browser HTTP request → no edge | |
| auth gate → renders for any logged-in user. | |
| Tests stub the Hub fetch via monkeypatch so the suite has zero | |
| network I/O. | |
| """ | |
| from __future__ import annotations | |
| import re | |
| import types | |
| import pandas as pd | |
| import app | |
| import leaderboard | |
| def test_serve_report_returns_html_when_file_exists(monkeypatch): | |
| """Successful fetch -> 200 + text/html + body passthrough.""" | |
| monkeypatch.setattr( | |
| app, | |
| "_fetch_report_html", | |
| lambda sid: b"<!DOCTYPE html><html><body>ok</body></html>", | |
| ) | |
| resp = app.serve_report("sub-test") | |
| assert resp.status_code == 200 | |
| assert resp.media_type.startswith("text/html") | |
| assert resp.body == b"<!DOCTYPE html><html><body>ok</body></html>" | |
| def test_serve_report_returns_404_when_file_missing(monkeypatch): | |
| """``_fetch_report_html`` returning None -> 404 with a small html body.""" | |
| monkeypatch.setattr(app, "_fetch_report_html", lambda sid: None) | |
| resp = app.serve_report("sub-missing") | |
| assert resp.status_code == 404 | |
| # 404 body is still HTML so the browser renders the message. | |
| assert "Report not found" in resp.body.decode("utf-8") | |
| def test_fetch_report_html_returns_none_on_hub_failure(monkeypatch): | |
| """A Hub-side exception is caught and surfaced as None. | |
| The serve handler relies on this to keep a transient Hub blip | |
| from leaking a stack trace into the Space's HTTP response. | |
| """ | |
| def boom(*a, **kw): | |
| raise RuntimeError("simulated Hub failure") | |
| monkeypatch.setattr(app, "hf_hub_download", boom) | |
| # The lru_cache on _fetch_report_html caches by arg; use a unique | |
| # id per test so prior runs don't shortcut this one. | |
| assert app._fetch_report_html("sub-failure-probe-unique-1") is None | |
| def test_proxy_route_is_registered(): | |
| """The mounted FastAPI app exposes ``/reports/{submission_id}.html`` as GET. | |
| Catches the regression where the ``add_api_route`` call moves | |
| below ``mount_gradio_app`` (which would still register the route | |
| but make this regression silent until someone tries to hit it). | |
| """ | |
| routes = [getattr(r, "path", None) for r in app.app.routes] | |
| assert "/reports/{submission_id}.html" in routes | |
| # --- Inline iframe viewer (_format_detail_and_report) ---------------- | |
| def _stub_row(**overrides): | |
| base = { | |
| "submission_id": "sub-test-x", | |
| "submission_name": "Test Agent", | |
| "submitter_name": "team-test", | |
| "status": "completed", | |
| "submitted_at": "2026-05-26T12:02:31Z", | |
| "notes": None, | |
| "model details (optional)": "_None_", | |
| "submission_blob_url": "https://example.test/sub-test-x.zip", | |
| "report_url": "/reports/sub-test-x.html", | |
| "failure_reason": None, | |
| } | |
| base.update(overrides) | |
| return pd.DataFrame([base]) | |
| def _fake_evt(idx=0): | |
| """Minimal stand-in for gr.SelectData with the .index attr we read.""" | |
| return types.SimpleNamespace(index=[idx, 0]) | |
| def test_iframe_viewer_inlines_report_for_modern_row(monkeypatch): | |
| """A completed modern row's HTML lands inside <iframe srcdoc>. | |
| Confirms the fetched bytes are HTML-escaped (so `<html>` is not | |
| re-parsed by the host page) and the iframe carries explicit | |
| sizing so Gradio's column flex can't clip it vertically. | |
| """ | |
| monkeypatch.setattr( | |
| app, "_fetch_report_html", | |
| lambda sid: b"<!DOCTYPE html><body><h1>Report for " + sid.encode() + b"</h1></body>", | |
| ) | |
| df = _stub_row() | |
| md, iframe = app._format_detail_and_report(df, _fake_evt()) | |
| assert "### Test Agent" in md | |
| assert iframe.startswith('<iframe srcdoc="') | |
| # HTML-escaped content: literal "<!DOCTYPE html>" becomes | |
| # "<!DOCTYPE html>" inside the attribute. | |
| assert "<!DOCTYPE html>" in iframe | |
| assert "sub-test-x" in iframe | |
| assert 'style="width:100%; height:90vh; border:0; display:block;"' in iframe | |
| def test_iframe_viewer_empty_for_pending_or_failed_row(monkeypatch): | |
| """Rows without a report_url get an empty viewer (no iframe at all). | |
| Pending: still evaluating; no report exists yet. Failed: eval | |
| crashed; no report uploaded. Legacy: pre-modern-pipeline; the | |
| file genuinely doesn't exist on the dataset. All three are | |
| handled by the same `report_url == ""` gate. | |
| """ | |
| monkeypatch.setattr(app, "_fetch_report_html", lambda sid: b"unused") | |
| for row_overrides in [ | |
| {"status": "pending", "report_url": ""}, | |
| {"status": "failed", "report_url": "", "failure_reason": "boom"}, | |
| {"status": "completed", "report_url": ""}, # legacy | |
| ]: | |
| df = _stub_row(**row_overrides) | |
| md, iframe = app._format_detail_and_report(df, _fake_evt()) | |
| assert iframe == "", f"expected empty viewer for {row_overrides}" | |
| # The metadata panel still renders. | |
| assert "### Test Agent" in md | |
| def test_iframe_viewer_falls_back_to_empty_when_fetch_fails(monkeypatch): | |
| """If _fetch_report_html returns None (Hub blip), no iframe is emitted. | |
| Avoids surfacing a broken iframe on a transient failure. | |
| """ | |
| monkeypatch.setattr(app, "_fetch_report_html", lambda sid: None) | |
| df = _stub_row() | |
| _md, iframe = app._format_detail_and_report(df, _fake_evt()) | |
| assert iframe == "" | |
| def test_iframe_viewer_returns_placeholder_on_null_event(): | |
| """A null SelectData (no row clicked) returns placeholder + empty viewer.""" | |
| df = _stub_row() | |
| fake = types.SimpleNamespace(index=None) | |
| md, iframe = app._format_detail_and_report(df, fake) | |
| assert md == app.DETAIL_PLACEHOLDER | |
| assert iframe == "" | |
| # --- Boot resilience: no silent fallback, but no crash either ------- | |
| # | |
| # leaderboard.load_leaderboard_split / load_admin_table *raise* | |
| # LeaderboardDataError on any Hub read failure (no fallback to stale | |
| # or bundled data). app.py must turn that into empty tables + a loud | |
| # banner / toast rather than crash at construction time (which would | |
| # take the whole Space down on an under-scoped HF_TOKEN). | |
| def test_safe_load_split_returns_empty_and_error_on_hub_failure(monkeypatch): | |
| """A failed Hub read yields empty, correctly-shaped frames + a message.""" | |
| def boom(): | |
| raise leaderboard.LeaderboardDataError("simulated hub failure") | |
| monkeypatch.setattr(app, "load_leaderboard_split", boom) | |
| validated, unvalidated, error = app._safe_load_split() | |
| assert error is not None | |
| assert "simulated hub failure" in error | |
| assert len(validated) == 0 | |
| assert len(unvalidated) == 0 | |
| # Empty frames keep the declared column shape so the widgets stay | |
| # consistent with their datatypes. | |
| assert list(validated.columns) == leaderboard.VALIDATED_LEADERBOARD_COLS | |
| assert list(unvalidated.columns) == leaderboard.LEADERBOARD_COLS | |
| def test_safe_load_split_passes_through_on_success(monkeypatch): | |
| """On success the wrapper returns the frames untouched with no error.""" | |
| v = pd.DataFrame(columns=leaderboard.VALIDATED_LEADERBOARD_COLS) | |
| u = pd.DataFrame(columns=leaderboard.LEADERBOARD_COLS) | |
| monkeypatch.setattr(app, "load_leaderboard_split", lambda: (v, u)) | |
| validated, unvalidated, error = app._safe_load_split() | |
| assert error is None | |
| assert validated is v | |
| assert unvalidated is u | |
| def test_safe_load_admin_returns_empty_and_error_on_hub_failure(monkeypatch): | |
| """Admin counterpart: empty admin frame + message, no exception.""" | |
| def boom(): | |
| raise leaderboard.LeaderboardDataError("simulated admin hub failure") | |
| monkeypatch.setattr(app, "load_admin_table", boom) | |
| admin_df, error = app._safe_load_admin() | |
| assert error is not None | |
| assert len(admin_df) == 0 | |
| assert list(admin_df.columns) == leaderboard.ADMIN_COLUMNS | |
| def test_gate_admin_controls_refreshes_live_table(monkeypatch): | |
| """Page/auth load refreshes the admin table, not just interactivity.""" | |
| live_df = pd.DataFrame( | |
| [ | |
| { | |
| "select": False, | |
| "validation_status": "unvalidated", | |
| "validation_method": None, | |
| "submission_name": "UC3 e2e test 20260602-205316", | |
| "submitter_name": "michaelr27", | |
| "submitted_at": "2026-06-02 19:00 UTC", | |
| "status": "completed", | |
| "aggregate_score": 0.5853, | |
| "submission_id": "uc3-e2e", | |
| } | |
| ], | |
| columns=leaderboard.ADMIN_COLUMNS, | |
| ) | |
| monkeypatch.setattr(app, "_safe_load_admin", lambda: (live_df, None)) | |
| monkeypatch.setattr(app, "is_admin", lambda profile: True) | |
| table_update = app._gate_admin_controls(types.SimpleNamespace(username="michaelr27"))[0] | |
| assert table_update.value["headers"] == leaderboard.ADMIN_COLUMNS | |
| assert table_update.value["data"][0][3] == "UC3 e2e test 20260602-205316" | |
| assert table_update.interactive is True | |
| def test_admin_delete_refreshes_gallery(monkeypatch): | |
| """Deleting rows also replaces the Gallery iframe srcdoc.""" | |
| table_df = pd.DataFrame( | |
| [ | |
| { | |
| "select": True, | |
| "submission_id": "validated-old-row", | |
| } | |
| ] | |
| ) | |
| empty_admin = pd.DataFrame(columns=leaderboard.ADMIN_COLUMNS) | |
| empty_validated = pd.DataFrame(columns=leaderboard.VALIDATED_LEADERBOARD_COLS) | |
| empty_unvalidated = pd.DataFrame(columns=leaderboard.LEADERBOARD_COLS) | |
| deleted: list[list[str]] = [] | |
| monkeypatch.setattr(app, "is_admin", lambda profile: True) | |
| monkeypatch.setattr(app, "delete_rows", lambda ids: deleted.append(ids)) | |
| monkeypatch.setattr( | |
| app, "_safe_load_split", lambda: (empty_validated, empty_unvalidated, None) | |
| ) | |
| monkeypatch.setattr(app, "_safe_load_admin", lambda: (empty_admin, None)) | |
| monkeypatch.setattr(app, "_gallery_iframe_html", lambda: "<iframe>empty</iframe>") | |
| monkeypatch.setattr(app.gr, "Info", lambda *a, **k: None) | |
| admin_df, validated, unvalidated, gallery_html, _confirm, _delete_btn, _stop_btn = ( | |
| app._admin_delete(table_df, True, types.SimpleNamespace(username="michaelr27")) | |
| ) | |
| assert deleted == [["validated-old-row"]] | |
| assert admin_df is empty_admin | |
| assert validated is empty_validated | |
| assert unvalidated is empty_unvalidated | |
| assert gallery_html == "<iframe>empty</iframe>" | |
| def test_data_error_banner_md_present_on_error_empty_otherwise(): | |
| """Banner markdown is non-empty (and names the cause) only on error.""" | |
| assert app._data_error_banner_md(None) == "" | |
| assert app._data_error_banner_md("") == "" | |
| banner = app._data_error_banner_md("boom: 403 Forbidden") | |
| assert "boom: 403 Forbidden" in banner | |
| assert "unavailable" in banner.lower() | |
| def test_refresh_handler_shows_banner_and_warns_on_error(monkeypatch): | |
| """Manual refresh surfaces the failure loudly: visible banner + warning toast. | |
| ``gr.Warning`` / ``gr.Info`` are stubbed so the test runs outside a | |
| Gradio request context; the assertion is that a failure path fires | |
| a warning (not an info) and flips the banner visible. | |
| """ | |
| def boom(): | |
| raise leaderboard.LeaderboardDataError("simulated hub failure") | |
| monkeypatch.setattr(app, "load_leaderboard_split", boom) | |
| toasts = {"warning": 0, "info": 0} | |
| monkeypatch.setattr(app.gr, "Warning", lambda *a, **k: toasts.__setitem__("warning", toasts["warning"] + 1)) | |
| monkeypatch.setattr(app.gr, "Info", lambda *a, **k: toasts.__setitem__("info", toasts["info"] + 1)) | |
| validated, unvalidated, banner = app._refresh_leaderboard_with_toast() | |
| assert toasts["warning"] == 1 | |
| assert toasts["info"] == 0 | |
| assert len(validated) == 0 and len(unvalidated) == 0 | |
| # The banner output is a gr.Markdown update flipped visible. | |
| assert getattr(banner, "visible", None) is True | |
| def test_iframe_escape_is_attribute_safe(monkeypatch): | |
| """Quotes / ampersands inside the report HTML are escaped properly. | |
| A `"` inside the report would otherwise terminate the srcdoc | |
| attribute prematurely and break parsing. Regression guard. | |
| """ | |
| monkeypatch.setattr( | |
| app, "_fetch_report_html", | |
| lambda sid: b'<html><body>tag: <a href="https://x.test">x</a> & co.</body></html>', | |
| ) | |
| df = _stub_row() | |
| _md, iframe = app._format_detail_and_report(df, _fake_evt()) | |
| # Within the srcdoc value, double-quotes must be HTML-escaped. | |
| srcdoc = re.search(r'srcdoc="(.*)"\s+style=', iframe, re.DOTALL) | |
| assert srcdoc is not None | |
| inner = srcdoc.group(1) | |
| # No unescaped " inside the attribute value. | |
| assert '"' not in inner | |
| assert """ in inner | |
| assert "&" in inner | |