Add endpoint to delete payments
This commit is contained in:
@@ -8,14 +8,12 @@ import net.kapcake.bankingservice.model.dtos.PaymentFilter;
|
|||||||
import net.kapcake.bankingservice.security.UserDetailsImpl;
|
import net.kapcake.bankingservice.security.UserDetailsImpl;
|
||||||
import net.kapcake.bankingservice.services.AccountService;
|
import net.kapcake.bankingservice.services.AccountService;
|
||||||
import net.kapcake.bankingservice.services.PaymentService;
|
import net.kapcake.bankingservice.services.PaymentService;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
import org.springframework.validation.BindingResult;
|
import org.springframework.validation.BindingResult;
|
||||||
import org.springframework.validation.FieldError;
|
import org.springframework.validation.FieldError;
|
||||||
import org.springframework.validation.ObjectError;
|
import org.springframework.validation.ObjectError;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -35,6 +33,7 @@ public class BankingServiceController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/payment")
|
@PostMapping("/payment")
|
||||||
|
@ResponseStatus(HttpStatus.CREATED)
|
||||||
public PaymentDTO createPayment(@AuthenticationPrincipal UserDetailsImpl authenticatedUser, @RequestBody @Valid PaymentDTO paymentDTO, BindingResult bindingResult) {
|
public PaymentDTO createPayment(@AuthenticationPrincipal UserDetailsImpl authenticatedUser, @RequestBody @Valid PaymentDTO paymentDTO, BindingResult bindingResult) {
|
||||||
if (bindingResult.hasErrors()) {
|
if (bindingResult.hasErrors()) {
|
||||||
String errorString = getErrorString(bindingResult);
|
String errorString = getErrorString(bindingResult);
|
||||||
@@ -52,6 +51,11 @@ public class BankingServiceController {
|
|||||||
return paymentService.getPaymentsForUser(authenticatedUser, paymentFilter);
|
return paymentService.getPaymentsForUser(authenticatedUser, paymentFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/payment/{id}")
|
||||||
|
public void deletePayment(@AuthenticationPrincipal UserDetailsImpl authenticatedUser, @PathVariable("id") Long id) {
|
||||||
|
paymentService.deletePayment(authenticatedUser, id);
|
||||||
|
}
|
||||||
|
|
||||||
private static String getErrorString(BindingResult bindingResult) {
|
private static String getErrorString(BindingResult bindingResult) {
|
||||||
StringBuilder builder = new StringBuilder("[");
|
StringBuilder builder = new StringBuilder("[");
|
||||||
List<ObjectError> allErrors = bindingResult.getAllErrors();
|
List<ObjectError> allErrors = bindingResult.getAllErrors();
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package net.kapcake.bankingservice.controllers;
|
package net.kapcake.bankingservice.controllers;
|
||||||
|
|
||||||
|
import net.kapcake.bankingservice.exceptions.AccessDeniedException;
|
||||||
|
import net.kapcake.bankingservice.exceptions.ResourceNotFoundException;
|
||||||
import net.kapcake.bankingservice.exceptions.ValidationException;
|
import net.kapcake.bankingservice.exceptions.ValidationException;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
@@ -9,11 +11,22 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
|
|||||||
import org.springframework.web.context.request.WebRequest;
|
import org.springframework.web.context.request.WebRequest;
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
|
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
|
||||||
|
|
||||||
|
|
||||||
@ControllerAdvice
|
@ControllerAdvice
|
||||||
public class BankingServiceExceptionHandler extends ResponseEntityExceptionHandler {
|
public class BankingServiceExceptionHandler extends ResponseEntityExceptionHandler {
|
||||||
@ExceptionHandler({ValidationException.class})
|
@ExceptionHandler({ValidationException.class})
|
||||||
protected ResponseEntity<Object> handlePaymentValidationException(ValidationException exception, WebRequest request) {
|
protected ResponseEntity<Object> handleValidationException(ValidationException exception, WebRequest request) {
|
||||||
return this.handleExceptionInternal(exception, exception.getMessage(),
|
return this.handleExceptionInternal(exception, exception.getMessage(),
|
||||||
new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
|
new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
|
||||||
}
|
}
|
||||||
|
@ExceptionHandler({ResourceNotFoundException.class})
|
||||||
|
protected ResponseEntity<Object> handleResourceNotFoundException(ResourceNotFoundException exception, WebRequest request) {
|
||||||
|
return this.handleExceptionInternal(exception, exception.getMessage(),
|
||||||
|
new HttpHeaders(), HttpStatus.NOT_FOUND, request);
|
||||||
|
}
|
||||||
|
@ExceptionHandler({AccessDeniedException.class})
|
||||||
|
protected ResponseEntity<Object> handleAccessDeniedException(AccessDeniedException exception, WebRequest request) {
|
||||||
|
return this.handleExceptionInternal(exception, exception.getMessage(),
|
||||||
|
new HttpHeaders(), HttpStatus.FORBIDDEN, request);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package net.kapcake.bankingservice.exceptions;
|
||||||
|
|
||||||
|
public class AccessDeniedException extends RuntimeException {
|
||||||
|
public AccessDeniedException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package net.kapcake.bankingservice.exceptions;
|
||||||
|
|
||||||
|
public class ResourceNotFoundException extends RuntimeException {
|
||||||
|
public ResourceNotFoundException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
package net.kapcake.bankingservice.exceptions;
|
package net.kapcake.bankingservice.exceptions;
|
||||||
|
|
||||||
public class ValidationException extends IllegalArgumentException {
|
public class ValidationException extends RuntimeException {
|
||||||
public ValidationException(String message) {
|
public ValidationException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
public ValidationException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public class User {
|
|||||||
@Id
|
@Id
|
||||||
@GeneratedValue
|
@GeneratedValue
|
||||||
private Long id;
|
private Long id;
|
||||||
@Column(nullable = false)
|
@Column(nullable = false, unique = true)
|
||||||
private String username;
|
private String username;
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private String password;
|
private String password;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package net.kapcake.bankingservice.services;
|
package net.kapcake.bankingservice.services;
|
||||||
|
|
||||||
import net.kapcake.bankingservice.converters.PaymentConverter;
|
import net.kapcake.bankingservice.converters.PaymentConverter;
|
||||||
import net.kapcake.bankingservice.model.domain.Balance;
|
import net.kapcake.bankingservice.exceptions.AccessDeniedException;
|
||||||
import net.kapcake.bankingservice.model.domain.BalanceType;
|
import net.kapcake.bankingservice.exceptions.ResourceNotFoundException;
|
||||||
import net.kapcake.bankingservice.model.domain.BankAccount;
|
import net.kapcake.bankingservice.exceptions.ValidationException;
|
||||||
import net.kapcake.bankingservice.model.domain.Payment;
|
import net.kapcake.bankingservice.model.domain.*;
|
||||||
import net.kapcake.bankingservice.model.dtos.PaymentDTO;
|
import net.kapcake.bankingservice.model.dtos.PaymentDTO;
|
||||||
import net.kapcake.bankingservice.model.dtos.PaymentFilter;
|
import net.kapcake.bankingservice.model.dtos.PaymentFilter;
|
||||||
import net.kapcake.bankingservice.repositories.BalanceRepository;
|
import net.kapcake.bankingservice.repositories.BalanceRepository;
|
||||||
@@ -79,7 +79,7 @@ public class PaymentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<PaymentDTO> getPaymentsForUser(UserDetailsImpl authenticatedUser, PaymentFilter paymentFilter) {
|
public List<PaymentDTO> getPaymentsForUser(UserDetailsImpl authenticatedUser, PaymentFilter paymentFilter) {
|
||||||
List<BankAccount> userBankAccounts = bankAccountRepository.findAllByUsers_username(authenticatedUser.getUsername());
|
List<BankAccount> userBankAccounts = authenticatedUser.user().getBankAccounts();
|
||||||
List<Payment> userPayments = paymentRepository.findAllByGiverAccountIn(userBankAccounts);
|
List<Payment> userPayments = paymentRepository.findAllByGiverAccountIn(userBankAccounts);
|
||||||
List<Payment> filteredPayments = getFilteredPayments(paymentFilter, userPayments);
|
List<Payment> filteredPayments = getFilteredPayments(paymentFilter, userPayments);
|
||||||
|
|
||||||
@@ -109,4 +109,16 @@ public class PaymentService {
|
|||||||
}
|
}
|
||||||
return filteredPayments;
|
return filteredPayments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void deletePayment(UserDetailsImpl authenticatedUser, Long id) {
|
||||||
|
List<BankAccount> userBankAccounts = authenticatedUser.user().getBankAccounts();
|
||||||
|
Payment payment = paymentRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("A payment with the given id does not exist"));
|
||||||
|
if (userBankAccounts.stream().noneMatch(account -> account.getId().equals(payment.getGiverAccount().getId()))) {
|
||||||
|
throw new AccessDeniedException("The payment with id [" + id + "] is not owned by the authenticated user");
|
||||||
|
}
|
||||||
|
if (PaymentStatus.EXECUTED.equals(payment.getStatus())) {
|
||||||
|
throw new ValidationException("The payment to be deleted has already been executed");
|
||||||
|
}
|
||||||
|
paymentRepository.deleteById(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package net.kapcake.bankingservice.services;
|
package net.kapcake.bankingservice.services;
|
||||||
|
|
||||||
import net.kapcake.bankingservice.converters.PaymentConverter;
|
import net.kapcake.bankingservice.converters.PaymentConverter;
|
||||||
|
import net.kapcake.bankingservice.exceptions.AccessDeniedException;
|
||||||
|
import net.kapcake.bankingservice.exceptions.ResourceNotFoundException;
|
||||||
|
import net.kapcake.bankingservice.exceptions.ValidationException;
|
||||||
import net.kapcake.bankingservice.model.domain.Currency;
|
import net.kapcake.bankingservice.model.domain.Currency;
|
||||||
import net.kapcake.bankingservice.model.domain.*;
|
import net.kapcake.bankingservice.model.domain.*;
|
||||||
import net.kapcake.bankingservice.model.dtos.PaymentDTO;
|
import net.kapcake.bankingservice.model.dtos.PaymentDTO;
|
||||||
@@ -20,14 +23,15 @@ import org.springframework.transaction.PlatformTransactionManager;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
class PaymentServiceTest {
|
class PaymentServiceTest {
|
||||||
|
|
||||||
public static final String TESTUSER1 = "test-user-1";
|
public static final String TEST_USER_1 = "test-user-1";
|
||||||
public static final String TESTUSER2 = "test-user-2";
|
public static final String TEST_USER_2 = "test-user-2";
|
||||||
public static final String ACCOUNT_NUMBER_1 = "LU347280737562934669";
|
public static final String ACCOUNT_NUMBER_1 = "LU347280737562934669";
|
||||||
public static final String ACCOUNT_NUMBER_2 = "LU559999005150266806";
|
public static final String ACCOUNT_NUMBER_2 = "LU559999005150266806";
|
||||||
public static final String ACCOUNT_NUMBER_3 = "LU293855258657559167";
|
public static final String ACCOUNT_NUMBER_3 = "LU293855258657559167";
|
||||||
@@ -58,8 +62,8 @@ class PaymentServiceTest {
|
|||||||
void beforeEach() {
|
void beforeEach() {
|
||||||
paymentService = new PaymentService(paymentRepository, bankAccountRepository, balanceRepository, paymentValidator, paymentConverter, transactionManager);
|
paymentService = new PaymentService(paymentRepository, bankAccountRepository, balanceRepository, paymentValidator, paymentConverter, transactionManager);
|
||||||
|
|
||||||
user1 = new User().setId(1L).setUsername(TESTUSER1);
|
user1 = new User().setId(1L).setUsername(TEST_USER_1);
|
||||||
user2 = new User().setId(2L).setUsername(TESTUSER2);
|
user2 = new User().setId(2L).setUsername(TEST_USER_2);
|
||||||
balance1 = new Balance().setId(1L).setType(BalanceType.AVAILABLE).setCurrency(Currency.EUR).setAmount(BigDecimal.valueOf(500));
|
balance1 = new Balance().setId(1L).setType(BalanceType.AVAILABLE).setCurrency(Currency.EUR).setAmount(BigDecimal.valueOf(500));
|
||||||
balance2 = new Balance().setId(2L).setType(BalanceType.AVAILABLE).setCurrency(Currency.EUR).setAmount(BigDecimal.valueOf(500));
|
balance2 = new Balance().setId(2L).setType(BalanceType.AVAILABLE).setCurrency(Currency.EUR).setAmount(BigDecimal.valueOf(500));
|
||||||
bankAccount1 = new BankAccount().setId(1L).setAccountNumber(ACCOUNT_NUMBER_1).setBalances(Collections.singletonList(balance1)).setUsers(Collections.singletonList(user1));
|
bankAccount1 = new BankAccount().setId(1L).setAccountNumber(ACCOUNT_NUMBER_1).setBalances(Collections.singletonList(balance1)).setUsers(Collections.singletonList(user1));
|
||||||
@@ -85,6 +89,8 @@ class PaymentServiceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getPaymentsForUser() {
|
void getPaymentsForUser() {
|
||||||
|
List<BankAccount> bankAccounts = List.of(bankAccount1);
|
||||||
|
user1.setBankAccounts(bankAccounts);
|
||||||
UserDetailsImpl authenticatedUser = new UserDetailsImpl(user1);
|
UserDetailsImpl authenticatedUser = new UserDetailsImpl(user1);
|
||||||
Payment payment1 = new Payment().setId(1L).setCreationDate(new GregorianCalendar(2023, Calendar.MAY, 11, 12, 0).getTime()).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2).setGiverAccount(bankAccount1);
|
Payment payment1 = new Payment().setId(1L).setCreationDate(new GregorianCalendar(2023, Calendar.MAY, 11, 12, 0).getTime()).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2).setGiverAccount(bankAccount1);
|
||||||
PaymentDTO paymentDTO1 = new PaymentDTO().setId(1L).setCreationDate(new GregorianCalendar(2023, Calendar.MAY, 11, 12, 0).getTime()).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2).setGiverAccount(1L);
|
PaymentDTO paymentDTO1 = new PaymentDTO().setId(1L).setCreationDate(new GregorianCalendar(2023, Calendar.MAY, 11, 12, 0).getTime()).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2).setGiverAccount(1L);
|
||||||
@@ -93,8 +99,6 @@ class PaymentServiceTest {
|
|||||||
Payment payment3 = new Payment().setId(3L).setCreationDate(new GregorianCalendar(2023, Calendar.MAY, 11, 12, 2).getTime()).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_3).setGiverAccount(bankAccount1);
|
Payment payment3 = new Payment().setId(3L).setCreationDate(new GregorianCalendar(2023, Calendar.MAY, 11, 12, 2).getTime()).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_3).setGiverAccount(bankAccount1);
|
||||||
PaymentDTO paymentDTO3 = new PaymentDTO().setId(3L).setCreationDate(new GregorianCalendar(2023, Calendar.MAY, 11, 12, 2).getTime()).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_3).setGiverAccount(1L);
|
PaymentDTO paymentDTO3 = new PaymentDTO().setId(3L).setCreationDate(new GregorianCalendar(2023, Calendar.MAY, 11, 12, 2).getTime()).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_3).setGiverAccount(1L);
|
||||||
|
|
||||||
List<BankAccount> bankAccounts = List.of(bankAccount1);
|
|
||||||
when(bankAccountRepository.findAllByUsers_username(TESTUSER1)).thenReturn(bankAccounts);
|
|
||||||
when(paymentRepository.findAllByGiverAccountIn(bankAccounts)).thenReturn(Arrays.asList(payment1, payment2, payment3));
|
when(paymentRepository.findAllByGiverAccountIn(bankAccounts)).thenReturn(Arrays.asList(payment1, payment2, payment3));
|
||||||
when(paymentConverter.mapToDTO(payment1)).thenReturn(paymentDTO1);
|
when(paymentConverter.mapToDTO(payment1)).thenReturn(paymentDTO1);
|
||||||
when(paymentConverter.mapToDTO(payment2)).thenReturn(paymentDTO2);
|
when(paymentConverter.mapToDTO(payment2)).thenReturn(paymentDTO2);
|
||||||
@@ -172,4 +176,31 @@ class PaymentServiceTest {
|
|||||||
|
|
||||||
assertEquals(3, paymentsForUser.size());
|
assertEquals(3, paymentsForUser.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void deletePayment() {
|
||||||
|
List<BankAccount> bankAccounts1 = List.of(bankAccount1);
|
||||||
|
user1.setBankAccounts(bankAccounts1);
|
||||||
|
List<BankAccount> bankAccounts2 = List.of(bankAccount2);
|
||||||
|
user2.setBankAccounts(bankAccounts2);
|
||||||
|
UserDetailsImpl authenticatedUser1 = new UserDetailsImpl(user1);
|
||||||
|
UserDetailsImpl authenticatedUser2 = new UserDetailsImpl(user2);
|
||||||
|
Payment payment1 = new Payment().setId(1L).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2).setGiverAccount(bankAccount1);
|
||||||
|
Payment payment2 = new Payment().setId(2L).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2).setGiverAccount(bankAccount1).setStatus(PaymentStatus.EXECUTED);
|
||||||
|
|
||||||
|
when(paymentRepository.findById(1L)).thenReturn(Optional.of(payment1));
|
||||||
|
when(paymentRepository.findById(2L)).thenReturn(Optional.of(payment2));
|
||||||
|
when(paymentRepository.findById(3L)).thenReturn(Optional.empty());
|
||||||
|
|
||||||
|
AccessDeniedException accessDeniedException = assertThrows(AccessDeniedException.class, () -> paymentService.deletePayment(authenticatedUser2, 1L));
|
||||||
|
assertEquals("The payment with id [1] is not owned by the authenticated user", accessDeniedException.getMessage());
|
||||||
|
ResourceNotFoundException resourceNotFoundException = assertThrows(ResourceNotFoundException.class, () -> paymentService.deletePayment(authenticatedUser2, 3L));
|
||||||
|
assertEquals("A payment with the given id does not exist", resourceNotFoundException.getMessage());
|
||||||
|
ValidationException validationException = assertThrows(ValidationException.class, () -> paymentService.deletePayment(authenticatedUser1, 2L));
|
||||||
|
assertEquals("The payment to be deleted has already been executed", validationException.getMessage());
|
||||||
|
|
||||||
|
assertDoesNotThrow(() -> paymentService.deletePayment(authenticatedUser1, 1L));
|
||||||
|
|
||||||
|
verify(paymentRepository).deleteById(1L);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user