summaryrefslogtreecommitdiff
path: root/src/test
diff options
context:
space:
mode:
Diffstat (limited to 'src/test')
-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