Files
hermes-webui/tests/test_issue1800_file_html_interactions.py
T
nesquena-hermes 8c803c0a07 fix(tests): clear two test failures (one pre-existing, one bumped by #2044)
1. test_issue1362_codex_oauth_onboarding.py::test_anthropic_onboarding_setup_allows_linked_oauth_without_api_key
   Pre-existing env-collision bug, surfaced when HERMES_WEBUI_SKIP_ONBOARDING=1
   is in the test runner env (set by hosting providers and by isolated test
   harnesses). `apply_onboarding_setup()` short-circuits without writing the
   config file when SKIP_ONBOARDING is set, but the test asserts the file was
   written, so it fails with FileNotFoundError on read_text().
   Fix: `monkeypatch.delenv("HERMES_WEBUI_SKIP_ONBOARDING", raising=False)` —
   matches the convention already used in test_issue1499_keyless_onboarding.py
   and test_issue1500_lmstudio_env_var_alignment.py.

2. test_issue1800_file_html_interactions.py::test_media_html_inline_keeps_csp_sandbox
   Slicing-based source-string assertion (4000-char window after `def _handle_media`)
   broke because PR #2044's MEDIA_ALLOWED_ROOTS parsing was inserted earlier in
   the function and pushed the CSP block to offset 4211. Widened window to 5000.
   Assertion content is structural (CSP sandbox string present), not positional.
2026-05-11 02:55:50 +00:00

87 lines
3.6 KiB
Python

"""Regression coverage for issue #1800 file-picker and HTML-open interactions."""
from __future__ import annotations
import re
from pathlib import Path
REPO = Path(__file__).resolve().parents[1]
INDEX_HTML = (REPO / "static" / "index.html").read_text(encoding="utf-8")
BOOT_JS = (REPO / "static" / "boot.js").read_text(encoding="utf-8")
UI_JS = (REPO / "static" / "ui.js").read_text(encoding="utf-8")
STYLE_CSS = (REPO / "static" / "style.css").read_text(encoding="utf-8")
ROUTES_PY = (REPO / "api" / "routes.py").read_text(encoding="utf-8")
def _slice_after(source: str, needle: str, chars: int = 900) -> str:
idx = source.find(needle)
assert idx >= 0, f"{needle!r} not found"
return source[idx : idx + chars]
def test_attach_button_is_non_submit_button():
"""Attach must not act like a submit button in browser/container shells."""
m = re.search(r"<button[^>]*id=\"btnAttach\"[^>]*>", INDEX_HTML)
assert m, "btnAttach button not found"
assert 'type="button"' in m.group(0)
def test_file_input_is_visually_hidden_not_display_none():
"""Hidden file inputs are more consistently opened by user-gesture clicks."""
m = re.search(r"<input[^>]*id=\"fileInput\"[^>]*>", INDEX_HTML)
assert m, "fileInput not found"
tag = m.group(0)
assert "file-input-visually-hidden" in tag
assert "display:none" not in tag
rule = _slice_after(STYLE_CSS, ".file-input-visually-hidden", 240)
assert "position:absolute" in rule
assert "opacity:0" in rule
def test_attach_click_prevents_default_and_opens_picker():
body = _slice_after(BOOT_JS, "$('btnAttach').onclick", 300)
assert "preventDefault" in body
assert "$('fileInput').value=''" in body
assert "$('fileInput').click()" in body
def test_html_chat_attachment_opens_sandboxed_inline_raw_file():
"""Uploaded .html attachments render as an openable link, not an inert badge."""
body = _slice_after(UI_JS, "function _renderAttachmentHtml", 900)
assert "_HTML_EXTS.test(fname)" in body
assert "inline=1" in body
assert "target=\"_blank\"" in body
assert "rel=\"noopener\"" in body
assert "msg-file-badge--html" in body
def test_html_media_open_full_uses_inline_new_tab_not_download():
"""MEDIA: HTML preview's Open full page link should open a browser view."""
body = _slice_after(UI_JS, "function loadHtmlInline", 1800)
assert "'&inline=1'" in body
assert "target=\"_blank\"" in body
assert "rel=\"noopener\"" in body
normal_open = next(line for line in body.splitlines() if "html-open-link" in line)
assert "download=" not in normal_open
def test_media_html_inline_keeps_csp_sandbox():
"""api/media may serve HTML inline only behind a CSP sandbox."""
# Slice widened to 5000 (was 4000) after PR #2044 added MEDIA_ALLOWED_ROOTS
# parsing earlier in _handle_media, which pushed the CSP block past the
# original window. The assertion is structural, not positional.
body = _slice_after(ROUTES_PY, "def _handle_media", 5000)
assert 'html_inline_ok = inline_preview and mime == "text/html"' in body
assert 'csp = "sandbox allow-scripts" if html_inline_ok else None' in body
assert "csp=csp" in body
assert "allow-same-origin" not in body
def test_sandboxed_file_responses_do_not_send_x_frame_options():
"""X-Frame-Options: DENY would block the sandbox iframe preview."""
body = _slice_after(ROUTES_PY, "def _serve_file_bytes", 1800)
csp_branch = body[body.find("if csp:") : body.find("else:", body.find("if csp:"))]
assert "Content-Security-Policy" in csp_branch
assert 'send_header("X-Frame-Options"' not in csp_branch