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:
0
prometheus/tests/app/services/__init__.py
Normal file
0
prometheus/tests/app/services/__init__.py
Normal file
17
prometheus/tests/app/services/test_database_service.py
Normal file
17
prometheus/tests/app/services/test_database_service.py
Normal 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}")
|
||||
122
prometheus/tests/app/services/test_invitation_code.py
Normal file
122
prometheus/tests/app/services/test_invitation_code.py
Normal 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
|
||||
157
prometheus/tests/app/services/test_issue_service.py
Normal file
157
prometheus/tests/app/services/test_issue_service.py
Normal 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)
|
||||
104
prometheus/tests/app/services/test_knowledge_graph_service.py
Normal file
104
prometheus/tests/app/services/test_knowledge_graph_service.py
Normal 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
|
||||
125
prometheus/tests/app/services/test_llm_service.py
Normal file
125
prometheus/tests/app/services/test_llm_service.py
Normal 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)
|
||||
19
prometheus/tests/app/services/test_neo4j_service.py
Normal file
19
prometheus/tests/app/services/test_neo4j_service.py
Normal 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()
|
||||
197
prometheus/tests/app/services/test_repository_service.py
Normal file
197
prometheus/tests/app/services/test_repository_service.py
Normal 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
|
||||
50
prometheus/tests/app/services/test_user_service.py
Normal file
50
prometheus/tests/app/services/test_user_service.py
Normal 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"
|
||||
Reference in New Issue
Block a user