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 5a3bc4e..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,13 @@ */ 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; @@ -45,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(); @@ -69,26 +83,33 @@ public void generateBeneficiaryIDs() throws Exception { 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(); - - Integer count = ConfigProperties.getInteger("no-of-benID-to-be-generate"); - List batchArgs = createBatchData(count); + logger.info("BengenApplication.createFile start"); + long strt = System.currentTimeMillis(); - // Batch insert using JdbcTemplate - String sql = "INSERT INTO `db_identity`.`m_beneficiaryregidmapping` " + - "(`BeneficiaryID`, `Provisioned`, `Deleted`, `CreatedDate`, `CreatedBy`) " + - "VALUES (?, b'0', b'0', ?, ?)"; + 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); + } + } - for (int i = 0; i < batchArgs.size(); i += BATCH_SIZE) { - List batch = batchArgs.subList(i, Math.min(i + BATCH_SIZE, batchArgs.size())); - jdbcTemplate.batchUpdate(sql, batch); - } - long fin = System.currentTimeMillis() - strt; - logger.info("BengenApplication.createFile finish. time = " + fin + " ms."); + long fin = System.currentTimeMillis() - strt; + logger.info("BengenApplication.createFile finish. time = " + fin + " ms."); } public List createBatchData(int num) { @@ -103,7 +124,7 @@ public List createBatchData(int num) { .mapToObj(i -> new Object[]{ g.generateBeneficiaryId(), // Assuming it's thread-safe ts, - "admin-batch" + ADMIN_BATCH }) .collect(Collectors.toList()); 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 c3daa35..a1bdcac 100644 --- a/src/main/java/com/iemr/common/bengen/utils/Generator.java +++ b/src/main/java/com/iemr/common/bengen/utils/Generator.java @@ -38,6 +38,8 @@ public class Generator { 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(); public BigInteger generateBeneficiaryId() { BigInteger bid1 = generateFirst(); @@ -68,27 +70,13 @@ public BigInteger generateFirst() { return BigInteger.valueOf(digit).multiply(TEN_POW_10); } - protected BigInteger generateNumN(int n) { - int[] source = new int[n]; - int[] target = new int[n]; - - for (int i = 0; i < n; i++) { - source[i] = getRandomDigit(); - } - - for (int i = 0, j = n - 1; i < n; i++, j--) { - int num = (j == 0) ? getRandomDigit() : getRandomDigit() % j; - target[j] = source[i]; - source[i] = num; - } - - StringBuilder sb = new StringBuilder(n); - for (int value : target) { - sb.append(value); - } - - return new BigInteger(sb.toString()); - } + protected BigInteger generateNumN(int n) { + StringBuilder sb = new StringBuilder(n); + for (int i = 0; i < n; i++) { + sb.append(getRandomDigit()); + } + return new BigInteger(sb.toString()); + } public int getDigitCount(BigInteger number) { double factor = Math.log10(2); @@ -97,20 +85,18 @@ public int getDigitCount(BigInteger number) { } private int getRandomDigit() { - SecureRandom secureRandom = new SecureRandom(); - return secureRandom.nextInt(10); + return SECURE_RANDOM.nextInt(10); } private int getRandomInRange(int min, int max) { - SecureRandom sr = new SecureRandom(); if (min > max) { throw new IllegalArgumentException("min must be <= max"); } - if (max == Integer.MAX_VALUE) { - return sr.nextInt(max - min) + min; - } - return sr.nextInt(min, max + 1); // safe here + 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 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 2e7da42..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,324 +47,364 @@ 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.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; -//@ExtendWith(MockitoExtension.class) +@ExtendWith(MockitoExtension.class) @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; - * - * // 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); } - * - * @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"); } - * - * } - * - * @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; + } +}