hermes-agent/tests/hermes_cli/test_opentui_node_resolutio...

70 lines
3.2 KiB
Python

"""Node-26 resolution for the OpenTUI engine + the launch-cwd channel.
Regression coverage for two ways the local TUI silently fell back to Ink /
showed the wrong directory:
1. fnm's active/default node was on an older line (v25) while a usable v26.3
sat installed-but-inactive — ``_node26_bin_or_none`` only checked
``HERMES_NODE`` + ``which node`` and so reported "no node 26" → OpenTUI
unavailable → Ink fallback.
2. ``TERMINAL_CWD`` (the gateway's launch-dir channel) was only exported in
worktree mode, so a normal launch let the gateway auto-detect the engine's
own package dir as the workspace.
"""
import os
import stat
import pytest
import hermes_cli.main as main_mod
def _fake_node(path, version: str) -> None:
"""Write a stub `node` that prints `version` for `--version`."""
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(f'#!/bin/sh\necho "{version}"\n')
path.chmod(path.stat().st_mode | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH)
class TestFnmNode26Discovery:
def test_discovers_inactive_v26_when_default_is_older(self, tmp_path, monkeypatch):
"""A v26.3 installed under fnm is found even when PATH `node` is v25."""
fnm_dir = tmp_path / "fnm"
for ver in ("24.11.0", "25.9.0", "26.3.0"):
_fake_node(fnm_dir / "node-versions" / f"v{ver}" / "installation" / "bin" / "node", f"v{ver}")
monkeypatch.setenv("FNM_DIR", str(fnm_dir))
monkeypatch.delenv("HERMES_NODE", raising=False)
# PATH node is the too-old default (v25).
monkeypatch.setattr(main_mod.shutil, "which", lambda _b: str(
fnm_dir / "node-versions" / "v25.9.0" / "installation" / "bin" / "node"
))
resolved = main_mod._node26_bin_or_none()
assert resolved is not None
assert "v26.3.0" in resolved # newest qualifying, not the v25 default
def test_candidates_sorted_newest_first(self, tmp_path, monkeypatch):
fnm_dir = tmp_path / "fnm"
for ver in ("26.1.0", "26.4.0", "25.0.0"):
_fake_node(fnm_dir / "node-versions" / f"v{ver}" / "installation" / "bin" / "node", f"v{ver}")
monkeypatch.setenv("FNM_DIR", str(fnm_dir))
cands = main_mod._fnm_node26_candidates()
# Directory-name order: 26.4 before 26.1 before 25.0.
idx = [next(i for i, c in enumerate(cands) if f"v{v}" in c) for v in ("26.4.0", "26.1.0", "25.0.0")]
assert idx == sorted(idx)
def test_no_fnm_dir_is_empty_not_error(self, tmp_path, monkeypatch):
monkeypatch.setenv("FNM_DIR", str(tmp_path / "does-not-exist"))
monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path / "xdg-none"))
monkeypatch.setattr(main_mod.Path, "home", classmethod(lambda cls: tmp_path / "home-none"))
assert main_mod._fnm_node26_candidates() == []
def test_hermes_node_still_wins(self, tmp_path, monkeypatch):
"""An explicit HERMES_NODE >= 26.3 takes precedence over fnm discovery."""
explicit = tmp_path / "explicit" / "node"
_fake_node(explicit, "v26.5.0")
monkeypatch.setenv("HERMES_NODE", str(explicit))
monkeypatch.setattr(main_mod.shutil, "which", lambda _b: None)
assert main_mod._node26_bin_or_none() == str(explicit)