Enable continuous dialogue, ready for @{gerritUserName}'s queries
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/Module.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/Module.java
index 28725cb..cfc0a1f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/Module.java
@@ -1,13 +1,17 @@
package com.googlesource.gerrit.plugins.chatgpt;
-import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.server.events.EventListener;
import com.google.inject.AbstractModule;
+import com.google.inject.multibindings.Multibinder;
+import com.googlesource.gerrit.plugins.chatgpt.listener.GptMentionedCommentListener;
+import com.googlesource.gerrit.plugins.chatgpt.listener.PatchSetCreatedListener;
public class Module extends AbstractModule {
@Override
protected void configure() {
- DynamicSet.bind(binder(), EventListener.class).to(PatchSetCreated.class);
+ Multibinder<EventListener> eventListenerBinder = Multibinder.newSetBinder(binder(), EventListener.class);
+ eventListenerBinder.addBinding().to(PatchSetCreatedListener.class);
+ eventListenerBinder.addBinding().to(GptMentionedCommentListener.class);
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetCreated.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetCreated.java
deleted file mode 100644
index e9330c5..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetCreated.java
+++ /dev/null
@@ -1,101 +0,0 @@
-package com.googlesource.gerrit.plugins.chatgpt;
-
-import com.google.common.base.Splitter;
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
-import com.google.gerrit.entities.Project;
-import com.google.gerrit.server.events.Event;
-import com.google.gerrit.server.events.EventListener;
-import com.google.gerrit.server.events.PatchSetCreatedEvent;
-import com.google.inject.Inject;
-import lombok.extern.slf4j.Slf4j;
-
-import java.util.List;
-import java.util.concurrent.*;
-
-@Slf4j
-public class PatchSetCreated implements EventListener {
- private final ConfigCreator configCreator;
- private final PatchSetReviewer reviewer;
- private final BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(100);
- private final RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
- private final ThreadFactory threadFactory = new ThreadFactoryBuilder()
- .setNameFormat("PatchSetReviewExecutorThread-%d")
- .build();
- private final ExecutorService executorService = new ThreadPoolExecutor(
- 1, 1, 0L, TimeUnit.MILLISECONDS, queue, threadFactory, handler);
- private CompletableFuture<Void> latestFuture;
-
- @Inject
- PatchSetCreated(ConfigCreator configCreator, PatchSetReviewer reviewer) {
- this.configCreator = configCreator;
- this.reviewer = reviewer;
-
- addShutdownHoot();
-
- }
-
- public static String buildFullChangeId(String projectName, String branchName, String changeKey) {
- return String.join("~", projectName, branchName, changeKey);
- }
-
- private void addShutdownHoot() {
- Runtime.getRuntime().addShutdownHook(new Thread(() -> {
- executorService.shutdown();
- try {
- if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
- executorService.shutdownNow();
- }
- } catch (InterruptedException ex) {
- executorService.shutdownNow();
- Thread.currentThread().interrupt();
- }
- }));
- }
-
- @Override
- public void onEvent(Event event) {
- // Execute the potentially time-consuming operation asynchronously
- latestFuture = CompletableFuture.runAsync(() -> {
- if (!(event instanceof PatchSetCreatedEvent)) {
- log.debug("The event is not a PatchSetCreatedEvent, it is: {}", event);
- return;
- }
-
- log.info("Processing event: {}", event);
- PatchSetCreatedEvent createdPatchSetEvent = (PatchSetCreatedEvent) event;
- Project.NameKey projectNameKey = createdPatchSetEvent.getProjectNameKey();
- String projectName = projectNameKey.get();
- String branchName = createdPatchSetEvent.getBranchNameKey().shortName();
- String changeKey = createdPatchSetEvent.getChangeKey().get();
-
- log.info("Processing patch set for project: {}, branch: {}, change key: {}", projectName, branchName, changeKey);
-
- try {
- Configuration config = configCreator.createConfig(projectNameKey);
- List<String> enabledProjects = Splitter.on(",").omitEmptyStrings()
- .splitToList(config.getEnabledProjects());
- if (!config.isGlobalEnable() &&
- !enabledProjects.contains(projectName) &&
- !config.isProjectEnable()) {
- log.info("The project {} is not enabled for review", projectName);
- return;
- }
-
- String fullChangeId = buildFullChangeId(projectName, branchName, changeKey);
-
- reviewer.review(config, fullChangeId);
- log.info("Review completed for project: {}, branch: {}, change key: {}", projectName, branchName, changeKey);
- } catch (Exception e) {
- log.error("Failed to submit review for project: {}, branch: {}, change key: {}", projectName, branchName, changeKey, e);
- if (e instanceof InterruptedException) {
- Thread.currentThread().interrupt();
- }
- }
- }, executorService);
- }
-
- public CompletableFuture<Void> getLatestFuture() {
- return latestFuture;
- }
-
-}
\ No newline at end of file
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 c640b7e..508f77b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewer.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewer.java
@@ -4,6 +4,7 @@
import com.google.inject.Singleton;
import com.googlesource.gerrit.plugins.chatgpt.client.GerritClient;
import com.googlesource.gerrit.plugins.chatgpt.client.OpenAiClient;
+import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClient.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClient.java
index 5bfc5b8..376468a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClient.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClient.java
@@ -3,7 +3,7 @@
import com.google.common.net.HttpHeaders;
import com.google.gson.Gson;
import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.chatgpt.Configuration;
+import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.entity.ContentType;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/OpenAiClient.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/OpenAiClient.java
index f595d89..1e2e519 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/OpenAiClient.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/OpenAiClient.java
@@ -3,7 +3,7 @@
import com.google.common.net.HttpHeaders;
import com.google.gson.Gson;
import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.chatgpt.Configuration;
+import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.entity.ContentType;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/ConfigCreator.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/ConfigCreator.java
similarity index 78%
rename from src/main/java/com/googlesource/gerrit/plugins/chatgpt/ConfigCreator.java
rename to src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/ConfigCreator.java
index e3877b5..861e7ac 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/ConfigCreator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/ConfigCreator.java
@@ -1,4 +1,4 @@
-package com.googlesource.gerrit.plugins.chatgpt;
+package com.googlesource.gerrit.plugins.chatgpt.config;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.annotations.PluginName;
@@ -26,9 +26,10 @@
public Configuration createConfig(Project.NameKey projectName)
throws NoSuchProjectException {
PluginConfig globalConfig = configFactory.getFromGerritConfig(pluginName);
- log.info("The names in global config: {}", globalConfig.getNames());
+ log.info("These configuration items have been set in the global configuration: {}", globalConfig.getNames());
PluginConfig projectConfig = configFactory.getFromProjectConfig(projectName, pluginName);
- log.info("The names in project config: {}", projectConfig.getNames());
+ log.info("These configuration items have been set in the project configuration: {}", projectConfig.getNames());
return new Configuration(globalConfig, projectConfig);
}
+
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/Configuration.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/Configuration.java
similarity index 84%
rename from src/main/java/com/googlesource/gerrit/plugins/chatgpt/Configuration.java
rename to src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/Configuration.java
index f99f743..2f30c57 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/Configuration.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/Configuration.java
@@ -1,41 +1,49 @@
-package com.googlesource.gerrit.plugins.chatgpt;
+package com.googlesource.gerrit.plugins.chatgpt.config;
+import com.google.common.collect.Maps;
import com.google.gerrit.server.config.PluginConfig;
-import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import java.util.Map;
+
@Slf4j
-@AllArgsConstructor
public class Configuration {
public static final String OPENAI_DOMAIN = "https://api.openai.com";
public static final String DEFAULT_GPT_MODEL = "gpt-3.5-turbo";
public static final String DEFAULT_GPT_PROMPT = "Act as a Code Review Helper, please review this patch set: ";
public static final String NOT_CONFIGURED_ERROR_MSG = "%s is not configured";
+ public static final String KEY_GPT_PROMPT = "gptPrompt";
private static final String DEFAULT_GPT_TEMPERATURE = "1";
private static final boolean DEFAULT_GLOBAL_ENABLE = false;
private static final String DEFAULT_ENABLED_PROJECTS = "";
private static final boolean DEFAULT_PATCH_SET_REDUCTION = false;
private static final boolean DEFAULT_PROJECT_ENABLE = false;
private static final int DEFAULT_MAX_REVIEW_LINES = 1000;
-
private static final String KEY_GPT_TOKEN = "gptToken";
private static final String KEY_GERRIT_AUTH_BASE_URL = "gerritAuthBaseUrl";
private static final String KEY_GERRIT_USERNAME = "gerritUserName";
private static final String KEY_GERRIT_PASSWORD = "gerritPassword";
private static final String KEY_GPT_DOMAIN = "gptDomain";
private static final String KEY_GPT_MODEL = "gptModel";
- private static final String KEY_GPT_PROMPT = "gptPrompt";
private static final String KEY_GPT_TEMPERATURE = "gptTemperature";
private static final String KEY_PROJECT_ENABLE = "isEnabled";
private static final String KEY_GLOBAL_ENABLE = "globalEnable";
private static final String KEY_ENABLED_PROJECTS = "enabledProjects";
private static final String KEY_PATCH_SET_REDUCTION = "patchSetReduction";
private static final String KEY_MAX_REVIEW_LINES = "maxReviewLines";
+ private final Map<String, Object> configsDynamically = Maps.newHashMap();
+ private final PluginConfig globalConfig;
+ private final PluginConfig projectConfig;
- private PluginConfig globalConfig;
+ public Configuration(PluginConfig globalConfig, PluginConfig projectConfig) {
+ this.globalConfig = globalConfig;
+ this.projectConfig = projectConfig;
+ }
- private PluginConfig projectConfig;
+ public <T> void configureDynamically(String key, T value) {
+ configsDynamically.put(key, value);
+ }
public String getGptToken() {
return getValidatedOrThrow(KEY_GPT_TOKEN);
@@ -62,6 +70,9 @@
}
public String getGptPrompt() {
+ if (configsDynamically.get(KEY_GPT_PROMPT) != null) {
+ return configsDynamically.get(KEY_GPT_PROMPT).toString();
+ }
return getString(KEY_GPT_PROMPT, DEFAULT_GPT_PROMPT);
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/EventListenerHandler.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/EventListenerHandler.java
new file mode 100644
index 0000000..0f7f6f6
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/EventListenerHandler.java
@@ -0,0 +1,90 @@
+package com.googlesource.gerrit.plugins.chatgpt.listener;
+
+import com.google.common.base.Splitter;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.events.ChangeEvent;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.chatgpt.PatchSetReviewer;
+import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.List;
+import java.util.concurrent.*;
+
+@Slf4j
+public class EventListenerHandler {
+
+ private final PatchSetReviewer reviewer;
+ private final BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(100);
+ private final RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
+ private final ThreadFactory threadFactory = new ThreadFactoryBuilder()
+ .setNameFormat("EventListenerHandler-%d")
+ .build();
+ private final ExecutorService executorService = new ThreadPoolExecutor(
+ 1, 1, 0L, TimeUnit.MILLISECONDS, queue, threadFactory, handler);
+ private CompletableFuture<Void> latestFuture;
+
+ @Inject
+ public EventListenerHandler(PatchSetReviewer reviewer) {
+ this.reviewer = reviewer;
+
+ addShutdownHoot();
+ }
+
+ public static String buildFullChangeId(Project.NameKey projectName, BranchNameKey branchName, Change.Key changeKey) {
+ return String.join("~", projectName.get(), branchName.shortName(), changeKey.get());
+ }
+
+ private void addShutdownHoot() {
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ executorService.shutdown();
+ try {
+ if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
+ executorService.shutdownNow();
+ }
+ } catch (InterruptedException ex) {
+ executorService.shutdownNow();
+ Thread.currentThread().interrupt();
+ }
+ }));
+ }
+
+ public void handleEvent(Configuration config, ChangeEvent changeEvent) {
+ Project.NameKey projectNameKey = changeEvent.getProjectNameKey();
+ BranchNameKey branchNameKey = changeEvent.getBranchNameKey();
+ Change.Key changeKey = changeEvent.getChangeKey();
+
+ String fullChangeId = buildFullChangeId(projectNameKey, branchNameKey, changeKey);
+
+ List<String> enabledProjects = Splitter.on(",").omitEmptyStrings()
+ .splitToList(config.getEnabledProjects());
+ if (!config.isGlobalEnable() &&
+ !enabledProjects.contains(projectNameKey.get()) &&
+ !config.isProjectEnable()) {
+ log.info("The project {} is not enabled for review", projectNameKey);
+ return;
+ }
+
+ // Execute the potentially time-consuming operation asynchronously
+ latestFuture = CompletableFuture.runAsync(() -> {
+ try {
+ log.info("Processing change: {}", fullChangeId);
+ reviewer.review(config, fullChangeId);
+ log.info("Finished processing change: {}", fullChangeId);
+ } catch (Exception e) {
+ log.error("Error while processing change: {}", fullChangeId, e);
+ if (e instanceof InterruptedException) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }, executorService);
+ }
+
+ public CompletableFuture<Void> getLatestFuture() {
+ return latestFuture;
+ }
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/GptMentionedCommentListener.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/GptMentionedCommentListener.java
new file mode 100644
index 0000000..d1a71e0
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/GptMentionedCommentListener.java
@@ -0,0 +1,56 @@
+package com.googlesource.gerrit.plugins.chatgpt.listener;
+
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.events.ChangeEvent;
+import com.google.gerrit.server.events.CommentAddedEvent;
+import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.EventListener;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.chatgpt.config.ConfigCreator;
+import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class GptMentionedCommentListener implements EventListener {
+
+ private final ConfigCreator configCreator;
+
+ private final EventListenerHandler eventListenerHandler;
+
+ @Inject
+ public GptMentionedCommentListener(ConfigCreator configCreator, EventListenerHandler eventListenerHandler) {
+ this.configCreator = configCreator;
+ this.eventListenerHandler = eventListenerHandler;
+ }
+
+ @Override
+ public void onEvent(Event event) {
+ if (!(event instanceof CommentAddedEvent)) {
+ log.debug("The event is not a CommentAddedEvent, it is: {}", event);
+ return;
+ }
+
+ log.info("Processing event: {}", event);
+ ChangeEvent changeEvent = (ChangeEvent) event;
+ Project.NameKey projectNameKey = changeEvent.getProjectNameKey();
+
+ try {
+ Configuration config = configCreator.createConfig(projectNameKey);
+ String commentText = ((CommentAddedEvent) event).comment;
+ if (commentText == null || !commentText.contains("@" + config.getGerritUserName())) {
+ log.info("The comment does not mention the bot: {}", commentText);
+ return;
+ }
+
+ String questionToGpt = commentText.substring(commentText.indexOf("@" + config.getGerritUserName())
+ + config.getGerritUserName().length() + 1);
+ config.configureDynamically(Configuration.KEY_GPT_PROMPT, questionToGpt);
+
+ eventListenerHandler.handleEvent(config, changeEvent);
+ } catch (NoSuchProjectException e) {
+ log.error("Project not found: {}", projectNameKey, e);
+ }
+
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/PatchSetCreatedListener.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/PatchSetCreatedListener.java
new file mode 100644
index 0000000..225ac88
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/PatchSetCreatedListener.java
@@ -0,0 +1,46 @@
+package com.googlesource.gerrit.plugins.chatgpt.listener;
+
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.events.ChangeEvent;
+import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.EventListener;
+import com.google.gerrit.server.events.PatchSetCreatedEvent;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.chatgpt.config.ConfigCreator;
+import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class PatchSetCreatedListener implements EventListener {
+ private final ConfigCreator configCreator;
+
+ private final EventListenerHandler eventListenerHandler;
+
+ @Inject
+ public PatchSetCreatedListener(ConfigCreator configCreator, EventListenerHandler eventListenerHandler) {
+ this.configCreator = configCreator;
+ this.eventListenerHandler = eventListenerHandler;
+ }
+
+ @Override
+ public void onEvent(Event event) {
+ if (!(event instanceof PatchSetCreatedEvent)) {
+ log.debug("The event is not a PatchSetCreatedEvent, it is: {}", event);
+ return;
+ }
+
+ log.info("Processing event: {}", event);
+ ChangeEvent changeEvent = (ChangeEvent) event;
+ Project.NameKey projectNameKey = changeEvent.getProjectNameKey();
+
+ try {
+ Configuration config = configCreator.createConfig(projectNameKey);
+ eventListenerHandler.handleEvent(config, changeEvent);
+ } catch (NoSuchProjectException e) {
+ log.error("Project not found: {}", projectNameKey, e);
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTests.java b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTests.java
new file mode 100644
index 0000000..b656eb8
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTests.java
@@ -0,0 +1,163 @@
+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.events.CommentAddedEvent;
+import com.google.gerrit.server.events.PatchSetCreatedEvent;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.googlesource.gerrit.plugins.chatgpt.client.GerritClient;
+import com.googlesource.gerrit.plugins.chatgpt.client.OpenAiClient;
+import com.googlesource.gerrit.plugins.chatgpt.client.UriResourceLocator;
+import com.googlesource.gerrit.plugins.chatgpt.config.ConfigCreator;
+import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import com.googlesource.gerrit.plugins.chatgpt.listener.EventListenerHandler;
+import com.googlesource.gerrit.plugins.chatgpt.listener.GptMentionedCommentListener;
+import com.googlesource.gerrit.plugins.chatgpt.listener.PatchSetCreatedListener;
+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.junit.MockitoJUnitRunner;
+
+import java.net.URI;
+import java.util.Base64;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+import static com.googlesource.gerrit.plugins.chatgpt.client.UriResourceLocator.gerritCommentUri;
+import static com.googlesource.gerrit.plugins.chatgpt.client.UriResourceLocator.gerritPatchSetUri;
+import static com.googlesource.gerrit.plugins.chatgpt.listener.EventListenerHandler.buildFullChangeId;
+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 ChatGptReviewTests {
+ 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");
+
+ @Rule
+ public WireMockRule wireMockRule = new WireMockRule(9527);
+
+ private Configuration config;
+
+ @Before
+ public void before() {
+ initConfig();
+ setupMockRequests();
+ }
+
+ private void initConfig() {
+ config = mock(Configuration.class);
+ when(config.getGerritAuthBaseUrl()).thenReturn("http://localhost:9527");
+ when(config.getGptDomain()).thenReturn("http://localhost:9527");
+ when(config.getGptTemperature()).thenReturn(1.0);
+ when(config.getMaxReviewLines()).thenReturn(500);
+ when(config.getEnabledProjects()).thenReturn("");
+ when(config.isProjectEnable()).thenReturn(true);
+ }
+
+ private void setupMockRequests() {
+ // Mocks the behavior of the getPatchSet request
+ WireMock.stubFor(WireMock.get(gerritPatchSetUri(buildFullChangeId(PROJECT_NAME, BRANCH_NAME, CHANGE_ID)))
+ .willReturn(WireMock.aResponse()
+ .withStatus(HTTP_OK)
+ .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
+ .withBody(Base64.getEncoder().encodeToString("myPatch".getBytes()))));
+
+ // Mocks the behavior of the askGpt request
+ byte[] gptAnswer = Base64.getDecoder().decode("ZGF0YTogeyJpZCI6ImNoYXRjbXBsLTdSZDVOYVpEOGJNVTRkdnBVV2" +
+ "9hM3Q2RG83RkkzIiwib2JqZWN0IjoiY2hhdC5jb21wbGV0aW9uLmNodW5rIiwiY3JlYXRlZCI6MTY4NjgxOTQ1NywibW9kZWw" +
+ "iOiJncHQtMy41LXR1cmJvLTAzMDEiLCJjaG9pY2VzIjpbeyJkZWx0YSI6eyJyb2xlIjoiYXNzaXN0YW50In0sImluZGV4Ijow" +
+ "LCJmaW5pc2hfcmVhc29uIjpudWxsfV19CgpkYXRhOiB7ImlkIjoiY2hhdGNtcGwtN1JkNU5hWkQ4Yk1VNGR2cFVXb2EzdDZEb" +
+ "zdGSTMiLCJvYmplY3QiOiJjaGF0LmNvbXBsZXRpb24uY2h1bmsiLCJjcmVhdGVkIjoxNjg2ODE5NDU3LCJtb2RlbCI6ImdwdC0" +
+ "zLjUtdHVyYm8tMDMwMSIsImNob2ljZXMiOlt7ImRlbHRhIjp7ImNvbnRlbnQiOiJIZWxsbyJ9LCJpbmRleCI6MCwiZmluaXNo" +
+ "X3JlYXNvbiI6bnVsbH1dfQoKZGF0YTogeyJpZCI6ImNoYXRjbXBsLTdSZDVOYVpEOGJNVTRkdnBVV29hM3Q2RG83RkkzIiwib" +
+ "2JqZWN0IjoiY2hhdC5jb21wbGV0aW9uLmNodW5rIiwiY3JlYXRlZCI6MTY4NjgxOTQ1NywibW9kZWwiOiJncHQtMy41LXR1cm" +
+ "JvLTAzMDEiLCJjaG9pY2VzIjpbeyJkZWx0YSI6eyJjb250ZW50IjoiISJ9LCJpbmRleCI6MCwiZmluaXNoX3JlYXNvbiI" +
+ "6bnVsbH1dfQ==");
+ WireMock.stubFor(WireMock.post(WireMock.urlEqualTo(URI.create(config.getGptDomain()
+ + UriResourceLocator.chatCompletionsUri()).getPath()))
+ .willReturn(WireMock.aResponse()
+ .withStatus(HTTP_OK)
+ .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
+ .withBody(new String(gptAnswer))));
+
+ // Mocks the behavior of the postReview request
+ WireMock.stubFor(WireMock.post(gerritCommentUri(buildFullChangeId(PROJECT_NAME, BRANCH_NAME, CHANGE_ID)))
+ .willReturn(WireMock.aResponse()
+ .withStatus(HTTP_OK)));
+ }
+
+ @Test
+ public void patchSetCreatedOrUpdated() throws InterruptedException, NoSuchProjectException, ExecutionException {
+ GerritClient gerritClient = new GerritClient();
+ OpenAiClient openAiClient = new OpenAiClient();
+ PatchSetReviewer patchSetReviewer = new PatchSetReviewer(gerritClient, openAiClient);
+ 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);
+ EventListenerHandler eventListenerHandler = new EventListenerHandler(patchSetReviewer);
+
+ PatchSetCreatedListener patchSetCreatedListener = new PatchSetCreatedListener(mockConfigCreator, eventListenerHandler);
+ patchSetCreatedListener.onEvent(event);
+ CompletableFuture<Void> future = eventListenerHandler.getLatestFuture();
+ future.get();
+
+ RequestPatternBuilder requestPatternBuilder = WireMock.postRequestedFor(
+ WireMock.urlEqualTo(gerritCommentUri(buildFullChangeId(PROJECT_NAME, BRANCH_NAME, CHANGE_ID))));
+ List<LoggedRequest> loggedRequests = WireMock.findAll(requestPatternBuilder);
+ Assert.assertEquals(1, loggedRequests.size());
+ String requestBody = loggedRequests.get(0).getBodyAsString();
+ Assert.assertEquals("{\"message\":\"Hello!\\n\"}", requestBody);
+
+ }
+
+ @Test
+ public void gptMentionedInComment() throws InterruptedException, NoSuchProjectException, ExecutionException {
+ GerritClient gerritClient = new GerritClient();
+ OpenAiClient openAiClient = new OpenAiClient();
+ PatchSetReviewer patchSetReviewer = new PatchSetReviewer(gerritClient, openAiClient);
+ ConfigCreator mockConfigCreator = mock(ConfigCreator.class);
+ when(config.getGerritUserName()).thenReturn("gpt");
+ when(mockConfigCreator.createConfig(ArgumentMatchers.any())).thenReturn(config);
+
+ CommentAddedEvent event = mock(CommentAddedEvent.class);
+ when(event.getProjectNameKey()).thenReturn(PROJECT_NAME);
+ when(event.getBranchNameKey()).thenReturn(BRANCH_NAME);
+ when(event.getChangeKey()).thenReturn(CHANGE_ID);
+ EventListenerHandler eventListenerHandler = new EventListenerHandler(patchSetReviewer);
+
+ GptMentionedCommentListener gptMentionedCommentListener = new GptMentionedCommentListener(mockConfigCreator, eventListenerHandler);
+ event.comment = "@gpt Hello!";
+ gptMentionedCommentListener.onEvent(event);
+ CompletableFuture<Void> future = eventListenerHandler.getLatestFuture();
+ future.get();
+
+ RequestPatternBuilder requestPatternBuilder = WireMock.postRequestedFor(
+ WireMock.urlEqualTo(gerritCommentUri(buildFullChangeId(PROJECT_NAME, BRANCH_NAME, CHANGE_ID))));
+ List<LoggedRequest> loggedRequests = WireMock.findAll(requestPatternBuilder);
+ Assert.assertEquals(1, loggedRequests.size());
+ String requestBody = loggedRequests.get(0).getBodyAsString();
+ Assert.assertEquals("{\"message\":\"Hello!\\n\"}", requestBody);
+
+ }
+
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewerTests.java b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewerTests.java
deleted file mode 100644
index 3f34a1e..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewerTests.java
+++ /dev/null
@@ -1,128 +0,0 @@
-package com.googlesource.gerrit.plugins.chatgpt;
-
-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.events.PatchSetCreatedEvent;
-import com.google.gerrit.server.project.NoSuchProjectException;
-import com.googlesource.gerrit.plugins.chatgpt.client.GerritClient;
-import com.googlesource.gerrit.plugins.chatgpt.client.OpenAiClient;
-import com.googlesource.gerrit.plugins.chatgpt.client.UriResourceLocator;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.http.entity.ContentType;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentMatchers;
-import org.mockito.Mockito;
-import org.mockito.junit.MockitoJUnitRunner;
-
-import java.net.URI;
-import java.util.Base64;
-import java.util.List;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-
-import static com.github.tomakehurst.wiremock.client.WireMock.*;
-import static com.googlesource.gerrit.plugins.chatgpt.PatchSetCreated.buildFullChangeId;
-import static com.googlesource.gerrit.plugins.chatgpt.client.UriResourceLocator.gerritCommentUri;
-import static com.googlesource.gerrit.plugins.chatgpt.client.UriResourceLocator.gerritPatchSetUri;
-import static java.net.HttpURLConnection.HTTP_OK;
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-@Slf4j
-@RunWith(MockitoJUnitRunner.class)
-public class PatchSetReviewerTests {
- private static final String PROJECT_NAME = "myProject";
- private static final String CHANGE_ID = "myChangeId";
- private static final String BRANCH_NAME = "myBranchName";
-
- @Rule
- public WireMockRule wireMockRule = new WireMockRule(9527);
-
- private Configuration config;
-
- @Before
- public void before() {
- initConfig();
- setupMockRequests();
- }
-
- private void initConfig() {
- config = Mockito.mock(Configuration.class);
- when(config.getGerritAuthBaseUrl()).thenReturn("http://localhost:9527");
- when(config.getGptDomain()).thenReturn("http://localhost:9527");
- when(config.getGptTemperature()).thenReturn(1.0);
- when(config.getMaxReviewLines()).thenReturn(500);
- when(config.getEnabledProjects()).thenReturn("");
- when(config.isProjectEnable()).thenReturn(true);
-
- }
-
- private void setupMockRequests() {
- // Mocks the behavior of the getPatchSet request
- stubFor(get(gerritPatchSetUri(buildFullChangeId(PROJECT_NAME, BRANCH_NAME, CHANGE_ID)))
- .willReturn(aResponse()
- .withStatus(HTTP_OK)
- .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
- .withBody(Base64.getEncoder().encodeToString("myPatch".getBytes()))));
-
- // Mocks the behavior of the askGpt request
- byte[] gptAnswer = Base64.getDecoder().decode("ZGF0YTogeyJpZCI6ImNoYXRjbXBsLTdSZDVOYVpEOGJNVTRkdnBVV2" +
- "9hM3Q2RG83RkkzIiwib2JqZWN0IjoiY2hhdC5jb21wbGV0aW9uLmNodW5rIiwiY3JlYXRlZCI6MTY4NjgxOTQ1NywibW9kZWw" +
- "iOiJncHQtMy41LXR1cmJvLTAzMDEiLCJjaG9pY2VzIjpbeyJkZWx0YSI6eyJyb2xlIjoiYXNzaXN0YW50In0sImluZGV4Ijow" +
- "LCJmaW5pc2hfcmVhc29uIjpudWxsfV19CgpkYXRhOiB7ImlkIjoiY2hhdGNtcGwtN1JkNU5hWkQ4Yk1VNGR2cFVXb2EzdDZEb" +
- "zdGSTMiLCJvYmplY3QiOiJjaGF0LmNvbXBsZXRpb24uY2h1bmsiLCJjcmVhdGVkIjoxNjg2ODE5NDU3LCJtb2RlbCI6ImdwdC0" +
- "zLjUtdHVyYm8tMDMwMSIsImNob2ljZXMiOlt7ImRlbHRhIjp7ImNvbnRlbnQiOiJIZWxsbyJ9LCJpbmRleCI6MCwiZmluaXNo" +
- "X3JlYXNvbiI6bnVsbH1dfQoKZGF0YTogeyJpZCI6ImNoYXRjbXBsLTdSZDVOYVpEOGJNVTRkdnBVV29hM3Q2RG83RkkzIiwib" +
- "2JqZWN0IjoiY2hhdC5jb21wbGV0aW9uLmNodW5rIiwiY3JlYXRlZCI6MTY4NjgxOTQ1NywibW9kZWwiOiJncHQtMy41LXR1cm" +
- "JvLTAzMDEiLCJjaG9pY2VzIjpbeyJkZWx0YSI6eyJjb250ZW50IjoiISJ9LCJpbmRleCI6MCwiZmluaXNoX3JlYXNvbiI" +
- "6bnVsbH1dfQ==");
- stubFor(post(urlEqualTo(URI.create(config.getGptDomain()
- + UriResourceLocator.chatCompletionsUri()).getPath()))
- .willReturn(aResponse()
- .withStatus(HTTP_OK)
- .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
- .withBody(new String(gptAnswer))));
-
- // Mocks the behavior of the postReview request
- stubFor(post(gerritCommentUri(buildFullChangeId(PROJECT_NAME, BRANCH_NAME, CHANGE_ID)))
- .willReturn(aResponse()
- .withStatus(HTTP_OK)));
- }
-
- @Test
- public void review() throws InterruptedException, NoSuchProjectException, ExecutionException {
- GerritClient gerritClient = new GerritClient();
- OpenAiClient openAiClient = new OpenAiClient();
- PatchSetReviewer patchSetReviewer = new PatchSetReviewer(gerritClient, openAiClient);
- ConfigCreator mockConfigCreator = mock(ConfigCreator.class);
- when(mockConfigCreator.createConfig(ArgumentMatchers.any())).thenReturn(config);
-
- PatchSetCreatedEvent event = mock(PatchSetCreatedEvent.class);
- when(event.getProjectNameKey()).thenReturn(Project.NameKey.parse(PROJECT_NAME));
- when(event.getBranchNameKey()).thenReturn(BranchNameKey.create(Project.NameKey.parse(PROJECT_NAME), BRANCH_NAME));
- when(event.getChangeKey()).thenReturn(Change.Key.parse(CHANGE_ID));
-
- PatchSetCreated patchSetCreated = new PatchSetCreated(mockConfigCreator, patchSetReviewer);
- patchSetCreated.onEvent(event);
- CompletableFuture<Void> future = patchSetCreated.getLatestFuture();
- future.get();
-
- RequestPatternBuilder requestPatternBuilder = postRequestedFor(
- urlEqualTo(gerritCommentUri(buildFullChangeId(PROJECT_NAME, BRANCH_NAME, CHANGE_ID))));
- List<LoggedRequest> loggedRequests = findAll(requestPatternBuilder);
- assertEquals(1, loggedRequests.size());
- String requestBody = loggedRequests.get(0).getBodyAsString();
- assertEquals("{\"message\":\"Hello!\\n\"}", requestBody);
-
- }
-
-}
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 c6a1d63..fef832f 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
@@ -1,8 +1,8 @@
package com.googlesource.gerrit.plugins.chatgpt.integration;
-import com.googlesource.gerrit.plugins.chatgpt.Configuration;
import com.googlesource.gerrit.plugins.chatgpt.client.GerritClient;
import com.googlesource.gerrit.plugins.chatgpt.client.OpenAiClient;
+import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
import lombok.extern.slf4j.Slf4j;
import org.junit.Ignore;
import org.junit.Test;