Rename restconf-nb-rfc8040 to restconf-nb
[netconf.git] / restconf / restconf-nb / src / test / java / org / opendaylight / restconf / nb / rfc8040 / streams / WebSocketSessionHandlerTest.java
1 /*
2  * Copyright © 2019 FRINX s.r.o. All rights reserved.
3  *
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
7  */
8 package org.opendaylight.restconf.nb.rfc8040.streams;
9
10 import static org.junit.Assert.assertEquals;
11 import static org.junit.Assert.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.mock;
17 import static org.mockito.Mockito.never;
18 import static org.mockito.Mockito.times;
19 import static org.mockito.Mockito.verify;
20 import static org.mockito.Mockito.verifyNoMoreInteractions;
21 import static org.mockito.Mockito.when;
22
23 import java.io.IOException;
24 import java.util.List;
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.Test;
31 import org.mockito.ArgumentCaptor;
32 import org.opendaylight.restconf.nb.rfc8040.streams.listeners.BaseListenerInterface;
33
34 public class WebSocketSessionHandlerTest {
35
36     private static final class WebSocketTestSessionState {
37         private final BaseListenerInterface listener;
38         private final ScheduledExecutorService executorService;
39         private final WebSocketSessionHandler webSocketSessionHandler;
40         private final int heartbeatInterval;
41         private final int maxFragmentSize;
42         private final ScheduledFuture pingFuture;
43
44         private WebSocketTestSessionState(final int maxFragmentSize, final int heartbeatInterval) {
45             listener = mock(BaseListenerInterface.class);
46             executorService = mock(ScheduledExecutorService.class);
47             this.heartbeatInterval = heartbeatInterval;
48             this.maxFragmentSize = maxFragmentSize;
49             webSocketSessionHandler = new WebSocketSessionHandler(executorService, listener, maxFragmentSize,
50                     heartbeatInterval);
51             pingFuture = mock(ScheduledFuture.class);
52             when(executorService.scheduleWithFixedDelay(any(Runnable.class), eq((long) heartbeatInterval),
53                 eq((long) heartbeatInterval), eq(TimeUnit.MILLISECONDS))).thenReturn(pingFuture);
54         }
55     }
56
57     @Test
58     public void onWebSocketConnectedWithEnabledPing() {
59         final int heartbeatInterval = 1000;
60         final WebSocketTestSessionState webSocketTestSessionState = new WebSocketTestSessionState(
61                 1000, heartbeatInterval);
62         final Session session = mock(Session.class);
63
64         webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
65         verify(webSocketTestSessionState.listener).addSubscriber(
66                 webSocketTestSessionState.webSocketSessionHandler);
67         verify(webSocketTestSessionState.executorService).scheduleWithFixedDelay(any(Runnable.class),
68                 eq((long) webSocketTestSessionState.heartbeatInterval),
69                 eq((long) webSocketTestSessionState.heartbeatInterval), eq(TimeUnit.MILLISECONDS));
70     }
71
72     @Test
73     public void onWebSocketConnectedWithDisabledPing() {
74         final int heartbeatInterval = 0;
75         final WebSocketTestSessionState webSocketTestSessionState = new WebSocketTestSessionState(
76                 1000, heartbeatInterval);
77         final Session session = mock(Session.class);
78
79         webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
80         verify(webSocketTestSessionState.listener).addSubscriber(
81                 webSocketTestSessionState.webSocketSessionHandler);
82         verifyNoMoreInteractions(webSocketTestSessionState.executorService);
83     }
84
85     @Test
86     public void onWebSocketConnectedWithAlreadyOpenSession() {
87         final WebSocketTestSessionState webSocketTestSessionState = new WebSocketTestSessionState(150, 8000);
88         final Session session = mock(Session.class);
89         when(session.isOpen()).thenReturn(true);
90
91         webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
92         webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
93         verify(webSocketTestSessionState.listener, times(1)).addSubscriber(any());
94     }
95
96     @Test
97     public void onWebSocketClosedWithOpenSession() {
98         final WebSocketTestSessionState webSocketTestSessionState = new WebSocketTestSessionState(200, 10000);
99         final Session session = mock(Session.class);
100
101         webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
102         verify(webSocketTestSessionState.listener).addSubscriber(
103                 webSocketTestSessionState.webSocketSessionHandler);
104
105         webSocketTestSessionState.webSocketSessionHandler.onWebSocketClosed(200, "Simulated close");
106         verify(webSocketTestSessionState.listener).removeSubscriber(
107                 webSocketTestSessionState.webSocketSessionHandler);
108     }
109
110     @Test
111     public void onWebSocketClosedWithNotInitialisedSession() {
112         final WebSocketTestSessionState webSocketTestSessionState = new WebSocketTestSessionState(300, 12000);
113         webSocketTestSessionState.webSocketSessionHandler.onWebSocketClosed(500, "Simulated close");
114         verifyNoMoreInteractions(webSocketTestSessionState.listener);
115     }
116
117     @Test
118     public void onWebSocketErrorWithEnabledPingAndLivingSession() {
119         final WebSocketTestSessionState webSocketTestSessionState = new WebSocketTestSessionState(150, 8000);
120         final Session session = mock(Session.class);
121         when(session.isOpen()).thenReturn(true);
122         final Throwable sampleError = new IllegalStateException("Simulated error");
123         webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
124         when(webSocketTestSessionState.pingFuture.isCancelled()).thenReturn(false);
125         when(webSocketTestSessionState.pingFuture.isDone()).thenReturn(false);
126
127         webSocketTestSessionState.webSocketSessionHandler.onWebSocketError(sampleError);
128         verify(webSocketTestSessionState.listener).removeSubscriber(
129                 webSocketTestSessionState.webSocketSessionHandler);
130         verify(session).close();
131         verify(webSocketTestSessionState.pingFuture).cancel(anyBoolean());
132     }
133
134     @Test
135     public void onWebSocketErrorWithEnabledPingAndDeadSession() {
136         final WebSocketTestSessionState webSocketTestSessionState = new WebSocketTestSessionState(150, 8000);
137         final Session session = mock(Session.class);
138         when(session.isOpen()).thenReturn(false);
139         final Throwable sampleError = new IllegalStateException("Simulated error");
140         webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
141
142         webSocketTestSessionState.webSocketSessionHandler.onWebSocketError(sampleError);
143         verify(webSocketTestSessionState.listener).removeSubscriber(
144                 webSocketTestSessionState.webSocketSessionHandler);
145         verify(session, never()).close();
146         verify(webSocketTestSessionState.pingFuture).cancel(anyBoolean());
147     }
148
149     @Test
150     public void onWebSocketErrorWithDisabledPingAndDeadSession() {
151         final WebSocketTestSessionState webSocketTestSessionState = new WebSocketTestSessionState(150, 8000);
152         final Session session = mock(Session.class);
153         when(session.isOpen()).thenReturn(false);
154         final Throwable sampleError = new IllegalStateException("Simulated error");
155         webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
156         when(webSocketTestSessionState.pingFuture.isCancelled()).thenReturn(false);
157         when(webSocketTestSessionState.pingFuture.isDone()).thenReturn(true);
158
159         webSocketTestSessionState.webSocketSessionHandler.onWebSocketError(sampleError);
160         verify(webSocketTestSessionState.listener).removeSubscriber(
161                 webSocketTestSessionState.webSocketSessionHandler);
162         verify(session, never()).close();
163         verify(webSocketTestSessionState.pingFuture, never()).cancel(anyBoolean());
164     }
165
166     @Test
167     public void sendDataMessageWithDisabledFragmentation() throws IOException {
168         final WebSocketTestSessionState webSocketTestSessionState = new WebSocketTestSessionState(0, 0);
169         final Session session = mock(Session.class);
170         final RemoteEndpoint remoteEndpoint = mock(RemoteEndpoint.class);
171         when(session.isOpen()).thenReturn(true);
172         when(session.getRemote()).thenReturn(remoteEndpoint);
173         webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
174
175         final String testMessage = generateRandomStringOfLength(100);
176         webSocketTestSessionState.webSocketSessionHandler.sendDataMessage(testMessage);
177         verify(remoteEndpoint).sendString(testMessage);
178     }
179
180     @Test
181     public void sendDataMessageWithDisabledFragAndDeadSession() {
182         final WebSocketTestSessionState webSocketTestSessionState = new WebSocketTestSessionState(0, 0);
183         final Session session = mock(Session.class);
184         final RemoteEndpoint remoteEndpoint = mock(RemoteEndpoint.class);
185         when(session.isOpen()).thenReturn(false);
186         when(session.getRemote()).thenReturn(remoteEndpoint);
187         webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
188
189         final String testMessage = generateRandomStringOfLength(11);
190         webSocketTestSessionState.webSocketSessionHandler.sendDataMessage(testMessage);
191         verifyNoMoreInteractions(remoteEndpoint);
192     }
193
194     @Test
195     public void sendDataMessageWithEnabledFragAndSmallMessage() throws IOException {
196         final WebSocketTestSessionState webSocketTestSessionState = new WebSocketTestSessionState(100, 0);
197         final Session session = mock(Session.class);
198         final RemoteEndpoint remoteEndpoint = mock(RemoteEndpoint.class);
199         when(session.isOpen()).thenReturn(true);
200         when(session.getRemote()).thenReturn(remoteEndpoint);
201         webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
202
203         // in both cases, fragmentation should not be applied
204         final String testMessage1 = generateRandomStringOfLength(100);
205         final String testMessage2 = generateRandomStringOfLength(50);
206         webSocketTestSessionState.webSocketSessionHandler.sendDataMessage(testMessage1);
207         webSocketTestSessionState.webSocketSessionHandler.sendDataMessage(testMessage2);
208         verify(remoteEndpoint).sendString(testMessage1);
209         verify(remoteEndpoint).sendString(testMessage2);
210         verify(remoteEndpoint, never()).sendPartialString(anyString(), anyBoolean());
211     }
212
213     @Test
214     public void sendDataMessageWithZeroLength() {
215         final WebSocketTestSessionState webSocketTestSessionState = new WebSocketTestSessionState(100, 0);
216         final Session session = mock(Session.class);
217         final RemoteEndpoint remoteEndpoint = mock(RemoteEndpoint.class);
218         when(session.isOpen()).thenReturn(true);
219         when(session.getRemote()).thenReturn(remoteEndpoint);
220         webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
221
222         webSocketTestSessionState.webSocketSessionHandler.sendDataMessage("");
223         verifyNoMoreInteractions(remoteEndpoint);
224     }
225
226     @Test
227     public void sendDataMessageWithEnabledFragAndLargeMessage1() throws IOException {
228         final WebSocketTestSessionState webSocketTestSessionState = new WebSocketTestSessionState(100, 0);
229         final Session session = mock(Session.class);
230         final RemoteEndpoint remoteEndpoint = mock(RemoteEndpoint.class);
231         when(session.isOpen()).thenReturn(true);
232         when(session.getRemote()).thenReturn(remoteEndpoint);
233         webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
234
235         // there should be 10 fragments of length 100 characters
236         final String testMessage = generateRandomStringOfLength(1000);
237         webSocketTestSessionState.webSocketSessionHandler.sendDataMessage(testMessage);
238         final ArgumentCaptor<String> messageCaptor = ArgumentCaptor.forClass(String.class);
239         final ArgumentCaptor<Boolean> isLastCaptor = ArgumentCaptor.forClass(Boolean.class);
240         verify(remoteEndpoint, times(10)).sendPartialString(
241                 messageCaptor.capture(), isLastCaptor.capture());
242
243         final List<String> allMessages = messageCaptor.getAllValues();
244         final List<Boolean> isLastFlags = isLastCaptor.getAllValues();
245         assertTrue(allMessages.stream().allMatch(s -> s.length() == webSocketTestSessionState.maxFragmentSize));
246         assertTrue(isLastFlags.subList(0, 9).stream().noneMatch(isLast -> isLast));
247         assertTrue(isLastFlags.get(9));
248     }
249
250     @Test
251     public void sendDataMessageWithEnabledFragAndLargeMessage2() throws IOException {
252         final WebSocketTestSessionState webSocketTestSessionState = new WebSocketTestSessionState(100, 0);
253         final Session session = mock(Session.class);
254         final RemoteEndpoint remoteEndpoint = mock(RemoteEndpoint.class);
255         when(session.isOpen()).thenReturn(true);
256         when(session.getRemote()).thenReturn(remoteEndpoint);
257         webSocketTestSessionState.webSocketSessionHandler.onWebSocketConnected(session);
258
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 ArgumentCaptor<String> messageCaptor = ArgumentCaptor.forClass(String.class);
263         final ArgumentCaptor<Boolean> isLastCaptor = ArgumentCaptor.forClass(Boolean.class);
264         verify(remoteEndpoint, times(10)).sendPartialString(
265                 messageCaptor.capture(), isLastCaptor.capture());
266
267         final List<String> allMessages = messageCaptor.getAllValues();
268         final List<Boolean> 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));
274     }
275
276     private static String generateRandomStringOfLength(final int length) {
277         final String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvxyz";
278         final StringBuilder 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));
282         }
283         return sb.toString();
284     }
285 }