Use more concise SAM implementation
[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 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"))
110         }
111
112         @Test
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)
117         }
118
119         @Test
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"))
127         }
128
129         @Test
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"))
136         }
137
138         @Test
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"))
147         }
148
149         @Test
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"))
159         }
160
161         @Test
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"))
171         }
172
173         @Test
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"))
183         }
184
185 }
186
187 class CommandSupplierTest {
188
189         private val core = mock<Core>()
190         private val commandSupplier = CommandSupplier()
191
192         @Test
193         fun `command supplier supplies all commands`() {
194                 val commands = commandSupplier.supplyCommands(core)
195                 assertThat(commands.keys, containsInAnyOrder(
196                                 "CreatePost",
197                                 "CreateReply",
198                                 "DeletePost",
199                                 "DeleteReply",
200                                 "GetLocalSones",
201                                 "GetPost",
202                                 "GetPostFeed",
203                                 "GetPosts",
204                                 "GetSone",
205                                 "GetSones",
206                                 "LikePost",
207                                 "LikeReply",
208                                 "LockSone",
209                                 "UnlockSone",
210                                 "Version"
211                 ))
212         }
213
214         @Test
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)))
218         }
219
220 }
221
222 class AccessAuthorizerTest {
223
224         private val accessAuthorizer = AccessAuthorizer()
225
226         @Test
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)))
230         }
231
232         @Test
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)
241                                         ))
242                                 }
243                         }
244                 }
245         }
246
247 }