Introduce PatchSet Voting functionality

Implemented the `enabledVoting` setting to allow ChatGPT to vote on a
Patch Set upon review. The vote's score will fall between
`votingMinScore` and `votingMaxScore`, which are configurable and
default to -1 and 1, respectively.

Jira-Id: IT-103
Change-Id: Id752642d306b1377269665fe25614e8a23dc8ae4
Signed-off-by: Patrizio <patrizio.gelosi@amarulasolutions.com>
diff --git a/README.md b/README.md
index 9339ea7..e2ed963 100644
--- a/README.md
+++ b/README.md
@@ -144,6 +144,10 @@
 - `enabledFileExtensions`: This limits the reviewed files to the given types. Default file extensions are ".py, .java,
   .js, .ts, .html, .css, .cs, .cpp, .c, .h, .php, .rb, .swift, .kt, .r, .jl, .go, .scala, .pl, .pm, .rs, .dart, .lua,
   .sh, .vb, .bat".
+- `enabledVoting`: Initially disabled (false). If set to true, allows ChatGPT to cast a vote on each reviewed Patch Set
+  by assigning a score.
+- `votingMinScore`: The lowest possible score that can be given to a Patch Set (Default value: -1).
+- `votingMaxScore`: The highest possible score that can be given to a Patch Set (Default value: +1).
 
 #### Optional Parameters for Global Configuration only
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewer.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewer.java
index c9d0a78..e2d7755 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewer.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewer.java
@@ -51,9 +51,10 @@
 
         String reviewReply = getReviewReply(config, fullChangeId, patchSet);
         log.debug("ChatGPT response: {}", reviewReply);
-        retrieveReviewFromJson(reviewReply, fullChangeId);
+        ChatGptResponseContent reviewJson = gson.fromJson(reviewReply, ChatGptResponseContent.class);
+        retrieveReviewFromJson(reviewJson, fullChangeId);
 
-        gerritClient.setReview(fullChangeId, reviewBatches);
+        gerritClient.setReview(fullChangeId, reviewBatches, reviewJson.getScore());
     }
 
     private void addReviewBatch(Integer batchID, String batch) {
@@ -100,8 +101,7 @@
         return gerritCommentRange;
     }
 
