137 lines
4.6 KiB
Python
137 lines
4.6 KiB
Python
|
|
import asyncio
|
||
|
|
import json
|
||
|
|
import sys
|
||
|
|
from collections.abc import Generator
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
from fastapi.testclient import TestClient
|
||
|
|
from sqlalchemy import create_engine
|
||
|
|
from sqlalchemy.orm import Session, sessionmaker
|
||
|
|
from sqlalchemy.pool import StaticPool
|
||
|
|
|
||
|
|
BACKEND_ROOT = Path(__file__).resolve().parents[1]
|
||
|
|
REPO_ROOT = BACKEND_ROOT.parent
|
||
|
|
NANOBOT_ROOT = REPO_ROOT / "nanobot"
|
||
|
|
if str(BACKEND_ROOT) not in sys.path:
|
||
|
|
sys.path.insert(0, str(BACKEND_ROOT))
|
||
|
|
if str(NANOBOT_ROOT) not in sys.path:
|
||
|
|
sys.path.insert(0, str(NANOBOT_ROOT))
|
||
|
|
|
||
|
|
from app.context import current_session_id
|
||
|
|
from app.core.security import CurrentUser, get_current_user
|
||
|
|
from app.database import Base, get_db
|
||
|
|
from app.models.project import Project
|
||
|
|
from app.models.user import User
|
||
|
|
from app.tools.subagent import InvokeSubagentTool, ListSubagentsTool
|
||
|
|
from main import app
|
||
|
|
|
||
|
|
|
||
|
|
def _seed_owner_and_project(db: Session) -> tuple[User, Project]:
|
||
|
|
user = User(
|
||
|
|
username="task4-owner",
|
||
|
|
email="task4-owner@example.com",
|
||
|
|
hashed_password="test",
|
||
|
|
is_admin=False,
|
||
|
|
)
|
||
|
|
db.add(user)
|
||
|
|
db.commit()
|
||
|
|
db.refresh(user)
|
||
|
|
|
||
|
|
project = Project(
|
||
|
|
name="task4-project",
|
||
|
|
description="task4",
|
||
|
|
owner_id=user.id,
|
||
|
|
)
|
||
|
|
db.add(project)
|
||
|
|
db.commit()
|
||
|
|
db.refresh(project)
|
||
|
|
return user, project
|
||
|
|
|
||
|
|
|
||
|
|
def test_create_subagent_then_list_and_invoke_success(monkeypatch) -> None:
|
||
|
|
engine = create_engine(
|
||
|
|
"sqlite://",
|
||
|
|
connect_args={"check_same_thread": False},
|
||
|
|
poolclass=StaticPool,
|
||
|
|
)
|
||
|
|
testing_session_local = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||
|
|
Base.metadata.create_all(bind=engine)
|
||
|
|
|
||
|
|
db = testing_session_local()
|
||
|
|
user, project = _seed_owner_and_project(db)
|
||
|
|
user_id = user.id
|
||
|
|
username = user.username
|
||
|
|
project_id = project.id
|
||
|
|
db.close()
|
||
|
|
|
||
|
|
def override_get_db() -> Generator[Session, None, None]:
|
||
|
|
override_db = testing_session_local()
|
||
|
|
try:
|
||
|
|
yield override_db
|
||
|
|
finally:
|
||
|
|
override_db.close()
|
||
|
|
|
||
|
|
def override_current_user() -> CurrentUser:
|
||
|
|
return CurrentUser(id=user_id, username=username, is_admin=False)
|
||
|
|
|
||
|
|
app.dependency_overrides[get_db] = override_get_db
|
||
|
|
app.dependency_overrides[get_current_user] = override_current_user
|
||
|
|
|
||
|
|
token = current_session_id.set("api:task4-regression")
|
||
|
|
captured: dict[str, object] = {}
|
||
|
|
|
||
|
|
async def fake_process_message(message, session_id, project_id, model_id):
|
||
|
|
captured["message"] = message
|
||
|
|
captured["session_id"] = session_id
|
||
|
|
captured["project_id"] = project_id
|
||
|
|
captured["model_id"] = model_id
|
||
|
|
return "invoke-ok"
|
||
|
|
|
||
|
|
try:
|
||
|
|
monkeypatch.setattr("app.tools.subagent.SessionLocal", testing_session_local)
|
||
|
|
monkeypatch.setattr(
|
||
|
|
"app.tools.subagent.session_alias_store.get_alias_meta",
|
||
|
|
lambda _session_id: {"project_id": project_id},
|
||
|
|
)
|
||
|
|
monkeypatch.setattr("app.tools.subagent.get_llm_configs", lambda: [])
|
||
|
|
monkeypatch.setattr("app.tools.subagent.get_active_llm_config", lambda: None)
|
||
|
|
monkeypatch.setattr("app.tools.subagent.nanobot_service.process_message", fake_process_message)
|
||
|
|
|
||
|
|
client = TestClient(app)
|
||
|
|
create_response = client.post(
|
||
|
|
f"/api/v1/projects/{project_id}/subagents",
|
||
|
|
json={
|
||
|
|
"name": "task4-subagent",
|
||
|
|
"description": "task4-desc",
|
||
|
|
"instructions": "focus on regression",
|
||
|
|
"model": "gpt-x",
|
||
|
|
},
|
||
|
|
)
|
||
|
|
assert create_response.status_code == 200
|
||
|
|
created = create_response.json()
|
||
|
|
assert created["project_id"] == project_id
|
||
|
|
assert created["name"] == "task4-subagent"
|
||
|
|
|
||
|
|
listed = asyncio.run(ListSubagentsTool().execute())
|
||
|
|
listed_payload = json.loads(listed)
|
||
|
|
assert len(listed_payload) == 1
|
||
|
|
assert listed_payload[0]["name"] == "task4-subagent"
|
||
|
|
assert listed_payload[0]["description"] == "task4-desc"
|
||
|
|
|
||
|
|
invoke_result = asyncio.run(
|
||
|
|
InvokeSubagentTool().execute(
|
||
|
|
subagent_name="task4-subagent",
|
||
|
|
task="run regression task",
|
||
|
|
)
|
||
|
|
)
|
||
|
|
assert "completed the task" in invoke_result
|
||
|
|
assert "invoke-ok" in invoke_result
|
||
|
|
assert captured["project_id"] == project_id
|
||
|
|
assert captured["session_id"] == f"api:task4-regression:subagent:{created['id']}"
|
||
|
|
assert "focus on regression" in str(captured["message"])
|
||
|
|
finally:
|
||
|
|
current_session_id.reset(token)
|
||
|
|
app.dependency_overrides.clear()
|
||
|
|
Base.metadata.drop_all(bind=engine)
|
||
|
|
engine.dispose()
|