Enable dynamic configuration via command

Plugin configuration can now be dynamically modified through messages
addressed to the ChatGPT user for testing/debugging purposes. This
functionality is activated when the `enableMessageDebugging`
configuration setting is set to true.
The command syntax is as follows:
- `/configure` shows the changed settings and their dynamically changed
values in a reply message.
- `/configure --<CONFIG_KEY_1>=<CONFIG_VALUE_1> [...
--<CONFIG_KEY_N>=<CONFIG_VALUE_N>]` allows assigning new value(s) to one
or more configuration keys.

Change-Id: Ia74f83a2b2cb3e8c36745e7d320fb65d4ff1eee4
Signed-off-by: Patrizio <patrizio.gelosi@amarulasolutions.com>
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 f94323b..b752f28 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewer.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewer.java
@@ -71,7 +71,7 @@
         }
         ChangeSetDataHandler.update(config, change, gerritClient, changeSetData, localizer);
 
-        if (changeSetData.getReviewSystemMessage() == null) {
+        if (changeSetData.shouldRequestChatGptReview()) {
             ChatGptResponseContent reviewReply = getReviewReply(change, patchSet);
             log.debug("ChatGPT response: {}", reviewReply);
 
@@ -121,11 +121,10 @@
             if (changeSetData.getReplyFilterEnabled() && isHidden) {
                 continue;
             }
-            if (changeSetData.getDebugMode()) {
+            if (changeSetData.getDebugReviewMode()) {
                 reply += debugCodeBlocksReview.getDebugCodeBlock(replyItem, isHidden);
             }
-            ReviewBatch batchMap = new ReviewBatch();
-            batchMap.setContent(reply);
+            ReviewBatch batchMap = new ReviewBatch(reply);
             if (change.getIsCommentEvent() && replyItem.getId() != null) {
                 setCommentBatchMap(batchMap, replyItem.getId());
             }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/ConfigCreator.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/ConfigCreator.java
index c3169ef..9cebdde 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/ConfigCreator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/ConfigCreator.java
@@ -1,6 +1,7 @@
 package com.googlesource.gerrit.plugins.chatgpt.config;
 
 import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.server.account.AccountCache;
@@ -12,9 +13,17 @@
 import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandlerBaseProvider;
+import lombok.NonNull;
 import lombok.extern.slf4j.Slf4j;
+import org.eclipse.jgit.lib.Config;
 
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
+
+import static com.googlesource.gerrit.plugins.chatgpt.config.DynamicConfiguration.KEY_DYNAMIC_CONFIG;
 
 @Singleton
 @Slf4j
@@ -26,17 +35,26 @@
 
     private final OneOffRequestContext context;
     private final GerritApi gerritApi;
+    private final PluginDataHandlerBaseProvider pluginDataHandlerBaseProvider;
 
     @Inject
-    ConfigCreator(@PluginName String pluginName, AccountCache accountCache, PluginConfigFactory configFactory, OneOffRequestContext context, GerritApi gerritApi) {
+    ConfigCreator(
+            @PluginName String pluginName,
+            AccountCache accountCache,
+            PluginConfigFactory configFactory,
+            OneOffRequestContext context,
+            GerritApi gerritApi,
+            PluginDataHandlerBaseProvider pluginDataHandlerBaseProvider
+    ) {
         this.pluginName = pluginName;
         this.accountCache = accountCache;
         this.configFactory = configFactory;
         this.context = context;
         this.gerritApi = gerritApi;
+        this.pluginDataHandlerBaseProvider = pluginDataHandlerBaseProvider;
     }
 
-    public Configuration createConfig(Project.NameKey projectName) throws NoSuchProjectException {
+    public Configuration createConfig(Project.NameKey projectName, Change.Key changeKey) throws NoSuchProjectException {
         PluginConfig globalConfig = configFactory.getFromGerritConfig(pluginName);
         log.debug(
             "These configuration items have been set in the global configuration: {}",
@@ -45,6 +63,14 @@
         log.debug(
             "These configuration items have been set in the project configuration: {}",
             projectConfig.getNames());
+        // `PluginDataHandlerProvider` cannot be injected because `GerritChange` is not initialized at this stage:
+        // instead of using `PluginDataHandlerProvider.getChangeScope`, `PluginDataHandlerBaseProvider.get` is employed
+        Map<String, String> dynamicConfig = pluginDataHandlerBaseProvider.get(changeKey.toString())
+                .getJsonValue(KEY_DYNAMIC_CONFIG, String.class);
+        if (dynamicConfig != null && !dynamicConfig.isEmpty()) {
+            log.info("DynamicConfig found for change '{}': {}", changeKey, dynamicConfig);
+            projectConfig = updateDynamicConfig(projectConfig, pluginName, dynamicConfig);
+        }
         Optional<AccountState> gptAccount = getAccount(globalConfig);
         String email = gptAccount.map(a -> a.account().preferredEmail()).orElse("");
         Account.Id accountId =
@@ -63,4 +89,36 @@
         String gptUser = globalConfig.getString(Configuration.KEY_GERRIT_USERNAME);
         return accountCache.getByUsername(gptUser);
     }
+
+    private PluginConfig updateDynamicConfig(
+            PluginConfig projectConfig,
+            String pluginName,
+            Map<String, String> dynamicConfig
+    ) {
+        // Retrieve all current configuration values
+        Set<String> keys = projectConfig.getNames();
+        Map<String, String> currentConfigValues = new HashMap<>();
+        for (String key : keys) {
+            currentConfigValues.put(key, projectConfig.getString(key));
+        }
+        // Merge current config with new values from dynamicConfig
+        currentConfigValues.putAll(dynamicConfig);
+        PluginConfig.Update configUpdater = getProjectUpdate(pluginName, currentConfigValues);
+
+        return configUpdater.asPluginConfig();
+    }
+
+    private PluginConfig.@NonNull Update getProjectUpdate(String pluginName, Map<String, String> currentConfigValues) {
+        // Use PluginConfig.Update to apply merged configuration
+        PluginConfig.Update configUpdater = new PluginConfig.Update(
+                pluginName,
+                new Config(),
+                Optional.empty()
+        );
+        // Set all merged values
+        for (Map.Entry<String, String> entry : currentConfigValues.entrySet()) {
+            configUpdater.setString(entry.getKey(), entry.getValue());
+        }
+        return configUpdater;
+    }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/DynamicConfiguration.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/DynamicConfiguration.java
new file mode 100644
index 0000000..c0ea2bf
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/DynamicConfiguration.java
@@ -0,0 +1,34 @@
+package com.googlesource.gerrit.plugins.chatgpt.config;
+
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandler;
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandlerProvider;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+@Slf4j
+public class DynamicConfiguration {
+    public static final String KEY_DYNAMIC_CONFIG = "dynamicConfig";
+
+    private final PluginDataHandler pluginDataHandler;
+    private final Map<String, String> dynamicConfig;
+
+    public DynamicConfiguration(PluginDataHandlerProvider pluginDataHandlerProvider) {
+        this.pluginDataHandler = pluginDataHandlerProvider.getChangeScope();
+        dynamicConfig = Optional.ofNullable(pluginDataHandler.getJsonValue(KEY_DYNAMIC_CONFIG, String.class))
+                .orElse(new HashMap<>());
+    }
+
+    public void setConfig(String key, String value) {
+        dynamicConfig.put(key, value);
+    }
+
+    public void updateConfiguration() {
+        if (dynamicConfig != null && !dynamicConfig.isEmpty()) {
+            log.info("Updating dynamic configuration with {}", dynamicConfig);
+            pluginDataHandler.setJsonValue(KEY_DYNAMIC_CONFIG, dynamicConfig);
+        }
+    }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/ChangeSetDataHandler.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/ChangeSetDataHandler.java
index 878202f..f4dd638 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/ChangeSetDataHandler.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/ChangeSetDataHandler.java
@@ -31,6 +31,7 @@
 
         changeSetData.setCommentPropertiesSize(gerritClientData.getCommentProperties().size());
         changeSetData.setDirectives(new HashSet<>());
+        changeSetData.setReviewSystemMessage(null);
         changeSetData.setGptRequestUserPrompt(chatGptUserPrompt.buildPrompt());
         if (config.isVotingEnabled() && !change.getIsCommentEvent()) {
             GerritPermittedVotingRange permittedVotingRange = gerritClient.getPermittedVotingRange(change);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandler.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandler.java
index c3f92dc..44c1817 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandler.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandler.java
@@ -1,13 +1,18 @@
 package com.googlesource.gerrit.plugins.chatgpt.data;
 
+import com.google.gson.reflect.TypeToken;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
 import java.io.IOException;
+import java.lang.reflect.Type;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.Map;
 import java.util.Properties;
 
+import static com.googlesource.gerrit.plugins.chatgpt.utils.GsonUtils.getGson;
+
 @Singleton
 public class PluginDataHandler {
     private final Path configFile;
@@ -28,10 +33,21 @@
         storeProperties();
     }
 
+    public synchronized void setJsonValue(String key, Object value) {
+        setValue(key, getGson().toJson(value));
+    }
+
     public String getValue(String key) {
         return configProperties.getProperty(key);
     }
 
+    public <T> Map<String, T> getJsonValue(String key, Class<T> clazz) {
+        String value = getValue(key);
+        if (value == null || value.isEmpty()) return null;
+        Type typeOfMap = TypeToken.getParameterized(Map.class, String.class, clazz).getType();
+        return getGson().fromJson(value, typeOfMap);
+    }
+
     public synchronized void removeValue(String key) {
         if (configProperties.containsKey(key)) {
             configProperties.remove(key);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandlerProvider.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandlerProvider.java
index ca52d51..54ba288 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandlerProvider.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandlerProvider.java
@@ -10,6 +10,7 @@
 @Singleton
 public class PluginDataHandlerProvider extends PluginDataHandlerBaseProvider implements Provider<PluginDataHandler> {
     private final String projectName;
+    private final String changeKey;
 
     @Inject
     public PluginDataHandlerProvider(
@@ -18,6 +19,7 @@
     ) {
         super(defaultPluginDataPath);
         projectName = change.getProjectName();
+        changeKey = change.getChangeKey().toString();
     }
 
     public PluginDataHandler getGlobalScope() {
@@ -27,4 +29,8 @@
     public PluginDataHandler getProjectScope() {
         return super.get(projectName);
     }
+
+    public PluginDataHandler getChangeScope() {
+        return super.get(changeKey);
+    }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/EventHandlerTask.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/EventHandlerTask.java
index b1877bf..872b308 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/EventHandlerTask.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/EventHandlerTask.java
@@ -90,7 +90,7 @@
         boolean isCommentEvent = EVENT_COMMENT_MAP.get(eventType);
         if (isCommentEvent) {
             if (!gerritClient.retrieveLastComments(change)) {
-                if (changeSetData.getForcedReview()) {
+                if (changeSetData.getForcedReview() || changeSetData.getForceDisplaySystemMessage()) {
                     isCommentEvent = false;
                 } else {
                     log.info("No comments found for review");
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/GerritListener.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/GerritListener.java
index a86dd4d..2e60049 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/GerritListener.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/GerritListener.java
@@ -1,6 +1,7 @@
 package com.googlesource.gerrit.plugins.chatgpt.listener;
 
 import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.server.config.GerritInstanceId;
 import com.google.gerrit.server.events.PatchSetEvent;
@@ -47,9 +48,10 @@
         log.info("Processing event: {}", event);
         PatchSetEvent patchSetEvent = (PatchSetEvent) event;
         Project.NameKey projectNameKey = patchSetEvent.getProjectNameKey();
+        Change.Key changeKey = patchSetEvent.getChangeKey();
 
         try {
-            Configuration config = configCreator.createConfig(projectNameKey);
+            Configuration config = configCreator.createConfig(projectNameKey, changeKey);
             evenHandlerExecutor.execute(config, patchSetEvent);
         } catch (NoSuchProjectException e) {
             log.error("Project not found: {}", projectNameKey, e);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/api/gerrit/GerritClientComments.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/api/gerrit/GerritClientComments.java
index f9cc867..96f9854 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/api/gerrit/GerritClientComments.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/api/gerrit/GerritClientComments.java
@@ -7,6 +7,7 @@
 import com.google.gerrit.server.util.ManualRequestContext;
 import com.google.inject.Inject;
 import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandlerProvider;
 import com.googlesource.gerrit.plugins.chatgpt.localization.Localizer;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.messages.ClientMessage;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.api.gerrit.GerritCodeRange;
@@ -32,6 +33,7 @@
     private final ChangeSetData changeSetData;
     private final HashMap<String, GerritComment> commentMap;
     private final HashMap<String, GerritComment> patchSetCommentMap;
+    private final PluginDataHandlerProvider pluginDataHandlerProvider;
     private final Localizer localizer;
 
     private String authorUsername;
@@ -44,10 +46,12 @@
             Configuration config,
             AccountCache accountCache,
             ChangeSetData changeSetData,
+            PluginDataHandlerProvider pluginDataHandlerProvider,
             Localizer localizer
     ) {
         super(config, accountCache);
         this.changeSetData = changeSetData;
+        this.pluginDataHandlerProvider = pluginDataHandlerProvider;
         this.localizer = localizer;
         commentProperties = new ArrayList<>();
         commentMap = new HashMap<>();
@@ -145,7 +149,7 @@
     }
 
     private void addLastComments(GerritChange change) {
-        ClientMessage clientMessage = new ClientMessage(config, changeSetData, localizer);
+        ClientMessage clientMessage = new ClientMessage(config, changeSetData, pluginDataHandlerProvider, localizer);
         try {
             List<GerritComment> latestComments = retrieveComments(change);
             if (latestComments == null) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/api/gerrit/GerritClientReview.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/api/gerrit/GerritClientReview.java
index c3c539e..5e707dc 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/api/gerrit/GerritClientReview.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/api/gerrit/GerritClientReview.java
@@ -11,7 +11,10 @@
 import com.google.gerrit.server.util.ManualRequestContext;
 import com.google.inject.Inject;
 import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandler;
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandlerProvider;
 import com.googlesource.gerrit.plugins.chatgpt.localization.Localizer;
+import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.messages.DebugCodeBlocksDynamicSettings;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.data.ChangeSetData;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.review.ReviewBatch;
 import lombok.extern.slf4j.Slf4j;
@@ -22,17 +25,28 @@
 import java.util.Map;
 import java.util.Optional;
 
+import static com.googlesource.gerrit.plugins.chatgpt.config.DynamicConfiguration.KEY_DYNAMIC_CONFIG;
 import static com.googlesource.gerrit.plugins.chatgpt.mode.common.client.prompt.MessageSanitizer.sanitizeChatGptMessage;
+import static com.googlesource.gerrit.plugins.chatgpt.utils.TextUtils.joinWithDoubleNewLine;
 
 @Slf4j
 public class GerritClientReview extends GerritClientAccount {
+    private final PluginDataHandler pluginDataHandler;
     private final Localizer localizer;
+    private final DebugCodeBlocksDynamicSettings debugCodeBlocksDynamicSettings;
 
     @VisibleForTesting
     @Inject
-    public GerritClientReview(Configuration config, AccountCache accountCache, Localizer localizer) {
+    public GerritClientReview(
+            Configuration config,
+            AccountCache accountCache,
+            PluginDataHandlerProvider pluginDataHandlerProvider,
+            Localizer localizer
+    ) {
         super(config, accountCache);
+        this.pluginDataHandler = pluginDataHandlerProvider.getChangeScope();
         this.localizer = localizer;
+        debugCodeBlocksDynamicSettings = new DebugCodeBlocksDynamicSettings(localizer);
     }
 
     public void setReview(
@@ -77,21 +91,33 @@
         if (changeSetData.getReviewSystemMessage() != null) {
             systemMessage = changeSetData.getReviewSystemMessage();
         }
-        else {
+        else if (!changeSetData.shouldHideChatGptReview()) {
             comments = getReviewComments(reviewBatches);
             if (reviewScore != null) {
                 reviewInput.label(LabelId.CODE_REVIEW, reviewScore);
             }
         }
-        if (comments.isEmpty()) {
-            reviewInput.message(localizer.getText("system.message.prefix") + ' ' + systemMessage);
-        }
-        else {
+        updateSystemMessage(reviewInput, comments.isEmpty(), systemMessage);
+        if (!comments.isEmpty()) {
             reviewInput.comments = comments;
         }
         return reviewInput;
     }
 
+    private void updateSystemMessage(ReviewInput reviewInput, boolean emptyComments, String systemMessage) {
+        List<String> messages = new ArrayList<>();
+        Map<String, String> dynamicConfig = pluginDataHandler.getJsonValue(KEY_DYNAMIC_CONFIG, String.class);
+        if (dynamicConfig != null && !dynamicConfig.isEmpty()) {
+            messages.add(debugCodeBlocksDynamicSettings.getDebugCodeBlock(dynamicConfig));
+        }
+        if (emptyComments) {
+            messages.add(localizer.getText("system.message.prefix") + ' ' + systemMessage);
+        }
+        if (!messages.isEmpty()) {
+            reviewInput.message(joinWithDoubleNewLine(messages));
+        }
+    }
+
     private Map<String, List<CommentInput>> getReviewComments(List<ReviewBatch> reviewBatches) {
         Map<String, List<CommentInput>> comments = new HashMap<>();
         for (ReviewBatch reviewBatch : reviewBatches) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/commands/ClientCommands.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/commands/ClientCommands.java
index 85b2d3e..f3ad30f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/commands/ClientCommands.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/commands/ClientCommands.java
@@ -1,6 +1,8 @@
 package com.googlesource.gerrit.plugins.chatgpt.mode.common.client.commands;
 
 import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import com.googlesource.gerrit.plugins.chatgpt.config.DynamicConfiguration;
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandlerProvider;
 import com.googlesource.gerrit.plugins.chatgpt.localization.Localizer;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.ClientBase;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.prompt.Directives;
@@ -20,9 +22,10 @@
     private enum COMMAND_SET {
         REVIEW,
         REVIEW_LAST,
-        DIRECTIVE
+        DIRECTIVE,
+        CONFIGURE
     }
-    private enum OPTION_SET {
+    private enum REVIEW_OPTION_SET {
         FILTER,
         DEBUG
     }
@@ -30,11 +33,12 @@
     private static final Map<String, COMMAND_SET> COMMAND_MAP = Map.of(
             "review", COMMAND_SET.REVIEW,
             "review_last", COMMAND_SET.REVIEW_LAST,
-            "directive", COMMAND_SET.DIRECTIVE
+            "directive", COMMAND_SET.DIRECTIVE,
+            "configure", COMMAND_SET.CONFIGURE
     );
-    private static final Map<String, OPTION_SET> OPTION_MAP = Map.of(
-            "filter", OPTION_SET.FILTER,
-            "debug", OPTION_SET.DEBUG
+    private static final Map<String, REVIEW_OPTION_SET> REVIEW_OPTION_MAP = Map.of(
+            "filter", REVIEW_OPTION_SET.FILTER,
+            "debug", REVIEW_OPTION_SET.DEBUG
     );
     private static final List<COMMAND_SET> REVIEW_COMMANDS = new ArrayList<>(List.of(
             COMMAND_SET.REVIEW,
@@ -44,22 +48,30 @@
             COMMAND_SET.DIRECTIVE
     ));
     private static final Pattern COMMAND_PATTERN = Pattern.compile("/(" + String.join("|",
-            COMMAND_MAP.keySet()) + ")\\b((?:\\s+--\\w+(?:=\\w+)?)+)?");
-    private static final Pattern OPTIONS_PATTERN = Pattern.compile("--(\\w+)(?:=(\\w+))?");
+            COMMAND_MAP.keySet()) + ")\\b((?:\\s+--\\w+(?:=\\S+)?)+)?");
+    private static final Pattern OPTIONS_PATTERN = Pattern.compile("--(\\w+)(?:=(\\S+))?");
 
     private final ChangeSetData changeSetData;
-    @Getter
     private final Directives directives;
     private final Localizer localizer;
 
-    @Getter
+    private DynamicConfiguration dynamicConfiguration;
     private boolean containingHistoryCommand;
 
-    public ClientCommands(Configuration config, ChangeSetData changeSetData, Localizer localizer) {
+    public ClientCommands(
+            Configuration config,
+            ChangeSetData changeSetData,
+            PluginDataHandlerProvider pluginDataHandlerProvider,
+            Localizer localizer
+    ) {
         super(config);
         this.localizer = localizer;
         this.changeSetData = changeSetData;
         directives = new Directives(changeSetData);
+        // The `dynamicConfiguration` instance is utilized only for parsing current client messages, not the history
+        if (pluginDataHandlerProvider != null) {
+            dynamicConfiguration = new DynamicConfiguration(pluginDataHandlerProvider);
+        }
         containingHistoryCommand = false;
     }
 
@@ -67,10 +79,9 @@
         boolean commandFound = false;
         Matcher reviewCommandMatcher = COMMAND_PATTERN.matcher(comment);
         while (reviewCommandMatcher.find()) {
-            parseCommand(comment, reviewCommandMatcher.group(1), isNotHistory);
-            if (reviewCommandMatcher.group(2) != null) {
-                parseOption(reviewCommandMatcher, isNotHistory);
-            }
+            COMMAND_SET command = COMMAND_MAP.get(reviewCommandMatcher.group(1));
+            parseOptions(command, reviewCommandMatcher, isNotHistory);
+            parseCommand(command, comment, isNotHistory);
             commandFound = true;
         }
         return commandFound;
@@ -88,52 +99,77 @@
         return reviewCommandMatcher.replaceAll("");
     }
 
-    private void parseCommand(String comment, String commandString, boolean isNotHistory) {
-        COMMAND_SET command = COMMAND_MAP.get(commandString);
-        if (isNotHistory && REVIEW_COMMANDS.contains(command)) {
-            changeSetData.setForcedReview(true);
-            if (command == COMMAND_SET.REVIEW_LAST) {
-                log.info("Forced review command applied to the last Patch Set");
-                changeSetData.setForcedReviewLastPatchSet(true);
+    private void parseCommand(COMMAND_SET command, String comment, boolean isNotHistory) {
+        if (isNotHistory) {
+            if (REVIEW_COMMANDS.contains(command)) {
+                changeSetData.setForcedReview(true);
+                if (command == COMMAND_SET.REVIEW_LAST) {
+                    log.info("Forced review command applied to the last Patch Set");
+                    changeSetData.setForcedReviewLastPatchSet(true);
+                }
+                else {
+                    log.info("Forced review command applied to the entire Change Set");
+                }
             }
-            else {
-                log.info("Forced review command applied to the entire Change Set");
+            else if (command == COMMAND_SET.CONFIGURE) {
+                if (config.getEnableMessageDebugging()) {
+                    changeSetData.setHideChatGptReview(true);
+                    changeSetData.setForceDisplaySystemMessage(true);
+                    dynamicConfiguration.updateConfiguration();
+                }
+                else {
+                    changeSetData.setReviewSystemMessage(localizer.getText(
+                            "message.configure.from.messages.disabled"
+                    ));
+                    log.debug("Unable to change configuration from messages: `enableMessageDebugging` config must" +
+                            "be set to true");
+                }
             }
         }
         if (HISTORY_COMMANDS.contains(command)) {
             containingHistoryCommand = true;
-            directives.addDirective(removeCommands(comment));
+            if (command == COMMAND_SET.DIRECTIVE) {
+                directives.addDirective(removeCommands(comment));
+            }
         }
     }
 
-    private void parseOption(Matcher reviewCommandMatcher, boolean isNotHistory) {
-        COMMAND_SET command = COMMAND_MAP.get(reviewCommandMatcher.group(1));
+    private void parseOptions(COMMAND_SET command, Matcher reviewCommandMatcher, boolean isNotHistory) {
+        // Command options need to be parsed only when processing the current message, not the message history
+        if (reviewCommandMatcher.group(2) == null || !isNotHistory) return;
         Matcher reviewOptionsMatcher = OPTIONS_PATTERN.matcher(reviewCommandMatcher.group(2));
         while (reviewOptionsMatcher.find()) {
-            OPTION_SET option = OPTION_MAP.get(reviewOptionsMatcher.group(1));
-            if (isNotHistory && REVIEW_COMMANDS.contains(command)) {
-                switch (option) {
-                    case FILTER:
-                        boolean value = Boolean.parseBoolean(reviewOptionsMatcher.group(2));
-                        log.debug("Option 'replyFilterEnabled' set to {}", value);
-                        changeSetData.setReplyFilterEnabled(value);
-                        break;
-                    case DEBUG:
-                        if (config.getEnableMessageDebugging()) {
-                            log.debug("Response Mode set to Debug");
-                            changeSetData.setDebugMode(true);
-                            changeSetData.setReplyFilterEnabled(false);
-                        }
-                        else {
-                            changeSetData.setReviewSystemMessage(localizer.getText(
-                                    "message.debugging.functionalities.disabled"
-                            ));
-                            log.debug("Unable to set Response Mode to Debug: `enableMessageDebugging` config must be " +
-                                    "set to true");
-                        }
-                        break;
-                }
+            parseSingleOption(command, reviewOptionsMatcher);
+        }
+    }
+
+    private void parseSingleOption(COMMAND_SET command, Matcher reviewOptionsMatcher) {
+        String optionKey = reviewOptionsMatcher.group(1);
+        String optionValue = reviewOptionsMatcher.group(2);
+        if (REVIEW_COMMANDS.contains(command)) {
+            switch (REVIEW_OPTION_MAP.get(optionKey)) {
+                case FILTER:
+                    boolean value = Boolean.parseBoolean(optionValue);
+                    log.debug("Option 'replyFilterEnabled' set to {}", value);
+                    changeSetData.setReplyFilterEnabled(value);
+                    break;
+                case DEBUG:
+                    if (config.getEnableMessageDebugging()) {
+                        log.debug("Response Mode set to Debug");
+                        changeSetData.setDebugReviewMode(true);
+                        changeSetData.setReplyFilterEnabled(false);
+                    } else {
+                        changeSetData.setReviewSystemMessage(localizer.getText(
+                                "message.debugging.review.disabled"
+                        ));
+                        log.debug("Unable to set Response Mode to Debug: `enableMessageDebugging` config " +
+                                "must be set to true");
+                    }
+                    break;
             }
+        } else if (command == COMMAND_SET.CONFIGURE && config.getEnableMessageDebugging()) {
+            log.debug("Updating configuration setting '{}' to '{}'", optionKey, optionValue);
+            dynamicConfiguration.setConfig(optionKey, optionValue);
         }
     }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/messages/ClientMessage.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/messages/ClientMessage.java
index 1d545f5..eac9527 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/messages/ClientMessage.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/messages/ClientMessage.java
@@ -1,6 +1,7 @@
 package com.googlesource.gerrit.plugins.chatgpt.mode.common.client.messages;
 
 import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandlerProvider;
 import com.googlesource.gerrit.plugins.chatgpt.localization.Localizer;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.ClientBase;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.commands.ClientCommands;
@@ -19,19 +20,26 @@
     private final Pattern botMentionPattern;
     private final ClientCommands clientCommands;
     private final DebugCodeBlocksReview debugCodeBlocksReview;
+    private final DebugCodeBlocksDynamicSettings debugCodeBlocksDynamicSettings;
 
     @Getter
     private String message;
 
-    public ClientMessage(Configuration config, ChangeSetData changeSetData, Localizer localizer) {
+    public ClientMessage(
+            Configuration config,
+            ChangeSetData changeSetData,
+            PluginDataHandlerProvider pluginDataHandlerProvider,
+            Localizer localizer
+    ) {
         super(config);
         botMentionPattern = getBotMentionPattern();
-        clientCommands = new ClientCommands(config, changeSetData, localizer);
+        clientCommands = new ClientCommands(config, changeSetData, pluginDataHandlerProvider, localizer);
         debugCodeBlocksReview = new DebugCodeBlocksReview(localizer);
+        debugCodeBlocksDynamicSettings = new DebugCodeBlocksDynamicSettings(localizer);
     }
 
     public ClientMessage(Configuration config, ChangeSetData changeSetData, String message, Localizer localizer) {
-        this(config, changeSetData, localizer);
+        this(config, changeSetData, (PluginDataHandlerProvider) null, localizer);
         this.message = message;
     }
 
@@ -62,11 +70,16 @@
         return this;
     }
 
-    public ClientMessage removeDebugCodeBlocks() {
+    public ClientMessage removeDebugCodeBlocksReview() {
         message = debugCodeBlocksReview.removeDebugCodeBlocks(message);
         return this;
     }
 
+    public ClientMessage removeDebugCodeBlocksDynamicSettings() {
+        message = debugCodeBlocksDynamicSettings.removeDebugCodeBlocks(message);
+        return this;
+    }
+
     public boolean isContainingHistoryCommand() {
         return clientCommands.isContainingHistoryCommand();
     }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/messages/DebugCodeBlocksDynamicSettings.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/messages/DebugCodeBlocksDynamicSettings.java
new file mode 100644
index 0000000..cb3829c
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/messages/DebugCodeBlocksDynamicSettings.java
@@ -0,0 +1,20 @@
+package com.googlesource.gerrit.plugins.chatgpt.mode.common.client.messages;
+
+import com.googlesource.gerrit.plugins.chatgpt.localization.Localizer;
+
+import java.util.List;
+import java.util.Map;
+
+import static com.googlesource.gerrit.plugins.chatgpt.utils.TextUtils.prettyStringifyMap;
+
+public class DebugCodeBlocksDynamicSettings extends DebugCodeBlocks {
+    public DebugCodeBlocksDynamicSettings(Localizer localizer) {
+        super(localizer.getText("message.dynamic.configuration.title"));
+    }
+
+    public String getDebugCodeBlock(Map<String, String> dynamicConfig) {
+        return super.getDebugCodeBlock(List.of(
+                prettyStringifyMap(dynamicConfig)
+        ));
+    }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/prompt/ChatGptComment.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/prompt/ChatGptComment.java
index fccc1a9..ebddc85 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/prompt/ChatGptComment.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/prompt/ChatGptComment.java
@@ -24,7 +24,7 @@
     protected String getCleanedMessage(GerritComment commentProperty) {
         commentMessage = new ClientMessage(config, changeSetData, commentProperty.getMessage(), localizer);
         if (isFromAssistant(commentProperty)) {
-            commentMessage.removeDebugCodeBlocks();
+            commentMessage.removeDebugCodeBlocksReview().removeDebugCodeBlocksDynamicSettings();
         }
         else {
             commentMessage.removeMentions().parseRemoveCommands();
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/prompt/Directives.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/prompt/Directives.java
index c3ccdaf..6cdeb98 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/prompt/Directives.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/prompt/Directives.java
@@ -15,7 +15,7 @@
     }
 
     public void copyDirectiveToSettings() {
-        if (!directive.isEmpty()) {
+        if (directive != null && !directive.isEmpty()) {
             changeSetData.getDirectives().add(directive);
         }
     }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/model/data/ChangeSetData.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/model/data/ChangeSetData.java
index af99c68..ab5cd23 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/model/data/ChangeSetData.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/model/data/ChangeSetData.java
@@ -24,8 +24,18 @@
     // Command variables
     private Boolean forcedReview = false;
     private Boolean forcedReviewLastPatchSet = false;
+    private Boolean forceDisplaySystemMessage = false;
     private Boolean replyFilterEnabled = true;
-    private Boolean debugMode = false;
+    private Boolean debugReviewMode = false;
+    private Boolean hideChatGptReview = false;
     private Set<String> directives = new HashSet<>();
     private String reviewSystemMessage;
+
+    public Boolean shouldHideChatGptReview() {
+        return hideChatGptReview && !forcedReview;
+    }
+
+    public Boolean shouldRequestChatGptReview() {
+        return reviewSystemMessage == null && !shouldHideChatGptReview();
+    }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/model/review/ReviewBatch.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/model/review/ReviewBatch.java
index ef8743f..73196d5 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/model/review/ReviewBatch.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/model/review/ReviewBatch.java
@@ -2,12 +2,16 @@
 
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.api.gerrit.GerritCodeRange;
 import lombok.Data;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
 
 import static com.googlesource.gerrit.plugins.chatgpt.settings.Settings.GERRIT_PATCH_SET_FILENAME;
 
 @Data
+@RequiredArgsConstructor
 public class ReviewBatch {
     private String id;
+    @NonNull
     private String content;
     private String filename;
     private Integer line;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/utils/TextUtils.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/utils/TextUtils.java
index 499df4f..b64c5a4 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/utils/TextUtils.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/utils/TextUtils.java
@@ -3,10 +3,7 @@
 import lombok.extern.slf4j.Slf4j;
 
 import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
@@ -46,6 +43,10 @@
         return String.join("\n", components);
     }
 
+    public static String joinWithDoubleNewLine(List<String> components) {
+        return String.join("\n\n", components);
+    }
+
     public static String joinWithComma(Set<String> components) {
         return String.join(COMMA, components);
     }
@@ -76,4 +77,12 @@
         }
         return joinWithNewLine(lines);
     }
+
+    public static String prettyStringifyMap(Map<String, String> map) {
+        return joinWithNewLine(
+                map.entrySet().stream()
+                        .map(entry -> entry.getKey() + ": " + entry.getValue())
+                        .collect(Collectors.toList())
+        );
+    }
 }
diff --git a/src/main/resources/localization/localTexts.properties b/src/main/resources/localization/localTexts.properties
index 00c5f06..433a0cf 100644
--- a/src/main/resources/localization/localTexts.properties
+++ b/src/main/resources/localization/localTexts.properties
@@ -1,4 +1,6 @@
 system.message.prefix=SYSTEM MESSAGE:
 message.empty.review=No update to show for this Change Set
-message.debugging.functionalities.disabled=Message Debugging functionalities are disabled
+message.debugging.review.disabled=Unable to set Response Mode to Debug: Message Debugging functionalities are disabled
+message.configure.from.messages.disabled=Unable to change configuration from messages: Message Debugging functionalities are disabled
 message.debugging.review.title=DEBUGGING DETAILS
+message.dynamic.configuration.title=CONFIGURATION SETTINGS DYNAMICALLY MODIFIED
diff --git a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatefulTest.java b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatefulTest.java
index 267f968..e6fbcda 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatefulTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatefulTest.java
@@ -8,7 +8,6 @@
 import com.google.gerrit.extensions.restapi.BinaryResult;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gson.JsonObject;
-import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandler;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.api.chatgpt.ChatGptResponseContent;
 import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.api.UriResourceLocatorStateful;
 import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.prompt.ChatGptPromptStateful;
@@ -20,7 +19,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.junit.MockitoJUnitRunner;
@@ -51,9 +49,6 @@
     private ChatGptPromptStateful chatGptPromptStateful;
     private JsonObject threadMessage;
 
-    @Mock
-    protected PluginDataHandler pluginDataHandler;
-
     public ChatGptReviewStatefulTest() {
         MockitoAnnotations.openMocks(this);
     }
@@ -65,7 +60,7 @@
         when(globalConfig.getString(Mockito.eq("gptMode"), Mockito.anyString()))
                 .thenReturn(MODES.stateful.name());
 
-        // Mock the pluginDataHandlerProvider to return the mocked pluginDataHandler
+        // Mock the pluginDataHandlerProvider to return the mocked project pluginDataHandler
         when(pluginDataHandlerProvider.getProjectScope()).thenReturn(pluginDataHandler);
     }
 
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 b91fddc..34d5d35 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTestBase.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTestBase.java
@@ -30,6 +30,7 @@
 import com.google.inject.util.Providers;
 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.data.PluginDataHandlerProvider;
 import com.googlesource.gerrit.plugins.chatgpt.listener.EventHandlerTask;
 import com.googlesource.gerrit.plugins.chatgpt.localization.Localizer;
@@ -97,6 +98,9 @@
     protected PluginDataHandlerProvider pluginDataHandlerProvider;
 
     @Mock
+    protected PluginDataHandler pluginDataHandler;
+
+    @Mock
     protected OneOffRequestContext context;
     @Mock
     protected GerritApi gerritApi;
@@ -157,7 +161,14 @@
     }
 
     protected void initConfig() {
-        config = new Configuration(context, gerritApi, globalConfig, projectConfig, "gpt@email.com", Account.id(1000000));
+        config = new Configuration(
+                context,
+                gerritApi,
+                globalConfig,
+                projectConfig,
+                "gpt@email.com",
+                Account.id(1000000)
+        );
     }
 
     protected void setupMockRequests() throws RestApiException {
@@ -184,6 +195,9 @@
 
         // Mock the GerritApi's revision API
         when(changeApiMock.current()).thenReturn(revisionApiMock);
+
+        // Mock the pluginDataHandlerProvider to return the mocked Change pluginDataHandler
+        when(pluginDataHandlerProvider.getChangeScope()).thenReturn(pluginDataHandler);
     }
 
     private Accounts mockGerritAccountsRestEndpoint() {
@@ -297,14 +311,20 @@
                 new GerritClientFacade(
                     config,
                     changeSetData,
-                    new GerritClientComments(config, accountCacheMock, changeSetData, localizer),
+                    new GerritClientComments(
+                            config,
+                            accountCacheMock,
+                            changeSetData,
+                            pluginDataHandlerProvider,
+                            localizer
+                    ),
                     getGerritClientPatchSet()));
         patchSetReviewer =
             new PatchSetReviewer(
                 gerritClient,
                 config,
                 changeSetData,
-                Providers.of(new GerritClientReview(config, accountCacheMock, localizer)),
+                Providers.of(new GerritClientReview(config, accountCacheMock, pluginDataHandlerProvider, localizer)),
                 getChatGptClient(),
                 localizer
             );
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 2f4c452..e61846a 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
@@ -2,6 +2,7 @@
 
 import com.google.gerrit.server.account.AccountCache;
 import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandlerProvider;
 import com.googlesource.gerrit.plugins.chatgpt.localization.Localizer;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritChange;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritClient;
@@ -33,6 +34,9 @@
     @Mock
     private Configuration config;
 
+    @Mock
+    protected PluginDataHandlerProvider pluginDataHandlerProvider;
+
     @InjectMocks
     private GerritClient gerritClient;
 
@@ -72,10 +76,9 @@
         when(config.getGerritUserName()).thenReturn("Your Gerrit username");
 
         List<ReviewBatch> reviewBatches = new ArrayList<>();
-        reviewBatches.add(new ReviewBatch());
-        reviewBatches.get(0).setContent("message");
+        reviewBatches.add(new ReviewBatch("message"));
 
-        GerritClientReview gerritClientReview = new GerritClientReview(config, accountCache, localizer);
+        GerritClientReview gerritClientReview = new GerritClientReview(config, accountCache, pluginDataHandlerProvider, localizer);
         gerritClientReview.setReview(new GerritChange("Your changeId"), reviewBatches, changeSetData);
     }
 }