-    private void retrieveReviewFromJson(String reviewReply, String fullChangeId) {
-        ChatGptResponseContent reviewJson = gson.fromJson(reviewReply, ChatGptResponseContent.class);
+    private void retrieveReviewFromJson(ChatGptResponseContent reviewJson, String fullChangeId) {
         fileDiffsProcessed = gerritClient.getFileDiffsProcessed(fullChangeId);
         for (ChatGptReplyItem replyItem : reviewJson.getReplies()) {
             ReviewBatch batchMap = new ReviewBatch();
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/chatgpt/ChatGptClient.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/chatgpt/ChatGptClient.java
index 9da827f..e8b942f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/chatgpt/ChatGptClient.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/chatgpt/ChatGptClient.java
@@ -8,14 +8,12 @@
 import com.googlesource.gerrit.plugins.chatgpt.client.UriResourceLocator;
 import com.googlesource.gerrit.plugins.chatgpt.client.model.chatGpt.*;
 import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
-import com.googlesource.gerrit.plugins.chatgpt.utils.FileUtils;
 import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.http.entity.ContentType;
 
 import java.io.BufferedReader;
 import java.io.IOException;
-import java.io.InputStreamReader;
 import java.io.StringReader;
 import java.net.URI;
 import java.net.http.HttpRequest;
@@ -34,6 +32,7 @@
             .disableHtmlEscaping()
             .create();
     private final HttpClientWithRetry httpClientWithRetry = new HttpClientWithRetry();
+    private ChatGptTools chatGptTools;
     private boolean isCommentEvent = false;
 
     public String ask(Configuration config, String changeId, String patchSet) throws Exception {
@@ -59,6 +58,7 @@
 
     public String ask(Configuration config, String changeId, String patchSet, boolean isCommentEvent) throws Exception {
         this.isCommentEvent = isCommentEvent;
+        chatGptTools = new ChatGptTools(isCommentEvent);
 
         return this.ask(config, changeId, patchSet);
     }
@@ -123,14 +123,7 @@
                 .build();
 
         List<ChatGptRequest.Message> messages = List.of(systemMessage, userMessage);
-
-        ChatGptRequest tools;
-        try (InputStreamReader reader = FileUtils.getInputStreamReader("Config/tools.json")) {
-            tools = gson.fromJson(reader, ChatGptRequest.class);
-        } catch (IOException e) {
-            throw new RuntimeException("Failed to load ChatGPT request tools", e);
-        }
-
+        ChatGptRequest tools = chatGptTools.retrieveTools(config);
         ChatGptRequest chatGptRequest = ChatGptRequest.builder()
                 .model(config.getGptModel())
                 .messages(messages)
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/chatgpt/ChatGptTools.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/chatgpt/ChatGptTools.java
new file mode 100644
index 0000000..395b2d7
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/chatgpt/ChatGptTools.java
@@ -0,0 +1,39 @@
+package com.googlesource.gerrit.plugins.chatgpt.client.chatgpt;
+
+import com.google.gson.Gson;
+import com.googlesource.gerrit.plugins.chatgpt.client.model.chatGpt.ChatGptRequest;
+import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import com.googlesource.gerrit.plugins.chatgpt.utils.FileUtils;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+class ChatGptTools {
+
+    private final Gson gson = new Gson();
+    private final boolean isCommentEvent;
+
+    public ChatGptTools(boolean isCommentEvent) {
+        this.isCommentEvent = isCommentEvent;
+    }
+
+    public ChatGptRequest retrieveTools(Configuration config) {
+        ChatGptRequest tools;
+        try (InputStreamReader reader = FileUtils.getInputStreamReader("Config/tools.json")) {
+            tools = gson.fromJson(reader, ChatGptRequest.class);
+        } catch (IOException e) {
+            throw new RuntimeException("Failed to load ChatGPT request tools", e);
+        }
+        if (!config.isVotingEnabled() || isCommentEvent) {
+            removeScoreFromTools(tools);
+        }
+        return tools;
+    }
+
+    private void removeScoreFromTools(ChatGptRequest tools) {
+        ChatGptRequest.Tool.Function.Parameters parameters = tools.getTools().get(0).getFunction().getParameters();
+        parameters.getProperties().setScore(null);
+        parameters.getRequired().remove("score");
+    }
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/gerrit/GerritClient.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/gerrit/GerritClient.java
index 658f83a..a93d10d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/gerrit/GerritClient.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/gerrit/GerritClient.java
@@ -71,9 +71,13 @@
         return gerritClientComments.getCommentProperties();
     }
 
-    public void setReview(String fullChangeId, List<ReviewBatch> reviewBatches) throws Exception {
+    public void setReview(String fullChangeId, List<ReviewBatch> reviewBatches, Integer reviewScore) throws Exception {
         updateGerritClient(GerritClientType.REVIEW, fullChangeId);
-        gerritClientReview.setReview(fullChangeId, reviewBatches);
+        gerritClientReview.setReview(fullChangeId, reviewBatches, reviewScore);
+    }
+
+    public void setReview(String fullChangeId, List<ReviewBatch> reviewBatches) throws Exception {
+        setReview(fullChangeId, reviewBatches, null);
     }
 
     public boolean retrieveLastComments(Event event, String fullChangeId) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/gerrit/GerritClientReview.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/gerrit/GerritClientReview.java
index c17ca25..3d883d5 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/gerrit/GerritClientReview.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/gerrit/GerritClientReview.java
@@ -34,8 +34,8 @@
         super(config);
     }
 
-    public void setReview(String fullChangeId, List<ReviewBatch> reviewBatches) throws Exception {
-        GerritReview reviewMap = buildReview(reviewBatches);
+    public void setReview(String fullChangeId, List<ReviewBatch> reviewBatches, Integer reviewScore) throws Exception {
+        GerritReview reviewMap = buildReview(reviewBatches, reviewScore);
         if (reviewMap.getComments() == null && reviewMap.getMessage() == null) {
             return;
         }
@@ -67,7 +67,7 @@
         return BULLET_POINT + String.join("\n\n" + BULLET_POINT, messages);
     }
 
-    private GerritReview buildReview(List<ReviewBatch> reviewBatches) {
+    private GerritReview buildReview(List<ReviewBatch> reviewBatches, Integer reviewScore) {
         GerritReview reviewMap = new GerritReview();
         List<String> messages = new ArrayList<>();
         Map<String, List<GerritComment>> comments = new HashMap<>();
@@ -98,6 +98,9 @@
         if (!comments.isEmpty()) {
             reviewMap.setComments(comments);
         }
+        if (reviewScore != null) {
+            reviewMap.setLabels(new GerritReview.Labels(reviewScore));
+        }
         return reviewMap;
     }
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/model/chatGpt/ChatGptRequest.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/model/chatGpt/ChatGptRequest.java
index 6e7094f..c7f7614 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/model/chatGpt/ChatGptRequest.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/model/chatGpt/ChatGptRequest.java
@@ -47,6 +47,7 @@
                 @Data
                 public static class Properties {
                     private Property replies;
+                    private Field score;
                     // Field `changeId` expected in the response to correspond with the PatchSet changeId in the request
                     private Field changeId;
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/model/chatGpt/ChatGptResponseContent.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/model/chatGpt/ChatGptResponseContent.java
index 8d16f7e..73495e8 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/model/chatGpt/ChatGptResponseContent.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/model/chatGpt/ChatGptResponseContent.java
@@ -7,5 +7,6 @@
 @Data
 public class ChatGptResponseContent {
     private List<ChatGptReplyItem> replies;
+    private Integer score;
     private String changeId;
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/model/gerrit/GerritReview.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/model/gerrit/GerritReview.java
index 0d9590c..1ea70d9 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/model/gerrit/GerritReview.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/model/gerrit/GerritReview.java
@@ -1,5 +1,7 @@
 package com.googlesource.gerrit.plugins.chatgpt.client.model.gerrit;
 
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
 import lombok.Data;
 
 import java.util.List;
@@ -9,4 +11,13 @@
 public class GerritReview {
     private Map<String, List<GerritComment>> comments;
     private String message;
+    private Labels labels;
+
+    @AllArgsConstructor
+    @Data
+    public static class Labels {
+        @SerializedName("Code-Review")
+        private int codeReview;
+    }
+
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/Configuration.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/Configuration.java
index 6f28cdd..f953df9 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/Configuration.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/Configuration.java
@@ -69,6 +69,9 @@
     private static final boolean DEFAULT_PROJECT_ENABLE = false;
     private static final int DEFAULT_MAX_REVIEW_LINES = 1000;
     private static final int DEFAULT_MAX_REVIEW_FILE_SIZE = 10000;
+    private static final boolean DEFAULT_ENABLED_VOTING = false;
+    private static final int DEFAULT_VOTING_MIN_SCORE = -1;
+    private static final int DEFAULT_VOTING_MAX_SCORE = 1;
 
     // Config setting keys
     public static final String KEY_GPT_SYSTEM_PROMPT = "gptSystemPrompt";
@@ -97,6 +100,9 @@
     private static final String KEY_MAX_REVIEW_LINES = "maxReviewLines";
     private static final String KEY_MAX_REVIEW_FILE_SIZE = "maxReviewFileSize";
     private static final String KEY_ENABLED_FILE_EXTENSIONS = "enabledFileExtensions";
+    private static final String KEY_ENABLED_VOTING = "enabledVoting";
+    private static final String KEY_VOTING_MIN_SCORE = "votingMinScore";
+    private static final String KEY_VOTING_MAX_SCORE = "votingMaxScore";
 
     // Prompt constants loaded from JSON file
     public static String DEFAULT_GPT_SYSTEM_PROMPT;
@@ -109,6 +115,7 @@
     public static String DEFAULT_GPT_REQUEST_USER_PROMPT_1;
     public static String DEFAULT_GPT_REQUEST_USER_PROMPT_2;
     public static String DEFAULT_GPT_COMMIT_MESSAGES_REVIEW_USER_PROMPT;
+    public static String DEFAULT_GPT_VOTING_REVIEW_USER_PROMPT;
 
     private final Map<String, Object> configsDynamically = Maps.newHashMap();
     private final PluginConfig globalConfig;
@@ -193,6 +200,10 @@
             if (getGptReviewCommitMessages()) {
                 prompt.add(DEFAULT_GPT_COMMIT_MESSAGES_REVIEW_USER_PROMPT);
             }
+            if (isVotingEnabled()) {
+                prompt.add(String.format(DEFAULT_GPT_VOTING_REVIEW_USER_PROMPT, getVotingMinScore(),
+                        getVotingMaxScore()));
+            }
             prompt.add(patchSet);
         }
         return String.join("\n", prompt);
@@ -266,6 +277,19 @@
         return splitConfig(globalConfig.getString(KEY_ENABLED_FILE_EXTENSIONS, DEFAULT_ENABLED_FILE_EXTENSIONS));
     }
 
+    public boolean isVotingEnabled() {
+        return globalConfig.getBoolean(KEY_ENABLED_VOTING, DEFAULT_ENABLED_VOTING);
+    }
+
+    public int getVotingMinScore() {
+        return getInt(KEY_VOTING_MIN_SCORE, DEFAULT_VOTING_MIN_SCORE);
+    }
+
+    public int getVotingMaxScore() {
+        return getInt(KEY_VOTING_MAX_SCORE, DEFAULT_VOTING_MAX_SCORE);
+    }
+
+
     private void loadPrompts() {
         // Avoid repeated loading of prompt constants
         if (DEFAULT_GPT_SYSTEM_PROMPT != null) return;
diff --git a/src/main/resources/Config/prompts.json b/src/main/resources/Config/prompts.json
index 0711f17..4207f2f 100644
--- a/src/main/resources/Config/prompts.json
+++ b/src/main/resources/Config/prompts.json
@@ -8,5 +8,6 @@
   "DEFAULT_GPT_JSON_USER_PROMPT_ENFORCE_RESPONSE_CHECK": "Make sure that the array in `replies` contains exactly %d element(s), one for each request.",
   "DEFAULT_GPT_REQUEST_USER_PROMPT_1": "I have some requests about the following PatchSet Diff:",
   "DEFAULT_GPT_REQUEST_USER_PROMPT_2": "My requests are given in a JSON-formatted array:",
-  "DEFAULT_GPT_COMMIT_MESSAGES_REVIEW_USER_PROMPT": "Also, perform a check on the commit message of the PatchSet. The commit message is provided in the \"content\" field of \"/COMMIT_MSG\" in the same way as the file changes. Ensure that the commit message accurately and succinctly describes the changes made, and verify if it matches the nature and scope of the changes in the PatchSet."
+  "DEFAULT_GPT_COMMIT_MESSAGES_REVIEW_USER_PROMPT": "Also, perform a check on the commit message of the PatchSet. The commit message is provided in the \"content\" field of \"/COMMIT_MSG\" in the same way as the file changes. Ensure that the commit message accurately and succinctly describes the changes made, and verify if it matches the nature and scope of the changes in the PatchSet.",
+  "DEFAULT_GPT_VOTING_REVIEW_USER_PROMPT": "Additionally, assign a score to the current PatchSet. The score must be an integer from %d to %d, calculated as the lower of the code or commit message error assessments. For example, if code issues rate +1 but the commit message is -1, the final score is -1."
 }
diff --git a/src/main/resources/Config/tools.json b/src/main/resources/Config/tools.json
index 4eaaf9d..2504795 100644
--- a/src/main/resources/Config/tools.json
+++ b/src/main/resources/Config/tools.json
@@ -34,12 +34,16 @@
                 ]
               }
             },
+            "score": {
+              "type": "integer"
+            },
             "changeId": {
               "type": "string"
             }
           },
           "required": [
             "replies",
+            "score",
             "changeId"
           ]
         }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTest.java b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTest.java
index 676cc94..a0feb6a 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTest.java
@@ -73,6 +73,8 @@
     private static final BranchNameKey BRANCH_NAME = BranchNameKey.create(PROJECT_NAME, "myBranchName");
     private static final boolean GPT_STREAM_OUTPUT = true;
     private static final long TEST_TIMESTAMP = 1699270812;
+    private static final int VOTING_MIN_SCORE = -1;
+    private static final int VOTING_MAX_SCORE = 1;
     private static final String REVIEW_TAG_COMMENTS = "[{\"request\":\"comment 2\",\"id\":0},{\"request\":" +
             "\"message\",\"id\":1,\"filename\":\"test_file.py\",\"lineNumber\":5,\"codeSnippet\":\"TypeClassOrPath\"" +
             "},{\"request\":\"[{\\\"role\\\":\\\"assistant\\\",\\\"content\\\":\\\"message from gpt\\\"},{" +
@@ -84,6 +86,7 @@
     private String expectedResponseStreamed;
     private String expectedSystemPrompt;
     private String reviewUserPrompt;
+    private String reviewVoteUserPrompt;
     private String diffContent;
     private String gerritPatchSetReview;
 
@@ -221,6 +224,13 @@
                 Configuration.DEFAULT_GPT_COMMIT_MESSAGES_REVIEW_USER_PROMPT,
                 diffContent
         ));
