import pytest from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from sqlalchemy.pool import StaticPool from hqt.db.models import Base from hqt.errors import ServiceError from hqt.mcp.service import McpService from hqt.projects.service import ProjectService @pytest.fixture def factory(): # StaticPool + check_same_thread=False: every session created by the # factory shares the single in-memory connection, so per-operation # sessions all see the same database. engine = create_engine( "sqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool, ) Base.metadata.create_all(engine) return sessionmaker(bind=engine, expire_on_commit=False) class TestProjectService: def test_create_and_get(self, factory): svc = ProjectService(factory) p = svc.create("test", "/tmp/test") assert p.id is not None assert svc.get(p.id).name == "test" def test_list_excludes_archived(self, factory): svc = ProjectService(factory) svc.create("a", "/a") p2 = svc.create("b", "/b") svc.archive(p2.id) assert len(svc.list_all()) == 1 assert len(svc.list_all(include_archived=True)) == 2 def test_update_name_and_path(self, factory): svc = ProjectService(factory) p = svc.create("old", "/old/path") updated = svc.update(p.id, "new", "/new/path") assert updated.name == "new" assert updated.path == "/new/path" assert svc.get(p.id).path == "/new/path" def test_update_duplicate_path_raises(self, factory): svc = ProjectService(factory) svc.create("a", "/a") p2 = svc.create("b", "/b") with pytest.raises(ServiceError, match="already uses path"): svc.update(p2.id, "b", "/a") # DB session must remain usable after rollback, values unchanged assert svc.get(p2.id).path == "/b" assert svc.get(p2.id).name == "b" def test_update_unknown_id_raises(self, factory): svc = ProjectService(factory) with pytest.raises(ServiceError, match="not found"): svc.update(9999, "x", "/x") def test_create_duplicate_path_raises_service_error(self, factory): svc = ProjectService(factory) svc.create("a", "/dup") with pytest.raises(ServiceError, match="already uses path"): svc.create("b", "/dup") class TestMcpService: def test_crud(self, factory): svc = McpService(factory) server = svc.create("test-mcp", "stdio", command="node", args=["server.js"]) assert server.id is not None assert svc.get("test-mcp") is not None assert len(svc.list_all()) == 1 svc.delete("test-mcp") assert svc.get("test-mcp") is None def test_bind_unbind(self, factory): psvc = ProjectService(factory) msvc = McpService(factory) project = psvc.create("proj", "/proj") server = msvc.create("s1", "stdio", command="cmd") msvc.bind_to_project(project.id, server.id) assert len(msvc.get_project_mcps(project.id)) == 1 msvc.unbind_from_project(project.id, server.id) assert len(msvc.get_project_mcps(project.id)) == 0