"""Dashboard chat workspace picker: ``GET /api/chat/workspaces`` + ``/api/pty?cwd=``. A fresh dashboard chat can be aimed at a host directory (the same projects/repos the Desktop sidebar lists); the pick reaches the TUI as ``HERMES_CWD``/``HERMES_TUI_CWD`` and a dead path fails closed instead of silently landing in the dashboard's launch dir. """ from pathlib import Path import pytest from fastapi import HTTPException from hermes_cli import web_server, web_server_chat from hermes_cli.web_routers import chat_workspaces pytest.importorskip("starlette.testclient") from starlette.testclient import TestClient @pytest.fixture def client(): previous = getattr(web_server.app.state, "auth_required", None) web_server.app.state.auth_required = False test_client = TestClient(web_server.app) test_client.headers[web_server._SESSION_HEADER_NAME] = web_server._SESSION_TOKEN try: yield test_client finally: if previous is None: try: delattr(web_server.app.state, "auth_required") except AttributeError: pass else: web_server.app.state.auth_required = previous def test_workspaces_lists_projects_and_discovered_repos(client, tmp_path): """Explicit project folders AND the discovery cache both surface; the payload names the default cwd a chat lands in when nothing is picked.""" from hermes_cli import projects_db as pdb proj_dir = tmp_path / "proj" proj_dir.mkdir() repo_dir = tmp_path / "scanned" repo_dir.mkdir() with pdb.connect_closing() as conn: pdb.create_project(conn, name="Demo", folders=[str(proj_dir)]) pdb.record_discovered_repos(conn, [(str(repo_dir), "scanned")], replace=True, policy_key="k") body = client.get("/api/chat/workspaces").json() assert [p["name"] for p in body["projects"]] == ["Demo"] assert body["projects"][0]["folders"][0]["path"] == str(proj_dir) assert str(repo_dir) in {r["root"] for r in body["repos"]} assert Path(body["default_cwd"]).is_dir() assert body["home"] == str(Path.home()) def test_resolve_chat_cwd_fails_closed_and_reaches_the_tui_env(monkeypatch, tmp_path): """A picked directory becomes HERMES_CWD + HERMES_TUI_CWD on the PTY child; a missing one is a 400 (never the launch-dir fallback); unset means no override.""" import hermes_cli.main_tui_launch as tui_launch monkeypatch.setattr( tui_launch, "_make_tui_argv", lambda *_a, **_k: (["node", "fake-tui.js"], tmp_path)) monkeypatch.setattr(tui_launch, "_apply_tui_python_env", lambda _env: None) assert chat_workspaces.resolve_chat_cwd("") is None with pytest.raises(HTTPException) as exc: chat_workspaces.resolve_chat_cwd(str(tmp_path / "gone")) assert exc.value.status_code == 400 picked = chat_workspaces.resolve_chat_cwd(str(tmp_path)) _argv, _cwd, env = web_server_chat._resolve_chat_argv(workspace_cwd=picked) assert env["HERMES_CWD"] == str(tmp_path) assert env["HERMES_TUI_CWD"] == str(tmp_path) _argv, _cwd, env = web_server_chat._resolve_chat_argv() assert "HERMES_TUI_CWD" not in env