feat: Add intelligent auto-router and enhanced integrations

- Add intelligent-router.sh hook for automatic agent routing
- Add AUTO-TRIGGER-SUMMARY.md documentation
- Add FINAL-INTEGRATION-SUMMARY.md documentation
- Complete Prometheus integration (6 commands + 4 tools)
- Complete Dexto integration (12 commands + 5 tools)
- Enhanced Ralph with access to all agents
- Fix /clawd command (removed disable-model-invocation)
- Update hooks.json to v5 with intelligent routing
- 291 total skills now available
- All 21 commands with automatic routing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
admin
2026-01-28 00:27:56 +04:00
Unverified
parent 3b128ba3bd
commit b52318eeae
1724 changed files with 351216 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
import pytest
from prometheus.app.services.database_service import DatabaseService
from tests.test_utils.fixtures import postgres_container_fixture # noqa: F401
@pytest.mark.slow
async def test_database_service(postgres_container_fixture): # noqa: F811
url = postgres_container_fixture.get_connection_url()
database_service = DatabaseService(url)
assert database_service.engine is not None
try:
await database_service.start()
await database_service.close()
except Exception as e:
pytest.fail(f"Connection verification failed: {e}")

View File

@@ -0,0 +1,122 @@
from datetime import datetime, timedelta, timezone
import pytest
from sqlalchemy.ext.asyncio import AsyncSession
from prometheus.app.entity.invitation_code import InvitationCode
from prometheus.app.services.database_service import DatabaseService
from prometheus.app.services.invitation_code_service import InvitationCodeService
from tests.test_utils.fixtures import postgres_container_fixture # noqa: F401
@pytest.fixture
async def mock_database_service(postgres_container_fixture): # noqa: F811
"""Fixture: provide a clean DatabaseService using the Postgres test container."""
service = DatabaseService(postgres_container_fixture.get_connection_url())
await service.start()
yield service
await service.close()
@pytest.fixture
def service(mock_database_service):
"""Fixture: construct an InvitationCodeService with the database service."""
return InvitationCodeService(database_service=mock_database_service)
async def _insert_code(
session: AsyncSession, code: str, is_used: bool = False, expires_in_seconds: int = 3600
) -> InvitationCode:
"""Helper: insert a single InvitationCode with given state and expiration."""
obj = InvitationCode(
code=code,
is_used=is_used,
expiration_time=datetime.now(timezone.utc) + timedelta(seconds=expires_in_seconds),
)
session.add(obj)
await session.commit()
await session.refresh(obj)
return obj
async def test_create_invitation_code(service):
"""Test that create_invitation_code correctly generates and returns an InvitationCode."""
invitation_code = await service.create_invitation_code()
# Verify the returned object is an InvitationCode instance
assert isinstance(invitation_code, InvitationCode)
assert isinstance(invitation_code.code, str)
assert len(invitation_code.code) == 36 # uuid4 string length
assert invitation_code.id is not None
# Verify the object is persisted in the database
async with AsyncSession(service.engine) as session:
db_obj = await session.get(InvitationCode, invitation_code.id)
assert db_obj is not None
assert db_obj.code == invitation_code.code
async def test_list_invitation_codes(service):
"""Test that list_invitation_codes returns all stored invitation codes."""
# Insert two invitation codes first
code1 = await service.create_invitation_code()
code2 = await service.create_invitation_code()
codes = await service.list_invitation_codes()
# Verify length
assert len(codes) >= 2
# Verify both created codes are included
all_codes = [c.code for c in codes]
assert code1.code in all_codes
assert code2.code in all_codes
async def test_check_invitation_code_returns_false_when_not_exists(service):
"""check_invitation_code should return False if the code does not exist."""
ok = await service.check_invitation_code("non-existent-code")
assert ok is False
async def test_check_invitation_code_returns_false_when_used(service):
"""check_invitation_code should return False if the code is already used."""
async with AsyncSession(service.engine) as session:
await _insert_code(session, "used-code", is_used=True, expires_in_seconds=3600)
ok = await service.check_invitation_code("used-code")
assert ok is False
async def test_check_invitation_code_returns_false_when_expired(service):
"""check_invitation_code should return False if the code is expired."""
async with AsyncSession(service.engine) as session:
# Negative expires_in_seconds makes it expire in the past
await _insert_code(session, "expired-code", is_used=False, expires_in_seconds=-60)
ok = await service.check_invitation_code("expired-code")
assert ok is False
async def test_check_invitation_code_returns_true_when_valid(service):
"""check_invitation_code should return True if the code exists, not used, and not expired."""
async with AsyncSession(service.engine) as session:
await _insert_code(session, "valid-code", is_used=False, expires_in_seconds=3600)
ok = await service.check_invitation_code("valid-code")
assert ok is True
async def test_mark_code_as_used_persists_state(service):
"""mark_code_as_used should set 'used' to True and persist to DB."""
async with AsyncSession(service.engine) as session:
created = await _insert_code(session, "to-use", is_used=False, expires_in_seconds=3600)
created_id = created.id
# Act
await service.mark_code_as_used("to-use")
# Assert persisted state
async with AsyncSession(service.engine) as session:
refreshed = await session.get(InvitationCode, created_id)
assert refreshed is not None
assert refreshed.is_used is True

