"""Phase 3 U4 — dashboard YunoHost-style grid + per-recipe history (pure-render + helpers). The dashboard reads a Drone admin token at import; point DRONE_TOKEN_FILE at a temp file so the module imports without the real secret. All tests here are pure (no network): they exercise the rendering + results.json projection, asserting the grid/history mirror the artifact and never present a run greener than its data (R5 / cardinal guardrail).""" from __future__ import annotations import json import os import sys import tempfile with tempfile.NamedTemporaryFile("w", delete=False, suffix=".tok") as _tok: _tok.write("test-token") os.environ["DRONE_TOKEN_FILE"] = _tok.name sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "dashboard")) import dashboard # noqa: E402 def _row(**kw): base = { "recipe": "custom-html", "status": "success", "number": 4, "ref": "db9a9502", "version": "db9a95024e9d", "level": 4, "level_cap_reason": "", "has_screenshot": True, "flags": {"clean_teardown": True, "no_secret_leak": True}, "finished": 0, "url": "https://drone.x/cc-ci/4", } base.update(kw) return base def test_level_color_ramp_and_fallback(): assert dashboard.level_color(0) == "#e5534b" assert dashboard.level_color(6) == "#3fb950" assert dashboard.level_color(4) == "#a0b93f" assert dashboard.level_color(99) == "#8b949e" assert dashboard.level_color(None) == "#8b949e" def test_overview_grid_mirrors_results(): out = dashboard.render_overview([_row()]) assert "custom-html" in out assert "level 4" in out # the corner level pill assert dashboard.level_color(4) in out # coloured by level assert "db9a95024e9d" in out # version from results.json assert "/runs/4/screenshot.png" in out # thumbnail assert "/runs/4/summary.png" in out # links to full card assert "/recipe/custom-html" in out # history link assert "✔ teardown" in out and "✔ no-leak" in out def test_overview_never_greener_than_data(): # A failed run at level 0 must show level 0 + the failure pill — never a green/high level. out = dashboard.render_overview( [ _row( status="failure", level=0, has_screenshot=False, flags={}, level_cap_reason="L1 install FAILED", ) ] ) assert "level 0" in out assert dashboard.level_color(0) in out # red assert dashboard._COLORS["failure"] in out assert "level 4" not in out and "level 5" not in out and "level 6" not in out assert "no screenshot" in out # placeholder, no broken image def test_level_pill_unknown_when_no_results(): assert "level —" in dashboard._level_pill(None) assert "#8b949e" in dashboard._level_pill(None) def test_history_table_lists_runs(): out = dashboard.render_history("custom-html", [_row(number=4), _row(number=3, level=2)]) assert "custom-html — run history" in out assert "#4" in out and "#3" in out assert "L4" in out and "L2" in out assert "← all recipes" in out assert "/runs/4/summary.png" in out # per-run card link def test_history_empty(): out = dashboard.render_history("hedgedoc", []) assert "no runs for this recipe yet" in out def test_build_row_projects_results(monkeypatch): monkeypatch.setattr( dashboard, "_results_for", lambda n: { "version": "1.2.3", "level": 2, "level_cap_reason": "cap", "screenshot": "screenshot.png", "flags": {"clean_teardown": True}, }, ) b = { "number": 7, "status": "success", "event": "custom", "params": {"RECIPE": "n8n", "REF": "abcdef1234567890"}, "finished": 10, } r = dashboard._build_row(b) assert r["recipe"] == "n8n" and r["number"] == 7 assert r["level"] == 2 and r["version"] == "1.2.3" assert r["has_screenshot"] is True assert r["url"].endswith("/cc-ci/7") def test_build_row_degrades_without_results(monkeypatch): # No results.json (e.g. an old run): grid still renders from Drone fields, level absent. monkeypatch.setattr(dashboard, "_results_for", lambda n: {}) b = { "number": 9, "status": "running", "event": "custom", "params": {"RECIPE": "ghost", "REF": "deadbeefcafe1234567890"}, "finished": 0, } r = dashboard._build_row(b) assert r["level"] is None and r["has_screenshot"] is False assert r["version"] == "deadbeefcafe" # ref[:12] fallback # render must not crash or claim a level assert "level —" in dashboard.render_overview([r]) def test_level_badge_shows_level_coloured(monkeypatch): svg = dashboard.render_level_badge("custom-html", 4) assert "custom-html" in svg and "level 4" in svg assert dashboard.level_color(4) in svg # coloured by level assert svg.startswith("