diff --git a/app/api/v1/endpoints/admin_metadata.py b/app/api/v1/endpoints/admin_metadata.py index 81c9a1c..a69579e 100644 --- a/app/api/v1/endpoints/admin_metadata.py +++ b/app/api/v1/endpoints/admin_metadata.py @@ -7,6 +7,7 @@ from sqlalchemy.engine.url import make_url from sqlalchemy.exc import IntegrityError, SQLAlchemyError from sqlalchemy.ext.asyncio import create_async_engine +from app.api.pagination import PaginatedList from app.auth.metadata_dependencies import ( get_current_metadata_admin, get_metadata_repository, @@ -236,7 +237,9 @@ async def list_metadata_users( metadata_repo: MetadataRepository = Depends(get_metadata_repository), ) -> List[MetadataUserResponse]: users = await metadata_repo.list_users(skip=skip, limit=limit) - return [MetadataUserResponse.model_validate(user) for user in users] + total = await metadata_repo.count_users() + items = [MetadataUserResponse.model_validate(user) for user in users] + return PaginatedList(items, total=total) @router.get("/admin/projects", response_model=List[AdminProjectResponse]) diff --git a/app/infra/db/metadb/repositories/metadata_repository.py b/app/infra/db/metadb/repositories/metadata_repository.py index 469683f..b75240f 100644 --- a/app/infra/db/metadb/repositories/metadata_repository.py +++ b/app/infra/db/metadb/repositories/metadata_repository.py @@ -4,7 +4,7 @@ from typing import Optional, List from uuid import UUID, uuid4 from cryptography.fernet import InvalidToken -from sqlalchemy import delete, select +from sqlalchemy import delete, func, select from sqlalchemy.ext.asyncio import AsyncSession from app.core.encryption import ( @@ -122,6 +122,12 @@ class MetadataRepository: ) return list(result.scalars().all()) + async def count_users(self) -> int: + result = await self.session.execute( + select(func.count()).select_from(models.User) + ) + return int(result.scalar() or 0) + async def upsert_user_from_keycloak( self, *, diff --git a/tests/api/test_admin_metadata_endpoints.py b/tests/api/test_admin_metadata_endpoints.py index 70161dc..3db355c 100644 --- a/tests/api/test_admin_metadata_endpoints.py +++ b/tests/api/test_admin_metadata_endpoints.py @@ -7,8 +7,9 @@ import pytest from fastapi import HTTPException from fastapi import Response -from app.auth.metadata_dependencies import get_current_metadata_admin from app.api.v1.endpoints import admin_metadata +from app.api.pagination import PaginatedList +from app.auth.metadata_dependencies import get_current_metadata_admin from app.domain.schemas.admin_metadata import ( AdminProjectCreateRequest, MetadataUsersBatchSyncRequest, @@ -83,6 +84,28 @@ def test_to_async_sqlalchemy_url_preserves_password(): assert "***" not in url +@pytest.mark.anyio +async def test_list_metadata_users_returns_page_with_real_total(): + users = [_user(username="alice"), _user(username="bob")] + repo = SimpleNamespace( + list_users=AsyncMock(return_value=users), + count_users=AsyncMock(return_value=7), + ) + + response = await admin_metadata.list_metadata_users( + skip=2, + limit=2, + current_user=_user(role="admin", is_superuser=True), + metadata_repo=repo, + ) + + assert isinstance(response, PaginatedList) + assert response.total == 7 + assert [item.username for item in response] == ["alice", "bob"] + repo.list_users.assert_awaited_once_with(skip=2, limit=2) + repo.count_users.assert_awaited_once_with() + + @pytest.mark.anyio async def test_sync_metadata_user_upserts_without_password(monkeypatch): keycloak_id = uuid4()