View File

@@ -0,0 +1,157 @@
from unittest.mock import Mock, create_autospec
import pytest
from prometheus.app.services.issue_service import IssueService
from prometheus.app.services.llm_service import LLMService
from prometheus.app.services.repository_service import RepositoryService
from prometheus.git.git_repository import GitRepository
from prometheus.graph.knowledge_graph import KnowledgeGraph
from prometheus.lang_graph.graphs.issue_state import IssueType
@pytest.fixture
def mock_llm_service():
service = create_autospec(LLMService, instance=True)
service.advanced_model = "gpt-4"
service.base_model = "gpt-3.5-turbo"
return service
@pytest.fixture
def mock_repository_service():
service = create_autospec(RepositoryService, instance=True)
return service
@pytest.fixture
def issue_service(mock_llm_service, mock_repository_service):
return IssueService(
llm_service=mock_llm_service,
working_directory="/tmp/working_dir/",
logging_level="DEBUG",
)
async def test_answer_issue_with_general_container(issue_service, monkeypatch):
# Setup
mock_issue_graph = Mock()
mock_issue_graph_class = Mock(return_value=mock_issue_graph)
monkeypatch.setattr("prometheus.app.services.issue_service.IssueGraph", mock_issue_graph_class)
mock_container = Mock()
mock_general_container_class = Mock(return_value=mock_container)
monkeypatch.setattr(
"prometheus.app.services.issue_service.GeneralContainer", mock_general_container_class
)
repository = Mock(spec=GitRepository)
repository.get_working_directory.return_value = "mock/working/directory"
knowledge_graph = Mock(spec=KnowledgeGraph)
# Mock output state for a bug type
mock_output_state = {
"issue_type": IssueType.BUG,
"edit_patch": "test_patch",
"passed_reproducing_test": True,
"passed_regression_test": True,
"passed_existing_test": True,
"issue_response": "test_response",
}
mock_issue_graph.invoke.return_value = mock_output_state
# Exercise
result = issue_service.answer_issue(
repository=repository,
knowledge_graph=knowledge_graph,
repository_id=1,
issue_title="Test Issue",
issue_body="Test Body",
issue_comments=[],
issue_type=IssueType.BUG,
run_build=True,
run_regression_test=True,
run_existing_test=True,
run_reproduce_test=True,
number_of_candidate_patch=1,
build_commands=None,
test_commands=None,
)
# Verify
mock_general_container_class.assert_called_once_with(
project_path=repository.get_working_directory(), build_commands=None, test_commands=None
)
mock_issue_graph_class.assert_called_once_with(
advanced_model=issue_service.llm_service.advanced_model,
base_model=issue_service.llm_service.base_model,
kg=knowledge_graph,
git_repo=repository,
container=mock_container,
repository_id=1,
test_commands=None,
)
assert result == ("test_patch", True, True, True, "test_response", IssueType.BUG)
async def test_answer_issue_with_user_defined_container(issue_service, monkeypatch):
# Setup
mock_issue_graph = Mock()
mock_issue_graph_class = Mock(return_value=mock_issue_graph)
monkeypatch.setattr("prometheus.app.services.issue_service.IssueGraph", mock_issue_graph_class)
mock_container = Mock()
mock_user_container_class = Mock(return_value=mock_container)
monkeypatch.setattr(
"prometheus.app.services.issue_service.UserDefinedContainer", mock_user_container_class
)
repository = Mock(spec=GitRepository)
repository.get_working_directory.return_value = "mock/working/directory"
knowledge_graph = Mock(spec=KnowledgeGraph)
# Mock output state for a question type
mock_output_state = {
"issue_type": IssueType.QUESTION,
"edit_patch": None,
"passed_reproducing_test": False,
"passed_regression_test": False,
"passed_existing_test": False,
"issue_response": "test_response",
}
mock_issue_graph.invoke.return_value = mock_output_state
# Exercise
result = issue_service.answer_issue(
repository=repository,
knowledge_graph=knowledge_graph,
repository_id=1,
issue_title="Test Issue",
issue_body="Test Body",
issue_comments=[],
issue_type=IssueType.QUESTION,
run_build=True,
run_regression_test=True,
run_existing_test=True,
run_reproduce_test=True,
number_of_candidate_patch=1,
dockerfile_content="FROM python:3.8",
image_name="test-image",
workdir="/app",
build_commands=["pip install -r requirements.txt"],
test_commands=["pytest"],
)
# Verify
mock_user_container_class.assert_called_once_with(
project_path=repository.get_working_directory(),
workdir="/app",
build_commands=["pip install -r requirements.txt"],
test_commands=["pytest"],
dockerfile_content="FROM python:3.8",
image_name="test-image",
)
assert result == (None, False, False, False, "test_response", IssueType.QUESTION)

