Search in sources :

Example 1 with GraphQLLocation

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);
}
Also used : GraphQLLocation(com.amplifyframework.api.graphql.GraphQLLocation) HashMap(java.util.HashMap) GraphQLResponse(com.amplifyframework.api.graphql.GraphQLResponse) GraphQLPathSegment(com.amplifyframework.api.graphql.GraphQLPathSegment) CountDownLatch(java.util.concurrent.CountDownLatch) ModelSchema(com.amplifyframework.core.model.ModelSchema) BlogOwner(com.amplifyframework.testmodels.commentsblog.BlogOwner) ModelMetadata(com.amplifyframework.datastore.appsync.ModelMetadata) Test(org.junit.Test)

Example 2 with GraphQLLocation

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);
}
Also used : GraphQLLocation(com.amplifyframework.api.graphql.GraphQLLocation) HashMap(java.util.HashMap) Gson(com.google.gson.Gson) JsonObject(com.google.gson.JsonObject) GraphQLResponse(com.amplifyframework.api.graphql.GraphQLResponse) GraphQLPathSegment(com.amplifyframework.api.graphql.GraphQLPathSegment) JsonObject(com.google.gson.JsonObject) NonNull(androidx.annotation.NonNull)

Example 3 with GraphQLLocation

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;
}
Also used : AmplifyException(com.amplifyframework.AmplifyException) ApplicationProvider.getApplicationContext(androidx.test.core.app.ApplicationProvider.getApplicationContext) ArgumentMatchers.argThat(org.mockito.ArgumentMatchers.argThat) ModelWithMetadata(com.amplifyframework.datastore.appsync.ModelWithMetadata) ModelProvider(com.amplifyframework.core.model.ModelProvider) GraphQLPathSegment(com.amplifyframework.api.graphql.GraphQLPathSegment) ApiChannelEventName(com.amplifyframework.api.events.ApiChannelEventName) ArgumentMatcher(org.mockito.ArgumentMatcher) JSONException(org.json.JSONException) JSONObject(org.json.JSONObject) GraphQLLocation(com.amplifyframework.api.graphql.GraphQLLocation) Map(java.util.Map) Mockito.doAnswer(org.mockito.Mockito.doAnswer) Mockito.atLeast(org.mockito.Mockito.atLeast) GraphQLResponse(com.amplifyframework.api.graphql.GraphQLResponse) Assert.fail(org.junit.Assert.fail) HubEvent(com.amplifyframework.hub.HubEvent) HubChannel(com.amplifyframework.hub.HubChannel) ApiCategory(com.amplifyframework.api.ApiCategory) InitializationStatus(com.amplifyframework.core.InitializationStatus) RobolectricTestRunner(org.robolectric.RobolectricTestRunner) Person(com.amplifyframework.testmodels.personcar.Person) CountDownLatch(java.util.concurrent.CountDownLatch) List(java.util.List) RandomString(com.amplifyframework.testutils.random.RandomString) Mockito.any(org.mockito.Mockito.any) Mockito.mock(org.mockito.Mockito.mock) Context(android.content.Context) GraphQLRequest(com.amplifyframework.api.graphql.GraphQLRequest) RunWith(org.junit.runner.RunWith) HashMap(java.util.HashMap) Car(com.amplifyframework.testmodels.personcar.Car) Mockito.spy(org.mockito.Mockito.spy) ArrayList(java.util.ArrayList) Consumer(com.amplifyframework.core.Consumer) ApiEndpointStatusChangeEvent(com.amplifyframework.api.events.ApiEndpointStatusChangeEvent) ModelMetadata(com.amplifyframework.datastore.appsync.ModelMetadata) PaginatedResult(com.amplifyframework.api.graphql.PaginatedResult) Before(org.junit.Before) Amplify(com.amplifyframework.core.Amplify) SynchronousDataStore(com.amplifyframework.testutils.sync.SynchronousDataStore) ApiPlugin(com.amplifyframework.api.ApiPlugin) CategoryType(com.amplifyframework.core.category.CategoryType) Assert.assertTrue(org.junit.Assert.assertTrue) Test(org.junit.Test) Completable(io.reactivex.rxjava3.core.Completable) Action(com.amplifyframework.core.Action) Mockito.when(org.mockito.Mockito.when) AmplifyCliGeneratedModelProvider(com.amplifyframework.testmodels.personcar.AmplifyCliGeneratedModelProvider) Mockito.verify(org.mockito.Mockito.verify) TimeUnit(java.util.concurrent.TimeUnit) ApiCategoryConfiguration(com.amplifyframework.api.ApiCategoryConfiguration) Temporal(com.amplifyframework.core.model.temporal.Temporal) GraphQLOperation(com.amplifyframework.api.graphql.GraphQLOperation) Collections(java.util.Collections) Assert.assertEquals(org.junit.Assert.assertEquals) Action(com.amplifyframework.core.Action) GraphQLLocation(com.amplifyframework.api.graphql.GraphQLLocation) ArrayList(java.util.ArrayList) GraphQLPathSegment(com.amplifyframework.api.graphql.GraphQLPathSegment) RandomString(com.amplifyframework.testutils.random.RandomString) GraphQLRequest(com.amplifyframework.api.graphql.GraphQLRequest) Consumer(com.amplifyframework.core.Consumer) List(java.util.List) ArrayList(java.util.ArrayList) ModelWithMetadata(com.amplifyframework.datastore.appsync.ModelWithMetadata) PaginatedResult(com.amplifyframework.api.graphql.PaginatedResult) GraphQLResponse(com.amplifyframework.api.graphql.GraphQLResponse) Car(com.amplifyframework.testmodels.personcar.Car) GraphQLOperation(com.amplifyframework.api.graphql.GraphQLOperation) Person(com.amplifyframework.testmodels.personcar.Person) Map(java.util.Map) HashMap(java.util.HashMap) ModelMetadata(com.amplifyframework.datastore.appsync.ModelMetadata)

