55from collections .abc import Callable
66from typing import Any
77
8+ import pytest
89import simcore_service_catalog .service .access_rights
910from fastapi import FastAPI
1011from models_library .groups import GroupID
3132]
3233
3334
35+ @pytest .fixture
36+ def new_service_metadata_published (user : dict [str , Any ]) -> ServiceMetaDataPublished :
37+ MOST_UPDATED_EXAMPLE = - 1
38+ metadata = ServiceMetaDataPublished .model_validate (
39+ ServiceMetaDataPublished .model_json_schema ()["examples" ][MOST_UPDATED_EXAMPLE ]
40+ )
41+ metadata .contact = user ["email" ]
42+ metadata .authors = [
43+ Author (name = user ["name" ], email = user ["email" ], affiliation = None )
44+ ]
45+ metadata .version = TypeAdapter (ServiceVersion ).validate_python ("1.0.11" )
46+ metadata .icon = None # Remove icon to test inheritance
47+ return metadata
48+
49+
50+ @pytest .fixture
51+ def app_with_repo (
52+ sqlalchemy_async_engine : AsyncEngine ,
53+ target_product : ProductName ,
54+ mocker : MockerFixture ,
55+ ) -> tuple [FastAPI , ServicesRepository ]:
56+ """Creates FastAPI app with services repository setup."""
57+ app = FastAPI ()
58+ app .state .engine = sqlalchemy_async_engine
59+ app .state .settings = mocker .Mock ()
60+ app .state .default_product_name = target_product
61+
62+ services_repo = ServicesRepository (app .state .engine )
63+ return app , services_repo
64+
65+
3466def test_reduce_access_rights ():
3567 sample = ServiceAccessRightsDB .model_validate (
3668 {
@@ -86,43 +118,104 @@ def test_reduce_access_rights():
86118 }
87119
88120
89- async def test_service_upgrade_metadata_inheritance (
90- sqlalchemy_async_engine : AsyncEngine ,
91- user : dict [str , Any ],
121+ async def test_service_upgrade_metadata_inheritance_old_service (
92122 user_groups_ids : list [GroupID ],
93123 target_product : ProductName ,
94- other_product : ProductName ,
95124 services_db_tables_injector : Callable ,
96125 create_fake_service_data : CreateFakeServiceDataCallable ,
97126 mocker : MockerFixture ,
127+ new_service_metadata_published : ServiceMetaDataPublished ,
128+ app_with_repo : tuple [FastAPI , ServicesRepository ],
98129):
130+ """Test inheritance behavior when the service is considered old"""
99131 everyone_gid , user_gid , team_gid = user_groups_ids
132+ app , services_repo = app_with_repo
100133
101- assert user_gid == user ["primary_gid" ]
102-
103- # Avoids calls to director API
134+ # Mock to make the service appear as old
104135 mocker .patch .object (
105136 simcore_service_catalog .service .access_rights ,
106137 "_is_old_service" ,
107- return_value = False ,
138+ return_value = True ,
108139 )
109140
110- # SETUP ---
111- MOST_UPDATED_EXAMPLE = - 1
112- new_service_metadata = ServiceMetaDataPublished .model_validate (
113- ServiceMetaDataPublished .model_json_schema ()["examples" ][MOST_UPDATED_EXAMPLE ]
141+ # Create latest-release service for testing inheritance
142+ latest_release_service , * latest_release_service_access_rights = (
143+ create_fake_service_data (
144+ new_service_metadata_published .key ,
145+ "1.0.10" ,
146+ team_access = "x" ,
147+ everyone_access = None ,
148+ product = target_product ,
149+ )
150+ )
151+
152+ latest_release_service ["icon" ] = "https://foo/previous_icon.svg"
153+ latest_release = (latest_release_service , * latest_release_service_access_rights )
154+
155+ await services_db_tables_injector ([latest_release ])
156+
157+ # DEFAULT policies for old service
158+ owner_gid , service_access_rights = (
159+ await evaluate_default_service_ownership_and_rights (
160+ app , service = new_service_metadata_published , product_name = target_product
161+ )
114162 )
115- new_service_metadata .contact = user ["email" ]
116- new_service_metadata .authors = [
117- Author (name = user ["name" ], email = user ["email" ], affiliation = None )
118- ]
119- new_service_metadata .version = TypeAdapter (ServiceVersion ).validate_python ("1.0.11" )
120- new_service_metadata .icon = None # Remove icon to test inheritance
121163
122- # latest-release
164+ # For old services, everyone should have access
165+ assert owner_gid == user_gid
166+ assert len (service_access_rights ) == 2 # Owner + everyone
167+ assert {a .gid for a in service_access_rights } == {owner_gid , everyone_gid }
168+
169+ # Check owner access
170+ owner_access = next (a for a in service_access_rights if a .gid == owner_gid )
171+ assert owner_access .model_dump (include = {"execute_access" , "write_access" }) == {
172+ "execute_access" : True ,
173+ "write_access" : True ,
174+ }
175+
176+ # Check everyone access
177+ everyone_access = next (a for a in service_access_rights if a .gid == everyone_gid )
178+ assert everyone_access .model_dump (include = {"execute_access" , "write_access" }) == {
179+ "execute_access" : True ,
180+ "write_access" : False , # Everyone can execute but not modify
181+ }
182+
183+ # Inheritance policy (both access rights and metadata)
184+ inherited_data = await inherit_from_latest_compatible_release (
185+ services_repo , service_metadata = new_service_metadata_published
186+ )
187+
188+ # Check metadata inheritance
189+ inherited_metadata = inherited_data ["metadata_updates" ]
190+ assert "icon" in inherited_metadata
191+ assert inherited_metadata ["icon" ] == latest_release_service ["icon" ]
192+
193+
194+ async def test_service_upgrade_metadata_inheritance_new_service_multi_product (
195+ user_groups_ids : list [GroupID ],
196+ target_product : ProductName ,
197+ other_product : ProductName ,
198+ services_db_tables_injector : Callable ,
199+ create_fake_service_data : CreateFakeServiceDataCallable ,
200+ mocker : MockerFixture ,
201+ new_service_metadata_published : ServiceMetaDataPublished ,
202+ app_with_repo : tuple [FastAPI , ServicesRepository ],
203+ ):
204+ """Test inheritance behavior when the service is new and latest version exists in multiple products"""
205+ everyone_gid , user_gid , team_gid = user_groups_ids
206+ app , services_repo = app_with_repo
207+
208+ # Avoids calls to director API - service is new
209+ mocker .patch .object (
210+ simcore_service_catalog .service .access_rights ,
211+ "_is_old_service" ,
212+ return_value = False ,
213+ )
214+
215+ # Create latest-release service
123216 latest_release_service , * latest_release_service_access_rights = (
124217 create_fake_service_data (
125- new_service_metadata .key ,
218+ new_service_metadata_published .key ,
126219 "1.0.10" ,
127220 team_access = "x" ,
128221 everyone_access = None ,
@@ -136,7 +229,7 @@ async def test_service_upgrade_metadata_inheritance(
136229 # latest-release in other product
137230 _ , * latest_release_service_access_rights_in_other_product = (
138231 create_fake_service_data (
139- new_service_metadata .key ,
232+ new_service_metadata_published .key ,
140233 latest_release_service ["version" ],
141234 team_access = "x" ,
142235 everyone_access = None ,
@@ -149,51 +242,40 @@ async def test_service_upgrade_metadata_inheritance(
149242 * latest_release_service_access_rights_in_other_product , # <-- different product
150243 )
151244
152- # we have three versions of the service in the database for which the sorting matters: (1.0.11 should inherit from 1.0.10 not 1.0.9)
245+ # Setup multiple versions in database
153246 await services_db_tables_injector (
154247 [
155248 create_fake_service_data (
156- new_service_metadata .key ,
249+ new_service_metadata_published .key ,
157250 "1.0.1" ,
158251 team_access = None ,
159252 everyone_access = None ,
160253 product = target_product ,
161254 ),
162255 create_fake_service_data (
163- new_service_metadata .key ,
256+ new_service_metadata_published .key ,
164257 "1.0.9" ,
165258 team_access = None ,
166259 everyone_access = None ,
167260 product = target_product ,
168261 ),
169- # new release is a patch on released 1.0.X
170- # which were released in two different product
171262 latest_release ,
172263 latest_release_in_other_product ,
173264 ]
174265 )
175266
176- # ------------
177-
178- app = FastAPI ()
179- app .state .engine = sqlalchemy_async_engine
180- app .state .settings = mocker .Mock ()
181- app .state .default_product_name = target_product
182-
183- services_repo = ServicesRepository (app .state .engine )
184-
185267 # DEFAULT policies
186268 owner_gid , service_access_rights = (
187269 await evaluate_default_service_ownership_and_rights (
188- app , service = new_service_metadata , product_name = target_product
270+ app , service = new_service_metadata_published , product_name = target_product
189271 )
190272 )
191273 assert owner_gid == user_gid
192- assert len (service_access_rights ) == 1
274+ assert len (service_access_rights ) == 1 # Only owner for new service
193275 assert {a .gid for a in service_access_rights } == {owner_gid }
194276 assert service_access_rights [0 ].model_dump () == {
195- "key" : new_service_metadata .key ,
196- "version" : new_service_metadata .version ,
277+ "key" : new_service_metadata_published .key ,
278+ "version" : new_service_metadata_published .version ,
197279 "gid" : user_gid ,
198280 "product_name" : target_product ,
199281 "execute_access" : True ,
@@ -203,7 +285,7 @@ async def test_service_upgrade_metadata_inheritance(
203285
204286 # Inheritance policy (both access rights and metadata)
205287 inherited_data = await inherit_from_latest_compatible_release (
206- services_repo , service_metadata = new_service_metadata
288+ services_repo , service_metadata = new_service_metadata_published
207289 )
208290
209291 # Check access rights inheritance
0 commit comments