@@ -346,3 +346,45 @@ def contribute_to_partial_replacement(instance_id, payload, extended_response_co
346346 assert studied_specimen_path not in server_state , (
347347 "second save should have sent a PATCH that cleared studiedSpecimen"
348348 )
349+
350+ def test_save_marks_raw_remote_data_stale (self , offline_kg_client , mocker ):
351+ """After a successful update/replace, `_raw_remote_data` must be set
352+ to None so it can't be silently out of sync with the cache and with
353+ the actual server state. exists() repopulates it on demand."""
354+ from fairgraph .openminds .core import DatasetVersion
355+
356+ server_state = {
357+ "@id" : self .uri ,
358+ "@type" : ["https://openminds.om-i.org/types/DatasetVersion" ],
359+ "http://schema.org/identifier" : [self .uri ],
360+ "https://core.kg.ebrains.eu/vocab/meta/space" : "myspace" ,
361+ "https://openminds.om-i.org/props/shortName" : "original" ,
362+ }
363+
364+ def get_by_id (stage , instance_id , extended_response_configuration ):
365+ return MockKGResponse (dict (server_state ))
366+
367+ def contribute_to_partial_replacement (instance_id , payload , extended_response_configuration ):
368+ for key , value in payload .items ():
369+ if value is None :
370+ server_state .pop (key , None )
371+ else :
372+ server_state [key ] = value
373+ return MockKGResponse (dict (server_state ))
374+
375+ mocker .patch .object (offline_kg_client ._kg_client .instances , "get_by_id" , get_by_id )
376+ mocker .patch .object (
377+ offline_kg_client ._kg_client .instances ,
378+ "contribute_to_partial_replacement" ,
379+ contribute_to_partial_replacement ,
380+ )
381+
382+ dsv = DatasetVersion .from_id (self .uuid , offline_kg_client , scope = "any" )
383+ assert dsv ._raw_remote_data is not None # populated by from_id
384+
385+ dsv .short_name = "updated"
386+ dsv .save (offline_kg_client , space = "myspace" , recursive = False )
387+
388+ assert dsv ._raw_remote_data is None , (
389+ "_raw_remote_data must be invalidated after a successful update"
390+ )
0 commit comments