Add tests for Neo4j

Also mention Neo4j as compatible with the reactive stack
and the QuerydslPredicateExecutor.

See gh-734
This commit is contained in:
Gerrit Meier
2023-06-20 11:33:56 +02:00
committed by rstoyanchev
parent e9f68948ec
commit f4c5003376
8 changed files with 703 additions and 2 deletions

View File

@@ -50,14 +50,14 @@ You can now register the above `DataFetcher` through a
<<execution.graphqlsource.runtimewiring-configurer>>.
The `DataFetcher` builds a Querydsl `Predicate` from GraphQL arguments, and uses it to
fetch data. Spring Data supports `QuerydslPredicateExecutor` for JPA, MongoDB, and LDAP.
fetch data. Spring Data supports `QuerydslPredicateExecutor` for JPA, MongoDB, Neo4j, and LDAP.
NOTE: For a single argument that is a GraphQL input type, `QuerydslDataFetcher` nests one
level down, and uses the values from the argument sub-map.
If the repository is `ReactiveQuerydslPredicateExecutor`, the builder returns
`DataFetcher<Mono<Account>>` or `DataFetcher<Flux<Account>>`. Spring Data supports this
variant for MongoDB.
variant for MongoDB and Neo4j.
[[data.querydsl.build]]

View File

