diff options
author | Kristóf Marussy <kristof@marussy.com> | 2021-10-24 14:34:39 +0200 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2021-10-31 19:26:11 +0100 |
commit | e90f0cd525c619b1d0e21bfcd7466a99853ee710 (patch) | |
tree | 91e8ed0bd2599a099dd67cc4e6132c375333b453 /language-web/src/test/java | |
parent | test(web): websockets fixes and tests (diff) | |
download | refinery-e90f0cd525c619b1d0e21bfcd7466a99853ee710.tar.gz refinery-e90f0cd525c619b1d0e21bfcd7466a99853ee710.tar.zst refinery-e90f0cd525c619b1d0e21bfcd7466a99853ee710.zip |
test(web): more websocket integration tests
Diffstat (limited to 'language-web/src/test/java')
6 files changed, 237 insertions, 105 deletions
diff --git a/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java b/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java index 60581b5c..5ccd155f 100644 --- a/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java +++ b/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java | |||
@@ -3,25 +3,25 @@ package tools.refinery.language.web; | |||
3 | import static org.hamcrest.MatcherAssert.assertThat; | 3 | import static org.hamcrest.MatcherAssert.assertThat; |
4 | import static org.hamcrest.Matchers.equalTo; | 4 | import static org.hamcrest.Matchers.equalTo; |
5 | import static org.hamcrest.Matchers.hasSize; | 5 | import static org.hamcrest.Matchers.hasSize; |
6 | import static org.hamcrest.Matchers.instanceOf; | ||
6 | import static org.hamcrest.Matchers.startsWith; | 7 | import static org.hamcrest.Matchers.startsWith; |
7 | import static org.junit.jupiter.api.Assertions.fail; | 8 | import static org.junit.jupiter.api.Assertions.assertThrows; |
8 | 9 | ||
9 | import java.io.IOException; | 10 | import java.io.IOException; |
10 | import java.net.InetSocketAddress; | 11 | import java.net.InetSocketAddress; |
11 | import java.net.URI; | 12 | import java.net.URI; |
12 | import java.time.Duration; | 13 | import java.util.concurrent.CompletableFuture; |
13 | import java.util.ArrayList; | 14 | import java.util.concurrent.CompletionException; |
14 | import java.util.List; | ||
15 | 15 | ||
16 | import org.eclipse.jetty.http.HttpHeader; | ||
17 | import org.eclipse.jetty.http.HttpStatus; | ||
16 | import org.eclipse.jetty.server.Server; | 18 | import org.eclipse.jetty.server.Server; |
17 | import org.eclipse.jetty.servlet.ServletContextHandler; | 19 | import org.eclipse.jetty.servlet.ServletContextHandler; |
20 | import org.eclipse.jetty.servlet.ServletHolder; | ||
18 | import org.eclipse.jetty.websocket.api.Session; | 21 | import org.eclipse.jetty.websocket.api.Session; |
19 | import org.eclipse.jetty.websocket.api.StatusCode; | 22 | import org.eclipse.jetty.websocket.api.StatusCode; |
20 | import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; | ||
21 | import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; | ||
22 | import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; | ||
23 | import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; | ||
24 | import org.eclipse.jetty.websocket.api.annotations.WebSocket; | 23 | import org.eclipse.jetty.websocket.api.annotations.WebSocket; |
24 | import org.eclipse.jetty.websocket.api.exceptions.UpgradeException; | ||
25 | import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; | 25 | import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; |
26 | import org.eclipse.jetty.websocket.client.WebSocketClient; | 26 | import org.eclipse.jetty.websocket.client.WebSocketClient; |
27 | import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; | 27 | import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; |
@@ -30,13 +30,17 @@ import org.eclipse.xtext.testing.GlobalRegistries.GlobalStateMemento; | |||
30 | import org.junit.jupiter.api.AfterEach; | 30 | import org.junit.jupiter.api.AfterEach; |
31 | import org.junit.jupiter.api.BeforeEach; | 31 | import org.junit.jupiter.api.BeforeEach; |
32 | import org.junit.jupiter.api.Test; | 32 | import org.junit.jupiter.api.Test; |
33 | import org.junit.jupiter.params.ParameterizedTest; | ||
34 | import org.junit.jupiter.params.provider.ValueSource; | ||
33 | 35 | ||
36 | import tools.refinery.language.web.tests.WebSocketIntegrationTestClient; | ||
37 | import tools.refinery.language.web.xtext.servlet.XtextStatusCode; | ||
34 | import tools.refinery.language.web.xtext.servlet.XtextWebSocketServlet; | 38 | import tools.refinery.language.web.xtext.servlet.XtextWebSocketServlet; |
35 | 39 | ||
36 | class ProblemWebSocketServletIntegrationTest { | 40 | class ProblemWebSocketServletIntegrationTest { |
37 | private static int SERVER_PORT = 28080; | 41 | private static int SERVER_PORT = 28080; |
38 | 42 | ||
39 | private static long TIMEOUT_MILLIS = Duration.ofSeconds(1).toMillis(); | 43 | private static String SERVLET_URI = "/xtext-service"; |
40 | 44 | ||
41 | private GlobalStateMemento stateBeforeInjectorCreation; | 45 | private GlobalStateMemento stateBeforeInjectorCreation; |
42 | 46 | ||
@@ -45,128 +49,156 @@ class ProblemWebSocketServletIntegrationTest { | |||
45 | private WebSocketClient client; | 49 | private WebSocketClient client; |
46 | 50 | ||
47 | @BeforeEach | 51 | @BeforeEach |
48 | void startServer() throws Exception { | 52 | void beforeEach() throws Exception { |
49 | stateBeforeInjectorCreation = GlobalRegistries.makeCopyOfGlobalState(); | 53 | stateBeforeInjectorCreation = GlobalRegistries.makeCopyOfGlobalState(); |
50 | server = new Server(new InetSocketAddress(SERVER_PORT)); | ||
51 | var handler = new ServletContextHandler(); | ||
52 | handler.addServlet(ProblemWebSocketServlet.class, "/xtext-service/*"); | ||
53 | JettyWebSocketServletContainerInitializer.configure(handler, null); | ||
54 | server.setHandler(handler); | ||
55 | server.start(); | ||
56 | client = new WebSocketClient(); | 54 | client = new WebSocketClient(); |
57 | client.start(); | 55 | client.start(); |
58 | } | 56 | } |
59 | 57 | ||
60 | @AfterEach | 58 | @AfterEach |
61 | void stopServer() throws Exception { | 59 | void afterEach() throws Exception { |
62 | client.stop(); | 60 | client.stop(); |
63 | server.stop(); | 61 | client = null; |
62 | if (server != null) { | ||
63 | server.stop(); | ||
64 | server = null; | ||
65 | } | ||
64 | stateBeforeInjectorCreation.restoreGlobalState(); | 66 | stateBeforeInjectorCreation.restoreGlobalState(); |
67 | stateBeforeInjectorCreation = null; | ||
65 | } | 68 | } |
66 | 69 | ||
67 | @Test | 70 | @Test |
68 | void updateTest() throws IOException { | 71 | void updateTest() { |
72 | startServer(null); | ||
69 | var clientSocket = new UpdateTestClient(); | 73 | var clientSocket = new UpdateTestClient(); |
70 | var upgradeRequest = new ClientUpgradeRequest(); | 74 | var session = connect(clientSocket, null, XtextWebSocketServlet.XTEXT_SUBPROTOCOL_V1); |
71 | upgradeRequest.setSubProtocols(XtextWebSocketServlet.XTEXT_SUBPROTOCOL_V1); | ||
72 | var sessionFuture = client.connect(clientSocket, URI.create("ws://localhost:" + SERVER_PORT + "/xtext-service"), | ||
73 | upgradeRequest); | ||
74 | var session = sessionFuture.join(); | ||
75 | assertThat(session.getUpgradeResponse().getAcceptedSubProtocol(), | 75 | assertThat(session.getUpgradeResponse().getAcceptedSubProtocol(), |
76 | equalTo(XtextWebSocketServlet.XTEXT_SUBPROTOCOL_V1)); | 76 | equalTo(XtextWebSocketServlet.XTEXT_SUBPROTOCOL_V1)); |
77 | clientSocket.waitForTestResult(); | 77 | clientSocket.waitForTestResult(); |
78 | assertThat(clientSocket.getCloseStatusCode(), equalTo(StatusCode.NORMAL)); | ||
79 | var responses = clientSocket.getResponses(); | ||
80 | assertThat(responses, hasSize(5)); | ||
81 | assertThat(responses.get(0), equalTo("{\"id\":\"foo\",\"response\":{\"stateId\":\"-80000000\"}}")); | ||
82 | assertThat(responses.get(1), startsWith( | ||
83 | "{\"resource\":\"test.problem\",\"stateId\":\"-80000000\",\"service\":\"highlight\",\"push\":{\"regions\":[")); | ||
84 | assertThat(responses.get(2), equalTo( | ||
85 | "{\"resource\":\"test.problem\",\"stateId\":\"-80000000\",\"service\":\"validate\",\"push\":{\"issues\":[]}}")); | ||
86 | assertThat(responses.get(3), equalTo("{\"id\":\"bar\",\"response\":{\"stateId\":\"-7fffffff\"}}")); | ||
87 | assertThat(responses.get(4), startsWith( | ||
88 | "{\"resource\":\"test.problem\",\"stateId\":\"-7fffffff\",\"service\":\"highlight\",\"push\":{\"regions\":[")); | ||
78 | } | 89 | } |
79 | 90 | ||
80 | @WebSocket | 91 | @WebSocket |
81 | public static class UpdateTestClient { | 92 | public static class UpdateTestClient extends WebSocketIntegrationTestClient { |
82 | private boolean finished = false; | 93 | @Override |
83 | 94 | protected void arrange(Session session, int responsesReceived) throws IOException { | |
84 | private Object lock = new Object(); | 95 | switch (responsesReceived) { |
96 | case 0 -> session.getRemote().sendString( | ||
97 | "{\"id\":\"foo\",\"request\":{\"resource\":\"test.problem\",\"serviceType\":\"update\",\"fullText\":\"class Person.\n\"}}"); | ||
98 | case 3 -> session.getRemote().sendString( | ||
99 | "{\"id\":\"bar\",\"request\":{\"resource\":\"test.problem\",\"serviceType\":\"update\",\"requiredStateId\":\"-80000000\",\"deltaText\":\"class Car.\n\",\"deltaOffset\":\"0\",\"deltaReplaceLength\":\"0\"}}"); | ||
100 | case 5 -> session.close(); | ||
101 | } | ||
102 | } | ||
103 | } | ||
85 | 104 | ||
86 | private Throwable error; | 105 | @Test |
106 | void badSubProtocolTest() { | ||
107 | startServer(null); | ||
108 | var clientSocket = new CloseImmediatelyTestClient(); | ||
109 | var session = connect(clientSocket, null, "<invalid sub-protocol>"); | ||
110 | assertThat(session.getUpgradeResponse().getAcceptedSubProtocol(), equalTo(null)); | ||
111 | clientSocket.waitForTestResult(); | ||
112 | assertThat(clientSocket.getCloseStatusCode(), equalTo(StatusCode.NORMAL)); | ||
113 | } | ||
87 | 114 | ||
88 | private int closeStatusCode; | 115 | @WebSocket |
116 | public static class CloseImmediatelyTestClient extends WebSocketIntegrationTestClient { | ||
117 | @Override | ||
118 | protected void arrange(Session session, int responsesReceived) throws IOException { | ||
119 | session.close(); | ||
120 | } | ||
121 | } | ||
89 | 122 | ||
90 | private String closeReason; | 123 | @Test |
124 | void subProtocolNegotiationTest() { | ||
125 | startServer(null); | ||
126 | var clientSocket = new CloseImmediatelyTestClient(); | ||
127 | var session = connect(clientSocket, null, "<invalid sub-protocol>", XtextWebSocketServlet.XTEXT_SUBPROTOCOL_V1); | ||
128 | assertThat(session.getUpgradeResponse().getAcceptedSubProtocol(), | ||
129 | equalTo(XtextWebSocketServlet.XTEXT_SUBPROTOCOL_V1)); | ||
130 | clientSocket.waitForTestResult(); | ||
131 | assertThat(clientSocket.getCloseStatusCode(), equalTo(StatusCode.NORMAL)); | ||
132 | } | ||
91 | 133 | ||
92 | private List<String> responses = new ArrayList<>(); | 134 | @Test |
135 | void invalidJsonTest() { | ||
136 | startServer(null); | ||
137 | var clientSocket = new InvalidJsonTestClient(); | ||
138 | connect(clientSocket, null, XtextWebSocketServlet.XTEXT_SUBPROTOCOL_V1); | ||
139 | clientSocket.waitForTestResult(); | ||
140 | assertThat(clientSocket.getCloseStatusCode(), equalTo(XtextStatusCode.INVALID_JSON)); | ||
141 | } | ||
93 | 142 | ||
94 | @OnWebSocketConnect | 143 | @WebSocket |
95 | public void onConnect(Session session) { | 144 | public static class InvalidJsonTestClient extends WebSocketIntegrationTestClient { |
96 | try { | 145 | @Override |
97 | session.getRemote().sendString( | 146 | protected void arrange(Session session, int responsesReceived) throws IOException { |
98 | "{\"id\":\"foo\",\"request\":{\"resource\":\"test.problem\",\"serviceType\":\"update\",\"fullText\":\"class Person.\n\"}}"); | 147 | session.getRemote().sendString("<invalid json>"); |
99 | } catch (IOException e) { | ||
100 | finishedWithError(e); | ||
101 | } | ||
102 | } | 148 | } |
149 | } | ||
103 | 150 | ||
104 | @OnWebSocketClose | 151 | @ParameterizedTest(name = "Origin: {0}") |
105 | public void onClose(int statusCode, String reason) { | 152 | @ValueSource(strings = { "https://refinery.example", "https://refinery.example:443", "HTTPS://REFINERY.EXAMPLE" }) |
106 | closeStatusCode = statusCode; | 153 | void validOriginTest(String origin) { |
107 | closeReason = reason; | 154 | startServer("https://refinery.example;https://refinery.example:443"); |
108 | testFinished(); | 155 | var clientSocket = new CloseImmediatelyTestClient(); |
109 | } | 156 | connect(clientSocket, origin, XtextWebSocketServlet.XTEXT_SUBPROTOCOL_V1); |
157 | clientSocket.waitForTestResult(); | ||
158 | assertThat(clientSocket.getCloseStatusCode(), equalTo(StatusCode.NORMAL)); | ||
159 | } | ||
110 | 160 | ||
111 | @OnWebSocketError | 161 | @Test |
112 | public void onError(Throwable error) { | 162 | void invalidOriginTest() { |
113 | finishedWithError(error); | 163 | startServer("https://refinery.example;https://refinery.example:443"); |
114 | } | 164 | var clientSocket = new CloseImmediatelyTestClient(); |
165 | var exception = assertThrows(CompletionException.class, | ||
166 | () -> connect(clientSocket, "https://invalid.example", XtextWebSocketServlet.XTEXT_SUBPROTOCOL_V1)); | ||
167 | var innerException = exception.getCause(); | ||
168 | assertThat(innerException, instanceOf(UpgradeException.class)); | ||
169 | assertThat(((UpgradeException) innerException).getResponseStatusCode(), equalTo(HttpStatus.FORBIDDEN_403)); | ||
170 | } | ||
115 | 171 | ||
116 | @OnWebSocketMessage | 172 | private void startServer(String allowedOrigins) { |
117 | public void onMessage(Session session, String message) { | 173 | server = new Server(new InetSocketAddress(SERVER_PORT)); |
118 | try { | 174 | var handler = new ServletContextHandler(); |
119 | responses.add(message); | 175 | var holder = new ServletHolder(ProblemWebSocketServlet.class); |
120 | switch (responses.size()) { | 176 | if (allowedOrigins != null) { |
121 | case 3 -> session.getRemote().sendString( | 177 | holder.setInitParameter(ProblemWebSocketServlet.ALLOWED_ORIGINS_INIT_PARAM, allowedOrigins); |
122 | "{\"id\":\"bar\",\"request\":{\"resource\":\"test.problem\",\"serviceType\":\"update\",\"requiredStateId\":\"-80000000\",\"deltaText\":\"class Car.\n\",\"deltaOffset\":\"0\",\"deltaReplaceLength\":\"0\"}}"); | ||
123 | case 5 -> session.close(); | ||
124 | } | ||
125 | } catch (IOException e) { | ||
126 | finishedWithError(e); | ||
127 | } | ||
128 | } | 178 | } |
129 | 179 | handler.addServlet(holder, SERVLET_URI); | |
130 | private void finishedWithError(Throwable t) { | 180 | JettyWebSocketServletContainerInitializer.configure(handler, null); |
131 | error = t; | 181 | server.setHandler(handler); |
132 | testFinished(); | 182 | try { |
183 | server.start(); | ||
184 | } catch (Exception e) { | ||
185 | throw new RuntimeException("Failed to start websocket server"); | ||
133 | } | 186 | } |
187 | } | ||
134 | 188 | ||
135 | private void testFinished() { | 189 | private Session connect(Object webSocketClient, String origin, String... subProtocols) { |
136 | synchronized (lock) { | 190 | var upgradeRequest = new ClientUpgradeRequest(); |
137 | finished = true; | 191 | if (origin != null) { |
138 | lock.notify(); | 192 | upgradeRequest.setHeader(HttpHeader.ORIGIN.name(), origin); |
139 | } | ||
140 | } | 193 | } |
141 | 194 | upgradeRequest.setSubProtocols(subProtocols); | |
142 | public void waitForTestResult() { | 195 | CompletableFuture<Session> sessionFuture; |
143 | synchronized (lock) { | 196 | try { |
144 | if (!finished) { | 197 | sessionFuture = client.connect(webSocketClient, URI.create("ws://localhost:" + SERVER_PORT + SERVLET_URI), |
145 | try { | 198 | upgradeRequest); |
146 | lock.wait(TIMEOUT_MILLIS); | 199 | } catch (IOException e) { |
147 | } catch (InterruptedException e) { | 200 | throw new AssertionError("Unexpected exception while connection to websocket", e); |
148 | fail("Unexpected InterruptedException", e); | ||
149 | } | ||
150 | } | ||
151 | } | ||
152 | if (!finished) { | ||
153 | fail("Test still not finished after timeout"); | ||
154 | } | ||
155 | if (error != null) { | ||
156 | fail("Unexpected exception in websocket thread", error); | ||
157 | } | ||
158 | if (closeStatusCode != StatusCode.NORMAL) { | ||
159 | fail("Abnormal close status " + closeStatusCode + ": " + closeReason); | ||
160 | } | ||
161 | assertThat(responses, hasSize(5)); | ||
162 | assertThat(responses.get(0), equalTo("{\"id\":\"foo\",\"response\":{\"stateId\":\"-80000000\"}}")); | ||
163 | assertThat(responses.get(1), startsWith( | ||
164 | "{\"resource\":\"test.problem\",\"stateId\":\"-80000000\",\"service\":\"highlight\",\"push\":{\"regions\":[")); | ||
165 | assertThat(responses.get(2), equalTo( | ||
166 | "{\"resource\":\"test.problem\",\"stateId\":\"-80000000\",\"service\":\"validate\",\"push\":{\"issues\":[]}}")); | ||
167 | assertThat(responses.get(3), equalTo("{\"id\":\"bar\",\"response\":{\"stateId\":\"-7fffffff\"}}")); | ||
168 | assertThat(responses.get(4), startsWith( | ||
169 | "{\"resource\":\"test.problem\",\"stateId\":\"-7fffffff\",\"service\":\"highlight\",\"push\":{\"regions\":[")); | ||
170 | } | 201 | } |
202 | return sessionFuture.join(); | ||
171 | } | 203 | } |
172 | } | 204 | } |
diff --git a/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/AwaitTerminationExecutorServiceProvider.java b/language-web/src/test/java/tools/refinery/language/web/tests/AwaitTerminationExecutorServiceProvider.java index 25bcec37..b70d0ed5 100644 --- a/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/AwaitTerminationExecutorServiceProvider.java +++ b/language-web/src/test/java/tools/refinery/language/web/tests/AwaitTerminationExecutorServiceProvider.java | |||
@@ -1,4 +1,4 @@ | |||
1 | package tools.refinery.language.web.xtext.servlet; | 1 | package tools.refinery.language.web.tests; |
2 | 2 | ||
3 | import java.util.ArrayList; | 3 | import java.util.ArrayList; |
4 | import java.util.List; | 4 | import java.util.List; |
diff --git a/language-web/src/test/java/tools/refinery/language/web/ProblemWebInjectorProvider.java b/language-web/src/test/java/tools/refinery/language/web/tests/ProblemWebInjectorProvider.java index 2db4590e..43c12faa 100644 --- a/language-web/src/test/java/tools/refinery/language/web/ProblemWebInjectorProvider.java +++ b/language-web/src/test/java/tools/refinery/language/web/tests/ProblemWebInjectorProvider.java | |||
@@ -1,4 +1,4 @@ | |||
1 | package tools.refinery.language.web; | 1 | package tools.refinery.language.web.tests; |
2 | 2 | ||
3 | import org.eclipse.xtext.ide.ExecutorServiceProvider; | 3 | import org.eclipse.xtext.ide.ExecutorServiceProvider; |
4 | import org.eclipse.xtext.util.DisposableRegistry; | 4 | import org.eclipse.xtext.util.DisposableRegistry; |
@@ -9,7 +9,8 @@ import com.google.inject.Injector; | |||
9 | 9 | ||
10 | import tools.refinery.language.ide.ProblemIdeModule; | 10 | import tools.refinery.language.ide.ProblemIdeModule; |
11 | import tools.refinery.language.tests.ProblemInjectorProvider; | 11 | import tools.refinery.language.tests.ProblemInjectorProvider; |
12 | import tools.refinery.language.web.xtext.servlet.AwaitTerminationExecutorServiceProvider; | 12 | import tools.refinery.language.web.ProblemWebModule; |
13 | import tools.refinery.language.web.ProblemWebSetup; | ||
13 | 14 | ||
14 | public class ProblemWebInjectorProvider extends ProblemInjectorProvider { | 15 | public class ProblemWebInjectorProvider extends ProblemInjectorProvider { |
15 | 16 | ||
diff --git a/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/RestartableCachedThreadPool.java b/language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java index 02ef38e2..1468273d 100644 --- a/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/RestartableCachedThreadPool.java +++ b/language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java | |||
@@ -1,4 +1,4 @@ | |||
1 | package tools.refinery.language.web.xtext.servlet; | 1 | package tools.refinery.language.web.tests; |
2 | 2 | ||
3 | import java.util.Collection; | 3 | import java.util.Collection; |
4 | import java.util.List; | 4 | import java.util.List; |
diff --git a/language-web/src/test/java/tools/refinery/language/web/tests/WebSocketIntegrationTestClient.java b/language-web/src/test/java/tools/refinery/language/web/tests/WebSocketIntegrationTestClient.java new file mode 100644 index 00000000..49464d27 --- /dev/null +++ b/language-web/src/test/java/tools/refinery/language/web/tests/WebSocketIntegrationTestClient.java | |||
@@ -0,0 +1,98 @@ | |||
1 | package tools.refinery.language.web.tests; | ||
2 | |||
3 | import static org.junit.jupiter.api.Assertions.fail; | ||
4 | |||
5 | import java.io.IOException; | ||
6 | import java.time.Duration; | ||
7 | import java.util.ArrayList; | ||
8 | import java.util.List; | ||
9 | |||
10 | import org.eclipse.jetty.websocket.api.Session; | ||
11 | import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; | ||
12 | import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; | ||
13 | import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; | ||
14 | import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; | ||
15 | |||
16 | public abstract class WebSocketIntegrationTestClient { | ||
17 | private static long TIMEOUT_MILLIS = Duration.ofSeconds(1).toMillis(); | ||
18 | |||
19 | private boolean finished = false; | ||
20 | |||
21 | private Object lock = new Object(); | ||
22 | |||
23 | private Throwable error; | ||
24 | |||
25 | private int closeStatusCode; | ||
26 | |||
27 | private List<String> responses = new ArrayList<>(); | ||
28 | |||
29 | public int getCloseStatusCode() { | ||
30 | return closeStatusCode; | ||
31 | } | ||
32 | |||
33 | public List<String> getResponses() { | ||
34 | return responses; | ||
35 | } | ||
36 | |||
37 | @OnWebSocketConnect | ||
38 | public void onConnect(Session session) { | ||
39 | arrangeAndCatchErrors(session); | ||
40 | } | ||
41 | |||
42 | private void arrangeAndCatchErrors(Session session) { | ||
43 | try { | ||
44 | arrange(session, responses.size()); | ||
45 | } catch (Exception e) { | ||
46 | finishedWithError(e); | ||
47 | } | ||
48 | } | ||
49 | |||
50 | protected abstract void arrange(Session session, int responsesReceived) throws IOException; | ||
51 | |||
52 | @OnWebSocketClose | ||
53 | public void onClose(int statusCode, String reason) { | ||
54 | closeStatusCode = statusCode; | ||
55 | testFinished(); | ||
56 | } | ||
57 | |||
58 | @OnWebSocketError | ||
59 | public void onError(Throwable error) { | ||
60 | finishedWithError(error); | ||
61 | } | ||
62 | |||
63 | @OnWebSocketMessage | ||
64 | public void onMessage(Session session, String message) { | ||
65 | responses.add(message); | ||
66 | arrangeAndCatchErrors(session); | ||
67 | } | ||
68 | |||
69 | private void finishedWithError(Throwable t) { | ||
70 | error = t; | ||
71 | testFinished(); | ||
72 | } | ||
73 | |||
74 | private void testFinished() { | ||
75 | synchronized (lock) { | ||
76 | finished = true; | ||
77 | lock.notify(); | ||
78 | } | ||
79 | } | ||
80 | |||
81 | public void waitForTestResult() { | ||
82 | synchronized (lock) { | ||
83 | if (!finished) { | ||
84 | try { | ||
85 | lock.wait(TIMEOUT_MILLIS); | ||
86 | } catch (InterruptedException e) { | ||
87 | fail("Unexpected InterruptedException", e); | ||
88 | } | ||
89 | } | ||
90 | } | ||
91 | if (!finished) { | ||
92 | fail("Test still not finished after timeout"); | ||
93 | } | ||
94 | if (error != null) { | ||
95 | fail("Unexpected exception in websocket thread", error); | ||
96 | } | ||
97 | } | ||
98 | } | ||
diff --git a/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java b/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java index 2d3f45d6..0892954b 100644 --- a/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java +++ b/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java | |||
@@ -26,7 +26,8 @@ import org.mockito.junit.jupiter.MockitoExtension; | |||
26 | 26 | ||
27 | import com.google.inject.Inject; | 27 | import com.google.inject.Inject; |
28 | 28 | ||
29 | import tools.refinery.language.web.ProblemWebInjectorProvider; | 29 | import tools.refinery.language.web.tests.AwaitTerminationExecutorServiceProvider; |
30 | import tools.refinery.language.web.tests.ProblemWebInjectorProvider; | ||
30 | import tools.refinery.language.web.xtext.server.ResponseHandler; | 31 | import tools.refinery.language.web.xtext.server.ResponseHandler; |
31 | import tools.refinery.language.web.xtext.server.ResponseHandlerException; | 32 | import tools.refinery.language.web.xtext.server.ResponseHandlerException; |
32 | import tools.refinery.language.web.xtext.server.TransactionExecutor; | 33 | import tools.refinery.language.web.xtext.server.TransactionExecutor; |