+        reviewVoteUserPrompt = String.join("\n", Arrays.asList(
+                Configuration.DEFAULT_GPT_REVIEW_USER_PROMPT,
+                Configuration.getPatchSetReviewUserPrompt(),
+                Configuration.DEFAULT_GPT_COMMIT_MESSAGES_REVIEW_USER_PROMPT,
+                String.format(Configuration.DEFAULT_GPT_VOTING_REVIEW_USER_PROMPT, VOTING_MIN_SCORE, VOTING_MAX_SCORE),
+                diffContent
+        ));
     }
 
     private AccountAttribute createTestAccountAttribute() {
@@ -279,6 +289,8 @@
     public void patchSetCreatedOrUpdatedUnstreamed() throws InterruptedException, NoSuchProjectException, ExecutionException {
         when(globalConfig.getBoolean(Mockito.eq("gptStreamOutput"), Mockito.anyBoolean()))
                 .thenReturn(false);
+        when(globalConfig.getBoolean(Mockito.eq("enabledVoting"), Mockito.anyBoolean()))
+                .thenReturn(true);
         Configuration config = new Configuration(globalConfig, projectConfig);
         GerritClient gerritClient = new GerritClient();
         ChatGptClient chatGptClient = new ChatGptClient();
@@ -312,7 +324,7 @@
         JsonObject gptRequestBody = gson.fromJson(chatGptClient.getRequestBody(), JsonObject.class);
         JsonArray prompts = gptRequestBody.get("messages").getAsJsonArray();
         String userPrompt = prompts.get(1).getAsJsonObject().get("content").getAsString();
-        Assert.assertEquals(reviewUserPrompt, userPrompt);
+        Assert.assertEquals(reviewVoteUserPrompt, userPrompt);
         String requestBody = loggedRequests.get(0).getBodyAsString();
         Assert.assertEquals(gerritPatchSetReview, requestBody);
 
diff --git a/src/test/resources/__files/chatGptResponseReview.json b/src/test/resources/__files/chatGptResponseReview.json
index 32d1eb0..01a21c6 100644
--- a/src/test/resources/__files/chatGptResponseReview.json
+++ b/src/test/resources/__files/chatGptResponseReview.json
@@ -11,7 +11,7 @@
             "type": "function",
             "function": {
               "name": "format_replies",
-              "arguments": "{\n  \"replies\": [\n    {\n      \"reply\": \"The commit message 'Test Commit Message' is too vague and does not provide information about the specific changes made. A more detailed message is necessary to understand what has been fixed.\"\n    },\n    {\n      \"reply\": \"Confirm that the method 'importclass' is meant to change its behavior when 'class_name' is None. The new lines suggest 'class_name' will be derived from the 'module_name' in such cases, which can have unintended effects if not explicitly intended.\",\n      \"filename\": \"test_file.py\",\n      \"lineNumber\": 19,\n      \"codeSnippet\": \"if not class_name:\nmodule_name,class_name=module_name.rsplit('.',1)\"\n    },\n    {\n      \"reply\": \"The added check to determine if 'class_name' is None seems to modify the 'module_name' by splitting it and taking the last element. There should be an assignment to 'class_name' since the class to be imported is meant to be the last part of 'module_name' after splitting.\",\n      \"filename\": \"test_file.py\",\n      \"lineNumber\": 20,\n      \"codeSnippet\": \"module_name, class_name = module_name.rsplit('.', 1)\"\n    },\n    {\n      \"reply\": \"The code line 'from types import Any, Callable, ...' should use 'typing' for imports instead of 'types'.\",\n      \"filename\": \"test_file.py\",\n      \"lineNumber\": 1,\n      \"codeSnippet\": \"from types import\"\n    },\n    {\n      \"reply\": \"There is a typo in the import statement. The correct function should be '__import__' from the 'importlib' module, not 'importclass' which does not exist. Correct code:\n```python\nloaded_module = import_module(module_name, fromlist=[class_name])```\",\n      \"filename\": \"test_file.py\",\n      \"lineNumber\": 21,\n      \"codeSnippet\": \"loaded_module = importclass(module_name, fromlist=[class_name])\"\n    }\n  ]\n  }"
+              "arguments": "{\n  \"replies\": [\n    {\n      \"reply\": \"The commit message 'Test Commit Message' is too vague and does not provide information about the specific changes made. A more detailed message is necessary to understand what has been fixed.\"\n    },\n    {\n      \"reply\": \"Confirm that the method 'importclass' is meant to change its behavior when 'class_name' is None. The new lines suggest 'class_name' will be derived from the 'module_name' in such cases, which can have unintended effects if not explicitly intended.\",\n      \"filename\": \"test_file.py\",\n      \"lineNumber\": 19,\n      \"codeSnippet\": \"if not class_name:\nmodule_name,class_name=module_name.rsplit('.',1)\"\n    },\n    {\n      \"reply\": \"The added check to determine if 'class_name' is None seems to modify the 'module_name' by splitting it and taking the last element. There should be an assignment to 'class_name' since the class to be imported is meant to be the last part of 'module_name' after splitting.\",\n      \"filename\": \"test_file.py\",\n      \"lineNumber\": 20,\n      \"codeSnippet\": \"module_name, class_name = module_name.rsplit('.', 1)\"\n    },\n    {\n      \"reply\": \"The code line 'from types import Any, Callable, ...' should use 'typing' for imports instead of 'types'.\",\n      \"filename\": \"test_file.py\",\n      \"lineNumber\": 1,\n      \"codeSnippet\": \"from types import\"\n    },\n    {\n      \"reply\": \"There is a typo in the import statement. The correct function should be '__import__' from the 'importlib' module, not 'importclass' which does not exist. Correct code:\n```python\nloaded_module = import_module(module_name, fromlist=[class_name])```\",\n      \"filename\": \"test_file.py\",\n      \"lineNumber\": 21,\n      \"codeSnippet\": \"loaded_module = importclass(module_name, fromlist=[class_name])\"\n    }\n  ],\n  \"score\": -1\n  }"
             }
           }
         ]
