Polishing contribution

Closes gh-201
This commit is contained in:
Rossen Stoyanchev
2021-11-25 17:53:19 +00:00
parent c9b5652833
commit 5c5cfdd277
10 changed files with 187 additions and 202 deletions

View File

@@ -45,7 +45,6 @@ import reactor.core.publisher.Mono;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.NoRepositoryBean;
@@ -87,35 +86,65 @@ import org.springframework.util.StringUtils;
* wiring.dataFetcher("book", QueryByExampleDataFetcher.builder(repository).single());
* </pre>
*
* <p>See methods on {@link QueryByExampleDataFetcher.Builder} and {@link QueryByExampleDataFetcher.ReactiveBuilder} for further
* options on GraphQL Query argument to Query by Example bindings, result projections, and sorting.
* <p>See methods on {@link QueryByExampleDataFetcher.Builder} and
* {@link QueryByExampleDataFetcher.ReactiveBuilder} for further options on
* GraphQL Query argument to Query by Example bindings, result projections, and
* sorting.
*
* @param <T> returned result type
* @author Greg Turnquist
* @since 1.0.0
*
* @see QueryByExampleExecutor
* @see ReactiveQueryByExampleExecutor
* @see Example
* @see <a href="https://docs.spring.io/spring-data/commons/docs/current/reference/html/#query-by-example">
* Spring Data Query By Example extension</a>
* @since 1.0.0
*/
public abstract class QueryByExampleDataFetcher<T> {
private final TypeInformation<T> domainType;
private GraphQlArgumentInstantiator instantiator;
private final GraphQlArgumentInstantiator instantiator;
QueryByExampleDataFetcher(TypeInformation<T> domainType, @Nullable ConversionService conversionService) {
QueryByExampleDataFetcher(TypeInformation<T> domainType) {
this.domainType = domainType;
this.instantiator = new GraphQlArgumentInstantiator(conversionService);
this.instantiator = new GraphQlArgumentInstantiator(null);
}
/**
* Prepare an {@link Example} from GraphQL query arguments.
* @param env contextual info for the GraphQL query
* @return the resulting example
*/
protected Example<T> buildExample(DataFetchingEnvironment env) {
return Example.of(this.instantiator.instantiate(env.getArguments(), this.domainType.getType()));
}
protected boolean requiresProjection(Class<?> resultType) {
return !resultType.equals(this.domainType.getType());
}
protected Collection<String> buildPropertyPaths(DataFetchingFieldSelectionSet selection, Class<?> resultType) {
// Compute selection only for non-projections
if (this.domainType.getType().equals(resultType) ||
this.domainType.getType().isAssignableFrom(resultType) ||
this.domainType.isSubTypeOf(resultType)) {
return PropertySelection.create(this.domainType, selection).toList();
}
return Collections.emptyList();
}
/**
* Create a new {@link Builder} accepting {@link QueryByExampleExecutor}
* to build a {@link DataFetcher}.
*
* @param executor the repository object to use
* @param <T> result type
* @param executor the QBE repository object to use
* @param <T> the domain type of the repository
* @return a new builder
*/
public static <T> Builder<T, T> builder(QueryByExampleExecutor<T> executor) {
@@ -124,10 +153,10 @@ public abstract class QueryByExampleDataFetcher<T> {
/**
* Create a new {@link ReactiveBuilder} accepting
* {@link ReactiveQueryByExampleExecutor} to build a reactive {@link DataFetcher}.
* {@link ReactiveQueryByExampleExecutor} to build a {@link DataFetcher}.
*
* @param executor the repository object to use
* @param <T> result type
* @param executor the QBE repository object to use
* @param <T> the domain type of the repository
* @return a new builder
*/
public static <T> ReactiveBuilder<T, T> builder(ReactiveQueryByExampleExecutor<T> executor) {
@@ -177,31 +206,6 @@ public abstract class QueryByExampleDataFetcher<T> {
String.format("Cannot resolve repository interface from %s", executor));
}
/**
* Prepare an {@link Example} from GraphQL query arguments.
*
* @param environment contextual info for the GraphQL query
* @return the resulting example
*/
@SuppressWarnings({"unchecked", "rawtypes"})
protected Example<T> buildExample(DataFetchingEnvironment environment) {
return Example.of(instantiator.instantiate(environment.getArguments(), domainType.getType()));
}
protected boolean requiresProjection(Class<?> resultType) {
return !resultType.equals(this.domainType.getType());
}
protected Collection<String> buildPropertyPaths(DataFetchingFieldSelectionSet selection, Class<?> resultType) {
// Compute selection only for non-projections
if (this.domainType.getType().equals(resultType) ||
this.domainType.getType().isAssignableFrom(resultType) ||
this.domainType.isSubTypeOf(resultType)) {
return PropertySelection.create(this.domainType, selection).toList();
}
return Collections.emptyList();
}
/**
* Builder for a Query by Example-based {@link DataFetcher}. Note that builder
@@ -221,78 +225,64 @@ public abstract class QueryByExampleDataFetcher<T> {
private final Sort sort;
private final ConversionService conversionService;
@SuppressWarnings("unchecked")
Builder(QueryByExampleExecutor<T> executor, Class<R> domainType) {
this(executor,
ClassTypeInformation.from((Class<T>) domainType),
domainType,
Sort.unsorted(),
null);
this(executor, ClassTypeInformation.from((Class<T>) domainType), domainType, Sort.unsorted());
}
Builder(QueryByExampleExecutor<T> executor, ClassTypeInformation<T> domainType, Class<R> resultType, Sort sort, ConversionService conversionService) {
Builder(QueryByExampleExecutor<T> executor, ClassTypeInformation<T> domainType, Class<R> resultType, Sort sort) {
this.executor = executor;
this.domainType = domainType;
this.resultType = resultType;
this.sort = sort;
this.conversionService = conversionService;
}
/**
* Project results returned from the {@link QueryByExampleExecutor}
* into the target {@code projectionType}. Projection types can be
* either interfaces declaring getters for properties to expose or
* regular classes outside the entity type hierarchy for
* DTO projection.
*
* either interfaces with property getters to expose or regular classes
* outside the entity type hierarchy for DTO projections.
* @param projectionType projection type
* @return a new {@link Builder} instance with all previously
* configured options and {@code projectionType} applied
*/
public <P> Builder<T, P> projectAs(Class<P> projectionType) {
Assert.notNull(projectionType, "Projection type must not be null");
return new Builder<>(this.executor, this.domainType, projectionType, this.sort, this.conversionService);
return new Builder<>(this.executor, this.domainType, projectionType, this.sort);
}
/**
* Apply a {@link Sort} order.
*
* @param sort the default sort order
* @return a new {@link Builder} instance with all previously configured
* options and {@code Sort} applied
*/
public Builder<T, R> sortBy(Sort sort) {
Assert.notNull(sort, "Sort must not be null");
return new Builder<>(this.executor, this.domainType, this.resultType, sort, this.conversionService);
return new Builder<>(this.executor, this.domainType, this.resultType, sort);
}
/**
* Build a {@link DataFetcher} to fetch single object instances.
*
* @return a {@link DataFetcher} based on Query by Example to fetch one object
*/
public DataFetcher<R> single() {
return new SingleEntityFetcher<>(
this.executor, this.domainType, this.resultType, this.sort, this.conversionService);
return new SingleEntityFetcher<>(this.executor, this.domainType, this.resultType, this.sort);
}
/**
* Build a {@link DataFetcher} to fetch many object instances.
*
* @return a {@link DataFetcher} based on Query Example to fetch many objects
*/
public DataFetcher<Iterable<R>> many() {
return new ManyEntityFetcher<>(
this.executor, this.domainType, this.resultType, this.sort, this.conversionService);
return new ManyEntityFetcher<>(this.executor, this.domainType, this.resultType, this.sort);
}
}
/**
* Builder for a reactive Query by Example-based {@link DataFetcher}. Note that builder
* instances are immutable and return a new instance of the builder when
* calling configuration methods.
* Builder for a reactive Query by Example-based {@link DataFetcher}.
* Note that builder instances are immutable and return a new instance of
* the builder when calling configuration methods.
*
* @param <T> domain type
* @param <R> result type
@@ -307,78 +297,63 @@ public abstract class QueryByExampleDataFetcher<T> {
private final Sort sort;
private final ConversionService conversionService;
@SuppressWarnings("unchecked")
ReactiveBuilder(ReactiveQueryByExampleExecutor<T> executor, Class<R> domainType) {
this(executor,
ClassTypeInformation.from((Class<T>) domainType),
domainType,
Sort.unsorted(),
null);
this(executor, ClassTypeInformation.from((Class<T>) domainType), domainType, Sort.unsorted());
}
ReactiveBuilder(ReactiveQueryByExampleExecutor<T> executor,
TypeInformation<T> domainType,
Class<R> resultType,
Sort sort,
ConversionService conversionService) {
ReactiveBuilder(
ReactiveQueryByExampleExecutor<T> executor, TypeInformation<T> domainType,
Class<R> resultType, Sort sort) {
this.executor = executor;
this.domainType = domainType;
this.resultType = resultType;
this.sort = sort;
this.conversionService = conversionService;
}
/**
* Project results returned from the {@link QueryByExampleExecutor}
* Project results returned from the {@link ReactiveQueryByExampleExecutor}
* into the target {@code projectionType}. Projection types can be
* either interfaces declaring getters for properties to expose or
* regular classes outside the entity type hierarchy for
* DTO projection.
*
* either interfaces with property getters to expose or regular classes
* outside the entity type hierarchy for DTO projections.
* @param projectionType projection type
* @return a new {@link ReactiveBuilder} instance with all previously
* configured options and {@code projectionType} applied
*/
public <P> ReactiveBuilder<T, P> projectAs(Class<P> projectionType) {
Assert.notNull(projectionType, "Projection type must not be null");
return new ReactiveBuilder<>(this.executor, this.domainType, projectionType, this.sort, this.conversionService);
return new ReactiveBuilder<>(this.executor, this.domainType, projectionType, this.sort);
}
/**
* Apply a {@link Sort} order.
*
* @param sort the default sort order
* @return a new {@link ReactiveBuilder} instance with all previously configured
* options and {@code Sort} applied
*/
public ReactiveBuilder<T, R> sortBy(Sort sort) {
Assert.notNull(sort, "Sort must not be null");
return new ReactiveBuilder<>(this.executor, this.domainType, this.resultType, sort, this.conversionService);
return new ReactiveBuilder<>(this.executor, this.domainType, this.resultType, sort);
}
/**
* Build a {@link DataFetcher} to fetch single object instances through {@link Mono}.
*
* @return a {@link DataFetcher} based on Query by Example to fetch one object
* Build a {@link DataFetcher} to fetch single object instances.
*/
public DataFetcher<Mono<R>> single() {
return new ReactiveSingleEntityFetcher<>(
this.executor, this.domainType, this.resultType, this.sort, this.conversionService);
return new ReactiveSingleEntityFetcher<>(this.executor, this.domainType, this.resultType, this.sort);
}
/**
* Build a {@link DataFetcher} to fetch many object instances through {@link Flux}.
*
* @return a {@link DataFetcher} based on Query by Example to fetch many objects
* Build a {@link DataFetcher} to fetch many object instances.
*/
public DataFetcher<Flux<R>> many() {
return new ReactiveManyEntityFetcher<>(
this.executor, this.domainType, this.resultType, this.sort, this.conversionService);
return new ReactiveManyEntityFetcher<>(this.executor, this.domainType, this.resultType, this.sort);
}
}
private static class SingleEntityFetcher<T, R> extends QueryByExampleDataFetcher<T> implements DataFetcher<R> {
private final QueryByExampleExecutor<T> executor;
@@ -388,13 +363,10 @@ public abstract class QueryByExampleDataFetcher<T> {
private final Sort sort;
@SuppressWarnings({"unchecked", "rawtypes"})
SingleEntityFetcher(QueryByExampleExecutor<T> executor,
TypeInformation<T> domainType,
Class<R> resultType,
Sort sort,
ConversionService conversionService) {
SingleEntityFetcher(
QueryByExampleExecutor<T> executor, TypeInformation<T> domainType, Class<R> resultType, Sort sort) {
super(domainType, conversionService);
super(domainType);
this.executor = executor;
this.resultType = resultType;
this.sort = sort;
@@ -424,6 +396,7 @@ public abstract class QueryByExampleDataFetcher<T> {
}
private static class ManyEntityFetcher<T, R> extends QueryByExampleDataFetcher<T> implements DataFetcher<Iterable<R>> {
private final QueryByExampleExecutor<T> executor;
@@ -433,12 +406,11 @@ public abstract class QueryByExampleDataFetcher<T> {
private final Sort sort;
@SuppressWarnings({"unchecked", "rawtypes"})
ManyEntityFetcher(QueryByExampleExecutor<T> executor,
TypeInformation<T> domainType,
Class<R> resultType,
Sort sort,
ConversionService conversionService) {
super(domainType, conversionService);
ManyEntityFetcher(
QueryByExampleExecutor<T> executor, TypeInformation<T> domainType,
Class<R> resultType, Sort sort) {
super(domainType);
this.executor = executor;
this.resultType = resultType;
this.sort = sort;
@@ -467,6 +439,7 @@ public abstract class QueryByExampleDataFetcher<T> {
}
private static class ReactiveSingleEntityFetcher<T, R> extends QueryByExampleDataFetcher<T> implements DataFetcher<Mono<R>> {
private final ReactiveQueryByExampleExecutor<T> executor;
@@ -476,13 +449,11 @@ public abstract class QueryByExampleDataFetcher<T> {
private final Sort sort;
@SuppressWarnings({"unchecked", "rawtypes"})
ReactiveSingleEntityFetcher(ReactiveQueryByExampleExecutor<T> executor,
TypeInformation<T> domainType,
Class<R> resultType,
Sort sort,
ConversionService conversionService) {
ReactiveSingleEntityFetcher(
ReactiveQueryByExampleExecutor<T> executor, TypeInformation<T> domainType,
Class<R> resultType, Sort sort) {
super(domainType, conversionService);
super(domainType);
this.executor = executor;
this.resultType = resultType;
this.sort = sort;
@@ -511,6 +482,7 @@ public abstract class QueryByExampleDataFetcher<T> {
}
private static class ReactiveManyEntityFetcher<T, R> extends QueryByExampleDataFetcher<T> implements DataFetcher<Flux<R>> {
private final ReactiveQueryByExampleExecutor<T> executor;
@@ -520,13 +492,11 @@ public abstract class QueryByExampleDataFetcher<T> {
private final Sort sort;
@SuppressWarnings({"unchecked", "rawtypes"})
ReactiveManyEntityFetcher(ReactiveQueryByExampleExecutor<T> executor,
TypeInformation<T> domainType,
Class<R> resultType,
Sort sort,
ConversionService conversionService) {
ReactiveManyEntityFetcher(
ReactiveQueryByExampleExecutor<T> executor, TypeInformation<T> domainType,
Class<R> resultType, Sort sort) {
super(domainType, conversionService);
super(domainType);
this.executor = executor;
this.resultType = resultType;
this.sort = sort;
@@ -552,8 +522,10 @@ public abstract class QueryByExampleDataFetcher<T> {
return queryToUse.all();
});
}
}
/**
* GraphQLTypeVisitor that auto-registers Query By Example Spring Data repositories.
*/
@@ -659,4 +631,5 @@ public abstract class QueryByExampleDataFetcher<T> {
return (fetcher != null && !(fetcher instanceof PropertyDataFetcher));
}
}
}

View File

@@ -15,8 +15,11 @@
*/
/**
* Support for {@link graphql.schema.DataFetcher}s backed by Querydsl based
* Spring Data repositories.
* {@link graphql.schema.DataFetcher} implementations built on the Spring Data,
* Query by Example support.
*
* @see <a href="https://docs.spring.io/spring-data/commons/docs/current/reference/html/#query-by-example">
* Spring Data Query By Example extension</a>
*/
@NonNullApi
@NonNullFields

View File

@@ -49,6 +49,7 @@ import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.data.repository.query.FluentQuery;
import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery;
import org.springframework.data.repository.query.QueryByExampleExecutor;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.graphql.data.GraphQlRepository;
@@ -255,11 +256,10 @@ public abstract class QuerydslDataFetcher<T> {
}
/**
* Project results returned from the {@link QuerydslPredicateExecutor}
* Project results returned from the {@link QueryByExampleExecutor}
* into the target {@code projectionType}. Projection types can be
* either interfaces declaring getters for properties to expose or
* regular classes outside the entity type hierarchy for
* DTO projection.
* either interfaces with property getters to expose or regular classes
* outside the entity type hierarchy for DTO projections.
* @param projectionType projection type
* @return a new {@link Builder} instance with all previously
* configured options and {@code projectionType} applied
@@ -293,7 +293,6 @@ public abstract class QuerydslDataFetcher<T> {
/**
* Build a {@link DataFetcher} to fetch single object instances.
* @return a {@link DataFetcher} based on Querydsl to fetch one object
*/
public DataFetcher<R> single() {
return new SingleEntityFetcher<>(
@@ -302,7 +301,6 @@ public abstract class QuerydslDataFetcher<T> {
/**
* Build a {@link DataFetcher} to fetch many object instances.
* @return a {@link DataFetcher} based on Querydsl to fetch many objects
*/
public DataFetcher<Iterable<R>> many() {
return new ManyEntityFetcher<>(
@@ -354,11 +352,10 @@ public abstract class QuerydslDataFetcher<T> {
}
/**
* Project results returned from the {@link QuerydslPredicateExecutor}
* Project results returned from the {@link ReactiveQueryByExampleExecutor}
* into the target {@code projectionType}. Projection types can be
* either interfaces declaring getters for properties to expose or
* regular classes outside the entity type hierarchy for
* DTO projection.
* either interfaces with property getters to expose or regular classes
* outside the entity type hierarchy for DTO projections.
* @param projectionType projection type
* @return a new {@link Builder} instance with all previously
* configured options and {@code projectionType} applied
@@ -391,8 +388,7 @@ public abstract class QuerydslDataFetcher<T> {
}
/**
* Build a {@link DataFetcher} to fetch single object instances through {@link Mono}.
* @return a {@link DataFetcher} based on Querydsl to fetch one object
* Build a {@link DataFetcher} to fetch single object instances}.
*/
public DataFetcher<Mono<R>> single() {
return new ReactiveSingleEntityFetcher<>(
@@ -400,8 +396,7 @@ public abstract class QuerydslDataFetcher<T> {
}
/**
* Build a {@link DataFetcher} to fetch many object instances through {@link Flux}.
* @return a {@link DataFetcher} based on Querydsl to fetch many objects
* Build a {@link DataFetcher} to fetch many object instances.
*/
public DataFetcher<Flux<R>> many() {
return new ReactiveManyEntityFetcher<>(

View File

@@ -15,8 +15,11 @@
*/
/**
* Support for {@link graphql.schema.DataFetcher}s backed by Querydsl based
* Spring Data repositories.
* {@link graphql.schema.DataFetcher} implementations built on Querydsl, Spring
* Data repositories.
*
* @see <a href="https://docs.spring.io/spring-data/commons/docs/current/reference/html/#core.extensions.querydsl">
* Spring Data Querydsl extension</a>
*/
@NonNullApi
@NonNullFields

View File

@@ -4,5 +4,5 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.graphql.data.GraphQlRepository;
@GraphQlRepository
public interface BookRepository extends JpaRepository<Book, Long> {
public interface BookJpaRepository extends JpaRepository<Book, Long> {
}

View File

@@ -70,27 +70,8 @@ import static org.mockito.Mockito.when;
class QueryByExampleDataFetcherJpaTests {
@Autowired
private BookRepository repository;
private BookJpaRepository repository;
static GraphQlSetup graphQlSetup(String fieldName, DataFetcher<?> fetcher) {
return initGraphQlSetup(null).queryFetcher(fieldName, fetcher);
}
static GraphQlSetup graphQlSetup(@Nullable QueryByExampleExecutor<?> executor) {
return initGraphQlSetup(executor);
}
private static GraphQlSetup initGraphQlSetup(
@Nullable QueryByExampleExecutor<?> executor) {
GraphQLTypeVisitor visitor = QueryByExampleDataFetcher.registrationTypeVisitor(
executor != null
? Collections.singletonList(executor)
: Collections.emptyList(),
Collections.emptyList());
return GraphQlSetup.schemaResource(BookSource.schema).typeVisitor(visitor);
}
@Test
void shouldFetchSingleItems() {
@@ -137,7 +118,7 @@ class QueryByExampleDataFetcherJpaTests {
@Test
void shouldFavorExplicitWiring() {
BookRepository mockRepository = mock(BookRepository.class);
BookJpaRepository mockRepository = mock(BookJpaRepository.class);
Book book = new Book(42L, "Hitchhiker's Guide to the Galaxy", new Author(0L, "Douglas", "Adams"));
when(mockRepository.findBy(any(), any())).thenReturn(Optional.of(book));
@@ -188,6 +169,23 @@ class QueryByExampleDataFetcherJpaTests {
assertThat(actualBook.getName()).isEqualTo("The book is: Hitchhiker's Guide to the Galaxy");
}
private static GraphQlSetup graphQlSetup(String fieldName, DataFetcher<?> fetcher) {
return initGraphQlSetup(null).queryFetcher(fieldName, fetcher);
}
private static GraphQlSetup graphQlSetup(@Nullable QueryByExampleExecutor<?> executor) {
return initGraphQlSetup(executor);
}
private static GraphQlSetup initGraphQlSetup(@Nullable QueryByExampleExecutor<?> executor) {
GraphQLTypeVisitor visitor = QueryByExampleDataFetcher.registrationTypeVisitor(
executor != null ? Collections.singletonList(executor) : Collections.emptyList(),
Collections.emptyList());
return GraphQlSetup.schemaResource(BookSource.schema).typeVisitor(visitor);
}
private WebInput input(String query) {
return new WebInput(URI.create("/"), new HttpHeaders(), Collections.singletonMap("query", query), null, "1");
}
@@ -200,6 +198,7 @@ class QueryByExampleDataFetcherJpaTests {
}
static class BookDto {
private final String name;
@@ -214,6 +213,7 @@ class QueryByExampleDataFetcherJpaTests {
}
@Configuration
@EnableJpaRepositories(considerNestedRepositories = true)
static class TestConfig {
@@ -246,4 +246,5 @@ class QueryByExampleDataFetcherJpaTests {
return transactionManager;
}
}
}

View File

@@ -4,5 +4,5 @@ import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.graphql.data.GraphQlRepository;
@GraphQlRepository
public interface BookRepository extends MongoRepository<Book, String> {
public interface BookMongoRepository extends MongoRepository<Book, String> {
}

View File

@@ -4,5 +4,5 @@ import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.graphql.data.GraphQlRepository;
@GraphQlRepository
public interface ReactiveBookRepository extends ReactiveMongoRepository<Book, String> {
public interface BookReactiveMongoRepository extends ReactiveMongoRepository<Book, String> {
}

View File

@@ -64,32 +64,15 @@ import static org.mockito.Mockito.when;
*/
@ExtendWith(SpringExtension.class)
@ContextConfiguration
@Testcontainers
@Testcontainers(disabledWithoutDocker = true)
class QueryByExampleDataFetcherMongoDbTests {
@Container
static MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:4.0.10"));
@Autowired
private BookRepository repository;
private BookMongoRepository repository;
static GraphQlSetup graphQlSetup(String fieldName, DataFetcher<?> fetcher) {
return initGraphQlSetup(null).queryFetcher(fieldName, fetcher);
}
static GraphQlSetup graphQlSetup(@Nullable QueryByExampleExecutor<?> executor) {
return initGraphQlSetup(executor);
}
private static GraphQlSetup initGraphQlSetup(
@Nullable QueryByExampleExecutor<?> executor) {
GraphQLTypeVisitor visitor = QueryByExampleDataFetcher.registrationTypeVisitor(
(executor != null ? Collections.singletonList(executor) : Collections.emptyList()),
Collections.emptyList());
return GraphQlSetup.schemaResource(BookSource.schema).typeVisitor(visitor);
}
@Test
void shouldFetchSingleItems() {
@@ -134,7 +117,7 @@ class QueryByExampleDataFetcherMongoDbTests {
@Test
void shouldFavorExplicitWiring() {
BookRepository mockRepository = mock(BookRepository.class);
BookMongoRepository mockRepository = mock(BookMongoRepository.class);
Book book = new Book("42", "Hitchhiker's Guide to the Galaxy", new Author("0", "Douglas", "Adams"));
when(mockRepository.findBy(any(), any())).thenReturn(Optional.of(book));
@@ -184,16 +167,35 @@ class QueryByExampleDataFetcherMongoDbTests {
assertThat(actualBook.getName()).isEqualTo("The book is: Hitchhiker's Guide to the Galaxy");
}
private static GraphQlSetup graphQlSetup(String fieldName, DataFetcher<?> fetcher) {
return initGraphQlSetup(null).queryFetcher(fieldName, fetcher);
}
private static GraphQlSetup graphQlSetup(@Nullable QueryByExampleExecutor<?> executor) {
return initGraphQlSetup(executor);
}
private static GraphQlSetup initGraphQlSetup(@Nullable QueryByExampleExecutor<?> executor) {
GraphQLTypeVisitor visitor = QueryByExampleDataFetcher.registrationTypeVisitor(
(executor != null ? Collections.singletonList(executor) : Collections.emptyList()),
Collections.emptyList());
return GraphQlSetup.schemaResource(BookSource.schema).typeVisitor(visitor);
}
private WebInput input(String query) {
return new WebInput(URI.create("/"), new HttpHeaders(), Collections.singletonMap("query", query), null, "1");
}
interface BookProjection {
@Value("#{target.name + ' by ' + target.author.firstName + ' ' + target.author.lastName}")
String getName();
}
static class BookDto {
private final String name;
@@ -207,6 +209,7 @@ class QueryByExampleDataFetcherMongoDbTests {
}
}
@Configuration
@EnableMongoRepositories(considerNestedRepositories = true)
static class TestConfig {
@@ -215,8 +218,8 @@ class QueryByExampleDataFetcherMongoDbTests {
MongoTemplate mongoTemplate() {
return new MongoTemplate(MongoClients.create(String.format("mongodb://%s:%d",
mongoDBContainer.getContainerIpAddress(),
mongoDBContainer.getFirstMappedPort())),
"test");
mongoDBContainer.getFirstMappedPort())), "test");
}
}
}

View File

@@ -60,32 +60,15 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
@ExtendWith(SpringExtension.class)
@ContextConfiguration
@Testcontainers
@Testcontainers(disabledWithoutDocker = true)
class QueryByExampleDataFetcherReactiveMongoDbTests {
@Container
static MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:4.0.10"));
@Autowired
private ReactiveBookRepository repository;
private BookReactiveMongoRepository repository;
static GraphQlSetup graphQlSetup(String fieldName, DataFetcher<?> fetcher) {
return initGraphQlSetup(null).queryFetcher(fieldName, fetcher);
}
static GraphQlSetup graphQlSetup(@Nullable ReactiveQueryByExampleExecutor<?> executor) {
return initGraphQlSetup(executor);
}
private static GraphQlSetup initGraphQlSetup(
@Nullable ReactiveQueryByExampleExecutor<?> reactiveExecutor) {
GraphQLTypeVisitor visitor = QueryByExampleDataFetcher.registrationTypeVisitor(
Collections.emptyList(),
(reactiveExecutor != null ? Collections.singletonList(reactiveExecutor) : Collections.emptyList()));
return GraphQlSetup.schemaResource(BookSource.schema).typeVisitor(visitor);
}
@Test
void shouldReactivelyFetchSingleItems() {
@@ -156,16 +139,36 @@ class QueryByExampleDataFetcherReactiveMongoDbTests {
tester.accept(graphQlSetup(repository));
}
private static GraphQlSetup graphQlSetup(String fieldName, DataFetcher<?> fetcher) {
return initGraphQlSetup(null).queryFetcher(fieldName, fetcher);
}
private static GraphQlSetup graphQlSetup(@Nullable ReactiveQueryByExampleExecutor<?> executor) {
return initGraphQlSetup(executor);
}
private static GraphQlSetup initGraphQlSetup(@Nullable ReactiveQueryByExampleExecutor<?> executor) {
GraphQLTypeVisitor visitor = QueryByExampleDataFetcher.registrationTypeVisitor(
Collections.emptyList(),
(executor != null ? Collections.singletonList(executor) : Collections.emptyList()));
return GraphQlSetup.schemaResource(BookSource.schema).typeVisitor(visitor);
}
private WebInput input(String query) {
return new WebInput(URI.create("/"), new HttpHeaders(), Collections.singletonMap("query", query), null, "1");
}
interface BookProjection {
@Value("#{target.name + ' by ' + target.author.firstName + ' ' + target.author.lastName}")
String getName();
}
static class BookDto {
private final String name;
@@ -177,8 +180,10 @@ class QueryByExampleDataFetcherReactiveMongoDbTests {
public String getName() {
return "The book is: " + name;
}
}
@Configuration
@EnableReactiveMongoRepositories(considerNestedRepositories = true)
static class TestConfig {
@@ -190,5 +195,7 @@ class QueryByExampleDataFetcherReactiveMongoDbTests {
mongoDBContainer.getFirstMappedPort())),
"test");
}
}
}