use of com.amplifyframework.datastore.appsync.ModelMetadata in project amplify-android by aws-amplify.
the class MergerTest method itemIsNotMergedWhenOutboxHasPendingMutation.
/**
* When an item comes into the merger to be merged,
* if there is a pending mutation in the outbox, for a model of the same ID,
* then that item shall NOT be merged.
* @throws DataStoreException On failure to arrange data into store
* @throws InterruptedException If interrupted while awaiting terminal result in test observer
* @throws AmplifyException On failure to arrange model schema
*/
@Test
public void itemIsNotMergedWhenOutboxHasPendingMutation() throws AmplifyException, InterruptedException {
// Arrange: some model with a well known ID exists on the system.
// We pretend that the user has recently updated it via the DataStore update() API.
String knownId = RandomString.string();
BlogOwner blogOwner = BlogOwner.builder().name("Jameson").id(knownId).build();
ModelMetadata localMetadata = new ModelMetadata(blogOwner.getId(), false, 1, Temporal.Timestamp.now());
storageAdapter.save(blogOwner, localMetadata);
ModelSchema schema = ModelSchema.fromModelClass(BlogOwner.class);
PendingMutation<BlogOwner> pendingMutation = PendingMutation.instance(blogOwner, schema, PendingMutation.Type.CREATE, QueryPredicates.all());
TestObserver<Void> enqueueObserver = mutationOutbox.enqueue(pendingMutation).test();
enqueueObserver.await(REASONABLE_WAIT_TIME, TimeUnit.MILLISECONDS);
enqueueObserver.assertNoErrors().assertComplete();
// Act: now, cloud sync happens, and the sync engine tries to apply an update
// for the same model ID, into the store. According to the cloud, this same
// item should be DELETED.
ModelMetadata cloudMetadata = new ModelMetadata(knownId, true, 2, Temporal.Timestamp.now());
TestObserver<Void> mergeObserver = merger.merge(new ModelWithMetadata<>(blogOwner, cloudMetadata)).test();
mergeObserver.await(REASONABLE_WAIT_TIME, TimeUnit.MILLISECONDS);
mergeObserver.assertNoErrors().assertComplete();
// Assert: the item is NOT deleted from the local store.
// The original is still there.
// Or in other words, the cloud data was NOT merged.
final List<BlogOwner> blogOwnersInStorage = storageAdapter.query(BlogOwner.class);
assertEquals(1, blogOwnersInStorage.size());
assertEquals(blogOwner, blogOwnersInStorage.get(0));
}
use of com.amplifyframework.datastore.appsync.ModelMetadata in project amplify-android by aws-amplify.
the class MergerTest method orphanedItemIsNotMerged.
/**
* Assume item A is dependent on item B, but the remote store has an
* orphaned item A without item B. Then, we try to merge a save for a
* item A. This should gracefully fail, with A not being in the local
* store, at the end.
* @throws DataStoreException On failure to query results for assertions
* @throws InterruptedException If interrupted while awaiting terminal result in test observer
*/
@Test
public void orphanedItemIsNotMerged() throws DataStoreException, InterruptedException {
// Arrange: an item and its parent are not in the local store
BlogOwner badOwner = BlogOwner.builder().name("Raphael").build();
Blog orphanedBlog = Blog.builder().name("How Not To Save Blogs").owner(badOwner).build();
ModelMetadata metadata = new ModelMetadata(orphanedBlog.getId(), false, 1, Temporal.Timestamp.now());
// Enforce foreign key constraint on in-memory storage adapter
doThrow(SQLiteConstraintException.class).when(inMemoryStorageAdapter).save(eq(orphanedBlog), any(), any(), any(), any());
// Act: merge a creation for an item
TestObserver<Void> observer = merger.merge(new ModelWithMetadata<>(orphanedBlog, metadata)).test();
assertTrue(observer.await(REASONABLE_WAIT_TIME, TimeUnit.MILLISECONDS));
observer.assertNoErrors().assertComplete();
// Assert: orphaned model was not merged locally
final List<Blog> blogsInStorage = storageAdapter.query(Blog.class);
assertTrue(blogsInStorage.isEmpty());
}
use of com.amplifyframework.datastore.appsync.ModelMetadata in project amplify-android by aws-amplify.
the class MergerTest method itemIsMergedAfterPendingMutationRemovedFromOutbox.
/**
* When processing a mutation response, the pending mutation should be removed from the outbox, and the mutation
* should be merged to local storage.
* @throws InterruptedException If interrupted while awaiting terminal result in test observer
* @throws AmplifyException On failure to arrange model schema
*/
@Test
public void itemIsMergedAfterPendingMutationRemovedFromOutbox() throws AmplifyException, InterruptedException {
// Arrange: some model with a well known ID exists on the system.
// We pretend that the user has recently updated it via the DataStore update() API.
String knownId = RandomString.string();
BlogOwner blogOwner = BlogOwner.builder().name("Jameson").id(knownId).build();
ModelMetadata localMetadata = new ModelMetadata(blogOwner.getId(), false, 1, Temporal.Timestamp.now());
storageAdapter.save(blogOwner, localMetadata);
ModelSchema schema = ModelSchema.fromModelClass(BlogOwner.class);
PendingMutation<BlogOwner> pendingMutation = PendingMutation.instance(blogOwner, schema, PendingMutation.Type.DELETE, QueryPredicates.all());
TestObserver<Void> enqueueObserver = mutationOutbox.enqueue(pendingMutation).test();
enqueueObserver.await(REASONABLE_WAIT_TIME, TimeUnit.MILLISECONDS);
enqueueObserver.assertNoErrors().assertComplete();
// Act: now, cloud sync happens, and the sync engine tries to apply an update
// for the same model ID, into the store. According to the cloud, this same
// item should be DELETED.
ModelMetadata cloudMetadata = new ModelMetadata(knownId, true, 2, Temporal.Timestamp.now());
TestObserver<Void> observer = mutationOutbox.remove(pendingMutation.getMutationId()).andThen(merger.merge(new ModelWithMetadata<>(blogOwner, cloudMetadata))).test();
observer.await(REASONABLE_WAIT_TIME, TimeUnit.MILLISECONDS);
observer.assertNoErrors().assertComplete();
// Assert: the item IS deleted from the local store.
// Or in other words, the cloud data WAS merged.
final List<BlogOwner> blogOwnersInStorage = storageAdapter.query(BlogOwner.class);
assertEquals(0, blogOwnersInStorage.size());
}
use of com.amplifyframework.datastore.appsync.ModelMetadata in project amplify-android by aws-amplify.
the class OrchestratorTest method setup.
/**
* Setup mocks and other common elements.
* @throws AmplifyException Not expected.
*/
@SuppressWarnings("unchecked")
@Before
public void setup() throws AmplifyException {
ShadowLog.stream = System.out;
// Arrange: create a BlogOwner
susan = BlogOwner.builder().name("Susan Quimby").build();
// SYNC_QUERIES_READY indicates that the sync queries have completed.
orchestratorInitObserver = HubAccumulator.create(HubChannel.DATASTORE, DataStoreChannelEventName.SYNC_QUERIES_READY, 1).start();
ModelMetadata metadata = new ModelMetadata(susan.getId(), false, 1, Temporal.Timestamp.now());
ModelWithMetadata<BlogOwner> modelWithMetadata = new ModelWithMetadata<>(susan, metadata);
// Mock behaviors from for the API category
mockApi = mock(GraphQLBehavior.class);
ApiMocking.mockSubscriptionStart(mockApi);
ApiMocking.mockSuccessfulMutation(mockApi, susan.getId(), modelWithMetadata);
ApiMocking.mockSuccessfulQuery(mockApi, modelWithMetadata);
AppSyncClient appSync = AppSyncClient.via(mockApi);
localStorageAdapter = InMemoryStorageAdapter.create();
ModelProvider modelProvider = SimpleModelProvider.withRandomVersion(BlogOwner.class);
SchemaRegistry schemaRegistry = SchemaRegistry.instance();
schemaRegistry.clear();
schemaRegistry.register(modelProvider.models());
orchestrator = new Orchestrator(modelProvider, schemaRegistry, localStorageAdapter, appSync, DataStoreConfiguration::defaults, () -> Orchestrator.State.SYNC_VIA_API, true);
}
use of com.amplifyframework.datastore.appsync.ModelMetadata in project amplify-android by aws-amplify.
the class AWSDataStorePluginTest method clearStopsSyncAndDeletesDatabase.
/**
* Verify that when the clear method is called, the following happens
* - All remote synchronization processes are stopped
* - The database is deleted.
* - On the next interaction with the DataStore, the synchronization processes are restarted.
* @throws JSONException on failure to arrange plugin config
* @throws AmplifyException on failure to arrange API plugin via Amplify facade
*/
@Test
public void clearStopsSyncAndDeletesDatabase() throws AmplifyException, JSONException {
ApiCategory mockApiCategory = mockApiCategoryWithGraphQlApi();
ApiPlugin<?> mockApiPlugin = mockApiCategory.getPlugin(MOCK_API_PLUGIN_NAME);
JSONObject dataStorePluginJson = new JSONObject().put("syncIntervalInMinutes", 60);
AWSDataStorePlugin awsDataStorePlugin = AWSDataStorePlugin.builder().modelProvider(modelProvider).apiCategory(mockApiCategory).build();
SynchronousDataStore synchronousDataStore = SynchronousDataStore.delegatingTo(awsDataStorePlugin);
awsDataStorePlugin.configure(dataStorePluginJson, context);
awsDataStorePlugin.initialize(context);
// Trick the DataStore since it's not getting initialized as part of the Amplify.initialize call chain
Amplify.Hub.publish(HubChannel.DATASTORE, HubEvent.create(InitializationStatus.SUCCEEDED));
// Setup objects
Person person1 = createPerson("Test", "Dummy I");
Person person2 = createPerson("Test", "Dummy II");
// Mock responses for person 1
doAnswer(invocation -> {
int indexOfResponseConsumer = 1;
Consumer<GraphQLResponse<ModelWithMetadata<Person>>> onResponse = invocation.getArgument(indexOfResponseConsumer);
ModelMetadata modelMetadata = new ModelMetadata(person1.getId(), false, 1, Temporal.Timestamp.now());
ModelWithMetadata<Person> modelWithMetadata = new ModelWithMetadata<>(person1, modelMetadata);
onResponse.accept(new GraphQLResponse<>(modelWithMetadata, Collections.emptyList()));
return mock(GraphQLOperation.class);
}).when(mockApiPlugin).mutate(any(), any(), any());
HubAccumulator apiInteractionObserver = HubAccumulator.create(HubChannel.DATASTORE, DataStoreChannelEventName.OUTBOX_MUTATION_PROCESSED, 1).start();
// Save person 1
synchronousDataStore.save(person1);
Person result1 = synchronousDataStore.get(Person.class, person1.getId());
assertEquals(person1, result1);
apiInteractionObserver.await();
verify(mockApiCategory).mutate(argThat(getMatcherFor(person1)), any(), any());
// Mock responses for person 2
doAnswer(invocation -> {
int indexOfResponseConsumer = 1;
Consumer<GraphQLResponse<ModelWithMetadata<Person>>> onResponse = invocation.getArgument(indexOfResponseConsumer);
ModelMetadata modelMetadata = new ModelMetadata(person2.getId(), false, 1, Temporal.Timestamp.now());
ModelWithMetadata<Person> modelWithMetadata = new ModelWithMetadata<>(person2, modelMetadata);
onResponse.accept(new GraphQLResponse<>(modelWithMetadata, Collections.emptyList()));
return mock(GraphQLOperation.class);
}).when(mockApiPlugin).mutate(any(), any(), any());
// Do the thing!
synchronousDataStore.clear();
assertRemoteSubscriptionsCancelled();
apiInteractionObserver = HubAccumulator.create(HubChannel.DATASTORE, DataStoreChannelEventName.OUTBOX_MUTATION_PROCESSED, 1).start();
HubAccumulator orchestratorInitObserver = HubAccumulator.create(HubChannel.DATASTORE, DataStoreChannelEventName.READY, 1).start();
// Interact with the DataStore after the clear
synchronousDataStore.save(person2);
// Verify person 2 was published to the cloud
apiInteractionObserver.await();
// Verify the orchestrator started back up and subscriptions are active.
orchestratorInitObserver.await();
assertRemoteSubscriptionsStarted();
Person result2 = synchronousDataStore.get(Person.class, person2.getId());
assertEquals(person2, result2);
verify(mockApiCategory, atLeastOnce()).mutate(argThat(getMatcherFor(person2)), any(), any());
}
Aggregations