Files
hermes-webui/tests/test_issue1955_worktree_sessions.py
2026-05-11 12:51:57 +08:00

242 lines
7.7 KiB
Python

import json
import subprocess
import time
from types import SimpleNamespace
import pytest
import api.models as models
from api.models import SESSIONS, Session, new_session
@pytest.fixture(autouse=True)
def _isolate_sessions(tmp_path, monkeypatch):
session_dir = tmp_path / "sessions"
session_dir.mkdir()
monkeypatch.setattr(models, "SESSION_DIR", session_dir)
monkeypatch.setattr(models, "SESSION_INDEX_FILE", session_dir / "_index.json")
SESSIONS.clear()
yield session_dir
SESSIONS.clear()
def test_worktree_metadata_round_trips_through_session_file(_isolate_sessions):
s = Session(
session_id="worktree001",
workspace=str(_isolate_sessions.parent / "repo" / ".worktrees" / "hermes-1234"),
worktree_path=str(_isolate_sessions.parent / "repo" / ".worktrees" / "hermes-1234"),
worktree_branch="hermes/hermes-1234",
worktree_repo_root=str(_isolate_sessions.parent / "repo"),
worktree_created_at=123.5,
)
s.save()
raw = json.loads(s.path.read_text(encoding="utf-8"))
assert raw["worktree_path"].endswith(".worktrees/hermes-1234")
assert raw["worktree_branch"] == "hermes/hermes-1234"
assert raw["worktree_repo_root"].endswith("repo")
assert raw["worktree_created_at"] == 123.5
loaded = Session.load("worktree001")
assert loaded.worktree_path == s.worktree_path
assert loaded.worktree_branch == "hermes/hermes-1234"
assert loaded.worktree_repo_root == s.worktree_repo_root
assert loaded.worktree_created_at == 123.5
assert loaded.compact()["worktree_branch"] == "hermes/hermes-1234"
def test_new_session_with_worktree_info_persists_immediately(_isolate_sessions):
repo = _isolate_sessions.parent / "repo"
worktree = repo / ".worktrees" / "hermes-abcd1234"
worktree.mkdir(parents=True)
s = new_session(
workspace=str(worktree),
worktree_info={
"path": str(worktree),
"branch": "hermes/hermes-abcd1234",
"repo_root": str(repo),
"created_at": 456.0,
},
)
assert s.path.exists(), (
"worktree-backed sessions must be persisted at creation time so the "
"real filesystem worktree is not orphaned by a browser/server restart"
)
assert s.worktree_path == str(worktree.resolve())
assert s.worktree_branch == "hermes/hermes-abcd1234"
assert s.worktree_repo_root == str(repo.resolve())
assert s.worktree_created_at == 456.0
def test_empty_worktree_session_remains_visible_in_sidebar(_isolate_sessions):
repo = _isolate_sessions.parent / "repo"
worktree = repo / ".worktrees" / "hermes-visible"
worktree.mkdir(parents=True)
s = new_session(
workspace=str(worktree),
worktree_info={
"path": str(worktree),
"branch": "hermes/hermes-visible",
"repo_root": str(repo),
"created_at": 789.0,
},
)
ids = {row["session_id"] for row in models.all_sessions()}
assert s.session_id in ids, (
"worktree-backed sessions represent real filesystem state immediately "
"and must survive the empty-session sidebar filter"
)
def test_find_git_repo_root_uses_git_from_nested_workspace(tmp_path):
from api.worktrees import find_git_repo_root
repo = tmp_path / "repo"
nested = repo / "apps" / "web"
nested.mkdir(parents=True)
subprocess.run(["git", "init"], cwd=repo, check=True, capture_output=True)
assert find_git_repo_root(nested) == repo.resolve()
def test_find_git_repo_root_rejects_non_git_workspace(tmp_path):
from api.worktrees import find_git_repo_root
with pytest.raises(ValueError, match="not inside a git repository"):
find_git_repo_root(tmp_path)
def test_create_worktree_for_workspace_calls_agent_setup_with_repo_root(tmp_path, monkeypatch):
import api.worktrees as worktrees
repo = tmp_path / "repo"
nested = repo / "src"
nested.mkdir(parents=True)
subprocess.run(["git", "init"], cwd=repo, check=True, capture_output=True)
seen = {}
def fake_setup(repo_root):
seen["repo_root"] = repo_root
return {
"path": str(repo / ".worktrees" / "hermes-test"),
"branch": "hermes/hermes-test",
"repo_root": str(repo),
}
monkeypatch.setattr(worktrees, "_setup_agent_worktree", fake_setup)
now = time.time()
info = worktrees.create_worktree_for_workspace(nested)
assert seen["repo_root"] == str(repo.resolve())
assert info["path"].endswith(".worktrees/hermes-test")
assert info["branch"] == "hermes/hermes-test"
assert info["repo_root"] == str(repo.resolve())
assert info["created_at"] >= now
def test_session_new_route_creates_worktree_backed_session(tmp_path, monkeypatch):
import api.routes as routes
import api.worktrees as worktrees
repo = tmp_path / "repo"
worktree = repo / ".worktrees" / "hermes-route"
repo.mkdir()
worktree.mkdir(parents=True)
monkeypatch.setattr(routes, "_check_csrf", lambda handler: True)
monkeypatch.setattr(
routes,
"read_body",
lambda handler: {
"workspace": str(repo),
"worktree": True,
"profile": "default",
},
)
monkeypatch.setattr(routes, "resolve_trusted_workspace", lambda raw: repo if raw == str(repo) else raw)
monkeypatch.setattr(
worktrees,
"create_worktree_for_workspace",
lambda workspace: {
"path": str(worktree),
"branch": "hermes/hermes-route",
"repo_root": str(repo),
"created_at": 321.0,
},
)
captured = {}
monkeypatch.setattr(
routes,
"j",
lambda handler, payload, status=200, extra_headers=None: captured.update(
payload=payload,
status=status,
) or True,
)
assert routes.handle_post(object(), SimpleNamespace(path="/api/session/new")) is True
assert captured["status"] == 200
session = captured["payload"]["session"]
assert session["workspace"] == str(worktree.resolve())
assert session["worktree_path"] == str(worktree.resolve())
assert session["worktree_branch"] == "hermes/hermes-route"
def test_session_new_worktree_fallback_workspace_is_resolved(tmp_path, monkeypatch):
import api.routes as routes
import api.worktrees as worktrees
repo = tmp_path / "repo"
worktree = repo / ".worktrees" / "hermes-route"
repo.mkdir()
worktree.mkdir(parents=True)
seen = {"resolved": []}
monkeypatch.setattr(routes, "_check_csrf", lambda handler: True)
monkeypatch.setattr(
routes,
"read_body",
lambda handler: {
"worktree": True,
"profile": "default",
},
)
monkeypatch.setattr(routes, "get_last_workspace", lambda: str(repo))
def fake_resolve(raw):
seen["resolved"].append(raw)
return repo
monkeypatch.setattr(routes, "resolve_trusted_workspace", fake_resolve)
monkeypatch.setattr(
worktrees,
"create_worktree_for_workspace",
lambda workspace: {
"path": str(worktree),
"branch": "hermes/hermes-route",
"repo_root": str(repo),
"created_at": 321.0,
},
)
captured = {}
monkeypatch.setattr(
routes,
"j",
lambda handler, payload, status=200, extra_headers=None: captured.update(
payload=payload,
status=status,
) or True,
)
assert routes.handle_post(object(), SimpleNamespace(path="/api/session/new")) is True
assert seen["resolved"] == [str(repo)]
assert captured["status"] == 200
session = captured["payload"]["session"]
assert session["workspace"] == str(worktree.resolve())