What is the equivalent of MockHttpServletRequestBuilder for websockets tests in Spring - spring

What is the equivalent of MockHttpServletRequestBuilder for websockets. i.e. In a situation where I want to test Websockets., I would like to test a long running websocket application and avoid the situation where SecurityContextPersistenceFilter is overriding the SecurityContex after the first http get call that is supposed to do the upgrade. For plain rest http apps this was done so far by leveraging the SecurityMockMvcRequestPostProcessors.
example here using the SecurityMockMvcRequestPostProcessors
But what to do when I want to test a long running websocket application. i.e. I want ot create something like MockHttpServletRequestBuilder for websockets. Does spring have something like that already? Or is there a way to use MockHttpServletRequestBuilder for that purpose? I.e. the target is to create the websocket endpoint and avoid the situation where the SecurityContex is beeing cleared after the upgrade.
I have found some alternatives such as passing the session as described here but this is not really an alternative for me as then the code that is using method level security does not work since the SecurityContex is being altered.

It appears that this can be done by providing a test sock config. Ex
#EnableWebSocketMessageBroker
static class TestWebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Autowired
Environment env;
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/portfolio").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// registry.enableSimpleBroker("/queue/", "/topic/");
registry.enableStompBrokerRelay("/queue/", "/topic/");
registry.setApplicationDestinationPrefixes("/app");
}
}
The full class can be found here:
https://github.com/rstoyanchev/spring-websocket-portfolio/blob/master/src/test/java/org/springframework/samples/portfolio/web/context/ContextPortfolioControllerTests.java
Here are also some additional examples provided by spring the demonstrate 3 different approaches to testing web sockets:
https://github.com/rstoyanchev/spring-websocket-portfolio/tree/master/src/test/java/org/springframework/samples/portfolio/web

Related

Spring Boot End Points are not routing correctly using STOMP

I was running into some issues in terms of getting Postman to correctly send messages over my web-socket via STOMP. I made a new project to do some testing and I am having a hard time understanding why my messages are not being sent over.
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gs-guide-websocket");
registry.addEndpoint("/gs-guide-websocket").setAllowedOrigins("http://localhost:8080").withSockJS();
}
}
As you can see, I registered two endpoints. The first end point is being used by Postman. To clarify this code actually works, I have tested it with SockJS() as a fallback and it works as expected.
For the Web Socket connection via Postman, I am using ws://localhost:3000/gs-guide-websocket, which it successfully connects to. However, when sending messages via Postman, it appears as if it's lost and is never correctly handled by the MessageMapping method as seen below.
#MessageMapping("/hello")
#SendTo("/topic/greetings")
public MessageResponse send(Message message) throws Exception {
return new MessageResponse(message.getContent());
}
I am confused through where my messages are actually being sent to, if anywhere at all. I have tried reading through Postman documentation to no avail. Is there a mistake somewhere in my code where I am not correctly handling this sort of end point or is this an error on Postman's part?
Link to the guide I am using for testing: https://spring.io/guides/gs/messaging-stomp-websocket/
From countless hours of researching this problem, it seems like Postman does not support the websocket process that STOMP requires.
From a comment via: Springboot websocket testing with Postman
For now, Postman does not support STOMP, only RAW. There is currently not any way to subscribe to a topic.
The problem I was having is that it can connect to the WebSocket without a problem. However, you are unable to send a message to the correct route at this given time.
For testing your WebSockets, I would recommend using JS/TS to perform operations. This is because you are able to subscribe to a topic via Stomp.over(socket).

Is it possible to test specific Spring REST endpoint security and avoid bootstrapping database connection?

We have a couple of Spring tests that call a secured controller endpoints. Our goal is to assure that absence of particular user roles will result into HTTP 403 status.
Our issue is that execution of those tests also bootstraps DB connection which we don't actually need.
I've already tried countless number of all kind of annotations and manual configurations to avoid initialization of DB connection but so far without luck. Can you please share example how to do that?
We use Spring Boot 2.7.
Yes, you can use #WebMvcTest, take a look at the docs. In summary, using #WebMvcTest will only bootstrap the Spring MVC components and avoid loading other application's layers. This annotation also automatically configures Spring Security for you, therefore you can test authentication/authorization rules.
Example:
#WebMvcTest(UserVehicleController.class)
class MyControllerTests {
#Autowired
private MockMvc mvc;
#MockBean
private UserVehicleService userVehicleService;
#Test
#WithMockUser(roles = "ADMIN")
void testAdminSuccess() throws Exception {
given(this.userVehicleService.getVehicleDetails("sboot"))
.willReturn(new VehicleDetails("Honda", "Civic"));
this.mvc.perform(get("/sboot/vehicle").accept(MediaType.TEXT_PLAIN))
.andExpect(status().isOk())
.andExpect(content().string("Honda Civic"));
}
#Test
#WithMockUser(roles = "USER")
void testUserForbidden() throws Exception {
this.mvc.perform(get("/sboot/vehicle").accept(MediaType.TEXT_PLAIN))
.andExpect(status().isForbidden());
}
}

Having trouble sending data to my websocket created in Spring-Boot from Flutter

