Skip to content

Commit 51338f3

Browse files
committed
test: add RateLimiter unit tests
1 parent dbb6baf commit 51338f3

2 files changed

Lines changed: 181 additions & 0 deletions

File tree

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package dev.objz.commandbridge.net;
2+
3+
import dev.objz.commandbridge.logging.Log;
4+
import dev.objz.commandbridge.net.proto.Envelope;
5+
import dev.objz.commandbridge.net.proto.MessageType;
6+
import org.junit.jupiter.api.BeforeAll;
7+
import org.junit.jupiter.api.Test;
8+
9+
import java.util.concurrent.atomic.AtomicInteger;
10+
11+
import static org.junit.jupiter.api.Assertions.assertEquals;
12+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
13+
import static org.junit.jupiter.api.Assertions.assertNotNull;
14+
15+
class InNodeTest {
16+
17+
@BeforeAll
18+
static void installLog() {
19+
try {
20+
Log.install(java.util.logging.Logger.getLogger("test"));
21+
} catch (IllegalStateException ignored) {
22+
// expected
23+
}
24+
}
25+
26+
private static final class CountingHandler extends InboundHandler {
27+
final AtomicInteger count = new AtomicInteger(0);
28+
Envelope lastEnv;
29+
30+
@Override
31+
public void accept(Endpoint endpoint, Envelope env) {
32+
count.incrementAndGet();
33+
this.lastEnv = env;
34+
}
35+
}
36+
37+
@Test
38+
void registersAndDispatchesByType() throws Exception {
39+
InNode inNode = new InNode();
40+
CountingHandler handler = new CountingHandler();
41+
inNode.register(MessageType.PING, handler);
42+
43+
Envelope testEnv = Envelope.make(MessageType.PING, "server1", "client1", null);
44+
String json = Envelope.MAPPER.writeValueAsString(testEnv);
45+
46+
TestEndpoint endpoint = new TestEndpoint();
47+
inNode.onText(endpoint, json);
48+
49+
assertEquals(1, handler.count.get());
50+
assertNotNull(handler.lastEnv);
51+
assertEquals(MessageType.PING, handler.lastEnv.type());
52+
}
53+
54+
@Test
55+
void unhandledTypeDoesNotThrow() throws Exception {
56+
InNode inNode = new InNode();
57+
CountingHandler handler = new CountingHandler();
58+
inNode.register(MessageType.PING, handler);
59+
60+
Envelope testEnv = Envelope.make(MessageType.PONG, "server1", "client1", null);
61+
String json = Envelope.MAPPER.writeValueAsString(testEnv);
62+
63+
TestEndpoint endpoint = new TestEndpoint();
64+
assertDoesNotThrow(() -> inNode.onText(endpoint, json));
65+
assertEquals(0, handler.count.get());
66+
}
67+
68+
@Test
69+
void badJsonDoesNotThrow() {
70+
InNode inNode = new InNode();
71+
TestEndpoint endpoint = new TestEndpoint();
72+
73+
assertDoesNotThrow(() -> inNode.onText(endpoint, "not valid json"));
74+
}
75+
76+
@Test
77+
void tapInterceptsBeforeHandler() throws Exception {
78+
InNode inNode = new InNode();
79+
CountingHandler handler = new CountingHandler();
80+
inNode.register(MessageType.PING, handler);
81+
inNode.setInboundTap(env -> true);
82+
83+
Envelope testEnv = Envelope.make(MessageType.PING, "server1", "client1", null);
84+
String json = Envelope.MAPPER.writeValueAsString(testEnv);
85+
86+
TestEndpoint endpoint = new TestEndpoint();
87+
inNode.onText(endpoint, json);
88+
89+
assertEquals(0, handler.count.get());
90+
}
91+
92+
@Test
93+
void tapFalsePassesToHandler() throws Exception {
94+
InNode inNode = new InNode();
95+
CountingHandler handler = new CountingHandler();
96+
inNode.register(MessageType.PING, handler);
97+
inNode.setInboundTap(env -> false);
98+
99+
Envelope testEnv = Envelope.make(MessageType.PING, "server1", "client1", null);
100+
String json = Envelope.MAPPER.writeValueAsString(testEnv);
101+
102+
TestEndpoint endpoint = new TestEndpoint();
103+
inNode.onText(endpoint, json);
104+
105+
assertEquals(1, handler.count.get());
106+
}
107+
108+
@Test
109+
void nullEndpointHandledGracefully() {
110+
InNode inNode = new InNode();
111+
112+
assertDoesNotThrow(() -> inNode.onText(null, "bad json"));
113+
}
114+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package dev.objz.commandbridge.util;
2+
3+
import static org.junit.jupiter.api.Assertions.assertFalse;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
6+
7+
import org.junit.jupiter.api.BeforeAll;
8+
import org.junit.jupiter.api.Test;
9+
10+
class RateLimiterTest {
11+
12+
@BeforeAll
13+
static void installLog() {
14+
try {
15+
dev.objz.commandbridge.logging.Log.install(java.util.logging.Logger.getLogger("test"));
16+
} catch (IllegalStateException ignored) {
17+
// expected
18+
}
19+
}
20+
21+
@Test
22+
void constructorRejectsZero() {
23+
assertThrows(IllegalArgumentException.class, () -> new RateLimiter<>(0));
24+
}
25+
26+
@Test
27+
void constructorRejectsNegative() {
28+
assertThrows(IllegalArgumentException.class, () -> new RateLimiter<>(-1));
29+
}
30+
31+
@Test
32+
void allowsUpToMax() {
33+
RateLimiter<String> limiter = new RateLimiter<>(5);
34+
for (int i = 0; i < 5; i++) {
35+
assertTrue(limiter.allow("k"), "Call " + (i + 1) + " should be allowed");
36+
}
37+
}
38+
39+
@Test
40+
void deniesOverMax() {
41+
RateLimiter<String> limiter = new RateLimiter<>(5);
42+
for (int i = 0; i < 5; i++) {
43+
limiter.allow("k");
44+
}
45+
assertFalse(limiter.allow("k"), "6th call should be denied");
46+
}
47+
48+
@Test
49+
void independentKeys() {
50+
RateLimiter<String> limiter = new RateLimiter<>(2);
51+
assertTrue(limiter.allow("A"));
52+
assertTrue(limiter.allow("A"));
53+
assertFalse(limiter.allow("A"));
54+
assertTrue(limiter.allow("B"), "Key B should still be allowed");
55+
assertTrue(limiter.allow("B"));
56+
assertFalse(limiter.allow("B"));
57+
}
58+
59+
@Test
60+
void windowResets() throws InterruptedException {
61+
RateLimiter<String> limiter = new RateLimiter<>(1);
62+
assertTrue(limiter.allow("k"), "First call should be allowed");
63+
assertFalse(limiter.allow("k"), "Second call in same second should be denied");
64+
Thread.sleep(1100);
65+
assertTrue(limiter.allow("k"), "Call after window reset should be allowed");
66+
}
67+
}

0 commit comments

Comments
 (0)