From b2788f75951e912f4e50c51d8fc0ed2a0e4a342a Mon Sep 17 00:00:00 2001 From: Simone Kalbermatter Date: Sun, 15 Oct 2023 23:16:49 +0200 Subject: [PATCH 01/10] refactor MultiMsgWebSocketClient --- .../be/features/election/electionEnd.feature | 8 +- .../src/test/java/be/utils/JsonConverter.java | 6 +- .../test/java/be/utils/JsonConverterTest.java | 4 +- .../src/test/java/be/utils/MockClient.java | 18 +++ .../common/net/MultiMsgWebSocketClient.java | 138 +++++++++--------- .../test/java/common/utils/Base64Utils.java | 7 + 6 files changed, 100 insertions(+), 81 deletions(-) diff --git a/tests/karate/src/test/java/be/features/election/electionEnd.feature b/tests/karate/src/test/java/be/features/election/electionEnd.feature index b50d4905fe..5b16051b5e 100644 --- a/tests/karate/src/test/java/be/features/election/electionEnd.feature +++ b/tests/karate/src/test/java/be/features/election/electionEnd.feature @@ -19,7 +19,7 @@ Feature: Terminate an election * def electionEnd = election.close() # After a successful election setup and cast vote sending a valid election end - # message should succeed + # message should succeed (message should be accepted and a broadcast with election results received in return) Scenario: Sending a valid election end should succeed Given def validElectionEnd = """ @@ -33,8 +33,10 @@ Feature: Terminate an election } """ When organizer.publish(validElectionEnd, election.channel) - And json answer = organizer.getBackendResponseWithElectionResults(validElectionEnd) - Then match answer contains ELECTION_RESULTS + And json answer = organizer.getBackendResponse(validElectionEnd) + And json results = organizer.getElectionResults() + And match answer contains VALID_MESSAGE + And match results contains ELECTION_RESULTS And match organizer.receiveNoMoreResponses() == true # After having a successful election setup and vote casts, sending an election end diff --git a/tests/karate/src/test/java/be/utils/JsonConverter.java b/tests/karate/src/test/java/be/utils/JsonConverter.java index af86c92ded..3a986479e8 100644 --- a/tests/karate/src/test/java/be/utils/JsonConverter.java +++ b/tests/karate/src/test/java/be/utils/JsonConverter.java @@ -28,13 +28,13 @@ public JsonConverter(String publicKey, String privateKey){ * Produces a valid Json representation of a message given the message data, the id of the message * and the channel where the message is supposed to be sent */ - public Json publishMessageFromData(String stringData, int id, String channel) { + public Json createPublishMessage(String highLevelMessageData, int messageId, String channel) { Json messageData = Json.object(); messageData.set("method", "publish"); - messageData.set("id", id); + messageData.set("id", messageId); Map paramsPart = new LinkedHashMap<>(); try{ - paramsPart = constructParamsField(channel, stringData); + paramsPart = constructParamsField(channel, highLevelMessageData); messageData.set("params", paramsPart); }catch (GeneralSecurityException e){ e.printStackTrace(); diff --git a/tests/karate/src/test/java/be/utils/JsonConverterTest.java b/tests/karate/src/test/java/be/utils/JsonConverterTest.java index 02d678e7af..35d97202bd 100644 --- a/tests/karate/src/test/java/be/utils/JsonConverterTest.java +++ b/tests/karate/src/test/java/be/utils/JsonConverterTest.java @@ -33,7 +33,7 @@ public void testJsonDataToBase64PrintsInDesiredFormat() { Map testMap = new LinkedHashMap<>(); testMap.put("test1", "test2"); Json testJson = Json.of(testMap); - Json testConverter = jsonConverter.publishMessageFromData(testJson.toString(), 2, "/root"); + Json testConverter = jsonConverter.createPublishMessage(testJson.toString(), 2, "/root"); String jsonString = testConverter.toString(); String result = "{\"method\":\"publish\",\"id\":2,\"params\":{\"channel\":\"/root\",\"message\":{\"data\":\"eyJ0ZXN0MSI6InRlc3QyIn0=\",\"sender\":\"J9fBzJV70Jk5c-i3277Uq4CmeL4t53WDfUghaK0HpeM=\",\"signature\":\"-waobQoP4TyXbTSXG0A8hZ2EPRB--p8G_F_NDerSoOhcBA1BE1JZux98ihvmP8-lG8WifZTx9gSVfWuN2dx2Bw==\",\"message_id\":\"Oj7kLJCLMvQrvBZmW0YyRUDbRX10p4mIg2gw0AuIu3E=\",\"witness_signatures\":[]}},\"jsonrpc\":\"2.0\"}"; assert jsonString.equals(result); @@ -52,7 +52,7 @@ public void testIfMessageFromDataCorrespondsToTrueMessageForValidLao() { @Test public void constructJsonMessageFromDataCorrespondsToTrueJsonMessage() { String laoDataJsonString = constructJsonDataForValidLao().toString(); - Json jsonValidLaoMessage = jsonConverter.publishMessageFromData(laoDataJsonString, 1, "/root"); + Json jsonValidLaoMessage = jsonConverter.createPublishMessage(laoDataJsonString, 1, "/root"); Map validStringMessage = new LinkedHashMap<>(); validStringMessage.put("method", "publish"); validStringMessage.put("id", 1); diff --git a/tests/karate/src/test/java/be/utils/MockClient.java b/tests/karate/src/test/java/be/utils/MockClient.java index 5ac1d641f1..8c2d73f881 100644 --- a/tests/karate/src/test/java/be/utils/MockClient.java +++ b/tests/karate/src/test/java/be/utils/MockClient.java @@ -1,10 +1,12 @@ package be.utils; import be.model.*; +import com.intuit.karate.Json; import com.intuit.karate.http.WebSocketOptions; import com.intuit.karate.Logger; import common.net.MessageQueue; import common.net.MultiMsgWebSocketClient; +import common.utils.Base64Utils; import java.security.GeneralSecurityException; import java.time.Instant; @@ -89,4 +91,20 @@ public Transaction issueCoins(MockClient receiver, long amountToGive) throws Gen transaction.issueInitialCoins(receiver.publicKey, publicKey, privateKey, amountToGive); return transaction; } + + /** + * Checks the contents of the message data of all broadcasts that the client received for election results. + * @return the message data containing the decoded election results, or throws an error if none are found + */ + public String getElectionResults(){ + for (String broadcast : broadcasts) { + // Extract the field params/message/data from the Json and decode it + String messageData = Base64Utils.decodeBase64JsonField(Json.of(broadcast), "params.message.data"); + if (messageData.contains("result") && messageData.contains("election")){ + return messageData; + } + } + assert false; + throw new IllegalArgumentException("No election results where received"); + } } diff --git a/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java b/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java index 394a37ca68..f04e2bd300 100644 --- a/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java +++ b/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java @@ -7,6 +7,7 @@ import com.intuit.karate.Logger; import com.intuit.karate.http.WebSocketClient; import com.intuit.karate.http.WebSocketOptions; +import common.utils.Base64Utils; import java.util.*; import java.util.function.Predicate; @@ -20,9 +21,10 @@ public class MultiMsgWebSocketClient extends WebSocketClient { private final MessageQueue queue; public final Logger logger; - private HashMap idAssociatedWithSentMessages = new HashMap<>(); - private HashMap idAssociatedWithAnswers = new HashMap<>(); - private ArrayList broadcasts = new ArrayList<>(); + /** Map of all the messages and their corresponding message ids that the client sent */ + private final HashMap sentMessageIds = new HashMap<>(); + /** Collects all broadcasts that were received */ + public ArrayList broadcasts = new ArrayList<>(); private final static int TIMEOUT = 5000; @@ -41,70 +43,54 @@ public MultiMsgWebSocketClient(WebSocketOptions options, Logger logger, MessageQ setTextHandler(m -> true); } - public void send(Map jsonDataMap){ - this.send(Json.of(jsonDataMap).toString()); - } - - @Override - public void signal(Object result) { - logger.trace("signal called: {}", result); - queue.onNewMsg(result.toString()); - } - - @Override - public synchronized Object listen(long timeout) { - logger.trace("entered listen wait state"); - String msg = queue.take(); - - if (msg == null) logger.error("listen timed out"); - - return msg; - } - - public MessageBuffer getBuffer() { - return queue; + /** + * JSON messages defined inside features are interpreted as maps from String to Object. + * This method is called directly inside features and is just a wrapper around the send method of WebsocketClient (that takes Strings). + * @param messageData the message to send as a JSON map + * (for example: subscribe and catchup in simpleScenarios.feature). + */ + public void send(Map messageData){ + this.send(mapToJson(messageData)); } - - public void publish(Map jsonDataMap, String channel){ - Json dataJson = Json.of(jsonDataMap); - String data = dataJson.toString(); - Random random = new Random(); - int id = random.nextInt(); - idAssociatedWithSentMessages.put(data, id); - Json request = jsonConverter.publishMessageFromData(data, id, channel); - System.out.println("The final sent request is : " + request.toString()); - this.send(request.toString()); + /** + * JSON messages defined inside features are interpreted as maps from String to Object. + * This method is called directly inside features to send a full publish message given the high-level message data to publish. + * @param highLevelMessageDataMap the high-level message to publish as a JSON map + * (for example: validCreateRollCall and badCreateRollCall in createRollCall.feature). + * @param channel the channel to publish on. + */ + public void publish(Map highLevelMessageDataMap, String channel){ + String highLevelMessageData = mapToJson(highLevelMessageDataMap); + int messageId = new Random().nextInt(); + sentMessageIds.put(highLevelMessageData, messageId); + Json publishMessageJson = jsonConverter.createPublishMessage(highLevelMessageData, messageId, channel); + String publishMessage = publishMessageJson.toString(); + System.out.println("The final sent publish message is : " + publishMessage); + this.send(publishMessage); } - public String getBackendResponse(Map jsonDataMap){ - return getBackendResponseWithOrWithoutBroadcasts(jsonDataMap, false); - } + /** + * Waits to receive the backend answer to a given message and returns the answer. + * @param highLevelMessageDataMap of the message that the answer is expected for. + * @return the answer to the given message or throws an error if there is none. + */ + public String getBackendResponse(Map highLevelMessageDataMap){ + String highLevelMessageData = mapToJson(highLevelMessageDataMap); + assert sentMessageIds.containsKey(highLevelMessageData); + int messageId = sentMessageIds.get(highLevelMessageData); - public String getBackendResponseWithOrWithoutBroadcasts(Map jsonDataMap, boolean withBroadcasts){ - Json dataJson = Json.of(jsonDataMap); - String data = dataJson.toString(); - assert idAssociatedWithSentMessages.containsKey(data); - int idData = idAssociatedWithSentMessages.get(data); - if (idAssociatedWithAnswers.containsKey(idData)){ - String answer = idAssociatedWithAnswers.get(idData); - idAssociatedWithAnswers.remove(idData); - idAssociatedWithSentMessages.remove(data); - return answer; - } String answer = getBuffer().takeTimeout(TIMEOUT); while(answer != null){ if(answer.contains("result") || answer.contains("error")){ Json resultJson = Json.of(answer); - int idResult = resultJson.get("id"); - if (idData == idResult){ - idAssociatedWithSentMessages.remove(data); + int resultId = resultJson.get("id"); + if (messageId == resultId){ + sentMessageIds.remove(highLevelMessageData); return answer; - }else{ - idAssociatedWithAnswers.put(idResult, answer); } } - if (withBroadcasts && answer.contains("broadcast")){ + if (answer.contains("broadcast")){ broadcasts.add(answer); } answer = getBuffer().takeTimeout(TIMEOUT); @@ -113,24 +99,6 @@ public String getBackendResponseWithOrWithoutBroadcasts(Map json throw new IllegalArgumentException("No answer from the backend"); } - public String getBackendResponseWithElectionResults(Map jsonDataMap){ - String answer = getBackendResponseWithOrWithoutBroadcasts(jsonDataMap, true); - Base64.Decoder decoder = Base64.getDecoder(); - for (String broadcast : broadcasts) { - String base64Data = - (((LinkedHashMap) - ((LinkedHashMap) Json.of(broadcast).get("params")).get("message")) - .get("data") - .toString()); - String broadcastData = new String(decoder.decode(base64Data.getBytes())); - if (broadcastData.contains("result")){ - return broadcastData; - } - } - assert false; - throw new IllegalArgumentException("No election results where received"); - } - /** * Retrieves all messages with the specified method type from the messages buffer. * @param method The method type to filter the messages by. @@ -150,6 +118,10 @@ public List getMessagesByMethod(String method) { return messages; } + public MessageBuffer getBuffer() { + return queue; + } + public boolean receiveNoMoreResponses(){ String result = getBuffer().takeTimeout(TIMEOUT); return result == null; @@ -171,4 +143,24 @@ public void useWrongSignature() { logger.info("setting wrong signature: " + wrongSignature); jsonConverter.setSignature(wrongSignature); } + + @Override + public void signal(Object result) { + logger.trace("signal called: {}", result); + queue.onNewMsg(result.toString()); + } + + @Override + public synchronized Object listen(long timeout) { + logger.trace("entered listen wait state"); + String msg = queue.take(); + + if (msg == null) logger.error("listen timed out"); + + return msg; + } + + private String mapToJson(Map jsonAsMap){ + return Json.of(jsonAsMap).toString(); + } } diff --git a/tests/karate/src/test/java/common/utils/Base64Utils.java b/tests/karate/src/test/java/common/utils/Base64Utils.java index 0bcb54bcda..8d7550f52a 100644 --- a/tests/karate/src/test/java/common/utils/Base64Utils.java +++ b/tests/karate/src/test/java/common/utils/Base64Utils.java @@ -22,9 +22,16 @@ public static byte[] decode(String data) { return Base64.getUrlDecoder().decode(data); } + /** Produces the base64 variant of the json file passed as argument */ public static String convertJsonToBase64(Json json) { String stringJson = json.toString(); return Base64Utils.encode(stringJson.getBytes()); } + + /** Extracts the Base64 encoded data of a (possibly nested) field in a Json and decodes it */ + public static String decodeBase64JsonField(Json json, String fieldPath) { + String base64Data = json.get(fieldPath); + return new String(decode(base64Data)); + } } From 729321286c4d1e9237af32730ea330bb24349533 Mon Sep 17 00:00:00 2001 From: Simone Kalbermatter Date: Sun, 29 Oct 2023 13:10:46 +0100 Subject: [PATCH 02/10] refactor json converter --- .../src/test/java/be/utils/JsonConverter.java | 68 +++++++++++-------- .../test/java/be/utils/JsonConverterTest.java | 4 +- .../src/test/java/be/utils/MockClient.java | 3 +- .../common/net/MultiMsgWebSocketClient.java | 29 ++++---- 4 files changed, 56 insertions(+), 48 deletions(-) diff --git a/tests/karate/src/test/java/be/utils/JsonConverter.java b/tests/karate/src/test/java/be/utils/JsonConverter.java index 3a986479e8..65eb6d6c11 100644 --- a/tests/karate/src/test/java/be/utils/JsonConverter.java +++ b/tests/karate/src/test/java/be/utils/JsonConverter.java @@ -25,30 +25,48 @@ public JsonConverter(String publicKey, String privateKey){ } /** - * Produces a valid Json representation of a message given the message data, the id of the message + * Produces a valid Json representation of a publish message given the high level message data, the id of the message * and the channel where the message is supposed to be sent */ - public Json createPublishMessage(String highLevelMessageData, int messageId, String channel) { - Json messageData = Json.object(); - messageData.set("method", "publish"); - messageData.set("id", messageId); - Map paramsPart = new LinkedHashMap<>(); + public Json constructPublishMessage(String highLevelMessageData, int messageId, String channel) { + Json publishMessage = Json.object(); + publishMessage.set("jsonrpc", "2.0"); + publishMessage.set("id", messageId); + publishMessage.set("method", "publish"); + + Map paramsField = new LinkedHashMap<>(); try{ - paramsPart = constructParamsField(channel, highLevelMessageData); - messageData.set("params", paramsPart); + paramsField = constructParamsField(channel, highLevelMessageData); + publishMessage.set("params", paramsField); }catch (GeneralSecurityException e){ e.printStackTrace(); } - messageData.set("jsonrpc", "2.0"); + return publishMessage; + } + + /** + * Creates a JSON map of the params field of a publish message. + * Consists of sub-fields channel and message. + */ + public Map constructParamsField(String channel, String highLevelMessageData) throws GeneralSecurityException { + Map paramsField = new LinkedHashMap<>(); + Map messageField = constructMessageField(highLevelMessageData); + + paramsField.put("channel", channel); + paramsField.put("message", messageField); - return messageData; + return paramsField; } - public Map constructMessageField(String stringData) throws GeneralSecurityException{ - Map messagePart = new LinkedHashMap<>(); - Json messageData = Json.of(stringData); + /** + * Creates a JSON map of the message field of a publish message. + * Consists of sub-fields data, sender, signature, message id and witness signatures. + */ + public Map constructMessageField(String highLevelMessageData) throws GeneralSecurityException{ + Map messageField = new LinkedHashMap<>(); + Json messageData = Json.of(highLevelMessageData); String messageDataBase64 = Base64Utils.convertJsonToBase64(messageData); - String signature = constructSignature(stringData); + String signature = constructSignature(highLevelMessageData); if(isSignatureForced){ signature = this.signatureForced; isSignatureForced = false; @@ -56,23 +74,13 @@ public Map constructMessageField(String stringData) throws Gener String messageId = Hash.hash(messageDataBase64.getBytes(), signature.getBytes()); String[] witness = new String[0]; - messagePart.put("data", messageDataBase64); - messagePart.put("sender", publicKey); - messagePart.put("signature", signature); - messagePart.put("message_id", messageId); - messagePart.put("witness_signatures", witness); - - return messagePart; - } - - public Map constructParamsField(String channel, String messageDataBase64) throws GeneralSecurityException { - Map paramsPart = new LinkedHashMap<>(); - Map messagePart = constructMessageField(messageDataBase64); - - paramsPart.put("channel", channel); - paramsPart.put("message", messagePart); + messageField.put("data", messageDataBase64); + messageField.put("sender", publicKey); + messageField.put("signature", signature); + messageField.put("message_id", messageId); + messageField.put("witness_signatures", witness); - return paramsPart; + return messageField; } /** Constructs a valid signature on given data */ diff --git a/tests/karate/src/test/java/be/utils/JsonConverterTest.java b/tests/karate/src/test/java/be/utils/JsonConverterTest.java index 35d97202bd..86f7366df0 100644 --- a/tests/karate/src/test/java/be/utils/JsonConverterTest.java +++ b/tests/karate/src/test/java/be/utils/JsonConverterTest.java @@ -33,7 +33,7 @@ public void testJsonDataToBase64PrintsInDesiredFormat() { Map testMap = new LinkedHashMap<>(); testMap.put("test1", "test2"); Json testJson = Json.of(testMap); - Json testConverter = jsonConverter.createPublishMessage(testJson.toString(), 2, "/root"); + Json testConverter = jsonConverter.constructPublishMessage(testJson.toString(), 2, "/root"); String jsonString = testConverter.toString(); String result = "{\"method\":\"publish\",\"id\":2,\"params\":{\"channel\":\"/root\",\"message\":{\"data\":\"eyJ0ZXN0MSI6InRlc3QyIn0=\",\"sender\":\"J9fBzJV70Jk5c-i3277Uq4CmeL4t53WDfUghaK0HpeM=\",\"signature\":\"-waobQoP4TyXbTSXG0A8hZ2EPRB--p8G_F_NDerSoOhcBA1BE1JZux98ihvmP8-lG8WifZTx9gSVfWuN2dx2Bw==\",\"message_id\":\"Oj7kLJCLMvQrvBZmW0YyRUDbRX10p4mIg2gw0AuIu3E=\",\"witness_signatures\":[]}},\"jsonrpc\":\"2.0\"}"; assert jsonString.equals(result); @@ -52,7 +52,7 @@ public void testIfMessageFromDataCorrespondsToTrueMessageForValidLao() { @Test public void constructJsonMessageFromDataCorrespondsToTrueJsonMessage() { String laoDataJsonString = constructJsonDataForValidLao().toString(); - Json jsonValidLaoMessage = jsonConverter.createPublishMessage(laoDataJsonString, 1, "/root"); + Json jsonValidLaoMessage = jsonConverter.constructPublishMessage(laoDataJsonString, 1, "/root"); Map validStringMessage = new LinkedHashMap<>(); validStringMessage.put("method", "publish"); validStringMessage.put("id", 1); diff --git a/tests/karate/src/test/java/be/utils/MockClient.java b/tests/karate/src/test/java/be/utils/MockClient.java index 8c2d73f881..904440f346 100644 --- a/tests/karate/src/test/java/be/utils/MockClient.java +++ b/tests/karate/src/test/java/be/utils/MockClient.java @@ -97,10 +97,11 @@ public Transaction issueCoins(MockClient receiver, long amountToGive) throws Gen * @return the message data containing the decoded election results, or throws an error if none are found */ public String getElectionResults(){ - for (String broadcast : broadcasts) { + for (String broadcast : receivedBroadcasts) { // Extract the field params/message/data from the Json and decode it String messageData = Base64Utils.decodeBase64JsonField(Json.of(broadcast), "params.message.data"); if (messageData.contains("result") && messageData.contains("election")){ + System.out.println("Received election results: " + messageData); return messageData; } } diff --git a/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java b/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java index f04e2bd300..20186c04b2 100644 --- a/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java +++ b/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java @@ -7,7 +7,6 @@ import com.intuit.karate.Logger; import com.intuit.karate.http.WebSocketClient; import com.intuit.karate.http.WebSocketOptions; -import common.utils.Base64Utils; import java.util.*; import java.util.function.Predicate; @@ -22,9 +21,9 @@ public class MultiMsgWebSocketClient extends WebSocketClient { public final Logger logger; /** Map of all the messages and their corresponding message ids that the client sent */ - private final HashMap sentMessageIds = new HashMap<>(); + private final HashMap sentMessages = new HashMap<>(); /** Collects all broadcasts that were received */ - public ArrayList broadcasts = new ArrayList<>(); + public ArrayList receivedBroadcasts = new ArrayList<>(); private final static int TIMEOUT = 5000; @@ -50,23 +49,23 @@ public MultiMsgWebSocketClient(WebSocketOptions options, Logger logger, MessageQ * (for example: subscribe and catchup in simpleScenarios.feature). */ public void send(Map messageData){ - this.send(mapToJson(messageData)); + this.send(mapToJsonString(messageData)); } /** * JSON messages defined inside features are interpreted as maps from String to Object. - * This method is called directly inside features to send a full publish message given the high-level message data to publish. + * This method is called directly inside features to send a complete publish message given the high-level message data to publish. * @param highLevelMessageDataMap the high-level message to publish as a JSON map * (for example: validCreateRollCall and badCreateRollCall in createRollCall.feature). * @param channel the channel to publish on. */ public void publish(Map highLevelMessageDataMap, String channel){ - String highLevelMessageData = mapToJson(highLevelMessageDataMap); + String highLevelMessageData = mapToJsonString(highLevelMessageDataMap); int messageId = new Random().nextInt(); - sentMessageIds.put(highLevelMessageData, messageId); - Json publishMessageJson = jsonConverter.createPublishMessage(highLevelMessageData, messageId, channel); + sentMessages.put(highLevelMessageData, messageId); + Json publishMessageJson = jsonConverter.constructPublishMessage(highLevelMessageData, messageId, channel); String publishMessage = publishMessageJson.toString(); - System.out.println("The final sent publish message is : " + publishMessage); + System.out.println("The complete publish message sent is : " + publishMessage); this.send(publishMessage); } @@ -76,9 +75,9 @@ public void publish(Map highLevelMessageDataMap, String channel) * @return the answer to the given message or throws an error if there is none. */ public String getBackendResponse(Map highLevelMessageDataMap){ - String highLevelMessageData = mapToJson(highLevelMessageDataMap); - assert sentMessageIds.containsKey(highLevelMessageData); - int messageId = sentMessageIds.get(highLevelMessageData); + String highLevelMessageData = mapToJsonString(highLevelMessageDataMap); + assert sentMessages.containsKey(highLevelMessageData); + int messageId = sentMessages.get(highLevelMessageData); String answer = getBuffer().takeTimeout(TIMEOUT); while(answer != null){ @@ -86,12 +85,12 @@ public String getBackendResponse(Map highLevelMessageDataMap){ Json resultJson = Json.of(answer); int resultId = resultJson.get("id"); if (messageId == resultId){ - sentMessageIds.remove(highLevelMessageData); + sentMessages.remove(highLevelMessageData); return answer; } } if (answer.contains("broadcast")){ - broadcasts.add(answer); + receivedBroadcasts.add(answer); } answer = getBuffer().takeTimeout(TIMEOUT); } @@ -160,7 +159,7 @@ public synchronized Object listen(long timeout) { return msg; } - private String mapToJson(Map jsonAsMap){ + private String mapToJsonString(Map jsonAsMap){ return Json.of(jsonAsMap).toString(); } } From 0a200b7d698ca90d1b6a97bcb32d57dac001f614 Mon Sep 17 00:00:00 2001 From: Simone Kalbermatter Date: Sun, 5 Nov 2023 14:13:08 +0100 Subject: [PATCH 03/10] fix typo and negative message ids --- tests/karate/src/test/java/be/utils/MockClient.java | 2 +- .../src/test/java/common/net/MultiMsgWebSocketClient.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/karate/src/test/java/be/utils/MockClient.java b/tests/karate/src/test/java/be/utils/MockClient.java index 904440f346..6d10829964 100644 --- a/tests/karate/src/test/java/be/utils/MockClient.java +++ b/tests/karate/src/test/java/be/utils/MockClient.java @@ -106,6 +106,6 @@ public String getElectionResults(){ } } assert false; - throw new IllegalArgumentException("No election results where received"); + throw new IllegalArgumentException("No election results were received"); } } diff --git a/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java b/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java index 20186c04b2..931066120b 100644 --- a/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java +++ b/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java @@ -61,7 +61,7 @@ public void send(Map messageData){ */ public void publish(Map highLevelMessageDataMap, String channel){ String highLevelMessageData = mapToJsonString(highLevelMessageDataMap); - int messageId = new Random().nextInt(); + int messageId = new Random().nextInt(Integer.MAX_VALUE); sentMessages.put(highLevelMessageData, messageId); Json publishMessageJson = jsonConverter.constructPublishMessage(highLevelMessageData, messageId, channel); String publishMessage = publishMessageJson.toString(); From 2cc68085f4ce208c8a2cbac8e4c873c477ae48bd Mon Sep 17 00:00:00 2001 From: Simone Kalbermatter Date: Sun, 5 Nov 2023 16:25:50 +0100 Subject: [PATCH 04/10] improve hearbeat response checks --- .../decentralizedCom/heartbeat.feature | 11 +++-- .../common/net/MultiMsgWebSocketClient.java | 45 ++++++++++++++++--- 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/tests/karate/src/test/java/be/features/decentralizedCom/heartbeat.feature b/tests/karate/src/test/java/be/features/decentralizedCom/heartbeat.feature index 3c1f362b27..48c952adae 100644 --- a/tests/karate/src/test/java/be/features/decentralizedCom/heartbeat.feature +++ b/tests/karate/src/test/java/be/features/decentralizedCom/heartbeat.feature @@ -17,13 +17,14 @@ Feature: Send heartbeats to other servers # (lao creation, subscribe, catchup) * call read(createLaoScenario) { organizer: '#(mockServer)', lao: '#(lao)' } - # After lao creation, wait and do nothing (30 seconds for now) and check that a heartbeat message was received + # After lao creation, wait and do nothing (40 seconds for now) and check that more than one heartbeat message was received. + # (The initial one would be a response to publishing lao creation) Scenario: Server should send heartbeat messages automatically after a time interval - Given wait(30) + Given wait(40) When def heartbeatMessages = mockServer.getMessagesByMethod('heartbeat') - Then assert heartbeatMessages.length > 0 + Then assert heartbeatMessages.length > 1 # Check that after receiving a publish message (in this case a create roll call), the server sends a heartbeat Scenario: Server should send heartbeat messages after receiving a publish @@ -43,6 +44,4 @@ Feature: Send heartbeats to other servers """ When mockServer.publish(validCreateRollCall, lao.channel) - And def heartbeatMessages = mockServer.getMessagesByMethod('heartbeat') - - Then assert heartbeatMessages.length == 1 + Then assert mockServer.receivedHeartbeatContainingMessageId(validCreateRollCall) diff --git a/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java b/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java index 931066120b..c48cc7ed0b 100644 --- a/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java +++ b/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java @@ -21,9 +21,11 @@ public class MultiMsgWebSocketClient extends WebSocketClient { public final Logger logger; /** Map of all the messages and their corresponding message ids that the client sent */ - private final HashMap sentMessages = new HashMap<>(); + private final HashMap sentMessages = new HashMap<>(); /** Collects all broadcasts that were received */ - public ArrayList receivedBroadcasts = new ArrayList<>(); + public List receivedBroadcasts = new ArrayList<>(); + /** Collects all heartbeats that were received */ + public List receivedHeartbeats = new ArrayList<>(); private final static int TIMEOUT = 5000; @@ -62,22 +64,24 @@ public void send(Map messageData){ public void publish(Map highLevelMessageDataMap, String channel){ String highLevelMessageData = mapToJsonString(highLevelMessageDataMap); int messageId = new Random().nextInt(Integer.MAX_VALUE); - sentMessages.put(highLevelMessageData, messageId); Json publishMessageJson = jsonConverter.constructPublishMessage(highLevelMessageData, messageId, channel); + PublishMessageIds publishMessageIds = new PublishMessageIds(messageId, publishMessageJson.get("params.message.message_id")); + sentMessages.put(highLevelMessageData, publishMessageIds); String publishMessage = publishMessageJson.toString(); System.out.println("The complete publish message sent is : " + publishMessage); this.send(publishMessage); } /** - * Waits to receive the backend answer to a given message and returns the answer. + * Retrieves the backend answer to a given message and returns it. + * Also stores broadcasts that were received while waiting for an answer. * @param highLevelMessageDataMap of the message that the answer is expected for. * @return the answer to the given message or throws an error if there is none. */ public String getBackendResponse(Map highLevelMessageDataMap){ String highLevelMessageData = mapToJsonString(highLevelMessageDataMap); assert sentMessages.containsKey(highLevelMessageData); - int messageId = sentMessages.get(highLevelMessageData); + int messageId = sentMessages.get(highLevelMessageData).id; String answer = getBuffer().takeTimeout(TIMEOUT); while(answer != null){ @@ -85,7 +89,6 @@ public String getBackendResponse(Map highLevelMessageDataMap){ Json resultJson = Json.of(answer); int resultId = resultJson.get("id"); if (messageId == resultId){ - sentMessages.remove(highLevelMessageData); return answer; } } @@ -117,6 +120,22 @@ public List getMessagesByMethod(String method) { return messages; } + /** + * Checks if a heartbeat containing the message_id corresponding to a given high level message was received. + */ + public boolean receivedHeartbeatContainingMessageId(Map highLevelMessageDataMap){ + receivedHeartbeats = getMessagesByMethod("heartbeat"); + String highLevelMessageData = mapToJsonString(highLevelMessageDataMap); + String message_id = sentMessages.get(highLevelMessageData).message_id; + for(String heartbeat : receivedHeartbeats){ + if(heartbeat.contains(message_id)){ + System.out.println("Found a heartbeat containing message_id " + message_id); + return true; + } + } + return false; + } + public MessageBuffer getBuffer() { return queue; } @@ -162,4 +181,18 @@ public synchronized Object listen(long timeout) { private String mapToJsonString(Map jsonAsMap){ return Json.of(jsonAsMap).toString(); } + + /** A publish message has two ids: + * The id field, a random integer: It is used to match sent query messages to the corresponding backend answer. + * The params/message/message_id field, a String: The hash of the base64 message data and the signature, + * computed in {@link JsonConverter#constructMessageField(String)} */ + private static class PublishMessageIds { + public int id; + public String message_id; + + public PublishMessageIds(int id, String message_id){ + this.id = id; + this.message_id = message_id; + } + } } From 43bb4fc74552baf0b8892b7d49239bdfc4ca906c Mon Sep 17 00:00:00 2001 From: Simone Kalbermatter Date: Sun, 5 Nov 2023 21:38:00 +0100 Subject: [PATCH 05/10] add greet server simple scenario --- .../decentralizedCom/heartbeat.feature | 10 ++++++---- .../java/be/features/utils/constants.feature | 1 + .../be/features/utils/simpleScenarios.feature | 19 +++++++++++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/tests/karate/src/test/java/be/features/decentralizedCom/heartbeat.feature b/tests/karate/src/test/java/be/features/decentralizedCom/heartbeat.feature index 48c952adae..8f9e714f78 100644 --- a/tests/karate/src/test/java/be/features/decentralizedCom/heartbeat.feature +++ b/tests/karate/src/test/java/be/features/decentralizedCom/heartbeat.feature @@ -10,12 +10,14 @@ Feature: Send heartbeats to other servers * call read(serverFeature) * call read(mockClientFeature) * def mockServer = call createMockClient - * def lao = mockServer.createValidLao() - * def validRollCall = mockServer.createValidRollCall(lao) + * def mockFrontend = call createMockClient + * def lao = mockFrontend.createValidLao() + * def validRollCall = mockFrontend.createValidRollCall(lao) # This call executes all the steps to create a valid lao on the server before every scenario # (lao creation, subscribe, catchup) - * call read(createLaoScenario) { organizer: '#(mockServer)', lao: '#(lao)' } + * call read(createLaoScenario) { organizer: '#(mockFrontend)', lao: '#(lao)' } + * call read(greetServerScenario) { mockServer: '#(mockServer)' } # After lao creation, wait and do nothing (40 seconds for now) and check that more than one heartbeat message was received. # (The initial one would be a response to publishing lao creation) @@ -43,5 +45,5 @@ Feature: Send heartbeats to other servers } """ - When mockServer.publish(validCreateRollCall, lao.channel) + When mockFrontend.publish(validCreateRollCall, lao.channel) Then assert mockServer.receivedHeartbeatContainingMessageId(validCreateRollCall) diff --git a/tests/karate/src/test/java/be/features/utils/constants.feature b/tests/karate/src/test/java/be/features/utils/constants.feature index eec410170d..a673d13fdf 100644 --- a/tests/karate/src/test/java/be/features/utils/constants.feature +++ b/tests/karate/src/test/java/be/features/utils/constants.feature @@ -29,3 +29,4 @@ Feature: Constants * def castVoteScenario = simpleScenario + 'cast_vote' * def setupCoinChannelScenario = simpleScenario + 'setup_coin_channel' * def validCoinIssuanceScenario = simpleScenario + 'valid_coin_issuance' + * def greetServerScenario = simpleScenario + 'greet_server' diff --git a/tests/karate/src/test/java/be/features/utils/simpleScenarios.feature b/tests/karate/src/test/java/be/features/utils/simpleScenarios.feature index d561dcffb6..acfe26d32a 100644 --- a/tests/karate/src/test/java/be/features/utils/simpleScenarios.feature +++ b/tests/karate/src/test/java/be/features/utils/simpleScenarios.feature @@ -298,3 +298,22 @@ * karate.log("sending a transaction to issue coins :\n", karate.pretty(validTransaction)) * organizer.publish(validTransaction, lao.cashChannel) * json answer = organizer.getBackendResponse(validTransaction) + + # mockServer needs to be passed as argument when calling this scenario + @name=greet_server + Scenario: Sends a greet server message + Given def greetServer = + """ + { + "method": "greet_server", + "params": { + "public_key": '#(mockServer.publicKey)', + "client_address": '#(wsURL)', + "server_address": '#(wsURL)' + }, + "jsonrpc": "2.0" + } + """ + * karate.log("sending a greet_server\n", karate.pretty(greetServer)) + * mockServer.send(greetServer) + * def subs = mockServer.takeTimeout(timeout) From 4a8acb371ac421a72e3a75067e57f64c3a857ef3 Mon Sep 17 00:00:00 2001 From: Simone Kalbermatter Date: Mon, 27 Nov 2023 16:51:16 +0100 Subject: [PATCH 06/10] check message ids in heartbeats --- .../decentralizedCom/getMessagesById.feature | 6 +- .../decentralizedCom/heartbeat.feature | 9 +- .../common/net/MultiMsgWebSocketClient.java | 86 ++++++++++++------- 3 files changed, 62 insertions(+), 39 deletions(-) diff --git a/tests/karate/src/test/java/be/features/decentralizedCom/getMessagesById.feature b/tests/karate/src/test/java/be/features/decentralizedCom/getMessagesById.feature index 1fdeada9cb..16f76ef348 100644 --- a/tests/karate/src/test/java/be/features/decentralizedCom/getMessagesById.feature +++ b/tests/karate/src/test/java/be/features/decentralizedCom/getMessagesById.feature @@ -32,7 +32,7 @@ Feature: Request messages by id from other servers Given eval heartbeat.params[lao.channel] = messageIds When mockServer.send(heartbeat) - And def getMessagesByIdMessages = mockServer.getMessagesByMethod('get_messages_by_id') + And def getMessagesByIdMessages = mockServer.getGetMessagesById() Then assert getMessagesByIdMessages.length == 1 And match getMessagesByIdMessages[0] contains randomMessageId @@ -43,7 +43,7 @@ Feature: Request messages by id from other servers Given eval heartbeat.params[lao.id] = messageIds When mockServer.send(heartbeat) - And def getMessagesByIdMessages = mockServer.getMessagesByMethod('get_messages_by_id') + And def getMessagesByIdMessages = mockServer.getGetMessagesById() Then assert getMessagesByIdMessages.length == 0 @@ -54,7 +54,7 @@ Feature: Request messages by id from other servers And eval heartbeat.params[lao.channel] = invalidMessageIds When mockServer.send(heartbeat) - And def getMessagesByIdMessages = mockServer.getMessagesByMethod('get_messages_by_id') + And def getMessagesByIdMessages = mockServer.getGetMessagesById() Then assert getMessagesByIdMessages.length == 0 diff --git a/tests/karate/src/test/java/be/features/decentralizedCom/heartbeat.feature b/tests/karate/src/test/java/be/features/decentralizedCom/heartbeat.feature index 8f9e714f78..58054084de 100644 --- a/tests/karate/src/test/java/be/features/decentralizedCom/heartbeat.feature +++ b/tests/karate/src/test/java/be/features/decentralizedCom/heartbeat.feature @@ -17,18 +17,18 @@ Feature: Send heartbeats to other servers # This call executes all the steps to create a valid lao on the server before every scenario # (lao creation, subscribe, catchup) * call read(createLaoScenario) { organizer: '#(mockFrontend)', lao: '#(lao)' } - * call read(greetServerScenario) { mockServer: '#(mockServer)' } # After lao creation, wait and do nothing (40 seconds for now) and check that more than one heartbeat message was received. # (The initial one would be a response to publishing lao creation) Scenario: Server should send heartbeat messages automatically after a time interval Given wait(40) - When def heartbeatMessages = mockServer.getMessagesByMethod('heartbeat') + When def heartbeatMessages = mockServer.getHeartbeats() Then assert heartbeatMessages.length > 1 - # Check that after receiving a publish message (in this case a create roll call), the server sends a heartbeat + # Check that after receiving a publish message (in this case a create roll call), the server sends a heartbeat containing + # the message id of that publish message. Scenario: Server should send heartbeat messages after receiving a publish Given def validCreateRollCall = """ @@ -46,4 +46,5 @@ Feature: Send heartbeats to other servers """ When mockFrontend.publish(validCreateRollCall, lao.channel) - Then assert mockServer.receivedHeartbeatContainingMessageId(validCreateRollCall) + And def message_id = mockFrontend.getPublishMessageId(validCreateRollCall) + Then assert mockServer.receivedHeartbeatWithSubstring(message_id) diff --git a/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java b/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java index c48cc7ed0b..3c9546be8d 100644 --- a/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java +++ b/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java @@ -20,13 +20,10 @@ public class MultiMsgWebSocketClient extends WebSocketClient { private final MessageQueue queue; public final Logger logger; - /** Map of all the messages and their corresponding message ids that the client sent */ + /** Map of all the messages and their corresponding message ids that this client sent */ private final HashMap sentMessages = new HashMap<>(); /** Collects all broadcasts that were received */ public List receivedBroadcasts = new ArrayList<>(); - /** Collects all heartbeats that were received */ - public List receivedHeartbeats = new ArrayList<>(); - private final static int TIMEOUT = 5000; @@ -81,14 +78,14 @@ public void publish(Map highLevelMessageDataMap, String channel) public String getBackendResponse(Map highLevelMessageDataMap){ String highLevelMessageData = mapToJsonString(highLevelMessageDataMap); assert sentMessages.containsKey(highLevelMessageData); - int messageId = sentMessages.get(highLevelMessageData).id; + int id = sentMessages.get(highLevelMessageData).id; String answer = getBuffer().takeTimeout(TIMEOUT); while(answer != null){ if(answer.contains("result") || answer.contains("error")){ Json resultJson = Json.of(answer); int resultId = resultJson.get("id"); - if (messageId == resultId){ + if (id == resultId){ return answer; } } @@ -101,41 +98,38 @@ public String getBackendResponse(Map highLevelMessageDataMap){ throw new IllegalArgumentException("No answer from the backend"); } - /** - * Retrieves all messages with the specified method type from the messages buffer. - * @param method The method type to filter the messages by. - * @return A list containing all received messages that match the specified method type. - */ - public List getMessagesByMethod(String method) { - List messages = new ArrayList<>(); - Predicate filter = MessageFilters.withMethod(method); + public List getHeartbeats(){ + return getMessagesByMethod("heartbeat"); + } - String message = getBuffer().takeTimeout(TIMEOUT); - while (message != null) { - if (filter.test(message)) { - messages.add(message); - } - message = getBuffer().takeTimeout(TIMEOUT); - } - return messages; + public List getGetMessagesById(){ + return getMessagesByMethod("get_messages_by_id"); } - /** - * Checks if a heartbeat containing the message_id corresponding to a given high level message was received. - */ - public boolean receivedHeartbeatContainingMessageId(Map highLevelMessageDataMap){ - receivedHeartbeats = getMessagesByMethod("heartbeat"); - String highLevelMessageData = mapToJsonString(highLevelMessageDataMap); - String message_id = sentMessages.get(highLevelMessageData).message_id; - for(String heartbeat : receivedHeartbeats){ - if(heartbeat.contains(message_id)){ - System.out.println("Found a heartbeat containing message_id " + message_id); + public boolean receivedHeartbeatWithSubstring(String substring){ + for(String heartbeat : getHeartbeats()){ + if(heartbeat.contains(substring)){ + System.out.println("Found a heartbeat containing substring " + substring); return true; } } return false; } + /** + * Returns the message_id field of the publish message corresponding to highLevelMessageDataMap. + */ + public String getPublishMessageId(Map highLevelMessageDataMap){ + return getPublishMessageIds(highLevelMessageDataMap).message_id; + } + + /** + * Returns the id field of the publish message corresponding to highLevelMessageDataMap. + */ + public int getPublishId(Map highLevelMessageDataMap){ + return getPublishMessageIds(highLevelMessageDataMap).id; + } + public MessageBuffer getBuffer() { return queue; } @@ -178,6 +172,34 @@ public synchronized Object listen(long timeout) { return msg; } + private PublishMessageIds getPublishMessageIds(Map highLevelMessageDataMap){ + String highLevelMessageData = mapToJsonString(highLevelMessageDataMap); + PublishMessageIds message_ids = sentMessages.get(highLevelMessageData); + if(message_ids == null){ + throw new IllegalArgumentException("Did not send this message"); + } + return message_ids; + } + + /** + * Retrieves all messages with the specified method type from the messages buffer. + * @param method The method type to filter the messages by. + * @return A list containing all received messages that match the specified method type. + */ + private List getMessagesByMethod(String method) { + List messages = new ArrayList<>(); + Predicate filter = MessageFilters.withMethod(method); + + String message = getBuffer().takeTimeout(TIMEOUT); + while (message != null) { + if (filter.test(message)) { + messages.add(message); + } + message = getBuffer().takeTimeout(TIMEOUT); + } + return messages; + } + private String mapToJsonString(Map jsonAsMap){ return Json.of(jsonAsMap).toString(); } From 503d9c21c50cfc0b9d39a6555a2236a9901d9e5f Mon Sep 17 00:00:00 2001 From: Simone Kalbermatter Date: Mon, 27 Nov 2023 18:33:04 +0100 Subject: [PATCH 07/10] add getMessagesById test case --- .../decentralizedCom/getMessagesById.feature | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/tests/karate/src/test/java/be/features/decentralizedCom/getMessagesById.feature b/tests/karate/src/test/java/be/features/decentralizedCom/getMessagesById.feature index 16f76ef348..c292a52a43 100644 --- a/tests/karate/src/test/java/be/features/decentralizedCom/getMessagesById.feature +++ b/tests/karate/src/test/java/be/features/decentralizedCom/getMessagesById.feature @@ -10,7 +10,8 @@ Feature: Request messages by id from other servers * call read(serverFeature) * call read(mockClientFeature) * def mockServer = call createMockClient - * def lao = mockServer.createValidLao() + * def mockFrontend = call createMockClient + * def lao = mockFrontend.createValidLao() # Create the template for heartbeat message # This is used in combination with 'eval' to dynamically resolve the channel keys in the heartbeat JSON @@ -24,7 +25,7 @@ Feature: Request messages by id from other servers # This call executes all the steps to create a valid lao on the server before every scenario # (lao creation, subscribe, catchup) - * call read(createLaoScenario) { organizer: '#(mockServer)', lao: '#(lao)' } + * call read(createLaoScenario) { organizer: '#(mockFrontend)', lao: '#(lao)' } # Check that after sending a heartbeat message with unknown message id, the server responds with a # getMessagesByID requesting this message @@ -54,7 +55,40 @@ Feature: Request messages by id from other servers And eval heartbeat.params[lao.channel] = invalidMessageIds When mockServer.send(heartbeat) - And def getMessagesByIdMessages = mockServer.getGetMessagesById() - Then assert getMessagesByIdMessages.length == 0 + Then assert mockServer.getGetMessagesById().length == 0 + + + # Check that after the server confirms it received a message, sending a heartbeat containing that message id does not + # trigger a getMessagesById anymore + Scenario: Server should not request messages that it already has + Given def validRollCall = mockFrontend.createValidRollCall(lao) + And def validCreateRollCall = + """ + { + "object": "roll_call", + "action": "create", + "id": '#(validRollCall.id)', + "name": '#(validRollCall.name)', + "creation": '#(validRollCall.creation)', + "proposed_start": '#(validRollCall.start)', + "proposed_end": '#(validRollCall.end)', + "location": '#(validRollCall.location)', + "description": '#(validRollCall.description)', + } + """ + + And mockFrontend.publish(validCreateRollCall, lao.channel) + And json answer = mockFrontend.getBackendResponse(validCreateRollCall) + And def message_id = mockFrontend.getPublishMessageId(validCreateRollCall) + And def messageIds = [] + And eval messageIds.push(message_id) + And eval heartbeat.params[lao.channel] = messageIds + + When mockServer.send(heartbeat) + + Then match answer contains VALID_MESSAGE + And assert mockServer.getGetMessagesById().length == 0 + + From b4d2e259453e7ad8c72c4b8226e6f55549eb21be Mon Sep 17 00:00:00 2001 From: Simone Kalbermatter Date: Mon, 27 Nov 2023 19:06:31 +0100 Subject: [PATCH 08/10] remove greet server --- .../java/be/features/utils/constants.feature | 1 - .../be/features/utils/simpleScenarios.feature | 19 ------------------- .../src/test/java/be/utils/MockClient.java | 19 ------------------- .../common/net/MultiMsgWebSocketClient.java | 18 ++++++++++++++++++ 4 files changed, 18 insertions(+), 39 deletions(-) diff --git a/tests/karate/src/test/java/be/features/utils/constants.feature b/tests/karate/src/test/java/be/features/utils/constants.feature index a673d13fdf..eec410170d 100644 --- a/tests/karate/src/test/java/be/features/utils/constants.feature +++ b/tests/karate/src/test/java/be/features/utils/constants.feature @@ -29,4 +29,3 @@ Feature: Constants * def castVoteScenario = simpleScenario + 'cast_vote' * def setupCoinChannelScenario = simpleScenario + 'setup_coin_channel' * def validCoinIssuanceScenario = simpleScenario + 'valid_coin_issuance' - * def greetServerScenario = simpleScenario + 'greet_server' diff --git a/tests/karate/src/test/java/be/features/utils/simpleScenarios.feature b/tests/karate/src/test/java/be/features/utils/simpleScenarios.feature index acfe26d32a..d561dcffb6 100644 --- a/tests/karate/src/test/java/be/features/utils/simpleScenarios.feature +++ b/tests/karate/src/test/java/be/features/utils/simpleScenarios.feature @@ -298,22 +298,3 @@ * karate.log("sending a transaction to issue coins :\n", karate.pretty(validTransaction)) * organizer.publish(validTransaction, lao.cashChannel) * json answer = organizer.getBackendResponse(validTransaction) - - # mockServer needs to be passed as argument when calling this scenario - @name=greet_server - Scenario: Sends a greet server message - Given def greetServer = - """ - { - "method": "greet_server", - "params": { - "public_key": '#(mockServer.publicKey)', - "client_address": '#(wsURL)', - "server_address": '#(wsURL)' - }, - "jsonrpc": "2.0" - } - """ - * karate.log("sending a greet_server\n", karate.pretty(greetServer)) - * mockServer.send(greetServer) - * def subs = mockServer.takeTimeout(timeout) diff --git a/tests/karate/src/test/java/be/utils/MockClient.java b/tests/karate/src/test/java/be/utils/MockClient.java index 6d10829964..5ac1d641f1 100644 --- a/tests/karate/src/test/java/be/utils/MockClient.java +++ b/tests/karate/src/test/java/be/utils/MockClient.java @@ -1,12 +1,10 @@ package be.utils; import be.model.*; -import com.intuit.karate.Json; import com.intuit.karate.http.WebSocketOptions; import com.intuit.karate.Logger; import common.net.MessageQueue; import common.net.MultiMsgWebSocketClient; -import common.utils.Base64Utils; import java.security.GeneralSecurityException; import java.time.Instant; @@ -91,21 +89,4 @@ public Transaction issueCoins(MockClient receiver, long amountToGive) throws Gen transaction.issueInitialCoins(receiver.publicKey, publicKey, privateKey, amountToGive); return transaction; } - - /** - * Checks the contents of the message data of all broadcasts that the client received for election results. - * @return the message data containing the decoded election results, or throws an error if none are found - */ - public String getElectionResults(){ - for (String broadcast : receivedBroadcasts) { - // Extract the field params/message/data from the Json and decode it - String messageData = Base64Utils.decodeBase64JsonField(Json.of(broadcast), "params.message.data"); - if (messageData.contains("result") && messageData.contains("election")){ - System.out.println("Received election results: " + messageData); - return messageData; - } - } - assert false; - throw new IllegalArgumentException("No election results were received"); - } } diff --git a/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java b/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java index 3c9546be8d..6b3ee65b46 100644 --- a/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java +++ b/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java @@ -7,6 +7,7 @@ import com.intuit.karate.Logger; import com.intuit.karate.http.WebSocketClient; import com.intuit.karate.http.WebSocketOptions; +import common.utils.Base64Utils; import java.util.*; import java.util.function.Predicate; @@ -106,6 +107,23 @@ public List getGetMessagesById(){ return getMessagesByMethod("get_messages_by_id"); } + /** + * Checks the contents of the message data of all broadcasts that the client received for election results. + * @return the message data containing the decoded election results, or throws an error if none are found + */ + public String getElectionResults(){ + for (String broadcast : receivedBroadcasts) { + // Extract the field params/message/data from the Json and decode it + String messageData = Base64Utils.decodeBase64JsonField(Json.of(broadcast), "params.message.data"); + if (messageData.contains("result") && messageData.contains("election")){ + System.out.println("Received election results: " + messageData); + return messageData; + } + } + assert false; + throw new IllegalArgumentException("No election results were received"); + } + public boolean receivedHeartbeatWithSubstring(String substring){ for(String heartbeat : getHeartbeats()){ if(heartbeat.contains(substring)){ From fc8b02c93208dab86f46d1f967eca2334006b2f6 Mon Sep 17 00:00:00 2001 From: Simone Kalbermatter Date: Thu, 30 Nov 2023 20:49:12 +0100 Subject: [PATCH 09/10] improve parameter name --- .../src/test/java/common/net/MultiMsgWebSocketClient.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java b/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java index 6b3ee65b46..873e44d035 100644 --- a/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java +++ b/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java @@ -45,11 +45,11 @@ public MultiMsgWebSocketClient(WebSocketOptions options, Logger logger, MessageQ /** * JSON messages defined inside features are interpreted as maps from String to Object. * This method is called directly inside features and is just a wrapper around the send method of WebsocketClient (that takes Strings). - * @param messageData the message to send as a JSON map + * @param jsonRpcMsg the message to send as a JSON map * (for example: subscribe and catchup in simpleScenarios.feature). */ - public void send(Map messageData){ - this.send(mapToJsonString(messageData)); + public void send(Map jsonRpcMsg){ + this.send(mapToJsonString(jsonRpcMsg)); } /** From 20710ee101c974599060101dee1c4763fef3b04f Mon Sep 17 00:00:00 2001 From: Simone Kalbermatter Date: Tue, 5 Dec 2023 11:17:39 +0100 Subject: [PATCH 10/10] specify number of hearbeats --- .../java/be/features/decentralizedCom/heartbeat.feature | 2 +- .../src/test/java/common/net/MultiMsgWebSocketClient.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/karate/src/test/java/be/features/decentralizedCom/heartbeat.feature b/tests/karate/src/test/java/be/features/decentralizedCom/heartbeat.feature index 58054084de..fb61a9d4cf 100644 --- a/tests/karate/src/test/java/be/features/decentralizedCom/heartbeat.feature +++ b/tests/karate/src/test/java/be/features/decentralizedCom/heartbeat.feature @@ -25,7 +25,7 @@ Feature: Send heartbeats to other servers When def heartbeatMessages = mockServer.getHeartbeats() - Then assert heartbeatMessages.length > 1 + Then assert heartbeatMessages.length == 2 # Check that after receiving a publish message (in this case a create roll call), the server sends a heartbeat containing # the message id of that publish message. diff --git a/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java b/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java index 873e44d035..2fbb88c15d 100644 --- a/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java +++ b/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java @@ -61,7 +61,7 @@ public void send(Map jsonRpcMsg){ */ public void publish(Map highLevelMessageDataMap, String channel){ String highLevelMessageData = mapToJsonString(highLevelMessageDataMap); - int messageId = new Random().nextInt(Integer.MAX_VALUE); + int messageId = new Random().nextInt(); Json publishMessageJson = jsonConverter.constructPublishMessage(highLevelMessageData, messageId, channel); PublishMessageIds publishMessageIds = new PublishMessageIds(messageId, publishMessageJson.get("params.message.message_id")); sentMessages.put(highLevelMessageData, publishMessageIds); @@ -192,11 +192,11 @@ public synchronized Object listen(long timeout) { private PublishMessageIds getPublishMessageIds(Map highLevelMessageDataMap){ String highLevelMessageData = mapToJsonString(highLevelMessageDataMap); - PublishMessageIds message_ids = sentMessages.get(highLevelMessageData); - if(message_ids == null){ + PublishMessageIds messageIds = sentMessages.get(highLevelMessageData); + if(messageIds == null){ throw new IllegalArgumentException("Did not send this message"); } - return message_ids; + return messageIds; } /**