Skip to content

Commit d6c56aa

Browse files
Merge branch 'master' into is7622/fix-automatic-api-keys-generation
2 parents 1dcebab + 50710a8 commit d6c56aa

File tree

11 files changed

+115
-42
lines changed

11 files changed

+115
-42
lines changed

packages/pytest-simcore/src/pytest_simcore/helpers/catalog_rpc_server.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
# pylint: disable=no-self-use
12
# pylint: disable=not-context-manager
23
# pylint: disable=protected-access
34
# pylint: disable=redefined-outer-name
45
# pylint: disable=unused-argument
56
# pylint: disable=unused-variable
67

78

9+
from dataclasses import dataclass
10+
811
from models_library.api_schemas_catalog.services import (
912
LatestServiceGet,
1013
ServiceGetV2,
@@ -122,7 +125,7 @@ async def update_service(
122125
return got.model_copy(update=update.model_dump(exclude_unset=True))
123126

124127
@validate_call(config={"arbitrary_types_allowed": True})
125-
async def list_my_service_history_paginated(
128+
async def list_my_service_history_latest_first(
126129
self,
127130
rpc_client: RabbitMQRPCClient | MockType,
128131
*,
@@ -171,3 +174,20 @@ async def get_service_ports(
171174
return TypeAdapter(list[ServicePortGet]).validate_python(
172175
ServicePortGet.model_json_schema()["examples"],
173176
)
177+
178+
179+
@dataclass
180+
class ZeroListingCatalogRpcSideEffects:
181+
"""Catalog RPC mocks that return empty lists"""
182+
183+
async def list_services_paginated(self, *args, **kwargs): ...
184+
async def get_service(self, *args, **kwargs): ...
185+
async def update_service(self, *args, **kwargs): ...
186+
async def get_service_ports(self, *args, **kwargs): ...
187+
async def list_my_service_history_latest_first(self, *args, **kwargs):
188+
return PageRpc[ServiceRelease].create(
189+
[],
190+
total=0,
191+
limit=10,
192+
offset=0,
193+
)

packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/catalog/services.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ async def batch_get_my_services(
194194

195195

196196
@validate_call(config={"arbitrary_types_allowed": True})
197-
async def list_my_service_history_paginated( # pylint: disable=too-many-arguments
197+
async def list_my_service_history_latest_first( # pylint: disable=too-many-arguments
198198
rpc_client: RabbitMQRPCClient,
199199
*,
200200
product_name: ProductName,
@@ -205,13 +205,15 @@ async def list_my_service_history_paginated( # pylint: disable=too-many-argumen
205205
filters: ServiceListFilters | None = None,
206206
) -> PageRpcServiceRelease:
207207
"""
208-
208+
Sorts service releases by version (latest first)
209209
Raises:
210210
ValidationError: on invalid arguments
211211
"""
212212
result = await rpc_client.request(
213213
CATALOG_RPC_NAMESPACE,
214-
TypeAdapter(RPCMethodName).validate_python("list_my_service_history_paginated"),
214+
TypeAdapter(RPCMethodName).validate_python(
215+
"list_my_service_history_latest_first"
216+
),
215217
product_name=product_name,
216218
user_id=user_id,
217219
service_key=service_key,

services/api-server/src/simcore_service_api_server/_service_programs.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,15 @@ async def list_program_history(
6363
offset: NonNegativeInt,
6464
limit: PositiveInt,
6565
) -> tuple[list[Program], PageMetaInfoLimitOffset]:
66-
page, page_meta = await self._catalog_service.list_release_history(
66+
page, page_meta = await self._catalog_service.list_release_history_latest_first(
6767
user_id=user_id,
6868
service_key=program_key,
6969
product_name=product_name,
7070
offset=offset,
7171
limit=limit,
7272
)
73+
if len(page) == 0:
74+
return [], page_meta
7375

7476
program_instance = await self._catalog_service.get(
7577
user_id=user_id,

services/api-server/src/simcore_service_api_server/_service_solvers.py

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from common_library.pagination_tools import iter_pagination_params
21
from models_library.api_schemas_catalog.services import ServiceListFilters
32
from models_library.basic_types import VersionStr
43
from models_library.products import ProductName
@@ -10,10 +9,11 @@
109
)
1110
from models_library.rpc_pagination import PageLimitInt
1211
from models_library.services_enums import ServiceType
13-
from models_library.services_history import ServiceRelease
1412
from models_library.users import UserID
15-
from packaging.version import Version
1613
from pydantic import NonNegativeInt, PositiveInt
14+
from simcore_service_api_server.exceptions.backend_errors import (
15+
ProgramOrSolverOrStudyNotFoundError,
16+
)
1717
from simcore_service_api_server.exceptions.custom_errors import (
1818
SolverServiceListJobsFiltersError,
1919
)
@@ -69,23 +69,20 @@ async def get_latest_release(
6969
user_id: int,
7070
solver_key: SolverKeyId,
7171
) -> Solver:
72-
service_releases: list[ServiceRelease] = []
73-
for page_params in iter_pagination_params(limit=DEFAULT_PAGINATION_LIMIT):
74-
releases, page_meta = await self._catalog_service.list_release_history(
75-
user_id=user_id,
76-
service_key=solver_key,
77-
product_name=product_name,
78-
offset=page_params.offset,
79-
limit=page_params.limit,
80-
)
81-
page_params.total_number_of_items = page_meta.total
82-
service_releases.extend(releases)
72+
releases, _ = await self._catalog_service.list_release_history_latest_first(
73+
user_id=user_id,
74+
service_key=solver_key,
75+
product_name=product_name,
76+
offset=0,
77+
limit=1,
78+
)
8379

84-
release = sorted(service_releases, key=lambda s: Version(s.version))[-1]
80+
if len(releases) == 0:
81+
raise ProgramOrSolverOrStudyNotFoundError(name=solver_key, version="latest")
8582
service = await self._catalog_service.get(
8683
user_id=user_id,
8784
name=solver_key,
88-
version=release.version,
85+
version=releases[0].version,
8986
product_name=product_name,
9087
)
9188

@@ -171,12 +168,14 @@ async def solver_release_history(
171168
limit: PositiveInt,
172169
) -> tuple[list[Solver], PageMetaInfoLimitOffset]:
173170

174-
releases, page_meta = await self._catalog_service.list_release_history(
175-
user_id=user_id,
176-
service_key=solver_key,
177-
product_name=product_name,
178-
offset=offset,
179-
limit=limit,
171+
releases, page_meta = (
172+
await self._catalog_service.list_release_history_latest_first(
173+
user_id=user_id,
174+
service_key=solver_key,
175+
product_name=product_name,
176+
offset=offset,
177+
limit=limit,
178+
)
180179
)
181180

182181
service_instance = await self._catalog_service.get(

services/api-server/src/simcore_service_api_server/services_rpc/catalog.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ async def list_latest_releases(
7171
)
7272
return page.data, meta
7373

74-
async def list_release_history(
74+
async def list_release_history_latest_first(
7575
self,
7676
*,
7777
product_name: ProductName,
@@ -81,7 +81,7 @@ async def list_release_history(
8181
limit: PageLimitInt = DEFAULT_NUMBER_OF_ITEMS_PER_PAGE,
8282
) -> tuple[list[ServiceRelease], PageMetaInfoLimitOffset]:
8383

84-
page = await catalog_rpc.list_my_service_history_paginated(
84+
page = await catalog_rpc.list_my_service_history_latest_first(
8585
self._client,
8686
product_name=product_name,
8787
user_id=user_id,

services/api-server/tests/unit/conftest.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -491,9 +491,16 @@ def mocked_webserver_rpc_api(
491491
}
492492

493493

494+
@pytest.fixture
495+
def catalog_rpc_side_effects(request) -> Any:
496+
if "param" in dir(request) and request.param is not None:
497+
return request.param
498+
return CatalogRpcSideEffects()
499+
500+
494501
@pytest.fixture
495502
def mocked_catalog_rpc_api(
496-
mocked_app_dependencies: None, mocker: MockerFixture
503+
mocked_app_dependencies: None, mocker: MockerFixture, catalog_rpc_side_effects: Any
497504
) -> dict[str, MockType]:
498505
"""
499506
Mocks the catalog's simcore service RPC API for testing purposes.
@@ -502,37 +509,36 @@ def mocked_catalog_rpc_api(
502509
services as catalog_rpc, # keep import here
503510
)
504511

505-
side_effects = CatalogRpcSideEffects()
506512
return {
507513
"list_services_paginated": mocker.patch.object(
508514
catalog_rpc,
509515
"list_services_paginated",
510516
autospec=True,
511-
side_effect=side_effects.list_services_paginated,
517+
side_effect=catalog_rpc_side_effects.list_services_paginated,
512518
),
513519
"get_service": mocker.patch.object(
514520
catalog_rpc,
515521
"get_service",
516522
autospec=True,
517-
side_effect=side_effects.get_service,
523+
side_effect=catalog_rpc_side_effects.get_service,
518524
),
519525
"update_service": mocker.patch.object(
520526
catalog_rpc,
521527
"update_service",
522528
autospec=True,
523-
side_effect=side_effects.update_service,
529+
side_effect=catalog_rpc_side_effects.update_service,
524530
),
525-
"list_my_service_history_paginated": mocker.patch.object(
531+
"list_my_service_history_latest_first": mocker.patch.object(
526532
catalog_rpc,
527-
"list_my_service_history_paginated",
533+
"list_my_service_history_latest_first",
528534
autospec=True,
529-
side_effect=side_effects.list_my_service_history_paginated,
535+
side_effect=catalog_rpc_side_effects.list_my_service_history_latest_first,
530536
),
531537
"get_service_ports": mocker.patch.object(
532538
catalog_rpc,
533539
"get_service_ports",
534540
autospec=True,
535-
side_effect=side_effects.get_service_ports,
541+
side_effect=catalog_rpc_side_effects.get_service_ports,
536542
),
537543
}
538544

services/api-server/tests/unit/test_api_programs.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from models_library.api_schemas_storage.storage_schemas import FileUploadSchema
1515
from models_library.users import UserID
1616
from pytest_mock import MockerFixture, MockType
17+
from pytest_simcore.helpers.catalog_rpc_server import ZeroListingCatalogRpcSideEffects
1718
from pytest_simcore.helpers.faker_factories import DEFAULT_FAKER
1819
from pytest_simcore.helpers.httpx_calls_capture_models import (
1920
CreateRespxMockCallback,
@@ -168,3 +169,19 @@ async def test_list_program_history(
168169
f"/{API_VTAG}/programs/{program_key}/releases", auth=auth
169170
)
170171
assert response.status_code == status.HTTP_200_OK
172+
173+
174+
@pytest.mark.parametrize(
175+
"catalog_rpc_side_effects", [ZeroListingCatalogRpcSideEffects()], indirect=True
176+
)
177+
async def test_list_program_history_no_program(
178+
auth: httpx.BasicAuth,
179+
client: AsyncClient,
180+
mocked_catalog_rpc_api: dict[str, MockType],
181+
):
182+
program_key = "simcore/services/dynamic/my_program"
183+
# Arrange
184+
response = await client.get(
185+
f"/{API_VTAG}/programs/{program_key}/releases", auth=auth
186+
)
187+
assert response.status_code == status.HTTP_200_OK

services/api-server/tests/unit/test_api_solvers.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from httpx import AsyncClient
1313
from models_library.api_schemas_api_server.pricing_plans import ServicePricingPlanGet
1414
from pytest_mock import MockType
15+
from pytest_simcore.helpers.catalog_rpc_server import ZeroListingCatalogRpcSideEffects
1516
from pytest_simcore.helpers.httpx_calls_capture_models import CreateRespxMockCallback
1617
from simcore_service_api_server._meta import API_VTAG
1718

@@ -75,3 +76,28 @@ async def test_get_latest_solver_release(
7576
if response.status_code == status.HTTP_200_OK:
7677
assert "id" in response.json()
7778
assert response.json()["id"] == solver_key
79+
80+
81+
@pytest.mark.parametrize(
82+
"catalog_rpc_side_effects",
83+
[ZeroListingCatalogRpcSideEffects()],
84+
indirect=True,
85+
)
86+
@pytest.mark.parametrize(
87+
"solver_key,expected_status_code",
88+
[
89+
("simcore/services/comp/valid_solver", status.HTTP_404_NOT_FOUND),
90+
],
91+
)
92+
async def test_get_latest_solver_release_zero_releases(
93+
client: AsyncClient,
94+
mocked_catalog_rpc_api,
95+
auth: httpx.BasicAuth,
96+
solver_key: str,
97+
expected_status_code: int,
98+
):
99+
response = await client.get(
100+
f"{API_VTAG}/solvers/{solver_key}/latest",
101+
auth=auth,
102+
)
103+
assert response.status_code == expected_status_code

services/api-server/tests/unit/test_services_catalog.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ async def test_catalog_service_read_solvers(
4949

5050
# Step 2: Select one release and list solver releases
5151
selected_solver = solver_releases_page[0]
52-
releases, meta = await catalog_service.list_release_history(
52+
releases, meta = await catalog_service.list_release_history_latest_first(
5353
product_name=product_name,
5454
user_id=user_id,
5555
service_key=selected_solver.id,
@@ -85,6 +85,6 @@ async def test_catalog_service_read_solvers(
8585

8686
# checks calls to rpc
8787
mocked_catalog_rpc_api["list_services_paginated"].assert_called_once()
88-
mocked_catalog_rpc_api["list_my_service_history_paginated"].assert_called_once()
88+
mocked_catalog_rpc_api["list_my_service_history_latest_first"].assert_called_once()
8989
mocked_catalog_rpc_api["get_service"].assert_called_once()
9090
mocked_catalog_rpc_api["get_service_ports"].assert_called_once()

services/catalog/src/simcore_service_catalog/api/rpc/_services.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ async def batch_get_my_services(
242242
@router.expose(reraise_if_error_type=(ValidationError,))
243243
@log_decorator(_logger, level=logging.DEBUG)
244244
@validate_call(config={"arbitrary_types_allowed": True})
245-
async def list_my_service_history_paginated(
245+
async def list_my_service_history_latest_first(
246246
app: FastAPI,
247247
*,
248248
product_name: ProductName,
@@ -252,6 +252,7 @@ async def list_my_service_history_paginated(
252252
offset: PageOffsetInt = 0,
253253
filters: ServiceListFilters | None = None,
254254
) -> PageRpcServiceRelease:
255+
"""sorts service releases by version (latest first)"""
255256
assert app.state.engine # nosec
256257

257258
total_count, items = await catalog_services.list_user_service_release_history(

0 commit comments

Comments
 (0)