Skip to content

Commit b866ded

Browse files
authored
Add pagination to user/resources (#601)
* Add pagination to `my resources` * Add backwards compatibility
1 parent 4d6aee7 commit b866ded

File tree

2 files changed

+77
-3
lines changed

2 files changed

+77
-3
lines changed

src/routers/user_router.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from sqlalchemy import select
66
from sqlmodel import Session
77

8+
from dependencies.pagination import PaginationParams
89
from routers.resource_routers import versioned_routers
910
from authentication import KeycloakUser, get_user_or_raise
1011
from database.authorization import Permission, PermissionType
@@ -32,17 +33,34 @@ def create(url_prefix: str, version: Version) -> APIRouter:
3233
},
3334
)
3435

36+
resources_for_user_description = "Return all assets for which you have administrator rights."
37+
if version == Version.V2:
38+
resources_for_user_description += (
39+
" For backwards compatibility reasons, if `limit=10` no limit is applied."
40+
" In V3 and later, the default limit of 10 is respected."
41+
)
42+
3543
@router.get(
3644
f"/user/resources",
45+
description=resources_for_user_description,
3746
tags=["User"],
38-
description="Return all assets for which you have administrator rights",
3947
response_model=Catalogue,
4048
)
4149
def get_versioned_resources_for_user(
50+
pagination: PaginationParams,
4251
user: KeycloakUser = Depends(get_user_or_raise),
4352
session: Session = Depends(get_session),
4453
) -> dict[str, list[AIoDConcept]]:
45-
resources = _get_resources_for_user(user, session)
54+
limit: int | None = pagination.limit
55+
if limit == 10 and version == Version.V2:
56+
limit = None
57+
58+
resources = _get_resources_for_user(
59+
user,
60+
session,
61+
offset=pagination.offset,
62+
limit=limit,
63+
)
4664
orm_to_read = {
4765
r.resource_class.__tablename__: r.orm_to_read
4866
for r in versioned_routers.get(version, [])
@@ -55,7 +73,13 @@ def get_versioned_resources_for_user(
5573
return router
5674

5775

58-
def _get_resources_for_user(user: KeycloakUser, session: Session) -> dict[str, list[AIoDConcept]]:
76+
def _get_resources_for_user(
77+
user: KeycloakUser,
78+
session: Session,
79+
*,
80+
offset: int = 0,
81+
limit: int | None = None,
82+
) -> dict[str, list[AIoDConcept]]:
5983
# "Ownership" is currently equivalent to having ADMIN permissions
6084
stmt = (
6185
select(AIoDEntryORM)
@@ -64,7 +88,10 @@ def _get_resources_for_user(user: KeycloakUser, session: Session) -> dict[str, l
6488
Permission.user_identifier == user._subject_identifier,
6589
Permission.type_ == PermissionType.ADMIN,
6690
)
91+
.offset(offset)
6792
)
93+
if limit:
94+
stmt = stmt.limit(limit)
6895
entries = session.scalars(stmt).all()
6996
assets_to_fetch = [entry.identifier for entry in entries]
7097
# We have AIoD entries, but want their respective asset information (e.g. publication).

src/tests/test_user_endpoints.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from http import HTTPStatus
22
from typing import Callable
33

4+
import pytest
45
from starlette.testclient import TestClient
56

67
from database.authorization import set_permission, PermissionType, register_user
@@ -10,6 +11,7 @@
1011
from database.model.agent.organisation import Organisation
1112
from tests.testutils.users import register_asset, logged_in_user, ALICE, BOB
1213
from tests.testutils.default_instances import publication_factory, publication
14+
from versioning import Version
1315

1416

1517
def test_my_resources_can_be_empty(client: TestClient) -> None:
@@ -98,3 +100,48 @@ def test_my_resources_counts_only_if_admin(client: TestClient, publication_facto
98100
def test_my_resources_must_be_authorized(client: TestClient) -> None:
99101
response = client.get("/user/resources")
100102
assert response.status_code == HTTPStatus.UNAUTHORIZED
103+
104+
105+
def test_my_resources_paginates(client: TestClient, publication_factory: Publication) -> None:
106+
register_asset(publication_factory(), owner=ALICE, status=EntryStatus.PUBLISHED)
107+
register_asset(publication_factory(), owner=ALICE, status=EntryStatus.PUBLISHED)
108+
register_asset(publication_factory(), owner=ALICE, status=EntryStatus.PUBLISHED)
109+
110+
with logged_in_user(ALICE):
111+
response = client.get("/user/resources?limit=2", headers={"Authorization": "fake token"})
112+
assert response.status_code == HTTPStatus.OK
113+
assert len(response.json()["publication"]) == 2, "Set limit should be respected"
114+
115+
first_asset = response.json()["publication"][0]["identifier"]
116+
117+
response = client.get("/user/resources?offset=1", headers={"Authorization": "fake token"})
118+
assert response.status_code == HTTPStatus.OK
119+
assert len(response.json()["publication"]) == 2, "Using an offset can reduce the amount of returned results."
120+
msg = "Increasing offset should lead to different assets."
121+
assert first_asset not in [pub["identifier"] for pub in response.json()["publication"]], msg
122+
123+
response = client.get("/user/resources?offset=1&limit=1", headers={"Authorization": "fake token"})
124+
assert response.status_code == HTTPStatus.OK
125+
assert len(response.json()["publication"]) == 1, "Offset and limit should be able to be used together."
126+
127+
128+
@pytest.mark.versions(Version.V2)
129+
def test_my_resources_no_limit_default_in_v2(client: TestClient, publication_factory: Publication) -> None:
130+
for _ in range(11): # The default pagination limit is 10
131+
register_asset(publication_factory(), owner=ALICE, status=EntryStatus.PUBLISHED)
132+
133+
with logged_in_user(ALICE):
134+
response = client.get("/user/resources", headers={"Authorization": "fake token"})
135+
assert response.status_code == HTTPStatus.OK
136+
assert len(response.json()["publication"]) == 11
137+
138+
139+
@pytest.mark.versions(Version.LATEST)
140+
def test_my_resources_limit_default_in_latest(client: TestClient, publication_factory: Publication) -> None:
141+
for _ in range(11): # The default pagination limit is 10
142+
register_asset(publication_factory(), owner=ALICE, status=EntryStatus.PUBLISHED)
143+
144+
with logged_in_user(ALICE):
145+
response = client.get("/user/resources", headers={"Authorization": "fake token"})
146+
assert response.status_code == HTTPStatus.OK
147+
assert len(response.json()["publication"]) == 10, "Set limit should be respected"

0 commit comments

Comments
 (0)