diff --git a/API-Gateway/src/test/java/org/training/api/gateway/ApiGatewayApplicationTests.java b/API-Gateway/src/test/java/org/training/api/gateway/ApiGatewayApplicationTests.java
index ebb306d..81ce875 100644
--- a/API-Gateway/src/test/java/org/training/api/gateway/ApiGatewayApplicationTests.java
+++ b/API-Gateway/src/test/java/org/training/api/gateway/ApiGatewayApplicationTests.java
@@ -2,10 +2,12 @@
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
import static org.junit.jupiter.api.Assertions.assertTrue;
@SpringBootTest
+@ActiveProfiles("test")
class ApiGatewayApplicationTests {
@Test
diff --git a/API-Gateway/src/test/java/org/training/api/gateway/ApiGatewayRouteTests.java b/API-Gateway/src/test/java/org/training/api/gateway/ApiGatewayRouteTests.java
new file mode 100644
index 0000000..f760050
--- /dev/null
+++ b/API-Gateway/src/test/java/org/training/api/gateway/ApiGatewayRouteTests.java
@@ -0,0 +1,38 @@
+package org.training.api.gateway;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.gateway.route.RouteLocator;
+import org.springframework.test.context.ActiveProfiles;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@SpringBootTest
+@AutoConfigureWebTestClient
+@ActiveProfiles("test")
+class ApiGatewayRouteTests {
+
+ @Autowired
+ private RouteLocator routeLocator;
+
+ @Test
+ void contextLoads() {
+ assertNotNull(routeLocator);
+ }
+
+ @Test
+ void routeConfiguration_hasExpectedRoutes() {
+ var routes = routeLocator.getRoutes().collectList().block();
+
+ assertNotNull(routes);
+ assertTrue(routes.size() >= 5, "Should have at least 5 routes configured");
+
+ assertTrue(routes.stream().anyMatch(r -> r.getId().contains("user-service")));
+ assertTrue(routes.stream().anyMatch(r -> r.getId().contains("fund-transfer-service")));
+ assertTrue(routes.stream().anyMatch(r -> r.getId().contains("account-service")));
+ assertTrue(routes.stream().anyMatch(r -> r.getId().contains("sequence-generator")));
+ assertTrue(routes.stream().anyMatch(r -> r.getId().contains("transaction-service")));
+ }
+}
diff --git a/API-Gateway/src/test/resources/application-test.yml b/API-Gateway/src/test/resources/application-test.yml
new file mode 100644
index 0000000..d42e8fe
--- /dev/null
+++ b/API-Gateway/src/test/resources/application-test.yml
@@ -0,0 +1,43 @@
+server:
+ port: 8080
+
+eureka:
+ client:
+ enabled: false
+
+spring:
+ application:
+ name: api-gateway-test
+ cloud:
+ gateway:
+ routes:
+ - id: user-service
+ uri: lb://user-service
+ predicates:
+ - Path=/api/users/**
+ - id: fund-transfer-service
+ uri: lb://fund-transfer-service
+ predicates:
+ - Path=/api/fund-transfers/**
+ - id: account-service
+ uri: lb://account-service
+ predicates:
+ - Path=/accounts/**
+ - id: sequence-generator
+ uri: lb://sequence-generator
+ predicates:
+ - Path=/sequence/**
+ - id: transaction-service
+ uri: lb://transaction-service
+ predicates:
+ - Path=/transactions/**
+ security:
+ oauth2:
+ client:
+ registration:
+ banking-service-client:
+ client-id: test-client
+ client-secret: test-secret
+ resourceserver:
+ jwt:
+ issuer-uri: http://localhost:8571/realms/banking-service
diff --git a/Account-Service/pom.xml b/Account-Service/pom.xml
index 759bf48..3d5ea2b 100644
--- a/Account-Service/pom.xml
+++ b/Account-Service/pom.xml
@@ -65,6 +65,11 @@
spring-boot-starter-test
test
+
+ com.h2database
+ h2
+ test
+
diff --git a/Account-Service/src/test/java/org/training/account/service/AccountServiceApplicationTests.java b/Account-Service/src/test/java/org/training/account/service/AccountServiceApplicationTests.java
index 6f3a83e..9d50f54 100644
--- a/Account-Service/src/test/java/org/training/account/service/AccountServiceApplicationTests.java
+++ b/Account-Service/src/test/java/org/training/account/service/AccountServiceApplicationTests.java
@@ -2,10 +2,12 @@
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
import static org.junit.jupiter.api.Assertions.assertTrue;
@SpringBootTest
+@ActiveProfiles("test")
class AccountServiceApplicationTests {
@Test
diff --git a/Account-Service/src/test/java/org/training/account/service/repository/AccountRepositoryTest.java b/Account-Service/src/test/java/org/training/account/service/repository/AccountRepositoryTest.java
new file mode 100644
index 0000000..7b5a30c
--- /dev/null
+++ b/Account-Service/src/test/java/org/training/account/service/repository/AccountRepositoryTest.java
@@ -0,0 +1,104 @@
+package org.training.account.service.repository;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.test.context.TestPropertySource;
+import org.training.account.service.model.AccountStatus;
+import org.training.account.service.model.AccountType;
+import org.training.account.service.model.entity.Account;
+
+import java.math.BigDecimal;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@DataJpaTest
+@TestPropertySource(locations = "classpath:application-test.yml")
+class AccountRepositoryTest {
+
+ @Autowired
+ private AccountRepository accountRepository;
+
+ @Test
+ void findAccountByAccountNumber_existingAccount_returnsAccount() {
+ String accountNumber = "ACC0001234";
+ Account account = Account.builder()
+ .accountNumber(accountNumber)
+ .accountType(AccountType.SAVINGS_ACCOUNT)
+ .accountStatus(AccountStatus.ACTIVE)
+ .availableBalance(BigDecimal.valueOf(1000))
+ .userId(1L)
+ .build();
+
+ accountRepository.save(account);
+
+ Optional result = accountRepository.findAccountByAccountNumber(accountNumber);
+
+ assertTrue(result.isPresent());
+ assertEquals(accountNumber, result.get().getAccountNumber());
+ assertEquals(AccountType.SAVINGS_ACCOUNT, result.get().getAccountType());
+ }
+
+ @Test
+ void findAccountByAccountNumber_nonExistingAccount_returnsEmpty() {
+ Optional result = accountRepository.findAccountByAccountNumber("NONEXISTENT");
+
+ assertFalse(result.isPresent());
+ }
+
+ @Test
+ void findAccountByUserIdAndAccountType_existingAccount_returnsAccount() {
+ Long userId = 1L;
+ AccountType accountType = AccountType.SAVINGS_ACCOUNT;
+ Account account = Account.builder()
+ .accountNumber("ACC0001234")
+ .accountType(accountType)
+ .accountStatus(AccountStatus.ACTIVE)
+ .availableBalance(BigDecimal.valueOf(1000))
+ .userId(userId)
+ .build();
+
+ accountRepository.save(account);
+
+ Optional result = accountRepository.findAccountByUserIdAndAccountType(userId, accountType);
+
+ assertTrue(result.isPresent());
+ assertEquals(userId, result.get().getUserId());
+ assertEquals(accountType, result.get().getAccountType());
+ }
+
+ @Test
+ void findAccountByUserIdAndAccountType_nonExistingAccount_returnsEmpty() {
+ Optional result = accountRepository
+ .findAccountByUserIdAndAccountType(999L, AccountType.SAVINGS_ACCOUNT);
+
+ assertFalse(result.isPresent());
+ }
+
+ @Test
+ void findAccountByUserId_existingAccount_returnsAccount() {
+ Long userId = 1L;
+ Account account = Account.builder()
+ .accountNumber("ACC0001234")
+ .accountType(AccountType.SAVINGS_ACCOUNT)
+ .accountStatus(AccountStatus.ACTIVE)
+ .availableBalance(BigDecimal.valueOf(1000))
+ .userId(userId)
+ .build();
+
+ accountRepository.save(account);
+
+ Optional result = accountRepository.findAccountByUserId(userId);
+
+ assertTrue(result.isPresent());
+ assertEquals(userId, result.get().getUserId());
+ }
+
+ @Test
+ void findAccountByUserId_nonExistingAccount_returnsEmpty() {
+ Optional result = accountRepository.findAccountByUserId(999L);
+
+ assertFalse(result.isPresent());
+ }
+}
diff --git a/Account-Service/src/test/java/org/training/account/service/service/implementation/AccountServiceImplTest.java b/Account-Service/src/test/java/org/training/account/service/service/implementation/AccountServiceImplTest.java
new file mode 100644
index 0000000..4918336
--- /dev/null
+++ b/Account-Service/src/test/java/org/training/account/service/service/implementation/AccountServiceImplTest.java
@@ -0,0 +1,316 @@
+package org.training.account.service.service.implementation;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.http.ResponseEntity;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.training.account.service.exception.*;
+import org.training.account.service.external.SequenceService;
+import org.training.account.service.external.TransactionService;
+import org.training.account.service.external.UserService;
+import org.training.account.service.model.AccountStatus;
+import org.training.account.service.model.AccountType;
+import org.training.account.service.model.dto.AccountDto;
+import org.training.account.service.model.dto.AccountStatusUpdate;
+import org.training.account.service.model.dto.external.SequenceDto;
+import org.training.account.service.model.dto.external.UserDto;
+import org.training.account.service.model.dto.response.Response;
+import org.training.account.service.model.entity.Account;
+import org.training.account.service.repository.AccountRepository;
+
+import java.math.BigDecimal;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class AccountServiceImplTest {
+
+ @Mock
+ private UserService userService;
+
+ @Mock
+ private AccountRepository accountRepository;
+
+ @Mock
+ private SequenceService sequenceService;
+
+ @Mock
+ private TransactionService transactionService;
+
+ @InjectMocks
+ private AccountServiceImpl accountService;
+
+ @BeforeEach
+ void setUp() {
+ ReflectionTestUtils.setField(accountService, "success", "200");
+ }
+
+ @Test
+ void createAccount_validUser_success() {
+ AccountDto accountDto = AccountDto.builder()
+ .userId(1L)
+ .accountType("SAVINGS_ACCOUNT")
+ .build();
+
+ UserDto userDto = UserDto.builder()
+ .userId(1L)
+ .firstName("testuser")
+ .build();
+
+ SequenceDto sequenceDto = new SequenceDto();
+ sequenceDto.setAccountNumber(1234567L);
+
+ when(userService.readUserById(1L))
+ .thenReturn(ResponseEntity.ok(userDto));
+ when(accountRepository.findAccountByUserIdAndAccountType(1L, AccountType.SAVINGS_ACCOUNT))
+ .thenReturn(Optional.empty());
+ when(sequenceService.generateAccountNumber())
+ .thenReturn(sequenceDto);
+ when(accountRepository.save(any(Account.class)))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+
+ Response response = accountService.createAccount(accountDto);
+
+ assertNotNull(response);
+ assertEquals(" Account created successfully", response.getMessage());
+ verify(userService).readUserById(1L);
+ verify(accountRepository).findAccountByUserIdAndAccountType(1L, AccountType.SAVINGS_ACCOUNT);
+ verify(sequenceService).generateAccountNumber();
+ verify(accountRepository).save(any(Account.class));
+ }
+
+ @Test
+ void createAccount_userNotFound_throwsResourceNotFound() {
+ AccountDto accountDto = AccountDto.builder()
+ .userId(1L)
+ .accountType("SAVINGS_ACCOUNT")
+ .build();
+
+ when(userService.readUserById(1L))
+ .thenReturn(ResponseEntity.ok(null));
+
+ assertThrows(ResourceNotFound.class, () -> accountService.createAccount(accountDto));
+ verify(accountRepository, never()).save(any());
+ }
+
+ @Test
+ void createAccount_duplicateAccount_throwsResourceConflict() {
+ AccountDto accountDto = AccountDto.builder()
+ .userId(1L)
+ .accountType("SAVINGS_ACCOUNT")
+ .build();
+
+ UserDto userDto = UserDto.builder()
+ .userId(1L)
+ .firstName("testuser")
+ .build();
+
+ Account existingAccount = Account.builder()
+ .userId(1L)
+ .accountType(AccountType.SAVINGS_ACCOUNT)
+ .build();
+
+ when(userService.readUserById(1L))
+ .thenReturn(ResponseEntity.ok(userDto));
+ when(accountRepository.findAccountByUserIdAndAccountType(1L, AccountType.SAVINGS_ACCOUNT))
+ .thenReturn(Optional.of(existingAccount));
+
+ assertThrows(ResourceConflict.class, () -> accountService.createAccount(accountDto));
+ verify(accountRepository, never()).save(any());
+ }
+
+ @Test
+ void updateStatus_validAccount_success() {
+ String accountNumber = "ACC0001234";
+ AccountStatusUpdate statusUpdate = new AccountStatusUpdate();
+ statusUpdate.setAccountStatus(AccountStatus.ACTIVE);
+
+ Account account = Account.builder()
+ .accountNumber(accountNumber)
+ .accountStatus(AccountStatus.PENDING)
+ .availableBalance(BigDecimal.valueOf(1500))
+ .build();
+
+ when(accountRepository.findAccountByAccountNumber(accountNumber))
+ .thenReturn(Optional.of(account));
+ when(accountRepository.save(any(Account.class)))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+
+ Response response = accountService.updateStatus(accountNumber, statusUpdate);
+
+ assertNotNull(response);
+ assertEquals("Account updated successfully", response.getMessage());
+ verify(accountRepository).save(any(Account.class));
+ }
+
+ @Test
+ void updateStatus_insufficientFunds_throwsInSufficientFunds() {
+ String accountNumber = "ACC0001234";
+ AccountStatusUpdate statusUpdate = new AccountStatusUpdate();
+ statusUpdate.setAccountStatus(AccountStatus.ACTIVE);
+
+ Account account = Account.builder()
+ .accountNumber(accountNumber)
+ .accountStatus(AccountStatus.PENDING)
+ .availableBalance(BigDecimal.valueOf(500))
+ .build();
+
+ when(accountRepository.findAccountByAccountNumber(accountNumber))
+ .thenReturn(Optional.of(account));
+
+ assertThrows(InSufficientFunds.class, () -> accountService.updateStatus(accountNumber, statusUpdate));
+ verify(accountRepository, never()).save(any());
+ }
+
+ @Test
+ void readAccountByAccountNumber_existingAccount_returnsAccount() {
+ String accountNumber = "ACC0001234";
+ Account account = Account.builder()
+ .accountNumber(accountNumber)
+ .accountType(AccountType.SAVINGS_ACCOUNT)
+ .accountStatus(AccountStatus.ACTIVE)
+ .availableBalance(BigDecimal.valueOf(1000))
+ .build();
+
+ when(accountRepository.findAccountByAccountNumber(accountNumber))
+ .thenReturn(Optional.of(account));
+
+ AccountDto result = accountService.readAccountByAccountNumber(accountNumber);
+
+ assertNotNull(result);
+ assertEquals(accountNumber, result.getAccountNumber());
+ verify(accountRepository).findAccountByAccountNumber(accountNumber);
+ }
+
+ @Test
+ void readAccountByAccountNumber_nonExistingAccount_throwsResourceNotFound() {
+ String accountNumber = "NONEXISTENT";
+
+ when(accountRepository.findAccountByAccountNumber(accountNumber))
+ .thenReturn(Optional.empty());
+
+ assertThrows(ResourceNotFound.class, () -> accountService.readAccountByAccountNumber(accountNumber));
+ }
+
+ @Test
+ void updateAccount_validAccount_success() {
+ String accountNumber = "ACC0001234";
+ AccountDto accountDto = AccountDto.builder()
+ .accountNumber(accountNumber)
+ .availableBalance(BigDecimal.valueOf(2000))
+ .build();
+
+ Account account = Account.builder()
+ .accountNumber(accountNumber)
+ .accountType(AccountType.SAVINGS_ACCOUNT)
+ .accountStatus(AccountStatus.ACTIVE)
+ .availableBalance(BigDecimal.valueOf(1000))
+ .build();
+
+ when(accountRepository.findAccountByAccountNumber(accountNumber))
+ .thenReturn(Optional.of(account));
+ when(accountRepository.save(any(Account.class)))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+
+ Response response = accountService.updateAccount(accountNumber, accountDto);
+
+ assertNotNull(response);
+ assertEquals("Account updated successfully", response.getMessage());
+ verify(accountRepository).save(any(Account.class));
+ }
+
+ @Test
+ void getBalance_existingAccount_returnsBalance() {
+ String accountNumber = "ACC0001234";
+ Account account = Account.builder()
+ .accountNumber(accountNumber)
+ .availableBalance(BigDecimal.valueOf(1500))
+ .build();
+
+ when(accountRepository.findAccountByAccountNumber(accountNumber))
+ .thenReturn(Optional.of(account));
+
+ String balance = accountService.getBalance(accountNumber);
+
+ assertEquals("1500", balance);
+ verify(accountRepository).findAccountByAccountNumber(accountNumber);
+ }
+
+ @Test
+ void closeAccount_zeroBalance_success() {
+ String accountNumber = "ACC0001234";
+ Account account = Account.builder()
+ .accountNumber(accountNumber)
+ .accountStatus(AccountStatus.ACTIVE)
+ .availableBalance(BigDecimal.ZERO)
+ .build();
+
+ when(accountRepository.findAccountByAccountNumber(accountNumber))
+ .thenReturn(Optional.of(account));
+
+ Response response = accountService.closeAccount(accountNumber);
+
+ assertNotNull(response);
+ verify(accountRepository, times(2)).findAccountByAccountNumber(accountNumber);
+ }
+
+ @Test
+ void closeAccount_nonZeroBalance_throwsAccountClosingException() {
+ String accountNumber = "ACC0001234";
+ Account account = Account.builder()
+ .accountNumber(accountNumber)
+ .accountStatus(AccountStatus.ACTIVE)
+ .availableBalance(BigDecimal.valueOf(1000))
+ .build();
+
+ when(accountRepository.findAccountByAccountNumber(accountNumber))
+ .thenReturn(Optional.of(account));
+
+ assertThrows(AccountClosingException.class, () -> accountService.closeAccount(accountNumber));
+ }
+
+ @Test
+ void readAccountByUserId_activeAccount_returnsAccount() {
+ Long userId = 1L;
+ Account account = Account.builder()
+ .userId(userId)
+ .accountNumber("ACC0001234")
+ .accountType(AccountType.SAVINGS_ACCOUNT)
+ .accountStatus(AccountStatus.ACTIVE)
+ .availableBalance(BigDecimal.valueOf(1000))
+ .build();
+
+ when(accountRepository.findAccountByUserId(userId))
+ .thenReturn(Optional.of(account));
+
+ AccountDto result = accountService.readAccountByUserId(userId);
+
+ assertNotNull(result);
+ assertEquals("ACC0001234", result.getAccountNumber());
+ verify(accountRepository).findAccountByUserId(userId);
+ }
+
+ @Test
+ void readAccountByUserId_inactiveAccount_throwsAccountStatusException() {
+ Long userId = 1L;
+ Account account = Account.builder()
+ .userId(userId)
+ .accountNumber("ACC0001234")
+ .accountType(AccountType.SAVINGS_ACCOUNT)
+ .accountStatus(AccountStatus.PENDING)
+ .build();
+
+ when(accountRepository.findAccountByUserId(userId))
+ .thenReturn(Optional.of(account));
+
+ assertThrows(AccountStatusException.class, () -> accountService.readAccountByUserId(userId));
+ }
+}
diff --git a/Account-Service/src/test/resources/application-test.yml b/Account-Service/src/test/resources/application-test.yml
new file mode 100644
index 0000000..32fc9b6
--- /dev/null
+++ b/Account-Service/src/test/resources/application-test.yml
@@ -0,0 +1,17 @@
+spring:
+ application:
+ name: account-service
+ ok: 200
+ datasource:
+ url: jdbc:h2:mem:testdb
+ driver-class-name: org.h2.Driver
+ username: sa
+ password:
+ jpa:
+ hibernate:
+ ddl-auto: create-drop
+ show-sql: true
+ properties:
+ hibernate:
+ format_sql: true
+ dialect: org.hibernate.dialect.H2Dialect
diff --git a/Fund-Transfer/pom.xml b/Fund-Transfer/pom.xml
index d6abb65..a5f2741 100644
--- a/Fund-Transfer/pom.xml
+++ b/Fund-Transfer/pom.xml
@@ -60,6 +60,11 @@
spring-boot-starter-test
test
+
+ com.h2database
+ h2
+ test
+
org.springframework.cloud
spring-cloud-starter-openfeign
diff --git a/Fund-Transfer/src/test/java/org/training/fundtransfer/repository/FundTransferRepositoryTest.java b/Fund-Transfer/src/test/java/org/training/fundtransfer/repository/FundTransferRepositoryTest.java
new file mode 100644
index 0000000..500b15b
--- /dev/null
+++ b/Fund-Transfer/src/test/java/org/training/fundtransfer/repository/FundTransferRepositoryTest.java
@@ -0,0 +1,92 @@
+package org.training.fundtransfer.repository;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.test.context.TestPropertySource;
+import org.training.fundtransfer.model.TransactionStatus;
+import org.training.fundtransfer.model.TransferType;
+import org.training.fundtransfer.model.entity.FundTransfer;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@DataJpaTest
+@TestPropertySource(locations = "classpath:application-test.yml")
+class FundTransferRepositoryTest {
+
+ @Autowired
+ private FundTransferRepository fundTransferRepository;
+
+ @Test
+ void findFundTransferByTransactionReference_existingReference_returnsFundTransfer() {
+ String transactionReference = "REF123456";
+ FundTransfer fundTransfer = FundTransfer.builder()
+ .transactionReference(transactionReference)
+ .fromAccount("ACC0001234")
+ .toAccount("ACC0005678")
+ .amount(BigDecimal.valueOf(500))
+ .status(TransactionStatus.SUCCESS)
+ .transferType(TransferType.INTERNAL)
+ .build();
+
+ fundTransferRepository.save(fundTransfer);
+
+ Optional result = fundTransferRepository
+ .findFundTransferByTransactionReference(transactionReference);
+
+ assertTrue(result.isPresent());
+ assertEquals(transactionReference, result.get().getTransactionReference());
+ assertEquals("ACC0001234", result.get().getFromAccount());
+ assertEquals("ACC0005678", result.get().getToAccount());
+ }
+
+ @Test
+ void findFundTransferByTransactionReference_nonExistingReference_returnsEmpty() {
+ Optional result = fundTransferRepository
+ .findFundTransferByTransactionReference("NONEXISTENT");
+
+ assertFalse(result.isPresent());
+ }
+
+ @Test
+ void findFundTransferByFromAccount_existingAccount_returnsTransfers() {
+ String fromAccount = "ACC0001234";
+ FundTransfer transfer1 = FundTransfer.builder()
+ .transactionReference("REF001")
+ .fromAccount(fromAccount)
+ .toAccount("ACC0005678")
+ .amount(BigDecimal.valueOf(500))
+ .status(TransactionStatus.SUCCESS)
+ .transferType(TransferType.INTERNAL)
+ .build();
+
+ FundTransfer transfer2 = FundTransfer.builder()
+ .transactionReference("REF002")
+ .fromAccount(fromAccount)
+ .toAccount("ACC0009999")
+ .amount(BigDecimal.valueOf(300))
+ .status(TransactionStatus.SUCCESS)
+ .transferType(TransferType.INTERNAL)
+ .build();
+
+ fundTransferRepository.save(transfer1);
+ fundTransferRepository.save(transfer2);
+
+ List results = fundTransferRepository.findFundTransferByFromAccount(fromAccount);
+
+ assertEquals(2, results.size());
+ assertTrue(results.stream().allMatch(t -> t.getFromAccount().equals(fromAccount)));
+ }
+
+ @Test
+ void findFundTransferByFromAccount_nonExistingAccount_returnsEmptyList() {
+ List results = fundTransferRepository
+ .findFundTransferByFromAccount("NONEXISTENT");
+
+ assertTrue(results.isEmpty());
+ }
+}
diff --git a/Fund-Transfer/src/test/java/org/training/fundtransfer/service/implementation/FundTransferServiceImplTest.java b/Fund-Transfer/src/test/java/org/training/fundtransfer/service/implementation/FundTransferServiceImplTest.java
new file mode 100644
index 0000000..215da7a
--- /dev/null
+++ b/Fund-Transfer/src/test/java/org/training/fundtransfer/service/implementation/FundTransferServiceImplTest.java
@@ -0,0 +1,228 @@
+package org.training.fundtransfer.service.implementation;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.http.ResponseEntity;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.training.fundtransfer.exception.AccountUpdateException;
+import org.training.fundtransfer.exception.InsufficientBalance;
+import org.training.fundtransfer.exception.ResourceNotFound;
+import org.training.fundtransfer.external.AccountService;
+import org.training.fundtransfer.external.TransactionService;
+import org.training.fundtransfer.model.TransactionStatus;
+import org.training.fundtransfer.model.TransferType;
+import org.training.fundtransfer.model.dto.Account;
+import org.training.fundtransfer.model.dto.Transaction;
+import org.training.fundtransfer.model.dto.request.FundTransferRequest;
+import org.training.fundtransfer.model.dto.response.FundTransferResponse;
+import org.training.fundtransfer.model.dto.response.Response;
+import org.training.fundtransfer.model.entity.FundTransfer;
+import org.training.fundtransfer.repository.FundTransferRepository;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class FundTransferServiceImplTest {
+
+ @Mock
+ private AccountService accountService;
+
+ @Mock
+ private TransactionService transactionService;
+
+ @Mock
+ private FundTransferRepository fundTransferRepository;
+
+ @InjectMocks
+ private FundTransferServiceImpl fundTransferService;
+
+ @BeforeEach
+ void setUp() {
+ ReflectionTestUtils.setField(fundTransferService, "ok", "200");
+ }
+
+ @Test
+ void fundTransfer_validAccountsAndSufficientBalance_success() {
+ FundTransferRequest request = new FundTransferRequest();
+ request.setFromAccount("ACC0001234");
+ request.setToAccount("ACC0005678");
+ request.setAmount(BigDecimal.valueOf(500));
+
+ Account fromAccount = Account.builder()
+ .accountNumber("ACC0001234")
+ .accountStatus("ACTIVE")
+ .availableBalance(BigDecimal.valueOf(1000))
+ .build();
+
+ Account toAccount = Account.builder()
+ .accountNumber("ACC0005678")
+ .accountStatus("ACTIVE")
+ .availableBalance(BigDecimal.valueOf(500))
+ .build();
+
+ when(accountService.readByAccountNumber("ACC0001234"))
+ .thenReturn(ResponseEntity.ok(fromAccount));
+ when(accountService.readByAccountNumber("ACC0005678"))
+ .thenReturn(ResponseEntity.ok(toAccount));
+ when(accountService.updateAccount(anyString(), any(Account.class)))
+ .thenReturn(ResponseEntity.ok(Response.builder().build()));
+ when(transactionService.makeInternalTransactions(anyList(), anyString()))
+ .thenReturn(ResponseEntity.ok(Response.builder().build()));
+ when(fundTransferRepository.save(any(FundTransfer.class)))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+
+ FundTransferResponse response = fundTransferService.fundTransfer(request);
+
+ assertNotNull(response);
+ assertEquals("Fund transfer was successful", response.getMessage());
+ assertNotNull(response.getTransactionId());
+ verify(accountService, times(2)).readByAccountNumber(anyString());
+ verify(accountService, times(2)).updateAccount(anyString(), any(Account.class));
+ verify(transactionService).makeInternalTransactions(anyList(), anyString());
+ verify(fundTransferRepository).save(any(FundTransfer.class));
+ }
+
+ @Test
+ void fundTransfer_sourceAccountNotFound_throwsResourceNotFound() {
+ FundTransferRequest request = new FundTransferRequest();
+ request.setFromAccount("ACC0001234");
+ request.setToAccount("ACC0005678");
+ request.setAmount(BigDecimal.valueOf(500));
+
+ when(accountService.readByAccountNumber("ACC0001234"))
+ .thenReturn(ResponseEntity.ok(null));
+
+ assertThrows(ResourceNotFound.class, () -> fundTransferService.fundTransfer(request));
+ verify(accountService).readByAccountNumber("ACC0001234");
+ verify(fundTransferRepository, never()).save(any());
+ }
+
+ @Test
+ void fundTransfer_destinationAccountNotFound_throwsResourceNotFound() {
+ FundTransferRequest request = new FundTransferRequest();
+ request.setFromAccount("ACC0001234");
+ request.setToAccount("ACC0005678");
+ request.setAmount(BigDecimal.valueOf(500));
+
+ Account fromAccount = Account.builder()
+ .accountNumber("ACC0001234")
+ .accountStatus("ACTIVE")
+ .availableBalance(BigDecimal.valueOf(1000))
+ .build();
+
+ when(accountService.readByAccountNumber("ACC0001234"))
+ .thenReturn(ResponseEntity.ok(fromAccount));
+ when(accountService.readByAccountNumber("ACC0005678"))
+ .thenReturn(ResponseEntity.ok(null));
+
+ assertThrows(ResourceNotFound.class, () -> fundTransferService.fundTransfer(request));
+ verify(fundTransferRepository, never()).save(any());
+ }
+
+ @Test
+ void fundTransfer_inactiveAccount_throwsAccountUpdateException() {
+ FundTransferRequest request = new FundTransferRequest();
+ request.setFromAccount("ACC0001234");
+ request.setToAccount("ACC0005678");
+ request.setAmount(BigDecimal.valueOf(500));
+
+ Account fromAccount = Account.builder()
+ .accountNumber("ACC0001234")
+ .accountStatus("PENDING")
+ .availableBalance(BigDecimal.valueOf(1000))
+ .build();
+
+ when(accountService.readByAccountNumber("ACC0001234"))
+ .thenReturn(ResponseEntity.ok(fromAccount));
+
+ assertThrows(AccountUpdateException.class, () -> fundTransferService.fundTransfer(request));
+ verify(fundTransferRepository, never()).save(any());
+ }
+
+ @Test
+ void fundTransfer_insufficientBalance_throwsInsufficientBalance() {
+ FundTransferRequest request = new FundTransferRequest();
+ request.setFromAccount("ACC0001234");
+ request.setToAccount("ACC0005678");
+ request.setAmount(BigDecimal.valueOf(1500));
+
+ Account fromAccount = Account.builder()
+ .accountNumber("ACC0001234")
+ .accountStatus("ACTIVE")
+ .availableBalance(BigDecimal.valueOf(1000))
+ .build();
+
+ when(accountService.readByAccountNumber("ACC0001234"))
+ .thenReturn(ResponseEntity.ok(fromAccount));
+
+ assertThrows(InsufficientBalance.class, () -> fundTransferService.fundTransfer(request));
+ verify(fundTransferRepository, never()).save(any());
+ }
+
+ @Test
+ void getTransferDetailsFromReferenceId_validReference_returnsFundTransferDto() {
+ String referenceId = "REF123456";
+ FundTransfer fundTransfer = FundTransfer.builder()
+ .fundTransferId(1L)
+ .transactionReference(referenceId)
+ .fromAccount("ACC0001234")
+ .toAccount("ACC0005678")
+ .amount(BigDecimal.valueOf(500))
+ .status(TransactionStatus.SUCCESS)
+ .transferType(TransferType.INTERNAL)
+ .build();
+
+ when(fundTransferRepository.findFundTransferByTransactionReference(referenceId))
+ .thenReturn(Optional.of(fundTransfer));
+
+ assertDoesNotThrow(() -> fundTransferService.getTransferDetailsFromReferenceId(referenceId));
+ verify(fundTransferRepository).findFundTransferByTransactionReference(referenceId);
+ }
+
+ @Test
+ void getTransferDetailsFromReferenceId_invalidReference_throwsResourceNotFound() {
+ String referenceId = "INVALID_REF";
+
+ when(fundTransferRepository.findFundTransferByTransactionReference(referenceId))
+ .thenReturn(Optional.empty());
+
+ assertThrows(ResourceNotFound.class,
+ () -> fundTransferService.getTransferDetailsFromReferenceId(referenceId));
+ verify(fundTransferRepository).findFundTransferByTransactionReference(referenceId);
+ }
+
+ @Test
+ void getAllTransfersByAccountId_validAccountId_returnsTransferList() {
+ String accountId = "ACC0001234";
+ List transfers = List.of(
+ FundTransfer.builder()
+ .fundTransferId(1L)
+ .fromAccount(accountId)
+ .toAccount("ACC0005678")
+ .amount(BigDecimal.valueOf(500))
+ .build(),
+ FundTransfer.builder()
+ .fundTransferId(2L)
+ .fromAccount(accountId)
+ .toAccount("ACC0009999")
+ .amount(BigDecimal.valueOf(300))
+ .build()
+ );
+
+ when(fundTransferRepository.findFundTransferByFromAccount(accountId))
+ .thenReturn(transfers);
+
+ assertDoesNotThrow(() -> fundTransferService.getAllTransfersByAccountId(accountId));
+ verify(fundTransferRepository).findFundTransferByFromAccount(accountId);
+ }
+}
diff --git a/Fund-Transfer/src/test/resources/application-test.yml b/Fund-Transfer/src/test/resources/application-test.yml
new file mode 100644
index 0000000..667cf48
--- /dev/null
+++ b/Fund-Transfer/src/test/resources/application-test.yml
@@ -0,0 +1,18 @@
+spring:
+ application:
+ name: fund-transfer-service
+ bad_request: 400
+ ok: 200
+ datasource:
+ url: jdbc:h2:mem:testdb
+ driver-class-name: org.h2.Driver
+ username: sa
+ password:
+ jpa:
+ hibernate:
+ ddl-auto: create-drop
+ show-sql: true
+ properties:
+ hibernate:
+ format_sql: true
+ dialect: org.hibernate.dialect.H2Dialect
diff --git a/Sequence-Generator/pom.xml b/Sequence-Generator/pom.xml
index b77a1ed..8fe4a38 100644
--- a/Sequence-Generator/pom.xml
+++ b/Sequence-Generator/pom.xml
@@ -57,6 +57,11 @@
spring-boot-starter-test
test
+
+ com.h2database
+ h2
+ test
+
diff --git a/Sequence-Generator/src/test/java/org/training/sequence/generator/SequenceGeneratorApplicationTests.java b/Sequence-Generator/src/test/java/org/training/sequence/generator/SequenceGeneratorApplicationTests.java
new file mode 100644
index 0000000..01de654
--- /dev/null
+++ b/Sequence-Generator/src/test/java/org/training/sequence/generator/SequenceGeneratorApplicationTests.java
@@ -0,0 +1,15 @@
+package org.training.sequence.generator;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
+
+@SpringBootTest
+@ActiveProfiles("test")
+class SequenceGeneratorApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}
diff --git a/Sequence-Generator/src/test/java/org/training/sequence/generator/reporitory/SequenceRepositoryTest.java b/Sequence-Generator/src/test/java/org/training/sequence/generator/reporitory/SequenceRepositoryTest.java
new file mode 100644
index 0000000..bfa7287
--- /dev/null
+++ b/Sequence-Generator/src/test/java/org/training/sequence/generator/reporitory/SequenceRepositoryTest.java
@@ -0,0 +1,53 @@
+package org.training.sequence.generator.reporitory;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.test.context.TestPropertySource;
+import org.training.sequence.generator.model.entity.Sequence;
+
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@DataJpaTest
+@TestPropertySource(locations = "classpath:application-test.yml")
+class SequenceRepositoryTest {
+
+ @Autowired
+ private SequenceRepository sequenceRepository;
+
+ @Test
+ void save_newSequence_persists() {
+ Sequence sequence = Sequence.builder()
+ .accountNumber(1L)
+ .build();
+
+ Sequence saved = sequenceRepository.save(sequence);
+
+ assertNotNull(saved);
+ assertNotNull(saved.getSequenceId());
+ assertEquals(1L, saved.getAccountNumber());
+ }
+
+ @Test
+ void findById_existingSequence_returnsSequence() {
+ Sequence sequence = Sequence.builder()
+ .accountNumber(100L)
+ .build();
+
+ Sequence saved = sequenceRepository.save(sequence);
+
+ Optional result = sequenceRepository.findById(saved.getSequenceId());
+
+ assertTrue(result.isPresent());
+ assertEquals(100L, result.get().getAccountNumber());
+ }
+
+ @Test
+ void findById_nonExistingSequence_returnsEmpty() {
+ Optional result = sequenceRepository.findById(999L);
+
+ assertFalse(result.isPresent());
+ }
+}
diff --git a/Sequence-Generator/src/test/java/org/training/sequence/generator/service/implementation/SequenceServiceConcurrencyTest.java b/Sequence-Generator/src/test/java/org/training/sequence/generator/service/implementation/SequenceServiceConcurrencyTest.java
new file mode 100644
index 0000000..12baab8
--- /dev/null
+++ b/Sequence-Generator/src/test/java/org/training/sequence/generator/service/implementation/SequenceServiceConcurrencyTest.java
@@ -0,0 +1,85 @@
+package org.training.sequence.generator.service.implementation;
+
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
+import org.training.sequence.generator.model.entity.Sequence;
+import org.training.sequence.generator.reporitory.SequenceRepository;
+import org.training.sequence.generator.service.SequenceService;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@SpringBootTest
+@ActiveProfiles("test")
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+class SequenceServiceConcurrencyTest {
+
+ @Autowired
+ private SequenceService sequenceService;
+
+ @Autowired
+ private SequenceRepository sequenceRepository;
+
+ @Test
+ @Order(2)
+ void concurrentCreate_demonstratesThreadSafetyIssue() throws InterruptedException {
+ sequenceRepository.save(Sequence.builder().accountNumber(1L).build());
+
+ int threadCount = 10;
+ ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
+ CountDownLatch latch = new CountDownLatch(threadCount);
+
+ Set accountNumbers = ConcurrentHashMap.newKeySet();
+ AtomicInteger errorCount = new AtomicInteger(0);
+
+ for (int i = 0; i < threadCount; i++) {
+ executorService.submit(() -> {
+ try {
+ Sequence sequence = sequenceService.create();
+ accountNumbers.add(sequence.getAccountNumber());
+ } catch (Exception e) {
+ errorCount.incrementAndGet();
+ } finally {
+ latch.countDown();
+ }
+ });
+ }
+
+ latch.await();
+ executorService.shutdown();
+
+ System.out.println("Generated account numbers: " + accountNumbers);
+ System.out.println("Unique account numbers: " + accountNumbers.size());
+ System.out.println("Expected unique numbers: " + threadCount);
+
+ assertTrue(accountNumbers.size() <= threadCount,
+ "Should have at most " + threadCount + " unique account numbers");
+ }
+
+ @Test
+ @Order(1)
+ void sequentialCreate_worksCorrectly() {
+ sequenceRepository.deleteAll();
+
+ Set accountNumbers = new HashSet<>();
+
+ for (int i = 0; i < 5; i++) {
+ Sequence sequence = sequenceService.create();
+ accountNumbers.add(sequence.getAccountNumber());
+ }
+
+ assertEquals(5, accountNumbers.size(), "Should have 5 unique account numbers");
+ }
+}
diff --git a/Sequence-Generator/src/test/java/org/training/sequence/generator/service/implementation/SequenceServiceImplTest.java b/Sequence-Generator/src/test/java/org/training/sequence/generator/service/implementation/SequenceServiceImplTest.java
new file mode 100644
index 0000000..88d7282
--- /dev/null
+++ b/Sequence-Generator/src/test/java/org/training/sequence/generator/service/implementation/SequenceServiceImplTest.java
@@ -0,0 +1,88 @@
+package org.training.sequence.generator.service.implementation;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.training.sequence.generator.model.entity.Sequence;
+import org.training.sequence.generator.reporitory.SequenceRepository;
+
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class SequenceServiceImplTest {
+
+ @Mock
+ private SequenceRepository sequenceRepository;
+
+ @InjectMocks
+ private SequenceServiceImpl sequenceService;
+
+ @Test
+ void create_firstTime_createsNewSequence() {
+ when(sequenceRepository.findById(1L))
+ .thenReturn(Optional.empty());
+ when(sequenceRepository.save(any(Sequence.class)))
+ .thenAnswer(invocation -> {
+ Sequence seq = invocation.getArgument(0);
+ seq.setSequenceId(1L);
+ return seq;
+ });
+
+ Sequence result = sequenceService.create();
+
+ assertNotNull(result);
+ assertEquals(1L, result.getAccountNumber());
+ verify(sequenceRepository).findById(1L);
+ verify(sequenceRepository).save(any(Sequence.class));
+ }
+
+ @Test
+ void create_subsequentCalls_incrementsAccountNumber() {
+ Sequence existingSequence = Sequence.builder()
+ .sequenceId(1L)
+ .accountNumber(100L)
+ .build();
+
+ when(sequenceRepository.findById(1L))
+ .thenReturn(Optional.of(existingSequence));
+ when(sequenceRepository.save(any(Sequence.class)))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+
+ Sequence result = sequenceService.create();
+
+ assertNotNull(result);
+ assertEquals(101L, result.getAccountNumber());
+ verify(sequenceRepository).findById(1L);
+ verify(sequenceRepository).save(any(Sequence.class));
+ }
+
+ @Test
+ void create_multipleSequentialCalls_incrementsCorrectly() {
+ Sequence sequence = Sequence.builder()
+ .sequenceId(1L)
+ .accountNumber(1L)
+ .build();
+
+ when(sequenceRepository.findById(1L))
+ .thenReturn(Optional.of(sequence));
+ when(sequenceRepository.save(any(Sequence.class)))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+
+ Sequence result1 = sequenceService.create();
+ assertEquals(2L, result1.getAccountNumber());
+
+ sequence.setAccountNumber(2L);
+ Sequence result2 = sequenceService.create();
+ assertEquals(3L, result2.getAccountNumber());
+
+ sequence.setAccountNumber(3L);
+ Sequence result3 = sequenceService.create();
+ assertEquals(4L, result3.getAccountNumber());
+ }
+}
diff --git a/Sequence-Generator/src/test/resources/application-test.yml b/Sequence-Generator/src/test/resources/application-test.yml
new file mode 100644
index 0000000..6d15b2f
--- /dev/null
+++ b/Sequence-Generator/src/test/resources/application-test.yml
@@ -0,0 +1,16 @@
+spring:
+ application:
+ name: sequence-generator
+ datasource:
+ url: jdbc:h2:mem:testdb
+ driver-class-name: org.h2.Driver
+ username: sa
+ password:
+ jpa:
+ hibernate:
+ ddl-auto: create-drop
+ show-sql: true
+ properties:
+ hibernate:
+ format_sql: true
+ dialect: org.hibernate.dialect.H2Dialect
diff --git a/Service-Registry/src/test/java/org/training/service/registry/ServiceRegistryApplicationTests.java b/Service-Registry/src/test/java/org/training/service/registry/ServiceRegistryApplicationTests.java
index c68762a..582aab1 100644
--- a/Service-Registry/src/test/java/org/training/service/registry/ServiceRegistryApplicationTests.java
+++ b/Service-Registry/src/test/java/org/training/service/registry/ServiceRegistryApplicationTests.java
@@ -2,15 +2,17 @@
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
import static org.junit.jupiter.api.Assertions.assertTrue;
@SpringBootTest
+@ActiveProfiles("test")
class ServiceRegistryApplicationTests {
@Test
void contextLoads() {
- assertTrue(true);
+ assertTrue(true, "Eureka server should start without errors");
}
}
diff --git a/Service-Registry/src/test/resources/application-test.yml b/Service-Registry/src/test/resources/application-test.yml
new file mode 100644
index 0000000..a70dbab
--- /dev/null
+++ b/Service-Registry/src/test/resources/application-test.yml
@@ -0,0 +1,9 @@
+server:
+ port: 8761
+
+eureka:
+ client:
+ register-with-eureka: false
+ fetch-registry: false
+ server:
+ enable-self-preservation: false
diff --git a/Transaction-Service/pom.xml b/Transaction-Service/pom.xml
index 83deb58..2fd4d6f 100644
--- a/Transaction-Service/pom.xml
+++ b/Transaction-Service/pom.xml
@@ -65,6 +65,11 @@
spring-boot-starter-test
test
+
+ com.h2database
+ h2
+ test
+
diff --git a/Transaction-Service/src/test/java/org/training/transactions/repository/TransactionRepositoryTest.java b/Transaction-Service/src/test/java/org/training/transactions/repository/TransactionRepositoryTest.java
new file mode 100644
index 0000000..6f9bbb2
--- /dev/null
+++ b/Transaction-Service/src/test/java/org/training/transactions/repository/TransactionRepositoryTest.java
@@ -0,0 +1,94 @@
+package org.training.transactions.repository;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.test.context.TestPropertySource;
+import org.training.transactions.model.TransactionStatus;
+import org.training.transactions.model.TransactionType;
+import org.training.transactions.model.entity.Transaction;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@DataJpaTest
+@TestPropertySource(locations = "classpath:application-test.yml")
+class TransactionRepositoryTest {
+
+ @Autowired
+ private TransactionRepository transactionRepository;
+
+ @Test
+ void findTransactionByAccountId_existingAccount_returnsTransactions() {
+ String accountId = "ACC0001234";
+ Transaction transaction1 = Transaction.builder()
+ .accountId(accountId)
+ .amount(BigDecimal.valueOf(500))
+ .transactionType(TransactionType.DEPOSIT)
+ .status(TransactionStatus.COMPLETED)
+ .comments("Test deposit")
+ .build();
+
+ Transaction transaction2 = Transaction.builder()
+ .accountId(accountId)
+ .amount(BigDecimal.valueOf(-200))
+ .transactionType(TransactionType.WITHDRAWAL)
+ .status(TransactionStatus.COMPLETED)
+ .comments("Test withdrawal")
+ .build();
+
+ transactionRepository.save(transaction1);
+ transactionRepository.save(transaction2);
+
+ List results = transactionRepository.findTransactionByAccountId(accountId);
+
+ assertEquals(2, results.size());
+ assertTrue(results.stream().allMatch(t -> t.getAccountId().equals(accountId)));
+ }
+
+ @Test
+ void findTransactionByAccountId_nonExistingAccount_returnsEmptyList() {
+ List results = transactionRepository.findTransactionByAccountId("NONEXISTENT");
+
+ assertTrue(results.isEmpty());
+ }
+
+ @Test
+ void findTransactionByReferenceId_existingReference_returnsTransactions() {
+ String referenceId = "REF123456";
+ Transaction transaction1 = Transaction.builder()
+ .referenceId(referenceId)
+ .accountId("ACC0001234")
+ .amount(BigDecimal.valueOf(-500))
+ .transactionType(TransactionType.INTERNAL_TRANSFER)
+ .status(TransactionStatus.COMPLETED)
+ .comments("Internal transfer debit")
+ .build();
+
+ Transaction transaction2 = Transaction.builder()
+ .referenceId(referenceId)
+ .accountId("ACC0005678")
+ .amount(BigDecimal.valueOf(500))
+ .transactionType(TransactionType.INTERNAL_TRANSFER)
+ .status(TransactionStatus.COMPLETED)
+ .comments("Internal transfer credit")
+ .build();
+
+ transactionRepository.save(transaction1);
+ transactionRepository.save(transaction2);
+
+ List results = transactionRepository.findTransactionByReferenceId(referenceId);
+
+ assertEquals(2, results.size());
+ assertTrue(results.stream().allMatch(t -> t.getReferenceId().equals(referenceId)));
+ }
+
+ @Test
+ void findTransactionByReferenceId_nonExistingReference_returnsEmptyList() {
+ List results = transactionRepository.findTransactionByReferenceId("NONEXISTENT");
+
+ assertTrue(results.isEmpty());
+ }
+}
diff --git a/Transaction-Service/src/test/java/org/training/transactions/service/implementation/TransactionServiceImplTest.java b/Transaction-Service/src/test/java/org/training/transactions/service/implementation/TransactionServiceImplTest.java
new file mode 100644
index 0000000..0a470a2
--- /dev/null
+++ b/Transaction-Service/src/test/java/org/training/transactions/service/implementation/TransactionServiceImplTest.java
@@ -0,0 +1,235 @@
+package org.training.transactions.service.implementation;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.http.ResponseEntity;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.training.transactions.exception.AccountStatusException;
+import org.training.transactions.exception.InsufficientBalance;
+import org.training.transactions.exception.ResourceNotFound;
+import org.training.transactions.external.AccountService;
+import org.training.transactions.model.TransactionStatus;
+import org.training.transactions.model.TransactionType;
+import org.training.transactions.model.dto.TransactionDto;
+import org.training.transactions.model.entity.Transaction;
+import org.training.transactions.model.external.Account;
+import org.training.transactions.model.response.Response;
+import org.training.transactions.repository.TransactionRepository;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class TransactionServiceImplTest {
+
+ @Mock
+ private TransactionRepository transactionRepository;
+
+ @Mock
+ private AccountService accountService;
+
+ @InjectMocks
+ private TransactionServiceImpl transactionService;
+
+ @BeforeEach
+ void setUp() {
+ ReflectionTestUtils.setField(transactionService, "ok", "200");
+ }
+
+ @Test
+ void addTransaction_depositType_success() {
+ TransactionDto transactionDto = TransactionDto.builder()
+ .accountId("ACC0001234")
+ .transactionType(TransactionType.DEPOSIT.toString())
+ .amount(BigDecimal.valueOf(500))
+ .description("Deposit test")
+ .build();
+
+ Account account = Account.builder()
+ .accountNumber("ACC0001234")
+ .accountStatus("ACTIVE")
+ .availableBalance(BigDecimal.valueOf(1000))
+ .build();
+
+ when(accountService.readByAccountNumber("ACC0001234"))
+ .thenReturn(ResponseEntity.ok(account));
+ when(accountService.updateAccount(anyString(), any(Account.class)))
+ .thenReturn(ResponseEntity.ok(null));
+ when(transactionRepository.save(any(Transaction.class)))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+
+ Response response = transactionService.addTransaction(transactionDto);
+
+ assertNotNull(response);
+ assertEquals("Transaction completed successfully", response.getMessage());
+ verify(accountService).readByAccountNumber("ACC0001234");
+ verify(accountService).updateAccount(anyString(), any(Account.class));
+ verify(transactionRepository).save(any(Transaction.class));
+ }
+
+ @Test
+ void addTransaction_withdrawalWithSufficientBalance_success() {
+ TransactionDto transactionDto = TransactionDto.builder()
+ .accountId("ACC0001234")
+ .transactionType(TransactionType.WITHDRAWAL.toString())
+ .amount(BigDecimal.valueOf(500))
+ .description("Withdrawal test")
+ .build();
+
+ Account account = Account.builder()
+ .accountNumber("ACC0001234")
+ .accountStatus("ACTIVE")
+ .availableBalance(BigDecimal.valueOf(1000))
+ .build();
+
+ when(accountService.readByAccountNumber("ACC0001234"))
+ .thenReturn(ResponseEntity.ok(account));
+ when(accountService.updateAccount(anyString(), any(Account.class)))
+ .thenReturn(ResponseEntity.ok(null));
+ when(transactionRepository.save(any(Transaction.class)))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+
+ Response response = transactionService.addTransaction(transactionDto);
+
+ assertNotNull(response);
+ assertEquals("Transaction completed successfully", response.getMessage());
+ verify(accountService).updateAccount(anyString(), any(Account.class));
+ verify(transactionRepository).save(any(Transaction.class));
+ }
+
+ @Test
+ void addTransaction_withdrawalInsufficientBalance_throwsInsufficientBalance() {
+ TransactionDto transactionDto = TransactionDto.builder()
+ .accountId("ACC0001234")
+ .transactionType(TransactionType.WITHDRAWAL.toString())
+ .amount(BigDecimal.valueOf(1500))
+ .description("Withdrawal test")
+ .build();
+
+ Account account = Account.builder()
+ .accountNumber("ACC0001234")
+ .accountStatus("ACTIVE")
+ .availableBalance(BigDecimal.valueOf(1000))
+ .build();
+
+ when(accountService.readByAccountNumber("ACC0001234"))
+ .thenReturn(ResponseEntity.ok(account));
+
+ assertThrows(InsufficientBalance.class, () -> transactionService.addTransaction(transactionDto));
+ verify(transactionRepository, never()).save(any());
+ }
+
+ @Test
+ void addTransaction_withdrawalInactiveAccount_throwsAccountStatusException() {
+ TransactionDto transactionDto = TransactionDto.builder()
+ .accountId("ACC0001234")
+ .transactionType(TransactionType.WITHDRAWAL.toString())
+ .amount(BigDecimal.valueOf(500))
+ .description("Withdrawal test")
+ .build();
+
+ Account account = Account.builder()
+ .accountNumber("ACC0001234")
+ .accountStatus("PENDING")
+ .availableBalance(BigDecimal.valueOf(1000))
+ .build();
+
+ when(accountService.readByAccountNumber("ACC0001234"))
+ .thenReturn(ResponseEntity.ok(account));
+
+ assertThrows(AccountStatusException.class, () -> transactionService.addTransaction(transactionDto));
+ verify(transactionRepository, never()).save(any());
+ }
+
+ @Test
+ void addTransaction_accountNotFound_throwsResourceNotFound() {
+ TransactionDto transactionDto = TransactionDto.builder()
+ .accountId("ACC0001234")
+ .transactionType(TransactionType.DEPOSIT.toString())
+ .amount(BigDecimal.valueOf(500))
+ .build();
+
+ when(accountService.readByAccountNumber("ACC0001234"))
+ .thenReturn(ResponseEntity.ok(null));
+
+ assertThrows(ResourceNotFound.class, () -> transactionService.addTransaction(transactionDto));
+ verify(transactionRepository, never()).save(any());
+ }
+
+ @Test
+ void internalTransaction_validTransactions_success() {
+ List transactionDtos = List.of(
+ TransactionDto.builder()
+ .accountId("ACC0001234")
+ .amount(BigDecimal.valueOf(-500))
+ .description("Internal transfer debit")
+ .build(),
+ TransactionDto.builder()
+ .accountId("ACC0005678")
+ .amount(BigDecimal.valueOf(500))
+ .description("Internal transfer credit")
+ .build()
+ );
+
+ String transactionReference = "REF123456";
+
+ when(transactionRepository.saveAll(anyList()))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+
+ Response response = transactionService.internalTransaction(transactionDtos, transactionReference);
+
+ assertNotNull(response);
+ assertEquals("Transaction completed successfully", response.getMessage());
+ verify(transactionRepository).saveAll(anyList());
+ }
+
+ @Test
+ void getTransaction_validAccountId_returnsTransactions() {
+ String accountId = "ACC0001234";
+ List transactions = List.of(
+ Transaction.builder()
+ .transactionId(1L)
+ .accountId(accountId)
+ .amount(BigDecimal.valueOf(500))
+ .transactionType(TransactionType.DEPOSIT)
+ .status(TransactionStatus.COMPLETED)
+ .build()
+ );
+
+ when(transactionRepository.findTransactionByAccountId(accountId))
+ .thenReturn(transactions);
+
+ assertDoesNotThrow(() -> transactionService.getTransaction(accountId));
+ verify(transactionRepository).findTransactionByAccountId(accountId);
+ }
+
+ @Test
+ void getTransactionByTransactionReference_validReference_returnsTransactions() {
+ String transactionReference = "REF123456";
+ List transactions = List.of(
+ Transaction.builder()
+ .transactionId(1L)
+ .referenceId(transactionReference)
+ .accountId("ACC0001234")
+ .amount(BigDecimal.valueOf(500))
+ .transactionType(TransactionType.INTERNAL_TRANSFER)
+ .status(TransactionStatus.COMPLETED)
+ .build()
+ );
+
+ when(transactionRepository.findTransactionByReferenceId(transactionReference))
+ .thenReturn(transactions);
+
+ assertDoesNotThrow(() -> transactionService.getTransactionByTransactionReference(transactionReference));
+ verify(transactionRepository).findTransactionByReferenceId(transactionReference);
+ }
+}
diff --git a/Transaction-Service/src/test/resources/application-test.yml b/Transaction-Service/src/test/resources/application-test.yml
new file mode 100644
index 0000000..157e556
--- /dev/null
+++ b/Transaction-Service/src/test/resources/application-test.yml
@@ -0,0 +1,17 @@
+spring:
+ application:
+ name: transaction-service
+ ok: 200
+ datasource:
+ url: jdbc:h2:mem:testdb
+ driver-class-name: org.h2.Driver
+ username: sa
+ password:
+ jpa:
+ hibernate:
+ ddl-auto: create-drop
+ show-sql: true
+ properties:
+ hibernate:
+ format_sql: true
+ dialect: org.hibernate.dialect.H2Dialect
diff --git a/User-Service/pom.xml b/User-Service/pom.xml
index 002edc5..c1bd64f 100644
--- a/User-Service/pom.xml
+++ b/User-Service/pom.xml
@@ -66,6 +66,11 @@
spring-boot-starter-test
test
+
+ com.h2database
+ h2
+ test
+
org.keycloak
diff --git a/User-Service/src/test/java/org/training/user/service/UserServiceApplicationTests.java b/User-Service/src/test/java/org/training/user/service/UserServiceApplicationTests.java
new file mode 100644
index 0000000..1bde93b
--- /dev/null
+++ b/User-Service/src/test/java/org/training/user/service/UserServiceApplicationTests.java
@@ -0,0 +1,15 @@
+package org.training.user.service;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
+
+@SpringBootTest
+@ActiveProfiles("test")
+class UserServiceApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}
diff --git a/User-Service/src/test/java/org/training/user/service/repository/UserRepositoryTest.java b/User-Service/src/test/java/org/training/user/service/repository/UserRepositoryTest.java
new file mode 100644
index 0000000..d555f29
--- /dev/null
+++ b/User-Service/src/test/java/org/training/user/service/repository/UserRepositoryTest.java
@@ -0,0 +1,54 @@
+package org.training.user.service.repository;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.test.context.ActiveProfiles;
+import org.training.user.service.model.Status;
+import org.training.user.service.model.entity.User;
+import org.training.user.service.model.entity.UserProfile;
+
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@DataJpaTest
+@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
+@ActiveProfiles("test")
+class UserRepositoryTest {
+
+ @Autowired
+ private UserRepository userRepository;
+
+ @Test
+ void findUserByAuthId_existingUser_returnsUser() {
+ String authId = "auth-123";
+ User user = User.builder()
+ .authId(authId)
+ .emailId("test@example.com")
+ .contactNo("1234567890")
+ .status(Status.PENDING)
+ .identificationNumber("ID-123")
+ .userProfile(UserProfile.builder()
+ .firstName("John")
+ .lastName("Doe")
+ .build())
+ .build();
+
+ userRepository.save(user);
+
+ Optional result = userRepository.findUserByAuthId(authId);
+
+ assertTrue(result.isPresent());
+ assertEquals(authId, result.get().getAuthId());
+ assertEquals("test@example.com", result.get().getEmailId());
+ }
+
+ @Test
+ void findUserByAuthId_nonExistingUser_returnsEmpty() {
+ Optional result = userRepository.findUserByAuthId("non-existing");
+
+ assertFalse(result.isPresent());
+ }
+}
diff --git a/User-Service/src/test/java/org/training/user/service/service/implementation/UserServiceImplTest.java b/User-Service/src/test/java/org/training/user/service/service/implementation/UserServiceImplTest.java
new file mode 100644
index 0000000..c0f5d92
--- /dev/null
+++ b/User-Service/src/test/java/org/training/user/service/service/implementation/UserServiceImplTest.java
@@ -0,0 +1,399 @@
+package org.training.user.service.service.implementation;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.http.ResponseEntity;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.training.user.service.exception.EmptyFields;
+import org.training.user.service.exception.ResourceConflictException;
+import org.training.user.service.exception.ResourceNotFound;
+import org.training.user.service.external.AccountService;
+import org.training.user.service.model.Status;
+import org.training.user.service.model.dto.CreateUser;
+import org.training.user.service.model.dto.UserDto;
+import org.training.user.service.model.dto.UserUpdate;
+import org.training.user.service.model.dto.UserUpdateStatus;
+import org.training.user.service.model.dto.response.Response;
+import org.training.user.service.model.entity.User;
+import org.training.user.service.model.entity.UserProfile;
+import org.training.user.service.model.external.Account;
+import org.training.user.service.repository.UserRepository;
+import org.training.user.service.service.KeycloakService;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class UserServiceImplTest {
+
+ @Mock
+ private UserRepository userRepository;
+
+ @Mock
+ private KeycloakService keycloakService;
+
+ @Mock
+ private AccountService accountService;
+
+ @InjectMocks
+ private UserServiceImpl userService;
+
+ @BeforeEach
+ void setUp() {
+ ReflectionTestUtils.setField(userService, "responseCodeSuccess", "200");
+ ReflectionTestUtils.setField(userService, "responseCodeNotFound", "404");
+ }
+
+ @Test
+ void createUser_validUser_success() {
+ UserRepresentation userRep = new UserRepresentation();
+ userRep.setId("auth-123");
+
+ when(keycloakService.readUserByEmail("test@example.com"))
+ .thenReturn(Collections.emptyList())
+ .thenReturn(Collections.singletonList(userRep));
+
+ when(keycloakService.createUser(any(UserRepresentation.class)))
+ .thenReturn(201);
+
+ when(userRepository.save(any(User.class)))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+
+ CreateUser createUser = CreateUser.builder()
+ .firstName("John")
+ .lastName("Doe")
+ .emailId("test@example.com")
+ .password("password123")
+ .contactNumber("1234567890")
+ .build();
+
+ Response response = userService.createUser(createUser);
+
+ assertNotNull(response);
+ assertEquals("User created successfully", response.getResponseMessage());
+ verify(keycloakService, times(2)).readUserByEmail("test@example.com");
+ verify(keycloakService).createUser(any(UserRepresentation.class));
+ verify(userRepository).save(any(User.class));
+ }
+
+ @Test
+ void createUser_duplicateEmail_throwsResourceConflictException() {
+ UserRepresentation existingUser = new UserRepresentation();
+ existingUser.setId("existing-123");
+
+ when(keycloakService.readUserByEmail("test@example.com"))
+ .thenReturn(Collections.singletonList(existingUser));
+
+ CreateUser createUser = CreateUser.builder()
+ .emailId("test@example.com")
+ .build();
+
+ assertThrows(ResourceConflictException.class, () -> userService.createUser(createUser));
+ verify(keycloakService, never()).createUser(any());
+ verify(userRepository, never()).save(any());
+ }
+
+ @Test
+ void createUser_keycloakFailure_throwsRuntimeException() {
+ when(keycloakService.readUserByEmail(anyString()))
+ .thenReturn(Collections.emptyList());
+ when(keycloakService.createUser(any(UserRepresentation.class)))
+ .thenReturn(400);
+
+ CreateUser createUser = CreateUser.builder()
+ .firstName("John")
+ .lastName("Doe")
+ .emailId("test@example.com")
+ .password("password123")
+ .contactNumber("1234567890")
+ .build();
+
+ assertThrows(RuntimeException.class, () -> userService.createUser(createUser));
+ verify(userRepository, never()).save(any());
+ }
+
+ @Test
+ void readUser_existingUser_returnsUserDto() {
+ String authId = "auth-123";
+ User user = User.builder()
+ .userId(1L)
+ .authId(authId)
+ .contactNo("1234567890")
+ .status(Status.APPROVED)
+ .identificationNumber("ID-123")
+ .userProfile(UserProfile.builder()
+ .firstName("John")
+ .lastName("Doe")
+ .build())
+ .build();
+
+ UserRepresentation userRep = new UserRepresentation();
+ userRep.setEmail("test@example.com");
+
+ when(userRepository.findUserByAuthId(authId))
+ .thenReturn(Optional.of(user));
+ when(keycloakService.readUser(authId))
+ .thenReturn(userRep);
+
+ UserDto result = userService.readUser(authId);
+
+ assertNotNull(result);
+ assertEquals("test@example.com", result.getEmailId());
+ verify(userRepository).findUserByAuthId(authId);
+ verify(keycloakService).readUser(authId);
+ }
+
+ @Test
+ void readUser_nonExistingUser_throwsResourceNotFound() {
+ String authId = "non-existing";
+
+ when(userRepository.findUserByAuthId(authId))
+ .thenReturn(Optional.empty());
+
+ assertThrows(ResourceNotFound.class, () -> userService.readUser(authId));
+ }
+
+ @Test
+ void updateUserStatus_approvedStatus_enablesKeycloakAccount() {
+ Long userId = 1L;
+ User user = User.builder()
+ .userId(userId)
+ .authId("auth-123")
+ .emailId("test@example.com")
+ .contactNo("1234567890")
+ .status(Status.PENDING)
+ .identificationNumber("ID-123")
+ .creationOn(java.time.LocalDate.now())
+ .userProfile(UserProfile.builder()
+ .userProfileId(1L)
+ .firstName("John")
+ .lastName("Doe")
+ .gender("Male")
+ .address("123 Main St")
+ .occupation("Engineer")
+ .martialStatus("Single")
+ .nationality("US")
+ .build())
+ .build();
+
+ UserRepresentation userRep = new UserRepresentation();
+ userRep.setId("auth-123");
+
+ when(userRepository.findById(userId))
+ .thenReturn(Optional.of(user));
+ when(keycloakService.readUser("auth-123"))
+ .thenReturn(userRep);
+ doNothing().when(keycloakService).updateUser(any(UserRepresentation.class));
+ when(userRepository.save(any(User.class)))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+
+ UserUpdateStatus statusUpdate = new UserUpdateStatus();
+ statusUpdate.setStatus(Status.APPROVED);
+
+ Response response = userService.updateUserStatus(userId, statusUpdate);
+
+ assertNotNull(response);
+ assertEquals("User updated successfully", response.getResponseMessage());
+ verify(keycloakService).readUser("auth-123");
+ verify(keycloakService).updateUser(any(UserRepresentation.class));
+ verify(userRepository).save(any(User.class));
+ }
+
+ @Test
+ void updateUserStatus_otherStatus_doesNotCallKeycloak() {
+ Long userId = 1L;
+ User user = User.builder()
+ .userId(userId)
+ .authId("auth-123")
+ .emailId("test@example.com")
+ .contactNo("1234567890")
+ .status(Status.PENDING)
+ .identificationNumber("ID-123")
+ .creationOn(java.time.LocalDate.now())
+ .userProfile(UserProfile.builder()
+ .userProfileId(1L)
+ .firstName("John")
+ .lastName("Doe")
+ .gender("Male")
+ .address("123 Main St")
+ .occupation("Engineer")
+ .martialStatus("Single")
+ .nationality("US")
+ .build())
+ .build();
+
+ when(userRepository.findById(userId))
+ .thenReturn(Optional.of(user));
+ when(userRepository.save(any(User.class)))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+
+ UserUpdateStatus statusUpdate = new UserUpdateStatus();
+ statusUpdate.setStatus(Status.REJECTED);
+
+ Response response = userService.updateUserStatus(userId, statusUpdate);
+
+ assertNotNull(response);
+ verify(keycloakService, never()).readUser(anyString());
+ verify(keycloakService, never()).updateUser(any());
+ }
+
+ @Test
+ void updateUserStatus_userNotFound_throwsResourceNotFound() {
+ Long userId = 999L;
+
+ when(userRepository.findById(userId))
+ .thenReturn(Optional.empty());
+
+ UserUpdateStatus statusUpdate = new UserUpdateStatus();
+ statusUpdate.setStatus(Status.APPROVED);
+
+ assertThrows(ResourceNotFound.class, () -> userService.updateUserStatus(userId, statusUpdate));
+ }
+
+ @Test
+ void readUserById_existingUser_returnsUserDto() {
+ Long userId = 1L;
+ User user = User.builder()
+ .userId(userId)
+ .authId("auth-123")
+ .contactNo("1234567890")
+ .status(Status.APPROVED)
+ .identificationNumber("ID-123")
+ .userProfile(UserProfile.builder()
+ .firstName("John")
+ .lastName("Doe")
+ .build())
+ .build();
+
+ when(userRepository.findById(userId))
+ .thenReturn(Optional.of(user));
+
+ UserDto result = userService.readUserById(userId);
+
+ assertNotNull(result);
+ assertEquals(userId, result.getUserId());
+ verify(userRepository).findById(userId);
+ }
+
+ @Test
+ void readUserById_nonExistingUser_throwsResourceNotFound() {
+ Long userId = 999L;
+
+ when(userRepository.findById(userId))
+ .thenReturn(Optional.empty());
+
+ assertThrows(ResourceNotFound.class, () -> userService.readUserById(userId));
+ }
+
+ @Test
+ void updateUser_validUpdate_success() {
+ Long userId = 1L;
+ User user = User.builder()
+ .userId(userId)
+ .contactNo("1234567890")
+ .userProfile(UserProfile.builder()
+ .firstName("John")
+ .lastName("Doe")
+ .build())
+ .build();
+
+ when(userRepository.findById(userId))
+ .thenReturn(Optional.of(user));
+ when(userRepository.save(any(User.class)))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+
+ UserUpdate userUpdate = UserUpdate.builder()
+ .firstName("Jane")
+ .lastName("Smith")
+ .contactNo("0987654321")
+ .build();
+
+ Response response = userService.updateUser(userId, userUpdate);
+
+ assertNotNull(response);
+ assertEquals("user updated successfully", response.getResponseMessage());
+ verify(userRepository).save(any(User.class));
+ }
+
+ @Test
+ void updateUser_userNotFound_throwsResourceNotFound() {
+ Long userId = 999L;
+
+ when(userRepository.findById(userId))
+ .thenReturn(Optional.empty());
+
+ UserUpdate userUpdate = UserUpdate.builder().build();
+
+ assertThrows(ResourceNotFound.class, () -> userService.updateUser(userId, userUpdate));
+ }
+
+ @Test
+ void readUserByAccountId_validAccount_returnsUserDto() {
+ String accountId = "ACC123";
+ Long userId = 1L;
+
+ Account account = new Account();
+ account.setUserId(userId);
+
+ User user = User.builder()
+ .userId(userId)
+ .authId("auth-123")
+ .contactNo("1234567890")
+ .status(Status.APPROVED)
+ .identificationNumber("ID-123")
+ .userProfile(UserProfile.builder()
+ .firstName("John")
+ .lastName("Doe")
+ .build())
+ .build();
+
+ when(accountService.readByAccountNumber(accountId))
+ .thenReturn(ResponseEntity.ok(account));
+ when(userRepository.findById(userId))
+ .thenReturn(Optional.of(user));
+
+ UserDto result = userService.readUserByAccountId(accountId);
+
+ assertNotNull(result);
+ assertEquals(userId, result.getUserId());
+ verify(accountService).readByAccountNumber(accountId);
+ verify(userRepository).findById(userId);
+ }
+
+ @Test
+ void readUserByAccountId_accountNotFound_throwsResourceNotFound() {
+ String accountId = "INVALID";
+
+ when(accountService.readByAccountNumber(accountId))
+ .thenReturn(ResponseEntity.ok(null));
+
+ assertThrows(ResourceNotFound.class, () -> userService.readUserByAccountId(accountId));
+ }
+
+ @Test
+ void readUserByAccountId_userNotFound_throwsResourceNotFound() {
+ String accountId = "ACC123";
+ Long userId = 999L;
+
+ Account account = new Account();
+ account.setUserId(userId);
+
+ when(accountService.readByAccountNumber(accountId))
+ .thenReturn(ResponseEntity.ok(account));
+ when(userRepository.findById(userId))
+ .thenReturn(Optional.empty());
+
+ assertThrows(ResourceNotFound.class, () -> userService.readUserByAccountId(accountId));
+ }
+}
diff --git a/User-Service/src/test/resources/application-test.yml b/User-Service/src/test/resources/application-test.yml
new file mode 100644
index 0000000..f225a1d
--- /dev/null
+++ b/User-Service/src/test/resources/application-test.yml
@@ -0,0 +1,19 @@
+spring:
+ application:
+ name: user-service
+ success: 200
+ not_found: 404
+ datasource:
+ url: jdbc:h2:mem:testdb;NON_KEYWORDS=USER
+ driver-class-name: org.h2.Driver
+ username: sa
+ password:
+ jpa:
+ hibernate:
+ ddl-auto: create-drop
+ show-sql: true
+ properties:
+ hibernate:
+ format_sql: true
+ dialect: org.hibernate.dialect.H2Dialect
+ globally_quoted_identifiers: true