2 * Copyright © 2019 FRINX s.r.o. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.restconf.nb.rfc8040.streams;
10 import static org.junit.jupiter.api.Assertions.assertEquals;
11 import static org.junit.jupiter.api.Assertions.assertTrue;
12 import static org.mockito.ArgumentMatchers.any;
13 import static org.mockito.ArgumentMatchers.anyBoolean;
14 import static org.mockito.ArgumentMatchers.anyString;
15 import static org.mockito.ArgumentMatchers.eq;
16 import static org.mockito.Mockito.doNothing;
17 import static org.mockito.Mockito.doReturn;
18 import static org.mockito.Mockito.mock;
19 import static org.mockito.Mockito.never;
20 import static org.mockito.Mockito.times;
21 import static org.mockito.Mockito.verify;
22 import static org.mockito.Mockito.verifyNoMoreInteractions;
23 import static org.mockito.Mockito.when;
25 import java.util.concurrent.TimeUnit;
26 import org.eclipse.jetty.websocket.api.RemoteEndpoint;
27 import org.eclipse.jetty.websocket.api.Session;
28 import org.junit.jupiter.api.Test;
29 import org.junit.jupiter.api.extension.ExtendWith;
30 import org.mockito.ArgumentCaptor;
31 import org.mockito.Mock;
32 import org.mockito.junit.jupiter.MockitoExtension;
33 import org.opendaylight.restconf.server.spi.RestconfStream;
34 import org.opendaylight.restconf.server.spi.RestconfStream.EncodingName;
35 import org.opendaylight.yangtools.concepts.Registration;
37 @ExtendWith(MockitoExtension.class)
38 class WebSocketSessionHandlerTest {
39 private final class WebSocketTestSessionState {
40 private final WebSocketSender webSocketSessionHandler;
41 private final long heartbeatInterval;
42 private final int maxFragmentSize;
44 WebSocketTestSessionState(final int maxFragmentSize, final long heartbeatInterval) {
45 this.heartbeatInterval = heartbeatInterval;
46 this.maxFragmentSize = maxFragmentSize;
47 webSocketSessionHandler = new WebSocketSender(pingExecutor, stream, ENCODING, null, maxFragmentSize,
50 if (heartbeatInterval != 0) {
51 doReturn(pingRegistration).when(pingExecutor).startPingProcess(any(Runnable.class),
52 eq(heartbeatInterval), eq(TimeUnit.MILLISECONDS));
57 static final EncodingName ENCODING = new EncodingName("encoding");
60 private RestconfStream<?> stream;
62 private PingExecutor pingExecutor;
64 private Registration pingRegistration;
66 private Session session;
69 void onWebSocketConnectedWithEnabledPing() throws Exception {
70 final int heartbeatInterval = 1000;
71 final var webSocketTestSessionState = new WebSocketTestSessionState(1000, heartbeatInterval);
73 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
74 verify(stream).addSubscriber(webSocketTestSessionState.webSocketSessionHandler, ENCODING, null);
75 verify(pingExecutor).startPingProcess(any(Runnable.class), eq(webSocketTestSessionState.heartbeatInterval),
76 eq(TimeUnit.MILLISECONDS));
80 void onWebSocketConnectedWithDisabledPing() throws Exception {
81 final int heartbeatInterval = 0;
82 final var webSocketTestSessionState = new WebSocketTestSessionState(1000, heartbeatInterval);
84 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
85 verify(stream).addSubscriber(webSocketTestSessionState.webSocketSessionHandler, ENCODING, null);
86 verifyNoMoreInteractions(pingExecutor);
90 void onWebSocketConnectedWithAlreadyOpenSession() throws Exception {
91 final var webSocketTestSessionState = new WebSocketTestSessionState(150, 8000);
92 when(session.isOpen()).thenReturn(true);
94 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
95 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
96 verify(stream).addSubscriber(any(), any(), any());
100 void onWebSocketClosedWithOpenSession() throws Exception {
101 final var webSocketTestSessionState = new WebSocketTestSessionState(200, 10000);
102 final var reg = mock(Registration.class);
104 doReturn(reg).when(stream).addSubscriber(webSocketTestSessionState.webSocketSessionHandler, ENCODING, null);
105 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
107 webSocketTestSessionState.webSocketSessionHandler.onWebSocketClosed(200, "Simulated close");
112 void onWebSocketClosedWithNotInitialisedSession() {
113 final var webSocketTestSessionState = new WebSocketTestSessionState(0, 0);
114 webSocketTestSessionState.webSocketSessionHandler.onWebSocketClosed(500, "Simulated close");
115 verifyNoMoreInteractions(stream);
119 void onWebSocketErrorWithEnabledPingAndLivingSession() throws Exception {
120 final var webSocketTestSessionState = new WebSocketTestSessionState(150, 8000);
121 final var reg = mock(Registration.class);
123 when(session.isOpen()).thenReturn(true);
124 when(stream.addSubscriber(webSocketTestSessionState.webSocketSessionHandler, ENCODING, null))
126 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
128 final var sampleError = new IllegalStateException("Simulated error");
129 doNothing().when(reg).close();
130 doNothing().when(pingRegistration).close();
131 webSocketTestSessionState.webSocketSessionHandler.onWebSocketError(sampleError);
132 verify(session).close();
136 void onWebSocketErrorWithEnabledPingAndDeadSession() throws Exception {
137 final var webSocketTestSessionState = new WebSocketTestSessionState(150, 8000);
138 final var reg = mock(Registration.class);
140 when(session.isOpen()).thenReturn(false);
141 when(stream.addSubscriber(webSocketTestSessionState.webSocketSessionHandler, ENCODING, null))
143 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
145 final var sampleError = new IllegalStateException("Simulated error");
146 doNothing().when(reg).close();
147 doNothing().when(pingRegistration).close();
148 webSocketTestSessionState.webSocketSessionHandler.onWebSocketError(sampleError);
149 verify(session, never()).close();
153 void onWebSocketErrorWithDisabledPingAndDeadSession() throws Exception {
154 final var webSocketTestSessionState = new WebSocketTestSessionState(150, 8000);
155 final var reg = mock(Registration.class);
157 when(session.isOpen()).thenReturn(false);
158 when(stream.addSubscriber(webSocketTestSessionState.webSocketSessionHandler, ENCODING, null))
160 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
162 final var sampleError = new IllegalStateException("Simulated error");
163 webSocketTestSessionState.webSocketSessionHandler.onWebSocketError(sampleError);
165 verify(session, never()).close();
169 void sendDataMessageWithDisabledFragmentation() throws Exception {
170 final var webSocketTestSessionState = new WebSocketTestSessionState(0, 0);
171 final var remoteEndpoint = mock(RemoteEndpoint.class);
172 when(session.isOpen()).thenReturn(true);
173 when(session.getRemote()).thenReturn(remoteEndpoint);
174 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
176 final String testMessage = generateRandomStringOfLength(100);
177 webSocketTestSessionState.webSocketSessionHandler.sendDataMessage(testMessage);
178 verify(remoteEndpoint).sendString(testMessage);
182 void sendDataMessageWithDisabledFragAndDeadSession() {
183 final var webSocketTestSessionState = new WebSocketTestSessionState(0, 0);
184 final var remoteEndpoint = mock(RemoteEndpoint.class);
185 when(session.isOpen()).thenReturn(false);
186 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
188 final String testMessage = generateRandomStringOfLength(11);
189 webSocketTestSessionState.webSocketSessionHandler.sendDataMessage(testMessage);
190 verifyNoMoreInteractions(remoteEndpoint);
194 void sendDataMessageWithEnabledFragAndSmallMessage() throws Exception {
195 final var webSocketTestSessionState = new WebSocketTestSessionState(100, 0);
196 final var remoteEndpoint = mock(RemoteEndpoint.class);
197 when(session.isOpen()).thenReturn(true);
198 when(session.getRemote()).thenReturn(remoteEndpoint);
199 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
201 // in both cases, fragmentation should not be applied
202 final String testMessage1 = generateRandomStringOfLength(100);
203 final String testMessage2 = generateRandomStringOfLength(50);
204 webSocketTestSessionState.webSocketSessionHandler.sendDataMessage(testMessage1);
205 webSocketTestSessionState.webSocketSessionHandler.sendDataMessage(testMessage2);
206 verify(remoteEndpoint).sendString(testMessage1);
207 verify(remoteEndpoint).sendString(testMessage2);
208 verify(remoteEndpoint, never()).sendPartialString(anyString(), anyBoolean());
212 void sendDataMessageWithZeroLength() {
213 final var webSocketTestSessionState = new WebSocketTestSessionState(100, 0);
214 final var remoteEndpoint = mock(RemoteEndpoint.class);
215 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
217 webSocketTestSessionState.webSocketSessionHandler.sendDataMessage("");
218 verifyNoMoreInteractions(remoteEndpoint);
222 void sendDataMessageWithEnabledFragAndLargeMessage1() throws Exception {
223 final var webSocketTestSessionState = new WebSocketTestSessionState(100, 0);
224 final var remoteEndpoint = mock(RemoteEndpoint.class);
225 when(session.isOpen()).thenReturn(true);
226 when(session.getRemote()).thenReturn(remoteEndpoint);
227 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
229 // there should be 10 fragments of length 100 characters
230 final String testMessage = generateRandomStringOfLength(1000);
231 webSocketTestSessionState.webSocketSessionHandler.sendDataMessage(testMessage);
232 final var messageCaptor = ArgumentCaptor.forClass(String.class);
233 final var isLastCaptor = ArgumentCaptor.forClass(Boolean.class);
234 verify(remoteEndpoint, times(10)).sendPartialString(
235 messageCaptor.capture(), isLastCaptor.capture());
237 final var allMessages = messageCaptor.getAllValues();
238 final var isLastFlags = isLastCaptor.getAllValues();
239 assertTrue(allMessages.stream().allMatch(s -> s.length() == webSocketTestSessionState.maxFragmentSize));
240 assertTrue(isLastFlags.subList(0, 9).stream().noneMatch(isLast -> isLast));
241 assertTrue(isLastFlags.get(9));
245 void sendDataMessageWithEnabledFragAndLargeMessage2() throws Exception {
246 final var webSocketTestSessionState = new WebSocketTestSessionState(100, 0);
247 final var remoteEndpoint = mock(RemoteEndpoint.class);
248 when(session.isOpen()).thenReturn(true);
249 when(session.getRemote()).thenReturn(remoteEndpoint);
250 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
252 // there should be 10 fragments, the last fragment should be the shortest one
253 final String testMessage = generateRandomStringOfLength(950);
254 webSocketTestSessionState.webSocketSessionHandler.sendDataMessage(testMessage);
255 final var messageCaptor = ArgumentCaptor.forClass(String.class);
256 final var isLastCaptor = ArgumentCaptor.forClass(Boolean.class);
257 verify(remoteEndpoint, times(10)).sendPartialString(
258 messageCaptor.capture(), isLastCaptor.capture());
260 final var allMessages = messageCaptor.getAllValues();
261 final var isLastFlags = isLastCaptor.getAllValues();
262 assertTrue(allMessages.subList(0, 9).stream().allMatch(s ->
263 s.length() == webSocketTestSessionState.maxFragmentSize));
264 assertEquals(50, allMessages.get(9).length());
265 assertTrue(isLastFlags.subList(0, 9).stream().noneMatch(isLast -> isLast));
266 assertTrue(isLastFlags.get(9));
269 private static String generateRandomStringOfLength(final int length) {
270 final String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvxyz";
271 final var sb = new StringBuilder(length);
272 for (int i = 0; i < length; i++) {
273 int index = (int) (alphabet.length() * Math.random());
274 sb.append(alphabet.charAt(index));
276 return sb.toString();