use of com.amplifyframework.datastore.appsync.ModelWithMetadata in project amplify-android by aws-amplify.
the class SubscriptionProcessorTest method arrangeDataEmittingSubscription.
@SuppressWarnings("SameParameterValue")
private static <T extends Model> void arrangeDataEmittingSubscription(AppSync appSync, ModelSchema modelSchema, SubscriptionType subscriptionType, GraphQLResponse<ModelWithMetadata<T>> response) throws DataStoreException {
Answer<Cancelable> answer = invocation -> {
final int startConsumerIndex = 1;
Consumer<String> onStart = invocation.getArgument(startConsumerIndex);
onStart.accept(RandomString.string());
final int dataConsumerIndex = 2;
Consumer<GraphQLResponse<ModelWithMetadata<T>>> onData = invocation.getArgument(dataConsumerIndex);
onData.accept(response);
return new NoOpCancelable();
};
arrangeSubscription(appSync, answer, modelSchema, subscriptionType);
}
use of com.amplifyframework.datastore.appsync.ModelWithMetadata in project amplify-android by aws-amplify.
the class SyncProcessorTest method syncAndExpect.
private void syncAndExpect(int numPages, int maxSyncRecords) throws AmplifyException, InterruptedException {
initSyncProcessor(maxSyncRecords);
// Arrange a subscription to the storage adapter. We're going to watch for changes.
// We expect to see content here as a result of the SyncProcessor applying updates.
final TestObserver<StorageItemChange<? extends Model>> adapterObserver = storageAdapter.observe().test();
// Arrange: return some responses for the sync() call on the RemoteModelState
AppSyncMocking.SyncConfigurator configurator = AppSyncMocking.sync(appSync);
List<ModelWithMetadata<BlogOwner>> expectedResponseItems = new ArrayList<>();
String token = null;
for (int pageIndex = 0; pageIndex < numPages; pageIndex++) {
String nextToken = pageIndex < numPages - 1 ? RandomString.string() : null;
ModelWithMetadata<BlogOwner> randomBlogOwner = randomBlogOwnerWithMetadata();
configurator.mockSuccessResponse(BlogOwner.class, token, nextToken, randomBlogOwner);
if (expectedResponseItems.size() < maxSyncRecords) {
expectedResponseItems.add(randomBlogOwner);
}
token = nextToken;
}
// Act: Call hydrate, and await its completion - assert it completed without error
TestObserver<ModelWithMetadata<? extends Model>> hydrationObserver = TestObserver.create();
syncProcessor.hydrate().subscribe(hydrationObserver);
// Wait 2 seconds, or 1 second per 100 pages, whichever is greater
long timeoutMs = Math.max(OP_TIMEOUT_MS, TimeUnit.SECONDS.toMillis(numPages / 100));
assertTrue(hydrationObserver.await(timeoutMs, TimeUnit.MILLISECONDS));
hydrationObserver.assertNoErrors();
hydrationObserver.assertComplete();
// Since hydrate() completed, the storage adapter observer should see some values.
// There should be a total of four changes on storage adapter
// A model and a metadata save for each of the two BlogOwner-type items
// Additionally, there should be 4 last sync time records, one for each of the
// models managed by the system.
adapterObserver.awaitCount(expectedResponseItems.size() * 2 + 4);
// Validate the changes emitted from the storage adapter's observe().
assertEquals(// Expect items as described above.
Observable.fromIterable(expectedResponseItems).flatMap(modelWithMutation -> Observable.fromArray(modelWithMutation.getModel(), modelWithMutation.getSyncMetadata())).toSortedList(SortByModelId::compare).blockingGet(), // Actually...
Observable.fromIterable(adapterObserver.values()).map(StorageItemChange::item).filter(item -> !LastSyncMetadata.class.isAssignableFrom(item.getClass())).toSortedList(SortByModelId::compare).blockingGet());
// Lastly: validate the current contents of the storage adapter.
// There should be 2 BlogOwners, and 2 MetaData records.
List<? extends Model> itemsInStorage = storageAdapter.query(modelProvider);
assertEquals(itemsInStorage.toString(), expectedResponseItems.size() * 2 + modelProvider.models().size(), itemsInStorage.size());
assertEquals(// Expect the 4 items for the bloggers (2 models and their metadata)
Observable.fromIterable(expectedResponseItems).flatMap(blogger -> Observable.fromArray(blogger.getModel(), blogger.getSyncMetadata())).toList().map(HashSet::new).blockingGet(), Observable.fromIterable(storageAdapter.query(modelProvider)).filter(item -> !LastSyncMetadata.class.isAssignableFrom(item.getClass())).toList().map(HashSet::new).blockingGet());
adapterObserver.dispose();
hydrationObserver.dispose();
}
use of com.amplifyframework.datastore.appsync.ModelWithMetadata in project amplify-android by aws-amplify.
the class ConflictResolverTest method conflictIsResolvedByRetryingLocalData.
/**
* When the user elects to retry the mutation using the local copy of the data,
* the following is expected:
* 1. The AppSync API is invoked, with the local mutation data
* 2. We assume that the AppSync API will respond differently
* upon retry (TODO: why? Will the user be expected to manually
* intervene and modify the backend state somehow?)
* @throws DataStoreException On failure to arrange metadata into storage
*/
@Test
public void conflictIsResolvedByRetryingLocalData() throws DataStoreException {
// Arrange for the user-provided conflict handler to always request local retry.
when(configurationProvider.getConfiguration()).thenReturn(DataStoreConfiguration.builder().conflictHandler(DataStoreConflictHandler.alwaysRetryLocal()).build());
// Arrange a pending mutation that includes the local data
BlogOwner localModel = BlogOwner.builder().name("Local Blogger").build();
PendingMutation<BlogOwner> mutation = PendingMutation.update(localModel, schema);
// Arrange server state for the model, in conflict to local data
BlogOwner serverModel = localModel.copyOfBuilder().name("Server Blogger").build();
Temporal.Timestamp now = Temporal.Timestamp.now();
ModelMetadata metadata = new ModelMetadata(serverModel.getId(), false, 4, now);
ModelWithMetadata<BlogOwner> serverData = new ModelWithMetadata<>(serverModel, metadata);
// Arrange a hypothetical conflict error from AppSync
AppSyncConflictUnhandledError<BlogOwner> unhandledConflictError = AppSyncConflictUnhandledErrorFactory.createUnhandledConflictError(serverData);
// Assume that the AppSync call succeeds this time.
ModelWithMetadata<BlogOwner> versionFromAppSyncResponse = new ModelWithMetadata<>(localModel, metadata);
AppSyncMocking.update(appSync).mockSuccessResponse(localModel, metadata.getVersion(), versionFromAppSyncResponse);
// Act: when the resolver is invoked, we expect the resolved version
// to include the server's metadata, but with the local data.
resolver.resolve(mutation, unhandledConflictError).test().awaitDone(TIMEOUT_SECONDS, TimeUnit.SECONDS).assertValue(versionFromAppSyncResponse);
// The handler should have called up to AppSync to update hte model
verify(appSync).update(eq(localModel), any(), eq(metadata.getVersion()), any(), any());
}
use of com.amplifyframework.datastore.appsync.ModelWithMetadata in project amplify-android by aws-amplify.
the class ConflictResolverTest method conflictIsResolvedByRetryingLocalDataWithSerializedModel.
/**
* When the user elects to retry the mutation using the local copy of the data,
* the following is expected:
* 1. The AppSync API is invoked, with the local mutation data
* 2. We assume that the AppSync API will respond differently
* upon retry
* @throws AmplifyException On failure to arrange metadata into storage
*/
@Test
public void conflictIsResolvedByRetryingLocalDataWithSerializedModel() throws AmplifyException {
// Arrange for the user-provided conflict handler to always request local retry.
when(configurationProvider.getConfiguration()).thenReturn(DataStoreConfiguration.builder().conflictHandler(DataStoreConflictHandler.alwaysRetryLocal()).build());
// Arrange a pending mutation that includes the local data
BlogOwner localModel = BlogOwner.builder().name("Local Blogger").build();
Map<String, Object> ownerData = new HashMap<>();
ownerData.put("id", localModel.getId());
ownerData.put("name", localModel.getName());
SerializedModel serializedOwner = SerializedModel.builder().serializedData(ownerData).modelSchema(ModelSchema.fromModelClass(BlogOwner.class)).build();
PendingMutation<SerializedModel> mutation = PendingMutation.update(serializedOwner, schema);
// Arrange server state for the model, in conflict to local data
BlogOwner serverModel = localModel.copyOfBuilder().name("Server Blogger").build();
Temporal.Timestamp now = Temporal.Timestamp.now();
ModelMetadata metadata = new ModelMetadata(serverModel.getId(), false, 4, now);
ModelWithMetadata<SerializedModel> serverData = new ModelWithMetadata<>(serializedOwner, metadata);
// Arrange a hypothetical conflict error from AppSync
AppSyncConflictUnhandledError<SerializedModel> unhandledConflictError = AppSyncConflictUnhandledErrorFactory.createUnhandledConflictError(serverData);
// Assume that the AppSync call succeeds this time.
ModelWithMetadata<BlogOwner> versionFromAppSyncResponse = new ModelWithMetadata<>(localModel, metadata);
AppSyncMocking.update(appSync).mockSuccessResponse(localModel, metadata.getVersion(), versionFromAppSyncResponse);
// Act: when the resolver is invoked, we expect the resolved version
// to include the server's metadata, but with the local data.
resolver.resolve(mutation, unhandledConflictError).test();
// The handler should have called AppSync to update the model
verify(appSync).update(eq(localModel), any(), eq(metadata.getVersion()), any(), any());
}
use of com.amplifyframework.datastore.appsync.ModelWithMetadata in project amplify-android by aws-amplify.
the class ConflictResolverTest method conflictIsResolvedByRetryingWithCustomModel.
/**
* When the user elects to retry with a custom model, and that model
* is not null, it means to try an update mutation against AppSync.
* We expect:
* 1. The AppSync API is invoked with an update mutation request.
* @throws DataStoreException upon failure to arrange metadata into storage
*/
@Test
public void conflictIsResolvedByRetryingWithCustomModel() throws DataStoreException {
// Arrange local model
BlogOwner localModel = BlogOwner.builder().name("Local model").build();
PendingMutation<BlogOwner> mutation = PendingMutation.update(localModel, schema);
// Arrange a server model
BlogOwner remoteModel = localModel.copyOfBuilder().name("Remote model").build();
Temporal.Timestamp now = Temporal.Timestamp.now();
ModelMetadata remoteMetadata = new ModelMetadata(remoteModel.getId(), false, 4, now);
ModelWithMetadata<BlogOwner> remoteData = new ModelWithMetadata<>(remoteModel, remoteMetadata);
// Arrange an unhandled conflict error based on the server data
AppSyncConflictUnhandledError<BlogOwner> unhandledConflictError = AppSyncConflictUnhandledErrorFactory.createUnhandledConflictError(remoteData);
// Arrange a conflict handler that returns a custom model
BlogOwner customModel = localModel.copyOfBuilder().name("Custom model").build();
when(configurationProvider.getConfiguration()).thenReturn(DataStoreConfiguration.builder().conflictHandler(RetryWithModelHandler.create(customModel)).build());
// When the AppSync update API is called, return a mock response
ModelMetadata metadata = new ModelMetadata(customModel.getId(), false, remoteMetadata.getVersion(), now);
ModelWithMetadata<BlogOwner> responseData = new ModelWithMetadata<>(customModel, metadata);
AppSyncMocking.update(appSync).mockSuccessResponse(customModel, remoteMetadata.getVersion(), responseData);
// When the resolver is called, the AppSync update API should be called,
// and the resolver should return its response data.
resolver.resolve(mutation, unhandledConflictError).test().awaitDone(TIMEOUT_SECONDS, TimeUnit.SECONDS).assertValue(responseData);
// Verify that the update API was invoked
verify(appSync).update(eq(customModel), any(), eq(remoteMetadata.getVersion()), any(), any());
}
Aggregations