Add endpoint to list payments

This commit is contained in:
2023-05-12 23:32:30 +02:00
parent df46537266
commit 890c030109
17 changed files with 370 additions and 75 deletions

View File

@@ -1,18 +1,17 @@
package net.kapcake.bankingservice.controllers;
import jakarta.validation.Valid;
import net.kapcake.bankingservice.converters.BankAccountConverter;
import net.kapcake.bankingservice.converters.PaymentConverter;
import net.kapcake.bankingservice.exceptions.PaymentValidationException;
import net.kapcake.bankingservice.model.domain.PaymentDTO;
import net.kapcake.bankingservice.model.domain.BankAccountDTO;
import net.kapcake.bankingservice.model.entities.Payment;
import net.kapcake.bankingservice.exceptions.ValidationException;
import net.kapcake.bankingservice.model.dtos.PaymentDTO;
import net.kapcake.bankingservice.model.dtos.BankAccountDTO;
import net.kapcake.bankingservice.model.dtos.PaymentFilter;
import net.kapcake.bankingservice.security.UserDetailsImpl;
import net.kapcake.bankingservice.services.AccountService;
import net.kapcake.bankingservice.services.PaymentService;
import org.modelmapper.ModelMapper;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@@ -38,10 +37,35 @@ public class BankingServiceController {
@PostMapping("/payment")
public PaymentDTO createPayment(@AuthenticationPrincipal UserDetailsImpl authenticatedUser, @RequestBody @Valid PaymentDTO paymentDTO, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
throw new PaymentValidationException("Payment request invalid: " + bindingResult.getAllErrors());
String errorString = getErrorString(bindingResult);
throw new ValidationException("Payment request invalid: " + errorString);
}
return paymentService.createPayment(authenticatedUser, paymentDTO);
}
@GetMapping("/payments")
public List<PaymentDTO> getPayments(@AuthenticationPrincipal UserDetailsImpl authenticatedUser, @RequestBody(required = false) @Valid PaymentFilter paymentFilter, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
String errorString = getErrorString(bindingResult);
throw new ValidationException("Payment filter is invalid: " + errorString);
}
return paymentService.getPaymentsForUser(authenticatedUser, paymentFilter);
}
private static String getErrorString(BindingResult bindingResult) {
StringBuilder builder = new StringBuilder("[");
List<ObjectError> allErrors = bindingResult.getAllErrors();
for (int i = 0; i < allErrors.size(); i++){
ObjectError error = allErrors.get(i);
if (i != 0) {
builder.append("\n");
}
if (error instanceof FieldError) {
builder.append(((FieldError) error).getField());
}
builder.append(": ").append(error.getDefaultMessage());
}
builder.append("]");
return builder.toString();
}
}

View File

