diff options
Diffstat (limited to 'language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java')
-rw-r--r-- | language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java | 172 |
1 files changed, 172 insertions, 0 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 new file mode 100644 index 00000000..60581b5c --- /dev/null +++ b/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java | |||
@@ -0,0 +1,172 @@ | |||
1 | package tools.refinery.language.web; | ||
2 | |||
3 | import static org.hamcrest.MatcherAssert.assertThat; | ||
4 | import static org.hamcrest.Matchers.equalTo; | ||
5 | import static org.hamcrest.Matchers.hasSize; | ||
6 | import static org.hamcrest.Matchers.startsWith; | ||
7 | import static org.junit.jupiter.api.Assertions.fail; | ||
8 | |||
9 | import java.io.IOException; | ||
10 | import java.net.InetSocketAddress; | ||
11 | import java.net.URI; | ||
12 | import java.time.Duration; | ||
13 | import java.util.ArrayList; | ||
14 | import java.util.List; | ||
15 | |||
16 | import org.eclipse.jetty.server.Server; | ||
17 | import org.eclipse.jetty.servlet.ServletContextHandler; | ||
18 | import org.eclipse.jetty.websocket.api.Session; | ||
19 | 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; | ||
25 | import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; | ||
26 | import org.eclipse.jetty.websocket.client.WebSocketClient; | ||
27 | import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; | ||
28 | import org.eclipse.xtext.testing.GlobalRegistries; | ||
29 | import org.eclipse.xtext.testing.GlobalRegistries.GlobalStateMemento; | ||
30 | import org.junit.jupiter.api.AfterEach; | ||
31 | import org.junit.jupiter.api.BeforeEach; | ||
32 | import org.junit.jupiter.api.Test; | ||
33 | |||
34 | import tools.refinery.language.web.xtext.servlet.XtextWebSocketServlet; | ||
35 | |||
36 | class ProblemWebSocketServletIntegrationTest { | ||
37 | private static int SERVER_PORT = 28080; | ||
38 | |||
39 | private static long TIMEOUT_MILLIS = Duration.ofSeconds(1).toMillis(); | ||
40 | |||
41 | private GlobalStateMemento stateBeforeInjectorCreation; | ||
42 | |||
43 | private Server server; | ||
44 | |||
45 | private WebSocketClient client; | ||
46 | |||
47 | @BeforeEach | ||
48 | void startServer() throws Exception { | ||
49 | 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(); | ||
57 | client.start(); | ||
58 | } | ||
59 | |||
60 | @AfterEach | ||
61 | void stopServer() throws Exception { | ||
62 | client.stop(); | ||
63 | server.stop(); | ||
64 | stateBeforeInjectorCreation.restoreGlobalState(); | ||
65 | } | ||
66 | |||
67 | @Test | ||
68 | void updateTest() throws IOException { | ||
69 | var clientSocket = new UpdateTestClient(); | ||
70 | var upgradeRequest = new ClientUpgradeRequest(); | ||
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(), | ||
76 | equalTo(XtextWebSocketServlet.XTEXT_SUBPROTOCOL_V1)); | ||
77 | clientSocket.waitForTestResult(); | ||
78 | } | ||
79 | |||
80 | @WebSocket | ||
81 | public static class UpdateTestClient { | ||
82 | private boolean finished = false; | ||
83 | |||
84 | private Object lock = new Object(); | ||
85 | |||
86 | private Throwable error; | ||
87 | |||
88 | private int closeStatusCode; | ||
89 | |||
90 | private String closeReason; | ||
91 | |||
92 | private List<String> responses = new ArrayList<>(); | ||
93 | |||
94 | @OnWebSocketConnect | ||
95 | public void onConnect(Session session) { | ||
96 | try { | ||
97 | session.getRemote().sendString( | ||
98 | "{\"id\":\"foo\",\"request\":{\"resource\":\"test.problem\",\"serviceType\":\"update\",\"fullText\":\"class Person.\n\"}}"); | ||
99 | } catch (IOException e) { | ||
100 | finishedWithError(e); | ||
101 | } | ||
102 | } | ||
103 | |||
104 | @OnWebSocketClose | ||
105 | public void onClose(int statusCode, String reason) { | ||
106 | closeStatusCode = statusCode; | ||
107 | closeReason = reason; | ||
108 | testFinished(); | ||
109 | } | ||
110 | |||
111 | @OnWebSocketError | ||
112 | public void onError(Throwable error) { | ||
113 | finishedWithError(error); | ||
114 | } | ||
115 | |||
116 | @OnWebSocketMessage | ||
117 | public void onMessage(Session session, String message) { | ||
118 | try { | ||
119 | responses.add(message); | ||
120 | switch (responses.size()) { | ||
121 | case 3 -> session.getRemote().sendString( | ||
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 | } | ||
129 | |||
130 | private void finishedWithError(Throwable t) { | ||
131 | error = t; | ||
132 | testFinished(); | ||
133 | } | ||
134 | |||
135 | private void testFinished() { | ||
136 | synchronized (lock) { | ||
137 | finished = true; | ||
138 | lock.notify(); | ||
139 | } | ||
140 | } | ||
141 | |||
142 | public void waitForTestResult() { | ||
143 | synchronized (lock) { | ||
144 | if (!finished) { | ||
145 | try { | ||
146 | lock.wait(TIMEOUT_MILLIS); | ||
147 | } catch (InterruptedException 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 | } | ||
171 | } | ||
172 | } | ||