View File

@@ -0,0 +1,104 @@
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock
import pytest
from prometheus.app.services.knowledge_graph_service import KnowledgeGraphService
from prometheus.app.services.neo4j_service import Neo4jService
from prometheus.graph.knowledge_graph import KnowledgeGraph
from prometheus.neo4j.knowledge_graph_handler import KnowledgeGraphHandler
@pytest.fixture
def mock_neo4j_service():
"""Mock the Neo4jService."""
neo4j_service = MagicMock(Neo4jService)
return neo4j_service
@pytest.fixture
def mock_kg_handler():
"""Mock the KnowledgeGraphHandler."""
kg_handler = MagicMock(KnowledgeGraphHandler)
return kg_handler
@pytest.fixture
def knowledge_graph_service(mock_neo4j_service, mock_kg_handler):
"""Fixture to create KnowledgeGraphService instance."""
mock_neo4j_service.neo4j_driver = MagicMock() # Mocking Neo4j driver
mock_kg_handler.get_new_knowledge_graph_root_node_id = AsyncMock(return_value=123)
mock_kg_handler.write_knowledge_graph = AsyncMock()
knowledge_graph_service = KnowledgeGraphService(
neo4j_service=mock_neo4j_service,
neo4j_batch_size=1000,
max_ast_depth=5,
chunk_size=1000,
chunk_overlap=100,
)
knowledge_graph_service.kg_handler = mock_kg_handler
return knowledge_graph_service
async def test_build_and_save_knowledge_graph(knowledge_graph_service, mock_kg_handler):
"""Test the build_and_save_knowledge_graph method."""
# Given
source_code_path = Path("/mock/path/to/source/code") # Mock path to source code
# Mock KnowledgeGraph and its methods
mock_kg = MagicMock(KnowledgeGraph)
mock_kg.build_graph = AsyncMock(return_value=None) # Mock async method to build graph
mock_kg_handler.get_new_knowledge_graph_root_node_id = AsyncMock(
return_value=123
) # Mock return value
mock_kg_handler.write_knowledge_graph = AsyncMock(return_value=None)
# When
with pytest.raises(Exception):
knowledge_graph_service.kg_handler = mock_kg_handler
result = await knowledge_graph_service.build_and_save_knowledge_graph(source_code_path)
# Then
assert result == 123 # Ensure that the correct root node ID is returned
mock_kg.build_graph.assert_awaited_once_with(
source_code_path
) # Check if build_graph was called correctly
mock_kg_handler.write_knowledge_graph.assert_called_once() # Ensure graph write happened
mock_kg_handler.get_new_knowledge_graph_root_node_id.assert_called_once() # Ensure the root node ID was fetched
async def test_clear_kg(knowledge_graph_service, mock_kg_handler):
"""Test the clear_kg method."""
# Given
root_node_id = 123 # Mock root node ID
# When
await knowledge_graph_service.clear_kg(root_node_id)
# Then
mock_kg_handler.clear_knowledge_graph.assert_called_once_with(root_node_id)
async def test_get_knowledge_graph(knowledge_graph_service, mock_kg_handler):
"""Test the get_knowledge_graph method."""
# Given
root_node_id = 123 # Mock root node ID
max_ast_depth = 5
chunk_size = 1000
chunk_overlap = 100
# Mock KnowledgeGraph
mock_kg = MagicMock(KnowledgeGraph)
mock_kg_handler.read_knowledge_graph = AsyncMock(return_value=mock_kg) # Mock return value
# When
result = await knowledge_graph_service.get_knowledge_graph(
root_node_id, max_ast_depth, chunk_size, chunk_overlap
)
# Then
mock_kg_handler.read_knowledge_graph.assert_called_once_with(
root_node_id, max_ast_depth, chunk_size, chunk_overlap
) # Ensure read_knowledge_graph is called with the correct parameters
assert result == mock_kg # Ensure the correct KnowledgeGraph object is returned

