diff --git a/pom.xml b/pom.xml
index 3d8377c..47c1e0b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -54,6 +54,17 @@
postgresql
test
+
+ org.springframework.security
+ spring-security-test
+ test
+
+
+ org.projectlombok
+ lombok
+ true
+
+
@@ -72,8 +83,16 @@
org.springframework.boot
spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
-
+
\ No newline at end of file
diff --git a/src/main/java/com/skypro/simplebanking/controller/AccountController.java b/src/main/java/com/skypro/simplebanking/controller/AccountController.java
index efa764b..243bf2a 100644
--- a/src/main/java/com/skypro/simplebanking/controller/AccountController.java
+++ b/src/main/java/com/skypro/simplebanking/controller/AccountController.java
@@ -32,8 +32,8 @@ public AccountDTO depositToAccount(Authentication authentication,
@PostMapping("/withdraw/{id}")
public AccountDTO withdrawFromAccount(Authentication authentication,
- @PathVariable("id") Long accountId,
- @RequestBody BalanceChangeRequest balanceChangeRequest){
+ @PathVariable("id") Long accountId,
+ @RequestBody BalanceChangeRequest balanceChangeRequest){
BankingUserDetails bankingUserDetails = (BankingUserDetails) authentication.getPrincipal();
return accountService.withdrawFromAccount(bankingUserDetails.getId(),accountId, balanceChangeRequest.getAmount());
}
diff --git a/src/main/java/com/skypro/simplebanking/controller/TransferController.java b/src/main/java/com/skypro/simplebanking/controller/TransferController.java
index 7a8ef7c..516ef54 100644
--- a/src/main/java/com/skypro/simplebanking/controller/TransferController.java
+++ b/src/main/java/com/skypro/simplebanking/controller/TransferController.java
@@ -20,8 +20,8 @@ public TransferController(TransferService transferService) {
@PostMapping
public void transfer(
- Authentication authentication, @RequestBody TransferRequest transferRequest) {
- BankingUserDetails bankingUserDetails = (BankingUserDetails) authentication.getPrincipal();
- transferService.transfer(bankingUserDetails.getId(), transferRequest);
+ Authentication authentication, @RequestBody TransferRequest transferRequest) {
+ BankingUserDetails userDetails = (BankingUserDetails) authentication.getPrincipal();
+ transferService.transfer(userDetails.getId(), transferRequest);
}
}
diff --git a/src/main/java/com/skypro/simplebanking/controller/UserController.java b/src/main/java/com/skypro/simplebanking/controller/UserController.java
index 3f38118..ca60bd5 100644
--- a/src/main/java/com/skypro/simplebanking/controller/UserController.java
+++ b/src/main/java/com/skypro/simplebanking/controller/UserController.java
@@ -7,6 +7,7 @@
import javax.validation.Valid;
import com.skypro.simplebanking.service.UserService;
+import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
@@ -22,8 +23,9 @@ public UserController(UserService userService) {
}
@PostMapping
+ @PreAuthorize("hasRole('ADMIN')")
public UserDTO createUser(@RequestBody @Valid CreateUserRequest userRequest) {
- return userService.createUser(userRequest.getUsername(), userRequest.getPassword());
+ return userService.createUser(userRequest.getUsername(), userRequest.getPassword(), userRequest.getRole());
}
@GetMapping("/list")
public List getAllUsers(){
diff --git a/src/main/java/com/skypro/simplebanking/dto/BalanceChangeRequest.java b/src/main/java/com/skypro/simplebanking/dto/BalanceChangeRequest.java
index 1c6e605..07f26a8 100644
--- a/src/main/java/com/skypro/simplebanking/dto/BalanceChangeRequest.java
+++ b/src/main/java/com/skypro/simplebanking/dto/BalanceChangeRequest.java
@@ -1,10 +1,19 @@
package com.skypro.simplebanking.dto;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Range;
+@NoArgsConstructor
public class BalanceChangeRequest {
+ @JsonProperty("amount")
private long amount;
+
+ public BalanceChangeRequest(long amount) {
+ this.amount = amount;
+ }
+
public long getAmount() {
return amount;
}
@@ -12,4 +21,4 @@ public long getAmount() {
public void setAmount(long amount) {
this.amount = amount;
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/skypro/simplebanking/dto/BankingUserDetails.java b/src/main/java/com/skypro/simplebanking/dto/BankingUserDetails.java
index 480896e..5989401 100644
--- a/src/main/java/com/skypro/simplebanking/dto/BankingUserDetails.java
+++ b/src/main/java/com/skypro/simplebanking/dto/BankingUserDetails.java
@@ -23,9 +23,9 @@ public BankingUserDetails(long id, String username, String password, boolean isA
@Override
public Collection extends GrantedAuthority> getAuthorities() {
SimpleGrantedAuthority authority =
- isAdmin
- ? new SimpleGrantedAuthority("ROLE_ADMIN")
- : new SimpleGrantedAuthority("ROLE_USER");
+ isAdmin
+ ? new SimpleGrantedAuthority("ROLE_ADMIN")
+ : new SimpleGrantedAuthority("ROLE_USER");
return Collections.singleton(authority);
}
diff --git a/src/main/java/com/skypro/simplebanking/dto/CreateUserRequest.java b/src/main/java/com/skypro/simplebanking/dto/CreateUserRequest.java
index 05d6dba..e27dc51 100644
--- a/src/main/java/com/skypro/simplebanking/dto/CreateUserRequest.java
+++ b/src/main/java/com/skypro/simplebanking/dto/CreateUserRequest.java
@@ -6,6 +6,7 @@
public class CreateUserRequest {
private String username;
private String password;
+ private String role;
public String getUsername() {
return username;
@@ -22,4 +23,12 @@ public String getPassword() {
public void setPassword(String password) {
this.password = password;
}
+
+ public String getRole() {
+ return role;
+ }
+
+ public void setRole(String role) {
+ this.role = role;
+ }
}
diff --git a/src/main/java/com/skypro/simplebanking/dto/ListUserDTO.java b/src/main/java/com/skypro/simplebanking/dto/ListUserDTO.java
index 3404ae1..c913974 100644
--- a/src/main/java/com/skypro/simplebanking/dto/ListUserDTO.java
+++ b/src/main/java/com/skypro/simplebanking/dto/ListUserDTO.java
@@ -30,8 +30,8 @@ public List getAccounts() {
public static ListUserDTO from(User user) {
return new ListUserDTO(
- user.getId(),
- user.getUsername(),
- user.getAccounts().stream().map(ListAccountDTO::from).collect(Collectors.toList()));
+ user.getId(),
+ user.getUsername(),
+ user.getAccounts().stream().map(ListAccountDTO::from).collect(Collectors.toList()));
}
}
diff --git a/src/main/java/com/skypro/simplebanking/dto/TransferRequest.java b/src/main/java/com/skypro/simplebanking/dto/TransferRequest.java
index 5c4a2f8..c4c702d 100644
--- a/src/main/java/com/skypro/simplebanking/dto/TransferRequest.java
+++ b/src/main/java/com/skypro/simplebanking/dto/TransferRequest.java
@@ -6,6 +6,12 @@ public class TransferRequest {
private long toAccountId;
private long amount;
+ public TransferRequest(long fromAccountId, long toAccountId, long amount) {
+ this.fromAccountId = fromAccountId;
+ this.toAccountId = toAccountId;
+ this.amount = amount;
+ }
+
public long getToUserId() {
return toUserId;
}
@@ -37,4 +43,14 @@ public void setFromAccountId(long fromAccountId) {
public void setToAccountId(long toAccountId) {
this.toAccountId = toAccountId;
}
+
+ @Override
+ public String toString() {
+ return "TransferRequest{" +
+ "fromAccountId=" + fromAccountId +
+ ", toUserId=" + toUserId +
+ ", toAccountId=" + toAccountId +
+ ", amount=" + amount +
+ '}';
+ }
}
diff --git a/src/main/java/com/skypro/simplebanking/dto/UserDTO.java b/src/main/java/com/skypro/simplebanking/dto/UserDTO.java
index 76544aa..b67fc81 100644
--- a/src/main/java/com/skypro/simplebanking/dto/UserDTO.java
+++ b/src/main/java/com/skypro/simplebanking/dto/UserDTO.java
@@ -29,8 +29,8 @@ public List getAccounts() {
public static UserDTO from(User user) {
return new UserDTO(
- user.getId(),
- user.getUsername(),
- user.getAccounts().stream().map(AccountDTO::from).collect(Collectors.toList()));
+ user.getId(),
+ user.getUsername(),
+ user.getAccounts().stream().map(AccountDTO::from).collect(Collectors.toList()));
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/skypro/simplebanking/entity/Account.java b/src/main/java/com/skypro/simplebanking/entity/Account.java
index 5c9e036..842bccd 100644
--- a/src/main/java/com/skypro/simplebanking/entity/Account.java
+++ b/src/main/java/com/skypro/simplebanking/entity/Account.java
@@ -48,4 +48,4 @@ public User getUser() {
public void setUser(User user) {
this.user = user;
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/skypro/simplebanking/entity/Role.java b/src/main/java/com/skypro/simplebanking/entity/Role.java
new file mode 100644
index 0000000..819601e
--- /dev/null
+++ b/src/main/java/com/skypro/simplebanking/entity/Role.java
@@ -0,0 +1,7 @@
+package com.skypro.simplebanking.entity;
+
+
+public enum Role {
+ ROLE_USER,
+ ROLE_ADMIN
+}
diff --git a/src/main/java/com/skypro/simplebanking/entity/User.java b/src/main/java/com/skypro/simplebanking/entity/User.java
index d9290c6..90b3d94 100644
--- a/src/main/java/com/skypro/simplebanking/entity/User.java
+++ b/src/main/java/com/skypro/simplebanking/entity/User.java
@@ -12,10 +12,15 @@ public class User {
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user-generator")
@SequenceGenerator(name = "user-generator", sequenceName = "user_sequence")
private Long id;
+ @Column(name = "username", unique = true)
private String username;
+ @Column(name = "password")
private String password;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
private Collection accounts;
+ @Column(name = "role")
+ @Enumerated(EnumType.STRING)
+ private Role role;
public Long getId() {
return id;
@@ -48,4 +53,12 @@ public Collection getAccounts() {
public void setAccounts(Collection accounts) {
this.accounts = accounts;
}
-}
+
+ public Role getRole() {
+ return role;
+ }
+
+ public void setRole(Role role) {
+ this.role = role;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/skypro/simplebanking/exception/UserNotFoundException.java b/src/main/java/com/skypro/simplebanking/exception/UserNotFoundException.java
new file mode 100644
index 0000000..adf6d75
--- /dev/null
+++ b/src/main/java/com/skypro/simplebanking/exception/UserNotFoundException.java
@@ -0,0 +1,15 @@
+package com.skypro.simplebanking.exception;
+
+public class UserNotFoundException extends RuntimeException {
+ public UserNotFoundException() {
+ super("User not found");
+ }
+
+ public UserNotFoundException(String message) {
+ super(message);
+ }
+
+ public UserNotFoundException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/com/skypro/simplebanking/service/AccountService.java b/src/main/java/com/skypro/simplebanking/service/AccountService.java
index b4d8ce4..e2263bf 100644
--- a/src/main/java/com/skypro/simplebanking/service/AccountService.java
+++ b/src/main/java/com/skypro/simplebanking/service/AccountService.java
@@ -4,12 +4,11 @@
import com.skypro.simplebanking.entity.Account;
import com.skypro.simplebanking.entity.AccountCurrency;
import com.skypro.simplebanking.entity.User;
-import com.skypro.simplebanking.exception.AccountNotFoundException;
-import com.skypro.simplebanking.exception.InsufficientFundsException;
-import com.skypro.simplebanking.exception.InvalidAmountException;
-import com.skypro.simplebanking.exception.WrongCurrencyException;
+import com.skypro.simplebanking.exception.*;
import com.skypro.simplebanking.repository.AccountRepository;
import java.util.ArrayList;
+
+import com.skypro.simplebanking.repository.UserRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@@ -17,9 +16,11 @@
@Service
public class AccountService {
private final AccountRepository accountRepository;
+ private final UserRepository userRepository;
- public AccountService(AccountRepository accountRepository) {
+ public AccountService(AccountRepository accountRepository, UserRepository userRepository) {
this.accountRepository = accountRepository;
+ this.userRepository = userRepository;
}
@Transactional(propagation = Propagation.MANDATORY)
@@ -30,7 +31,6 @@ public void createDefaultAccounts(User user) {
account.setUser(user);
account.setAccountCurrency(currency);
account.setAmount(1L);
- user.getAccounts().add(account);
accountRepository.save(account);
}
}
@@ -38,18 +38,16 @@ public void createDefaultAccounts(User user) {
@Transactional(readOnly = true)
public AccountDTO getAccount(long userId, Long accountId) {
return accountRepository
- .getAccountByUser_IdAndId(userId, accountId)
- .map(AccountDTO::from)
- .orElseThrow(AccountNotFoundException::new);
+ .getAccountByUser_IdAndId(userId, accountId)
+ .map(AccountDTO::from)
+ .orElseThrow(AccountNotFoundException::new);
}
@Transactional
public void validateCurrency(long sourceAccount, long destinationAccount) {
- Account acc1 =
- accountRepository.findById(sourceAccount).orElseThrow(AccountNotFoundException::new);
- Account acc2 =
- accountRepository.findById(destinationAccount).orElseThrow(AccountNotFoundException::new);
- if (!acc1.getAccountCurrency().equals(acc2.getAccountCurrency())){
+ Account acc1 = accountRepository.findById(sourceAccount).orElseThrow(AccountNotFoundException::new);
+ Account acc2 = accountRepository.findById(destinationAccount).orElseThrow(AccountNotFoundException::new);
+ if (!acc1.getAccountCurrency().equals(acc2.getAccountCurrency())) {
throw new WrongCurrencyException();
}
}
@@ -59,8 +57,7 @@ public AccountDTO depositToAccount(long userId, Long accountId, long amount) {
if (amount < 0) {
throw new InvalidAmountException();
}
- Account account =
- accountRepository
+ Account account = accountRepository
.getAccountByUser_IdAndId(userId, accountId)
.orElseThrow(AccountNotFoundException::new);
account.setAmount(account.getAmount() + amount);
@@ -72,15 +69,67 @@ public AccountDTO withdrawFromAccount(long id, Long accountId, long amount) {
if (amount < 0) {
throw new InvalidAmountException();
}
- Account account =
- accountRepository
+ Account account = accountRepository
.getAccountByUser_IdAndId(id, accountId)
.orElseThrow(AccountNotFoundException::new);
if (account.getAmount() < amount) {
- throw new InsufficientFundsException(
- "Cannot withdraw " + amount + " " + account.getAccountCurrency().name());
+ throw new InsufficientFundsException("Cannot withdraw " + amount + " " + account.getAccountCurrency().name());
}
account.setAmount(account.getAmount() - amount);
return AccountDTO.from(account);
}
+
+ @Transactional
+ public void transferBetweenAccounts(long userId, Long sourceAccountId, Long destinationAccountId, long amount) {
+ if (amount < 0) {
+ throw new InvalidAmountException();
+ }
+ Account sourceAccount = getAccountByUserIdAndAccountId(userId, sourceAccountId);
+ Account destinationAccount = getAccountByAccountId(destinationAccountId);
+
+ if (!sourceAccount.getAccountCurrency().equals(destinationAccount.getAccountCurrency())) {
+ throw new WrongCurrencyException();
+ }
+
+ if (sourceAccount.getAmount() < amount) {
+ throw new InsufficientFundsException("Cannot transfer " + amount + " " + sourceAccount.getAccountCurrency().name() + " from account " + sourceAccountId);
+ }
+
+ sourceAccount.setAmount(sourceAccount.getAmount() - amount);
+ destinationAccount.setAmount(destinationAccount.getAmount() + amount);
+ }
+
+ @Transactional
+ public AccountDTO createAccount(Long userId, long initialBalance) {
+ Account account = new Account();
+ account.setUser(userRepository.findById(userId).orElseThrow(UserNotFoundException::new));
+ account.setAmount(initialBalance);
+ account.setAccountCurrency(AccountCurrency.USD);
+ Account saved = accountRepository.save(account);
+ return AccountDTO.from(saved);
+ }
+
+ @Transactional
+ public void changeAccountCurrency(Long accountId, AccountCurrency newCurrency) {
+ Account account = accountRepository.findById(accountId).orElseThrow(AccountNotFoundException::new);
+ account.setAccountCurrency(newCurrency);
+ }
+ private Account getAccountByUserIdAndAccountId(long userId, Long accountId) {
+ return accountRepository.getAccountByUser_IdAndId(userId, accountId).orElseThrow(AccountNotFoundException::new);
+ }
+
+ private Account getAccountByAccountId(Long accountId) {
+ return accountRepository.findById(accountId).orElseThrow(AccountNotFoundException::new);
+ }
+ @Transactional
+ public AccountDTO createTestAccount(Long userId, long initialBalance, Long accountId) {
+ Account account = new Account();
+ account.setId(accountId);
+ account.setUser(userRepository.findById(userId).orElseThrow(UserNotFoundException::new));
+ account.setAmount(initialBalance);
+ account.setAccountCurrency(AccountCurrency.USD);
+ Account saved = accountRepository.save(account);
+ return AccountDTO.from(saved);
+ }
+
}
diff --git a/src/main/java/com/skypro/simplebanking/service/TransferService.java b/src/main/java/com/skypro/simplebanking/service/TransferService.java
index 80bc0eb..f743d89 100644
--- a/src/main/java/com/skypro/simplebanking/service/TransferService.java
+++ b/src/main/java/com/skypro/simplebanking/service/TransferService.java
@@ -1,10 +1,12 @@
package com.skypro.simplebanking.service;
+
import com.skypro.simplebanking.dto.TransferRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestBody;
+
@Service
public class TransferService {
private final AccountService accountService;
@@ -14,14 +16,14 @@ public TransferService(AccountService accountService) {
}
@Transactional
- public void transfer(long id, @RequestBody TransferRequest transferRequest) {
+ public void transfer(Long id, TransferRequest transferRequest) {
accountService.validateCurrency(
- transferRequest.getFromAccountId(), transferRequest.getToAccountId());
+ transferRequest.getFromAccountId(), transferRequest.getToAccountId());
accountService.withdrawFromAccount(
- id, transferRequest.getFromAccountId(), transferRequest.getAmount());
+ id, transferRequest.getFromAccountId(), transferRequest.getAmount());
accountService.depositToAccount(
- transferRequest.getToUserId(),
- transferRequest.getToAccountId(),
- transferRequest.getAmount());
+ transferRequest.getToUserId(),
+ transferRequest.getToAccountId(),
+ transferRequest.getAmount());
}
}
diff --git a/src/main/java/com/skypro/simplebanking/service/UserService.java b/src/main/java/com/skypro/simplebanking/service/UserService.java
index 9f06d7b..72fe8ac 100644
--- a/src/main/java/com/skypro/simplebanking/service/UserService.java
+++ b/src/main/java/com/skypro/simplebanking/service/UserService.java
@@ -1,14 +1,16 @@
package com.skypro.simplebanking.service;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
import com.skypro.simplebanking.dto.BankingUserDetails;
import com.skypro.simplebanking.dto.ListUserDTO;
import com.skypro.simplebanking.dto.UserDTO;
+import com.skypro.simplebanking.entity.Role;
import com.skypro.simplebanking.entity.User;
import com.skypro.simplebanking.exception.UserAlreadyExistsException;
import com.skypro.simplebanking.repository.UserRepository;
-import java.util.List;
-import java.util.Optional;
-import java.util.stream.Collectors;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
@@ -16,6 +18,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
+
@Service
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
@@ -23,9 +26,9 @@ public class UserService implements UserDetailsService {
private final PasswordEncoder passwordEncoder;
public UserService(
- UserRepository userRepository,
- AccountService accountService,
- PasswordEncoder passwordEncoder) {
+ UserRepository userRepository,
+ AccountService accountService,
+ PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.accountService = accountService;
this.passwordEncoder = passwordEncoder;
@@ -35,13 +38,13 @@ public UserService(
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository
- .findByUsername(username)
- .map(BankingUserDetails::from)
- .orElseThrow(() -> new UsernameNotFoundException("User not found"));
+ .findByUsername(username)
+ .map(BankingUserDetails::from)
+ .orElseThrow(() -> new UsernameNotFoundException("User not found"));
}
@Transactional
- public UserDTO createUser(String username, String password) {
+ public UserDTO createUser(String username, String password, String role) {
Optional existingUser = userRepository.findByUsername(username);
if (existingUser.isPresent()) {
throw new UserAlreadyExistsException();
@@ -49,9 +52,10 @@ public UserDTO createUser(String username, String password) {
User user = new User();
user.setUsername(username);
user.setPassword(passwordEncoder.encode(password));
- userRepository.save(user);
- accountService.createDefaultAccounts(user);
- return UserDTO.from(user);
+ user.setRole(Role.valueOf(role));
+ User saved = userRepository.save(user);
+ accountService.createDefaultAccounts(saved);
+ return UserDTO.from(saved);
}
@Transactional(readOnly = true)
public UserDTO getUser(long id) {
@@ -61,4 +65,23 @@ public UserDTO getUser(long id) {
public List listUsers() {
return userRepository.findAll().stream().map(ListUserDTO::from).collect(Collectors.toList());
}
-}
+ @Transactional(readOnly = true)
+ public long getUserIdByUsername(String username) {
+ return userRepository
+ .findByUsername(username)
+ .map(User::getId)
+ .orElseThrow(() -> new UsernameNotFoundException("User not found"));
+ }
+ public void deleteAllUsers() {
+ userRepository.deleteAll();
+ }
+
+ public User createTestUser(String username, String password, Long id) {
+ User user = new User();
+ user.setId(id);
+ user.setUsername(username);
+ user.setPassword(passwordEncoder.encode(password));
+ userRepository.save(user);
+ return user;
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 682ccce..ebf761f 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1,6 +1,8 @@
-spring.datasource.url=jdbc:postgresql://localhost:5432/banking
-spring.datasource.username=banking
-spring.datasource.password=super-safe-pass
+spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
+spring.datasource.username=postgres
+spring.datasource.password=slavakakraft228
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.open-in-view=false
+spring.liquibase.enabled=false
app.security.admin-token=SUPER_SECRET_KEY_FROM_ADMIN
+app.env=dev
diff --git a/src/test/java/com/skypro/simplebanking/ControllerIntegrationTest/UserControllerIntegrationTest.java b/src/test/java/com/skypro/simplebanking/ControllerIntegrationTest/UserControllerIntegrationTest.java
new file mode 100644
index 0000000..2c61b3b
--- /dev/null
+++ b/src/test/java/com/skypro/simplebanking/ControllerIntegrationTest/UserControllerIntegrationTest.java
@@ -0,0 +1,232 @@
+package com.skypro.simplebanking.ControllerIntegrationTest;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.skypro.simplebanking.dto.*;
+import com.skypro.simplebanking.entity.AccountCurrency;
+import com.skypro.simplebanking.service.AccountService;
+import com.skypro.simplebanking.service.UserService;
+import net.minidev.json.JSONObject;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+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.web.client.TestRestTemplate;
+import org.springframework.boot.web.server.LocalServerPort;
+import org.springframework.context.annotation.Bean;
+import org.springframework.http.MediaType;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+import org.springframework.test.context.jdbc.Sql;
+import org.springframework.test.web.servlet.MockMvc;
+import org.testcontainers.containers.PostgreSQLContainer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+
+import static org.assertj.core.api.Assertions.assertThat;
+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.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@AutoConfigureMockMvc
+public class UserControllerIntegrationTest {
+
+ @LocalServerPort
+ private int port;
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Autowired
+ private ObjectMapper objectMapper;
+
+ @Autowired
+ private UserService userService;
+
+ @Autowired
+ private AccountService accountService;
+
+ private UserDTO userAdmin;
+ private UserDTO userUser;
+
+ @BeforeEach
+ public void setupTestUsers() {
+ userAdmin = userService.createUser("adminUser", "adminPassword", "ROLE_ADMIN");
+ userUser = userService.createUser("regularUser", "regularPassword", "ROLE_USER");
+ }
+ @AfterEach
+ public void cleanup() {
+ userService.deleteAllUsers();
+ }
+
+ @Test
+ @WithMockUser(username = "adminUser", password = "adminPassword", roles = "ADMIN")
+ public void testCreateUserWithAdminRole_Success() throws Exception {
+ CreateUserRequest userRequest = new CreateUserRequest();
+ userRequest.setUsername("newUser");
+ userRequest.setPassword("newPassword");
+ userRequest.setRole("ROLE_USER");
+
+ mockMvc.perform(post("/user")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(userRequest)))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.username").value("newUser"));
+ }
+
+
+ @Test
+ @WithMockUser(username = "regularUser", password = "regularPassword", roles = "USER")
+ public void testGetAllUsersByRegularUser_Success() throws Exception {
+ mockMvc.perform(get("/user/list"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.length()").value(2));
+ }
+
+
+ @Test
+ public void testDepositToOwnAccount_Success() throws Exception {
+ UserDetails userDetails = userService.loadUserByUsername(userUser.getUsername());
+ long userId = userService.getUserIdByUsername(userUser.getUsername());
+
+ Long sourceAccountId = createAccountForUserWithCurrency(userId, AccountCurrency.USD);
+
+ BankingUserDetails bankingDetails = new BankingUserDetails(userUser.getId(), userUser.getUsername(), userDetails.getPassword(), false);
+
+ Authentication authentication = new UsernamePasswordAuthenticationToken(bankingDetails, null, bankingDetails.getAuthorities());
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+
+ long depositAmount = 500L;
+ long finalDepositAmount = 1500L;
+
+ mockMvc.perform(post("/account/deposit/{id}", sourceAccountId)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(new BalanceChangeRequest(depositAmount))))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.amount").value(finalDepositAmount));
+ }
+
+ @Test
+ public void testWithdrawFromOwnAccount_Success() throws Exception {
+ UserDetails userDetails = userService.loadUserByUsername(userUser.getUsername());
+ long userId = userService.getUserIdByUsername(userUser.getUsername());
+
+ Long sourceAccountId = createAccountForUserWithCurrency(userId, AccountCurrency.USD);
+ long withdrawAmount = 200L;
+ BankingUserDetails bankingDetails = new BankingUserDetails(userUser.getId(), userUser.getUsername(), userDetails.getPassword(), false);
+
+ Authentication authentication = new UsernamePasswordAuthenticationToken(bankingDetails, null, bankingDetails.getAuthorities());
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+
+
+
+ mockMvc.perform(post("/account/withdraw/{id}", sourceAccountId)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(new BalanceChangeRequest(withdrawAmount))))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.amount").value(800L));
+ }
+
+ @Test
+ public void testTransferBetweenOwnAccounts_Success() throws Exception {
+
+ UserDetails userDetails = userService.loadUserByUsername(userUser.getUsername());
+ long userId = userService.getUserIdByUsername(userUser.getUsername());
+
+ Long fromAccountID = createAccountForUserWithCurrency(userId, AccountCurrency.USD);
+ Long toAccountID = createAccountForUserWithCurrency(userId, AccountCurrency.USD);
+ long amount = 200L;
+
+ BankingUserDetails bankingDetails = new BankingUserDetails(userUser.getId(), userUser.getUsername(), userDetails.getPassword(), false);
+
+ Authentication authentication = new UsernamePasswordAuthenticationToken(bankingDetails, null, bankingDetails.getAuthorities());
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+
+ JSONObject requestBody = new JSONObject();
+ requestBody.put("fromAccountId", fromAccountID);
+ requestBody.put("toAccountId", toAccountID);
+ requestBody.put("toUserId", userId);
+ requestBody.put("amount", amount);
+
+ mockMvc.perform(post("/transfer")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(requestBody.toString()))
+ .andExpect(status().isOk());
+
+ AccountDTO sourceAccount = accountService.getAccount(userId, fromAccountID);
+ AccountDTO destinationAccount = accountService.getAccount(userId, toAccountID);
+
+ assertThat(sourceAccount.getAmount()).isEqualByComparingTo(800L);
+ assertThat(destinationAccount.getAmount()).isEqualByComparingTo(1200L);
+ }
+
+ @Test
+ public void testTransferBetweenAccounts_WithDifferentCurrencies_Failure() throws Exception {
+ UserDetails userDetails = userService.loadUserByUsername(userUser.getUsername());
+ long userId = userService.getUserIdByUsername(userUser.getUsername());
+
+ Long sourceAccountId = createAccountForUserWithCurrency(userId, AccountCurrency.USD);
+ Long destinationAccountId = createAccountForUserWithCurrency(userId, AccountCurrency.EUR);
+ long transferAmount = 200L;
+
+ BankingUserDetails bankingDetails = new BankingUserDetails(userUser.getId(), userUser.getUsername(), userDetails.getPassword(), false);
+
+ Authentication authentication = new UsernamePasswordAuthenticationToken(bankingDetails, null, bankingDetails.getAuthorities());
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+
+ mockMvc.perform(post("/transfer")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(new TransferRequest(sourceAccountId, destinationAccountId, transferAmount))))
+ .andExpect(status().isBadRequest());
+ }
+
+ @Test
+ public void testWithdrawFromOwnAccount_InsufficientFunds_Failure() throws Exception {
+ UserDetails userDetails = userService.loadUserByUsername(userUser.getUsername());
+ long userId = userService.getUserIdByUsername(userUser.getUsername());
+
+ Long sourceAccountId = createAccountForUserWithCurrency(userId, AccountCurrency.USD);
+ long withdrawAmount = 1200L;
+ BankingUserDetails bankingDetails = new BankingUserDetails(userUser.getId(), userUser.getUsername(), userDetails.getPassword(), false);
+
+ Authentication authentication = new UsernamePasswordAuthenticationToken(bankingDetails, null, bankingDetails.getAuthorities());
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+
+ mockMvc.perform(post("/account/withdraw/{id}", sourceAccountId)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(new BalanceChangeRequest(withdrawAmount))))
+ .andExpect(status().isBadRequest());
+ }
+
+ private long getCurrentUserId() {
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+ return userService.getUserIdByUsername(((UserDetails) authentication.getPrincipal()).getUsername());
+ }
+
+ private Long createAccountForUser(long userId) {
+ AccountDTO accountDTO = accountService.createTestAccount(userId, 1000L, 1L);
+ return accountDTO.getId();
+ }
+
+ private Long createAccountForUserWithCurrency(long userId, AccountCurrency currency) {
+ AccountDTO accountDTO = accountService.createAccount(userId, 1000L);
+ accountService.changeAccountCurrency(accountDTO.getId(), currency);
+ return accountDTO.getId();
+ }
+
+
+ public TestRestTemplate restTemplate() {
+ return new TestRestTemplate();
+ }
+}
diff --git a/src/test/java/com/skypro/simplebanking/SimpleBankingApplicationTests.java b/src/test/java/com/skypro/simplebanking/SimpleBankingApplicationTests.java
index 1d4dd66..3cd5062 100644
--- a/src/test/java/com/skypro/simplebanking/SimpleBankingApplicationTests.java
+++ b/src/test/java/com/skypro/simplebanking/SimpleBankingApplicationTests.java
@@ -5,5 +5,7 @@
@SpringBootTest
class SimpleBankingApplicationTests {
-
+ @Test
+ void contextLoads() {
+ }
}
diff --git a/src/test/resources/application-test.properties b/src/test/resources/application-test.properties
new file mode 100644
index 0000000..37b00cf
--- /dev/null
+++ b/src/test/resources/application-test.properties
@@ -0,0 +1,9 @@
+app.env=test
+spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
+spring.datasource.username=postgres
+spring.datasource.password=slavakakraft228
+spring.jpa.hibernate.ddl-auto=create
+spring.jpa.open-in-view=false
+app.security.admin-token=SUPER_SECRET_KEY_FROM_ADMIN
+
+spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
\ No newline at end of file