Add ClientPut command implementation
[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 org.hamcrest.Description;
25 import org.hamcrest.Matcher;
26 import org.hamcrest.TypeSafeDiagnosingMatcher;
27 import org.junit.After;
28 import org.junit.Test;
29
30 /**
31  * Unit test for {@link DefaultFcpClient}.
32  *
33  * @author <a href="bombe@freenetproject.org">David ‘Bombe’ Roden</a>
34  */
35 public class DefaultFcpClientTest {
36
37         private static final String INSERT_URI = "SSK@RVCHbJdkkyTCeNN9AYukEg76eyqmiosSaNKgE3U9zUw,7SHH53gletBVb9JD7nBsyClbLQsBubDPEIcwg908r7Y,AQECAAE/";
38         private static final String REQUEST_URI = "SSK@wtbgd2loNcJCXvtQVOftl2tuWBomDQHfqS6ytpPRhfw,7SHH53gletBVb9JD7nBsyClbLQsBubDPEIcwg908r7Y,AQACAAE/";
39
40         private static int threadCounter = 0;
41         private final ExecutorService threadPool = Executors.newCachedThreadPool((r) -> new Thread(r, "Test-Thread-" + threadCounter++));
42         private final FakeTcpServer fcpServer;
43         private final DefaultFcpClient fcpClient;
44
45         public DefaultFcpClientTest() throws IOException {
46                 fcpServer = new FakeTcpServer(threadPool);
47                 fcpClient = new DefaultFcpClient(threadPool, "localhost", fcpServer.getPort(), () -> "Test", () -> "2.0");
48         }
49
50         @After
51         public void tearDown() throws IOException {
52                 fcpServer.close();
53         }
54
55         @Test
56         public void defaultFcpClientCanGenerateKeypair() throws ExecutionException, InterruptedException, IOException {
57                 Future<FcpKeyPair> keyPairFuture = fcpClient.generateKeypair().execute();
58                 connectNode();
59                 fcpServer.collectUntil(is("EndMessage"));
60                 fcpServer.writeLine("SSKKeypair",
61                         "InsertURI=" + INSERT_URI + "",
62                         "RequestURI=" + REQUEST_URI + "",
63                         "Identifier=My Identifier from GenerateSSK",
64                         "EndMessage");
65                 FcpKeyPair keyPair = keyPairFuture.get();
66                 assertThat(keyPair.getPublicKey(), is(REQUEST_URI));
67                 assertThat(keyPair.getPrivateKey(), is(INSERT_URI));
68         }
69
70         private void connectNode() throws InterruptedException, ExecutionException, IOException {
71                 fcpServer.connect().get();
72                 fcpServer.collectUntil(is("EndMessage"));
73                 fcpServer.writeLine("NodeHello",
74                         "CompressionCodecs=4 - GZIP(0), BZIP2(1), LZMA(2), LZMA_NEW(3)",
75                         "Revision=build01466",
76                         "Testnet=false",
77                         "Version=Fred,0.7,1.0,1466",
78                         "Build=1466",
79                         "ConnectionIdentifier=14318898267048452a81b36e7f13a3f0",
80                         "Node=Fred",
81                         "ExtBuild=29",
82                         "FCPVersion=2.0",
83                         "NodeLanguage=ENGLISH",
84                         "ExtRevision=v29",
85                         "EndMessage"
86                 );
87         }
88
89         @Test
90         public void clientGetCanDownloadData() throws InterruptedException, ExecutionException, IOException {
91                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
92                 connectNode();
93                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
94                 assertThat(lines, matchesFcpMessage("ClientGet", "ReturnType=direct", "URI=KSK@foo.txt"));
95                 String identifier = extractIdentifier(lines);
96                 fcpServer.writeLine(
97                         "AllData",
98                         "Identifier=" + identifier,
99                         "DataLength=6",
100                         "StartupTime=1435610539000",
101                         "CompletionTime=1435610540000",
102                         "Metadata.ContentType=text/plain;charset=utf-8",
103                         "Data",
104                         "Hello"
105                 );
106                 Optional<Data> data = dataFuture.get();
107                 assertThat(data.get().getMimeType(), is("text/plain;charset=utf-8"));
108                 assertThat(data.get().size(), is(6L));
109                 assertThat(ByteStreams.toByteArray(data.get().getInputStream()), is("Hello\n".getBytes(StandardCharsets.UTF_8)));
110         }
111
112         private String extractIdentifier(List<String> lines) {
113                 return lines.stream().filter(s -> s.startsWith("Identifier=")).map(s -> s.substring(s.indexOf('=') + 1)).findFirst().orElse("");
114         }
115
116         @Test
117         public void clientGetDownloadsDataForCorrectIdentifier() throws InterruptedException, ExecutionException, IOException {
118                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
119                 connectNode();
120                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
121                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
122                 String identifier = extractIdentifier(lines);
123                 fcpServer.writeLine(
124                         "AllData",
125                         "Identifier=not-test",
126                         "DataLength=12",
127                         "StartupTime=1435610539000",
128                         "CompletionTime=1435610540000",
129                         "Metadata.ContentType=text/plain;charset=latin-9",
130                         "Data",
131                         "Hello World"
132                 );
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()), is("Hello\n".getBytes(StandardCharsets.UTF_8)));
147         }
148
149         @Test
150         public void clientGetRecognizesGetFailed() throws InterruptedException, ExecutionException, IOException {
151                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
152                 connectNode();
153                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
154                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
155                 String identifier = extractIdentifier(lines);
156                 fcpServer.writeLine(
157                         "GetFailed",
158                         "Identifier=" + identifier,
159                         "Code=3",
160                         "EndMessage"
161                 );
162                 Optional<Data> data = dataFuture.get();
163                 assertThat(data.isPresent(), is(false));
164         }
165
166         @Test
167         public void clientGetRecognizesGetFailedForCorrectIdentifier() throws InterruptedException, ExecutionException, IOException {
168                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
169                 connectNode();
170                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
171                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
172                 String identifier = extractIdentifier(lines);
173                 fcpServer.writeLine(
174                         "GetFailed",
175                         "Identifier=not-test",
176                         "Code=3",
177                         "EndMessage"
178                 );
179                 fcpServer.writeLine(
180                         "GetFailed",
181                         "Identifier=" + identifier,
182                         "Code=3",
183                         "EndMessage"
184                 );
185                 Optional<Data> data = dataFuture.get();
186                 assertThat(data.isPresent(), is(false));
187         }
188
189         @Test
190         public void clientGetRecognizesConnectionClosed() throws InterruptedException, ExecutionException, IOException {
191                 Future<Optional<Data>> dataFuture = fcpClient.clientGet().uri("KSK@foo.txt");
192                 connectNode();
193                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
194                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt"));
195                 fcpServer.close();
196                 Optional<Data> data = dataFuture.get();
197                 assertThat(data.isPresent(), is(false));
198         }
199
200         @Test
201         public void clientGetWithIgnoreDataStoreSettingSendsCorrectCommands() throws InterruptedException, ExecutionException, IOException {
202                 fcpClient.clientGet().ignoreDataStore().uri("KSK@foo.txt");
203                 connectNode();
204                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
205                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "IgnoreDS=true"));
206         }
207
208         @Test
209         public void clientGetWithDataStoreOnlySettingSendsCorrectCommands() throws InterruptedException, ExecutionException, IOException {
210                 fcpClient.clientGet().dataStoreOnly().uri("KSK@foo.txt");
211                 connectNode();
212                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
213                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "DSonly=true"));
214         }
215
216         @Test
217         public void clientGetWithMaxSizeSettingSendsCorrectCommands() throws InterruptedException, ExecutionException, IOException {
218                 fcpClient.clientGet().maxSize(1048576).uri("KSK@foo.txt");
219                 connectNode();
220                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
221                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "MaxSize=1048576"));
222         }
223
224         @Test
225         public void clientGetWithPrioritySettingSendsCorrectCommands() throws InterruptedException, ExecutionException, IOException {
226                 fcpClient.clientGet().priority(Priority.interactive).uri("KSK@foo.txt");
227                 connectNode();
228                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
229                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "PriorityClass=1"));
230         }
231
232         @Test
233         public void clientGetWithRealTimeSettingSendsCorrectCommands() throws InterruptedException, ExecutionException, IOException {
234                 fcpClient.clientGet().realTime().uri("KSK@foo.txt");
235                 connectNode();
236                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
237                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "RealTimeFlag=true"));
238         }
239
240         @Test
241         public void clientGetWithGlobalSettingSendsCorrectCommands() throws InterruptedException, ExecutionException, IOException {
242                 fcpClient.clientGet().global().uri("KSK@foo.txt");
243                 connectNode();
244                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
245                 assertThat(lines, matchesFcpMessage("ClientGet", "URI=KSK@foo.txt", "Global=true"));
246         }
247
248         private Matcher<List<String>> matchesFcpMessage(String name, String... requiredLines) {
249                 return new TypeSafeDiagnosingMatcher<List<String>>() {
250                         @Override
251                         protected boolean matchesSafely(List<String> item, Description mismatchDescription) {
252                                 if (!item.get(0).equals(name)) {
253                                         mismatchDescription.appendText("FCP message is named ").appendValue(item.get(0));
254                                         return false;
255                                 }
256                                 for (String requiredLine : requiredLines) {
257                                         if (item.indexOf(requiredLine) < 1) {
258                                                 mismatchDescription.appendText("FCP message does not contain ").appendValue(requiredLine);
259                                                 return false;
260                                         }
261                                 }
262                                 return true;
263                         }
264
265                         @Override
266                         public void describeTo(Description description) {
267                                 description.appendText("FCP message named ").appendValue(name);
268                                 description.appendValueList(", containing the lines ", ", ", "", requiredLines);
269                         }
270                 };
271         }
272
273         @Test
274         public void clientPutWithDirectDataSendsCorrectCommand()
275         throws IOException, ExecutionException, InterruptedException {
276                 fcpClient.clientPut()
277                         .from(new ByteArrayInputStream("Hello\n".getBytes()))
278                         .length(6)
279                         .key(new Key("KSK@foo.txt"));
280                 connectNode();
281                 List<String> lines = fcpServer.collectUntil(is("Hello"));
282                 assertThat(lines, matchesFcpMessage("ClientPut", "UploadFrom=direct", "DataLength=6", "URI=KSK@foo.txt"));
283         }
284
285         @Test
286         public void clientPutWithDirectDataSucceedsOnCorrectIdentifier()
287         throws InterruptedException, ExecutionException, IOException {
288                 Future<Optional<Key>> key = fcpClient.clientPut()
289                         .from(new ByteArrayInputStream("Hello\n".getBytes()))
290                         .length(6)
291                         .key(new Key("KSK@foo.txt"));
292                 connectNode();
293                 List<String> lines = fcpServer.collectUntil(is("Hello"));
294                 String identifier = extractIdentifier(lines);
295                 fcpServer.writeLine(
296                         "PutFailed",
297                         "Identifier=not-the-right-one",
298                         "EndMessage"
299                 );
300                 fcpServer.writeLine(
301                         "PutSuccessful",
302                         "URI=KSK@foo.txt",
303                         "Identifier=" + identifier,
304                         "EndMessage"
305                 );
306                 assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
307         }
308
309         @Test
310         public void clientPutWithDirectDataFailsOnCorrectIdentifier()
311         throws InterruptedException, ExecutionException, IOException {
312                 Future<Optional<Key>> key = fcpClient.clientPut()
313                         .from(new ByteArrayInputStream("Hello\n".getBytes()))
314                         .length(6)
315                         .key(new Key("KSK@foo.txt"));
316                 connectNode();
317                 List<String> lines = fcpServer.collectUntil(is("Hello"));
318                 String identifier = extractIdentifier(lines);
319                 fcpServer.writeLine(
320                         "PutSuccessful",
321                         "Identifier=not-the-right-one",
322                         "URI=KSK@foo.txt",
323                         "EndMessage"
324                 );
325                 fcpServer.writeLine(
326                         "PutFailed",
327                         "Identifier=" + identifier,
328                         "EndMessage"
329                 );
330                 assertThat(key.get().isPresent(), is(false));
331         }
332
333         @Test
334         public void clientPutWithRenamedDirectDataSendsCorrectCommand()
335         throws InterruptedException, ExecutionException, IOException {
336                 fcpClient.clientPut()
337                         .named("otherName.txt")
338                         .from(new ByteArrayInputStream("Hello\n".getBytes()))
339                         .length(6)
340                         .key(new Key("KSK@foo.txt"));
341                 connectNode();
342                 List<String> lines = fcpServer.collectUntil(is("Hello"));
343                 assertThat(lines, matchesFcpMessage("ClientPut", "TargetFilename=otherName.txt", "UploadFrom=direct",
344                         "DataLength=6", "URI=KSK@foo.txt"));
345         }
346
347         @Test
348         public void clientPutWithRedirectSendsCorrectCommand()
349         throws IOException, ExecutionException, InterruptedException {
350                 fcpClient.clientPut().redirectTo(new Key("KSK@bar.txt")).key(new Key("KSK@foo.txt"));
351                 connectNode();
352                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
353                 assertThat(lines,
354                         matchesFcpMessage("ClientPut", "UploadFrom=redirect", "URI=KSK@foo.txt", "TargetURI=KSK@bar.txt"));
355         }
356
357         @Test
358         public void clientPutWithFileSendsCorrectCommand() throws InterruptedException, ExecutionException, IOException {
359                 fcpClient.clientPut().from(new File("/tmp/data.txt")).key(new Key("KSK@foo.txt"));
360                 connectNode();
361                 List<String> lines = fcpServer.collectUntil(is("EndMessage"));
362                 assertThat(lines,
363                         matchesFcpMessage("ClientPut", "UploadFrom=disk", "URI=KSK@foo.txt", "Filename=/tmp/data.txt"));
364         }
365
366 }