Add method to add peer from a URL
[jFCPlib.git] / src / test / java / net / pterodactylus / fcp / quelaton / DefaultFcpClientTest.java
1 package net.pterodactylus.fcp.quelaton;
2
3 import static org.hamcrest.MatcherAssert.assertThat;
4 import static org.hamcrest.Matchers.containsInAnyOrder;
5 import static org.hamcrest.Matchers.hasSize;
6 import static org.hamcrest.Matchers.is;
7 import static org.hamcrest.Matchers.notNullValue;
8
9 import java.io.ByteArrayInputStream;
10 import java.io.File;
11 import java.io.IOException;
12 import java.net.URL;
13 import java.nio.charset.StandardCharsets;
14 import java.util.Collection;
15 import java.util.List;
16 import java.util.Optional;
17 import java.util.concurrent.ExecutionException;
18 import java.util.concurrent.ExecutorService;
19 import java.util.concurrent.Executors;
20 import java.util.concurrent.Future;
21 import java.util.stream.Collectors;
22
23 import net.pterodactylus.fcp.FcpKeyPair;
24 import net.pterodactylus.fcp.Key;
25 import net.pterodactylus.fcp.NodeData;
26 import net.pterodactylus.fcp.Peer;
27 import net.pterodactylus.fcp.Priority;
28 import net.pterodactylus.fcp.fake.FakeTcpServer;
29 import net.pterodactylus.fcp.quelaton.ClientGetCommand.Data;
30
31 import com.google.common.io.ByteStreams;
32 import com.google.common.io.Files;
33 import org.hamcrest.Description;
34 import org.hamcrest.Matcher;
35 import org.hamcrest.TypeSafeDiagnosingMatcher;
36 import org.junit.After;
37 import org.junit.Assert;
38 import org.junit.Test;
39
40 /**
41  * Unit test for {@link DefaultFcpClient}.
42  *
43  * @author <a href="bombe@freenetproject.org">David ‘Bombe’ Roden</a>
44  */
45 public class DefaultFcpClientTest {
46
47         private static final String INSERT_URI =
48                 "SSK@RVCHbJdkkyTCeNN9AYukEg76eyqmiosSaNKgE3U9zUw,7SHH53gletBVb9JD7nBsyClbLQsBubDPEIcwg908r7Y,AQECAAE/";
49         private static final String REQUEST_URI =
50                 "SSK@wtbgd2loNcJCXvtQVOftl2tuWBomDQHfqS6ytpPRhfw,7SHH53gletBVb9JD7nBsyClbLQsBubDPEIcwg908r7Y,AQACAAE/";
51
52         private static int threadCounter = 0;
53         private final ExecutorService threadPool =
54                 Executors.newCachedThreadPool(r -> new Thread(r, "Test-Thread-" + threadCounter++));
55         private final FakeTcpServer fcpServer;
56         private final DefaultFcpClient fcpClient;
57
58         public DefaultFcpClientTest() throws IOException {
59                 fcpServer = new FakeTcpServer(threadPool);
60                 fcpClient = new DefaultFcpClient(threadPool, "localhost", fcpServer.getPort(), () -> "Test");
61         }
62
63         @After
64         public void tearDown() throws IOException {
65                 fcpServer.close();
66         }
67
68         @Test(expected = ExecutionException.class)
69         public void defaultFcpClientThrowsExceptionIfItCanNotConnect()
70         throws IOException, ExecutionException, InterruptedException {
71                 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
72                 fcpServer.connect().get();
73                 fcpServer.collectUntil(is("EndMessage"));
74                 fcpServer.writeLine(
75                         "CloseConnectionDuplicateClientName",
76                         "EndMessage"
77                 );
78                 keyPairFuture.get();
79         }
80
81         @Test(expected = ExecutionException.class)
82         public void defaultFcpClientThrowsExceptionIfConnectionIsClosed()
83         throws IOException, ExecutionException, InterruptedException {
84                 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
85                 fcpServer.connect().get();
86                 fcpServer.collectUntil(is("EndMessage"));
87                 fcpServer.close();
88                 keyPairFuture.get();
89         }
90
91         @Test
92         public void defaultFcpClientCanGenerateKeypair() throws ExecutionException, InterruptedException, IOException {
93                 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
94                 connectNode();
95                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
96                 String identifier = extractIdentifier(lines);
97                 fcpServer.writeLine("SSKKeypair",
98                         "InsertURI=" + INSERT_URI + "",
99                         "RequestURI=" + REQUEST_URI + "",
100                         "Identifier=" + identifier,
101                         "EndMessage");
102                 FcpKeyPair keyPair = keyPairFuture.get();
103                 assertThat(keyPair.getPublicKey(), is(REQUEST_URI));
104                 assertThat(keyPair.getPrivateKey(), is(INSERT_URI));
105         }
106
107         private void connectNode() throws InterruptedException, ExecutionException, IOException {
108                 fcpServer.connect().get();
109                 fcpServer.collectUntil(is("EndMessage"));
110                 fcpServer.writeLine("NodeHello",
111                         "CompressionCodecs=4 - GZIP(0), BZIP2(1), LZMA(2), LZMA_NEW(3)",
112                         "Revision=build01466",
113                         "Testnet=false",
114                         "Version=Fred,0.7,1.0,1466",
115                         "Build=1466",
116                         "ConnectionIdentifier=14318898267048452a81b36e7f13a3f0",
117                         "Node=Fred",
118                         "ExtBuild=29",
119                         "FCPVersion=2.0",
120                         "NodeLanguage=ENGLISH",
121                         "ExtRevision=v29",
122                         "EndMessage"
123                 );
124         }
125
126         @Test
127         public void clientGetCanDownloadData() throws InterruptedException, ExecutionException, IOException {
128                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
129                 connectNode();
130                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
131                 assertThat(lines, matchesFcpMessage("ClientGet", "ReturnType=direct", "URI=KSK@foo.txt"));
132                 String identifier = extractIdentifier(lines);
133                 fcpServer.writeLine(
134                         "AllData",
135                         "Identifier=" + identifier,
136                         "DataLength=6",
137                         "StartupTime=1435610539000",
138                         "CompletionTime=1435610540000",
139                         "Metadata.ContentType=text/plain;charset=utf-8",
140                         "Data",
141                         "Hello"
142                 );
143                 Optional<Data> data = dataFuture.get();
144                 assertThat(data.get().getMimeType(), is("text/plain;charset=utf-8"));
145                 assertThat(data.get().size(), is(6L));
146                 assertThat(ByteStreams.toByteArray(data.get().getInputStream()),
147                         is("Hello\n".getBytes(StandardCharsets.UTF_8)));
148         }
149
150         private String extractIdentifier(List<String> lines) {
151                 return lines.stream()
152                         .filter(s -> s.startsWith("Identifier="))
153                         .map(s -> s.substring(s.indexOf('=') + 1))
154                         .findFirst()
155                         .orElse("");
156         }
157
158         @Test
159         public void clientGetDownloadsDataForCorrectIdentifier()
160         throws InterruptedException, ExecutionException, IOException {
161                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
162                 connectNode();
163                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
164                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
165                 String identifier = extractIdentifier(lines);
166                 fcpServer.writeLine(
167                         "AllData",
168                         "Identifier=not-test",
169                         "DataLength=12",
170                         "StartupTime=1435610539000",
171                         "CompletionTime=1435610540000",
172                         "Metadata.ContentType=text/plain;charset=latin-9",
173                         "Data",
174                         "Hello World"
175                 );
176                 fcpServer.writeLine(
177                         "AllData",
178                         "Identifier=" + identifier,
179                         "DataLength=6",
180                         "StartupTime=1435610539000",
181                         "CompletionTime=1435610540000",
182                         "Metadata.ContentType=text/plain;charset=utf-8",
183                         "Data",
184                         "Hello"
185                 );
186                 Optional<Data> data = dataFuture.get();
187                 assertThat(data.get().getMimeType(), is("text/plain;charset=utf-8"));
188                 assertThat(data.get().size(), is(6L));
189                 assertThat(ByteStreams.toByteArray(data.get().getInputStream()),
190                         is("Hello\n".getBytes(StandardCharsets.UTF_8)));
191         }
192
193         @Test
194         public void clientGetRecognizesGetFailed() throws InterruptedException, ExecutionException, IOException {
195                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
196                 connectNode();
197                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
198                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
199                 String identifier = extractIdentifier(lines);
200                 fcpServer.writeLine(
201                         "GetFailed",
202                         "Identifier=" + identifier,
203                         "Code=3",
204                         "EndMessage"
205                 );
206                 Optional<Data> data = dataFuture.get();
207                 assertThat(data.isPresent(), is(false));
208         }
209
210         @Test
211         public void clientGetRecognizesGetFailedForCorrectIdentifier()
212         throws InterruptedException, ExecutionException, IOException {
213                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
214                 connectNode();
215                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
216                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
217                 String identifier = extractIdentifier(lines);
218                 fcpServer.writeLine(
219                         "GetFailed",
220                         "Identifier=not-test",
221                         "Code=3",
222                         "EndMessage"
223                 );
224                 fcpServer.writeLine(
225                         "GetFailed",
226                         "Identifier=" + identifier,
227                         "Code=3",
228                         "EndMessage"
229                 );
230                 Optional<Data> data = dataFuture.get();
231                 assertThat(data.isPresent(), is(false));
232         }
233
234         @Test(expected = ExecutionException.class)
235         public void clientGetRecognizesConnectionClosed() throws InterruptedException, ExecutionException, IOException {
236                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt").execute();
237                 connectNode();
238                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
239                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
240                 fcpServer.close();
241                 dataFuture.get();
242         }
243
244         @Test
245         public void defaultFcpClientReusesConnection() throws InterruptedException, ExecutionException, IOException {
246                 Future<FcpKeyPair> keyPair = fcpClient.generateKeypair().execute();
247                 connectNode();
248                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
249                 String identifier = extractIdentifier(lines);
250                 fcpServer.writeLine(
251                         "SSKKeypair",
252                         "InsertURI=" + INSERT_URI + "",
253                         "RequestURI=" + REQUEST_URI + "",
254                         "Identifier=" + identifier,
255                         "EndMessage"
256                 );
257                 keyPair.get();
258                 keyPair = fcpClient.generateKeypair().execute();
259                 lines = fcpServer.collectUntil(is("EndMessage"));
260                 identifier = extractIdentifier(lines);
261                 fcpServer.writeLine(
262                         "SSKKeypair",
263                         "InsertURI=" + INSERT_URI + "",
264                         "RequestURI=" + REQUEST_URI + "",
265                         "Identifier=" + identifier,
266                         "EndMessage"
267                 );
268                 keyPair.get();
269         }
270
271         @Test
272         public void defaultFcpClientCanReconnectAfterConnectionHasBeenClosed()
273         throws InterruptedException, ExecutionException, IOException {
274                 Future<FcpKeyPair> keyPair = fcpClient.generateKeypair().execute();
275                 connectNode();
276                 fcpServer.collectUntil(is("EndMessage"));
277                 fcpServer.close();
278                 try {
279                         keyPair.get();
280                         Assert.fail();
281                 } catch (ExecutionException e) {
282                 }
283                 keyPair = fcpClient.generateKeypair().execute();
284                 connectNode();
285                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
286                 String identifier = extractIdentifier(lines);
287                 fcpServer.writeLine(
288                         "SSKKeypair",
289                         "InsertURI=" + INSERT_URI + "",
290                         "RequestURI=" + REQUEST_URI + "",
291                         "Identifier=" + identifier,
292                         "EndMessage"
293                 );
294                 keyPair.get();
295         }
296
297         @Test
298         public void clientGetWithIgnoreDataStoreSettingSendsCorrectCommands()
299         throws InterruptedException, ExecutionException, IOException {
300                 fcpClient.clientGet().ignoreDataStore().uri("KSK@foo.txt").execute();
301                 connectNode();
302                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
303                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "IgnoreDS=true"));
304         }
305
306         @Test
307         public void clientGetWithDataStoreOnlySettingSendsCorrectCommands()
308         throws InterruptedException, ExecutionException, IOException {
309                 fcpClient.clientGet().dataStoreOnly().uri("KSK@foo.txt").execute();
310                 connectNode();
311                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
312                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "DSonly=true"));
313         }
314
315         @Test
316         public void clientGetWithMaxSizeSettingSendsCorrectCommands()
317         throws InterruptedException, ExecutionException, IOException {
318                 fcpClient.clientGet().maxSize(1048576).uri("KSK@foo.txt").execute();
319                 connectNode();
320                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
321                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "MaxSize=1048576"));
322         }
323
324         @Test
325         public void clientGetWithPrioritySettingSendsCorrectCommands()
326         throws InterruptedException, ExecutionException, IOException {
327                 fcpClient.clientGet().priority(Priority.interactive).uri("KSK@foo.txt").execute();
328                 connectNode();
329                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
330                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "PriorityClass=1"));
331         }
332
333         @Test
334         public void clientGetWithRealTimeSettingSendsCorrectCommands()
335         throws InterruptedException, ExecutionException, IOException {
336                 fcpClient.clientGet().realTime().uri("KSK@foo.txt").execute();
337                 connectNode();
338                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
339                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "RealTimeFlag=true"));
340         }
341
342         @Test
343         public void clientGetWithGlobalSettingSendsCorrectCommands()
344         throws InterruptedException, ExecutionException, IOException {
345                 fcpClient.clientGet().global().uri("KSK@foo.txt").execute();
346                 connectNode();
347                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
348                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "Global=true"));
349         }
350
351         private Matcher<List<String>> matchesFcpMessage(String name, String... requiredLines) {
352                 return new TypeSafeDiagnosingMatcher<List<String>>() {
353                         @Override
354                         protected boolean matchesSafely(List<String> item, Description mismatchDescription) {
355                                 if (!item.get(0).equals(name)) {
356                                         mismatchDescription.appendText("FCP message is named ").appendValue(item.get(0));
357                                         return false;
358                                 }
359                                 for (String requiredLine : requiredLines) {
360                                         if (item.indexOf(requiredLine) < 1) {
361                                                 mismatchDescription.appendText("FCP message does not contain ").appendValue(requiredLine);
362                                                 return false;
363                                         }
364                                 }
365                                 return true;
366                         }
367
368                         @Override
369                         public void describeTo(Description description) {
370                                 description.appendText("FCP message named ").appendValue(name);
371                                 description.appendValueList(", containing the lines ", ", ", "", requiredLines);
372                         }
373                 };
374         }
375
376         @Test
377         public void clientPutWithDirectDataSendsCorrectCommand()
378         throws IOException, ExecutionException, InterruptedException {
379                 fcpClient.clientPut()
380                         .from(new ByteArrayInputStream("Hello\n".getBytes()))
381                         .length(6)
382                         .uri("KSK@foo.txt")
383                         .execute();
384                 connectNode();
385                 List<String> lines = fcpServer.collectUntil(is("Hello"));
386                 assertThat(lines, matchesFcpMessage("ClientPut", "UploadFrom=direct", "DataLength=6", "URI=KSK@foo.txt"));
387         }
388
389         @Test
390         public void clientPutWithDirectDataSucceedsOnCorrectIdentifier()
391         throws InterruptedException, ExecutionException, IOException {
392                 Future<Optional<Key>> key = fcpClient.clientPut()
393                         .from(new ByteArrayInputStream("Hello\n".getBytes()))
394                         .length(6)
395                         .uri("KSK@foo.txt")
396                         .execute();
397                 connectNode();
398                 List<String> lines = fcpServer.collectUntil(is("Hello"));
399                 String identifier = extractIdentifier(lines);
400                 fcpServer.writeLine(
401                         "PutFailed",
402                         "Identifier=not-the-right-one",
403                         "EndMessage"
404                 );
405                 fcpServer.writeLine(
406                         "PutSuccessful",
407                         "URI=KSK@foo.txt",
408                         "Identifier=" + identifier,
409                         "EndMessage"
410                 );
411                 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
412         }
413
414         @Test
415         public void clientPutWithDirectDataFailsOnCorrectIdentifier()
416         throws InterruptedException, ExecutionException, IOException {
417                 Future<Optional<Key>> key = fcpClient.clientPut()
418                         .from(new ByteArrayInputStream("Hello\n".getBytes()))
419                         .length(6)
420                         .uri("KSK@foo.txt")
421                         .execute();
422                 connectNode();
423                 List<String> lines = fcpServer.collectUntil(is("Hello"));
424                 String identifier = extractIdentifier(lines);
425                 fcpServer.writeLine(
426                         "PutSuccessful",
427                         "Identifier=not-the-right-one",
428                         "URI=KSK@foo.txt",
429                         "EndMessage"
430                 );
431                 fcpServer.writeLine(
432                         "PutFailed",
433                         "Identifier=" + identifier,
434                         "EndMessage"
435                 );
436                 assertThat(key.get().isPresent(), is(false));
437         }
438
439         @Test
440         public void clientPutWithRenamedDirectDataSendsCorrectCommand()
441         throws InterruptedException, ExecutionException, IOException {
442                 fcpClient.clientPut()
443                         .named("otherName.txt")
444                         .from(new ByteArrayInputStream("Hello\n".getBytes()))
445                         .length(6)
446                         .uri("KSK@foo.txt")
447                         .execute();
448                 connectNode();
449                 List<String> lines = fcpServer.collectUntil(is("Hello"));
450                 assertThat(lines, matchesFcpMessage("ClientPut", "TargetFilename=otherName.txt", "UploadFrom=direct",
451                         "DataLength=6", "URI=KSK@foo.txt"));
452         }
453
454         @Test
455         public void clientPutWithRedirectSendsCorrectCommand()
456         throws IOException, ExecutionException, InterruptedException {
457                 fcpClient.clientPut().redirectTo("KSK@bar.txt").uri("KSK@foo.txt").execute();
458                 connectNode();
459                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
460                 assertThat(lines,
461                         matchesFcpMessage("ClientPut", "UploadFrom=redirect", "URI=KSK@foo.txt", "TargetURI=KSK@bar.txt"));
462         }
463
464         @Test
465         public void clientPutWithFileSendsCorrectCommand() throws InterruptedException, ExecutionException, IOException {
466                 fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt").execute();
467                 connectNode();
468                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
469                 assertThat(lines,
470                         matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt", "Filename=/tmp/data.txt"));
471         }
472
473         @Test
474         public void clientPutWithFileCanCompleteTestDdaSequence()
475         throws IOException, ExecutionException, InterruptedException {
476                 File tempFile = createTempFile();
477                 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
478                 connectNode();
479                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
480                 String identifier = extractIdentifier(lines);
481                 fcpServer.writeLine(
482                         "ProtocolError",
483                         "Identifier=" + identifier,
484                         "Code=25",
485                         "EndMessage"
486                 );
487                 lines = fcpServer.collectUntil(is("EndMessage"));
488                 assertThat(lines, matchesFcpMessage(
489                         "TestDDARequest",
490                         "Directory=" + tempFile.getParent(),
491                         "WantReadDirectory=true",
492                         "WantWriteDirectory=false",
493                         "EndMessage"
494                 ));
495                 fcpServer.writeLine(
496                         "TestDDAReply",
497                         "Directory=" + tempFile.getParent(),
498                         "ReadFilename=" + tempFile,
499                         "EndMessage"
500                 );
501                 lines = fcpServer.collectUntil(is("EndMessage"));
502                 assertThat(lines, matchesFcpMessage(
503                         "TestDDAResponse",
504                         "Directory=" + tempFile.getParent(),
505                         "ReadContent=test-content",
506                         "EndMessage"
507                 ));
508                 fcpServer.writeLine(
509                         "TestDDAComplete",
510                         "Directory=" + tempFile.getParent(),
511                         "ReadDirectoryAllowed=true",
512                         "EndMessage"
513                 );
514                 lines = fcpServer.collectUntil(is("EndMessage"));
515                 assertThat(lines,
516                         matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt",
517                                 "Filename=" + new File(tempFile.getParent(), "test.dat")));
518         }
519
520         private File createTempFile() throws IOException {
521                 File tempFile = File.createTempFile("test-dda-", ".dat");
522                 tempFile.deleteOnExit();
523                 Files.write("test-content", tempFile, StandardCharsets.UTF_8);
524                 return tempFile;
525         }
526
527         @Test
528         public void clientPutDoesNotReactToProtocolErrorForDifferentIdentifier()
529         throws InterruptedException, ExecutionException, IOException {
530                 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt").execute();
531                 connectNode();
532                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
533                 String identifier = extractIdentifier(lines);
534                 fcpServer.writeLine(
535                         "ProtocolError",
536                         "Identifier=not-the-right-one",
537                         "Code=25",
538                         "EndMessage"
539                 );
540                 fcpServer.writeLine(
541                         "PutSuccessful",
542                         "Identifier=" + identifier,
543                         "URI=KSK@foo.txt",
544                         "EndMessage"
545                 );
546                 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
547         }
548
549         @Test
550         public void clientPutAbortsOnProtocolErrorOtherThan25()
551         throws InterruptedException, ExecutionException, IOException {
552                 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt").execute();
553                 connectNode();
554                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
555                 String identifier = extractIdentifier(lines);
556                 fcpServer.writeLine(
557                         "ProtocolError",
558                         "Identifier=" + identifier,
559                         "Code=1",
560                         "EndMessage"
561                 );
562                 assertThat(key.get().isPresent(), is(false));
563         }
564
565         @Test
566         public void clientPutDoesNotReplyToWrongTestDdaReply() throws IOException, ExecutionException,
567         InterruptedException {
568                 File tempFile = createTempFile();
569                 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
570                 connectNode();
571                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
572                 String identifier = extractIdentifier(lines);
573                 fcpServer.writeLine(
574                         "ProtocolError",
575                         "Identifier=" + identifier,
576                         "Code=25",
577                         "EndMessage"
578                 );
579                 lines = fcpServer.collectUntil(is("EndMessage"));
580                 assertThat(lines, matchesFcpMessage(
581                         "TestDDARequest",
582                         "Directory=" + tempFile.getParent(),
583                         "WantReadDirectory=true",
584                         "WantWriteDirectory=false",
585                         "EndMessage"
586                 ));
587                 fcpServer.writeLine(
588                         "TestDDAReply",
589                         "Directory=/some-other-directory",
590                         "ReadFilename=" + tempFile,
591                         "EndMessage"
592                 );
593                 fcpServer.writeLine(
594                         "TestDDAReply",
595                         "Directory=" + tempFile.getParent(),
596                         "ReadFilename=" + tempFile,
597                         "EndMessage"
598                 );
599                 lines = fcpServer.collectUntil(is("EndMessage"));
600                 assertThat(lines, matchesFcpMessage(
601                         "TestDDAResponse",
602                         "Directory=" + tempFile.getParent(),
603                         "ReadContent=test-content",
604                         "EndMessage"
605                 ));
606         }
607
608         @Test
609         public void clientPutSendsResponseEvenIfFileCanNotBeRead()
610         throws IOException, ExecutionException, InterruptedException {
611                 File tempFile = createTempFile();
612                 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
613                 connectNode();
614                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
615                 String identifier = extractIdentifier(lines);
616                 fcpServer.writeLine(
617                         "ProtocolError",
618                         "Identifier=" + identifier,
619                         "Code=25",
620                         "EndMessage"
621                 );
622                 lines = fcpServer.collectUntil(is("EndMessage"));
623                 assertThat(lines, matchesFcpMessage(
624                         "TestDDARequest",
625                         "Directory=" + tempFile.getParent(),
626                         "WantReadDirectory=true",
627                         "WantWriteDirectory=false",
628                         "EndMessage"
629                 ));
630                 fcpServer.writeLine(
631                         "TestDDAReply",
632                         "Directory=" + tempFile.getParent(),
633                         "ReadFilename=" + tempFile + ".foo",
634                         "EndMessage"
635                 );
636                 lines = fcpServer.collectUntil(is("EndMessage"));
637                 assertThat(lines, matchesFcpMessage(
638                         "TestDDAResponse",
639                         "Directory=" + tempFile.getParent(),
640                         "ReadContent=failed-to-read",
641                         "EndMessage"
642                 ));
643         }
644
645         @Test
646         public void clientPutDoesNotResendOriginalClientPutOnTestDDACompleteWithWrongDirectory()
647         throws IOException, ExecutionException, InterruptedException {
648                 File tempFile = createTempFile();
649                 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt").execute();
650                 connectNode();
651                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
652                 String identifier = extractIdentifier(lines);
653                 fcpServer.writeLine(
654                         "TestDDAComplete",
655                         "Directory=/some-other-directory",
656                         "EndMessage"
657                 );
658                 fcpServer.writeLine(
659                         "ProtocolError",
660                         "Identifier=" + identifier,
661                         "Code=25",
662                         "EndMessage"
663                 );
664                 lines = fcpServer.collectUntil(is("EndMessage"));
665                 assertThat(lines, matchesFcpMessage(
666                         "TestDDARequest",
667                         "Directory=" + tempFile.getParent(),
668                         "WantReadDirectory=true",
669                         "WantWriteDirectory=false",
670                         "EndMessage"
671                 ));
672         }
673
674         @Test
675         public void clientCanListPeers() throws IOException, ExecutionException, InterruptedException {
676                 Future<Collection<Peer>> peers = fcpClient.listPeers().execute();
677                 connectNode();
678                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
679                 assertThat(lines, matchesFcpMessage(
680                         "ListPeers",
681                         "WithVolatile=false",
682                         "WithMetadata=false",
683                         "EndMessage"
684                 ));
685                 String identifier = extractIdentifier(lines);
686                 fcpServer.writeLine(
687                         "Peer",
688                         "Identifier=" + identifier,
689                         "identity=id1",
690                         "EndMessage"
691                 );
692                 fcpServer.writeLine(
693                         "Peer",
694                         "Identifier=" + identifier,
695                         "identity=id2",
696                         "EndMessage"
697                 );
698                 fcpServer.writeLine(
699                         "EndListPeers",
700                         "Identifier=" + identifier,
701                         "EndMessage"
702                 );
703                 assertThat(peers.get(), hasSize(2));
704                 assertThat(peers.get().stream().map(Peer::getIdentity).collect(Collectors.toList()),
705                         containsInAnyOrder("id1", "id2"));
706         }
707
708         @Test
709         public void clientCanListPeersWithMetadata() throws IOException, ExecutionException, InterruptedException {
710                 Future<Collection<Peer>> peers = fcpClient.listPeers().includeMetadata().execute();
711                 connectNode();
712                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
713                 assertThat(lines, matchesFcpMessage(
714                         "ListPeers",
715                         "WithVolatile=false",
716                         "WithMetadata=true",
717                         "EndMessage"
718                 ));
719                 String identifier = extractIdentifier(lines);
720                 fcpServer.writeLine(
721                         "Peer",
722                         "Identifier=" + identifier,
723                         "identity=id1",
724                         "metadata.foo=bar1",
725                         "EndMessage"
726                 );
727                 fcpServer.writeLine(
728                         "Peer",
729                         "Identifier=" + identifier,
730                         "identity=id2",
731                         "metadata.foo=bar2",
732                         "EndMessage"
733                 );
734                 fcpServer.writeLine(
735                         "EndListPeers",
736                         "Identifier=" + identifier,
737                         "EndMessage"
738                 );
739                 assertThat(peers.get(), hasSize(2));
740                 assertThat(peers.get().stream().map(peer -> peer.getMetadata("foo")).collect(Collectors.toList()),
741                         containsInAnyOrder("bar1", "bar2"));
742         }
743
744         @Test
745         public void clientCanListPeersWithVolatiles() throws IOException, ExecutionException, InterruptedException {
746                 Future<Collection<Peer>> peers = fcpClient.listPeers().includeVolatile().execute();
747                 connectNode();
748                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
749                 assertThat(lines, matchesFcpMessage(
750                         "ListPeers",
751                         "WithVolatile=true",
752                         "WithMetadata=false",
753                         "EndMessage"
754                 ));
755                 String identifier = extractIdentifier(lines);
756                 fcpServer.writeLine(
757                         "Peer",
758                         "Identifier=" + identifier,
759                         "identity=id1",
760                         "volatile.foo=bar1",
761                         "EndMessage"
762                 );
763                 fcpServer.writeLine(
764                         "Peer",
765                         "Identifier=" + identifier,
766                         "identity=id2",
767                         "volatile.foo=bar2",
768                         "EndMessage"
769                 );
770                 fcpServer.writeLine(
771                         "EndListPeers",
772                         "Identifier=" + identifier,
773                         "EndMessage"
774                 );
775                 assertThat(peers.get(), hasSize(2));
776                 assertThat(peers.get().stream().map(peer -> peer.getVolatile("foo")).collect(Collectors.toList()),
777                         containsInAnyOrder("bar1", "bar2"));
778         }
779
780         @Test
781         public void defaultFcpClientCanGetNodeInformation() throws InterruptedException, ExecutionException, IOException {
782                 Future<NodeData> nodeData = fcpClient.getNode().execute();
783                 connectNode();
784                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
785                 String identifier = extractIdentifier(lines);
786                 assertThat(lines, matchesFcpMessage(
787                         "GetNode",
788                         "Identifier=" + identifier,
789                         "GiveOpennetRef=false",
790                         "WithPrivate=false",
791                         "WithVolatile=false",
792                         "EndMessage"
793                 ));
794                 fcpServer.writeLine(
795                         "NodeData",
796                         "Identifier=" + identifier,
797                         "ark.pubURI=SSK@3YEf.../ark",
798                         "ark.number=78",
799                         "auth.negTypes=2",
800                         "version=Fred,0.7,1.0,1466",
801                         "lastGoodVersion=Fred,0.7,1.0,1466",
802                         "EndMessage"
803                 );
804                 assertThat(nodeData.get(), notNullValue());
805         }
806
807         @Test
808         public void defaultFcpClientCanGetNodeInformationWithOpennetRef()
809         throws InterruptedException, ExecutionException, IOException {
810                 Future<NodeData> nodeData = fcpClient.getNode().opennetRef().execute();
811                 connectNode();
812                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
813                 String identifier = extractIdentifier(lines);
814                 assertThat(lines, matchesFcpMessage(
815                         "GetNode",
816                         "Identifier=" + identifier,
817                         "GiveOpennetRef=true",
818                         "WithPrivate=false",
819                         "WithVolatile=false",
820                         "EndMessage"
821                 ));
822                 fcpServer.writeLine(
823                         "NodeData",
824                         "Identifier=" + identifier,
825                         "opennet=true",
826                         "ark.pubURI=SSK@3YEf.../ark",
827                         "ark.number=78",
828                         "auth.negTypes=2",
829                         "version=Fred,0.7,1.0,1466",
830                         "lastGoodVersion=Fred,0.7,1.0,1466",
831                         "EndMessage"
832                 );
833                 assertThat(nodeData.get().getVersion().toString(), is("Fred,0.7,1.0,1466"));
834         }
835
836         @Test
837         public void defaultFcpClientCanGetNodeInformationWithPrivateData()
838         throws InterruptedException, ExecutionException, IOException {
839                 Future<NodeData> nodeData = fcpClient.getNode().includePrivate().execute();
840                 connectNode();
841                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
842                 String identifier = extractIdentifier(lines);
843                 assertThat(lines, matchesFcpMessage(
844                         "GetNode",
845                         "Identifier=" + identifier,
846                         "GiveOpennetRef=false",
847                         "WithPrivate=true",
848                         "WithVolatile=false",
849                         "EndMessage"
850                 ));
851                 fcpServer.writeLine(
852                         "NodeData",
853                         "Identifier=" + identifier,
854                         "opennet=false",
855                         "ark.pubURI=SSK@3YEf.../ark",
856                         "ark.number=78",
857                         "auth.negTypes=2",
858                         "version=Fred,0.7,1.0,1466",
859                         "lastGoodVersion=Fred,0.7,1.0,1466",
860                         "ark.privURI=SSK@XdHMiRl",
861                         "EndMessage"
862                 );
863                 assertThat(nodeData.get().getARK().getPrivateURI(), is("SSK@XdHMiRl"));
864         }
865
866         @Test
867         public void defaultFcpClientCanGetNodeInformationWithVolatileData()
868         throws InterruptedException, ExecutionException, IOException {
869                 Future<NodeData> nodeData = fcpClient.getNode().includeVolatile().execute();
870                 connectNode();
871                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
872                 String identifier = extractIdentifier(lines);
873                 assertThat(lines, matchesFcpMessage(
874                         "GetNode",
875                         "Identifier=" + identifier,
876                         "GiveOpennetRef=false",
877                         "WithPrivate=false",
878                         "WithVolatile=true",
879                         "EndMessage"
880                 ));
881                 fcpServer.writeLine(
882                         "NodeData",
883                         "Identifier=" + identifier,
884                         "opennet=false",
885                         "ark.pubURI=SSK@3YEf.../ark",
886                         "ark.number=78",
887                         "auth.negTypes=2",
888                         "version=Fred,0.7,1.0,1466",
889                         "lastGoodVersion=Fred,0.7,1.0,1466",
890                         "volatile.freeJavaMemory=205706528",
891                         "EndMessage"
892                 );
893                 assertThat(nodeData.get().getVolatile("freeJavaMemory").toString(), is("205706528"));
894         }
895
896         @Test
897         public void defaultFcpClientCanListSinglePeerByIdentity()
898         throws InterruptedException, ExecutionException, IOException {
899                 Future<Optional<Peer>> peer = fcpClient.listPeer().byIdentity("id1").execute();
900                 connectNode();
901                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
902                 String identifier = extractIdentifier(lines);
903                 assertThat(lines, matchesFcpMessage(
904                         "ListPeer",
905                         "Identifier=" + identifier,
906                         "NodeIdentifier=id1",
907                         "EndMessage"
908                 ));
909                 fcpServer.writeLine(
910                         "Peer",
911                         "Identifier=" + identifier,
912                         "identity=id1",
913                         "opennet=false",
914                         "ark.pubURI=SSK@3YEf.../ark",
915                         "ark.number=78",
916                         "auth.negTypes=2",
917                         "version=Fred,0.7,1.0,1466",
918                         "lastGoodVersion=Fred,0.7,1.0,1466",
919                         "EndMessage"
920                 );
921                 assertThat(peer.get().get().getIdentity().toString(), is("id1"));
922         }
923
924         @Test
925         public void defaultFcpClientCanListSinglePeerByHostAndPort()
926         throws InterruptedException, ExecutionException, IOException {
927                 Future<Optional<Peer>> peer = fcpClient.listPeer().byHostAndPort("host.free.net", 12345).execute();
928                 connectNode();
929                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
930                 String identifier = extractIdentifier(lines);
931                 assertThat(lines, matchesFcpMessage(
932                         "ListPeer",
933                         "Identifier=" + identifier,
934                         "NodeIdentifier=host.free.net:12345",
935                         "EndMessage"
936                 ));
937                 fcpServer.writeLine(
938                         "Peer",
939                         "Identifier=" + identifier,
940                         "identity=id1",
941                         "opennet=false",
942                         "ark.pubURI=SSK@3YEf.../ark",
943                         "ark.number=78",
944                         "auth.negTypes=2",
945                         "version=Fred,0.7,1.0,1466",
946                         "lastGoodVersion=Fred,0.7,1.0,1466",
947                         "EndMessage"
948                 );
949                 assertThat(peer.get().get().getIdentity().toString(), is("id1"));
950         }
951
952         @Test
953         public void defaultFcpClientCanListSinglePeerByName()
954         throws InterruptedException, ExecutionException, IOException {
955                 Future<Optional<Peer>> peer = fcpClient.listPeer().byName("FriendNode").execute();
956                 connectNode();
957                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
958                 String identifier = extractIdentifier(lines);
959                 assertThat(lines, matchesFcpMessage(
960                         "ListPeer",
961                         "Identifier=" + identifier,
962                         "NodeIdentifier=FriendNode",
963                         "EndMessage"
964                 ));
965                 fcpServer.writeLine(
966                         "Peer",
967                         "Identifier=" + identifier,
968                         "identity=id1",
969                         "opennet=false",
970                         "ark.pubURI=SSK@3YEf.../ark",
971                         "ark.number=78",
972                         "auth.negTypes=2",
973                         "version=Fred,0.7,1.0,1466",
974                         "lastGoodVersion=Fred,0.7,1.0,1466",
975                         "EndMessage"
976                 );
977                 assertThat(peer.get().get().getIdentity().toString(), is("id1"));
978         }
979
980         @Test
981         public void defaultFcpClientRecognizesUnknownNodeIdentifiers()
982         throws InterruptedException, ExecutionException, IOException {
983                 Future<Optional<Peer>> peer = fcpClient.listPeer().byIdentity("id2").execute();
984                 connectNode();
985                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
986                 String identifier = extractIdentifier(lines);
987                 assertThat(lines, matchesFcpMessage(
988                         "ListPeer",
989                         "Identifier=" + identifier,
990                         "NodeIdentifier=id2",
991                         "EndMessage"
992                 ));
993                 fcpServer.writeLine(
994                         "UnknownNodeIdentifier",
995                         "Identifier=" + identifier,
996                         "NodeIdentifier=id2",
997                         "EndMessage"
998                 );
999                 assertThat(peer.get().isPresent(), is(false));
1000         }
1001
1002         @Test
1003         public void defaultFcpClientCanAddPeerFromFile() throws InterruptedException, ExecutionException, IOException {
1004                 Future<Optional<Peer>> peer = fcpClient.addPeer().withFile(new File("/tmp/ref.txt")).execute();
1005                 connectNode();
1006                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1007                 String identifier = extractIdentifier(lines);
1008                 assertThat(lines, matchesFcpMessage(
1009                         "AddPeer",
1010                         "Identifier=" + identifier,
1011                         "File=/tmp/ref.txt",
1012                         "EndMessage"
1013                 ));
1014                 fcpServer.writeLine(
1015                         "Peer",
1016                         "Identifier=" + identifier,
1017                         "identity=id1",
1018                         "opennet=false",
1019                         "ark.pubURI=SSK@3YEf.../ark",
1020                         "ark.number=78",
1021                         "auth.negTypes=2",
1022                         "version=Fred,0.7,1.0,1466",
1023                         "lastGoodVersion=Fred,0.7,1.0,1466",
1024                         "EndMessage"
1025                 );
1026                 assertThat(peer.get().get().getIdentity().toString(), is("id1"));
1027         }
1028
1029         @Test
1030         public void defaultFcpClientCanAddPeerFromURL() throws InterruptedException, ExecutionException, IOException {
1031                 Future<Optional<Peer>> peer = fcpClient.addPeer().fromURL(new URL("http://node.ref/")).execute();
1032                 connectNode();
1033                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
1034                 String identifier = extractIdentifier(lines);
1035                 assertThat(lines, matchesFcpMessage(
1036                         "AddPeer",
1037                         "Identifier=" + identifier,
1038                         "URL=http://node.ref/",
1039                         "EndMessage"
1040                 ));
1041                 fcpServer.writeLine(
1042                         "Peer",
1043                         "Identifier=" + identifier,
1044                         "identity=id1",
1045                         "opennet=false",
1046                         "ark.pubURI=SSK@3YEf.../ark",
1047                         "ark.number=78",
1048                         "auth.negTypes=2",
1049                         "version=Fred,0.7,1.0,1466",
1050                         "lastGoodVersion=Fred,0.7,1.0,1466",
1051                         "EndMessage"
1052                 );
1053                 assertThat(peer.get().get().getIdentity().toString(), is("id1"));
1054         }
1055
1056 }