Migrate GerritClientPatchSetStateless to direct GerritApi

The corresponing tests were updated to mock required GerritApi
endpoints.

Bug: Issue 336577745
Change-Id: I13cbfc773ae9e44ab74928144092f49fd26845b5
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateless/client/api/gerrit/GerritClientPatchSetStateless.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateless/client/api/gerrit/GerritClientPatchSetStateless.java
index 531d872..de313da 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateless/client/api/gerrit/GerritClientPatchSetStateless.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateless/client/api/gerrit/GerritClientPatchSetStateless.java
@@ -1,25 +1,27 @@
 package com.googlesource.gerrit.plugins.chatgpt.mode.stateless.client.api.gerrit;
 
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
 import com.google.inject.Inject;
+import com.google.gerrit.extensions.common.DiffInfo;
+import com.google.gerrit.extensions.common.FileInfo;
+import com.google.gerrit.server.util.ManualRequestContext;
 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.client.api.gerrit.GerritClientPatchSet;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.patch.diff.FileDiffProcessed;
+import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.api.gerrit.GerritFileDiff;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.api.gerrit.GerritPatchSetFileDiff;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.api.gerrit.GerritReviewFileDiff;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.data.ChangeSetData;
 import com.googlesource.gerrit.plugins.chatgpt.mode.interfaces.client.api.gerrit.IGerritClientPatchSet;
-import com.googlesource.gerrit.plugins.chatgpt.mode.stateless.client.api.UriResourceLocatorStateless;
 import lombok.extern.slf4j.Slf4j;
 
-import java.net.URI;
+import static java.util.stream.Collectors.toList;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+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
@@ -37,41 +39,65 @@
         int revisionBase = getChangeSetRevisionBase(changeSetData);
         log.debug("Revision base: {}", revisionBase);
 
-        List<String> files = getAffectedFiles(change.getFullChangeId(), revisionBase);
+        List<String> files = getAffectedFiles(change, revisionBase);
         log.debug("Patch files: {}", files);
 
-        String fileDiffsJson = getFileDiffsJson(change.getFullChangeId(), files, revisionBase);
+        String fileDiffsJson = getFileDiffsJson(change, files, revisionBase);
         log.debug("File diffs: {}", fileDiffsJson);
 
         return fileDiffsJson;
     }
 
