Add command that loads a plugin
[jFCPlib.git] / src / test / java / net / pterodactylus / fcp / quelaton / DefaultFcpClientTest.java
index 574f4d7..77e3588 100644 (file)
@@ -1,10 +1,13 @@
 package net.pterodactylus.fcp.quelaton;
 
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
 import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
 import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.startsWith;
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
@@ -14,16 +17,23 @@ import java.nio.charset.StandardCharsets;
 import java.util.Collection;
 import java.util.List;
 import java.util.Optional;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.stream.Collectors;
 
+import net.pterodactylus.fcp.ARK;
+import net.pterodactylus.fcp.ConfigData;
+import net.pterodactylus.fcp.DSAGroup;
 import net.pterodactylus.fcp.FcpKeyPair;
 import net.pterodactylus.fcp.Key;
 import net.pterodactylus.fcp.NodeData;
+import net.pterodactylus.fcp.NodeRef;
 import net.pterodactylus.fcp.Peer;
+import net.pterodactylus.fcp.PeerNote;
+import net.pterodactylus.fcp.PluginInfo;
 import net.pterodactylus.fcp.Priority;
 import net.pterodactylus.fcp.fake.FakeTcpServer;
 import net.pterodactylus.fcp.quelaton.ClientGetCommand.Data;
@@ -49,7 +59,7 @@ public class DefaultFcpClientTest {
        private static final String REQUEST_URI =
                "SSK@wtbgd2loNcJCXvtQVOftl2tuWBomDQHfqS6ytpPRhfw,7SHH53gletBVb9JD7nBsyClbLQsBubDPEIcwg908r7Y,AQACAAE/";
 
-       private static int threadCounter = 0;
+       private int threadCounter = 0;
        private final ExecutorService threadPool =
                Executors.newCachedThreadPool(r -> new Thread(r, "Test-Thread-" + threadCounter++));
        private final FakeTcpServer fcpServer;
@@ -63,6 +73,7 @@ public class DefaultFcpClientTest {
        @After
        public void tearDown() throws IOException {
                fcpServer.close();
+               threadPool.shutdown();
        }
 
        @Test(expected = ExecutionException.class)
@@ -672,6 +683,35 @@ public class DefaultFcpClientTest {
        }
 
        @Test
+       public void clientPutSendsNotificationsForGeneratedKeys()
+       throws InterruptedException, ExecutionException, IOException {
+               List<String> generatedKeys = new CopyOnWriteArrayList<>();
+               Future<Optional<Key>> key = fcpClient.clientPut()
+                       .onKeyGenerated(generatedKeys::add)
+                       .from(new ByteArrayInputStream("Hello\n".getBytes()))
+                       .length(6)
+                       .uri("KSK@foo.txt")
+                       .execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("Hello"));
+               String identifier = extractIdentifier(lines);
+               fcpServer.writeLine(
+                       "URIGenerated",
+                       "Identifier=" + identifier,
+                       "URI=KSK@foo.txt",
+                       "EndMessage"
+               );
+               fcpServer.writeLine(
+                       "PutSuccessful",
+                       "URI=KSK@foo.txt",
+                       "Identifier=" + identifier,
+                       "EndMessage"
+               );
+               assertThat(key.get().get().getKey(), is("KSK@foo.txt"));
+               assertThat(generatedKeys, contains("KSK@foo.txt"));
+       }
+
+       @Test
        public void clientCanListPeers() throws IOException, ExecutionException, InterruptedException {
                Future<Collection<Peer>> peers = fcpClient.listPeers().execute();
                connectNode();
@@ -890,7 +930,7 @@ public class DefaultFcpClientTest {
                        "volatile.freeJavaMemory=205706528",
                        "EndMessage"
                );
-               assertThat(nodeData.get().getVolatile("freeJavaMemory").toString(), is("205706528"));
+               assertThat(nodeData.get().getVolatile("freeJavaMemory"), is("205706528"));
        }
 
        @Test
@@ -918,7 +958,7 @@ public class DefaultFcpClientTest {
                        "lastGoodVersion=Fred,0.7,1.0,1466",
                        "EndMessage"
                );
-               assertThat(peer.get().get().getIdentity().toString(), is("id1"));
+               assertThat(peer.get().get().getIdentity(), is("id1"));
        }
 
        @Test
