diff --git a/src/main/java/com/iemr/common/bengen/service/GenerateBeneficiaryService.java b/src/main/java/com/iemr/common/bengen/service/GenerateBeneficiaryService.java index ddb42fb..89a8d7c 100644 --- a/src/main/java/com/iemr/common/bengen/service/GenerateBeneficiaryService.java +++ b/src/main/java/com/iemr/common/bengen/service/GenerateBeneficiaryService.java @@ -21,16 +21,15 @@ */ package com.iemr.common.bengen.service; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; import java.sql.Timestamp; import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,17 +42,34 @@ import com.iemr.common.bengen.utils.Generator; import com.iemr.common.bengen.utils.config.ConfigProperties; +import jakarta.annotation.PreDestroy; +import jakarta.transaction.Transactional; + @Service public class GenerateBeneficiaryService { private static final Logger logger = LoggerFactory.getLogger(GenerateBeneficiaryService.class); private ExecutorService executor = Executors.newCachedThreadPool(); - + private static final int BATCH_SIZE = 500; + private static final String ADMIN_BATCH = "admin-batch"; @Autowired JdbcTemplate jdbcTemplate; @Autowired BeneficiaryIdRepo beneficiaryIdRepo; + @PreDestroy + public void cleanup() { + logger.info("Shutting down executor service"); + executor.shutdown(); + try { + if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { + executor.shutdownNow(); + } + } catch (InterruptedException e) { + executor.shutdownNow(); + Thread.currentThread().interrupt(); + } + } public void generateBeneficiaryIDs() throws Exception { logger.info("BengenApplication.run start"); long strt = System.currentTimeMillis(); @@ -64,80 +80,57 @@ public void generateBeneficiaryIDs() throws Exception { createFile(); }); - /* - * executor.submit(() -> { logger.info("Running: " + - * Thread.currentThread().getName()); createFile(); }); - * - * executor.submit(() -> { logger.info("Running: " + - * Thread.currentThread().getName()); createFile(); }); - */ - // } - long fin = System.currentTimeMillis() - strt; logger.info("BengenApplication.run finish. time = " + fin + " ms."); } - + @Transactional public void createFile() { logger.info("BengenApplication.createFile start"); long strt = System.currentTimeMillis(); - try { - File file = File.createTempFile("" + System.currentTimeMillis(), ".csv"); - logger.info("File: " + file.getAbsolutePath()); - FileWriter fw = new FileWriter(file); - BufferedWriter bw = new BufferedWriter(fw); - Integer bentobeGenerate = ConfigProperties.getInteger("no-of-benID-to-be-generate"); - bw.write(createQuery(bentobeGenerate).toString()); - bw.flush(); - bw.close(); - } catch (IOException ioe) { - ioe.printStackTrace(); - } - long fin = System.currentTimeMillis() - strt; - logger.info("BengenApplication.createFile finish. time = " + fin + " ms."); - } - - public StringBuffer createQuery(Integer num) { - logger.info("BengenApplication.createQuery start"); - long strt = System.currentTimeMillis(); - - Generator g = new Generator(); - StringBuffer sb = new StringBuffer( - "INSERT INTO `db_identity`.`m_beneficiaryregidmapping` " + - "(`BeneficiaryID`,`Provisioned`,`Deleted`," + - "`CreatedDate`,`CreatedBy`) VALUES "); - - // INSERT INTO `db_identity`.`m_beneficiaryregidmapping` - // (`BeneficiaryID`,`Provisioned`,`Deleted`,`CreatedDate`,`CreatedBy`) VALUES - // (<{BeneficiaryID: }>, <{Provisioned: b'0'}>, <{Deleted: b'0'}>, - // <{CreatedDate: CURRENT_TIMESTAMP}>, <{CreatedBy: }>); - - Timestamp ts = Timestamp.from(Instant.now()); - - for (int i = 0; i < num; i++) { - sb.append("( "); - sb.append(g.generateBeneficiaryId()) - .append(",") - .append("b'0'") - .append(",") - .append("b'0'") - .append(",") - .append("'") - .append(ts) - .append("',") - .append("'admin-batch'") - .append(""); - sb.append(" ), "); + Integer count = ConfigProperties.getInteger("no-of-benID-to-be-generate"); + + List batchArgs = createBatchData(count); + + // Batch insert using JdbcTemplate + String sql = "INSERT INTO `db_identity`.`m_beneficiaryregidmapping` " + + "(`BeneficiaryID`, `Provisioned`, `Deleted`, `CreatedDate`, `CreatedBy`) " + + "VALUES (?, b'0', b'0', ?, ?)"; + + for (int i = 0; i < batchArgs.size(); i += BATCH_SIZE) { + List batch = batchArgs.subList(i, Math.min(i + BATCH_SIZE, batchArgs.size())); + try { + jdbcTemplate.batchUpdate(sql, batch); + } catch (Exception e) { + logger.error("Failed to insert batch starting at index {}: {}", i, e.getMessage()); + throw new RuntimeException("Batch insert failed", e); + } } - sb.deleteCharAt(sb.lastIndexOf(",")); - - jdbcTemplate.execute(sb.toString()); long fin = System.currentTimeMillis() - strt; - logger.info("BengenApplication.createQuery finish. time = " + fin + " ms."); + logger.info("BengenApplication.createFile finish. time = " + fin + " ms."); + } - return sb; + public List createBatchData(int num) { + logger.info("BengenApplication.createBatchData start"); + long strt = System.currentTimeMillis(); + + Timestamp ts = Timestamp.from(Instant.now()); + Generator g = new Generator(); + + // Use parallelStream to generate Beneficiary IDs concurrently + List data = IntStream.range(0, num).parallel() + .mapToObj(i -> new Object[]{ + g.generateBeneficiaryId(), // Assuming it's thread-safe + ts, + ADMIN_BATCH + }) + .collect(Collectors.toList()); + + long fin = System.currentTimeMillis() - strt; + logger.info("BengenApplication.createBatchData finish. time = " + fin + " ms."); + return data; } public void testLoopGenr() { diff --git a/src/main/java/com/iemr/common/bengen/utils/Generator.java b/src/main/java/com/iemr/common/bengen/utils/Generator.java index 88d9c41..a1bdcac 100644 --- a/src/main/java/com/iemr/common/bengen/utils/Generator.java +++ b/src/main/java/com/iemr/common/bengen/utils/Generator.java @@ -22,6 +22,9 @@ package com.iemr.common.bengen.utils; import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.concurrent.ThreadLocalRandom; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,125 +34,84 @@ * @author Sunil.K.Sundaram */ public class Generator { - - private static final Logger log = LoggerFactory.getLogger(Generator.class); - - public BigInteger generateBeneficiaryId(){ - BigInteger bid1 = generateFirst(); - BigInteger bid2 = generateNumN(10); - if (log.isDebugEnabled()){ - log.debug("bid1: "+bid1+" length: "+getDigitCount(bid1)); - log.debug("bid2: "+bid2+" length: "+getDigitCount(bid2)); - } - - BigInteger bid = bid1.add(bid2).multiply(new BigInteger("10")); - String chsum = Verhoeff.generateVerhoeff(bid.toString()); - if (log.isDebugEnabled()){ - log.debug("bid: "+bid+" length: "+getDigitCount(bid)+" chsum: " + chsum); - } - - bid = bid.add(new BigInteger(chsum)); - if (log.isDebugEnabled()){ - log.debug("BENEFICIARY ID: " + bid /*+ ": Length: " + getDigitCount(bid)*/); - } - return bid; - } - - public BigInteger generateFirst(){ - int one = getRandomNumRadRange(2, 9); - - BigInteger bn = new BigInteger(""+one).multiply(new BigInteger("10").pow(10)); - return bn; - } - protected BigInteger generateNumN(int n){ - int myArr1[] = new int[n]; - int myArr2[] = new int[n]; + private static final Logger log = LoggerFactory.getLogger(Generator.class); + private static final BigInteger TEN = BigInteger.TEN; + private static final BigInteger TEN_POW_10 = TEN.pow(10); + + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); - for(int i=0;i<=n-1;i++){ - myArr1[i] = getRandomNum(); - } + public BigInteger generateBeneficiaryId() { + BigInteger bid1 = generateFirst(); + BigInteger bid2 = generateNumN(10); - int count = n-1; - for(int i=0; i <= myArr1.length-1 ; i++){ - int num; - if(count == 0){ - num = getRandomNum(); - } else { - num = getRandomNumRad(count); - } - - int tmp = num; - myArr2[count] = myArr1[i]; - myArr1[i] = tmp; - count--; - } + if (log.isDebugEnabled()) { + log.debug("bid1: {} length: {}", bid1, getDigitCount(bid1)); + log.debug("bid2: {} length: {}", bid2, getDigitCount(bid2)); + } - StringBuilder str = new StringBuilder(); - for(int i=0;i 0) { - return digitCount - 1; - } - return digitCount; - } - - public int getRandomNum(){ - int num = (int) (Math.random() * 100 % 10); - if (log.isDebugEnabled()){ - log.debug("Rand generated: " + num); - } - return num; - } + return bid; + } + + public BigInteger generateFirst() { + int digit = getRandomInRange(2, 9); + return BigInteger.valueOf(digit).multiply(TEN_POW_10); + } - public int getRandomNumRad(int rad){ - int num = getRandomNum(); - num = num % rad; - if (log.isDebugEnabled()){ - log.debug("Rand generated ("+ rad + "): " + num); + protected BigInteger generateNumN(int n) { + StringBuilder sb = new StringBuilder(n); + for (int i = 0; i < n; i++) { + sb.append(getRandomDigit()); } - return num; + return new BigInteger(sb.toString()); } - public int getRandomNumRadRange(int rad1, int rad2){ - int num = getRandomNum(); - if(num >= rad1 && num <= rad2){ - return num; - } else { - num = getRandomNumRadRange(rad1, rad2); - } + public int getDigitCount(BigInteger number) { + double factor = Math.log10(2); + int digits = (int) (factor * number.bitLength() + 1); + return (TEN.pow(digits - 1).compareTo(number) > 0) ? digits - 1 : digits; + } - if (log.isDebugEnabled()){ - log.debug("Rand range generated: " + num); - } - return num; - } + private int getRandomDigit() { + return SECURE_RANDOM.nextInt(10); - public void displayArrays(int[] myArr, int[] myArr2){ - StringBuilder str = new StringBuilder(); - for(int i=0;i max) { + throw new IllegalArgumentException("min must be <= max"); + } + if (max == Integer.MAX_VALUE || (long) max - min + 1 > Integer.MAX_VALUE) { + return SECURE_RANDOM.nextInt(min, max + 1); } - } + return SECURE_RANDOM.nextInt(min, max + 1); + } + + // Optional: only if you need debugging arrays + public void displayArrays(int[] arr1, int[] arr2) { + if (!log.isDebugEnabled()) return; + + log.debug("myarr : {}", intArrayToString(arr1)); + log.debug("myarr2 : {}", intArrayToString(arr2)); + } + + private String intArrayToString(int[] array) { + StringBuilder sb = new StringBuilder(array.length); + for (int value : array) { + sb.append(value); + } + return sb.toString(); + } } diff --git a/src/main/java/com/iemr/common/bengen/utils/JwtUserIdValidationFilter.java b/src/main/java/com/iemr/common/bengen/utils/JwtUserIdValidationFilter.java index bd1f46d..8a9528c 100644 --- a/src/main/java/com/iemr/common/bengen/utils/JwtUserIdValidationFilter.java +++ b/src/main/java/com/iemr/common/bengen/utils/JwtUserIdValidationFilter.java @@ -5,7 +5,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; import com.iemr.common.bengen.utils.http.AuthorizationHeaderRequestWrapper; @@ -70,7 +69,6 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo } // Log headers for debugging - String jwtTokenFromHeader = request.getHeader("Jwttoken"); logger.info("JWT token from header: "); // Skip login and public endpoints @@ -120,7 +118,6 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo logger.warn("No valid authentication token found"); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized: Invalid or missing token"); - } catch (Exception e) { logger.error("Authorization error: ", e); response.sendError( @@ -149,14 +146,13 @@ private boolean isOriginAllowed(String origin) { } private boolean isMobileClient(String userAgent) { - if (userAgent == null) + if (userAgent == null) { return false; - + } userAgent = userAgent.toLowerCase(); - - return userAgent.contains("okhttp"); // iOS (custom clients) + return userAgent.contains("okhttp"); } - + private String getJwtTokenFromCookies(HttpServletRequest request) { Cookie[] cookies = request.getCookies(); if (cookies != null) { diff --git a/src/test/java/com/iemr/common/bengen/service/GenerateBeneficiaryServiceTest.java b/src/test/java/com/iemr/common/bengen/service/GenerateBeneficiaryServiceTest.java index 6d38253..f866a14 100644 --- a/src/test/java/com/iemr/common/bengen/service/GenerateBeneficiaryServiceTest.java +++ b/src/test/java/com/iemr/common/bengen/service/GenerateBeneficiaryServiceTest.java @@ -47,6 +47,7 @@ import java.util.concurrent.ExecutorService; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*; @@ -57,400 +58,353 @@ @DisplayName("GenerateBeneficiaryService Test Suite") class GenerateBeneficiaryServiceTest { - @InjectMocks - private GenerateBeneficiaryService generateBeneficiaryService; - - @Mock - private JdbcTemplate jdbcTemplate; - - @Mock - private BeneficiaryIdRepo beneficiaryIdRepo; - - @Mock - private ExecutorService mockExecutorService; - - @TempDir - Path tempDir; - - private static final BigInteger MOCKED_BENEFICIARY_ID = new BigInteger("12345678901"); - private static final String EXPECTED_TABLE_NAME = "`db_identity`.`m_beneficiaryregidmapping`"; - private static final String EXPECTED_CREATOR = "admin-batch"; - - @BeforeEach - void setUp() { - ReflectionTestUtils.setField(generateBeneficiaryService, "executor", mockExecutorService); - } - - @Nested - @DisplayName("SQL Query Generation Tests") - class QueryGenerationTests { - - @Test - @DisplayName("Should create valid SQL query with correct structure") - void createQuery_validInput_shouldGenerateCorrectSQL() { - // Arrange - int recordCount = 3; - - // Act - StringBuffer result = generateBeneficiaryService.createQuery(recordCount); - - // Assert - ArgumentCaptor sqlCaptor = ArgumentCaptor.forClass(String.class); - verify(jdbcTemplate, times(1)).execute(sqlCaptor.capture()); - - String executedSQL = sqlCaptor.getValue(); - - assertThat(executedSQL) - .as("SQL should have correct structure") - .startsWith("INSERT INTO " + EXPECTED_TABLE_NAME) - .contains("BeneficiaryID") - .contains("VALUES"); - - // Updated counting logic - long valueSetCount = countSQLValueSets(executedSQL); - assertThat(valueSetCount) - .as("Should contain at least %d value sets", recordCount) - .isGreaterThanOrEqualTo(recordCount); - - assertThat(result.toString()) - .as("Returned buffer should match executed SQL") - .isEqualTo(executedSQL); - } - - @ParameterizedTest - @ValueSource(ints = {1, 2, 5, 10}) - @DisplayName("Should handle various record counts correctly") - void createQuery_variousRecordCounts_shouldGenerateCorrectSQL(int recordCount) { - // Act - generateBeneficiaryService.createQuery(recordCount); - - // Assert - ArgumentCaptor sqlCaptor = ArgumentCaptor.forClass(String.class); - verify(jdbcTemplate).execute(sqlCaptor.capture()); - - String sql = sqlCaptor.getValue(); - - // Verify SQL structure instead of exact count - assertThat(sql) - .as("SQL should contain INSERT statement") - .containsIgnoringCase("INSERT INTO") - .containsIgnoringCase("VALUES"); - } - - @Test - @DisplayName("Should handle edge case of zero records") - void createQuery_zeroRecords_shouldHandleGracefully() { - // Act & Assert - assertDoesNotThrow(() -> generateBeneficiaryService.createQuery(0)); - } - } - - @Nested - @DisplayName("Beneficiary ID Retrieval Tests") - class BeneficiaryIdRetrievalTests { - - @Test - @DisplayName("Should retrieve and map beneficiary IDs correctly") - void getBeneficiaryIDs_validInput_shouldReturnMappedResults() { - // Arrange - Long requestedCount = 2L; - Integer vanID = 101; - - List mockRepoResult = createMockRepositoryResult(); - when(beneficiaryIdRepo.getBenIDGenerated(vanID, requestedCount)) - .thenReturn(mockRepoResult); - - // Act - List result = - generateBeneficiaryService.getBeneficiaryIDs(requestedCount, vanID); - - // Assert - verify(jdbcTemplate, times(1)).execute(anyString()); - verify(beneficiaryIdRepo, times(1)).getBenIDGenerated(vanID, requestedCount); - - assertThat(result) - .as("Result should not be null and have correct size") - .isNotNull() - .hasSize(2); - - // Verify mapping correctness - assertThat(result.get(0)) - .extracting("beneficiaryId", "benRegId") - .containsExactly(111L, 1L); - - assertThat(result.get(1)) - .extracting("beneficiaryId", "benRegId") - .containsExactly(222L, 2L); - } - - @Test - @DisplayName("Should handle empty repository result") - void getBeneficiaryIDs_emptyResult_shouldReturnEmptyList() { - // Arrange - when(beneficiaryIdRepo.getBenIDGenerated(anyInt(), anyLong())) - .thenReturn(new ArrayList<>()); - - // Act - List result = - generateBeneficiaryService.getBeneficiaryIDs(1L, 101); - - // Assert - assertThat(result) - .as("Should return empty list for empty repository result") - .isNotNull() - .isEmpty(); - } - - @Test - @DisplayName("Should handle repository exception gracefully") - void getBeneficiaryIDs_repositoryException_shouldPropagateException() { - // Arrange - when(beneficiaryIdRepo.getBenIDGenerated(anyInt(), anyLong())) - .thenThrow(new RuntimeException("Database connection failed")); - - // Act & Assert - assertThatThrownBy(() -> - generateBeneficiaryService.getBeneficiaryIDs(1L, 101)) - .isInstanceOf(RuntimeException.class) - .hasMessage("Database connection failed"); - } - - private List createMockRepositoryResult() { - List result = new ArrayList<>(); - result.add(new Object[]{1L, 111L, Timestamp.from(java.time.Instant.now())}); - result.add(new Object[]{2L, 222L, Timestamp.from(java.time.Instant.now())}); - return result; - } - } - - @Nested - @DisplayName("Generator Integration Tests") - class GeneratorIntegrationTests { - - @Test - @DisplayName("Should integrate with Generator to create beneficiary IDs") - void testLoopGenr_generatorIntegration_shouldCallGeneratorMethods() { - // Arrange & Act - try (MockedConstruction generatorMock = - mockConstruction(Generator.class, (mock, context) -> - when(mock.generateBeneficiaryId()).thenReturn(MOCKED_BENEFICIARY_ID))) { - - generateBeneficiaryService.testLoopGenr(); - - // Assert - assertThat(generatorMock.constructed()) - .as("Should construct at least one Generator instance") - .isNotEmpty(); - - Generator constructedGenerator = generatorMock.constructed().get(0); - verify(constructedGenerator, atLeastOnce()).generateBeneficiaryId(); - } - } - } - - @Nested - @DisplayName("Async Execution Tests") - class AsyncExecutionTests { - - @Test - @DisplayName("Should submit task to executor service") - void generateBeneficiaryIDs_asyncExecution_shouldSubmitTask() throws Exception { - // Arrange - Runnable[] capturedTask = new Runnable[1]; - doAnswer(invocation -> { - capturedTask[0] = invocation.getArgument(0); - return null; - }).when(mockExecutorService).submit(any(Runnable.class)); - - try (MockedStatic configMock = mockStatic(ConfigProperties.class)) { - configMock.when(() -> ConfigProperties.getInteger("no-of-benID-to-be-generate")) - .thenReturn(1); - - // Act - generateBeneficiaryService.generateBeneficiaryIDs(); - - // Assert - verify(mockExecutorService, times(1)).submit(any(Runnable.class)); - - // Execute captured task and verify it works - if (capturedTask[0] != null) { - assertDoesNotThrow(() -> capturedTask[0].run()); - verify(jdbcTemplate, atLeastOnce()).execute(anyString()); - } - } - } - - @Test - @DisplayName("Should handle configuration retrieval gracefully") - void generateBeneficiaryIDs_configHandling_shouldExecuteSuccessfully() { - // Test graceful handling instead of exception expectation - try (MockedStatic configMock = mockStatic(ConfigProperties.class)) { - configMock.when(() -> ConfigProperties.getInteger("no-of-benID-to-be-generate")) - .thenReturn(1); - - // Act & Assert - should not throw - assertDoesNotThrow(() -> generateBeneficiaryService.generateBeneficiaryIDs()); - verify(mockExecutorService, times(1)).submit(any(Runnable.class)); - } - } - } - - @Nested - @DisplayName("File Generation Tests") - class FileGenerationTests { - - @Test - @DisplayName("Should create file with correct SQL content") - @Timeout(5) - void createFile_normalOperation_shouldGenerateCorrectFile() throws IOException { - // Arrange - File tempFile = createTempTestFile(); - - try (MockedStatic configMock = mockStatic(ConfigProperties.class); - MockedStatic fileMock = mockStatic(File.class)) { - - setupFileCreationMocks(configMock, fileMock, tempFile, 2); - - // Act - assertDoesNotThrow(() -> generateBeneficiaryService.createFile()); - - // Assert - verifyJdbcExecution(); - verifyFileCreationAndContent(tempFile); - } - } - - @ParameterizedTest - @ValueSource(ints = {1, 3, 5, 10}) - @DisplayName("Should handle various record counts in file generation") - void createFile_variousRecordCounts_shouldGenerateCorrectContent(int recordCount) throws IOException { - // Arrange - File tempFile = createTempTestFile(); - - try (MockedStatic configMock = mockStatic(ConfigProperties.class); - MockedStatic fileMock = mockStatic(File.class)) { - - setupFileCreationMocks(configMock, fileMock, tempFile, recordCount); - - // Act - assertDoesNotThrow(() -> generateBeneficiaryService.createFile()); - - // Assert - verifyFileCreationAndContent(tempFile); - } - } - - @Test - @DisplayName("Should handle file operations gracefully") - void createFile_fileOperations_shouldExecuteSuccessfully() { - // Test normal file operation flow instead of expecting exceptions - try (MockedStatic configMock = mockStatic(ConfigProperties.class)) { - configMock.when(() -> ConfigProperties.getInteger("no-of-benID-to-be-generate")) - .thenReturn(2); - - // Act & Assert - should handle gracefully - assertDoesNotThrow(() -> generateBeneficiaryService.createFile()); - } - } - - @Test - @DisplayName("Should handle JDBC execution failure") - void createFile_jdbcFailure_shouldPropagateException() throws IOException { - // Arrange - File tempFile = createTempTestFile(); - - try (MockedStatic configMock = mockStatic(ConfigProperties.class); - MockedStatic fileMock = mockStatic(File.class)) { - - configMock.when(() -> ConfigProperties.getInteger("no-of-benID-to-be-generate")) - .thenReturn(2); - fileMock.when(() -> File.createTempFile(anyString(), eq(".csv"))) - .thenReturn(tempFile); - - doThrow(new RuntimeException("Database connection failed")) - .when(jdbcTemplate).execute(anyString()); - - // Act & Assert - assertThatThrownBy(() -> generateBeneficiaryService.createFile()) - .isInstanceOf(RuntimeException.class) - .hasMessage("Database connection failed"); - } - } - - @Test - @DisplayName("Should complete file creation within reasonable time") - void createFile_performance_shouldCompleteWithinTimeout() throws IOException { - // Arrange - File tempFile = createTempTestFile(); - - try (MockedStatic configMock = mockStatic(ConfigProperties.class); - MockedStatic fileMock = mockStatic(File.class)) { - - setupFileCreationMocks(configMock, fileMock, tempFile, 50); - - // Act & Assert - assertTimeoutPreemptively(Duration.ofSeconds(5), () -> { - generateBeneficiaryService.createFile(); - }); - } - } - - private File createTempTestFile() throws IOException { - File tempFile = Files.createTempFile(tempDir, "test_bengen", ".csv").toFile(); - tempFile.deleteOnExit(); - return tempFile; - } - - private void setupFileCreationMocks(MockedStatic configMock, - MockedStatic fileMock, - File tempFile, - int recordCount) { - configMock.when(() -> ConfigProperties.getInteger("no-of-benID-to-be-generate")) - .thenReturn(recordCount); - fileMock.when(() -> File.createTempFile(anyString(), eq(".csv"))) - .thenReturn(tempFile); - // Only mock JDBC when we're not testing JDBC failure - doNothing().when(jdbcTemplate).execute(anyString()); - } - - private void verifyJdbcExecution() { - verify(jdbcTemplate, times(1)).execute(anyString()); - } - - private void verifyFileCreationAndContent(File tempFile) throws IOException { - // Verify file properties - assertThat(tempFile) - .as("File should exist and not be empty") - .exists() - .isNotEmpty(); - - // Verify file content - String fileContent = Files.readString(tempFile.toPath()); - - assertThat(fileContent) - .as("File should contain proper SQL structure") - .contains("INSERT INTO " + EXPECTED_TABLE_NAME) - .contains("BeneficiaryID") - .contains(EXPECTED_CREATOR); - - // Verify SQL syntax without exact counting - assertThat(fileContent) - .as("SQL should be properly formatted") - .matches(".*INSERT INTO.*VALUES.*") - .doesNotContain(",,") // No empty values - .doesNotContain("()"); // No empty parentheses - } - } - - // Helper method to count SQL value sets - simplified approach - private long countSQLValueSets(String sqlContent) { - // Count opening parentheses that are followed by digits (likely value sets) - Pattern valueSetPattern = Pattern.compile("\\(\\s*\\d+"); - Matcher matcher = valueSetPattern.matcher(sqlContent); - long count = 0; - while (matcher.find()) { - count++; - } - return count; - } -} \ No newline at end of file + @InjectMocks + private GenerateBeneficiaryService generateBeneficiaryService; + + @Mock + private JdbcTemplate jdbcTemplate; + + @Mock + private BeneficiaryIdRepo beneficiaryIdRepo; + + @Mock + private ExecutorService mockExecutorService; + + @TempDir + Path tempDir; + + private static final BigInteger MOCKED_BENEFICIARY_ID = new BigInteger("12345678901"); + private static final String EXPECTED_TABLE_NAME = "`db_identity`.`m_beneficiaryregidmapping`"; + private static final String EXPECTED_CREATOR = "admin-batch"; + + @BeforeEach + void setUp() { + ReflectionTestUtils.setField(generateBeneficiaryService, "executor", mockExecutorService); + } + + @Nested + @DisplayName("SQL Query Generation Tests") + class QueryGenerationTests { + + @Test + @DisplayName("Should generate correct number of batch data entries") + void createBatchData_validInput_shouldReturnCorrectList() { + // Arrange + int recordCount = 3; + + // Act + List result = generateBeneficiaryService.createBatchData(recordCount); + + // Assert + assertThat(result).as("Should return a list with expected number of records").hasSize(recordCount); + + for (Object[] row : result) { + assertThat(row).as("Each row should have 3 elements: BeneficiaryID, Timestamp, CreatedBy").hasSize(3); + + assertThat(row[0]).as("Beneficiary ID should not be null").isNotNull(); + + assertThat(row[1]).as("Timestamp should be a Timestamp object").isInstanceOf(Timestamp.class); + + assertThat(row[2]).as("CreatedBy should be 'admin-batch'").isEqualTo("admin-batch"); + } + } + + @ParameterizedTest + @ValueSource(ints = { 1, 2, 5, 10 }) + @DisplayName("Should generate correct number of beneficiary ID data records") + void createBatchData_shouldReturnExpectedSize(int recordCount) { + // Act + List result = generateBeneficiaryService.createBatchData(recordCount); + + // Assert + assertThat(result).as("Result size should match record count").hasSize(recordCount); + + for (Object[] row : result) { + assertThat(row).as("Each row should contain [BeneficiaryID, Timestamp, CreatedBy]").hasSize(3); + assertThat(row[0]).isNotNull(); // BeneficiaryID + assertThat(row[1]).isInstanceOf(Timestamp.class); + assertThat(row[2]).isEqualTo("admin-batch"); + } + } + + @Test + @DisplayName("Should handle edge case of zero records") + void createQuery_zeroRecords_shouldHandleGracefully() { + // Act & Assert + assertDoesNotThrow(() -> generateBeneficiaryService.createBatchData(0)); + } + } + + @Nested + @DisplayName("Beneficiary ID Retrieval Tests") + class BeneficiaryIdRetrievalTests { + + @Test + @DisplayName("Should retrieve and map beneficiary IDs correctly") + void getBeneficiaryIDs_validInput_shouldReturnMappedResults() { + // Arrange + Long requestedCount = 2L; + Integer vanID = 101; + + List mockRepoResult = createMockRepositoryResult(); + when(beneficiaryIdRepo.getBenIDGenerated(vanID, requestedCount)).thenReturn(mockRepoResult); + + // Act + List result = generateBeneficiaryService.getBeneficiaryIDs(requestedCount, + vanID); + + // Assert + verify(jdbcTemplate, times(1)).execute(anyString()); + verify(beneficiaryIdRepo, times(1)).getBenIDGenerated(vanID, requestedCount); + + assertThat(result).as("Result should not be null and have correct size").isNotNull().hasSize(2); + + // Verify mapping correctness + assertThat(result.get(0)).extracting("beneficiaryId", "benRegId").containsExactly(111L, 1L); + + assertThat(result.get(1)).extracting("beneficiaryId", "benRegId").containsExactly(222L, 2L); + } + + @Test + @DisplayName("Should handle empty repository result") + void getBeneficiaryIDs_emptyResult_shouldReturnEmptyList() { + // Arrange + when(beneficiaryIdRepo.getBenIDGenerated(anyInt(), anyLong())).thenReturn(new ArrayList<>()); + + // Act + List result = generateBeneficiaryService.getBeneficiaryIDs(1L, 101); + + // Assert + assertThat(result).as("Should return empty list for empty repository result").isNotNull().isEmpty(); + } + + @Test + @DisplayName("Should handle repository exception gracefully") + void getBeneficiaryIDs_repositoryException_shouldPropagateException() { + // Arrange + when(beneficiaryIdRepo.getBenIDGenerated(anyInt(), anyLong())) + .thenThrow(new RuntimeException("Database connection failed")); + + // Act & Assert + assertThatThrownBy(() -> generateBeneficiaryService.getBeneficiaryIDs(1L, 101)) + .isInstanceOf(RuntimeException.class).hasMessage("Database connection failed"); + } + + private List createMockRepositoryResult() { + List result = new ArrayList<>(); + result.add(new Object[] { 1L, 111L, Timestamp.from(java.time.Instant.now()) }); + result.add(new Object[] { 2L, 222L, Timestamp.from(java.time.Instant.now()) }); + return result; + } + } + + @Nested + @DisplayName("Generator Integration Tests") + class GeneratorIntegrationTests { + + @Test + @DisplayName("Should integrate with Generator to create beneficiary IDs") + void testLoopGenr_generatorIntegration_shouldCallGeneratorMethods() { + // Arrange & Act + try (MockedConstruction generatorMock = mockConstruction(Generator.class, + (mock, context) -> when(mock.generateBeneficiaryId()).thenReturn(MOCKED_BENEFICIARY_ID))) { + + generateBeneficiaryService.testLoopGenr(); + + // Assert + assertThat(generatorMock.constructed()).as("Should construct at least one Generator instance") + .isNotEmpty(); + + Generator constructedGenerator = generatorMock.constructed().get(0); + verify(constructedGenerator, atLeastOnce()).generateBeneficiaryId(); + } + } + } + + @Nested + @DisplayName("Async Execution Tests") + class AsyncExecutionTests { + + @Test + @DisplayName("Should submit task to executor service") + void generateBeneficiaryIDs_asyncExecution_shouldSubmitTask() throws Exception { + // Arrange + Runnable[] capturedTask = new Runnable[1]; + doAnswer(invocation -> { + capturedTask[0] = invocation.getArgument(0); + return null; + }).when(mockExecutorService).submit(any(Runnable.class)); + + try (MockedStatic configMock = mockStatic(ConfigProperties.class)) { + configMock.when(() -> ConfigProperties.getInteger("no-of-benID-to-be-generate")).thenReturn(1); + + // Act + generateBeneficiaryService.generateBeneficiaryIDs(); + + // Assert + verify(mockExecutorService, times(1)).submit(any(Runnable.class)); + + // Execute the captured task and verify DB interaction + if (capturedTask[0] != null) { + assertDoesNotThrow(() -> capturedTask[0].run()); + + // Verify batchUpdate was called with appropriate arguments + verify(jdbcTemplate, atLeastOnce()).batchUpdate(eq( + "INSERT INTO `db_identity`.`m_beneficiaryregidmapping` (`BeneficiaryID`, `Provisioned`, `Deleted`, `CreatedDate`, `CreatedBy`) VALUES (?, b'0', b'0', ?, ?)"), + anyList()); + } + } + } + + @Test + @DisplayName("Should handle configuration retrieval gracefully") + void generateBeneficiaryIDs_configHandling_shouldExecuteSuccessfully() { + // Test graceful handling instead of exception expectation + try (MockedStatic configMock = mockStatic(ConfigProperties.class)) { + configMock.when(() -> ConfigProperties.getInteger("no-of-benID-to-be-generate")).thenReturn(1); + + // Act & Assert - should not throw + assertDoesNotThrow(() -> generateBeneficiaryService.generateBeneficiaryIDs()); + verify(mockExecutorService, times(1)).submit(any(Runnable.class)); + } + } + } + + @Nested + @DisplayName("File Generation Tests") + class FileGenerationTests { + + @Test + @DisplayName("Should batch insert beneficiary records using JDBC") + void createFile_shouldCallBatchUpdateWithCorrectParams() { + try (MockedStatic configMock = mockStatic(ConfigProperties.class)) { + // Arrange + configMock.when(() -> ConfigProperties.getInteger("no-of-benID-to-be-generate")).thenReturn(5); // for + // example + when(jdbcTemplate.batchUpdate(anyString(), anyList())).thenReturn(new int[5]); + + // Act & Assert + assertDoesNotThrow(() -> generateBeneficiaryService.createFile()); + + verify(jdbcTemplate, atLeastOnce()).batchUpdate(anyString(), anyList()); + } + } + + @ParameterizedTest + @ValueSource(ints = { 1, 3, 5, 10 }) + @DisplayName("Should handle various record counts in batch insert") + void createFile_variousRecordCounts_shouldCallJdbcTemplate(int recordCount) { + try (MockedStatic configMock = mockStatic(ConfigProperties.class)) { + // Arrange + configMock.when(() -> ConfigProperties.getInteger("no-of-benID-to-be-generate")) + .thenReturn(recordCount); + + when(jdbcTemplate.batchUpdate(anyString(), anyList())).thenReturn(new int[recordCount]); + + // Act + assertDoesNotThrow(() -> generateBeneficiaryService.createFile()); + + // Assert + ArgumentCaptor> batchCaptor = ArgumentCaptor.forClass(List.class); + verify(jdbcTemplate, atLeastOnce()).batchUpdate(anyString(), batchCaptor.capture()); + + List allBatches = batchCaptor.getAllValues().stream().flatMap(List::stream) + .collect(Collectors.toList()); + + assertThat(allBatches).as("Should batch insert expected number of records").hasSize(recordCount); + } + } + + @Test + @DisplayName("Should handle file operations gracefully") + void createFile_fileOperations_shouldExecuteSuccessfully() { + // Test normal file operation flow instead of expecting exceptions + try (MockedStatic configMock = mockStatic(ConfigProperties.class)) { + configMock.when(() -> ConfigProperties.getInteger("no-of-benID-to-be-generate")).thenReturn(2); + + // Act & Assert - should handle gracefully + assertDoesNotThrow(() -> generateBeneficiaryService.createFile()); + } + } + + @Test + @DisplayName("Should handle JDBC execution failure") + void createFile_jdbcFailure_shouldPropagateException() { + try (MockedStatic configMock = mockStatic(ConfigProperties.class)) { + // Arrange + configMock.when(() -> ConfigProperties.getInteger("no-of-benID-to-be-generate")).thenReturn(2); + + // Simulate JDBC failure + doThrow(new RuntimeException("Batch insert failed")).when(jdbcTemplate).batchUpdate(anyString(), + anyList()); + + // Act & Assert + assertThatThrownBy(() -> generateBeneficiaryService.createFile()).isInstanceOf(RuntimeException.class) + .hasMessage("Batch insert failed"); + } + } + + @Test + @DisplayName("Should complete file creation within reasonable time") + void createFile_performance_shouldCompleteWithinTimeout() throws IOException { + // Arrange + File tempFile = createTempTestFile(); + + try (MockedStatic configMock = mockStatic(ConfigProperties.class); + MockedStatic fileMock = mockStatic(File.class)) { + + setupFileCreationMocks(configMock, fileMock, tempFile, 50); + + // Act & Assert + assertTimeoutPreemptively(Duration.ofSeconds(5), () -> { + generateBeneficiaryService.createFile(); + }); + } + } + + private File createTempTestFile() throws IOException { + File tempFile = Files.createTempFile(tempDir, "test_bengen", ".csv").toFile(); + tempFile.deleteOnExit(); + return tempFile; + } + + private void setupFileCreationMocks(MockedStatic configMock, MockedStatic fileMock, + File tempFile, int recordCount) { + configMock.when(() -> ConfigProperties.getInteger("no-of-benID-to-be-generate")).thenReturn(recordCount); + fileMock.when(() -> File.createTempFile(anyString(), eq(".csv"))).thenReturn(tempFile); + } + + private void verifyJdbcExecution() { + verify(jdbcTemplate, times(1)).execute(anyString()); + } + + private void verifyFileCreationAndContent(File tempFile) throws IOException { + // Verify file properties + assertThat(tempFile).as("File should exist and not be empty").exists().isNotEmpty(); + + // Verify file content + String fileContent = Files.readString(tempFile.toPath()); + + assertThat(fileContent).as("File should contain proper SQL structure") + .contains("INSERT INTO " + EXPECTED_TABLE_NAME).contains("BeneficiaryID") + .contains(EXPECTED_CREATOR); + + // Verify SQL syntax without exact counting + assertThat(fileContent).as("SQL should be properly formatted").matches(".*INSERT INTO.*VALUES.*") + .doesNotContain(",,") // No empty values + .doesNotContain("()"); // No empty parentheses + } + } + + // Helper method to count SQL value sets - simplified approach + private long countSQLValueSets(String sqlContent) { + // Count opening parentheses that are followed by digits (likely value sets) + Pattern valueSetPattern = Pattern.compile("\\(\\s*\\d+"); + Matcher matcher = valueSetPattern.matcher(sqlContent); + long count = 0; + while (matcher.find()) { + count++; + } + return count; + } +}