1515
1616package com .amplifyframework .datastore .syncengine ;
1717
18+ import android .database .sqlite .SQLiteConstraintException ;
19+
1820import com .amplifyframework .AmplifyException ;
1921import com .amplifyframework .core .model .ModelSchema ;
2022import com .amplifyframework .core .model .query .Where ;
2527import com .amplifyframework .datastore .appsync .ModelWithMetadata ;
2628import com .amplifyframework .datastore .storage .InMemoryStorageAdapter ;
2729import com .amplifyframework .datastore .storage .SynchronousStorageAdapter ;
30+ import com .amplifyframework .testmodels .commentsblog .Blog ;
2831import com .amplifyframework .testmodels .commentsblog .BlogOwner ;
2932import com .amplifyframework .testutils .random .RandomString ;
3033
4144
4245import static org .junit .Assert .assertEquals ;
4346import static org .junit .Assert .assertTrue ;
47+ import static org .mockito .ArgumentMatchers .any ;
48+ import static org .mockito .ArgumentMatchers .eq ;
49+ import static org .mockito .Mockito .doThrow ;
50+ import static org .mockito .Mockito .spy ;
4451
4552/**
4653 * Tests the {@link Merger}.
4956public final class MergerTest {
5057 private static final long REASONABLE_WAIT_TIME = TimeUnit .SECONDS .toMillis (2 );
5158
59+ private InMemoryStorageAdapter inMemoryStorageAdapter ;
5260 private SynchronousStorageAdapter storageAdapter ;
5361 private MutationOutbox mutationOutbox ;
5462 private Merger merger ;
@@ -62,7 +70,7 @@ public final class MergerTest {
6270 */
6371 @ Before
6472 public void setup () {
65- InMemoryStorageAdapter inMemoryStorageAdapter = InMemoryStorageAdapter .create ();
73+ this . inMemoryStorageAdapter = spy ( InMemoryStorageAdapter .create () );
6674 this .storageAdapter = SynchronousStorageAdapter .delegatingTo (inMemoryStorageAdapter );
6775 this .mutationOutbox = new PersistentMutationOutbox (inMemoryStorageAdapter );
6876 VersionRepository versionRepository = new VersionRepository (inMemoryStorageAdapter );
@@ -347,4 +355,39 @@ public void itemWithoutVersionIsNotMerged() throws DataStoreException, Interrupt
347355 storageAdapter .query (ModelMetadata .class , Where .id (existingModel .getId ()))
348356 );
349357 }
358+
359+ /**
360+ * Assume item A is dependent on item B, but the remote store has an
361+ * orphaned item A without item B. Then, we try to merge a save for a
362+ * item A. This should gracefully fail, with A not being in the local
363+ * store, at the end.
364+ * @throws DataStoreException On failure to query results for assertions
365+ * @throws InterruptedException If interrupted while awaiting terminal result in test observer
366+ */
367+ @ Test
368+ public void orphanedItemIsNotMerged () throws DataStoreException , InterruptedException {
369+ // Arrange: an item and its parent are not in the local store
370+ BlogOwner badOwner = BlogOwner .builder ()
371+ .name ("Raphael" )
372+ .build ();
373+ Blog orphanedBlog = Blog .builder ()
374+ .name ("How Not To Save Blogs" )
375+ .owner (badOwner )
376+ .build ();
377+ ModelMetadata metadata = new ModelMetadata (orphanedBlog .getId (), false , 1 , Temporal .Timestamp .now ());
378+
379+ // Enforce foreign key constraint on in-memory storage adapter
380+ doThrow (SQLiteConstraintException .class )
381+ .when (inMemoryStorageAdapter )
382+ .save (eq (orphanedBlog ), any (), any (), any (), any ());
383+
384+ // Act: merge a creation for an item
385+ TestObserver <Void > observer = merger .merge (new ModelWithMetadata <>(orphanedBlog , metadata )).test ();
386+ assertTrue (observer .await (REASONABLE_WAIT_TIME , TimeUnit .MILLISECONDS ));
387+ observer .assertNoErrors ().assertComplete ();
388+
389+ // Assert: orphaned model was not merged locally
390+ final List <Blog > blogsInStorage = storageAdapter .query (Blog .class );
391+ assertTrue (blogsInStorage .isEmpty ());
392+ }
350393}
0 commit comments