@@ -900,3 +900,262 @@ async def test_list_services_from_published_templates_with_invalid_service(
900900 "service {'key': 'simcore/services/dynamic/invalid-service', 'version': 'invalid'} could not be validated"
901901 in caplog .text
902902 )
903+
904+
905+ async def test_compare_list_all_and_latest_services (
906+ target_product : ProductName ,
907+ create_fake_service_data : CreateFakeServiceDataCallable ,
908+ services_db_tables_injector : Callable ,
909+ services_repo : ServicesRepository ,
910+ user_id : UserID ,
911+ ):
912+ # Setup: Create multiple versions of the same service and a few distinct services
913+ service_data : list [tuple ] = []
914+
915+ # Service 1 with multiple versions
916+ service_key_1 = "simcore/services/dynamic/multi-version"
917+ service_versions_1 = ["1.0.0" , "1.1.0" , "2.0.0" ]
918+ service_data .extend (
919+ [
920+ create_fake_service_data (
921+ service_key_1 ,
922+ version_ ,
923+ team_access = None ,
924+ everyone_access = None ,
925+ product = target_product ,
926+ )
927+ for version_ in service_versions_1
928+ ]
929+ )
930+
931+ # Service 2 with single version
932+ service_key_2 = "simcore/services/dynamic/single-version"
933+ service_data .append (
934+ create_fake_service_data (
935+ service_key_2 ,
936+ "1.0.0" ,
937+ team_access = None ,
938+ everyone_access = None ,
939+ product = target_product ,
940+ )
941+ )
942+
943+ # Service 3 with computational type
944+ service_key_3 = "simcore/services/comp/computational-service"
945+ service_versions_3 = ["0.5.0" , "1.0.0" ]
946+ service_data .extend (
947+ [
948+ create_fake_service_data (
949+ service_key_3 ,
950+ version_ ,
951+ team_access = None ,
952+ everyone_access = None ,
953+ product = target_product ,
954+ )
955+ for version_ in service_versions_3
956+ ]
957+ )
958+
959+ await services_db_tables_injector (service_data )
960+
961+ # Test 1: Compare all services vs latest without filters
962+ total_all , all_services = await services_repo .list_all_services (
963+ product_name = target_product , user_id = user_id
964+ )
965+ total_latest , latest_services = await services_repo .list_latest_services (
966+ product_name = target_product , user_id = user_id
967+ )
968+
969+ # Verify counts
970+ # All services should be 6 (3 versions of service 1, 1 of service 2, 2 of service 3)
971+ assert total_all == 6
972+ # Latest services should be 3 (one latest for each distinct service key)
973+ assert total_latest == 3
974+
975+ # Verify latest services are contained in all services
976+ latest_key_versions = {(s .key , s .version ) for s in latest_services }
977+ all_key_versions = {(s .key , s .version ) for s in all_services }
978+ assert latest_key_versions .issubset (all_key_versions )
979+
980+ # Verify latest versions are correct
981+ latest_versions_by_key = {s .key : s .version for s in latest_services }
982+ assert latest_versions_by_key [service_key_1 ] == "2.0.0"
983+ assert latest_versions_by_key [service_key_2 ] == "1.0.0"
984+ assert latest_versions_by_key [service_key_3 ] == "1.0.0"
985+
986+ # Test 2: Using service_type filter to get only dynamic services
987+ filters = ServiceDBFilters (service_type = ServiceType .DYNAMIC )
988+
989+ total_all_filtered , all_services_filtered = await services_repo .list_all_services (
990+ product_name = target_product , user_id = user_id , filters = filters
991+ )
992+ total_latest_filtered , latest_services_filtered = (
993+ await services_repo .list_latest_services (
994+ product_name = target_product , user_id = user_id , filters = filters
995+ )
996+ )
997+
998+ # Verify counts with filter
999+ assert total_all_filtered == 4 # 3 versions of service 1, 1 of service 2
1000+ assert total_latest_filtered == 2 # 1 latest each for service 1 and 2
1001+
1002+ # Verify service types are correct after filtering
1003+ assert all (
1004+ s .key .startswith (DYNAMIC_SERVICE_KEY_PREFIX ) for s in all_services_filtered
1005+ )
1006+ assert all (
1007+ s .key .startswith (DYNAMIC_SERVICE_KEY_PREFIX ) for s in latest_services_filtered
1008+ )
1009+
1010+ # Verify latest versions are correct
1011+ latest_versions_by_key = {s .key : s .version for s in latest_services_filtered }
1012+ assert latest_versions_by_key [service_key_1 ] == "2.0.0"
1013+ assert latest_versions_by_key [service_key_2 ] == "1.0.0"
1014+ assert service_key_3 not in latest_versions_by_key # Filtered out
1015+
1016+ # Test 3: Using service_key_pattern to find specific service
1017+ filters = ServiceDBFilters (service_key_pattern = "*/multi-*" )
1018+
1019+ total_all_filtered , all_services_filtered = await services_repo .list_all_services (
1020+ product_name = target_product , user_id = user_id , filters = filters
1021+ )
1022+ total_latest_filtered , latest_services_filtered = (
1023+ await services_repo .list_latest_services (
1024+ product_name = target_product , user_id = user_id , filters = filters
1025+ )
1026+ )
1027+
1028+ # Verify counts with key pattern filter
1029+ assert total_all_filtered == 3 # All 3 versions of service 1
1030+ assert total_latest_filtered == 1 # Only latest version of service 1
1031+
1032+ # Verify service key pattern is matched
1033+ assert all (s .key == service_key_1 for s in all_services_filtered )
1034+ assert all (s .key == service_key_1 for s in latest_services_filtered )
1035+
1036+ # Test 4: Pagination
1037+ # Get first page (limit=2)
1038+ total_all_page1 , all_services_page1 = await services_repo .list_all_services (
1039+ product_name = target_product ,
1040+ user_id = user_id ,
1041+ pagination_limit = 2 ,
1042+ pagination_offset = 0 ,
1043+ )
1044+
1045+ # Get second page (limit=2, offset=2)
1046+ total_all_page2 , all_services_page2 = await services_repo .list_all_services (
1047+ product_name = target_product ,
1048+ user_id = user_id ,
1049+ pagination_limit = 2 ,
1050+ pagination_offset = 2 ,
1051+ )
1052+
1053+ # Verify pagination
1054+ assert total_all_page1 == 6 # Total count should still be total
1055+ assert len (all_services_page1 ) == 2 # But only 2 items on first page
1056+ assert len (all_services_page2 ) == 2 # And 2 items on second page
1057+
1058+ # Ensure pages have different items
1059+ page1_key_versions = {(s .key , s .version ) for s in all_services_page1 }
1060+ page2_key_versions = {(s .key , s .version ) for s in all_services_page2 }
1061+ assert not page1_key_versions .intersection (page2_key_versions )
1062+
1063+
1064+ async def test_list_all_services_empty_database (
1065+ target_product : ProductName ,
1066+ services_repo : ServicesRepository ,
1067+ user_id : UserID ,
1068+ ):
1069+ """Test list_all_services and list_latest_services with an empty database."""
1070+ # Test with empty database
1071+ total_all , all_services = await services_repo .list_all_services (
1072+ product_name = target_product , user_id = user_id
1073+ )
1074+ total_latest , latest_services = await services_repo .list_latest_services (
1075+ product_name = target_product , user_id = user_id
1076+ )
1077+
1078+ assert total_all == 0
1079+ assert len (all_services ) == 0
1080+ assert total_latest == 0
1081+ assert len (latest_services ) == 0
1082+
1083+
1084+ async def test_list_all_services_deprecated_versions (
1085+ target_product : ProductName ,
1086+ create_fake_service_data : CreateFakeServiceDataCallable ,
1087+ services_db_tables_injector : Callable ,
1088+ services_repo : ServicesRepository ,
1089+ user_id : UserID ,
1090+ ):
1091+ """Test that list_all_services includes deprecated versions while list_latest_services ignores them."""
1092+ from datetime import datetime , timedelta
1093+
1094+ # Create a service with regular and deprecated versions
1095+ service_key = "simcore/services/dynamic/with-deprecated"
1096+ service_data = []
1097+
1098+ # Add regular version
1099+ service_data .append (
1100+ create_fake_service_data (
1101+ service_key ,
1102+ "1.0.0" ,
1103+ team_access = None ,
1104+ everyone_access = None ,
1105+ product = target_product ,
1106+ )
1107+ )
1108+
1109+ # Add deprecated version (with higher version number)
1110+ deprecated_service = create_fake_service_data (
1111+ service_key ,
1112+ "2.0.0" ,
1113+ team_access = None ,
1114+ everyone_access = None ,
1115+ product = target_product ,
1116+ )
1117+ # Set deprecated timestamp to yesterday
1118+ deprecated_service [0 ]["deprecated" ] = datetime .now () - timedelta (days = 1 )
1119+ service_data .append (deprecated_service )
1120+
1121+ # Add newer non-deprecated version
1122+ service_data .append (
1123+ create_fake_service_data (
1124+ service_key ,
1125+ "3.0.0" ,
1126+ team_access = None ,
1127+ everyone_access = None ,
1128+ product = target_product ,
1129+ )
1130+ )
1131+
1132+ await services_db_tables_injector (service_data )
1133+
1134+ # Get all services - should include both deprecated and non-deprecated
1135+ total_all , all_services = await services_repo .list_all_services (
1136+ product_name = target_product , user_id = user_id
1137+ )
1138+
1139+ # Get latest services - should only show latest non-deprecated
1140+ total_latest , latest_services = await services_repo .list_latest_services (
1141+ product_name = target_product , user_id = user_id
1142+ )
1143+
1144+ # Verify counts
1145+ assert total_all == 3 # All 3 versions
1146+
1147+ # Verify latest is the newest non-deprecated version
1148+ assert len (latest_services ) == 1
1149+ assert latest_services [0 ].key == service_key
1150+ assert latest_services [0 ].version == "3.0.0"
1151+
1152+ # Get versions from all services
1153+ versions = [s .version for s in all_services if s .key == service_key ]
1154+ assert sorted (versions ) == ["1.0.0" , "2.0.0" , "3.0.0" ]
1155+
1156+ # Verify the deprecated status is correctly set
1157+ for service in all_services :
1158+ if service .key == service_key and service .version == "2.0.0" :
1159+ assert service .deprecated is not None
1160+ else :
1161+ assert service .deprecated is None
0 commit comments