Skip to content

Commit 8100650

Browse files
committed
Improve RSocket tests and minor refactoring
See gh-339
1 parent a5a5594 commit 8100650

File tree

8 files changed

+238
-38
lines changed

8 files changed

+238
-38
lines changed

spring-graphql/src/main/java/org/springframework/graphql/client/RSocketGraphQlTransport.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ public Mono<GraphQlResponse> execute(GraphQlRequest request) {
8383
public Flux<GraphQlResponse> executeSubscription(GraphQlRequest request) {
8484
return this.rsocketRequester.route(this.route).data(request.toMap())
8585
.retrieveFlux(MAP_TYPE)
86-
.doOnError(ex -> System.out.println(ex))
8786
.onErrorResume(RejectedException.class, ex -> Flux.error(decodeErrors(request, ex)))
8887
.map(ResponseMapGraphQlResponse::new);
8988
}

spring-graphql/src/main/java/org/springframework/graphql/server/DefaultWebGraphQlHandlerBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public WebGraphQlHandler build() {
9393

9494
Chain executionChain = this.interceptors.stream()
9595
.reduce(WebGraphQlInterceptor::andThen)
96-
.map(interceptor -> (Chain) (request) -> interceptor.intercept(request, endOfChain))
96+
.map(interceptor -> interceptor.apply(endOfChain))
9797
.orElse(endOfChain);
9898

9999
return new WebGraphQlHandler() {

spring-graphql/src/main/java/org/springframework/graphql/server/GraphQlRSocketHandler.java

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -102,20 +102,16 @@ public GraphQlRSocketHandler(
102102
Assert.notNull(graphQlService, "ExecutionGraphQlService is required");
103103
Assert.notNull(jsonEncoder, "JSON Encoder is required");
104104

105-
this.executionChain = initExecutionChain(graphQlService, interceptors);
105+
this.executionChain = initChain(graphQlService, interceptors);
106106
this.jsonEncoder = jsonEncoder;
107107
}
108108

109-
private static Chain initExecutionChain(
110-
ExecutionGraphQlService graphQlService, List<RSocketGraphQlInterceptor> interceptors) {
111-
112-
Chain endOfChain = request ->
113-
graphQlService.execute(request).map(RSocketGraphQlResponse::new);
114-
109+
private static Chain initChain(ExecutionGraphQlService service, List<RSocketGraphQlInterceptor> interceptors) {
110+
Chain endOfChain = request -> service.execute(request).map(RSocketGraphQlResponse::new);
115111
return interceptors.isEmpty() ? endOfChain :
116112
interceptors.stream()
117113
.reduce(RSocketGraphQlInterceptor::andThen)
118-
.map(interceptor -> (Chain) request -> interceptor.intercept(request, endOfChain))
114+
.map(interceptor -> interceptor.apply(endOfChain))
119115
.orElse(endOfChain);
120116
}
121117

spring-graphql/src/main/java/org/springframework/graphql/server/RSocketGraphQlInterceptor.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,15 @@ default RSocketGraphQlInterceptor andThen(RSocketGraphQlInterceptor nextIntercep
5656
return (request, chain) -> intercept(request, nextRequest -> nextInterceptor.intercept(nextRequest, chain));
5757
}
5858

59+
/**
60+
* Apply this interceptor to the given {@code Chain} resulting in an intercepted chain.
61+
* @param chain the chain to add interception around
62+
* @return a new chain instance
63+
*/
64+
default Chain apply(Chain chain) {
65+
return request -> intercept(request, chain);
66+
}
67+
5968

6069
/**
6170
* Contract for delegation to the rest of the chain.

spring-graphql/src/main/java/org/springframework/graphql/server/WebGraphQlInterceptor.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,15 @@ default WebGraphQlInterceptor andThen(WebGraphQlInterceptor nextInterceptor) {
6060
return (request, chain) -> intercept(request, nextRequest -> nextInterceptor.intercept(nextRequest, chain));
6161
}
6262

63+
/**
64+
* Apply this interceptor to the given {@code Chain} resulting in an intercepted chain.
65+
* @param chain the chain to add interception around
66+
* @return a new chain instance
67+
*/
68+
default Chain apply(Chain chain) {
69+
return request -> intercept(request, chain);
70+
}
71+
6372

6473
/**
6574
* Contract for delegation to the rest of the chain.

spring-graphql/src/test/java/org/springframework/graphql/client/RSocketGraphQlClientTests.java renamed to spring-graphql/src/test/java/org/springframework/graphql/client/RSocketGraphQlClientBuilderTests.java

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,11 @@
1919
import java.time.Duration;
2020
import java.util.Collections;
2121
import java.util.HashMap;
22-
import java.util.List;
2322
import java.util.Map;
2423

2524
import graphql.ExecutionInput;
2625
import graphql.ExecutionResult;
2726
import graphql.ExecutionResultImpl;
28-
import graphql.GraphQLError;
29-
import graphql.GraphqlErrorBuilder;
3027
import io.rsocket.Closeable;
3128
import io.rsocket.SocketAcceptor;
3229
import io.rsocket.core.RSocketServer;
@@ -36,14 +33,12 @@
3633
import org.junit.jupiter.api.Test;
3734
import reactor.core.publisher.Flux;
3835
import reactor.core.publisher.Mono;
39-
import reactor.test.StepVerifier;
4036

4137
import org.springframework.graphql.ExecutionGraphQlResponse;
4238
import org.springframework.graphql.ExecutionGraphQlService;
4339
import org.springframework.graphql.GraphQlRequest;
44-
import org.springframework.graphql.ResponseError;
45-
import org.springframework.graphql.support.DefaultExecutionGraphQlResponse;
4640
import org.springframework.graphql.server.GraphQlRSocketHandler;
41+
import org.springframework.graphql.support.DefaultExecutionGraphQlResponse;
4742
import org.springframework.http.codec.json.Jackson2JsonDecoder;
4843
import org.springframework.http.codec.json.Jackson2JsonEncoder;
4944
import org.springframework.lang.Nullable;
@@ -63,7 +58,7 @@
6358
*
6459
* @author Rossen Stoyanchev
6560
*/
66-
public class RSocketGraphQlClientTests {
61+
public class RSocketGraphQlClientBuilderTests {
6762

6863
private static final String DOCUMENT = "{ Query }";
6964

@@ -97,27 +92,6 @@ void mutate() {
9792
assertThat(request).isNotNull();
9893
}
9994

100-
@Test
101-
void subscriptionError() {
102-
103-
String document = "subscription { greetings }";
104-
GraphQLError error = GraphqlErrorBuilder.newError().message("boo").build();
105-
ExecutionResult result = ExecutionResultImpl.newExecutionResult().addError(error).build();
106-
this.builderSetup.setMockResponse(document, result);
107-
108-
Flux<ClientGraphQlResponse> responseFlux = this.builderSetup.initBuilder().build()
109-
.document(document).executeSubscription();
110-
111-
StepVerifier.create(responseFlux)
112-
.expectErrorSatisfies(ex -> {
113-
assertThat(ex).isInstanceOf(SubscriptionErrorException.class);
114-
List<ResponseError> errors = ((SubscriptionErrorException) ex).getErrors();
115-
assertThat(errors).hasSize(1);
116-
assertThat(errors.get(0).getMessage()).isEqualTo("boo");
117-
})
118-
.verify(TIMEOUT);
119-
}
120-
12195

12296
private static class BuilderSetup {
12397

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.graphql.client;
18+
19+
import java.time.Duration;
20+
import java.util.List;
21+
22+
import io.rsocket.Closeable;
23+
import io.rsocket.SocketAcceptor;
24+
import io.rsocket.core.RSocketServer;
25+
import io.rsocket.exceptions.RejectedException;
26+
import io.rsocket.transport.local.LocalClientTransport;
27+
import io.rsocket.transport.local.LocalServerTransport;
28+
import org.junit.jupiter.api.AfterEach;
29+
import org.junit.jupiter.api.Test;
30+
import reactor.core.publisher.Flux;
31+
import reactor.test.StepVerifier;
32+
33+
import org.springframework.graphql.GraphQlResponse;
34+
import org.springframework.graphql.ResponseError;
35+
import org.springframework.graphql.support.DefaultGraphQlRequest;
36+
import org.springframework.http.codec.json.Jackson2JsonDecoder;
37+
import org.springframework.http.codec.json.Jackson2JsonEncoder;
38+
import org.springframework.lang.Nullable;
39+
import org.springframework.messaging.rsocket.RSocketRequester;
40+
import org.springframework.messaging.rsocket.RSocketStrategies;
41+
42+
import static org.assertj.core.api.Assertions.assertThat;
43+
44+
/**
45+
* Tests for {@link RSocketGraphQlTransport} connecting to a
46+
* {@link LocalServerTransport} and receiving stubbed responses via {@link SocketAcceptor}.
47+
*
48+
* @author Rossen Stoyanchev
49+
*/
50+
public class RSocketGraphQlTransportTests {
51+
52+
private static final Jackson2JsonEncoder jsonEncoder = new Jackson2JsonEncoder();
53+
54+
private static final Jackson2JsonDecoder jsonDecoder = new Jackson2JsonDecoder();
55+
56+
57+
@Nullable
58+
private Closeable server;
59+
60+
61+
@AfterEach
62+
void tearDown() {
63+
if (this.server != null) {
64+
this.server.dispose();
65+
}
66+
}
67+
68+
69+
@Test
70+
void subscriptionError() {
71+
72+
RSocketGraphQlTransport transport = createTransport(SocketAcceptor.forRequestStream(payload ->
73+
Flux.error(new RejectedException(
74+
"[{\"message\":\"boo\"," +
75+
"\"locations\":[]," +
76+
"\"errorType\":\"DataFetchingException\"," +
77+
"\"path\":null," +
78+
"\"extensions\":null}]"))));
79+
80+
Flux<GraphQlResponse> responseFlux =
81+
transport.executeSubscription(new DefaultGraphQlRequest("subscription { greetings }"));
82+
83+
StepVerifier.create(responseFlux)
84+
.expectErrorSatisfies(ex -> {
85+
assertThat(ex).isInstanceOf(SubscriptionErrorException.class);
86+
List<ResponseError> errors = ((SubscriptionErrorException) ex).getErrors();
87+
assertThat(errors).hasSize(1);
88+
assertThat(errors.get(0).getMessage()).isEqualTo("boo");
89+
})
90+
.verify(Duration.ofSeconds(5));
91+
}
92+
93+
private RSocketGraphQlTransport createTransport(SocketAcceptor acceptor) {
94+
95+
this.server = RSocketServer.create()
96+
.acceptor(acceptor)
97+
.bind(LocalServerTransport.create("local"))
98+
.block();
99+
100+
RSocketRequester requester = RSocketRequester.builder()
101+
.rsocketStrategies(RSocketStrategies.builder().encoder(jsonEncoder).decoder(jsonDecoder).build())
102+
.transport(LocalClientTransport.create("local"));
103+
104+
return new RSocketGraphQlTransport("route", requester, jsonDecoder);
105+
}
106+
107+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.graphql.server;
18+
19+
import java.time.Duration;
20+
import java.util.Collections;
21+
import java.util.Map;
22+
23+
import graphql.ExecutionInput;
24+
import graphql.ExecutionResult;
25+
import graphql.ExecutionResultImpl;
26+
import graphql.GraphQLError;
27+
import graphql.GraphqlErrorBuilder;
28+
import io.rsocket.exceptions.InvalidException;
29+
import io.rsocket.exceptions.RejectedException;
30+
import org.junit.jupiter.api.Test;
31+
import reactor.core.publisher.Flux;
32+
import reactor.core.publisher.Mono;
33+
import reactor.test.StepVerifier;
34+
35+
import org.springframework.core.codec.Encoder;
36+
import org.springframework.graphql.ExecutionGraphQlService;
37+
import org.springframework.graphql.server.webflux.GraphQlWebSocketHandler;
38+
import org.springframework.graphql.support.DefaultExecutionGraphQlResponse;
39+
import org.springframework.http.codec.json.Jackson2JsonEncoder;
40+
41+
import static org.assertj.core.api.Assertions.assertThat;
42+
43+
/**
44+
* Unit tests for {@link GraphQlWebSocketHandler}.
45+
* @author Rossen Stoyanchev
46+
*/
47+
public class GraphQlRSocketHandlerTests {
48+
49+
private static final Duration TIMEOUT = Duration.ofSeconds(5);
50+
51+
private final Encoder<?> encoder = new Jackson2JsonEncoder();
52+
53+
54+
@Test
55+
void subscriptionWithFailedResponse() {
56+
57+
String document = "subscription { greetings }";
58+
GraphQLError error = GraphqlErrorBuilder.newError().message("boo").build();
59+
ExecutionResult result = ExecutionResultImpl.newExecutionResult().addError(error).build();
60+
61+
Flux<Map<String, Object>> responseFlux = handleSubscription(document, result);
62+
63+
StepVerifier.create(responseFlux)
64+
.expectErrorSatisfies(ex -> {
65+
assertThat(ex).isInstanceOf(RejectedException.class);
66+
assertThat(ex.getMessage()).isEqualTo(
67+
"[{\"message\":\"boo\"," +
68+
"\"locations\":[]," +
69+
"\"errorType\":\"DataFetchingException\"," +
70+
"\"path\":null," +
71+
"\"extensions\":null}]");
72+
})
73+
.verify(TIMEOUT);
74+
}
75+
76+
@Test
77+
void subscriptionWithValidResponseButNotPublisher() {
78+
79+
String document = "subscription { greetings }";
80+
ExecutionResult result = ExecutionResultImpl.newExecutionResult().data(Collections.emptyMap()).build();
81+
82+
Flux<Map<String, Object>> responseFlux = handleSubscription(document, result);
83+
84+
StepVerifier.create(responseFlux)
85+
.expectErrorSatisfies(ex -> {
86+
assertThat(ex).isInstanceOf(InvalidException.class);
87+
assertThat(ex.getMessage()).startsWith("Expected a Publisher for a subscription operation.");
88+
})
89+
.verify(TIMEOUT);
90+
}
91+
92+
private Flux<Map<String, Object>> handleSubscription(String document, ExecutionResult executionResult) {
93+
ExecutionGraphQlService service = stubService(document, executionResult);
94+
GraphQlRSocketHandler handler = new GraphQlRSocketHandler(service, Collections.emptyList(), this.encoder);
95+
return handler.handleSubscription(Collections.singletonMap("query", document));
96+
}
97+
98+
private ExecutionGraphQlService stubService(String document, ExecutionResult result) {
99+
return request -> {
100+
assertThat(request.getDocument()).isEqualTo(document);
101+
ExecutionInput executionInput = ExecutionInput.newExecutionInput().query(document).build();
102+
return Mono.just(new DefaultExecutionGraphQlResponse(executionInput, result));
103+
};
104+
}
105+
106+
}

0 commit comments

Comments
 (0)