"""Unit tests for the pure card/badge renderers (harness.card) — phase lvl5 semantics. Covers the deterministic HTML + SVG string builders (the PNG step needs Playwright + is exercised live). The cardinal check: the card REPORTS the data verbatim — level/marks come straight from the dict, never recomputed — the badge is NUMBER + COLOUR ONLY, and the per-rung table rows (incl. intentional-skip / unverified) are the sole carrier of "why isn't the level higher". Old schema-1 artifacts (4-rung ladder, cap fields present) must render without error and without relabeling. Run cold: cc-ci-run -m pytest tests/unit/test_card.py -q """ from __future__ import annotations import os import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner")) from harness import card as C # noqa: E402 def _data(level=5, **kw): d = { "schema": 2, "recipe": "uptime-kuma", "version": "1.23.0", "level": level, "rungs": { "install": "pass", "upgrade": "pass", "backup_restore": "pass", "functional": "pass", "lint": "pass", }, "flags": {"clean_teardown": True, "no_secret_leak": True}, "screenshot": "screenshot.png", "stages": [ { "name": "install", "status": "pass", "tests": [{"name": "test_serving", "status": "pass", "ms": 168}], }, { "name": "custom", "status": "fail", "tests": [ {"name": "test_health", "status": "pass", "ms": 17}, {"name": "test_broken", "status": "fail", "ms": 5}, ], }, { "name": "lint", "status": "pass", "tests": [{"name": "abra recipe lint", "status": "pass", "ms": 0}], }, ], } d.update(kw) return d def test_level_color_ramp(): # 0 (red) … 5 (bright green — full 5-rung climb); unknown → grey. assert C.level_color(0) != C.level_color(5) assert C.level_color(5) == "#3fb950" assert C.level_color(99) == "#8b949e" def test_badge_svg_is_number_and_color_only(): svg = C.level_badge_svg(4) assert svg.startswith("") assert "level 4" in svg assert C.level_color(4) in svg # operator-specified (phase lvl5): NOTHING but the level on the badge — no annotation # segment of any kind, whatever the rung situation. assert "expected" not in svg and "gap?" not in svg and "skip" not in svg def test_badge_svg_level5(): svg = C.level_badge_svg(5) assert "level 5" in svg and "#3fb950" in svg def test_skip_rows_intentional_and_unverified(): html_out = C._skip_rows( {"intentional": {"backup_restore": "no persistent data"}, "unintentional": ["functional"]} ) # intentional skip: labelled row (muted green) + the reason on its own line assert "intentional skip" in html_out and C.SKIP_GREEN in html_out assert "backup/restore" in html_out and "no persistent data" in html_out # unverified rung: amber row + the blocks-the-level explanation assert "unverified" in html_out and C.GAP_COLOR in html_out assert "functional" in html_out and "cannot rise above" in html_out def test_skip_rows_lint_label_known(): html_out = C._skip_rows({"intentional": {}, "unintentional": ["lint"]}) assert ">lint<" in html_out.replace("", "<") # rung label renders, not a KeyError def test_skip_rows_empty_when_no_skips(): assert C._skip_rows({"intentional": {}, "unintentional": []}) == "" def test_card_html_reports_level_verbatim(): html = C.render_card_html(_data(level=2)) assert "uptime-kuma" in html assert "1.23.0" in html # the level shown is exactly what was passed (no recompute) assert ">2<" in html assert "level 2 of 5" in html assert C.level_color(2) in html def test_card_html_no_cap_language(): html = C.render_card_html(_data()) assert "capped" not in html and "cap_reason" not in html assert "level 5 of 5" in html def test_card_html_old_schema1_artifact_renders(): # history compatibility: a pre-lvl5 results.json (4-rung ladder, cap fields, "na" statuses) # renders without KeyError and shows ITS OWN ladder height (no retroactive relabeling). old = { "schema": 1, "recipe": "legacy", "version": "0.9", "level": 4, "level_cap_reason": "", "level_cap_rung": None, "rungs": { "install": "pass", "upgrade": "pass", "backup_restore": "pass", "functional": "pass", }, "skips": {"intentional": {}, "unintentional": []}, "flags": {"clean_teardown": True, "no_secret_leak": True}, "screenshot": None, "stages": [], } html = C.render_card_html(old) assert "legacy" in html assert "level 4 of 4" in html # the old top, not 5 assert "capped" not in html def test_card_html_shows_stage_and_test_marks_incl_lint(): html = C.render_card_html(_data()) assert "install" in html and "custom" in html assert "abra recipe lint" in html assert "test_serving" in html and "test_broken" in html assert C.STATUS_MARK["pass"] in html and C.STATUS_MARK["fail"] in html def test_card_html_unver_stage_mark_renders(): d = _data() d["stages"][2] = { "name": "lint", "status": "unver", "tests": [{"name": "abra recipe lint", "status": "unver", "ms": 0, "message": "timed out"}], } html = C.render_card_html(d) assert C.STATUS_MARK["unver"] in html assert C.STATUS_COLOR["unver"] in html def test_card_html_flags_rendered(): html = C.render_card_html(_data()) assert "clean teardown" in html and "no secret leak" in html def test_card_html_no_screenshot_placeholder(): d = _data() d["screenshot"] = None html = C.render_card_html(d) assert "no screenshot" in html def test_card_html_escapes_recipe_name(): d = _data() d["recipe"] = "" html = C.render_card_html(d) assert "