From 611ff3b7762556eef526adc0c78388dd680b4cd4 Mon Sep 17 00:00:00 2001 From: Sumit Kumar Date: Mon, 27 Sep 2021 20:40:14 +0530 Subject: [PATCH] feat: make Mongodb plugin error messages more readable. (#7857) * Make MongoDB plugin's error messages more readable. A Client side change is required before this change becomes visible to the end user. --- .../AppsmithPluginException.java | 6 + .../models/ActionExecutionResult.java | 12 +- .../plugins/AppsmithPluginErrorUtils.java | 13 ++ .../com/external/plugins/MongoPlugin.java | 18 ++- .../external/plugins/commands/Aggregate.java | 2 +- .../com/external/plugins/commands/Count.java | 2 +- .../com/external/plugins/commands/Delete.java | 2 +- .../external/plugins/commands/Distinct.java | 2 +- .../com/external/plugins/commands/Find.java | 2 +- .../com/external/plugins/commands/Insert.java | 2 +- .../external/plugins/commands/UpdateMany.java | 2 +- .../plugins/utils/MongoErrorUtils.java | 107 +++++++++++++++ .../plugins/{ => utils}/MongoPluginUtils.java | 2 +- .../com/external/plugins/MongoPluginTest.java | 129 +++++++++++++++++- 14 files changed, 283 insertions(+), 18 deletions(-) create mode 100644 app/server/appsmith-interfaces/src/main/java/com/appsmith/external/plugins/AppsmithPluginErrorUtils.java create mode 100644 app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/utils/MongoErrorUtils.java rename app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/{ => utils}/MongoPluginUtils.java (99%) diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/exceptions/pluginExceptions/AppsmithPluginException.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/exceptions/pluginExceptions/AppsmithPluginException.java index e576290b68..3d77d71c5b 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/exceptions/pluginExceptions/AppsmithPluginException.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/exceptions/pluginExceptions/AppsmithPluginException.java @@ -8,11 +8,17 @@ import lombok.Setter; @Getter @Setter public class AppsmithPluginException extends BaseException { + private final Throwable externalError; private final AppsmithPluginError error; private final Object[] args; public AppsmithPluginException(AppsmithPluginError error, Object... args) { + this(null, error, args); + } + + public AppsmithPluginException(Throwable externalError, AppsmithPluginError error, Object... args) { super(error.getMessage(args)); + this.externalError = externalError; this.error = error; this.args = args; } diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/ActionExecutionResult.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/ActionExecutionResult.java index 9939d1fd94..7a20ec36c6 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/ActionExecutionResult.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/ActionExecutionResult.java @@ -2,6 +2,7 @@ package com.appsmith.external.models; import com.appsmith.external.exceptions.BaseException; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException; +import com.appsmith.external.plugins.AppsmithPluginErrorUtils; import com.fasterxml.jackson.databind.JsonNode; import lombok.Getter; import lombok.NoArgsConstructor; @@ -22,6 +23,7 @@ public class ActionExecutionResult { String errorType; JsonNode headers; Object body; + String readableError; Boolean isExecutionSuccess = false; /* @@ -36,16 +38,24 @@ public class ActionExecutionResult { List suggestedWidgets; - public void setErrorInfo(Throwable error) { + public void setErrorInfo(Throwable error, AppsmithPluginErrorUtils pluginErrorUtils) { this.body = error.getMessage(); if (error instanceof AppsmithPluginException) { this.statusCode = ((AppsmithPluginException) error).getAppErrorCode().toString(); this.title = ((AppsmithPluginException) error).getTitle(); this.errorType = ((AppsmithPluginException) error).getErrorType(); + + if (((AppsmithPluginException) error).getExternalError() != null && pluginErrorUtils != null) { + this.readableError = pluginErrorUtils.getReadableError(error); + } } else if (error instanceof BaseException) { this.statusCode = ((BaseException) error).getAppErrorCode().toString(); this.title = ((BaseException) error).getTitle(); } } + + public void setErrorInfo(Throwable error) { + this.setErrorInfo(error, null); + } } diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/plugins/AppsmithPluginErrorUtils.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/plugins/AppsmithPluginErrorUtils.java new file mode 100644 index 0000000000..9be6bfe198 --- /dev/null +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/plugins/AppsmithPluginErrorUtils.java @@ -0,0 +1,13 @@ +package com.appsmith.external.plugins; + +/** + * Defines a common interface that plugin agnostic code flow can use to work with plugin related error objects. Each + * plugin must override the common methods to provide plugin specific functionality. One use case is to extract + * human-readable strings from otherwise huge error messages. For this use case, each plugin must override + * `getReadableError` method to define its own way of extracting the readable error message. + */ +public abstract class AppsmithPluginErrorUtils { + public String getReadableError(Throwable error) { + return error.getMessage(); + } +} diff --git a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/MongoPlugin.java b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/MongoPlugin.java index f9ca4927bb..c97ccb1216 100644 --- a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/MongoPlugin.java +++ b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/MongoPlugin.java @@ -24,6 +24,7 @@ import com.appsmith.external.models.SSLDetails; import com.appsmith.external.plugins.BasePlugin; import com.appsmith.external.plugins.PluginExecutor; import com.appsmith.external.plugins.SmartSubstitutionInterface; +import com.external.plugins.utils.MongoErrorUtils; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.mongodb.MongoCommandException; @@ -65,13 +66,13 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import static com.appsmith.external.constants.ActionConstants.ACTION_CONFIGURATION_BODY; -import static com.external.plugins.MongoPluginUtils.convertMongoFormInputToRawCommand; -import static com.external.plugins.MongoPluginUtils.generateTemplatesAndStructureForACollection; -import static com.external.plugins.MongoPluginUtils.getDatabaseName; +import static com.external.plugins.utils.MongoPluginUtils.convertMongoFormInputToRawCommand; +import static com.external.plugins.utils.MongoPluginUtils.generateTemplatesAndStructureForACollection; +import static com.external.plugins.utils.MongoPluginUtils.getDatabaseName; import static com.appsmith.external.helpers.PluginUtils.getValueSafelyFromFormData; -import static com.external.plugins.MongoPluginUtils.isRawCommand; +import static com.external.plugins.utils.MongoPluginUtils.isRawCommand; import static com.appsmith.external.helpers.PluginUtils.setValueSafelyInFormData; -import static com.external.plugins.MongoPluginUtils.urlEncode; +import static com.external.plugins.utils.MongoPluginUtils.urlEncode; import static com.appsmith.external.helpers.PluginUtils.validConfigurationPresentInFormData; import static com.external.plugins.constants.FieldName.AGGREGATE_PIPELINE; import static com.external.plugins.constants.FieldName.COUNT_QUERY; @@ -156,6 +157,8 @@ public class MongoPlugin extends BasePlugin { UPDATE_OPERATION )); + private static final MongoErrorUtils mongoErrorUtils = MongoErrorUtils.getInstance(); + public MongoPlugin(PluginWrapper wrapper) { super(wrapper); } @@ -276,6 +279,7 @@ public class MongoPlugin extends BasePlugin { .onErrorMap( MongoCommandException.class, error -> new AppsmithPluginException( + error, AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR, error.getErrorMessage() ) @@ -383,7 +387,7 @@ public class MongoPlugin extends BasePlugin { } ActionExecutionResult actionExecutionResult = new ActionExecutionResult(); actionExecutionResult.setIsExecutionSuccess(false); - actionExecutionResult.setErrorInfo(error); + actionExecutionResult.setErrorInfo(error, mongoErrorUtils); return Mono.just(actionExecutionResult); }) // Now set the request in the result to be returned back to the server @@ -804,7 +808,7 @@ public class MongoPlugin extends BasePlugin { return Mono.just(new DatasourceTestResult()); } - return Mono.just(new DatasourceTestResult(error.getMessage())); + return Mono.just(new DatasourceTestResult(mongoErrorUtils.getReadableError(error))); }) .subscribeOn(scheduler); } diff --git a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/commands/Aggregate.java b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/commands/Aggregate.java index 9f7869b160..8b8380a9ae 100644 --- a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/commands/Aggregate.java +++ b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/commands/Aggregate.java @@ -16,7 +16,7 @@ import java.util.ArrayList; import java.util.Map; import static com.appsmith.external.helpers.PluginUtils.getValueSafelyFromFormData; -import static com.external.plugins.MongoPluginUtils.parseSafely; +import static com.external.plugins.utils.MongoPluginUtils.parseSafely; import static com.appsmith.external.helpers.PluginUtils.validConfigurationPresentInFormData; import static com.external.plugins.constants.FieldName.AGGREGATE_PIPELINE; diff --git a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/commands/Count.java b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/commands/Count.java index 9fe496621a..73bc5e9d6d 100644 --- a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/commands/Count.java +++ b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/commands/Count.java @@ -9,7 +9,7 @@ import org.pf4j.util.StringUtils; import java.util.Map; import static com.appsmith.external.helpers.PluginUtils.getValueSafelyFromFormData; -import static com.external.plugins.MongoPluginUtils.parseSafely; +import static com.external.plugins.utils.MongoPluginUtils.parseSafely; import static com.appsmith.external.helpers.PluginUtils.validConfigurationPresentInFormData; import static com.external.plugins.constants.FieldName.COUNT_QUERY; diff --git a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/commands/Delete.java b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/commands/Delete.java index 2c0f8489e5..98b9dee9fb 100644 --- a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/commands/Delete.java +++ b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/commands/Delete.java @@ -15,7 +15,7 @@ import java.util.List; import java.util.Map; import static com.appsmith.external.helpers.PluginUtils.getValueSafelyFromFormData; -import static com.external.plugins.MongoPluginUtils.parseSafely; +import static com.external.plugins.utils.MongoPluginUtils.parseSafely; import static com.appsmith.external.helpers.PluginUtils.setValueSafelyInFormData; import static com.appsmith.external.helpers.PluginUtils.validConfigurationPresentInFormData; import static com.external.plugins.constants.FieldName.COLLECTION; diff --git a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/commands/Distinct.java b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/commands/Distinct.java index 52829542eb..fd01e900be 100644 --- a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/commands/Distinct.java +++ b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/commands/Distinct.java @@ -10,7 +10,7 @@ import org.pf4j.util.StringUtils; import java.util.Map; import static com.appsmith.external.helpers.PluginUtils.getValueSafelyFromFormData; -import static com.external.plugins.MongoPluginUtils.parseSafely; +import static com.external.plugins.utils.MongoPluginUtils.parseSafely; import static com.appsmith.external.helpers.PluginUtils.validConfigurationPresentInFormData; import static com.external.plugins.constants.FieldName.DISTINCT_QUERY; diff --git a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/commands/Find.java b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/commands/Find.java index cd779017a5..28a96912b2 100644 --- a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/commands/Find.java +++ b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/commands/Find.java @@ -14,7 +14,7 @@ import java.util.List; import java.util.Map; import static com.appsmith.external.helpers.PluginUtils.getValueSafelyFromFormData; -import static com.external.plugins.MongoPluginUtils.parseSafely; +import static com.external.plugins.utils.MongoPluginUtils.parseSafely; import static com.appsmith.external.helpers.PluginUtils.setValueSafelyInFormData; import static com.appsmith.external.helpers.PluginUtils.validConfigurationPresentInFormData; import static com.external.plugins.constants.FieldName.COLLECTION; diff --git a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/commands/Insert.java b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/commands/Insert.java index 0f27b7040d..3f6aeea276 100644 --- a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/commands/Insert.java +++ b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/commands/Insert.java @@ -22,7 +22,7 @@ import java.util.Map; import java.util.stream.Collectors; import static com.appsmith.external.helpers.PluginUtils.getValueSafelyFromFormData; -import static com.external.plugins.MongoPluginUtils.parseSafely; +import static com.external.plugins.utils.MongoPluginUtils.parseSafely; import static com.appsmith.external.helpers.PluginUtils.setValueSafelyInFormData; import static com.appsmith.external.helpers.PluginUtils.validConfigurationPresentInFormData; import static com.external.plugins.constants.FieldName.COLLECTION; diff --git a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/commands/UpdateMany.java b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/commands/UpdateMany.java index 660d24fca9..0d0b1705ef 100644 --- a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/commands/UpdateMany.java +++ b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/commands/UpdateMany.java @@ -15,7 +15,7 @@ import java.util.List; import java.util.Map; import static com.appsmith.external.helpers.PluginUtils.getValueSafelyFromFormData; -import static com.external.plugins.MongoPluginUtils.parseSafely; +import static com.external.plugins.utils.MongoPluginUtils.parseSafely; import static com.appsmith.external.helpers.PluginUtils.setValueSafelyInFormData; import static com.appsmith.external.helpers.PluginUtils.validConfigurationPresentInFormData; import static com.external.plugins.constants.FieldName.COLLECTION; diff --git a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/utils/MongoErrorUtils.java b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/utils/MongoErrorUtils.java new file mode 100644 index 0000000000..9f5608abbc --- /dev/null +++ b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/utils/MongoErrorUtils.java @@ -0,0 +1,107 @@ +package com.external.plugins.utils; + +import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException; +import com.appsmith.external.plugins.AppsmithPluginErrorUtils; +import com.mongodb.MongoCommandException; +import com.mongodb.MongoSecurityException; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class MongoErrorUtils extends AppsmithPluginErrorUtils { + private static MongoErrorUtils mongoErrorUtils; + + public static MongoErrorUtils getInstance() { + if (mongoErrorUtils == null) { + mongoErrorUtils = new MongoErrorUtils(); + } + + return mongoErrorUtils; + } + + /** + * Extract small readable portion of error message from a larger less comprehensible error message. + * @param error - any error object + * @return readable error message + */ + @Override + public String getReadableError(Throwable error) { + Throwable externalError; + + // If the external error is wrapped inside Appsmith error, then extract the external error first. + if (error instanceof AppsmithPluginException) { + if (((AppsmithPluginException) error).getExternalError() == null) { + return error.getMessage(); + } + + externalError = ((AppsmithPluginException) error).getExternalError(); + } + else { + externalError = error; + } + + if (externalError instanceof MongoCommandException) { + MongoCommandException mongoCommandError = (MongoCommandException) externalError; + int errorCode = mongoCommandError.getCode(); + + // Error codes ref: https://github.com/mongodb/mongo/blob/50dc6dbe394c42d03659aa3410954f1e3ff46740/src/mongo/base/error_codes.err#L12 + switch (errorCode) { + case 9: // FailedToParse error + + /** + * Sample external error message: + * Failed to parse: { find: "newAction", limit: [ 10 ], $db: "mobtools", ... }. 'limit' field must + * be numeric. + * + * Return string: 'limit' field must be numeric. + */ + return getLast(mongoCommandError.getErrorMessage().split("\\.")).trim() + "."; + default: + + /** + * Sample external error message: + * Error getting filter : Expected 'filter' to be BSON (or equivalent), but got string instead. + * Doc = [{find newAction} {filter filterx} {limit 10} {$db mobtools} ...] + * + * Return string: Error getting filter : Expected 'filter' to be BSON (or equivalent), but got + * string instead. + */ + return mongoCommandError.getErrorMessage().split("\\.")[0].trim() + "."; + } + } + else if (externalError instanceof MongoSecurityException) { + MongoSecurityException mongoSecurityError = (MongoSecurityException) externalError; + int errorCode = mongoSecurityError.getCode(); + switch (errorCode) { + default: + /** + * Sample external error message: + * Exception authenticating MongoCredential{mechanism=SCRAM-SHA-1, userName='username', + * source='admin', password=, mechanismProperties=} + * + * Return string: Exception authenticating MongoCredential. + */ + return mongoSecurityError.getMessage().split("\\{")[0].trim() + "."; + } + } + + /** + * Sample external error message: + * Error getting filter : Expected 'filter' to be BSON (or equivalent), but got string instead. + * Doc = [{find newAction} {filter filterx} {limit 10} {$db mobtools} ...] + * + * Return string: Error getting filter : Expected 'filter' to be BSON (or equivalent), but got + * string instead. + */ + return error.getMessage().split("\\.")[0].trim() + "."; + } + + // Get last element from array. + private String getLast(String[] messageArray) { + if (messageArray.length == 0) { + return ""; + } + + return messageArray[messageArray.length - 1]; + } +} diff --git a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/MongoPluginUtils.java b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/utils/MongoPluginUtils.java similarity index 99% rename from app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/MongoPluginUtils.java rename to app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/utils/MongoPluginUtils.java index c606452c1b..2f593439cd 100644 --- a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/MongoPluginUtils.java +++ b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/utils/MongoPluginUtils.java @@ -1,4 +1,4 @@ -package com.external.plugins; +package com.external.plugins.utils; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException; diff --git a/app/server/appsmith-plugins/mongoPlugin/src/test/java/com/external/plugins/MongoPluginTest.java b/app/server/appsmith-plugins/mongoPlugin/src/test/java/com/external/plugins/MongoPluginTest.java index d1987a39f2..65a9315cf7 100644 --- a/app/server/appsmith-plugins/mongoPlugin/src/test/java/com/external/plugins/MongoPluginTest.java +++ b/app/server/appsmith-plugins/mongoPlugin/src/test/java/com/external/plugins/MongoPluginTest.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.mongodb.MongoCommandException; +import com.mongodb.MongoSecurityException; import com.mongodb.reactivestreams.client.MongoClient; import com.mongodb.reactivestreams.client.MongoClients; import com.mongodb.reactivestreams.client.MongoCollection; @@ -87,14 +88,13 @@ public class MongoPluginTest { private static String address; private static Integer port; + private JsonNode value; @SuppressWarnings("rawtypes") @ClassRule public static GenericContainer mongoContainer = new GenericContainer(CompletableFuture.completedFuture("mongo:4.4")) .withExposedPorts(27017); - private JsonNode value; - @BeforeClass public static void setUp() { address = mongoContainer.getContainerIpAddress(); @@ -1691,4 +1691,129 @@ public class MongoPluginTest { dsConnectionMono.flatMap(conn -> pluginExecutor.executeParameterized(conn, new ExecuteActionDTO(), dsConfig, actionConfiguration)).block(); } + @Test + public void testReadableErrorWithFilterKeyError() { + DatasourceConfiguration dsConfig = createDatasourceConfiguration(); + Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); + + ActionConfiguration actionConfiguration = new ActionConfiguration(); + // Set bad attribute for limit key + actionConfiguration.setBody("{\n" + + " find: \"users\",\n" + + " filter: \"filter\",\n" + + " limit: 10,\n" + + " }"); + + Map configMap = new HashMap<>(); + setValueSafelyInFormData(configMap, SMART_SUBSTITUTION, Boolean.TRUE); + setValueSafelyInFormData(configMap, COMMAND, "RAW"); + actionConfiguration.setFormData(configMap); + + Mono executeMono = dsConnectionMono.flatMap(conn -> pluginExecutor.executeParameterized(conn, + new ExecuteActionDTO(), dsConfig, actionConfiguration)); + StepVerifier.create(executeMono) + .assertNext(obj -> { + ActionExecutionResult result = (ActionExecutionResult) obj; + assertNotNull(result); + assertFalse(result.getIsExecutionSuccess()); + assertNotNull(result.getBody()); + + // Verify readable error. + String expectedReadableError = "'filter' field must be of BSON type object."; + assertEquals(expectedReadableError, result.getReadableError()); + }) + .verifyComplete(); + } + + @Test + public void testReadableErrorWithMongoFailedToParseError() { + DatasourceConfiguration dsConfig = createDatasourceConfiguration(); + Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); + + ActionConfiguration actionConfiguration = new ActionConfiguration(); + // Set bad attribute for limit key + actionConfiguration.setBody("{\n" + + " find: \"users\",\n" + + " limit: [10],\n" + + " }"); + + Map configMap = new HashMap<>(); + setValueSafelyInFormData(configMap, SMART_SUBSTITUTION, Boolean.TRUE); + setValueSafelyInFormData(configMap, COMMAND, "RAW"); + actionConfiguration.setFormData(configMap); + + Mono executeMono = dsConnectionMono.flatMap(conn -> pluginExecutor.executeParameterized(conn, + new ExecuteActionDTO(), dsConfig, actionConfiguration)); + StepVerifier.create(executeMono) + .assertNext(obj -> { + ActionExecutionResult result = (ActionExecutionResult) obj; + assertNotNull(result); + assertFalse(result.getIsExecutionSuccess()); + assertNotNull(result.getBody()); + + // Verify readable error. + String expectedReadableError = "'limit' field must be numeric."; + assertEquals(expectedReadableError, result.getReadableError()); + }) + .verifyComplete(); + } + + @Test + public void testReadableErrorWithMongoBadKeyError() { + DatasourceConfiguration dsConfig = createDatasourceConfiguration(); + Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); + + ActionConfiguration actionConfiguration = new ActionConfiguration(); + // Set unrecognized key limitx + actionConfiguration.setBody("{\n" + + " find: \"users\",\n" + + " limitx: 10,\n" + + " }"); + + Map configMap = new HashMap<>(); + setValueSafelyInFormData(configMap, SMART_SUBSTITUTION, Boolean.TRUE); + setValueSafelyInFormData(configMap, COMMAND, "RAW"); + actionConfiguration.setFormData(configMap); + + Mono executeMono = dsConnectionMono.flatMap(conn -> pluginExecutor.executeParameterized(conn, + new ExecuteActionDTO(), dsConfig, actionConfiguration)); + StepVerifier.create(executeMono) + .assertNext(obj -> { + ActionExecutionResult result = (ActionExecutionResult) obj; + assertNotNull(result); + assertFalse(result.getIsExecutionSuccess()); + assertNotNull(result.getBody()); + + // Verify readable error. + String expectedReadableError = "Unrecognized field 'limitx'."; + assertEquals(expectedReadableError, result.getReadableError()); + }) + .verifyComplete(); + } + + @Test + public void testReadableErrorOnTestDatasourceFailWithBadCredentials() { + // Mock exception on authentication failure. + MongoSecurityException mockMongoSecurityException = mock(MongoSecurityException.class); + when(mockMongoSecurityException.getCode()).thenReturn(-4); + when(mockMongoSecurityException.getMessage()).thenReturn("Exception authenticating " + + "MongoCredential{mechanism=SCRAM-SHA-1, userName='username', source='admin', password=," + + " mechanismProperties=}"); + + // Throw mock error on datasource create method call. + MongoPlugin.MongoPluginExecutor spyMongoPluginExecutor = spy(pluginExecutor); + doReturn(Mono.error(mockMongoSecurityException)).when(spyMongoPluginExecutor).datasourceCreate(any()); + + DatasourceConfiguration dsConfig = createDatasourceConfiguration(); + StepVerifier.create(spyMongoPluginExecutor.testDatasource(dsConfig)) + .assertNext(datasourceTestResult -> { + assertNotNull(datasourceTestResult); + assertFalse(datasourceTestResult.isSuccess()); + + // Verify readable error. + String expectedReadableError = "Exception authenticating MongoCredential."; + assertEquals(expectedReadableError, datasourceTestResult.getInvalids().toArray()[0]); + }) + .verifyComplete(); + } }