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