@@ -47,6 +47,9 @@ func TestMemoryDataStore_Merge(t *testing.T) {
4747 excpectedChainMetadataCount int
4848 expectedContractMetadataCount int
4949 expectedError error
50+ expectedAddressDRK []string
51+ expectedChainMetaDRK []string
52+ expectedContractMetaDRK []string
5053 }{
5154 {
5255 name : "Merge single address" ,
@@ -72,7 +75,7 @@ func TestMemoryDataStore_Merge(t *testing.T) {
7275 expectedContractMetadataCount : 1 ,
7376 },
7477 {
75- name : "Merge deletions errors: delete address ref record that does not exist produces an error " ,
78+ name : "Merge propagate deletions: stages delete for address ref record not present in destination " ,
7679 setup : func () (* MemoryDataStore , * MemoryDataStore ) {
7780 dataStore1 := NewMemoryDataStore ()
7881 dataStore2 := NewMemoryDataStore ()
@@ -90,17 +93,18 @@ func TestMemoryDataStore_Merge(t *testing.T) {
9093 require .NoError (t , err , "Adding env metadata to dataStore1 should not fail" )
9194
9295 // dataStore2 stages a record for deletion that does not exist in dataStore1
93- require .NoError (t , dataStore2 .Addresses ().RemoteDelete (NewAddressRefKey (0 , "typeA" , semver .MustParse ("1.0.0" ), "q" )))
96+ stagedKey := NewAddressRefKey (0 , "typeA" , semver .MustParse ("1.0.0" ), "q" )
97+ require .NoError (t , dataStore2 .Addresses ().RemoteDelete (stagedKey ))
9498
9599 return dataStore1 , dataStore2
96100 },
97101 expectedAddrRefsCount : 1 ,
98102 excpectedChainMetadataCount : 1 ,
99103 expectedContractMetadataCount : 1 ,
100- expectedError : ErrAddressRefNotFound ,
104+ expectedAddressDRK : [] string { NewAddressRefKey ( 0 , "typeA" , semver . MustParse ( "1.0.0" ), "q" ). String ()} ,
101105 },
102106 {
103- name : "Merge deletions errors: delete chain metadata record that does not exist produces an error " ,
107+ name : "Merge propagate deletions: stages delete for chain metadata record not present in destination " ,
104108 setup : func () (* MemoryDataStore , * MemoryDataStore ) {
105109 dataStore1 := NewMemoryDataStore ()
106110 dataStore2 := NewMemoryDataStore ()
@@ -118,17 +122,18 @@ func TestMemoryDataStore_Merge(t *testing.T) {
118122 require .NoError (t , err , "Adding env metadata to dataStore2 should not fail" )
119123
120124 // dataStore2 stages a record for deletion that does not exist in dataStore1
121- require .NoError (t , dataStore2 .ChainMetadata ().RemoteDelete (NewChainMetadataKey (10 )))
125+ stagedKey := NewChainMetadataKey (10 )
126+ require .NoError (t , dataStore2 .ChainMetadata ().RemoteDelete (stagedKey ))
122127
123128 return dataStore1 , dataStore2
124129 },
125130 expectedAddrRefsCount : 1 ,
126131 excpectedChainMetadataCount : 1 ,
127132 expectedContractMetadataCount : 1 ,
128- expectedError : ErrChainMetadataNotFound ,
133+ expectedChainMetaDRK : [] string { NewChainMetadataKey ( 10 ). String ()} ,
129134 },
130135 {
131- name : "Merge deletions errors: delete contract metadata record that does not exist produces an error " ,
136+ name : "Merge propagate deletions: stages delete for contract metadata record not present in destination " ,
132137 setup : func () (* MemoryDataStore , * MemoryDataStore ) {
133138 dataStore1 := NewMemoryDataStore ()
134139 dataStore2 := NewMemoryDataStore ()
@@ -146,14 +151,15 @@ func TestMemoryDataStore_Merge(t *testing.T) {
146151 require .NoError (t , err , "Adding env metadata to dataStore2 should not fail" )
147152
148153 // dataStore2 stages a record for deletion that does not exist in dataStore1
149- require .NoError (t , dataStore2 .ContractMetadata ().RemoteDelete (NewContractMetadataKey (10 , "0x111" )))
154+ stagedKey := NewContractMetadataKey (10 , "0x111" )
155+ require .NoError (t , dataStore2 .ContractMetadata ().RemoteDelete (stagedKey ))
150156
151157 return dataStore1 , dataStore2
152158 },
153159 expectedAddrRefsCount : 1 ,
154160 excpectedChainMetadataCount : 1 ,
155161 expectedContractMetadataCount : 1 ,
156- expectedError : ErrContractMetadataNotFound ,
162+ expectedContractMetaDRK : [] string { NewContractMetadataKey ( 10 , "0x111" ). String ()} ,
157163 },
158164 {
159165 name : "Merge propagate deletions: deletes record from remote data store" ,
@@ -183,6 +189,9 @@ func TestMemoryDataStore_Merge(t *testing.T) {
183189 expectedAddrRefsCount : 0 ,
184190 excpectedChainMetadataCount : 0 ,
185191 expectedContractMetadataCount : 0 ,
192+ expectedAddressDRK : []string {addressRefRecord .Key ().String ()},
193+ expectedChainMetaDRK : []string {chainMetadataRecord .Key ().String ()},
194+ expectedContractMetaDRK : []string {contractMetadataRecord .Key ().String ()},
186195 },
187196 {
188197 name : "Match existing address with labels" ,
@@ -275,6 +284,115 @@ func TestMemoryDataStore_Merge(t *testing.T) {
275284
276285 _ , err = dataStore1 .EnvMetadata ().Get ()
277286 require .NoError (t , err , "Fetching env metadata from dataStore1 should not fail" )
287+
288+ // Verify staged deletes were propagated to DeletedRemoteKeys
289+ if len (tt .expectedAddressDRK ) > 0 {
290+ require .ElementsMatch (t , tt .expectedAddressDRK , dataStore1 .AddressRefStore .DeletedRemoteKeys ,
291+ "dataStore1 AddressRefStore.DeletedRemoteKeys should contain the expected staged keys after merge" )
292+ }
293+ if len (tt .expectedChainMetaDRK ) > 0 {
294+ require .ElementsMatch (t , tt .expectedChainMetaDRK , dataStore1 .ChainMetadataStore .DeletedRemoteKeys ,
295+ "dataStore1 ChainMetadataStore.DeletedRemoteKeys should contain the expected staged keys after merge" )
296+ }
297+ if len (tt .expectedContractMetaDRK ) > 0 {
298+ require .ElementsMatch (t , tt .expectedContractMetaDRK , dataStore1 .ContractMetadataStore .DeletedRemoteKeys ,
299+ "dataStore1 ContractMetadataStore.DeletedRemoteKeys should contain the expected staged keys after merge" )
300+ }
278301 })
279302 }
280303}
304+
305+ func TestMemoryDataStore_Merge_ChainedComposition (t * testing.T ) {
306+ t .Parallel ()
307+
308+ recA := AddressRef {
309+ Address : "0xAAA" ,
310+ Type : "typeA" ,
311+ Version : semver .MustParse ("2.0.0" ),
312+ Qualifier : "qa" ,
313+ }
314+
315+ t .Run ("Chained merge preserves DRK across two hops" , func (t * testing.T ) {
316+ t .Parallel ()
317+
318+ dataStore1 := NewMemoryDataStore ()
319+ require .NoError (t , dataStore1 .Addresses ().Add (recA ))
320+
321+ dataStore2 := NewMemoryDataStore ()
322+ require .NoError (t , dataStore2 .Addresses ().RemoteDelete (recA .Key ()))
323+
324+ dataStore3 := NewMemoryDataStore ()
325+ require .NoError (t , dataStore3 .Merge (dataStore2 .Seal ()))
326+
327+ dataStore4 := NewMemoryDataStore ()
328+ require .NoError (t , dataStore4 .Merge (dataStore3 .Seal ()))
329+
330+ require .Contains (t , dataStore4 .AddressRefStore .DeletedRemoteKeys , recA .Key ().String (),
331+ "DRK should survive two merge hops" )
332+
333+ addressRefs , err := dataStore4 .Addresses ().Fetch ()
334+ require .NoError (t , err )
335+ require .Empty (t , addressRefs , "recA should not appear in dataStore4 records after chained deletes" )
336+ })
337+
338+ t .Run ("Merge is idempotent on staged deletes" , func (t * testing.T ) {
339+ t .Parallel ()
340+
341+ dataStore1 := NewMemoryDataStore ()
342+ require .NoError (t , dataStore1 .Addresses ().Add (recA ))
343+
344+ dataStore2 := NewMemoryDataStore ()
345+ require .NoError (t , dataStore2 .Addresses ().RemoteDelete (recA .Key ()))
346+
347+ sealed := dataStore2 .Seal ()
348+
349+ require .NoError (t , dataStore1 .Merge (sealed ), "first merge should succeed" )
350+ require .NoError (t , dataStore1 .Merge (sealed ), "second merge should succeed (idempotent)" )
351+
352+ addressRefs , err := dataStore1 .Addresses ().Fetch ()
353+ require .NoError (t , err )
354+ require .Empty (t , addressRefs , "recA should not be present after idempotent merges" )
355+
356+ require .Contains (t , dataStore1 .AddressRefStore .DeletedRemoteKeys , recA .Key ().String (),
357+ "DRK should be present after idempotent merges" )
358+ })
359+ }
360+
361+ // TestMemoryDataStore_Merge_SourceLiveRecordClearsDestStagedDelete pins the
362+ // precedence rule documented on MemoryDataStore.Merge: when src.<Store>.Records
363+ // contains a live record for key K and src.<Store>.DeletedRemoteKeys does NOT
364+ // contain K, Merge's upsert clears K from dst.<Store>.DeletedRemoteKeys.
365+ //
366+ // In other words, a live record on the source side overrides a staged delete on
367+ // the destination side for the same key. Staged deletes are sticky only on the
368+ // source side of Merge.
369+ //
370+ // If this test starts failing, the precedence note on MemoryDataStore.Merge is
371+ // out of date — update either the assertion or the docstring to keep them in sync.
372+ func TestMemoryDataStore_Merge_SourceLiveRecordClearsDestStagedDelete (t * testing.T ) {
373+ t .Parallel ()
374+
375+ rec := AddressRef {
376+ Address : "0xBEEF" ,
377+ Type : "typeP" ,
378+ Version : semver .MustParse ("1.0.0" ),
379+ Qualifier : "qP" ,
380+ }
381+
382+ dst := NewMemoryDataStore ()
383+ require .NoError (t , dst .Addresses ().RemoteDelete (rec .Key ()))
384+ require .Contains (t , dst .AddressRefStore .DeletedRemoteKeys , rec .Key ().String (),
385+ "precondition: dst.DRK should contain the staged key before Merge" )
386+
387+ src := NewMemoryDataStore ()
388+ require .NoError (t , src .Addresses ().Add (rec ))
389+
390+ require .NoError (t , dst .Merge (src .Seal ()))
391+
392+ addressRefs , err := dst .Addresses ().Fetch ()
393+ require .NoError (t , err )
394+ require .Len (t , addressRefs , 1 , "dst should now contain src's live record" )
395+
396+ require .NotContains (t , dst .AddressRefStore .DeletedRemoteKeys , rec .Key ().String (),
397+ "dst.DRK should no longer contain the previously-staged key — Upsert cleared it" )
398+ }
0 commit comments