1 @file:Suppress("DEPRECATION")
3 package net.pterodactylus.sone.fcp
5 import com.google.inject.Guice
6 import freenet.pluginmanager.PluginNotFoundException
7 import freenet.pluginmanager.PluginReplySender
8 import freenet.support.SimpleFieldSet
9 import net.pterodactylus.sone.core.Core
10 import net.pterodactylus.sone.fcp.FcpInterface.AccessAuthorizer
11 import net.pterodactylus.sone.fcp.FcpInterface.CommandSupplier
12 import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired
13 import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.ALWAYS
14 import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.NO
15 import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.WRITING
16 import net.pterodactylus.sone.fcp.event.FcpInterfaceActivatedEvent
17 import net.pterodactylus.sone.fcp.event.FcpInterfaceDeactivatedEvent
18 import net.pterodactylus.sone.fcp.event.FullAccessRequiredChanged
19 import net.pterodactylus.sone.freenet.fcp.Command.AccessType
20 import net.pterodactylus.sone.freenet.fcp.Command.AccessType.FULL_FCP
21 import net.pterodactylus.sone.freenet.fcp.Command.AccessType.RESTRICTED_FCP
22 import net.pterodactylus.sone.freenet.fcp.Command.Response
23 import net.pterodactylus.sone.test.capture
24 import net.pterodactylus.sone.test.isProvidedBy
25 import net.pterodactylus.sone.test.mock
26 import net.pterodactylus.sone.test.whenever
27 import org.hamcrest.MatcherAssert.assertThat
28 import org.hamcrest.Matchers.containsInAnyOrder
29 import org.hamcrest.Matchers.equalTo
30 import org.hamcrest.Matchers.sameInstance
32 import org.mockito.ArgumentMatchers
33 import org.mockito.Mockito.any
34 import org.mockito.Mockito.anyBoolean
35 import org.mockito.Mockito.verify
38 * Unit test for [FcpInterface] and its subclasses.
40 class FcpInterfaceTest {
42 private val core = mock<Core>()
43 private val workingCommand = mock<AbstractSoneCommand>().apply {
44 whenever(execute(any())).thenReturn(Response("Working", SimpleFieldSet(true).apply {
45 putSingle("ReallyWorking", "true")
48 private val brokenCommand = mock<AbstractSoneCommand>().apply {
49 whenever(execute(any())).thenThrow(RuntimeException::class.java)
51 private val commandSupplier = object : CommandSupplier() {
52 override fun supplyCommands(core: Core): Map<String, AbstractSoneCommand> {
54 "Working" to workingCommand,
55 "Broken" to brokenCommand
59 private val accessAuthorizer = mock<AccessAuthorizer>()
60 private val fcpInterface = FcpInterface(core, commandSupplier, accessAuthorizer)
61 private val pluginReplySender = mock<PluginReplySender>()
62 private val parameters = SimpleFieldSet(true)
63 private val replyParameters = capture<SimpleFieldSet>()
66 fun `fcp interface is instantiated as singleton`() {
67 val injector = Guice.createInjector(Core::class.isProvidedBy(core))
68 assertThat(injector.getInstance(FcpInterface::class.java), sameInstance(injector.getInstance(FcpInterface::class.java)))
72 fun `fcp interface can be activated`() {
73 fcpInterface.fcpInterfaceActivated(FcpInterfaceActivatedEvent())
74 assertThat(fcpInterface.isActive, equalTo(true))
78 fun `fcp interface can be deactivated`() {
79 fcpInterface.fcpInterfaceDeactivated(FcpInterfaceDeactivatedEvent())
80 assertThat(fcpInterface.isActive, equalTo(false))
83 private fun setAndVerifyAccessRequired(fullAccessRequired: FullAccessRequired) {
84 fcpInterface.fullAccessRequiredChanged(FullAccessRequiredChanged(fullAccessRequired))
85 assertThat(fcpInterface.fullAccessRequired, equalTo(fullAccessRequired))
89 fun `set full access required can set access to no`() {
90 setAndVerifyAccessRequired(NO)
94 fun `set full access required can set access to writing`() {
95 setAndVerifyAccessRequired(WRITING)
99 fun `set full access required can set access to always`() {
100 setAndVerifyAccessRequired(ALWAYS)
104 fun `sending command to inactive fcp interface results in 503 error reply`() {
105 fcpInterface.fcpInterfaceDeactivated(FcpInterfaceDeactivatedEvent())
106 parameters.putSingle("Identifier", "Test")
107 fcpInterface.handle(pluginReplySender, parameters, null, 0)
108 verify(pluginReplySender).send(replyParameters.capture())
109 assertThat(replyParameters.value["Message"], equalTo("Error"))
110 assertThat(replyParameters.value["ErrorCode"], equalTo("503"))
114 fun `exception while sending reply does not result in exception`() {
115 fcpInterface.fcpInterfaceDeactivated(FcpInterfaceDeactivatedEvent())
116 whenever(pluginReplySender.send(ArgumentMatchers.any())).thenThrow(PluginNotFoundException::class.java)
117 fcpInterface.handle(pluginReplySender, parameters, null, 0)
121 fun `sending command over non-authorized connection results in 401 error reply`() {
122 fcpInterface.fcpInterfaceActivated(FcpInterfaceActivatedEvent())
123 parameters.putSingle("Identifier", "Test")
124 parameters.putSingle("Message", "Working")
125 fcpInterface.handle(pluginReplySender, parameters, null, RESTRICTED_FCP.ordinal)
126 verify(pluginReplySender).send(replyParameters.capture())
127 assertThat(replyParameters.value["Message"], equalTo("Error"))
128 assertThat(replyParameters.value["ErrorCode"], equalTo("401"))
132 fun `sending unknown command results in 404 error reply`() {
133 fcpInterface.fcpInterfaceActivated(FcpInterfaceActivatedEvent())
134 parameters.putSingle("Identifier", "Test")
135 fcpInterface.handle(pluginReplySender, parameters, null, RESTRICTED_FCP.ordinal)
136 verify(pluginReplySender).send(replyParameters.capture())
137 assertThat(replyParameters.value["Message"], equalTo("Error"))
138 assertThat(replyParameters.value["ErrorCode"], equalTo("404"))
142 fun `sending working command without identifier results in 400 error code`() {
143 fcpInterface.fcpInterfaceActivated(FcpInterfaceActivatedEvent())
144 whenever(accessAuthorizer.authorized(any(), any(), anyBoolean())).thenReturn(true)
145 parameters.putSingle("Message", "Working")
146 fcpInterface.handle(pluginReplySender, parameters, null, FULL_FCP.ordinal)
147 verify(pluginReplySender).send(replyParameters.capture())
148 assertThat(replyParameters.value["Message"], equalTo("Error"))
149 assertThat(replyParameters.value["ErrorCode"], equalTo("400"))
153 fun `sending working command with empty identifier results in 400 error code`() {
154 fcpInterface.fcpInterfaceActivated(FcpInterfaceActivatedEvent())
155 whenever(accessAuthorizer.authorized(any(), any(), anyBoolean())).thenReturn(true)
156 parameters.putSingle("Message", "Working")
157 parameters.putSingle("Identifier", "")
158 fcpInterface.handle(pluginReplySender, parameters, null, FULL_FCP.ordinal)
159 verify(pluginReplySender).send(replyParameters.capture())
160 assertThat(replyParameters.value["Message"], equalTo("Error"))
161 assertThat(replyParameters.value["ErrorCode"], equalTo("400"))
165 fun `sending working command with identifier results in working reply`() {
166 fcpInterface.fcpInterfaceActivated(FcpInterfaceActivatedEvent())
167 whenever(accessAuthorizer.authorized(any(), any(), anyBoolean())).thenReturn(true)
168 parameters.putSingle("Message", "Working")
169 parameters.putSingle("Identifier", "Test")
170 fcpInterface.handle(pluginReplySender, parameters, null, FULL_FCP.ordinal)
171 verify(pluginReplySender).send(replyParameters.capture())
172 assertThat(replyParameters.value["Message"], equalTo("Working"))
173 assertThat(replyParameters.value["ReallyWorking"], equalTo("true"))
177 fun `sending broken command with identifier results in 500 error reply`() {
178 fcpInterface.fcpInterfaceActivated(FcpInterfaceActivatedEvent())
179 whenever(accessAuthorizer.authorized(any(), any(), anyBoolean())).thenReturn(true)
180 parameters.putSingle("Message", "Broken")
181 parameters.putSingle("Identifier", "Test")
182 fcpInterface.handle(pluginReplySender, parameters, null, FULL_FCP.ordinal)
183 verify(pluginReplySender).send(replyParameters.capture())
184 assertThat(replyParameters.value["Message"], equalTo("Error"))
185 assertThat(replyParameters.value["ErrorCode"], equalTo("500"))
190 class CommandSupplierTest {
192 private val core = mock<Core>()
193 private val commandSupplier = CommandSupplier()
196 fun `command supplier supplies all commands`() {
197 val commands = commandSupplier.supplyCommands(core)
198 assertThat(commands.keys, containsInAnyOrder(
218 fun `command supplier is instantiated as singleton`() {
219 val injector = Guice.createInjector()
220 assertThat(injector.getInstance(CommandSupplier::class.java), sameInstance(injector.getInstance(CommandSupplier::class.java)))
225 class AccessAuthorizerTest {
227 private val accessAuthorizer = AccessAuthorizer()
230 fun `access authorizer is instantiated as singleton`() {
231 val injector = Guice.createInjector()
232 assertThat(injector.getInstance(AccessAuthorizer::class.java), sameInstance(injector.getInstance(AccessAuthorizer::class.java)))
236 fun `access authorizer makes correct decisions`() {
237 AccessType.values().forEach { accessType ->
238 FullAccessRequired.values().forEach { fullAccessRequired ->
239 listOf(false, true).forEach { commandRequiresWriteAccess ->
240 assertThat("$accessType, $fullAccessRequired, $commandRequiresWriteAccess", accessAuthorizer.authorized(accessType, fullAccessRequired, commandRequiresWriteAccess), equalTo(
241 accessType != RESTRICTED_FCP ||
242 fullAccessRequired == NO ||
243 (fullAccessRequired == WRITING && !commandRequiresWriteAccess)