diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/PluginUtils.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/PluginUtils.java index f71fb122ae..c9d659ef4a 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/PluginUtils.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/PluginUtils.java @@ -1,11 +1,14 @@ package com.appsmith.external.helpers; import com.appsmith.external.constants.ConditionalOperator; +import com.appsmith.external.datatypes.ClientDataType; +import com.appsmith.external.dtos.ExecuteActionDTO; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException; import com.appsmith.external.models.Condition; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.Endpoint; +import com.appsmith.external.models.Param; import com.appsmith.external.models.Property; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; @@ -25,6 +28,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -236,7 +240,7 @@ public class PluginUtils { } } - public static void setDataValueSafelyInFormData(Map formData, String field, Object value) { + public static Map setDataValueSafelyInFormData(Map formData, String field, Object value) { // In case the formData has not been initialized before the fxn call, assign a new HashMap to the variable if (formData == null) { @@ -269,9 +273,11 @@ public class PluginUtils { formData.put(field, valueMap); } } + + return formData; } - public static void setValueSafelyInFormData(Map formData, String field, Object value) { + public static Map setValueSafelyInFormData(Map formData, String field, Object value) { // In case the formData has not been initialized before the fxn call, assign a new HashMap to the variable if (formData == null) { @@ -297,6 +303,8 @@ public class PluginUtils { // This is a top level field. Set the value formData.put(field, value); } + + return formData; } public static boolean endpointContainsLocalhost(Endpoint endpoint) { @@ -464,4 +472,22 @@ public class PluginUtils { } } } + + public static ExecuteActionDTO getExecuteDTOForTestWithBindingAndValueAndDataType(LinkedHashMap bindingValueDataTypeMap) { + List params = new ArrayList<>(); + bindingValueDataTypeMap.keySet().stream() + .forEach(bindingName -> { + String bindingValue = (String) (bindingValueDataTypeMap.get(bindingName)).get(0); + ClientDataType clientDataType = (ClientDataType) (bindingValueDataTypeMap.get(bindingName)).get(1); + Param param = new Param(); + param.setKey(bindingName); + param.setValue(bindingValue); + param.setClientDataType(clientDataType); + params.add(param); + }); + + ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); + executeActionDTO.setParams(params); + return executeActionDTO; + } } diff --git a/app/server/appsmith-plugins/oraclePlugin/pom.xml b/app/server/appsmith-plugins/oraclePlugin/pom.xml index 31a388ac53..d3e6761b1e 100755 --- a/app/server/appsmith-plugins/oraclePlugin/pom.xml +++ b/app/server/appsmith-plugins/oraclePlugin/pom.xml @@ -34,25 +34,20 @@ - - - - + com.oracle.database.jdbc ojdbc8 21.9.0.0 - - - org.testcontainers - oracle-xe - 1.17.2 - test - - + + + + + org.testcontainers - jdbc-test - 1.11.4 - + oracle-xe + 1.18.0 + test + diff --git a/app/server/appsmith-plugins/oraclePlugin/src/main/java/com/external/plugins/OraclePlugin.java b/app/server/appsmith-plugins/oraclePlugin/src/main/java/com/external/plugins/OraclePlugin.java index 07442ed764..b08c22e832 100644 --- a/app/server/appsmith-plugins/oraclePlugin/src/main/java/com/external/plugins/OraclePlugin.java +++ b/app/server/appsmith-plugins/oraclePlugin/src/main/java/com/external/plugins/OraclePlugin.java @@ -115,13 +115,18 @@ public class OraclePlugin extends BasePlugin { } @Override - public Mono execute(HikariDataSource connection, DatasourceConfiguration datasourceConfiguration, ActionConfiguration actionConfiguration) { + public Mono execute(HikariDataSource connection, + DatasourceConfiguration datasourceConfiguration, + ActionConfiguration actionConfiguration) { return Mono.error( new AppsmithPluginException(OraclePluginError.QUERY_EXECUTION_FAILED, "Unsupported Operation")); } @Override - public Mono executeParameterized(HikariDataSource connection, ExecuteActionDTO executeActionDTO, DatasourceConfiguration datasourceConfiguration, ActionConfiguration actionConfiguration) { + public Mono executeParameterized(HikariDataSource connectionPool, + ExecuteActionDTO executeActionDTO, + DatasourceConfiguration datasourceConfiguration, + ActionConfiguration actionConfiguration) { final Map formData = actionConfiguration.getFormData(); String query = getDataValueSafelyFromFormData(formData, BODY, STRING_TYPE, null); // Check for query parameter before performing the probably expensive fetch connection from the pool op. @@ -144,7 +149,7 @@ public class OraclePlugin extends BasePlugin { // In case of non-prepared statement, simply do binding-replacement and execute if (FALSE.equals(isPreparedStatement)) { prepareConfigurationsForExecution(executeActionDTO, actionConfiguration, datasourceConfiguration); - return executeCommon(connection, datasourceConfiguration, actionConfiguration, FALSE, null, null); + return executeCommon(connectionPool, datasourceConfiguration, actionConfiguration, FALSE, null, null); } // First extract all the bindings in order @@ -162,7 +167,7 @@ public class OraclePlugin extends BasePlugin { updatedQuery = removeSemicolonFromQuery(updatedQuery); } setDataValueSafelyInFormData(formData, BODY, updatedQuery); - return executeCommon(connection, datasourceConfiguration, actionConfiguration, TRUE, + return executeCommon(connectionPool, datasourceConfiguration, actionConfiguration, TRUE, mustacheKeysInOrder, executeActionDTO); } @@ -288,8 +293,9 @@ public class OraclePlugin extends BasePlugin { } @Override - public Mono getStructure(HikariDataSource connection, DatasourceConfiguration datasourceConfiguration) { - return OracleDatasourceUtils.getStructure(connection, datasourceConfiguration); + public Mono getStructure(HikariDataSource connectionPool, + DatasourceConfiguration datasourceConfiguration) { + return OracleDatasourceUtils.getStructure(connectionPool, datasourceConfiguration); } private Set populateHintMessages(List columnNames) { diff --git a/app/server/appsmith-plugins/oraclePlugin/src/main/java/com/external/plugins/exceptions/OracleErrorMessages.java b/app/server/appsmith-plugins/oraclePlugin/src/main/java/com/external/plugins/exceptions/OracleErrorMessages.java index 50ff4874bc..334f2c73a3 100644 --- a/app/server/appsmith-plugins/oraclePlugin/src/main/java/com/external/plugins/exceptions/OracleErrorMessages.java +++ b/app/server/appsmith-plugins/oraclePlugin/src/main/java/com/external/plugins/exceptions/OracleErrorMessages.java @@ -19,7 +19,8 @@ public class OracleErrorMessages { public static final String INVALID_SSL_OPTION_ERROR_MSG = "The Appsmith server has found an unexpected SSL option: %s."; - public static final String CONNECTION_POOL_CREATION_FAILED_ERROR_MSG = "An exception occurred while creating connection pool. One or more arguments in the datasource configuration may be invalid."; + public static final String CONNECTION_POOL_CREATION_FAILED_ERROR_MSG = "An exception occurred while creating " + + "connection pool. One or more arguments in the datasource configuration may be invalid."; /* ************************************************************************************************************************************************ diff --git a/app/server/appsmith-plugins/oraclePlugin/src/main/java/com/external/plugins/utils/OracleDatasourceUtils.java b/app/server/appsmith-plugins/oraclePlugin/src/main/java/com/external/plugins/utils/OracleDatasourceUtils.java index bbef663399..de6cb82b45 100644 --- a/app/server/appsmith-plugins/oraclePlugin/src/main/java/com/external/plugins/utils/OracleDatasourceUtils.java +++ b/app/server/appsmith-plugins/oraclePlugin/src/main/java/com/external/plugins/utils/OracleDatasourceUtils.java @@ -307,10 +307,10 @@ public class OracleDatasourceUtils { getSampleColumnNamesCSVString(columnNameToSampleColumnDataMap), getSampleColumnDataCSVString(columnNameToSampleColumnDataMap)); String updateQueryTemplate = MessageFormat.format("UPDATE {0} SET {1} WHERE " + - "1=0; -- Specify a valid condition here. Removing the condition may " + + "1=0 -- Specify a valid condition here. Removing the condition may " + "update every row in the table!", table.getName(), getSampleOneColumnUpdateString(columnNameToSampleColumnDataMap)); - String deleteQueryTemplate = MessageFormat.format("DELETE FROM {0} WHERE 1=0;" + + String deleteQueryTemplate = MessageFormat.format("DELETE FROM {0} WHERE 1=0" + " -- Specify a valid condition here. Removing the condition may " + "delete everything in the table!", table.getName()); @@ -460,7 +460,7 @@ public class OracleDatasourceUtils { int activeConnections = poolProxy.getActiveConnections(); int totalConnections = poolProxy.getTotalConnections(); int threadsAwaitingConnection = poolProxy.getThreadsAwaitingConnection(); - log.debug("{0}: Hikari Pool stats : active - {1} , idle - {2}, awaiting - {3} , total - {4}", logPrefix, - activeConnections, idleConnections, threadsAwaitingConnection, totalConnections); + log.debug(MessageFormat.format("{0}: Hikari Pool stats : active - {1} , idle - {2}, awaiting - {3} , total - {4}", + logPrefix, activeConnections, idleConnections, threadsAwaitingConnection, totalConnections)); } } diff --git a/app/server/appsmith-plugins/oraclePlugin/src/main/java/com/external/plugins/utils/OracleExecuteUtils.java b/app/server/appsmith-plugins/oraclePlugin/src/main/java/com/external/plugins/utils/OracleExecuteUtils.java index 6dc8dfc54f..e2876677eb 100644 --- a/app/server/appsmith-plugins/oraclePlugin/src/main/java/com/external/plugins/utils/OracleExecuteUtils.java +++ b/app/server/appsmith-plugins/oraclePlugin/src/main/java/com/external/plugins/utils/OracleExecuteUtils.java @@ -2,8 +2,8 @@ package com.external.plugins.utils; import com.appsmith.external.plugins.SmartSubstitutionInterface; import oracle.jdbc.OracleArray; +import oracle.jdbc.OracleBlob; import oracle.sql.CLOB; -import oracle.sql.Datum; import org.apache.commons.lang.ObjectUtils; import java.sql.Connection; @@ -13,9 +13,9 @@ import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; import java.text.MessageFormat; -import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; +import java.util.Base64; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -32,6 +32,8 @@ public class OracleExecuteUtils implements SmartSubstitutionInterface { public static final String TIMESTAMPLTZ_TYPE_NAME = "TIMESTAMP WITH LOCAL TIME ZONE"; public static final String CLOB_TYPE_NAME = "CLOB"; public static final String NCLOB_TYPE_NAME = "NCLOB"; + public static final String RAW_TYPE_NAME = "RAW"; + public static final String BLOB_TYPE_NAME = "BLOB"; public static final String AFFECTED_ROWS_KEY = "affectedRows"; /** @@ -136,23 +138,36 @@ public class OracleExecuteUtils implements SmartSubstitutionInterface { } else if (DATE_COLUMN_TYPE_NAME.equalsIgnoreCase(typeName)) { value = DateTimeFormatter.ISO_DATE.format(resultSet.getDate(i).toLocalDate()); - } else if (TIMESTAMP_TYPE_NAME.equalsIgnoreCase(typeName)) { - value = DateTimeFormatter.ISO_DATE_TIME.format( - LocalDateTime.of( - resultSet.getDate(i).toLocalDate(), - resultSet.getTime(i).toLocalTime() - ) - ) + "Z"; - - } else if (TIMESTAMPTZ_TYPE_NAME.equalsIgnoreCase(typeName) || TIMESTAMPLTZ_TYPE_NAME.equalsIgnoreCase(typeName)) { + } else if (TIMESTAMP_TYPE_NAME.equalsIgnoreCase(typeName) || TIMESTAMPTZ_TYPE_NAME.equalsIgnoreCase(typeName) || TIMESTAMPLTZ_TYPE_NAME.equalsIgnoreCase(typeName)) { value = DateTimeFormatter.ISO_DATE_TIME.format( resultSet.getObject(i, OffsetDateTime.class) ); - } else if (CLOB_TYPE_NAME.equalsIgnoreCase(typeName) || NCLOB_TYPE_NAME.equals(typeName)) { + /** + * clob, nclob are textual data. + * Ref: https://docs.oracle.com/javadb/10.10.1.2/ref/rrefclob.html + */ value = String.valueOf(((CLOB)resultSet.getObject(i)).getTarget().getPrefetchedData()); } else if (resultSet.getObject(i) instanceof OracleArray) { value = ((OracleArray)resultSet.getObject(i)).getArray(); + } else if (RAW_TYPE_NAME.equalsIgnoreCase(typeName)) { + /** + * Raw / Blob data cannot be interpreted as anything but a byte array. Hence, send it back as a + * base64 encoded string. The correct way to read the data for these types is for the user to + * cast them to a type before reading them, example: + * select utl_raw.cast_to_varchar2(c_raw) as c_raw, utl_raw.cast_to_varchar2(c_blob) as c_blob from TYPESTEST4 + */ + value = Base64.getEncoder().encodeToString((byte[]) resultSet.getObject(i)); + } + else if (BLOB_TYPE_NAME.equalsIgnoreCase(typeName)) { + /** + * Raw / Blob data cannot be interpreted as anything but a byte array. Hence, send it back as a + * base64 encoded string. The correct way to read the data for these types is for the user to + * cast them to a type before reading them, example: + * select utl_raw.cast_to_varchar2(c_raw) as c_raw, utl_raw.cast_to_varchar2(c_blob) as c_blob from TYPESTEST4 + */ + value = ((OracleBlob)resultSet.getObject(i)).getBytes(1L, + (int) ((OracleBlob)resultSet.getObject(i)).length()); } else { value = resultSet.getObject(i).toString(); diff --git a/app/server/appsmith-plugins/oraclePlugin/src/test/java/com/external/plugins/OracleExecutionTest.java b/app/server/appsmith-plugins/oraclePlugin/src/test/java/com/external/plugins/OracleExecutionTest.java new file mode 100644 index 0000000000..62052a93f2 --- /dev/null +++ b/app/server/appsmith-plugins/oraclePlugin/src/test/java/com/external/plugins/OracleExecutionTest.java @@ -0,0 +1,328 @@ +package com.external.plugins; + +import com.appsmith.external.datatypes.ClientDataType; +import com.appsmith.external.dtos.ExecuteActionDTO; +import com.appsmith.external.models.ActionConfiguration; +import com.appsmith.external.models.ActionExecutionResult; +import com.zaxxer.hikari.HikariDataSource; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.OracleContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.sql.SQLException; +import java.text.MessageFormat; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import static com.appsmith.external.helpers.PluginUtils.getExecuteDTOForTestWithBindingAndValueAndDataType; +import static com.appsmith.external.helpers.PluginUtils.setDataValueSafelyInFormData; +import static com.external.plugins.OracleTestDBContainerManager.getDefaultDatasourceConfig; +import static com.external.plugins.OracleTestDBContainerManager.oraclePluginExecutor; +import static com.external.plugins.OracleTestDBContainerManager.runSQLQueryOnOracleTestDB; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Testcontainers +public class OracleExecutionTest { + public static final String SQL_QUERY_CREATE_TABLE_FORMAT = + "create table {0} (\n" + + "c_varchar2 varchar2(20),\n" + + "c_nvarchar2 nvarchar2(20),\n" + + "c_number number,\n" + + "c_float float,\n" + + "c_date date,\n" + + "c_binary_float binary_float,\n" + + "c_binary_double binary_double,\n" + + "c_timestamp timestamp,\n" + + "c_timestamp_tz timestamp with time zone,\n" + + "c_timestamp_ltz timestamp with local time zone,\n" + + "c_interval_year interval year to month,\n" + + "c_interval_day interval day to second,\n" + + "c_rowid rowid,\n" + + "c_urowid urowid,\n" + + "c_char char(20),\n" + + "c_nchar nchar(20),\n" + + "c_clob clob,\n" + + "c_nclob nclob\n" + + ")\n"; + private static final String SQL_QUERY_TO_INSERT_ONE_ROW_FORMAT = + "insert into {0} values (\n" + + "''varchar2'',\n" + + "''nvarchar2'',\n" + + "{1},\n" + + "11.22,\n" + + "''03-OCT-02'',\n" + + "11.22,\n" + + "11.22,\n" + + "TIMESTAMP''1997-01-01 09:26:50.124'',\n" + + "TIMESTAMP''1997-01-01 09:26:56.66 +02:00'',\n" + + "TIMESTAMP''1999-04-05 8:00:00 US/Pacific'',\n" + + "INTERVAL ''1'' YEAR(3),\n" + + "INTERVAL ''1'' HOUR,\n" + + "''000001F8.0001.0006'',\n" + + "''000001F8.0001.0006'',\n" + + "''char'',\n" + + "''nchar'',\n" + + "''clob'',\n" + + "''nclob''\n" + + ")"; + + private static final String SQL_QUERY_TO_INSERT_ONE_ROW_WITH_BINDING_FORMAT = + "insert into {0} values (\n" + + "'{{'binding1'}}',\n" + + "'{{'binding2'}}',\n" + + "'{{'binding3'}}',\n" + + "'{{'binding4'}}',\n" + + "'{{'binding5'}}',\n" + + "'{{'binding6'}}',\n" + + "'{{'binding7'}}',\n" + + "TO_TIMESTAMP('{{'binding8'}}', ''YYYY-MM-DD HH24:MI:SS.FF''),\n" + + "TO_TIMESTAMP('{{'binding9'}}', ''YYYY-MM-DD HH24:MI:SS.FF''),\n" + + "TO_TIMESTAMP('{{'binding10'}}', ''YYYY-MM-DD HH24:MI:SS.FF''),\n" + + "NUMTOYMINTERVAL('{{'binding11'}}', ''YEAR''),\n" + + "NUMTODSINTERVAL('{{'binding12'}}', ''HOUR''),\n" + + "'{{'binding13'}}',\n" + + "'{{'binding14'}}',\n" + + "'{{'binding15'}}',\n" + + "'{{'binding16'}}',\n" + + "'{{'binding17'}}',\n" + + "'{{'binding18'}}'\n" + + ")"; + + public static final String SELECT_TEST_WITHOUT_PREPARED_STMT_TABLE_NAME = "testSelectWithPreparedStatementWithoutAnyBinding"; + public static final String SELECT_TEST_WITH_PREPARED_STMT_TABLE_NAME = "testSelectWithPreparedStatementWithBinding"; + public static final String INSERT_TEST_WITHOUT_PREPARED_STMT_TABLE_NAME = "testInsertWithPreparedStatementWithoutAnyBinding"; + public static final String INSERT_TEST_WITH_PREPARED_STMT_TABLE_NAME = "testInsertWithPreparedStatementWithBinding"; + public static final String UPDATE_TEST_WITHOUT_PREPARED_STMT_TABLE_NAME = "testUpdateWithPreparedStatementWithoutAnyBinding"; + public static final String UPDATE_TEST_WITH_PREPARED_STMT_TABLE_NAME = "testUpdateWithPreparedStatementWithBinding"; + public static final String DELETE_TEST_WITHOUT_PREPARED_STMT_TABLE_NAME = "testDeleteWithPreparedStatementWithoutAnyBinding"; + public static final String DELETE_TEST_WITH_PREPARED_STMT_TABLE_NAME = "testDeleteWithPreparedStatementWithBinding"; + + @SuppressWarnings("rawtypes") // The type parameter for the container type is just itself and is pseudo-optional. + @Container + private static final OracleContainer oracleDB = OracleTestDBContainerManager.getOracleDBForTest(); + + private static HikariDataSource sharedConnectionPool = null; + + @BeforeAll + public static void setup() throws SQLException { + sharedConnectionPool = oraclePluginExecutor.datasourceCreate(getDefaultDatasourceConfig(oracleDB)).block(); + createTablesForTest(); + } + + public static void createTablesForTest() throws SQLException { + createTableWithName(SELECT_TEST_WITHOUT_PREPARED_STMT_TABLE_NAME); + createTableWithName(SELECT_TEST_WITH_PREPARED_STMT_TABLE_NAME); + createTableWithName(INSERT_TEST_WITHOUT_PREPARED_STMT_TABLE_NAME); + createTableWithName(INSERT_TEST_WITH_PREPARED_STMT_TABLE_NAME); + createTableWithName(UPDATE_TEST_WITHOUT_PREPARED_STMT_TABLE_NAME); + createTableWithName(UPDATE_TEST_WITH_PREPARED_STMT_TABLE_NAME); + createTableWithName(DELETE_TEST_WITHOUT_PREPARED_STMT_TABLE_NAME); + createTableWithName(DELETE_TEST_WITH_PREPARED_STMT_TABLE_NAME); + } + + private static void createTableWithName(String tableName) throws SQLException { + String sqlQueryToCreateTable = MessageFormat.format(SQL_QUERY_CREATE_TABLE_FORMAT, tableName); + runSQLQueryOnOracleTestDB(sqlQueryToCreateTable, sharedConnectionPool); + + String sqlQueryToInsertRow1 = MessageFormat.format(SQL_QUERY_TO_INSERT_ONE_ROW_FORMAT, tableName, 1); + runSQLQueryOnOracleTestDB(sqlQueryToInsertRow1, sharedConnectionPool); + + String sqlQueryToInsertRow2 = MessageFormat.format(SQL_QUERY_TO_INSERT_ONE_ROW_FORMAT, tableName, 2); + runSQLQueryOnOracleTestDB(sqlQueryToInsertRow2, sharedConnectionPool); + } + + @Test + public void testSelectQueryWithPreparedStatementWithoutAnyBinding() { + String sqlSelectQuery = MessageFormat.format("SELECT c_number FROM {0} ORDER BY c_number", + SELECT_TEST_WITHOUT_PREPARED_STMT_TABLE_NAME); + Map formData = setDataValueSafelyInFormData(null, "body", sqlSelectQuery); + ActionConfiguration actionConfig = new ActionConfiguration(); + actionConfig.setFormData(formData); + Mono executionResultMono = oraclePluginExecutor.executeParameterized(sharedConnectionPool, new ExecuteActionDTO(), + getDefaultDatasourceConfig(oracleDB), actionConfig); + String expectedResultString = "[{\"C_NUMBER\":\"1\"},{\"C_NUMBER\":\"2\"}]"; + verifyColumnValue(executionResultMono, expectedResultString); + } + + @Test + public void testQueryWorksWithSemicolonInTheEnd() { + String sqlSelectQuery = MessageFormat.format("SELECT c_number FROM {0} ORDER BY c_number;", + SELECT_TEST_WITHOUT_PREPARED_STMT_TABLE_NAME); + Map formData = setDataValueSafelyInFormData(null, "body", sqlSelectQuery); + ActionConfiguration actionConfig = new ActionConfiguration(); + actionConfig.setFormData(formData); + Mono executionResultMono = oraclePluginExecutor.executeParameterized(sharedConnectionPool, new ExecuteActionDTO(), + getDefaultDatasourceConfig(oracleDB), actionConfig); + String expectedResultString = "[{\"C_NUMBER\":\"1\"},{\"C_NUMBER\":\"2\"}]"; + verifyColumnValue(executionResultMono, expectedResultString); + } + + @Test + public void testSelectQueryWithPreparedStatementWithBinding() { + String sqlSelectQuery = MessageFormat.format("SELECT c_number FROM {0} WHERE " + + "c_varchar2='{{'binding1'}}' ORDER BY c_number DESC", SELECT_TEST_WITH_PREPARED_STMT_TABLE_NAME); + Map formData = setDataValueSafelyInFormData(null, "body", sqlSelectQuery); + ActionConfiguration actionConfig = new ActionConfiguration(); + actionConfig.setFormData(formData); + + LinkedHashMap bindingNameToValueAndDataTypeMap = new LinkedHashMap<>(); + bindingNameToValueAndDataTypeMap.put("binding1", List.of("varchar2", ClientDataType.STRING)); + ExecuteActionDTO executeActionDTO = + getExecuteDTOForTestWithBindingAndValueAndDataType(bindingNameToValueAndDataTypeMap); + + Mono executionResultMono = + oraclePluginExecutor.executeParameterized(sharedConnectionPool, executeActionDTO, + getDefaultDatasourceConfig(oracleDB), actionConfig); + String expectedResultString = "[{\"C_NUMBER\":\"2\"},{\"C_NUMBER\":\"1\"}]"; + verifyColumnValue(executionResultMono, expectedResultString); + } + + @Test + public void testInsertQueryReturnValueWithPreparedStatementWithoutAnyBinding() { + String sqlInsertQuery = MessageFormat.format(SQL_QUERY_TO_INSERT_ONE_ROW_FORMAT, + INSERT_TEST_WITHOUT_PREPARED_STMT_TABLE_NAME, 3); + Map insertQueryFormData = setDataValueSafelyInFormData(null, "body", sqlInsertQuery); + ActionConfiguration insertQueryActionConfig = new ActionConfiguration(); + insertQueryActionConfig.setFormData(insertQueryFormData); + Mono insertQueryExecutionResultMono = + oraclePluginExecutor.executeParameterized(sharedConnectionPool, new ExecuteActionDTO(), + getDefaultDatasourceConfig(oracleDB), insertQueryActionConfig); + String insertQueryExpectedResultString = "[{\"affectedRows\":1}]"; + verifyColumnValue(insertQueryExecutionResultMono, insertQueryExpectedResultString); + } + + @Test + public void testInsertQueryVerifyNewRowAddedWithPreparedStatementWithoutAnyBinding() { + String sqlInsertQuery = MessageFormat.format(SQL_QUERY_TO_INSERT_ONE_ROW_FORMAT, + INSERT_TEST_WITHOUT_PREPARED_STMT_TABLE_NAME, 4); + Map insertQueryFormData = setDataValueSafelyInFormData(null, "body", sqlInsertQuery); + ActionConfiguration insertQueryActionConfig = new ActionConfiguration(); + insertQueryActionConfig.setFormData(insertQueryFormData); + oraclePluginExecutor.executeParameterized(sharedConnectionPool, new ExecuteActionDTO(), + getDefaultDatasourceConfig(oracleDB), insertQueryActionConfig).block(); + + String sqlSelectQuery = MessageFormat.format("SELECT * FROM {0} WHERE c_number=4", + INSERT_TEST_WITHOUT_PREPARED_STMT_TABLE_NAME); + Map selectQueryFormData = setDataValueSafelyInFormData(null, "body", sqlSelectQuery); + ActionConfiguration selectQueryActionConfig = new ActionConfiguration(); + selectQueryActionConfig.setFormData(selectQueryFormData); + Mono selectQueryExecutionResultMono = + oraclePluginExecutor.executeParameterized(sharedConnectionPool, new ExecuteActionDTO(), + getDefaultDatasourceConfig(oracleDB), selectQueryActionConfig); + String selectQueryExpectedResultString = "[{\"C_VARCHAR2\":\"varchar2\",\"C_NVARCHAR2\":\"nvarchar2\"," + + "\"C_NUMBER\":\"4\",\"C_FLOAT\":\"11.22\",\"C_DATE\":\"2002-10-03\",\"C_BINARY_FLOAT\":\"11.22\"," + + "\"C_BINARY_DOUBLE\":\"11.22\",\"C_TIMESTAMP\":\"1997-01-01T09:26:50.124Z\"," + + "\"C_TIMESTAMP_TZ\":\"1997-01-01T09:26:56.66+02:00\",\"C_TIMESTAMP_LTZ\":\"1999-04-05T15:00:00Z\"," + + "\"C_INTERVAL_YEAR\":\"1-0\",\"C_INTERVAL_DAY\":\"0 1:0:0.0\",\"C_ROWID\":\"AAAAAAAAGAAAAH4AAB\"," + + "\"C_UROWID\":\"000001F8.0001.0006\",\"C_CHAR\":\"char \",\"C_NCHAR\":\"nchar " + + " \",\"C_CLOB\":\"clob\",\"C_NCLOB\":\"nclob\"}]"; + verifyColumnValue(selectQueryExecutionResultMono, selectQueryExpectedResultString); + } + + @Test + public void testInsertQueryReturnValueWithPreparedStatementWithBinding() { + String sqlInsertQuery = MessageFormat.format(SQL_QUERY_TO_INSERT_ONE_ROW_WITH_BINDING_FORMAT, + INSERT_TEST_WITH_PREPARED_STMT_TABLE_NAME); + Map insertQueryFormData = setDataValueSafelyInFormData(null, "body", sqlInsertQuery); + ActionConfiguration insertQueryActionConfig = new ActionConfiguration(); + insertQueryActionConfig.setFormData(insertQueryFormData); + + LinkedHashMap bindingNameToValueAndDataTypeMap = new LinkedHashMap<>(); + bindingNameToValueAndDataTypeMap.put("binding1", List.of("varchar2", ClientDataType.STRING)); + bindingNameToValueAndDataTypeMap.put("binding2", List.of("nvarchar2", ClientDataType.STRING)); + bindingNameToValueAndDataTypeMap.put("binding3", List.of("3", ClientDataType.NUMBER)); + bindingNameToValueAndDataTypeMap.put("binding4", List.of("11.22", ClientDataType.NUMBER)); + bindingNameToValueAndDataTypeMap.put("binding5", List.of("03-OCT-02", ClientDataType.STRING)); + bindingNameToValueAndDataTypeMap.put("binding6", List.of("11.22", ClientDataType.NUMBER)); + bindingNameToValueAndDataTypeMap.put("binding7", List.of("11.22", ClientDataType.NUMBER)); + bindingNameToValueAndDataTypeMap.put("binding8", List.of("1997-01-01 09:26:50.124", ClientDataType.STRING)); + bindingNameToValueAndDataTypeMap.put("binding9", List.of("1997-01-01 09:26:50.124", ClientDataType.STRING)); + bindingNameToValueAndDataTypeMap.put("binding10", List.of("1997-01-01 09:26:50.124", ClientDataType.STRING)); + bindingNameToValueAndDataTypeMap.put("binding11", List.of("1", ClientDataType.NUMBER)); + bindingNameToValueAndDataTypeMap.put("binding12", List.of("1", ClientDataType.NUMBER)); + bindingNameToValueAndDataTypeMap.put("binding13", List.of("000001F8.0001.0006", ClientDataType.STRING)); + bindingNameToValueAndDataTypeMap.put("binding14", List.of("000001F8.0001.0006", ClientDataType.STRING)); + bindingNameToValueAndDataTypeMap.put("binding15", List.of("char", ClientDataType.STRING)); + bindingNameToValueAndDataTypeMap.put("binding16", List.of("nchar", ClientDataType.STRING)); + bindingNameToValueAndDataTypeMap.put("binding17", List.of("clob", ClientDataType.STRING)); + bindingNameToValueAndDataTypeMap.put("binding18", List.of("nclob", ClientDataType.STRING)); + + ExecuteActionDTO executeActionDTO = + getExecuteDTOForTestWithBindingAndValueAndDataType(bindingNameToValueAndDataTypeMap); + + Mono insertQueryExecutionResultMono = + oraclePluginExecutor.executeParameterized(sharedConnectionPool, executeActionDTO, + getDefaultDatasourceConfig(oracleDB), insertQueryActionConfig); + String insertQueryExpectedResultString = "[{\"affectedRows\":1}]"; + verifyColumnValue(insertQueryExecutionResultMono, insertQueryExpectedResultString); + } + + @Test + public void testInsertQueryVerifyNewRowAddedWithPreparedStatementWithBinding() { + String sqlInsertQuery = MessageFormat.format(SQL_QUERY_TO_INSERT_ONE_ROW_WITH_BINDING_FORMAT, + INSERT_TEST_WITH_PREPARED_STMT_TABLE_NAME); + Map insertQueryFormData = setDataValueSafelyInFormData(null, "body", sqlInsertQuery); + ActionConfiguration insertQueryActionConfig = new ActionConfiguration(); + insertQueryActionConfig.setFormData(insertQueryFormData); + + LinkedHashMap bindingNameToValueAndDataTypeMap = new LinkedHashMap<>(); + bindingNameToValueAndDataTypeMap.put("binding1", List.of("varchar2", ClientDataType.STRING)); + bindingNameToValueAndDataTypeMap.put("binding2", List.of("nvarchar2", ClientDataType.STRING)); + bindingNameToValueAndDataTypeMap.put("binding3", List.of("5", ClientDataType.NUMBER)); + bindingNameToValueAndDataTypeMap.put("binding4", List.of("11.22", ClientDataType.NUMBER)); + bindingNameToValueAndDataTypeMap.put("binding5", List.of("03-OCT-02", ClientDataType.STRING)); + bindingNameToValueAndDataTypeMap.put("binding6", List.of("11.22", ClientDataType.NUMBER)); + bindingNameToValueAndDataTypeMap.put("binding7", List.of("11.22", ClientDataType.NUMBER)); + bindingNameToValueAndDataTypeMap.put("binding8", List.of("1997-01-01 09:26:50.124", ClientDataType.STRING)); + bindingNameToValueAndDataTypeMap.put("binding9", List.of("1997-01-01 09:26:50.124", ClientDataType.STRING)); + bindingNameToValueAndDataTypeMap.put("binding10", List.of("1997-01-01 09:26:50.124", ClientDataType.STRING)); + bindingNameToValueAndDataTypeMap.put("binding11", List.of("1", ClientDataType.NUMBER)); + bindingNameToValueAndDataTypeMap.put("binding12", List.of("1", ClientDataType.NUMBER)); + bindingNameToValueAndDataTypeMap.put("binding13", List.of("000001F8.0001.0006", ClientDataType.STRING)); + bindingNameToValueAndDataTypeMap.put("binding14", List.of("000001F8.0001.0006", ClientDataType.STRING)); + bindingNameToValueAndDataTypeMap.put("binding15", List.of("char", ClientDataType.STRING)); + bindingNameToValueAndDataTypeMap.put("binding16", List.of("nchar", ClientDataType.STRING)); + bindingNameToValueAndDataTypeMap.put("binding17", List.of("clob", ClientDataType.STRING)); + bindingNameToValueAndDataTypeMap.put("binding18", List.of("nclob", ClientDataType.STRING)); + + ExecuteActionDTO executeActionDTO = + getExecuteDTOForTestWithBindingAndValueAndDataType(bindingNameToValueAndDataTypeMap); + oraclePluginExecutor.executeParameterized(sharedConnectionPool, executeActionDTO, + getDefaultDatasourceConfig(oracleDB), insertQueryActionConfig).block(); + + String sqlSelectQuery = MessageFormat.format("SELECT c_varchar2, c_nvarchar2, c_number, c_float, c_date, " + + "c_binary_float, c_binary_double, c_timestamp, c_interval_year, " + + "c_interval_day, c_rowid, c_urowid, c_char, c_nchar, c_clob, c_nclob FROM {0} WHERE c_number=5", + INSERT_TEST_WITH_PREPARED_STMT_TABLE_NAME); + Map selectQueryFormData = setDataValueSafelyInFormData(null, "body", sqlSelectQuery); + ActionConfiguration selectQueryActionConfig = new ActionConfiguration(); + selectQueryActionConfig.setFormData(selectQueryFormData); + Mono selectQueryExecutionResultMono = + oraclePluginExecutor.executeParameterized(sharedConnectionPool, new ExecuteActionDTO(), + getDefaultDatasourceConfig(oracleDB), selectQueryActionConfig); + String selectQueryExpectedResultString = "[{\"C_VARCHAR2\":\"varchar2\",\"C_NVARCHAR2\":\"nvarchar2\"," + + "\"C_NUMBER\":\"5\",\"C_FLOAT\":\"11.22\",\"C_DATE\":\"2002-10-03\",\"C_BINARY_FLOAT\":\"11.22\"," + + "\"C_BINARY_DOUBLE\":\"11.22\",\"C_TIMESTAMP\":\"1997-01-01T09:26:50.124Z\"," + + "\"C_INTERVAL_YEAR\":\"1-0\",\"C_INTERVAL_DAY\":\"0 1:0:0.0\"," + + "\"C_ROWID\":\"AAAAAAAAGAAAAH4AAB\",\"C_UROWID\":\"000001F8.0001.0006\",\"C_CHAR\":\"char " + + " \",\"C_NCHAR\":\"nchar \",\"C_CLOB\":\"clob\",\"C_NCLOB\":\"nclob\"}]"; + verifyColumnValue(selectQueryExecutionResultMono, selectQueryExpectedResultString); + } + + private void verifyColumnValue(Mono executionResultMono, String expectedResult) { + StepVerifier.create(executionResultMono) + .assertNext(actionExecutionResult -> { + assertTrue(actionExecutionResult.getIsExecutionSuccess(), actionExecutionResult.getBody().toString()); + if (expectedResult != null) { + assertEquals(expectedResult, actionExecutionResult.getBody().toString()); + } + }) + .verifyComplete(); + } +} diff --git a/app/server/appsmith-plugins/oraclePlugin/src/test/java/com/external/plugins/OracleGetDBSchemaTest.java b/app/server/appsmith-plugins/oraclePlugin/src/test/java/com/external/plugins/OracleGetDBSchemaTest.java new file mode 100644 index 0000000000..86936a3091 --- /dev/null +++ b/app/server/appsmith-plugins/oraclePlugin/src/test/java/com/external/plugins/OracleGetDBSchemaTest.java @@ -0,0 +1,163 @@ +package com.external.plugins; + +import com.appsmith.external.models.DatasourceStructure; +import com.zaxxer.hikari.HikariDataSource; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.OracleContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.sql.SQLException; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static com.appsmith.external.helpers.PluginUtils.STRING_TYPE; +import static com.appsmith.external.helpers.PluginUtils.getDataValueSafelyFromFormData; +import static com.external.plugins.OracleTestDBContainerManager.getDefaultDatasourceConfig; +import static com.external.plugins.OracleTestDBContainerManager.oraclePluginExecutor; +import static com.external.plugins.OracleTestDBContainerManager.runSQLQueryOnOracleTestDB; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Testcontainers +public class OracleGetDBSchemaTest { + public static final String SQL_QUERY_TO_CREATE_TABLE_WITH_PRIMARY_KEY = + "CREATE TABLE supplier\n" + + "( supplier_id numeric(10) not null,\n" + + " supplier_name varchar2(50) not null,\n" + + " contact_name varchar2(50),\n" + + " CONSTRAINT supplier_pk PRIMARY KEY (supplier_id)\n" + + ")"; + + public static final String SQL_QUERY_TO_CREATE_TABLE_WITH_FOREIGN_KEY = + "CREATE TABLE products\n" + + "( product_id numeric(10) not null,\n" + + " supplier_id numeric(10) not null,\n" + + " CONSTRAINT fk_supplier\n" + + " FOREIGN KEY (supplier_id)\n" + + " REFERENCES supplier(supplier_id)\n" + + ")"; + + @SuppressWarnings("rawtypes") // The type parameter for the container type is just itself and is pseudo-optional. + @Container + private static final OracleContainer oracleDB = OracleTestDBContainerManager.getOracleDBForTest(); + + private static HikariDataSource sharedConnectionPool = null; + + @BeforeAll + public static void setup() throws SQLException { + sharedConnectionPool = oraclePluginExecutor.datasourceCreate(getDefaultDatasourceConfig(oracleDB)).block(); + createTablesForTest(); + } + + private static void createTablesForTest() throws SQLException { + runSQLQueryOnOracleTestDB(SQL_QUERY_TO_CREATE_TABLE_WITH_PRIMARY_KEY, sharedConnectionPool); + runSQLQueryOnOracleTestDB(SQL_QUERY_TO_CREATE_TABLE_WITH_FOREIGN_KEY, sharedConnectionPool); + } + + @Test + public void testDBSchemaShowsAllTables() { + Mono datasourceStructureMono = + oraclePluginExecutor.getStructure(sharedConnectionPool, + getDefaultDatasourceConfig(oracleDB)); + + StepVerifier.create(datasourceStructureMono) + .assertNext(datasourceStructure -> { + Set setOfAllTableNames = datasourceStructure.getTables().stream() + .map(DatasourceStructure.Table::getName) + .map(String::toLowerCase) + .collect(Collectors.toSet()); + + assertTrue(setOfAllTableNames.equals(Set.of("supplier","products")), setOfAllTableNames.toString()); + }) + .verifyComplete(); + } + + @Test + public void testDBSchemaShowsAllColumnsAndTypesInATable() { + Mono datasourceStructureMono = + oraclePluginExecutor.getStructure(sharedConnectionPool, + getDefaultDatasourceConfig(oracleDB)); + + StepVerifier.create(datasourceStructureMono) + .assertNext(datasourceStructure -> { + DatasourceStructure.Table supplierTable = datasourceStructure.getTables().stream() + .filter(table -> "supplier".equalsIgnoreCase(table.getName())) + .findFirst() + .get(); + + assertTrue(supplierTable != null, "supplier table not found in DB schema"); + + Set allColumnNames = supplierTable.getColumns().stream() + .map(DatasourceStructure.Column::getName) + .map(String::toLowerCase) + .collect(Collectors.toSet()); + Set expectedColumnNames = Set.of("supplier_id", "supplier_name", "contact_name"); + assertEquals(expectedColumnNames, allColumnNames, allColumnNames.toString()); + + supplierTable.getColumns().stream() + .forEach(column -> { + String columnName = column.getName().toLowerCase(); + String columnType = column.getType().toLowerCase(); + String expectedColumnType = null; + + if ("supplier_id".equals(columnName)) { + expectedColumnType = "number"; + } + else { + expectedColumnType = "varchar2"; + } + + assertEquals(expectedColumnType, columnType, columnType); + }); + }) + .verifyComplete(); + } + + @Test + public void testDynamicSqlTemplateQueriesForATable() { + Mono datasourceStructureMono = + oraclePluginExecutor.getStructure(sharedConnectionPool, + getDefaultDatasourceConfig(oracleDB)); + + StepVerifier.create(datasourceStructureMono) + .assertNext(datasourceStructure -> { + DatasourceStructure.Table supplierTable = datasourceStructure.getTables().stream() + .filter(table -> "supplier".equalsIgnoreCase(table.getName())) + .findFirst() + .get(); + + assertTrue(supplierTable != null, "supplier table not found in DB schema"); + + supplierTable.getTemplates().stream() + .filter(template -> "select".equalsIgnoreCase(template.getTitle()) || "delete".equalsIgnoreCase(template.getTitle())) + .forEach(template -> { + /** + * Not sure how to test query templates for insert and update queries as these + * queries include column names in an order that is not fixed. Hence, skipping testing + * them for now. + */ + + String expectedSelectQueryTemplate = null; + if ("select".equalsIgnoreCase(template.getTitle())) { + expectedSelectQueryTemplate = "select * from supplier where rownum < 10"; + } + else if ("delete".equalsIgnoreCase(template.getTitle())) { + expectedSelectQueryTemplate = "delete from supplier where 1=0 -- specify a valid" + + " condition here. removing the condition may delete everything in the " + + "table!"; + } + + String templateQuery = + getDataValueSafelyFromFormData((Map) template.getConfiguration(), "body", STRING_TYPE); + assertEquals(expectedSelectQueryTemplate, templateQuery.toLowerCase(), + templateQuery.toLowerCase()); + }); + }) + .verifyComplete(); + } +} diff --git a/app/server/appsmith-plugins/oraclePlugin/src/test/java/com/external/plugins/OraclePluginConnectionTest.java b/app/server/appsmith-plugins/oraclePlugin/src/test/java/com/external/plugins/OraclePluginConnectionTest.java new file mode 100755 index 0000000000..30c8a5d87a --- /dev/null +++ b/app/server/appsmith-plugins/oraclePlugin/src/test/java/com/external/plugins/OraclePluginConnectionTest.java @@ -0,0 +1,81 @@ +package com.external.plugins; + + +import com.appsmith.external.models.DBAuth; +import com.appsmith.external.models.DatasourceConfiguration; +import com.appsmith.external.models.DatasourceTestResult; +import com.external.plugins.exceptions.OraclePluginError; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.OracleContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.util.Arrays; +import java.util.stream.Collectors; + +import static com.external.plugins.OracleTestDBContainerManager.getDefaultDatasourceConfig; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Testcontainers +public class OraclePluginConnectionTest { + + OraclePlugin.OraclePluginExecutor oraclePluginExecutor = new OraclePlugin.OraclePluginExecutor(); + + @SuppressWarnings("rawtypes") // The type parameter for the container type is just itself and is pseudo-optional. + @Container + private static final OracleContainer oracleDB = OracleTestDBContainerManager.getOracleDBForTest(); + + @Test + public void testDatasourceConnectionTestPassWithValidConfig() { + Mono testDsResultMono = + oraclePluginExecutor.testDatasource(getDefaultDatasourceConfig(oracleDB)); + StepVerifier.create(testDsResultMono) + .assertNext(testResult -> { + assertEquals(0, testResult.getInvalids().size()); + }) + .verifyComplete(); + } + + @Test + public void testDatasourceConnectionTestFailWithInvalidPassword() { + DatasourceConfiguration invalidDsConfig = getDefaultDatasourceConfig(oracleDB); + ((DBAuth)invalidDsConfig.getAuthentication()).setPassword("invalid_password"); + + Mono testDsResultMono = + oraclePluginExecutor.testDatasource(invalidDsConfig); + StepVerifier.create(testDsResultMono) + .assertNext(testResult -> { + assertNotEquals(0, testResult.getInvalids().size()); + String expectedError = "Failed to initialize pool: ORA-01017: invalid username/password; logon " + + "denied"; + boolean isExpectedErrorReceived = testResult.getInvalids().stream() + .anyMatch(errorString -> expectedError.equals(errorString.trim())); + assertTrue(isExpectedErrorReceived); + }) + .verifyComplete(); + } + + @Test + public void testDatasourceConnectionTestFailWithInvalidUsername() { + DatasourceConfiguration invalidDsConfig = getDefaultDatasourceConfig(oracleDB); + ((DBAuth)invalidDsConfig.getAuthentication()).setUsername("invalid_username"); + + Mono testDsResultMono = + oraclePluginExecutor.testDatasource(invalidDsConfig); + StepVerifier.create(testDsResultMono) + .assertNext(testResult -> { + assertNotEquals(0, testResult.getInvalids().size()); + String expectedError = "Failed to initialize pool: ORA-01017: invalid username/password; logon " + + "denied"; + boolean isExpectedErrorReceived = testResult.getInvalids().stream() + .anyMatch(errorString -> expectedError.equals(errorString.trim())); + assertTrue(isExpectedErrorReceived); + }) + .verifyComplete(); + } + +} diff --git a/app/server/appsmith-plugins/oraclePlugin/src/test/java/com/external/plugins/OraclePluginDatasourceValidityErrorsTest.java b/app/server/appsmith-plugins/oraclePlugin/src/test/java/com/external/plugins/OraclePluginDatasourceValidityErrorsTest.java new file mode 100644 index 0000000000..d9201f35ed --- /dev/null +++ b/app/server/appsmith-plugins/oraclePlugin/src/test/java/com/external/plugins/OraclePluginDatasourceValidityErrorsTest.java @@ -0,0 +1,64 @@ +package com.external.plugins; + +import com.appsmith.external.models.DBAuth; +import com.appsmith.external.models.DatasourceConfiguration; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Set; + +import static com.external.plugins.OracleTestDBContainerManager.getDefaultDatasourceConfig; +import static com.external.plugins.exceptions.OracleErrorMessages.DS_MISSING_ENDPOINT_ERROR_MSG; +import static com.external.plugins.exceptions.OracleErrorMessages.DS_MISSING_HOSTNAME_ERROR_MSG; +import static com.external.plugins.exceptions.OracleErrorMessages.DS_MISSING_PASSWORD_ERROR_MSG; +import static com.external.plugins.exceptions.OracleErrorMessages.DS_MISSING_USERNAME_ERROR_MSG; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class OraclePluginDatasourceValidityErrorsTest { + + OraclePlugin.OraclePluginExecutor oraclePluginExecutor = new OraclePlugin.OraclePluginExecutor(); + + @Test + public void testErrorOnMissingUsername() { + DatasourceConfiguration dsConfigWithMissingUsername = getDefaultDatasourceConfig(null); + ((DBAuth)dsConfigWithMissingUsername.getAuthentication()).setUsername(""); + + Set dsValidateResult = oraclePluginExecutor.validateDatasource(dsConfigWithMissingUsername); + boolean isExpectedErrorReceived = dsValidateResult.stream() + .anyMatch(errorString -> DS_MISSING_USERNAME_ERROR_MSG.equals(errorString.trim())); + assertTrue(isExpectedErrorReceived); + } + + @Test + public void testErrorOnMissingPassword() { + DatasourceConfiguration dsConfigWithMissingPassword = getDefaultDatasourceConfig(null); + ((DBAuth)dsConfigWithMissingPassword.getAuthentication()).setPassword(""); + + Set dsValidateResult = oraclePluginExecutor.validateDatasource(dsConfigWithMissingPassword); + boolean isExpectedErrorReceived = dsValidateResult.stream() + .anyMatch(errorString -> DS_MISSING_PASSWORD_ERROR_MSG.equals(errorString.trim())); + assertTrue(isExpectedErrorReceived); + } + + @Test + public void testErrorOnMissingEndpoint() { + DatasourceConfiguration dsConfigWithMissingEndpoint = getDefaultDatasourceConfig(null); + dsConfigWithMissingEndpoint.setEndpoints(new ArrayList<>()); + + Set dsValidateResult = oraclePluginExecutor.validateDatasource(dsConfigWithMissingEndpoint); + boolean isExpectedErrorReceived = dsValidateResult.stream() + .anyMatch(errorString -> DS_MISSING_ENDPOINT_ERROR_MSG.equals(errorString.trim())); + assertTrue(isExpectedErrorReceived); + } + + @Test + public void testErrorOnMissingHost() { + DatasourceConfiguration dsConfigWithMissingHost = getDefaultDatasourceConfig(null); + dsConfigWithMissingHost.getEndpoints().get(0).setHost(""); + + Set dsValidateResult = oraclePluginExecutor.validateDatasource(dsConfigWithMissingHost); + boolean isExpectedErrorReceived = dsValidateResult.stream() + .anyMatch(errorString -> DS_MISSING_HOSTNAME_ERROR_MSG.equals(errorString.trim())); + assertTrue(isExpectedErrorReceived); + } +} diff --git a/app/server/appsmith-plugins/oraclePlugin/src/test/java/com/external/plugins/OraclePluginTest.java b/app/server/appsmith-plugins/oraclePlugin/src/test/java/com/external/plugins/OraclePluginErrorsTest.java old mode 100755 new mode 100644 similarity index 82% rename from app/server/appsmith-plugins/oraclePlugin/src/test/java/com/external/plugins/OraclePluginTest.java rename to app/server/appsmith-plugins/oraclePlugin/src/test/java/com/external/plugins/OraclePluginErrorsTest.java index 1cb732ac9d..37b9df1e47 --- a/app/server/appsmith-plugins/oraclePlugin/src/test/java/com/external/plugins/OraclePluginTest.java +++ b/app/server/appsmith-plugins/oraclePlugin/src/test/java/com/external/plugins/OraclePluginErrorsTest.java @@ -1,17 +1,12 @@ package com.external.plugins; - import com.external.plugins.exceptions.OraclePluginError; -import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; -import org.testcontainers.junit.jupiter.Testcontainers; import java.util.Arrays; import java.util.stream.Collectors; -@Slf4j -@Testcontainers -public class OraclePluginTest { +public class OraclePluginErrorsTest { @Test public void verifyUniquenessOfOraclePluginErrorCode() { assert (Arrays.stream(OraclePluginError.values()).map(OraclePluginError::getAppErrorCode).distinct().count() == OraclePluginError.values().length); @@ -19,6 +14,5 @@ public class OraclePluginTest { assert (Arrays.stream(OraclePluginError.values()).map(OraclePluginError::getAppErrorCode) .filter(appErrorCode -> appErrorCode.length() != 11 || !appErrorCode.startsWith("PE-ORC")) .collect(Collectors.toList()).size() == 0); - } } diff --git a/app/server/appsmith-plugins/oraclePlugin/src/test/java/com/external/plugins/OracleTestDBContainerManager.java b/app/server/appsmith-plugins/oraclePlugin/src/test/java/com/external/plugins/OracleTestDBContainerManager.java new file mode 100644 index 0000000000..9cb628d848 --- /dev/null +++ b/app/server/appsmith-plugins/oraclePlugin/src/test/java/com/external/plugins/OracleTestDBContainerManager.java @@ -0,0 +1,57 @@ +package com.external.plugins; + +import com.appsmith.external.models.Connection; +import com.appsmith.external.models.DBAuth; +import com.appsmith.external.models.DatasourceConfiguration; +import com.appsmith.external.models.Endpoint; +import com.appsmith.external.models.SSLDetails; +import com.zaxxer.hikari.HikariDataSource; +import org.testcontainers.containers.OracleContainer; + +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; + +import static com.external.plugins.utils.OracleDatasourceUtils.getConnectionFromConnectionPool; +import static com.external.plugins.utils.OracleExecuteUtils.closeConnectionPostExecution; + +public class OracleTestDBContainerManager { + public static final String ORACLE_USERNAME = "testUser"; + public static final String ORACLE_PASSWORD = "testPassword"; + public static final String ORACLE_DB_NAME = "testDB"; + public static final String ORACLE_DOCKER_HUB_CONTAINER = "gvenzl/oracle-xe:21-slim-faststart"; + static OraclePlugin.OraclePluginExecutor oraclePluginExecutor = new OraclePlugin.OraclePluginExecutor(); + + public static OracleContainer getOracleDBForTest() { + return new OracleContainer(ORACLE_DOCKER_HUB_CONTAINER) + .withDatabaseName(ORACLE_DB_NAME) + .withUsername(ORACLE_USERNAME) + .withPassword(ORACLE_PASSWORD); + } + + public static DatasourceConfiguration getDefaultDatasourceConfig(OracleContainer oracleDB) { + DatasourceConfiguration dsConfig = new DatasourceConfiguration(); + dsConfig.setAuthentication(new DBAuth()); + ((DBAuth)dsConfig.getAuthentication()).setUsername(OracleTestDBContainerManager.ORACLE_USERNAME); + ((DBAuth)dsConfig.getAuthentication()).setPassword(OracleTestDBContainerManager.ORACLE_PASSWORD); + ((DBAuth)dsConfig.getAuthentication()).setDatabaseName(OracleTestDBContainerManager.ORACLE_DB_NAME); + + dsConfig.setEndpoints(new ArrayList<>()); + String host = oracleDB == null ? "host" : oracleDB.getHost(); + long port = oracleDB == null ? 1521L : (long)oracleDB.getOraclePort(); + dsConfig.getEndpoints().add(new Endpoint(host, port)); + + dsConfig.setConnection(new Connection()); + dsConfig.getConnection().setSsl(new SSLDetails()); + dsConfig.getConnection().getSsl().setAuthType(SSLDetails.AuthType.DISABLE); + + return dsConfig; + } + + static void runSQLQueryOnOracleTestDB(String sqlQuery, HikariDataSource sharedConnectionPool) throws SQLException { + java.sql.Connection connectionFromPool = getConnectionFromConnectionPool(sharedConnectionPool); + Statement statement = connectionFromPool.createStatement(); + statement.execute(sqlQuery); + closeConnectionPostExecution(null, statement, null, connectionFromPool); + } +}