Skip to content

Commit 36ac6f5

Browse files
committed
test(appserver): define serialized websocket send contract
1 parent 7e60ec8 commit 36ac6f5

1 file changed

Lines changed: 99 additions & 0 deletions

File tree

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright (c) 2018-present, easy-4-java (https://github.com/easy-4-java).
3+
*
4+
* Licensed under the Apache License, Version 2.0.
5+
*/
6+
package io.github.easy4j.codex.appserver;
7+
8+
import static org.junit.jupiter.api.Assertions.assertEquals;
9+
import static org.junit.jupiter.api.Assertions.assertThrows;
10+
11+
import java.lang.reflect.Proxy;
12+
import java.net.http.WebSocket;
13+
import java.util.ArrayDeque;
14+
import java.util.ArrayList;
15+
import java.util.List;
16+
import java.util.Queue;
17+
import java.util.concurrent.CompletableFuture;
18+
import java.util.concurrent.CompletionException;
19+
import java.util.concurrent.CompletionStage;
20+
21+
import org.junit.jupiter.api.Test;
22+
23+
class CodexWebSocketSenderTest {
24+
25+
@Test
26+
void shouldSerializeWritesUntilPreviousSendCompletes() {
27+
List<String> sent = new ArrayList<>();
28+
Queue<CompletableFuture<WebSocket>> completions = new ArrayDeque<>();
29+
CompletableFuture<WebSocket> firstGate = new CompletableFuture<>();
30+
CompletableFuture<WebSocket> secondGate = new CompletableFuture<>();
31+
completions.add(firstGate);
32+
completions.add(secondGate);
33+
WebSocket socket = socket(sent, completions);
34+
35+
CodexWebSocketSender sender = new CodexWebSocketSender(socket);
36+
CompletionStage<WebSocket> first = sender.send("one");
37+
CompletionStage<WebSocket> second = sender.send("two");
38+
39+
assertEquals(List.of("one"), sent, "second write must wait for the first send future");
40+
41+
firstGate.complete(socket);
42+
assertEquals(List.of("one", "two"), sent);
43+
44+
secondGate.complete(socket);
45+
first.toCompletableFuture().join();
46+
second.toCompletableFuture().join();
47+
}
48+
49+
@Test
50+
void shouldShortCircuitLaterWritesWhenPreviousSendFails() {
51+
List<String> sent = new ArrayList<>();
52+
Queue<CompletableFuture<WebSocket>> completions = new ArrayDeque<>();
53+
CompletableFuture<WebSocket> firstGate = new CompletableFuture<>();
54+
completions.add(firstGate);
55+
WebSocket socket = socket(sent, completions);
56+
57+
CodexWebSocketSender sender = new CodexWebSocketSender(socket);
58+
CompletionStage<WebSocket> first = sender.send("one");
59+
CompletionStage<WebSocket> second = sender.send("two");
60+
61+
RuntimeException failure = new RuntimeException("write failed");
62+
firstGate.completeExceptionally(failure);
63+
64+
assertThrows(CompletionException.class, () -> first.toCompletableFuture().join());
65+
assertThrows(CompletionException.class, () -> second.toCompletableFuture().join());
66+
assertEquals(List.of("one"), sent, "failed connection must not emit queued writes");
67+
}
68+
69+
private WebSocket socket(List<String> sent,
70+
Queue<CompletableFuture<WebSocket>> completions) {
71+
return (WebSocket) Proxy.newProxyInstance(
72+
WebSocket.class.getClassLoader(),
73+
new Class<?>[]{WebSocket.class},
74+
(proxy, method, args) -> {
75+
if ("sendText".equals(method.getName())) {
76+
sent.add((String) args[0]);
77+
CompletableFuture<WebSocket> completion = completions.poll();
78+
if (completion == null) {
79+
return CompletableFuture.completedFuture((WebSocket) proxy);
80+
}
81+
return completion;
82+
}
83+
if ("getSubprotocol".equals(method.getName())) {
84+
return "";
85+
}
86+
if ("isOutputClosed".equals(method.getName())
87+
|| "isInputClosed".equals(method.getName())) {
88+
return false;
89+
}
90+
if ("sendClose".equals(method.getName())
91+
|| "sendPing".equals(method.getName())
92+
|| "sendPong".equals(method.getName())
93+
|| "sendBinary".equals(method.getName())) {
94+
return CompletableFuture.completedFuture((WebSocket) proxy);
95+
}
96+
return null;
97+
});
98+
}
99+
}

0 commit comments

Comments
 (0)