Add support for RSocket interface client

See gh-24456
This commit is contained in:
rstoyanchev
2022-09-05 16:54:30 +01:00
parent ae861a2b3e
commit 8423b2cab7
22 changed files with 1956 additions and 93 deletions

View File

@@ -28,17 +28,14 @@ import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Single;
import io.rsocket.Payload;
import io.rsocket.RSocket;
import io.rsocket.metadata.WellKnownMimeType;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.lang.Nullable;
import org.springframework.messaging.rsocket.RSocketRequester.RequestSpec;
import org.springframework.messaging.rsocket.RSocketRequester.RetrieveSpec;
import org.springframework.util.MimeType;
@@ -246,73 +243,4 @@ public class DefaultRSocketRequesterTests {
return PayloadUtils.createPayload(DefaultDataBufferFactory.sharedInstance.wrap(bytes));
}
private static class TestRSocket implements RSocket {
private Mono<Payload> payloadMonoToReturn = Mono.empty();
private Flux<Payload> payloadFluxToReturn = Flux.empty();
@Nullable private volatile String savedMethodName;
@Nullable private volatile Payload savedPayload;
@Nullable private volatile Flux<Payload> savedPayloadFlux;
void setPayloadMonoToReturn(Mono<Payload> payloadMonoToReturn) {
this.payloadMonoToReturn = payloadMonoToReturn;
}
void setPayloadFluxToReturn(Flux<Payload> payloadFluxToReturn) {
this.payloadFluxToReturn = payloadFluxToReturn;
}
@Nullable
String getSavedMethodName() {
return this.savedMethodName;
}
@Nullable
Payload getSavedPayload() {
return this.savedPayload;
}
@Nullable
Flux<Payload> getSavedPayloadFlux() {
return this.savedPayloadFlux;
}
public void reset() {
this.savedMethodName = null;
this.savedPayload = null;
this.savedPayloadFlux = null;
}
@Override
public Mono<Void> fireAndForget(Payload payload) {
this.savedMethodName = "fireAndForget";
this.savedPayload = payload;
return Mono.empty();
}
@Override
public Mono<Payload> requestResponse(Payload payload) {
this.savedMethodName = "requestResponse";
this.savedPayload = payload;
return this.payloadMonoToReturn;
}
@Override
public Flux<Payload> requestStream(Payload payload) {
this.savedMethodName = "requestStream";
this.savedPayload = payload;
return this.payloadFluxToReturn;
}
@Override
public Flux<Payload> requestChannel(Publisher<Payload> publisher) {
this.savedMethodName = "requestChannel";
this.savedPayloadFlux = Flux.from(publisher);
return this.payloadFluxToReturn;
}
}
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2002-2022 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.messaging.rsocket;
import io.rsocket.Payload;
import io.rsocket.RSocket;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.lang.Nullable;
/**
* {@link RSocket} that saves the name of the invoked method and the input payload(s).
*/
public class TestRSocket implements RSocket {
private Mono<Payload> payloadMonoToReturn = Mono.empty();
private Flux<Payload> payloadFluxToReturn = Flux.empty();
@Nullable private volatile String savedMethodName;
@Nullable private volatile Payload savedPayload;
@Nullable private volatile Flux<Payload> savedPayloadFlux;
public void setPayloadMonoToReturn(Mono<Payload> payloadMonoToReturn) {
this.payloadMonoToReturn = payloadMonoToReturn;
}
public void setPayloadFluxToReturn(Flux<Payload> payloadFluxToReturn) {
this.payloadFluxToReturn = payloadFluxToReturn;
}
@Nullable
public String getSavedMethodName() {
return this.savedMethodName;
}
@Nullable
public Payload getSavedPayload() {
return this.savedPayload;
}
@Nullable
public Flux<Payload> getSavedPayloadFlux() {
return this.savedPayloadFlux;
}
public void reset() {
this.savedMethodName = null;
this.savedPayload = null;
this.savedPayloadFlux = null;
}
@Override
public Mono<Void> fireAndForget(Payload payload) {
this.savedMethodName = "fireAndForget";
this.savedPayload = payload;
return Mono.empty();
}
@Override
public Mono<Payload> requestResponse(Payload payload) {
this.savedMethodName = "requestResponse";
this.savedPayload = payload;
return this.payloadMonoToReturn;
}
@Override
public Flux<Payload> requestStream(Payload payload) {
this.savedMethodName = "requestStream";
this.savedPayload = payload;
return this.payloadFluxToReturn;
}
@Override
public Flux<Payload> requestChannel(Publisher<Payload> publisher) {
this.savedMethodName = "requestChannel";
this.savedPayloadFlux = Flux.from(publisher);
return this.payloadFluxToReturn;
}
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright 2002-2022 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.messaging.rsocket.service;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.core.MethodParameter;
import org.springframework.messaging.handler.annotation.DestinationVariable;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Unit tests for {@link DestinationVariableArgumentResolver}.
* @author Rossen Stoyanchev
*/
public class DestinationVariableArgumentResolverTests extends RSocketServiceArgumentResolverTestSupport {
@Override
protected RSocketServiceArgumentResolver initResolver() {
return new DestinationVariableArgumentResolver();
}
@Test
void variable() {
String value = "foo";
boolean resolved = execute(value, initMethodParameter(Service.class, "execute", 0));
assertThat(resolved).isTrue();
assertThat(getRequestValues().getRouteVariables()).containsExactly(value);
}
@Test
void variableList() {
List<String> values = Arrays.asList("foo", "bar", "baz");
boolean resolved = execute(values, initMethodParameter(Service.class, "execute", 0));
assertThat(resolved).isTrue();
assertThat(getRequestValues().getRouteVariables()).containsExactlyElementsOf(values);
}
@Test
void variableArray() {
String[] values = new String[] {"foo", "bar", "baz"};
boolean resolved = execute(values, initMethodParameter(Service.class, "execute", 0));
assertThat(resolved).isTrue();
assertThat(getRequestValues().getRouteVariables()).containsExactlyElementsOf(Arrays.asList(values));
}
@Test
void notRequestBody() {
MethodParameter parameter = initMethodParameter(Service.class, "executeNotAnnotated", 0);
boolean resolved = execute("value", parameter);
assertThat(resolved).isFalse();
}
@Test
void ignoreNull() {
boolean resolved = execute(null, initMethodParameter(Service.class, "execute", 0));
assertThat(resolved).isTrue();
assertThat(getRequestValues().getPayloadValue()).isNull();
assertThat(getRequestValues().getPayload()).isNull();
assertThat(getRequestValues().getPayloadElementType()).isNull();
}
@SuppressWarnings("unused")
private interface Service {
void execute(@DestinationVariable String variable);
void executeList(@DestinationVariable List<String> variables);
void executeArray(@DestinationVariable String[] variables);
void executeNotAnnotated(String variable);
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2002-2022 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.messaging.rsocket.service;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.core.MethodParameter;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Unit tests for {@link MetadataArgumentResolver}.
* @author Rossen Stoyanchev
*/
public class MetadataArgumentResolverTests extends RSocketServiceArgumentResolverTestSupport {
@Override
protected RSocketServiceArgumentResolver initResolver() {
return new MetadataArgumentResolver();
}
@Test
void metadata() {
MethodParameter param1 = initMethodParameter(Service.class, "execute", 0);
MethodParameter param2 = initMethodParameter(Service.class, "execute", 1);
MethodParameter param3 = initMethodParameter(Service.class, "execute", 2);
MethodParameter param4 = initMethodParameter(Service.class, "execute", 3);
assertThat(execute("foo", param1)).isTrue();
assertThat(execute(MimeTypeUtils.APPLICATION_JSON, param2)).isTrue();
assertThat(execute("bar", param3)).isTrue();
assertThat(execute(MimeTypeUtils.APPLICATION_XML, param4)).isTrue();
Map<Object, MimeType> expected = new LinkedHashMap<>();
expected.put("foo", MimeTypeUtils.APPLICATION_JSON);
expected.put("bar", MimeTypeUtils.APPLICATION_XML);
assertThat(getRequestValues().getMetadata()).containsExactlyEntriesOf(expected);
}
@SuppressWarnings("unused")
private interface Service {
void execute(String metadata1, MimeType mimeType1, String metadata2, MimeType mimeType2);
void executeNotAnnotated(String foo, String bar);
}
}

View File

@@ -0,0 +1,129 @@
/*
* Copyright 2002-2022 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.messaging.rsocket.service;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Single;
import org.junit.jupiter.api.Test;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.messaging.handler.annotation.Payload;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Unit tests for {@link PayloadArgumentResolver}.
* @author Rossen Stoyanchev
*/
public class PayloadArgumentResolverTests extends RSocketServiceArgumentResolverTestSupport {
@Override
protected RSocketServiceArgumentResolver initResolver() {
return new PayloadArgumentResolver(ReactiveAdapterRegistry.getSharedInstance(), false);
}
@Test
void stringPayload() {
String payload = "payloadValue";
boolean resolved = execute(payload, initMethodParameter(Service.class, "execute", 0));
assertThat(resolved).isTrue();
assertThat(getRequestValues().getPayloadValue()).isEqualTo(payload);
assertThat(getRequestValues().getPayload()).isNull();
}
@Test
void monoPayload() {
Mono<String> payloadMono = Mono.just("payloadValue");
boolean resolved = execute(payloadMono, initMethodParameter(Service.class, "executeMono", 0));
assertThat(resolved).isTrue();
assertThat(getRequestValues().getPayloadValue()).isNull();
assertThat(getRequestValues().getPayload()).isSameAs(payloadMono);
assertThat(getRequestValues().getPayloadElementType()).isEqualTo(new ParameterizedTypeReference<String>() {});
}
@Test
@SuppressWarnings("unchecked")
void singlePayload() {
boolean resolved = execute(Single.just("bodyValue"), initMethodParameter(Service.class, "executeSingle", 0));
assertThat(resolved).isTrue();
assertThat(getRequestValues().getPayloadValue()).isNull();
assertThat(getRequestValues().getPayloadElementType()).isEqualTo(new ParameterizedTypeReference<String>() {});
Publisher<?> payload = getRequestValues().getPayload();
assertThat(payload).isNotNull();
assertThat(((Mono<String>) payload).block()).isEqualTo("bodyValue");
}
@Test
void monoVoid() {
assertThatIllegalArgumentException()
.isThrownBy(() -> execute(Mono.empty(), initMethodParameter(Service.class, "executeMonoVoid", 0)))
.withMessage("Async type for @Payload should produce value(s)");
}
@Test
void completable() {
assertThatIllegalArgumentException()
.isThrownBy(() -> execute(Completable.complete(), initMethodParameter(Service.class, "executeCompletable", 0)))
.withMessage("Async type for @Payload should produce value(s)");
}
@Test
void notRequestBody() {
MethodParameter parameter = initMethodParameter(Service.class, "executeNotAnnotated", 0);
boolean resolved = execute("value", parameter);
assertThat(resolved).isFalse();
}
@Test
void ignoreNull() {
boolean resolved = execute(null, initMethodParameter(Service.class, "execute", 0));
assertThat(resolved).isTrue();
assertThat(getRequestValues().getPayloadValue()).isNull();
assertThat(getRequestValues().getPayload()).isNull();
assertThat(getRequestValues().getPayloadElementType()).isNull();
}
@SuppressWarnings("unused")
private interface Service {
void execute(@Payload String body);
void executeMono(@Payload Mono<String> body);
void executeSingle(@Payload Single<String> body);
void executeMonoVoid(@Payload Mono<Void> body);
void executeCompletable(@Payload Completable body);
void executeNotAnnotated(String body);
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright 2002-2022 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.messaging.rsocket.service;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.util.MimeTypeUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Unit tests for {@link RSocketRequestValues}.
* @author Rossen Stoyanchev
*/
public class RSocketRequestValuesTests {
@Test
void route() {
String myRoute = "myRoute";
RSocketRequestValues values = RSocketRequestValues.builder(myRoute).build();
assertThat(values.getRoute()).isEqualTo(myRoute);
}
@Test
void routeOverride() {
RSocketRequestValues values = RSocketRequestValues.builder("route1").setRoute("route2").build();
assertThat(values.getRoute()).isEqualTo("route2");
}
@Test
void payloadValue() {
String payload = "myValue";
RSocketRequestValues values = RSocketRequestValues.builder(null).setPayloadValue(payload).build();
assertThat(values.getPayloadValue()).isEqualTo(payload);
assertThat(values.getPayload()).isNull();
}
@Test
void payloadPublisher() {
Mono<String> payloadMono = Mono.just( "myValue");
RSocketRequestValues values = RSocketRequestValues.builder(null)
.setPayload(payloadMono, new ParameterizedTypeReference<>() { })
.build();
assertThat(values.getPayloadValue()).isNull();
assertThat(values.getPayload()).isSameAs(payloadMono);
}
@Test
void metadata() {
RSocketRequestValues values = RSocketRequestValues.builder(null)
.addMetadata("myMetadata1").addMimeType(MimeTypeUtils.TEXT_PLAIN)
.addMetadata("myMetadata2").addMimeType(MimeTypeUtils.TEXT_HTML)
.build();
assertThat(values.getMetadata())
.hasSize(2)
.containsEntry("myMetadata1", MimeTypeUtils.TEXT_PLAIN)
.containsEntry("myMetadata2", MimeTypeUtils.TEXT_HTML);
}
@Test
void metadataInvalidEntry() {
// MimeType without metadata
assertThatIllegalArgumentException()
.isThrownBy(() -> RSocketRequestValues.builder(null).addMimeType(MimeTypeUtils.TEXT_PLAIN));
// Metadata without MimeType
assertThatIllegalArgumentException()
.isThrownBy(() -> RSocketRequestValues.builder(null)
.addMetadata("metadata1")
.addMetadata("metadata2"));
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2002-2022 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.messaging.rsocket.service;
import java.lang.reflect.Method;
import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
/**
* Base class for {@link RSocketServiceArgumentResolver} test fixtures.
* @author Rossen Stoyanchev
*/
public abstract class RSocketServiceArgumentResolverTestSupport {
@Nullable
private RSocketServiceArgumentResolver resolver;
private final RSocketRequestValues.Builder requestValuesBuilder = RSocketRequestValues.builder(null);
@Nullable
private RSocketRequestValues requestValues;
protected RSocketServiceArgumentResolverTestSupport() {
this.resolver = initResolver();
}
protected abstract RSocketServiceArgumentResolver initResolver();
protected static MethodParameter initMethodParameter(Class<?> serviceClass, String methodName, int index) {
Method method = ClassUtils.getMethod(serviceClass, methodName, (Class<?>[]) null);
return new MethodParameter(method, index);
}
protected boolean execute(Object payload, MethodParameter parameter) {
return this.resolver.resolve(payload, parameter, this.requestValuesBuilder);
}
protected RSocketRequestValues getRequestValues() {
this.requestValues = (this.requestValues != null ? this.requestValues : this.requestValuesBuilder.build());
return this.requestValues;
}
}

View File

@@ -0,0 +1,162 @@
/*
* Copyright 2002-2022 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.messaging.rsocket.service;
import java.time.Duration;
import io.rsocket.SocketAcceptor;
import io.rsocket.core.RSocketServer;
import io.rsocket.metadata.WellKnownMimeType;
import io.rsocket.transport.netty.server.CloseableChannel;
import io.rsocket.transport.netty.server.TcpServerTransport;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.messaging.rsocket.RSocketStrategies;
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
import org.springframework.stereotype.Controller;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
/**
* Integration tests with RSocket Service client.
*
* @author Rossen Stoyanchev
*/
public class RSocketServiceIntegrationTests {
private static CloseableChannel server;
private static RSocketRequester requester;
private static Service serviceProxy;
@BeforeAll
@SuppressWarnings("ConstantConditions")
public static void setupOnce() throws Exception {
MimeType metadataMimeType = MimeTypeUtils.parseMimeType(
WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA.getString());
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ServerConfig.class);
RSocketMessageHandler messageHandler = context.getBean(RSocketMessageHandler.class);
SocketAcceptor responder = messageHandler.responder();
server = RSocketServer.create(responder)
.bind(TcpServerTransport.create("localhost", 7000))
.block();
requester = RSocketRequester.builder()
.metadataMimeType(metadataMimeType)
.rsocketStrategies(context.getBean(RSocketStrategies.class))
.tcp("localhost", 7000);
RSocketServiceProxyFactory proxyFactory = new RSocketServiceProxyFactory(requester);
proxyFactory.afterPropertiesSet();
serviceProxy = proxyFactory.createClient(Service.class);
}
@AfterAll
public static void tearDownOnce() {
requester.rsocketClient().dispose();
server.dispose();
}
@Test
public void echoAsync() {
Flux<String> result = Flux.range(1, 3).concatMap(i -> serviceProxy.echoAsync("Hello " + i));
StepVerifier.create(result)
.expectNext("Hello 1 async").expectNext("Hello 2 async").expectNext("Hello 3 async")
.expectComplete()
.verify(Duration.ofSeconds(5));
}
@Test
public void echoStream() {
Flux<String> result = serviceProxy.echoStream("Hello");
StepVerifier.create(result)
.expectNext("Hello 0").expectNextCount(6).expectNext("Hello 7")
.thenCancel()
.verify(Duration.ofSeconds(5));
}
@Controller
interface Service {
@RSocketExchange("echo-async")
Mono<String> echoAsync(String payload);
@RSocketExchange("echo-stream")
Flux<String> echoStream(String payload);
}
@Controller
static class ServerController {
@MessageMapping("echo-async")
Mono<String> echoAsync(String payload) {
return Mono.delay(Duration.ofMillis(10)).map(aLong -> payload + " async");
}
@MessageMapping("echo-stream")
Flux<String> echoStream(String payload) {
return Flux.interval(Duration.ofMillis(10)).map(aLong -> payload + " " + aLong);
}
}
@Configuration
static class ServerConfig {
@Bean
public ServerController controller() {
return new ServerController();
}
@Bean
public RSocketMessageHandler messageHandler(RSocketStrategies rsocketStrategies) {
RSocketMessageHandler handler = new RSocketMessageHandler();
handler.setRSocketStrategies(rsocketStrategies);
return handler;
}
@Bean
public RSocketStrategies rsocketStrategies() {
return RSocketStrategies.create();
}
}
}

View File

@@ -0,0 +1,150 @@
/*
* Copyright 2002-2022 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.messaging.rsocket.service;
import java.time.Duration;
import java.util.List;
import io.rsocket.util.DefaultPayload;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.messaging.rsocket.RSocketStrategies;
import org.springframework.messaging.rsocket.TestRSocket;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.util.MimeTypeUtils.TEXT_PLAIN;
/**
* Tests for {@link RSocketServiceMethod} that create an interface client with
* an {@link RSocketRequester} delegating to a {@link TestRSocket}.
*
* @author Rossen Stoyanchev
*/
public class RSocketServiceMethodTests {
private TestRSocket rsocket;
private RSocketServiceProxyFactory proxyFactory;
@BeforeEach
public void setUp() throws Exception {
this.rsocket = new TestRSocket();
RSocketRequester requester = RSocketRequester.wrap(this.rsocket, TEXT_PLAIN, TEXT_PLAIN, RSocketStrategies.create());
this.proxyFactory = new RSocketServiceProxyFactory(requester);
this.proxyFactory.afterPropertiesSet();
}
@Test
void fireAndForget() {
ReactorService service = this.proxyFactory.createClient(ReactorService.class);
String payload = "p1";
service.fireAndForget(Mono.just(payload)).block(Duration.ofSeconds(5));
assertThat(this.rsocket.getSavedMethodName()).isEqualTo("fireAndForget");
assertThat(this.rsocket.getSavedPayload().getMetadataUtf8()).isEqualTo("ff");
assertThat(this.rsocket.getSavedPayload().getDataUtf8()).isEqualTo(payload);
}
@Test
void requestResponse() {
ReactorService service = this.proxyFactory.createClient(ReactorService.class);
String payload1 = "p1";
String payload2 = "p2";
this.rsocket.setPayloadMonoToReturn(
Mono.just(DefaultPayload.create(payload2)));
String response = service.requestResponse(Mono.just(payload1)).block(Duration.ofSeconds(5));
assertThat(response).isEqualTo(payload2);
assertThat(this.rsocket.getSavedMethodName()).isEqualTo("requestResponse");
assertThat(this.rsocket.getSavedPayload().getMetadataUtf8()).isEqualTo("rr");
assertThat(this.rsocket.getSavedPayload().getDataUtf8()).isEqualTo(payload1);
}
@Test
void requestStream() {
ReactorService service = this.proxyFactory.createClient(ReactorService.class);
String payload1 = "p1";
String payload2 = "p2";
String payload3 = "p3";
this.rsocket.setPayloadFluxToReturn(
Flux.just(DefaultPayload.create(payload2), DefaultPayload.create(payload3)));
List<String> response = service.requestStream(Mono.just(payload1))
.collectList()
.block(Duration.ofSeconds(5));
assertThat(response).containsExactly(payload2, payload3);
assertThat(this.rsocket.getSavedMethodName()).isEqualTo("requestStream");
assertThat(this.rsocket.getSavedPayload().getMetadataUtf8()).isEqualTo("rs");
assertThat(this.rsocket.getSavedPayload().getDataUtf8()).isEqualTo(payload1);
}
@Test
void requestChannel() {
ReactorService service = this.proxyFactory.createClient(ReactorService.class);
String payload1 = "p1";
String payload2 = "p2";
String payload3 = "p3";
String payload4 = "p4";
this.rsocket.setPayloadFluxToReturn(
Flux.just(DefaultPayload.create(payload3), DefaultPayload.create(payload4)));
List<String> response = service.requestChannel(Flux.just(payload1, payload2))
.collectList()
.block(Duration.ofSeconds(5));
assertThat(response).containsExactly(payload3, payload4);
assertThat(this.rsocket.getSavedMethodName()).isEqualTo("requestChannel");
List<String> savedPayloads = this.rsocket.getSavedPayloadFlux()
.map(io.rsocket.Payload::getDataUtf8)
.collectList()
.block(Duration.ofSeconds(5));
assertThat(savedPayloads).containsExactly("p1", "p2");
}
private interface ReactorService {
@RSocketExchange("ff")
Mono<Void> fireAndForget(@Payload Mono<String> input);
@RSocketExchange("rr")
Mono<String> requestResponse(@Payload Mono<String> input);
@RSocketExchange("rs")
Flux<String> requestStream(@Payload Mono<String> input);
@RSocketExchange("rc")
Flux<String> requestChannel(@Payload Flux<String> input);
}
}