@@ -51,9 +51,11 @@ dependencies {
testImplementation 'org.hibernate:hibernate-core'
testImplementation 'org.hibernate.validator:hibernate-validator'
testImplementation 'org.springframework.data:spring-data-mongodb'
testImplementation 'org.springframework.data:spring-data-neo4j:7.1.1'
testImplementation 'org.mongodb:mongodb-driver-sync'
testImplementation 'org.mongodb:mongodb-driver-reactivestreams'
testImplementation 'org.testcontainers:mongodb'
testImplementation 'org.testcontainers:neo4j'
testImplementation 'org.testcontainers:junit-jupiter'
testImplementation 'org.springframework.security:spring-security-core'
testImplementation 'com.querydsl:querydsl-core'

View File

@@ -0,0 +1,57 @@
package org.springframework.graphql.data.query.neo4j;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;
/**
* @author Gerrit Meier
*/
@Node
public class Author {
@Id
String id;
String firstName;
String lastName;
// needed for response mapping
public Author() {
}
public Author(String id, String firstName, String lastName) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
}
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return this.lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFullName() {
return this.firstName + " " + this.lastName;
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2020-2021 the original author or 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
*
* https://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 org.springframework.graphql.data.query.neo4j;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.data.neo4j.core.schema.Relationship;
@Node
public class Book {
@Id
String id;
String name;
@Relationship("AUTHOR")
Author author;
// needed for response mapping
public Book() {
}
public Book(String id, String name, Author author) {
this.id = id;
this.name = name;
this.author = author;
}
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Author getAuthor() {
return this.author;
}
public void setAuthor(Author author) {
this.author = author;
}
}

View File

@@ -0,0 +1,11 @@
package org.springframework.graphql.data.query.neo4j;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.graphql.data.GraphQlRepository;
/**
* @author Gerrit Meier
*/
@GraphQlRepository
public interface BookNeo4jRepository extends Neo4jRepository<Book, String> {
}

View File

@@ -0,0 +1,11 @@
package org.springframework.graphql.data.query.neo4j;
import org.springframework.data.neo4j.repository.ReactiveNeo4jRepository;
import org.springframework.graphql.data.GraphQlRepository;
/**
* @author Gerrit Meier
*/
@GraphQlRepository
public interface BookReactiveNeo4jRepository extends ReactiveNeo4jRepository<Book, String> {
}

View File

@@ -0,0 +1,279 @@
/*
* Copyright 2002-2023 the original author or 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 org.springframework.graphql.data.query.neo4j;
import graphql.schema.DataFetcher;
import org.junit.jupiter.api.Test;
import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.Driver;
import org.neo4j.driver.GraphDatabase;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.neo4j.config.AbstractNeo4jConfig;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
import org.springframework.data.repository.query.QueryByExampleExecutor;
import org.springframework.graphql.BookSource;
import org.springframework.graphql.GraphQlSetup;
import org.springframework.graphql.ResponseHelper;
import org.springframework.graphql.data.query.QueryByExampleDataFetcher;
import org.springframework.graphql.data.query.ScrollPositionCursorStrategy;
import org.springframework.graphql.data.query.ScrollSubrange;
import org.springframework.graphql.data.query.WindowConnectionAdapter;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
import org.springframework.graphql.server.WebGraphQlHandler;
import org.springframework.graphql.server.WebGraphQlRequest;
import org.springframework.graphql.server.WebGraphQlResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Unit tests for {@link QueryByExampleDataFetcher}.
*/
@SpringJUnitConfig
@Testcontainers(disabledWithoutDocker = true)
class QueryByExampleDataFetcherNeo4jTests {
@Container
static Neo4jContainer neo4jContainer = new Neo4jContainer(DockerImageName.parse("neo4j:5")).withRandomPassword();
@Autowired
private BookNeo4jRepository repository;
@Test
void shouldFetchSingleItems() {
Book book = new Book("42", "Hitchhiker's Guide to the Galaxy", new Author("0", "Douglas", "Adams"));
repository.save(book);
Consumer<GraphQlSetup> tester = setup -> {
WebGraphQlRequest request = request("{ bookById(id: 42) {name}}");
Mono<WebGraphQlResponse> responseMono = setup.toWebGraphQlHandler().handleRequest(request);
Book actualBook = ResponseHelper.forResponse(responseMono).toEntity("bookById", Book.class);
assertThat(actualBook.getName()).isEqualTo(book.getName());
};
// explicit wiring
tester.accept(graphQlSetup("bookById", QueryByExampleDataFetcher.builder(repository).single()));
// auto registration
tester.accept(graphQlSetup(repository));
}
@Test
void shouldFetchMultipleItems() {
Book book1 = new Book("42", "Hitchhiker's Guide to the Galaxy", new Author("0", "Douglas", "Adams"));
Book book2 = new Book("53", "Breaking Bad", new Author("0", "", "Heisenberg"));
repository.saveAll(Arrays.asList(book1, book2));
Consumer<GraphQlSetup> tester = graphQlSetup -> {
WebGraphQlRequest request = request("{ books {name}}");
Mono<WebGraphQlResponse> responseMono = graphQlSetup.toWebGraphQlHandler().handleRequest(request);
List<String> names = ResponseHelper.forResponse(responseMono).toList("books", Book.class)
.stream().map(Book::getName).collect(Collectors.toList());
assertThat(names).containsExactlyInAnyOrder(book1.getName(), book2.getName());
};
// explicit wiring
tester.accept(graphQlSetup("books", QueryByExampleDataFetcher.builder(repository).many()));
// auto registration
tester.accept(graphQlSetup(repository));
}
@Test
void shouldFetchWindow() {
repository.saveAll(List.of(
new Book("1", "Nineteen Eighty-Four", new Author("0", "George", "Orwell")),
new Book("2", "The Great Gatsby", new Author("0", "F. Scott", "Fitzgerald")),
new Book("3", "Catch-22", new Author("0", "Joseph", "Heller")),
new Book("42", "Hitchhiker's Guide to the Galaxy", new Author("0", "Douglas", "Adams")),
new Book("53", "Breaking Bad", new Author("0", "", "Heisenberg"))));
Consumer<GraphQlSetup> tester = graphQlSetup -> {
Mono<WebGraphQlResponse> response = graphQlSetup
.toWebGraphQlHandler()
.handleRequest(request(BookSource.booksConnectionQuery("first:2, after:\"O_3\"")));
List<Map<String, Object>> edges = ResponseHelper.forResponse(response).toEntity("books.edges", List.class);
assertThat(edges.size()).isEqualTo(2);
assertThat(edges.get(0).get("cursor")).isEqualTo("O_4");
assertThat(edges.get(1).get("cursor")).isEqualTo("O_5");
Map<String, Object> pageInfo = ResponseHelper.forResponse(response).toEntity("books.pageInfo", Map.class);
assertThat(pageInfo.size()).isEqualTo(4);
assertThat(pageInfo.get("startCursor")).isEqualTo("O_4");
assertThat(pageInfo.get("endCursor")).isEqualTo("O_5");
assertThat(pageInfo.get("hasPreviousPage")).isEqualTo(true);
assertThat(pageInfo.get("hasNextPage")).isEqualTo(false);
};
// explicit wiring
ScrollPositionCursorStrategy cursorStrategy = new ScrollPositionCursorStrategy();
DataFetcher<Iterable<Book>> dataFetcher =
QueryByExampleDataFetcher.builder(repository).cursorStrategy(cursorStrategy).scrollable();
GraphQlSetup graphQlSetup = paginationSetup(cursorStrategy).queryFetcher("books", dataFetcher);
tester.accept(graphQlSetup);
// auto registration
graphQlSetup = paginationSetup(cursorStrategy).runtimeWiring(createRuntimeWiringConfigurer(repository));
tester.accept(graphQlSetup);
}
@Test
void shouldFavorExplicitWiring() {
BookNeo4jRepository mockRepository = mock(BookNeo4jRepository.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));
// 1) Automatic registration only
WebGraphQlHandler handler = graphQlSetup(mockRepository).toWebGraphQlHandler();
Mono<WebGraphQlResponse> responseMono = handler.handleRequest(request("{ bookById(id: 1) {name}}"));
Book actualBook = ResponseHelper.forResponse(responseMono).toEntity("bookById", Book.class);
assertThat(actualBook.getName()).isEqualTo("Hitchhiker's Guide to the Galaxy");
// 2) Automatic registration and explicit wiring
handler = graphQlSetup(mockRepository)
.queryFetcher("bookById", env -> new Book("53", "Breaking Bad", new Author("0", "", "Heisenberg")))
.toWebGraphQlHandler();
responseMono = handler.handleRequest(request("{ bookById(id: 1) {name}}"));
actualBook = ResponseHelper.forResponse(responseMono).toEntity("bookById", Book.class);
assertThat(actualBook.getName()).isEqualTo("Breaking Bad");
}
@Test
void shouldFetchSingleItemsWithInterfaceProjection() {
Book book = new Book("42", "Hitchhiker's Guide to the Galaxy", new Author("0", "Douglas", "Adams"));
repository.save(book);
DataFetcher<?> fetcher = QueryByExampleDataFetcher.builder(repository).projectAs(BookProjection.class).single();
WebGraphQlHandler handler = graphQlSetup("bookById", fetcher).toWebGraphQlHandler();
Mono<WebGraphQlResponse> responseMono = handler.handleRequest(request("{ bookById(id: 42) {name}}"));
Book actualBook = ResponseHelper.forResponse(responseMono).toEntity("bookById", Book.class);
assertThat(actualBook.getName()).isEqualTo("Hitchhiker's Guide to the Galaxy by Douglas Adams");
}
@Test
void shouldFetchSingleItemsWithDtoProjection() {
Book book = new Book("42", "Hitchhiker's Guide to the Galaxy", new Author("0", "Douglas", "Adams"));
repository.save(book);
DataFetcher<?> fetcher = QueryByExampleDataFetcher.builder(repository).projectAs(BookDto.class).single();
WebGraphQlHandler handler = graphQlSetup("bookById", fetcher).toWebGraphQlHandler();
Mono<WebGraphQlResponse> responseMono = handler.handleRequest(request("{ bookById(id: 42) {name}}"));
Book actualBook = ResponseHelper.forResponse(responseMono).toEntity("bookById", Book.class);
assertThat(actualBook.getName()).isEqualTo("The book is: Hitchhiker's Guide to the Galaxy");
}
private static GraphQlSetup graphQlSetup(String fieldName, DataFetcher<?> fetcher) {
return GraphQlSetup.schemaResource(BookSource.schema).queryFetcher(fieldName, fetcher);
}
private static GraphQlSetup graphQlSetup(@Nullable QueryByExampleExecutor<?> executor) {
return GraphQlSetup.schemaResource(BookSource.schema)
.runtimeWiring(createRuntimeWiringConfigurer(executor));
}
private static GraphQlSetup paginationSetup(ScrollPositionCursorStrategy cursorStrategy) {
return GraphQlSetup.schemaResource(BookSource.paginationSchema)
.connectionSupport(new WindowConnectionAdapter(cursorStrategy));
}
private static RuntimeWiringConfigurer createRuntimeWiringConfigurer(QueryByExampleExecutor<?> executor) {
return QueryByExampleDataFetcher.autoRegistrationConfigurer(
(executor != null ? Collections.singletonList(executor) : Collections.emptyList()),
Collections.emptyList(),
new ScrollPositionCursorStrategy(),
new ScrollSubrange(ScrollPosition.offset(), 10, true));
}
private WebGraphQlRequest request(String query) {
return new WebGraphQlRequest(
URI.create("/"), new HttpHeaders(), null, Collections.emptyMap(),
Collections.singletonMap("query", query), "1", null);
}
interface BookProjection {
@Value("#{target.name + ' by ' + target.author.firstName + ' ' + target.author.lastName}")
String getName();
}
static class BookDto {
private final String name;
public BookDto(String name) {
this.name = name;
}
public String getName() {
return "The book is: " + name;
}
}
@Configuration
@EnableNeo4jRepositories(considerNestedRepositories = true)
static class TestConfig extends AbstractNeo4jConfig {
@Bean
public Driver driver() {
return GraphDatabase.driver(neo4jContainer.getBoltUrl(), AuthTokens.basic("neo4j", neo4jContainer.getAdminPassword()));
}
}
}

View File

@@ -0,0 +1,272 @@
/*
* Copyright 2002-2023 the original author or 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 org.springframework.graphql.data.query.neo4j;
import graphql.schema.DataFetcher;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.neo4j.cypherdsl.core.renderer.Dialect;
import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.Driver;
import org.neo4j.driver.GraphDatabase;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.neo4j.config.AbstractReactiveNeo4jConfig;
import org.springframework.data.neo4j.repository.config.EnableReactiveNeo4jRepositories;
import org.springframework.data.repository.query.ReactiveQueryByExampleExecutor;
import org.springframework.graphql.BookSource;
import org.springframework.graphql.GraphQlSetup;
import org.springframework.graphql.ResponseHelper;
import org.springframework.graphql.data.query.QueryByExampleDataFetcher;
import org.springframework.graphql.data.query.ScrollPositionCursorStrategy;
import org.springframework.graphql.data.query.ScrollSubrange;
import org.springframework.graphql.data.query.WindowConnectionAdapter;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
import org.springframework.graphql.server.WebGraphQlHandler;
import org.springframework.graphql.server.WebGraphQlRequest;
import org.springframework.graphql.server.WebGraphQlResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Unit tests for {@link QueryByExampleDataFetcher}.
*/
@SpringJUnitConfig
@Testcontainers(disabledWithoutDocker = true)
class QueryByExampleDataFetcherReactiveNeo4jDbTests {
@Container
static Neo4jContainer neo4jContainer = new Neo4jContainer(DockerImageName.parse("neo4j:5")).withRandomPassword();
@Autowired
private BookReactiveNeo4jRepository repository;
@Autowired
private Driver driver;
@BeforeEach
void cleanDatabase() {
try (var session = driver.session()) {
session.run("MATCH (n) detach delete n").consume();
}
}
@Test
void shouldReactivelyFetchSingleItems() {
Book book = new Book("42", "Hitchhiker's Guide to the Galaxy", new Author("0", "Douglas", "Adams"));
repository.save(book).block();
Consumer<GraphQlSetup> tester = setup -> {
WebGraphQlRequest request = request("{ bookById(id: 42) {name}}");
Mono<WebGraphQlResponse> responseMono = setup.toWebGraphQlHandler().handleRequest(request);
Book actualBook = ResponseHelper.forResponse(responseMono).toEntity("bookById", Book.class);
assertThat(actualBook.getName()).isEqualTo(book.getName());
};
// explicit wiring
tester.accept(graphQlSetup("bookById", QueryByExampleDataFetcher.builder(repository).single()));
// auto registration
tester.accept(graphQlSetup(repository));
}
@Test
void shouldFetchSingleItemsReactivelyWithInterfaceProjection() {
Book book = new Book("42", "Hitchhiker's Guide to the Galaxy", new Author("0", "Douglas", "Adams"));
repository.save(book).block();
DataFetcher<?> fetcher = QueryByExampleDataFetcher.builder(repository).projectAs(BookProjection.class).single();
WebGraphQlHandler handler = graphQlSetup("bookById", fetcher).toWebGraphQlHandler();
Mono<WebGraphQlResponse> responseMono = handler.handleRequest(request("{ bookById(id: 42) {name}}"));
Book actualBook = ResponseHelper.forResponse(responseMono).toEntity("bookById", Book.class);
assertThat(actualBook.getName()).isEqualTo("Hitchhiker's Guide to the Galaxy by Douglas Adams");
}
@Test
void shouldFetchSingleItemsReactivelyWithDtoProjection() {
Book book = new Book("42", "Hitchhiker's Guide to the Galaxy", new Author("0", "Douglas", "Adams"));
repository.save(book).block();
DataFetcher<?> fetcher = QueryByExampleDataFetcher.builder(repository).projectAs(BookDto.class).single();
WebGraphQlHandler handler = graphQlSetup("bookById", fetcher).toWebGraphQlHandler();
Mono<WebGraphQlResponse> responseMono = handler.handleRequest(request("{ bookById(id: 42) {name}}"));
Book actualBook = ResponseHelper.forResponse(responseMono).toEntity("bookById", Book.class);
assertThat(actualBook.getName()).isEqualTo("The book is: Hitchhiker's Guide to the Galaxy");
}
@Test
void shouldReactivelyFetchMultipleItems() {
Book book1 = new Book("42", "Hitchhiker's Guide to the Galaxy", new Author("0", "Douglas", "Adams"));
Book book2 = new Book("53", "Breaking Bad", new Author("0", "", "Heisenberg"));
repository.saveAll(Flux.just(book1, book2)).blockLast();
Consumer<GraphQlSetup> tester = setup -> {
WebGraphQlRequest request = request("{ books {name}}");
Mono<WebGraphQlResponse> responseMono = setup.toWebGraphQlHandler().handleRequest(request);
List<String> names = ResponseHelper.forResponse(responseMono).toList("books", Book.class)
.stream().map(Book::getName).collect(Collectors.toList());
assertThat(names).containsExactlyInAnyOrder("Breaking Bad", "Hitchhiker's Guide to the Galaxy");
};
// explicit wiring
tester.accept(graphQlSetup("books", QueryByExampleDataFetcher.builder(repository).many()));
// auto registration
tester.accept(graphQlSetup(repository));
}
@Test
void shouldFetchWindow() {
repository.saveAll(Flux.just(
new Book("1", "Nineteen Eighty-Four", new Author("0", "George", "Orwell")),
new Book("2", "The Great Gatsby", new Author("1", "F. Scott", "Fitzgerald")),
new Book("3", "Catch-22", new Author("2", "Joseph", "Heller")),
new Book("42", "Hitchhiker's Guide to the Galaxy", new Author("3", "Douglas", "Adams")),
new Book("53", "Breaking Bad", new Author("4", "", "Heisenberg"))))
.blockLast();
Consumer<GraphQlSetup> tester = graphQlSetup -> {
Mono<WebGraphQlResponse> response = graphQlSetup
.toWebGraphQlHandler()
.handleRequest(request(BookSource.booksConnectionQuery("first:2, after:\"O_3\"")));
List<Map<String, Object>> edges = ResponseHelper.forResponse(response).toEntity("books.edges", List.class);
assertThat(edges.size()).isEqualTo(2);
assertThat(edges.get(0).get("cursor")).isEqualTo("O_4");
assertThat(edges.get(1).get("cursor")).isEqualTo("O_5");
Map<String, Object> pageInfo = ResponseHelper.forResponse(response).toEntity("books.pageInfo", Map.class);
assertThat(pageInfo.size()).isEqualTo(4);
assertThat(pageInfo.get("startCursor")).isEqualTo("O_4");
assertThat(pageInfo.get("endCursor")).isEqualTo("O_5");
assertThat(pageInfo.get("hasPreviousPage")).isEqualTo(true);
assertThat(pageInfo.get("hasNextPage")).isEqualTo(false);
};
// explicit wiring
ScrollPositionCursorStrategy cursorStrategy = new ScrollPositionCursorStrategy();
DataFetcher<Mono<Iterable<Book>>> dataFetcher =
QueryByExampleDataFetcher.builder(repository).cursorStrategy(cursorStrategy).scrollable();
GraphQlSetup graphQlSetup = paginationSetup(cursorStrategy).queryFetcher("books", dataFetcher);
tester.accept(graphQlSetup);
// auto registration
graphQlSetup = paginationSetup(cursorStrategy).runtimeWiring(createRuntimeWiringConfigurer(repository));
tester.accept(graphQlSetup);
}
private static GraphQlSetup graphQlSetup(String fieldName, DataFetcher<?> fetcher) {
return GraphQlSetup.schemaResource(BookSource.schema).queryFetcher(fieldName, fetcher);
}
private static GraphQlSetup graphQlSetup(@Nullable ReactiveQueryByExampleExecutor<?> executor) {
return GraphQlSetup.schemaResource(BookSource.schema)
.runtimeWiring(createRuntimeWiringConfigurer(executor));
}
private static GraphQlSetup paginationSetup(ScrollPositionCursorStrategy cursorStrategy) {
return GraphQlSetup.schemaResource(BookSource.paginationSchema)
.connectionSupport(new WindowConnectionAdapter(cursorStrategy));
}
private static RuntimeWiringConfigurer createRuntimeWiringConfigurer(ReactiveQueryByExampleExecutor<?> executor) {
return QueryByExampleDataFetcher.autoRegistrationConfigurer(
Collections.emptyList(),
(executor != null ? Collections.singletonList(executor) : Collections.emptyList()),
new ScrollPositionCursorStrategy(),
new ScrollSubrange(ScrollPosition.offset(), 10, true));
}
private WebGraphQlRequest request(String query) {
return new WebGraphQlRequest(
URI.create("/"), new HttpHeaders(), null, Collections.emptyMap(),
Collections.singletonMap("query", query), "1", null);
}
interface BookProjection {
@Value("#{target.name + ' by ' + target.author.firstName + ' ' + target.author.lastName}")
String getName();
}
static class BookDto {
private final String name;
public BookDto(String name) {
this.name = name;
}
public String getName() {
return "The book is: " + name;
}
}
@Configuration
@EnableReactiveNeo4jRepositories(considerNestedRepositories = true)
static class TestConfig extends AbstractReactiveNeo4jConfig {
@Bean
public Driver driver() {
return GraphDatabase.driver(neo4jContainer.getBoltUrl(), AuthTokens.basic("neo4j", neo4jContainer.getAdminPassword()));
}
@Bean
public org.neo4j.cypherdsl.core.renderer.Configuration cypherDslConfiguration() {
return org.neo4j.cypherdsl.core.renderer.Configuration.newConfig()
.withDialect(Dialect.NEO4J_5)
.build();
}
}
}