Extract class for supplying commands, improve tests for FCP interface
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Sun, 8 Jan 2017 14:07:32 +0000 (15:07 +0100)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Sun, 8 Jan 2017 14:07:38 +0000 (15:07 +0100)
src/main/java/net/pterodactylus/sone/fcp/FcpInterface.java
src/test/java/net/pterodactylus/sone/fcp/FcpInterfaceTest.kt
src/test/kotlin/net/pterodactylus/sone/test/Guice.kt [new file with mode: 0644]

index 36e18e6..7fb4462 100644 (file)
@@ -20,7 +20,6 @@ package net.pterodactylus.sone.fcp;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static java.util.logging.Logger.getLogger;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static java.util.logging.Logger.getLogger;
 
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -28,6 +27,8 @@ import java.util.concurrent.atomic.AtomicReference;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import javax.inject.Singleton;
+
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.fcp.event.FcpInterfaceActivatedEvent;
 import net.pterodactylus.sone.fcp.event.FcpInterfaceDeactivatedEvent;
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.fcp.event.FcpInterfaceActivatedEvent;
 import net.pterodactylus.sone.fcp.event.FcpInterfaceDeactivatedEvent;
@@ -45,7 +46,6 @@ import freenet.support.api.Bucket;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.eventbus.Subscribe;
 import com.google.inject.Inject;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.eventbus.Subscribe;
 import com.google.inject.Inject;
-import com.google.inject.Singleton;
 
 /**
  * Implementation of an FCP interface for other clients or plugins to
 
 /**
  * Implementation of an FCP interface for other clients or plugins to
@@ -84,7 +84,7 @@ public class FcpInterface {
        private final AtomicReference<FullAccessRequired> fullAccessRequired = new AtomicReference<FullAccessRequired>(FullAccessRequired.ALWAYS);
 
        /** All available FCP commands. */
        private final AtomicReference<FullAccessRequired> fullAccessRequired = new AtomicReference<FullAccessRequired>(FullAccessRequired.ALWAYS);
 
        /** All available FCP commands. */
-       private final Map<String, AbstractSoneCommand> commands = Collections.synchronizedMap(new HashMap<String, AbstractSoneCommand>());
+       private final Map<String, AbstractSoneCommand> commands;
 
        /**
         * Creates a new FCP interface.
 
        /**
         * Creates a new FCP interface.
@@ -93,22 +93,8 @@ public class FcpInterface {
         *            The core
         */
        @Inject
         *            The core
         */
        @Inject
-       public FcpInterface(Core core) {
-               commands.put("Version", new VersionCommand(core));
-               commands.put("GetLocalSones", new GetLocalSonesCommand(core));
-               commands.put("GetSones", new GetSonesCommand(core));
-               commands.put("GetSone", new GetSoneCommand(core));
-               commands.put("GetPost", new GetPostCommand(core));
-               commands.put("GetPosts", new GetPostsCommand(core));
-               commands.put("GetPostFeed", new GetPostFeedCommand(core));
-               commands.put("LockSone", new LockSoneCommand(core));
-               commands.put("UnlockSone", new UnlockSoneCommand(core));
-               commands.put("LikePost", new LikePostCommand(core));
-               commands.put("LikeReply", new LikeReplyCommand(core));
-               commands.put("CreatePost", new CreatePostCommand(core));
-               commands.put("CreateReply", new CreateReplyCommand(core));
-               commands.put("DeletePost", new DeletePostCommand(core));
-               commands.put("DeleteReply", new DeleteReplyCommand(core));
+       public FcpInterface(Core core, CommandSupplier commandSupplier) {
+               commands = commandSupplier.supplyCommands(core);
        }
 
        //
        }
 
        //
@@ -232,4 +218,29 @@ public class FcpInterface {
                setFullAccessRequired(fullAccessRequiredChanged.getFullAccessRequired());
        }
 
                setFullAccessRequired(fullAccessRequiredChanged.getFullAccessRequired());
        }
 
