whisperkey / tests /test_ui.py
chiruu12's picture
Deploy: working gr.Server frontend + review fixes
5a811e2 verified
Raw
History Blame Contribute Delete
3.79 kB
"""HUD rendering - pure functions, no Gradio runtime or model needed."""
import pytest
pytest.importorskip("gradio")
from jailbreak_dojo.levels import load_levels # noqa: E402
from jailbreak_dojo.ui import ( # noqa: E402
FORGE_PRESETS,
_meter,
_parse_forge_query,
_share_link,
_shields_panel,
_trail,
make_custom_level,
render_hud,
shield_card,
)
LEVELS = load_levels()
def test_trail_marks_done_current_and_locked():
html = _trail(LEVELS, current_id=3, won_levels=[1, 2])
assert html.count("node done") == 2
assert html.count("node current") == 1
assert html.count("node locked") == 2 # levels 4 and 5
def test_shields_panel_shows_open_gate_at_l1():
assert "gate stands open" in _shields_panel(LEVELS[0]) # L1: no defenses
def test_shields_panel_lights_all_five_at_l5():
# regex + crescendo + wardings + ML + warden
assert _shields_panel(LEVELS[4]).count("chip on") == 5
def test_shields_panel_is_honest_when_ml_did_not_load():
assert "degraded to regex" in _shields_panel(LEVELS[4], ml_ok=False)
def test_meter_flips_to_over_budget_past_1000():
assert 'class="fill"' in _meter(300)
assert "fill over" in _meter(1200)
def test_render_hud_includes_level_palette_and_panels():
html = render_hud(LEVELS, LEVELS[4], won_levels=[1, 2, 3, 4], tokens=1180)
assert "level-5" in html and 'class="trail"' in html
assert 'class="meter"' in html and 'class="shields"' in html
def test_shield_card_names_the_ml_sentinel():
class _S:
stage, subcategory, reason = "model", "span_model", "looks like an injection"
assert "Sentinel (ML)" in shield_card(_S())
def test_shield_card_escapes_html_in_evidence():
class _S:
stage, subcategory, reason = "regex", "<script>", '<img src=x onerror=alert(1)>'
html_out = shield_card(_S())
assert "<script>" not in html_out and "<img src=x" not in html_out
assert "&lt;script&gt;" in html_out
def test_message_too_long_rejects():
from jailbreak_dojo.ui import MAX_MESSAGE_LEN, _message_too_long
assert _message_too_long("x" * MAX_MESSAGE_LEN) is None
assert _message_too_long("x" * (MAX_MESSAGE_LEN + 1)) is not None
def test_forge_presets_map_to_escalating_defenses():
pushover = make_custom_level("A", "p", "sk-", "Pushover - no shields")
fortress = make_custom_level("B", "p", "sk-", "Fortress - every Unplug shield")
assert not (pushover.input_scan or pushover.guardrails or pushover.input_ml)
assert fortress.input_scan and fortress.guardrails and fortress.output_san and fortress.input_ml
def test_forge_inputs_are_html_escaped_no_xss():
from jailbreak_dojo.ui import _portrait, render_custom_hud
lvl = make_custom_level('<img src=x onerror=alert(1)>', "persona", '"><svg>', list(FORGE_PRESETS)[0])
html_out = render_custom_hud(lvl, 0) + _portrait(lvl)
assert "<img src=x onerror" not in html_out # the raw payload must not render as live HTML
assert "&lt;img" in html_out # it's escaped instead
assert "<svg>" not in html_out and "&lt;svg&gt;" in html_out # malicious prefix escaped too
def test_forge_share_link_round_trips_config_but_not_secret():
class _Req:
headers = {"host": "x.hf.space", "x-forwarded-proto": "https"}
link = _share_link(_Req(), "Mossbeard", "You are Mossbeard.", "tk-", list(FORGE_PRESETS)[1])
assert "Mossbeard" in link and "sk-" not in link # name encoded, default prefix not forced
from urllib.parse import parse_qs, urlparse
q = parse_qs(urlparse(link).query)
class _Req2:
query_params = {k: v[0] for k, v in q.items()}
name, persona, prefix, diff = _parse_forge_query(_Req2())
assert name == "Mossbeard" and prefix == "tk-" and diff == list(FORGE_PRESETS)[1]