View File

@@ -0,0 +1,125 @@
from unittest.mock import Mock, patch
import pytest
from prometheus.app.services.llm_service import CustomChatOpenAI, LLMService, get_model
@pytest.fixture
def mock_custom_chat_openai():
with patch("prometheus.app.services.llm_service.CustomChatOpenAI") as mock:
yield mock
@pytest.fixture
def mock_chat_anthropic():
with patch("prometheus.app.services.llm_service.ChatAnthropic") as mock:
yield mock
@pytest.fixture
def mock_chat_google():
with patch("prometheus.app.services.llm_service.ChatGoogleGenerativeAI") as mock:
yield mock
def test_llm_service_init(mock_custom_chat_openai, mock_chat_anthropic):
# Setup
mock_gpt_instance = Mock()
mock_claude_instance = Mock()
mock_custom_chat_openai.return_value = mock_gpt_instance
mock_chat_anthropic.return_value = mock_claude_instance
# Exercise
service = LLMService(
advanced_model_name="gpt-4",
base_model_name="claude-2.1",
advanced_model_temperature=0.0,
base_model_temperature=0.0,
openai_format_api_key="openai-key",
openai_format_base_url="https://api.openai.com/v1",
anthropic_api_key="anthropic-key",
)
# Verify
assert service.advanced_model == mock_gpt_instance
assert service.base_model == mock_claude_instance
mock_custom_chat_openai.assert_called_once_with(
model="gpt-4",
api_key="openai-key",
base_url="https://api.openai.com/v1",
temperature=0.0,
max_retries=3,
)
mock_chat_anthropic.assert_called_once_with(
model_name="claude-2.1",
api_key="anthropic-key",
temperature=0.0,
max_retries=3,
)
def test_get_openai_format_model(mock_custom_chat_openai):
# Exercise
get_model(
model_name="openrouter/model",
openai_format_api_key="openrouter-key",
openai_format_base_url="https://openrouter.ai/api/v1",
temperature=0.0,
)
# Verify
mock_custom_chat_openai.assert_called_once_with(
model="openrouter/model",
api_key="openrouter-key",
base_url="https://openrouter.ai/api/v1",
temperature=0.0,
max_retries=3,
)
def test_get_model_claude(mock_chat_anthropic):
# Exercise
get_model(
model_name="claude-2.1",
anthropic_api_key="anthropic-key",
temperature=0.0,
)
# Verify
mock_chat_anthropic.assert_called_once_with(
model_name="claude-2.1",
api_key="anthropic-key",
temperature=0.0,
max_retries=3,
)
def test_get_model_gemini(mock_chat_google):
# Exercise
get_model(
model_name="gemini-pro",
gemini_api_key="gemini-key",
temperature=0.0,
)
# Verify
mock_chat_google.assert_called_once_with(
model="gemini-pro",
api_key="gemini-key",
temperature=0.0,
max_retries=3,
)
def test_custom_chat_openai_bind_tools():
# Setup
model = CustomChatOpenAI(api_key="test-key", max_input_tokens=64000)
mock_tools = [Mock()]
# Exercise
with patch("prometheus.chat_models.custom_chat_openai.ChatOpenAI.bind_tools") as mock_bind:
model.bind_tools(mock_tools)
# Verify
mock_bind.assert_called_once_with(mock_tools, tool_choice=None, parallel_tool_calls=False)