+       @Singleton
+       public static class CommandSupplier {
+
+               public Map<String, AbstractSoneCommand> supplyCommands(Core core) {
+                       Map<String, AbstractSoneCommand> commands = new HashMap<>();
+                       commands.put("Version", new VersionCommand(core));
+                       commands.put("GetLocalSones", new GetLocalSonesCommand(core));
+                       commands.put("GetSones", new GetSonesCommand(core));
+                       commands.put("GetSone", new GetSoneCommand(core));
+                       commands.put("GetPost", new GetPostCommand(core));
+                       commands.put("GetPosts", new GetPostsCommand(core));
+                       commands.put("GetPostFeed", new GetPostFeedCommand(core));
+                       commands.put("LockSone", new LockSoneCommand(core));
+                       commands.put("UnlockSone", new UnlockSoneCommand(core));
+                       commands.put("LikePost", new LikePostCommand(core));
+                       commands.put("LikeReply", new LikeReplyCommand(core));
+                       commands.put("CreatePost", new CreatePostCommand(core));
+                       commands.put("CreateReply", new CreateReplyCommand(core));
+                       commands.put("DeletePost", new DeletePostCommand(core));
+                       commands.put("DeleteReply", new DeleteReplyCommand(core));
+                       return commands;
+               }
+
+       }
+
 }
 }
index 9e9df16..9611196 100644 (file)
@@ -2,10 +2,12 @@
 
 package net.pterodactylus.sone.fcp
 
 
 package net.pterodactylus.sone.fcp
 
+import com.google.inject.Guice
 import freenet.pluginmanager.PluginNotFoundException
 import freenet.pluginmanager.PluginReplySender
 import freenet.support.SimpleFieldSet
 import net.pterodactylus.sone.core.Core
 import freenet.pluginmanager.PluginNotFoundException
 import freenet.pluginmanager.PluginReplySender
 import freenet.support.SimpleFieldSet
 import net.pterodactylus.sone.core.Core
+import net.pterodactylus.sone.fcp.FcpInterface.CommandSupplier
 import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired
 import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.ALWAYS
 import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.NO
 import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired
 import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.ALWAYS
 import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.NO
@@ -15,14 +17,18 @@ import net.pterodactylus.sone.fcp.event.FcpInterfaceDeactivatedEvent
 import net.pterodactylus.sone.fcp.event.FullAccessRequiredChanged
 import net.pterodactylus.sone.freenet.fcp.Command.AccessType.FULL_FCP
 import net.pterodactylus.sone.freenet.fcp.Command.AccessType.RESTRICTED_FCP
 import net.pterodactylus.sone.fcp.event.FullAccessRequiredChanged
 import net.pterodactylus.sone.freenet.fcp.Command.AccessType.FULL_FCP
 import net.pterodactylus.sone.freenet.fcp.Command.AccessType.RESTRICTED_FCP
-import net.pterodactylus.sone.main.SonePlugin
+import net.pterodactylus.sone.freenet.fcp.Command.Response
+import net.pterodactylus.sone.test.bindAs
 import net.pterodactylus.sone.test.capture
 import net.pterodactylus.sone.test.mock
 import net.pterodactylus.sone.test.whenever
 import org.hamcrest.MatcherAssert.assertThat
 import net.pterodactylus.sone.test.capture
 import net.pterodactylus.sone.test.mock
 import net.pterodactylus.sone.test.whenever
 import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.containsInAnyOrder
 import org.hamcrest.Matchers.equalTo
 import org.hamcrest.Matchers.equalTo
+import org.hamcrest.Matchers.sameInstance
 import org.junit.Test
 import org.mockito.ArgumentMatchers
 import org.junit.Test
 import org.mockito.ArgumentMatchers
