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/__init__.py
Normal file
0
prometheus/tests/app/__init__.py
Normal file
0
prometheus/tests/app/api/__init__.py
Normal file
0
prometheus/tests/app/api/__init__.py
Normal file
57
prometheus/tests/app/api/test_auth.py
Normal file
57
prometheus/tests/app/api/test_auth.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from unittest import mock
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from prometheus.app.api.routes import auth
|
||||
from prometheus.app.exception_handler import register_exception_handlers
|
||||
|
||||
app = FastAPI()
|
||||
register_exception_handlers(app)
|
||||
app.include_router(auth.router, prefix="/auth", tags=["auth"])
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_service():
|
||||
service = mock.MagicMock()
|
||||
app.state.service = service
|
||||
yield service
|
||||
|
||||
|
||||
def test_login(mock_service):
|
||||
mock_service["user_service"].login = AsyncMock(return_value="your_access_token")
|
||||
response = client.post(
|
||||
"/auth/login",
|
||||
json={
|
||||
"username": "testuser",
|
||||
"email": "test@gmail.com",
|
||||
"password": "passwordpassword",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {"access_token": "your_access_token"},
|
||||
}
|
||||
|
||||
|
||||
def test_register(mock_service):
|
||||
mock_service["invitation_code_service"].check_invitation_code = AsyncMock(return_value=True)
|
||||
mock_service["user_service"].create_user = AsyncMock(return_value=None)
|
||||
mock_service["invitation_code_service"].mark_code_as_used = AsyncMock(return_value=None)
|
||||
|
||||
response = client.post(
|
||||
"/auth/register",
|
||||
json={
|
||||
"username": "testuser",
|
||||
"email": "test@gmail.com",
|
||||
"password": "passwordpassword",
|
||||
"invitation_code": "f23ee204-ff33-401d-8291-1f128d0db08a",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"code": 200, "message": "User registered successfully", "data": None}
|
||||
65
prometheus/tests/app/api/test_github_token.py
Normal file
65
prometheus/tests/app/api/test_github_token.py
Normal file
@@ -0,0 +1,65 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from prometheus.app.api.routes.github import router
|
||||
|
||||
# Create test app
|
||||
app = FastAPI()
|
||||
app.include_router(router, prefix="/github")
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_issue_data():
|
||||
"""Fixture for mock issue data."""
|
||||
return {
|
||||
"number": 123,
|
||||
"title": "Test Issue",
|
||||
"body": "This is a test issue body",
|
||||
"state": "open",
|
||||
"html_url": "https://github.com/owner/repo/issues/123",
|
||||
"comments": [
|
||||
{"username": "user1", "comment": "First comment"},
|
||||
{"username": "user2", "comment": "Second comment"},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def test_get_github_issue_success(mock_issue_data):
|
||||
"""Test successful retrieval of GitHub issue through the API endpoint."""
|
||||
|
||||
with patch("prometheus.app.api.routes.github.get_github_issue") as mock_get_issue:
|
||||
# Configure the mock
|
||||
mock_get_issue.return_value = mock_issue_data
|
||||
|
||||
# Make the request
|
||||
response = client.get(
|
||||
"/github/issue/",
|
||||
params={"repo": "owner/repo", "issue_number": 123, "github_token": "test_token"},
|
||||
)
|
||||
|
||||
# Assert response status
|
||||
assert response.status_code == 200
|
||||
|
||||
# Parse response
|
||||
response_data = response.json()
|
||||
|
||||
# Assert response structure
|
||||
assert "data" in response_data
|
||||
assert "message" in response_data
|
||||
assert "code" in response_data
|
||||
|
||||
# Assert data content
|
||||
data = response_data["data"]
|
||||
assert data["number"] == 123
|
||||
assert data["title"] == "Test Issue"
|
||||
assert data["body"] == "This is a test issue body"
|
||||
assert data["state"] == "open"
|
||||
assert len(data["comments"]) == 2
|
||||
assert data["comments"][0]["username"] == "user1"
|
||||
|
||||
# Verify the function was called with correct parameters
|
||||
mock_get_issue.assert_called_once_with("owner/repo", 123, "test_token")
|
||||
94
prometheus/tests/app/api/test_invitation_code.py
Normal file
94
prometheus/tests/app/api/test_invitation_code.py
Normal file
@@ -0,0 +1,94 @@
|
||||
import datetime
|
||||
from unittest import mock
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from prometheus.app.api.routes import invitation_code
|
||||
from prometheus.app.entity.invitation_code import InvitationCode
|
||||
from prometheus.app.exception_handler import register_exception_handlers
|
||||
|
||||
app = FastAPI()
|
||||
register_exception_handlers(app)
|
||||
app.include_router(invitation_code.router, prefix="/invitation-code", tags=["invitation_code"])
|
||||
|
||||
|
||||
@app.middleware("mock_jwt_middleware")
|
||||
async def add_user_id(request: Request, call_next):
|
||||
request.state.user_id = 1 # Set user_id to 1 for testing purposes
|
||||
response = await call_next(request)
|
||||
return response
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_service():
|
||||
service = mock.MagicMock()
|
||||
app.state.service = service
|
||||
yield service
|
||||
|
||||
|
||||
def test_create_invitation_code(mock_service):
|
||||
# Mock the return value of create_invitation_code
|
||||
mock_service["invitation_code_service"].create_invitation_code = AsyncMock(
|
||||
return_value=InvitationCode(
|
||||
id=1,
|
||||
code="testcode",
|
||||
is_used=False,
|
||||
expiration_time=datetime.datetime(
|
||||
year=2025, month=1, day=1, hour=0, minute=0, second=0
|
||||
),
|
||||
)
|
||||
)
|
||||
mock_service["user_service"].is_admin = AsyncMock(return_value=True)
|
||||
|
||||
# Test the creation endpoint
|
||||
response = client.post("invitation-code/create/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"code": "testcode",
|
||||
"is_used": False,
|
||||
"expiration_time": "2025-01-01T00:00:00",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_list(mock_service):
|
||||
# Mock user as admin and return a list of invitation codes
|
||||
mock_service["invitation_code_service"].list_invitation_codes = AsyncMock(
|
||||
return_value=[
|
||||
InvitationCode(
|
||||
id=1,
|
||||
code="testcode",
|
||||
is_used=False,
|
||||
expiration_time=datetime.datetime(
|
||||
year=2025, month=1, day=1, hour=0, minute=0, second=0
|
||||
),
|
||||
)
|
||||
]
|
||||
)
|
||||
mock_service["user_service"].is_admin = AsyncMock(return_value=True)
|
||||
|
||||
# Test the list endpoint
|
||||
response = client.get("invitation-code/list/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"code": "testcode",
|
||||
"is_used": False,
|
||||
"expiration_time": "2025-01-01T00:00:00",
|
||||
}
|
||||
],
|
||||
}
|
||||
174
prometheus/tests/app/api/test_issue.py
Normal file
174
prometheus/tests/app/api/test_issue.py
Normal file
@@ -0,0 +1,174 @@
|
||||
from unittest import mock
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from prometheus.app.api.routes import issue
|
||||
from prometheus.app.entity.repository import Repository
|
||||
from prometheus.app.exception_handler import register_exception_handlers
|
||||
from prometheus.lang_graph.graphs.issue_state import IssueType
|
||||
|
||||
app = FastAPI()
|
||||
register_exception_handlers(app)
|
||||
app.include_router(issue.router, prefix="/issue", tags=["issue"])
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_service():
|
||||
service = mock.MagicMock()
|
||||
app.state.service = service
|
||||
yield service
|
||||
|
||||
|
||||
def test_answer_issue(mock_service):
|
||||
mock_service["repository_service"].get_repository_by_id = AsyncMock(
|
||||
return_value=Repository(
|
||||
id=1,
|
||||
url="https://github.com/fake/repo.git",
|
||||
commit_id=None,
|
||||
playground_path="/path/to/playground",
|
||||
kg_root_node_id=0,
|
||||
user_id=None,
|
||||
kg_max_ast_depth=100,
|
||||
kg_chunk_size=1000,
|
||||
kg_chunk_overlap=100,
|
||||
)
|
||||
)
|
||||
mock_service["knowledge_graph_service"].get_knowledge_graph = AsyncMock(
|
||||
return_value=mock.MagicMock()
|
||||
)
|
||||
mock_service["repository_service"].update_repository_status = AsyncMock(return_value=None)
|
||||
mock_service["issue_service"].answer_issue.return_value = (
|
||||
"test patch", # patch
|
||||
True, # passed_reproducing_test
|
||||
True, # passed_regression_test
|
||||
True, # passed_existing_test
|
||||
"Issue fixed", # issue_response
|
||||
IssueType.BUG, # issue_type
|
||||
)
|
||||
|
||||
response = client.post(
|
||||
"/issue/answer/",
|
||||
json={
|
||||
"repository_id": 1,
|
||||
"issue_title": "Test Issue",
|
||||
"issue_body": "Test description",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"patch": "test patch",
|
||||
"passed_reproducing_test": True,
|
||||
"passed_regression_test": True,
|
||||
"passed_existing_test": True,
|
||||
"issue_response": "Issue fixed",
|
||||
"issue_type": "bug",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_answer_issue_no_repository(mock_service):
|
||||
mock_service["repository_service"].get_repository_by_id = AsyncMock(return_value=None)
|
||||
|
||||
response = client.post(
|
||||
"/issue/answer/",
|
||||
json={
|
||||
"repository_id": 1,
|
||||
"issue_title": "Test Issue",
|
||||
"issue_body": "Test description",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_answer_issue_invalid_container_config(mock_service):
|
||||
mock_service["repository_service"].get_repository_by_id = AsyncMock(
|
||||
return_value=Repository(
|
||||
id=1,
|
||||
url="https://github.com/fake/repo.git",
|
||||
commit_id=None,
|
||||
playground_path="/path/to/playground",
|
||||
kg_root_node_id=0,
|
||||
user_id=None,
|
||||
kg_max_ast_depth=100,
|
||||
kg_chunk_size=1000,
|
||||
kg_chunk_overlap=100,
|
||||
)
|
||||
)
|
||||
|
||||
response = client.post(
|
||||
"/issue/answer/",
|
||||
json={
|
||||
"repository_id": 1,
|
||||
"issue_title": "Test Issue",
|
||||
"issue_body": "Test description",
|
||||
"dockerfile_content": "FROM python:3.11",
|
||||
"workdir": None,
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
def test_answer_issue_with_container(mock_service):
|
||||
mock_service["repository_service"].get_repository_by_id = AsyncMock(
|
||||
return_value=Repository(
|
||||
id=1,
|
||||
url="https://github.com/fake/repo.git",
|
||||
commit_id=None,
|
||||
playground_path="/path/to/playground",
|
||||
kg_root_node_id=0,
|
||||
user_id=None,
|
||||
kg_max_ast_depth=100,
|
||||
kg_chunk_size=1000,
|
||||
kg_chunk_overlap=100,
|
||||
)
|
||||
)
|
||||
|
||||
mock_service["issue_service"].answer_issue.return_value = (
|
||||
"test patch",
|
||||
True,
|
||||
True,
|
||||
True,
|
||||
"Issue fixed",
|
||||
IssueType.BUG,
|
||||
)
|
||||
mock_service["knowledge_graph_service"].get_knowledge_graph = AsyncMock(
|
||||
return_value=mock.MagicMock()
|
||||
)
|
||||
mock_service["repository_service"].update_repository_status = AsyncMock(return_value=None)
|
||||
|
||||
test_payload = {
|
||||
"repository_id": 1,
|
||||
"issue_title": "Test Issue",
|
||||
"issue_body": "Test description",
|
||||
"dockerfile_content": "FROM python:3.11",
|
||||
"run_reproduce_test": True,
|
||||
"workdir": "/app",
|
||||
"build_commands": ["pip install -r requirements.txt"],
|
||||
"test_commands": ["pytest ."],
|
||||
}
|
||||
|
||||
response = client.post("/issue/answer/", json=test_payload)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"patch": "test patch",
|
||||
"passed_reproducing_test": True,
|
||||
"passed_regression_test": True,
|
||||
"passed_existing_test": True,
|
||||
"issue_response": "Issue fixed",
|
||||
"issue_type": "bug",
|
||||
},
|
||||
}
|
||||
168
prometheus/tests/app/api/test_repository.py
Normal file
168
prometheus/tests/app/api/test_repository.py
Normal file
@@ -0,0 +1,168 @@
|
||||
from unittest import mock
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from prometheus.app.api.routes import repository
|
||||
from prometheus.app.entity.repository import Repository
|
||||
from prometheus.app.exception_handler import register_exception_handlers
|
||||
|
||||
app = FastAPI()
|
||||
register_exception_handlers(app)
|
||||
app.include_router(repository.router, prefix="/repository", tags=["repository"])
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_service():
|
||||
service = mock.MagicMock()
|
||||
app.state.service = service
|
||||
yield service
|
||||
|
||||
|
||||
def test_upload_repository(mock_service):
|
||||
mock_service["repository_service"].clone_github_repo = AsyncMock(return_value="/mock/path")
|
||||
mock_service["repository_service"].get_repository_by_url_and_commit_id = AsyncMock(
|
||||
return_value=None
|
||||
)
|
||||
mock_service["repository_service"].create_new_repository = AsyncMock(return_value=1)
|
||||
mock_service["knowledge_graph_service"].build_and_save_knowledge_graph = AsyncMock(
|
||||
return_value=0
|
||||
)
|
||||
|
||||
response = client.post(
|
||||
"/repository/upload",
|
||||
json={
|
||||
"github_token": "mock_token",
|
||||
"https_url": "https://github.com/Pantheon-temple/Prometheus",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {"repository_id": 1},
|
||||
}
|
||||
|
||||
|
||||
def test_upload_repository_at_commit(mock_service):
|
||||
mock_service["repository_service"].clone_github_repo = AsyncMock(return_value="/mock/path")
|
||||
mock_service["repository_service"].get_repository_by_url_and_commit_id = AsyncMock(
|
||||
return_value=None
|
||||
)
|
||||
mock_service["repository_service"].create_new_repository = AsyncMock(return_value=1)
|
||||
mock_service["knowledge_graph_service"].build_and_save_knowledge_graph = AsyncMock(
|
||||
return_value=0
|
||||
)
|
||||
|
||||
response = client.post(
|
||||
"/repository/upload/",
|
||||
json={
|
||||
"github_token": "mock_token",
|
||||
"https_url": "https://github.com/Pantheon-temple/Prometheus",
|
||||
"commit_id": "0c554293648a8705769fa53ec896ae24da75f4fc",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_create_branch_and_push(mock_service):
|
||||
# Mock git_repo
|
||||
git_repo_mock = MagicMock()
|
||||
git_repo_mock.create_and_push_branch = AsyncMock(return_value=None)
|
||||
|
||||
# Let repository_service.get_repository return the mocked git_repo
|
||||
mock_service["repository_service"].get_repository.return_value = git_repo_mock
|
||||
mock_service["repository_service"].get_repository_by_id = AsyncMock(
|
||||
return_value=Repository(
|
||||
id=1,
|
||||
url="https://github.com/fake/repo.git",
|
||||
commit_id=None,
|
||||
playground_path="/path/to/playground",
|
||||
kg_root_node_id=0,
|
||||
user_id=None,
|
||||
kg_max_ast_depth=100,
|
||||
kg_chunk_size=1000,
|
||||
kg_chunk_overlap=100,
|
||||
)
|
||||
)
|
||||
|
||||
response = client.post(
|
||||
"/repository/create-branch-and-push/",
|
||||
json={
|
||||
"repository_id": 1,
|
||||
"branch_name": "new_branch",
|
||||
"commit_message": "Initial commit on new branch",
|
||||
"patch": "mock_patch_content",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
@mock.patch("prometheus.app.api.routes.repository.delete_repository_memory")
|
||||
def test_delete(mock_delete_memory, mock_service):
|
||||
# Mock the delete_repository_memory to return success
|
||||
mock_delete_memory.return_value = {"code": 200, "message": "success", "data": None}
|
||||
|
||||
mock_service["repository_service"].get_repository_by_id = AsyncMock(
|
||||
return_value=Repository(
|
||||
id=1,
|
||||
url="https://github.com/fake/repo.git",
|
||||
commit_id=None,
|
||||
playground_path="/path/to/playground",
|
||||
kg_root_node_id=0,
|
||||
user_id=None,
|
||||
kg_max_ast_depth=100,
|
||||
kg_chunk_size=1000,
|
||||
kg_chunk_overlap=100,
|
||||
)
|
||||
)
|
||||
mock_service["knowledge_graph_service"].clear_kg = AsyncMock(return_value=None)
|
||||
mock_service["repository_service"].clean_repository.return_value = None
|
||||
mock_service["repository_service"].delete_repository = AsyncMock(return_value=None)
|
||||
response = client.delete(
|
||||
"repository/delete",
|
||||
params={
|
||||
"repository_id": 1,
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_list(mock_service):
|
||||
mock_service["repository_service"].get_all_repositories = AsyncMock(
|
||||
return_value=[
|
||||
Repository(
|
||||
id=1,
|
||||
url="https://github.com/fake/repo.git",
|
||||
commit_id=None,
|
||||
playground_path="/path/to/playground",
|
||||
kg_root_node_id=0,
|
||||
user_id=None,
|
||||
kg_max_ast_depth=100,
|
||||
kg_chunk_size=1000,
|
||||
kg_chunk_overlap=100,
|
||||
)
|
||||
]
|
||||
)
|
||||
response = client.get("repository/list/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"url": "https://github.com/fake/repo.git",
|
||||
"commit_id": None,
|
||||
"is_working": False,
|
||||
"user_id": None,
|
||||
"kg_max_ast_depth": 100,
|
||||
"kg_chunk_size": 1000,
|
||||
"kg_chunk_overlap": 100,
|
||||
}
|
||||
],
|
||||
}
|
||||
82
prometheus/tests/app/api/test_user.py
Normal file
82
prometheus/tests/app/api/test_user.py
Normal file
@@ -0,0 +1,82 @@
|
||||
from unittest import mock
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from prometheus.app.api.routes import user
|
||||
from prometheus.app.entity.user import User
|
||||
from prometheus.app.exception_handler import register_exception_handlers
|
||||
|
||||
app = FastAPI()
|
||||
register_exception_handlers(app)
|
||||
app.include_router(user.router, prefix="/user", tags=["user"])
|
||||
|
||||
|
||||
@app.middleware("mock_jwt_middleware")
|
||||
async def add_user_id(request: Request, call_next):
|
||||
request.state.user_id = 1 # Set user_id to 1 for testing purposes
|
||||
response = await call_next(request)
|
||||
return response
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_service():
|
||||
service = mock.MagicMock()
|
||||
app.state.service = service
|
||||
yield service
|
||||
|
||||
|
||||
def test_list(mock_service):
|
||||
# Mock user as admin and return a list of users
|
||||
mock_service["user_service"].list_users = AsyncMock(
|
||||
return_value=[
|
||||
User(
|
||||
id=1,
|
||||
username="testuser",
|
||||
email="test@gmail.com",
|
||||
password_hash="hashedpassword",
|
||||
github_token="ghp_1234567890abcdef1234567890abcdef1234",
|
||||
issue_credit=10,
|
||||
is_superuser=False,
|
||||
)
|
||||
]
|
||||
)
|
||||
mock_service["user_service"].is_admin = AsyncMock(return_value=True)
|
||||
|
||||
# Test the list endpoint
|
||||
response = client.get("user/list/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"username": "testuser",
|
||||
"email": "test@gmail.com",
|
||||
"issue_credit": 10,
|
||||
"is_superuser": False,
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def test_set_github_token(mock_service):
|
||||
# Mock user as admin and return a list of users
|
||||
mock_service["user_service"].set_github_token = AsyncMock(return_value=None)
|
||||
|
||||
# Test the list endpoint
|
||||
response = client.put(
|
||||
"user/set-github-token/", json={"github_token": "ghp_1234567890abcdef1234567890abcdef1234"}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": None,
|
||||
}
|
||||
0
prometheus/tests/app/middlewares/__init__.py
Normal file
0
prometheus/tests/app/middlewares/__init__.py
Normal file
138
prometheus/tests/app/middlewares/test_jwt_middleware.py
Normal file
138
prometheus/tests/app/middlewares/test_jwt_middleware.py
Normal file
@@ -0,0 +1,138 @@
|
||||
import pytest
|
||||
from fastapi import FastAPI, Request, Response
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from prometheus.app.middlewares.jwt_middleware import JWTMiddleware
|
||||
from prometheus.exceptions.jwt_exception import JWTException
|
||||
from prometheus.utils.jwt_utils import JWTUtils
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
"""
|
||||
Create a FastAPI app with JWTMiddleware installed.
|
||||
We mark certain (method, path) pairs as login-required.
|
||||
"""
|
||||
app = FastAPI()
|
||||
|
||||
# Only these routes require login:
|
||||
login_required_routes = {
|
||||
("GET", "/protected"),
|
||||
("OPTIONS", "/protected"), # include options here if you want middleware to check it
|
||||
("GET", "/me"),
|
||||
}
|
||||
|
||||
app.add_middleware(JWTMiddleware, login_required_routes=login_required_routes)
|
||||
|
||||
@app.get("/public")
|
||||
def public():
|
||||
return {"ok": True, "route": "public"}
|
||||
|
||||
@app.get("/protected")
|
||||
def protected(request: Request):
|
||||
# Return back the user_id the middleware stores on request.state
|
||||
return {
|
||||
"ok": True,
|
||||
"route": "protected",
|
||||
"user_id": getattr(request.state, "user_id", None),
|
||||
}
|
||||
|
||||
@app.get("/me")
|
||||
def me(request: Request):
|
||||
return {"user_id": getattr(request.state, "user_id", None)}
|
||||
|
||||
# Explicit OPTIONS route to ensure 200/204 so we can assert behavior
|
||||
@app.options("/protected")
|
||||
def options_protected():
|
||||
return Response(status_code=204)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client(app):
|
||||
return TestClient(app)
|
||||
|
||||
|
||||
def test_non_protected_route_bypasses_auth(client):
|
||||
"""
|
||||
Requests to routes not listed in login_required_routes must bypass JWT check.
|
||||
"""
|
||||
resp = client.get("/public")
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["route"] == "public"
|
||||
|
||||
|
||||
def test_missing_authorization_returns_401_on_protected(client):
|
||||
"""
|
||||
Missing Authorization header on a protected endpoint should return 401.
|
||||
"""
|
||||
resp = client.get("/protected")
|
||||
assert resp.status_code == 401
|
||||
body = resp.json()
|
||||
assert body["code"] == 401
|
||||
assert "Valid JWT Token is missing" in body["message"]
|
||||
|
||||
|
||||
def test_wrong_scheme_returns_401_on_protected(client):
|
||||
"""
|
||||
Wrong Authorization scheme (not Bearer) should return 401 on protected endpoint.
|
||||
"""
|
||||
resp = client.get("/protected", headers={"Authorization": "Token abc.def.ghi"})
|
||||
assert resp.status_code == 401
|
||||
body = resp.json()
|
||||
assert body["code"] == 401
|
||||
assert "Valid JWT Token is missing" in body["message"]
|
||||
|
||||
|
||||
def test_invalid_token_raises_and_returns_error(client, monkeypatch):
|
||||
"""
|
||||
If JWTUtils.decode_token raises JWTException, middleware should map it to the response.
|
||||
"""
|
||||
|
||||
def fake_decode(_self, _: str):
|
||||
raise JWTException(code=403, message="Invalid or expired token")
|
||||
|
||||
# Patch the method on the class; middleware instantiates JWTUtils() internally
|
||||
monkeypatch.setattr(JWTUtils, "decode_token", fake_decode, raising=True)
|
||||
|
||||
resp = client.get("/protected", headers={"Authorization": "Bearer bad.token"})
|
||||
assert resp.status_code == 403
|
||||
body = resp.json()
|
||||
assert body["code"] == 403
|
||||
assert body["message"] == "Invalid or expired token"
|
||||
|
||||
|
||||
def test_valid_token_sets_user_id_and_passes(client, monkeypatch):
|
||||
"""
|
||||
With a valid token, request should pass and user_id should be present on request.state.
|
||||
"""
|
||||
|
||||
def fake_decode(_self, _: str):
|
||||
# Return payload with user_id as middleware expects
|
||||
return {"user_id": 123}
|
||||
|
||||
monkeypatch.setattr(JWTUtils, "decode_token", fake_decode, raising=True)
|
||||
|
||||
resp = client.get("/protected", headers={"Authorization": "Bearer good.token"})
|
||||
assert resp.status_code == 200
|
||||
body = resp.json()
|
||||
assert body["ok"] is True
|
||||
assert body["user_id"] == 123
|
||||
|
||||
|
||||
def test_options_request_passes_through(client, monkeypatch):
|
||||
"""
|
||||
OPTIONS preflight should be allowed through without requiring a valid token.
|
||||
The middleware explicitly bypasses OPTIONS before checking Authorization.
|
||||
"""
|
||||
|
||||
# Even if decode_token would fail, OPTIONS should not trigger it.
|
||||
def boom(_self, _: str):
|
||||
raise AssertionError("decode_token should not be called for OPTIONS")
|
||||
|
||||
monkeypatch.setattr(JWTUtils, "decode_token", boom, raising=True)
|
||||
|
||||
resp = client.options("/protected")
|
||||
# Our route returns 204; any 2xx is acceptable depending on your route
|
||||
assert resp.status_code in (200, 204)
|
||||
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"
|
||||
28
prometheus/tests/app/test_main.py
Normal file
28
prometheus/tests/app/test_main.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_dependencies():
|
||||
"""Mock the service dependencies"""
|
||||
mock_service = MagicMock()
|
||||
with patch("prometheus.app.dependencies.initialize_services", return_value=mock_service):
|
||||
yield mock_service
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_client(mock_dependencies):
|
||||
"""Create a TestClient instance with mocked settings and dependencies"""
|
||||
# Import app here to ensure settings are properly mocked
|
||||
from prometheus.app.main import app
|
||||
|
||||
with TestClient(app) as client:
|
||||
yield client
|
||||
|
||||
|
||||
def test_app_initialization(test_client, mock_dependencies):
|
||||
"""Test that the app initializes correctly with mocked dependencies"""
|
||||
assert test_client.app.state.service is not None
|
||||
assert test_client.app.state.service == mock_dependencies
|
||||
Reference in New Issue
Block a user