View File

@@ -0,0 +1,19 @@
import pytest
from prometheus.app.services.neo4j_service import Neo4jService
from tests.test_utils.fixtures import neo4j_container_with_kg_fixture # noqa: F401
@pytest.mark.slow
async def test_neo4j_service(neo4j_container_with_kg_fixture): # noqa: F811
neo4j_container, kg = neo4j_container_with_kg_fixture
neo4j_service = Neo4jService(
neo4j_container.get_connection_url(), neo4j_container.username, neo4j_container.password
)
assert neo4j_service.neo4j_driver is not None
neo4j_service.start()
try:
await neo4j_service.neo4j_driver.verify_connectivity()
except Exception as e:
pytest.fail(f"Connection verification failed: {e}")
await neo4j_service.close()

View File

@@ -0,0 +1,197 @@
from pathlib import Path
from unittest.mock import create_autospec, patch
import pytest
from sqlalchemy.ext.asyncio import AsyncSession
from prometheus.app.entity.repository import Repository
from prometheus.app.services.database_service import DatabaseService
from prometheus.app.services.knowledge_graph_service import KnowledgeGraphService
from prometheus.app.services.repository_service import RepositoryService
from prometheus.git.git_repository import GitRepository
from tests.test_utils.fixtures import postgres_container_fixture # noqa: F401
@pytest.fixture
def mock_kg_service():
# Mock KnowledgeGraphService; RepositoryService only reads its attributes in other paths
kg_service = create_autospec(KnowledgeGraphService, instance=True)
kg_service.max_ast_depth = 3
kg_service.chunk_size = 1000
kg_service.chunk_overlap = 100
return kg_service
@pytest.fixture
async def mock_database_service(postgres_container_fixture): # noqa: F811
service = DatabaseService(postgres_container_fixture.get_connection_url())
await service.start()
yield service
await service.close()
@pytest.fixture
def mock_git_repository():
repo = create_autospec(GitRepository, instance=True)
repo.get_working_directory.return_value = Path("/test/working/dir/repositories/repo")
return repo
@pytest.fixture
def service(mock_kg_service, mock_database_service, monkeypatch):
working_dir = "/test/working/dir"
# Avoid touching the real filesystem when creating the base repo folder
monkeypatch.setattr(Path, "mkdir", lambda *args, **kwargs: None)
return RepositoryService(
kg_service=mock_kg_service,
database_service=mock_database_service, # <-- use the correct fixture here
working_dir=working_dir,
)
async def test_clone_new_github_repo(service, mock_git_repository, monkeypatch):
# Arrange
test_url = "https://github.com/test/repo"
test_commit = "abc123"
test_github_token = "test_token"
expected_path = Path("/test/working/dir/repositories/repo")
# Force get_new_playground_path() to return a deterministic path for assertions
monkeypatch.setattr(service, "get_new_playground_path", lambda: expected_path)
# Patch GitRepository so its constructor returns our mock instance
with patch(
"prometheus.app.services.repository_service.GitRepository",
return_value=mock_git_repository,
) as mock_git_class:
# Act
result_path = await service.clone_github_repo(test_github_token, test_url, test_commit)
# Assert
# GitRepository should be instantiated without args (per current implementation)
mock_git_class.assert_called_once_with()
# Ensure the clone method was invoked with correct parameters
mock_git_repository.from_clone_repository.assert_called_once_with(
test_url, test_github_token, expected_path
)
# Verify the requested commit was checked out
mock_git_repository.checkout_commit.assert_called_once_with(test_commit)
# The returned path should be the working directory of the mocked repo
assert result_path == expected_path
def test_get_new_playground_path(service):
expected_uuid = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
expected_path = service.target_directory / expected_uuid
with patch("uuid.uuid4") as mock_uuid:
mock_uuid.return_value.hex = expected_uuid
result = service.get_new_playground_path()
assert result == expected_path
def test_clean_repository_removes_dir_and_parent(service, monkeypatch):
"""
Should call shutil.rmtree on the repository path and remove its parent directory
when the path exists.
"""
repo_path = Path("/tmp/repositories/abc123")
repository = Repository(playground_path=str(repo_path))
# Patch path.exists() to return True
monkeypatch.setattr(Path, "exists", lambda self: self == repo_path)
# Track calls to shutil.rmtree and Path.rmdir
removed = {"rmtree": None, "rmdir": []}
monkeypatch.setattr(
"shutil.rmtree",
lambda target: removed.update(rmtree=target),
)
monkeypatch.setattr(
Path,
"rmdir",
lambda self: removed["rmdir"].append(self),
)
service.clean_repository(repository)
# Assert rmtree called with correct path string
assert removed["rmtree"] == str(repo_path)
# Assert rmdir called on the parent directory
assert repo_path.parent in removed["rmdir"]
def test_clean_repository_skips_when_not_exists(service, monkeypatch):
"""
Should not call rmtree or rmdir when the repository path does not exist.
"""
repo_path = Path("/tmp/repositories/abc123")
repository = Repository(playground_path=str(repo_path))
# Path.exists returns False
monkeypatch.setattr(Path, "exists", lambda self: False)
monkeypatch.setattr("shutil.rmtree", lambda target: pytest.fail("rmtree should not be called"))
monkeypatch.setattr(Path, "rmdir", lambda self: pytest.fail("rmdir should not be called"))
# No exception means pass
service.clean_repository(repository)
def test_get_repository_returns_git_repo_instance(service):
"""
Should create a GitRepository, call from_local_repository with the given path,
and return the GitRepository instance.
"""
test_path = "/some/local/path"
mock_git_repo_instance = create_autospec(GitRepository, instance=True)
# Patch GitRepository() constructor to return our mock instance
with patch(
"prometheus.app.services.repository_service.GitRepository",
return_value=mock_git_repo_instance,
) as mock_git_class:
result = service.get_repository(test_path)
# Verify GitRepository() was called with no args
mock_git_class.assert_called_once_with()
# Verify from_local_repository was called with the correct Path object
mock_git_repo_instance.from_local_repository.assert_called_once_with(Path(test_path))
# Verify the returned object is the same as the mock instance
assert result == mock_git_repo_instance
async def test_create_new_repository(service):
# Exercise
await service.create_new_repository(
url="https://github.com/test/repo",
commit_id="abc123",
playground_path="/tmp/repositories/repo",
user_id=None,
kg_root_node_id=0,
)
# Verify the object is persisted in the database
async with AsyncSession(service.engine) as session:
db_obj = await session.get(Repository, 1)
assert db_obj is not None
assert db_obj.url == "https://github.com/test/repo"
assert db_obj.commit_id == "abc123"
assert db_obj.playground_path == "/tmp/repositories/repo"
assert db_obj.user_id is None
assert db_obj.kg_root_node_id == 0
async def test_get_all_repositories(service):
# Exercise
repos = await service.get_all_repositories()
# Verify
assert len(repos) == 1