+import org.mockito.Mockito.any
 import org.mockito.Mockito.verify
 
 /**
 import org.mockito.Mockito.verify
 
 /**
@@ -31,12 +37,34 @@ import org.mockito.Mockito.verify
 class FcpInterfaceTest {
 
        private val core = mock<Core>()
 class FcpInterfaceTest {
 
        private val core = mock<Core>()
-       private val fcpInterface = FcpInterface(core)
+       private val workingCommand = mock<AbstractSoneCommand>().apply {
+               whenever(execute(any(), any(), any())).thenReturn(Response("Working", SimpleFieldSet(true).apply {
+                       putSingle("ReallyWorking", "true")
+               }))
+       }
+       private val brokenCommand = mock<AbstractSoneCommand>().apply {
+               whenever(execute(any(), any(), any())).thenThrow(RuntimeException::class.java)
+       }
+       private val commandSupplier = object : CommandSupplier() {
+               override fun supplyCommands(core: Core): Map<String, AbstractSoneCommand> {
+                       return mapOf(
+                                       "Working" to workingCommand,
+                                       "Broken" to brokenCommand
+                       )
+               }
+       }
+       private val fcpInterface = FcpInterface(core, commandSupplier)
        private val pluginReplySender = mock<PluginReplySender>()
        private val parameters = SimpleFieldSet(true)
        private val replyParameters = capture<SimpleFieldSet>()
 
        @Test
        private val pluginReplySender = mock<PluginReplySender>()
        private val parameters = SimpleFieldSet(true)
        private val replyParameters = capture<SimpleFieldSet>()
 
        @Test
+       fun `fcp interface is instantiated as singleton`() {
+               val injector = Guice.createInjector(core.bindAs(Core::class))
+               assertThat(injector.getInstance(FcpInterface::class.java), sameInstance(injector.getInstance(FcpInterface::class.java)))
+       }
+
+       @Test
        fun `fcp interface can be activated`() {
                fcpInterface.fcpInterfaceActivated(FcpInterfaceActivatedEvent())
                assertThat(fcpInterface.isActive, equalTo(true))
        fun `fcp interface can be activated`() {
                fcpInterface.fcpInterfaceActivated(FcpInterfaceActivatedEvent())
                assertThat(fcpInterface.isActive, equalTo(true))
@@ -103,9 +131,9 @@ class FcpInterfaceTest {
        }
 
        @Test
        }
 
        @Test
-       fun `sending version command without identifier results in 400 error code`() {
+       fun `sending working command without identifier results in 400 error code`() {
                fcpInterface.fcpInterfaceActivated(FcpInterfaceActivatedEvent())
                fcpInterface.fcpInterfaceActivated(FcpInterfaceActivatedEvent())
-               parameters.putSingle("Message", "Version")
+               parameters.putSingle("Message", "Working")
                fcpInterface.handle(pluginReplySender, parameters, null, FULL_FCP.ordinal)
                verify(pluginReplySender).send(replyParameters.capture())
                assertThat(replyParameters.value["Message"], equalTo("Error"))
                fcpInterface.handle(pluginReplySender, parameters, null, FULL_FCP.ordinal)
                verify(pluginReplySender).send(replyParameters.capture())
                assertThat(replyParameters.value["Message"], equalTo("Error"))
@@ -113,9 +141,9 @@ class FcpInterfaceTest {
        }
 
        @Test
        }
 
        @Test
-       fun `sending version command with empty identifier results in 400 error code`() {
+       fun `sending working command with empty identifier results in 400 error code`() {
                fcpInterface.fcpInterfaceActivated(FcpInterfaceActivatedEvent())
                fcpInterface.fcpInterfaceActivated(FcpInterfaceActivatedEvent())
-               parameters.putSingle("Message", "Version")
+               parameters.putSingle("Message", "Working")
                parameters.putSingle("Identifier", "")
                fcpInterface.handle(pluginReplySender, parameters, null, FULL_FCP.ordinal)
                verify(pluginReplySender).send(replyParameters.capture())
                parameters.putSingle("Identifier", "")
                fcpInterface.handle(pluginReplySender, parameters, null, FULL_FCP.ordinal)
                verify(pluginReplySender).send(replyParameters.capture())
@@ -124,15 +152,60 @@ class FcpInterfaceTest {
        }
 
        @Test
        }
 
        @Test
-       fun `sending version command with identifier results in version reply`() {
+       fun `sending working command with identifier results in working reply`() {
+               fcpInterface.fcpInterfaceActivated(FcpInterfaceActivatedEvent())
+               parameters.putSingle("Message", "Working")
+               parameters.putSingle("Identifier", "Test")
+               fcpInterface.handle(pluginReplySender, parameters, null, FULL_FCP.ordinal)
+               verify(pluginReplySender).send(replyParameters.capture())
+               assertThat(replyParameters.value["Message"], equalTo("Working"))
+               assertThat(replyParameters.value["ReallyWorking"], equalTo("true"))
+       }
+
+       @Test
+       fun `sending broken  command with identifier results in 500 error reply`() {
                fcpInterface.fcpInterfaceActivated(FcpInterfaceActivatedEvent())
                fcpInterface.fcpInterfaceActivated(FcpInterfaceActivatedEvent())
-               parameters.putSingle("Message", "Version")
+               parameters.putSingle("Message", "Broken")
                parameters.putSingle("Identifier", "Test")
                fcpInterface.handle(pluginReplySender, parameters, null, FULL_FCP.ordinal)
                verify(pluginReplySender).send(replyParameters.capture())
                parameters.putSingle("Identifier", "Test")
                fcpInterface.handle(pluginReplySender, parameters, null, FULL_FCP.ordinal)
                verify(pluginReplySender).send(replyParameters.capture())
-               assertThat(replyParameters.value["Message"], equalTo("Version"))
-               assertThat(replyParameters.value["Version"], equalTo(SonePlugin.getPluginVersion()))
-               assertThat(replyParameters.value["ProtocolVersion"], equalTo("1"))
+               assertThat(replyParameters.value["Message"], equalTo("Error"))
+               assertThat(replyParameters.value["ErrorCode"], equalTo("500"))
+       }
+
+}
+
+class CommandSupplierTest {
+
+       private val core = mock<Core>()
+       private val commandSupplier = CommandSupplier()
+
+       @Test
+       fun `command supplier supplies all commands`() {
+               val commands = commandSupplier.supplyCommands(core)
+               assertThat(commands.keys, containsInAnyOrder(
+                               "CreatePost",
+                               "CreateReply",
+                               "DeletePost",
+                               "DeleteReply",
+                               "GetLocalSones",
+                               "GetPost",
+                               "GetPostFeed",
+                               "GetPosts",
+                               "GetSone",
+                               "GetSones",
+                               "LikePost",
+                               "LikeReply",
+                               "LockSone",
+                               "UnlockSone",
+                               "Version"
+               ))
+       }
+
+       @Test
+       fun `command supplier is instantiated as singleton`() {
+               val injector = Guice.createInjector()
+               assertThat(injector.getInstance(CommandSupplier::class.java), sameInstance(injector.getInstance(CommandSupplier::class.java)))
        }
 
 }
        }
 
 }
diff --git a/src/test/kotlin/net/pterodactylus/sone/test/Guice.kt b/src/test/kotlin/net/pterodactylus/sone/test/Guice.kt
new file mode 100644 (file)
index 0000000..f8feeef
--- /dev/null
@@ -0,0 +1,6 @@
+package net.pterodactylus.sone.test
+
+import com.google.inject.Module
+import kotlin.reflect.KClass
+
+fun <T : Any> T.bindAs(bindClass: KClass<T>) = Module { it.bind(bindClass.java).toInstance(this@bindAs) }