33# pylint: disable=unused-variable
44# pylint: disable=too-many-arguments
55
6+ import logging
67import random
78from collections import Counter
89from collections .abc import Callable
10+ from contextlib import AsyncExitStack
911from dataclasses import dataclass , field
12+ from typing import Any
1013
1114import pytest
1215from models_library .products import ProductName
1316from models_library .users import UserID
1417from packaging import version
1518from pydantic import EmailStr , HttpUrl , TypeAdapter
19+ from pytest_simcore .helpers .faker_factories import random_project
20+ from pytest_simcore .helpers .postgres_tools import insert_and_get_row_lifespan
21+ from simcore_postgres_database .models .projects import ProjectType , projects
1622from simcore_service_catalog .models .services_db import (
1723 ServiceAccessRightsAtDB ,
1824 ServiceMetaDataDBCreate ,
1925 ServiceMetaDataDBGet ,
2026 ServiceMetaDataDBPatch ,
2127)
28+ from simcore_service_catalog .repository .projects import ProjectsRepository
2229from simcore_service_catalog .repository .services import ServicesRepository
2330from simcore_service_catalog .utils .versioning import is_patch_release
2431from sqlalchemy .ext .asyncio import AsyncEngine
@@ -36,6 +43,11 @@ def services_repo(sqlalchemy_async_engine: AsyncEngine) -> ServicesRepository:
3643 return ServicesRepository (sqlalchemy_async_engine )
3744
3845
46+ @pytest .fixture
47+ def projects_repo (sqlalchemy_async_engine : AsyncEngine ) -> ProjectsRepository :
48+ return ProjectsRepository (sqlalchemy_async_engine )
49+
50+
3951@dataclass
4052class FakeCatalogInfo :
4153 jupyter_service_key : str = "simcore/services/dynamic/jupyterlab"
@@ -549,3 +561,109 @@ async def test_get_service_history_page(
549561
550562 # compare paginated results with the corresponding slice of the full history
551563 assert paginated_history == history [offset : offset + limit ]
564+
565+
566+ async def test_list_services_from_published_templates (
567+ user : dict [str , Any ],
568+ projects_repo : ProjectsRepository ,
569+ sqlalchemy_async_engine : AsyncEngine ,
570+ ):
571+ # Setup: Use AsyncExitStack to manage multiple insert_and_get_row_lifespan
572+ async with AsyncExitStack () as stack :
573+ await stack .enter_async_context (
574+ insert_and_get_row_lifespan (
575+ sqlalchemy_async_engine ,
576+ table = projects ,
577+ values = random_project (
578+ uuid = "template-1" ,
579+ type = ProjectType .TEMPLATE ,
580+ published = True ,
581+ prj_owner = user ["id" ],
582+ workbench = {
583+ "node-1" : {
584+ "key" : "simcore/services/dynamic/jupyterlab" ,
585+ "version" : "1.0.0" ,
586+ },
587+ "node-2" : {
588+ "key" : "simcore/services/frontend/file-picker" ,
589+ "version" : "1.0.0" ,
590+ },
591+ },
592+ ),
593+ pk_col = projects .c .uuid ,
594+ pk_value = "template-1" ,
595+ )
596+ )
597+ await stack .enter_async_context (
598+ insert_and_get_row_lifespan (
599+ sqlalchemy_async_engine ,
600+ table = projects ,
601+ values = random_project (
602+ uuid = "template-2" ,
603+ type = ProjectType .TEMPLATE ,
604+ published = False ,
605+ prj_owner = user ["id" ],
606+ workbench = {
607+ "node-1" : {
608+ "key" : "simcore/services/dynamic/some-service" ,
609+ "version" : "2.0.0" ,
610+ },
611+ },
612+ ),
613+ pk_col = projects .c .uuid ,
614+ pk_value = "template-2" ,
615+ )
616+ )
617+
618+ # Act: Call the method
619+ services = await projects_repo .list_services_from_published_templates ()
620+
621+ # Assert: Validate the results
622+ assert len (services ) == 1
623+ assert services [0 ].key == "simcore/services/dynamic/jupyterlab"
624+ assert services [0 ].version == "1.0.0"
625+
626+
627+ async def test_list_services_from_published_templates_with_invalid_service (
628+ user : dict [str , Any ],
629+ projects_repo : ProjectsRepository ,
630+ sqlalchemy_async_engine : AsyncEngine ,
631+ caplog ,
632+ ):
633+ # Setup: Use AsyncExitStack to manage insert_and_get_row_lifespan
634+ async with AsyncExitStack () as stack :
635+ await stack .enter_async_context (
636+ insert_and_get_row_lifespan (
637+ sqlalchemy_async_engine ,
638+ table = projects ,
639+ values = random_project (
640+ uuid = "template-1" ,
641+ type = ProjectType .TEMPLATE ,
642+ published = True ,
643+ prj_owner = user ["id" ],
644+ workbench = {
645+ "node-1" : {
646+ "key" : "simcore/services/frontend/file-picker" ,
647+ "version" : "1.0.0" ,
648+ },
649+ "node-2" : {
650+ "key" : "simcore/services/dynamic/invalid-service" ,
651+ "version" : "invalid" ,
652+ },
653+ },
654+ ),
655+ pk_col = projects .c .uuid ,
656+ pk_value = "template-1" ,
657+ )
658+ )
659+
660+ # Act: Call the method and capture logs
661+ with caplog .at_level (logging .WARNING ):
662+ services = await projects_repo .list_services_from_published_templates ()
663+
664+ # Assert: Validate the results
665+ assert len (services ) == 0 # No valid services should be returned
666+ assert (
667+ "service {'key': 'simcore/services/dynamic/invalid-service', 'version': 'invalid'} could not be validated"
668+ in caplog .text
669+ )
0 commit comments