170 lines
5.6 KiB
Python
170 lines
5.6 KiB
Python
from __future__ import annotations
|
|
|
|
import pytest
|
|
import pytest_asyncio
|
|
from fastapi import HTTPException
|
|
from fastapi.routing import APIRoute
|
|
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
|
|
|
from server.routers.dashboard_router import (
|
|
dashboard,
|
|
get_all_conversations,
|
|
get_conversation_detail,
|
|
get_tool_call_stats,
|
|
get_user_activity_stats,
|
|
)
|
|
from server.utils.auth_middleware import get_superadmin_user
|
|
from yuxi.storage.postgres.models_business import Base, Conversation, Department, Message, ToolCall, User
|
|
from yuxi.utils.datetime_utils import utc_now_naive
|
|
|
|
pytestmark = [pytest.mark.asyncio, pytest.mark.unit]
|
|
|
|
|
|
@pytest_asyncio.fixture()
|
|
async def dashboard_session():
|
|
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
|
|
async with engine.begin() as conn:
|
|
await conn.run_sync(Base.metadata.create_all)
|
|
factory = async_sessionmaker(engine, expire_on_commit=False)
|
|
async with factory() as db:
|
|
dept_a = Department(name="Dept A")
|
|
dept_b = Department(name="Dept B")
|
|
superadmin = User(
|
|
username="Super Admin",
|
|
uid="superadmin",
|
|
password_hash="$argon2id$placeholder",
|
|
role="superadmin",
|
|
department=dept_a,
|
|
)
|
|
admin_a = User(
|
|
username="Admin A",
|
|
uid="admin_a",
|
|
password_hash="$argon2id$placeholder",
|
|
role="admin",
|
|
department=dept_a,
|
|
)
|
|
user_a = User(
|
|
username="User A",
|
|
uid="user_a",
|
|
password_hash="$argon2id$placeholder",
|
|
role="user",
|
|
department=dept_a,
|
|
)
|
|
admin_b = User(
|
|
username="Admin B",
|
|
uid="admin_b",
|
|
password_hash="$argon2id$placeholder",
|
|
role="admin",
|
|
department=dept_b,
|
|
)
|
|
user_b = User(
|
|
username="User B",
|
|
uid="user_b",
|
|
password_hash="$argon2id$placeholder",
|
|
role="user",
|
|
department=dept_b,
|
|
)
|
|
now = utc_now_naive()
|
|
conversation_a = Conversation(
|
|
thread_id="thread-a",
|
|
uid="user_a",
|
|
agent_id="agent-shared",
|
|
title="Dept A conversation",
|
|
status="active",
|
|
created_at=now,
|
|
updated_at=now,
|
|
)
|
|
conversation_b = Conversation(
|
|
thread_id="thread-b",
|
|
uid="user_b",
|
|
agent_id="agent-shared",
|
|
title="Dept B conversation",
|
|
status="active",
|
|
created_at=now,
|
|
updated_at=now,
|
|
)
|
|
message_a = Message(conversation=conversation_a, role="assistant", content="A", created_at=now)
|
|
message_b = Message(conversation=conversation_b, role="assistant", content="B", created_at=now)
|
|
tool_call_a = ToolCall(message=message_a, tool_name="dept_a_tool", status="success", created_at=now)
|
|
tool_call_b = ToolCall(message=message_b, tool_name="dept_b_tool", status="success", created_at=now)
|
|
db.add_all(
|
|
[
|
|
dept_a,
|
|
dept_b,
|
|
superadmin,
|
|
admin_a,
|
|
user_a,
|
|
admin_b,
|
|
user_b,
|
|
conversation_a,
|
|
conversation_b,
|
|
message_a,
|
|
message_b,
|
|
tool_call_a,
|
|
tool_call_b,
|
|
]
|
|
)
|
|
await db.commit()
|
|
for item in [
|
|
dept_a,
|
|
dept_b,
|
|
superadmin,
|
|
admin_a,
|
|
user_a,
|
|
admin_b,
|
|
user_b,
|
|
conversation_a,
|
|
conversation_b,
|
|
]:
|
|
await db.refresh(item)
|
|
yield {"db": db, "superadmin": superadmin, "admin_a": admin_a}
|
|
await engine.dispose()
|
|
|
|
|
|
async def test_dashboard_routes_require_superadmin_dependency():
|
|
dashboard_routes = [route for route in dashboard.routes if isinstance(route, APIRoute)]
|
|
|
|
assert dashboard_routes
|
|
for route in dashboard_routes:
|
|
dependency_calls = {dependency.call for dependency in route.dependant.dependencies}
|
|
assert get_superadmin_user in dependency_calls
|
|
|
|
|
|
async def test_dashboard_dependency_rejects_department_admin(dashboard_session):
|
|
with pytest.raises(HTTPException) as exc:
|
|
await get_superadmin_user(dashboard_session["admin_a"])
|
|
|
|
assert exc.value.status_code == 403
|
|
|
|
|
|
async def test_conversation_list_superadmin_sees_all_departments(dashboard_session):
|
|
response = await get_all_conversations(db=dashboard_session["db"], current_user=dashboard_session["superadmin"])
|
|
|
|
assert {item["thread_id"] for item in response} == {"thread-a", "thread-b"}
|
|
|
|
|
|
async def test_conversation_detail_superadmin_can_view_other_department(dashboard_session):
|
|
response = await get_conversation_detail(
|
|
"thread-b",
|
|
db=dashboard_session["db"],
|
|
current_user=dashboard_session["superadmin"],
|
|
)
|
|
|
|
assert response["thread_id"] == "thread-b"
|
|
|
|
|
|
async def test_user_activity_stats_superadmin_include_all_departments(dashboard_session):
|
|
stats = await get_user_activity_stats(db=dashboard_session["db"], current_user=dashboard_session["superadmin"])
|
|
|
|
assert stats.total_users == 5
|
|
assert stats.active_users_24h == 2
|
|
assert stats.active_users_30d == 2
|
|
|
|
|
|
async def test_tool_stats_superadmin_include_all_departments(dashboard_session):
|
|
stats = await get_tool_call_stats(db=dashboard_session["db"], current_user=dashboard_session["superadmin"])
|
|
|
|
assert stats.total_calls == 2
|
|
assert stats.successful_calls == 2
|
|
assert {tool["tool_name"] for tool in stats.most_used_tools} == {"dept_a_tool", "dept_b_tool"}
|