@@ -1,6 +1,6 @@
package net.kapcake.bankingservice.controllers;
import net.kapcake.bankingservice.exceptions.PaymentValidationException;
import net.kapcake.bankingservice.exceptions.ValidationException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@@ -11,8 +11,8 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExcep
@ControllerAdvice
public class BankingServiceExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler({PaymentValidationException.class})
protected ResponseEntity<Object> handlePaymentValidationException(PaymentValidationException exception, WebRequest request) {
@ExceptionHandler({ValidationException.class})
protected ResponseEntity<Object> handlePaymentValidationException(ValidationException exception, WebRequest request) {
return this.handleExceptionInternal(exception, exception.getMessage(),
new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
}

View File

@@ -1,6 +1,6 @@
package net.kapcake.bankingservice.converters;
import net.kapcake.bankingservice.exceptions.PaymentValidationException;
import net.kapcake.bankingservice.exceptions.ValidationException;
import net.kapcake.bankingservice.model.dtos.PaymentDTO;
import net.kapcake.bankingservice.model.domain.BankAccount;
import net.kapcake.bankingservice.model.domain.Payment;
@@ -25,8 +25,7 @@ public class PaymentConverter extends AbstractConverter<Payment, PaymentDTO> {
TypeMap<Payment, PaymentDTO> paymentPaymentDTOTypeMap = modelMapper
.typeMap(Payment.class, PaymentDTO.class)
.addMapping(src -> src.getGiverAccount().getId(), PaymentDTO::setGiverAccount);
PaymentDTO paymentDTO = paymentPaymentDTOTypeMap.map(payment);
return paymentDTO;
return paymentPaymentDTOTypeMap.map(payment);
}
@Override
@@ -34,7 +33,7 @@ public class PaymentConverter extends AbstractConverter<Payment, PaymentDTO> {
Payment payment = modelMapper.map(paymentDTO, Payment.class);
Optional<BankAccount> bankAccountOptional = bankAccountRepository.findById(paymentDTO.getGiverAccount());
if (bankAccountOptional.isEmpty()) {
throw new PaymentValidationException("Payment request invalid: Giver account does not exist.");
throw new ValidationException("Payment request invalid: Giver account does not exist.");
}
payment.setGiverAccount(bankAccountOptional.get());
return payment;

View File

@@ -1,10 +0,0 @@
package net.kapcake.bankingservice.exceptions;
public class PaymentValidationException extends IllegalArgumentException {
public PaymentValidationException(String message) {
super(message);
}
public PaymentValidationException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,10 @@
package net.kapcake.bankingservice.exceptions;
public class ValidationException extends IllegalArgumentException {
public ValidationException(String message) {
super(message);
}
public ValidationException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -3,13 +3,16 @@ package net.kapcake.bankingservice.model.dtos;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.experimental.Accessors;
import net.kapcake.bankingservice.model.domain.BalanceType;
import net.kapcake.bankingservice.model.domain.Currency;
import java.io.Serializable;
import java.math.BigDecimal;
@Data
public class BalanceDTO {
@Accessors(chain = true)
public class BalanceDTO implements Serializable {
private Long id;
@NotNull
@DecimalMin(value = "0")

View File

@@ -5,12 +5,15 @@ import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.experimental.Accessors;
import net.kapcake.bankingservice.model.domain.AccountStatus;
import java.io.Serializable;
import java.util.List;
@Data
public class BankAccountDTO {
@Accessors(chain = true)
public class BankAccountDTO implements Serializable {
private Long id;
@NotBlank
private String accountNumber;

View File

@@ -3,14 +3,17 @@ package net.kapcake.bankingservice.model.dtos;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.experimental.Accessors;
import net.kapcake.bankingservice.model.domain.Currency;
import net.kapcake.bankingservice.model.domain.PaymentStatus;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
@Data
public class PaymentDTO {
@Accessors(chain = true)
public class PaymentDTO implements Serializable {
private Long id;
@NotNull
@DecimalMin(value = "0", inclusive = false)

View File

@@ -0,0 +1,33 @@
package net.kapcake.bankingservice.model.dtos;
import jakarta.validation.constraints.AssertTrue;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
@Data
@Accessors(chain = true)
public class PaymentFilter implements Serializable {
private String beneficiaryAccountNumber;
private Date startDate;
private Date endDate;
@AssertTrue(message = "Start and end date need to be in order")
public boolean isValidRange() {
if (startDate == null || endDate == null) {
return true;
} else {
return startDate.compareTo(endDate) <= 0;
}
}
@AssertTrue(message = "At least one field needs to be supplied")
public boolean isAtLeastOneFieldFilled() {
return startDate != null ||
endDate != null ||
(beneficiaryAccountNumber != null && !beneficiaryAccountNumber.isBlank());
}
}

View File

@@ -2,9 +2,13 @@ package net.kapcake.bankingservice.model.dtos;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
public class UserDTO {
@Accessors(chain = true)
public class UserDTO implements Serializable {
private Long id;
@NotEmpty
private String username;

View File

@@ -7,7 +7,7 @@ import java.util.List;
import java.util.Optional;
public interface BankAccountRepository extends JpaRepository<BankAccount, Long> {
Optional<List<BankAccount>> findAllByUsers_username(String username);
List<BankAccount> findAllByUsers_username(String username);
Optional<BankAccount> findByAccountNumber(String accountNumber);
}

View File

@@ -1,7 +1,11 @@
package net.kapcake.bankingservice.repositories;
import net.kapcake.bankingservice.model.domain.BankAccount;
import net.kapcake.bankingservice.model.domain.Payment;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface PaymentRepository extends JpaRepository<Payment, Long> {
List<Payment> findAllByGiverAccountIn(List<BankAccount> bankAccounts);
}

View File

@@ -6,7 +6,6 @@ import net.kapcake.bankingservice.repositories.BankAccountRepository;
import net.kapcake.bankingservice.security.UserDetailsImpl;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
@Service
@@ -20,7 +19,7 @@ public class AccountService {
}
public List<BankAccountDTO> getAccounts(UserDetailsImpl authenticatedUser) {
return bankAccountRepository.findAllByUsers_username(authenticatedUser.getUsername()).orElse(Collections.emptyList())
return bankAccountRepository.findAllByUsers_username(authenticatedUser.getUsername())
.stream().map(bankAccountConverter::mapToDTO).toList();
}
}

View File

@@ -1,11 +1,12 @@
package net.kapcake.bankingservice.services;
import net.kapcake.bankingservice.converters.PaymentConverter;
import net.kapcake.bankingservice.model.domain.BalanceType;
import net.kapcake.bankingservice.model.dtos.PaymentDTO;
import net.kapcake.bankingservice.model.domain.Balance;
import net.kapcake.bankingservice.model.domain.BalanceType;
import net.kapcake.bankingservice.model.domain.BankAccount;
import net.kapcake.bankingservice.model.domain.Payment;
import net.kapcake.bankingservice.model.dtos.PaymentDTO;
import net.kapcake.bankingservice.model.dtos.PaymentFilter;
import net.kapcake.bankingservice.repositories.BalanceRepository;
import net.kapcake.bankingservice.repositories.BankAccountRepository;
import net.kapcake.bankingservice.repositories.PaymentRepository;
@@ -15,7 +16,11 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Service
public class PaymentService {
@@ -47,16 +52,61 @@ public class PaymentService {
Payment persistedPayment = transactionTemplate.execute(status -> {
Payment savedPayment = paymentRepository.save(payment);
Balance giverAvailableBalance = giverAccount.getBalances().stream().filter(balance -> BalanceType.AVAILABLE.equals(balance.getType())).findFirst().orElseThrow();
giverAvailableBalance.setAmount(giverAvailableBalance.getAmount().subtract(payment.getAmount()));
balanceRepository.save(giverAvailableBalance);
if (beneficiaryAccount.isPresent()) {
Balance beneficiaryBalance = beneficiaryAccount.get().getBalances().stream().filter(balance -> BalanceType.AVAILABLE.equals(balance.getType())).findFirst().orElseThrow();
beneficiaryBalance.setAmount(beneficiaryBalance.getAmount().add(payment.getAmount()));
balanceRepository.save(beneficiaryBalance);
}
updateBalances(payment, giverAccount, beneficiaryAccount);
return savedPayment;
});
return paymentConverter.mapToDTO(persistedPayment);
}
private void updateBalances(Payment payment, BankAccount giverAccount, Optional<BankAccount> beneficiaryAccount) {
updateGiverBalance(payment, giverAccount);
updateBeneficiaryBalance(payment, beneficiaryAccount);
}
private void updateBeneficiaryBalance(Payment payment, Optional<BankAccount> beneficiaryAccount) {
if (beneficiaryAccount.isPresent()) {
Balance beneficiaryBalance = beneficiaryAccount.get().getBalances().stream().filter(balance -> BalanceType.AVAILABLE.equals(balance.getType())).findFirst().orElseThrow();
beneficiaryBalance.setAmount(beneficiaryBalance.getAmount().add(payment.getAmount()));
balanceRepository.save(beneficiaryBalance);
}
}
private void updateGiverBalance(Payment payment, BankAccount giverAccount) {
Balance giverAvailableBalance = giverAccount.getBalances().stream().filter(balance -> BalanceType.AVAILABLE.equals(balance.getType())).findFirst().orElseThrow();
giverAvailableBalance.setAmount(giverAvailableBalance.getAmount().subtract(payment.getAmount()));
balanceRepository.save(giverAvailableBalance);
}
public List<PaymentDTO> getPaymentsForUser(UserDetailsImpl authenticatedUser, PaymentFilter paymentFilter) {
List<BankAccount> userBankAccounts = bankAccountRepository.findAllByUsers_username(authenticatedUser.getUsername());
List<Payment> userPayments = paymentRepository.findAllByGiverAccountIn(userBankAccounts);
List<Payment> filteredPayments = getFilteredPayments(paymentFilter, userPayments);
filteredPayments.sort(Comparator.comparing(Payment::getCreationDate));
return filteredPayments.stream().map(paymentConverter::mapToDTO).toList();
}
private static List<Payment> getFilteredPayments(PaymentFilter paymentFilter, List<Payment> userPayments) {
List<Payment> filteredPayments = userPayments;
if (paymentFilter != null) {
String beneficiaryAccountNumber = paymentFilter.getBeneficiaryAccountNumber();
Date startDate = paymentFilter.getStartDate();
Date endDate = paymentFilter.getEndDate();
filteredPayments = userPayments.stream().filter(payment -> {
boolean filter = true;
if (beneficiaryAccountNumber != null) {
filter &= payment.getBeneficiaryAccountNumber().equals(beneficiaryAccountNumber);
}
if (startDate != null) {
filter &= payment.getCreationDate().compareTo(startDate) >= 0;
}
if (endDate != null) {
filter &= payment.getCreationDate().compareTo(endDate) <= 0;
}
return filter;
}).collect(Collectors.toList());
}
return filteredPayments;
}
}

View File

@@ -1,6 +1,6 @@
package net.kapcake.bankingservice.validation;
import net.kapcake.bankingservice.exceptions.PaymentValidationException;
import net.kapcake.bankingservice.exceptions.ValidationException;
import net.kapcake.bankingservice.model.domain.BalanceType;
import net.kapcake.bankingservice.model.domain.IbanValidationResponse;
import net.kapcake.bankingservice.model.domain.Balance;
@@ -29,7 +29,7 @@ public class PaymentValidator {
}
public void validate(User user, Payment payment) throws PaymentValidationException {
public void validate(User user, Payment payment) throws ValidationException {
validateGiverAccount(user, payment);
validateBeneficiaryAccount(payment);
validateAccountBalance(payment);
@@ -38,45 +38,45 @@ public class PaymentValidator {
private static void validateGiverAccount(User user, Payment payment) {
boolean userOwnsGiverAccount = user.getBankAccounts().stream().anyMatch(bankAccount -> bankAccount.getId().equals(payment.getGiverAccount().getId()));
if (!userOwnsGiverAccount) {
throw new PaymentValidationException("Giver account not owned by authenticated user.");
throw new ValidationException("Giver account not owned by authenticated user.");
}
}
private void validateBeneficiaryAccount(Payment payment) {
IbanValidationResponse validationResponse = null;
IbanValidationResponse validationResponse;
try {
validationResponse = restTemplate.getForObject(validationUrl + "/" + payment.getBeneficiaryAccountNumber(), IbanValidationResponse.class);
} catch (RestClientException e) {
throw new PaymentValidationException("Beneficiary account could not be validated.");
throw new ValidationException("Beneficiary account could not be validated.");
}
if (validationResponse == null) {
throw new PaymentValidationException("Beneficiary account could not be validated.");
throw new ValidationException("Beneficiary account could not be validated.");
} else if (!validationResponse.valid()) {
throw new PaymentValidationException("Beneficiary account not valid: " + validationResponse.messages());
throw new ValidationException("Beneficiary account not valid: " + validationResponse.messages());
}
boolean sameGiverAndBeneficiary = payment.getGiverAccount().getAccountNumber().equals(payment.getBeneficiaryAccountNumber());
if (sameGiverAndBeneficiary) {
throw new PaymentValidationException("Beneficiary and giver account are the same.");
throw new ValidationException("Beneficiary and giver account are the same.");
}
boolean isBeneficiaryForbidden = forbiddenAccounts.contains(payment.getBeneficiaryAccountNumber());
if (isBeneficiaryForbidden) {
throw new PaymentValidationException("Beneficiary account is forbidden.");
throw new ValidationException("Beneficiary account is forbidden.");
}
}
private void validateAccountBalance(Payment payment) {
List<Balance> availableBalances = payment.getGiverAccount().getBalances().stream().filter(balance -> BalanceType.AVAILABLE.equals(balance.getType())).toList();
if (availableBalances.size() != 1) {
throw new PaymentValidationException("Unable to retrieve available account balance.");
throw new ValidationException("Unable to retrieve available account balance.");
}
Balance balance = availableBalances.get(0);
boolean sameCurrencies = balance.getCurrency().equals(payment.getCurrency());
if (!sameCurrencies) {
throw new PaymentValidationException("Payment and account currency must be the same.");
throw new ValidationException("Payment and account currency must be the same.");
}
int compareResult = balance.getAmount().compareTo(payment.getAmount());
if (compareResult < 0) {
throw new PaymentValidationException("Available account balance not sufficient.");
throw new ValidationException("Available account balance not sufficient.");
}
}
}

View File

@@ -0,0 +1,175 @@
package net.kapcake.bankingservice.services;
import net.kapcake.bankingservice.converters.PaymentConverter;
import net.kapcake.bankingservice.model.domain.Currency;
import net.kapcake.bankingservice.model.domain.*;
import net.kapcake.bankingservice.model.dtos.PaymentDTO;
import net.kapcake.bankingservice.model.dtos.PaymentFilter;
import net.kapcake.bankingservice.repositories.BalanceRepository;
import net.kapcake.bankingservice.repositories.BankAccountRepository;
import net.kapcake.bankingservice.repositories.PaymentRepository;
import net.kapcake.bankingservice.security.UserDetailsImpl;
import net.kapcake.bankingservice.validation.PaymentValidator;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.transaction.PlatformTransactionManager;
import java.math.BigDecimal;
import java.util.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class PaymentServiceTest {
public static final String TESTUSER1 = "test-user-1";
public static final String TESTUSER2 = "test-user-2";
public static final String ACCOUNT_NUMBER_1 = "LU347280737562934669";
public static final String ACCOUNT_NUMBER_2 = "LU559999005150266806";
public static final String ACCOUNT_NUMBER_3 = "LU293855258657559167";
@Mock
private PaymentValidator paymentValidator;
@Mock
private PaymentRepository paymentRepository;
@Mock
private BankAccountRepository bankAccountRepository;
@Mock
private BalanceRepository balanceRepository;
@Mock
private PlatformTransactionManager transactionManager;
@Mock
private PaymentConverter paymentConverter;
private PaymentService paymentService;
private User user1;
private User user2;
private BankAccount bankAccount1;
private BankAccount bankAccount2;
private Balance balance1;
private Balance balance2;
@BeforeEach
void beforeEach() {
paymentService = new PaymentService(paymentRepository, bankAccountRepository, balanceRepository, paymentValidator, paymentConverter, transactionManager);
user1 = new User().setId(1L).setUsername(TESTUSER1);
user2 = new User().setId(2L).setUsername(TESTUSER2);
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));
bankAccount1 = new BankAccount().setId(1L).setAccountNumber(ACCOUNT_NUMBER_1).setBalances(Collections.singletonList(balance1)).setUsers(Collections.singletonList(user1));
bankAccount2 = new BankAccount().setId(2L).setAccountNumber(ACCOUNT_NUMBER_2).setBalances(Collections.singletonList(balance2)).setUsers(Collections.singletonList(user2));
}
@Test
void createPayment() {
UserDetailsImpl authenticatedUser = new UserDetailsImpl(user1);
PaymentDTO paymentDTO = new PaymentDTO().setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2).setGiverAccount(1L);
Payment payment = new Payment().setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2).setGiverAccount(bankAccount1);
when(bankAccountRepository.findByAccountNumber(ACCOUNT_NUMBER_2)).thenReturn(Optional.of(bankAccount2));
when(paymentConverter.mapToEntity(paymentDTO)).thenReturn(payment);
when(paymentRepository.save(payment)).thenReturn(payment);
when(paymentConverter.mapToDTO(payment)).thenReturn(paymentDTO);
paymentService.createPayment(authenticatedUser, paymentDTO);
assertEquals(balance1.getAmount(), BigDecimal.valueOf(450));
assertEquals(balance2.getAmount(), BigDecimal.valueOf(550));
}
@Test
void getPaymentsForUser() {
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);
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);
Payment payment2 = new Payment().setId(2L).setCreationDate(new GregorianCalendar(2023, Calendar.MAY, 11, 12, 1).getTime()).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2).setGiverAccount(bankAccount1);
PaymentDTO paymentDTO2 = new PaymentDTO().setId(2L).setCreationDate(new GregorianCalendar(2023, Calendar.MAY, 11, 12, 1).getTime()).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2).setGiverAccount(1L);
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);
List<BankAccount> bankAccounts = List.of(bankAccount1);
when(bankAccountRepository.findAllByUsers_username(TESTUSER1)).thenReturn(bankAccounts);
when(paymentRepository.findAllByGiverAccountIn(bankAccounts)).thenReturn(Arrays.asList(payment1, payment2, payment3));
when(paymentConverter.mapToDTO(payment1)).thenReturn(paymentDTO1);
when(paymentConverter.mapToDTO(payment2)).thenReturn(paymentDTO2);
when(paymentConverter.mapToDTO(payment3)).thenReturn(paymentDTO3);
getPaymentsForUsers_withoutFilter(authenticatedUser);
getPaymentsForUsers_withBeneficiaryAccountFilter(authenticatedUser);
getPaymentsForUsers_withStartDateFilter(authenticatedUser);
getPaymentsForUsers_withEndDateFilter(authenticatedUser);
getPaymentsForUsers_withStartAndEndDateFilter(authenticatedUser);
getPaymentsForUsers_withAllFilter(authenticatedUser);
getPaymentsForUsers_withOtherUser(new UserDetailsImpl(user2));
}
private void getPaymentsForUsers_withOtherUser(UserDetailsImpl authenticatedUser) {
List<PaymentDTO> paymentsForUser = paymentService.getPaymentsForUser(authenticatedUser, null);
assertEquals(0, paymentsForUser.size());
}
private void getPaymentsForUsers_withAllFilter(UserDetailsImpl authenticatedUser) {
Date startDate = new GregorianCalendar(2023, Calendar.MAY, 11, 12, 1).getTime();
Date endDate = new GregorianCalendar(2023, Calendar.MAY, 11, 12, 2).getTime();
PaymentFilter paymentFilter = new PaymentFilter().setStartDate(startDate).setEndDate(endDate).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_3);
List<PaymentDTO> paymentsForUser = paymentService.getPaymentsForUser(authenticatedUser, paymentFilter);
assertEquals(1, paymentsForUser.size());
assertEquals(3, paymentsForUser.get(0).getId());
}
private void getPaymentsForUsers_withStartAndEndDateFilter(UserDetailsImpl authenticatedUser) {
Date date = new GregorianCalendar(2023, Calendar.MAY, 11, 12, 1).getTime();
PaymentFilter paymentFilter = new PaymentFilter().setStartDate(date).setEndDate(date);
List<PaymentDTO> paymentsForUser = paymentService.getPaymentsForUser(authenticatedUser, paymentFilter);
assertEquals(1, paymentsForUser.size());
assertEquals(2, paymentsForUser.get(0).getId());
}
private void getPaymentsForUsers_withEndDateFilter(UserDetailsImpl authenticatedUser) {
PaymentFilter paymentFilter = new PaymentFilter().setEndDate(new GregorianCalendar(2023, Calendar.MAY, 11, 12, 1).getTime());
List<PaymentDTO> paymentsForUser = paymentService.getPaymentsForUser(authenticatedUser, paymentFilter);
assertEquals(2, paymentsForUser.size());
assertEquals(1, paymentsForUser.get(0).getId());
assertEquals(2, paymentsForUser.get(1).getId());
}
private void getPaymentsForUsers_withStartDateFilter(UserDetailsImpl authenticatedUser) {
PaymentFilter paymentFilter = new PaymentFilter().setStartDate(new GregorianCalendar(2023, Calendar.MAY, 11, 12, 1).getTime());
List<PaymentDTO> paymentsForUser = paymentService.getPaymentsForUser(authenticatedUser, paymentFilter);
assertEquals(2, paymentsForUser.size());
assertEquals(2, paymentsForUser.get(0).getId());
assertEquals(3, paymentsForUser.get(1).getId());
}
private void getPaymentsForUsers_withBeneficiaryAccountFilter(UserDetailsImpl authenticatedUser) {
PaymentFilter paymentFilter = new PaymentFilter().setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2);
List<PaymentDTO> paymentsForUser = paymentService.getPaymentsForUser(authenticatedUser, paymentFilter);
assertEquals(2, paymentsForUser.size());
assertEquals(1, paymentsForUser.get(0).getId());
assertEquals(2, paymentsForUser.get(1).getId());
}
private void getPaymentsForUsers_withoutFilter(UserDetailsImpl authenticatedUser) {
List<PaymentDTO> paymentsForUser = paymentService.getPaymentsForUser(authenticatedUser, null);
assertEquals(3, paymentsForUser.size());
}
}

View File

@@ -3,7 +3,7 @@ package net.kapcake.bankingservice.validation;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.kapcake.bankingservice.config.BankingServiceConfig;
import net.kapcake.bankingservice.exceptions.PaymentValidationException;
import net.kapcake.bankingservice.exceptions.ValidationException;
import net.kapcake.bankingservice.model.domain.BalanceType;
import net.kapcake.bankingservice.model.domain.Currency;
import net.kapcake.bankingservice.model.domain.IbanValidationResponse;
@@ -44,7 +44,6 @@ class PaymentValidatorTest {
private static final List<String> FORBIDDEN_ACCOUNTS = Arrays.asList(FORBIDDEN_ACCOUNT_1, FORBIDDEN_ACCOUNT_2);
private static final String USERNAME = "username";
private static final String GIVER_ACCOUNT_NUMBER = "LU038359494013886902";
private final RestTemplate restTemplate;
private final PaymentValidator paymentValidator;
private final MockRestServiceServer server;
private final ObjectMapper objectMapper;
@@ -55,7 +54,6 @@ class PaymentValidatorTest {
@Autowired
public PaymentValidatorTest(RestTemplate restTemplate, MockRestServiceServer server) {
this.restTemplate = restTemplate;
this.paymentValidator = new PaymentValidator(restTemplate, VALIDATION_URL, FORBIDDEN_ACCOUNTS);
objectMapper = new ObjectMapper();
this.server = server;
@@ -90,8 +88,8 @@ class PaymentValidatorTest {
void validate_invalidGiverAccount() {
user.setBankAccounts(Collections.emptyList());
PaymentValidationException paymentValidationException = Assertions.assertThrows(PaymentValidationException.class, () -> paymentValidator.validate(user, payment));
Assertions.assertEquals("Giver account not owned by authenticated user.", paymentValidationException.getMessage());
ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> paymentValidator.validate(user, payment));
Assertions.assertEquals("Giver account not owned by authenticated user.", validationException.getMessage());
}
@Test
@@ -103,8 +101,8 @@ class PaymentValidatorTest {
.andExpect(method(HttpMethod.GET))
.andRespond(withServiceUnavailable());
PaymentValidationException paymentValidationException = Assertions.assertThrows(PaymentValidationException.class, () -> paymentValidator.validate(user, payment));
Assertions.assertEquals("Beneficiary account could not be validated.", paymentValidationException.getMessage());
ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> paymentValidator.validate(user, payment));
Assertions.assertEquals("Beneficiary account could not be validated.", validationException.getMessage());
server.reset();
server
@@ -112,8 +110,8 @@ class PaymentValidatorTest {
.andExpect(method(HttpMethod.GET))
.andRespond(withSuccess());
paymentValidationException = Assertions.assertThrows(PaymentValidationException.class, () -> paymentValidator.validate(user, payment));
Assertions.assertEquals("Beneficiary account could not be validated.", paymentValidationException.getMessage());
validationException = Assertions.assertThrows(ValidationException.class, () -> paymentValidator.validate(user, payment));
Assertions.assertEquals("Beneficiary account could not be validated.", validationException.getMessage());
}
@Test
@@ -125,16 +123,16 @@ class PaymentValidatorTest {
.andExpect(method(HttpMethod.GET))
.andRespond(withSuccess(objectMapper.writeValueAsString(new IbanValidationResponse(false, Collections.singletonList("Invalid"), VALID_BENEFICIARY)), MediaType.APPLICATION_JSON));
PaymentValidationException paymentValidationException = Assertions.assertThrows(PaymentValidationException.class, () -> paymentValidator.validate(user, payment));
Assertions.assertEquals("Beneficiary account not valid: [Invalid]", paymentValidationException.getMessage());
ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> paymentValidator.validate(user, payment));
Assertions.assertEquals("Beneficiary account not valid: [Invalid]", validationException.getMessage());
}
@Test
void validate_invalidBeneficiaryAccount_giverAndBeneficiarySame() {
bankAccount.setAccountNumber(VALID_BENEFICIARY);
PaymentValidationException paymentValidationException = Assertions.assertThrows(PaymentValidationException.class, () -> paymentValidator.validate(user, payment));
Assertions.assertEquals("Beneficiary and giver account are the same.", paymentValidationException.getMessage());
ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> paymentValidator.validate(user, payment));
Assertions.assertEquals("Beneficiary and giver account are the same.", validationException.getMessage());
}
@Test
@@ -146,31 +144,31 @@ class PaymentValidatorTest {
.andExpect(method(HttpMethod.GET))
.andRespond(withSuccess(objectMapper.writeValueAsString(new IbanValidationResponse(true, Collections.emptyList(), FORBIDDEN_ACCOUNT_1)), MediaType.APPLICATION_JSON));
PaymentValidationException paymentValidationException = Assertions.assertThrows(PaymentValidationException.class, () -> paymentValidator.validate(user, payment));
Assertions.assertEquals("Beneficiary account is forbidden.", paymentValidationException.getMessage());
ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> paymentValidator.validate(user, payment));
Assertions.assertEquals("Beneficiary account is forbidden.", validationException.getMessage());
}
@Test
void validate_invalidAccountBalance_notFound() {
balance.setType(BalanceType.END_OF_DAY);
PaymentValidationException paymentValidationException = Assertions.assertThrows(PaymentValidationException.class, () -> paymentValidator.validate(user, payment));
Assertions.assertEquals("Unable to retrieve available account balance.", paymentValidationException.getMessage());
ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> paymentValidator.validate(user, payment));
Assertions.assertEquals("Unable to retrieve available account balance.", validationException.getMessage());
}
@Test
void validate_invalidAccountBalance_differentCurrencies() {
balance.setCurrency(Currency.USD);
PaymentValidationException paymentValidationException = Assertions.assertThrows(PaymentValidationException.class, () -> paymentValidator.validate(user, payment));
Assertions.assertEquals("Payment and account currency must be the same.", paymentValidationException.getMessage());
ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> paymentValidator.validate(user, payment));
Assertions.assertEquals("Payment and account currency must be the same.", validationException.getMessage());
}
@Test
void validate_invalidAccountBalance_insufficientBalance() {
balance.setAmount(BigDecimal.valueOf(2));
PaymentValidationException paymentValidationException = Assertions.assertThrows(PaymentValidationException.class, () -> paymentValidator.validate(user, payment));
Assertions.assertEquals("Available account balance not sufficient.", paymentValidationException.getMessage());
ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> paymentValidator.validate(user, payment));
Assertions.assertEquals("Available account balance not sufficient.", validationException.getMessage());
}
}