Files
hermes-agent/tests/tools/test_bot_desktop_launcher_seed.py
ethernet 3f4b8840f1 Merge remote-tracking branch 'origin/main' into ethie/pm-clean
# Conflicts:
#	pyproject.toml
#	tests/fixtures/resolution_allowlist.json
2026-09-23 07:36:31 -04:00

103 lines
5.5 KiB
Python

"""Bot Desktop launcher seeds: the dock only points at programs that exist, the look is applied."""
from __future__ import annotations
import os
import shutil
import subprocess
import xml.etree.ElementTree as ET
from pathlib import Path
import pytest
LAUNCHER = Path(__file__).resolve().parents[2] / "tools" / "bot_desktop" / "launcher.sh"
pytestmark = pytest.mark.platforms("linux")
def _seed(tmp_path: Path, fake_bins: list[str], browser_exec: str = "", profile_dir: str = "") -> Path:
bindir = tmp_path / "bin"
bindir.mkdir(exist_ok=True)
for name in fake_bins:
exe = bindir / name
exe.write_text("#!/bin/sh\n", encoding="utf-8")
exe.chmod(0o755)
# The launcher only needs xauth to accept the generated cookie; the test host need not ship X11 tools.
xauth = bindir / "xauth"
xauth.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8")
xauth.chmod(0o755)
# The script's own tooling (mkdir, sed, cat, awk...) symlinked in, so PATH need not contain the
# host's /usr/bin where a real chrome/thunar would leak into the dock under test.
for tool in ("mkdir", "sed", "cat", "printf", "dirname", "bash", "sh", "rm", "ln", "touch", "chmod", "xauth", "od", "tr", "awk", "grep"):
real = shutil.which(tool)
if real and not (bindir / tool).exists():
(bindir / tool).symlink_to(real)
cfg = tmp_path / "xdg"
env = {
"PATH": str(bindir),
"HOME": str(tmp_path),
"HERMES_BD_PROFILE": "t", "HERMES_BD_DISPLAY_NUM": "99",
"HERMES_BD_SOCKET": str(tmp_path / "rfb.sock"), "HERMES_BD_XAUTH": str(tmp_path / "Xauthority"),
"HERMES_BD_ENV_FILE": str(tmp_path / "env"), "HERMES_BD_CONFIG_HOME": str(cfg),
"HERMES_BD_SEED_ONLY": "1",
**({"HERMES_BD_BROWSER_EXEC": browser_exec, "HERMES_BD_BROWSER_EXEC_LINE": f"Exec={browser_exec} --user-data-dir={profile_dir or f'{tmp_path}/bp'}"} if browser_exec else {}),
}
subprocess.run(["bash", str(LAUNCHER)], env=env, check=True, stdin=subprocess.DEVNULL, capture_output=True, timeout=30)
return cfg
def test_dock_lists_only_programs_present_on_path(tmp_path):
chrome = tmp_path / "bin" / "chrome" # the browser is the one runtime.py resolved, never a PATH scan
cfg = _seed(tmp_path, ["xfce4-terminal", "chrome", "firefox"], browser_exec=str(chrome))
panel = ET.parse(cfg / "xfce4/xfconf/xfce-perchannel-xml/xfce4-panel.xml") # well-formed or this raises
launcher_ids = [str(p.get("name")) for p in panel.iter("property") if p.get("value") == "launcher"]
execs = sorted(
line.split("=", 1)[1]
for pid in launcher_ids
for line in (cfg / "xfce4/panel" / pid.replace("plugin-", "launcher-") / "hermes.desktop").read_text(encoding="utf-8-sig").splitlines()
if line.startswith("Exec=")
)
assert execs == [f"{chrome} --user-data-dir={tmp_path}/bp", "xfce4-terminal"]
def test_browser_launcher_follows_the_profile_without_reseeding_the_panel(tmp_path):
"""The panel layout is seeded once (the human may have rearranged it), but the Browser entry's Exec=
binds the bot's user-data-dir by absolute path: after `hermes profile rename` the dock must open the
renamed profile's cookie jar, the same one agent-browser now drives, while the layout stays untouched."""
chrome = tmp_path / "bin" / "chrome"
cfg = _seed(tmp_path, ["xfce4-terminal", "chrome"], browser_exec=str(chrome), profile_dir="/old name/bp")
panel_xml = cfg / "xfce4/xfconf/xfce-perchannel-xml/xfce4-panel.xml"
layout_before = panel_xml.read_text(encoding="utf-8-sig")
shutil.rmtree(tmp_path / "bin")
_seed(tmp_path, ["xfce4-terminal", "chrome"], browser_exec=str(chrome), profile_dir="/new name/bp")
execs = [
line for d in (cfg / "xfce4/panel").glob("launcher-*/hermes.desktop")
for line in d.read_text(encoding="utf-8-sig").splitlines() if line.startswith("Exec=") and "user-data-dir" in line
]
assert execs == [f"Exec={chrome} --user-data-dir=/new name/bp"]
assert panel_xml.read_text(encoding="utf-8-sig") == layout_before
def test_browser_launcher_seeded_before_the_marker_still_follows_a_rename(tmp_path):
"""Profiles seeded by an earlier launcher have the Browser entry but no `.hermes-browser-launcher`;
a rename must still reach their Exec= (the marker is recovered from our own entry), not leave them
opening the old profile's empty cookie jar forever."""
chrome = tmp_path / "bin" / "chrome"
cfg = _seed(tmp_path, ["xfce4-terminal", "chrome"], browser_exec=str(chrome), profile_dir="/old name/bp")
(cfg / "xfce4/panel/.hermes-browser-launcher").unlink()
shutil.rmtree(tmp_path / "bin")
_seed(tmp_path, ["xfce4-terminal", "chrome"], browser_exec=str(chrome), profile_dir="/new name/bp")
execs = [
line for d in (cfg / "xfce4/panel").glob("launcher-*/hermes.desktop")
for line in d.read_text(encoding="utf-8-sig").splitlines() if line.startswith("Exec=") and "user-data-dir" in line
]
assert execs == [f"Exec={chrome} --user-data-dir=/new name/bp"]
def test_look_is_seeded_with_wallpaper_and_theme(tmp_path):
cfg = _seed(tmp_path, ["xfce4-terminal"])
desktop = (cfg / "xfce4/xfconf/xfce-perchannel-xml/xfce4-desktop.xml").read_text(encoding="utf-8-sig")
xsettings = (cfg / "xfce4/xfconf/xfce-perchannel-xml/xsettings.xml").read_text(encoding="utf-8-sig")
assert str(LAUNCHER.with_name("wallpaper.png")) in desktop
assert "PLACEHOLDER" not in desktop + xsettings
assert os.path.isfile(LAUNCHER.with_name("wallpaper.png"))