From 450cedcab05a69429c2c6f5730c996242501c2a6 Mon Sep 17 00:00:00 2001 From: Vipin Date: Thu, 2 Oct 2025 18:24:30 +0530 Subject: [PATCH] Test coverage by codex --- .../msg/sql/ReadFileFromResources.java | 8 +- .../ParameterMetadataExtractorTest.java | 77 ++++++++++- .../UtilityClassConstructorCoverageTest.java | 55 ++++++++ .../DatabaseConnectionExceptionTest.java | 25 ++++ .../DatabaseConnectionFactoryTest.java | 53 +++++++- .../DeleteMetadataExtractorTest.java | 22 +++- .../InsertMetadataExtractorTest.java | 16 ++- .../codegen/dbmetadata/SqlMetadataTest.java | 20 ++- .../UpdateMetadataExtractorTest.java | 116 ++++++++++++++++- .../domain/GeneratedMicroserviceTest.java | 58 +++++++++ .../domain/ProjectDirectoryStructureTest.java | 26 ++++ .../DirectoryCleanupExceptionTest.java | 25 ++++ .../MicroserviceDirectoryCleanerTest.java | 56 +++++++- .../MicroserviceProjectWriterTest.java | 121 +++++++++++++++++- .../ProjectDirectoryBuilderTest.java | 17 ++- .../ResultSetMappingGeneratorTest.java | 12 +- .../msg/codegen/util/FieldBuildersTest.java | 10 ++ .../codegen/util/ParameterBuildersTest.java | 18 +++ .../util/SqlStatementDetectorTest.java | 20 ++- .../msg/sql/ReadFileFromResourcesTest.java | 46 ++++++- 20 files changed, 785 insertions(+), 16 deletions(-) create mode 100644 src/test/java/com/jfeatures/msg/codegen/UtilityClassConstructorCoverageTest.java create mode 100644 src/test/java/com/jfeatures/msg/codegen/database/DatabaseConnectionExceptionTest.java create mode 100644 src/test/java/com/jfeatures/msg/codegen/domain/GeneratedMicroserviceTest.java create mode 100644 src/test/java/com/jfeatures/msg/codegen/domain/ProjectDirectoryStructureTest.java create mode 100644 src/test/java/com/jfeatures/msg/codegen/filesystem/DirectoryCleanupExceptionTest.java diff --git a/src/main/java/com/jfeatures/msg/sql/ReadFileFromResources.java b/src/main/java/com/jfeatures/msg/sql/ReadFileFromResources.java index 10ef5de..88b8d12 100644 --- a/src/main/java/com/jfeatures/msg/sql/ReadFileFromResources.java +++ b/src/main/java/com/jfeatures/msg/sql/ReadFileFromResources.java @@ -11,6 +11,8 @@ */ public final class ReadFileFromResources { + private static java.util.function.Supplier classLoaderSupplier = ReadFileFromResources.class::getClassLoader; + private ReadFileFromResources() { throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); } @@ -53,7 +55,7 @@ public static String readFileFromResources(String fileName, Charset charset) { // Normalize path separators for cross-platform compatibility String normalizedFileName = fileName.replace('\\', '/'); - try (var inputStream = ReadFileFromResources.class.getClassLoader().getResourceAsStream(normalizedFileName)) { + try (var inputStream = classLoaderSupplier.get().getResourceAsStream(normalizedFileName)) { // Critical fix: Check for null before calling readAllBytes() if (inputStream == null) { throw new IllegalArgumentException( @@ -101,4 +103,8 @@ private static void validateFileNameSecurity(String fileName) { } } } + + static void setClassLoaderSupplier(java.util.function.Supplier supplier) { + classLoaderSupplier = supplier != null ? supplier : ReadFileFromResources.class::getClassLoader; + } } diff --git a/src/test/java/com/jfeatures/msg/codegen/ParameterMetadataExtractorTest.java b/src/test/java/com/jfeatures/msg/codegen/ParameterMetadataExtractorTest.java index c99fb39..8aa3a08 100644 --- a/src/test/java/com/jfeatures/msg/codegen/ParameterMetadataExtractorTest.java +++ b/src/test/java/com/jfeatures/msg/codegen/ParameterMetadataExtractorTest.java @@ -1,10 +1,12 @@ package com.jfeatures.msg.codegen; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; import static org.mockito.Mockito.lenient; import com.jfeatures.msg.codegen.domain.DBColumn; +import java.lang.reflect.InvocationTargetException; import java.sql.*; import java.util.List; import java.util.stream.Stream; @@ -244,6 +246,79 @@ void testExtractParameters_UnknownSqlType_UsesFallbackType() throws SQLException assertEquals("VARCHAR", result.get(0).jdbcType()); // Default fallback } + @Test + void testExtractParameters_SqlLongerThanLimit_ThrowsException() { + StringBuilder builder = new StringBuilder("SELECT * FROM big_table WHERE "); + while (builder.length() <= 10_050) { + builder.append("column = ? AND "); + } + String longSql = builder.append("1=1").toString(); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> + extractor.extractParameters(longSql) + ); + + assertEquals("SQL query too long", exception.getMessage()); + } + + @Test + void testExtractParameters_ParameterMetadataUnavailable_FallsBackToParsing() throws SQLException { + // Given + String sql = "UPDATE customers SET name = ? WHERE id = ?"; + + setupParameterMetaData(2, new int[]{Types.VARCHAR, Types.INTEGER}); + when(preparedStatement.getParameterMetaData()).thenThrow(new SQLException("metadata not available")); + + // When + List result = extractor.extractParameters(sql); + + // Then - fallback parsing should provide at least one where column entry + assertNotNull(result); + assertEquals(2, result.size()); + assertEquals("id", result.get(0).columnName()); + assertEquals("param2", result.get(1).columnName()); + } + + @Test + void testExtractParameters_WhereClauseTooLong_ThrowsException() throws Exception { + StringBuilder whereBuilder = new StringBuilder(); + while (whereBuilder.length() <= 10_100) { + whereBuilder.append("column").append(whereBuilder.length()).append(" = ? AND "); + } + String longWhereSql = "SELECT * FROM customers WHERE " + whereBuilder.append("1=1").toString(); + + var method = ParameterMetadataExtractor.class.getDeclaredMethod("extractColumnNamesFromWhereClause", String.class, int.class); + method.setAccessible(true); + + InvocationTargetException exception = assertThrows(InvocationTargetException.class, () -> + method.invoke(extractor, longWhereSql, 1) + ); + + assertThat(exception.getCause()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("WHERE clause too long"); + } + + @Test + void testExtractParameters_ExtractWhereClauseSqlTooLong_ThrowsException() throws Exception { + StringBuilder sqlBuilder = new StringBuilder("SELECT * FROM customers WHERE "); + while (sqlBuilder.length() <= 10_050) { + sqlBuilder.append("column = ? OR "); + } + sqlBuilder.append("1=1"); + + var method = ParameterMetadataExtractor.class.getDeclaredMethod("extractWhereClause", String.class); + method.setAccessible(true); + + InvocationTargetException exception = assertThrows(InvocationTargetException.class, () -> + method.invoke(extractor, sqlBuilder.toString()) + ); + + assertThat(exception.getCause()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("SQL query too long"); + } + @Test void testExtractParameters_DatabaseConnectionFails_ThrowsSQLException() throws SQLException { // Given @@ -585,4 +660,4 @@ void testExtractParameters_NegativeParameterCount_ReturnsEmptyList() throws SQLE assertNotNull(result); assertEquals(0, result.size()); } -} \ No newline at end of file +} diff --git a/src/test/java/com/jfeatures/msg/codegen/UtilityClassConstructorCoverageTest.java b/src/test/java/com/jfeatures/msg/codegen/UtilityClassConstructorCoverageTest.java new file mode 100644 index 0000000..10a24e9 --- /dev/null +++ b/src/test/java/com/jfeatures/msg/codegen/UtilityClassConstructorCoverageTest.java @@ -0,0 +1,55 @@ +package com.jfeatures.msg.codegen; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +class UtilityClassConstructorCoverageTest { + + @ParameterizedTest + @MethodSource("utilityClasses") + void utilityConstructorsThrowUnsupportedOperationException(Class utilityClass) throws Exception { + Constructor constructor = utilityClass.getDeclaredConstructor(); + constructor.setAccessible(true); + + InvocationTargetException exception = assertThrows(InvocationTargetException.class, constructor::newInstance); + assertThat(exception.getCause()) + .isInstanceOf(UnsupportedOperationException.class); + } + + private static Stream> utilityClasses() { + return Stream.of( + com.jfeatures.msg.codegen.GenerateController.class, + com.jfeatures.msg.codegen.GenerateDAO.class, + com.jfeatures.msg.codegen.GenerateDTO.class, + com.jfeatures.msg.codegen.GenerateDatabaseConfig.class, + com.jfeatures.msg.codegen.GenerateDeleteController.class, + com.jfeatures.msg.codegen.GenerateDeleteDAO.class, + com.jfeatures.msg.codegen.GenerateDeleteDTO.class, + com.jfeatures.msg.codegen.GenerateInsertController.class, + com.jfeatures.msg.codegen.GenerateInsertDAO.class, + com.jfeatures.msg.codegen.GenerateInsertDTO.class, + com.jfeatures.msg.codegen.GenerateSpringBootApp.class, + com.jfeatures.msg.codegen.GenerateUpdateController.class, + com.jfeatures.msg.codegen.GenerateUpdateDAO.class, + com.jfeatures.msg.codegen.GenerateUpdateDTO.class, + com.jfeatures.msg.codegen.sql.SqlParameterReplacer.class, + com.jfeatures.msg.codegen.util.SqlBuilders.class, + com.jfeatures.msg.codegen.util.DtoFieldNameConverter.class, + com.jfeatures.msg.codegen.util.JavaPackageNameBuilder.class, + com.jfeatures.msg.codegen.util.JavaPoetClassNameBuilder.class, + com.jfeatures.msg.codegen.util.JavaPoetTypeNameBuilder.class, + com.jfeatures.msg.codegen.jdbc.JdbcMethodSelector.class, + com.jfeatures.msg.codegen.mapping.ResultSetMappingGenerator.class, + com.jfeatures.msg.codegen.util.SqlStatementDetector.class, + com.jfeatures.msg.sql.ReadFileFromResources.class, + com.jfeatures.msg.codegen.util.ParameterBuilders.class, + com.jfeatures.msg.codegen.util.FieldBuilders.class + ); + } +} diff --git a/src/test/java/com/jfeatures/msg/codegen/database/DatabaseConnectionExceptionTest.java b/src/test/java/com/jfeatures/msg/codegen/database/DatabaseConnectionExceptionTest.java new file mode 100644 index 0000000..aee4c88 --- /dev/null +++ b/src/test/java/com/jfeatures/msg/codegen/database/DatabaseConnectionExceptionTest.java @@ -0,0 +1,25 @@ +package com.jfeatures.msg.codegen.database; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +class DatabaseConnectionExceptionTest { + + @Test + void constructorsPreserveMessage() { + DatabaseConnectionException exception = new DatabaseConnectionException("failure"); + assertThat(exception) + .hasMessage("failure") + .hasNoCause(); + } + + @Test + void constructorsPreserveCause() { + Throwable cause = new IllegalStateException("root"); + DatabaseConnectionException exception = new DatabaseConnectionException("failure", cause); + assertThat(exception) + .hasMessage("failure") + .hasCause(cause); + } +} diff --git a/src/test/java/com/jfeatures/msg/codegen/database/DatabaseConnectionFactoryTest.java b/src/test/java/com/jfeatures/msg/codegen/database/DatabaseConnectionFactoryTest.java index 1fcf0cc..ee53b02 100644 --- a/src/test/java/com/jfeatures/msg/codegen/database/DatabaseConnectionFactoryTest.java +++ b/src/test/java/com/jfeatures/msg/codegen/database/DatabaseConnectionFactoryTest.java @@ -1,11 +1,20 @@ package com.jfeatures.msg.codegen.database; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; import com.jfeatures.msg.codegen.domain.DatabaseConnection; +import java.sql.SQLException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.mockito.MockedConstruction; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import com.jfeatures.msg.config.DataSourceConfig; +import com.jfeatures.msg.config.JdbcTemplateConfig; +import com.jfeatures.msg.config.NamedParameterJdbcTemplateConfig; class DatabaseConnectionFactoryTest { @@ -70,6 +79,48 @@ void testCreateDatabaseConnection_ErrorHandling() { } } + @Test + void testCreateDatabaseConnection_RuntimeExceptionWrappedAsDatabaseConnectionException() { + try (MockedConstruction dataSourceConfigConstruction = mockConstruction(DataSourceConfig.class, (mock, context) -> { + javax.sql.DataSource dataSource = mock(javax.sql.DataSource.class); + when(mock.dataSource()).thenReturn(dataSource); + }); + MockedConstruction jdbcTemplateConstruction = mockConstruction(JdbcTemplateConfig.class, (mock, context) -> { + when(mock.jdbcTemplate(any(javax.sql.DataSource.class))).thenThrow(new RuntimeException("boom")); + }); + MockedConstruction namedConstruction = mockConstruction(NamedParameterJdbcTemplateConfig.class)) { + + DatabaseConnectionFactory factory = new DatabaseConnectionFactory(); + DatabaseConnectionException exception = assertThrows(DatabaseConnectionException.class, factory::createDatabaseConnection); + assertThat(exception) + .hasMessage("Failed to create database connection due to configuration error") + .hasCauseInstanceOf(RuntimeException.class); + } + } + + @Test + void testCreateDatabaseConnection_CheckedExceptionWrappedAsDatabaseConnectionException() { + javax.sql.DataSource dataSource = mock(javax.sql.DataSource.class); + JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class); + + try (MockedConstruction dataSourceConfigConstruction = mockConstruction(DataSourceConfig.class, (mock, context) -> { + when(mock.dataSource()).thenReturn(dataSource); + }); + MockedConstruction jdbcTemplateConstruction = mockConstruction(JdbcTemplateConfig.class, (mock, context) -> { + when(mock.jdbcTemplate(dataSource)).thenReturn(jdbcTemplate); + }); + MockedConstruction namedConstruction = mockConstruction(NamedParameterJdbcTemplateConfig.class, (mock, context) -> { + when(mock.namedParameterJdbcTemplate(dataSource)).thenThrow(new SQLException("failure")); + })) { + + DatabaseConnectionFactory factory = new DatabaseConnectionFactory(); + DatabaseConnectionException exception = assertThrows(DatabaseConnectionException.class, factory::createDatabaseConnection); + assertThat(exception) + .hasMessage("Failed to create database connection due to unexpected error") + .hasCauseInstanceOf(SQLException.class); + } + } + @Test void testCreateDatabaseConnection_LoggingBehavior() { // Test that the factory logs appropriate messages @@ -163,4 +214,4 @@ void testCreateDatabaseConnection_ComponentIntegration() { assertNotNull(e.getMessage(), "Failure should have meaningful message"); } } -} \ No newline at end of file +} diff --git a/src/test/java/com/jfeatures/msg/codegen/dbmetadata/DeleteMetadataExtractorTest.java b/src/test/java/com/jfeatures/msg/codegen/dbmetadata/DeleteMetadataExtractorTest.java index 19486df..d310c9a 100644 --- a/src/test/java/com/jfeatures/msg/codegen/dbmetadata/DeleteMetadataExtractorTest.java +++ b/src/test/java/com/jfeatures/msg/codegen/dbmetadata/DeleteMetadataExtractorTest.java @@ -93,6 +93,26 @@ void testExtractDeleteMetadata_InvalidSqlStatement_ThrowsException() { assertEquals("SQL is not a DELETE statement", exception.getMessage()); } + + @Test + void testExtractDeleteMetadata_NullSql_ThrowsException() { + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> extractor.extractDeleteMetadata(null) + ); + + assertEquals("SQL is not a DELETE statement", exception.getMessage()); + } + + @Test + void testExtractDeleteMetadata_BlankSql_ThrowsException() { + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> extractor.extractDeleteMetadata(" ") + ); + + assertEquals("SQL is not a DELETE statement", exception.getMessage()); + } @Test void testExtractDeleteMetadata_UnparsableSql_ThrowsJSQLParserException() { @@ -346,4 +366,4 @@ private void setupColumnMetadata() throws SQLException { when(columnsResultSet.getInt("NULLABLE")) .thenReturn(0, 1); } -} \ No newline at end of file +} diff --git a/src/test/java/com/jfeatures/msg/codegen/dbmetadata/InsertMetadataExtractorTest.java b/src/test/java/com/jfeatures/msg/codegen/dbmetadata/InsertMetadataExtractorTest.java index be69b12..116c571 100644 --- a/src/test/java/com/jfeatures/msg/codegen/dbmetadata/InsertMetadataExtractorTest.java +++ b/src/test/java/com/jfeatures/msg/codegen/dbmetadata/InsertMetadataExtractorTest.java @@ -85,6 +85,20 @@ void shouldThrowExceptionForNonInsertStatement() { .hasMessageContaining("SQL is not an INSERT statement"); } + @Test + void shouldThrowExceptionForNullSql() { + assertThatThrownBy(() -> extractor.extractInsertMetadata(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("SQL is not an INSERT statement"); + } + + @Test + void shouldThrowExceptionForBlankSql() { + assertThatThrownBy(() -> extractor.extractInsertMetadata(" ")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("SQL is not an INSERT statement"); + } + @Test void shouldThrowExceptionForInsertWithoutColumnNames() { // Given @@ -201,4 +215,4 @@ private void setupMockResultSetForOrderDetails() throws SQLException { when(resultSet.getInt("NULLABLE")) .thenReturn(0, 0); } -} \ No newline at end of file +} diff --git a/src/test/java/com/jfeatures/msg/codegen/dbmetadata/SqlMetadataTest.java b/src/test/java/com/jfeatures/msg/codegen/dbmetadata/SqlMetadataTest.java index 67e9458..1a7f759 100644 --- a/src/test/java/com/jfeatures/msg/codegen/dbmetadata/SqlMetadataTest.java +++ b/src/test/java/com/jfeatures/msg/codegen/dbmetadata/SqlMetadataTest.java @@ -40,6 +40,24 @@ void setUp() { sqlMetadata = new SqlMetadata(jdbcTemplate); } + @Test + void testGetColumnMetadata_NullQuery_ThrowsException() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> + sqlMetadata.getColumnMetadata(null) + ); + + assertEquals("SQL query cannot be null or empty", exception.getMessage()); + } + + @Test + void testGetColumnMetadata_EmptyQuery_ThrowsException() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> + sqlMetadata.getColumnMetadata(" ") + ); + + assertEquals("SQL query cannot be null or empty", exception.getMessage()); + } + @Test void testGetColumnMetadata_ValidQuery_ReturnsColumnMetadata() throws SQLException { // Given @@ -563,4 +581,4 @@ private void setupNullableAutoIncrementMetadata() throws SQLException { when(resultSetMetaData.isSigned(i)).thenReturn(true); } } -} \ No newline at end of file +} diff --git a/src/test/java/com/jfeatures/msg/codegen/dbmetadata/UpdateMetadataExtractorTest.java b/src/test/java/com/jfeatures/msg/codegen/dbmetadata/UpdateMetadataExtractorTest.java index 2c10dd3..89300c9 100644 --- a/src/test/java/com/jfeatures/msg/codegen/dbmetadata/UpdateMetadataExtractorTest.java +++ b/src/test/java/com/jfeatures/msg/codegen/dbmetadata/UpdateMetadataExtractorTest.java @@ -1,15 +1,26 @@ package com.jfeatures.msg.codegen.dbmetadata; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.sql.*; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import javax.sql.DataSource; import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.update.Update; +import net.sf.jsqlparser.statement.update.UpdateSet; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; +import org.mockito.MockedStatic; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) @@ -88,6 +99,48 @@ void testExtractUpdateMetadata_InvalidSqlStatement_ThrowsException() { assertEquals("SQL is not an UPDATE statement", exception.getMessage()); } + + @Test + void testExtractSetColumnsSkipsNullColumnList() throws Exception { + Update update = mock(Update.class); + when(update.getTable()).thenReturn(new Table("customers")); + + UpdateSet updateSet = mock(UpdateSet.class); + when(updateSet.getColumns()).thenReturn(null); + when(update.getUpdateSets()).thenReturn(List.of(updateSet)); + + ResultSet emptyColumns = mock(ResultSet.class); + when(connection.getMetaData()).thenReturn(databaseMetaData); + when(databaseMetaData.getColumns(null, null, "customers", null)).thenReturn(emptyColumns); + when(emptyColumns.next()).thenReturn(false); + + Method method = UpdateMetadataExtractor.class.getDeclaredMethod("extractSetColumns", Update.class); + method.setAccessible(true); + + @SuppressWarnings("unchecked") + List result = (List) method.invoke(extractor, update); + assertThat(result).isEmpty(); + } + + @Test + void testExtractUpdateMetadata_NullSql_ThrowsException() { + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> extractor.extractUpdateMetadata(null) + ); + + assertEquals("SQL is not an UPDATE statement", exception.getMessage()); + } + + @Test + void testExtractUpdateMetadata_BlankSql_ThrowsException() { + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> extractor.extractUpdateMetadata(" ") + ); + + assertEquals("SQL is not an UPDATE statement", exception.getMessage()); + } @Test void testExtractUpdateMetadata_UnparsableSql_ThrowsJSQLParserException() { @@ -175,7 +228,33 @@ void testExtractUpdateMetadata_ParameterMetadataFails_UsesParsing() throws Excep assertEquals(1, result.whereColumns().size()); assertEquals("VARCHAR", result.whereColumns().get(0).getColumnTypeName()); // Fallback parsing } - + + @Test + void testExtractUpdateMetadata_CountSetParametersFallbackToStringAnalysis() throws Exception { + String sql = "UPDATE customers SET name = ?, email = ? WHERE id = ?"; + + setupColumnMetadata(); + setupParameterMetadata(3, 2); + + Statement parsedStatement = CCJSqlParserUtil.parse(sql); + AtomicInteger callCounter = new AtomicInteger(); + + try (MockedStatic parserMock = mockStatic(CCJSqlParserUtil.class)) { + parserMock.when(() -> CCJSqlParserUtil.parse(sql)).thenAnswer(invocation -> { + int index = callCounter.getAndIncrement(); + if (index == 1) { + throw new JSQLParserException("forced failure"); + } + return parsedStatement; + }); + + UpdateMetadata result = extractor.extractUpdateMetadata(sql); + assertNotNull(result); + assertEquals(2, result.setColumns().size()); + assertEquals(1, result.whereColumns().size()); + } + } + @Test void testExtractUpdateMetadata_ComplexUpdateWithJoin_HandlesCorrectly() throws Exception { // Given @@ -218,7 +297,38 @@ WHERE customer_id IN (SELECT customer_id FROM orders WHERE order_date > ?) assertEquals(1, result.setColumns().size()); assertEquals(1, result.whereColumns().size()); } - + + @Test + void testCountSetParametersByParsingNonUpdateReturnsZero() throws Exception { + Method method = UpdateMetadataExtractor.class.getDeclaredMethod("countSetParametersByParsing", String.class); + method.setAccessible(true); + + Object result = method.invoke(extractor, "SELECT * FROM customers WHERE id = ?"); + + assertEquals(0, result); + } + + @Test + void testCountParametersInUpdateSetWithNullValuesReturnsZero() throws Exception { + Method method = UpdateMetadataExtractor.class.getDeclaredMethod("countParametersInUpdateSet", UpdateSet.class); + method.setAccessible(true); + + UpdateSet updateSet = mock(UpdateSet.class); + when(updateSet.getValues()).thenReturn(null); + + Object result = method.invoke(extractor, updateSet); + assertEquals(0, result); + } + + @Test + void testExtractWhereColumnsByParsingHandlesParserFailure() throws Exception { + Method method = UpdateMetadataExtractor.class.getDeclaredMethod("extractWhereColumnsByParsing", String.class); + method.setAccessible(true); + + Object result = method.invoke(extractor, "UPDATE customers SET name = ? WHERE -- invalid"); + assertThat(result).isInstanceOf(List.class); + } + @Test void testExtractUpdateMetadata_CaseInsensitiveSql_HandlesCorrectly() throws Exception { // Given @@ -303,4 +413,4 @@ private void setupParameterMetadata(int totalParams, int setParams) throws SQLEx lenient().when(parameterMetaData.isNullable(i)).thenReturn(ParameterMetaData.parameterNullable); } } -} \ No newline at end of file +} diff --git a/src/test/java/com/jfeatures/msg/codegen/domain/GeneratedMicroserviceTest.java b/src/test/java/com/jfeatures/msg/codegen/domain/GeneratedMicroserviceTest.java new file mode 100644 index 0000000..02ceb16 --- /dev/null +++ b/src/test/java/com/jfeatures/msg/codegen/domain/GeneratedMicroserviceTest.java @@ -0,0 +1,58 @@ +package com.jfeatures.msg.codegen.domain; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.jfeatures.msg.codegen.util.SqlStatementType; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.TypeSpec; +import javax.lang.model.element.Modifier; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class GeneratedMicroserviceTest { + + private JavaFile sampleFile; + + @BeforeEach + void setUp() { + TypeSpec type = TypeSpec.classBuilder("Sample").addModifiers(Modifier.PUBLIC).build(); + sampleFile = JavaFile.builder("com.example", type).build(); + } + + @Test + void constructorValidatesArguments() { + assertThrows(IllegalArgumentException.class, () -> + new GeneratedMicroservice(null, sampleFile, sampleFile, sampleFile, sampleFile, "config", SqlStatementType.SELECT)); + + assertThrows(IllegalArgumentException.class, () -> + new GeneratedMicroservice(" ", sampleFile, sampleFile, sampleFile, sampleFile, "config", SqlStatementType.SELECT)); + + assertThrows(IllegalArgumentException.class, () -> + new GeneratedMicroservice("Customer", null, sampleFile, sampleFile, sampleFile, "config", SqlStatementType.SELECT)); + + assertThrows(IllegalArgumentException.class, () -> + new GeneratedMicroservice("Customer", sampleFile, null, sampleFile, sampleFile, "config", SqlStatementType.SELECT)); + + assertThrows(IllegalArgumentException.class, () -> + new GeneratedMicroservice("Customer", sampleFile, sampleFile, null, sampleFile, "config", SqlStatementType.SELECT)); + + assertThrows(IllegalArgumentException.class, () -> + new GeneratedMicroservice("Customer", sampleFile, sampleFile, sampleFile, null, "config", SqlStatementType.SELECT)); + + assertThrows(IllegalArgumentException.class, () -> + new GeneratedMicroservice("Customer", sampleFile, sampleFile, sampleFile, sampleFile, null, SqlStatementType.SELECT)); + + assertThrows(IllegalArgumentException.class, () -> + new GeneratedMicroservice("Customer", sampleFile, sampleFile, sampleFile, sampleFile, " ", SqlStatementType.SELECT)); + + assertThrows(IllegalArgumentException.class, () -> + new GeneratedMicroservice("Customer", sampleFile, sampleFile, sampleFile, sampleFile, "config", null)); + } + + @Test + void constructorAcceptsValidValues() { + assertDoesNotThrow(() -> + new GeneratedMicroservice("Customer", sampleFile, sampleFile, sampleFile, sampleFile, "config", SqlStatementType.SELECT)); + } +} diff --git a/src/test/java/com/jfeatures/msg/codegen/domain/ProjectDirectoryStructureTest.java b/src/test/java/com/jfeatures/msg/codegen/domain/ProjectDirectoryStructureTest.java new file mode 100644 index 0000000..9b9b8bc --- /dev/null +++ b/src/test/java/com/jfeatures/msg/codegen/domain/ProjectDirectoryStructureTest.java @@ -0,0 +1,26 @@ +package com.jfeatures.msg.codegen.domain; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.nio.file.Path; +import org.junit.jupiter.api.Test; + +class ProjectDirectoryStructureTest { + + @Test + void constructorValidatesNonNullPaths() { + Path sample = Path.of("/tmp"); + + assertThrows(IllegalArgumentException.class, () -> new ProjectDirectoryStructure(null, sample, sample, sample)); + assertThrows(IllegalArgumentException.class, () -> new ProjectDirectoryStructure(sample, null, sample, sample)); + assertThrows(IllegalArgumentException.class, () -> new ProjectDirectoryStructure(sample, sample, null, sample)); + assertThrows(IllegalArgumentException.class, () -> new ProjectDirectoryStructure(sample, sample, sample, null)); + } + + @Test + void constructorAcceptsValidPaths() { + Path sample = Path.of("/tmp"); + assertDoesNotThrow(() -> new ProjectDirectoryStructure(sample, sample, sample, sample)); + } +} diff --git a/src/test/java/com/jfeatures/msg/codegen/filesystem/DirectoryCleanupExceptionTest.java b/src/test/java/com/jfeatures/msg/codegen/filesystem/DirectoryCleanupExceptionTest.java new file mode 100644 index 0000000..51f49c3 --- /dev/null +++ b/src/test/java/com/jfeatures/msg/codegen/filesystem/DirectoryCleanupExceptionTest.java @@ -0,0 +1,25 @@ +package com.jfeatures.msg.codegen.filesystem; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +class DirectoryCleanupExceptionTest { + + @Test + void constructorsPreserveMessage() { + DirectoryCleanupException exception = new DirectoryCleanupException("cleanup failed"); + assertThat(exception) + .hasMessage("cleanup failed") + .hasNoCause(); + } + + @Test + void constructorsPreserveCause() { + Throwable cause = new RuntimeException("io"); + DirectoryCleanupException exception = new DirectoryCleanupException("cleanup failed", cause); + assertThat(exception) + .hasMessage("cleanup failed") + .hasCause(cause); + } +} diff --git a/src/test/java/com/jfeatures/msg/codegen/filesystem/MicroserviceDirectoryCleanerTest.java b/src/test/java/com/jfeatures/msg/codegen/filesystem/MicroserviceDirectoryCleanerTest.java index c2cedb8..0618d44 100644 --- a/src/test/java/com/jfeatures/msg/codegen/filesystem/MicroserviceDirectoryCleanerTest.java +++ b/src/test/java/com/jfeatures/msg/codegen/filesystem/MicroserviceDirectoryCleanerTest.java @@ -1,7 +1,10 @@ package com.jfeatures.msg.codegen.filesystem; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.Path; import java.util.stream.Stream; @@ -11,6 +14,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.MockedStatic; class MicroserviceDirectoryCleanerTest { @@ -154,4 +158,54 @@ void testCleanGeneratedCodeDirectories_WithReadOnlyFiles(@TempDir Path tempDir) // Should handle read-only files gracefully (may log warnings but not throw exceptions) assertDoesNotThrow(() -> cleaner.cleanGeneratedCodeDirectories(tempDir.toString())); } -} \ No newline at end of file + + @Test + void testCleanDirectoryIfExistsDeletionFailure() throws Exception { + Path tempDir = Files.createTempDirectory("cleaner-test"); + Path file = tempDir.resolve("file.txt"); + Files.writeString(file, "data"); + + Method method = MicroserviceDirectoryCleaner.class.getDeclaredMethod("cleanDirectoryIfExists", Path.class); + method.setAccessible(true); + + try (MockedStatic filesMock = mockStatic(Files.class)) { + filesMock.when(() -> Files.exists(tempDir)).thenReturn(true); + filesMock.when(() -> Files.walk(tempDir)).thenReturn(Stream.of(file, tempDir)); + filesMock.when(() -> Files.delete(file)).thenThrow(new RuntimeException("failure")); + filesMock.when(() -> Files.delete(tempDir)).thenReturn(null); + + assertDoesNotThrow(() -> method.invoke(cleaner, tempDir)); + } + } + + @Test + void testCleanDirectoryIfExistsThrowsDirectoryCleanupException() throws Exception { + Path tempDir = Files.createTempDirectory("cleaner-throws"); + + Method method = MicroserviceDirectoryCleaner.class.getDeclaredMethod("cleanDirectoryIfExists", Path.class); + method.setAccessible(true); + + try (MockedStatic filesMock = mockStatic(Files.class)) { + filesMock.when(() -> Files.exists(tempDir)).thenReturn(true); + filesMock.when(() -> Files.walk(tempDir)).thenThrow(new RuntimeException("walk failed")); + + InvocationTargetException exception = assertThrows(InvocationTargetException.class, () -> method.invoke(cleaner, tempDir)); + assertTrue(exception.getCause() instanceof DirectoryCleanupException); + } + } + + @Test + void testDeleteFileIfExistsFailureIsIgnored() throws Exception { + Path tempFile = Files.createTempFile("cleaner-file", ".txt"); + + Method method = MicroserviceDirectoryCleaner.class.getDeclaredMethod("deleteFileIfExists", Path.class); + method.setAccessible(true); + + try (MockedStatic filesMock = mockStatic(Files.class)) { + filesMock.when(() -> Files.exists(tempFile)).thenReturn(true); + filesMock.when(() -> Files.delete(tempFile)).thenThrow(new RuntimeException("delete failed")); + + assertDoesNotThrow(() -> method.invoke(cleaner, tempFile)); + } + } +} diff --git a/src/test/java/com/jfeatures/msg/codegen/filesystem/MicroserviceProjectWriterTest.java b/src/test/java/com/jfeatures/msg/codegen/filesystem/MicroserviceProjectWriterTest.java index 160af09..29e42b2 100644 --- a/src/test/java/com/jfeatures/msg/codegen/filesystem/MicroserviceProjectWriterTest.java +++ b/src/test/java/com/jfeatures/msg/codegen/filesystem/MicroserviceProjectWriterTest.java @@ -1,5 +1,6 @@ package com.jfeatures.msg.codegen.filesystem; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @@ -8,6 +9,9 @@ import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.TypeSpec; import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.Path; import java.util.stream.Stream; @@ -19,6 +23,7 @@ import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.MockedStatic; class MicroserviceProjectWriterTest { @@ -65,6 +70,28 @@ void testWriteMicroserviceProject_InvalidDestination(String testName, String des assertEquals("Destination path cannot be null or empty", exception.getMessage()); } + @Test + void testWriteMicroserviceProject_InvalidPathFormat(@TempDir Path tempDir) throws IOException { + setupMinimalMicroserviceMocks(); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> + writer.writeMicroserviceProject(mockMicroservice, "\0invalid") + ); + + assertTrue(exception.getMessage().contains("Invalid path format")); + } + + @Test + void testWriteMicroserviceProject_ForbidsSystemDirectories() throws IOException { + setupMinimalMicroserviceMocks(); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> + writer.writeMicroserviceProject(mockMicroservice, "/etc/msg") + ); + + assertTrue(exception.getMessage().contains("Access to system directories")); + } + @Test void testWriteMicroserviceProject_Success(@TempDir Path tempDir) throws IOException { // Setup mock microservice @@ -205,7 +232,7 @@ void testWriteMicroserviceProject_CreateDatabaseConfigFile(@TempDir Path tempDir assertTrue(configContent.contains("DatabaseConfig")); } - @Test + @Test void testWriteMicroserviceProject_AllStatementTypes(@TempDir Path tempDir) throws IOException { // Test different statement types: SELECT, INSERT, UPDATE, DELETE SqlStatementType[] statementTypes = {SqlStatementType.SELECT, SqlStatementType.INSERT, SqlStatementType.UPDATE, SqlStatementType.DELETE}; @@ -237,4 +264,94 @@ void testWriteMicroserviceProject_AllStatementTypes(@TempDir Path tempDir) throw "Should handle " + statementType + " statement type"); } } -} \ No newline at end of file + + @Test + void testValidatePathSecurityNullDoesNothing() throws Exception { + Method method = MicroserviceProjectWriter.class.getDeclaredMethod("validatePathSecurity", String.class); + method.setAccessible(true); + + assertDoesNotThrow(() -> method.invoke(writer, new Object[]{null})); + } + + @Test + void testWriteMicroserviceProject_RuntimeExceptionWrapped(@TempDir Path tempDir) throws Exception { + setupMinimalMicroserviceMocks(); + + ProjectDirectoryBuilder failingBuilder = mock(ProjectDirectoryBuilder.class); + when(failingBuilder.buildDirectoryStructure(anyString())).thenThrow(new IllegalStateException("runtime failure")); + + Field field = MicroserviceProjectWriter.class.getDeclaredField("directoryBuilder"); + field.setAccessible(true); + field.set(writer, failingBuilder); + + IOException exception = assertThrows(IOException.class, () -> + writer.writeMicroserviceProject(mockMicroservice, tempDir.toString()) + ); + + assertThat(exception) + .hasMessage("Failed to write microservice project due to runtime error") + .hasCauseInstanceOf(IllegalStateException.class); + } + + @Test + void testCopyResourceFileMissingThrowsIOException(@TempDir Path tempDir) throws Exception { + Method method = MicroserviceProjectWriter.class.getDeclaredMethod("copyResourceFileToPath", String.class, Path.class); + method.setAccessible(true); + + InvocationTargetException exception = assertThrows(InvocationTargetException.class, () -> + method.invoke(writer, "missing_resource", tempDir.resolve("out.txt")) + ); + + assertThat(exception.getCause()) + .isInstanceOf(IOException.class) + .hasMessageContaining("Template resource file not found"); + } + + @Test + void testCopyResourceFileWriteFailure(@TempDir Path tempDir) throws Exception { + Method method = MicroserviceProjectWriter.class.getDeclaredMethod("copyResourceFileToPath", String.class, Path.class); + method.setAccessible(true); + Path target = tempDir.resolve("pom.xml"); + + try (MockedStatic filesMock = mockStatic(Files.class)) { + filesMock.when(() -> Files.write(eq(target), any(byte[].class))).thenThrow(new IOException("disk full")); + filesMock.when(() -> Files.write(any(Path.class), any(byte[].class))).thenThrow(new IOException("disk full")); + + InvocationTargetException exception = assertThrows(InvocationTargetException.class, () -> + method.invoke(writer, com.jfeatures.msg.codegen.constants.ProjectConstants.POM_TEMPLATE_FILE, target) + ); + + assertThat(exception.getCause()) + .isInstanceOf(IOException.class) + .hasMessageContaining("Failed to copy resource file"); + } + } + + @Test + void testWriteMicroserviceProject_InvalidDatabaseConfigWrite(@TempDir Path tempDir) throws Exception { + setupMinimalMicroserviceMocks(); + + try (MockedStatic filesMock = mockStatic(Files.class)) { + filesMock.when(() -> Files.createDirectories(any(Path.class))).thenThrow(new IOException("permission denied")); + + IOException exception = assertThrows(IOException.class, () -> + writer.writeMicroserviceProject(mockMicroservice, tempDir.toString()) + ); + + assertThat(exception.getMessage()).contains("Failed to write database config file"); + } + } + + private void setupMinimalMicroserviceMocks() throws IOException { + TypeSpec typeSpec = TypeSpec.classBuilder("Sample").build(); + JavaFile javaFile = JavaFile.builder("com.test", typeSpec).build(); + + when(mockMicroservice.statementType()).thenReturn(SqlStatementType.SELECT); + when(mockMicroservice.businessDomainName()).thenReturn("Sample"); + when(mockMicroservice.springBootApplication()).thenReturn(javaFile); + when(mockMicroservice.dtoFile()).thenReturn(javaFile); + when(mockMicroservice.controllerFile()).thenReturn(javaFile); + when(mockMicroservice.daoFile()).thenReturn(javaFile); + when(mockMicroservice.databaseConfigContent()).thenReturn("config"); + } +} diff --git a/src/test/java/com/jfeatures/msg/codegen/filesystem/ProjectDirectoryBuilderTest.java b/src/test/java/com/jfeatures/msg/codegen/filesystem/ProjectDirectoryBuilderTest.java index a0b5e16..edb1bb0 100644 --- a/src/test/java/com/jfeatures/msg/codegen/filesystem/ProjectDirectoryBuilderTest.java +++ b/src/test/java/com/jfeatures/msg/codegen/filesystem/ProjectDirectoryBuilderTest.java @@ -1,6 +1,7 @@ package com.jfeatures.msg.codegen.filesystem; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; import com.jfeatures.msg.codegen.domain.ProjectDirectoryStructure; import java.io.IOException; @@ -13,6 +14,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.MockedStatic; class ProjectDirectoryBuilderTest { @@ -166,4 +168,17 @@ void testBuildDirectoryStructure_MultipleBuildsOnSamePath(@TempDir Path tempDir) assertEquals(structure1.srcTestJava(), structure2.srcTestJava()); assertEquals(structure1.srcMainResources(), structure2.srcMainResources()); } -} \ No newline at end of file + + @Test + void testBuildDirectoryStructure_CreateDirectoriesFailure(@TempDir Path tempDir) { + try (MockedStatic filesMock = mockStatic(Files.class)) { + filesMock.when(() -> Files.createDirectories(any(Path.class))).thenThrow(new IOException("disk full")); + + IOException exception = assertThrows(IOException.class, () -> + builder.buildDirectoryStructure(tempDir.toString()) + ); + + assertTrue(exception.getMessage().contains("Failed to create directory")); + } + } +} diff --git a/src/test/java/com/jfeatures/msg/codegen/mapping/ResultSetMappingGeneratorTest.java b/src/test/java/com/jfeatures/msg/codegen/mapping/ResultSetMappingGeneratorTest.java index 72566a4..b9b3fca 100644 --- a/src/test/java/com/jfeatures/msg/codegen/mapping/ResultSetMappingGeneratorTest.java +++ b/src/test/java/com/jfeatures/msg/codegen/mapping/ResultSetMappingGeneratorTest.java @@ -6,6 +6,7 @@ import com.jfeatures.msg.codegen.dbmetadata.ColumnMetadata; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.TypeName; +import java.lang.reflect.Method; import java.sql.Types; import java.util.ArrayList; import java.util.Arrays; @@ -331,6 +332,15 @@ void testSimpleClassNames() { assertTrue(result.contains("SimpleDTO")); } + @Test + void capitalizeHandlesNullAndEmptyValues() throws Exception { + Method method = ResultSetMappingGenerator.class.getDeclaredMethod("capitalize", String.class); + method.setAccessible(true); + + assertNull(method.invoke(null, (String) null)); + assertEquals("", method.invoke(null, "")); + } + private ColumnMetadata createColumn(String columnName) { ColumnMetadata column = new ColumnMetadata(); column.setColumnName(columnName); @@ -338,4 +348,4 @@ private ColumnMetadata createColumn(String columnName) { column.setColumnType(Types.VARCHAR); return column; } -} \ No newline at end of file +} diff --git a/src/test/java/com/jfeatures/msg/codegen/util/FieldBuildersTest.java b/src/test/java/com/jfeatures/msg/codegen/util/FieldBuildersTest.java index eb597a4..5958ab8 100644 --- a/src/test/java/com/jfeatures/msg/codegen/util/FieldBuildersTest.java +++ b/src/test/java/com/jfeatures/msg/codegen/util/FieldBuildersTest.java @@ -232,6 +232,16 @@ void shouldCreatePublicDtoField() { assertThat(field.javadoc.toString()).isEmpty(); // Public DTO fields don't have JavaDoc } + @Test + void shouldUseAliasForPublicDtoField() { + ColumnMetadata metadata = createColumnMetadata("customer_id", "bigint", false); + metadata.setColumnAlias("id_alias"); + + FieldSpec field = FieldBuilders.publicDtoField(metadata); + + assertThat(field.name).isEqualTo("id_alias"); + } + @Test void shouldThrowExceptionForNullColumnMetadataInDtoField() { assertThrows(IllegalArgumentException.class, diff --git a/src/test/java/com/jfeatures/msg/codegen/util/ParameterBuildersTest.java b/src/test/java/com/jfeatures/msg/codegen/util/ParameterBuildersTest.java index f8713c5..b97d140 100644 --- a/src/test/java/com/jfeatures/msg/codegen/util/ParameterBuildersTest.java +++ b/src/test/java/com/jfeatures/msg/codegen/util/ParameterBuildersTest.java @@ -2,7 +2,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mockStatic; +import com.jfeatures.msg.codegen.SQLServerDataTypeEnum; import com.jfeatures.msg.codegen.dbmetadata.ColumnMetadata; import com.jfeatures.msg.codegen.domain.DBColumn; import com.squareup.javapoet.ClassName; @@ -14,6 +16,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.MockedStatic; /** * Comprehensive tests for ParameterBuilders utility class to achieve 90%+ coverage. @@ -549,6 +552,21 @@ void shouldHandlePrimitiveTypeBoxingInColumnMetadata() { assertParameter(parameters.get(0), "countValue", ClassName.get(Integer.class)); } + @Test + void shouldBoxPrimitiveTypesReturnedFromTypeLookup() { + ColumnMetadata column = createColumnMetadata("primitive_flag", "bit"); + + try (MockedStatic mocked = mockStatic(SQLServerDataTypeEnum.class)) { + mocked.when(() -> SQLServerDataTypeEnum.getClassForType("bit")).thenReturn(boolean.class); + + List columns = List.of(column); + List parameters = ParameterBuilders.fromColumnMetadata(columns, false); + + assertThat(parameters).hasSize(1); + assertParameter(parameters.get(0), "primitiveFlag", ClassName.get(Boolean.class)); + } + } + private static void assertParameter( ParameterSpec parameter, String expectedName, diff --git a/src/test/java/com/jfeatures/msg/codegen/util/SqlStatementDetectorTest.java b/src/test/java/com/jfeatures/msg/codegen/util/SqlStatementDetectorTest.java index 2cd9257..9e10328 100644 --- a/src/test/java/com/jfeatures/msg/codegen/util/SqlStatementDetectorTest.java +++ b/src/test/java/com/jfeatures/msg/codegen/util/SqlStatementDetectorTest.java @@ -59,4 +59,22 @@ void shouldReturnUnknownForUnsupportedStatement() throws Exception { assertThat(SqlStatementDetector.detectStatementType("DROP TABLE customer")) .isEqualTo(SqlStatementType.UNKNOWN); } -} \ No newline at end of file + + @Test + void shouldFallbackToUpdateWhenParsingFails() throws Exception { + assertThat(SqlStatementDetector.detectStatementType("UPDATE broken set")) + .isEqualTo(SqlStatementType.UPDATE); + } + + @Test + void shouldFallbackToInsertWhenParsingFails() throws Exception { + assertThat(SqlStatementDetector.detectStatementType("INSERT broken values")) + .isEqualTo(SqlStatementType.INSERT); + } + + @Test + void shouldFallbackToDeleteWhenParsingFails() throws Exception { + assertThat(SqlStatementDetector.detectStatementType("DELETE broken")) + .isEqualTo(SqlStatementType.DELETE); + } +} diff --git a/src/test/java/com/jfeatures/msg/sql/ReadFileFromResourcesTest.java b/src/test/java/com/jfeatures/msg/sql/ReadFileFromResourcesTest.java index fe9f3ce..2aa54f8 100644 --- a/src/test/java/com/jfeatures/msg/sql/ReadFileFromResourcesTest.java +++ b/src/test/java/com/jfeatures/msg/sql/ReadFileFromResourcesTest.java @@ -2,10 +2,19 @@ import static org.junit.jupiter.api.Assertions.*; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; class ReadFileFromResourcesTest { + @AfterEach + void resetClassLoaderSupplier() { + ReadFileFromResources.setClassLoaderSupplier(null); + } + @Test void testReadFileFromResourcesValidFile() { // Test reading an existing resource file @@ -63,6 +72,41 @@ void testReadFileFromResourcesWithSubdirectory() { } } + @Test + void testReadFileFromResourcesNullCharsetUsesDefault() { + String content = ReadFileFromResources.readFileFromResources("sample_parameterized_sql.sql", null); + assertNotNull(content); + } + + @Test + void testReadFileFromResourcesIoFailureWrappedAsUncheckedIOException() { + ReadFileFromResources.setClassLoaderSupplier(() -> new ClassLoader(ReadFileFromResourcesTest.class.getClassLoader()) { + @Override + public InputStream getResourceAsStream(String name) { + if ("throwing.sql".equals(name)) { + return new InputStream() { + @Override + public int read() throws IOException { + throw new IOException("forced failure"); + } + + @Override + public byte[] readAllBytes() throws IOException { + throw new IOException("forced failure"); + } + }; + } + return super.getResourceAsStream(name); + } + }); + + UncheckedIOException exception = assertThrows(UncheckedIOException.class, () -> + ReadFileFromResources.readFileFromResources("throwing.sql") + ); + + assertTrue(exception.getMessage().contains("throwing.sql")); + } + @Test void testReadFileFromResourcesWithLeadingSlash() { // Test with leading slash (should be blocked for security) @@ -264,4 +308,4 @@ void testReadFileFromResourcesEmptyFileContent() { assertNotNull(e); } } -} \ No newline at end of file +}