✅ Add test for WebOfTrustPlugin.getIdentitiesByScore()
authorDavid ‘Bombe’ Roden <bombe@freenetproject.org>
Sun, 19 Jan 2025 17:56:50 +0000 (18:56 +0100)
committerDavid ‘Bombe’ Roden <bombe@freenetproject.org>
Sun, 19 Jan 2025 17:57:14 +0000 (18:57 +0100)
Also, extract parsers for the different identity formats of WOT.

src/main/java/net/pterodactylus/fcp/plugin/IdentityGenerator.java [new file with mode: 0644]
src/main/java/net/pterodactylus/fcp/plugin/IdentityParser.java [new file with mode: 0644]
src/main/java/net/pterodactylus/fcp/plugin/IdentityParserV1.java [new file with mode: 0644]
src/main/java/net/pterodactylus/fcp/plugin/IdentityParserV2.java [new file with mode: 0644]
src/main/java/net/pterodactylus/fcp/plugin/WebOfTrustPlugin.java
src/test/java/net/pterodactylus/fcp/plugin/WebOfTrustPluginTest.java

diff --git a/src/main/java/net/pterodactylus/fcp/plugin/IdentityGenerator.java b/src/main/java/net/pterodactylus/fcp/plugin/IdentityGenerator.java
new file mode 100644 (file)
index 0000000..acf9fff
--- /dev/null
@@ -0,0 +1,27 @@
+package net.pterodactylus.fcp.plugin;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Generates an {@link Identity}.
+ *
+ * @param <I> The type of identity to generate
+ */
+@FunctionalInterface
+interface IdentityGenerator<I extends Identity> {
+
+       /**
+        * Generates an identity from the given parameters
+        *
+        * @param identity The ID of the identity
+        * @param nickname The nickname of the identity
+        * @param requestUri The request URI of the identity
+        * @param insertUri The insert URI of the identity
+        * @param contexts The contexts of the identity
+        * @param properties The properties of the identity
+        * @return The generated identity
+        */
+       I createIdentity(String identity, String nickname, String requestUri, String insertUri, Set<String> contexts, Map<String, String> properties);
+
+}
diff --git a/src/main/java/net/pterodactylus/fcp/plugin/IdentityParser.java b/src/main/java/net/pterodactylus/fcp/plugin/IdentityParser.java
new file mode 100644 (file)
index 0000000..a97333f
--- /dev/null
@@ -0,0 +1,43 @@
+package net.pterodactylus.fcp.plugin;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Interface for parsers for WebOfTrust’s on-wire identity formats.
+ */
+interface IdentityParser {
+
+       /**
+        * Whether this parser can parse an identity from the given fields.
+        *
+        * @param fields The fields to parse the identity from
+        * @param prefix A prefix used for all accessed fields, may be an empty String
+        * @return {@code true} if this parse can parse identities from the given
+        *              fields, {@code false} otherwise
+        */
+       boolean canParse(Map<String, String> fields, String prefix);
+
+       /**
+        * Parses an identity from the given fields.
+        *
+        * @param fields The fields to parse the identity from
+        * @param prefix A prefix used for all access fields, may be an empty String
+        * @param identityGenerator An identity generator
+        * @param <I> The type of the identity to parse
+        * @return The generated identity
+        */
+       <I extends Identity> I parseSingleIdentity(Map<String, String> fields, String prefix, IdentityGenerator<I> identityGenerator);
+
+       /**
+        * Parses multiple identities from the given fields.
+        *
+        * @param fields The fields to parse the identities from
+        * @param prefix A prefix used for all identities, may be an empty String
+        * @param identityGenerator An identity generator
+        * @param <I> The type of the identity to parse
+        * @return The generated identities
+        */
+       <I extends Identity> Set<I> parseMultipleIdentities(Map<String, String> fields, String prefix, IdentityGenerator<I> identityGenerator);
+
+}
diff --git a/src/main/java/net/pterodactylus/fcp/plugin/IdentityParserV1.java b/src/main/java/net/pterodactylus/fcp/plugin/IdentityParserV1.java
new file mode 100644 (file)
index 0000000..1824c16
--- /dev/null
@@ -0,0 +1,55 @@
+package net.pterodactylus.fcp.plugin;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+
+/**
+ * {@link IdentityParser} for the first WebOfTrust identity format. This
+ * format is now deprecated for most of the transferred identities, but not
+ * all of them; e.g. {@code GetOwnIdentities} still uses this format.
+ */
+class IdentityParserV1 implements IdentityParser {
+
+       @Override
+       public boolean canParse(Map<String, String> fields, String prefix) {
+               return fields.containsKey(prefix + "Identity0");
+       }
+
+       @Override
+       public <I extends Identity> I parseSingleIdentity(Map<String, String> fields, String prefix, IdentityGenerator<I> identityGenerator) {
+               return parseIdentity(fields, field -> prefix + field, identityGenerator);
+       }
+
+       @Override
+       public <I extends Identity> Set<I> parseMultipleIdentities(Map<String, String> fields, String prefix, IdentityGenerator<I> identityGenerator) {
+               Set<I> identities = new HashSet<>();
+               for (int identityIndex = 0; fields.containsKey(prefix + "Identity" + identityIndex); identityIndex++) {
+                       int index = identityIndex;
+                       identities.add(parseIdentity(fields, field -> prefix + field + index, identityGenerator));
+               }
+               return identities;
+       }
+
+       private static <I extends Identity> I parseIdentity(Map<String, String> fields, Function<String, String> fieldPackager, IdentityGenerator<I> identityGenerator) {
+               String id = fields.get(fieldPackager.apply("Identity"));
+               String name = fields.get(fieldPackager.apply("Nickname"));
+               String requestUri = fields.get(fieldPackager.apply("RequestURI"));
+               String insertUri = fields.get(fieldPackager.apply("InsertURI"));
+               Set<String> contexts = new HashSet<>();
+               for (int contextIndex = 0; fields.containsKey(fieldPackager.apply("Contexts") + ".Context" + contextIndex); contextIndex++) {
+                       contexts.add(fields.get(fieldPackager.apply("Contexts") + ".Context" + contextIndex));
+               }
+               Map<String, String> properties = new HashMap<>();
+               for (int propertyIndex = 0; fields.containsKey(fieldPackager.apply("Properties") + ".Property" + propertyIndex + ".Name"); propertyIndex++) {
+                       String key = fields.get(fieldPackager.apply("Properties") + ".Property" + propertyIndex + ".Name");
+                       String value = fields.get(fieldPackager.apply("Properties") + ".Property" + propertyIndex + ".Value");
+                       properties.put(key, value);
+               }
+               return identityGenerator.createIdentity(id, name, requestUri, insertUri, contexts, properties);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/fcp/plugin/IdentityParserV2.java b/src/main/java/net/pterodactylus/fcp/plugin/IdentityParserV2.java
new file mode 100644 (file)
index 0000000..714009f
--- /dev/null
@@ -0,0 +1,54 @@
+package net.pterodactylus.fcp.plugin;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+
+/**
+ * {@link IdentityParser} for the second (and current) WebOfTrust identity
+ * format. This format is more consequent in how the data is structured, but
+ * it is not yet used for all WebOfTrust messages.
+ */
+class IdentityParserV2 implements IdentityParser {
+
+       @Override
+       public boolean canParse(Map<String, String> fields, String prefix) {
+               return fields.containsKey(prefix + "Identities.0.ID");
+       }
+
+       @Override
+       public <I extends Identity> I parseSingleIdentity(Map<String, String> fields, String prefix, IdentityGenerator<I> identityGenerator) {
+               return parseIdentity(fields, field -> prefix + field, identityGenerator);
+       }
+
+       @Override
+       public <I extends Identity> Set<I> parseMultipleIdentities(Map<String, String> fields, String prefix, IdentityGenerator<I> identityGenerator) {
+               Set<I> identities = new HashSet<>();
+               for (int identityIndex = 0; fields.containsKey(prefix + "Identities." + identityIndex + ".ID"); identityIndex++) {
+                       int index = identityIndex;
+                       identities.add(parseIdentity(fields, field -> prefix + "Identities." + index + "." + field, identityGenerator));
+               }
+               return identities;
+       }
+
+       private <I extends Identity> I parseIdentity(Map<String, String> fields, Function<String, String> fieldPackager, IdentityGenerator<I> identityGenerator) {
+               String id = fields.get(fieldPackager.apply("ID"));
+               String name = fields.get(fieldPackager.apply("Nickname"));
+               String requestUri = fields.get(fieldPackager.apply("RequestURI"));
+               String insertUri = fields.get(fieldPackager.apply("InsertURI"));
+               Set<String> contexts = new HashSet<>();
+               for (int contextIndex = 0; fields.containsKey(fieldPackager.apply("Contexts.") + contextIndex + ".Name"); contextIndex++) {
+                       contexts.add(fields.get(fieldPackager.apply("Contexts.") + contextIndex + ".Name"));
+               }
+               Map<String, String> properties = new HashMap<>();
+               for (int propertyIndex = 0; fields.containsKey(fieldPackager.apply("Properties.") + propertyIndex + ".Name"); propertyIndex++) {
+                       String key = fields.get(fieldPackager.apply("Properties.") + propertyIndex + ".Name");
+                       String value = fields.get(fieldPackager.apply("Properties.") + propertyIndex + ".Value");
+                       properties.put(key, value);
+               }
+               return identityGenerator.createIdentity(id, name, requestUri, insertUri, contexts, properties);
+       }
+
+}
index 6079fa3..bec9dd3 100644 (file)
 
 package net.pterodactylus.fcp.plugin;
 
+import net.pterodactylus.fcp.highlevel.FcpClient;
+import net.pterodactylus.fcp.highlevel.FcpException;
+
 import java.io.IOException;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
-import net.pterodactylus.fcp.highlevel.FcpClient;
-import net.pterodactylus.fcp.highlevel.FcpException;
-
 import static java.util.Collections.emptyMap;
 import static java.util.Collections.emptySet;
 
@@ -243,18 +242,11 @@ public class WebOfTrustPlugin {
         *             if an FCP error occurs
         */
        public Set<Identity> getIdentitesByScore(OwnIdentity ownIdentity, String context, Boolean positive) throws IOException, FcpException {
-               Map<String, String> replies = fcpClient.sendPluginMessage(webOfTrustPluginName, createParameters("Message", "GetIdentitiesByScore", "TreeOwner", ownIdentity.getIdentifier(), "Context", context, "Selection", ((positive == null) ? "0" : (positive ? "+" : "-"))));
+               Map<String, String> replies = fcpClient.sendPluginMessage(webOfTrustPluginName, createParameters("Message", "GetIdentitiesByScore", "Truster", ownIdentity.getIdentifier(), "Context", context, "Selection", ((positive == null) ? "0" : (positive ? "+" : "-"))));
                if (!replies.get("Message").equals("Identities")) {
                        throw new FcpException("WebOfTrust Plugin did not reply with “Identities” message!");
                }
-               Set<Identity> identities = new HashSet<Identity>();
-               for (int identityIndex = 1; replies.containsKey("Identity" + identityIndex); identityIndex++) {
-                       String identifier = replies.get("Identity" + identityIndex);
-                       String nickname = replies.get("Nickname" + identityIndex);
-                       String requestUri = replies.get("RequestURI" + identityIndex);
-                       identities.add(new Identity(identifier, nickname, requestUri, emptySet(), emptyMap()));
-               }
-               return identities;
+               return parseIdentities(replies, WebOfTrustPlugin::createIdentity);
        }
 
        /**
@@ -461,6 +453,22 @@ public class WebOfTrustPlugin {
                return parameterMap;
        }
 
+       private static <I extends Identity> Set<I> parseIdentities(Map<String, String> replies, IdentityGenerator<I> identityGenerator) {
+               IdentityParser parser = v2IdentityParser.canParse(replies, "") ? v2IdentityParser : v1IdentityParser;
+               return parser.parseMultipleIdentities(replies, "", identityGenerator);
+       }
+
+       private static final IdentityParser v1IdentityParser = new IdentityParserV1();
+       private static final IdentityParser v2IdentityParser = new IdentityParserV2();
+
+       private static Identity createIdentity(String identity, String nickname, String requestUri, @SuppressWarnings("unused") String insertUri, Set<String> contexts, Map<String, String> properties) {
+               return new Identity(identity, nickname, requestUri, contexts, properties);
+       }
+
+       private static OwnIdentity createOwnIdentity(String identity, String nickname, String requestUri, String insertUri, Set<String> contexts, Map<String, String> properties) {
+               return new OwnIdentity(identity, nickname, requestUri, insertUri, contexts, properties);
+       }
+
        private static final String webOfTrustPluginName = "plugins.WebOfTrust.WebOfTrust";
 
 }
index ab03b27..695bcb0 100644 (file)
@@ -16,6 +16,7 @@ import org.junit.runner.RunWith;
 import java.io.IOException;
 import java.util.AbstractMap.SimpleEntry;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -374,6 +375,91 @@ public class WebOfTrustPluginTest {
 
        }
 
+       public static class GetIdentitiesByScoreTests extends Common {
+
+               @Test
+               public void getIdentitiesByScoreSendsCorrectMessageWhenPositiveIsNull() throws Exception {
+                       verifySentMessageHasCorrectSelectionValueForPositiveValue(null, "0");
+               }
+
+               @Test
+               public void getIdentitiesByScoreSendsCorrectMessageWhenPositiveIsTrue() throws Exception {
+                       verifySentMessageHasCorrectSelectionValueForPositiveValue(true, "+");
+               }
+
+               @Test
+               public void getIdentitiesByScoreSendsCorrectMessageWhenPositiveIsFalse() throws Exception {
+                       verifySentMessageHasCorrectSelectionValueForPositiveValue(false, "-");
+               }
+
+               private void verifySentMessageHasCorrectSelectionValueForPositiveValue(Boolean positive, String selectionValue) throws Exception {
+                       TestFcpConnection fcpConnection = createConnectionThatSendsIdentitiesByScore();
+                       WebOfTrustPlugin webOfTrustPlugin = createWebOfTrustPlugin(fcpConnection);
+                       OwnIdentity ownIdentity = new OwnIdentity("test-owner", "Test Owner", "request-uri", "insert-uri", emptySet(), emptyMap());
+                       webOfTrustPlugin.getIdentitesByScore(ownIdentity, "test", positive);
+                       assertThat(fcpConnection.sentMessages.get(0), allOf(
+                                       isNamed(equalTo("FCPPluginMessage")),
+                                       hasField("Identifier", notNullValue()),
+                                       hasField("PluginName", equalTo("plugins.WebOfTrust.WebOfTrust")),
+                                       hasField("Param.Message", equalTo("GetIdentitiesByScore")),
+                                       hasField("Param.Truster", equalTo("test-owner")),
+                                       hasField("Param.Context", equalTo("test")),
+                                       hasField("Param.Selection", equalTo(selectionValue))
+                       ));
+               }
+
+               @Test
+               public void getIdentitiesByScoreThrowsExceptionWhenDifferentReplyIsSentByPlugin() {
+                       FcpConnection fcpConnection = createConnectionThatSendsOtherMessage();
+                       WebOfTrustPlugin webOfTrustPlugin = createWebOfTrustPlugin(fcpConnection);
+                       OwnIdentity ownIdentity = new OwnIdentity("test-owner", "Test Owner", "request-uri", "insert-uri", emptySet(), emptyMap());
+                       assertThrows(FcpException.class, () -> webOfTrustPlugin.getIdentitesByScore(ownIdentity, "test", true));
+               }
+
+               @Test
+               public void getIdentitiesByScoreParsesReturnedIdentitiesCorrectly() throws Exception {
+                       TestFcpConnection fcpConnection = createConnectionThatSendsIdentitiesByScore();
+                       WebOfTrustPlugin webOfTrustPlugin = createWebOfTrustPlugin(fcpConnection);
+                       OwnIdentity ownIdentity = new OwnIdentity("test-owner", "Test Owner", "request-uri", "insert-uri", emptySet(), emptyMap());
+                       Collection<Identity> identities = webOfTrustPlugin.getIdentitesByScore(ownIdentity, "test", true);
+                       assertThat(identities, containsInAnyOrder(
+                                       allOf(
+                                                       isIdentity(equalTo("id1"), equalTo("ID 1"), equalTo("request-1")),
+                                                       hasContexts(containsInAnyOrder("test1", "test2")),
+                                                       hasProperties(allOf(
+                                                                       aMapWithSize(2),
+                                                                       hasEntry("prop1", "value1"),
+                                                                       hasEntry("prop2", "value2")
+                                                       ))
+                                       ),
+                                       allOf(
+                                                       isIdentity(equalTo("id2"), equalTo("ID 2"), equalTo("request-2")),
+                                                       hasContexts(containsInAnyOrder("test2", "test3")),
+                                                       hasProperties(allOf(
+                                                                       aMapWithSize(2),
+                                                                       hasEntry("prop3", "value3"),
+                                                                       hasEntry("prop4", "value4")
+                                                       ))
+                                       )
+                       ));
+               }
+
+               private TestFcpConnection createConnectionThatSendsIdentitiesByScore() {
+                       return createConnection("Identities",
+                                       prefixed("Identities.0.", "Type", "Identity", "Nickname", "ID 1", "RequestURI", "request-1", "ID", "id1", "VersionID", "4-4", "PublishesTrustList", "true"),
+                                       prefixed("Identities.0.Contexts.", "0.Name", "test1", "1.Name", "test2", "Amount", "2"),
+                                       prefixed("Identities.0.Properties.", "0.Name", "prop1", "0.Value", "value1", "1.Name", "prop2", "1.Value", "value2", "Amounts", "2"),
+                                       prefixed("Identities.1.", "Type", "Identity", "Nickname", "ID 2", "RequestURI", "request-2", "ID", "id2", "VersionID", "5-5", "PublishesTrustList", "false"),
+                                       prefixed("Identities.1.Contexts.", "0.Name", "test2", "1.Name", "test3", "Amount", "2"),
+                                       prefixed("Identities.1.Properties.", "0.Name", "prop3", "0.Value", "value3", "1.Name", "prop4", "1.Value", "value4", "Amounts", "2"),
+                                       prefixed("Scores.0.", "Truster", "test-owner", "Trustee", "id1", "Capacity", "10", "Rank", "2", "Value", "25", "VersionID", "3-3"),
+                                       prefixed("Scores.1.", "Truster", "test-owner", "Trustee", "id2", "Capacity", "15", "Rank", "1", "Value", "50", "VersionID", "6-6"),
+                                       entries("Scores.Amount", "1", "Identities.Amount", "2")
+                       );
+               }
+
+       }
+
        private static class Common {
 
                @SafeVarargs
@@ -416,14 +502,18 @@ public class WebOfTrustPluginTest {
                        return new TestFcpConnection(messageConsumer);
                }
 
-               protected List<Entry<String, String>> entries(String... replies) {
+               protected List<Entry<String, String>> prefixed(String prefix, String... replies) {
                        List<Entry<String, String>> entries = new ArrayList<>();
                        for (int replyIndex = 0; replyIndex < replies.length - 1; replyIndex += 2) {
-                               entries.add(new SimpleEntry<>(replies[replyIndex], replies[replyIndex + 1]));
+                               entries.add(new SimpleEntry<>(prefix + replies[replyIndex], replies[replyIndex + 1]));
                        }
                        return entries;
                }
 
+               protected List<Entry<String, String>> entries(String... replies) {
+                       return prefixed("", replies);
+               }
+
                @Rule
                public final Timeout timeout = Timeout.seconds(5);