From e3ace0d4e152bfec7b2abc5befe29ce33afccf93 Mon Sep 17 00:00:00 2001 From: =?utf8?q?David=20=E2=80=98Bombe=E2=80=99=20Roden?= Date: Sun, 19 Jan 2025 18:56:50 +0100 Subject: [PATCH] =?utf8?q?=E2=9C=85=20Add=20test=20for=20WebOfTrustPlugin.?= =?utf8?q?getIdentitiesByScore()?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Also, extract parsers for the different identity formats of WOT. --- .../fcp/plugin/IdentityGenerator.java | 27 +++++++ .../pterodactylus/fcp/plugin/IdentityParser.java | 43 ++++++++++ .../pterodactylus/fcp/plugin/IdentityParserV1.java | 55 +++++++++++++ .../pterodactylus/fcp/plugin/IdentityParserV2.java | 54 +++++++++++++ .../pterodactylus/fcp/plugin/WebOfTrustPlugin.java | 34 +++++--- .../fcp/plugin/WebOfTrustPluginTest.java | 94 +++++++++++++++++++++- 6 files changed, 292 insertions(+), 15 deletions(-) create mode 100644 src/main/java/net/pterodactylus/fcp/plugin/IdentityGenerator.java create mode 100644 src/main/java/net/pterodactylus/fcp/plugin/IdentityParser.java create mode 100644 src/main/java/net/pterodactylus/fcp/plugin/IdentityParserV1.java create mode 100644 src/main/java/net/pterodactylus/fcp/plugin/IdentityParserV2.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 index 0000000..acf9fff --- /dev/null +++ b/src/main/java/net/pterodactylus/fcp/plugin/IdentityGenerator.java @@ -0,0 +1,27 @@ +package net.pterodactylus.fcp.plugin; + +import java.util.Map; +import java.util.Set; + +/** + * Generates an {@link Identity}. + * + * @param The type of identity to generate + */ +@FunctionalInterface +interface IdentityGenerator { + + /** + * 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 contexts, Map 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 index 0000000..a97333f --- /dev/null +++ b/src/main/java/net/pterodactylus/fcp/plugin/IdentityParser.java @@ -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 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 The type of the identity to parse + * @return The generated identity + */ + I parseSingleIdentity(Map fields, String prefix, IdentityGenerator 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 The type of the identity to parse + * @return The generated identities + */ + Set parseMultipleIdentities(Map fields, String prefix, IdentityGenerator 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 index 0000000..1824c16 --- /dev/null +++ b/src/main/java/net/pterodactylus/fcp/plugin/IdentityParserV1.java @@ -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 fields, String prefix) { + return fields.containsKey(prefix + "Identity0"); + } + + @Override + public I parseSingleIdentity(Map fields, String prefix, IdentityGenerator identityGenerator) { + return parseIdentity(fields, field -> prefix + field, identityGenerator); + } + + @Override + public Set parseMultipleIdentities(Map fields, String prefix, IdentityGenerator identityGenerator) { + Set 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 parseIdentity(Map fields, Function fieldPackager, IdentityGenerator 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 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 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 index 0000000..714009f --- /dev/null +++ b/src/main/java/net/pterodactylus/fcp/plugin/IdentityParserV2.java @@ -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 fields, String prefix) { + return fields.containsKey(prefix + "Identities.0.ID"); + } + + @Override + public I parseSingleIdentity(Map fields, String prefix, IdentityGenerator identityGenerator) { + return parseIdentity(fields, field -> prefix + field, identityGenerator); + } + + @Override + public Set parseMultipleIdentities(Map fields, String prefix, IdentityGenerator identityGenerator) { + Set 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 parseIdentity(Map fields, Function fieldPackager, IdentityGenerator 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 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 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); + } + +} diff --git a/src/main/java/net/pterodactylus/fcp/plugin/WebOfTrustPlugin.java b/src/main/java/net/pterodactylus/fcp/plugin/WebOfTrustPlugin.java index 6079fa3..bec9dd3 100644 --- a/src/main/java/net/pterodactylus/fcp/plugin/WebOfTrustPlugin.java +++ b/src/main/java/net/pterodactylus/fcp/plugin/WebOfTrustPlugin.java @@ -17,15 +17,14 @@ 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 getIdentitesByScore(OwnIdentity ownIdentity, String context, Boolean positive) throws IOException, FcpException { - Map replies = fcpClient.sendPluginMessage(webOfTrustPluginName, createParameters("Message", "GetIdentitiesByScore", "TreeOwner", ownIdentity.getIdentifier(), "Context", context, "Selection", ((positive == null) ? "0" : (positive ? "+" : "-")))); + Map 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 identities = new HashSet(); - 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 Set parseIdentities(Map replies, IdentityGenerator 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 contexts, Map properties) { + return new Identity(identity, nickname, requestUri, contexts, properties); + } + + private static OwnIdentity createOwnIdentity(String identity, String nickname, String requestUri, String insertUri, Set contexts, Map properties) { + return new OwnIdentity(identity, nickname, requestUri, insertUri, contexts, properties); + } + private static final String webOfTrustPluginName = "plugins.WebOfTrust.WebOfTrust"; } diff --git a/src/test/java/net/pterodactylus/fcp/plugin/WebOfTrustPluginTest.java b/src/test/java/net/pterodactylus/fcp/plugin/WebOfTrustPluginTest.java index ab03b27..695bcb0 100644 --- a/src/test/java/net/pterodactylus/fcp/plugin/WebOfTrustPluginTest.java +++ b/src/test/java/net/pterodactylus/fcp/plugin/WebOfTrustPluginTest.java @@ -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 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> entries(String... replies) { + protected List> prefixed(String prefix, String... replies) { List> 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> entries(String... replies) { + return prefixed("", replies); + } + @Rule public final Timeout timeout = Timeout.seconds(5); -- 2.7.4