io.stargate.it.http.graphql.cqlfirst.ApolloTestBase Maven / Gradle / Ivy
/*
* Copyright The Stargate Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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 io.stargate.it.http.graphql.cqlfirst;
import static org.assertj.core.api.Assertions.assertThat;
import com.apollographql.apollo.ApolloCall;
import com.apollographql.apollo.ApolloClient;
import com.apollographql.apollo.ApolloMutationCall;
import com.apollographql.apollo.api.CustomTypeAdapter;
import com.apollographql.apollo.api.CustomTypeValue;
import com.apollographql.apollo.api.Error;
import com.apollographql.apollo.api.Mutation;
import com.apollographql.apollo.api.Operation;
import com.apollographql.apollo.api.Response;
import com.apollographql.apollo.exception.ApolloException;
import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.CqlSession;
import com.example.graphql.client.betterbotz.products.DeleteProductsMutation;
import com.example.graphql.client.betterbotz.products.GetProductsWithFilterQuery;
import com.example.graphql.client.betterbotz.type.CustomType;
import com.example.graphql.client.betterbotz.type.ProductsFilterInput;
import com.example.graphql.client.betterbotz.type.ProductsInput;
import com.example.graphql.client.betterbotz.type.QueryConsistency;
import com.example.graphql.client.betterbotz.type.QueryOptions;
import com.example.graphql.client.betterbotz.type.UuidFilterInput;
import io.stargate.it.driver.TestKeyspace;
import io.stargate.it.http.RestUtils;
import io.stargate.it.storage.StargateConnectionInfo;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.util.List;
import java.util.Optional;
import java.util.TimeZone;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import javax.validation.constraints.NotNull;
import okhttp3.OkHttpClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base class for GraphQL tests that use the apollo-runtime client library.
*
* Note that we are trying to limit usage of that library in our tests. Do not subclass this in
* new tests; instead, use {@link CqlFirstClient} (see {@link SelectTest}, {@link InsertTest}, etc).
*
*
Eventually, {@link ApolloTest} should be the only subclass, and we might merge this into it.
*/
public class ApolloTestBase extends BetterbotzTestBase {
protected static final Logger logger = LoggerFactory.getLogger(ApolloTest.class);
protected static CqlSession session;
protected static String authToken;
protected static StargateConnectionInfo stargate;
protected static String keyspace;
@BeforeAll
public static void setup(
StargateConnectionInfo stargateInfo,
CqlSession session,
@TestKeyspace CqlIdentifier keyspaceId)
throws Exception {
stargate = stargateInfo;
ApolloTest.session = session;
keyspace = keyspaceId.asInternal();
authToken = RestUtils.getAuthToken(stargate.seedAddress());
}
@AfterEach
public void cleanUpProducts() {
ApolloClient client = getApolloClient("/graphql/" + keyspace);
getProducts(client, 100, Optional.empty())
.flatMap(GetProductsWithFilterQuery.Products::getValues)
.ifPresent(
products ->
products.forEach(p -> p.getId().ifPresent(id -> cleanupProduct(client, id))));
}
protected static Optional getProducts(
ApolloClient client, int pageSize, Optional pageState) {
ProductsFilterInput filterInput = ProductsFilterInput.builder().build();
QueryOptions.Builder optionsBuilder =
QueryOptions.builder().pageSize(pageSize).consistency(QueryConsistency.LOCAL_QUORUM);
pageState.ifPresent(optionsBuilder::pageState);
QueryOptions options = optionsBuilder.build();
GetProductsWithFilterQuery query =
GetProductsWithFilterQuery.builder().filter(filterInput).options(options).build();
GetProductsWithFilterQuery.Data result = getObservable(client.query(query));
assertThat(result.getProducts())
.hasValueSatisfying(
products -> {
assertThat(products.getValues())
.hasValueSatisfying(
values -> {
assertThat(values).hasSizeLessThanOrEqualTo(pageSize);
});
});
return result.getProducts();
}
private DeleteProductsMutation.Data cleanupProduct(ApolloClient client, Object productId) {
DeleteProductsMutation mutation =
DeleteProductsMutation.builder()
.value(ProductsInput.builder().id(productId).build())
.build();
DeleteProductsMutation.Data result = getObservable(client.mutate(mutation));
return result;
}
protected GetProductsWithFilterQuery.Value getProduct(ApolloClient client, String productId) {
List valuesList = getProductValues(client, productId);
return valuesList.get(0);
}
protected List getProductValues(
ApolloClient client, String productId) {
ProductsFilterInput filterInput =
ProductsFilterInput.builder().id(UuidFilterInput.builder().eq(productId).build()).build();
QueryOptions options =
QueryOptions.builder().consistency(QueryConsistency.LOCAL_QUORUM).build();
GetProductsWithFilterQuery query =
GetProductsWithFilterQuery.builder().filter(filterInput).options(options).build();
GetProductsWithFilterQuery.Data result = getObservable(client.query(query));
assertThat(result.getProducts()).isPresent();
GetProductsWithFilterQuery.Products products = result.getProducts().get();
assertThat(products.getValues()).isPresent();
return products.getValues().get();
}
protected static T getObservable(ApolloCall> observable) {
CompletableFuture future = new CompletableFuture<>();
observable.enqueue(queryCallback(future));
try {
return future.get();
} catch (ExecutionException e) {
// Unwrap exception
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
}
throw new RuntimeException("Unexpected exception", e);
} catch (Exception e) {
throw new RuntimeException("Operation could not be completed", e);
} finally {
observable.cancel();
}
}
@SuppressWarnings("unchecked")
private static D mutateAndGet(
ApolloClient client, Mutation mutation) {
return getObservable((ApolloMutationCall>) client.mutate(mutation));
}
protected OkHttpClient getHttpClient() {
return new OkHttpClient.Builder()
.connectTimeout(Duration.ofMinutes(3))
.callTimeout(Duration.ofMinutes(3))
.readTimeout(Duration.ofMinutes(3))
.writeTimeout(Duration.ofMinutes(3))
.addInterceptor(
chain ->
chain.proceed(
chain.request().newBuilder().addHeader("X-Cassandra-Token", authToken).build()))
.build();
}
protected ApolloClient getApolloClient(String path) {
return ApolloClient.builder()
.serverUrl(String.format("http://%s:8080%s", stargate.seedAddress(), path))
.okHttpClient(getHttpClient())
.addCustomTypeAdapter(
CustomType.TIMESTAMP,
new CustomTypeAdapter() {
@NotNull
@Override
public CustomTypeValue> encode(Instant instant) {
return new CustomTypeValue.GraphQLString(instant.toString());
}
@Override
public Instant decode(@NotNull CustomTypeValue> customTypeValue) {
return parseInstant(customTypeValue.value.toString());
}
})
.build();
}
protected static ApolloCall.Callback> queryCallback(CompletableFuture future) {
return new ApolloCall.Callback>() {
@Override
public void onResponse(@NotNull Response> response) {
if (response.getErrors() != null && response.getErrors().size() > 0) {
logger.info(
"GraphQL error found in test: {}",
response.getErrors().stream().map(Error::getMessage).collect(Collectors.toList()));
future.completeExceptionally(
new GraphQLTestException("GraphQL error response", response.getErrors()));
return;
}
if (response.getData().isPresent()) {
future.complete(response.getData().get());
return;
}
future.completeExceptionally(
new IllegalStateException("Unexpected empty data and errors properties"));
}
@Override
public void onFailure(@NotNull ApolloException e) {
future.completeExceptionally(e);
}
};
}
protected static class GraphQLTestException extends RuntimeException {
protected final List errors;
GraphQLTestException(String message, List errors) {
super(message);
this.errors = errors;
}
}
private static Instant parseInstant(String source) {
try {
return TIMESTAMP_FORMAT.get().parse(source).toInstant();
} catch (ParseException e) {
throw new AssertionError("Unexpected error while parsing timestamp in response", e);
}
}
private static final ThreadLocal TIMESTAMP_FORMAT =
ThreadLocal.withInitial(
() -> {
SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
parser.setTimeZone(TimeZone.getTimeZone(ZoneId.systemDefault()));
return parser;
});
}