33# pylint: disable=unused-variable
44
55from collections .abc import Callable
6- from typing import Any
6+ from typing import Any , Protocol
77
88import pytest
99import simcore_service_catalog .service .access_rights
@@ -43,7 +43,9 @@ def new_service_metadata_published(user: dict[str, Any]) -> ServiceMetaDataPubli
4343 Author (name = user ["name" ], email = user ["email" ], affiliation = None )
4444 ]
4545 metadata .version = TypeAdapter (ServiceVersion ).validate_python ("1.0.11" )
46- metadata .icon = None # Remove icon to test inheritance
46+ # Set all inheritable fields to None initially
47+ metadata .icon = None
48+ metadata .thumbnail = None
4749 return metadata
4850
4951
@@ -63,6 +65,47 @@ def app_with_repo(
6365 return app , services_repo
6466
6567
68+ class CreateLatetReleaseCallable (Protocol ):
69+ """Callable to create a latest release with specified metadata fields."""
70+
71+ async def __call__ (self , metadata_fields : dict [str , Any ]) -> dict [str , Any ]: ...
72+
73+
74+ @pytest .fixture
75+ def create_latest_release (
76+ create_fake_service_data : CreateFakeServiceDataCallable ,
77+ new_service_metadata_published : ServiceMetaDataPublished ,
78+ target_product : ProductName ,
79+ services_db_tables_injector : Callable ,
80+ ) -> CreateLatetReleaseCallable :
81+ """Creates a latest release with specified metadata fields."""
82+
83+ from packaging .version import Version
84+
85+ new_version = Version (new_service_metadata_published .version )
86+
87+ async def _create (metadata_fields : dict [str , Any ]) -> dict [str , Any ]:
88+ latest_release_service , * latest_release_service_access_rights = (
89+ create_fake_service_data (
90+ new_service_metadata_published .key ,
91+ f"{ new_version .major } .{ new_version .minor } .{ new_version .micro - 1 } " ,
92+ team_access = "x" ,
93+ everyone_access = None ,
94+ product = target_product ,
95+ )
96+ )
97+
98+ # Update with provided metadata fields
99+ for field , value in metadata_fields .items ():
100+ latest_release_service [field ] = value
101+
102+ latest_release = (latest_release_service , * latest_release_service_access_rights )
103+ await services_db_tables_injector ([latest_release ])
104+ return latest_release_service
105+
106+ return _create
107+
108+
66109def test_reduce_access_rights ():
67110 sample = ServiceAccessRightsDB .model_validate (
68111 {
@@ -118,14 +161,105 @@ def test_reduce_access_rights():
118161 }
119162
120163
164+ async def test_metadata_inheritance_variations (
165+ new_service_metadata_published : ServiceMetaDataPublished ,
166+ app_with_repo : tuple [FastAPI , ServicesRepository ],
167+ create_latest_release : CreateLatetReleaseCallable ,
168+ ):
169+ """Test different variations of metadata inheritance with complete previous release."""
170+ app , services_repo = app_with_repo
171+
172+ # Set up a previous release with all metadata fields
173+ latest_release_service = await create_latest_release (
174+ {
175+ "icon" : "https://foo/previous_icon.svg" ,
176+ "thumbnail" : "https://foo/previous_thumbnail.jpg" ,
177+ }
178+ )
179+
180+ # Case 1: All fields missing in new service - only icon and thumbnail should be inherited
181+ new_service = new_service_metadata_published .model_copy (deep = True )
182+ inherited_data = await inherit_from_latest_compatible_release (
183+ services_repo , service_metadata = new_service
184+ )
185+
186+ assert inherited_data ["metadata_updates" ] == {
187+ "icon" : latest_release_service ["icon" ],
188+ "thumbnail" : latest_release_service ["thumbnail" ],
189+ }
190+
191+ # Case 2: Some fields present, others missing
192+ new_service = new_service_metadata_published .model_copy (
193+ deep = True ,
194+ update = {
195+ "thumbnail" : None , # Missing field
196+ "icon" : "https://foo/new_icon.svg" ,
197+ },
198+ )
199+
200+ inherited_data = await inherit_from_latest_compatible_release (
201+ services_repo , service_metadata = new_service
202+ )
203+
204+ # Only thumbnail should be inherited
205+ assert "icon" not in inherited_data ["metadata_updates" ]
206+ assert inherited_data ["metadata_updates" ] == {
207+ "thumbnail" : latest_release_service ["thumbnail" ],
208+ }
209+
210+ # Case 3: All fields present in new service - nothing should be inherited
211+ new_service = new_service_metadata_published .model_copy (
212+ deep = True ,
213+ update = {
214+ "icon" : "https://foo/new_icon.svg" ,
215+ "thumbnail" : "https://foo/new_thumbnail.jpg" ,
216+ },
217+ )
218+
219+ inherited_data = await inherit_from_latest_compatible_release (
220+ services_repo , service_metadata = new_service
221+ )
222+
223+ # No metadata should be inherited
224+ assert inherited_data ["metadata_updates" ] == {}
225+
226+
227+ async def test_metadata_inheritance_with_incomplete_previous_release (
228+ new_service_metadata_published : ServiceMetaDataPublished ,
229+ app_with_repo : tuple [FastAPI , ServicesRepository ],
230+ create_latest_release : CreateLatetReleaseCallable ,
231+ ):
232+ """Test metadata inheritance when previous release has incomplete metadata fields."""
233+ app , services_repo = app_with_repo
234+
235+ # Case 4: Previous release missing some fields
236+ latest_release_service = await create_latest_release (
237+ {
238+ "icon" : "https://foo/previous_icon.svg" ,
239+ "thumbnail" : "https://foo/previous_thumbnail.jpg" ,
240+ }
241+ )
242+
243+ new_service = new_service_metadata_published .model_copy (deep = True )
244+ inherited_data = await inherit_from_latest_compatible_release (
245+ services_repo , service_metadata = new_service
246+ )
247+
248+ # Only icon and thumbnail should be inherited
249+ assert inherited_data ["metadata_updates" ] == {
250+ "icon" : latest_release_service ["icon" ],
251+ "thumbnail" : latest_release_service ["thumbnail" ],
252+ }
253+ assert "description" not in inherited_data ["metadata_updates" ]
254+
255+
121256async def test_service_upgrade_metadata_inheritance_old_service (
122257 user_groups_ids : list [GroupID ],
123258 target_product : ProductName ,
124- services_db_tables_injector : Callable ,
125- create_fake_service_data : CreateFakeServiceDataCallable ,
126259 mocker : MockerFixture ,
127260 new_service_metadata_published : ServiceMetaDataPublished ,
128261 app_with_repo : tuple [FastAPI , ServicesRepository ],
262+ create_latest_release : CreateLatetReleaseCallable ,
129263):
130264 """Test inheritance behavior when the service is considered old"""
131265 everyone_gid , user_gid , team_gid = user_groups_ids
@@ -138,22 +272,15 @@ async def test_service_upgrade_metadata_inheritance_old_service(
138272 return_value = True ,
139273 )
140274
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- )
275+ # Create latest-release service for testing inheritance with all metadata fields
276+ latest_release_service = await create_latest_release (
277+ {
278+ "icon" : "https://foo/previous_icon.svg" ,
279+ "description" : "Previous description" , # This won't be inherited
280+ "thumbnail" : "https://foo/previous_thumbnail.jpg" ,
281+ }
150282 )
151283
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-
157284 # DEFAULT policies for old service
158285 owner_gid , service_access_rights = (
159286 await evaluate_default_service_ownership_and_rights (
@@ -185,10 +312,13 @@ async def test_service_upgrade_metadata_inheritance_old_service(
185312 services_repo , service_metadata = new_service_metadata_published
186313 )
187314
188- # Check metadata inheritance
315+ # Check metadata inheritance - only icon and thumbnail should be inherited
189316 inherited_metadata = inherited_data ["metadata_updates" ]
190- assert "icon" in inherited_metadata
191- assert inherited_metadata ["icon" ] == latest_release_service ["icon" ]
317+ assert "description" not in inherited_metadata
318+ assert inherited_metadata == {
319+ "icon" : latest_release_service ["icon" ],
320+ "thumbnail" : latest_release_service ["thumbnail" ],
321+ }
192322
193323
194324async def test_service_upgrade_metadata_inheritance_new_service_multi_product (
0 commit comments