summaryrefslogtreecommitdiff
path: root/src/test/java
diff options
context:
space:
mode:
authorKeuin <[email protected]>2021-12-17 23:55:44 +0800
committerKeuin <[email protected]>2021-12-17 23:55:44 +0800
commit12ddbba66e6f2585e59d05d1782c0e8ce9fe6146 (patch)
tree0d98ee95c01c8509160658080523e351357d4a9b /src/test/java
parent7fc64f506ea7ebc68fcb0a9e98351deed7c1d212 (diff)
Use the latest Velocity API.
Implement API server for online players and server status. Implement core message routing abstraction and concrete BungeeCross, Velocity, Telegram endpoint impl. Load config from config file "crosslink/config.json". Test core components. Proxy API related stuff are not tested. Add README in English and Chinese. TODO: Add config hot reloading. More configurable system. PSMB endpoint impl.
Diffstat (limited to 'src/test/java')
-rw-r--r--src/test/java/com/keuin/crosslink/messaging/action/BaseFilterActionTest.java36
-rw-r--r--src/test/java/com/keuin/crosslink/messaging/action/BaseReplaceActionTest.java23
-rw-r--r--src/test/java/com/keuin/crosslink/messaging/action/DropActionTest.java25
-rw-r--r--src/test/java/com/keuin/crosslink/messaging/action/Re2placeActionTest.java30
-rw-r--r--src/test/java/com/keuin/crosslink/messaging/action/ReFilterActionTest.java42
-rw-r--r--src/test/java/com/keuin/crosslink/messaging/action/RouteActionTest.java46
-rw-r--r--src/test/java/com/keuin/crosslink/messaging/config/RouterConfigurerTest.java89
-rw-r--r--src/test/java/com/keuin/crosslink/messaging/config/RuleListsTest.java38
-rw-r--r--src/test/java/com/keuin/crosslink/messaging/filter/IFilterTest.java32
-rw-r--r--src/test/java/com/keuin/crosslink/messaging/message/ComponentBackedMessageTest.java22
-rw-r--r--src/test/java/com/keuin/crosslink/messaging/router/ConcreteRouterTest.java132
-rw-r--r--src/test/java/com/keuin/crosslink/messaging/rule/ImmutableRuleTest.java95
-rw-r--r--src/test/java/com/keuin/crosslink/testable/FakeEndpoint.java51
-rw-r--r--src/test/java/com/keuin/crosslink/testable/FakeRouter.java65
-rw-r--r--src/test/java/com/keuin/crosslink/util/EggFactoryTest.java38
-rw-r--r--src/test/java/com/keuin/crosslink/util/HttpQueryTest.java48
-rw-r--r--src/test/java/com/keuin/crosslink/util/LazyEvaluatedTest.java28
-rw-r--r--src/test/java/com/keuin/crosslink/util/LoggerNamingTest.java14
18 files changed, 854 insertions, 0 deletions
diff --git a/src/test/java/com/keuin/crosslink/messaging/action/BaseFilterActionTest.java b/src/test/java/com/keuin/crosslink/messaging/action/BaseFilterActionTest.java
new file mode 100644
index 0000000..5b3e1e5
--- /dev/null
+++ b/src/test/java/com/keuin/crosslink/messaging/action/BaseFilterActionTest.java
@@ -0,0 +1,36 @@
+package com.keuin.crosslink.messaging.action;
+
+import com.keuin.crosslink.messaging.message.IMessage;
+import com.keuin.crosslink.messaging.sender.ISender;
+import com.keuin.crosslink.testable.FakeEndpoint;
+import org.junit.jupiter.api.Test;
+
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class BaseFilterActionTest {
+ @Test
+ void processAlwaysTrue() {
+ var action = new BaseFilterAction(m -> true);
+ var sender = ISender.create("123", UUID.randomUUID());
+ var source = new FakeEndpoint();
+ var message = IMessage.create(source, sender, "message");
+ assertTrue(action.process(message).isValid());
+ assertFalse(action.process(message).isDropped());
+ assertFalse(action.process(message).isFiltered());
+ assertNotNull(action.process(message).getResult());
+ }
+
+ @Test
+ void processAlwaysFalse() {
+ var action = new BaseFilterAction(m -> false);
+ var sender = ISender.create("123", UUID.randomUUID());
+ var source = new FakeEndpoint();
+ var message = IMessage.create(source, sender, "message");
+ var result = action.process(message);
+ assertFalse(result.isValid());
+ assertFalse(result.isDropped());
+ assertTrue(result.isFiltered());
+ }
+} \ No newline at end of file
diff --git a/src/test/java/com/keuin/crosslink/messaging/action/BaseReplaceActionTest.java b/src/test/java/com/keuin/crosslink/messaging/action/BaseReplaceActionTest.java
new file mode 100644
index 0000000..78d4e6e
--- /dev/null
+++ b/src/test/java/com/keuin/crosslink/messaging/action/BaseReplaceActionTest.java
@@ -0,0 +1,23 @@
+package com.keuin.crosslink.messaging.action;
+
+import com.keuin.crosslink.messaging.message.IMessage;
+import com.keuin.crosslink.messaging.sender.ISender;
+import com.keuin.crosslink.testable.FakeEndpoint;
+import org.junit.jupiter.api.Test;
+
+import java.util.Objects;
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class BaseReplaceActionTest {
+ @Test
+ void processSimpleReplace() {
+ var action = new BaseReplaceAction((msg) -> IMessage.create(msg.source(), msg.sender(), "replaced"));
+ var sender = ISender.create("123", UUID.randomUUID());
+ var source = new FakeEndpoint();
+ var message = IMessage.create(source, sender, "message");
+ assertEquals("message", message.pureString());
+ assertEquals("replaced", Objects.requireNonNull(action.process(message).getResult()).pureString());
+ }
+} \ No newline at end of file
diff --git a/src/test/java/com/keuin/crosslink/messaging/action/DropActionTest.java b/src/test/java/com/keuin/crosslink/messaging/action/DropActionTest.java
new file mode 100644
index 0000000..4e0f441
--- /dev/null
+++ b/src/test/java/com/keuin/crosslink/messaging/action/DropActionTest.java
@@ -0,0 +1,25 @@
+package com.keuin.crosslink.messaging.action;
+
+import com.keuin.crosslink.messaging.message.IMessage;
+import com.keuin.crosslink.messaging.sender.ISender;
+import com.keuin.crosslink.testable.FakeEndpoint;
+import org.junit.jupiter.api.Test;
+
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class DropActionTest {
+
+ @Test
+ void testDrop() {
+ var action = new DropAction();
+ var sender = ISender.create("123", UUID.randomUUID());
+ var source = new FakeEndpoint();
+ var message = IMessage.create(source, sender, "message");
+ var result = action.process(message);
+ assertTrue(result.isDropped());
+ assertFalse(result.isFiltered());
+ }
+} \ No newline at end of file
diff --git a/src/test/java/com/keuin/crosslink/messaging/action/Re2placeActionTest.java b/src/test/java/com/keuin/crosslink/messaging/action/Re2placeActionTest.java
new file mode 100644
index 0000000..79431f1
--- /dev/null
+++ b/src/test/java/com/keuin/crosslink/messaging/action/Re2placeActionTest.java
@@ -0,0 +1,30 @@
+package com.keuin.crosslink.messaging.action;
+
+import com.keuin.crosslink.messaging.message.IMessage;
+import com.keuin.crosslink.messaging.sender.ISender;
+import com.keuin.crosslink.testable.FakeEndpoint;
+import org.junit.jupiter.api.Test;
+
+import java.util.Objects;
+import java.util.UUID;
+import java.util.regex.Pattern;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class Re2placeActionTest {
+ @Test
+ public void testRemoveHeadingSharp() {
+ var action = new Re2placeAction(Pattern.compile("^#(.*)"), "$1");
+ var sender = ISender.create("123", UUID.randomUUID());
+ var source = new FakeEndpoint();
+ var message = IMessage.create(source, sender, "#message");
+ assertEquals("#message", message.pureString());
+ assertEquals("message", Objects.requireNonNull(action.process(message).getResult()).pureString());
+ message = IMessage.create(source, sender, "message");
+ assertEquals("message", message.pureString());
+ assertEquals("message", Objects.requireNonNull(action.process(message).getResult()).pureString());
+ message = IMessage.create(source, sender, "mess#age");
+ assertEquals("mess#age", message.pureString());
+ assertEquals("mess#age", Objects.requireNonNull(action.process(message).getResult()).pureString());
+ }
+} \ No newline at end of file
diff --git a/src/test/java/com/keuin/crosslink/messaging/action/ReFilterActionTest.java b/src/test/java/com/keuin/crosslink/messaging/action/ReFilterActionTest.java
new file mode 100644
index 0000000..257a54c
--- /dev/null
+++ b/src/test/java/com/keuin/crosslink/messaging/action/ReFilterActionTest.java
@@ -0,0 +1,42 @@
+package com.keuin.crosslink.messaging.action;
+
+import com.keuin.crosslink.messaging.message.IMessage;
+import com.keuin.crosslink.messaging.sender.ISender;
+import com.keuin.crosslink.testable.FakeEndpoint;
+import org.junit.jupiter.api.Test;
+
+import java.util.UUID;
+import java.util.regex.Pattern;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class ReFilterActionTest {
+ @Test
+ public void testFilterHeadingSharp() {
+ var action = new ReFilterAction(Pattern.compile("#.*"));
+ var sender = ISender.create("123", UUID.randomUUID());
+ var source = new FakeEndpoint();
+ var message1 = IMessage.create(source, sender, "#good");
+ var message2 = IMessage.create(source, sender, "bad");
+ assertFalse(action.process(message1).isFiltered());
+ assertFalse(action.process(message1).isDropped());
+ assertNotNull(action.process(message1).getResult());
+ assertTrue(action.process(message2).isFiltered());
+ assertFalse(action.process(message2).isDropped());
+ }
+
+ @Test
+ public void testFilterAlwaysTrue() {
+ var action = new ReFilterAction(Pattern.compile(".*"));
+ var sender = ISender.create("123", UUID.randomUUID());
+ var source = new FakeEndpoint();
+ var message1 = IMessage.create(source, sender, "#good");
+ var message2 = IMessage.create(source, sender, "bad");
+ assertFalse(action.process(message1).isFiltered());
+ assertFalse(action.process(message1).isDropped());
+ assertNotNull(action.process(message1).getResult());
+ assertFalse(action.process(message2).isFiltered());
+ assertFalse(action.process(message2).isDropped());
+ assertNotNull(action.process(message2).getResult());
+ }
+} \ No newline at end of file
diff --git a/src/test/java/com/keuin/crosslink/messaging/action/RouteActionTest.java b/src/test/java/com/keuin/crosslink/messaging/action/RouteActionTest.java
new file mode 100644
index 0000000..b9ad631
--- /dev/null
+++ b/src/test/java/com/keuin/crosslink/messaging/action/RouteActionTest.java
@@ -0,0 +1,46 @@
+package com.keuin.crosslink.messaging.action;
+
+import com.keuin.crosslink.messaging.endpoint.IEndpoint;
+import com.keuin.crosslink.messaging.message.IMessage;
+import com.keuin.crosslink.messaging.sender.ISender;
+import com.keuin.crosslink.testable.FakeEndpoint;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class RouteActionTest {
+
+ @Test
+ void testRouteNoBackflow() {
+ var dest1 = new FakeEndpoint("a");
+ var dest2 = new FakeEndpoint("b");
+ var dest3 = new FakeEndpoint("c");
+ var dests = new HashSet<IEndpoint>(Arrays.asList(dest1, dest2, dest3));
+ var action = new RouteAction(() -> dests, false);
+ var sender = ISender.create("123", UUID.randomUUID());
+ var message = IMessage.create(dest1, sender, "message");
+ assertSame(message, action.process(message).getResult());
+ assertTrue(dest1.messages.isEmpty());
+ assertEquals(1, dest2.messages.size());
+ assertEquals(1, dest3.messages.size());
+ }
+
+ @Test
+ void testRouteWithBackflow() {
+ var dest1 = new FakeEndpoint("a");
+ var dest2 = new FakeEndpoint("b");
+ var dest3 = new FakeEndpoint("c");
+ var dests = new HashSet<IEndpoint>(Arrays.asList(dest1, dest2, dest3));
+ var action = new RouteAction(() -> dests, true);
+ var sender = ISender.create("123", UUID.randomUUID());
+ var message = IMessage.create(dest1, sender, "message");
+ assertSame(message, action.process(message).getResult());
+ assertEquals(1, dest1.messages.size());
+ assertEquals(1, dest2.messages.size());
+ assertEquals(1, dest3.messages.size());
+ }
+}
diff --git a/src/test/java/com/keuin/crosslink/messaging/config/RouterConfigurerTest.java b/src/test/java/com/keuin/crosslink/messaging/config/RouterConfigurerTest.java
new file mode 100644
index 0000000..6c9738e
--- /dev/null
+++ b/src/test/java/com/keuin/crosslink/messaging/config/RouterConfigurerTest.java
@@ -0,0 +1,89 @@
+package com.keuin.crosslink.messaging.config;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.keuin.crosslink.messaging.config.router.RouterConfigurer;
+import com.keuin.crosslink.messaging.rule.IRule;
+import com.keuin.crosslink.messaging.rule.ObjectType;
+import com.keuin.crosslink.testable.FakeRouter;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class RouterConfigurerTest {
+
+ private static final String config = """
+ [
+ // all rules are processed sequentially
+ // a message may match multiple rules and thus may be duplicate in your case
+ // if the message is dropped in an action in one rule,
+ // (the action type is just "drop" and it does not have any argument)
+ // all subsequent rules will NOT see this message
+ {
+ // inbound chat messages (remote -> all servers)
+ "object": "chat_message", // match chat messages
+ "from": "remote:.*", // regexp matching source,
+ // only messages with matched source will be
+ // processed by this rule, otherwise this rule is skipped
+ "actions": [{ // actions run sequentially
+ "type": "route", // route this message to matched destinations
+ "to": "server:.*" // regexp matching destination \s
+ }, {
+ "type": "format",
+ "color": "green"
+ }]
+ },
+ {
+ // outbound messages (starting with '#', server -> all remotes)
+ "object": "chat_message",
+ "from": "server:.*",
+ "actions": [{
+ "type": "filter", // filter the message using given regexp
+ // if the message does not match given pattern,
+ // it won't be passed into subsequent actions
+ "pattern": "#.+" // match all messages starts with char '#'
+ }, {
+ "type": "replace", // replace the message, removing heading '#'
+ "from": "^#\\\\(.*\\\\)", // capture all chars after the heading '#'
+ "to": "$1" // and make them as the output
+ }, {
+ "type": "route", // send the message to all remotes
+ "to": "remote:.*"
+ }]
+ },
+ {
+ // cross-server messages (server -> all other servers)
+ "object": "chat_message",
+ "from": "server:.*",
+ "actions": [{
+ "type": "route",
+ "to": "server:.*",
+ "backflow": false // do not repeat to sender, true by default
+ // since the destination pattern will match the source,
+ // we have to set backflow to false to prevent
+ // players from seeing duplicate messages
+ }]
+ }
+ ]""";
+
+ @Test
+ void decode() throws ConfigSyntaxError, JsonProcessingException {
+ var router = new FakeRouter();
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
+ var rc = new RouterConfigurer(mapper.readTree(config));
+ rc.configure(router);
+ var chain = router.getRules();
+
+ IRule r;
+ assertEquals(3, chain.size());
+ r = chain.get(0);
+ assertEquals(r.object(), ObjectType.CHAT_MESSAGE);
+ r = chain.get(1);
+ assertEquals(r.object(), ObjectType.CHAT_MESSAGE);
+ r = chain.get(2);
+ assertEquals(r.object(), ObjectType.CHAT_MESSAGE);
+ // TODO
+ }
+} \ No newline at end of file
diff --git a/src/test/java/com/keuin/crosslink/messaging/config/RuleListsTest.java b/src/test/java/com/keuin/crosslink/messaging/config/RuleListsTest.java
new file mode 100644
index 0000000..b9a10d6
--- /dev/null
+++ b/src/test/java/com/keuin/crosslink/messaging/config/RuleListsTest.java
@@ -0,0 +1,38 @@
+package com.keuin.crosslink.messaging.config;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+class RuleListsTest {
+
+ @Test
+ void fromJson() throws JsonProcessingException {
+ var json = new String(Base64.getDecoder()
+ .decode("WwogICAgLy8gYWxsIHJ1bGVzIGFyZSBwcm9jZXNzZWQgc2VxdWVudGlhbGx5CiAgICAvLyBhIG1lc3NhZ2UgbWF5IG1hdGNoIG11bHRpcGxlIHJ1bGVzIGFuZCB0aHVzIG1heSBiZSBkdXBsaWNhdGUgaW4geW91ciBjYXNlCiAgICAvLyBpZiB0aGUgbWVzc2FnZSBpcyBkcm9wcGVkIGluIGFuIGFjdGlvbiBpbiBvbmUgcnVsZSwKICAgIC8vICh0aGUgYWN0aW9uIHR5cGUgaXMganVzdCAiZHJvcCIgYW5kIGl0IGRvZXMgbm90IGhhdmUgYW55IGFyZ3VtZW50KQogICAgLy8gYWxsIHN1YnNlcXVlbnQgcnVsZXMgd2lsbCBOT1Qgc2VlIHRoaXMgbWVzc2FnZQogICAgewogICAgICAgIC8vIGluYm91bmQgY2hhdCBtZXNzYWdlcyAocmVtb3RlIC0+IGFsbCBzZXJ2ZXJzKQogICAgICAgICJvYmplY3QiOiAiY2hhdF9tZXNzYWdlIiwgLy8gbWF0Y2ggY2hhdCBtZXNzYWdlcwogICAgICAgICJmcm9tIjogInJlbW90ZTouKiIsICAgICAgLy8gcmVnZXhwIG1hdGNoaW5nIHNvdXJjZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vIG9ubHkgbWVzc2FnZXMgd2l0aCBtYXRjaGVkIHNvdXJjZSB3aWxsIGJlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyBwcm9jZXNzZWQgYnkgdGhpcyBydWxlLCBvdGhlcndpc2UgdGhpcyBydWxlIGlzIHNraXBwZWQKICAgICAgICAiYWN0aW9ucyI6IFt7ICAgICAgICAgICAgIC8vIGFjdGlvbnMgcnVuIHNlcXVlbnRpYWxseQogICAgICAgICAgICAidHlwZSI6ICJyb3V0ZSIsICAgICAgLy8gcm91dGUgdGhpcyBtZXNzYWdlIHRvIG1hdGNoZWQgZGVzdGluYXRpb25zCiAgICAgICAgICAgICJ0byI6ICJzZXJ2ZXI6LioiICAgICAvLyByZWdleHAgbWF0Y2hpbmcgZGVzdGluYXRpb24gIAogICAgICAgIH1dCiAgICB9LAogICAgewogICAgICAgIC8vIG91dGJvdW5kIG1lc3NhZ2VzIChzdGFydGluZyB3aXRoICcjJywgc2VydmVyIC0+IGFsbCByZW1vdGVzKQogICAgICAgICJvYmplY3QiOiAiY2hhdF9tZXNzYWdlIiwKICAgICAgICAiZnJvbSI6ICJzZXJ2ZXI6LioiLAogICAgICAgICJhY3Rpb25zIjogW3sKICAgICAgICAgICAgInR5cGUiOiAiZmlsdGVyIiwgICAgIC8vIGZpbHRlciB0aGUgbWVzc2FnZSB1c2luZyBnaXZlbiByZWdleHAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vIGlmIHRoZSBtZXNzYWdlIGRvZXMgbm90IG1hdGNoIGdpdmVuIHBhdHRlcm4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyBpdCB3b24ndCBiZSBwYXNzZWQgaW50byBzdWJzZXF1ZW50IGFjdGlvbnMKICAgICAgICAgICAgInBhdHRlcm4iOiAiIy4rIiAgICAgIC8vIG1hdGNoIGFsbCBtZXNzYWdlcyBzdGFydHMgd2l0aCBjaGFyICcjJwogICAgICAgIH0sIHsKICAgICAgICAgICAgInR5cGUiOiAicmVwbGFjZSIsICAgIC8vIHJlcGxhY2UgdGhlIG1lc3NhZ2UsIHJlbW92aW5nIGhlYWRpbmcgJyMnCiAgICAgICAgICAgICJmcm9tIjogIl4jXFwoLipcXCkiLCAvLyBjYXB0dXJlIGFsbCBjaGFycyBhZnRlciB0aGUgaGVhZGluZyAnIycKICAgICAgICAgICAgInRvIjogIiQxIiAgICAgICAgICAgIC8vIGFuZCBtYWtlIHRoZW0gYXMgdGhlIG91dHB1dAogICAgICAgIH0sIHsKICAgICAgICAgICAgInR5cGUiOiAicm91dGUiLCAgICAgIC8vIHNlbmQgdGhlIG1lc3NhZ2UgdG8gYWxsIHJlbW90ZXMKICAgICAgICAgICAgInRvIjogInJlbW90ZTouKiIKICAgICAgICB9XQogICAgfSwKICAgIHsKICAgICAgICAvLyBjcm9zcy1zZXJ2ZXIgbWVzc2FnZXMgKHNlcnZlciAtPiBhbGwgb3RoZXIgc2VydmVycykKICAgICAgICAib2JqZWN0IjogImNoYXRfbWVzc2FnZSIsCiAgICAgICAgImZvcm0iOiAic2VydmVyOi4qIiwKICAgICAgICAiYWN0aW9ucyI6IFt7CiAgICAgICAgICAgICJ0eXBlIjogInJvdXRlIiwKICAgICAgICAgICAgInRvIjogInNlcnZlcjouKiIsCiAgICAgICAgICAgICJiYWNrZmxvdyI6IGZhbHNlICAvLyBkbyBub3QgcmVwZWF0IHRvIHNlbmRlciwgdHJ1ZSBieSBkZWZhdWx0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyBzaW5jZSB0aGUgZGVzdGluYXRpb24gcGF0dGVybiB3aWxsIG1hdGNoIHRoZSBzb3VyY2UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyB3ZSBoYXZlIHRvIHNldCBiYWNrZmxvdyB0byBmYWxzZSB0byBwcmV2ZW50CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyBwbGF5ZXJzIGZyb20gc2VlaW5nIGR1cGxpY2F0ZSBtZXNzYWdlcwogICAgICAgIH1dCiAgICB9Cl0="),
+ StandardCharsets.UTF_8);
+ var mapper = new ObjectMapper();
+ mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
+ var t = mapper.readTree(json);
+ for (var rule : t) {
+ var object = rule.get("object");
+ var from = rule.get("from");
+ var actions = rule.get("actions");
+ System.out.printf("%s | %s | %s%n", object, from, actions);
+ }
+ }
+
+ @Test
+ void testReadObjectType() {
+
+ }
+
+ @Test
+ void testReadReIdFilter() {
+
+ }
+} \ No newline at end of file
diff --git a/src/test/java/com/keuin/crosslink/messaging/filter/IFilterTest.java b/src/test/java/com/keuin/crosslink/messaging/filter/IFilterTest.java
new file mode 100644
index 0000000..be20077
--- /dev/null
+++ b/src/test/java/com/keuin/crosslink/messaging/filter/IFilterTest.java
@@ -0,0 +1,32 @@
+package com.keuin.crosslink.messaging.filter;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class IFilterTest {
+
+ @Test
+ void fromPatternString() throws ReIdFilter.InvalidPatternStringException {
+ assertThrows(ReIdFilter.InvalidPatternStringException.class, () -> IFilter.fromPatternString(":::"));
+ assertThrows(ReIdFilter.InvalidPatternStringException.class, () -> IFilter.fromPatternString("::"));
+ assertThrows(ReIdFilter.InvalidPatternStringException.class, () -> IFilter.fromPatternString(":"));
+ assertThrows(ReIdFilter.InvalidPatternStringException.class, () -> IFilter.fromPatternString(""));
+ assertThrows(ReIdFilter.InvalidPatternStringException.class, () -> IFilter.fromPatternString("server:::"));
+ assertThrows(ReIdFilter.InvalidPatternStringException.class, () -> IFilter.fromPatternString("server::"));
+ assertThrows(ReIdFilter.InvalidPatternStringException.class, () -> IFilter.fromPatternString("server:"));
+ assertThrows(ReIdFilter.InvalidPatternStringException.class, () -> IFilter.fromPatternString(":server::"));
+ assertThrows(ReIdFilter.InvalidPatternStringException.class, () -> IFilter.fromPatternString(":server:"));
+ assertThrows(ReIdFilter.InvalidPatternStringException.class, () -> IFilter.fromPatternString(":server"));
+ assertThrows(ReIdFilter.InvalidPatternStringException.class, () -> IFilter.fromPatternString(":::server"));
+ assertThrows(ReIdFilter.InvalidPatternStringException.class, () -> IFilter.fromPatternString("::server"));
+ assertThrows(ReIdFilter.InvalidPatternStringException.class, () -> IFilter.fromPatternString("server"));
+ assertThrows(ReIdFilter.InvalidPatternStringException.class, () -> IFilter.fromPatternString("server:"));
+ assertThrows(ReIdFilter.InvalidPatternStringException.class, () -> IFilter.fromPatternString("server:bbb:"));
+ assertThrows(ReIdFilter.InvalidPatternStringException.class, () -> IFilter.fromPatternString("server:aaa:bbb"));
+ assertThrows(ReIdFilter.InvalidPatternStringException.class, () -> IFilter.fromPatternString("remote:aa\n"));
+ assertThrows(ReIdFilter.InvalidPatternStringException.class, () -> IFilter.fromPatternString("server:aa\n"));
+ assertDoesNotThrow(() -> IFilter.fromPatternString("server:b"));
+ assertDoesNotThrow(() -> IFilter.fromPatternString("remote:bbb"));
+ }
+} \ No newline at end of file
diff --git a/src/test/java/com/keuin/crosslink/messaging/message/ComponentBackedMessageTest.java b/src/test/java/com/keuin/crosslink/messaging/message/ComponentBackedMessageTest.java
new file mode 100644
index 0000000..ad483d5
--- /dev/null
+++ b/src/test/java/com/keuin/crosslink/messaging/message/ComponentBackedMessageTest.java
@@ -0,0 +1,22 @@
+package com.keuin.crosslink.messaging.message;
+
+import com.keuin.crosslink.messaging.sender.ISender;
+import com.keuin.crosslink.testable.FakeEndpoint;
+import net.kyori.adventure.text.Component;
+import org.junit.jupiter.api.Test;
+
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class ComponentBackedMessageTest {
+
+ @Test
+ void testPureString() {
+ var source = new FakeEndpoint("endpoint");
+ var sender = ISender.create("sender", UUID.randomUUID());
+ var comp = Component.text("text").append(Component.text("message"));
+ var msg = new ComponentBackedMessage(source, sender, comp);
+ assertEquals("textmessage", msg.pureString());
+ }
+} \ No newline at end of file
diff --git a/src/test/java/com/keuin/crosslink/messaging/router/ConcreteRouterTest.java b/src/test/java/com/keuin/crosslink/messaging/router/ConcreteRouterTest.java
new file mode 100644
index 0000000..b9ace8d
--- /dev/null
+++ b/src/test/java/com/keuin/crosslink/messaging/router/ConcreteRouterTest.java
@@ -0,0 +1,132 @@
+package com.keuin.crosslink.messaging.router;
+
+import com.keuin.crosslink.messaging.action.DropAction;
+import com.keuin.crosslink.messaging.action.ReFilterAction;
+import com.keuin.crosslink.messaging.action.RouteAction;
+import com.keuin.crosslink.messaging.filter.IFilter;
+import com.keuin.crosslink.messaging.filter.ReIdFilter;
+import com.keuin.crosslink.messaging.message.IMessage;
+import com.keuin.crosslink.messaging.rule.IRule;
+import com.keuin.crosslink.messaging.rule.ImmutableRule;
+import com.keuin.crosslink.messaging.rule.ObjectType;
+import com.keuin.crosslink.messaging.sender.ISender;
+import com.keuin.crosslink.testable.FakeEndpoint;
+import com.keuin.crosslink.testable.FakeRouter;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+import java.util.regex.Pattern;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class ConcreteRouterTest {
+
+ private ConcreteRouter router;
+ private FakeEndpoint ep1 = new FakeEndpoint("a");
+ private FakeEndpoint ep2 = new FakeEndpoint("b");
+ private FakeEndpoint ep3 = new FakeEndpoint("c");
+
+ @BeforeEach
+ void setUp() {
+ router = new ConcreteRouter();
+ router.addEndpoint(ep1);
+ router.addEndpoint(ep2);
+ router.addEndpoint(ep3);
+ }
+
+ @Test
+ void testBroadcast() throws ReIdFilter.InvalidPatternStringException {
+ var ep0 = new FakeEndpoint("z");
+ var sender = ISender.create("sender", UUID.randomUUID());
+ var msg = IMessage.create(ep0, sender, "MSG,,,");
+ var action = new RouteAction(() -> router.resolveEndpoints("server", Pattern.compile(".*")), true);
+ var rule = new ImmutableRule(ObjectType.CHAT_MESSAGE, IFilter.fromPatternString("server:z"), Collections.singletonList(action));
+ router.updateRuleChain(Collections.singletonList(rule));
+ router.sendMessage(msg);
+ assertEquals(1, ep1.messages.size());
+ assertEquals(1, ep2.messages.size());
+ assertEquals(1, ep3.messages.size());
+ assertEquals(msg, ep1.messages.get(0));
+ assertEquals(msg, ep2.messages.get(0));
+ assertEquals(msg, ep3.messages.get(0));
+ }
+
+ @Test
+ void testBroadcastBackflow() throws ReIdFilter.InvalidPatternStringException {
+ var sender = ISender.create("sender", UUID.randomUUID());
+ var msg = IMessage.create(ep1, sender, "MSG,,,");
+ var action = new RouteAction(() -> router.resolveEndpoints("server", Pattern.compile(".*")), true);
+ var rule = new ImmutableRule(ObjectType.CHAT_MESSAGE, IFilter.fromPatternString("server:a"), Collections.singletonList(action));
+ router.updateRuleChain(Collections.singletonList(rule));
+ router.sendMessage(msg);
+ assertEquals(1, ep1.messages.size());
+ assertEquals(1, ep2.messages.size());
+ assertEquals(1, ep3.messages.size());
+ assertEquals(msg, ep1.messages.get(0));
+ assertEquals(msg, ep2.messages.get(0));
+ assertEquals(msg, ep3.messages.get(0));
+ }
+
+ @Test
+ void testBroadcastBackflowDisabled() throws ReIdFilter.InvalidPatternStringException {
+ var sender = ISender.create("sender", UUID.randomUUID());
+ var msg = IMessage.create(ep1, sender, "MSG,,,");
+ var action = new RouteAction(() -> router.resolveEndpoints("server", Pattern.compile(".*")), false);
+ var rule = new ImmutableRule(ObjectType.CHAT_MESSAGE, IFilter.fromPatternString("server:a"), Collections.singletonList(action));
+ router.updateRuleChain(Collections.singletonList(rule));
+ router.sendMessage(msg);
+ assertEquals(0, ep1.messages.size());
+ assertEquals(1, ep2.messages.size());
+ assertEquals(1, ep3.messages.size());
+ assertEquals(msg, ep2.messages.get(0));
+ assertEquals(msg, ep3.messages.get(0));
+ }
+
+ @Test
+ void testFilteredMessagePass() throws ReIdFilter.InvalidPatternStringException {
+ var sender = ISender.create("sender", UUID.randomUUID());
+ var msg = IMessage.create(ep1, sender, "MSG,,,");
+ var filter = new ReFilterAction(Pattern.compile("asdasdasda"));
+ var action = new RouteAction(() -> router.resolveEndpoints("server", Pattern.compile(".*")), false);
+ var rule1 = new ImmutableRule(ObjectType.CHAT_MESSAGE, IFilter.fromPatternString("server:.+"), Collections.singletonList(filter));
+ var rule2 = new ImmutableRule(ObjectType.CHAT_MESSAGE, IFilter.fromPatternString("server:a"), List.of(filter, action));
+ var rule3 = new ImmutableRule(ObjectType.CHAT_MESSAGE, IFilter.fromPatternString("server:a"), List.of(action));
+ router.updateRuleChain(List.of(rule1, rule2, rule3));
+ router.sendMessage(msg);
+ assertEquals(0, ep1.messages.size());
+ assertEquals(1, ep2.messages.size());
+ assertEquals(1, ep3.messages.size());
+ assertEquals(msg, ep2.messages.get(0));
+ assertEquals(msg, ep3.messages.get(0));
+ }
+
+ @Test
+ void testDropMessagePass() throws ReIdFilter.InvalidPatternStringException {
+ var sender = ISender.create("sender", UUID.randomUUID());
+ var msg = IMessage.create(ep1, sender, "MSG,,,");
+ var drop = new DropAction();
+ var action = new RouteAction(() -> router.resolveEndpoints("server", Pattern.compile(".*")), false);
+ var rule1 = new ImmutableRule(ObjectType.CHAT_MESSAGE, IFilter.fromPatternString("server:a"), List.of(action));
+ var rule2 = new ImmutableRule(ObjectType.CHAT_MESSAGE, IFilter.fromPatternString("server:.+"), Collections.singletonList(drop));
+ var rule3 = new ImmutableRule(ObjectType.CHAT_MESSAGE, IFilter.fromPatternString("server:a"), List.of(action));
+ router.updateRuleChain(List.of(rule1, rule2, rule3));
+ router.sendMessage(msg);
+ assertEquals(0, ep1.messages.size());
+ assertEquals(1, ep2.messages.size());
+ assertEquals(1, ep3.messages.size());
+ assertEquals(msg, ep2.messages.get(0));
+ assertEquals(msg, ep3.messages.get(0));
+ }
+
+ @AfterEach
+ void tearDown() throws Exception {
+ ep1.close();
+ ep2.close();
+ ep3.close();
+ router.close();
+ }
+} \ No newline at end of file
diff --git a/src/test/java/com/keuin/crosslink/messaging/rule/ImmutableRuleTest.java b/src/test/java/com/keuin/crosslink/messaging/rule/ImmutableRuleTest.java
new file mode 100644
index 0000000..f50425f
--- /dev/null
+++ b/src/test/java/com/keuin/crosslink/messaging/rule/ImmutableRuleTest.java
@@ -0,0 +1,95 @@
+package com.keuin.crosslink.messaging.rule;
+
+import com.keuin.crosslink.messaging.action.Re2placeAction;
+import com.keuin.crosslink.messaging.action.ReFilterAction;
+import com.keuin.crosslink.messaging.filter.IFilter;
+import com.keuin.crosslink.messaging.filter.ReIdFilter;
+import com.keuin.crosslink.messaging.message.IMessage;
+import com.keuin.crosslink.messaging.sender.ISender;
+import com.keuin.crosslink.testable.FakeEndpoint;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class ImmutableRuleTest {
+
+ @Test
+ void testEmptyRule() throws ReIdFilter.InvalidPatternStringException {
+ var fromFilter = IFilter.fromPatternString("server:.*");
+ var rule = new ImmutableRule(ObjectType.CHAT_MESSAGE, fromFilter, Collections.emptyList());
+ var source = new FakeEndpoint("endpoint");
+ var sender = ISender.create("sender", UUID.randomUUID());
+ var msg = IMessage.create(source, sender, "message");
+ var result = rule.process(msg);
+ assertTrue(result.isValid());
+ assertEquals(msg, result.getResult());
+ }
+
+ @Test
+ void testSingleFilter() throws ReIdFilter.InvalidPatternStringException {
+ var fromFilter = IFilter.fromPatternString("server:.*");
+ var msgFilter = new ReFilterAction(Pattern.compile("mess..."));
+ var rule = new ImmutableRule(ObjectType.CHAT_MESSAGE, fromFilter, Collections.singletonList(msgFilter));
+ var source = new FakeEndpoint("endpoint");
+ var sender = ISender.create("sender", UUID.randomUUID());
+ var msg = IMessage.create(source, sender, "message");
+ var result = rule.process(msg);
+ assertTrue(result.isValid());
+ assertEquals(msg, result.getResult());
+ }
+
+ @Test
+ void testFilterReplace() throws ReIdFilter.InvalidPatternStringException {
+ var fromFilter = IFilter.fromPatternString("server:.*");
+ var msgFilter = new ReFilterAction(Pattern.compile("mess..."));
+ var msgReplace = new Re2placeAction(Pattern.compile("me."), "u");
+ var rule = new ImmutableRule(ObjectType.CHAT_MESSAGE, fromFilter, List.of(msgFilter, msgReplace));
+ var source = new FakeEndpoint("endpoint");
+ var sender = ISender.create("sender", UUID.randomUUID());
+ var msg = IMessage.create(source, sender, "message");
+ var result = rule.process(msg);
+ assertTrue(result.isValid());
+ assertFalse(result.isFiltered());
+ assertFalse(result.isDropped());
+ assertEquals("usage", Objects.requireNonNull(result.getResult()).pureString());
+ }
+
+ @Test
+ void testFilterReplaceFilter1() throws ReIdFilter.InvalidPatternStringException {
+ var fromFilter = IFilter.fromPatternString("server:.*");
+ var msgFilter = new ReFilterAction(Pattern.compile("mess..."));
+ var msgReplace = new Re2placeAction(Pattern.compile("me."), "u");
+ var rule = new ImmutableRule(ObjectType.CHAT_MESSAGE, fromFilter, List.of(msgFilter, msgReplace, msgFilter));
+ var source = new FakeEndpoint("endpoint");
+ var sender = ISender.create("sender", UUID.randomUUID());
+ var msg = IMessage.create(source, sender, "message");
+ var result = rule.process(msg);
+ assertTrue(result.isFiltered());
+ assertFalse(result.isDropped());
+ assertFalse(result.isValid());
+ }
+
+ @Test
+ void testFilterReplaceFilter2() throws ReIdFilter.InvalidPatternStringException {
+ var fromFilter = IFilter.fromPatternString("server:.*");
+ var msgFilter = new ReFilterAction(Pattern.compile("mess..."));
+ var msgReplace = new Re2placeAction(Pattern.compile("me."), "u");
+ var msgFilter2 = new ReFilterAction(Pattern.compile("us..."));
+ var rule = new ImmutableRule(ObjectType.CHAT_MESSAGE, fromFilter, List.of(msgFilter, msgReplace, msgFilter2));
+ var source = new FakeEndpoint("endpoint");
+ var sender = ISender.create("sender", UUID.randomUUID());
+ var msg = IMessage.create(source, sender, "message");
+ var result = rule.process(msg);
+ assertFalse(result.isFiltered());
+ assertFalse(result.isDropped());
+ assertTrue(result.isValid());
+ assertEquals("usage", Objects.requireNonNull(result.getResult()).pureString());
+ }
+} \ No newline at end of file
diff --git a/src/test/java/com/keuin/crosslink/testable/FakeEndpoint.java b/src/test/java/com/keuin/crosslink/testable/FakeEndpoint.java
new file mode 100644
index 0000000..ada299e
--- /dev/null
+++ b/src/test/java/com/keuin/crosslink/testable/FakeEndpoint.java
@@ -0,0 +1,51 @@
+package com.keuin.crosslink.testable;
+
+import com.keuin.crosslink.messaging.endpoint.EndpointNamespace;
+import com.keuin.crosslink.messaging.endpoint.IEndpoint;
+import com.keuin.crosslink.messaging.message.IMessage;
+import com.keuin.crosslink.messaging.router.IRouter;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public class FakeEndpoint implements IEndpoint {
+ public final List<IMessage> messages = new ArrayList<>();
+ public IRouter router = null;
+ private final String id;
+
+ public FakeEndpoint(String id) {
+ this.id = id;
+ }
+
+ public FakeEndpoint() {
+ this.id = "fake";
+ }
+
+ @Override
+ public void sendMessage(IMessage message) {
+ Objects.requireNonNull(message);
+ messages.add(message);
+ }
+
+ @Override
+ public void setRouter(IRouter router) {
+ this.router = router;
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public @NotNull String id() {
+ return id;
+ }
+
+ @Override
+ public @NotNull EndpointNamespace namespace() {
+ return EndpointNamespace.SERVER;
+ }
+}
diff --git a/src/test/java/com/keuin/crosslink/testable/FakeRouter.java b/src/test/java/com/keuin/crosslink/testable/FakeRouter.java
new file mode 100644
index 0000000..0512b3e
--- /dev/null
+++ b/src/test/java/com/keuin/crosslink/testable/FakeRouter.java
@@ -0,0 +1,65 @@
+package com.keuin.crosslink.testable;
+
+import com.keuin.crosslink.messaging.endpoint.IEndpoint;
+import com.keuin.crosslink.messaging.message.IMessage;
+import com.keuin.crosslink.messaging.router.IRouter;
+import com.keuin.crosslink.messaging.rule.IRule;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+public class FakeRouter implements IRouter {
+ private final List<IMessage> messages = new ArrayList<>();
+ private final AtomicBoolean isClosed = new AtomicBoolean(false);
+ private final List<IEndpoint> endpoints = new ArrayList<>();
+ private List<IRule> rules = Collections.emptyList();
+
+ @Override
+ public void sendMessage(IMessage message) {
+ messages.add(message);
+ }
+
+ @Override
+ public void close() throws Exception {
+ isClosed.set(true);
+ }
+
+ @Override
+ public boolean addEndpoint(@NotNull IEndpoint endpoint) {
+ return endpoints.add(endpoint);
+ }
+
+ @Override
+ public @NotNull Set<IEndpoint> resolveEndpoints(@NotNull String namespace, @NotNull Pattern idPattern) {
+ return endpoints.stream()
+ .filter((ep) -> ep.namespace().toString().equals(namespace) && idPattern.matcher(ep.id()).matches())
+ .collect(Collectors.toUnmodifiableSet());
+ }
+
+ @Override
+ public void updateRuleChain(@NotNull List<IRule> newChain) {
+ this.rules = newChain;
+ }
+
+ public List<IMessage> getMessages() {
+ return messages;
+ }
+
+ public AtomicBoolean getIsClosed() {
+ return isClosed;
+ }
+
+ public List<IEndpoint> getEndpoints() {
+ return endpoints;
+ }
+
+ public List<IRule> getRules() {
+ return rules;
+ }
+}
diff --git a/src/test/java/com/keuin/crosslink/util/EggFactoryTest.java b/src/test/java/com/keuin/crosslink/util/EggFactoryTest.java
new file mode 100644
index 0000000..2c26e38
--- /dev/null
+++ b/src/test/java/com/keuin/crosslink/util/EggFactoryTest.java
@@ -0,0 +1,38 @@
+package com.keuin.crosslink.util;
+
+import net.time4j.PlainDate;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class EggFactoryTest {
+
+ @Test
+ void getEgg() {
+ // 2021 lunar new year (2021.02.12)
+ assertFalse(EggFactory.getEgg(PlainDate.of(2021, 2, 11)).isPresent());
+ assertTrue(EggFactory.getEgg(PlainDate.of(2021, 2, 12)).isPresent());
+ assertFalse(EggFactory.getEgg(PlainDate.of(2021, 2, 13)).isPresent());
+
+ // 2022 lunar new year (2022.02.01)
+ assertFalse(EggFactory.getEgg(PlainDate.of(2022, 1, 31)).isPresent());
+ assertTrue(EggFactory.getEgg(PlainDate.of(2022, 2, 1)).isPresent());
+ assertFalse(EggFactory.getEgg(PlainDate.of(2022, 2, 2)).isPresent());
+
+ // 2023 lunar new year (2023.01.22)
+ assertFalse(EggFactory.getEgg(PlainDate.of(2023, 1, 21)).isPresent());
+ assertTrue(EggFactory.getEgg(PlainDate.of(2023, 1, 22)).isPresent());
+ assertFalse(EggFactory.getEgg(PlainDate.of(2023, 1, 23)).isPresent());
+
+ // 2024 lunar new year (2024.02.10)
+ assertFalse(EggFactory.getEgg(PlainDate.of(2024, 2, 9)).isPresent());
+ assertTrue(EggFactory.getEgg(PlainDate.of(2024, 2, 10)).isPresent());
+ assertFalse(EggFactory.getEgg(PlainDate.of(2024, 2, 11)).isPresent());
+
+ // 2025 lunar new year (2025.01.29)
+ assertFalse(EggFactory.getEgg(PlainDate.of(2025, 1, 28)).isPresent());
+ assertTrue(EggFactory.getEgg(PlainDate.of(2025, 1, 29)).isPresent());
+ assertFalse(EggFactory.getEgg(PlainDate.of(2025, 1, 30)).isPresent());
+ }
+} \ No newline at end of file
diff --git a/src/test/java/com/keuin/crosslink/util/HttpQueryTest.java b/src/test/java/com/keuin/crosslink/util/HttpQueryTest.java
new file mode 100644
index 0000000..480c854
--- /dev/null
+++ b/src/test/java/com/keuin/crosslink/util/HttpQueryTest.java
@@ -0,0 +1,48 @@
+package com.keuin.crosslink.util;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class HttpQueryTest {
+ @Test
+ void testMixedFlagValue() {
+ var map = HttpQuery.getParamMap("var1=1&flag1");
+ assertTrue(map.containsKey("flag1"));
+ assertTrue(map.containsKey("var1"));
+ assertEquals("1", map.get("var1"));
+ assertEquals("", map.get("flag1"));
+ }
+
+ @Test
+ void testSingleFlag() {
+ var map = HttpQuery.getParamMap("flag1");
+ assertTrue(map.containsKey("flag1"));
+ assertEquals("", map.get("flag1"));
+ }
+
+ @Test
+ void testSingleVar() {
+ var map = HttpQuery.getParamMap("var1=value");
+ assertTrue(map.containsKey("var1"));
+ assertEquals("value", map.get("var1"));
+ }
+
+ @Test
+ void testMultipleValue() {
+ var map = HttpQuery.getParamMap("var1=111&var2=222");
+ assertTrue(map.containsKey("var1"));
+ assertTrue(map.containsKey("var2"));
+ assertEquals("111", map.get("var1"));
+ assertEquals("222", map.get("var2"));
+ }
+
+ @Test
+ void testMultipleKey() {
+ var map = HttpQuery.getParamMap("flag1&flag2");
+ assertTrue(map.containsKey("flag1"));
+ assertTrue(map.containsKey("flag2"));
+ assertEquals("", map.get("flag1"));
+ assertEquals("", map.get("flag2"));
+ }
+} \ No newline at end of file
diff --git a/src/test/java/com/keuin/crosslink/util/LazyEvaluatedTest.java b/src/test/java/com/keuin/crosslink/util/LazyEvaluatedTest.java
new file mode 100644
index 0000000..e8ffdc9
--- /dev/null
+++ b/src/test/java/com/keuin/crosslink/util/LazyEvaluatedTest.java
@@ -0,0 +1,28 @@
+package com.keuin.crosslink.util;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class LazyEvaluatedTest {
+
+ @Test
+ void get() {
+ var flag = new AtomicBoolean(false); // if evaluated
+ var cnt = new AtomicInteger(0); // counter
+ var le = new LazyEvaluated<>(() -> {
+ flag.set(true);
+ return cnt.getAndIncrement();
+ });
+ assertFalse(flag.get());
+ assertEquals(0, le.get());
+ assertTrue(flag.get());
+ assertEquals(0, le.get());
+ assertEquals(0, le.get());
+ assertEquals(0, le.get());
+ assertEquals(0, le.get());
+ }
+} \ No newline at end of file
diff --git a/src/test/java/com/keuin/crosslink/util/LoggerNamingTest.java b/src/test/java/com/keuin/crosslink/util/LoggerNamingTest.java
new file mode 100644
index 0000000..5fbdcf1
--- /dev/null
+++ b/src/test/java/com/keuin/crosslink/util/LoggerNamingTest.java
@@ -0,0 +1,14 @@
+package com.keuin.crosslink.util;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class LoggerNamingTest {
+
+ @Test
+ void name() {
+ assertEquals("crosslink", LoggerNaming.name().toString());
+ assertEquals("crosslink.actions.replace", LoggerNaming.name().of("actions").of("replace").toString());
+ }
+} \ No newline at end of file