Repository: pglob/ocpp-charger-sim
Pull request #99: Added test cases for Rx/Tx messages
Author: login: sille369
Created: 2025-03-06T05:21:57Z | Merged: 2025-03-08T07:25:01Z



Diff:
diff --git a/backend/src/main/java/com/sim_backend/websockets/OCPPWebSocketClient.java b/backend/src/main/java/com/sim_backend/websockets/OCPPWebSocketClient.java
index 4bd1232b..127b4771 100644
--- a/backend/src/main/java/com/sim_backend/websockets/OCPPWebSocketClient.java
+++ b/backend/src/main/java/com/sim_backend/websockets/OCPPWebSocketClient.java
@@ -98,8 +98,8 @@ private static class ParseResults {
   /** List to store received messages. */
   private final List<String> rxMessages = new CopyOnWriteArrayList<>();
 
-  /** List to store Rx CallRequest message names. */
-  private final List<String> rxRequestNames = new CopyOnWriteArrayList<>();
+  /** Store Rx CallRequest message name. */
+  public String rxRequestName = null;
 
   /**
    * Inserts a JsonElement at the specified index in the JsonArray.
@@ -135,16 +135,26 @@ public static JsonArray insertElementAt(JsonArray jsonArray, int index, JsonElem
    * @param message The transmitted message.
    */
   public void recordTxMessage(String message) {
+    if (message == null) {
+      log.error("Sent null message");
+      return;
+    }
+
     Gson gson = GsonUtilities.getGson();
     JsonArray array = gson.fromJson(message, JsonArray.class);
+    if (array == null) {
+      log.error("Failed to parse message: " + message);
+      return;
+    }
+
     String result;
     String messageName;
     if (array.get(0).getAsInt() == 3) {
-      if (rxRequestNames.size() <= 0) {
+      if (rxRequestName == null) {
         log.error("Failed to find the CallRequest Name");
         return;
       }
-      messageName = rxRequestNames.get(rxRequestNames.size() - 1);
+      messageName = rxRequestName;
       JsonElement MsgName = new JsonPrimitive(messageName);
       JsonArray newArray = insertElementAt(array, 2, MsgName);
       result = gson.toJson(newArray);
@@ -165,8 +175,22 @@ public void recordTxMessage(String message) {
    * @param message The received message.
    */
   public void recordRxMessage(String message, String messageName) {
+    if (message == null) {
+      log.error("Received null message");
+      return;
+    }
+
+    if (messageName == null) {
+      messageName = "Unknown";
+    }
+
     Gson gson = GsonUtilities.getGson();
     JsonArray array = gson.fromJson(message, JsonArray.class);
+    if (array == null) {
+      log.error("Failed to parse message: " + message);
+      return;
+    }
+
     String result;
     if (array.get(0).getAsInt() == 2) { // remove the extra Message name
       array.remove(NAME_INDEX);
@@ -305,7 +329,7 @@ void handleMessage(final String json) throws Exception {
         case OCPPMessage.CALL_ID_REQUEST -> {
           results = this.parseOCPPRequest(json, msgId, array);
           String RequestName = array.get(NAME_INDEX).getAsString();
-          rxRequestNames.add(RequestName);
+          rxRequestName = RequestName;
         }
         case OCPPMessage.CALL_ID_RESPONSE -> results = this.parseOCPPResponse(json, msgId, array);
         case OCPPMessage.CALL_ID_ERROR -> {
diff --git a/backend/src/test/java/com/sim_backend/websockets/OCPPWebSocketClientTest.java b/backend/src/test/java/com/sim_backend/websockets/OCPPWebSocketClientTest.java
index 5fdb3ef7..730c7435 100644
--- a/backend/src/test/java/com/sim_backend/websockets/OCPPWebSocketClientTest.java
+++ b/backend/src/test/java/com/sim_backend/websockets/OCPPWebSocketClientTest.java
@@ -849,4 +849,43 @@ public void onMessageReceived(OnOCPPMessage message) {}
     verify(listener, times(1)).onTimeout();
     assertTrue(beat.isErrored());
   }
+
+  @Test
+  public void testrecordTxMessage() throws Exception {
+    String message =
+        "[3,\"633428f1-5b68-4a44-8a43-a7fee463be62\",{\"configurationKey\":[{\"key\":\"MeterValueSampleInterval\",\"value\":\"30\",\"readonly\":false}],\"unknownKey\":[]}]";
+    client.rxRequestName = "GetConfiguration";
+    client.recordTxMessage(message);
+    assertTrue(client.getSentMessages().get(0).contains("GetConfiguration"));
+    assertTrue(client.getSentMessages().get(0).contains("configurationKey"));
+    assertEquals(1, client.getSentMessages().size());
+  }
+
+  @Test
+  public void testrecordTxMessage_rxRequestName_null() throws Exception {
+    String message =
+        "[3,\"633428f1-5b68-4a44-8a43-a7fee463be62\",{\"configurationKey\":[{\"key\":\"MeterValueSampleInterval\",\"value\":\"30\",\"readonly\":false}],\"unknownKey\":[]}]";
+    client.rxRequestName = null;
+    client.recordTxMessage(message);
+    assertEquals(0, client.getSentMessages().size());
+  }
+
+  @Test
+  public void testrecordRxMessage() throws Exception {
+    String message = "[2,\"12345\",\"GetConfiguration\",{\"key\":[\"MeterValueSampleInterval\"]}]";
+    String messageName = "GetConfiguration";
+
+    client.recordRxMessage(message, messageName);
+    assertEquals(1, client.getReceivedMessages().size());
+    assertTrue(client.getReceivedMessages().get(0).contains("GetConfiguration"));
+    assertTrue(client.getReceivedMessages().get(0).contains("MeterValueSampleInterval"));
+  }
+
+  @Test
+  public void testrecordRxMessage_WithemptyMsg() throws Exception {
+    String message = null;
+    String messageName = "GetConfiguration";
+    client.recordRxMessage(message, messageName);
+    assertEquals(0, client.getReceivedMessages().size());
+  }
 }


Reviews:
user: login: pglob
state: CHANGES_REQUESTED
author_association: OWNER
submitted_at: 2025-03-06T07:58:04Z
user: login: sille369
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-06T21:15:55Z
user: login: pglob
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-06T21:21:59Z
user: login: sille369
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-06T23:30:14Z
user: login: pglob
body: Just a couple things, then it's good to go.
state: APPROVED
author_association: OWNER
submitted_at: 2025-03-07T23:57:15Z

Review comments:
diff_hunk: @@ -165,8 +182,29 @@ public void recordTxMessage(String message) {
    * @param message The received message.
    */
   public void recordRxMessage(String message, String messageName) {
+    if (message == null) {
+      this.pushCallError(ErrorCode.FormatViolation, "Received null message");
path: backend/src/main/java/com/sim_backend/websockets/OCPPWebSocketClient.java
user: login: pglob
body: Don't push any CallErrors for the `recordRxMessage` and `recordTxMessage` functions. Since these functions are intended for frontend communication, they are "separate" from the OCPP charger.
created_at: 2025-03-06T06:06:17Z
author_association: OWNER
diff_hunk: @@ -93,13 +93,13 @@ private static class ParseResults {
   public static final Map<String, String> headers = Map.of("Sec-WebSocket-Protocol", "ocpp1.6");
 
   /** List to store transmitted messages. */
-  private final List<String> txMessages = new CopyOnWriteArrayList<>();
+  public final List<String> txMessages = new CopyOnWriteArrayList<>();
path: backend/src/main/java/com/sim_backend/websockets/OCPPWebSocketClient.java
user: login: pglob
body: Keep these private. We already have `getSentMessages()` and `getReceivedMessages()`
created_at: 2025-03-06T06:08:37Z
author_association: OWNER
diff_hunk: @@ -849,4 +849,52 @@ public void onMessageReceived(OnOCPPMessage message) {}
     verify(listener, times(1)).onTimeout();
     assertTrue(beat.isErrored());
   }
+
+  @Test
+  public void testrecordTxMessage() throws Exception {
+    client.rxRequestNames.add("GetConfiguration");
+    String message =
+        "[3,\"633428f1-5b68-4a44-8a43-a7fee463be62\",{\"configurationKey\":[{\"key\":\"MeterValueSampleInterval\",\"value\":\"30\",\"readonly\":false}],\"unknownKey\":[]}]";
+    client.recordTxMessage(message);
+    assertEquals(1, client.txMessages.size());
+    assertTrue(client.txMessages.get(0).contains("GetConfiguration"));
+    assertTrue(client.txMessages.get(0).contains("configurationKey"));
+
+    client.txMessages.clear();
path: backend/src/test/java/com/sim_backend/websockets/OCPPWebSocketClientTest.java
user: login: pglob
body: Since there is this:
```
  @BeforeEach
  void setUp() throws URISyntaxException {
    onOCPPMessageMock = mock(OnOCPPMessage.class);
    client = spy(new TestOCPPWebSocketClient(new URI(""), statusNotificationObserver));
    queue = mock(MessageQueue.class);
  }
```
Instead of resetting txMessages, use a new test case/function. Avoiding manual state changes will make the tests more isolated.
created_at: 2025-03-06T06:18:49Z
author_association: OWNER
path: backend/src/main/java/com/sim_backend/websockets/OCPPWebSocketClient.java
user: login: pglob
body: Looking more closely at `rxRequestNames`, is there a reason it needs to be a list? We are only every accessing the last element from it, so it could just be a single `String`.
created_at: 2025-03-06T07:57:18Z
author_association: OWNER
path: backend/src/main/java/com/sim_backend/websockets/OCPPWebSocketClient.java
user: login: sille369
body: I think we need it as a list, because we need to keep tracking of the the last Rx CallRequest. But if "Getconfiguration" is the only Rx CallRequest, then we don't need rxRequestNames as a list. otherwise, we need it as a list to track the last Rx CallRequest name.
created_at: 2025-03-06T21:15:55Z
author_association: COLLABORATOR
path: backend/src/main/java/com/sim_backend/websockets/OCPPWebSocketClient.java
user: login: pglob
body: I mean that the only time `rxRequestNames` is accessed is with `messageName = rxRequestNames.get(rxRequestNames.size() - 1);`. 

So if our `rxRequestNames` list is:
```
{GetConfiguration, ChangeAvailability, TriggerMessage}
```
those first 2 elements are not needed and will never be accessed. So we could store it with a single `String`
created_at: 2025-03-06T21:21:59Z
author_association: OWNER
path: backend/src/main/java/com/sim_backend/websockets/OCPPWebSocketClient.java
user: login: sille369
body: oh, I see. 
created_at: 2025-03-06T23:30:14Z
author_association: COLLABORATOR
diff_hunk: @@ -851,50 +851,43 @@ public void onMessageReceived(OnOCPPMessage message) {}
   }
 
   @Test
-  public void testrecordTxMessage() throws Exception {
-    client.rxRequestNames.add("GetConfiguration");
+  public void testrecordTxMessage_case1() throws Exception {
path: backend/src/test/java/com/sim_backend/websockets/OCPPWebSocketClientTest.java
user: login: pglob
body: Have a more descriptive name than `case1` or `case2`
created_at: 2025-03-07T23:53:51Z
author_association: OWNER
diff_hunk: @@ -135,16 +135,31 @@ public static JsonArray insertElementAt(JsonArray jsonArray, int index, JsonElem
    * @param message The transmitted message.
    */
   public void recordTxMessage(String message) {
+    if (message == null) {
+      log.error("Sent null message");
+      return;
+    }
+
     Gson gson = GsonUtilities.getGson();
     JsonArray array = gson.fromJson(message, JsonArray.class);
+    try {
path: backend/src/main/java/com/sim_backend/websockets/OCPPWebSocketClient.java
user: login: pglob
body: If `JsonSyntaxException` is from `JsonArray array = gson.fromJson(message, JsonArray.class);`, the try catch block below won't do anything.
created_at: 2025-03-07T23:55:30Z
author_association: OWNER
diff_hunk: @@ -135,16 +135,31 @@ public static JsonArray insertElementAt(JsonArray jsonArray, int index, JsonElem
    * @param message The transmitted message.
    */
   public void recordTxMessage(String message) {
+    if (message == null) {
+      log.error("Sent null message");
+      return;
+    }
+
     Gson gson = GsonUtilities.getGson();
     JsonArray array = gson.fromJson(message, JsonArray.class);
+    try {
path: backend/src/main/java/com/sim_backend/websockets/OCPPWebSocketClient.java
user: login: pglob
body: Same thing down at line 194
created_at: 2025-03-07T23:57:03Z
author_association: OWNER

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-06T05:22:38Z
body: |Overall Project|81.48% **`-0.41%`**|:green_apple:|
|:-|:-|:-:|
|Files changed|58.44%|:x:|
<br>

|File|Coverage||
|:-|:-|:-:|
|[OCPPWebSocketClient.java](https://github.com/pglob/ocpp-charger-sim/blob/4b8806a20974b1bae99d3ad86ca4f046870300da/backend%2Fsrc%2Fmain%2Fjava%2Fcom%2Fsim_backend%2Fwebsockets%2FOCPPWebSocketClient.java)|88.12% **`-3.02%`**|:x:|
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-07T23:52:07Z
body: |Overall Project|81.48% **`-0.38%`**|:green_apple:|
|:-|:-|:-:|
|Files changed|48.28%|:x:|
<br>

|File|Coverage||
|:-|:-|:-:|
|[OCPPWebSocketClient.java](https://github.com/pglob/ocpp-charger-sim/blob/70b12a019b2fd19964d7fa79e81acd5ce5acf1b3/backend%2Fsrc%2Fmain%2Fjava%2Fcom%2Fsim_backend%2Fwebsockets%2FOCPPWebSocketClient.java)|88.29% **`-2.9%`**|:x:|
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-07T23:54:32Z
body: |Overall Project|81.48% **`-0.38%`**|:green_apple:|
|:-|:-|:-:|
|Files changed|48.28%|:x:|
<br>

|File|Coverage||
|:-|:-|:-:|
|[OCPPWebSocketClient.java](https://github.com/pglob/ocpp-charger-sim/blob/83e1b53536ba995425b47076f5cebd570ea4c380/backend%2Fsrc%2Fmain%2Fjava%2Fcom%2Fsim_backend%2Fwebsockets%2FOCPPWebSocketClient.java)|88.29% **`-2.9%`**|:x:|
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-08T00:21:45Z
body: |Overall Project|81.62% **`-0.2%`**|:green_apple:|
|:-|:-|:-:|
|Files changed|61.9%|:green_apple:|
<br>

|File|Coverage||
|:-|:-|:-:|
|[OCPPWebSocketClient.java](https://github.com/pglob/ocpp-charger-sim/blob/7bede593df46eec9fbbd67d63eb6a52dd339fb1f/backend%2Fsrc%2Fmain%2Fjava%2Fcom%2Fsim_backend%2Fwebsockets%2FOCPPWebSocketClient.java)|89.48% **`-1.57%`**|:green_apple:|
author_association: NONE