🔀 Merge branch 'release/v82'
[Sone.git] / src / test / kotlin / net / pterodactylus / sone / fcp / FcpInterfaceTest.kt
1 @file:Suppress("DEPRECATION")
2
3 package net.pterodactylus.sone.fcp
4
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
31 import org.junit.Test
32 import org.mockito.ArgumentMatchers
33 import org.mockito.Mockito.any
34 import org.mockito.Mockito.anyBoolean
35 import org.mockito.Mockito.verify
36
37 /**
38  * Unit test for [FcpInterface] and its subclasses.
39  */
40 class FcpInterfaceTest {
41
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")
46                 }))
47         }
48         private val brokenCommand = mock<AbstractSoneCommand>().apply {
49                 whenever(execute(any())).thenThrow(RuntimeException::class.java)
50         }
51         private val commandSupplier = object : CommandSupplier() {
52                 override fun supplyCommands(core: Core): Map<String, AbstractSoneCommand> {
53                         return mapOf(
54                                         "Working" to workingCommand,
55                                         "Broken" to brokenCommand
56                         )
57                 }
58         }
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>()
64
65         @Test
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)))
69         }
70
71         @Test
72         fun `fcp interface can be activated`() {
73                 fcpInterface.fcpInterfaceActivated(FcpInterfaceActivatedEvent())
74                 assertThat(fcpInterface.isActive, equalTo(true))
75         }
76
77         @Test
78         fun `fcp interface can be deactivated`() {
79                 fcpInterface.fcpInterfaceDeactivated(FcpInterfaceDeactivatedEvent())
80                 assertThat(fcpInterface.isActive, equalTo(false))
81         }
82
83         private fun setAndVerifyAccessRequired(fullAccessRequired: FullAccessRequired) {
84                 fcpInterface.fullAccessRequiredChanged(FullAccessRequiredChanged(fullAccessRequired))
85                 assertThat(fcpInterface.fullAccessRequired, equalTo(fullAccessRequired))
86         }
87
88         @Test
89         fun `set full access required can set access to no`() {
90                 setAndVerifyAccessRequired(NO)
91         }
92
93         @Test
94         fun `set full access required can set access to writing`() {
95                 setAndVerifyAccessRequired(WRITING)
96         }
97
98         @Test
99         fun `set full access required can set access to always`() {
100                 setAndVerifyAccessRequired(ALWAYS)
101         }
102
103         @Test
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"))
111         }
112
113         @Test
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)
118         }
119
120         @Test
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"))
129         }
130
131         @Test
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"))
139         }
140
141         @Test
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"))
150         }
151
152         @Test
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"))
162         }
163
164         @Test
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"))
174         }
175
176         @Test
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"))
186         }
187
188 }
189
190 class CommandSupplierTest {
191
192         private val core = mock<Core>()
193         private val commandSupplier = CommandSupplier()
194
195         @Test
196         fun `command supplier supplies all commands`() {
197                 val commands = commandSupplier.supplyCommands(core)
198                 assertThat(commands.keys, containsInAnyOrder(
199                                 "CreatePost",
200                                 "CreateReply",
201                                 "DeletePost",
202                                 "DeleteReply",
203                                 "GetLocalSones",
204                                 "GetPost",
205                                 "GetPostFeed",
206                                 "GetPosts",
207                                 "GetSone",
208                                 "GetSones",
209                                 "LikePost",
210                                 "LikeReply",
211                                 "LockSone",
212                                 "UnlockSone",
213                                 "Version"
214                 ))
215         }
216
217         @Test
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)))
221         }
222
223 }
224
225 class AccessAuthorizerTest {
226
227         private val accessAuthorizer = AccessAuthorizer()
228
229         @Test
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)))
233         }
234
235         @Test
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)
244                                         ))
245                                 }
246                         }
247                 }
248         }
249
250 }