View File

@@ -0,0 +1,50 @@
import pytest
from sqlalchemy.ext.asyncio import AsyncSession
from prometheus.app.entity.user import User
from prometheus.app.services.database_service import DatabaseService
from prometheus.app.services.user_service import UserService
from tests.test_utils.fixtures import postgres_container_fixture # noqa: F401
@pytest.fixture
async def mock_database_service(postgres_container_fixture): # noqa: F811
service = DatabaseService(postgres_container_fixture.get_connection_url())
await service.start()
yield service
await service.close()
async def test_create_superuser(mock_database_service):
# Exercise
service = UserService(mock_database_service)
await service.create_superuser(
"testuser", "test@gmail.com", "password123", github_token="gh_token"
)
# Verify
async with AsyncSession(service.engine) as session:
user = await session.get(User, 1)
assert user is not None
assert user.username == "testuser"
assert user.email == "test@gmail.com"
assert user.github_token == "gh_token"
async def test_login(mock_database_service):
# Exercise
service = UserService(mock_database_service)
access_token = await service.login("testuser", "test@gmail.com", "password123")
# Verify
assert access_token is not None
async def test_set_github_token(mock_database_service):
# Exercise
service = UserService(mock_database_service)
await service.set_github_token(1, "new_gh_token")
# Verify
async with AsyncSession(service.engine) as session:
user = await session.get(User, 1)
assert user is not None
assert user.github_token == "new_gh_token"