import pytest 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