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

View File

View 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}

View 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")

View 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",
}
],
}

View 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",
},
}

View 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,
}
],
}

View 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,
}

View 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)

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"

View 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