Refactor: separate stateless mode tests
Tests specific to stateless mode functions have been refactored, with
code potentially shared with stateful mode being segregated into the
ChatGptReviewTestBase class.
Change-Id: Ide3bc6d6e22f5fd952c94b96f4efbc3ebd0f74c7
Signed-off-by: Patrizio <patrizio.gelosi@amarulasolutions.com>
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 c4debeb..67a6aef 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatelessTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatelessTest.java
@@ -1,82 +1,29 @@
package com.googlesource.gerrit.plugins.chatgpt;
import com.github.tomakehurst.wiremock.client.WireMock;
-import com.github.tomakehurst.wiremock.junit.WireMockRule;
-import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder;
-import com.github.tomakehurst.wiremock.verification.LoggedRequest;
import com.google.common.net.HttpHeaders;
-import com.google.gerrit.entities.BranchNameKey;
-import com.google.gerrit.entities.Change;
-import com.google.gerrit.entities.Project;
-import com.google.gerrit.server.config.PluginConfig;
-import com.google.gerrit.server.data.AccountAttribute;
-import com.google.gerrit.server.data.PatchSetAttribute;
-import com.google.gerrit.server.events.CommentAddedEvent;
-import com.google.gerrit.server.events.PatchSetCreatedEvent;
-import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonObject;
-import com.googlesource.gerrit.plugins.chatgpt.config.ConfigCreator;
-import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
-import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandler;
-import com.googlesource.gerrit.plugins.chatgpt.listener.EventListenerHandler;
-import com.googlesource.gerrit.plugins.chatgpt.listener.GerritListener;
-import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritChange;
-import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritClient;
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.http.entity.ContentType;
import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentMatchers;
-import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
-import org.mockito.stubbing.Answer;
import java.net.URI;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.io.IOException;
import java.util.Arrays;
-import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
-import static com.google.gerrit.extensions.client.ChangeKind.REWORK;
-import static com.googlesource.gerrit.plugins.chatgpt.mode.stateless.client.api.UriResourceLocatorStateless.*;
-import static com.googlesource.gerrit.plugins.chatgpt.utils.GsonUtils.getGson;
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
@RunWith(MockitoJUnitRunner.class)
-public class ChatGptReviewStatelessTest {
- private static final Path basePath = Paths.get("src/test/resources");
- private static final String GERRIT_AUTH_BASE_URL = "http://localhost:9527";
- private static final int GERRIT_GPT_ACCOUNT_ID = 1000000;
- private static final String GERRIT_GPT_USERNAME = "gpt";
- private static final int GERRIT_USER_ACCOUNT_ID = 1000001;
- private static final String GERRIT_USER_ACCOUNT_NAME = "Test";
- private static final String GERRIT_USER_ACCOUNT_EMAIL = "test@example.com";
- private static final String GERRIT_USER_USERNAME = "test";
- private static final String GERRIT_USER_PASSWORD = "test";
- private static final String GERRIT_USER_GROUP = "Test";
- private static final String GPT_TOKEN = "tk-test";
- private static final String GPT_DOMAIN = "http://localhost:9527";
- private static final Project.NameKey PROJECT_NAME = Project.NameKey.parse("myProject");
- private static final Change.Key CHANGE_ID = Change.Key.parse("myChangeId");
- 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;
-
+public class ChatGptReviewStatelessTest extends ChatGptReviewTestBase {
private String expectedResponseStreamed;
private String expectedSystemPromptReview;
private String promptTagReview;
@@ -84,107 +31,20 @@
private String diffContent;
private String gerritPatchSetReview;
- @Rule
- public WireMockRule wireMockRule = new WireMockRule(9527);
-
- @Mock
- private PluginDataHandler pluginDataHandler;
-
- private PluginConfig globalConfig;
- private PluginConfig projectConfig;
- private Configuration config;
private ChatGptPromptStateless chatGptPromptStateless;
- @Before
- public void before() throws IOException {
- initConfig();
- setupMockRequests();
- initComparisonContent();
- }
-
- private GerritChange getGerritChange() {
- return new GerritChange(PROJECT_NAME, BRANCH_NAME, CHANGE_ID);
- }
-
- private void initConfig() {
- globalConfig = mock(PluginConfig.class);
- Answer<Object> returnDefaultArgument = invocation -> {
- // Return the second argument (i.e., the Default value) passed to the method
- return invocation.getArgument(1);
- };
-
- // Mock the Global Config values not provided by Default
- when(globalConfig.getString("gerritAuthBaseUrl")).thenReturn(GERRIT_AUTH_BASE_URL);
- when(globalConfig.getString("gerritUserName")).thenReturn(GERRIT_USER_USERNAME);
- when(globalConfig.getString("gerritPassword")).thenReturn(GERRIT_USER_PASSWORD);
- when(globalConfig.getString("gptToken")).thenReturn(GPT_TOKEN);
-
- // Mock the Global Config values to the Defaults passed as second arguments of the `get*` methods.
- when(globalConfig.getString(Mockito.anyString(), Mockito.anyString())).thenAnswer(returnDefaultArgument);
- when(globalConfig.getInt(Mockito.anyString(), Mockito.anyInt())).thenAnswer(returnDefaultArgument);
- when(globalConfig.getBoolean(Mockito.anyString(), Mockito.anyBoolean())).thenAnswer(returnDefaultArgument);
-
- // Mock the Global Config values that differ from the ones provided by Default
- when(globalConfig.getString(Mockito.eq("gptDomain"), Mockito.anyString()))
- .thenReturn(GPT_DOMAIN);
- when(globalConfig.getBoolean(Mockito.eq("gptStreamOutput"), Mockito.anyBoolean()))
- .thenReturn(GPT_STREAM_OUTPUT);
- when(globalConfig.getBoolean(Mockito.eq("gptReviewCommitMessages"), Mockito.anyBoolean()))
- .thenReturn(true);
-
- projectConfig = mock(PluginConfig.class);
-
- // Mock the Project Config values
- when(projectConfig.getBoolean(Mockito.eq("isEnabled"), Mockito.anyBoolean())).thenReturn(true);
-
- config = new Configuration(globalConfig, projectConfig);
-
- // Mock the config instance values
- when(config.getGerritUserName()).thenReturn(GERRIT_GPT_USERNAME);
+ protected void initConfig() {
+ super.initConfig();
// Load the prompts
chatGptPromptStateless = new ChatGptPromptStateless(config);
}
- private void setupMockRequests() {
+ protected void setupMockRequests() {
+ super.setupMockRequests();
+
String fullChangeId = getGerritChange().getFullChangeId();
- // Mock the behavior of the gerritAccountIdUri request
- WireMock.stubFor(WireMock.get(gerritAccountIdUri(GERRIT_GPT_USERNAME))
- .willReturn(WireMock.aResponse()
- .withStatus(HTTP_OK)
- .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
- .withBody("[{\"_account_id\": " + GERRIT_GPT_ACCOUNT_ID + "}]")));
-
- // Mock the behavior of the gerritAccountIdUri request
- WireMock.stubFor(WireMock.get(gerritAccountIdUri(GERRIT_USER_USERNAME))
- .willReturn(WireMock.aResponse()
- .withStatus(HTTP_OK)
- .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
- .withBody("[{\"_account_id\": " + GERRIT_USER_ACCOUNT_ID + "}]")));
-
- // Mock the behavior of the gerritAccountGroups request
- WireMock.stubFor(WireMock.get(UriResourceLocatorStateless.gerritAccountsUri() +
- gerritGroupPostfixUri(GERRIT_USER_ACCOUNT_ID))
- .willReturn(WireMock.aResponse()
- .withStatus(HTTP_OK)
- .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
- .withBodyFile("gerritAccountGroups.json")));
-
- // Mock the behavior of the gerritPatchSetRevisionsUri request
- WireMock.stubFor(WireMock.get(gerritPatchSetRevisionsUri(fullChangeId))
- .willReturn(WireMock.aResponse()
- .withStatus(HTTP_OK)
- .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
- .withBody("{\"revisions\":{\"aa5be5ebb80846475ec4dfe43e0799eb73c6415a\":{}}}")));
-
- // Mock the behavior of the gerritGetPatchSetDetailUri request
- WireMock.stubFor(WireMock.get(gerritGetPatchSetDetailUri(fullChangeId))
- .willReturn(WireMock.aResponse()
- .withStatus(HTTP_OK)
- .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
- .withBodyFile("gerritPatchSetDetail.json")));
-
// Mock the behavior of the gerritPatchSetFiles request
WireMock.stubFor(WireMock.get(UriResourceLocatorStateless.gerritPatchSetFilesUri(fullChangeId))
.willReturn(WireMock.aResponse()
@@ -206,13 +66,6 @@
.withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
.withBodyFile("gerritPatchSetDiffTestFile.json")));
- // Mock the behavior of the gerritPatchSet comments request
- WireMock.stubFor(WireMock.get(gerritGetAllPatchSetCommentsUri(fullChangeId))
- .willReturn(WireMock.aResponse()
- .withStatus(HTTP_OK)
- .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
- .withBodyFile("gerritPatchSetComments.json")));
-
// Mock the behavior of the askGpt request
WireMock.stubFor(WireMock.post(WireMock.urlEqualTo(URI.create(config.getGptDomain()
+ UriResourceLocatorStateless.chatCompletionsUri()).getPath()))
@@ -220,46 +73,21 @@
.withStatus(HTTP_OK)
.withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
.withBodyFile("chatGptResponseStreamed.txt")));
-
- // Mock the behavior of the postReview request
- WireMock.stubFor(WireMock.post(gerritSetReviewUri(fullChangeId))
- .willReturn(WireMock.aResponse()
- .withStatus(HTTP_OK)));
}
- private void initComparisonContent() throws IOException {
- diffContent = new String(Files.readAllBytes(basePath.resolve("reducePatchSet/patchSetDiffOutput.json")));
- gerritPatchSetReview = new String(Files.readAllBytes(basePath.resolve("__files/gerritPatchSetReview.json")));
- expectedResponseStreamed = new String(Files.readAllBytes(basePath.resolve(
- "__files/chatGptExpectedResponseStreamed.json")));
- promptTagReview = new String(Files.readAllBytes(basePath.resolve(
- "__files/chatGptPromptTagReview.json")));
- promptTagComments = new String(Files.readAllBytes(basePath.resolve("__files/chatGptPromptTagRequests.json")));
+ protected void initComparisonContent() {
+ super.initComparisonContent();
+
+ diffContent = readTestFile("reducePatchSet/patchSetDiffOutput.json");
+ gerritPatchSetReview = readTestFile("__files/gerritPatchSetReview.json");
+ expectedResponseStreamed = readTestFile("__files/chatGptExpectedResponseStreamed.json");
+ promptTagReview = readTestFile("__files/chatGptPromptTagReview.json");
+ promptTagComments = readTestFile("__files/chatGptPromptTagRequests.json");
expectedSystemPromptReview = ChatGptPromptStateless.getDefaultGptReviewSystemPrompt();
}
- private AccountAttribute createTestAccountAttribute() {
- AccountAttribute accountAttribute = new AccountAttribute();
- accountAttribute.name = GERRIT_USER_ACCOUNT_NAME;
- accountAttribute.username = GERRIT_USER_USERNAME;
- accountAttribute.email = GERRIT_USER_ACCOUNT_EMAIL;
- return accountAttribute;
- }
-
- private PatchSetAttribute createPatchSetAttribute() {
- PatchSetAttribute patchSetAttribute = new PatchSetAttribute();
- patchSetAttribute.kind = REWORK;
- patchSetAttribute.author = createTestAccountAttribute();
- return patchSetAttribute;
- }
-
- @Test
- public void patchSetCreatedOrUpdatedStreamed() throws InterruptedException, NoSuchProjectException, ExecutionException {
- GerritClient gerritClient = new GerritClient();
- PatchSetReviewer patchSetReviewer = new PatchSetReviewer(gerritClient);
- ConfigCreator mockConfigCreator = mock(ConfigCreator.class);
- when(mockConfigCreator.createConfig(ArgumentMatchers.any())).thenReturn(config);
- String reviewUserPrompt = joinWithNewLine(Arrays.asList(
+ private String getReviewUserPrompt() {
+ return joinWithNewLine(Arrays.asList(
ChatGptPromptStateless.DEFAULT_GPT_REVIEW_PROMPT,
ChatGptPromptStateless.DEFAULT_GPT_REVIEW_PROMPT_REVIEW + " " +
chatGptPromptStateless.getPatchSetReviewUserPrompt(),
@@ -269,57 +97,33 @@
ChatGptPromptStateless.DEFAULT_GPT_REVIEW_PROMPT_MESSAGE_HISTORY,
promptTagReview
));
+ }
+
+ @Test
+ public void patchSetCreatedOrUpdatedStreamed() throws InterruptedException, ExecutionException {
+ String reviewUserPrompt = getReviewUserPrompt();
chatGptPromptStateless.setCommentEvent(false);
- PatchSetCreatedEvent event = mock(PatchSetCreatedEvent.class);
- when(event.getProjectNameKey()).thenReturn(PROJECT_NAME);
- when(event.getBranchNameKey()).thenReturn(BRANCH_NAME);
- when(event.getChangeKey()).thenReturn(CHANGE_ID);
- when(event.getType()).thenReturn("patchset-created");
- event.patchSet = this::createPatchSetAttribute;
- EventListenerHandler eventListenerHandler = new EventListenerHandler(patchSetReviewer, gerritClient);
-
- GerritListener gerritListener = new GerritListener(mockConfigCreator, eventListenerHandler, pluginDataHandler);
- gerritListener.onEvent(event);
- CompletableFuture<Void> future = eventListenerHandler.getLatestFuture();
+ CompletableFuture<Void> future = handleEventBasedOnType(false);
future.get();
- RequestPatternBuilder requestPatternBuilder = WireMock.postRequestedFor(
- WireMock.urlEqualTo(gerritSetReviewUri(getGerritChange().getFullChangeId())));
- List<LoggedRequest> loggedRequests = WireMock.findAll(requestPatternBuilder);
- Assert.assertEquals(1, loggedRequests.size());
- JsonObject gptRequestBody = getGson().fromJson(patchSetReviewer.getChatGptClient().getRequestBody(), JsonObject.class);
- JsonArray prompts = gptRequestBody.get("messages").getAsJsonArray();
+ testRequestSent();
String systemPrompt = prompts.get(0).getAsJsonObject().get("content").getAsString();
Assert.assertEquals(expectedSystemPromptReview, systemPrompt);
String userPrompt = prompts.get(1).getAsJsonObject().get("content").getAsString();
Assert.assertEquals(reviewUserPrompt, userPrompt);
String requestBody = loggedRequests.get(0).getBodyAsString();
Assert.assertEquals(expectedResponseStreamed, requestBody);
-
}
@Test
- public void patchSetCreatedOrUpdatedUnstreamed() throws InterruptedException, NoSuchProjectException, ExecutionException {
+ public void patchSetCreatedOrUpdatedUnstreamed() throws InterruptedException, ExecutionException {
when(globalConfig.getBoolean(Mockito.eq("gptStreamOutput"), Mockito.anyBoolean()))
.thenReturn(false);
when(globalConfig.getBoolean(Mockito.eq("enabledVoting"), Mockito.anyBoolean()))
.thenReturn(true);
- GerritClient gerritClient = new GerritClient();
- PatchSetReviewer patchSetReviewer = new PatchSetReviewer(gerritClient);
- ConfigCreator mockConfigCreator = mock(ConfigCreator.class);
- when(mockConfigCreator.createConfig(ArgumentMatchers.any())).thenReturn(config);
- String reviewVoteUserPrompt = joinWithNewLine(Arrays.asList(
- ChatGptPromptStateless.DEFAULT_GPT_REVIEW_PROMPT,
- ChatGptPromptStateless.DEFAULT_GPT_REVIEW_PROMPT_REVIEW + " " +
- chatGptPromptStateless.getPatchSetReviewUserPrompt(),
- ChatGptPromptStateless.DEFAULT_GPT_REVIEW_PROMPT_COMMIT_MESSAGES,
- ChatGptPromptStateless.DEFAULT_GPT_REVIEW_PROMPT_DIFF,
- diffContent,
- ChatGptPromptStateless.DEFAULT_GPT_REVIEW_PROMPT_MESSAGE_HISTORY,
- promptTagReview
- ));
+ String reviewUserPrompt = getReviewUserPrompt();
chatGptPromptStateless.setCommentEvent(false);
WireMock.stubFor(WireMock.post(WireMock.urlEqualTo(URI.create(config.getGptDomain()
+ UriResourceLocatorStateless.chatCompletionsUri()).getPath()))
@@ -328,64 +132,28 @@
.withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
.withBodyFile("chatGptResponseReview.json")));
- PatchSetCreatedEvent event = mock(PatchSetCreatedEvent.class);
- when(event.getProjectNameKey()).thenReturn(PROJECT_NAME);
- when(event.getBranchNameKey()).thenReturn(BRANCH_NAME);
- when(event.getChangeKey()).thenReturn(CHANGE_ID);
- when(event.getType()).thenReturn("patchset-created");
- event.patchSet = this::createPatchSetAttribute;
- EventListenerHandler eventListenerHandler = new EventListenerHandler(patchSetReviewer, gerritClient);
-
- GerritListener gerritListener = new GerritListener(mockConfigCreator, eventListenerHandler, pluginDataHandler);
- gerritListener.onEvent(event);
- CompletableFuture<Void> future = eventListenerHandler.getLatestFuture();
+ CompletableFuture<Void> future = handleEventBasedOnType(false);
future.get();
- RequestPatternBuilder requestPatternBuilder = WireMock.postRequestedFor(
- WireMock.urlEqualTo(gerritSetReviewUri(getGerritChange().getFullChangeId())));
- List<LoggedRequest> loggedRequests = WireMock.findAll(requestPatternBuilder);
- Assert.assertEquals(1, loggedRequests.size());
- JsonObject gptRequestBody = getGson().fromJson(patchSetReviewer.getChatGptClient().getRequestBody(), JsonObject.class);
- JsonArray prompts = gptRequestBody.get("messages").getAsJsonArray();
+ testRequestSent();
String userPrompt = prompts.get(1).getAsJsonObject().get("content").getAsString();
- Assert.assertEquals(reviewVoteUserPrompt, userPrompt);
+ Assert.assertEquals(reviewUserPrompt, userPrompt);
String requestBody = loggedRequests.get(0).getBodyAsString();
Assert.assertEquals(gerritPatchSetReview, requestBody);
-
}
@Test
- public void patchSetDisableUserGroup() throws NoSuchProjectException {
+ public void patchSetDisableUserGroup() {
when(globalConfig.getString(Mockito.eq("disabledGroups"), Mockito.anyString()))
.thenReturn(GERRIT_USER_GROUP);
- GerritClient gerritClient = new GerritClient();
- PatchSetReviewer patchSetReviewer = new PatchSetReviewer(gerritClient);
- ConfigCreator mockConfigCreator = mock(ConfigCreator.class);
- when(mockConfigCreator.createConfig(ArgumentMatchers.any())).thenReturn(config);
- PatchSetCreatedEvent event = mock(PatchSetCreatedEvent.class);
- when(event.getProjectNameKey()).thenReturn(PROJECT_NAME);
- when(event.getBranchNameKey()).thenReturn(BRANCH_NAME);
- when(event.getChangeKey()).thenReturn(CHANGE_ID);
- when(event.getType()).thenReturn("patchset-created");
- event.patchSet = this::createPatchSetAttribute;
- EventListenerHandler eventListenerHandler = new EventListenerHandler(patchSetReviewer, gerritClient);
-
- GerritListener gerritListener = new GerritListener(mockConfigCreator, eventListenerHandler, pluginDataHandler);
- gerritListener.onEvent(event);
- CompletableFuture<Void> future = eventListenerHandler.getLatestFuture();
+ CompletableFuture<Void> future = handleEventBasedOnType(false);
Assert.assertThrows(NullPointerException.class, () -> future.get());
-
}
@Test
- public void gptMentionedInComment() throws InterruptedException, NoSuchProjectException, ExecutionException {
- GerritChange gerritChange = getGerritChange();
- GerritClient gerritClient = new GerritClient();
- PatchSetReviewer patchSetReviewer = new PatchSetReviewer(gerritClient);
- ConfigCreator mockConfigCreator = mock(ConfigCreator.class);
+ public void gptMentionedInComment() throws InterruptedException, ExecutionException {
when(config.getGerritUserName()).thenReturn(GERRIT_GPT_USERNAME);
- when(mockConfigCreator.createConfig(ArgumentMatchers.any())).thenReturn(config);
chatGptPromptStateless.setCommentEvent(true);
WireMock.stubFor(WireMock.post(WireMock.urlEqualTo(URI.create(config.getGptDomain()
+ UriResourceLocatorStateless.chatCompletionsUri()).getPath()))
@@ -394,20 +162,8 @@
.withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
.withBodyFile("chatGptResponseRequests.json")));
- CommentAddedEvent event = mock(CommentAddedEvent.class);
- when(event.getProjectNameKey()).thenReturn(PROJECT_NAME);
- when(event.getBranchNameKey()).thenReturn(BRANCH_NAME);
- when(event.getChangeKey()).thenReturn(CHANGE_ID);
- when(event.getType()).thenReturn("comment-added");
- event.author = this::createTestAccountAttribute;
- event.patchSet = this::createPatchSetAttribute;
- event.eventCreatedOn = TEST_TIMESTAMP;
- EventListenerHandler eventListenerHandler = new EventListenerHandler(patchSetReviewer, gerritClient);
-
- GerritListener gerritListener = new GerritListener(mockConfigCreator, eventListenerHandler, pluginDataHandler);
- gerritListener.onEvent(event);
- int commentPropertiesSize = gerritClient.getClientData(gerritChange).getCommentProperties().size();
- CompletableFuture<Void> future = eventListenerHandler.getLatestFuture();
+ CompletableFuture<Void> future = handleEventBasedOnType(true);
+ int commentPropertiesSize = gerritClient.getClientData(getGerritChange()).getCommentProperties().size();
future.get();
String commentUserPrompt = joinWithNewLine(Arrays.asList(
@@ -417,15 +173,9 @@
promptTagComments,
ChatGptPromptStateless.getCommentRequestUserPrompt(commentPropertiesSize)
));
- RequestPatternBuilder requestPatternBuilder = WireMock.postRequestedFor(
- WireMock.urlEqualTo(gerritSetReviewUri(gerritChange.getFullChangeId())));
- List<LoggedRequest> loggedRequests = WireMock.findAll(requestPatternBuilder);
- Assert.assertEquals(1, loggedRequests.size());
- JsonObject gptRequestBody = getGson().fromJson(patchSetReviewer.getChatGptClient().getRequestBody(), JsonObject.class);
- JsonArray prompts = gptRequestBody.get("messages").getAsJsonArray();
+ testRequestSent();
String userPrompt = prompts.get(1).getAsJsonObject().get("content").getAsString();
Assert.assertEquals(commentUserPrompt, userPrompt);
-
}
}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTestBase.java b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTestBase.java
new file mode 100644
index 0000000..d4669ee
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTestBase.java
@@ -0,0 +1,272 @@
+package com.googlesource.gerrit.plugins.chatgpt;
+
+import com.github.tomakehurst.wiremock.client.WireMock;
+import com.github.tomakehurst.wiremock.junit.WireMockRule;
+import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder;
+import com.github.tomakehurst.wiremock.verification.LoggedRequest;
+import com.google.common.net.HttpHeaders;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.config.PluginConfig;
+import com.google.gerrit.server.data.AccountAttribute;
+import com.google.gerrit.server.data.PatchSetAttribute;
+import com.google.gerrit.server.events.CommentAddedEvent;
+import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.PatchSetCreatedEvent;
+import com.google.gerrit.server.events.PatchSetEvent;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.googlesource.gerrit.plugins.chatgpt.config.ConfigCreator;
+import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandler;
+import com.googlesource.gerrit.plugins.chatgpt.listener.EventListenerHandler;
+import com.googlesource.gerrit.plugins.chatgpt.listener.GerritListener;
+import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritChange;
+import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritClient;
+import com.googlesource.gerrit.plugins.chatgpt.mode.stateless.client.api.UriResourceLocatorStateless;
+import lombok.NonNull;
+import org.apache.http.entity.ContentType;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+
+import static com.google.gerrit.extensions.client.ChangeKind.REWORK;
+import static com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.UriResourceLocator.*;
+import static com.googlesource.gerrit.plugins.chatgpt.utils.GsonUtils.getGson;
+import static java.net.HttpURLConnection.HTTP_OK;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ChatGptReviewTestBase {
+ protected static final Path basePath = Paths.get("src/test/resources");
+ protected static final String GERRIT_AUTH_BASE_URL = "http://localhost:9527";
+ protected static final int GERRIT_GPT_ACCOUNT_ID = 1000000;
+ protected static final String GERRIT_GPT_USERNAME = "gpt";
+ protected static final int GERRIT_USER_ACCOUNT_ID = 1000001;
+ protected static final String GERRIT_USER_ACCOUNT_NAME = "Test";
+ protected static final String GERRIT_USER_ACCOUNT_EMAIL = "test@example.com";
+ protected static final String GERRIT_USER_USERNAME = "test";
+ protected static final String GERRIT_USER_PASSWORD = "test";
+ protected static final String GERRIT_USER_GROUP = "Test";
+ protected static final String GPT_TOKEN = "tk-test";
+ protected static final String GPT_DOMAIN = "http://localhost:9527";
+ protected static final Project.NameKey PROJECT_NAME = Project.NameKey.parse("myProject");
+ protected static final Change.Key CHANGE_ID = Change.Key.parse("myChangeId");
+ protected static final BranchNameKey BRANCH_NAME = BranchNameKey.create(PROJECT_NAME, "myBranchName");
+ protected static final boolean GPT_STREAM_OUTPUT = true;
+ protected static final long TEST_TIMESTAMP = 1699270812;
+
+ @Rule
+ public WireMockRule wireMockRule = new WireMockRule(9527);
+
+ @Mock
+ protected PluginDataHandler pluginDataHandler;
+
+ protected PluginConfig globalConfig;
+ protected PluginConfig projectConfig;
+ protected Configuration config;
+ protected GerritClient gerritClient;
+ protected PatchSetReviewer patchSetReviewer;
+ protected ConfigCreator mockConfigCreator;
+ protected List<LoggedRequest> loggedRequests;
+ protected JsonArray prompts;
+
+ @Before
+ public void before() throws NoSuchProjectException {
+ initConfig();
+ setupMockRequests();
+ initComparisonContent();
+ initTest();
+ }
+
+ protected GerritChange getGerritChange() {
+ return new GerritChange(PROJECT_NAME, BRANCH_NAME, CHANGE_ID);
+ }
+
+ protected void initConfig() {
+ globalConfig = mock(PluginConfig.class);
+ Answer<Object> returnDefaultArgument = invocation -> {
+ // Return the second argument (i.e., the Default value) passed to the method
+ return invocation.getArgument(1);
+ };
+
+ // Mock the Global Config values not provided by Default
+ when(globalConfig.getString("gerritAuthBaseUrl")).thenReturn(GERRIT_AUTH_BASE_URL);
+ when(globalConfig.getString("gerritUserName")).thenReturn(GERRIT_USER_USERNAME);
+ when(globalConfig.getString("gerritPassword")).thenReturn(GERRIT_USER_PASSWORD);
+ when(globalConfig.getString("gptToken")).thenReturn(GPT_TOKEN);
+
+ // Mock the Global Config values to the Defaults passed as second arguments of the `get*` methods.
+ when(globalConfig.getString(Mockito.anyString(), Mockito.anyString())).thenAnswer(returnDefaultArgument);
+ when(globalConfig.getInt(Mockito.anyString(), Mockito.anyInt())).thenAnswer(returnDefaultArgument);
+ when(globalConfig.getBoolean(Mockito.anyString(), Mockito.anyBoolean())).thenAnswer(returnDefaultArgument);
+
+ // Mock the Global Config values that differ from the ones provided by Default
+ when(globalConfig.getString(Mockito.eq("gptDomain"), Mockito.anyString()))
+ .thenReturn(GPT_DOMAIN);
+ when(globalConfig.getBoolean(Mockito.eq("gptStreamOutput"), Mockito.anyBoolean()))
+ .thenReturn(GPT_STREAM_OUTPUT);
+ when(globalConfig.getBoolean(Mockito.eq("gptReviewCommitMessages"), Mockito.anyBoolean()))
+ .thenReturn(true);
+
+ projectConfig = mock(PluginConfig.class);
+
+ // Mock the Project Config values
+ when(projectConfig.getBoolean(Mockito.eq("isEnabled"), Mockito.anyBoolean())).thenReturn(true);
+
+ config = new Configuration(globalConfig, projectConfig);
+
+ // Mock the config instance values
+ when(config.getGerritUserName()).thenReturn(GERRIT_GPT_USERNAME);
+ }
+
+ protected void setupMockRequests() {
+ String fullChangeId = getGerritChange().getFullChangeId();
+
+ // Mock the behavior of the gerritAccountIdUri request
+ WireMock.stubFor(WireMock.get(gerritAccountIdUri(GERRIT_GPT_USERNAME))
+ .willReturn(WireMock.aResponse()
+ .withStatus(HTTP_OK)
+ .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
+ .withBody("[{\"_account_id\": " + GERRIT_GPT_ACCOUNT_ID + "}]")));
+
+ // Mock the behavior of the gerritAccountIdUri request
+ WireMock.stubFor(WireMock.get(gerritAccountIdUri(GERRIT_USER_USERNAME))
+ .willReturn(WireMock.aResponse()
+ .withStatus(HTTP_OK)
+ .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
+ .withBody("[{\"_account_id\": " + GERRIT_USER_ACCOUNT_ID + "}]")));
+
+ // Mock the behavior of the gerritAccountGroups request
+ WireMock.stubFor(WireMock.get(UriResourceLocatorStateless.gerritAccountsUri() +
+ gerritGroupPostfixUri(GERRIT_USER_ACCOUNT_ID))
+ .willReturn(WireMock.aResponse()
+ .withStatus(HTTP_OK)
+ .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
+ .withBodyFile("gerritAccountGroups.json")));
+
+ // Mock the behavior of the gerritPatchSetRevisionsUri request
+ WireMock.stubFor(WireMock.get(gerritPatchSetRevisionsUri(fullChangeId))
+ .willReturn(WireMock.aResponse()
+ .withStatus(HTTP_OK)
+ .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
+ .withBody("{\"revisions\":{\"aa5be5ebb80846475ec4dfe43e0799eb73c6415a\":{}}}")));
+
+ // Mock the behavior of the gerritGetPatchSetDetailUri request
+ WireMock.stubFor(WireMock.get(gerritGetPatchSetDetailUri(fullChangeId))
+ .willReturn(WireMock.aResponse()
+ .withStatus(HTTP_OK)
+ .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
+ .withBodyFile("gerritPatchSetDetail.json")));
+
+ // Mock the behavior of the gerritPatchSet comments request
+ WireMock.stubFor(WireMock.get(gerritGetAllPatchSetCommentsUri(fullChangeId))
+ .willReturn(WireMock.aResponse()
+ .withStatus(HTTP_OK)
+ .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
+ .withBodyFile("gerritPatchSetComments.json")));
+
+ // Mock the behavior of the postReview request
+ WireMock.stubFor(WireMock.post(gerritSetReviewUri(fullChangeId))
+ .willReturn(WireMock.aResponse()
+ .withStatus(HTTP_OK)));
+ }
+
+ protected void initComparisonContent() {}
+
+ protected String readTestFile(String filename) {
+ try {
+ return new String(Files.readAllBytes(basePath.resolve(filename)));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected CompletableFuture<Void> handleEventBasedOnType(boolean isCommentEvent) {
+ Consumer<Event> typeSpecificSetup = getTypeSpecificSetup(isCommentEvent);
+ Event event = isCommentEvent ? mock(CommentAddedEvent.class) : mock(PatchSetCreatedEvent.class);
+ setupCommonEventMocks((PatchSetEvent) event); // Apply common mock configurations
+ typeSpecificSetup.accept(event);
+
+ EventListenerHandler eventListenerHandler = new EventListenerHandler(patchSetReviewer, gerritClient);
+ GerritListener gerritListener = new GerritListener(mockConfigCreator, eventListenerHandler, pluginDataHandler);
+ gerritListener.onEvent(event);
+
+ return eventListenerHandler.getLatestFuture();
+ }
+
+ protected void testRequestSent() {
+ RequestPatternBuilder requestPatternBuilder = WireMock.postRequestedFor(
+ WireMock.urlEqualTo(gerritSetReviewUri(getGerritChange().getFullChangeId())));
+ loggedRequests = WireMock.findAll(requestPatternBuilder);
+ Assert.assertEquals(1, loggedRequests.size());
+ JsonObject gptRequestBody = getGson().fromJson(patchSetReviewer.getChatGptClient().getRequestBody(),
+ JsonObject.class);
+ prompts = gptRequestBody.get("messages").getAsJsonArray();
+ }
+
+ private void initTest () throws NoSuchProjectException {
+ gerritClient = new GerritClient();
+ patchSetReviewer = new PatchSetReviewer(gerritClient);
+ mockConfigCreator = mock(ConfigCreator.class);
+ when(mockConfigCreator.createConfig(ArgumentMatchers.any())).thenReturn(config);
+ }
+
+ private AccountAttribute createTestAccountAttribute() {
+ AccountAttribute accountAttribute = new AccountAttribute();
+ accountAttribute.name = GERRIT_USER_ACCOUNT_NAME;
+ accountAttribute.username = GERRIT_USER_USERNAME;
+ accountAttribute.email = GERRIT_USER_ACCOUNT_EMAIL;
+ return accountAttribute;
+ }
+
+ private PatchSetAttribute createPatchSetAttribute() {
+ PatchSetAttribute patchSetAttribute = new PatchSetAttribute();
+ patchSetAttribute.kind = REWORK;
+ patchSetAttribute.author = createTestAccountAttribute();
+ return patchSetAttribute;
+ }
+
+ @NonNull
+ private Consumer<Event> getTypeSpecificSetup(boolean isCommentEvent) {
+ Consumer<Event> typeSpecificSetup;
+
+ if (isCommentEvent) {
+ typeSpecificSetup = event -> {
+ CommentAddedEvent commentEvent = (CommentAddedEvent) event;
+ commentEvent.author = this::createTestAccountAttribute;
+ commentEvent.patchSet = this::createPatchSetAttribute;
+ commentEvent.eventCreatedOn = TEST_TIMESTAMP;
+ when(commentEvent.getType()).thenReturn("comment-added");
+ };
+ } else {
+ typeSpecificSetup = event -> {
+ PatchSetCreatedEvent patchEvent = (PatchSetCreatedEvent) event;
+ patchEvent.patchSet = this::createPatchSetAttribute;
+ when(patchEvent.getType()).thenReturn("patchset-created");
+ };
+ }
+ return typeSpecificSetup;
+ }
+
+ private void setupCommonEventMocks(PatchSetEvent event) {
+ when(event.getProjectNameKey()).thenReturn(PROJECT_NAME);
+ when(event.getBranchNameKey()).thenReturn(BRANCH_NAME);
+ when(event.getChangeKey()).thenReturn(CHANGE_ID);
+ }
+
+}