I am attempting to send data through IOWebSocketChannel in Flutter.io to a WebSocket created in Spring-Boot.
In spring-boot I have created the typical WebSocket config and controllers that are dealing with client's manipulation of my servers WebSocket. I will post them below just for reference.
WebSocketConfiguration.java
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer{
#Override
public void registerStompEndpoints(StompEndpointRegistry registry){
registry.addEndpoint("/websocket")
.setAllowedOrigins("*") // allow us to connect to ws://localhost:8080/websocket with the default Spring port configuration.
.withSockJS(); // allows a client which does not support WebSocket natively mimic a WebSocket over an HTTP connection
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry){ //The configureMessageBroker method sets up a simple (in-memory) message broker for our application
registry.enableSimpleBroker("/topic"); //topic to be routed back to client
registry.setApplicationDestinationPrefixes("/app"); //This configuration allows Spring to understand that any message sent to a WebSocket channel name prefixed with /app should be routed to a #MessageMapping in our application.
}
}
WebSocketController.java
#Controller
public class WebSocketController {
private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketController.class);
#MessageMapping("/send")
#SendTo("/topic/messages")
public Message send(Message message) {
LOGGER.info(String.format("Received message [%s]", message.toString()));
LocalDateTime timestamp = LocalDateTime.now();
return new Message(message.getFrom(), message.getMessage(), timestamp);
}
}
Now When I try using IOWebSocketChannel I perform the typical protocol of connecting to my configured websocket. Below is the code
final channel = IOWebSocketChannel.connect(
"ws://10.0.2.2:8080/websocket"
);
I have then created a method that is supposed to send data to my websocket so I attempt to connect to that endpoint which you see is created in WebSocketController.java called app/send/. Below is the code:
void _sendMessage() {
IOWebSocketChannel channel = IOWebSocketChannel.connect('ws://10.0.2.2:8080/app/send');
channel.sink.add(
json.encode({
"message": "bars",
})
);
}
Now when I check my Spring-Boot server nothing is logged, however, whenever I hot reload in Flutter Spring Boot and my connection to the websocket times out, tomcat server returns this:
So my question is if anybody has been able to make a breakthrough with sending data through websockets from Flutter into Spring-Boot using IOWebSocketChannel? I am also wondering if anyone has found a way to successfully use a STOMP protocol in Flutter.io? I was using stomp_client as it seemed like it was going to do the trick, however correct if I'm wrong, but flutter was giving me errors saying that there doesn't exist any html files, so I'm assuming that library is only for dart in the web.
Your Spring configuration looks good. But client-side you need some tweaks.
I spent some time to figure this out with the https://pub.dev/packages/stomp package. Use a modified version of the connect function provided here. Make sure to use this custom implementation of the connect function.
Future<StompClient> client = customStomp.connect('ws://10.0.2.2:8080/websocket', ...)
Once connected, according to your configuration, you can then send message on the following destination: /app/send.

HTTP Basic Authentication in Spring Web Services Integration Tests

I have a SOAP web service written in Spring Web Services that I would like to integration test.
I would like to use spring-ws-test as the reference documentation points to. So, the test code is similar to the example in the reference, something like that:
#Autowired
private ApplicationContext applicationContext;
private MockWebServiceClient mockClient;
#Before
public void createClient() {
mockClient = MockWebServiceClient.createClient(applicationContext);
}
#Test
public void customerEndpoint() throws Exception {
Source requestEnvelope = new ResourceSource(new ClassPathResource("request.xml"));
Source responsePayload = new ResourceSource(new ClassPathResource("response.xml"));
mockClient.sendRequest(withSoapEnvelope(requestPayload)).
andExpect(payload(responsePayload));
}
However, the endpoint I am testing is using basic authentication and it expects to read values in the Authorization header. It is not using spring-security for that task but it has custom logic that gets the HTTP headers by getting the HttpServletResponse from the TransportContextHolder. So, the request triggers the endpoint but it fails to retrieve the basic authentication base64 token.
The question is, how may I pass HTTP headers in that situation? Is it possible at all? If not what is the preferred alternative?
I have read the javadoc and I cannot find a way to pass the headers. Also, I have found this question which is similar but it doesn't help me much.

Spring Boot: Serving aggregated data from a websocket connection

I'd like to make a RESTful web app with Spring Boot that would be constantly receiving information from a websocket connection, aggregating it, and serving those aggregated data via its REST API.
So I'll need to open and maintain a WebSocket connection, while also running SpringApplication.run() in the main() function.
The #Scheduled annotation seems to only run tasks at a specific time or interval, but doesn't seem to have a way to always run something in the background.
What is a sensible way to achieve what I've described?
Just have your application connect to a socket on application startup
#SpringBootApplication
public class SpringBootConsoleApplication
implements CommandLineRunner {
private static Logger LOG = LoggerFactory
.getLogger(SpringBootConsoleApplication.class);
public static void main(String[] args) {
LOG.info("STARTING THE APPLICATION");
SpringApplication.run(SpringBootConsoleApplication.class, args);
LOG.info("APPLICATION FINISHED");
}
#Override
public void run(String... args) {
//Connect to socket and cache the results if you want to
}}
Spring Boot will automatically call the run method of all beans implementing this interface after the application context has been loaded
for more information about command line runner you can visit https://www.baeldung.com/spring-boot-console-app
Why not use the WebSocketClient provided by Spring? After you connect you will get notified via a callback method each time a new websocket frame is received.
You can follow the example here https://www.baeldung.com/websockets-api-java-spring-client
Just put it in a class annotated with #Component or #Service that starts the websocket session through the constructor.
You won't need to do the new Scanner(System.in).nextLine() since you have a server that is always running.

Resources