Skip to content

Commit 927dc9c

Browse files
committed
draft tests
1 parent f762de7 commit 927dc9c

File tree

4 files changed

+158
-25
lines changed

4 files changed

+158
-25
lines changed

services/catalog/src/simcore_service_catalog/db/repositories/_services_sql.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def list_services_stmt(
6666
return stmt
6767

6868

69-
def _version(column_or_value):
69+
def by_version(column_or_value):
7070
# converts version value string to array[integer] that can be compared
7171
# i.e. '1.2.3' -> [1, 2, 3]
7272
return sa.func.string_to_array(column_or_value, ".").cast(ARRAY(INTEGER))
@@ -165,7 +165,7 @@ def list_latest_services_stmt(
165165
.where(access_rights)
166166
.order_by(
167167
services_meta_data.c.key,
168-
sa.desc(_version(services_meta_data.c.version)), # latest first
168+
sa.desc(by_version(services_meta_data.c.version)), # latest first
169169
)
170170
.distinct(services_meta_data.c.key) # get only first
171171
.limit(limit)
@@ -312,12 +312,7 @@ def get_service_history_stmt(
312312
user_id: UserID,
313313
access_rights: sa.sql.ClauseElement,
314314
service_key: ServiceKey,
315-
limit: int | None,
316-
offset: int | None,
317315
):
318-
assert offset is None, "UNDER DEV"
319-
assert limit is None, "UNDER DEV"
320-
321316
_sq = (
322317
sa.select(
323318
services_meta_data.c.key,
@@ -356,7 +351,7 @@ def get_service_history_stmt(
356351
history_subquery = (
357352
sa.select(_sq)
358353
.order_by(
359-
sa.desc(_version(_sq.c.version)), # latest version first
354+
sa.desc(by_version(_sq.c.version)), # latest version first
360355
)
361356
.alias("history_subquery")
362357
)

services/catalog/src/simcore_service_catalog/db/repositories/services.py

Lines changed: 87 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
from models_library.users import UserID
1717
from psycopg2.errors import ForeignKeyViolation
1818
from pydantic import PositiveInt, TypeAdapter, ValidationError
19+
from simcore_postgres_database.utils_repos import pass_or_acquire_connection
1920
from simcore_postgres_database.utils_services import create_select_latest_services_query
21+
from sqlalchemy import sql
2022
from sqlalchemy.dialects.postgresql import insert as pg_insert
2123
from sqlalchemy.sql import and_, or_
2224
from sqlalchemy.sql.expression import tuple_
@@ -30,11 +32,18 @@
3032
ServiceWithHistoryDBGet,
3133
)
3234
from ...models.services_specifications import ServiceSpecificationsAtDB
33-
from ..tables import services_access_rights, services_meta_data, services_specifications
35+
from ..tables import (
36+
services_access_rights,
37+
services_compatibility,
38+
services_meta_data,
39+
services_specifications,
40+
user_to_groups,
41+
)
3442
from ._base import BaseRepository
3543
from ._services_sql import (
3644
SERVICES_META_DATA_COLS,
3745
AccessRightsClauses,
46+
by_version,
3847
can_get_service_stmt,
3948
get_service_history_stmt,
4049
get_service_stmt,
@@ -340,8 +349,6 @@ async def get_service_with_history(
340349
user_id=user_id,
341350
access_rights=AccessRightsClauses.can_read,
342351
service_key=key,
343-
limit=None,
344-
offset=None,
345352
)
346353
async with self.db_engine.begin() as conn:
347354
result = await conn.execute(stmt_history)
@@ -440,30 +447,95 @@ async def get_service_history(
440447
user_id: UserID,
441448
# get args
442449
key: ServiceKey,
443-
# list args: pagination
444-
limit: int | None = None,
445-
offset: int | None = None,
446-
) -> tuple[PositiveInt, list[ReleaseDBGet]]:
447-
448-
stmt_total, stmt_history = get_service_history_stmt(
450+
) -> list[ReleaseDBGet]:
451+
"""
452+
DEPRECATED: use get_service_history_page instead!
453+
"""
454+
stmt_history = get_service_history_stmt(
449455
product_name=product_name,
450456
user_id=user_id,
451457
access_rights=AccessRightsClauses.can_read,
452458
service_key=key,
453-
offset=offset,
454-
limit=limit,
455459
)
456460
async with self.db_engine.connect() as conn:
457-
result = await conn.execute(stmt_total)
458-
total_count = result.scalar() or 0
459-
460461
result = await conn.execute(stmt_history)
461462
row = result.one_or_none()
462463

463-
items = (
464+
return (
464465
TypeAdapter(list[ReleaseDBGet]).validate_python(row.history) if row else []
465466
)
466467

468+
async def get_service_history_page(
469+
self,
470+
# access-rights
471+
product_name: ProductName,
472+
user_id: UserID,
473+
# get args
474+
key: ServiceKey,
475+
# list args: pagination
476+
limit: int | None = None,
477+
offset: int | None = None,
478+
) -> tuple[PositiveInt, list[ReleaseDBGet]]:
479+
480+
base_query = (
481+
sql.select(
482+
services_meta_data.c.key,
483+
services_meta_data.c.version,
484+
services_meta_data.c.version_display,
485+
services_meta_data.c.deprecated,
486+
services_meta_data.c.created,
487+
services_compatibility.c.custom_policy.label(
488+
"compatibility_policy"
489+
), # CompatiblePolicyDict | None
490+
)
491+
.select_from(
492+
# joins because access-rights might change per version
493+
services_meta_data.join(
494+
services_access_rights,
495+
(services_meta_data.c.key == services_access_rights.c.key)
496+
& (
497+
services_meta_data.c.version == services_access_rights.c.version
498+
),
499+
)
500+
.join(
501+
user_to_groups,
502+
(user_to_groups.c.gid == services_access_rights.c.gid),
503+
)
504+
.outerjoin(
505+
services_compatibility,
506+
(services_meta_data.c.key == services_compatibility.c.key)
507+
& (
508+
services_meta_data.c.version == services_compatibility.c.version
509+
),
510+
)
511+
)
512+
.where(
513+
(services_meta_data.c.key == key)
514+
& (services_access_rights.c.product_name == product_name)
515+
& (user_to_groups.c.uid == user_id)
516+
& AccessRightsClauses.can_read
517+
)
518+
.distinct()
519+
)
520+
521+
subquery = base_query.subquery()
522+
count_query = sql.select(sql.func.count()).select_from(subquery)
523+
524+
page_query = (
525+
base_query.order_by(sql.desc(by_version(base_query.c.version)))
526+
.offset(offset)
527+
.limit(limit)
528+
)
529+
530+
async with pass_or_acquire_connection(self.db_engine) as conn:
531+
total_count: PositiveInt = await conn.scalar(count_query) or 0
532+
533+
result = await conn.stream(page_query)
534+
items: list[ReleaseDBGet] = [
535+
ReleaseDBGet.model_validate(row, from_attributes=True)
536+
async for row in result
537+
]
538+
467539
return total_count, items
468540

469541
# Service Access Rights ----

services/catalog/src/simcore_service_catalog/services/services_api.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -476,15 +476,15 @@ async def get_service_history(
476476
offset: NonNegativeInt | None = None,
477477
include_compatibility: bool = False,
478478
) -> tuple[PageTotalCount, list[ServiceRelease]]:
479-
total_count, history = await repo.get_service_history(
479+
total_count, history = await repo.get_service_history_page(
480480
# NOTE: that the service history might be different for each user
481481
# since access-rights are defined on a k:v basis
482482
product_name=product_name,
483483
user_id=user_id,
484484
key=service_key,
485485
limit=limit,
486486
offset=offset,
487-
) or (0, [])
487+
)
488488

489489
compatibility_map = {}
490490
if include_compatibility:

services/catalog/tests/unit/with_dbs/test_db_repositories.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,3 +476,69 @@ async def test_can_get_service(
476476
key=service_key,
477477
version=service_version,
478478
)
479+
480+
481+
async def test_get_service_history_page(
482+
target_product: ProductName,
483+
create_fake_service_data: Callable,
484+
services_db_tables_injector: Callable,
485+
services_repo: ServicesRepository,
486+
user_id: UserID,
487+
):
488+
# inject services with multiple versions
489+
service_key = "simcore/services/dynamic/test-service"
490+
num_versions = 10
491+
await services_db_tables_injector(
492+
[
493+
create_fake_service_data(
494+
service_key,
495+
f"{v}.0.0",
496+
team_access=None,
497+
everyone_access=None,
498+
product=target_product,
499+
)
500+
for v in range(num_versions)
501+
]
502+
)
503+
504+
# fetch full history using get_service_history_page
505+
total_count, history = await services_repo.get_service_history_page(
506+
product_name=target_product,
507+
user_id=user_id,
508+
key=service_key,
509+
)
510+
assert total_count == num_versions
511+
assert len(history) == num_versions
512+
assert [release.version for release in history] == [
513+
f"{v}.0.0" for v in reversed(range(num_versions))
514+
]
515+
516+
# fetch full history using deprecated get_service_history
517+
deprecated_history = await services_repo.get_service_history(
518+
product_name=target_product,
519+
user_id=user_id,
520+
key=service_key,
521+
)
522+
assert len(deprecated_history) == len(history)
523+
assert [release.version for release in deprecated_history] == [
524+
release.version for release in history
525+
]
526+
527+
# fetch paginated history
528+
limit = 3
529+
offset = 2
530+
total_count, paginated_history = await services_repo.get_service_history_page(
531+
product_name=target_product,
532+
user_id=user_id,
533+
key=service_key,
534+
limit=limit,
535+
offset=offset,
536+
)
537+
assert total_count == num_versions
538+
assert len(paginated_history) == limit
539+
assert [release.version for release in paginated_history] == [
540+
f"{v}.0.0" for v in reversed(range(offset, offset + limit))
541+
]
542+
543+
# compare paginated results with the corresponding slice of the full history
544+
assert paginated_history == history[offset : offset + limit]

0 commit comments

Comments
 (0)