use of com.amplifyframework.api.graphql.GraphQLLocation in project amplify-android by aws-amplify.
the class MutationProcessorTest method conflictHandlerInvokedForUnhandledConflictError.
/**
* If the AppSync response to the mutation contains a ConflictUnhandled
* error in the GraphQLResponse error list, then the user-provided
* conflict handler should be invoked.
* @throws DataStoreException On failure to obtain configuration from the provider
* @throws AmplifyException On failure to build {@link ModelSchema}
*/
@Test
public void conflictHandlerInvokedForUnhandledConflictError() throws AmplifyException {
// Arrange a user-provided conflict handler.
CountDownLatch handlerInvocationsRemainingCount = new CountDownLatch(1);
when(configurationProvider.getConfiguration()).thenReturn(DataStoreConfiguration.builder().conflictHandler((conflictData, onDecision) -> handlerInvocationsRemainingCount.countDown()).build());
// Save a model, its metadata, and its last sync data.
BlogOwner model = BlogOwner.builder().name("Exceptional Blogger").build();
ModelMetadata metadata = new ModelMetadata(model.getModelName() + "|" + model.getId(), false, 1, Temporal.Timestamp.now());
ModelSchema schema = schemaRegistry.getModelSchemaForModelClass(BlogOwner.class);
LastSyncMetadata lastSyncMetadata = LastSyncMetadata.baseSyncedAt(schema.getName(), 1_000L);
synchronousStorageAdapter.save(model, metadata, lastSyncMetadata);
// Enqueue an update in the mutation outbox
assertTrue(mutationOutbox.enqueue(PendingMutation.update(model, schema)).blockingAwait(TIMEOUT_SECONDS, TimeUnit.SECONDS));
// Fields that represent the "server's" understanding of the model state
Map<String, Object> serverModelData = new HashMap<>();
serverModelData.put("id", model.getId());
serverModelData.put("name", "Server blogger name");
serverModelData.put("_version", 1);
serverModelData.put("_deleted", false);
serverModelData.put("_lastChangedAt", 1_000);
// When AppSync receives that update, have it respond
// with a ConflictUnhandledError.
String message = "Conflict resolver rejects mutation.";
List<GraphQLPathSegment> paths = Collections.singletonList(new GraphQLPathSegment("updateBlogOwner"));
List<GraphQLLocation> locations = Collections.singletonList(new GraphQLLocation(2, 3));
Map<String, Object> extensions = new HashMap<>();
extensions.put("errorType", "ConflictUnhandled");
extensions.put("data", serverModelData);
GraphQLResponse.Error error = new GraphQLResponse.Error(message, locations, paths, extensions);
AppSyncMocking.update(appSync).mockErrorResponse(model, 1, error);
// Start the mutation processor.
mutationProcessor.startDrainingMutationOutbox();
// Wait for the conflict handler to be called.
Latch.await(handlerInvocationsRemainingCount);
}
use of com.amplifyframework.api.graphql.GraphQLLocation in project amplify-android by aws-amplify.
the class AppSyncConflictUnhandledErrorFactory method createUnhandledConflictError.
/**
* Creates an {@link AppSyncConflictUnhandledError}, given a {@link ModelWithMetadata}
* with representative server data.
* @param serverData Server data to include in the app sync error
* @param <T> Type of model that is experiencing conflict
* @return A non-null AppSyncConflictUnhandledError
*/
@SuppressWarnings("unchecked")
@NonNull
public static <T extends Model> AppSyncConflictUnhandledError<T> createUnhandledConflictError(@NonNull ModelWithMetadata<T> serverData) {
Objects.requireNonNull(serverData);
// Get the MWM as an object map
Gson gson = GsonFactory.instance();
JsonObject jsonObject = gson.fromJson(gson.toJson(serverData), JsonObject.class);
Map<String, Object> serverModelData = GsonObjectConverter.toMap(jsonObject);
// The model class is needed to get the simple name, and to pass to the
// findFirst() utility method.
Class<T> modelClass = (Class<T>) serverData.getModel().getClass();
// When AppSync receives that update, have it respond
// with a ConflictUnhandledError.
String message = "Conflict resolver rejects mutation.";
String pathSegmentText = "update" + modelClass.getSimpleName();
List<GraphQLPathSegment> paths = Collections.singletonList(new GraphQLPathSegment(pathSegmentText));
List<GraphQLLocation> locations = Collections.singletonList(new GraphQLLocation(2, 3));
Map<String, Object> extensions = new HashMap<>();
extensions.put("errorType", "ConflictUnhandled");
extensions.put("data", serverModelData);
GraphQLResponse.Error error = new GraphQLResponse.Error(message, locations, paths, extensions);
AppSyncConflictUnhandledError<T> conflictUnhandledError = AppSyncConflictUnhandledError.findFirst(modelClass, Collections.singletonList(error));
return Objects.requireNonNull(conflictUnhandledError);
}
use of com.amplifyframework.api.graphql.GraphQLLocation in project amplify-android by aws-amplify.
the class ConflictResolverIntegrationTest method setupApiMock.
@SuppressWarnings("unchecked")
private Person setupApiMock(CountDownLatch latch, ApiCategory mockApiCategory) {
Person person1 = createPerson("Test", "Dummy I");
// Mock success on subscription.
doAnswer(invocation -> {
int indexOfStartConsumer = 1;
Consumer<String> onStart = invocation.getArgument(indexOfStartConsumer);
GraphQLOperation<?> mockOperation = mock(GraphQLOperation.class);
doAnswer(opAnswer -> {
return null;
}).when(mockOperation).cancel();
// Trigger the subscription start event.
onStart.accept(RandomString.string());
return mockOperation;
}).when(mockApiCategory).subscribe(any(GraphQLRequest.class), any(Consumer.class), any(Consumer.class), any(Consumer.class), any(Action.class));
// When mutate is called on the appsync for the first time unhandled conflict error is returned.
doAnswer(invocation -> {
int indexOfResponseConsumer = 1;
Consumer<GraphQLResponse<ModelWithMetadata<Person>>> onResponse = invocation.getArgument(indexOfResponseConsumer);
List<GraphQLLocation> locations = new ArrayList<>();
locations.add(new GraphQLLocation(2, 3));
List<GraphQLPathSegment> path = new ArrayList<>();
path.add(new GraphQLPathSegment("updatePost"));
Map<String, Object> serverModelData = new HashMap<>();
serverModelData.put("id", "5c895eae-88ef-4ce8-9d58-e27d0c7cbe99");
serverModelData.put("createdAt", "2022-02-04T19:41:05.973Z");
serverModelData.put("first_name", "test");
serverModelData.put("last_name", "server last");
serverModelData.put("_version", 92);
serverModelData.put("_deleted", false);
serverModelData.put("_lastChangedAt", 1_000);
Map<String, Object> extensions = new HashMap<>();
extensions.put("errorInfo", null);
extensions.put("data", serverModelData);
extensions.put("errorType", "ConflictUnhandled");
ArrayList<GraphQLResponse.Error> errorList = new ArrayList<>();
errorList.add(new GraphQLResponse.Error("Conflict resolver rejects mutation.", locations, path, extensions));
onResponse.accept(new GraphQLResponse<>(null, errorList));
// latch makes sure conflict unhandled response is returned.
latch.countDown();
return mock(GraphQLOperation.class);
}).doAnswer(invocation -> {
// When mutate is called on the appsync for the second time success response is returned
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()));
verify(mockApiCategory, atLeast(2)).mutate(argThat(getMatcherFor(person1)), any(), any());
// latch makes sure success response is returned.
latch.countDown();
return mock(GraphQLOperation.class);
}).when(mockApiCategory).mutate(any(), any(), any());
// Setup to mimic successful sync
doAnswer(invocation -> {
int indexOfResponseConsumer = 1;
ModelMetadata modelMetadata = new ModelMetadata(person1.getId(), false, 1, Temporal.Timestamp.now());
ModelWithMetadata<Person> modelWithMetadata = new ModelWithMetadata<>(person1, modelMetadata);
// Mock the API emitting an ApiEndpointStatusChangeEvent event.
Consumer<GraphQLResponse<PaginatedResult<ModelWithMetadata<Person>>>> onResponse = invocation.getArgument(indexOfResponseConsumer);
PaginatedResult<ModelWithMetadata<Person>> data = new PaginatedResult<>(Collections.singletonList(modelWithMetadata), null);
onResponse.accept(new GraphQLResponse<>(data, Collections.emptyList()));
latch.countDown();
return mock(GraphQLOperation.class);
}).doAnswer(invocation -> {
int indexOfResponseConsumer = 1;
Car car = Car.builder().build();
ModelMetadata modelMetadata = new ModelMetadata(car.getId(), false, 1, Temporal.Timestamp.now());
ModelWithMetadata<Car> modelWithMetadata = new ModelWithMetadata<>(car, modelMetadata);
Consumer<GraphQLResponse<PaginatedResult<ModelWithMetadata<Car>>>> onResponse = invocation.getArgument(indexOfResponseConsumer);
PaginatedResult<ModelWithMetadata<Car>> data = new PaginatedResult<>(Collections.singletonList(modelWithMetadata), null);
onResponse.accept(new GraphQLResponse<>(data, Collections.emptyList()));
latch.countDown();
return mock(GraphQLOperation.class);
}).when(mockApiCategory).query(any(), any(), any());
return person1;
}
use of com.amplifyframework.api.graphql.GraphQLLocation in project amplify-android by aws-amplify.
the class GsonGraphQLResponseFactoryTest method errorResponseDeserializesExtensionsMap.
/**
* This tests the GsonErrorDeserializer. The test JSON response has 4 errors, which are all in
* different formats, but are expected to be parsed into the same resulting object:
* 1. Error contains errorType, errorInfo, data (AppSync specific fields) at root level
* 2. Error contains errorType, errorInfo, data inside extensions object
* 3. Error contains errorType, errorInfo, data at root AND inside extensions (fields inside
* extensions take precedence)
* 4. Error contains errorType at root, and errorInfo, data inside extensions (all should be
* merged into extensions)
*
* @throws ApiException From API configuration
*/
@Test
public void errorResponseDeserializesExtensionsMap() throws ApiException {
// Arrange some JSON string from a "server"
final String partialResponseJson = Resources.readAsString("error-extensions-gql-response.json");
// Act! Parse it into a model.
Type responseType = TypeMaker.getParameterizedType(PaginatedResult.class, Todo.class);
GraphQLRequest<PaginatedResult<Todo>> request = buildDummyRequest(responseType);
final GraphQLResponse<PaginatedResult<Todo>> response = responseFactory.buildResponse(request, partialResponseJson);
// Build the expected response.
String message = "Conflict resolver rejects mutation.";
List<GraphQLLocation> locations = Collections.singletonList(new GraphQLLocation(11, 3));
List<GraphQLPathSegment> path = Arrays.asList(new GraphQLPathSegment("listTodos"), new GraphQLPathSegment("items"), new GraphQLPathSegment(0), new GraphQLPathSegment("name"));
Map<String, Object> data = new HashMap<>();
data.put("id", "EF48518C-92EB-4F7A-A64E-D1B9325205CF");
data.put("title", "new3");
data.put("content", "Original content from DataStoreEndToEndTests at 2020-03-26 21:55:47 " + "+0000");
data.put("_version", 2);
Map<String, Object> extensions = new HashMap<>();
extensions.put("errorType", "ConflictUnhandled");
extensions.put("errorInfo", null);
extensions.put("data", data);
GraphQLResponse.Error expectedError = new GraphQLResponse.Error(message, locations, path, extensions);
GraphQLResponse<PaginatedResult<Todo>> expectedResponse = new GraphQLResponse<>(null, Arrays.asList(expectedError, expectedError, expectedError, expectedError));
// Assert that the response is expected
assertEquals(expectedResponse, response);
}
use of com.amplifyframework.api.graphql.GraphQLLocation in project amplify-android by aws-amplify.
the class GsonGraphQLResponseFactoryTest method responseRendersAsPaginatedResult.
/**
* Validates that the converter is able to parse a partial GraphQL
* response into a result. In this case, the result contains some
* data, but also a list of errors.
* @throws AmplifyException From API configuration
*/
@Test
public void responseRendersAsPaginatedResult() throws AmplifyException {
// Expect
final List<Todo> expectedTodos = Arrays.asList(Todo.builder().id("fa1c21cc-0458-4bca-bcb1-101579fb85c7").name(null).description("Test").build(), Todo.builder().id("68bad242-dec5-415b-acb3-daee3b069ce5").name(null).description("Test").build(), Todo.builder().id("f64e2e9a-42ad-4455-b8ee-d1cfae7e9f01").name(null).description("Test").build());
String nextToken = "eyJ2ZXJzaW9uIjoyLCJ0b2tlbiI6IkFRSUNBSGg5OUIvN3BjWU41eE96NDZJMW5GeGM4";
Type responseType = TypeMaker.getParameterizedType(PaginatedResult.class, Todo.class);
AppSyncGraphQLRequest<PaginatedResult<Todo>> expectedRequest = buildDummyRequest(responseType);
expectedRequest = expectedRequest.newBuilder().variable("nextToken", "String", nextToken).build();
final PaginatedResult<Todo> expectedPaginatedResult = new PaginatedResult<>(expectedTodos, expectedRequest);
final List<GraphQLResponse.Error> expectedErrors = new ArrayList<>();
for (int i = 0; i < 3; i++) {
String message = "failed";
List<GraphQLLocation> locations = Collections.singletonList(new GraphQLLocation(5, 7));
List<GraphQLPathSegment> path = Arrays.asList(new GraphQLPathSegment("listTodos"), new GraphQLPathSegment("items"), new GraphQLPathSegment(i), new GraphQLPathSegment("name"));
Map<String, Object> extensions = new HashMap<>();
extensions.put("errorType", null);
extensions.put("errorInfo", null);
extensions.put("data", null);
expectedErrors.add(new GraphQLResponse.Error(message, locations, path, extensions));
}
final GraphQLResponse<PaginatedResult<Todo>> expectedResponse = new GraphQLResponse<>(expectedPaginatedResult, expectedErrors);
// Act
final String partialResponseJson = Resources.readAsString("partial-gql-response.json");
final GraphQLRequest<PaginatedResult<Todo>> request = buildDummyRequest(responseType);
final GraphQLResponse<PaginatedResult<Todo>> response = responseFactory.buildResponse(request, partialResponseJson);
// Assert
assertEquals(expectedResponse, response);
}
Aggregations