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 400 error reply`() {
105 fcpInterface.fcpInterfaceDeactivated(FcpInterfaceDeactivatedEvent())
106 fcpInterface.handle(pluginReplySender, parameters, null, 0)
107 verify(pluginReplySender).send(replyParameters.capture())
108 assertThat(replyParameters.value["Message"], equalTo("Error"))
109 assertThat(replyParameters.value["ErrorCode"], equalTo("503"))
113 fun `exception while sending reply does not result in exception`() {
114 fcpInterface.fcpInterfaceDeactivated(FcpInterfaceDeactivatedEvent())
115 whenever(pluginReplySender.send(ArgumentMatchers.any())).thenThrow(PluginNotFoundException::class.java)
116 fcpInterface.handle(pluginReplySender, parameters, null, 0)
120 fun `sending command over non-authorized connection results in 401 error reply`() {
121 fcpInterface.fcpInterfaceActivated(FcpInterfaceActivatedEvent())
122 parameters.putSingle("Message", "Working")
123 fcpInterface.handle(pluginReplySender, parameters, null, RESTRICTED_FCP.ordinal)
124 verify(pluginReplySender).send(replyParameters.capture())
125 assertThat(replyParameters.value["Message"], equalTo("Error"))
126 assertThat(replyParameters.value["ErrorCode"], equalTo("401"))
130 fun `sending unknown command results in 404 error reply`() {
131 fcpInterface.fcpInterfaceActivated(FcpInterfaceActivatedEvent())
132 fcpInterface.handle(pluginReplySender, parameters, null, RESTRICTED_FCP.ordinal)
133 verify(pluginReplySender).send(replyParameters.capture())
134 assertThat(replyParameters.value["Message"], equalTo("Error"))
135 assertThat(replyParameters.value["ErrorCode"], equalTo("404"))
139 fun `sending working command without identifier results in 400 error code`() {
140 fcpInterface.fcpInterfaceActivated(FcpInterfaceActivatedEvent())
141 whenever(accessAuthorizer.authorized(any(), any(), anyBoolean())).thenReturn(true)
142 parameters.putSingle("Message", "Working")
143 fcpInterface.handle(pluginReplySender, parameters, null, FULL_FCP.ordinal)
144 verify(pluginReplySender).send(replyParameters.capture())
145 assertThat(replyParameters.value["Message"], equalTo("Error"))
146 assertThat(replyParameters.value["ErrorCode"], equalTo("400"))
150 fun `sending working command with empty identifier results in 400 error code`() {
151 fcpInterface.fcpInterfaceActivated(FcpInterfaceActivatedEvent())
152 whenever(accessAuthorizer.authorized(any(), any(), anyBoolean())).thenReturn(true)
153 parameters.putSingle("Message", "Working")
154 parameters.putSingle("Identifier", "")
155 fcpInterface.handle(pluginReplySender, parameters, null, FULL_FCP.ordinal)
156 verify(pluginReplySender).send(replyParameters.capture())
157 assertThat(replyParameters.value["Message"], equalTo("Error"))
158 assertThat(replyParameters.value["ErrorCode"], equalTo("400"))
162 fun `sending working command with identifier results in working reply`() {
163 fcpInterface.fcpInterfaceActivated(FcpInterfaceActivatedEvent())
164 whenever(accessAuthorizer.authorized(any(), any(), anyBoolean())).thenReturn(true)
165 parameters.putSingle("Message", "Working")
166 parameters.putSingle("Identifier", "Test")
167 fcpInterface.handle(pluginReplySender, parameters, null, FULL_FCP.ordinal)
168 verify(pluginReplySender).send(replyParameters.capture())
169 assertThat(replyParameters.value["Message"], equalTo("Working"))
170 assertThat(replyParameters.value["ReallyWorking"], equalTo("true"))
174 fun `sending broken command with identifier results in 500 error reply`() {
175 fcpInterface.fcpInterfaceActivated(FcpInterfaceActivatedEvent())
176 whenever(accessAuthorizer.authorized(any(), any(), anyBoolean())).thenReturn(true)
177 parameters.putSingle("Message", "Broken")
178 parameters.putSingle("Identifier", "Test")
179 fcpInterface.handle(pluginReplySender, parameters, null, FULL_FCP.ordinal)
180 verify(pluginReplySender).send(replyParameters.capture())
181 assertThat(replyParameters.value["Message"], equalTo("Error"))
182 assertThat(replyParameters.value["ErrorCode"], equalTo("500"))
187 class CommandSupplierTest {
189 private val core = mock<Core>()
190 private val commandSupplier = CommandSupplier()
193 fun `command supplier supplies all commands`() {
194 val commands = commandSupplier.supplyCommands(core)
195 assertThat(commands.keys, containsInAnyOrder(
215 fun `command supplier is instantiated as singleton`() {
216 val injector = Guice.createInjector()
217 assertThat(injector.getInstance(CommandSupplier::class.java), sameInstance(injector.getInstance(CommandSupplier::class.java)))
222 class AccessAuthorizerTest {
224 private val accessAuthorizer = AccessAuthorizer()
227 fun `access authorizer is instantiated as singleton`() {
228 val injector = Guice.createInjector()
229 assertThat(injector.getInstance(AccessAuthorizer::class.java), sameInstance(injector.getInstance(AccessAuthorizer::class.java)))
233 fun `access authorizer makes correct decisions`() {
234 AccessType.values().forEach { accessType ->
235 FullAccessRequired.values().forEach { fullAccessRequired ->
236 listOf(false, true).forEach { commandRequiresWriteAccess ->
237 assertThat("$accessType, $fullAccessRequired, $commandRequiresWriteAccess", accessAuthorizer.authorized(accessType, fullAccessRequired, commandRequiresWriteAccess), equalTo(
238 accessType != RESTRICTED_FCP ||
239 fullAccessRequired == NO ||
240 (fullAccessRequired == WRITING && !commandRequiresWriteAccess)