feat: add /api/admin/users/search endpoint
- Add endpoint to search users by email (case-insensitive) - Limit results to 10 for autocomplete purposes - Require VIEW_ALL_EXCHANGES permission (admin only) - Add tests for search functionality and access control
This commit is contained in:
parent
27896ed136
commit
ef01a970d5
2 changed files with 89 additions and 1 deletions
|
|
@ -813,5 +813,39 @@ async def admin_cancel_trade(
|
|||
return _to_admin_exchange_response(exchange)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Admin User Search Endpoint
|
||||
# =============================================================================
|
||||
|
||||
admin_users_router = APIRouter(prefix="/api/admin/users", tags=["admin-users"])
|
||||
|
||||
|
||||
class UserSearchResult(BaseModel):
|
||||
"""Result item for user search."""
|
||||
|
||||
id: int
|
||||
email: str
|
||||
|
||||
|
||||
@admin_users_router.get("/search", response_model=list[UserSearchResult])
|
||||
async def search_users(
|
||||
q: str = Query(..., min_length=1, description="Search query for user email"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_current_user: User = Depends(require_permission(Permission.VIEW_ALL_EXCHANGES)),
|
||||
) -> list[UserSearchResult]:
|
||||
"""
|
||||
Search users by email for autocomplete.
|
||||
|
||||
Returns users whose email contains the search query (case-insensitive).
|
||||
Limited to 10 results for autocomplete purposes.
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(User).where(User.email.ilike(f"%{q}%")).order_by(User.email).limit(10)
|
||||
)
|
||||
users = result.scalars().all()
|
||||
|
||||
return [UserSearchResult(id=u.id, email=u.email) for u in users]
|
||||
|
||||
|
||||
# All routers from this module for easy registration
|
||||
routers = [router, trades_router, admin_trades_router]
|
||||
routers = [router, trades_router, admin_trades_router, admin_users_router]
|
||||
|
|
|
|||
|
|
@ -862,3 +862,57 @@ class TestAdminCancelTrade:
|
|||
response = await client.post(f"/api/admin/trades/{trade_id}/cancel")
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# User Search Tests
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestAdminUserSearch:
|
||||
"""Test admin user search endpoint."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_admin_can_search_users(
|
||||
self, client_factory, admin_user, regular_user
|
||||
):
|
||||
"""Admin can search for users by email."""
|
||||
async with client_factory.create(cookies=admin_user["cookies"]) as client:
|
||||
# Search for the regular user
|
||||
response = await client.get(
|
||||
f"/api/admin/users/search?q={regular_user['user']['email'][:5]}"
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert isinstance(data, list)
|
||||
# Should find the regular user
|
||||
emails = [u["email"] for u in data]
|
||||
assert regular_user["user"]["email"] in emails
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_returns_limited_results(self, client_factory, admin_user):
|
||||
"""Search results are limited to 10."""
|
||||
async with client_factory.create(cookies=admin_user["cookies"]) as client:
|
||||
# Search with a common pattern
|
||||
response = await client.get("/api/admin/users/search?q=@")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data) <= 10
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_regular_user_cannot_search(self, client_factory, regular_user):
|
||||
"""Regular user cannot access user search."""
|
||||
async with client_factory.create(cookies=regular_user["cookies"]) as client:
|
||||
response = await client.get("/api/admin/users/search?q=test")
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_requires_query(self, client_factory, admin_user):
|
||||
"""Search requires a query parameter."""
|
||||
async with client_factory.create(cookies=admin_user["cookies"]) as client:
|
||||
response = await client.get("/api/admin/users/search")
|
||||
|
||||
assert response.status_code == 422 # Validation error
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue