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
9 package org.opendaylight.restconf.nb.rfc8040.streams.websockets;
11 import java.io.IOException;
12 import java.util.List;
13 import java.util.concurrent.ScheduledExecutorService;
14 import java.util.concurrent.ScheduledFuture;
15 import java.util.concurrent.TimeUnit;
16 import org.eclipse.jetty.websocket.api.RemoteEndpoint;
17 import org.eclipse.jetty.websocket.api.Session;
18 import org.junit.Assert;
19 import org.junit.Test;
20 import org.mockito.ArgumentCaptor;
21 import org.mockito.Mockito;
22 import org.opendaylight.restconf.nb.rfc8040.streams.listeners.BaseListenerInterface;
24 public class WebSocketSessionHandlerTest {
26 private static final class WebSocketTestSessionState {
27 private final BaseListenerInterface listener;
28 private final ScheduledExecutorService executorService;
29 private final WebSocketSessionHandler webSocketSessionHandler;
30 private final int heartbeatInterval;
31 private final int maxFragmentSize;
32 private final ScheduledFuture pingFuture;
34 private WebSocketTestSessionState(final int maxFragmentSize, final int heartbeatInterval) {
35 listener = Mockito.mock(BaseListenerInterface.class);
36 executorService = Mockito.mock(ScheduledExecutorService.class);
37 this.heartbeatInterval = heartbeatInterval;
38 this.maxFragmentSize = maxFragmentSize;
39 webSocketSessionHandler = new WebSocketSessionHandler(executorService, listener, maxFragmentSize,
41 pingFuture = Mockito.mock(ScheduledFuture.class);
42 Mockito.when(executorService.scheduleWithFixedDelay(Mockito.any(Runnable.class),
43 Mockito.eq((long) heartbeatInterval), Mockito.eq((long) heartbeatInterval),
44 Mockito.eq(TimeUnit.MILLISECONDS))).thenReturn(pingFuture);
49 public void onWebSocketConnectedWithEnabledPing() {
50 final int heartbeatInterval = 1000;
51 final WebSocketTestSessionState webSocketTestSessionState = new WebSocketTestSessionState(
52 1000, heartbeatInterval);
53 final Session session = Mockito.mock(Session.class);
55 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
56 Mockito.verify(webSocketTestSessionState.listener).addSubscriber(
57 webSocketTestSessionState.webSocketSessionHandler);
58 Mockito.verify(webSocketTestSessionState.executorService).scheduleWithFixedDelay(Mockito.any(Runnable.class),
59 Mockito.eq((long) webSocketTestSessionState.heartbeatInterval),
60 Mockito.eq((long) webSocketTestSessionState.heartbeatInterval), Mockito.eq(TimeUnit.MILLISECONDS));
64 public void onWebSocketConnectedWithDisabledPing() {
65 final int heartbeatInterval = 0;
66 final WebSocketTestSessionState webSocketTestSessionState = new WebSocketTestSessionState(
67 1000, heartbeatInterval);
68 final Session session = Mockito.mock(Session.class);
70 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
71 Mockito.verify(webSocketTestSessionState.listener).addSubscriber(
72 webSocketTestSessionState.webSocketSessionHandler);
73 Mockito.verifyZeroInteractions(webSocketTestSessionState.executorService);
77 public void onWebSocketConnectedWithAlreadyOpenSession() {
78 final WebSocketTestSessionState webSocketTestSessionState = new WebSocketTestSessionState(150, 8000);
79 final Session session = Mockito.mock(Session.class);
80 Mockito.when(session.isOpen()).thenReturn(true);
82 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
83 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
84 Mockito.verify(webSocketTestSessionState.listener, Mockito.times(1)).addSubscriber(Mockito.any());
88 public void onWebSocketClosedWithOpenSession() {
89 final WebSocketTestSessionState webSocketTestSessionState = new WebSocketTestSessionState(200, 10000);
90 final Session session = Mockito.mock(Session.class);
92 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
93 Mockito.verify(webSocketTestSessionState.listener).addSubscriber(
94 webSocketTestSessionState.webSocketSessionHandler);
96 webSocketTestSessionState.webSocketSessionHandler.onWebSocketClosed(200, "Simulated close");
97 Mockito.verify(webSocketTestSessionState.listener).removeSubscriber(
98 webSocketTestSessionState.webSocketSessionHandler);
102 public void onWebSocketClosedWithNotInitialisedSession() {
103 final WebSocketTestSessionState webSocketTestSessionState = new WebSocketTestSessionState(300, 12000);
104 webSocketTestSessionState.webSocketSessionHandler.onWebSocketClosed(500, "Simulated close");
105 Mockito.verifyZeroInteractions(webSocketTestSessionState.listener);
109 public void onWebSocketErrorWithEnabledPingAndLivingSession() {
110 final WebSocketTestSessionState webSocketTestSessionState = new WebSocketTestSessionState(150, 8000);
111 final Session session = Mockito.mock(Session.class);
112 Mockito.when(session.isOpen()).thenReturn(true);
113 final Throwable sampleError = new IllegalStateException("Simulated error");
114 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
115 Mockito.when(webSocketTestSessionState.pingFuture.isCancelled()).thenReturn(false);
116 Mockito.when(webSocketTestSessionState.pingFuture.isDone()).thenReturn(false);
118 webSocketTestSessionState.webSocketSessionHandler.onWebSocketError(sampleError);
119 Mockito.verify(webSocketTestSessionState.listener).removeSubscriber(
120 webSocketTestSessionState.webSocketSessionHandler);
121 Mockito.verify(session).close();
122 Mockito.verify(webSocketTestSessionState.pingFuture).cancel(Mockito.anyBoolean());
126 public void onWebSocketErrorWithEnabledPingAndDeadSession() {
127 final WebSocketTestSessionState webSocketTestSessionState = new WebSocketTestSessionState(150, 8000);
128 final Session session = Mockito.mock(Session.class);
129 Mockito.when(session.isOpen()).thenReturn(false);
130 final Throwable sampleError = new IllegalStateException("Simulated error");
131 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
133 webSocketTestSessionState.webSocketSessionHandler.onWebSocketError(sampleError);
134 Mockito.verify(webSocketTestSessionState.listener).removeSubscriber(
135 webSocketTestSessionState.webSocketSessionHandler);
136 Mockito.verify(session, Mockito.never()).close();
137 Mockito.verify(webSocketTestSessionState.pingFuture).cancel(Mockito.anyBoolean());
141 public void onWebSocketErrorWithDisabledPingAndDeadSession() {
142 final WebSocketTestSessionState webSocketTestSessionState = new WebSocketTestSessionState(150, 8000);
143 final Session session = Mockito.mock(Session.class);
144 Mockito.when(session.isOpen()).thenReturn(false);
145 final Throwable sampleError = new IllegalStateException("Simulated error");
146 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
147 Mockito.when(webSocketTestSessionState.pingFuture.isCancelled()).thenReturn(false);
148 Mockito.when(webSocketTestSessionState.pingFuture.isDone()).thenReturn(true);
150 webSocketTestSessionState.webSocketSessionHandler.onWebSocketError(sampleError);
151 Mockito.verify(webSocketTestSessionState.listener).removeSubscriber(
152 webSocketTestSessionState.webSocketSessionHandler);
153 Mockito.verify(session, Mockito.never()).close();
154 Mockito.verify(webSocketTestSessionState.pingFuture, Mockito.never()).cancel(Mockito.anyBoolean());
158 public void sendDataMessageWithDisabledFragmentation() throws IOException {
159 final WebSocketTestSessionState webSocketTestSessionState = new WebSocketTestSessionState(0, 0);
160 final Session session = Mockito.mock(Session.class);
161 final RemoteEndpoint remoteEndpoint = Mockito.mock(RemoteEndpoint.class);
162 Mockito.when(session.isOpen()).thenReturn(true);
163 Mockito.when(session.getRemote()).thenReturn(remoteEndpoint);
164 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
166 final String testMessage = generateRandomStringOfLength(100);
167 webSocketTestSessionState.webSocketSessionHandler.sendDataMessage(testMessage);
168 Mockito.verify(remoteEndpoint).sendString(testMessage);
172 public void sendDataMessageWithDisabledFragAndDeadSession() {
173 final WebSocketTestSessionState webSocketTestSessionState = new WebSocketTestSessionState(0, 0);
174 final Session session = Mockito.mock(Session.class);
175 final RemoteEndpoint remoteEndpoint = Mockito.mock(RemoteEndpoint.class);
176 Mockito.when(session.isOpen()).thenReturn(false);
177 Mockito.when(session.getRemote()).thenReturn(remoteEndpoint);
178 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
180 final String testMessage = generateRandomStringOfLength(11);
181 webSocketTestSessionState.webSocketSessionHandler.sendDataMessage(testMessage);
182 Mockito.verifyZeroInteractions(remoteEndpoint);
186 public void sendDataMessageWithEnabledFragAndSmallMessage() throws IOException {
187 final WebSocketTestSessionState webSocketTestSessionState = new WebSocketTestSessionState(100, 0);
188 final Session session = Mockito.mock(Session.class);
189 final RemoteEndpoint remoteEndpoint = Mockito.mock(RemoteEndpoint.class);
190 Mockito.when(session.isOpen()).thenReturn(true);
191 Mockito.when(session.getRemote()).thenReturn(remoteEndpoint);
192 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
194 // in both cases, fragmentation should not be applied
195 final String testMessage1 = generateRandomStringOfLength(100);
196 final String testMessage2 = generateRandomStringOfLength(50);
197 webSocketTestSessionState.webSocketSessionHandler.sendDataMessage(testMessage1);
198 webSocketTestSessionState.webSocketSessionHandler.sendDataMessage(testMessage2);
199 Mockito.verify(remoteEndpoint).sendString(testMessage1);
200 Mockito.verify(remoteEndpoint).sendString(testMessage2);
201 Mockito.verify(remoteEndpoint, Mockito.never()).sendPartialString(Mockito.anyString(), Mockito.anyBoolean());
205 public void sendDataMessageWithZeroLength() {
206 final WebSocketTestSessionState webSocketTestSessionState = new WebSocketTestSessionState(100, 0);
207 final Session session = Mockito.mock(Session.class);
208 final RemoteEndpoint remoteEndpoint = Mockito.mock(RemoteEndpoint.class);
209 Mockito.when(session.isOpen()).thenReturn(true);
210 Mockito.when(session.getRemote()).thenReturn(remoteEndpoint);
211 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
213 webSocketTestSessionState.webSocketSessionHandler.sendDataMessage("");
214 Mockito.verifyZeroInteractions(remoteEndpoint);
218 public void sendDataMessageWithEnabledFragAndLargeMessage1() throws IOException {
219 final WebSocketTestSessionState webSocketTestSessionState = new WebSocketTestSessionState(100, 0);
220 final Session session = Mockito.mock(Session.class);
221 final RemoteEndpoint remoteEndpoint = Mockito.mock(RemoteEndpoint.class);
222 Mockito.when(session.isOpen()).thenReturn(true);
223 Mockito.when(session.getRemote()).thenReturn(remoteEndpoint);
224 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
226 // there should be 10 fragments of length 100 characters
227 final String testMessage = generateRandomStringOfLength(1000);
228 webSocketTestSessionState.webSocketSessionHandler.sendDataMessage(testMessage);
229 final ArgumentCaptor<String> messageCaptor = ArgumentCaptor.forClass(String.class);
230 final ArgumentCaptor<Boolean> isLastCaptor = ArgumentCaptor.forClass(Boolean.class);
231 Mockito.verify(remoteEndpoint, Mockito.times(10)).sendPartialString(
232 messageCaptor.capture(), isLastCaptor.capture());
234 final List<String> allMessages = messageCaptor.getAllValues();
235 final List<Boolean> isLastFlags = isLastCaptor.getAllValues();
236 Assert.assertTrue(allMessages.stream().allMatch(s -> s.length() == webSocketTestSessionState.maxFragmentSize));
237 Assert.assertTrue(isLastFlags.subList(0, 9).stream().noneMatch(isLast -> isLast));
238 Assert.assertTrue(isLastFlags.get(9));
242 public void sendDataMessageWithEnabledFragAndLargeMessage2() throws IOException {
243 final WebSocketTestSessionState webSocketTestSessionState = new WebSocketTestSessionState(100, 0);
244 final Session session = Mockito.mock(Session.class);
245 final RemoteEndpoint remoteEndpoint = Mockito.mock(RemoteEndpoint.class);
246 Mockito.when(session.isOpen()).thenReturn(true);
247 Mockito.when(session.getRemote()).thenReturn(remoteEndpoint);
248 webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
250 // there should be 10 fragments, the last fragment should be the shortest one
251 final String testMessage = generateRandomStringOfLength(950);
252 webSocketTestSessionState.webSocketSessionHandler.sendDataMessage(testMessage);
253 final ArgumentCaptor<String> messageCaptor = ArgumentCaptor.forClass(String.class);
254 final ArgumentCaptor<Boolean> isLastCaptor = ArgumentCaptor.forClass(Boolean.class);
255 Mockito.verify(remoteEndpoint, Mockito.times(10)).sendPartialString(
256 messageCaptor.capture(), isLastCaptor.capture());
258 final List<String> allMessages = messageCaptor.getAllValues();
259 final List<Boolean> isLastFlags = isLastCaptor.getAllValues();
260 Assert.assertTrue(allMessages.subList(0, 9).stream().allMatch(s ->
261 s.length() == webSocketTestSessionState.maxFragmentSize));
262 Assert.assertEquals(50, allMessages.get(9).length());
263 Assert.assertTrue(isLastFlags.subList(0, 9).stream().noneMatch(isLast -> isLast));
264 Assert.assertTrue(isLastFlags.get(9));
267 private static String generateRandomStringOfLength(final int length) {
268 final String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvxyz";
269 final StringBuilder sb = new StringBuilder(length);
270 for (int i = 0; i < length; i++) {
271 int index = (int) (alphabet.length() * Math.random());
272 sb.append(alphabet.charAt(index));
274 return sb.toString();