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.ScheduledExecutorService;
26 import java.util.concurrent.ScheduledFuture;
27 import java.util.concurrent.TimeUnit;
28 import org.eclipse.jetty.websocket.api.RemoteEndpoint;
29 import org.eclipse.jetty.websocket.api.Session;
30 import org.junit.jupiter.api.Test;
31 import org.junit.jupiter.api.extension.ExtendWith;
32 import org.mockito.ArgumentCaptor;
33 import org.mockito.Mock;
34 import org.mockito.junit.jupiter.MockitoExtension;
35 import org.opendaylight.restconf.nb.rfc8040.streams.RestconfStream.EncodingName;
36 import org.opendaylight.yangtools.concepts.Registration;
38 @ExtendWith(MockitoExtension.class)
39 class WebSocketSessionHandlerTest {
40 private final class WebSocketTestSessionState {
41 private final WebSocketSessionHandler webSocketSessionHandler;
42 private final int heartbeatInterval;
43 private final int maxFragmentSize;
45 WebSocketTestSessionState(final int maxFragmentSize, final int heartbeatInterval) {
46 this.heartbeatInterval = heartbeatInterval;
47 this.maxFragmentSize = maxFragmentSize;
48 webSocketSessionHandler = new WebSocketSessionHandler(executorService, stream,
49 ENCODING, null, maxFragmentSize, heartbeatInterval);
51 if (heartbeatInterval != 0) {
52 doReturn(pingFuture).when(executorService).scheduleWithFixedDelay(any(Runnable.class),
53 eq((long) heartbeatInterval), eq((long) heartbeatInterval), eq(TimeUnit.MILLISECONDS));
58 static final EncodingName ENCODING = new EncodingName("encoding");
61 private RestconfStream<?> stream;
63 private ScheduledExecutorService executorService;
65 private ScheduledFuture pingFuture;
67 private Session session;
70 void onWebSocketConnectedWithEnabledPing() throws Exception {
71 final int heartbeatInterval = 1000;
72 final var webSocketTestSessionState = new WebSocketTestSessionState(1000, heartbeatInterval);
74 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
75 verify(stream).addSubscriber(webSocketTestSessionState.webSocketSessionHandler, ENCODING, null);
76 verify(executorService).scheduleWithFixedDelay(any(Runnable.class),
77 eq((long) webSocketTestSessionState.heartbeatInterval),
78 eq((long) webSocketTestSessionState.heartbeatInterval), eq(TimeUnit.MILLISECONDS));
82 void onWebSocketConnectedWithDisabledPing() throws Exception {
83 final int heartbeatInterval = 0;
84 final var webSocketTestSessionState = new WebSocketTestSessionState(1000, heartbeatInterval);
86 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
87 verify(stream).addSubscriber(webSocketTestSessionState.webSocketSessionHandler, ENCODING, null);
88 verifyNoMoreInteractions(executorService);
92 void onWebSocketConnectedWithAlreadyOpenSession() throws Exception {
93 final var webSocketTestSessionState = new WebSocketTestSessionState(150, 8000);
94 when(session.isOpen()).thenReturn(true);
96 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
97 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
98 verify(stream).addSubscriber(any(), any(), any());
102 void onWebSocketClosedWithOpenSession() throws Exception {
103 final var webSocketTestSessionState = new WebSocketTestSessionState(200, 10000);
104 final var reg = mock(Registration.class);
106 doReturn(reg).when(stream).addSubscriber(webSocketTestSessionState.webSocketSessionHandler, ENCODING, null);
107 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
109 webSocketTestSessionState.webSocketSessionHandler.onWebSocketClosed(200, "Simulated close");
114 void onWebSocketClosedWithNotInitialisedSession() {
115 final var webSocketTestSessionState = new WebSocketTestSessionState(0, 0);
116 webSocketTestSessionState.webSocketSessionHandler.onWebSocketClosed(500, "Simulated close");
117 verifyNoMoreInteractions(stream);
121 void onWebSocketErrorWithEnabledPingAndLivingSession() throws Exception {
122 final var webSocketTestSessionState = new WebSocketTestSessionState(150, 8000);
123 final var reg = mock(Registration.class);
125 when(session.isOpen()).thenReturn(true);
126 when(stream.addSubscriber(webSocketTestSessionState.webSocketSessionHandler, ENCODING, null))
128 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
129 when(pingFuture.isCancelled()).thenReturn(false);
130 when(pingFuture.isDone()).thenReturn(false);
132 final var sampleError = new IllegalStateException("Simulated error");
133 doNothing().when(reg).close();
134 webSocketTestSessionState.webSocketSessionHandler.onWebSocketError(sampleError);
136 verify(session).close();
137 verify(pingFuture).cancel(anyBoolean());
141 void onWebSocketErrorWithEnabledPingAndDeadSession() throws Exception {
142 final var webSocketTestSessionState = new WebSocketTestSessionState(150, 8000);
143 final var reg = mock(Registration.class);
145 when(session.isOpen()).thenReturn(false);
146 when(stream.addSubscriber(webSocketTestSessionState.webSocketSessionHandler, ENCODING, null))
148 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
150 final var sampleError = new IllegalStateException("Simulated error");
151 webSocketTestSessionState.webSocketSessionHandler.onWebSocketError(sampleError);
153 verify(session, never()).close();
154 verify(pingFuture).cancel(anyBoolean());
158 void onWebSocketErrorWithDisabledPingAndDeadSession() throws Exception {
159 final var webSocketTestSessionState = new WebSocketTestSessionState(150, 8000);
160 final var reg = mock(Registration.class);
162 when(session.isOpen()).thenReturn(false);
163 when(stream.addSubscriber(webSocketTestSessionState.webSocketSessionHandler, ENCODING, null))
165 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
166 when(pingFuture.isDone()).thenReturn(true);
168 final var sampleError = new IllegalStateException("Simulated error");
169 webSocketTestSessionState.webSocketSessionHandler.onWebSocketError(sampleError);
171 verify(session, never()).close();
172 verify(pingFuture, never()).cancel(anyBoolean());
176 void sendDataMessageWithDisabledFragmentation() throws Exception {
177 final var webSocketTestSessionState = new WebSocketTestSessionState(0, 0);
178 final var remoteEndpoint = mock(RemoteEndpoint.class);
179 when(session.isOpen()).thenReturn(true);
180 when(session.getRemote()).thenReturn(remoteEndpoint);
181 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
183 final String testMessage = generateRandomStringOfLength(100);
184 webSocketTestSessionState.webSocketSessionHandler.sendDataMessage(testMessage);
185 verify(remoteEndpoint).sendString(testMessage);
189 void sendDataMessageWithDisabledFragAndDeadSession() {
190 final var webSocketTestSessionState = new WebSocketTestSessionState(0, 0);
191 final var remoteEndpoint = mock(RemoteEndpoint.class);
192 when(session.isOpen()).thenReturn(false);
193 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
195 final String testMessage = generateRandomStringOfLength(11);
196 webSocketTestSessionState.webSocketSessionHandler.sendDataMessage(testMessage);
197 verifyNoMoreInteractions(remoteEndpoint);
201 void sendDataMessageWithEnabledFragAndSmallMessage() throws Exception {
202 final var webSocketTestSessionState = new WebSocketTestSessionState(100, 0);
203 final var remoteEndpoint = mock(RemoteEndpoint.class);
204 when(session.isOpen()).thenReturn(true);
205 when(session.getRemote()).thenReturn(remoteEndpoint);
206 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
208 // in both cases, fragmentation should not be applied
209 final String testMessage1 = generateRandomStringOfLength(100);
210 final String testMessage2 = generateRandomStringOfLength(50);
211 webSocketTestSessionState.webSocketSessionHandler.sendDataMessage(testMessage1);
212 webSocketTestSessionState.webSocketSessionHandler.sendDataMessage(testMessage2);
213 verify(remoteEndpoint).sendString(testMessage1);
214 verify(remoteEndpoint).sendString(testMessage2);
215 verify(remoteEndpoint, never()).sendPartialString(anyString(), anyBoolean());
219 void sendDataMessageWithZeroLength() {
220 final var webSocketTestSessionState = new WebSocketTestSessionState(100, 0);
221 final var remoteEndpoint = mock(RemoteEndpoint.class);
222 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
224 webSocketTestSessionState.webSocketSessionHandler.sendDataMessage("");
225 verifyNoMoreInteractions(remoteEndpoint);
229 void sendDataMessageWithEnabledFragAndLargeMessage1() throws Exception {
230 final var webSocketTestSessionState = new WebSocketTestSessionState(100, 0);
231 final var remoteEndpoint = mock(RemoteEndpoint.class);
232 when(session.isOpen()).thenReturn(true);
233 when(session.getRemote()).thenReturn(remoteEndpoint);
234 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
236 // there should be 10 fragments of length 100 characters
237 final String testMessage = generateRandomStringOfLength(1000);
238 webSocketTestSessionState.webSocketSessionHandler.sendDataMessage(testMessage);
239 final var messageCaptor = ArgumentCaptor.forClass(String.class);
240 final var isLastCaptor = ArgumentCaptor.forClass(Boolean.class);
241 verify(remoteEndpoint, times(10)).sendPartialString(
242 messageCaptor.capture(), isLastCaptor.capture());
244 final var allMessages = messageCaptor.getAllValues();
245 final var isLastFlags = isLastCaptor.getAllValues();
246 assertTrue(allMessages.stream().allMatch(s -> s.length() == webSocketTestSessionState.maxFragmentSize));
247 assertTrue(isLastFlags.subList(0, 9).stream().noneMatch(isLast -> isLast));
248 assertTrue(isLastFlags.get(9));
252 void sendDataMessageWithEnabledFragAndLargeMessage2() throws Exception {
253 final var webSocketTestSessionState = new WebSocketTestSessionState(100, 0);
254 final var remoteEndpoint = mock(RemoteEndpoint.class);
255 when(session.isOpen()).thenReturn(true);
256 when(session.getRemote()).thenReturn(remoteEndpoint);
257 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
259 // there should be 10 fragments, the last fragment should be the shortest one
260 final String testMessage = generateRandomStringOfLength(950);
261 webSocketTestSessionState.webSocketSessionHandler.sendDataMessage(testMessage);
262 final var messageCaptor = ArgumentCaptor.forClass(String.class);
263 final var isLastCaptor = ArgumentCaptor.forClass(Boolean.class);
264 verify(remoteEndpoint, times(10)).sendPartialString(
265 messageCaptor.capture(), isLastCaptor.capture());
267 final var allMessages = messageCaptor.getAllValues();
268 final var isLastFlags = isLastCaptor.getAllValues();
269 assertTrue(allMessages.subList(0, 9).stream().allMatch(s ->
270 s.length() == webSocketTestSessionState.maxFragmentSize));
271 assertEquals(50, allMessages.get(9).length());
272 assertTrue(isLastFlags.subList(0, 9).stream().noneMatch(isLast -> isLast));
273 assertTrue(isLastFlags.get(9));
276 private static String generateRandomStringOfLength(final int length) {
277 final String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvxyz";
278 final var sb = new StringBuilder(length);
279 for (int i = 0; i < length; i++) {
280 int index = (int) (alphabet.length() * Math.random());
281 sb.append(alphabet.charAt(index));
283 return sb.toString();