diff --git a/pom.xml b/pom.xml
index fc5bfeac..da3f7cc2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -67,6 +67,11 @@
spring-security-test
test
+
+ com.h2database
+ h2
+ test
+
diff --git a/src/test/java/com/example/bankapp/config/SecurityConfigTest.java b/src/test/java/com/example/bankapp/config/SecurityConfigTest.java
new file mode 100644
index 00000000..9ba4b3b5
--- /dev/null
+++ b/src/test/java/com/example/bankapp/config/SecurityConfigTest.java
@@ -0,0 +1,175 @@
+package com.example.bankapp.config;
+
+import com.example.bankapp.model.Account;
+import com.example.bankapp.service.AccountService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.web.servlet.MockMvc;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.when;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+class SecurityConfigTest {
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Autowired
+ private PasswordEncoder passwordEncoder;
+
+ @MockBean
+ private AccountService accountService;
+
+ private Account testAccount;
+
+ @BeforeEach
+ void setUp() {
+ testAccount = new Account();
+ testAccount.setId(1L);
+ testAccount.setUsername("testuser");
+ testAccount.setPassword("encodedPassword");
+ testAccount.setBalance(new BigDecimal("1000.00"));
+ testAccount.setTransactions(new ArrayList<>());
+ }
+
+ @Test
+ void passwordEncoder_ReturnsBCryptPasswordEncoder() {
+ assertNotNull(passwordEncoder);
+ assertTrue(passwordEncoder instanceof BCryptPasswordEncoder);
+ }
+
+ @Test
+ void passwordEncoder_EncodesPassword() {
+ String rawPassword = "testPassword123";
+ String encodedPassword = passwordEncoder.encode(rawPassword);
+
+ assertNotNull(encodedPassword);
+ assertNotEquals(rawPassword, encodedPassword);
+ assertTrue(passwordEncoder.matches(rawPassword, encodedPassword));
+ }
+
+ @Test
+ void passwordEncoder_DifferentEncodingsForSamePassword() {
+ String rawPassword = "testPassword123";
+ String encodedPassword1 = passwordEncoder.encode(rawPassword);
+ String encodedPassword2 = passwordEncoder.encode(rawPassword);
+
+ assertNotEquals(encodedPassword1, encodedPassword2);
+ assertTrue(passwordEncoder.matches(rawPassword, encodedPassword1));
+ assertTrue(passwordEncoder.matches(rawPassword, encodedPassword2));
+ }
+
+ @Test
+ void registerEndpoint_IsPublic() throws Exception {
+ mockMvc.perform(get("/register"))
+ .andExpect(status().isOk());
+ }
+
+ @Test
+ void loginEndpoint_IsPublic() throws Exception {
+ mockMvc.perform(get("/login"))
+ .andExpect(status().isOk());
+ }
+
+ @Test
+ void dashboardEndpoint_RequiresAuthentication() throws Exception {
+ mockMvc.perform(get("/dashboard"))
+ .andExpect(status().is3xxRedirection())
+ .andExpect(redirectedUrlPattern("**/login"));
+ }
+
+ @Test
+ void transactionsEndpoint_RequiresAuthentication() throws Exception {
+ mockMvc.perform(get("/transactions"))
+ .andExpect(status().is3xxRedirection())
+ .andExpect(redirectedUrlPattern("**/login"));
+ }
+
+ @Test
+ void depositEndpoint_RequiresAuthentication() throws Exception {
+ mockMvc.perform(post("/deposit")
+ .with(csrf())
+ .param("amount", "100.00"))
+ .andExpect(status().is3xxRedirection())
+ .andExpect(redirectedUrlPattern("**/login"));
+ }
+
+ @Test
+ void withdrawEndpoint_RequiresAuthentication() throws Exception {
+ mockMvc.perform(post("/withdraw")
+ .with(csrf())
+ .param("amount", "100.00"))
+ .andExpect(status().is3xxRedirection())
+ .andExpect(redirectedUrlPattern("**/login"));
+ }
+
+ @Test
+ void transferEndpoint_RequiresAuthentication() throws Exception {
+ mockMvc.perform(post("/transfer")
+ .with(csrf())
+ .param("toUsername", "recipient")
+ .param("amount", "100.00"))
+ .andExpect(status().is3xxRedirection())
+ .andExpect(redirectedUrlPattern("**/login"));
+ }
+
+ @Test
+ @WithMockUser(username = "testuser")
+ void authenticatedUser_CanAccessDashboard() throws Exception {
+ when(accountService.findAccountByUsername("testuser")).thenReturn(testAccount);
+
+ mockMvc.perform(get("/dashboard"))
+ .andExpect(status().isOk());
+ }
+
+ @Test
+ @WithMockUser(username = "testuser")
+ void authenticatedUser_CanAccessTransactions() throws Exception {
+ when(accountService.findAccountByUsername("testuser")).thenReturn(testAccount);
+ when(accountService.getTransactionHistory(testAccount)).thenReturn(new ArrayList<>());
+
+ mockMvc.perform(get("/transactions"))
+ .andExpect(status().isOk());
+ }
+
+ @Test
+ void logout_RedirectsToLoginWithLogoutParam() throws Exception {
+ mockMvc.perform(post("/logout")
+ .with(csrf()))
+ .andExpect(status().is3xxRedirection())
+ .andExpect(redirectedUrl("/login?logout"));
+ }
+
+ @Test
+ void loginPage_HasCorrectUrl() throws Exception {
+ mockMvc.perform(get("/login"))
+ .andExpect(status().isOk())
+ .andExpect(view().name("login"));
+ }
+
+ @Test
+ void registerPost_WithValidData_RedirectsToLogin() throws Exception {
+ mockMvc.perform(post("/register")
+ .with(csrf())
+ .param("username", "newuser")
+ .param("password", "password123"))
+ .andExpect(status().is3xxRedirection())
+ .andExpect(redirectedUrl("/login"));
+ }
+}
diff --git a/src/test/java/com/example/bankapp/controller/BankControllerTest.java b/src/test/java/com/example/bankapp/controller/BankControllerTest.java
new file mode 100644
index 00000000..f8aa1f59
--- /dev/null
+++ b/src/test/java/com/example/bankapp/controller/BankControllerTest.java
@@ -0,0 +1,300 @@
+package com.example.bankapp.controller;
+
+import com.example.bankapp.config.SecurityConfig;
+import com.example.bankapp.model.Account;
+import com.example.bankapp.model.Transaction;
+import com.example.bankapp.service.AccountService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.web.servlet.MockMvc;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+@WebMvcTest(BankController.class)
+@Import(SecurityConfig.class)
+class BankControllerTest {
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @MockBean
+ private AccountService accountService;
+
+ private Account testAccount;
+
+ @BeforeEach
+ void setUp() {
+ testAccount = new Account();
+ testAccount.setId(1L);
+ testAccount.setUsername("testuser");
+ testAccount.setPassword("encodedPassword");
+ testAccount.setBalance(new BigDecimal("1000.00"));
+ testAccount.setTransactions(new ArrayList<>());
+ }
+
+ @Test
+ @WithMockUser(username = "testuser")
+ void dashboard_Success() throws Exception {
+ when(accountService.findAccountByUsername("testuser")).thenReturn(testAccount);
+
+ mockMvc.perform(get("/dashboard"))
+ .andExpect(status().isOk())
+ .andExpect(view().name("dashboard"))
+ .andExpect(model().attributeExists("account"))
+ .andExpect(model().attribute("account", testAccount));
+
+ verify(accountService, times(1)).findAccountByUsername("testuser");
+ }
+
+ @Test
+ void dashboard_Unauthenticated() throws Exception {
+ mockMvc.perform(get("/dashboard"))
+ .andExpect(status().is3xxRedirection())
+ .andExpect(redirectedUrlPattern("**/login"));
+ }
+
+ @Test
+ void showRegistrationForm_Success() throws Exception {
+ mockMvc.perform(get("/register"))
+ .andExpect(status().isOk())
+ .andExpect(view().name("register"));
+ }
+
+ @Test
+ void registerAccount_Success() throws Exception {
+ when(accountService.registerAccount("newuser", "password123")).thenReturn(testAccount);
+
+ mockMvc.perform(post("/register")
+ .with(csrf())
+ .param("username", "newuser")
+ .param("password", "password123"))
+ .andExpect(status().is3xxRedirection())
+ .andExpect(redirectedUrl("/login"));
+
+ verify(accountService, times(1)).registerAccount("newuser", "password123");
+ }
+
+ @Test
+ void registerAccount_UsernameExists() throws Exception {
+ when(accountService.registerAccount("existinguser", "password123"))
+ .thenThrow(new RuntimeException("Username already exists"));
+
+ mockMvc.perform(post("/register")
+ .with(csrf())
+ .param("username", "existinguser")
+ .param("password", "password123"))
+ .andExpect(status().isOk())
+ .andExpect(view().name("register"))
+ .andExpect(model().attributeExists("error"))
+ .andExpect(model().attribute("error", "Username already exists"));
+
+ verify(accountService, times(1)).registerAccount("existinguser", "password123");
+ }
+
+ @Test
+ void login_Success() throws Exception {
+ mockMvc.perform(get("/login"))
+ .andExpect(status().isOk())
+ .andExpect(view().name("login"));
+ }
+
+ @Test
+ @WithMockUser(username = "testuser")
+ void deposit_Success() throws Exception {
+ when(accountService.findAccountByUsername("testuser")).thenReturn(testAccount);
+ doNothing().when(accountService).deposit(any(Account.class), any(BigDecimal.class));
+
+ mockMvc.perform(post("/deposit")
+ .with(csrf())
+ .param("amount", "500.00"))
+ .andExpect(status().is3xxRedirection())
+ .andExpect(redirectedUrl("/dashboard"));
+
+ verify(accountService, times(1)).findAccountByUsername("testuser");
+ verify(accountService, times(1)).deposit(eq(testAccount), eq(new BigDecimal("500.00")));
+ }
+
+ @Test
+ void deposit_Unauthenticated() throws Exception {
+ mockMvc.perform(post("/deposit")
+ .with(csrf())
+ .param("amount", "500.00"))
+ .andExpect(status().is3xxRedirection())
+ .andExpect(redirectedUrlPattern("**/login"));
+ }
+
+ @Test
+ @WithMockUser(username = "testuser")
+ void withdraw_Success() throws Exception {
+ when(accountService.findAccountByUsername("testuser")).thenReturn(testAccount);
+ doNothing().when(accountService).withdraw(any(Account.class), any(BigDecimal.class));
+
+ mockMvc.perform(post("/withdraw")
+ .with(csrf())
+ .param("amount", "300.00"))
+ .andExpect(status().is3xxRedirection())
+ .andExpect(redirectedUrl("/dashboard"));
+
+ verify(accountService, times(1)).findAccountByUsername("testuser");
+ verify(accountService, times(1)).withdraw(eq(testAccount), eq(new BigDecimal("300.00")));
+ }
+
+ @Test
+ @WithMockUser(username = "testuser")
+ void withdraw_InsufficientFunds() throws Exception {
+ when(accountService.findAccountByUsername("testuser")).thenReturn(testAccount);
+ doThrow(new RuntimeException("Insufficient funds"))
+ .when(accountService).withdraw(any(Account.class), any(BigDecimal.class));
+
+ mockMvc.perform(post("/withdraw")
+ .with(csrf())
+ .param("amount", "5000.00"))
+ .andExpect(status().isOk())
+ .andExpect(view().name("dashboard"))
+ .andExpect(model().attributeExists("error"))
+ .andExpect(model().attribute("error", "Insufficient funds"))
+ .andExpect(model().attributeExists("account"));
+
+ verify(accountService, times(1)).findAccountByUsername("testuser");
+ verify(accountService, times(1)).withdraw(eq(testAccount), eq(new BigDecimal("5000.00")));
+ }
+
+ @Test
+ void withdraw_Unauthenticated() throws Exception {
+ mockMvc.perform(post("/withdraw")
+ .with(csrf())
+ .param("amount", "300.00"))
+ .andExpect(status().is3xxRedirection())
+ .andExpect(redirectedUrlPattern("**/login"));
+ }
+
+ @Test
+ @WithMockUser(username = "testuser")
+ void transactionHistory_Success() throws Exception {
+ List transactions = new ArrayList<>();
+ Transaction t1 = new Transaction(new BigDecimal("100.00"), "Deposit", LocalDateTime.now(), testAccount);
+ Transaction t2 = new Transaction(new BigDecimal("50.00"), "Withdrawal", LocalDateTime.now(), testAccount);
+ transactions.add(t1);
+ transactions.add(t2);
+
+ when(accountService.findAccountByUsername("testuser")).thenReturn(testAccount);
+ when(accountService.getTransactionHistory(testAccount)).thenReturn(transactions);
+
+ mockMvc.perform(get("/transactions"))
+ .andExpect(status().isOk())
+ .andExpect(view().name("transactions"))
+ .andExpect(model().attributeExists("transactions"))
+ .andExpect(model().attribute("transactions", transactions));
+
+ verify(accountService, times(1)).findAccountByUsername("testuser");
+ verify(accountService, times(1)).getTransactionHistory(testAccount);
+ }
+
+ @Test
+ @WithMockUser(username = "testuser")
+ void transactionHistory_Empty() throws Exception {
+ when(accountService.findAccountByUsername("testuser")).thenReturn(testAccount);
+ when(accountService.getTransactionHistory(testAccount)).thenReturn(new ArrayList<>());
+
+ mockMvc.perform(get("/transactions"))
+ .andExpect(status().isOk())
+ .andExpect(view().name("transactions"))
+ .andExpect(model().attributeExists("transactions"));
+
+ verify(accountService, times(1)).findAccountByUsername("testuser");
+ verify(accountService, times(1)).getTransactionHistory(testAccount);
+ }
+
+ @Test
+ void transactionHistory_Unauthenticated() throws Exception {
+ mockMvc.perform(get("/transactions"))
+ .andExpect(status().is3xxRedirection())
+ .andExpect(redirectedUrlPattern("**/login"));
+ }
+
+ @Test
+ @WithMockUser(username = "testuser")
+ void transfer_Success() throws Exception {
+ when(accountService.findAccountByUsername("testuser")).thenReturn(testAccount);
+ doNothing().when(accountService).transferAmount(any(Account.class), anyString(), any(BigDecimal.class));
+
+ mockMvc.perform(post("/transfer")
+ .with(csrf())
+ .param("toUsername", "recipient")
+ .param("amount", "200.00"))
+ .andExpect(status().is3xxRedirection())
+ .andExpect(redirectedUrl("/dashboard"));
+
+ verify(accountService, times(1)).findAccountByUsername("testuser");
+ verify(accountService, times(1)).transferAmount(eq(testAccount), eq("recipient"), eq(new BigDecimal("200.00")));
+ }
+
+ @Test
+ @WithMockUser(username = "testuser")
+ void transfer_InsufficientFunds() throws Exception {
+ when(accountService.findAccountByUsername("testuser")).thenReturn(testAccount);
+ doThrow(new RuntimeException("Insufficient funds"))
+ .when(accountService).transferAmount(any(Account.class), anyString(), any(BigDecimal.class));
+
+ mockMvc.perform(post("/transfer")
+ .with(csrf())
+ .param("toUsername", "recipient")
+ .param("amount", "5000.00"))
+ .andExpect(status().isOk())
+ .andExpect(view().name("dashboard"))
+ .andExpect(model().attributeExists("error"))
+ .andExpect(model().attribute("error", "Insufficient funds"))
+ .andExpect(model().attributeExists("account"));
+
+ verify(accountService, times(1)).findAccountByUsername("testuser");
+ verify(accountService, times(1)).transferAmount(eq(testAccount), eq("recipient"), eq(new BigDecimal("5000.00")));
+ }
+
+ @Test
+ @WithMockUser(username = "testuser")
+ void transfer_RecipientNotFound() throws Exception {
+ when(accountService.findAccountByUsername("testuser")).thenReturn(testAccount);
+ doThrow(new RuntimeException("Recipient account not found"))
+ .when(accountService).transferAmount(any(Account.class), anyString(), any(BigDecimal.class));
+
+ mockMvc.perform(post("/transfer")
+ .with(csrf())
+ .param("toUsername", "nonexistent")
+ .param("amount", "200.00"))
+ .andExpect(status().isOk())
+ .andExpect(view().name("dashboard"))
+ .andExpect(model().attributeExists("error"))
+ .andExpect(model().attribute("error", "Recipient account not found"))
+ .andExpect(model().attributeExists("account"));
+
+ verify(accountService, times(1)).findAccountByUsername("testuser");
+ verify(accountService, times(1)).transferAmount(eq(testAccount), eq("nonexistent"), eq(new BigDecimal("200.00")));
+ }
+
+ @Test
+ void transfer_Unauthenticated() throws Exception {
+ mockMvc.perform(post("/transfer")
+ .with(csrf())
+ .param("toUsername", "recipient")
+ .param("amount", "200.00"))
+ .andExpect(status().is3xxRedirection())
+ .andExpect(redirectedUrlPattern("**/login"));
+ }
+}
diff --git a/src/test/java/com/example/bankapp/model/AccountTest.java b/src/test/java/com/example/bankapp/model/AccountTest.java
new file mode 100644
index 00000000..f54b572e
--- /dev/null
+++ b/src/test/java/com/example/bankapp/model/AccountTest.java
@@ -0,0 +1,103 @@
+package com.example.bankapp.model;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class AccountTest {
+
+ @Test
+ void testDefaultConstructor() {
+ Account account = new Account();
+ assertNull(account.getId());
+ assertNull(account.getUsername());
+ assertNull(account.getPassword());
+ assertNull(account.getBalance());
+ assertNull(account.getTransactions());
+ assertNull(account.getAuthorities());
+ }
+
+ @Test
+ void testParameterizedConstructor() {
+ List transactions = new ArrayList<>();
+ Collection authorities = Arrays.asList(new SimpleGrantedAuthority("USER"));
+
+ Account account = new Account("testuser", "password123", new BigDecimal("1000.00"), transactions, authorities);
+
+ assertEquals("testuser", account.getUsername());
+ assertEquals("password123", account.getPassword());
+ assertEquals(new BigDecimal("1000.00"), account.getBalance());
+ assertEquals(transactions, account.getTransactions());
+ assertEquals(authorities, account.getAuthorities());
+ }
+
+ @Test
+ void testSetAndGetId() {
+ Account account = new Account();
+ account.setId(1L);
+ assertEquals(1L, account.getId());
+ }
+
+ @Test
+ void testSetAndGetUsername() {
+ Account account = new Account();
+ account.setUsername("testuser");
+ assertEquals("testuser", account.getUsername());
+ }
+
+ @Test
+ void testSetAndGetPassword() {
+ Account account = new Account();
+ account.setPassword("password123");
+ assertEquals("password123", account.getPassword());
+ }
+
+ @Test
+ void testSetAndGetBalance() {
+ Account account = new Account();
+ BigDecimal balance = new BigDecimal("500.50");
+ account.setBalance(balance);
+ assertEquals(balance, account.getBalance());
+ }
+
+ @Test
+ void testSetAndGetTransactions() {
+ Account account = new Account();
+ List transactions = new ArrayList<>();
+ Transaction transaction = new Transaction();
+ transactions.add(transaction);
+ account.setTransactions(transactions);
+ assertEquals(transactions, account.getTransactions());
+ assertEquals(1, account.getTransactions().size());
+ }
+
+ @Test
+ void testSetAndGetAuthorities() {
+ Account account = new Account();
+ Collection authorities = Arrays.asList(
+ new SimpleGrantedAuthority("USER"),
+ new SimpleGrantedAuthority("ADMIN")
+ );
+ account.setAuthorities(authorities);
+ assertEquals(authorities, account.getAuthorities());
+ assertEquals(2, account.getAuthorities().size());
+ }
+
+ @Test
+ void testUserDetailsImplementation() {
+ Collection authorities = Arrays.asList(new SimpleGrantedAuthority("USER"));
+ Account account = new Account("testuser", "password123", new BigDecimal("1000.00"), null, authorities);
+
+ assertEquals("testuser", account.getUsername());
+ assertEquals("password123", account.getPassword());
+ assertEquals(authorities, account.getAuthorities());
+ }
+}
diff --git a/src/test/java/com/example/bankapp/model/TransactionTest.java b/src/test/java/com/example/bankapp/model/TransactionTest.java
new file mode 100644
index 00000000..a7d0ca13
--- /dev/null
+++ b/src/test/java/com/example/bankapp/model/TransactionTest.java
@@ -0,0 +1,128 @@
+package com.example.bankapp.model;
+
+import org.junit.jupiter.api.Test;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class TransactionTest {
+
+ @Test
+ void testDefaultConstructor() {
+ Transaction transaction = new Transaction();
+ assertNull(transaction.getId());
+ assertNull(transaction.getAmount());
+ assertNull(transaction.getType());
+ assertNull(transaction.getTimestamp());
+ assertNull(transaction.getAccount());
+ }
+
+ @Test
+ void testParameterizedConstructor() {
+ Account account = new Account();
+ account.setId(1L);
+ account.setUsername("testuser");
+
+ BigDecimal amount = new BigDecimal("100.00");
+ String type = "Deposit";
+ LocalDateTime timestamp = LocalDateTime.now();
+
+ Transaction transaction = new Transaction(amount, type, timestamp, account);
+
+ assertEquals(amount, transaction.getAmount());
+ assertEquals(type, transaction.getType());
+ assertEquals(timestamp, transaction.getTimestamp());
+ assertEquals(account, transaction.getAccount());
+ }
+
+ @Test
+ void testSetAndGetId() {
+ Transaction transaction = new Transaction();
+ transaction.setId(1L);
+ assertEquals(1L, transaction.getId());
+ }
+
+ @Test
+ void testSetAndGetAmount() {
+ Transaction transaction = new Transaction();
+ BigDecimal amount = new BigDecimal("250.75");
+ transaction.setAmount(amount);
+ assertEquals(amount, transaction.getAmount());
+ }
+
+ @Test
+ void testSetAndGetType() {
+ Transaction transaction = new Transaction();
+ transaction.setType("Withdrawal");
+ assertEquals("Withdrawal", transaction.getType());
+ }
+
+ @Test
+ void testSetAndGetTimestamp() {
+ Transaction transaction = new Transaction();
+ LocalDateTime timestamp = LocalDateTime.of(2024, 1, 15, 10, 30, 0);
+ transaction.setTimestamp(timestamp);
+ assertEquals(timestamp, transaction.getTimestamp());
+ }
+
+ @Test
+ void testSetAndGetAccount() {
+ Transaction transaction = new Transaction();
+ Account account = new Account();
+ account.setId(1L);
+ account.setUsername("testuser");
+ transaction.setAccount(account);
+ assertEquals(account, transaction.getAccount());
+ assertEquals("testuser", transaction.getAccount().getUsername());
+ }
+
+ @Test
+ void testDepositTransaction() {
+ Account account = new Account();
+ account.setUsername("depositor");
+
+ Transaction transaction = new Transaction(
+ new BigDecimal("500.00"),
+ "Deposit",
+ LocalDateTime.now(),
+ account
+ );
+
+ assertEquals("Deposit", transaction.getType());
+ assertEquals(new BigDecimal("500.00"), transaction.getAmount());
+ }
+
+ @Test
+ void testWithdrawalTransaction() {
+ Account account = new Account();
+ account.setUsername("withdrawer");
+
+ Transaction transaction = new Transaction(
+ new BigDecimal("200.00"),
+ "Withdrawal",
+ LocalDateTime.now(),
+ account
+ );
+
+ assertEquals("Withdrawal", transaction.getType());
+ assertEquals(new BigDecimal("200.00"), transaction.getAmount());
+ }
+
+ @Test
+ void testTransferTransaction() {
+ Account account = new Account();
+ account.setUsername("sender");
+
+ Transaction transaction = new Transaction(
+ new BigDecimal("300.00"),
+ "Transfer Out to recipient",
+ LocalDateTime.now(),
+ account
+ );
+
+ assertEquals("Transfer Out to recipient", transaction.getType());
+ assertEquals(new BigDecimal("300.00"), transaction.getAmount());
+ }
+}
diff --git a/src/test/java/com/example/bankapp/repository/AccountRepositoryTest.java b/src/test/java/com/example/bankapp/repository/AccountRepositoryTest.java
new file mode 100644
index 00000000..bf50687b
--- /dev/null
+++ b/src/test/java/com/example/bankapp/repository/AccountRepositoryTest.java
@@ -0,0 +1,178 @@
+package com.example.bankapp.repository;
+
+import com.example.bankapp.model.Account;
+import org.junit.jupiter.api.BeforeEach;
+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.boot.test.autoconfigure.orm.jpa.TestEntityManager;
+
+import java.math.BigDecimal;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@DataJpaTest
+class AccountRepositoryTest {
+
+ @Autowired
+ private TestEntityManager entityManager;
+
+ @Autowired
+ private AccountRepository accountRepository;
+
+ private Account testAccount;
+
+ @BeforeEach
+ void setUp() {
+ testAccount = new Account();
+ testAccount.setUsername("testuser");
+ testAccount.setPassword("encodedPassword");
+ testAccount.setBalance(new BigDecimal("1000.00"));
+ }
+
+ @Test
+ void findByUsername_Success() {
+ entityManager.persist(testAccount);
+ entityManager.flush();
+
+ Optional found = accountRepository.findByUsername("testuser");
+
+ assertTrue(found.isPresent());
+ assertEquals("testuser", found.get().getUsername());
+ assertEquals("encodedPassword", found.get().getPassword());
+ assertEquals(new BigDecimal("1000.00"), found.get().getBalance());
+ }
+
+ @Test
+ void findByUsername_NotFound() {
+ Optional found = accountRepository.findByUsername("nonexistent");
+
+ assertFalse(found.isPresent());
+ }
+
+ @Test
+ void findByUsername_MultipleAccounts() {
+ Account account1 = new Account();
+ account1.setUsername("user1");
+ account1.setPassword("password1");
+ account1.setBalance(new BigDecimal("500.00"));
+
+ Account account2 = new Account();
+ account2.setUsername("user2");
+ account2.setPassword("password2");
+ account2.setBalance(new BigDecimal("750.00"));
+
+ entityManager.persist(account1);
+ entityManager.persist(account2);
+ entityManager.flush();
+
+ Optional foundUser1 = accountRepository.findByUsername("user1");
+ Optional foundUser2 = accountRepository.findByUsername("user2");
+
+ assertTrue(foundUser1.isPresent());
+ assertTrue(foundUser2.isPresent());
+ assertEquals("user1", foundUser1.get().getUsername());
+ assertEquals("user2", foundUser2.get().getUsername());
+ assertEquals(new BigDecimal("500.00"), foundUser1.get().getBalance());
+ assertEquals(new BigDecimal("750.00"), foundUser2.get().getBalance());
+ }
+
+ @Test
+ void save_NewAccount() {
+ Account savedAccount = accountRepository.save(testAccount);
+
+ assertNotNull(savedAccount.getId());
+ assertEquals("testuser", savedAccount.getUsername());
+ assertEquals("encodedPassword", savedAccount.getPassword());
+ assertEquals(new BigDecimal("1000.00"), savedAccount.getBalance());
+ }
+
+ @Test
+ void save_UpdateAccount() {
+ entityManager.persist(testAccount);
+ entityManager.flush();
+
+ testAccount.setBalance(new BigDecimal("1500.00"));
+ Account updatedAccount = accountRepository.save(testAccount);
+
+ assertEquals(new BigDecimal("1500.00"), updatedAccount.getBalance());
+ }
+
+ @Test
+ void findById_Success() {
+ entityManager.persist(testAccount);
+ entityManager.flush();
+
+ Optional found = accountRepository.findById(testAccount.getId());
+
+ assertTrue(found.isPresent());
+ assertEquals(testAccount.getId(), found.get().getId());
+ assertEquals("testuser", found.get().getUsername());
+ }
+
+ @Test
+ void findById_NotFound() {
+ Optional found = accountRepository.findById(999L);
+
+ assertFalse(found.isPresent());
+ }
+
+ @Test
+ void delete_Account() {
+ entityManager.persist(testAccount);
+ entityManager.flush();
+
+ Long accountId = testAccount.getId();
+ accountRepository.delete(testAccount);
+ entityManager.flush();
+
+ Optional found = accountRepository.findById(accountId);
+ assertFalse(found.isPresent());
+ }
+
+ @Test
+ void findAll_Empty() {
+ assertTrue(accountRepository.findAll().isEmpty());
+ }
+
+ @Test
+ void findAll_MultipleAccounts() {
+ Account account1 = new Account();
+ account1.setUsername("user1");
+ account1.setPassword("password1");
+ account1.setBalance(new BigDecimal("500.00"));
+
+ Account account2 = new Account();
+ account2.setUsername("user2");
+ account2.setPassword("password2");
+ account2.setBalance(new BigDecimal("750.00"));
+
+ entityManager.persist(account1);
+ entityManager.persist(account2);
+ entityManager.flush();
+
+ assertEquals(2, accountRepository.findAll().size());
+ }
+
+ @Test
+ void count_Accounts() {
+ entityManager.persist(testAccount);
+ entityManager.flush();
+
+ assertEquals(1, accountRepository.count());
+ }
+
+ @Test
+ void existsById_True() {
+ entityManager.persist(testAccount);
+ entityManager.flush();
+
+ assertTrue(accountRepository.existsById(testAccount.getId()));
+ }
+
+ @Test
+ void existsById_False() {
+ assertFalse(accountRepository.existsById(999L));
+ }
+}
diff --git a/src/test/java/com/example/bankapp/repository/TransactionRepositoryTest.java b/src/test/java/com/example/bankapp/repository/TransactionRepositoryTest.java
new file mode 100644
index 00000000..834d467e
--- /dev/null
+++ b/src/test/java/com/example/bankapp/repository/TransactionRepositoryTest.java
@@ -0,0 +1,250 @@
+package com.example.bankapp.repository;
+
+import com.example.bankapp.model.Account;
+import com.example.bankapp.model.Transaction;
+import org.junit.jupiter.api.BeforeEach;
+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.boot.test.autoconfigure.orm.jpa.TestEntityManager;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@DataJpaTest
+class TransactionRepositoryTest {
+
+ @Autowired
+ private TestEntityManager entityManager;
+
+ @Autowired
+ private TransactionRepository transactionRepository;
+
+ private Account testAccount;
+ private Transaction testTransaction;
+
+ @BeforeEach
+ void setUp() {
+ testAccount = new Account();
+ testAccount.setUsername("testuser");
+ testAccount.setPassword("encodedPassword");
+ testAccount.setBalance(new BigDecimal("1000.00"));
+ entityManager.persist(testAccount);
+
+ testTransaction = new Transaction();
+ testTransaction.setAmount(new BigDecimal("100.00"));
+ testTransaction.setType("Deposit");
+ testTransaction.setTimestamp(LocalDateTime.now());
+ testTransaction.setAccount(testAccount);
+ }
+
+ @Test
+ void findByAccountId_Success() {
+ entityManager.persist(testTransaction);
+ entityManager.flush();
+
+ List transactions = transactionRepository.findByAccountId(testAccount.getId());
+
+ assertFalse(transactions.isEmpty());
+ assertEquals(1, transactions.size());
+ assertEquals(new BigDecimal("100.00"), transactions.get(0).getAmount());
+ assertEquals("Deposit", transactions.get(0).getType());
+ }
+
+ @Test
+ void findByAccountId_MultipleTransactions() {
+ Transaction t1 = new Transaction(new BigDecimal("100.00"), "Deposit", LocalDateTime.now(), testAccount);
+ Transaction t2 = new Transaction(new BigDecimal("50.00"), "Withdrawal", LocalDateTime.now(), testAccount);
+ Transaction t3 = new Transaction(new BigDecimal("200.00"), "Transfer In from sender", LocalDateTime.now(), testAccount);
+
+ entityManager.persist(t1);
+ entityManager.persist(t2);
+ entityManager.persist(t3);
+ entityManager.flush();
+
+ List transactions = transactionRepository.findByAccountId(testAccount.getId());
+
+ assertEquals(3, transactions.size());
+ }
+
+ @Test
+ void findByAccountId_NoTransactions() {
+ List transactions = transactionRepository.findByAccountId(testAccount.getId());
+
+ assertTrue(transactions.isEmpty());
+ }
+
+ @Test
+ void findByAccountId_DifferentAccounts() {
+ Account account2 = new Account();
+ account2.setUsername("user2");
+ account2.setPassword("password2");
+ account2.setBalance(new BigDecimal("500.00"));
+ entityManager.persist(account2);
+
+ Transaction t1 = new Transaction(new BigDecimal("100.00"), "Deposit", LocalDateTime.now(), testAccount);
+ Transaction t2 = new Transaction(new BigDecimal("200.00"), "Deposit", LocalDateTime.now(), account2);
+
+ entityManager.persist(t1);
+ entityManager.persist(t2);
+ entityManager.flush();
+
+ List transactionsAccount1 = transactionRepository.findByAccountId(testAccount.getId());
+ List transactionsAccount2 = transactionRepository.findByAccountId(account2.getId());
+
+ assertEquals(1, transactionsAccount1.size());
+ assertEquals(1, transactionsAccount2.size());
+ assertEquals(new BigDecimal("100.00"), transactionsAccount1.get(0).getAmount());
+ assertEquals(new BigDecimal("200.00"), transactionsAccount2.get(0).getAmount());
+ }
+
+ @Test
+ void findByAccountId_NonExistentAccount() {
+ List transactions = transactionRepository.findByAccountId(999L);
+
+ assertTrue(transactions.isEmpty());
+ }
+
+ @Test
+ void save_NewTransaction() {
+ Transaction savedTransaction = transactionRepository.save(testTransaction);
+
+ assertNotNull(savedTransaction.getId());
+ assertEquals(new BigDecimal("100.00"), savedTransaction.getAmount());
+ assertEquals("Deposit", savedTransaction.getType());
+ assertEquals(testAccount, savedTransaction.getAccount());
+ }
+
+ @Test
+ void save_UpdateTransaction() {
+ entityManager.persist(testTransaction);
+ entityManager.flush();
+
+ testTransaction.setAmount(new BigDecimal("150.00"));
+ Transaction updatedTransaction = transactionRepository.save(testTransaction);
+
+ assertEquals(new BigDecimal("150.00"), updatedTransaction.getAmount());
+ }
+
+ @Test
+ void findById_Success() {
+ entityManager.persist(testTransaction);
+ entityManager.flush();
+
+ Optional found = transactionRepository.findById(testTransaction.getId());
+
+ assertTrue(found.isPresent());
+ assertEquals(testTransaction.getId(), found.get().getId());
+ assertEquals("Deposit", found.get().getType());
+ }
+
+ @Test
+ void findById_NotFound() {
+ Optional found = transactionRepository.findById(999L);
+
+ assertFalse(found.isPresent());
+ }
+
+ @Test
+ void delete_Transaction() {
+ entityManager.persist(testTransaction);
+ entityManager.flush();
+
+ Long transactionId = testTransaction.getId();
+ transactionRepository.delete(testTransaction);
+ entityManager.flush();
+
+ Optional found = transactionRepository.findById(transactionId);
+ assertFalse(found.isPresent());
+ }
+
+ @Test
+ void findAll_Empty() {
+ assertTrue(transactionRepository.findAll().isEmpty());
+ }
+
+ @Test
+ void findAll_MultipleTransactions() {
+ Transaction t1 = new Transaction(new BigDecimal("100.00"), "Deposit", LocalDateTime.now(), testAccount);
+ Transaction t2 = new Transaction(new BigDecimal("50.00"), "Withdrawal", LocalDateTime.now(), testAccount);
+
+ entityManager.persist(t1);
+ entityManager.persist(t2);
+ entityManager.flush();
+
+ assertEquals(2, transactionRepository.findAll().size());
+ }
+
+ @Test
+ void count_Transactions() {
+ entityManager.persist(testTransaction);
+ entityManager.flush();
+
+ assertEquals(1, transactionRepository.count());
+ }
+
+ @Test
+ void existsById_True() {
+ entityManager.persist(testTransaction);
+ entityManager.flush();
+
+ assertTrue(transactionRepository.existsById(testTransaction.getId()));
+ }
+
+ @Test
+ void existsById_False() {
+ assertFalse(transactionRepository.existsById(999L));
+ }
+
+ @Test
+ void saveDepositTransaction() {
+ Transaction deposit = new Transaction(
+ new BigDecimal("500.00"),
+ "Deposit",
+ LocalDateTime.now(),
+ testAccount
+ );
+
+ Transaction saved = transactionRepository.save(deposit);
+
+ assertNotNull(saved.getId());
+ assertEquals("Deposit", saved.getType());
+ assertEquals(new BigDecimal("500.00"), saved.getAmount());
+ }
+
+ @Test
+ void saveWithdrawalTransaction() {
+ Transaction withdrawal = new Transaction(
+ new BigDecimal("200.00"),
+ "Withdrawal",
+ LocalDateTime.now(),
+ testAccount
+ );
+
+ Transaction saved = transactionRepository.save(withdrawal);
+
+ assertNotNull(saved.getId());
+ assertEquals("Withdrawal", saved.getType());
+ assertEquals(new BigDecimal("200.00"), saved.getAmount());
+ }
+
+ @Test
+ void saveTransferTransaction() {
+ Transaction transfer = new Transaction(
+ new BigDecimal("300.00"),
+ "Transfer Out to recipient",
+ LocalDateTime.now(),
+ testAccount
+ );
+
+ Transaction saved = transactionRepository.save(transfer);
+
+ assertNotNull(saved.getId());
+ assertTrue(saved.getType().contains("Transfer"));
+ assertEquals(new BigDecimal("300.00"), saved.getAmount());
+ }
+}
diff --git a/src/test/java/com/example/bankapp/service/AccountServiceTest.java b/src/test/java/com/example/bankapp/service/AccountServiceTest.java
new file mode 100644
index 00000000..cecafb53
--- /dev/null
+++ b/src/test/java/com/example/bankapp/service/AccountServiceTest.java
@@ -0,0 +1,366 @@
+package com.example.bankapp.service;
+
+import com.example.bankapp.model.Account;
+import com.example.bankapp.model.Transaction;
+import com.example.bankapp.repository.AccountRepository;
+import com.example.bankapp.repository.TransactionRepository;
+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.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+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 AccountServiceTest {
+
+ @Mock
+ private AccountRepository accountRepository;
+
+ @Mock
+ private TransactionRepository transactionRepository;
+
+ @Mock
+ private PasswordEncoder passwordEncoder;
+
+ @InjectMocks
+ private AccountService accountService;
+
+ private Account testAccount;
+
+ @BeforeEach
+ void setUp() {
+ testAccount = new Account();
+ testAccount.setId(1L);
+ testAccount.setUsername("testuser");
+ testAccount.setPassword("encodedPassword");
+ testAccount.setBalance(new BigDecimal("1000.00"));
+ testAccount.setTransactions(new ArrayList<>());
+ }
+
+ @Test
+ void findAccountByUsername_Success() {
+ when(accountRepository.findByUsername("testuser")).thenReturn(Optional.of(testAccount));
+
+ Account result = accountService.findAccountByUsername("testuser");
+
+ assertNotNull(result);
+ assertEquals("testuser", result.getUsername());
+ assertEquals(new BigDecimal("1000.00"), result.getBalance());
+ verify(accountRepository, times(1)).findByUsername("testuser");
+ }
+
+ @Test
+ void findAccountByUsername_NotFound() {
+ when(accountRepository.findByUsername("nonexistent")).thenReturn(Optional.empty());
+
+ RuntimeException exception = assertThrows(RuntimeException.class, () -> {
+ accountService.findAccountByUsername("nonexistent");
+ });
+
+ assertEquals("Account not found", exception.getMessage());
+ verify(accountRepository, times(1)).findByUsername("nonexistent");
+ }
+
+ @Test
+ void registerAccount_Success() {
+ when(accountRepository.findByUsername("newuser")).thenReturn(Optional.empty());
+ when(passwordEncoder.encode("password123")).thenReturn("encodedPassword");
+ when(accountRepository.save(any(Account.class))).thenAnswer(invocation -> {
+ Account savedAccount = invocation.getArgument(0);
+ savedAccount.setId(2L);
+ return savedAccount;
+ });
+
+ Account result = accountService.registerAccount("newuser", "password123");
+
+ assertNotNull(result);
+ assertEquals("newuser", result.getUsername());
+ assertEquals("encodedPassword", result.getPassword());
+ assertEquals(BigDecimal.ZERO, result.getBalance());
+ verify(accountRepository, times(1)).findByUsername("newuser");
+ verify(passwordEncoder, times(1)).encode("password123");
+ verify(accountRepository, times(1)).save(any(Account.class));
+ }
+
+ @Test
+ void registerAccount_UsernameAlreadyExists() {
+ when(accountRepository.findByUsername("existinguser")).thenReturn(Optional.of(testAccount));
+
+ RuntimeException exception = assertThrows(RuntimeException.class, () -> {
+ accountService.registerAccount("existinguser", "password123");
+ });
+
+ assertEquals("Username already exists", exception.getMessage());
+ verify(accountRepository, times(1)).findByUsername("existinguser");
+ verify(accountRepository, never()).save(any(Account.class));
+ }
+
+ @Test
+ void deposit_Success() {
+ BigDecimal depositAmount = new BigDecimal("500.00");
+ when(accountRepository.save(any(Account.class))).thenReturn(testAccount);
+ when(transactionRepository.save(any(Transaction.class))).thenAnswer(invocation -> invocation.getArgument(0));
+
+ accountService.deposit(testAccount, depositAmount);
+
+ assertEquals(new BigDecimal("1500.00"), testAccount.getBalance());
+ verify(accountRepository, times(1)).save(testAccount);
+ verify(transactionRepository, times(1)).save(any(Transaction.class));
+ }
+
+ @Test
+ void deposit_VerifyTransactionCreated() {
+ BigDecimal depositAmount = new BigDecimal("250.00");
+ when(accountRepository.save(any(Account.class))).thenReturn(testAccount);
+ when(transactionRepository.save(any(Transaction.class))).thenAnswer(invocation -> {
+ Transaction savedTransaction = invocation.getArgument(0);
+ assertEquals(depositAmount, savedTransaction.getAmount());
+ assertEquals("Deposit", savedTransaction.getType());
+ assertEquals(testAccount, savedTransaction.getAccount());
+ assertNotNull(savedTransaction.getTimestamp());
+ return savedTransaction;
+ });
+
+ accountService.deposit(testAccount, depositAmount);
+
+ verify(transactionRepository, times(1)).save(any(Transaction.class));
+ }
+
+ @Test
+ void withdraw_Success() {
+ BigDecimal withdrawAmount = new BigDecimal("300.00");
+ when(accountRepository.save(any(Account.class))).thenReturn(testAccount);
+ when(transactionRepository.save(any(Transaction.class))).thenAnswer(invocation -> invocation.getArgument(0));
+
+ accountService.withdraw(testAccount, withdrawAmount);
+
+ assertEquals(new BigDecimal("700.00"), testAccount.getBalance());
+ verify(accountRepository, times(1)).save(testAccount);
+ verify(transactionRepository, times(1)).save(any(Transaction.class));
+ }
+
+ @Test
+ void withdraw_InsufficientFunds() {
+ BigDecimal withdrawAmount = new BigDecimal("1500.00");
+
+ RuntimeException exception = assertThrows(RuntimeException.class, () -> {
+ accountService.withdraw(testAccount, withdrawAmount);
+ });
+
+ assertEquals("Insufficient funds", exception.getMessage());
+ assertEquals(new BigDecimal("1000.00"), testAccount.getBalance());
+ verify(accountRepository, never()).save(any(Account.class));
+ verify(transactionRepository, never()).save(any(Transaction.class));
+ }
+
+ @Test
+ void withdraw_ExactBalance() {
+ BigDecimal withdrawAmount = new BigDecimal("1000.00");
+ when(accountRepository.save(any(Account.class))).thenReturn(testAccount);
+ when(transactionRepository.save(any(Transaction.class))).thenAnswer(invocation -> invocation.getArgument(0));
+
+ accountService.withdraw(testAccount, withdrawAmount);
+
+ assertEquals(BigDecimal.ZERO.setScale(2), testAccount.getBalance().setScale(2));
+ verify(accountRepository, times(1)).save(testAccount);
+ verify(transactionRepository, times(1)).save(any(Transaction.class));
+ }
+
+ @Test
+ void withdraw_VerifyTransactionCreated() {
+ BigDecimal withdrawAmount = new BigDecimal("200.00");
+ when(accountRepository.save(any(Account.class))).thenReturn(testAccount);
+ when(transactionRepository.save(any(Transaction.class))).thenAnswer(invocation -> {
+ Transaction savedTransaction = invocation.getArgument(0);
+ assertEquals(withdrawAmount, savedTransaction.getAmount());
+ assertEquals("Withdrawal", savedTransaction.getType());
+ assertEquals(testAccount, savedTransaction.getAccount());
+ assertNotNull(savedTransaction.getTimestamp());
+ return savedTransaction;
+ });
+
+ accountService.withdraw(testAccount, withdrawAmount);
+
+ verify(transactionRepository, times(1)).save(any(Transaction.class));
+ }
+
+ @Test
+ void getTransactionHistory_Success() {
+ List transactions = new ArrayList<>();
+ Transaction t1 = new Transaction(new BigDecimal("100.00"), "Deposit", null, testAccount);
+ Transaction t2 = new Transaction(new BigDecimal("50.00"), "Withdrawal", null, testAccount);
+ transactions.add(t1);
+ transactions.add(t2);
+
+ when(transactionRepository.findByAccountId(1L)).thenReturn(transactions);
+
+ List result = accountService.getTransactionHistory(testAccount);
+
+ assertNotNull(result);
+ assertEquals(2, result.size());
+ verify(transactionRepository, times(1)).findByAccountId(1L);
+ }
+
+ @Test
+ void getTransactionHistory_Empty() {
+ when(transactionRepository.findByAccountId(1L)).thenReturn(new ArrayList<>());
+
+ List result = accountService.getTransactionHistory(testAccount);
+
+ assertNotNull(result);
+ assertTrue(result.isEmpty());
+ verify(transactionRepository, times(1)).findByAccountId(1L);
+ }
+
+ @Test
+ void loadUserByUsername_Success() {
+ when(accountRepository.findByUsername("testuser")).thenReturn(Optional.of(testAccount));
+
+ UserDetails result = accountService.loadUserByUsername("testuser");
+
+ assertNotNull(result);
+ assertEquals("testuser", result.getUsername());
+ assertEquals("encodedPassword", result.getPassword());
+ assertNotNull(result.getAuthorities());
+ verify(accountRepository, times(1)).findByUsername("testuser");
+ }
+
+ @Test
+ void loadUserByUsername_NotFound() {
+ when(accountRepository.findByUsername("nonexistent")).thenReturn(Optional.empty());
+
+ assertThrows(RuntimeException.class, () -> {
+ accountService.loadUserByUsername("nonexistent");
+ });
+
+ verify(accountRepository, times(1)).findByUsername("nonexistent");
+ }
+
+ @Test
+ void authorities_ReturnsUserAuthority() {
+ Collection extends GrantedAuthority> authorities = accountService.authorities();
+
+ assertNotNull(authorities);
+ assertEquals(1, authorities.size());
+ assertTrue(authorities.stream().anyMatch(a -> a.getAuthority().equals("USER")));
+ }
+
+ @Test
+ void transferAmount_Success() {
+ Account toAccount = new Account();
+ toAccount.setId(2L);
+ toAccount.setUsername("recipient");
+ toAccount.setBalance(new BigDecimal("500.00"));
+
+ BigDecimal transferAmount = new BigDecimal("200.00");
+
+ when(accountRepository.findByUsername("recipient")).thenReturn(Optional.of(toAccount));
+ when(accountRepository.save(any(Account.class))).thenAnswer(invocation -> invocation.getArgument(0));
+ when(transactionRepository.save(any(Transaction.class))).thenAnswer(invocation -> invocation.getArgument(0));
+
+ accountService.transferAmount(testAccount, "recipient", transferAmount);
+
+ assertEquals(new BigDecimal("800.00"), testAccount.getBalance());
+ assertEquals(new BigDecimal("700.00"), toAccount.getBalance());
+ verify(accountRepository, times(2)).save(any(Account.class));
+ verify(transactionRepository, times(2)).save(any(Transaction.class));
+ }
+
+ @Test
+ void transferAmount_InsufficientFunds() {
+ BigDecimal transferAmount = new BigDecimal("1500.00");
+
+ RuntimeException exception = assertThrows(RuntimeException.class, () -> {
+ accountService.transferAmount(testAccount, "recipient", transferAmount);
+ });
+
+ assertEquals("Insufficient funds", exception.getMessage());
+ assertEquals(new BigDecimal("1000.00"), testAccount.getBalance());
+ verify(accountRepository, never()).save(any(Account.class));
+ verify(transactionRepository, never()).save(any(Transaction.class));
+ }
+
+ @Test
+ void transferAmount_RecipientNotFound() {
+ BigDecimal transferAmount = new BigDecimal("200.00");
+
+ when(accountRepository.findByUsername("nonexistent")).thenReturn(Optional.empty());
+
+ RuntimeException exception = assertThrows(RuntimeException.class, () -> {
+ accountService.transferAmount(testAccount, "nonexistent", transferAmount);
+ });
+
+ assertEquals("Recipient account not found", exception.getMessage());
+ verify(accountRepository, times(1)).findByUsername("nonexistent");
+ verify(accountRepository, never()).save(any(Account.class));
+ }
+
+ @Test
+ void transferAmount_VerifyTransactionsCreated() {
+ Account toAccount = new Account();
+ toAccount.setId(2L);
+ toAccount.setUsername("recipient");
+ toAccount.setBalance(new BigDecimal("500.00"));
+
+ BigDecimal transferAmount = new BigDecimal("100.00");
+
+ when(accountRepository.findByUsername("recipient")).thenReturn(Optional.of(toAccount));
+ when(accountRepository.save(any(Account.class))).thenAnswer(invocation -> invocation.getArgument(0));
+
+ List savedTransactions = new ArrayList<>();
+ when(transactionRepository.save(any(Transaction.class))).thenAnswer(invocation -> {
+ Transaction t = invocation.getArgument(0);
+ savedTransactions.add(t);
+ return t;
+ });
+
+ accountService.transferAmount(testAccount, "recipient", transferAmount);
+
+ assertEquals(2, savedTransactions.size());
+
+ Transaction debitTransaction = savedTransactions.get(0);
+ assertEquals(transferAmount, debitTransaction.getAmount());
+ assertTrue(debitTransaction.getType().contains("Transfer Out"));
+ assertEquals(testAccount, debitTransaction.getAccount());
+
+ Transaction creditTransaction = savedTransactions.get(1);
+ assertEquals(transferAmount, creditTransaction.getAmount());
+ assertTrue(creditTransaction.getType().contains("Transfer In"));
+ assertEquals(toAccount, creditTransaction.getAccount());
+ }
+
+ @Test
+ void transferAmount_ExactBalance() {
+ Account toAccount = new Account();
+ toAccount.setId(2L);
+ toAccount.setUsername("recipient");
+ toAccount.setBalance(new BigDecimal("500.00"));
+
+ BigDecimal transferAmount = new BigDecimal("1000.00");
+
+ when(accountRepository.findByUsername("recipient")).thenReturn(Optional.of(toAccount));
+ when(accountRepository.save(any(Account.class))).thenAnswer(invocation -> invocation.getArgument(0));
+ when(transactionRepository.save(any(Transaction.class))).thenAnswer(invocation -> invocation.getArgument(0));
+
+ accountService.transferAmount(testAccount, "recipient", transferAmount);
+
+ assertEquals(BigDecimal.ZERO.setScale(2), testAccount.getBalance().setScale(2));
+ assertEquals(new BigDecimal("1500.00"), toAccount.getBalance());
+ }
+}
diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties
new file mode 100644
index 00000000..1d443813
--- /dev/null
+++ b/src/test/resources/application.properties
@@ -0,0 +1,11 @@
+# H2 Database Configuration for Testing
+spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
+spring.datasource.driverClassName=org.h2.Driver
+spring.datasource.username=sa
+spring.datasource.password=
+spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
+spring.jpa.hibernate.ddl-auto=create-drop
+spring.jpa.show-sql=false
+
+# Disable Thymeleaf caching for tests
+spring.thymeleaf.cache=false