-    private List<String> getAffectedFiles(String fullChangeId, int revisionBase) throws Exception {
-        URI uri = URI.create(config.getGerritAuthBaseUrl()
-                + UriResourceLocatorStateless.gerritPatchSetFilesUri(fullChangeId)
-                + UriResourceLocatorStateless.gerritRevisionBasePostfixUri(revisionBase));
-        log.debug("Affected Files URI: '{}'", uri);
-        JsonObject affectedFileMap = forwardGetRequestReturnJsonObject(uri);
-        List<String> files = new ArrayList<>();
-        for (Map.Entry<String, JsonElement> file : affectedFileMap.entrySet()) {
-            String filename = file.getKey();
-            if (!filename.equals("/COMMIT_MSG") || config.getGptReviewCommitMessages()) {
-                int size = Integer.parseInt(file.getValue().getAsJsonObject().get("size").getAsString());
-                if (size > config.getMaxReviewFileSize()) {
-                    log.info("File '{}' not reviewed because its size exceeds the fixed maximum allowable size.",
-                            filename);
-                }
-                else {
-                    files.add(filename);
-                }
-            }
+    private List<String> getAffectedFiles(GerritChange change, int revisionBase) throws Exception {
+        try (ManualRequestContext requestContext = config.openRequestContext()) {
+            Map<String, FileInfo> files =
+                config
+                    .getGerritApi()
+                    .changes()
+                    .id(
+                        change.getProjectName(),
+                        change.getBranchNameKey().shortName(),
+                        change.getChangeKey().get())
+                    .current()
+                    .files(revisionBase);
+            return files.entrySet().stream()
+                .filter(
+                    fileEntry -> {
+                      String filename = fileEntry.getKey();
+                      if (!filename.equals("/COMMIT_MSG") || config.getGptReviewCommitMessages()) {
+                        if (fileEntry.getValue().size > config.getMaxReviewFileSize()) {
+                          log.info(
+                              "File '{}' not reviewed because its size exceeds the fixed maximum allowable size.",
+                              filename);
+                        } else {
+                          return true;
+                        }
+                      }
+                      return false;
+                    })
+                .map(Map.Entry::getKey)
+                .collect(toList());
         }
-        return files;
     }
 
-    private void processFileDiff(String filename, String fileDiffJson) {
-        log.debug("FileDiff Json processed: {}", fileDiffJson);
-        GerritPatchSetFileDiff gerritPatchSetFileDiff = getGson().fromJson(fileDiffJson, GerritPatchSetFileDiff.class);
+    private void processFileDiff(String filename, DiffInfo diff) {
+        log.debug("FileDiff content processed: {}", filename);
+
+        GerritPatchSetFileDiff gerritPatchSetFileDiff = new GerritPatchSetFileDiff();
+        Optional.ofNullable(diff.metaA)
+            .ifPresent(
+                meta -> gerritPatchSetFileDiff.setMetaA(GerritClientPatchSetStateless.toMeta(meta)));
+        Optional.ofNullable(diff.metaB)
+            .ifPresent(
+                meta -> gerritPatchSetFileDiff.setMetaB(GerritClientPatchSetStateless.toMeta(meta)));
+        Optional.ofNullable(diff.content)
+            .ifPresent(
+                content ->
+                    gerritPatchSetFileDiff.setContent(
+                        content.stream()
+                            .map(GerritClientPatchSetStateless::toContent)
+                            .collect(toList())));
+
         // Initialize the reduced file diff for the Gerrit review with fields `meta_a` and `meta_b`
         GerritReviewFileDiff gerritReviewFileDiff = new GerritReviewFileDiff(gerritPatchSetFileDiff.getMetaA(),
                 gerritPatchSetFileDiff.getMetaB());
@@ -81,24 +107,45 @@
         diffs.add(getNoEscapedGson().toJson(gerritReviewFileDiff));
     }
 
-    private String getFileDiffsJson(String fullChangeId, List<String> files, int revisionBase) throws Exception {
+    private String getFileDiffsJson(GerritChange change, List<String> files, int revisionBase) throws Exception {
         List<String> enabledFileExtensions = config.getEnabledFileExtensions();
-        for (String filename : files) {
-            isCommitMessage = filename.equals("/COMMIT_MSG");
-            if (!isCommitMessage && (filename.lastIndexOf(".") < 1 ||
-                    !enabledFileExtensions.contains(filename.substring(filename.lastIndexOf("."))))) {
-                continue;
+        try (ManualRequestContext requestContext = config.openRequestContext()) {
+            for (String filename : files) {
+                isCommitMessage = filename.equals("/COMMIT_MSG");
+                if (!isCommitMessage && (filename.lastIndexOf(".") < 1 ||
+                        !enabledFileExtensions.contains(filename.substring(filename.lastIndexOf("."))))) {
+                    continue;
+                }
+                DiffInfo diff =
+                    config
+                        .getGerritApi()
+                        .changes()
+                        .id(
+                            change.getProjectName(),
+                            change.getBranchNameKey().shortName(),
+                            change.getChangeKey().get())
+                        .current()
+                        .file(filename)
+                        .diff(revisionBase);
+                processFileDiff(filename, diff);
             }
-            URI uri = URI.create(config.getGerritAuthBaseUrl()
-                    + UriResourceLocatorStateless.gerritPatchSetFilesUri(fullChangeId)
-                    + UriResourceLocatorStateless.gerritDiffPostfixUri(filename)
-                    + UriResourceLocatorStateless.gerritRevisionBasePostfixUri(revisionBase));
-            log.debug("getFileDiffsJson URI: '{}'", uri);
-            String fileDiffJson = forwardGetRequest(uri).replaceAll("^[')\\]}]+", "");
-            processFileDiff(filename, fileDiffJson);
         }
-        diffs.add(String.format("{\"changeId\": \"%s\"}", fullChangeId));
+        diffs.add(String.format("{\"changeId\": \"%s\"}", change.getFullChangeId()));
         return "[" + String.join(",", diffs) + "]\n";
     }
 
+    private static GerritFileDiff.Meta toMeta(DiffInfo.FileMeta input) {
+        GerritFileDiff.Meta meta = new GerritFileDiff.Meta();
+        meta.setContentType(input.contentType);
+        meta.setName(input.name);
+        return meta;
+    }
+
+    private static GerritPatchSetFileDiff.Content toContent(DiffInfo.ContentEntry input) {
+        GerritPatchSetFileDiff.Content content = new GerritPatchSetFileDiff.Content();
+        content.a = input.a;
+        content.b = input.b;
+        content.ab = input.ab;
+        return content;
+    }
 }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatelessTest.java b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatelessTest.java
index de04876..c4fb24f 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatelessTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatelessTest.java
@@ -3,13 +3,18 @@
 import com.github.tomakehurst.wiremock.client.WireMock;
 import com.google.common.net.HttpHeaders;
 import com.googlesource.gerrit.plugins.chatgpt.listener.EventHandlerTask;
+import com.google.gerrit.extensions.api.changes.FileApi;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.common.DiffInfo;
+import com.google.gerrit.extensions.common.FileInfo;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.json.OutputFormat;
 import com.google.gson.Gson;
 import com.googlesource.gerrit.plugins.chatgpt.mode.stateless.client.api.UriResourceLocatorStateless;
 import com.googlesource.gerrit.plugins.chatgpt.mode.stateless.client.prompt.ChatGptPromptStateless;
 import lombok.extern.slf4j.Slf4j;
+
+import org.apache.commons.lang3.reflect.TypeLiteral;
 import org.apache.http.entity.ContentType;
 import org.junit.Assert;
 import org.junit.Ignore;
@@ -21,9 +26,11 @@
 
 import java.net.URI;
 import java.util.Arrays;
+import java.util.Map;
 
 import static com.googlesource.gerrit.plugins.chatgpt.utils.TextUtils.joinWithNewLine;
 import static java.net.HttpURLConnection.HTTP_OK;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 @Slf4j
@@ -55,28 +62,25 @@
     protected void setupMockRequests() throws RestApiException {
         super.setupMockRequests();
 
-        String fullChangeId = getGerritChange().getFullChangeId();
+        // Mock the GerritApi's revision API
+        when(changeApiMock.current()).thenReturn(revisionApiMock);
 
         // Mock the behavior of the gerritPatchSetFiles request
-        WireMock.stubFor(WireMock.get(UriResourceLocatorStateless.gerritPatchSetFilesUri(fullChangeId))
-                .willReturn(WireMock.aResponse()
-                        .withStatus(HTTP_OK)
-                        .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
-                        .withBodyFile("gerritPatchSetFiles.json")));
+        Map<String, FileInfo> files =
+            readTestFileToType(
+                "__files/gerritPatchSetFiles.json",
+                new TypeLiteral<Map<String, FileInfo>>() {}.getType());
+        when(revisionApiMock.files(0)).thenReturn(files);
 
         // Mock the behavior of the gerritPatchSet diff requests
-        WireMock.stubFor(WireMock.get(UriResourceLocatorStateless.gerritPatchSetFilesUri(fullChangeId) +
-                            UriResourceLocatorStateless.gerritDiffPostfixUri("/COMMIT_MSG"))
-                .willReturn(WireMock.aResponse()
-                        .withStatus(HTTP_OK)
-                        .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
-                        .withBodyFile("gerritPatchSetDiffCommitMsg.json")));
-        WireMock.stubFor(WireMock.get(UriResourceLocatorStateless.gerritPatchSetFilesUri(fullChangeId) +
-                            UriResourceLocatorStateless.gerritDiffPostfixUri("test_file.py"))
-                .willReturn(WireMock.aResponse()
-                        .withStatus(HTTP_OK)
-                        .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
-                        .withBodyFile("gerritPatchSetDiffTestFile.json")));
+        FileApi commitMsgFileMock = mock(FileApi.class);
+        when(revisionApiMock.file("/COMMIT_MSG")).thenReturn(commitMsgFileMock);
+        DiffInfo commitMsgFileDiff = readTestFileToClass("__files/gerritPatchSetDiffCommitMsg.json", DiffInfo.class);
+        when(commitMsgFileMock.diff(0)).thenReturn(commitMsgFileDiff);
+        FileApi testFileMock = mock(FileApi.class);
+        when(revisionApiMock.file("test_file.py")).thenReturn(testFileMock);
+        DiffInfo testFileDiff = readTestFileToClass("__files/gerritPatchSetDiffTestFile.json", DiffInfo.class);
+        when(testFileMock.diff(0)).thenReturn(testFileDiff);
 
         // Mock the behavior of the askGpt request
         WireMock.stubFor(WireMock.post(WireMock.urlEqualTo(URI.create(config.getGptDomain()
@@ -85,9 +89,6 @@
                         .withStatus(HTTP_OK)
                         .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
                         .withBodyFile("chatGptResponseStreamed.txt")));
-
-        // Mock the GerritApi's revision API
-        when(changeApiMock.current()).thenReturn(revisionApiMock);
     }
 
     protected void initComparisonContent() {
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 a8ee958..c969231 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTestBase.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTestBase.java
@@ -64,6 +64,7 @@
 import org.mockito.stubbing.Answer;
 
 import java.io.IOException;
+import java.lang.reflect.Type;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -263,6 +264,11 @@
         return gson.fromJson(readTestFile(filename), clazz);
     }
 
+    protected <T> T readTestFileToType(String filename, Type type) {
+        Gson gson = OutputFormat.JSON.newGson();
+        return gson.fromJson(readTestFile(filename), type);
+    }
+
     protected String readTestFile(String filename) {
         try {
             return new String(Files.readAllBytes(basePath.resolve(filename)));