/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package com.amplifyframework.api.aws; import com.amplifyframework.api.ApiException; import com.amplifyframework.api.aws.test.R; import com.amplifyframework.api.graphql.GraphQLRequest; import com.amplifyframework.api.graphql.GraphQLResponse; import com.amplifyframework.api.graphql.SimpleGraphQLRequest; import com.amplifyframework.testutils.Assets; import com.amplifyframework.testutils.Await; import com.amplifyframework.testutils.Resources; import com.amplifyframework.testutils.random.RandomString; import org.json.JSONException; import org.json.JSONObject; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; import static androidx.test.core.app.ApplicationProvider.getApplicationContext; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; /** * Tests the {@link SubscriptionEndpoint}. */ public final class SubscriptionEndpointTest { private Executor executor; private SubscriptionEndpoint subscriptionEndpoint; private String eventId; private Set subscriptionIdsForRelease; /** * Create an {@link SubscriptionEndpoint}. * @throws ApiException On failure to load API configuration from config file * @throws JSONException On failure to manipulate configuration JSON during test arrangement */ @Before public void setup() throws ApiException, JSONException { this.executor = Executors.newCachedThreadPool(); String endpointConfigKey = "eventsApi"; // The endpoint config in amplifyconfiguration.json with this name JSONObject configJson = Resources.readAsJson(getApplicationContext(), R.raw.amplifyconfiguration) .getJSONObject("api") .getJSONObject("plugins") .getJSONObject("awsAPIPlugin"); AWSApiPluginConfiguration pluginConfiguration = AWSApiPluginConfigurationReader.readFrom(configJson); ApiConfiguration apiConfiguration = pluginConfiguration.getApi(endpointConfigKey); assertNotNull(apiConfiguration); final GraphQLResponse.Factory responseFactory = new GsonGraphQLResponseFactory(); final SubscriptionAuthorizer authorizer = new SubscriptionAuthorizer(apiConfiguration); this.subscriptionEndpoint = new SubscriptionEndpoint(apiConfiguration, null, responseFactory, authorizer); this.eventId = RandomString.string(); this.subscriptionIdsForRelease = new HashSet<>(); } /** * After the test, tear down the subscriptions. * @throws RuntimeException On failure to release subscription */ @After public void releaseSubscriptions() throws RuntimeException { for (String subscriptionId : subscriptionIdsForRelease) { executor.execute(() -> Await.result((onResult, onError) -> { try { subscriptionEndpoint.releaseSubscription(subscriptionId); } catch (ApiException failureToLetGo) { // Friend, it is time. It is time. throw new RuntimeException(failureToLetGo); } }) ); } } /** * It should be possible to create two subscriptions to the same type of model. * @throws ApiException On failure to subscribe */ @Test public void twoSubscriptionsToTheSameThing() throws ApiException { // Okay, request a first subscription. String firstSubscriptionId = subscribeToEventComments(eventId); assertNotNull(firstSubscriptionId); subscriptionIdsForRelease.add(firstSubscriptionId); // Now, request a second subscription, with the same request data. // This is around where the test fails. String secondSubscriptionId = subscribeToEventComments(eventId); assertNotNull(secondSubscriptionId); subscriptionIdsForRelease.add(secondSubscriptionId); // Theoretically would expect that we'd have two subscriptions, // and that their subscription IDs would be different/unique. assertNotEquals(firstSubscriptionId, secondSubscriptionId); } /** * It uses a configurator if present. * * This test verifies that a configurator can add an interceptor and that it will be run when establishing * subscriptions. * @throws ApiException On failure to load API configuration from config file * @throws JSONException On failure to manipulate configuration JSON during test arrangement */ @Test public void usesConfiguratorIfPresent() throws ApiException, JSONException { String endpointConfigKey = "eventsApi"; // The endpoint config in amplifyconfiguration.json with this name JSONObject configJson = Resources.readAsJson(getApplicationContext(), R.raw.amplifyconfiguration) .getJSONObject("api") .getJSONObject("plugins") .getJSONObject("awsAPIPlugin"); AWSApiPluginConfiguration pluginConfiguration = AWSApiPluginConfigurationReader.readFrom(configJson); ApiConfiguration apiConfiguration = pluginConfiguration.getApi(endpointConfigKey); assertNotNull(apiConfiguration); final GraphQLResponse.Factory responseFactory = new GsonGraphQLResponseFactory(); final SubscriptionAuthorizer authorizer = new SubscriptionAuthorizer(apiConfiguration); final AtomicInteger counter = new AtomicInteger(); final OkHttpConfigurator configurator = okHttpBuilder -> { counter.incrementAndGet(); okHttpBuilder.addInterceptor(chain -> { counter.incrementAndGet(); return chain.proceed(chain.request()); }); }; this.subscriptionEndpoint = new SubscriptionEndpoint(apiConfiguration, configurator, responseFactory, authorizer); String firstSubscriptionId = subscribeToEventComments(eventId); assertNotNull(firstSubscriptionId); subscriptionIdsForRelease.add(firstSubscriptionId); assertEquals(2, counter.get()); } /** * Subscribe to comments on an event. * @param eventId ID of event for which comments are being made * @return Subscription ID received from subscription_ack message payload * @throws ApiException If outcome of subscription request is anything other than an ACK w/ new ID */ private String subscribeToEventComments(String eventId) throws ApiException { // Arrange a request to start a subscription. String document = Assets.readAsString("subscribe-event-comments.graphql"); GsonVariablesSerializer serializer = new GsonVariablesSerializer(); Map variables = Collections.singletonMap("eventId", eventId); GraphQLRequest request = new SimpleGraphQLRequest<>(document, variables, String.class, serializer); return Await.result((onResult, onError) -> executor.execute(() -> subscriptionEndpoint.requestSubscription( request, onResult, item -> { final String message; if (item.hasErrors()) { message = "Subscription error: " + item.getErrors().toString(); } else { message = "Unexpected subscription data: " + item.getData(); } ApiException apiException = new ApiException(message, "Not expected."); onError.accept(apiException); }, error -> onError.accept(new ApiException("Subscription failed.", error, "Not expected.")), () -> onError.accept(new ApiException("Subscription completed too soon.", "Not expected.")) ) ) ); } }