7a43771efad1770b49c19dcb107c7c6524e282ea
[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.is;
5
6 import java.io.ByteArrayInputStream;
7 import java.io.File;
8 import java.io.IOException;
9 import java.nio.charset.StandardCharsets;
10 import java.util.List;
11 import java.util.Optional;
12 import java.util.concurrent.ExecutionException;
13 import java.util.concurrent.ExecutorService;
14 import java.util.concurrent.Executors;
15 import java.util.concurrent.Future;
16
17 import net.pterodactylus.fcp.FcpKeyPair;
18 import net.pterodactylus.fcp.Key;
19 import net.pterodactylus.fcp.Priority;
20 import net.pterodactylus.fcp.fake.FakeTcpServer;
21 import net.pterodactylus.fcp.quelaton.ClientGetCommand.Data;
22
23 import com.google.common.io.ByteStreams;
24 import com.google.common.io.Files;
25 import org.hamcrest.Description;
26 import org.hamcrest.Matcher;
27 import org.hamcrest.TypeSafeDiagnosingMatcher;
28 import org.junit.After;
29 import org.junit.Test;
30
31 /**
32  * Unit test for {@link DefaultFcpClient}.
33  *
34  * @author <a href="bombe@freenetproject.org">David ‘Bombe’ Roden</a>
35  */
36 public class DefaultFcpClientTest {
37
38         private static final String INSERT_URI =
39                 "SSK@RVCHbJdkkyTCeNN9AYukEg76eyqmiosSaNKgE3U9zUw,7SHH53gletBVb9JD7nBsyClbLQsBubDPEIcwg908r7Y,AQECAAE/";
40         private static final String REQUEST_URI =
41                 "SSK@wtbgd2loNcJCXvtQVOftl2tuWBomDQHfqS6ytpPRhfw,7SHH53gletBVb9JD7nBsyClbLQsBubDPEIcwg908r7Y,AQACAAE/";
42
43         private static int threadCounter = 0;
44         private final ExecutorService threadPool =
45                 Executors.newCachedThreadPool(r -> new Thread(r, "Test-Thread-" + threadCounter++));
46         private final FakeTcpServer fcpServer;
47         private final DefaultFcpClient fcpClient;
48
49         public DefaultFcpClientTest() throws IOException {
50                 fcpServer = new FakeTcpServer(threadPool);
51                 fcpClient = new DefaultFcpClient(threadPool, "localhost", fcpServer.getPort(), () -> "Test");
52         }
53
54         @After
55         public void tearDown() throws IOException {
56                 fcpServer.close();
57         }
58
59         @Test
60         public void defaultFcpClientCanGenerateKeypair() throws ExecutionException, InterruptedException, IOException {
61                 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
62                 connectNode();
63                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
64                 String identifier = extractIdentifier(lines);
65                 fcpServer.writeLine("SSKKeypair",
66                         "InsertURI=" + INSERT_URI + "",
67                         "RequestURI=" + REQUEST_URI + "",
68                         "Identifier=" + identifier,
69                         "EndMessage");
70                 FcpKeyPair keyPair = keyPairFuture.get();
71                 assertThat(keyPair.getPublicKey(), is(REQUEST_URI));
72                 assertThat(keyPair.getPrivateKey(), is(INSERT_URI));
73         }
74
75         private void connectNode() throws InterruptedException, ExecutionException, IOException {
76                 fcpServer.connect().get();
77                 fcpServer.collectUntil(is("EndMessage"));
78                 fcpServer.writeLine("NodeHello",
79                         "CompressionCodecs=4 - GZIP(0), BZIP2(1), LZMA(2), LZMA_NEW(3)",
80                         "Revision=build01466",
81                         "Testnet=false",
82                         "Version=Fred,0.7,1.0,1466",
83                         "Build=1466",
84                         "ConnectionIdentifier=14318898267048452a81b36e7f13a3f0",
85                         "Node=Fred",
86                         "ExtBuild=29",
87                         "FCPVersion=2.0",
88                         "NodeLanguage=ENGLISH",
89                         "ExtRevision=v29",
90                         "EndMessage"
91                 );
92         }
93
94         @Test
95         public void clientGetCanDownloadData() throws InterruptedException, ExecutionException, IOException {
96                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
97                 connectNode();
98                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
99                 assertThat(lines, matchesFcpMessage("ClientGet", "ReturnType=direct", "URI=KSK@foo.txt"));
100                 String identifier = extractIdentifier(lines);
101                 fcpServer.writeLine(
102                         "AllData",
103                         "Identifier=" + identifier,
104                         "DataLength=6",
105                         "StartupTime=1435610539000",
106                         "CompletionTime=1435610540000",
107                         "Metadata.ContentType=text/plain;charset=utf-8",
108                         "Data",
109                         "Hello"
110                 );
111                 Optional<Data> data = dataFuture.get();
112                 assertThat(data.get().getMimeType(), is("text/plain;charset=utf-8"));
113                 assertThat(data.get().size(), is(6L));
114                 assertThat(ByteStreams.toByteArray(data.get().getInputStream()),
115                         is("Hello\n".getBytes(StandardCharsets.UTF_8)));
116         }
117
118         private String extractIdentifier(List<String> lines) {
119                 return lines.stream()
120                         .filter(s -> s.startsWith("Identifier="))
121                         .map(s -> s.substring(s.indexOf('=') + 1))
122                         .findFirst()
123                         .orElse("");
124         }
125
126         @Test
127         public void clientGetDownloadsDataForCorrectIdentifier()
128         throws InterruptedException, ExecutionException, IOException {
129                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
130                 connectNode();
131                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
132                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
133                 String identifier = extractIdentifier(lines);
134                 fcpServer.writeLine(
135                         "AllData",
136                         "Identifier=not-test",
137                         "DataLength=12",
138                         "StartupTime=1435610539000",
139                         "CompletionTime=1435610540000",
140                         "Metadata.ContentType=text/plain;charset=latin-9",
141                         "Data",
142                         "Hello World"
143                 );
144                 fcpServer.writeLine(
145                         "AllData",
146                         "Identifier=" + identifier,
147                         "DataLength=6",
148                         "StartupTime=1435610539000",
149                         "CompletionTime=1435610540000",
150                         "Metadata.ContentType=text/plain;charset=utf-8",
151                         "Data",
152                         "Hello"
153                 );
154                 Optional<Data> data = dataFuture.get();
155                 assertThat(data.get().getMimeType(), is("text/plain;charset=utf-8"));
156                 assertThat(data.get().size(), is(6L));
157                 assertThat(ByteStreams.toByteArray(data.get().getInputStream()),
158                         is("Hello\n".getBytes(StandardCharsets.UTF_8)));
159         }
160
161         @Test
162         public void clientGetRecognizesGetFailed() throws InterruptedException, ExecutionException, IOException {
163                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
164                 connectNode();
165                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
166                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
167                 String identifier = extractIdentifier(lines);
168                 fcpServer.writeLine(
169                         "GetFailed",
170                         "Identifier=" + identifier,
171                         "Code=3",
172                         "EndMessage"
173                 );
174                 Optional<Data> data = dataFuture.get();
175                 assertThat(data.isPresent(), is(false));
176         }
177
178         @Test
179         public void clientGetRecognizesGetFailedForCorrectIdentifier()
180         throws InterruptedException, ExecutionException, IOException {
181                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
182                 connectNode();
183                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
184                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
185                 String identifier = extractIdentifier(lines);
186                 fcpServer.writeLine(
187                         "GetFailed",
188                         "Identifier=not-test",
189                         "Code=3",
190                         "EndMessage"
191                 );
192                 fcpServer.writeLine(
193                         "GetFailed",
194                         "Identifier=" + identifier,
195                         "Code=3",
196                         "EndMessage"
197                 );
198                 Optional<Data> data = dataFuture.get();
199                 assertThat(data.isPresent(), is(false));
200         }
201
202         @Test
203         public void clientGetRecognizesConnectionClosed() throws InterruptedException, ExecutionException, IOException {
204                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
205                 connectNode();
206                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
207                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
208                 fcpServer.close();
209                 Optional<Data> data = dataFuture.get();
210                 assertThat(data.isPresent(), is(false));
211         }
212
213         @Test
214         public void clientGetWithIgnoreDataStoreSettingSendsCorrectCommands()
215         throws InterruptedException, ExecutionException, IOException {
216                 fcpClient.clientGet().ignoreDataStore().uri("KSK@foo.txt");
217                 connectNode();
218                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
219                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "IgnoreDS=true"));
220         }
221
222         @Test
223         public void clientGetWithDataStoreOnlySettingSendsCorrectCommands()
224         throws InterruptedException, ExecutionException, IOException {
225                 fcpClient.clientGet().dataStoreOnly().uri("KSK@foo.txt");
226                 connectNode();
227                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
228                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "DSonly=true"));
229         }
230
231         @Test
232         public void clientGetWithMaxSizeSettingSendsCorrectCommands()
233         throws InterruptedException, ExecutionException, IOException {
234                 fcpClient.clientGet().maxSize(1048576).uri("KSK@foo.txt");
235                 connectNode();
236                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
237                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "MaxSize=1048576"));
238         }
239
240         @Test
241         public void clientGetWithPrioritySettingSendsCorrectCommands()
242         throws InterruptedException, ExecutionException, IOException {
243                 fcpClient.clientGet().priority(Priority.interactive).uri("KSK@foo.txt");
244                 connectNode();
245                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
246                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "PriorityClass=1"));
247         }
248
249         @Test
250         public void clientGetWithRealTimeSettingSendsCorrectCommands()
251         throws InterruptedException, ExecutionException, IOException {
252                 fcpClient.clientGet().realTime().uri("KSK@foo.txt");
253                 connectNode();
254                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
255                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "RealTimeFlag=true"));
256         }
257
258         @Test
259         public void clientGetWithGlobalSettingSendsCorrectCommands()
260         throws InterruptedException, ExecutionException, IOException {
261                 fcpClient.clientGet().global().uri("KSK@foo.txt");
262                 connectNode();
263                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
264                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "Global=true"));
265         }
266
267         private Matcher<List<String>> matchesFcpMessage(String name, String... requiredLines) {
268                 return new TypeSafeDiagnosingMatcher<List<String>>() {
269                         @Override
270                         protected boolean matchesSafely(List<String> item, Description mismatchDescription) {
271                                 if (!item.get(0).equals(name)) {
272                                         mismatchDescription.appendText("FCP message is named ").appendValue(item.get(0));
273                                         return false;
274                                 }
275                                 for (String requiredLine : requiredLines) {
276                                         if (item.indexOf(requiredLine) < 1) {
277                                                 mismatchDescription.appendText("FCP message does not contain ").appendValue(requiredLine);
278                                                 return false;
279                                         }
280                                 }
281                                 return true;
282                         }
283
284                         @Override
285                         public void describeTo(Description description) {
286                                 description.appendText("FCP message named ").appendValue(name);
287                                 description.appendValueList(", containing the lines ", ", ", "", requiredLines);
288                         }
289                 };
290         }
291
292         @Test
293         public void clientPutWithDirectDataSendsCorrectCommand()
294         throws IOException, ExecutionException, InterruptedException {
295                 fcpClient.clientPut()
296                         .from(new ByteArrayInputStream("Hello\n".getBytes()))
297                         .length(6)
298                         .uri("KSK@foo.txt");
299                 connectNode();
300                 List<String> lines = fcpServer.collectUntil(is("Hello"));
301                 assertThat(lines, matchesFcpMessage("ClientPut", "UploadFrom=direct", "DataLength=6", "URI=KSK@foo.txt"));
302         }
303
304         @Test
305         public void clientPutWithDirectDataSucceedsOnCorrectIdentifier()
306         throws InterruptedException, ExecutionException, IOException {
307                 Future<Optional<Key>> key = fcpClient.clientPut()
308                         .from(new ByteArrayInputStream("Hello\n".getBytes()))
309                         .length(6)
310                         .uri("KSK@foo.txt");
311                 connectNode();
312                 List<String> lines = fcpServer.collectUntil(is("Hello"));
313                 String identifier = extractIdentifier(lines);
314                 fcpServer.writeLine(
315                         "PutFailed",
316                         "Identifier=not-the-right-one",
317                         "EndMessage"
318                 );
319                 fcpServer.writeLine(
320                         "PutSuccessful",
321                         "URI=KSK@foo.txt",
322                         "Identifier=" + identifier,
323                         "EndMessage"
324                 );
325                 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
326         }
327
328         @Test
329         public void clientPutWithDirectDataFailsOnCorrectIdentifier()
330         throws InterruptedException, ExecutionException, IOException {
331                 Future<Optional<Key>> key = fcpClient.clientPut()
332                         .from(new ByteArrayInputStream("Hello\n".getBytes()))
333                         .length(6)
334                         .uri("KSK@foo.txt");
335                 connectNode();
336                 List<String> lines = fcpServer.collectUntil(is("Hello"));
337                 String identifier = extractIdentifier(lines);
338                 fcpServer.writeLine(
339                         "PutSuccessful",
340                         "Identifier=not-the-right-one",
341                         "URI=KSK@foo.txt",
342                         "EndMessage"
343                 );
344                 fcpServer.writeLine(
345                         "PutFailed",
346                         "Identifier=" + identifier,
347                         "EndMessage"
348                 );
349                 assertThat(key.get().isPresent(), is(false));
350         }
351
352         @Test
353         public void clientPutWithRenamedDirectDataSendsCorrectCommand()
354         throws InterruptedException, ExecutionException, IOException {
355                 fcpClient.clientPut()
356                         .named("otherName.txt")
357                         .from(new ByteArrayInputStream("Hello\n".getBytes()))
358                         .length(6)
359                         .uri("KSK@foo.txt");
360                 connectNode();
361                 List<String> lines = fcpServer.collectUntil(is("Hello"));
362                 assertThat(lines, matchesFcpMessage("ClientPut", "TargetFilename=otherName.txt", "UploadFrom=direct",
363                         "DataLength=6", "URI=KSK@foo.txt"));
364         }
365
366         @Test
367         public void clientPutWithRedirectSendsCorrectCommand()
368         throws IOException, ExecutionException, InterruptedException {
369                 fcpClient.clientPut().redirectTo("KSK@bar.txt").uri("KSK@foo.txt");
370                 connectNode();
371                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
372                 assertThat(lines,
373                         matchesFcpMessage("ClientPut", "UploadFrom=redirect", "URI=KSK@foo.txt", "TargetURI=KSK@bar.txt"));
374         }
375
376         @Test
377         public void clientPutWithFileSendsCorrectCommand() throws InterruptedException, ExecutionException, IOException {
378                 fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt");
379                 connectNode();
380                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
381                 assertThat(lines,
382                         matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt", "Filename=/tmp/data.txt"));
383         }
384
385         @Test
386         public void clientPutWithFileCanCompleteTestDdaSequence()
387         throws IOException, ExecutionException, InterruptedException {
388                 File tempFile = createTempFile();
389                 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt");
390                 connectNode();
391                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
392                 String identifier = extractIdentifier(lines);
393                 fcpServer.writeLine(
394                         "ProtocolError",
395                         "Identifier=" + identifier,
396                         "Code=25",
397                         "EndMessage"
398                 );
399                 lines = fcpServer.collectUntil(is("EndMessage"));
400                 assertThat(lines, matchesFcpMessage(
401                         "TestDDARequest",
402                         "Directory=" + tempFile.getParent(),
403                         "WantReadDirectory=true",
404                         "WantWriteDirectory=false",
405                         "EndMessage"
406                 ));
407                 fcpServer.writeLine(
408                         "TestDDAReply",
409                         "Directory=" + tempFile.getParent(),
410                         "ReadFilename=" + tempFile,
411                         "EndMessage"
412                 );
413                 lines = fcpServer.collectUntil(is("EndMessage"));
414                 assertThat(lines, matchesFcpMessage(
415                         "TestDDAResponse",
416                         "Directory=" + tempFile.getParent(),
417                         "ReadContent=test-content",
418                         "EndMessage"
419                 ));
420                 fcpServer.writeLine(
421                         "TestDDAComplete",
422                         "Directory=" + tempFile.getParent(),
423                         "ReadDirectoryAllowed=true",
424                         "EndMessage"
425                 );
426                 lines = fcpServer.collectUntil(is("EndMessage"));
427                 assertThat(lines,
428                         matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt",
429                                 "Filename=" + new File(tempFile.getParent(), "test.dat")));
430         }
431
432         private File createTempFile() throws IOException {
433                 File tempFile = File.createTempFile("test-dda-", ".dat");
434                 tempFile.deleteOnExit();
435                 Files.write("test-content", tempFile, StandardCharsets.UTF_8);
436                 return tempFile;
437         }
438
439         @Test
440         public void clientPutDoesNotReactToProtocolErrorForDifferentIdentifier()
441         throws InterruptedException, ExecutionException, IOException {
442                 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt");
443                 connectNode();
444                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
445                 String identifier = extractIdentifier(lines);
446                 fcpServer.writeLine(
447                         "ProtocolError",
448                         "Identifier=not-the-right-one",
449                         "Code=25",
450                         "EndMessage"
451                 );
452                 fcpServer.writeLine(
453                         "PutSuccessful",
454                         "Identifier=" + identifier,
455                         "URI=KSK@foo.txt",
456                         "EndMessage"
457                 );
458                 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
459         }
460
461         @Test
462         public void clientPutAbortsOnProtocolErrorOtherThan25()
463         throws InterruptedException, ExecutionException, IOException {
464                 Future<Optional<Key>> key = fcpClient.clientPut().from(new File("/tmp/data.txt")).uri("KSK@foo.txt");
465                 connectNode();
466                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
467                 String identifier = extractIdentifier(lines);
468                 fcpServer.writeLine(
469                         "ProtocolError",
470                         "Identifier=" + identifier,
471                         "Code=1",
472                         "EndMessage"
473                 );
474                 assertThat(key.get().isPresent(), is(false));
475         }
476
477         @Test
478         public void clientPutDoesNotReplyToWrongTestDdaReply() throws IOException, ExecutionException,
479         InterruptedException {
480                 File tempFile = createTempFile();
481                 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt");
482                 connectNode();
483                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
484                 String identifier = extractIdentifier(lines);
485                 fcpServer.writeLine(
486                         "ProtocolError",
487                         "Identifier=" + identifier,
488                         "Code=25",
489                         "EndMessage"
490                 );
491                 lines = fcpServer.collectUntil(is("EndMessage"));
492                 assertThat(lines, matchesFcpMessage(
493                         "TestDDARequest",
494                         "Directory=" + tempFile.getParent(),
495                         "WantReadDirectory=true",
496                         "WantWriteDirectory=false",
497                         "EndMessage"
498                 ));
499                 fcpServer.writeLine(
500                         "TestDDAReply",
501                         "Directory=/some-other-directory",
502                         "ReadFilename=" + tempFile,
503                         "EndMessage"
504                 );
505                 fcpServer.writeLine(
506                         "TestDDAReply",
507                         "Directory=" + tempFile.getParent(),
508                         "ReadFilename=" + tempFile,
509                         "EndMessage"
510                 );
511                 lines = fcpServer.collectUntil(is("EndMessage"));
512                 assertThat(lines, matchesFcpMessage(
513                         "TestDDAResponse",
514                         "Directory=" + tempFile.getParent(),
515                         "ReadContent=test-content",
516                         "EndMessage"
517                 ));
518         }
519
520         @Test
521         public void clientPutSendsResponseEvenIfFileCanNotBeRead()
522         throws IOException, ExecutionException, InterruptedException {
523                 File tempFile = createTempFile();
524                 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt");
525                 connectNode();
526                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
527                 String identifier = extractIdentifier(lines);
528                 fcpServer.writeLine(
529                         "ProtocolError",
530                         "Identifier=" + identifier,
531                         "Code=25",
532                         "EndMessage"
533                 );
534                 lines = fcpServer.collectUntil(is("EndMessage"));
535                 assertThat(lines, matchesFcpMessage(
536                         "TestDDARequest",
537                         "Directory=" + tempFile.getParent(),
538                         "WantReadDirectory=true",
539                         "WantWriteDirectory=false",
540                         "EndMessage"
541                 ));
542                 fcpServer.writeLine(
543                         "TestDDAReply",
544                         "Directory=" + tempFile.getParent(),
545                         "ReadFilename=" + tempFile + ".foo",
546                         "EndMessage"
547                 );
548                 lines = fcpServer.collectUntil(is("EndMessage"));
549                 assertThat(lines, matchesFcpMessage(
550                         "TestDDAResponse",
551                         "Directory=" + tempFile.getParent(),
552                         "ReadContent=failed-to-read",
553                         "EndMessage"
554                 ));
555         }
556
557         @Test
558         public void clientPutDoesNotResendOriginalClientPutOnTestDDACompleteWithWrongDirectory()
559         throws IOException, ExecutionException, InterruptedException {
560                 File tempFile = createTempFile();
561                 fcpClient.clientPut().from(new File(tempFile.getParent(), "test.dat")).uri("KSK@foo.txt");
562                 connectNode();
563                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
564                 String identifier = extractIdentifier(lines);
565                 fcpServer.writeLine(
566                         "TestDDAComplete",
567                         "Directory=/some-other-directory",
568                         "EndMessage"
569                 );
570                 fcpServer.writeLine(
571                         "ProtocolError",
572                         "Identifier=" + identifier,
573                         "Code=25",
574                         "EndMessage"
575                 );
576                 lines = fcpServer.collectUntil(is("EndMessage"));
577                 assertThat(lines, matchesFcpMessage(
578                         "TestDDARequest",
579                         "Directory=" + tempFile.getParent(),
580                         "WantReadDirectory=true",
581                         "WantWriteDirectory=false",
582                         "EndMessage"
583                 ));
584         }
585
586 }