@@ -946,7 +986,7 @@ public class DefaultFcpClientTest {
                        "lastGoodVersion=Fred,0.7,1.0,1466",
                        "EndMessage"
                );
-               assertThat(peer.get().get().getIdentity().toString(), is("id1"));
+               assertThat(peer.get().get().getIdentity(), is("id1"));
        }
 
        @Test
@@ -974,7 +1014,7 @@ public class DefaultFcpClientTest {
                        "lastGoodVersion=Fred,0.7,1.0,1466",
                        "EndMessage"
                );
-               assertThat(peer.get().get().getIdentity().toString(), is("id1"));
+               assertThat(peer.get().get().getIdentity(), is("id1"));
        }
 
        @Test
@@ -1001,7 +1041,7 @@ public class DefaultFcpClientTest {
 
        @Test
        public void defaultFcpClientCanAddPeerFromFile() throws InterruptedException, ExecutionException, IOException {
-               Future<Optional<Peer>> peer = fcpClient.addPeer().withFile(new File("/tmp/ref.txt")).execute();
+               Future<Optional<Peer>> peer = fcpClient.addPeer().fromFile(new File("/tmp/ref.txt")).execute();
                connectNode();
                List<String> lines = fcpServer.collectUntil(is("EndMessage"));
                String identifier = extractIdentifier(lines);
@@ -1023,7 +1063,7 @@ public class DefaultFcpClientTest {
                        "lastGoodVersion=Fred,0.7,1.0,1466",
                        "EndMessage"
                );
-               assertThat(peer.get().get().getIdentity().toString(), is("id1"));
+               assertThat(peer.get().get().getIdentity(), is("id1"));
        }
 
        @Test
@@ -1050,7 +1090,987 @@ public class DefaultFcpClientTest {
                        "lastGoodVersion=Fred,0.7,1.0,1466",
                        "EndMessage"
                );
-               assertThat(peer.get().get().getIdentity().toString(), is("id1"));
+               assertThat(peer.get().get().getIdentity(), is("id1"));
+       }
+
+       @Test
+       public void defaultFcpClientCanAddPeerFromNodeRef() throws InterruptedException, ExecutionException, IOException {
+               NodeRef nodeRef = new NodeRef();
+               nodeRef.setIdentity("id1");
+               nodeRef.setName("name");
+               nodeRef.setARK(new ARK("public", "1"));
+               nodeRef.setDSAGroup(new DSAGroup("base", "prime", "subprime"));
+               nodeRef.setNegotiationTypes(new int[] { 3, 5 });
+               nodeRef.setPhysicalUDP("1.2.3.4:5678");
+               nodeRef.setDSAPublicKey("dsa-public");
+               nodeRef.setSignature("sig");
+               Future<Optional<Peer>> peer = fcpClient.addPeer().fromNodeRef(nodeRef).execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "AddPeer",
+                       "Identifier=" + identifier,
+                       "identity=id1",
+                       "myName=name",
+                       "ark.pubURI=public",
+                       "ark.number=1",
+                       "dsaGroup.g=base",
+                       "dsaGroup.p=prime",
+                       "dsaGroup.q=subprime",
+                       "dsaPubKey.y=dsa-public",
+                       "physical.udp=1.2.3.4:5678",
+                       "auth.negTypes=3;5",
+                       "sig=sig",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "Peer",
+                       "Identifier=" + identifier,
+                       "identity=id1",
+                       "opennet=false",
+                       "ark.pubURI=SSK@3YEf.../ark",
+                       "ark.number=78",
+                       "auth.negTypes=2",
+                       "version=Fred,0.7,1.0,1466",
+                       "lastGoodVersion=Fred,0.7,1.0,1466",
+                       "EndMessage"
+               );
+               assertThat(peer.get().get().getIdentity(), is("id1"));
+       }
+
+       @Test
+       public void listPeerNotesCanGetPeerNotesByNodeName() throws InterruptedException, ExecutionException, IOException {
+               Future<Optional<PeerNote>> peerNote = fcpClient.listPeerNotes().byName("Friend1").execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "ListPeerNotes",
+                       "NodeIdentifier=Friend1",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "PeerNote",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=Friend1",
+                       "NoteText=RXhhbXBsZSBUZXh0Lg==",
+                       "PeerNoteType=1",
+                       "EndMessage"
+               );
+               fcpServer.writeLine(
+                       "EndListPeerNotes",
+                       "Identifier=" + identifier,
+                       "EndMessage"
+               );
+               assertThat(peerNote.get().get().getNoteText(), is("RXhhbXBsZSBUZXh0Lg=="));
+               assertThat(peerNote.get().get().getPeerNoteType(), is(1));
+       }
+
+       @Test
+       public void listPeerNotesReturnsEmptyOptionalWhenNodeIdenfierUnknown()
+       throws InterruptedException, ExecutionException,
+       IOException {
+               Future<Optional<PeerNote>> peerNote = fcpClient.listPeerNotes().byName("Friend1").execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "ListPeerNotes",
+                       "NodeIdentifier=Friend1",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "UnknownNodeIdentifier",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=Friend1",
+                       "EndMessage"
+               );
+               assertThat(peerNote.get().isPresent(), is(false));
+       }
+
+       @Test
+       public void listPeerNotesCanGetPeerNotesByNodeIdentifier()
+       throws InterruptedException, ExecutionException, IOException {
+               Future<Optional<PeerNote>> peerNote = fcpClient.listPeerNotes().byIdentity("id1").execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "ListPeerNotes",
+                       "NodeIdentifier=id1",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "PeerNote",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=id1",
+                       "NoteText=RXhhbXBsZSBUZXh0Lg==",
+                       "PeerNoteType=1",
+                       "EndMessage"
+               );
+               fcpServer.writeLine(
+                       "EndListPeerNotes",
+                       "Identifier=" + identifier,
+                       "EndMessage"
+               );
+               assertThat(peerNote.get().get().getNoteText(), is("RXhhbXBsZSBUZXh0Lg=="));
+               assertThat(peerNote.get().get().getPeerNoteType(), is(1));
+       }
+
+       @Test
+       public void listPeerNotesCanGetPeerNotesByHostNameAndPortNumber()
+       throws InterruptedException, ExecutionException, IOException {
+               Future<Optional<PeerNote>> peerNote = fcpClient.listPeerNotes().byHostAndPort("1.2.3.4", 5678).execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "ListPeerNotes",
+                       "NodeIdentifier=1.2.3.4:5678",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "PeerNote",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=id1",
+                       "NoteText=RXhhbXBsZSBUZXh0Lg==",
+                       "PeerNoteType=1",
+                       "EndMessage"
+               );
+               fcpServer.writeLine(
+                       "EndListPeerNotes",
+                       "Identifier=" + identifier,
+                       "EndMessage"
+               );
+               assertThat(peerNote.get().get().getNoteText(), is("RXhhbXBsZSBUZXh0Lg=="));
+               assertThat(peerNote.get().get().getPeerNoteType(), is(1));
+       }
+
+       @Test
+       public void defaultFcpClientCanEnablePeerByName() throws InterruptedException, ExecutionException, IOException {
+               Future<Optional<Peer>> peer = fcpClient.modifyPeer().enable().byName("Friend1").execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "ModifyPeer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=Friend1",
+                       "IsDisabled=false",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "Peer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=Friend1",
+                       "identity=id1",
+                       "EndMessage"
+               );
+               assertThat(peer.get().get().getIdentity(), is("id1"));
+       }
+
+       @Test
+       public void defaultFcpClientCanDisablePeerByName() throws InterruptedException, ExecutionException, IOException {
+               Future<Optional<Peer>> peer = fcpClient.modifyPeer().disable().byName("Friend1").execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "ModifyPeer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=Friend1",
+                       "IsDisabled=true",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "Peer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=Friend1",
+                       "identity=id1",
+                       "EndMessage"
+               );
+               assertThat(peer.get().get().getIdentity(), is("id1"));
+       }
+
+       @Test
+       public void defaultFcpClientCanEnablePeerByIdentity() throws InterruptedException, ExecutionException, IOException {
+               Future<Optional<Peer>> peer = fcpClient.modifyPeer().enable().byIdentity("id1").execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "ModifyPeer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=id1",
+                       "IsDisabled=false",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "Peer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=Friend1",
+                       "identity=id1",
+                       "EndMessage"
+               );
+               assertThat(peer.get().get().getIdentity(), is("id1"));
+       }
+
+       @Test
+       public void defaultFcpClientCanEnablePeerByHostAndPort()
+       throws InterruptedException, ExecutionException, IOException {
+               Future<Optional<Peer>> peer = fcpClient.modifyPeer().enable().byHostAndPort("1.2.3.4", 5678).execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "ModifyPeer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=1.2.3.4:5678",
+                       "IsDisabled=false",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "Peer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=Friend1",
+                       "identity=id1",
+                       "EndMessage"
+               );
+               assertThat(peer.get().get().getIdentity(), is("id1"));
+       }
+
+       @Test
+       public void defaultFcpClientCanNotModifyPeerOfUnknownNode()
+       throws InterruptedException, ExecutionException, IOException {
+               Future<Optional<Peer>> peer = fcpClient.modifyPeer().enable().byIdentity("id1").execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "ModifyPeer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=id1",
+                       "IsDisabled=false",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "UnknownNodeIdentifier",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=id1",
+                       "EndMessage"
+               );
+               assertThat(peer.get().isPresent(), is(false));
+       }
+
+       @Test
+       public void defaultFcpClientCanAllowLocalAddressesOfPeer()
+       throws InterruptedException, ExecutionException, IOException {
+               Future<Optional<Peer>> peer = fcpClient.modifyPeer().allowLocalAddresses().byIdentity("id1").execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "ModifyPeer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=id1",
+                       "AllowLocalAddresses=true",
+                       "EndMessage"
+               ));
+               assertThat(lines, not(contains(startsWith("IsDisabled="))));
+               fcpServer.writeLine(
+                       "Peer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=Friend1",
+                       "identity=id1",
+                       "EndMessage"
+               );
+               assertThat(peer.get().get().getIdentity(), is("id1"));
+       }
+
+       @Test
+       public void defaultFcpClientCanDisallowLocalAddressesOfPeer()
+       throws InterruptedException, ExecutionException, IOException {
+               Future<Optional<Peer>> peer = fcpClient.modifyPeer().disallowLocalAddresses().byIdentity("id1").execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "ModifyPeer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=id1",
+                       "AllowLocalAddresses=false",
+                       "EndMessage"
+               ));
+               assertThat(lines, not(contains(startsWith("IsDisabled="))));
+               fcpServer.writeLine(
+                       "Peer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=Friend1",
+                       "identity=id1",
+                       "EndMessage"
+               );
+               assertThat(peer.get().get().getIdentity(), is("id1"));
+       }
+
+       @Test
+       public void defaultFcpClientCanSetBurstOnlyForPeer()
+       throws InterruptedException, ExecutionException, IOException {
+               Future<Optional<Peer>> peer = fcpClient.modifyPeer().setBurstOnly().byIdentity("id1").execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "ModifyPeer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=id1",
+                       "IsBurstOnly=true",
+                       "EndMessage"
+               ));
+               assertThat(lines, not(contains(startsWith("AllowLocalAddresses="))));
+               assertThat(lines, not(contains(startsWith("IsDisabled="))));
+               fcpServer.writeLine(
+                       "Peer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=Friend1",
+                       "identity=id1",
+                       "EndMessage"
+               );
+               assertThat(peer.get().get().getIdentity(), is("id1"));
+       }
+
+       @Test
+       public void defaultFcpClientCanClearBurstOnlyForPeer()
+       throws InterruptedException, ExecutionException, IOException {
+               Future<Optional<Peer>> peer = fcpClient.modifyPeer().clearBurstOnly().byIdentity("id1").execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "ModifyPeer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=id1",
+                       "IsBurstOnly=false",
+                       "EndMessage"
+               ));
+               assertThat(lines, not(contains(startsWith("AllowLocalAddresses="))));
+               assertThat(lines, not(contains(startsWith("IsDisabled="))));
+               fcpServer.writeLine(
+                       "Peer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=Friend1",
+                       "identity=id1",
+                       "EndMessage"
+               );
+               assertThat(peer.get().get().getIdentity(), is("id1"));
+       }
+
+       @Test
+       public void defaultFcpClientCanSetListenOnlyForPeer()
+       throws InterruptedException, ExecutionException, IOException {
+               Future<Optional<Peer>> peer = fcpClient.modifyPeer().setListenOnly().byIdentity("id1").execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "ModifyPeer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=id1",
+                       "IsListenOnly=true",
+                       "EndMessage"
+               ));
+               assertThat(lines, not(contains(startsWith("AllowLocalAddresses="))));
+               assertThat(lines, not(contains(startsWith("IsDisabled="))));
+               assertThat(lines, not(contains(startsWith("IsBurstOnly="))));
+               fcpServer.writeLine(
+                       "Peer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=Friend1",
+                       "identity=id1",
+                       "EndMessage"
+               );
+               assertThat(peer.get().get().getIdentity(), is("id1"));
+       }
+
+       @Test
+       public void defaultFcpClientCanClearListenOnlyForPeer()
+       throws InterruptedException, ExecutionException, IOException {
+               Future<Optional<Peer>> peer = fcpClient.modifyPeer().clearListenOnly().byIdentity("id1").execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "ModifyPeer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=id1",
+                       "IsListenOnly=false",
+                       "EndMessage"
+               ));
+               assertThat(lines, not(contains(startsWith("AllowLocalAddresses="))));
+               assertThat(lines, not(contains(startsWith("IsDisabled="))));
+               assertThat(lines, not(contains(startsWith("IsBurstOnly="))));
+               fcpServer.writeLine(
+                       "Peer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=Friend1",
+                       "identity=id1",
+                       "EndMessage"
+               );
+               assertThat(peer.get().get().getIdentity(), is("id1"));
+       }
+
+       @Test
+       public void defaultFcpClientCanIgnoreSourceForPeer()
+       throws InterruptedException, ExecutionException, IOException {
+               Future<Optional<Peer>> peer = fcpClient.modifyPeer().ignoreSource().byIdentity("id1").execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "ModifyPeer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=id1",
+                       "IgnoreSourcePort=true",
+                       "EndMessage"
+               ));
+               assertThat(lines, not(contains(startsWith("AllowLocalAddresses="))));
+               assertThat(lines, not(contains(startsWith("IsDisabled="))));
+               assertThat(lines, not(contains(startsWith("IsBurstOnly="))));
+               assertThat(lines, not(contains(startsWith("IsListenOnly="))));
+               fcpServer.writeLine(
+                       "Peer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=Friend1",
+                       "identity=id1",
+                       "EndMessage"
+               );
+               assertThat(peer.get().get().getIdentity(), is("id1"));
+       }
+
+       @Test
+       public void defaultFcpClientCanUseSourceForPeer()
+       throws InterruptedException, ExecutionException, IOException {
+               Future<Optional<Peer>> peer = fcpClient.modifyPeer().useSource().byIdentity("id1").execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "ModifyPeer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=id1",
+                       "IgnoreSourcePort=false",
+                       "EndMessage"
+               ));
+               assertThat(lines, not(contains(startsWith("AllowLocalAddresses="))));
+               assertThat(lines, not(contains(startsWith("IsDisabled="))));
+               assertThat(lines, not(contains(startsWith("IsBurstOnly="))));
+               assertThat(lines, not(contains(startsWith("IsListenOnly="))));
+               fcpServer.writeLine(
+                       "Peer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=Friend1",
+                       "identity=id1",
+                       "EndMessage"
+               );
+               assertThat(peer.get().get().getIdentity(), is("id1"));
+       }
+
+       @Test
+       public void defaultFcpClientCanRemovePeerByName() throws InterruptedException, ExecutionException, IOException {
+               Future<Boolean> peer = fcpClient.removePeer().byName("Friend1").execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "RemovePeer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=Friend1",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "PeerRemoved",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=Friend1",
+                       "EndMessage"
+               );
+               assertThat(peer.get(), is(true));
+       }
+
+       @Test
+       public void defaultFcpClientCanNotRemovePeerByInvalidName()
+       throws InterruptedException, ExecutionException, IOException {
+               Future<Boolean> peer = fcpClient.removePeer().byName("NotFriend1").execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "RemovePeer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=NotFriend1",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "UnknownNodeIdentifier",
+                       "Identifier=" + identifier,
+                       "EndMessage"
+               );
+               assertThat(peer.get(), is(false));
+       }
+
+       @Test
+       public void defaultFcpClientCanRemovePeerByIdentity() throws InterruptedException, ExecutionException, IOException {
+               Future<Boolean> peer = fcpClient.removePeer().byIdentity("id1").execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "RemovePeer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=id1",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "PeerRemoved",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=Friend1",
+                       "EndMessage"
+               );
+               assertThat(peer.get(), is(true));
+       }
+
+       @Test
+       public void defaultFcpClientCanRemovePeerByHostAndPort()
+       throws InterruptedException, ExecutionException, IOException {
+               Future<Boolean> peer = fcpClient.removePeer().byHostAndPort("1.2.3.4", 5678).execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "RemovePeer",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=1.2.3.4:5678",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "PeerRemoved",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=Friend1",
+                       "EndMessage"
+               );
+               assertThat(peer.get(), is(true));
+       }
+
+       @Test
+       public void defaultFcpClientCanModifyPeerNoteByName()
+       throws InterruptedException, ExecutionException, IOException {
+               Future<Boolean> noteUpdated = fcpClient.modifyPeerNote().darknetComment("foo").byName("Friend1").execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "ModifyPeerNote",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=Friend1",
+                       "PeerNoteType=1",
+                       "NoteText=Zm9v",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "PeerNote",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=Friend1",
+                       "NoteText=Zm9v",
+                       "PeerNoteType=1",
+                       "EndMessage"
+               );
+               assertThat(noteUpdated.get(), is(true));
+       }
+
+       @Test
+       public void defaultFcpClientKnowsPeerNoteWasNotModifiedOnUnknownNodeIdentifier()
+       throws InterruptedException, ExecutionException, IOException {
+               Future<Boolean> noteUpdated = fcpClient.modifyPeerNote().darknetComment("foo").byName("Friend1").execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "ModifyPeerNote",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=Friend1",
+                       "PeerNoteType=1",
+                       "NoteText=Zm9v",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "UnknownNodeIdentifier",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=Friend1",
+                       "EndMessage"
+               );
+               assertThat(noteUpdated.get(), is(false));
+       }
+
+       @Test
+       public void defaultFcpClientFailsToModifyPeerNoteWithoutPeerNote()
+       throws InterruptedException, ExecutionException, IOException {
+               Future<Boolean> noteUpdated = fcpClient.modifyPeerNote().byName("Friend1").execute();
+               assertThat(noteUpdated.get(), is(false));
+       }
+
+       @Test
+       public void defaultFcpClientCanModifyPeerNoteByIdentifier()
+       throws InterruptedException, ExecutionException, IOException {
+               Future<Boolean> noteUpdated = fcpClient.modifyPeerNote().darknetComment("foo").byIdentifier("id1").execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "ModifyPeerNote",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=id1",
+                       "PeerNoteType=1",
+                       "NoteText=Zm9v",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "PeerNote",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=id1",
+                       "NoteText=Zm9v",
+                       "PeerNoteType=1",
+                       "EndMessage"
+               );
+               assertThat(noteUpdated.get(), is(true));
+       }
+
+       @Test
+       public void defaultFcpClientCanModifyPeerNoteByHostAndPort()
+       throws InterruptedException, ExecutionException, IOException {
+               Future<Boolean> noteUpdated =
+                       fcpClient.modifyPeerNote().darknetComment("foo").byHostAndPort("1.2.3.4", 5678).execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "ModifyPeerNote",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=1.2.3.4:5678",
+                       "PeerNoteType=1",
+                       "NoteText=Zm9v",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "PeerNote",
+                       "Identifier=" + identifier,
+                       "NodeIdentifier=1.2.3.4:5678",
+                       "NoteText=Zm9v",
+                       "PeerNoteType=1",
+                       "EndMessage"
+               );
+               assertThat(noteUpdated.get(), is(true));
+       }
+
+       @Test
+       public void defaultFcpClientCanGetConfigWithoutDetails()
+       throws InterruptedException, ExecutionException, IOException {
+               Future<ConfigData> configData = fcpClient.getConfig().execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "GetConfig",
+                       "Identifier=" + identifier,
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "ConfigData",
+                       "Identifier=" + identifier,
+                       "EndMessage"
+               );
+               assertThat(configData.get(), notNullValue());
+       }
+
+       @Test
+       public void defaultFcpClientCanGetConfigWithCurrent()
+       throws InterruptedException, ExecutionException, IOException {
+               Future<ConfigData> configData = fcpClient.getConfig().withCurrent().execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "GetConfig",
+                       "Identifier=" + identifier,
+                       "WithCurrent=true",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "ConfigData",
+                       "Identifier=" + identifier,
+                       "current.foo=bar",
+                       "EndMessage"
+               );
+               assertThat(configData.get().getCurrent("foo"), is("bar"));
+       }
+
+       @Test
+       public void defaultFcpClientCanGetConfigWithDefaults()
+       throws InterruptedException, ExecutionException, IOException {
+               Future<ConfigData> configData = fcpClient.getConfig().withDefaults().execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "GetConfig",
+                       "Identifier=" + identifier,
+                       "WithDefaults=true",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "ConfigData",
+                       "Identifier=" + identifier,
+                       "default.foo=bar",
+                       "EndMessage"
+               );
+               assertThat(configData.get().getDefault("foo"), is("bar"));
+       }
+
+       @Test
+       public void defaultFcpClientCanGetConfigWithSortOrder()
+       throws InterruptedException, ExecutionException, IOException {
+               Future<ConfigData> configData = fcpClient.getConfig().withSortOrder().execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "GetConfig",
+                       "Identifier=" + identifier,
+                       "WithSortOrder=true",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "ConfigData",
+                       "Identifier=" + identifier,
+                       "sortOrder.foo=17",
+                       "EndMessage"
+               );
+               assertThat(configData.get().getSortOrder("foo"), is(17));
+       }
+
+       @Test
+       public void defaultFcpClientCanGetConfigWithExpertFlag()
+       throws InterruptedException, ExecutionException, IOException {
+               Future<ConfigData> configData = fcpClient.getConfig().withExpertFlag().execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "GetConfig",
+                       "Identifier=" + identifier,
+                       "WithExpertFlag=true",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "ConfigData",
+                       "Identifier=" + identifier,
+                       "expertFlag.foo=true",
+                       "EndMessage"
+               );
+               assertThat(configData.get().getExpertFlag("foo"), is(true));
+       }
+
+       @Test
+       public void defaultFcpClientCanGetConfigWithForceWriteFlag()
+       throws InterruptedException, ExecutionException, IOException {
+               Future<ConfigData> configData = fcpClient.getConfig().withForceWriteFlag().execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "GetConfig",
+                       "Identifier=" + identifier,
+                       "WithForceWriteFlag=true",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "ConfigData",
+                       "Identifier=" + identifier,
+                       "forceWriteFlag.foo=true",
+                       "EndMessage"
+               );
+               assertThat(configData.get().getForceWriteFlag("foo"), is(true));
+       }
+
+       @Test
+       public void defaultFcpClientCanGetConfigWithShortDescription()
+       throws InterruptedException, ExecutionException, IOException {
+               Future<ConfigData> configData = fcpClient.getConfig().withShortDescription().execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "GetConfig",
+                       "Identifier=" + identifier,
+                       "WithShortDescription=true",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "ConfigData",
+                       "Identifier=" + identifier,
+                       "shortDescription.foo=bar",
+                       "EndMessage"
+               );
+               assertThat(configData.get().getShortDescription("foo"), is("bar"));
+       }
+
+       @Test
+       public void defaultFcpClientCanGetConfigWithLongDescription()
+       throws InterruptedException, ExecutionException, IOException {
+               Future<ConfigData> configData = fcpClient.getConfig().withLongDescription().execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "GetConfig",
+                       "Identifier=" + identifier,
+                       "WithLongDescription=true",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "ConfigData",
+                       "Identifier=" + identifier,
+                       "longDescription.foo=bar",
+                       "EndMessage"
+               );
+               assertThat(configData.get().getLongDescription("foo"), is("bar"));
+       }
+
+       @Test
+       public void defaultFcpClientCanGetConfigWithDataTypes()
+       throws InterruptedException, ExecutionException, IOException {
+               Future<ConfigData> configData = fcpClient.getConfig().withDataTypes().execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "GetConfig",
+                       "Identifier=" + identifier,
+                       "WithDataTypes=true",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "ConfigData",
+                       "Identifier=" + identifier,
+                       "dataType.foo=number",
+                       "EndMessage"
+               );
+               assertThat(configData.get().getDataType("foo"), is("number"));
+       }
+
+       @Test
+       public void defaultFcpClientCanModifyConfigData() throws InterruptedException, ExecutionException, IOException {
+               Future<ConfigData> newConfigData = fcpClient.modifyConfig().set("foo.bar").to("baz").execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "ModifyConfig",
+                       "Identifier=" + identifier,
+                       "foo.bar=baz",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "ConfigData",
+                       "Identifier=" + identifier,
+                       "current.foo.bar=baz",
+                       "EndMessage"
+               );
+               assertThat(newConfigData.get().getCurrent("foo.bar"), is("baz"));
+       }
+
+       @Test
+       public void defaultFcpClientCanLoadPluginFromFreenet() throws ExecutionException, InterruptedException,
+       IOException {
+               Future<Optional<PluginInfo>> pluginInfo = fcpClient.loadPlugin().officialFromFreenet("superPlugin").execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "LoadPlugin",
+                       "Identifier=" + identifier,
+                       "PluginURL=superPlugin",
+                       "URLType=official",
+                       "OfficialSource=freenet",
+                       "EndMessage"
+               ));
+               assertThat(lines, not(contains(startsWith("Store="))));
+               fcpServer.writeLine(
+                       "PluginInfo",
+                       "Identifier=" + identifier,
+                       "PluginName=superPlugin",
+                       "IsTalkable=true",
+                       "LongVersion=1.2.3",
+                       "Version=42",
+                       "OriginUri=superPlugin",
+                       "Started=true",
+                       "EndMessage"
+               );
+               assertThat(pluginInfo.get().get().getPluginName(), is("superPlugin"));
+               assertThat(pluginInfo.get().get().getOriginalURI(), is("superPlugin"));
+               assertThat(pluginInfo.get().get().isTalkable(), is(true));
+               assertThat(pluginInfo.get().get().getVersion(), is("42"));
+               assertThat(pluginInfo.get().get().getLongVersion(), is("1.2.3"));
+               assertThat(pluginInfo.get().get().isStarted(), is(true));
+       }
+
+       @Test
+       public void defaultFcpClientCanLoadPersistentPluginFromFreenet() throws ExecutionException, InterruptedException,
+       IOException {
+               Future<Optional<PluginInfo>> pluginInfo =
+                       fcpClient.loadPlugin().addToConfig().officialFromFreenet("superPlugin").execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "LoadPlugin",
+                       "Identifier=" + identifier,
+                       "PluginURL=superPlugin",
+                       "URLType=official",
+                       "OfficialSource=freenet",
+                       "Store=true",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "PluginInfo",
+                       "Identifier=" + identifier,
+                       "PluginName=superPlugin",
+                       "IsTalkable=true",
+                       "LongVersion=1.2.3",
+                       "Version=42",
+                       "OriginUri=superPlugin",
+                       "Started=true",
+                       "EndMessage"
+               );
+               assertThat(pluginInfo.get().get().getPluginName(), is("superPlugin"));
+               assertThat(pluginInfo.get().get().getOriginalURI(), is("superPlugin"));
+               assertThat(pluginInfo.get().get().isTalkable(), is(true));
+               assertThat(pluginInfo.get().get().getVersion(), is("42"));
+               assertThat(pluginInfo.get().get().getLongVersion(), is("1.2.3"));
+               assertThat(pluginInfo.get().get().isStarted(), is(true));
+       }
+
+       @Test
+       public void failedLoadingPluginIsRecognized() throws ExecutionException, InterruptedException,
+       IOException {
+               Future<Optional<PluginInfo>> pluginInfo = fcpClient.loadPlugin().officialFromFreenet("superPlugin").execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               fcpServer.writeLine(
+                       "ProtocolError",
+                       "Identifier=" + identifier,
+                       "EndMessage"
+               );
+               assertThat(pluginInfo.get().isPresent(), is(false));
        }
 
 }