diff --git a/libs/nabu-v0.0.1-SNAPSHOT-jar-with-dependencies.jar b/libs/nabu-v0.0.1-SNAPSHOT-jar-with-dependencies.jar index efce6070a..98aed7f65 100644 Binary files a/libs/nabu-v0.0.1-SNAPSHOT-jar-with-dependencies.jar and b/libs/nabu-v0.0.1-SNAPSHOT-jar-with-dependencies.jar differ diff --git a/src/main/java/com/limechain/chain/ChainService.java b/src/main/java/com/limechain/chain/ChainService.java index 2b25a7dfc..597bba822 100644 --- a/src/main/java/com/limechain/chain/ChainService.java +++ b/src/main/java/com/limechain/chain/ChainService.java @@ -34,7 +34,10 @@ public class ChainService { public ChainService(HostConfig hostConfig, KVRepository repository) { this.repository = repository; + initialize(hostConfig); + } + protected void initialize(HostConfig hostConfig) { Optional genesis = repository.find(genesisKey); /* WORKAROUND @@ -56,10 +59,11 @@ public ChainService(HostConfig hostConfig, KVRepository reposito this.setGenesis(ChainSpec.newFromJSON(hostConfig.getGenesisPath())); log.log(Level.INFO, "✅️Loaded chain spec from JSON"); - repository.save(genesisKey, this.getGenesis()); + repository.save(this.getGenesisKey(), this.getGenesis()); log.log(Level.FINE, "Saved chain spec to database"); } catch (IOException e) { throw new RuntimeException(e); } + } } diff --git a/src/main/java/com/limechain/network/Network.java b/src/main/java/com/limechain/network/Network.java index 228aaa093..a1e23798f 100644 --- a/src/main/java/com/limechain/network/Network.java +++ b/src/main/java/com/limechain/network/Network.java @@ -94,12 +94,8 @@ public static Network initialize(ChainService chainService, HostConfig hostConfi * Periodically searched for new peers */ @Scheduled(fixedDelay = TEN_SECONDS_IN_MS) - public void findPeers() throws InterruptedException { + public void findPeers() { log.log(Level.INFO, "Searching for nodes..."); - try { - kademliaService.findNewPeers(); - } catch (Exception e) { - log.log(Level.SEVERE, "Error: " + e.getMessage()); - } + kademliaService.findNewPeers(); } } diff --git a/src/main/java/com/limechain/rpc/http/server/HttpRpcContext.java b/src/main/java/com/limechain/rpc/http/server/HttpRpcContext.java index 68f7e7d99..6e948d23d 100644 --- a/src/main/java/com/limechain/rpc/http/server/HttpRpcContext.java +++ b/src/main/java/com/limechain/rpc/http/server/HttpRpcContext.java @@ -12,6 +12,7 @@ *

* Pattern taken from here */ +@Deprecated @Component @Deprecated public class HttpRpcContext implements ApplicationContextAware { diff --git a/src/main/java/com/limechain/rpc/pubsub/PubSubService.java b/src/main/java/com/limechain/rpc/pubsub/PubSubService.java index b0dc75758..4d5b1f0cf 100644 --- a/src/main/java/com/limechain/rpc/pubsub/PubSubService.java +++ b/src/main/java/com/limechain/rpc/pubsub/PubSubService.java @@ -13,7 +13,7 @@ import java.util.logging.Level; /** - * A mediator class standing between {@link com.limechain.rpc.pubsub.publisher.Publisher} + * A singleton mediator class standing between {@link com.limechain.rpc.pubsub.publisher.Publisher} * and {@link AbstractSubscriberChannel} which accepts messages from the formers and sends them at some point * to the latter. *

@@ -102,18 +102,14 @@ public void removeSubscriber(Topic topic, String sessionId) { * {@link #messagesQueue} will be empty after broadcasting. */ public void broadcast() { - if (messagesQueue.isEmpty()) { - log.log(Level.FINE, "No messages to broadcast"); - } else { - while (!messagesQueue.isEmpty()) { - Message message = messagesQueue.remove(); - String topic = message.topic(); + while (!messagesQueue.isEmpty()) { + Message message = messagesQueue.remove(); + String topic = message.topic(); - AbstractSubscriberChannel subscriberChannel = subscribersTopicMap.get(Topic.fromString(topic)); - // If subscriberChannel is null, the message will get lost - if (subscriberChannel != null) { - subscriberChannel.getPendingMessages().add(message); - } + AbstractSubscriberChannel subscriberChannel = subscribersTopicMap.get(Topic.fromString(topic)); + // If subscriberChannel is null, the message will get lost + if (subscriberChannel != null) { + subscriberChannel.addMessage(message); } } } @@ -130,26 +126,4 @@ public void notifySubscribers() { } } } - - /** - * Sends all messages about a topic to the subscriber channel for that topic - * - * @param subscriberChannel the subscriber channel - */ - public void sendMessagesToChannel(AbstractSubscriberChannel subscriberChannel) { - if (messagesQueue.isEmpty()) { - log.log(Level.FINE, "No messages to send"); - return; - } - - while (!messagesQueue.isEmpty()) { - Message message = messagesQueue.remove(); - if (message.topic().equalsIgnoreCase(subscriberChannel.getTopic().getValue())) { - if (subscribersTopicMap.get(subscriberChannel.getTopic()).equals(subscriberChannel)) { - // Add broadcast message to subscriber message queue - subscriberChannel.getPendingMessages().add(message); - } - } - } - } } diff --git a/src/main/java/com/limechain/rpc/pubsub/subscriberchannel/AbstractSubscriberChannel.java b/src/main/java/com/limechain/rpc/pubsub/subscriberchannel/AbstractSubscriberChannel.java index 5a0be6ff1..edc91a66f 100644 --- a/src/main/java/com/limechain/rpc/pubsub/subscriberchannel/AbstractSubscriberChannel.java +++ b/src/main/java/com/limechain/rpc/pubsub/subscriberchannel/AbstractSubscriberChannel.java @@ -1,7 +1,6 @@ package com.limechain.rpc.pubsub.subscriberchannel; import com.limechain.rpc.pubsub.Message; -import com.limechain.rpc.pubsub.PubSubService; import com.limechain.rpc.pubsub.Topic; import lombok.Getter; import lombok.Setter; @@ -63,11 +62,11 @@ public AbstractSubscriberChannel(Topic topic) { public abstract void removeSubscriber(WebSocketSession session); /** - * Request messages for this channel from {@link PubSubService} + * Adds a message to the channel * - * @param pubSubService the pub-sub service mediator + * @param message the message to add */ - public abstract void getMessagesForSubscriberOfTopic(PubSubService pubSubService); + public abstract void addMessage(Message message); /** * Send all messages from {@link #pendingMessages} to every subscriber from {@link #subscribers} diff --git a/src/main/java/com/limechain/rpc/pubsub/subscriberchannel/SubscriberChannel.java b/src/main/java/com/limechain/rpc/pubsub/subscriberchannel/SubscriberChannel.java index 411c705cb..389e8b315 100644 --- a/src/main/java/com/limechain/rpc/pubsub/subscriberchannel/SubscriberChannel.java +++ b/src/main/java/com/limechain/rpc/pubsub/subscriberchannel/SubscriberChannel.java @@ -1,6 +1,6 @@ package com.limechain.rpc.pubsub.subscriberchannel; -import com.limechain.rpc.pubsub.PubSubService; +import com.limechain.rpc.pubsub.Message; import com.limechain.rpc.pubsub.Topic; import org.springframework.web.socket.WebSocketSession; @@ -23,7 +23,8 @@ public void removeSubscriber(WebSocketSession session) { this.getSubscribers().removeIf(s -> s.getId().equals(session.getId())); } - public void getMessagesForSubscriberOfTopic(PubSubService pubSubService) { - pubSubService.sendMessagesToChannel(this); + @Override + public void addMessage(Message message) { + this.getPendingMessages().add(message); } } diff --git a/src/test/java/com/limechain/chain/ChainServiceTest.java b/src/test/java/com/limechain/chain/ChainServiceTest.java new file mode 100644 index 000000000..60d79cc3a --- /dev/null +++ b/src/test/java/com/limechain/chain/ChainServiceTest.java @@ -0,0 +1,79 @@ +package com.limechain.chain; + +import com.limechain.config.HostConfig; +import com.limechain.storage.KVRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.io.IOException; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class ChainServiceTest { + private ChainService chainService; + private HostConfig hostConfig; + private KVRepository repository; + + @BeforeEach + public void setup() { + hostConfig = mock(HostConfig.class); + repository = mock(KVRepository.class); + } + + @Test + public void setsChainSpecFromDB_when_chainSpecIsInDB() { + var chainSpec = new ChainSpec() {{ + this.setName("testName"); + }}; + Optional mockGenesis = Optional.of(chainSpec); + + doReturn(mockGenesis).when(repository).find(any()); + + chainService = new ChainService(hostConfig, repository); + + assertEquals(chainService.getGenesis(), chainSpec); + } + + @Test + public void savesChainSpecToDB_when_chainSpecIsNotInDB() { + var chainSpec = new ChainSpec() {{ + this.setName("testName"); + }}; + + Optional mockGenesis = Optional.ofNullable(null); + + doReturn(mockGenesis).when(repository).find(any()); + + try (MockedStatic chainSpecStatic = Mockito.mockStatic(ChainSpec.class)) { + chainSpecStatic.when(() -> + ChainSpec.newFromJSON(any())).thenReturn(chainSpec); + + chainService = new ChainService(hostConfig, repository); + verify(repository, times(1)).save("genesis", chainSpec); + } + } + + @Test + public void throwsRuntimeException_when_saveFails() { + Optional mockGenesis = Optional.ofNullable(null); + + doReturn(mockGenesis).when(repository).find(any()); + + try (MockedStatic chainSpecStatic = Mockito.mockStatic(ChainSpec.class)) { + chainSpecStatic.when(() -> + ChainSpec.newFromJSON(any())).thenThrow(IOException.class); + + assertThrows(RuntimeException.class, () -> chainService = new ChainService(hostConfig, repository)); + } + } + +} diff --git a/src/test/java/com/limechain/chain/ChainTest.java b/src/test/java/com/limechain/chain/ChainTest.java index cf6cff432..3e6dc4660 100644 --- a/src/test/java/com/limechain/chain/ChainTest.java +++ b/src/test/java/com/limechain/chain/ChainTest.java @@ -1,5 +1,6 @@ package com.limechain.chain; +import com.limechain.rpc.config.SubscriptionName; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -20,9 +21,9 @@ public void fromStringTest() { } @Test - public void invalidFromStringTest(){ - String testString = "string"; - Chain chain = Chain.fromString(testString); - assertNull(chain); + public void FromString_returns_correctValue() { + assertEquals(Chain.fromString("polkadot"), + Chain.POLKADOT); + assertNull(SubscriptionName.fromString("invalid")); } } diff --git a/src/test/java/com/limechain/config/HostConfigTest.java b/src/test/java/com/limechain/config/HostConfigTest.java index 17a0b629a..027b07885 100644 --- a/src/test/java/com/limechain/config/HostConfigTest.java +++ b/src/test/java/com/limechain/config/HostConfigTest.java @@ -14,6 +14,9 @@ import static org.springframework.test.util.ReflectionTestUtils.setField; public class HostConfigTest { + private final String westendGenesisPath = "genesis/westend.json"; + private final String kusamaGenesisPath = "genesis/kusama.json"; + private final String polkadotGenesisPath = "genesis/polkadot.json"; private CliArguments cliArguments; @BeforeEach @@ -29,7 +32,6 @@ public void HostConfig_Succeeds_PassedCliArguments() { HostConfig hostConfig = new HostConfig(cliArguments); assertEquals(Chain.WESTEND, hostConfig.getChain()); - String westendGenesisPath = "genesis/westend.json"; setField(hostConfig, "westendGenesisPath", westendGenesisPath); assertEquals(westendGenesisPath, hostConfig.getGenesisPath()); @@ -48,4 +50,26 @@ public void HostConfig_throwsException_whenNetworkInvalid() { assertTrue(actualMessage.contains(expectedMessage)); } + + @Test + public void GetGenesisPath_returnsCorrectPath_whenPassedChain() { + // Westend + when(cliArguments.network()).thenReturn(Chain.WESTEND.getValue()); + when(cliArguments.dbPath()).thenReturn(DBInitializer.DEFAULT_DIRECTORY); + HostConfig hostConfig = new HostConfig(cliArguments); + setField(hostConfig, "westendGenesisPath", westendGenesisPath); + assertEquals(hostConfig.getGenesisPath(), westendGenesisPath); + + // Kusama + when(cliArguments.network()).thenReturn(Chain.KUSAMA.getValue()); + hostConfig = new HostConfig(cliArguments); + setField(hostConfig, "kusamaGenesisPath", kusamaGenesisPath); + assertEquals(hostConfig.getGenesisPath(), kusamaGenesisPath); + + // Polkadot + when(cliArguments.network()).thenReturn(Chain.POLKADOT.getValue()); + hostConfig = new HostConfig(cliArguments); + setField(hostConfig, "polkadotGenesisPath", polkadotGenesisPath); + assertEquals(hostConfig.getGenesisPath(), polkadotGenesisPath); + } } diff --git a/src/test/java/com/limechain/rpc/config/SubscriptionNameTest.java b/src/test/java/com/limechain/rpc/config/SubscriptionNameTest.java new file mode 100644 index 000000000..660a8b693 --- /dev/null +++ b/src/test/java/com/limechain/rpc/config/SubscriptionNameTest.java @@ -0,0 +1,28 @@ +package com.limechain.rpc.config; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class SubscriptionNameTest { + @Test + public void SubscriptionNames_haveCorrectValues() { + assertEquals(SubscriptionName.CHAIN_HEAD_UNSTABLE_FOLLOW.getValue(), "chainHead_unstable_follow"); + assertEquals(SubscriptionName.CHAIN_HEAD_UNSTABLE_UNFOLLOW.getValue(), "chainHead_unstable_unfollow"); + assertEquals(SubscriptionName.CHAIN_HEAD_UNSTABLE_UNPIN.getValue(), "chainHead_unstable_unpin"); + assertEquals(SubscriptionName.CHAIN_HEAD_UNSTABLE_STORAGE.getValue(), "chainHead_unstable_storage"); + assertEquals(SubscriptionName.CHAIN_HEAD_UNSTABLE_CALL.getValue(), "chainHead_unstable_call"); + assertEquals(SubscriptionName.CHAIN_HEAD_UNSTABLE_STOP_CALL.getValue(), "chainHead_unstable_stopCall"); + assertEquals(SubscriptionName.TRANSACTION_UNSTABLE_SUBMIT_AND_WATCH.getValue(), + "transaction_unstable_submitAndWatch"); + assertEquals(SubscriptionName.TRANSACTION_UNSTABLE_UNWATCH.getValue(), "transaction_unstable_unwatch"); + } + + @Test + public void FromString_returns_correctValue() { + assertEquals(SubscriptionName.fromString("chainHead_unstable_follow"), + SubscriptionName.CHAIN_HEAD_UNSTABLE_FOLLOW); + assertNull(SubscriptionName.fromString("invalid")); + } +} \ No newline at end of file diff --git a/src/test/java/com/limechain/rpc/pubsub/PubSubServiceTest.java b/src/test/java/com/limechain/rpc/pubsub/PubSubServiceTest.java new file mode 100644 index 000000000..e771a52af --- /dev/null +++ b/src/test/java/com/limechain/rpc/pubsub/PubSubServiceTest.java @@ -0,0 +1,224 @@ +package com.limechain.rpc.pubsub; + +import com.limechain.rpc.pubsub.subscriberchannel.AbstractSubscriberChannel; +import com.limechain.rpc.pubsub.subscriberchannel.SubscriberChannel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.web.socket.WebSocketSession; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.Queue; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +class PubSubServiceTest { + private final PubSubService service = PubSubService.getInstance(); + + // Setting private fields. Not a good idea in general + private void setPrivateField(String fieldName, Object value) + throws NoSuchFieldException, IllegalAccessException { + Field privateField = PubSubService.class.getDeclaredField(fieldName); + privateField.setAccessible(true); + + privateField.set(service, value); + } + + // Accessing private fields. Not a good idea in general + private Object getPrivateField(String fieldName) + throws NoSuchFieldException, IllegalAccessException { + Field privateField = PubSubService.class.getDeclaredField(fieldName); + privateField.setAccessible(true); + + return privateField.get(service); + } + + @BeforeEach + public void setup() throws NoSuchFieldException, IllegalAccessException { + // Reset state of singleton manually before each state + // Not the best approach but can't reset it using new PubSubService() because of private constructor + setPrivateField("subscribersTopicMap", new HashMap<>() {{ + // TODO: Instantiate more subscriber channels in the future + put(Topic.UNSTABLE_FOLLOW, new SubscriberChannel(Topic.UNSTABLE_FOLLOW)); + put(Topic.UNSTABLE_TRANSACTION_WATCH, new SubscriberChannel(Topic.UNSTABLE_TRANSACTION_WATCH)); + }}); + + setPrivateField("messagesQueue", new LinkedList<>()); + } + + @Test + public void GetInstance_returns_sameReference() { + PubSubService reference1 = PubSubService.getInstance(); + PubSubService reference2 = PubSubService.getInstance(); + + assertEquals(reference1, reference2); + } + + @Test + public void AddMessageToQueue_addsMessage() throws NoSuchFieldException, IllegalAccessException { + Message message = new Message(Topic.UNSTABLE_TRANSACTION_WATCH.getValue(), "test payload"); + + // How to proceed? Can't verify since messagesQueue is private + + service.addMessageToQueue(message); + + Queue messageQueue = (Queue) getPrivateField("messagesQueue"); + + assertEquals(1, messageQueue.size()); + assertEquals(message, messageQueue.remove()); + } + + @Test + public void AddSubscriber_callsChannelAddSubscriber_whenTopicExists() + throws NoSuchFieldException, IllegalAccessException { + SubscriberChannel channel = mock(SubscriberChannel.class); + WebSocketSession session = mock(WebSocketSession.class); + + Map map = + (Map) getPrivateField("subscribersTopicMap"); + + // Overwrite channel with mocked channel + map.put(Topic.UNSTABLE_FOLLOW, channel); + + service.addSubscriber(Topic.UNSTABLE_FOLLOW, session); + + verify(channel, times(1)).addSubscriber(session); + } + + @Test + public void AddSubscriber_doesNotCallChannelAddSubscriber_whenTopicDoesNotExist() + throws NoSuchFieldException, IllegalAccessException { + SubscriberChannel channel = mock(SubscriberChannel.class); + WebSocketSession session = mock(WebSocketSession.class); + + // Simulate that we don't have a channel for a topic + HashMap map = new HashMap<>() {{ + put(Topic.UNSTABLE_FOLLOW, channel); + }}; + setPrivateField("subscribersTopicMap", map); + + service.addSubscriber(Topic.UNSTABLE_TRANSACTION_WATCH, session); + + verify(channel, times(0)).addSubscriber(session); + } + + @Test + public void RemoveSubscriber_callsChannelRemoveSubscriber_whenSessionExist() + throws NoSuchFieldException, IllegalAccessException { + SubscriberChannel channel = mock(SubscriberChannel.class); + WebSocketSession session = mock(WebSocketSession.class); + + doReturn("1").when(session).getId(); + doReturn(new ArrayList<>() {{ + add(session); + }}).when(channel).getSubscribers(); + + Map map = + (Map) getPrivateField("subscribersTopicMap"); + + // Overwrite channel with mocked channel + map.put(Topic.UNSTABLE_FOLLOW, channel); + + service.removeSubscriber(Topic.UNSTABLE_FOLLOW, session.getId()); + + verify(channel, times(1)).removeSubscriber(session); + } + + @Test + public void RemoveSubscriber_doesNotCallChannelRemoveSubscriber_whenSessionDoesNotExist() + throws NoSuchFieldException, IllegalAccessException { + SubscriberChannel channel = mock(SubscriberChannel.class); + WebSocketSession session = mock(WebSocketSession.class); + + doReturn("1").when(session).getId(); + doReturn(new ArrayList<>()).when(channel).getSubscribers(); + + Map map = + (Map) getPrivateField("subscribersTopicMap"); + + // Overwrite channel with mocked channel + map.put(Topic.UNSTABLE_FOLLOW, channel); + + service.removeSubscriber(Topic.UNSTABLE_FOLLOW, session.getId()); + + verify(channel, times(0)).removeSubscriber(session); + } + + @Test + public void RemoveSubscriber_doesNotCallChannelRemoveSubscriber_whenTopicDoesNotExist() + throws NoSuchFieldException, IllegalAccessException { + SubscriberChannel channel = mock(SubscriberChannel.class); + WebSocketSession session = mock(WebSocketSession.class); + + doReturn("1").when(session).getId(); + + // Simulate that we don't have a channel for a topic + HashMap map = new HashMap<>() {{ + put(Topic.UNSTABLE_FOLLOW, channel); + }}; + setPrivateField("subscribersTopicMap", map); + + service.removeSubscriber(Topic.UNSTABLE_TRANSACTION_WATCH, session.getId()); + + verify(channel, times(0)).removeSubscriber(session); + } + + @Test + public void broadcast_emptiesMessageQueue_whenCalled() throws NoSuchFieldException, IllegalAccessException { + Message message1 = new Message(Topic.UNSTABLE_FOLLOW.getValue(), "message1"); + Message message2 = new Message(Topic.UNSTABLE_FOLLOW.getValue(), "message2"); + Message message3 = new Message(Topic.UNSTABLE_TRANSACTION_WATCH.getValue(), "message3"); + + service.addMessageToQueue(message1); + service.addMessageToQueue(message2); + service.addMessageToQueue(message3); + + Queue map = (Queue) getPrivateField("messagesQueue"); + + assertEquals(3, map.size()); + + service.broadcast(); + + assertEquals(0, map.size()); + } + + @Test + public void notifySubscribers_callsNotifySubscribers_forAllChannels() + throws NoSuchFieldException, IllegalAccessException, IOException { + SubscriberChannel channel1 = mock(SubscriberChannel.class); + SubscriberChannel channel2 = mock(SubscriberChannel.class); + + setPrivateField("subscribersTopicMap", new HashMap<>() {{ + put(Topic.UNSTABLE_FOLLOW, channel1); + put(Topic.UNSTABLE_TRANSACTION_WATCH, channel2); + }}); + + service.notifySubscribers(); + + verify(channel1, times(1)).notifySubscribers(); + verify(channel2, times(1)).notifySubscribers(); + } + + @Test + public void notifySubscribers_throwsRuntimeException_whenNotifySubscribersFails() + throws NoSuchFieldException, IllegalAccessException, IOException { + SubscriberChannel channel1 = mock(SubscriberChannel.class); + doThrow(new IOException()).when(channel1).notifySubscribers(); + setPrivateField("subscribersTopicMap", new HashMap<>() {{ + put(Topic.UNSTABLE_FOLLOW, channel1); + }}); + + assertThrows(RuntimeException.class, () -> service.notifySubscribers()); + } + +} \ No newline at end of file diff --git a/src/test/java/com/limechain/rpc/pubsub/TopicTest.java b/src/test/java/com/limechain/rpc/pubsub/TopicTest.java new file mode 100644 index 000000000..48475d45e --- /dev/null +++ b/src/test/java/com/limechain/rpc/pubsub/TopicTest.java @@ -0,0 +1,22 @@ +package com.limechain.rpc.pubsub; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class TopicTest { + + @Test + public void Topics_haveCorrectValues() { + assertEquals(Topic.UNSTABLE_FOLLOW.getValue(), "unstable_follow"); + assertEquals(Topic.UNSTABLE_TRANSACTION_WATCH.getValue(), "transaction_watch"); + } + + @Test + public void FromString_returns_correctValue() { + assertEquals(Topic.fromString("unstable_follow"), + Topic.UNSTABLE_FOLLOW); + assertNull(Topic.fromString("invalid")); + } +} \ No newline at end of file diff --git a/src/test/java/com/limechain/rpc/pubsub/publisher/PublisherImplTest.java b/src/test/java/com/limechain/rpc/pubsub/publisher/PublisherImplTest.java new file mode 100644 index 000000000..3024d10eb --- /dev/null +++ b/src/test/java/com/limechain/rpc/pubsub/publisher/PublisherImplTest.java @@ -0,0 +1,23 @@ +package com.limechain.rpc.pubsub.publisher; + +import com.limechain.rpc.pubsub.Message; +import com.limechain.rpc.pubsub.PubSubService; +import org.junit.jupiter.api.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +class PublisherImplTest { + + @Test + void publish_callsAddMessageToQueue() { + PubSubService pubSubService = mock(PubSubService.class); + Message message = mock(Message.class); + + var publisher = new PublisherImpl(); + publisher.publish(message, pubSubService); + + verify(pubSubService, times(1)).addMessageToQueue(message); + } +} \ No newline at end of file diff --git a/src/test/java/com/limechain/rpc/pubsub/subscriberchannel/AbstractSubscriberChannelTest.java b/src/test/java/com/limechain/rpc/pubsub/subscriberchannel/AbstractSubscriberChannelTest.java new file mode 100644 index 000000000..34891c396 --- /dev/null +++ b/src/test/java/com/limechain/rpc/pubsub/subscriberchannel/AbstractSubscriberChannelTest.java @@ -0,0 +1,57 @@ +package com.limechain.rpc.pubsub.subscriberchannel; + +import com.limechain.rpc.pubsub.Message; +import com.limechain.rpc.pubsub.Topic; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.web.socket.WebSocketSession; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +class AbstractSubscriberChannelTest { + private final Topic topic = Topic.UNSTABLE_FOLLOW; + private SubscriberChannel channel; + + @BeforeEach + public void setup() { + this.channel = new SubscriberChannel(topic); + } + + @Test + public void constructor_setsTopic() { + assertEquals(new SubscriberChannel(Topic.UNSTABLE_TRANSACTION_WATCH).getTopic(), + Topic.UNSTABLE_TRANSACTION_WATCH); + } + + @Test + public void notifySubscribers_callsSessionSendMessage_forEveryMessage() throws IOException { + Message message1 = new Message(topic.getValue(), "message1"); + Message message2 = new Message(topic.getValue(), "message2"); + + WebSocketSession session1 = mock(WebSocketSession.class); + WebSocketSession session2 = mock(WebSocketSession.class); + doReturn("1").when(session1).getId(); + doReturn("2").when(session2).getId(); + + this.channel.addMessage(message1); + this.channel.addMessage(message2); + + this.channel.addSubscriber(session1); + this.channel.addSubscriber(session2); + + this.channel.notifySubscribers(); + + verify(session1, times(2)).sendMessage(any()); + verify(session2, times(2)).sendMessage(any()); + + assertEquals(0, this.channel.getPendingMessages().size()); + } + +} \ No newline at end of file diff --git a/src/test/java/com/limechain/rpc/pubsub/subscriberchannel/SubscriberChannelTest.java b/src/test/java/com/limechain/rpc/pubsub/subscriberchannel/SubscriberChannelTest.java new file mode 100644 index 000000000..d06a78be8 --- /dev/null +++ b/src/test/java/com/limechain/rpc/pubsub/subscriberchannel/SubscriberChannelTest.java @@ -0,0 +1,44 @@ +package com.limechain.rpc.pubsub.subscriberchannel; + +import com.limechain.rpc.pubsub.Topic; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.web.socket.WebSocketSession; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class SubscriberChannelTest { + + private SubscriberChannel channel; + + @BeforeEach + public void setup() { + this.channel = new SubscriberChannel(Topic.UNSTABLE_FOLLOW); + } + + @Test + void removeSubscriber_removesCorrectSubscriber() { + WebSocketSession session1 = mock(WebSocketSession.class); + WebSocketSession session2 = mock(WebSocketSession.class); + WebSocketSession session3 = mock(WebSocketSession.class); + + when(session1.getId()).thenReturn("1").thenReturn("1"); + when(session2.getId()).thenReturn("2").thenReturn("2"); + when(session3.getId()).thenReturn("3").thenReturn("3"); + + this.channel.addSubscriber(session1); + this.channel.addSubscriber(session2); + this.channel.addSubscriber(session3); + assertEquals(3, this.channel.getSubscribers().size()); + + this.channel.removeSubscriber(session2); + + assertTrue(this.channel.getSubscribers().contains(session1)); + assertTrue(this.channel.getSubscribers().contains(session3)); + assertEquals(2, this.channel.getSubscribers().size()); + + } +} \ No newline at end of file diff --git a/src/test/java/com/limechain/rpc/subscriptions/utils/UtilsTest.java b/src/test/java/com/limechain/rpc/subscriptions/utils/UtilsTest.java new file mode 100644 index 000000000..078f81a8c --- /dev/null +++ b/src/test/java/com/limechain/rpc/subscriptions/utils/UtilsTest.java @@ -0,0 +1,16 @@ +package com.limechain.rpc.subscriptions.utils; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class UtilsTest { + + @Test + void wrapWithDoubleQuotes_worksCorrect() { + assertEquals("\"\"", Utils.wrapWithDoubleQuotes("")); + assertEquals("\"1\"", Utils.wrapWithDoubleQuotes("1")); + assertEquals("\"0x123\"", Utils.wrapWithDoubleQuotes("0x123")); + assertEquals("\"some_string\"", Utils.wrapWithDoubleQuotes("some_string")); + } +} \ No newline at end of file diff --git a/src/test/java/com/limechain/storage/DBInitializerTest.java b/src/test/java/com/limechain/storage/DBInitializerTest.java new file mode 100644 index 000000000..d8783a7f9 --- /dev/null +++ b/src/test/java/com/limechain/storage/DBInitializerTest.java @@ -0,0 +1,88 @@ +package com.limechain.storage; + +import com.limechain.chain.Chain; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.util.AbstractMap; +import java.util.Map; +import java.util.Set; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class DBInitializerTest { + private DBInitializer initializer; + + @BeforeEach + public void setup() { + this.initializer = new DBInitializer(); + } + + @AfterEach + public void close() { + this.initializer.closeInstances(); + } + + // Setting private fields. Not a good idea in general + private void setPrivateField(String fieldName, Object value) + throws NoSuchFieldException, IllegalAccessException { + Field privateField = DBInitializer.class.getDeclaredField(fieldName); + privateField.setAccessible(true); + + privateField.set(initializer, value); + } + + // Accessing private fields. Not a good idea in general + private Object getPrivateField(String fieldName) + throws NoSuchFieldException, IllegalAccessException { + Field privateField = DBInitializer.class.getDeclaredField(fieldName); + privateField.setAccessible(true); + + return privateField.get(initializer); + } + + @Test + public void initialize_addsRepository() throws NoSuchFieldException, IllegalAccessException { + DBInitializer initializer = mock(DBInitializer.class); + Map instances = mock(Map.class); + setPrivateField("instances", instances); + String testPath = "test/path1"; + initializer.initialize(testPath, Chain.WESTEND); + + verify(instances, times(1)).put(eq(testPath), any()); + verify(instances, never()).get(testPath); + + when(instances.containsKey(testPath)).thenReturn(true); + + initializer.initialize(testPath, Chain.WESTEND); + + verify(instances, times(1)).get(testPath); + verify(instances, times(1)).put(eq(testPath), any()); + } + + @Test + public void closeInstances_closesConnection() throws NoSuchFieldException, IllegalAccessException { + Map instances = mock(Map.class); + String testPath1 = "test/path1"; + String testPath2 = "test/path2"; + Map.Entry entrySet1 = new AbstractMap.SimpleEntry(testPath1, mock(DBRepository.class)); + Map.Entry entrySet2 = new AbstractMap.SimpleEntry(testPath2, mock(DBRepository.class)); + + setPrivateField("instances", instances); + Set> set = Set.of(entrySet1, entrySet2); + when(instances.entrySet()).thenReturn(set); + + initializer.closeInstances(); + verify(entrySet1.getValue(), times(1)).closeConnection(); + verify(entrySet2.getValue(), times(1)).closeConnection(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/limechain/storage/DBRepositoryTest.java b/src/test/java/com/limechain/storage/DBRepositoryTest.java index 1dd04429f..c48f3bebb 100644 --- a/src/test/java/com/limechain/storage/DBRepositoryTest.java +++ b/src/test/java/com/limechain/storage/DBRepositoryTest.java @@ -22,7 +22,6 @@ public void setup() { @AfterEach public void close() { dbRepository = null; - DBInitializer.closeInstances(); } @Test