Example 4 with GraphQLLocation

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);
}
Also used : GraphQLLocation(com.amplifyframework.api.graphql.GraphQLLocation) HashMap(java.util.HashMap) PaginatedResult(com.amplifyframework.api.graphql.PaginatedResult) GraphQLResponse(com.amplifyframework.api.graphql.GraphQLResponse) GraphQLPathSegment(com.amplifyframework.api.graphql.GraphQLPathSegment) QueryType(com.amplifyframework.api.graphql.QueryType) Type(java.lang.reflect.Type) JSONObject(org.json.JSONObject) Test(org.junit.Test)

Example 5 with GraphQLLocation

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);
}
Also used : GraphQLLocation(com.amplifyframework.api.graphql.GraphQLLocation) HashMap(java.util.HashMap) PaginatedResult(com.amplifyframework.api.graphql.PaginatedResult) ArrayList(java.util.ArrayList) GraphQLResponse(com.amplifyframework.api.graphql.GraphQLResponse) GraphQLPathSegment(com.amplifyframework.api.graphql.GraphQLPathSegment) QueryType(com.amplifyframework.api.graphql.QueryType) Type(java.lang.reflect.Type) JSONObject(org.json.JSONObject) Test(org.junit.Test)

Aggregations

GraphQLLocation (com.amplifyframework.api.graphql.GraphQLLocation)6 GraphQLPathSegment (com.amplifyframework.api.graphql.GraphQLPathSegment)6 GraphQLResponse (com.amplifyframework.api.graphql.GraphQLResponse)6 HashMap (java.util.HashMap)6 Test (org.junit.Test)5 PaginatedResult (com.amplifyframework.api.graphql.PaginatedResult)4 JSONObject (org.json.JSONObject)4 QueryType (com.amplifyframework.api.graphql.QueryType)3 Type (java.lang.reflect.Type)3 ArrayList (java.util.ArrayList)3 ModelMetadata (com.amplifyframework.datastore.appsync.ModelMetadata)2 CountDownLatch (java.util.concurrent.CountDownLatch)2 Context (android.content.Context)1 NonNull (androidx.annotation.NonNull)1 ApplicationProvider.getApplicationContext (androidx.test.core.app.ApplicationProvider.getApplicationContext)1 AmplifyException (com.amplifyframework.AmplifyException)1 ApiCategory (com.amplifyframework.api.ApiCategory)1 ApiCategoryConfiguration (com.amplifyframework.api.ApiCategoryConfiguration)1 ApiPlugin (com.amplifyframework.api.ApiPlugin)1 ApiChannelEventName (com.amplifyframework.api.events.ApiChannelEventName)1