Initiate ChatGPT thread for patch set review

At the Patch Set Review event, a ChatGPT thread for the assistant
associated with the Git Project of the Patch Set is initiated. The
thread begins by posting a first message requesting a review of the
formatted patch of the Patch Set.

Change-Id: Idde4742d3df74ff9ec7cacf0218adb20db973855
Signed-off-by: Patrizio <patrizio.gelosi@amarulasolutions.com>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/api/chatgpt/ChatGptClient.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/api/chatgpt/ChatGptClient.java
new file mode 100644
index 0000000..13b96da
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/api/chatgpt/ChatGptClient.java
@@ -0,0 +1,74 @@
+package com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.chatgpt;
+
+import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.api.chatgpt.*;
+
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.BufferedReader;
+import java.io.StringReader;
+import java.util.List;
+import java.util.Optional;
+
+import static com.googlesource.gerrit.plugins.chatgpt.utils.GsonUtils.getGson;
+
+@Slf4j
+abstract public class ChatGptClient {
+    protected boolean isCommentEvent = false;
+    @Getter
+    protected String requestBody;
+
+    protected String extractContent(Configuration config, String body) throws Exception {
+        if (config.getGptStreamOutput() && !isCommentEvent) {
+            StringBuilder finalContent = new StringBuilder();
+            try (BufferedReader reader = new BufferedReader(new StringReader(body))) {
+                String line;
+                while ((line = reader.readLine()) != null) {
+                    extractContentFromLine(line).ifPresent(finalContent::append);
+                }
+            }
+            return finalContent.toString();
+        }
+        else {
+            ChatGptResponseUnstreamed chatGptResponseUnstreamed =
+                    getGson().fromJson(body, ChatGptResponseUnstreamed.class);
+            return getResponseContent(chatGptResponseUnstreamed.getChoices().get(0).getMessage().getToolCalls());
+        }
+    }
+
+    protected boolean validateResponse(String contentExtracted, String changeId, int attemptInd) {
+        ChatGptResponseContent chatGptResponseContent =
+                getGson().fromJson(contentExtracted, ChatGptResponseContent.class);
+        String returnedChangeId = chatGptResponseContent.getChangeId();
+        // A response is considered valid if either no changeId is returned or the changeId returned matches the one
+        // provided in the request
+        boolean isValidated = returnedChangeId == null || changeId.equals(returnedChangeId);
+        if (!isValidated) {
+            log.error("ChangedId mismatch error (attempt #{}).\nExpected value: {}\nReturned value: {}", attemptInd,
+                    changeId, returnedChangeId);
+        }
+        return isValidated;
+    }
+
+    protected String getResponseContent(List<ChatGptToolCall> toolCalls) {
+        return toolCalls.get(0).getFunction().getArguments();
+    }
+
+    protected Optional<String> extractContentFromLine(String line) {
+        String dataPrefix = "data: {\"id\"";
+
+        if (!line.startsWith(dataPrefix)) {
+            return Optional.empty();
+        }
+        ChatGptResponseStreamed chatGptResponseStreamed =
+                getGson().fromJson(line.substring("data: ".length()), ChatGptResponseStreamed.class);
+        ChatGptResponseMessage delta = chatGptResponseStreamed.getChoices().get(0).getDelta();
+        if (delta == null || delta.getToolCalls() == null) {
+            return Optional.empty();
+        }
+        String content = getResponseContent(delta.getToolCalls());
+        return Optional.ofNullable(content);
+    }
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/interfaces/client/api/chatgpt/IChatGptClient.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/interfaces/client/api/chatgpt/IChatGptClient.java
index c13e410..9a3f6fd 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/interfaces/client/api/chatgpt/IChatGptClient.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/interfaces/client/api/chatgpt/IChatGptClient.java
@@ -5,7 +5,6 @@
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.data.ChangeSetData;
 
 public interface IChatGptClient {
-    String ask(Configuration config, ChangeSetData changeSetData, String changeId, String patchSet) throws Exception;
     String ask(Configuration config, ChangeSetData changeSetData, GerritChange change, String patchSet) throws Exception;
     String getRequestBody();
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/UriResourceLocatorStateful.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/UriResourceLocatorStateful.java
index c4f7dc1..ed152bd 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/UriResourceLocatorStateful.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/UriResourceLocatorStateful.java
@@ -9,4 +9,15 @@
         return "/v1/assistants";
     }
 
+    public static String threadsUri() {
+        return "/v1/threads";
+    }
+
+    public static String threadRetrieveUri(String threadId) {
+        return threadsUri() + "/" + threadId;
+    }
+
+    public static String threadMessagesUri(String threadId) {
+        return threadRetrieveUri(threadId) + "/messages";
+    }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptClientStateful.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptClientStateful.java
index f409fb5..0cf3af8 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptClientStateful.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptClientStateful.java
@@ -1,7 +1,11 @@
 package com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.api.chatgpt;
 
+import com.google.common.annotations.VisibleForTesting;
+import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandler;
+import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.chatgpt.ChatGptClient;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritChange;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.data.ChangeSetData;
 import com.googlesource.gerrit.plugins.chatgpt.mode.interfaces.client.api.chatgpt.IChatGptClient;
@@ -9,23 +13,27 @@
 
 @Slf4j
 @Singleton
-public class ChatGptClientStateful implements IChatGptClient {
-    @Override
-    public String ask(Configuration config, ChangeSetData changeSetData, String changeId, String patchSet) throws Exception {
-        // Placeholder implementation, change to actual logic later.
-        throw new UnsupportedOperationException("Method not implemented yet.");
+public class ChatGptClientStateful extends ChatGptClient implements IChatGptClient {
+    private final PluginDataHandler pluginDataHandler;
+
+    @VisibleForTesting
+    @Inject
+    public ChatGptClientStateful(PluginDataHandler pluginDataHandler) {
+        super();
+        this.pluginDataHandler = pluginDataHandler;
     }
 
-    @Override
-    public String ask(Configuration config, ChangeSetData changeSetData, GerritChange change, String patchSet) throws Exception {
+    public String ask(Configuration config, ChangeSetData changeSetData, GerritChange change, String patchSet) {
+        isCommentEvent = change.getIsCommentEvent();
+        String changeId = change.getFullChangeId();
+        log.info("Processing STATEFUL ChatGPT Request with changeId: {}, Patch Set: {}", changeId, patchSet);
+
+        ChatGptThread chatGptThread = new ChatGptThread(config, change, patchSet);
+        String threadId = chatGptThread.createThread();
+        chatGptThread.addMessage();
+
         // Placeholder implementation, change to actual logic later.
         throw new UnsupportedOperationException("Method not implemented yet.");
     }
 
-    @Override
-    public String getRequestBody() {
-        // Placeholder implementation, change to actual logic later.
-        return "Method not implemented yet.";
-    }
-
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptThread.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptThread.java
new file mode 100644
index 0000000..f0b4920
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptThread.java
@@ -0,0 +1,74 @@
+package com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.api.chatgpt;
+
+import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritChange;
+import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.api.chatgpt.ChatGptRequestMessage;
+import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.api.UriResourceLocatorStateful;
+import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.prompt.ChatGptPromptStateful;
+import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.model.api.chatgpt.*;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.Request;
+
+import java.net.URI;
+
+import static com.googlesource.gerrit.plugins.chatgpt.utils.GsonUtils.getGson;
+
+@Slf4j
+public class ChatGptThread {
+    private final ChatGptHttpClient httpClient = new ChatGptHttpClient();
+    private final Configuration config;
+    private final GerritChange change;
+    private final String patchSet;
+
+    private String threadId;
+    private ChatGptRequestMessage addMessageRequestBody;
+
+    public ChatGptThread(Configuration config, GerritChange change, String patchSet) {
+        this.config = config;
+        this.change = change;
+        this.patchSet = patchSet;
+    }
+
+    public String createThread() {
+        Request request = createThreadRequest();
+        log.debug("ChatGPT Create Thread request: {}", request);
+
+        ChatGptResponse threadResponse = getGson().fromJson(httpClient.execute(request), ChatGptResponse.class);
+        log.info("Thread created: {}", threadResponse);
+        threadId = threadResponse.getId();
+
+        return threadId;
+    }
+
+    public void addMessage() {
+        Request request = addMessageRequest();
+        log.debug("ChatGPT Add Message request: {}", request);
+
+        ChatGptResponse addMessageResponse = getGson().fromJson(httpClient.execute(request), ChatGptResponse.class);
+        log.info("Message added: {}", addMessageResponse);
+    }
+
+    public String getAddMessageRequestBody() {
+        return getGson().toJson(addMessageRequestBody);
+    }
+
+    private Request createThreadRequest() {
+        URI uri = URI.create(config.getGptDomain() + UriResourceLocatorStateful.threadsUri());
+        log.debug("ChatGPT Create Thread request URI: {}", uri);
+
+        return httpClient.createRequestFromJson(uri.toString(), config.getGptToken(), new Object());
+    }
+
+    private Request addMessageRequest() {
+        URI uri = URI.create(config.getGptDomain() + UriResourceLocatorStateful.threadMessagesUri(threadId));
+        log.debug("ChatGPT Add Message request URI: {}", uri);
+        ChatGptPromptStateful chatGptPromptStateful = new ChatGptPromptStateful(config, change);
+        addMessageRequestBody = ChatGptRequestMessage.builder()
+                .role("user")
+                .content(chatGptPromptStateful.getDefaultGptThreadReviewMessage(patchSet))
+                .build();
+
+        return httpClient.createRequestFromJson(uri.toString(), config.getGptToken(), addMessageRequestBody);
+    }
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/prompt/ChatGptPromptStateful.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/prompt/ChatGptPromptStateful.java
index d54a12d..8299cb1 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/prompt/ChatGptPromptStateful.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/prompt/ChatGptPromptStateful.java
@@ -11,6 +11,7 @@
     public static String DEFAULT_GPT_ASSISTANT_NAME;
     public static String DEFAULT_GPT_ASSISTANT_DESCRIPTION;
     public static String DEFAULT_GPT_ASSISTANT_INSTRUCTIONS;
+    public static String DEFAULT_GPT_MESSAGE_REVIEW;
 
     private final GerritChange change;
 
@@ -30,8 +31,12 @@
 
     public String getDefaultGptAssistantInstructions() {
         return DEFAULT_GPT_SYSTEM_PROMPT + DOT +
-                String.format(DEFAULT_GPT_ASSISTANT_INSTRUCTIONS, change.getProjectName()) +
+                String.format(DEFAULT_GPT_ASSISTANT_INSTRUCTIONS, change.getProjectName()) + SPACE +
                 getPatchSetReviewUserPrompt();
     }
 
+    public String getDefaultGptThreadReviewMessage(String patchSet) {
+        return String.format(DEFAULT_GPT_MESSAGE_REVIEW, patchSet);
+    }
+
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateless/client/api/chatgpt/ChatGptClientStateless.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateless/client/api/chatgpt/ChatGptClientStateless.java
index 5cddeb7..e1b34ac 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateless/client/api/chatgpt/ChatGptClientStateless.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateless/client/api/chatgpt/ChatGptClientStateless.java
@@ -3,6 +3,7 @@
 import com.google.common.net.HttpHeaders;
 import com.google.inject.Singleton;
 import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.chatgpt.ChatGptClient;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.chatgpt.ChatGptParameters;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.chatgpt.ChatGptTools;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritChange;
@@ -13,33 +14,29 @@
 import com.googlesource.gerrit.plugins.chatgpt.mode.stateless.client.api.UriResourceLocatorStateless;
 import com.googlesource.gerrit.plugins.chatgpt.mode.stateless.client.prompt.ChatGptPromptStateless;
 import com.googlesource.gerrit.plugins.chatgpt.mode.stateless.model.api.chatgpt.ChatGptCompletionRequest;
-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.StringReader;
 import java.net.URI;
 import java.net.http.HttpRequest;
 import java.net.http.HttpResponse;
 import java.util.List;
-import java.util.Optional;
 
-import static com.googlesource.gerrit.plugins.chatgpt.utils.GsonUtils.getGson;
 import static com.googlesource.gerrit.plugins.chatgpt.utils.GsonUtils.getNoEscapedGson;
 
 @Slf4j
 @Singleton
-public class ChatGptClientStateless implements IChatGptClient {
+public class ChatGptClientStateless extends ChatGptClient implements IChatGptClient {
     private static final int REVIEW_ATTEMPT_LIMIT = 3;
-    @Getter
-    private String requestBody;
-    private final HttpClientWithRetry httpClientWithRetry = new HttpClientWithRetry();
-    private boolean isCommentEvent = false;
 
-    @Override
-    public String ask(Configuration config, ChangeSetData changeSetData, String changeId, String patchSet) throws Exception {
+    private final HttpClientWithRetry httpClientWithRetry = new HttpClientWithRetry();
+
+    public String ask(Configuration config, ChangeSetData changeSetData, GerritChange change, String patchSet)
+            throws Exception {
+        isCommentEvent = change.getIsCommentEvent();
+        String changeId = change.getFullChangeId();
+        log.info("Processing STATELESS ChatGPT Request with changeId: {}, Patch Set: {}", changeId, patchSet);
         for (int attemptInd = 0; attemptInd < REVIEW_ATTEMPT_LIMIT; attemptInd++) {
             HttpRequest request = createRequest(config, changeSetData, patchSet);
             log.debug("ChatGPT request: {}", request.toString());
@@ -47,7 +44,7 @@
             HttpResponse<String> response = httpClientWithRetry.execute(request);
 
             String body = response.body();
-            log.debug("body: {}", body);
+            log.debug("ChatGPT response body: {}", body);
             if (body == null) {
                 throw new IOException("ChatGPT response body is null");
             }
@@ -60,50 +57,7 @@
         throw new RuntimeException("Failed to receive valid ChatGPT response");
     }
 
-    @Override
-    public String ask(Configuration config, ChangeSetData changeSetData, GerritChange change, String patchSet) throws Exception {
-        isCommentEvent = change.getIsCommentEvent();
-
-        return this.ask(config, changeSetData, change.getFullChangeId(), patchSet);
-    }
-
-    private String extractContent(Configuration config, String body) throws Exception {
-        if (config.getGptStreamOutput() && !isCommentEvent) {
-            StringBuilder finalContent = new StringBuilder();
-            try (BufferedReader reader = new BufferedReader(new StringReader(body))) {
-                String line;
-                while ((line = reader.readLine()) != null) {
-                    extractContentFromLine(line).ifPresent(finalContent::append);
-                }
-            }
-            return finalContent.toString();
-        }
-        else {
-            ChatGptResponseUnstreamed chatGptResponseUnstreamed =
-                    getGson().fromJson(body, ChatGptResponseUnstreamed.class);
-            return getResponseContent(chatGptResponseUnstreamed.getChoices().get(0).getMessage().getToolCalls());
-        }
-    }
-
-    private boolean validateResponse(String contentExtracted, String changeId, int attemptInd) {
-        ChatGptResponseContent chatGptResponseContent =
-                getGson().fromJson(contentExtracted, ChatGptResponseContent.class);
-        String returnedChangeId = chatGptResponseContent.getChangeId();
-        // A response is considered valid if either no changeId is returned or the changeId returned matches the one
-        // provided in the request
-        boolean isValidated = returnedChangeId == null || changeId.equals(returnedChangeId);
-        if (!isValidated) {
-            log.error("ChangedId mismatch error (attempt #{}).\nExpected value: {}\nReturned value: {}", attemptInd,
-                    changeId, returnedChangeId);
-        }
-        return isValidated;
-    }
-
-    private String getResponseContent(List<ChatGptToolCall> toolCalls) {
-        return toolCalls.get(0).getFunction().getArguments();
-    }
-
-    private HttpRequest createRequest(Configuration config, ChangeSetData changeSetData, String patchSet) {
+    protected HttpRequest createRequest(Configuration config, ChangeSetData changeSetData, String patchSet) {
         URI uri = URI.create(config.getGptDomain() + UriResourceLocatorStateless.chatCompletionsUri());
         log.debug("ChatGPT request URI: {}", uri);
         requestBody = createRequestBody(config, changeSetData, patchSet);
@@ -147,20 +101,4 @@
         return getNoEscapedGson().toJson(chatGptCompletionRequest);
     }
 
-    private Optional<String> extractContentFromLine(String line) {
-        String dataPrefix = "data: {\"id\"";
-
-        if (!line.startsWith(dataPrefix)) {
-            return Optional.empty();
-        }
-        ChatGptResponseStreamed chatGptResponseStreamed =
-                getGson().fromJson(line.substring("data: ".length()), ChatGptResponseStreamed.class);
-        ChatGptResponseMessage delta = chatGptResponseStreamed.getChoices().get(0).getDelta();
-        if (delta == null || delta.getToolCalls() == null) {
-            return Optional.empty();
-        }
-        String content = getResponseContent(delta.getToolCalls());
-        return Optional.ofNullable(content);
-    }
-
 }
diff --git a/src/main/resources/Config/promptsStateful.json b/src/main/resources/Config/promptsStateful.json
index c34f909..26ed239 100644
--- a/src/main/resources/Config/promptsStateful.json
+++ b/src/main/resources/Config/promptsStateful.json
@@ -1,5 +1,6 @@
 {
   "DEFAULT_GPT_ASSISTANT_NAME": "PatchSet Reviewer",
   "DEFAULT_GPT_ASSISTANT_DESCRIPTION": "PatchSet Reviewer for project %s.",
-  "DEFAULT_GPT_ASSISTANT_INSTRUCTIONS": "The JSON project file uploaded includes the source files for the `%s` project. The structure uses the file paths from the project's root as keys, and arrays of lines as their values. This arrangement ensures that the line number for any given line corresponds to its index in the array plus one. You will receive a patch in the standard git format-patch format. Your tasks include: 1. applying this patch to the corresponding existing files, and 2. Conducting a review of the patch. Here are the guidelines for reviewing the patch: A. identify any potential problems and offer suggestions for enhancements, presenting each point as a separate reply; B. Focus solely on identifying and suggesting solutions for issues; refrain from highlighting any positive aspects; C. Only evaluate the code that has been modified in the patch; refrain from reviewing any other parts of the project's code that were not changed."
+  "DEFAULT_GPT_ASSISTANT_INSTRUCTIONS": "The JSON project file uploaded includes the source files for the `%s` project. The structure uses the file paths from the project's root as keys, and arrays of lines as their values. This arrangement ensures that the line number for any given line corresponds to its index in the array plus one. You will receive a patch in the standard git format-patch format. Your tasks include: 1. applying this patch to the corresponding existing files, and 2. conducting a review of the patch. Here are the guidelines for reviewing the patch: A. Identify any potential problems and offer suggestions for enhancements, presenting each point as a separate reply; B. Focus solely on identifying and suggesting solutions for issues; refrain from highlighting any positive aspects; C. Only evaluate the code that has been modified in the patch; refrain from reviewing any other parts of the project's code that were not changed.",
+  "DEFAULT_GPT_MESSAGE_REVIEW": "Review the following Patch Set: ```%s```"
 }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTestBase.java b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTestBase.java
index ab257c1..aa7e361 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTestBase.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTestBase.java
@@ -374,7 +374,7 @@
 
     private IChatGptClient getChatGptClient() {
         return switch (config.getGptMode()) {
-            case stateful -> new ChatGptClientStateful();
+            case stateful -> new ChatGptClientStateful(pluginDataHandler);
             case stateless -> new ChatGptClientStateless();
         };
     }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/integration/CodeReviewPluginIT.java b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/integration/CodeReviewPluginIT.java
index 4cf4436..d6f873b 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/integration/CodeReviewPluginIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/integration/CodeReviewPluginIT.java
@@ -49,7 +49,7 @@
         when(config.getGptModel()).thenReturn(Configuration.DEFAULT_GPT_MODEL);
         when(chatGptPromptStateless.getGptSystemPrompt()).thenReturn(ChatGptPromptStateless.DEFAULT_GPT_SYSTEM_PROMPT);
 
-        String answer = chatGptClient.ask(config, changeSetData, "", "hello");
+        String answer = chatGptClient.ask(config, changeSetData, new GerritChange(""), "hello");
         log.info("answer: {}", answer);
         assertNotNull(answer);
     }