Add unit test for FCP interface.
[Sone.git] / src / test / java / net / pterodactylus / sone / fcp / FcpInterfaceTest.java
1 /*
2  * Sone - FcpInterfaceTest.java - Copyright © 2013 David Roden
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 package net.pterodactylus.sone.fcp;
19
20 import static freenet.pluginmanager.FredPluginFCP.ACCESS_DIRECT;
21 import static freenet.pluginmanager.FredPluginFCP.ACCESS_FCP_FULL;
22 import static freenet.pluginmanager.FredPluginFCP.ACCESS_FCP_RESTRICTED;
23 import static net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.NO;
24 import static net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.WRITING;
25 import static org.hamcrest.MatcherAssert.assertThat;
26 import static org.hamcrest.Matchers.hasSize;
27 import static org.hamcrest.Matchers.is;
28 import static org.hamcrest.Matchers.notNullValue;
29 import static org.mockito.Mockito.mock;
30
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.util.List;
34
35 import net.pterodactylus.sone.core.Core;
36 import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder;
37 import net.pterodactylus.sone.freenet.fcp.FcpException;
38
39 import freenet.pluginmanager.PluginNotFoundException;
40 import freenet.pluginmanager.PluginReplySender;
41 import freenet.support.SimpleFieldSet;
42 import freenet.support.api.Bucket;
43 import freenet.support.io.ArrayBucket;
44
45 import com.google.common.collect.Lists;
46 import org.hamcrest.Description;
47 import org.hamcrest.Matcher;
48 import org.hamcrest.TypeSafeMatcher;
49 import org.junit.Test;
50
51 /**
52  * Unit test for {@link FcpInterface}.
53  *
54  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
55  */
56 public class FcpInterfaceTest {
57
58         private final Core core = mock(Core.class);
59         private final FcpInterface fcpInterface = new FcpInterface(core);
60         private final CapturingPluginReplySender pluginReplySender = new CapturingPluginReplySender();
61
62         @Test
63         public void testThatAnInactiveFcpInterfaceReturnsAnErrorForDirectAccess() throws PluginNotFoundException {
64                 SimpleFieldSet fieldSet = new SimpleFieldSetBuilder().get();
65                 fcpInterface.handle(pluginReplySender, fieldSet, null, ACCESS_DIRECT);
66                 verifyErrorWithCode("400");
67         }
68
69         @Test
70         public void testThatAnInactiveFcpInterfaceReturnsAnErrorForRestrictedFcpAccess() throws PluginNotFoundException {
71                 SimpleFieldSet fieldSet = new SimpleFieldSetBuilder().get();
72                 fcpInterface.handle(pluginReplySender, fieldSet, null, ACCESS_FCP_RESTRICTED);
73                 verifyErrorWithCode("400");
74         }
75
76         @Test
77         public void testThatAnInactiveFcpInterfaceReturnsAnErrorForFullFcpAccess() throws PluginNotFoundException {
78                 SimpleFieldSet fieldSet = new SimpleFieldSetBuilder().get();
79                 fcpInterface.handle(pluginReplySender, fieldSet, null, ACCESS_FCP_FULL);
80                 verifyErrorWithCode("400");
81         }
82
83         @Test
84         public void testThatAnActiveFcpInterfaceReturnsAnErrorForAnUnknownMessage() {
85                 fcpInterface.setActive(true);
86                 SimpleFieldSet fieldSet = new SimpleFieldSetBuilder().put("Message", "Foo").get();
87                 fcpInterface.handle(pluginReplySender, fieldSet, null, ACCESS_DIRECT);
88                 verifyError();
89         }
90
91         @Test
92         public void testThatAnActiveFcpInterfaceReturnsAnErrorForAMessageWithoutIdentifier() {
93                 fcpInterface.setActive(true);
94                 fcpInterface.addCommand("ReadOnlyPing", new ReadOnlyPing());
95                 SimpleFieldSet fieldSet = new SimpleFieldSetBuilder().put("Message", "ReadOnlyPing").get();
96                 fcpInterface.handle(pluginReplySender, fieldSet, null, ACCESS_DIRECT);
97                 verifyError();
98         }
99
100         @Test
101         public void testThatAnActiveFcpInterfaceRequiringFullAccessAllowsDirectFcpAccessForReadOnlyCommand() {
102                 fcpInterface.setActive(true);
103                 fcpInterface.addCommand("ReadOnlyPing", new ReadOnlyPing());
104                 SimpleFieldSet fieldSet = new SimpleFieldSetBuilder().put("Message", "ReadOnlyPing").put("Identifier", "foo").get();
105                 fcpInterface.handle(pluginReplySender, fieldSet, null, ACCESS_DIRECT);
106                 verifyReplyWithMessage("ReadOnlyPong");
107         }
108
109         @Test
110         public void testThatAnActiveFcpInterfaceRequiringFullAccessForWritesAllowsDirectFcpAccessForReadOnlyCommand() {
111                 fcpInterface.setActive(true);
112                 fcpInterface.setFullAccessRequired(WRITING);
113                 fcpInterface.addCommand("ReadOnlyPing", new ReadOnlyPing());
114                 SimpleFieldSet fieldSet = new SimpleFieldSetBuilder().put("Message", "ReadOnlyPing").put("Identifier", "foo").get();
115                 fcpInterface.handle(pluginReplySender, fieldSet, null, ACCESS_DIRECT);
116                 verifyReplyWithMessage("ReadOnlyPong");
117         }
118
119         @Test
120         public void testThatAnActiveFcpInterfaceNotRequiringFullAccessAllowsDirectFcpAccessForReadOnlyCommand() {
121                 fcpInterface.setActive(true);
122                 fcpInterface.setFullAccessRequired(NO);
123                 fcpInterface.addCommand("ReadOnlyPing", new ReadOnlyPing());
124                 SimpleFieldSet fieldSet = new SimpleFieldSetBuilder().put("Message", "ReadOnlyPing").put("Identifier", "foo").get();
125                 fcpInterface.handle(pluginReplySender, fieldSet, null, ACCESS_DIRECT);
126                 verifyReplyWithMessage("ReadOnlyPong");
127         }
128
129         @Test
130         public void testThatAnActiveFcpInterfaceRequiringFullAccessAllowsDirectFcpAccessForReadWriteCommand() {
131                 fcpInterface.setActive(true);
132                 fcpInterface.addCommand("ReadWritePing", new ReadWritePing());
133                 SimpleFieldSet fieldSet = new SimpleFieldSetBuilder().put("Message", "ReadWritePing").put("Identifier", "foo").get();
134                 fcpInterface.handle(pluginReplySender, fieldSet, null, ACCESS_DIRECT);
135                 verifyReplyWithMessage("ReadWritePong");
136         }
137
138         @Test
139         public void testThatAnActiveFcpInterfaceRequiringFullAccessForWritesAllowsDirectFcpAccessForReadWriteCommand() {
140                 fcpInterface.setActive(true);
141                 fcpInterface.setFullAccessRequired(WRITING);
142                 fcpInterface.addCommand("ReadWritePing", new ReadWritePing());
143                 SimpleFieldSet fieldSet = new SimpleFieldSetBuilder().put("Message", "ReadWritePing").put("Identifier", "foo").get();
144                 fcpInterface.handle(pluginReplySender, fieldSet, null, ACCESS_DIRECT);
145                 verifyReplyWithMessage("ReadWritePong");
146         }
147
148         @Test
149         public void testThatAnActiveFcpInterfaceNotRequiringFullAccessAllowsDirectFcpAccessForReadWriteCommand() {
150                 fcpInterface.setActive(true);
151                 fcpInterface.setFullAccessRequired(NO);
152                 fcpInterface.addCommand("ReadWritePing", new ReadWritePing());
153                 SimpleFieldSet fieldSet = new SimpleFieldSetBuilder().put("Message", "ReadWritePing").put("Identifier", "foo").get();
154                 fcpInterface.handle(pluginReplySender, fieldSet, null, ACCESS_DIRECT);
155                 verifyReplyWithMessage("ReadWritePong");
156         }
157
158         @Test
159         public void testThatAnActiveFcpInterfaceRequiringFullAccessAllowsFullFcpAccessForReadOnlyCommand() {
160                 fcpInterface.setActive(true);
161                 fcpInterface.addCommand("ReadOnlyPing", new ReadOnlyPing());
162                 SimpleFieldSet fieldSet = new SimpleFieldSetBuilder().put("Message", "ReadOnlyPing").put("Identifier", "foo").get();
163                 fcpInterface.handle(pluginReplySender, fieldSet, null, ACCESS_FCP_FULL);
164                 verifyReplyWithMessage("ReadOnlyPong");
165         }
166
167         @Test
168         public void testThatAnActiveFcpInterfaceRequiringFullAccessForWritesAllowsFullFcpAccessForReadOnlyCommand() {
169                 fcpInterface.setActive(true);
170                 fcpInterface.setFullAccessRequired(WRITING);
171                 fcpInterface.addCommand("ReadOnlyPing", new ReadOnlyPing());
172                 SimpleFieldSet fieldSet = new SimpleFieldSetBuilder().put("Message", "ReadOnlyPing").put("Identifier", "foo").get();
173                 fcpInterface.handle(pluginReplySender, fieldSet, null, ACCESS_FCP_FULL);
174                 verifyReplyWithMessage("ReadOnlyPong");
175         }
176
177         @Test
178         public void testThatAnActiveFcpInterfaceNotRequiringFullAccessAllowsFullFcpAccessForReadOnlyCommand() {
179                 fcpInterface.setActive(true);
180                 fcpInterface.setFullAccessRequired(NO);
181                 fcpInterface.addCommand("ReadOnlyPing", new ReadOnlyPing());
182                 SimpleFieldSet fieldSet = new SimpleFieldSetBuilder().put("Message", "ReadOnlyPing").put("Identifier", "foo").get();
183                 fcpInterface.handle(pluginReplySender, fieldSet, null, ACCESS_FCP_FULL);
184                 verifyReplyWithMessage("ReadOnlyPong");
185         }
186
187         private void verifyReplyWithMessage(String messageName) {
188                 assertThat(pluginReplySender.results, hasSize(1));
189                 assertThat(pluginReplySender.results.get(0).fieldSet, notNullValue());
190                 assertThat(pluginReplySender.results.get(0).fieldSet.get("Message"), is(messageName));
191         }
192
193         @Test
194         public void testThatAnActiveFcpInterfaceRequiringFullAccessAllowsFullFcpAccessForReadWriteCommand() {
195                 fcpInterface.setActive(true);
196                 fcpInterface.addCommand("ReadWritePing", new ReadWritePing());
197                 SimpleFieldSet fieldSet = new SimpleFieldSetBuilder().put("Message", "ReadWritePing").put("Identifier", "foo").get();
198                 fcpInterface.handle(pluginReplySender, fieldSet, null, ACCESS_FCP_FULL);
199                 verifyReplyWithMessage("ReadWritePong");
200         }
201
202         @Test
203         public void testThatAnActiveFcpInterfaceRequiringFullAccessForWritesAllowsFullFcpAccessForReadWriteCommand() {
204                 fcpInterface.setActive(true);
205                 fcpInterface.setFullAccessRequired(WRITING);
206                 fcpInterface.addCommand("ReadWritePing", new ReadWritePing());
207                 SimpleFieldSet fieldSet = new SimpleFieldSetBuilder().put("Message", "ReadWritePing").put("Identifier", "foo").get();
208                 fcpInterface.handle(pluginReplySender, fieldSet, null, ACCESS_FCP_FULL);
209                 verifyReplyWithMessage("ReadWritePong");
210         }
211
212         @Test
213         public void testThatAnActiveFcpInterfaceNotRequiringFullAccessAllowsFullFcpAccessForReadWriteCommand() {
214                 fcpInterface.setActive(true);
215                 fcpInterface.setFullAccessRequired(NO);
216                 fcpInterface.addCommand("ReadWritePing", new ReadWritePing());
217                 SimpleFieldSet fieldSet = new SimpleFieldSetBuilder().put("Message", "ReadWritePing").put("Identifier", "foo").get();
218                 fcpInterface.handle(pluginReplySender, fieldSet, null, ACCESS_FCP_FULL);
219                 verifyReplyWithMessage("ReadWritePong");
220         }
221
222         @Test
223         public void testThatAnActiveFcpInterfaceRequiringFullAccessDoesNotAllowRestrictedFcpAccessForReadOnlyCommand() {
224                 fcpInterface.setActive(true);
225                 fcpInterface.addCommand("ReadOnlyPing", new ReadOnlyPing());
226                 SimpleFieldSet fieldSet = new SimpleFieldSetBuilder().put("Message", "ReadOnlyPing").put("Identifier", "foo").get();
227                 fcpInterface.handle(pluginReplySender, fieldSet, null, ACCESS_FCP_RESTRICTED);
228                 verifyErrorWithCode("401");
229         }
230
231         @Test
232         public void testThatAnActiveFcpInterfaceRequiringFullAccessForWritesAllowsRestrictedFcpAccessForReadOnlyCommand() {
233                 fcpInterface.setActive(true);
234                 fcpInterface.setFullAccessRequired(WRITING);
235                 fcpInterface.addCommand("ReadOnlyPing", new ReadOnlyPing());
236                 SimpleFieldSet fieldSet = new SimpleFieldSetBuilder().put("Message", "ReadOnlyPing").put("Identifier", "foo").get();
237                 fcpInterface.handle(pluginReplySender, fieldSet, null, ACCESS_FCP_RESTRICTED);
238                 verifyReplyWithMessage("ReadOnlyPong");
239         }
240
241         @Test
242         public void testThatAnActiveFcpInterfaceNotRequiringFullAccessAllowsRestrictedFcpAccessForReadOnlyCommand() {
243                 fcpInterface.setActive(true);
244                 fcpInterface.setFullAccessRequired(NO);
245                 fcpInterface.addCommand("ReadOnlyPing", new ReadOnlyPing());
246                 SimpleFieldSet fieldSet = new SimpleFieldSetBuilder().put("Message", "ReadOnlyPing").put("Identifier", "foo").get();
247                 fcpInterface.handle(pluginReplySender, fieldSet, null, ACCESS_FCP_RESTRICTED);
248                 verifyReplyWithMessage("ReadOnlyPong");
249         }
250
251         @Test
252         public void testThatAnActiveFcpInterfaceRequiringFullAccessDoesNotAllowRestrictedFcpAccessForReadWriteCommand() {
253                 fcpInterface.setActive(true);
254                 fcpInterface.addCommand("ReadWritePing", new ReadWritePing());
255                 SimpleFieldSet fieldSet = new SimpleFieldSetBuilder().put("Message", "ReadWritePing").put("Identifier", "foo").get();
256                 fcpInterface.handle(pluginReplySender, fieldSet, null, ACCESS_FCP_RESTRICTED);
257                 verifyErrorWithCode("401");
258         }
259
260         @Test
261         public void testThatAnActiveFcpInterfaceRequiringFullAccessForWritesDoesNotAllowRestrictedFcpAccessForReadWriteCommand() {
262                 fcpInterface.setActive(true);
263                 fcpInterface.setFullAccessRequired(WRITING);
264                 fcpInterface.addCommand("ReadWritePing", new ReadWritePing());
265                 SimpleFieldSet fieldSet = new SimpleFieldSetBuilder().put("Message", "ReadWritePing").put("Identifier", "foo").get();
266                 fcpInterface.handle(pluginReplySender, fieldSet, null, ACCESS_FCP_RESTRICTED);
267                 verifyErrorWithCode("401");
268         }
269
270         @Test
271         public void testThatAnActiveFcpInterfaceNotRequiringFullAccessAllowsRestrictedFcpAccessForReadWriteCommand() {
272                 fcpInterface.setActive(true);
273                 fcpInterface.setFullAccessRequired(NO);
274                 fcpInterface.addCommand("ReadWritePing", new ReadWritePing());
275                 SimpleFieldSet fieldSet = new SimpleFieldSetBuilder().put("Message", "ReadWritePing").put("Identifier", "foo").get();
276                 fcpInterface.handle(pluginReplySender, fieldSet, null, ACCESS_FCP_RESTRICTED);
277                 verifyReplyWithMessage("ReadWritePong");
278         }
279
280         @Test
281         public void testThatAFaultyCommandResultsInAnError() {
282                 fcpInterface.setActive(true);
283                 fcpInterface.addCommand("Faulty", new FaultyCommand());
284                 SimpleFieldSet fieldSet = new SimpleFieldSetBuilder().put("Message", "Faulty").put("Identifier", "foo").get();
285                 fcpInterface.handle(pluginReplySender, fieldSet, null, ACCESS_FCP_FULL);
286                 verifyError();
287         }
288
289         @Test
290         public void testThatAFaultyPluginReplySenderIsHandled() {
291                 fcpInterface.setActive(true);
292                 SimpleFieldSet fieldSet = new SimpleFieldSetBuilder().put("Message", "Faulty").put("Identifier", "foo").get();
293                 fcpInterface.handle(new FaultyPluginReplySender(), fieldSet, null, ACCESS_FCP_FULL);
294         }
295
296         @Test
297         public void testThatACommandWithDataIsHandledCorrectly() throws IOException {
298                 fcpInterface.setActive(true);
299                 fcpInterface.addCommand("CommandWithData", new CommandWithData());
300                 SimpleFieldSet fieldSet = new SimpleFieldSetBuilder().put("Message", "CommandWithData").put("Identifier", "foo").get();
301                 fcpInterface.handle(pluginReplySender, fieldSet, null, ACCESS_FCP_FULL);
302                 verifyReplyWithMessage("ReturnedData");
303                 assertThat(pluginReplySender.results.get(0).bucket, notNullValue());
304                 assertThat(pluginReplySender.results.get(0).bucket.size(), is(3L));
305                 assertThat(pluginReplySender.results.get(0).bucket.getInputStream(), delivers(new byte[] { 1, 2, 3 }));
306         }
307
308         @Test
309         public void testThatACommandWithABucketIsHandledCorrectly() throws IOException {
310                 fcpInterface.setActive(true);
311                 fcpInterface.addCommand("CommandWithBucket", new CommandWithBucket());
312                 SimpleFieldSet fieldSet = new SimpleFieldSetBuilder().put("Message", "CommandWithBucket").put("Identifier", "foo").get();
313                 fcpInterface.handle(pluginReplySender, fieldSet, null, ACCESS_FCP_FULL);
314                 verifyReplyWithMessage("ReturnedBucket");
315                 assertThat(pluginReplySender.results.get(0).bucket, notNullValue());
316                 assertThat(pluginReplySender.results.get(0).bucket.size(), is(3L));
317                 assertThat(pluginReplySender.results.get(0).bucket.getInputStream(), delivers(new byte[] { 4, 5, 6 }));
318         }
319
320         private Matcher<InputStream> delivers(final byte[] data) {
321                 return new TypeSafeMatcher<InputStream>() {
322                         byte[] readData = new byte[data.length];
323
324                         @Override
325                         protected boolean matchesSafely(InputStream inputStream) {
326                                 int offset = 0;
327                                 try {
328                                         while (true) {
329                                                 int r = inputStream.read();
330                                                 if (r == -1) {
331                                                         return offset == data.length;
332                                                 }
333                                                 readData[offset] = (byte) r;
334                                                 if (data[offset++] != r) {
335                                                         return false;
336                                                 }
337                                         }
338                                 } catch (IOException ioe1) {
339                                         return false;
340                                 }
341                         }
342
343                         @Override
344                         public void describeTo(Description description) {
345                                 description.appendValue(data);
346                         }
347
348                         @Override
349                         protected void describeMismatchSafely(InputStream item, Description mismatchDescription) {
350                                 mismatchDescription.appendValue(readData);
351                         }
352                 };
353         }
354
355         private void verifyError() {
356                 assertThat(pluginReplySender.results, hasSize(1));
357                 assertThat(pluginReplySender.results.get(0).fieldSet, notNullValue());
358                 assertThat(pluginReplySender.results.get(0).fieldSet.get("Message"), is("Error"));
359         }
360
361         private void verifyErrorWithCode(String errorCode) {
362                 verifyError();
363                 assertThat(pluginReplySender.results.get(0).fieldSet.get("ErrorCode"), is(errorCode));
364         }
365
366         private static class CapturingPluginReplySender extends PluginReplySender {
367
368                 public final List<PluginReplySenderResult> results = Lists.newArrayList();
369
370                 public CapturingPluginReplySender() {
371                         super(null, null);
372                 }
373
374                 @Override
375                 public void send(SimpleFieldSet params, Bucket bucket) throws PluginNotFoundException {
376                         results.add(new PluginReplySenderResult(params, bucket));
377                 }
378
379         }
380
381         private static class PluginReplySenderResult {
382
383                 public final SimpleFieldSet fieldSet;
384                 public final Bucket bucket;
385
386                 public PluginReplySenderResult(SimpleFieldSet fieldSet, Bucket bucket) {
387                         this.fieldSet = fieldSet;
388                         this.bucket = bucket;
389                 }
390
391         }
392
393         private static class ReadOnlyPing extends AbstractSoneCommand {
394
395                 public ReadOnlyPing() {
396                         super(null, false);
397                 }
398
399                 @Override
400                 public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException {
401                         return new Response("ReadOnlyPong", new SimpleFieldSetBuilder().get());
402                 }
403
404         }
405
406         private static class ReadWritePing extends AbstractSoneCommand {
407
408                 public ReadWritePing() {
409                         super(null, true);
410                 }
411
412                 @Override
413                 public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException {
414                         return new Response("ReadWritePong", new SimpleFieldSetBuilder().get());
415                 }
416
417         }
418
419         private static class FaultyCommand extends AbstractSoneCommand {
420
421                 public FaultyCommand() {
422                         super(null, false);
423                 }
424
425                 @Override
426                 public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException {
427                         throw new RuntimeException("I’m faulty!");
428                 }
429
430         }
431
432         private static class FaultyPluginReplySender extends PluginReplySender {
433
434                 public FaultyPluginReplySender() {
435                         super(null, null);
436                 }
437
438                 @Override
439                 public void send(SimpleFieldSet params, Bucket bucket) throws PluginNotFoundException {
440                         throw new PluginNotFoundException();
441                 }
442
443         }
444
445         private static class CommandWithData extends AbstractSoneCommand {
446
447                 protected CommandWithData() {
448                         super(null);
449                 }
450
451                 @Override
452                 public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException {
453                         return new Response("ReturnedData", new SimpleFieldSetBuilder().get(), new byte[] { 1, 2, 3 });
454                 }
455
456         }
457
458         private static class CommandWithBucket extends AbstractSoneCommand {
459
460                 protected CommandWithBucket() {
461                         super(null);
462                 }
463
464                 @Override
465                 public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException {
466                         return new Response("ReturnedBucket", new SimpleFieldSetBuilder().get(), new ArrayBucket(new byte[] { 4, 5, 6 }));
467                 }
468
469         }
470
471 }