From 6b72829f0dee35429d8ddda86e57ebe92eaf820c Mon Sep 17 00:00:00 2001 From: Clement Gibert Date: Thu, 22 Feb 2024 23:39:57 +0100 Subject: [PATCH] feature#25: Hexagonal architecture improvement and refactoring responsabilities of services --- .../bank/account/BankAccountServiceImpl.java | 92 ++---- .../transaction/TransactionRepository.java | 2 + .../transaction/TransactionServiceImpl.java | 78 ++--- .../TransactionStatusServiceImpl.java | 44 +++ .../TransactionTypeProcessingServiceImpl.java | 97 ++++++ .../account/BankAccountServiceImplTest.java | 44 +++ .../bank/account/BankAccountServiceTest.java | 305 ------------------ .../bank/account/BankAccountUtils.java | 4 + .../transaction/TransactionServiceTest.java | 67 ---- .../{bank => }/scheduler/SchedulerTest.java | 3 +- .../scheduler/SchedulerTestProfile.java | 2 +- .../transaction/TransactionMapperTest.java | 2 +- .../TransactionRepositoryTest.java | 1 + .../TransactionServiceImplTest.java | 179 ++++++++++ .../TransactionStatusServiceImplTest.java | 92 ++++++ .../transaction/TransactionTestUtils.java | 2 + ...nsactionTypeProcessingServiceImplTest.java | 282 ++++++++++++++++ client/pom.xml | 4 + .../bank/account/BankAccountResource.java | 22 +- .../bas/client/customer/CustomerResource.java | 5 +- .../transaction/TransactionResource.java | 44 ++- .../src/main/resources/application.properties | 4 + .../bank/account/BankAccountResourceTest.java | 6 +- .../account/BankAccountControllerPort.java | 13 +- .../bank/account/BankAccountServicePort.java | 40 ++- .../bas/domain/transaction/Transaction.java | 28 +- .../TransactionControllerPort.java | 30 ++ .../TransactionPersistencePort.java | 2 +- .../transaction/TransactionServicePort.java | 51 ++- .../domain/transaction/TransactionStatus.java | 3 +- .../TransactionStatusServicePort.java | 23 ++ .../TransactionTypeProcessingServicePort.java | 32 ++ .../validation/AccountMovement.java | 2 +- .../transaction/validation/CashMovement.java | 2 +- .../validation/ExistingTransaction.java | 2 +- .../validation/NewTransaction.java | 2 +- .../transaction/validation/ValidStatus.java | 2 +- .../transaction/TransactionValidatorTest.java | 28 +- 38 files changed, 1033 insertions(+), 608 deletions(-) create mode 100644 application/src/main/java/com/cdx/bas/application/transaction/TransactionStatusServiceImpl.java create mode 100644 application/src/main/java/com/cdx/bas/application/transaction/TransactionTypeProcessingServiceImpl.java create mode 100644 application/src/test/java/com/cdx/bas/application/bank/account/BankAccountServiceImplTest.java delete mode 100644 application/src/test/java/com/cdx/bas/application/bank/account/BankAccountServiceTest.java create mode 100644 application/src/test/java/com/cdx/bas/application/bank/account/BankAccountUtils.java delete mode 100644 application/src/test/java/com/cdx/bas/application/bank/transaction/TransactionServiceTest.java rename application/src/test/java/com/cdx/bas/application/{bank => }/scheduler/SchedulerTest.java (97%) rename application/src/test/java/com/cdx/bas/application/{bank => }/scheduler/SchedulerTestProfile.java (90%) rename application/src/test/java/com/cdx/bas/application/{bank => }/transaction/TransactionMapperTest.java (99%) create mode 100644 application/src/test/java/com/cdx/bas/application/transaction/TransactionServiceImplTest.java create mode 100644 application/src/test/java/com/cdx/bas/application/transaction/TransactionStatusServiceImplTest.java create mode 100644 application/src/test/java/com/cdx/bas/application/transaction/TransactionTestUtils.java create mode 100644 application/src/test/java/com/cdx/bas/application/transaction/TransactionTypeProcessingServiceImplTest.java create mode 100644 domain/src/main/java/com/cdx/bas/domain/transaction/TransactionStatusServicePort.java create mode 100644 domain/src/main/java/com/cdx/bas/domain/transaction/TransactionTypeProcessingServicePort.java diff --git a/application/src/main/java/com/cdx/bas/application/bank/account/BankAccountServiceImpl.java b/application/src/main/java/com/cdx/bas/application/bank/account/BankAccountServiceImpl.java index 6f767dd8..d4d60b42 100644 --- a/application/src/main/java/com/cdx/bas/application/bank/account/BankAccountServiceImpl.java +++ b/application/src/main/java/com/cdx/bas/application/bank/account/BankAccountServiceImpl.java @@ -1,6 +1,9 @@ package com.cdx.bas.application.bank.account; -import com.cdx.bas.domain.bank.account.*; +import com.cdx.bas.domain.bank.account.BankAccount; +import com.cdx.bas.domain.bank.account.BankAccountPersistencePort; +import com.cdx.bas.domain.bank.account.BankAccountServicePort; +import com.cdx.bas.domain.bank.account.BankAccountValidator; import com.cdx.bas.domain.money.Money; import com.cdx.bas.domain.transaction.Transaction; import com.cdx.bas.domain.transaction.TransactionException; @@ -8,18 +11,13 @@ import com.cdx.bas.domain.utils.ExchangeRateUtils; import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Inject; -import jakarta.transaction.Transactional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.math.BigDecimal; -import java.util.HashMap; -import java.util.Map; import java.util.NoSuchElementException; import java.util.Optional; -import static jakarta.transaction.Transactional.TxType.REQUIRES_NEW; - @RequestScoped public class BankAccountServiceImpl implements BankAccountServicePort { @@ -35,74 +33,44 @@ public class BankAccountServiceImpl implements BankAccountServicePort { TransactionServicePort transactionService; @Override - @Transactional(value = REQUIRES_NEW) - public Transaction deposit(Transaction transaction) { - Map metadata = new HashMap<>(); - try { - BankAccount senderBankAccount = bankAccountRepository.findById(transaction.getSenderAccountId()) - .orElseThrow(() -> new NoSuchElementException("Sender bank account " + transaction.getSenderAccountId() + " is not found.")); - - BankAccount receiverBankAccount = bankAccountRepository.findById(transaction.getReceiverAccountId()) - .orElseThrow(() -> new NoSuchElementException("Receiver bank account " + transaction.getReceiverAccountId() + " is not found.")); - - Transaction currentTransaction = transactionService.setAsOutstanding(transaction); - - logger.info("Bank account " + currentTransaction.getSenderAccountId() - + " process deposit transaction " + currentTransaction.getId() - + " with amount " + currentTransaction.getAmount() - + " to bank account " + currentTransaction.getReceiverAccountId()); - - metadata.put("sender_amount_before", senderBankAccount.getBalance().getAmount().toString()); - metadata.put("receiver_amount_before", receiverBankAccount.getBalance().getAmount().toString()); - processCreditTransaction(senderBankAccount, receiverBankAccount, currentTransaction); - bankAccountValidator.validateBankAccount(senderBankAccount); - bankAccountValidator.validateBankAccount(receiverBankAccount); - metadata.put("sender_amount_after", senderBankAccount.getBalance().getAmount().toString()); - metadata.put("receiver_amount_after", receiverBankAccount.getBalance().getAmount().toString()); - - Transaction completedTransaction = transactionService.setAsCompleted(currentTransaction, metadata); - BankAccount updatedSenderBankAccount = addTransaction(senderBankAccount, completedTransaction); - bankAccountRepository.update(receiverBankAccount); - - logger.info("Bank account " + updatedSenderBankAccount.getId() + " deposit transaction " + + currentTransaction.getId() + " completed."); - return completedTransaction; - } catch (NoSuchElementException exception) { - logger.error("Transaction " + transaction.getId() + " deposit error for amount "+ transaction.getAmount() + ": " + exception.getMessage()); - metadata.put("error", exception.getMessage()); - return transactionService.setAsError(transaction, metadata); - } catch (TransactionException exception) { - logger.error("Transaction " + transaction.getId() + " of " + transaction.getAmount() + " is invalid."); - metadata.put("error", exception.getMessage()); - return transactionService.setAsRefused(transaction, metadata); - } catch (BankAccountException exception) { - logger.error("Transaction " + transaction.getId() + " deposit refused for amount "+ transaction.getAmount() + ": " + exception.getMessage()); - metadata.put("error", exception.getMessage()); - return transactionService.setAsRefused(transaction, metadata); - } + public BankAccount findBankAccount(Long bankAccountId){ + return bankAccountRepository.findById(bankAccountId) + .orElseThrow(() -> new NoSuchElementException("Sender bank account " + bankAccountId + " is not found.")); } - private void processCreditTransaction(BankAccount senderBankAccount, BankAccount receiverBankAccount, Transaction transactionToCredit) { - BigDecimal euroAmount = ExchangeRateUtils.getEuroAmountFrom(transactionToCredit.getCurrency(), transactionToCredit.getAmount()); + @Override + public void postTransaction(Transaction transactionToPost, BankAccount emitterBankAccount, BankAccount receiverBankAccount) { + BigDecimal euroAmount = ExchangeRateUtils.getEuroAmountFrom(transactionToPost.getCurrency(), transactionToPost.getAmount()); if (euroAmount.signum() < 0) { - throw new TransactionException("Credit transaction " + transactionToCredit.getId() + " should have positive value, actual value: " + euroAmount); + throw new TransactionException("Credit transaction " + transactionToPost.getId() + " should have positive value, actual value: " + euroAmount); } - senderBankAccount.getBalance().minus(Money.of(euroAmount)); + emitterBankAccount.getBalance().minus(Money.of(euroAmount)); receiverBankAccount.getBalance().plus(Money.of(euroAmount)); + logger.debug("add amount " + emitterBankAccount.getBalance() + " " + transactionToPost.getCurrency() + + " to bank account" + receiverBankAccount.getId() + " from bank account " + emitterBankAccount.getId()); } - private BankAccount addTransaction(BankAccount currentSenderBankAccount, Transaction newTransaction) { - Optional optionalStoredTransaction = currentSenderBankAccount.getIssuedTransactions().stream() - .filter(transaction -> transaction.getId().equals(newTransaction.getId())) + @Override + public BankAccount addTransaction(Transaction transaction, BankAccount bankAccount) { + Optional optionalStoredTransaction = bankAccount.getIssuedTransactions().stream() + .filter(actualTransaction -> actualTransaction.getId().equals(transaction.getId())) .findFirst(); if (optionalStoredTransaction.isPresent()) { - Transaction mergedTransaction = transactionService.mergeTransactions(optionalStoredTransaction.get(), newTransaction); - currentSenderBankAccount.getIssuedTransactions() + Transaction mergedTransaction = transactionService.mergeTransactions(optionalStoredTransaction.get(), transaction); + bankAccount.getIssuedTransactions() .removeIf(existingTransaction -> existingTransaction.getId().equals(mergedTransaction.getId())); - currentSenderBankAccount.addTransaction(mergedTransaction); + bankAccount.addTransaction(mergedTransaction); } else { - currentSenderBankAccount.addTransaction(newTransaction); + bankAccount.addTransaction(transaction); } - return bankAccountRepository.update(currentSenderBankAccount); + return bankAccount; + } + + @Override + public BankAccount updateBankAccount(BankAccount bankAccount) { + logger.debug("update bank account" + bankAccount.getId()); + bankAccountValidator.validateBankAccount(bankAccount); + return bankAccountRepository.update(bankAccount); } } diff --git a/application/src/main/java/com/cdx/bas/application/transaction/TransactionRepository.java b/application/src/main/java/com/cdx/bas/application/transaction/TransactionRepository.java index 39b4df45..99a1d5ad 100644 --- a/application/src/main/java/com/cdx/bas/application/transaction/TransactionRepository.java +++ b/application/src/main/java/com/cdx/bas/application/transaction/TransactionRepository.java @@ -1,8 +1,10 @@ package com.cdx.bas.application.transaction; import com.cdx.bas.domain.transaction.Transaction; +import com.cdx.bas.domain.transaction.TransactionException; import com.cdx.bas.domain.transaction.TransactionPersistencePort; import com.cdx.bas.domain.transaction.TransactionStatus; +import com.cdx.bas.domain.transaction.validation.TransactionValidator; import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; import io.quarkus.panache.common.Parameters; import io.quarkus.panache.common.Sort; diff --git a/application/src/main/java/com/cdx/bas/application/transaction/TransactionServiceImpl.java b/application/src/main/java/com/cdx/bas/application/transaction/TransactionServiceImpl.java index 1a1ac326..0379aa88 100644 --- a/application/src/main/java/com/cdx/bas/application/transaction/TransactionServiceImpl.java +++ b/application/src/main/java/com/cdx/bas/application/transaction/TransactionServiceImpl.java @@ -1,81 +1,50 @@ package com.cdx.bas.application.transaction; -import com.cdx.bas.domain.bank.account.BankAccountServicePort; +import com.cdx.bas.application.bank.account.BankAccountServiceImpl; +import com.cdx.bas.domain.bank.account.*; import com.cdx.bas.domain.transaction.*; +import com.cdx.bas.domain.transaction.validation.TransactionValidator; import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Inject; import jakarta.transaction.Transactional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; import static com.cdx.bas.domain.transaction.TransactionStatus.*; -import static jakarta.transaction.Transactional.TxType.MANDATORY; +import static jakarta.transaction.Transactional.TxType.REQUIRES_NEW; @RequestScoped public class TransactionServiceImpl implements TransactionServicePort { - private static final Logger logger = LoggerFactory.getLogger(TransactionServiceImpl.class); - @Inject - TransactionPersistencePort transactionRepository; - + TransactionPersistencePort transactionPersistencePort; + @Inject - BankAccountServicePort bankAccountService; - - public List findAll() { -// return transactionRepository.findAll().stream().map(null); - return new ArrayList<>(); - } - - @Override - public void process(Transaction transaction) { - if (TransactionType.CREDIT.equals(transaction.getType())) { - logger.info("Transaction " + transaction.getId() + " processing..."); - bankAccountService.deposit(transaction); - } - } + TransactionValidator transactionValidator; - @Override - @Transactional(value = MANDATORY) - public Transaction setAsOutstanding(Transaction transaction) { - if (UNPROCESSED.equals(transaction.getStatus())) { - transaction.setStatus(OUTSTANDING); - } else { - throw new TransactionException("Transaction is not longer unprocessed."); - } - return transactionRepository.update(transaction); - } - @Override - @Transactional(value = MANDATORY) - public Transaction setAsCompleted(Transaction completedTransaction, Map metadata) { - setState(completedTransaction, metadata, COMPLETED); - return transactionRepository.update(completedTransaction); - } + @Inject + TransactionProcessingServicePort transactionProcessing; @Override - @Transactional(value = MANDATORY) - public Transaction setAsError(Transaction erroredTransaction, Map metadata) { - setState(erroredTransaction, metadata, ERROR); - return transactionRepository.update(erroredTransaction); + public Set getAll() { + return transactionPersistencePort.getAll(); } @Override - @Transactional(value = MANDATORY) - public Transaction setAsRefused(Transaction refusedTransaction, Map metadata) { - setState(refusedTransaction, metadata, REFUSED); - return transactionRepository.update(refusedTransaction); + public Set findAllByStatus(String status) throws IllegalArgumentException { + TransactionStatus transactionStatus = TransactionStatus.fromString(status); + return transactionPersistencePort.findAllByStatus(transactionStatus); } - private void setState(Transaction refusedTransaction, Map metadata, TransactionStatus status) { - refusedTransaction.setMetadata(metadata); - refusedTransaction.setStatus(status); - } @Override + public void createTransaction(Transaction transaction) throws TransactionException { + transactionValidator.validateNewTransaction(transaction); + transactionPersistencePort.create(transaction); + } + public Transaction mergeTransactions(Transaction oldTransaction, Transaction newTransaction){ oldTransaction.setId(newTransaction.getId()); oldTransaction.setSenderAccountId(newTransaction.getSenderAccountId()); @@ -89,4 +58,11 @@ public Transaction mergeTransactions(Transaction oldTransaction, Transaction new oldTransaction.setMetadata(newTransaction.getMetadata()); return oldTransaction; } + + @Override + public void process(Transaction transaction) { + if (TransactionType.CREDIT.equals(transaction.getType())) { + transactionProcessing.credit(transaction); + } + } } diff --git a/application/src/main/java/com/cdx/bas/application/transaction/TransactionStatusServiceImpl.java b/application/src/main/java/com/cdx/bas/application/transaction/TransactionStatusServiceImpl.java new file mode 100644 index 00000000..7ed925ef --- /dev/null +++ b/application/src/main/java/com/cdx/bas/application/transaction/TransactionStatusServiceImpl.java @@ -0,0 +1,44 @@ +package com.cdx.bas.application.transaction; + +import com.cdx.bas.domain.transaction.Transaction; +import com.cdx.bas.domain.transaction.TransactionException; +import com.cdx.bas.domain.transaction.TransactionStatus; +import com.cdx.bas.domain.transaction.TransactionStatusServicePort; +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; + +import java.util.Map; + +import static com.cdx.bas.domain.transaction.TransactionStatus.OUTSTANDING; +import static com.cdx.bas.domain.transaction.TransactionStatus.UNPROCESSED; +import static jakarta.transaction.Transactional.TxType.MANDATORY; + +@RequestScoped +public class TransactionStatusServiceImpl implements TransactionStatusServicePort { + + @Inject + TransactionRepository transactionPersistencePort; + + @Override + @Transactional(value = MANDATORY) + public Transaction setAsOutstanding(Transaction transaction) throws TransactionException { + if (UNPROCESSED.equals(transaction.getStatus())) { + transaction.setStatus(OUTSTANDING); + } else { + throw new TransactionException("Transaction is not longer unprocessed."); + } + return transactionPersistencePort.update(transaction); + } + + @Override + @Transactional(value = MANDATORY) + public Transaction setStatus(Transaction transaction, TransactionStatus status, Map metadata) throws TransactionException { + if (transaction == null) { + throw new TransactionException("Transaction is null."); + } + transaction.setStatus(status); + transaction.setMetadata(metadata); + return transactionPersistencePort.update(transaction); + } +} diff --git a/application/src/main/java/com/cdx/bas/application/transaction/TransactionTypeProcessingServiceImpl.java b/application/src/main/java/com/cdx/bas/application/transaction/TransactionTypeProcessingServiceImpl.java new file mode 100644 index 00000000..6bc104f1 --- /dev/null +++ b/application/src/main/java/com/cdx/bas/application/transaction/TransactionTypeProcessingServiceImpl.java @@ -0,0 +1,97 @@ +package com.cdx.bas.application.transaction; + +import com.cdx.bas.application.bank.account.BankAccountServiceImpl; +import com.cdx.bas.domain.bank.account.BankAccount; +import com.cdx.bas.domain.bank.account.BankAccountException; +import com.cdx.bas.domain.bank.account.BankAccountPersistencePort; +import com.cdx.bas.domain.bank.account.BankAccountServicePort; +import com.cdx.bas.domain.transaction.Transaction; +import com.cdx.bas.domain.transaction.TransactionException; +import com.cdx.bas.domain.transaction.TransactionProcessingServicePort; +import com.cdx.bas.domain.transaction.TransactionStatusServicePort; +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; + +import static com.cdx.bas.domain.transaction.TransactionStatus.*; +import static com.cdx.bas.domain.transaction.TransactionStatus.REFUSED; +import static jakarta.transaction.Transactional.TxType.REQUIRES_NEW; + +@RequestScoped +public class TransactionProcessingServiceImpl implements TransactionProcessingServicePort { + + private static final Logger logger = LoggerFactory.getLogger(BankAccountServiceImpl.class); + + @Inject + TransactionStatusServicePort transactionStatusService; + + @Inject + BankAccountServicePort bankAccountService; + + + @Override + @Transactional(value = REQUIRES_NEW) + public Transaction credit(Transaction transaction) { + logger.info("Transaction " + transaction.getId() + " processing..."); + + try { + BankAccount emitterBankAccount = bankAccountService.findBankAccount(transaction.getSenderAccountId()); + BankAccount receiverBankAccount = bankAccountService.findBankAccount(transaction.getReceiverAccountId()); + Transaction currentTransaction = transactionStatusService.setAsOutstanding(transaction); + + logger.info("Process credit transaction " + currentTransaction.getId() + + " from bank account " + currentTransaction.getSenderAccountId() + + " with amount " + currentTransaction.getAmount() + + " to bank account " + currentTransaction.getReceiverAccountId()); + + Map metadata = new HashMap<>(); + metadata.put("sender_amount_before", emitterBankAccount.getBalance().getAmount().toString()); + metadata.put("receiver_amount_before", receiverBankAccount.getBalance().getAmount().toString()); + bankAccountService.postTransaction(currentTransaction, emitterBankAccount, receiverBankAccount); + metadata.put("sender_amount_after", emitterBankAccount.getBalance().getAmount().toString()); + metadata.put("receiver_amount_after", receiverBankAccount.getBalance().getAmount().toString()); + Transaction completedTransaction = transactionStatusService.setStatus(currentTransaction, COMPLETED, metadata); + BankAccount updatedEmitterBankAccount = bankAccountService.addTransaction(completedTransaction, emitterBankAccount); + bankAccountService.updateBankAccount(receiverBankAccount); + + logger.info("Bank account " + updatedEmitterBankAccount.getId() + " deposit transaction " + + currentTransaction.getId() + " completed."); + return completedTransaction; + } catch (NoSuchElementException exception) { + logger.error("Transaction " + transaction.getId() + " deposit error for amount "+ transaction.getAmount() + ": " + exception.getMessage()); + Map metadata = Map.of("error", exception.getMessage()); + return transactionStatusService.setStatus(transaction, ERROR, metadata); + } catch (TransactionException exception) { + logger.error("Transaction " + transaction.getId() + " of " + transaction.getAmount() + " is invalid."); + Map metadata = Map.of("error", exception.getMessage()); + return transactionStatusService.setStatus(transaction, REFUSED, metadata); + } catch (BankAccountException exception) { + logger.error("Transaction " + transaction.getId() + " deposit refused for amount "+ transaction.getAmount() + ": " + exception.getMessage()); + Map metadata = Map.of("error", exception.getMessage()); + return transactionStatusService.setStatus(transaction, REFUSED, metadata); + } + } + + @Override + public Transaction debit(Transaction transaction) { + //TODO + return null; + } + + @Override + public Transaction deposit(Transaction transaction) { + //TODO + return null; + } + + @Override + public Transaction withdraw(Transaction transaction) { + //TODO + return null; + } +} diff --git a/application/src/test/java/com/cdx/bas/application/bank/account/BankAccountServiceImplTest.java b/application/src/test/java/com/cdx/bas/application/bank/account/BankAccountServiceImplTest.java new file mode 100644 index 00000000..effba431 --- /dev/null +++ b/application/src/test/java/com/cdx/bas/application/bank/account/BankAccountServiceImplTest.java @@ -0,0 +1,44 @@ +package com.cdx.bas.application.bank.account; + +import com.cdx.bas.domain.bank.account.*; +import com.cdx.bas.domain.bank.account.checking.CheckingBankAccount; +import com.cdx.bas.domain.money.Money; +import com.cdx.bas.domain.transaction.*; +import io.quarkus.test.InjectMock; +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.h2.H2DatabaseTestResource; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.time.Instant; +import java.util.*; + +import static com.cdx.bas.domain.transaction.TransactionStatus.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +@QuarkusTest +@QuarkusTestResource(H2DatabaseTestResource.class) +public class BankAccountServiceTest { + + @InjectMock + BankAccountPersistencePort bankAccountRepository; + + @InjectMock + BankAccountValidator bankAccountValidator; + + @InjectMock + TransactionServicePort transactionService; + + @InjectMock + TransactionStatusServicePort transactionStatusService; + + @Inject + BankAccountServicePort bankAccountService; + + + +} diff --git a/application/src/test/java/com/cdx/bas/application/bank/account/BankAccountServiceTest.java b/application/src/test/java/com/cdx/bas/application/bank/account/BankAccountServiceTest.java deleted file mode 100644 index 0592bb23..00000000 --- a/application/src/test/java/com/cdx/bas/application/bank/account/BankAccountServiceTest.java +++ /dev/null @@ -1,305 +0,0 @@ -package com.cdx.bas.application.bank.account; - -import com.cdx.bas.domain.bank.account.*; -import com.cdx.bas.domain.bank.account.checking.CheckingBankAccount; -import com.cdx.bas.domain.money.Money; -import com.cdx.bas.domain.transaction.Transaction; -import com.cdx.bas.domain.transaction.TransactionServicePort; -import com.cdx.bas.domain.transaction.TransactionStatus; -import com.cdx.bas.domain.transaction.TransactionType; -import io.quarkus.test.InjectMock; -import io.quarkus.test.common.QuarkusTestResource; -import io.quarkus.test.h2.H2DatabaseTestResource; -import io.quarkus.test.junit.QuarkusTest; -import jakarta.inject.Inject; -import org.junit.jupiter.api.Test; - -import java.math.BigDecimal; -import java.time.Instant; -import java.util.*; - -import static com.cdx.bas.domain.transaction.TransactionStatus.*; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; - -@QuarkusTest -@QuarkusTestResource(H2DatabaseTestResource.class) -public class BankAccountServiceTest { - - @InjectMock - BankAccountPersistencePort bankAccountRepository; - - @InjectMock - BankAccountValidator bankAccountValidator; - - @InjectMock - TransactionServicePort transactionService; - - @Inject - BankAccountServicePort bankAccountService; - - @Test - public void deposit_shouldThrowNoSuchElementException_whenSenderAccountIsNotFound() { - long senderAccountId = 99L; - long receiverAccountId = 77L; - Money amountOfMoney = Money.of(new BigDecimal(0)); - Instant instantDate = Instant.now(); - Transaction transaction = createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), UNPROCESSED, instantDate, new HashMap<>()); - Map metadata = Map.of("error", "Sender bank account 99 is not found."); - Transaction erroredTransaction = createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), ERROR, instantDate, metadata); - - when(bankAccountRepository.findById(senderAccountId)).thenThrow(new NoSuchElementException("Sender bank account 99 is not found.")); - when(transactionService.setAsError(eq(transaction), eq(metadata))).thenReturn(erroredTransaction); - - Transaction returnedTransaction = bankAccountService.deposit(transaction); - - assertThat(returnedTransaction).usingRecursiveComparison() - .isEqualTo(createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), ERROR, instantDate, metadata)); - verify(bankAccountRepository).findById(eq(senderAccountId)); - verify(transactionService).setAsError(eq(transaction), eq(metadata)); - verifyNoMoreInteractions(bankAccountRepository, transactionService); - verifyNoInteractions(bankAccountValidator); - } - - @Test - public void deposit_shouldThrowNoSuchElementException_whenReceiverAccountIsNotFound() { - long senderAccountId = 99L; - long receiverAccountId = 77L; - Money amountOfMoney = Money.of(new BigDecimal(0)); - Instant instantDate = Instant.now(); - Transaction transaction = createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), UNPROCESSED, instantDate, new HashMap<>()); - Map metadata = Map.of("error", "Receiver bank account 77 is not found."); - Transaction erroredTransaction = createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), ERROR, instantDate, metadata); - BankAccount senderBankAccount = createBankAccountUtils(senderAccountId, "0", transaction); - - when(bankAccountRepository.findById(senderAccountId)).thenReturn(Optional.of(senderBankAccount)); - when(bankAccountRepository.findById(receiverAccountId)).thenThrow(new NoSuchElementException("Receiver bank account 77 is not found.")); - when(transactionService.setAsError(eq(transaction), eq(metadata))).thenReturn(erroredTransaction); - - Transaction returnedTransaction = bankAccountService.deposit(transaction); - - assertThat(returnedTransaction).usingRecursiveComparison() - .isEqualTo(createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), ERROR, instantDate, metadata)); - verify(bankAccountRepository).findById(eq(senderAccountId)); - verify(bankAccountRepository).findById(eq(receiverAccountId)); - verify(transactionService).setAsError(eq(transaction), eq(metadata)); - verifyNoMoreInteractions(bankAccountRepository, transactionService); - verifyNoInteractions(bankAccountValidator); - } - - @Test - public void deposit_shouldReturnCompletedTransaction_whenAccountsAreFound_fromCreditTransaction() { - long senderAccountId = 99L; - long receiverAccountId = 77L; - Money amountOfMoney = Money.of(new BigDecimal(1000)); - Instant instantDate = Instant.now(); - Transaction oldTransaction = createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), UNPROCESSED, instantDate, new HashMap<>()); - BankAccount senderBankAccount = createBankAccountUtils(senderAccountId, "1000", oldTransaction); - BankAccount receiverBankAccount = createBankAccountUtils(receiverAccountId, "0"); - - Transaction transaction = createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), UNPROCESSED, instantDate, new HashMap<>()); - Transaction outstandingTransaction = createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), OUTSTANDING, instantDate, new HashMap<>()); - - Map metadataAfter = Map.of("sender_amount_before", "1000", - "receiver_amount_before", "0", - "sender_amount_after", "0", - "receiver_amount_after", "1000"); - Transaction completedTransaction = createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), COMPLETED, instantDate, metadataAfter); - BankAccount updatedSenderBankAccount = createBankAccountUtils(senderAccountId, "0", completedTransaction); - - when(bankAccountRepository.findById(senderAccountId)).thenReturn(Optional.of(senderBankAccount)); - when(bankAccountRepository.findById(receiverAccountId)).thenReturn(Optional.of(receiverBankAccount)); - when(transactionService.setAsOutstanding(transaction)).thenReturn(outstandingTransaction); - when(transactionService.setAsCompleted(outstandingTransaction, metadataAfter)).thenReturn(completedTransaction); - when(transactionService.mergeTransactions(transaction, completedTransaction)).thenReturn(completedTransaction); - when(bankAccountRepository.update(updatedSenderBankAccount)).thenReturn(updatedSenderBankAccount); - - Transaction returnedTransaction = bankAccountService.deposit(transaction); - - receiverBankAccount.getBalance().plus(senderBankAccount.getBalance()); - senderBankAccount.getBalance().minus(senderBankAccount.getBalance()); - - assertThat(returnedTransaction).usingRecursiveComparison() - .isEqualTo(createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), COMPLETED, instantDate, metadataAfter)); - } - - @Test - public void deposit_shouldAddMoneyToTheReceiverAccount_whenAccountsAreFound_fromCreditTransaction() { - long senderAccountId = 99L; - long receiverAccountId = 77L; - Money amountOfMoney = Money.of(new BigDecimal(1000)); - Instant instantDate = Instant.now(); - Transaction oldTransaction = createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), UNPROCESSED, instantDate, new HashMap<>()); - BankAccount senderBankAccount = createBankAccountUtils(senderAccountId, "1000", oldTransaction); - BankAccount receiverBankAccount = createBankAccountUtils(receiverAccountId, "0"); - - Transaction transaction = createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), UNPROCESSED, instantDate, new HashMap<>()); - Transaction outstandingTransaction = createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), OUTSTANDING, instantDate, new HashMap<>()); - - Map metadataAfter = Map.of("sender_amount_before", "1000", - "receiver_amount_before", "0", - "sender_amount_after", "0", - "receiver_amount_after", "1000"); - Transaction completedTransaction = createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), COMPLETED, instantDate, metadataAfter); - BankAccount updatedSenderBankAccount = createBankAccountUtils(senderAccountId, "0", completedTransaction); - BankAccount updatedReceiverBankAccount = createBankAccountUtils(receiverAccountId, "1000"); - - when(bankAccountRepository.findById(senderAccountId)).thenReturn(Optional.of(senderBankAccount)); - when(bankAccountRepository.findById(receiverAccountId)).thenReturn(Optional.of(receiverBankAccount)); - when(transactionService.setAsOutstanding(transaction)).thenReturn(outstandingTransaction); - when(transactionService.setAsCompleted(outstandingTransaction, metadataAfter)).thenReturn(completedTransaction); - when(transactionService.mergeTransactions(transaction, completedTransaction)).thenReturn(completedTransaction); - when(bankAccountRepository.update(updatedSenderBankAccount)).thenReturn(updatedSenderBankAccount); - - bankAccountService.deposit(transaction); - - verify(bankAccountRepository).findById(eq(senderAccountId)); - verify(bankAccountRepository).findById(eq(receiverAccountId)); - verify(transactionService).setAsOutstanding(transaction); - verify(bankAccountValidator).validateBankAccount(eq(senderBankAccount)); - verify(bankAccountValidator).validateBankAccount(eq(receiverBankAccount)); - verify(transactionService).setAsCompleted(eq(outstandingTransaction), eq(metadataAfter)); - verify(transactionService).mergeTransactions(transaction, completedTransaction); - verify(bankAccountRepository).update(eq(updatedSenderBankAccount)); - verify(bankAccountRepository).update(eq(updatedReceiverBankAccount)); - verifyNoMoreInteractions(bankAccountRepository, transactionService, bankAccountValidator); - } - - @Test - public void deposit_shouldReturnErroredTransaction_whenAmountIsNegative() { - long senderAccountId = 99L; - long receiverAccountId = 77L; - Money amountOfMoney = Money.of(new BigDecimal(-1000)); - Instant instantDate = Instant.now(); - Transaction oldTransaction = createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), UNPROCESSED, instantDate, new HashMap<>()); - BankAccount senderBankAccount = createBankAccountUtils(senderAccountId, "1000", oldTransaction); - BankAccount receiverBankAccount = createBankAccountUtils(receiverAccountId, "0"); - - Transaction transaction = createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), UNPROCESSED, instantDate, new HashMap<>()); - Transaction outstandingTransaction = createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), OUTSTANDING, instantDate, new HashMap<>()); - - Map metadataAfter = Map.of("sender_amount_before", "1000", - "receiver_amount_before", "0", - "error", "Credit transaction 1 should have positive value, actual value: -1000"); - Transaction refusedTransaction = createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), ERROR, instantDate, metadataAfter); - - when(bankAccountRepository.findById(senderAccountId)).thenReturn(Optional.of(senderBankAccount)); - when(bankAccountRepository.findById(receiverAccountId)).thenReturn(Optional.of(receiverBankAccount)); - when(transactionService.setAsOutstanding(transaction)).thenReturn(outstandingTransaction); - when(transactionService.setAsRefused(eq(transaction), eq(metadataAfter))).thenReturn(refusedTransaction); - - Transaction returnedTransaction = bankAccountService.deposit(transaction); - - assertThat(returnedTransaction).usingRecursiveComparison() - .isEqualTo(createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), ERROR, instantDate, metadataAfter)); - verify(bankAccountRepository).findById(eq(senderAccountId)); - verify(bankAccountRepository).findById(eq(receiverAccountId)); - verify(transactionService).setAsOutstanding(transaction); - verify(transactionService).setAsRefused(transaction, metadataAfter); - verifyNoMoreInteractions(bankAccountRepository, transactionService, bankAccountValidator); - } - - @Test - public void deposit_shouldReturnErroredTransaction_whenSenderBankAccountValidatorThrowsException() { - long senderAccountId = 99L; - long receiverAccountId = 77L; - Money amountOfMoney = Money.of(new BigDecimal(1000)); - Instant instantDate = Instant.now(); - Transaction oldTransaction = createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), UNPROCESSED, instantDate, new HashMap<>()); - BankAccount senderBankAccount = createBankAccountUtils(senderAccountId, "1000", oldTransaction); - BankAccount receiverBankAccount = createBankAccountUtils(receiverAccountId, "0"); - - Transaction transaction = createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), UNPROCESSED, instantDate, new HashMap<>()); - Transaction outstandingTransaction = createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), OUTSTANDING, instantDate, new HashMap<>()); - - Map metadataAfter = Map.of("sender_amount_before", "1000", - "receiver_amount_before", "0", - "error", "Amount of credit should not be negative"); - Transaction refusedTransaction = createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), ERROR, instantDate, metadataAfter); - - when(bankAccountRepository.findById(senderAccountId)).thenReturn(Optional.of(senderBankAccount)); - when(bankAccountRepository.findById(receiverAccountId)).thenReturn(Optional.of(receiverBankAccount)); - when(transactionService.setAsOutstanding(transaction)).thenReturn(outstandingTransaction); - doThrow(new BankAccountException("Amount of credit should not be negative")).when(bankAccountValidator).validateBankAccount(senderBankAccount); - when(transactionService.setAsRefused(eq(transaction), eq(metadataAfter))).thenReturn(refusedTransaction); - - Transaction returnedTransaction = bankAccountService.deposit(transaction); - - assertThat(returnedTransaction).usingRecursiveComparison() - .isEqualTo(createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), ERROR, instantDate, metadataAfter)); - verify(bankAccountRepository).findById(eq(senderAccountId)); - verify(bankAccountRepository).findById(eq(receiverAccountId)); - verify(transactionService).setAsOutstanding(transaction); - verify(bankAccountValidator).validateBankAccount(senderBankAccount); - verify(transactionService).setAsRefused(transaction, metadataAfter); - verifyNoMoreInteractions(bankAccountRepository, transactionService, bankAccountValidator); - } - - @Test - public void deposit_shouldReturnErroredTransaction_whenReceiverBankAccountValidatorThrowsException() { - long senderAccountId = 99L; - long receiverAccountId = 77L; - Money amountOfMoney = Money.of(new BigDecimal(1000)); - Instant instantDate = Instant.now(); - Transaction oldTransaction = createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), UNPROCESSED, instantDate, new HashMap<>()); - BankAccount senderBankAccount = createBankAccountUtils(senderAccountId, "1000", oldTransaction); - BankAccount receiverBankAccount = createBankAccountUtils(receiverAccountId, "0"); - - Transaction transaction = createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), UNPROCESSED, instantDate, new HashMap<>()); - Transaction outstandingTransaction = createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), OUTSTANDING, instantDate, new HashMap<>()); - - Map metadataAfter = Map.of("sender_amount_before", "1000", - "receiver_amount_before", "0", - "error", "Amount of credit should not be negative"); - Transaction refusedTransaction = createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), ERROR, instantDate, metadataAfter); - - when(bankAccountRepository.findById(senderAccountId)).thenReturn(Optional.of(senderBankAccount)); - when(bankAccountRepository.findById(receiverAccountId)).thenReturn(Optional.of(receiverBankAccount)); - when(transactionService.setAsOutstanding(transaction)).thenReturn(outstandingTransaction); - doThrow(new BankAccountException("Amount of credit should not be negative")).when(bankAccountValidator).validateBankAccount(receiverBankAccount); - when(transactionService.setAsRefused(eq(transaction), eq(metadataAfter))).thenReturn(refusedTransaction); - - Transaction returnedTransaction = bankAccountService.deposit(transaction); - - assertThat(returnedTransaction).usingRecursiveComparison() - .isEqualTo(createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), ERROR, instantDate, metadataAfter)); - verify(bankAccountRepository).findById(eq(senderAccountId)); - verify(bankAccountRepository).findById(eq(receiverAccountId)); - verify(transactionService).setAsOutstanding(transaction); - verify(bankAccountValidator).validateBankAccount(senderBankAccount); - verify(bankAccountValidator).validateBankAccount(receiverBankAccount); - verify(transactionService).setAsRefused(transaction, metadataAfter); - verifyNoMoreInteractions(bankAccountRepository, transactionService, bankAccountValidator); - } - - private static BankAccount createBankAccountUtils(long accountId, String amount, Transaction ...transactions) { - BankAccount bankAccount = new CheckingBankAccount(); - bankAccount.setId(accountId); - bankAccount.setType(AccountType.CHECKING); - bankAccount.setBalance(new Money(new BigDecimal(amount))); - List customersId = new ArrayList<>(); - customersId.add(99L); - bankAccount.setCustomersId(customersId); - HashSet transactionHistory = new HashSet<>(); - Collections.addAll(transactionHistory, transactions); - bankAccount.setIssuedTransactions(transactionHistory); - return bankAccount; - } - - private static Transaction createTransactionUtils(long senderAccountId, long receiverAccountId, - BigDecimal amount, TransactionStatus status, - Instant date, Map metadata) { - return Transaction.builder() - .id(1L) - .amount(amount) - .currency("EUR") - .senderAccountId(senderAccountId) - .receiverAccountId(receiverAccountId) - .type(TransactionType.CREDIT) - .status(status) - .date(date) - .label("transaction of " + amount) - .metadata(metadata).build(); - } -} diff --git a/application/src/test/java/com/cdx/bas/application/bank/account/BankAccountUtils.java b/application/src/test/java/com/cdx/bas/application/bank/account/BankAccountUtils.java new file mode 100644 index 00000000..f0e716dc --- /dev/null +++ b/application/src/test/java/com/cdx/bas/application/bank/account/BankAccountUtils.java @@ -0,0 +1,4 @@ +package com.cdx.bas.application.bank.account; + +public class BankAccountUtils { +} diff --git a/application/src/test/java/com/cdx/bas/application/bank/transaction/TransactionServiceTest.java b/application/src/test/java/com/cdx/bas/application/bank/transaction/TransactionServiceTest.java deleted file mode 100644 index 382fe4ce..00000000 --- a/application/src/test/java/com/cdx/bas/application/bank/transaction/TransactionServiceTest.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.cdx.bas.application.bank.transaction; - -import static com.cdx.bas.domain.transaction.TransactionStatus.UNPROCESSED; -import static com.cdx.bas.domain.transaction.TransactionType.CREDIT; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -import java.math.BigDecimal; -import java.time.Instant; -import io.quarkus.test.InjectMock; -import io.quarkus.test.common.QuarkusTestResource; -import io.quarkus.test.h2.H2DatabaseTestResource; -import jakarta.inject.Inject; - -import com.cdx.bas.domain.bank.account.BankAccountServicePort; -import com.cdx.bas.domain.transaction.Transaction; -import com.cdx.bas.domain.transaction.TransactionServicePort; - -import org.junit.jupiter.api.Test; - -import io.quarkus.test.junit.QuarkusTest; - -@QuarkusTest -@QuarkusTestResource(H2DatabaseTestResource.class) -public class TransactionServiceTest { - - @Inject - TransactionServicePort transactionService; - - @InjectMock - BankAccountServicePort bankAccountService; - - @Test - public void processTransaction_should_processBankAccountDeposit_when_creditTransactionWithPositiveAmount() { - Transaction transaction = Transaction.builder() - .id(1L) - .amount(new BigDecimal(100)) - .senderAccountId(100L) - .receiverAccountId(200L) - .type(CREDIT) - .status(UNPROCESSED) - .date(Instant.now()) - .label("deposit of 100 euros") - .build(); - transactionService.process(transaction); - - verify(bankAccountService).deposit(transaction); - verifyNoMoreInteractions(bankAccountService); - } - - @Test - public void processTransaction_should_processBankAccountDeposit_when_creditTransactionWithNegativeAmount() { - Transaction transaction = Transaction.builder() - .id(1L) - .amount(new BigDecimal(-100)) - .senderAccountId(99L) - .type(CREDIT) - .status(UNPROCESSED) - .date(Instant.now()) - .label("deposit of -100 euros") - .build(); - transactionService.process(transaction); - - verify(bankAccountService).deposit(transaction); - verifyNoMoreInteractions(bankAccountService); - } -} diff --git a/application/src/test/java/com/cdx/bas/application/bank/scheduler/SchedulerTest.java b/application/src/test/java/com/cdx/bas/application/scheduler/SchedulerTest.java similarity index 97% rename from application/src/test/java/com/cdx/bas/application/bank/scheduler/SchedulerTest.java rename to application/src/test/java/com/cdx/bas/application/scheduler/SchedulerTest.java index 91ddc409..9685be18 100644 --- a/application/src/test/java/com/cdx/bas/application/bank/scheduler/SchedulerTest.java +++ b/application/src/test/java/com/cdx/bas/application/scheduler/SchedulerTest.java @@ -1,6 +1,5 @@ -package com.cdx.bas.application.bank.scheduler; +package com.cdx.bas.application.scheduler; -import com.cdx.bas.application.scheduler.Scheduler; import com.cdx.bas.domain.transaction.*; import io.quarkus.test.InjectMock; import io.quarkus.test.junit.QuarkusTest; diff --git a/application/src/test/java/com/cdx/bas/application/bank/scheduler/SchedulerTestProfile.java b/application/src/test/java/com/cdx/bas/application/scheduler/SchedulerTestProfile.java similarity index 90% rename from application/src/test/java/com/cdx/bas/application/bank/scheduler/SchedulerTestProfile.java rename to application/src/test/java/com/cdx/bas/application/scheduler/SchedulerTestProfile.java index 3b51a2c7..e534c980 100644 --- a/application/src/test/java/com/cdx/bas/application/bank/scheduler/SchedulerTestProfile.java +++ b/application/src/test/java/com/cdx/bas/application/scheduler/SchedulerTestProfile.java @@ -1,4 +1,4 @@ -package com.cdx.bas.application.bank.scheduler; +package com.cdx.bas.application.scheduler; import io.quarkus.test.junit.QuarkusTestProfile; diff --git a/application/src/test/java/com/cdx/bas/application/bank/transaction/TransactionMapperTest.java b/application/src/test/java/com/cdx/bas/application/transaction/TransactionMapperTest.java similarity index 99% rename from application/src/test/java/com/cdx/bas/application/bank/transaction/TransactionMapperTest.java rename to application/src/test/java/com/cdx/bas/application/transaction/TransactionMapperTest.java index 388db71a..87237526 100644 --- a/application/src/test/java/com/cdx/bas/application/bank/transaction/TransactionMapperTest.java +++ b/application/src/test/java/com/cdx/bas/application/transaction/TransactionMapperTest.java @@ -1,4 +1,4 @@ -package com.cdx.bas.application.bank.transaction; +package com.cdx.bas.application.transaction; import com.cdx.bas.application.bank.account.BankAccountEntity; import com.cdx.bas.application.bank.account.BankAccountRepository; diff --git a/application/src/test/java/com/cdx/bas/application/transaction/TransactionRepositoryTest.java b/application/src/test/java/com/cdx/bas/application/transaction/TransactionRepositoryTest.java index 6a52f251..9199cc12 100644 --- a/application/src/test/java/com/cdx/bas/application/transaction/TransactionRepositoryTest.java +++ b/application/src/test/java/com/cdx/bas/application/transaction/TransactionRepositoryTest.java @@ -1,5 +1,6 @@ package com.cdx.bas.application.transaction; +import com.cdx.bas.application.transaction.TransactionRepository; import com.cdx.bas.domain.transaction.Transaction; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.h2.H2DatabaseTestResource; diff --git a/application/src/test/java/com/cdx/bas/application/transaction/TransactionServiceImplTest.java b/application/src/test/java/com/cdx/bas/application/transaction/TransactionServiceImplTest.java new file mode 100644 index 00000000..8a3a8ba7 --- /dev/null +++ b/application/src/test/java/com/cdx/bas/application/transaction/TransactionServiceImplTest.java @@ -0,0 +1,179 @@ +package com.cdx.bas.application.transaction; + +import com.cdx.bas.domain.transaction.*; +import com.cdx.bas.domain.transaction.validation.TransactionValidator; +import io.quarkus.test.InjectMock; +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.h2.H2DatabaseTestResource; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.time.Instant; +import java.util.Set; + +import static com.cdx.bas.domain.transaction.TransactionStatus.COMPLETED; +import static com.cdx.bas.domain.transaction.TransactionStatus.UNPROCESSED; +import static com.cdx.bas.domain.transaction.TransactionType.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +@QuarkusTest +@QuarkusTestResource(H2DatabaseTestResource.class) +public class TransactionServiceTest { + @InjectMock + TransactionPersistencePort transactionPersistencePort; + + @InjectMock + TransactionTypeProcessingServicePort transactionTypeProcessingServicePort; + + @InjectMock + TransactionValidator transactionValidator; + + @Inject + TransactionServicePort transactionService; + + + @Test + public void getAll_shouldReturnAllTransactions_whenRepositoryReturnsTransactions() { + Set transactions = Set.of( + Transaction.builder().id(1L).build(), + Transaction.builder().id(2L).build(), + Transaction.builder().id(3L).build() + ); + + when(transactionPersistencePort.getAll()).thenReturn(transactions); + + Set actual = transactionService.getAll(); + assertThat(actual).isEqualTo(transactions); + verify(transactionPersistencePort).getAll(); + verifyNoMoreInteractions(transactionPersistencePort); + } + + @Test + public void findAllByStatus_shouldReturnTransactionCorrespondingToStatus_whenStatusIsValid() { + Set transactions = Set.of( + Transaction.builder().id(1L).status(COMPLETED).build(), + Transaction.builder().id(2L).status(COMPLETED).build(), + Transaction.builder().id(3L).status(COMPLETED).build() + ); + + when(transactionPersistencePort.findAllByStatus(COMPLETED)).thenReturn(transactions); + + Set actual = transactionService.findAllByStatus("COMPLETED"); + assertThat(actual).isEqualTo(transactions); + verify(transactionPersistencePort).findAllByStatus(COMPLETED); + verifyNoMoreInteractions(transactionPersistencePort); + } + + @Test + public void findAllByStatus_shouldThrowException_whenStatusIsInvalid() { + try { + transactionService.findAllByStatus("INVALID"); + } catch (IllegalArgumentException exception) { + assertThat(exception.getMessage()).isEqualTo("Invalid status: INVALID"); + } + verifyNoInteractions(transactionPersistencePort); + } + + @Test + public void createTransaction_shouldCreateTransaction_whenTransactionIsValid() { + Transaction newTransaction = TransactionTestUtils.createTransactionUtils(1L, 100L, Instant.now(), "transaction test"); + transactionService.createTransaction(newTransaction); + verify(transactionValidator).validateNewTransaction(newTransaction); + verify(transactionPersistencePort).create(newTransaction); + verifyNoMoreInteractions(transactionValidator, transactionPersistencePort); + } + + @Test + public void createTransaction_shouldThrowException_whenTransactionIsInvalid() { + Transaction invalidTransaction = new Transaction(); + doThrow(new TransactionException("invalid transaction...")).when(transactionValidator).validateNewTransaction(invalidTransaction); + + try { + transactionService.createTransaction(new Transaction()); + } catch (TransactionException exception) { + assertThat(exception.getMessage()).isEqualTo("invalid transaction..."); + } + verify(transactionValidator).validateNewTransaction(invalidTransaction); + verifyNoMoreInteractions(transactionValidator); + verifyNoInteractions(transactionPersistencePort); + } + + @Test + public void mergeTransaction_shouldMergeOldTransactionWithNewTransaction_whenOldTransactionAndNewTransactionAreValid() { + Transaction oldTransaction = Transaction.builder() + .id(1L) + .amount(new BigDecimal(100)) + .senderAccountId(10L) + .receiverAccountId(11L) + .type(CREDIT) + .status(UNPROCESSED) + .date(Instant.now()) + .label("old transaction") + .build(); + + Instant dateAfter = Instant.now(); + BigDecimal bigDecimalAfter = new BigDecimal(200); + String labelAfter = "new transaction"; + Transaction newTransaction = Transaction.builder() + .id(2L) + .amount(bigDecimalAfter) + .senderAccountId(20L) + .receiverAccountId(22L) + .type(DEBIT) + .status(UNPROCESSED) + .date(dateAfter) + .label(labelAfter) + .build(); + + Transaction actualTransaction = transactionService.mergeTransactions(oldTransaction, newTransaction); + + oldTransaction.setId(2L); + oldTransaction.setAmount(bigDecimalAfter); + oldTransaction.setSenderAccountId(20L); + oldTransaction.setReceiverAccountId(22L); + oldTransaction.setType(DEBIT); + oldTransaction.setStatus(UNPROCESSED); + oldTransaction.setDate(dateAfter); + oldTransaction.setLabel(labelAfter); + assertThat(actualTransaction).isEqualTo(oldTransaction); + } + + @Test + public void processTransaction_shouldProcessBankAccountCredit_whenTransactionHasCreditType() { + Transaction transaction = Transaction.builder() + .id(1L) + .amount(new BigDecimal(100)) + .senderAccountId(100L) + .receiverAccountId(200L) + .type(CREDIT) + .status(UNPROCESSED) + .date(Instant.now()) + .label("deposit of 100 euros") + .build(); + + transactionService.process(transaction); + verify(transactionTypeProcessingServicePort).credit(transaction); + verifyNoMoreInteractions(transactionTypeProcessingServicePort); + } + + @Test + public void processTransaction_shouldProcessBankAccountDeposit_whenTransactionHasDepositType() { + Transaction transaction = Transaction.builder() + .id(1L) + .amount(new BigDecimal(100)) + .senderAccountId(100L) + .receiverAccountId(200L) + .type(DEPOSIT) + .status(UNPROCESSED) + .date(Instant.now()) + .label("deposit of 100 euros") + .build(); + + transactionService.process(transaction); + verify(transactionTypeProcessingServicePort).deposit(transaction); + verifyNoMoreInteractions(transactionTypeProcessingServicePort); + } +} diff --git a/application/src/test/java/com/cdx/bas/application/transaction/TransactionStatusServiceImplTest.java b/application/src/test/java/com/cdx/bas/application/transaction/TransactionStatusServiceImplTest.java new file mode 100644 index 00000000..7c8f9b8b --- /dev/null +++ b/application/src/test/java/com/cdx/bas/application/transaction/TransactionStatusServiceImplTest.java @@ -0,0 +1,92 @@ +package com.cdx.bas.application.transaction; + +import com.cdx.bas.domain.transaction.Transaction; +import com.cdx.bas.domain.transaction.TransactionException; +import com.cdx.bas.domain.transaction.TransactionStatusServicePort; +import io.quarkus.test.InjectMock; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static com.cdx.bas.domain.transaction.TransactionStatus.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@QuarkusTest +class TransactionStatusServiceTest { + + @Inject + TransactionStatusServicePort transactionStatusServicePort; + + @InjectMock + TransactionRepository transactionPersistencePort; + + @Test + @Transactional + public void setAsOutstanding_shouldSetStatusAndUpdate_whenStatusIsOutstanding() { + Transaction transaction = Transaction.builder() + .status(UNPROCESSED) + .build(); + + Transaction expectedTransaction = Transaction.builder() + .status(OUTSTANDING) + .build(); + + when(transactionPersistencePort.update(expectedTransaction)).thenReturn(expectedTransaction); + + Transaction actualTransaction = transactionStatusServicePort.setAsOutstanding(transaction); + assertThat(actualTransaction).isEqualTo(expectedTransaction); + verify(transactionPersistencePort).update(actualTransaction); + } + + @Test + @Transactional + public void setAsOutstanding_shouldThrowTransactionException_whenStatusIsNotOutstanding() { + Transaction transaction = Transaction.builder() + .status(ERROR) + .build(); + + try { + transactionStatusServicePort.setAsOutstanding(transaction); + } catch (TransactionException transactionException) { + assertThat(transactionException.getMessage()).isEqualTo("Transaction is not longer unprocessed."); + } + } + + @Test + @Transactional + public void setStatus_shouldSetStatusAndUpdate_whenTransactionIsValid() { + Transaction transaction = Transaction.builder() + .status(UNPROCESSED) + .build(); + + Map metadata = Map.of("error", "Transaction 1 deposit error for amount 100: error"); + Transaction expectedTransaction = Transaction.builder() + .status(ERROR) + .metadata(metadata) + .build(); + + when(transactionPersistencePort.update(expectedTransaction)).thenReturn(expectedTransaction); + + Transaction actualTransaction = transactionStatusServicePort.setStatus(transaction, ERROR, metadata); + assertThat(actualTransaction) + .extracting(Transaction::getStatus, Transaction::getMetadata) + .containsExactly(ERROR, metadata); + verify(transactionPersistencePort).update(actualTransaction); + } + + @Test + @Transactional + public void setStatus_shouldThrowTransactionException_whenTransactionIsNull() { + try { + transactionStatusServicePort.setStatus(null, COMPLETED, new HashMap<>()); + } catch (TransactionException transactionException) { + assertThat(transactionException.getMessage()).isEqualTo("Transaction is null."); + } + } +} \ No newline at end of file diff --git a/application/src/test/java/com/cdx/bas/application/transaction/TransactionTestUtils.java b/application/src/test/java/com/cdx/bas/application/transaction/TransactionTestUtils.java new file mode 100644 index 00000000..7f67dacc --- /dev/null +++ b/application/src/test/java/com/cdx/bas/application/transaction/TransactionTestUtils.java @@ -0,0 +1,2 @@ +package com.cdx.bas.application.transaction;public class TransactionTestUtils { +} diff --git a/application/src/test/java/com/cdx/bas/application/transaction/TransactionTypeProcessingServiceImplTest.java b/application/src/test/java/com/cdx/bas/application/transaction/TransactionTypeProcessingServiceImplTest.java new file mode 100644 index 00000000..cbb4d4c6 --- /dev/null +++ b/application/src/test/java/com/cdx/bas/application/transaction/TransactionTypeProcessingServiceImplTest.java @@ -0,0 +1,282 @@ +//package com.cdx.bas.application.transaction; +// +//import com.cdx.bas.domain.bank.account.AccountType; +//import com.cdx.bas.domain.bank.account.BankAccount; +//import com.cdx.bas.domain.bank.account.BankAccountServicePort; +//import com.cdx.bas.domain.bank.account.checking.CheckingBankAccount; +//import com.cdx.bas.domain.money.Money; +//import com.cdx.bas.domain.transaction.*; +//import io.quarkus.test.InjectMock; +//import io.quarkus.test.junit.QuarkusTest; +//import jakarta.inject.Inject; +//import org.junit.jupiter.api.Test; +// +//import java.math.BigDecimal; +//import java.time.Instant; +//import java.util.*; +// +//import static com.cdx.bas.domain.transaction.TransactionStatus.ERROR; +//import static org.assertj.core.api.Assertions.assertThat; +//import static org.mockito.Mockito.*; +// +//@QuarkusTest +//class TransactionTypeProcessingServiceImplTest { +// +// @InjectMock +// TransactionStatusServicePort transactionStatusService; +// +// @InjectMock +// BankAccountServicePort bankAccountService; +// +// +// @Inject +// TransactionTypeProcessingServicePort transactionProcessingService; +// +// @Test +// public void credit_shouldThrowNoSuchElementException_whenSenderAccountIsNotFound() { +// long senderAccountId = 99L; +// long receiverAccountId = 77L; +// Money amountOfMoney = Money.of(new BigDecimal("0")); +// Instant instantDate = Instant.now(); +// Transaction transaction = TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), UNPROCESSED, instantDate, new HashMap<>()); +// Map metadata = Map.of("error", "Sender bank account 99 is not found."); +// Transaction erroredTransaction = TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), ERROR, instantDate, metadata); +// +// when(bankAccountService.findBankAccount(senderAccountId)).thenThrow(new NoSuchElementException("Sender bank account 99 is not found.")); +// when(transactionStatusService.setStatus(eq(transaction), eq(ERROR), eq(metadata))).thenReturn(erroredTransaction); +// +// Transaction returnedTransaction = transactionProcessingService.credit(transaction); +// +// assertThat(returnedTransaction).usingRecursiveComparison() +// .isEqualTo(TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), ERROR, instantDate, metadata)); +// verify(bankAccountService).findBankAccount(eq(senderAccountId)); +// verify(transactionStatusService).setStatus(eq(transaction), eq(ERROR), eq(metadata)); +// verifyNoMoreInteractions(bankAccountService, transactionService); +// verifyNoInteractions(bankAccountValidator); +// } +// +// @Test +// public void credit_shouldThrowNoSuchElementException_whenReceiverAccountIsNotFound() { +// long senderAccountId = 99L; +// long receiverAccountId = 77L; +// Money amountOfMoney = Money.of(new BigDecimal("0")); +// Instant instantDate = Instant.now(); +// Transaction transaction = TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), UNPROCESSED, instantDate, new HashMap<>()); +// Map metadata = Map.of("error", "Receiver bank account 77 is not found."); +// Transaction erroredTransaction = TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), ERROR, instantDate, metadata); +// BankAccount senderBankAccount = createBankAccountUtils(senderAccountId, "0", transaction); +// +// when(bankAccountService.findBankAccount(senderAccountId)).thenReturn(Optional.of(senderBankAccount)); +// when(bankAccountService.findBankAccount(receiverAccountId)).thenThrow(new NoSuchElementException("Receiver bank account 77 is not found.")); +// when(transactionStatusService.setStatus(eq(transaction), eq(ERROR), eq(metadata))).thenReturn(erroredTransaction); +// +// Transaction returnedTransaction = transactionProcessingService.credit(transaction); +// +// assertThat(returnedTransaction).usingRecursiveComparison() +// .isEqualTo (TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), ERROR, instantDate, metadata)); +// verify(bankAccountService).findBankAccount(eq(senderAccountId)); +// verify(bankAccountService).findBankAccount(eq(receiverAccountId)); +// verify(transactionStatusService).setStatus(eq(transaction), eq(ERROR), eq(metadata)); +// verifyNoMoreInteractions(bankAccountService, transactionService); +// verifyNoInteractions(bankAccountValidator); +// } +// +// @Test +// public void credit_shouldReturnCompletedTransaction_whenAccountsAreFound_fromCreditTransaction() { +// long senderAccountId = 99L; +// long receiverAccountId = 77L; +// Money amountOfMoney = Money.of(new BigDecimal("1000")); +// Instant instantDate = Instant.now(); +// Transaction oldTransaction = TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), UNPROCESSED, instantDate, new HashMap<>()); +// BankAccount senderBankAccount = createBankAccountUtils(senderAccountId, "1000", oldTransaction); +// BankAccount receiverBankAccount = createBankAccountUtils(receiverAccountId, "0"); +// +// Transaction transaction = TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), UNPROCESSED, instantDate, new HashMap<>()); +// Transaction outstandingTransaction = TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), OUTSTANDING, instantDate, new HashMap<>()); +// +// Map metadataAfter = Map.of("sender_amount_before", "1000", +// "receiver_amount_before", "0", +// "sender_amount_after", "0", +// "receiver_amount_after", "1000"); +// Transaction completedTransaction = TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), COMPLETED, instantDate, metadataAfter); +// BankAccount updatedSenderBankAccount = createBankAccountUtils(senderAccountId, "0", completedTransaction); +// +// when(bankAccountService.findBankAccount(senderAccountId)).thenReturn(Optional.of(senderBankAccount)); +// when(bankAccountService.findBankAccount(receiverAccountId)).thenReturn(Optional.of(receiverBankAccount)); +// when(transactionStatusService.setAsOutstanding(transaction)).thenReturn(outstandingTransaction); +// when(transactionStatusService.setStatus(outstandingTransaction, eq(COMPLETED), metadataAfter)).thenReturn(completedTransaction); +// when(transactionService.mergeTransactions(transaction, completedTransaction)).thenReturn(completedTransaction); +// when(bankAccountService.update(updatedSenderBankAccount)).thenReturn(updatedSenderBankAccount); +// +// Transaction returnedTransaction = transactionProcessingService.credit(transaction); +// +// receiverBankAccount.getBalance().plus(senderBankAccount.getBalance()); +// senderBankAccount.getBalance().minus(senderBankAccount.getBalance()); +// +// assertThat(returnedTransaction).usingRecursiveComparison() +// .isEqualTo (TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), COMPLETED, instantDate, metadataAfter)); +// } +// +// @Test +// public void credit_shouldAddMoneyToTheReceiverAccount_whenAccountsAreFound_fromCreditTransaction() { +// long senderAccountId = 99L; +// long receiverAccountId = 77L; +// Money amountOfMoney = Money.of(new BigDecimal("1000")); +// Instant instantDate = Instant.now(); +// Transaction oldTransaction = TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), UNPROCESSED, instantDate, new HashMap<>()); +// BankAccount senderBankAccount = createBankAccountUtils(senderAccountId, "1000", oldTransaction); +// BankAccount receiverBankAccount = createBankAccountUtils(receiverAccountId, "0"); +// +// Transaction transaction = TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), UNPROCESSED, instantDate, new HashMap<>()); +// Transaction outstandingTransaction = TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), OUTSTANDING, instantDate, new HashMap<>()); +// +// Map metadataAfter = Map.of("sender_amount_before", "1000", +// "receiver_amount_before", "0", +// "sender_amount_after", "0", +// "receiver_amount_after", "1000"); +// Transaction completedTransaction = TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), COMPLETED, instantDate, metadataAfter); +// BankAccount updatedSenderBankAccount = createBankAccountUtils(senderAccountId, "0", completedTransaction); +// BankAccount updatedReceiverBankAccount = createBankAccountUtils(receiverAccountId, "1000"); +// +// when(bankAccountService.findBankAccount(senderAccountId)).thenReturn(Optional.of(senderBankAccount)); +// when(bankAccountService.findBankAccount(receiverAccountId)).thenReturn(Optional.of(receiverBankAccount)); +// when(transactionStatusService.setAsOutstanding(transaction)).thenReturn(outstandingTransaction); +// when(transactionStatusService.setStatus(outstandingTransaction, eq(COMPLETED), metadataAfter)).thenReturn(completedTransaction); +// when(transactionService.mergeTransactions(transaction, completedTransaction)).thenReturn(completedTransaction); +// when(bankAccountService.update(updatedSenderBankAccount)).thenReturn(updatedSenderBankAccount); +// +// transactionProcessingService.credit(transaction); +// +// verify(bankAccountService).findBankAccount(eq(senderAccountId)); +// verify(bankAccountService).findBankAccount(eq(receiverAccountId)); +// verify(transactionStatusService).setAsOutstanding(transaction); +// verify(bankAccountValidator).validateBankAccount(eq(senderBankAccount)); +// verify(bankAccountValidator).validateBankAccount(eq(receiverBankAccount)); +// verify(transactionStatusService).setStatus(eq(outstandingTransaction), eq(COMPLETED), eq(metadataAfter)); +// verify(transactionService).mergeTransactions(transaction, completedTransaction); +// verify(bankAccountService).update(eq(updatedSenderBankAccount)); +// verify(bankAccountService).update(eq(updatedReceiverBankAccount)); +// verifyNoMoreInteractions(bankAccountService, transactionService, bankAccountValidator); +// } +// +// @Test +// public void credit_shouldReturnErroredTransaction_whenAmountIsNegative() { +// long senderAccountId = 99L; +// long receiverAccountId = 77L; +// Money amountOfMoney = Money.of(new BigDecimal("-1000")); +// Instant instantDate = Instant.now(); +// Transaction oldTransaction = TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), UNPROCESSED, instantDate, new HashMap<>()); +// BankAccount senderBankAccount = createBankAccountUtils(senderAccountId, "1000", oldTransaction); +// BankAccount receiverBankAccount = createBankAccountUtils(receiverAccountId, "0"); +// +// Transaction transaction = TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), UNPROCESSED, instantDate, new HashMap<>()); +// Transaction outstandingTransaction = TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), OUTSTANDING, instantDate, new HashMap<>()); +// +// Map metadataAfter = Map.of("sender_amount_before", "1000", +// "receiver_amount_before", "0", +// "error", "Credit transaction 1 should have positive value, actual value: -1000"); +// Transaction refusedTransaction = TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), ERROR, instantDate, metadataAfter); +// +// when(bankAccountService.findBankAccount(senderAccountId)).thenReturn(Optional.of(senderBankAccount)); +// when(bankAccountService.findBankAccount(receiverAccountId)).thenReturn(Optional.of(receiverBankAccount)); +// when(transactionStatusService.setAsOutstanding(transaction)).thenReturn(outstandingTransaction); +// when(transactionStatusService.setStatus(eq(transaction), eq(REFUSED), eq(metadataAfter))).thenReturn(refusedTransaction); +// +// Transaction returnedTransaction = transactionProcessingService.credit(transaction); +// +// assertThat(returnedTransaction).usingRecursiveComparison() +// .isEqualTo (TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), ERROR, instantDate, metadataAfter)); +// verify(bankAccountService).findBankAccount(eq(senderAccountId)); +// verify(bankAccountService).findBankAccount(eq(receiverAccountId)); +// verify(transactionStatusService).setAsOutstanding(transaction); +// verify(transactionStatusService).setStatus(transaction, eq(REFUSED), metadataAfter); +// verifyNoMoreInteractions(bankAccountService, transactionService, bankAccountValidator); +// } +// +// @Test +// public void credit_shouldReturnErroredTransaction_whenSenderBankAccountValidatorThrowsException() { +// long senderAccountId = 99L; +// long receiverAccountId = 77L; +// Money amountOfMoney = Money.of(new BigDecimal("1000")); +// Instant instantDate = Instant.now(); +// Transaction oldTransaction = TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), UNPROCESSED, instantDate, new HashMap<>()); +// BankAccount senderBankAccount = createBankAccountUtils(senderAccountId, "1000", oldTransaction); +// BankAccount receiverBankAccount = createBankAccountUtils(receiverAccountId, "0"); +// +// Transaction transaction = TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), UNPROCESSED, instantDate, new HashMap<>()); +// Transaction outstandingTransaction = TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), OUTSTANDING, instantDate, new HashMap<>()); +// +// Map metadataAfter = Map.of("sender_amount_before", "1000", +// "receiver_amount_before", "0", +// "error", "Amount of credit should not be negative"); +// Transaction refusedTransaction = TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), ERROR, instantDate, metadataAfter); +// +// when(bankAccountService.findBankAccount(senderAccountId)).thenReturn(Optional.of(senderBankAccount)); +// when(bankAccountService.findBankAccount(receiverAccountId)).thenReturn(Optional.of(receiverBankAccount)); +// when(transactionStatusService.setAsOutstanding(transaction)).thenReturn(outstandingTransaction); +// doThrow(new BankAccountException("Amount of credit should not be negative")).when(bankAccountValidator).validateBankAccount(senderBankAccount); +// when(transactionStatusService.setStatus(eq(transaction), eq(REFUSED), eq(metadataAfter))).thenReturn(refusedTransaction); +// +// Transaction returnedTransaction = transactionProcessingService.credit(transaction); +// +// assertThat(returnedTransaction).usingRecursiveComparison() +// .isEqualTo (TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), ERROR, instantDate, metadataAfter)); +// verify(bankAccountService).findBankAccount(eq(senderAccountId)); +// verify(bankAccountService).findBankAccount(eq(receiverAccountId)); +// verify(transactionStatusService).setAsOutstanding(transaction); +// verify(bankAccountValidator).validateBankAccount(senderBankAccount); +// verify(transactionStatusService).setStatus(transaction, eq(REFUSED), metadataAfter); +// verifyNoMoreInteractions(bankAccountService, transactionService, bankAccountValidator); +// } +// +// @Test +// public void credit_shouldReturnErroredTransaction_whenReceiverBankAccountValidatorThrowsException() { +// long senderAccountId = 99L; +// long receiverAccountId = 77L; +// Money amountOfMoney = Money.of(new BigDecimal("1000")); +// Instant instantDate = Instant.now(); +// Transaction oldTransaction = TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), UNPROCESSED, instantDate, new HashMap<>()); +// BankAccount senderBankAccount = createBankAccountUtils(senderAccountId, "1000", oldTransaction); +// BankAccount receiverBankAccount = createBankAccountUtils(receiverAccountId, "0"); +// +// Transaction transaction = TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), UNPROCESSED, instantDate, new HashMap<>()); +// Transaction outstandingTransaction = TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), OUTSTANDING, instantDate, new HashMap<>()); +// +// Map metadataAfter = Map.of("sender_amount_before", "1000", +// "receiver_amount_before", "0", +// "error", "Amount of credit should not be negative"); +// Transaction refusedTransaction = TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), ERROR, instantDate, metadataAfter); +// +// when(bankAccountService.findBankAccount(senderAccountId)).thenReturn(Optional.of(senderBankAccount)); +// when(bankAccountService.findBankAccount(receiverAccountId)).thenReturn(Optional.of(receiverBankAccount)); +// when(transactionStatusService.setAsOutstanding(transaction)).thenReturn(outstandingTransaction); +// doThrow(new BankAccountException("Amount of credit should not be negative")).when(bankAccountValidator).validateBankAccount(receiverBankAccount); +// when(transactionStatusService.setStatus(eq(transaction), eq(REFUSED), eq(metadataAfter))).thenReturn(refusedTransaction); +// +// Transaction returnedTransaction = transactionProcessingService.credit(transaction); +// +// assertThat(returnedTransaction).usingRecursiveComparison() +// .isEqualTo (TransactionTestUtils.createTransactionUtils(senderAccountId, receiverAccountId, amountOfMoney.getAmount(), ERROR, instantDate, metadataAfter)); +// verify(bankAccountService).findBankAccount(eq(senderAccountId)); +// verify(bankAccountService).findBankAccount(eq(receiverAccountId)); +// verify(transactionStatusService).setAsOutstanding(transaction); +// verify(bankAccountValidator).validateBankAccount(senderBankAccount); +// verify(bankAccountValidator).validateBankAccount(receiverBankAccount); +// verify(transactionStatusService).setStatus(transaction, eq(REFUSED), metadataAfter); +// verifyNoMoreInteractions(bankAccountService, transactionService, bankAccountValidator); +// } +// +// private static BankAccount createBankAccountUtils(long accountId, String amount, Transaction ...transactions) { +// BankAccount bankAccount = new CheckingBankAccount(); +// bankAccount.setId(accountId); +// bankAccount.setType(AccountType.CHECKING); +// bankAccount.setBalance(new Money(new BigDecimal(amount))); +// List customersId = new ArrayList<>(); +// customersId.add(99L); +// bankAccount.setCustomersId(customersId); +// HashSet transactionHistory = new HashSet<>(); +// Collections.addAll(transactionHistory, transactions); +// bankAccount.setIssuedTransactions(transactionHistory); +// return bankAccount; +// } +//} \ No newline at end of file diff --git a/client/pom.xml b/client/pom.xml index bbc16069..f9fbc949 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -83,6 +83,10 @@ quarkus-test-h2 test + + io.quarkus + quarkus-smallrye-openapi + diff --git a/client/src/main/java/com/cdx/bas/client/bank/account/BankAccountResource.java b/client/src/main/java/com/cdx/bas/client/bank/account/BankAccountResource.java index 769ff389..33d4911f 100644 --- a/client/src/main/java/com/cdx/bas/client/bank/account/BankAccountResource.java +++ b/client/src/main/java/com/cdx/bas/client/bank/account/BankAccountResource.java @@ -3,13 +3,12 @@ import com.cdx.bas.domain.bank.account.BankAccount; import com.cdx.bas.domain.bank.account.BankAccountControllerPort; import com.cdx.bas.domain.bank.account.BankAccountPersistencePort; -import com.cdx.bas.domain.transaction.Transaction; import com.cdx.bas.domain.transaction.TransactionServicePort; import com.cdx.bas.domain.transaction.validation.TransactionValidator; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; -import jakarta.ws.rs.*; -import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; @Path("/accounts") @ApplicationScoped @@ -17,26 +16,9 @@ public class BankAccountResource implements BankAccountControllerPort { @Inject BankAccountPersistencePort bankAccountPersistencePort; - @Inject - TransactionValidator transactionValidator; - - @Inject - TransactionServicePort transactionServicePort; - @GET() @Override public BankAccount findById(long id) { return bankAccountPersistencePort.findById(id).orElse(null); } - - @POST - @Path("/{id}") - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - @Override - public Transaction deposite(@PathParam("id") Long id, Transaction depositTransaction) { - transactionValidator.validateNewTransaction(depositTransaction); - - return depositTransaction; - } } diff --git a/client/src/main/java/com/cdx/bas/client/customer/CustomerResource.java b/client/src/main/java/com/cdx/bas/client/customer/CustomerResource.java index 40f579e4..9d82e408 100644 --- a/client/src/main/java/com/cdx/bas/client/customer/CustomerResource.java +++ b/client/src/main/java/com/cdx/bas/client/customer/CustomerResource.java @@ -15,7 +15,7 @@ import java.util.Optional; import java.util.Set; -import static jakarta.transaction.Transactional.TxType.*; +import static jakarta.transaction.Transactional.TxType.REQUIRED; @Path("/customers") @ApplicationScoped @@ -28,15 +28,16 @@ public class CustomerResource { @Transactional(value = REQUIRED) @Produces(MediaType.APPLICATION_JSON) public Set getAll() { + //TODO use service return customerPersistencePort.getAll(); } - @GET @Path("/{id}") @Transactional(value = REQUIRED) @Produces(MediaType.APPLICATION_JSON) public Optional getCustomer(@PathParam("id") long id) { + //TODO use service return customerPersistencePort.findById(id); } } diff --git a/client/src/main/java/com/cdx/bas/client/transaction/TransactionResource.java b/client/src/main/java/com/cdx/bas/client/transaction/TransactionResource.java index df0ce86f..bcbf2807 100644 --- a/client/src/main/java/com/cdx/bas/client/transaction/TransactionResource.java +++ b/client/src/main/java/com/cdx/bas/client/transaction/TransactionResource.java @@ -2,13 +2,16 @@ import com.cdx.bas.domain.transaction.Transaction; import com.cdx.bas.domain.transaction.TransactionControllerPort; -import com.cdx.bas.domain.transaction.TransactionPersistencePort; -import com.cdx.bas.domain.transaction.TransactionStatus; +import com.cdx.bas.domain.transaction.TransactionException; +import com.cdx.bas.domain.transaction.TransactionServicePort; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,22 +25,45 @@ public class TransactionResource implements TransactionControllerPort { private static final Logger logger = LoggerFactory.getLogger(TransactionResource.class); @Inject - TransactionPersistencePort transactionPersistencePort; + TransactionServicePort transactionServicePort; @GET + @Override public Set getAll() { - return transactionPersistencePort.getAll(); + return transactionServicePort.getAll(); } @GET @Path("/{status}") + @Override public Set getAllByStatus(@PathParam("status") String status) { try { - TransactionStatus transactionStatus = TransactionStatus.fromString(status); - return transactionPersistencePort.findAllByStatus(transactionStatus); + return transactionServicePort.findAllByStatus(status); } catch (IllegalArgumentException illegalArgumentException) { logger.warn("Error: " + illegalArgumentException.getCause()); return Collections.emptySet(); } } + + @POST + @Path("/{id}") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Operation(summary = "Add deposit transaction", description = "Returns acceptance information about the added transaction") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Deposit transaction accepted"), + @APIResponse(responseCode = "400", description = "Transaction invalid check error details"), + @APIResponse(responseCode = "500", description = "Unexpected error happened") + }) + @Override + public Response deposit(@PathParam("id") Long id, Transaction depositTransaction) { + try { + transactionServicePort.createTransaction(depositTransaction); + return Response.status(Response.Status.ACCEPTED).entity("Deposit transaction accepted").build(); + } catch (TransactionException transactionException) { + return Response.status(Response.Status.BAD_REQUEST).entity(transactionException.getMessage()).build(); + } catch (Exception exception) { + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("Unexpected error happened").build(); + } + } } diff --git a/client/src/main/resources/application.properties b/client/src/main/resources/application.properties index e69de29b..6a536fd8 100644 --- a/client/src/main/resources/application.properties +++ b/client/src/main/resources/application.properties @@ -0,0 +1,4 @@ +quarkus.smallrye-openapi.enable=true +quarkus.smallrye-openapi.path=/swagger +quarkus.swagger-ui.always-include=true +quarkus.swagger-ui.path=/swagger-ui.html \ No newline at end of file diff --git a/client/src/test/java/com/cdx/bas/client/bank/account/BankAccountResourceTest.java b/client/src/test/java/com/cdx/bas/client/bank/account/BankAccountResourceTest.java index 2efd623a..1e2ab841 100644 --- a/client/src/test/java/com/cdx/bas/client/bank/account/BankAccountResourceTest.java +++ b/client/src/test/java/com/cdx/bas/client/bank/account/BankAccountResourceTest.java @@ -12,17 +12,17 @@ class BankAccountResourceTest { @Test - public void deposite_shouldCreateDepositeTransaction_whenBankAccountFound_andDepositeTransactionIsValid() { + public void deposit_shouldCreateDepositTransaction_whenBankAccountFound_andDepositTransactionIsValid() { } @Test - public void deposite_shouldReturnHTTPError_whenBankAccountFound_butDepositeTransactionIsInvalid() { + public void deposit_shouldReturnHTTPError_whenBankAccountFound_butDepositTransactionIsInvalid() { } @Test - public void deposite_shouldReturnHTTPError_whenBankAccountFound_butDepositeAmountReach() { + public void deposit_shouldReturnHTTPError_whenBankAccountFound_butDepositAmountReach() { } diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/account/BankAccountControllerPort.java b/domain/src/main/java/com/cdx/bas/domain/bank/account/BankAccountControllerPort.java index 9e1d1766..5c05edef 100644 --- a/domain/src/main/java/com/cdx/bas/domain/bank/account/BankAccountControllerPort.java +++ b/domain/src/main/java/com/cdx/bas/domain/bank/account/BankAccountControllerPort.java @@ -1,24 +1,15 @@ package com.cdx.bas.domain.bank.account; import com.cdx.bas.domain.transaction.Transaction; +import jakarta.ws.rs.core.Response; public interface BankAccountControllerPort { - /** - * find BankAccount from its id + * Find BankAccount from its id * * @param id of BankAccount * @return BankAccount corresponding to the id */ public BankAccount findById(long id); - - /** - * make a deposite on bank account - * - * @param id of BankAccount - * @param depositTransaction to add to the BankAccount - * @return deposit Transaction added to the BankAccount - */ - public Transaction deposite(Long id, Transaction depositTransaction); } diff --git a/domain/src/main/java/com/cdx/bas/domain/bank/account/BankAccountServicePort.java b/domain/src/main/java/com/cdx/bas/domain/bank/account/BankAccountServicePort.java index c6510d37..12051447 100644 --- a/domain/src/main/java/com/cdx/bas/domain/bank/account/BankAccountServicePort.java +++ b/domain/src/main/java/com/cdx/bas/domain/bank/account/BankAccountServicePort.java @@ -2,12 +2,38 @@ import com.cdx.bas.domain.transaction.Transaction; -/** - * Make a deposit from a transaction - * - * @param transaction for deposit - * @return transaction processing status - */ public interface BankAccountServicePort { - public Transaction deposit(Transaction transaction); + + /** + * find bank account from id + * + * @param bankAccountId + * @return bank account found + */ + BankAccount findBankAccount(Long bankAccountId); + + /** + * add transaction to bank account + * + * @param transaction to add + * @return bank account + */ + BankAccount addTransaction(Transaction transaction, BankAccount bankAccount); + + /** + * add transaction to bank account + * + * @param transactionToPost with amount and currency + * @param emitterBankAccount which emits transaction + * @param receiverBankAccount which receives transaction + */ + void postTransaction(Transaction transactionToPost, BankAccount emitterBankAccount, BankAccount receiverBankAccount) ; + + /** + * updated bank account + * + * @param bankAccount + * @return bank account updated + */ + BankAccount updateBankAccount(BankAccount bankAccount); } diff --git a/domain/src/main/java/com/cdx/bas/domain/transaction/Transaction.java b/domain/src/main/java/com/cdx/bas/domain/transaction/Transaction.java index 190ac688..9893081a 100644 --- a/domain/src/main/java/com/cdx/bas/domain/transaction/Transaction.java +++ b/domain/src/main/java/com/cdx/bas/domain/transaction/Transaction.java @@ -21,41 +21,41 @@ @AllArgsConstructor public class Transaction implements Comparable { - @Min(value = 1, message = "id must be positive and greater than 0 for existing transaction.", groups = ExistingTransaction.class) - @NotNull(message = "id must not be null for existing transaction.", groups = ExistingTransaction.class) - @Null(message = "id must be null for new transaction.", groups = NewTransaction.class) + @Min(value = 1, message = "Id must be positive and greater than 0 for existing transaction.", groups = ExistingTransaction.class) + @NotNull(message = "Id must not be null for existing transaction.", groups = ExistingTransaction.class) + @Null(message = "Id must be null for new transaction.", groups = NewTransaction.class) private Long id; - @Null(message = "sender account must be null for cash movement.", groups = CashMovement.class) - @NotNull(message = "sender account id must not be null.", groups = AccountMovement.class) + @Null(message = "Sender account must be null for cash movement.", groups = CashMovement.class) + @NotNull(message = "Sender account id must not be null.", groups = AccountMovement.class) private Long senderAccountId; @NotNull(message = "receiver account id must not be null.") private Long receiverAccountId; - @Min(value = 10, message = "amount must be greater than 10 for cash movement.", groups = CashMovement.class) - @Min(value = 1, message = "amount must be positive and greater than 0.", groups = AccountMovement.class) - @NotNull(message = "amount must not be null.") + @Min(value = 10, message = "Amount must be greater than 10 for cash movement.", groups = CashMovement.class) + @Min(value = 1, message = "Amount must be positive and greater than 0.", groups = AccountMovement.class) + @NotNull(message = "Amount must not be null.") private BigDecimal amount; @ValidCurrency - @NotNull(message = "currency must not be null.") + @NotNull(message = "Currency must not be null.") private String currency; - @NotNull(message = "type must not be null.") + @NotNull(message = "Type must not be null.") private TransactionType type; @ValidStatus(expectedStatus = UNPROCESSED, groups = NewTransaction.class) - @NotNull(message = "status must not be null.") + @NotNull(message = "Status must not be null.") private TransactionStatus status; - @NotNull(message = "date must not be null.") + @NotNull(message = "Date must not be null.") private Instant date; - @NotNull(message = "label must not be null.") + @NotNull(message = "Label must not be null.") private String label; - @NotNull(message = "bill must be define for cash movements.", groups = CashMovement.class) + @NotNull(message = "Bill must be define for cash movements.", groups = CashMovement.class) private Map metadata = new HashMap<>(); @Override diff --git a/domain/src/main/java/com/cdx/bas/domain/transaction/TransactionControllerPort.java b/domain/src/main/java/com/cdx/bas/domain/transaction/TransactionControllerPort.java index 10e08920..62f89af1 100644 --- a/domain/src/main/java/com/cdx/bas/domain/transaction/TransactionControllerPort.java +++ b/domain/src/main/java/com/cdx/bas/domain/transaction/TransactionControllerPort.java @@ -1,5 +1,35 @@ package com.cdx.bas.domain.transaction; +import com.cdx.bas.domain.bank.account.BankAccount; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.core.Response; + +import java.util.Set; + public interface TransactionControllerPort { + /** + * Find all Transaction + * + * @return all Transaction found + */ + public Set getAll(); + + /** + * Find all Transaction with matching status + * + * @param status of Transaction + * @return all Transaction corresponding to the status + */ + public Set getAllByStatus(@PathParam("status") String status) ; + + /** + * Make a deposit on bank account + * + * @param id of BankAccount + * @param depositTransaction to add to the BankAccount + * @return Response with status corresponding to transaction validation or not + */ + public Response deposit(Long id, Transaction depositTransaction); + } diff --git a/domain/src/main/java/com/cdx/bas/domain/transaction/TransactionPersistencePort.java b/domain/src/main/java/com/cdx/bas/domain/transaction/TransactionPersistencePort.java index ce806b3e..e8284a35 100644 --- a/domain/src/main/java/com/cdx/bas/domain/transaction/TransactionPersistencePort.java +++ b/domain/src/main/java/com/cdx/bas/domain/transaction/TransactionPersistencePort.java @@ -54,7 +54,7 @@ public interface TransactionPersistencePort { public Transaction update(Transaction transaction); /** - * delete the current Transaction + * delete current Transaction * * @param id to remove */ diff --git a/domain/src/main/java/com/cdx/bas/domain/transaction/TransactionServicePort.java b/domain/src/main/java/com/cdx/bas/domain/transaction/TransactionServicePort.java index c7a4c5f9..4e7f9932 100644 --- a/domain/src/main/java/com/cdx/bas/domain/transaction/TransactionServicePort.java +++ b/domain/src/main/java/com/cdx/bas/domain/transaction/TransactionServicePort.java @@ -1,55 +1,44 @@ package com.cdx.bas.domain.transaction; -import java.util.Map; +import java.util.Set; public interface TransactionServicePort { /** - * Process the transaction according to its values - * - * @param transaction to process + * find all transactions + * + * @return Set with all the transactions */ - void process(Transaction transaction); + public Set getAll(); /** - * Set transaction to outstanding with additional metadata and avoid multiple process + * find all transactions by status * - * @param transaction to set as OUTSTANDING - * @return outstanding transaction - */ - Transaction setAsOutstanding(Transaction transaction); - - /** - * Complete processed transaction - * - * @param completedTransaction to set as COMPLETED - * @param metadata with information to set to the transaction + * @return Set with all the transactions by status */ - Transaction setAsCompleted(Transaction completedTransaction, Map metadata); + public Set findAllByStatus(String status); + /** - * Set transaction on error with additional metadata + * add current transaction * - * @param erroredTransaction to set as ERROR - * @param metadata with error to set to the transaction + * @param transaction to add */ - Transaction setAsError(Transaction erroredTransaction, Map metadata); + void createTransaction(Transaction transaction); /** - * Set transaction refused with additional metadata + * merge two transactions * - * @param refusedTransaction to set as REFUSED - * @param metadata with information to set to the transaction + * @param oldTransaction to merge with next + * @param newTransaction to merge with previous */ - Transaction setAsRefused(Transaction refusedTransaction, Map metadata); - + Transaction mergeTransactions(Transaction oldTransaction, Transaction newTransaction) /** - * merge two transaction and return old transaction merged with new - * - * @param oldTransaction to adapt with new transaction - * @param newTransaction to merge with old transaction + * Process the transaction depending on its type + * + * @param transaction to process */ - Transaction mergeTransactions(Transaction oldTransaction, Transaction newTransaction); + void process(Transaction transaction); } diff --git a/domain/src/main/java/com/cdx/bas/domain/transaction/TransactionStatus.java b/domain/src/main/java/com/cdx/bas/domain/transaction/TransactionStatus.java index 4d2af020..39a65fbc 100644 --- a/domain/src/main/java/com/cdx/bas/domain/transaction/TransactionStatus.java +++ b/domain/src/main/java/com/cdx/bas/domain/transaction/TransactionStatus.java @@ -8,8 +8,7 @@ public enum TransactionStatus { REFUSED, ERROR; - - public static TransactionStatus fromString(String status) throws IllegalArgumentException{ + public static TransactionStatus fromString(String status) throws IllegalArgumentException { for (TransactionStatus value : values()) { if (value.name().equalsIgnoreCase(status)) { return value; diff --git a/domain/src/main/java/com/cdx/bas/domain/transaction/TransactionStatusServicePort.java b/domain/src/main/java/com/cdx/bas/domain/transaction/TransactionStatusServicePort.java new file mode 100644 index 00000000..ca4cce4b --- /dev/null +++ b/domain/src/main/java/com/cdx/bas/domain/transaction/TransactionStatusServicePort.java @@ -0,0 +1,23 @@ +package com.cdx.bas.domain.transaction; + +import java.util.Map; + +public interface TransactionStatusServicePort { + /** + * Set transaction to outstanding with additional metadata and avoid multiple process + * + * @param transaction to set as OUTSTANDING + * @return outstanding transaction + */ + Transaction setAsOutstanding(Transaction transaction); + + /** + * Complete processed transaction + * + * @param transaction to change status + * @param status to set to the transaction + * @param metadata with detail about the status + */ + Transaction setStatus(Transaction transaction, TransactionStatus status, Map metadata); + +} diff --git a/domain/src/main/java/com/cdx/bas/domain/transaction/TransactionTypeProcessingServicePort.java b/domain/src/main/java/com/cdx/bas/domain/transaction/TransactionTypeProcessingServicePort.java new file mode 100644 index 00000000..8499208d --- /dev/null +++ b/domain/src/main/java/com/cdx/bas/domain/transaction/TransactionTypeProcessingServicePort.java @@ -0,0 +1,32 @@ +package com.cdx.bas.domain.transaction; + +public interface TransactionProcessingServicePort { + + /** + * Credit bank account with transaction according to its amount + * + * @param transaction to process + */ + Transaction credit(Transaction transaction); + + /** + * Debit bank account with transaction according to its amount + * + * @param transaction to process + */ + Transaction debit(Transaction transaction); + + /** + * Deposit amount to a corresponding bank account + * + * @param transaction to process + */ + Transaction deposit(Transaction transaction); + + /** + * Withdraw amount to a corresponding bank account + * + * @param transaction to process + */ + Transaction withdraw(Transaction transaction); +} diff --git a/domain/src/main/java/com/cdx/bas/domain/transaction/validation/AccountMovement.java b/domain/src/main/java/com/cdx/bas/domain/transaction/validation/AccountMovement.java index c957a8e6..93fd6b61 100644 --- a/domain/src/main/java/com/cdx/bas/domain/transaction/validation/AccountMovement.java +++ b/domain/src/main/java/com/cdx/bas/domain/transaction/validation/AccountMovement.java @@ -1,7 +1,7 @@ package com.cdx.bas.domain.transaction.validation; /** - * transaction group that move money from an account to another + * Transaction group that move money from an account to another */ public interface AccountMovement { } diff --git a/domain/src/main/java/com/cdx/bas/domain/transaction/validation/CashMovement.java b/domain/src/main/java/com/cdx/bas/domain/transaction/validation/CashMovement.java index 3fbb20f1..cefe84ac 100644 --- a/domain/src/main/java/com/cdx/bas/domain/transaction/validation/CashMovement.java +++ b/domain/src/main/java/com/cdx/bas/domain/transaction/validation/CashMovement.java @@ -1,7 +1,7 @@ package com.cdx.bas.domain.transaction.validation; /** - * transaction group that use cash money to transfer to an account + * Transaction group that use cash money to transfer to an account */ public interface CashMovement { } diff --git a/domain/src/main/java/com/cdx/bas/domain/transaction/validation/ExistingTransaction.java b/domain/src/main/java/com/cdx/bas/domain/transaction/validation/ExistingTransaction.java index 96b6439c..b72c2ac5 100644 --- a/domain/src/main/java/com/cdx/bas/domain/transaction/validation/ExistingTransaction.java +++ b/domain/src/main/java/com/cdx/bas/domain/transaction/validation/ExistingTransaction.java @@ -1,7 +1,7 @@ package com.cdx.bas.domain.transaction.validation; /** - * transaction group for existing transactions + * Transaction group for existing transactions */ public interface ExistingTransaction { } diff --git a/domain/src/main/java/com/cdx/bas/domain/transaction/validation/NewTransaction.java b/domain/src/main/java/com/cdx/bas/domain/transaction/validation/NewTransaction.java index 5c150974..3fbe11cd 100644 --- a/domain/src/main/java/com/cdx/bas/domain/transaction/validation/NewTransaction.java +++ b/domain/src/main/java/com/cdx/bas/domain/transaction/validation/NewTransaction.java @@ -1,7 +1,7 @@ package com.cdx.bas.domain.transaction.validation; /** - * transaction group for new transactions + * Transaction group for new transactions */ public interface NewTransaction { } diff --git a/domain/src/main/java/com/cdx/bas/domain/transaction/validation/ValidStatus.java b/domain/src/main/java/com/cdx/bas/domain/transaction/validation/ValidStatus.java index 95a0b410..9f02568c 100644 --- a/domain/src/main/java/com/cdx/bas/domain/transaction/validation/ValidStatus.java +++ b/domain/src/main/java/com/cdx/bas/domain/transaction/validation/ValidStatus.java @@ -13,7 +13,7 @@ public @interface ValidStatus { TransactionStatus expectedStatus() default TransactionStatus.UNPROCESSED; - String message() default "unexpected transaction status."; + String message() default "Unexpected transaction status."; Class[] groups() default {}; Class[] payload() default {}; } diff --git a/domain/src/test/java/com/cdx/bas/domain/transaction/TransactionValidatorTest.java b/domain/src/test/java/com/cdx/bas/domain/transaction/TransactionValidatorTest.java index 43221114..415fa6c4 100644 --- a/domain/src/test/java/com/cdx/bas/domain/transaction/TransactionValidatorTest.java +++ b/domain/src/test/java/com/cdx/bas/domain/transaction/TransactionValidatorTest.java @@ -68,7 +68,7 @@ public void validateNewTransaction_shouldTrowTransactionException_whenNewTransac transactionValidator.validateNewTransaction(creditTransaction); } catch (TransactionException transactionException) { assertThat(transactionException).isInstanceOf(TransactionException.class) - .hasMessage("id must be null for new transaction.\n"); + .hasMessage("Id must be null for new transaction.\n"); } } @@ -89,7 +89,7 @@ public void validateNewTransaction_shouldTrowTransactionException_whenNewCreditT transactionValidator.validateNewTransaction(creditTransaction); } catch (TransactionException transactionException) { assertThat(transactionException).isInstanceOf(TransactionException.class) - .hasMessage("amount must be positive and greater than 0.\n"); + .hasMessage("Amount must be positive and greater than 0.\n"); } } @@ -126,7 +126,7 @@ public void validateNewTransaction_shouldTrowTransactionException_whenNewDebitTr transactionValidator.validateNewTransaction(creditTransaction); } catch (TransactionException transactionException) { assertThat(transactionException).isInstanceOf(TransactionException.class) - .hasMessage("id must be null for new transaction.\n"); + .hasMessage("Id must be null for new transaction.\n"); } } @@ -147,7 +147,7 @@ public void validateNewTransaction_shouldTrowTransactionException_whenNewDebitTr transactionValidator.validateNewTransaction(creditTransaction); } catch (TransactionException transactionException) { assertThat(transactionException).isInstanceOf(TransactionException.class) - .hasMessage("amount must be positive and greater than 0.\n"); + .hasMessage("Amount must be positive and greater than 0.\n"); } } @@ -197,7 +197,7 @@ public void validateNewTransaction_shouldTrowTransactionException_whenExistingCr transactionValidator.validateExistingTransaction(creditTransaction); } catch (TransactionException transactionException) { assertThat(transactionException).isInstanceOf(TransactionException.class) - .hasMessage("amount must be positive and greater than 0.\n"); + .hasMessage("Amount must be positive and greater than 0.\n"); } } @@ -234,7 +234,7 @@ public void validateNewTransaction_shouldTrowTransactionException_whenExistingDe transactionValidator.validateExistingTransaction(creditTransaction); } catch (TransactionException transactionException) { assertThat(transactionException).isInstanceOf(TransactionException.class) - .hasMessage("amount must be positive and greater than 0.\n"); + .hasMessage("Amount must be positive and greater than 0.\n"); } } @@ -273,7 +273,7 @@ public void validateNewTransaction_shouldThrowTransactionException_whenNewDeposi transactionValidator.validateExistingTransaction(creditTransaction); } catch (TransactionException transactionException) { assertThat(transactionException).isInstanceOf(TransactionException.class) - .hasMessage("sender account must be null for cash movement.\n"); + .hasMessage("Sender account must be null for cash movement.\n"); } } @@ -295,7 +295,7 @@ public void validateNewTransaction_shouldThrowTransactionException_whenNewDeposi transactionValidator.validateNewTransaction(creditTransaction); } catch (TransactionException transactionException) { assertThat(transactionException).isInstanceOf(TransactionException.class) - .hasMessage("amount must be greater than 10 for cash movement.\n"); + .hasMessage("Amount must be greater than 10 for cash movement.\n"); } } @@ -317,7 +317,7 @@ public void validateNewTransaction_shouldThrowTransactionException_whenNewDeposi transactionValidator.validateNewTransaction(creditTransaction); } catch (TransactionException transactionException) { assertThat(transactionException).isInstanceOf(TransactionException.class) - .hasMessage("bill must be define for cash movements.\n"); + .hasMessage("Bill must be define for cash movements.\n"); } } @@ -339,7 +339,7 @@ public void validateNewTransaction_shouldThrowTransactionException_whenNewDeposi transactionValidator.validateNewTransaction(creditTransaction); } catch (TransactionException transactionException) { assertThat(transactionException).isInstanceOf(TransactionException.class) - .hasMessage("unexpected transaction status.\n"); + .hasMessage("Unexpected transaction status.\n"); } } @@ -361,7 +361,7 @@ public void validateNewTransaction_shouldThrowTransactionException_whenNewDeposi transactionValidator.validateNewTransaction(creditTransaction); } catch (TransactionException transactionException) { assertThat(transactionException).isInstanceOf(TransactionException.class) - .hasMessage("bill must be define for cash movements.\n"); + .hasMessage("Bill must be define for cash movements.\n"); } } @@ -400,7 +400,7 @@ public void validateExistingTransaction_shouldThrowTransactionException_whenExis transactionValidator.validateExistingTransaction(creditTransaction); } catch (TransactionException transactionException) { assertThat(transactionException).isInstanceOf(TransactionException.class) - .hasMessage("amount must be greater than 10 for cash movement.\n"); + .hasMessage("Amount must be greater than 10 for cash movement.\n"); } } @@ -422,7 +422,7 @@ public void validateExistingTransaction_shouldThrowTransactionException_whenExis transactionValidator.validateExistingTransaction(creditTransaction); } catch (TransactionException transactionException) { assertThat(transactionException).isInstanceOf(TransactionException.class) - .hasMessage("bill must be define for cash movements.\n"); + .hasMessage("Bill must be define for cash movements.\n"); } } @@ -444,7 +444,7 @@ public void validateExistingTransaction_shouldThrowTransactionException_whenExis transactionValidator.validateExistingTransaction(creditTransaction); } catch (TransactionException transactionException) { assertThat(transactionException).isInstanceOf(TransactionException.class) - .hasMessage("bill must be define for cash movements.\n"); + .hasMessage("Bill must be define for cash movements.\n"); } } } \ No newline at end of file