diff --git a/src/test/resources/__files/gerritPatchSetReview.json b/src/test/resources/__files/gerritPatchSetReview.json
index edd434c..64212f2 100644
--- a/src/test/resources/__files/gerritPatchSetReview.json
+++ b/src/test/resources/__files/gerritPatchSetReview.json
@@ -1 +1 @@
-{"comments":{"test_file.py":[{"line":19,"range":{"start_line":19,"end_line":20,"start_character":4,"end_character":60},"message":"Confirm that the method \u0027importclass\u0027 is meant to change its behavior when \u0027class_name\u0027 is None. The new lines suggest \u0027class_name\u0027 will be derived from the \u0027module_name\u0027 in such cases, which can have unintended effects if not explicitly intended."},{"line":20,"range":{"start_line":20,"end_line":20,"start_character":8,"end_character":60},"message":"The added check to determine if \u0027class_name\u0027 is None seems to modify the \u0027module_name\u0027 by splitting it and taking the last element. There should be an assignment to \u0027class_name\u0027 since the class to be imported is meant to be the last part of \u0027module_name\u0027 after splitting."},{"line":1,"range":{"start_line":1,"end_line":1,"start_character":0,"end_character":17},"message":"The code line \u0027from types import Any, Callable, ...\u0027 should use \u0027typing\u0027 for imports instead of \u0027types\u0027."},{"line":21,"range":{"start_line":21,"end_line":21,"start_character":4,"end_character":67},"message":"There is a typo in the import statement. The correct function should be \u0027\\__import\\__\u0027 from the \u0027importlib\u0027 module, not \u0027importclass\u0027 which does not exist. Correct code:\n\n```\nloaded_module \u003d import_module(module_name, fromlist\u003d[class_name])\n```\n"}]},"message":"The commit message \u0027Test Commit Message\u0027 is too vague and does not provide information about the specific changes made. A more detailed message is necessary to understand what has been fixed."}
\ No newline at end of file
+{"comments":{"test_file.py":[{"line":19,"range":{"start_line":19,"end_line":20,"start_character":4,"end_character":60},"message":"Confirm that the method \u0027importclass\u0027 is meant to change its behavior when \u0027class_name\u0027 is None. The new lines suggest \u0027class_name\u0027 will be derived from the \u0027module_name\u0027 in such cases, which can have unintended effects if not explicitly intended."},{"line":20,"range":{"start_line":20,"end_line":20,"start_character":8,"end_character":60},"message":"The added check to determine if \u0027class_name\u0027 is None seems to modify the \u0027module_name\u0027 by splitting it and taking the last element. There should be an assignment to \u0027class_name\u0027 since the class to be imported is meant to be the last part of \u0027module_name\u0027 after splitting."},{"line":1,"range":{"start_line":1,"end_line":1,"start_character":0,"end_character":17},"message":"The code line \u0027from types import Any, Callable, ...\u0027 should use \u0027typing\u0027 for imports instead of \u0027types\u0027."},{"line":21,"range":{"start_line":21,"end_line":21,"start_character":4,"end_character":67},"message":"There is a typo in the import statement. The correct function should be \u0027\\__import\\__\u0027 from the \u0027importlib\u0027 module, not \u0027importclass\u0027 which does not exist. Correct code:\n\n```\nloaded_module \u003d import_module(module_name, fromlist\u003d[class_name])\n```\n"}]},"message":"The commit message \u0027Test Commit Message\u0027 is too vague and does not provide information about the specific changes made. A more detailed message is necessary to understand what has been fixed.","labels":{"Code-Review":-1}}
\ No newline at end of file