Files
hqt/tests/test_integration.py
T
felixm 90944948f5 Initial commit: hqt — HQ Terminal TUI
TUI for orchestrating AI coding harness sessions (Claude Code, Codex,
Kiro, etc.) via tmux. Click CLI bootstraps a Textual TUI over
ProjectService/SessionService backed by SQLite, spawning harness
sessions as tmux windows through TmuxManager.

Includes recent fixes:
- Visible Tab focus highlight on dialog OK/Cancel buttons
- Auto-select first project on launch
- Auto-select first session + per-project session-selection memory
- tmux new-window targets an explicit free index, fixing
  "index N in use" failures (broken spawn/attach in attached sessions)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 17:06:57 -04:00

79 lines
2.7 KiB
Python

import pytest
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from hqt.db.models import Base, Harness
from hqt.projects.service import ProjectService
from hqt.sessions.service import SessionService
from hqt.tmux.manager import TmuxManager, SpawnResult
from hqt.harnesses.configurators.claude import ClaudeConfigurator
@pytest.fixture
def setup():
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
factory = sessionmaker(bind=engine)
db = factory()
db.add(Harness(name="claude-code", display_name="Claude Code"))
db.commit()
tmux = MagicMock(spec=TmuxManager)
tmux.spawn = AsyncMock(return_value=SpawnResult(ok=True, window_id="@1", error=""))
tmux.kill = AsyncMock()
tmux.is_alive = AsyncMock(return_value=True)
tmux.window_exists = AsyncMock(return_value=True)
tmux.poll_info = AsyncMock(return_value={})
tmux.capture_pane = AsyncMock(return_value="")
tmux.attach = AsyncMock(return_value=True)
configurator = ClaudeConfigurator()
harnesses = {"claude-code": configurator}
proj_svc = ProjectService(db)
sess_svc = SessionService(db=db, tmux=tmux, harnesses=harnesses)
return proj_svc, sess_svc, tmux
@pytest.mark.asyncio
async def test_full_lifecycle(setup):
proj_svc, sess_svc, tmux = setup
# Create project
project = proj_svc.create(name="test-proj", path="/tmp/test-proj")
assert project.id == 1
# Create session
create_result = await sess_svc.create_session(project_id=project.id, harness_name="claude-code", nickname="e2e")
session = create_result.session
assert session.id is not None
tmux.spawn.assert_called_once()
# List sessions - alive
from hqt.tmux.runner import WindowInfo
tmux.poll_info.return_value = {session.tmux_session_name: WindowInfo(alive=True, last_activity=0)}
infos = await sess_svc.list_sessions(project_id=project.id)
assert len(infos) == 1
assert infos[0].alive is True
# Stop session
await sess_svc.stop_session(session_id=session.id)
tmux.kill.assert_called_once_with(session.tmux_session_name)
# List sessions - dead
tmux.poll_info.return_value = {session.tmux_session_name: WindowInfo(alive=False, last_activity=0)}
infos = await sess_svc.list_sessions(project_id=project.id)
assert len(infos) == 1
assert infos[0].alive is False
# Delete session
tmux.window_exists.return_value = False
await sess_svc.delete_session(session_id=session.id)
# List sessions - empty
infos = await sess_svc.list_sessions(project_id=project.id)
assert len(infos) == 0